From 17175b49f2b28772ffafc606715b5c72c96eafb4 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 11 Jun 2019 10:18:59 +0100 Subject: [PATCH 01/34] Add multicast interfaces to platform-specific defaults (this makes it easier to avoid bringing AWDL up by default on macOS as an example, or over L2 VPNs when not expected) --- src/config/config.go | 2 +- src/defaults/defaults.go | 3 +++ src/defaults/defaults_darwin.go | 6 ++++++ src/defaults/defaults_freebsd.go | 5 +++++ src/defaults/defaults_linux.go | 8 ++++++++ src/defaults/defaults_netbsd.go | 5 +++++ src/defaults/defaults_openbsd.go | 5 +++++ src/defaults/defaults_other.go | 5 +++++ src/defaults/defaults_windows.go | 5 +++++ 9 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/config/config.go b/src/config/config.go index 8137cac9..9f8f3f54 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -102,7 +102,7 @@ func GenerateConfig() *NodeConfig { cfg.Peers = []string{} cfg.InterfacePeers = map[string][]string{} cfg.AllowedEncryptionPublicKeys = []string{} - cfg.MulticastInterfaces = []string{".*"} + cfg.MulticastInterfaces = defaults.GetDefaults().DefaultMulticastInterfaces cfg.IfName = defaults.GetDefaults().DefaultIfName cfg.IfMTU = defaults.GetDefaults().DefaultIfMTU cfg.IfTAPMode = defaults.GetDefaults().DefaultIfTAPMode diff --git a/src/defaults/defaults.go b/src/defaults/defaults.go index 3834990e..a5784198 100644 --- a/src/defaults/defaults.go +++ b/src/defaults/defaults.go @@ -10,6 +10,9 @@ type platformDefaultParameters struct { // Configuration (used for yggdrasilctl) DefaultConfigFile string + // Multicast interfaces + DefaultMulticastInterfaces []string + // TUN/TAP MaximumIfMTU int DefaultIfMTU int diff --git a/src/defaults/defaults_darwin.go b/src/defaults/defaults_darwin.go index 9bac3aad..26fd1f2d 100644 --- a/src/defaults/defaults_darwin.go +++ b/src/defaults/defaults_darwin.go @@ -12,6 +12,12 @@ func GetDefaults() platformDefaultParameters { // Configuration (used for yggdrasilctl) DefaultConfigFile: "/etc/yggdrasil.conf", + // Multicast interfaces + DefaultMulticastInterfaces: []string{ + "en*", + "bridge*", + }, + // TUN/TAP MaximumIfMTU: 65535, DefaultIfMTU: 65535, diff --git a/src/defaults/defaults_freebsd.go b/src/defaults/defaults_freebsd.go index df1a3c65..0e523483 100644 --- a/src/defaults/defaults_freebsd.go +++ b/src/defaults/defaults_freebsd.go @@ -12,6 +12,11 @@ func GetDefaults() platformDefaultParameters { // Configuration (used for yggdrasilctl) DefaultConfigFile: "/etc/yggdrasil.conf", + // Multicast interfaces + DefaultMulticastInterfaces: []string{ + ".*", + }, + // TUN/TAP MaximumIfMTU: 32767, DefaultIfMTU: 32767, diff --git a/src/defaults/defaults_linux.go b/src/defaults/defaults_linux.go index 2f3459ca..67404e2d 100644 --- a/src/defaults/defaults_linux.go +++ b/src/defaults/defaults_linux.go @@ -12,6 +12,14 @@ func GetDefaults() platformDefaultParameters { // Configuration (used for yggdrasilctl) DefaultConfigFile: "/etc/yggdrasil.conf", + // Multicast interfaces + DefaultMulticastInterfaces: []string{ + "en*", + "eth*", + "wlan*", + "br*", + }, + // TUN/TAP MaximumIfMTU: 65535, DefaultIfMTU: 65535, diff --git a/src/defaults/defaults_netbsd.go b/src/defaults/defaults_netbsd.go index 40476dcb..52a487b7 100644 --- a/src/defaults/defaults_netbsd.go +++ b/src/defaults/defaults_netbsd.go @@ -12,6 +12,11 @@ func GetDefaults() platformDefaultParameters { // Configuration (used for yggdrasilctl) DefaultConfigFile: "/etc/yggdrasil.conf", + // Multicast interfaces + DefaultMulticastInterfaces: []string{ + ".*", + }, + // TUN/TAP MaximumIfMTU: 9000, DefaultIfMTU: 9000, diff --git a/src/defaults/defaults_openbsd.go b/src/defaults/defaults_openbsd.go index cd6d202a..d44e5714 100644 --- a/src/defaults/defaults_openbsd.go +++ b/src/defaults/defaults_openbsd.go @@ -12,6 +12,11 @@ func GetDefaults() platformDefaultParameters { // Configuration (used for yggdrasilctl) DefaultConfigFile: "/etc/yggdrasil.conf", + // Multicast interfaces + DefaultMulticastInterfaces: []string{ + ".*", + }, + // TUN/TAP MaximumIfMTU: 16384, DefaultIfMTU: 16384, diff --git a/src/defaults/defaults_other.go b/src/defaults/defaults_other.go index a01ab7af..0ba825c5 100644 --- a/src/defaults/defaults_other.go +++ b/src/defaults/defaults_other.go @@ -12,6 +12,11 @@ func GetDefaults() platformDefaultParameters { // Configuration (used for yggdrasilctl) DefaultConfigFile: "/etc/yggdrasil.conf", + // Multicast interfaces + DefaultMulticastInterfaces: []string{ + ".*", + }, + // TUN/TAP MaximumIfMTU: 65535, DefaultIfMTU: 65535, diff --git a/src/defaults/defaults_windows.go b/src/defaults/defaults_windows.go index 3b04783a..6d53225a 100644 --- a/src/defaults/defaults_windows.go +++ b/src/defaults/defaults_windows.go @@ -12,6 +12,11 @@ func GetDefaults() platformDefaultParameters { // Configuration (used for yggdrasilctl) DefaultConfigFile: "C:\\Program Files\\Yggdrasil\\yggdrasil.conf", + // Multicast interfaces + DefaultMulticastInterfaces: []string{ + ".*", + }, + // TUN/TAP MaximumIfMTU: 65535, DefaultIfMTU: 65535, From 720a078a35a8ce155574ef1a89176da13f1e5064 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 11 Jun 2019 10:52:21 +0100 Subject: [PATCH 02/34] Add SetSessionGatekeeper This allows you to define a function which determines whether a session connection (either incoming or outgoing) is allowed based on the public key. --- src/yggdrasil/api.go | 12 +++++ src/yggdrasil/session.go | 99 ++++++++++------------------------------ 2 files changed, 35 insertions(+), 76 deletions(-) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 5e58ffaf..9d640997 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -395,6 +395,18 @@ func (c *Core) GetNodeInfo(keyString, coordString string, nocache bool) (NodeInf return NodeInfoPayload{}, errors.New(fmt.Sprintf("getNodeInfo timeout: %s", keyString)) } +// SetSessionGatekeeper allows you to configure a handler function for deciding +// whether a session should be allowed or not. The default session firewall is +// implemented in this way. The function receives the public key of the remote +// side, and a boolean which is true if we initiated the session or false if we +// received an incoming session request. +func (c *Core) SetSessionGatekeeper(f func(pubkey *crypto.BoxPubKey, initiator bool) bool) { + c.sessions.isAllowedMutex.Lock() + defer c.sessions.isAllowedMutex.Unlock() + + c.sessions.isAllowedHandler = f +} + // SetLogger sets the output logger of the Yggdrasil node after startup. This // may be useful if you want to redirect the output later. func (c *Core) SetLogger(log *log.Logger) { diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 68b90950..46628c3e 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -6,7 +6,6 @@ package yggdrasil import ( "bytes" - "encoding/hex" "sync" "time" @@ -111,18 +110,20 @@ func (s *sessionInfo) update(p *sessionPing) bool { // Sessions are indexed by handle. // Additionally, stores maps of address/subnet onto keys, and keys onto handles. type sessions struct { - core *Core - listener *Listener - listenerMutex sync.Mutex - reconfigure chan chan error - lastCleanup time.Time - permShared map[crypto.BoxPubKey]*crypto.BoxSharedKey // Maps known permanent keys to their shared key, used by DHT a lot - sinfos map[crypto.Handle]*sessionInfo // Maps (secret) handle onto session info - conns map[crypto.Handle]*Conn // Maps (secret) handle onto connections - byMySes map[crypto.BoxPubKey]*crypto.Handle // Maps mySesPub onto handle - byTheirPerm map[crypto.BoxPubKey]*crypto.Handle // Maps theirPermPub onto handle - addrToPerm map[address.Address]*crypto.BoxPubKey - subnetToPerm map[address.Subnet]*crypto.BoxPubKey + core *Core + listener *Listener + listenerMutex sync.Mutex + reconfigure chan chan error + lastCleanup time.Time + isAllowedHandler func(pubkey *crypto.BoxPubKey, initiator bool) bool // Returns true or false if session setup is allowed + isAllowedMutex sync.RWMutex // Protects the above + permShared map[crypto.BoxPubKey]*crypto.BoxSharedKey // Maps known permanent keys to their shared key, used by DHT a lot + sinfos map[crypto.Handle]*sessionInfo // Maps (secret) handle onto session info + conns map[crypto.Handle]*Conn // Maps (secret) handle onto connections + byMySes map[crypto.BoxPubKey]*crypto.Handle // Maps mySesPub onto handle + byTheirPerm map[crypto.BoxPubKey]*crypto.Handle // Maps theirPermPub onto handle + addrToPerm map[address.Address]*crypto.BoxPubKey + subnetToPerm map[address.Subnet]*crypto.BoxPubKey } // Initializes the session struct. @@ -155,70 +156,17 @@ func (ss *sessions) init(core *Core) { ss.lastCleanup = time.Now() } -// Determines whether the session firewall is enabled. -func (ss *sessions) isSessionFirewallEnabled() bool { - ss.core.config.Mutex.RLock() - defer ss.core.config.Mutex.RUnlock() - - return ss.core.config.Current.SessionFirewall.Enable -} - // Determines whether the session with a given publickey is allowed based on // session firewall rules. func (ss *sessions) isSessionAllowed(pubkey *crypto.BoxPubKey, initiator bool) bool { - ss.core.config.Mutex.RLock() - defer ss.core.config.Mutex.RUnlock() + ss.isAllowedMutex.RLock() + defer ss.isAllowedMutex.RUnlock() - // Allow by default if the session firewall is disabled - if !ss.isSessionFirewallEnabled() { + if ss.isAllowedHandler == nil { return true } - // Prepare for checking whitelist/blacklist - var box crypto.BoxPubKey - // Reject blacklisted nodes - for _, b := range ss.core.config.Current.SessionFirewall.BlacklistEncryptionPublicKeys { - key, err := hex.DecodeString(b) - if err == nil { - copy(box[:crypto.BoxPubKeyLen], key) - if box == *pubkey { - return false - } - } - } - // Allow whitelisted nodes - for _, b := range ss.core.config.Current.SessionFirewall.WhitelistEncryptionPublicKeys { - key, err := hex.DecodeString(b) - if err == nil { - copy(box[:crypto.BoxPubKeyLen], key) - if box == *pubkey { - return true - } - } - } - // Allow outbound sessions if appropriate - if ss.core.config.Current.SessionFirewall.AlwaysAllowOutbound { - if initiator { - return true - } - } - // Look and see if the pubkey is that of a direct peer - var isDirectPeer bool - for _, peer := range ss.core.peers.ports.Load().(map[switchPort]*peer) { - if peer.box == *pubkey { - isDirectPeer = true - break - } - } - // Allow direct peers if appropriate - if ss.core.config.Current.SessionFirewall.AllowFromDirect && isDirectPeer { - return true - } - // Allow remote nodes if appropriate - if ss.core.config.Current.SessionFirewall.AllowFromRemote && !isDirectPeer { - return true - } - // Finally, default-deny if not matching any of the above rules - return false + + return ss.isAllowedHandler(pubkey, initiator) } // Gets the session corresponding to a given handle. @@ -444,12 +392,11 @@ func (ss *sessions) sendPingPong(sinfo *sessionInfo, isPong bool) { func (ss *sessions) handlePing(ping *sessionPing) { // Get the corresponding session (or create a new session) sinfo, isIn := ss.getByTheirPerm(&ping.SendPermPub) - // Check the session firewall - if !isIn && ss.isSessionFirewallEnabled() { - if !ss.isSessionAllowed(&ping.SendPermPub, false) { - return - } + // Check if the session is allowed + if !isIn && !ss.isSessionAllowed(&ping.SendPermPub, false) { + return } + // Create the session if it doesn't already exist if !isIn { ss.createSession(&ping.SendPermPub) sinfo, isIn = ss.getByTheirPerm(&ping.SendPermPub) From 907986f2009f649a27df4f73676975a59319a2d2 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 11 Jun 2019 12:50:01 +0100 Subject: [PATCH 03/34] Implement session firewall as gatekeeper func in cmd/yggdrasil --- cmd/yggdrasil/main.go | 85 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 75 insertions(+), 10 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 5dba3a51..fb6cccbd 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "encoding/hex" "encoding/json" "flag" "fmt" @@ -20,22 +21,21 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/admin" "github.com/yggdrasil-network/yggdrasil-go/src/config" + "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/multicast" "github.com/yggdrasil-network/yggdrasil-go/src/tuntap" "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" ) -type nodeConfig = config.NodeConfig -type Core = yggdrasil.Core - type node struct { - core Core + core yggdrasil.Core + state *config.NodeState tuntap tuntap.TunAdapter multicast multicast.Multicast admin admin.AdminSocket } -func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *nodeConfig { +func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *config.NodeConfig { // Use a configuration file. If -useconf, the configuration will be read // from stdin. If -useconffile, the configuration will be read from the // filesystem. @@ -116,7 +116,7 @@ func main() { logging := flag.String("logging", "info,warn,error", "comma-separated list of logging levels to enable") flag.Parse() - var cfg *nodeConfig + var cfg *config.NodeConfig var err error switch { case *version: @@ -181,18 +181,20 @@ func main() { n := node{} // Now start Yggdrasil - this starts the DHT, router, switch and other core // components needed for Yggdrasil to operate - state, err := n.core.Start(cfg, logger) + n.state, err = n.core.Start(cfg, logger) if err != nil { logger.Errorln("An error occurred during startup") panic(err) } + // Register the session firewall gatekeeper function + n.core.SetSessionGatekeeper(n.sessionFirewall) // Start the admin socket - n.admin.Init(&n.core, state, logger, nil) + n.admin.Init(&n.core, n.state, logger, nil) if err := n.admin.Start(); err != nil { logger.Errorln("An error occurred starting admin socket:", err) } // Start the multicast interface - n.multicast.Init(&n.core, state, logger, nil) + n.multicast.Init(&n.core, n.state, logger, nil) if err := n.multicast.Start(); err != nil { logger.Errorln("An error occurred starting multicast:", err) } @@ -200,7 +202,7 @@ func main() { // Start the TUN/TAP interface if listener, err := n.core.ConnListen(); err == nil { if dialer, err := n.core.ConnDialer(); err == nil { - n.tuntap.Init(state, logger, listener, dialer) + n.tuntap.Init(n.state, logger, listener, dialer) if err := n.tuntap.Start(); err != nil { logger.Errorln("An error occurred starting TUN/TAP:", err) } @@ -251,3 +253,66 @@ func main() { } exit: } + +func (n *node) sessionFirewall(pubkey *crypto.BoxPubKey, initiator bool) bool { + n.state.Mutex.RLock() + defer n.state.Mutex.RUnlock() + + // Allow by default if the session firewall is disabled + if !n.state.Current.SessionFirewall.Enable { + return true + } + + // Prepare for checking whitelist/blacklist + var box crypto.BoxPubKey + // Reject blacklisted nodes + for _, b := range n.state.Current.SessionFirewall.BlacklistEncryptionPublicKeys { + key, err := hex.DecodeString(b) + if err == nil { + copy(box[:crypto.BoxPubKeyLen], key) + if box == *pubkey { + return false + } + } + } + + // Allow whitelisted nodes + for _, b := range n.state.Current.SessionFirewall.WhitelistEncryptionPublicKeys { + key, err := hex.DecodeString(b) + if err == nil { + copy(box[:crypto.BoxPubKeyLen], key) + if box == *pubkey { + return true + } + } + } + + // Allow outbound sessions if appropriate + if n.state.Current.SessionFirewall.AlwaysAllowOutbound { + if initiator { + return true + } + } + + // Look and see if the pubkey is that of a direct peer + var isDirectPeer bool + for _, peer := range n.core.GetPeers() { + if peer.PublicKey == *pubkey { + isDirectPeer = true + break + } + } + + // Allow direct peers if appropriate + if n.state.Current.SessionFirewall.AllowFromDirect && isDirectPeer { + return true + } + + // Allow remote nodes if appropriate + if n.state.Current.SessionFirewall.AllowFromRemote && !isDirectPeer { + return true + } + + // Finally, default-deny if not matching any of the above rules + return false +} From e229ad6e2b7253a8db3da9876794aa220b5314bb Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 11 Jun 2019 12:52:13 +0100 Subject: [PATCH 04/34] Update comments --- src/yggdrasil/api.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 9d640997..25f9869c 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -398,8 +398,9 @@ func (c *Core) GetNodeInfo(keyString, coordString string, nocache bool) (NodeInf // SetSessionGatekeeper allows you to configure a handler function for deciding // whether a session should be allowed or not. The default session firewall is // implemented in this way. The function receives the public key of the remote -// side, and a boolean which is true if we initiated the session or false if we -// received an incoming session request. +// side and a boolean which is true if we initiated the session or false if we +// received an incoming session request. The function should return true to +// allow the session or false to reject it. func (c *Core) SetSessionGatekeeper(f func(pubkey *crypto.BoxPubKey, initiator bool) bool) { c.sessions.isAllowedMutex.Lock() defer c.sessions.isAllowedMutex.Unlock() From 9a7d3508846819320f761e638b154b6053c9bf05 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 11 Jun 2019 23:48:00 +0100 Subject: [PATCH 05/34] Fix expressions --- src/defaults/defaults_darwin.go | 4 ++-- src/defaults/defaults_linux.go | 5 +---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/defaults/defaults_darwin.go b/src/defaults/defaults_darwin.go index 26fd1f2d..47683bf9 100644 --- a/src/defaults/defaults_darwin.go +++ b/src/defaults/defaults_darwin.go @@ -14,8 +14,8 @@ func GetDefaults() platformDefaultParameters { // Multicast interfaces DefaultMulticastInterfaces: []string{ - "en*", - "bridge*", + "en.*", + "bridge.*", }, // TUN/TAP diff --git a/src/defaults/defaults_linux.go b/src/defaults/defaults_linux.go index 67404e2d..b0aaf855 100644 --- a/src/defaults/defaults_linux.go +++ b/src/defaults/defaults_linux.go @@ -14,10 +14,7 @@ func GetDefaults() platformDefaultParameters { // Multicast interfaces DefaultMulticastInterfaces: []string{ - "en*", - "eth*", - "wlan*", - "br*", + ".*", }, // TUN/TAP From f545060e89bb53ff540391fa67273dba96526165 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 13 Jun 2019 23:37:53 +0100 Subject: [PATCH 06/34] Add notes on isSessionAllowed checks --- src/yggdrasil/session.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 46628c3e..22118476 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -219,6 +219,7 @@ func (ss *sessions) getByTheirSubnet(snet *address.Subnet) (*sessionInfo, bool) // includse initializing session info to sane defaults (e.g. lowest supported // MTU). func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { + // TODO: this check definitely needs to be moved if !ss.isSessionAllowed(theirPermKey, true) { return nil } @@ -393,6 +394,7 @@ func (ss *sessions) handlePing(ping *sessionPing) { // Get the corresponding session (or create a new session) sinfo, isIn := ss.getByTheirPerm(&ping.SendPermPub) // Check if the session is allowed + // TODO: this check may need to be moved if !isIn && !ss.isSessionAllowed(&ping.SendPermPub, false) { return } From 54f1804101ff45e0b02580a17edaeab882beb21f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 20 Jun 2019 15:11:55 +0100 Subject: [PATCH 07/34] Try and solidify multicast interface behavior --- src/multicast/admin.go | 2 +- src/multicast/multicast.go | 143 +++++++++++++++--------------- src/multicast/multicast_darwin.go | 4 +- 3 files changed, 76 insertions(+), 73 deletions(-) diff --git a/src/multicast/admin.go b/src/multicast/admin.go index 672b7ca4..40e28af4 100644 --- a/src/multicast/admin.go +++ b/src/multicast/admin.go @@ -5,7 +5,7 @@ import "github.com/yggdrasil-network/yggdrasil-go/src/admin" func (m *Multicast) SetupAdminHandlers(a *admin.AdminSocket) { a.AddHandler("getMulticastInterfaces", []string{}, func(in admin.Info) (admin.Info, error) { var intfs []string - for _, v := range m.interfaces() { + for _, v := range m.GetInterfaces() { intfs = append(intfs, v.Name) } return admin.Info{"multicast_interfaces": intfs}, nil diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index cbde7fd9..f0b1a9a1 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -5,6 +5,7 @@ import ( "fmt" "net" "regexp" + "sync" "time" "github.com/gologme/log" @@ -19,14 +20,16 @@ import ( // configured multicast interface, Yggdrasil will attempt to peer with that node // automatically. type Multicast struct { - core *yggdrasil.Core - config *config.NodeState - log *log.Logger - reconfigure chan chan error - sock *ipv6.PacketConn - groupAddr string - listeners map[string]*yggdrasil.TcpListener - listenPort uint16 + core *yggdrasil.Core + config *config.NodeState + log *log.Logger + sock *ipv6.PacketConn + groupAddr string + listeners map[string]*yggdrasil.TcpListener + listenPort uint16 + interfaces map[string]net.Interface + interfacesMutex sync.RWMutex + interfacesTime time.Time } // Init prepares the multicast interface for use. @@ -34,25 +37,24 @@ func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log m.core = core m.config = state m.log = log - m.reconfigure = make(chan chan error, 1) m.listeners = make(map[string]*yggdrasil.TcpListener) + m.interfaces = make(map[string]net.Interface) current, _ := m.config.Get() m.listenPort = current.LinkLocalTCPPort + m.groupAddr = "[ff02::114]:9001" + // Perform our first check for multicast interfaces + if count := m.UpdateInterfaces(); count != 0 { + m.log.Infoln("Found", count, "multicast interface(s)") + } else { + m.log.Infoln("Multicast is not enabled on any interfaces") + } + // Keep checking quietly every minute in case they change go func() { for { - e := <-m.reconfigure - // There's nothing particularly to do here because the multicast module - // already consults the config.NodeState when enumerating multicast - // interfaces on each pass. We just need to return nil so that the - // reconfiguration doesn't block indefinitely - e <- nil + time.Sleep(time.Minute) + m.UpdateInterfaces() } }() - m.groupAddr = "[ff02::114]:9001" - // Check if we've been given any expressions - if count := len(m.interfaces()); count != 0 { - m.log.Infoln("Found", count, "multicast interface(s)") - } return nil } @@ -60,32 +62,27 @@ func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log // listen for multicast beacons from other hosts and will advertise multicast // beacons out to the network. func (m *Multicast) Start() error { - current, _ := m.config.Get() - if len(current.MulticastInterfaces) == 0 { - m.log.Infoln("Multicast discovery is disabled") - } else { - m.log.Infoln("Multicast discovery is enabled") - addr, err := net.ResolveUDPAddr("udp", m.groupAddr) - if err != nil { - return err - } - listenString := fmt.Sprintf("[::]:%v", addr.Port) - lc := net.ListenConfig{ - Control: m.multicastReuse, - } - conn, err := lc.ListenPacket(context.Background(), "udp6", listenString) - if err != nil { - return err - } - m.sock = ipv6.NewPacketConn(conn) - if err = m.sock.SetControlMessage(ipv6.FlagDst, true); err != nil { - // Windows can't set this flag, so we need to handle it in other ways - } - - go m.multicastStarted() - go m.listen() - go m.announce() + addr, err := net.ResolveUDPAddr("udp", m.groupAddr) + if err != nil { + return err } + listenString := fmt.Sprintf("[::]:%v", addr.Port) + lc := net.ListenConfig{ + Control: m.multicastReuse, + } + conn, err := lc.ListenPacket(context.Background(), "udp6", listenString) + if err != nil { + return err + } + m.sock = ipv6.NewPacketConn(conn) + if err = m.sock.SetControlMessage(ipv6.FlagDst, true); err != nil { + // Windows can't set this flag, so we need to handle it in other ways + } + + go m.multicastStarted() + go m.listen() + go m.announce() + return nil } @@ -102,34 +99,37 @@ func (m *Multicast) UpdateConfig(config *config.NodeConfig) { m.config.Replace(*config) - errors := 0 + m.log.Infoln("Multicast configuration reloaded successfully") - components := []chan chan error{ - m.reconfigure, - } - - for _, component := range components { - response := make(chan error) - component <- response - if err := <-response; err != nil { - m.log.Errorln(err) - errors++ - } - } - - if errors > 0 { - m.log.Warnln(errors, "multicast module(s) reported errors during configuration reload") + if count := m.UpdateInterfaces(); count != 0 { + m.log.Infoln("Found", count, "multicast interface(s)") } else { - m.log.Infoln("Multicast configuration reloaded successfully") + m.log.Infoln("Multicast is not enabled on any interfaces") } } -func (m *Multicast) interfaces() map[string]net.Interface { +// GetInterfaces returns the currently known/enabled multicast interfaces. It is +// expected that UpdateInterfaces has been called at least once before calling +// this method. +func (m *Multicast) GetInterfaces() map[string]net.Interface { + m.interfacesMutex.RLock() + defer m.interfacesMutex.RUnlock() + return m.interfaces +} + +// UpdateInterfaces re-enumerates the available multicast interfaces on the +// system, using the current MulticastInterfaces config option as a template. +// The number of selected interfaces is returned. +func (m *Multicast) UpdateInterfaces() int { + m.interfacesMutex.Lock() + defer m.interfacesMutex.Unlock() // Get interface expressions from config current, _ := m.config.Get() exprs := current.MulticastInterfaces // Ask the system for network interfaces - interfaces := make(map[string]net.Interface) + for i := range m.interfaces { + delete(m.interfaces, i) + } allifaces, err := net.Interfaces() if err != nil { panic(err) @@ -156,11 +156,12 @@ func (m *Multicast) interfaces() map[string]net.Interface { } // Does the interface match the regular expression? Store it if so if e.MatchString(iface.Name) { - interfaces[iface.Name] = iface + m.interfaces[iface.Name] = iface } } } - return interfaces + m.interfacesTime = time.Now() + return len(m.interfaces) } func (m *Multicast) announce() { @@ -173,7 +174,7 @@ func (m *Multicast) announce() { panic(err) } for { - interfaces := m.interfaces() + interfaces := m.GetInterfaces() // There might be interfaces that we configured listeners for but are no // longer up - if that's the case then we should stop the listeners for name, listener := range m.listeners { @@ -307,9 +308,11 @@ func (m *Multicast) listen() { if addr.IP.String() != from.IP.String() { continue } - addr.Zone = "" - if err := m.core.CallPeer("tcp://"+addr.String(), from.Zone); err != nil { - m.log.Debugln("Call from multicast failed:", err) + if _, ok := m.GetInterfaces()[from.Zone]; ok { + addr.Zone = "" + if err := m.core.CallPeer("tcp://"+addr.String(), from.Zone); err != nil { + m.log.Debugln("Call from multicast failed:", err) + } } } } diff --git a/src/multicast/multicast_darwin.go b/src/multicast/multicast_darwin.go index 900354c7..213bff3a 100644 --- a/src/multicast/multicast_darwin.go +++ b/src/multicast/multicast_darwin.go @@ -35,12 +35,12 @@ func (m *Multicast) multicastStarted() { if awdlGoroutineStarted { return } - m.log.Infoln("Multicast discovery will wake up AWDL if required") awdlGoroutineStarted = true for { C.StopAWDLBrowsing() - for _, intf := range m.interfaces() { + for _, intf := range m.GetInterfaces() { if intf.Name == "awdl0" { + m.log.Infoln("Multicast discovery is using AWDL discovery") C.StartAWDLBrowsing() break } From 29a0f8b572f5db41e86af8d657a578c62890922c Mon Sep 17 00:00:00 2001 From: Arceliar Date: Tue, 25 Jun 2019 19:31:29 -0500 Subject: [PATCH 08/34] some minor refactoring to dht callbacks and searches, work in progress --- src/yggdrasil/api.go | 13 ++----- src/yggdrasil/conn.go | 2 +- src/yggdrasil/dht.go | 29 ++++++++++------ src/yggdrasil/search.go | 75 +++++++++++++++++++++-------------------- 4 files changed, 61 insertions(+), 58 deletions(-) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 25f9869c..b98df3bf 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -517,21 +517,14 @@ func (c *Core) DHTPing(keyString, coordString, targetString string) (DHTRes, err rq := dhtReqKey{info.key, target} sendPing := func() { c.dht.addCallback(&rq, func(res *dhtRes) { - defer func() { recover() }() - select { - case resCh <- res: - default: - } + resCh <- res }) c.dht.ping(&info, &target) } c.router.doAdmin(sendPing) - go func() { - time.Sleep(6 * time.Second) - close(resCh) - }() // TODO: do something better than the below... - for res := range resCh { + res := <-resCh + if res != nil { r := DHTRes{ Coords: append([]byte{}, res.Coords...), } diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 9ce5563d..7216be91 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -128,7 +128,7 @@ func (c *Conn) startSearch() { c.core.log.Debugf("%s DHT search started: %p", c.String(), sinfo) } // Continue the search - c.core.searches.continueSearch(sinfo) + sinfo.continueSearch() } // Take a copy of the session object, in case it changes later c.mutex.RLock() diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index b081c92d..b53e29c9 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -68,9 +68,9 @@ type dht struct { core *Core reconfigure chan chan error nodeID crypto.NodeID - peers chan *dhtInfo // other goroutines put incoming dht updates here - reqs map[dhtReqKey]time.Time // Keeps track of recent outstanding requests - callbacks map[dhtReqKey]dht_callbackInfo // Search and admin lookup callbacks + peers chan *dhtInfo // other goroutines put incoming dht updates here + reqs map[dhtReqKey]time.Time // Keeps track of recent outstanding requests + callbacks map[dhtReqKey][]dht_callbackInfo // Search and admin lookup callbacks // These next two could be replaced by a single linked list or similar... table map[crypto.NodeID]*dhtInfo imp []*dhtInfo @@ -88,7 +88,7 @@ func (t *dht) init(c *Core) { }() t.nodeID = *t.core.NodeID() t.peers = make(chan *dhtInfo, 1024) - t.callbacks = make(map[dhtReqKey]dht_callbackInfo) + t.callbacks = make(map[dhtReqKey][]dht_callbackInfo) t.reset() } @@ -244,15 +244,17 @@ type dht_callbackInfo struct { // Adds a callback and removes it after some timeout. func (t *dht) addCallback(rq *dhtReqKey, callback func(*dhtRes)) { info := dht_callbackInfo{callback, time.Now().Add(6 * time.Second)} - t.callbacks[*rq] = info + t.callbacks[*rq] = append(t.callbacks[*rq], info) } // Reads a lookup response, checks that we had sent a matching request, and processes the response info. // This mainly consists of updating the node we asked in our DHT (they responded, so we know they're still alive), and deciding if we want to do anything with their responses func (t *dht) handleRes(res *dhtRes) { rq := dhtReqKey{res.Key, res.Dest} - if callback, isIn := t.callbacks[rq]; isIn { - callback.f(res) + if callbacks, isIn := t.callbacks[rq]; isIn { + for _, callback := range callbacks { + callback.f(res) + } delete(t.callbacks, rq) } _, isIn := t.reqs[rq] @@ -326,10 +328,15 @@ func (t *dht) doMaintenance() { } } t.reqs = newReqs - newCallbacks := make(map[dhtReqKey]dht_callbackInfo, len(t.callbacks)) - for key, callback := range t.callbacks { - if now.Before(callback.time) { - newCallbacks[key] = callback + newCallbacks := make(map[dhtReqKey][]dht_callbackInfo, len(t.callbacks)) + for key, cs := range t.callbacks { + for _, c := range cs { + if now.Before(c.time) { + newCallbacks[key] = append(newCallbacks[key], c) + } else { + // Signal failure + c.f(nil) + } } } t.callbacks = newCallbacks diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index 0a643363..576034bf 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -33,6 +33,7 @@ const search_RETRY_TIME = time.Second // Information about an ongoing search. // Includes the target NodeID, the bitmask to match it to an IP, and the list of nodes to visit / already visited. type searchInfo struct { + core *Core dest crypto.NodeID mask crypto.NodeID time time.Time @@ -40,6 +41,7 @@ type searchInfo struct { toVisit []*dhtInfo visited map[crypto.NodeID]bool callback func(*sessionInfo, error) + // TODO context.Context for timeout and cancellation } // This stores a map of active searches. @@ -49,7 +51,7 @@ type searches struct { searches map[crypto.NodeID]*searchInfo } -// Intializes the searches struct. +// Initializes the searches struct. func (s *searches) init(core *Core) { s.core = core s.reconfigure = make(chan chan error, 1) @@ -65,12 +67,13 @@ func (s *searches) init(core *Core) { // Creates a new search info, adds it to the searches struct, and returns a pointer to the info. func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID, callback func(*sessionInfo, error)) *searchInfo { now := time.Now() - for dest, sinfo := range s.searches { - if now.Sub(sinfo.time) > time.Minute { - delete(s.searches, dest) - } - } + //for dest, sinfo := range s.searches { + // if now.Sub(sinfo.time) > time.Minute { + // delete(s.searches, dest) + // } + //} info := searchInfo{ + core: s.core, dest: *dest, mask: *mask, time: now.Add(-time.Second), @@ -82,30 +85,29 @@ func (s *searches) createSearch(dest *crypto.NodeID, mask *crypto.NodeID, callba //////////////////////////////////////////////////////////////////////////////// -// Checks if there's an ongoing search relaed to a dhtRes. +// Checks if there's an ongoing search related to a dhtRes. // If there is, it adds the response info to the search and triggers a new search step. // If there's no ongoing search, or we if the dhtRes finished the search (it was from the target node), then don't do anything more. -func (s *searches) handleDHTRes(res *dhtRes) { - sinfo, isIn := s.searches[res.Dest] - if !isIn || s.checkDHTRes(sinfo, res) { +func (sinfo *searchInfo) handleDHTRes(res *dhtRes) { + if res == nil || sinfo.checkDHTRes(res) { // Either we don't recognize this search, or we just finished it return } // Add to the search and continue - s.addToSearch(sinfo, res) - s.doSearchStep(sinfo) + sinfo.addToSearch(res) + sinfo.doSearchStep() } // Adds the information from a dhtRes to an ongoing search. // Info about a node that has already been visited is not re-added to the search. // Duplicate information about nodes toVisit is deduplicated (the newest information is kept). // The toVisit list is sorted in ascending order of keyspace distance from the destination. -func (s *searches) addToSearch(sinfo *searchInfo, res *dhtRes) { +func (sinfo *searchInfo) addToSearch(res *dhtRes) { // Add responses to toVisit if closer to dest than the res node from := dhtInfo{key: res.Key, coords: res.Coords} sinfo.visited[*from.getNodeID()] = true for _, info := range res.Infos { - if *info.getNodeID() == s.core.dht.nodeID || sinfo.visited[*info.getNodeID()] { + if *info.getNodeID() == sinfo.core.dht.nodeID || sinfo.visited[*info.getNodeID()] { continue } if dht_ordered(&sinfo.dest, info.getNodeID(), from.getNodeID()) { @@ -135,10 +137,10 @@ func (s *searches) addToSearch(sinfo *searchInfo, res *dhtRes) { // If there are no nodes left toVisit, then this cleans up the search. // Otherwise, it pops the closest node to the destination (in keyspace) off of the toVisit list and sends a dht ping. -func (s *searches) doSearchStep(sinfo *searchInfo) { +func (sinfo *searchInfo) doSearchStep() { if len(sinfo.toVisit) == 0 { // Dead end, do cleanup - delete(s.searches, sinfo.dest) + delete(sinfo.core.searches.searches, sinfo.dest) go sinfo.callback(nil, errors.New("search reached dead end")) return } @@ -146,31 +148,32 @@ func (s *searches) doSearchStep(sinfo *searchInfo) { var next *dhtInfo next, sinfo.toVisit = sinfo.toVisit[0], sinfo.toVisit[1:] rq := dhtReqKey{next.key, sinfo.dest} - s.core.dht.addCallback(&rq, s.handleDHTRes) - s.core.dht.ping(next, &sinfo.dest) + sinfo.core.dht.addCallback(&rq, sinfo.handleDHTRes) + sinfo.core.dht.ping(next, &sinfo.dest) } // If we've recenty sent a ping for this search, do nothing. // Otherwise, doSearchStep and schedule another continueSearch to happen after search_RETRY_TIME. -func (s *searches) continueSearch(sinfo *searchInfo) { +func (sinfo *searchInfo) continueSearch() { if time.Since(sinfo.time) < search_RETRY_TIME { return } sinfo.time = time.Now() - s.doSearchStep(sinfo) + sinfo.doSearchStep() // In case the search dies, try to spawn another thread later // Note that this will spawn multiple parallel searches as time passes // Any that die aren't restarted, but a new one will start later retryLater := func() { - newSearchInfo := s.searches[sinfo.dest] + // FIXME this keeps the search alive forever if not for the searches map, fix that + newSearchInfo := sinfo.core.searches.searches[sinfo.dest] if newSearchInfo != sinfo { return } - s.continueSearch(sinfo) + sinfo.continueSearch() } go func() { time.Sleep(search_RETRY_TIME) - s.core.router.admin <- retryLater + sinfo.core.router.admin <- retryLater }() } @@ -185,37 +188,37 @@ func (s *searches) newIterSearch(dest *crypto.NodeID, mask *crypto.NodeID, callb // Checks if a dhtRes is good (called by handleDHTRes). // If the response is from the target, get/create a session, trigger a session ping, and return true. // Otherwise return false. -func (s *searches) checkDHTRes(info *searchInfo, res *dhtRes) bool { +func (sinfo *searchInfo) checkDHTRes(res *dhtRes) bool { them := crypto.GetNodeID(&res.Key) var destMasked crypto.NodeID var themMasked crypto.NodeID for idx := 0; idx < crypto.NodeIDLen; idx++ { - destMasked[idx] = info.dest[idx] & info.mask[idx] - themMasked[idx] = them[idx] & info.mask[idx] + destMasked[idx] = sinfo.dest[idx] & sinfo.mask[idx] + themMasked[idx] = them[idx] & sinfo.mask[idx] } if themMasked != destMasked { return false } // They match, so create a session and send a sessionRequest - sinfo, isIn := s.core.sessions.getByTheirPerm(&res.Key) + sess, isIn := sinfo.core.sessions.getByTheirPerm(&res.Key) if !isIn { - sinfo = s.core.sessions.createSession(&res.Key) - if sinfo == nil { + sess = sinfo.core.sessions.createSession(&res.Key) + if sess == nil { // nil if the DHT search finished but the session wasn't allowed - go info.callback(nil, errors.New("session not allowed")) + go sinfo.callback(nil, errors.New("session not allowed")) return true } - _, isIn := s.core.sessions.getByTheirPerm(&res.Key) + _, isIn := sinfo.core.sessions.getByTheirPerm(&res.Key) if !isIn { panic("This should never happen") } } // FIXME (!) replay attacks could mess with coords? Give it a handle (tstamp)? - sinfo.coords = res.Coords - sinfo.packet = info.packet - s.core.sessions.ping(sinfo) - go info.callback(sinfo, nil) + sess.coords = res.Coords + sess.packet = sinfo.packet + sinfo.core.sessions.ping(sess) + go sinfo.callback(sess, nil) // Cleanup - delete(s.searches, res.Dest) + delete(sinfo.core.searches.searches, res.Dest) return true } From 93a323c62c52b156207922414e31a1890d235d26 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 28 Jun 2019 23:45:04 +0100 Subject: [PATCH 09/34] Add support for logging to file or syslog instead of stdout --- cmd/yggdrasil/main.go | 20 +++++++++++++++++++- src/yggdrasil/api.go | 2 +- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index fb6cccbd..32069c01 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -7,6 +7,7 @@ import ( "flag" "fmt" "io/ioutil" + "log/syslog" "os" "os/signal" "strings" @@ -114,6 +115,7 @@ func main() { autoconf := flag.Bool("autoconf", false, "automatic mode (dynamic IP, peer with IPv6 neighbors)") version := flag.Bool("version", false, "prints the version of this build") logging := flag.String("logging", "info,warn,error", "comma-separated list of logging levels to enable") + logto := flag.String("logto", "stdout", "file path to log to, \"syslog\" or \"stdout\"") flag.Parse() var cfg *config.NodeConfig @@ -161,7 +163,23 @@ func main() { return } // Create a new logger that logs output to stdout. - logger := log.New(os.Stdout, "", log.Flags()) + var logger *log.Logger + switch *logto { + case "stdout": + logger = log.New(os.Stdout, "", log.Flags()) + case "syslog": + if syslogwriter, err := syslog.New(syslog.LOG_INFO, yggdrasil.BuildName()); err == nil { + logger = log.New(syslogwriter, "", log.Flags()) + } + default: + if logfd, err := os.Create(*logto); err == nil { + logger = log.New(logfd, "", log.Flags()) + } + } + if logger == nil { + logger = log.New(os.Stdout, "", log.Flags()) + logger.Warnln("Logging defaulting to stdout") + } //logger.EnableLevel("error") //logger.EnableLevel("warn") //logger.EnableLevel("info") diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 25f9869c..462353c0 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -232,7 +232,7 @@ func (c *Core) GetSessions() []Session { // from git, or returns "unknown" otherwise. func BuildName() string { if buildName == "" { - return "unknown" + return "yggdrasil" } return buildName } From 27b3b9b49bab0da7814951310081f8b360b3a81f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 29 Jun 2019 00:12:56 +0100 Subject: [PATCH 10/34] Return new copy of interfaces on each Interfaces() call --- src/multicast/admin.go | 2 +- src/multicast/multicast.go | 66 ++++++------------------------- src/multicast/multicast_darwin.go | 4 +- 3 files changed, 16 insertions(+), 56 deletions(-) diff --git a/src/multicast/admin.go b/src/multicast/admin.go index 40e28af4..cafee07f 100644 --- a/src/multicast/admin.go +++ b/src/multicast/admin.go @@ -5,7 +5,7 @@ import "github.com/yggdrasil-network/yggdrasil-go/src/admin" func (m *Multicast) SetupAdminHandlers(a *admin.AdminSocket) { a.AddHandler("getMulticastInterfaces", []string{}, func(in admin.Info) (admin.Info, error) { var intfs []string - for _, v := range m.GetInterfaces() { + for _, v := range m.Interfaces() { intfs = append(intfs, v.Name) } return admin.Info{"multicast_interfaces": intfs}, nil diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index f0b1a9a1..3c0d8c0f 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -5,7 +5,6 @@ import ( "fmt" "net" "regexp" - "sync" "time" "github.com/gologme/log" @@ -20,16 +19,13 @@ import ( // configured multicast interface, Yggdrasil will attempt to peer with that node // automatically. type Multicast struct { - core *yggdrasil.Core - config *config.NodeState - log *log.Logger - sock *ipv6.PacketConn - groupAddr string - listeners map[string]*yggdrasil.TcpListener - listenPort uint16 - interfaces map[string]net.Interface - interfacesMutex sync.RWMutex - interfacesTime time.Time + core *yggdrasil.Core + config *config.NodeState + log *log.Logger + sock *ipv6.PacketConn + groupAddr string + listeners map[string]*yggdrasil.TcpListener + listenPort uint16 } // Init prepares the multicast interface for use. @@ -38,23 +34,9 @@ func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log m.config = state m.log = log m.listeners = make(map[string]*yggdrasil.TcpListener) - m.interfaces = make(map[string]net.Interface) current, _ := m.config.Get() m.listenPort = current.LinkLocalTCPPort m.groupAddr = "[ff02::114]:9001" - // Perform our first check for multicast interfaces - if count := m.UpdateInterfaces(); count != 0 { - m.log.Infoln("Found", count, "multicast interface(s)") - } else { - m.log.Infoln("Multicast is not enabled on any interfaces") - } - // Keep checking quietly every minute in case they change - go func() { - for { - time.Sleep(time.Minute) - m.UpdateInterfaces() - } - }() return nil } @@ -96,40 +78,19 @@ func (m *Multicast) Stop() error { // needed. func (m *Multicast) UpdateConfig(config *config.NodeConfig) { m.log.Debugln("Reloading multicast configuration...") - m.config.Replace(*config) - m.log.Infoln("Multicast configuration reloaded successfully") - - if count := m.UpdateInterfaces(); count != 0 { - m.log.Infoln("Found", count, "multicast interface(s)") - } else { - m.log.Infoln("Multicast is not enabled on any interfaces") - } } // GetInterfaces returns the currently known/enabled multicast interfaces. It is // expected that UpdateInterfaces has been called at least once before calling // this method. -func (m *Multicast) GetInterfaces() map[string]net.Interface { - m.interfacesMutex.RLock() - defer m.interfacesMutex.RUnlock() - return m.interfaces -} - -// UpdateInterfaces re-enumerates the available multicast interfaces on the -// system, using the current MulticastInterfaces config option as a template. -// The number of selected interfaces is returned. -func (m *Multicast) UpdateInterfaces() int { - m.interfacesMutex.Lock() - defer m.interfacesMutex.Unlock() +func (m *Multicast) Interfaces() map[string]net.Interface { + interfaces := make(map[string]net.Interface) // Get interface expressions from config current, _ := m.config.Get() exprs := current.MulticastInterfaces // Ask the system for network interfaces - for i := range m.interfaces { - delete(m.interfaces, i) - } allifaces, err := net.Interfaces() if err != nil { panic(err) @@ -156,12 +117,11 @@ func (m *Multicast) UpdateInterfaces() int { } // Does the interface match the regular expression? Store it if so if e.MatchString(iface.Name) { - m.interfaces[iface.Name] = iface + interfaces[iface.Name] = iface } } } - m.interfacesTime = time.Now() - return len(m.interfaces) + return interfaces } func (m *Multicast) announce() { @@ -174,7 +134,7 @@ func (m *Multicast) announce() { panic(err) } for { - interfaces := m.GetInterfaces() + interfaces := m.Interfaces() // There might be interfaces that we configured listeners for but are no // longer up - if that's the case then we should stop the listeners for name, listener := range m.listeners { @@ -308,7 +268,7 @@ func (m *Multicast) listen() { if addr.IP.String() != from.IP.String() { continue } - if _, ok := m.GetInterfaces()[from.Zone]; ok { + if _, ok := m.Interfaces()[from.Zone]; ok { addr.Zone = "" if err := m.core.CallPeer("tcp://"+addr.String(), from.Zone); err != nil { m.log.Debugln("Call from multicast failed:", err) diff --git a/src/multicast/multicast_darwin.go b/src/multicast/multicast_darwin.go index 213bff3a..c88b4a81 100644 --- a/src/multicast/multicast_darwin.go +++ b/src/multicast/multicast_darwin.go @@ -38,8 +38,8 @@ func (m *Multicast) multicastStarted() { awdlGoroutineStarted = true for { C.StopAWDLBrowsing() - for _, intf := range m.GetInterfaces() { - if intf.Name == "awdl0" { + for intf := range m.Interfaces() { + if intf == "awdl0" { m.log.Infoln("Multicast discovery is using AWDL discovery") C.StartAWDLBrowsing() break From 23108e268b2888c27d62687a2e8adb0a4bba722a Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 29 Jun 2019 00:32:23 +0100 Subject: [PATCH 11/34] Use go-syslog to fix builds on Windows --- cmd/yggdrasil/main.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 32069c01..6af27725 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -7,7 +7,6 @@ import ( "flag" "fmt" "io/ioutil" - "log/syslog" "os" "os/signal" "strings" @@ -16,6 +15,7 @@ import ( "golang.org/x/text/encoding/unicode" "github.com/gologme/log" + gsyslog "github.com/hashicorp/go-syslog" "github.com/hjson/hjson-go" "github.com/kardianos/minwinsvc" "github.com/mitchellh/mapstructure" @@ -168,8 +168,8 @@ func main() { case "stdout": logger = log.New(os.Stdout, "", log.Flags()) case "syslog": - if syslogwriter, err := syslog.New(syslog.LOG_INFO, yggdrasil.BuildName()); err == nil { - logger = log.New(syslogwriter, "", log.Flags()) + if syslogger, err := gsyslog.NewLogger(gsyslog.LOG_NOTICE, "DAEMON", yggdrasil.BuildName()); err == nil { + logger = log.New(syslogger, "", log.Flags()) } default: if logfd, err := os.Create(*logto); err == nil { From 5df110ac7985e87d1f11dc840cdb55dd6b69cbe4 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 28 Jun 2019 18:42:31 -0500 Subject: [PATCH 12/34] make Dial block until the search finishes, and use it as such --- src/tuntap/iface.go | 55 ++++++++--- src/tuntap/tun.go | 2 + src/yggdrasil/conn.go | 199 ++++++++++++---------------------------- src/yggdrasil/dialer.go | 5 + src/yggdrasil/search.go | 6 +- 5 files changed, 111 insertions(+), 156 deletions(-) diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index d70a1305..f6cfec9c 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -134,7 +134,7 @@ func (tun *TunAdapter) reader() error { } // Then offset the buffer so that we can now just treat it as an IP // packet from now on - bs = bs[offset:] + bs = bs[offset:] // FIXME this breaks bs for the next read and means n is the wrong value } // From the IP header, work out what our source and destination addresses // and node IDs are. We will need these in order to work out where to send @@ -225,21 +225,46 @@ func (tun *TunAdapter) reader() error { panic("Given empty dstNodeID and dstNodeIDMask - this shouldn't happen") } // Dial to the remote node - if conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil { - // We've been given a connection so prepare the session wrapper - if s, err := tun.wrap(conn); err != nil { - // Something went wrong when storing the connection, typically that - // something already exists for this address or subnet - tun.log.Debugln("TUN/TAP iface wrap:", err) - } else { - // Update our reference to the connection - session, isIn = s, true + go func() { + // FIXME just spitting out a goroutine to do this is kind of ugly and means we drop packets until the dial finishes + tun.mutex.Lock() + _, known := tun.dials[*dstNodeID] + packet := append(util.GetBytes(), bs[:n]...) + tun.dials[*dstNodeID] = append(tun.dials[*dstNodeID], packet) + for len(tun.dials[*dstNodeID]) > 32 { + util.PutBytes(tun.dials[*dstNodeID][0]) + tun.dials[*dstNodeID] = tun.dials[*dstNodeID][1:] } - } else { - // We weren't able to dial for some reason so there's no point in - // continuing this iteration - skip to the next one - continue - } + tun.mutex.Unlock() + if known { + return + } + var tc *tunConn + if conn, err := tun.dialer.DialByNodeIDandMask(dstNodeID, dstNodeIDMask); err == nil { + // We've been given a connection so prepare the session wrapper + if tc, err = tun.wrap(conn); err != nil { + // Something went wrong when storing the connection, typically that + // something already exists for this address or subnet + tun.log.Debugln("TUN/TAP iface wrap:", err) + } + } + tun.mutex.Lock() + packets := tun.dials[*dstNodeID] + delete(tun.dials, *dstNodeID) + tun.mutex.Unlock() + if tc != nil { + for _, packet := range packets { + select { + case tc.send <- packet: + default: + util.PutBytes(packet) + } + } + } + }() + // While the dial is going on we can't do much else + // continuing this iteration - skip to the next one + continue } // If we have a connection now, try writing to it if isIn && session != nil { diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 683b83ac..a21f8711 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -49,6 +49,7 @@ type TunAdapter struct { mutex sync.RWMutex // Protects the below addrToConn map[address.Address]*tunConn subnetToConn map[address.Subnet]*tunConn + dials map[crypto.NodeID][][]byte // Buffer of packets to send after dialing finishes isOpen bool } @@ -113,6 +114,7 @@ func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, listener tun.dialer = dialer tun.addrToConn = make(map[address.Address]*tunConn) tun.subnetToConn = make(map[address.Subnet]*tunConn) + tun.dials = make(map[crypto.NodeID][][]byte) } // Start the setup process for the TUN/TAP adapter. If successful, starts the diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 7216be91..5c9a4136 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -45,23 +45,18 @@ type Conn struct { mutex sync.RWMutex closed bool session *sessionInfo - readDeadline atomic.Value // time.Time // TODO timer - writeDeadline atomic.Value // time.Time // TODO timer - searching atomic.Value // bool - searchwait chan struct{} // Never reset this, it's only used for the initial search - writebuf [][]byte // Packets to be sent if/when the search finishes + readDeadline atomic.Value // time.Time // TODO timer + writeDeadline atomic.Value // time.Time // TODO timer } // TODO func NewConn() that initializes additional fields as needed func newConn(core *Core, nodeID *crypto.NodeID, nodeMask *crypto.NodeID, session *sessionInfo) *Conn { conn := Conn{ - core: core, - nodeID: nodeID, - nodeMask: nodeMask, - session: session, - searchwait: make(chan struct{}), + core: core, + nodeID: nodeID, + nodeMask: nodeMask, + session: session, } - conn.searching.Store(false) return &conn } @@ -69,91 +64,38 @@ func (c *Conn) String() string { return fmt.Sprintf("conn=%p", c) } -// This method should only be called from the router goroutine -func (c *Conn) startSearch() { - // The searchCompleted callback is given to the search - searchCompleted := func(sinfo *sessionInfo, err error) { - defer c.searching.Store(false) - // If the search failed for some reason, e.g. it hit a dead end or timed - // out, then do nothing - if err != nil { - c.core.log.Debugln(c.String(), "DHT search failed:", err) - return - } - // Take the connection mutex - c.mutex.Lock() - defer c.mutex.Unlock() - // Were we successfully given a sessionInfo pointer? - if sinfo != nil { - // Store it, and update the nodeID and nodeMask (which may have been - // wildcarded before now) with their complete counterparts - c.core.log.Debugln(c.String(), "DHT search completed") - c.session = sinfo - c.nodeID = crypto.GetNodeID(&sinfo.theirPermPub) - for i := range c.nodeMask { - c.nodeMask[i] = 0xFF +// This should only be called from the router goroutine +func (c *Conn) search() error { + sinfo, isIn := c.core.searches.searches[*c.nodeID] + if !isIn { + done := make(chan struct{}, 1) + var sess *sessionInfo + var err error + searchCompleted := func(sinfo *sessionInfo, e error) { + sess = sinfo + err = e + // FIXME close can be called multiple times, do a non-blocking send instead + select { + case done <- struct{}{}: + default: } - // Make sure that any blocks on read/write operations are lifted - defer func() { recover() }() // So duplicate searches don't panic - close(c.searchwait) - } else { - // No session was returned - this shouldn't really happen because we - // should always return an error reason if we don't return a session - panic("DHT search didn't return an error or a sessionInfo") } - if c.closed { - // Things were closed before the search returned - // Go ahead and close it again to make sure the session is cleaned up - go c.Close() - } else { - // Send any messages we may have buffered - var msgs [][]byte - msgs, c.writebuf = c.writebuf, nil - go func() { - for _, msg := range msgs { - c.Write(msg) - util.PutBytes(msg) - } - }() - } - } - // doSearch will be called below in response to one or more conditions - doSearch := func() { - c.searching.Store(true) - // Check to see if there is a search already matching the destination - sinfo, isIn := c.core.searches.searches[*c.nodeID] - if !isIn { - // Nothing was found, so create a new search - sinfo = c.core.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) - c.core.log.Debugf("%s DHT search started: %p", c.String(), sinfo) - } - // Continue the search + sinfo = c.core.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) sinfo.continueSearch() - } - // Take a copy of the session object, in case it changes later - c.mutex.RLock() - sinfo := c.session - c.mutex.RUnlock() - if c.session == nil { - // No session object is present so previous searches, if we ran any, have - // not yielded a useful result (dead end, remote host not found) - doSearch() - } else { - sinfo.worker <- func() { - switch { - case !sinfo.init: - doSearch() - case time.Since(sinfo.time) > 6*time.Second: - if sinfo.time.Before(sinfo.pingTime) && time.Since(sinfo.pingTime) > 6*time.Second { - // TODO double check that the above condition is correct - doSearch() - } else { - c.core.sessions.ping(sinfo) - } - default: // Don't do anything, to keep traffic throttled - } + <-done + c.session = sess + if c.session == nil && err == nil { + panic("search failed but returend no error") } + c.nodeID = crypto.GetNodeID(&c.session.theirPermPub) + for i := range c.nodeMask { + c.nodeMask[i] = 0xFF + } + return err + } else { + return errors.New("search already exists") } + return nil } func getDeadlineTimer(value *atomic.Value) *time.Timer { @@ -167,30 +109,9 @@ func getDeadlineTimer(value *atomic.Value) *time.Timer { func (c *Conn) Read(b []byte) (int, error) { // Take a copy of the session object - c.mutex.RLock() sinfo := c.session - c.mutex.RUnlock() timer := getDeadlineTimer(&c.readDeadline) defer util.TimerStop(timer) - // If there is a search in progress then wait for the result - if sinfo == nil { - // Wait for the search to complete - select { - case <-c.searchwait: - case <-timer.C: - return 0, ConnError{errors.New("timeout"), true, false, 0} - } - // Retrieve our session info again - c.mutex.RLock() - sinfo = c.session - c.mutex.RUnlock() - // If sinfo is still nil at this point then the search failed and the - // searchwait channel has been recreated, so might as well give up and - // return an error code - if sinfo == nil { - return 0, errors.New("search failed") - } - } for { // Wait for some traffic to come through from the session select { @@ -253,32 +174,7 @@ func (c *Conn) Read(b []byte) (int, error) { } func (c *Conn) Write(b []byte) (bytesWritten int, err error) { - c.mutex.RLock() sinfo := c.session - c.mutex.RUnlock() - // If the session doesn't exist, or isn't initialised (which probably means - // that the search didn't complete successfully) then we may need to wait for - // the search to complete or start the search again - if sinfo == nil || !sinfo.init { - // Is a search already taking place? - if searching, sok := c.searching.Load().(bool); !sok || (sok && !searching) { - // No search was already taking place so start a new one - c.core.router.doAdmin(c.startSearch) - } - // Buffer the packet to be sent if/when the search is finished - c.mutex.Lock() - defer c.mutex.Unlock() - c.writebuf = append(c.writebuf, append(util.GetBytes(), b...)) - for len(c.writebuf) > 32 { - util.PutBytes(c.writebuf[0]) - c.writebuf = c.writebuf[1:] - } - return len(b), nil - } else { - // This triggers some session keepalive traffic - // FIXME this desparately needs to be refactored, since the ping case needlessly goes through the router goroutine just to have it pass a function to the session worker when it determines that a session already exists. - c.core.router.doAdmin(c.startSearch) - } var packet []byte done := make(chan struct{}) written := len(b) @@ -301,6 +197,34 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { } packet = p.encode() sinfo.bytesSent += uint64(len(b)) + // The rest of this work is session keep-alive traffic + doSearch := func() { + routerWork := func() { + // Check to see if there is a search already matching the destination + sinfo, isIn := c.core.searches.searches[*c.nodeID] + if !isIn { + // Nothing was found, so create a new search + searchCompleted := func(sinfo *sessionInfo, e error) {} + sinfo = c.core.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) + c.core.log.Debugf("%s DHT search started: %p", c.String(), sinfo) + } + // Continue the search + sinfo.continueSearch() + } + go func() { c.core.router.admin <- routerWork }() + } + switch { + case !sinfo.init: + doSearch() + case time.Since(sinfo.time) > 6*time.Second: + if sinfo.time.Before(sinfo.pingTime) && time.Since(sinfo.pingTime) > 6*time.Second { + // TODO double check that the above condition is correct + doSearch() + } else { + sinfo.core.sessions.ping(sinfo) + } + default: // Don't do anything, to keep traffic throttled + } } // Set up a timer so this doesn't block forever timer := getDeadlineTimer(&c.writeDeadline) @@ -327,7 +251,6 @@ func (c *Conn) Close() error { if c.session != nil { // Close the session, if it hasn't been closed already c.session.close() - c.session = nil } // This can't fail yet - TODO? c.closed = true diff --git a/src/yggdrasil/dialer.go b/src/yggdrasil/dialer.go index 1943c859..1e3e0d6e 100644 --- a/src/yggdrasil/dialer.go +++ b/src/yggdrasil/dialer.go @@ -14,6 +14,8 @@ type Dialer struct { core *Core } +// TODO DialContext that allows timeouts/cancellation, Dial should just call this with no timeout set in the context + // Dial opens a session to the given node. The first paramter should be "nodeid" // and the second parameter should contain a hexadecimal representation of the // target node ID. @@ -58,5 +60,8 @@ func (d *Dialer) Dial(network, address string) (*Conn, error) { // NodeID parameters. func (d *Dialer) DialByNodeIDandMask(nodeID, nodeMask *crypto.NodeID) (*Conn, error) { conn := newConn(d.core, nodeID, nodeMask, nil) + if err := conn.search(); err != nil { + return nil, err + } return conn, nil } diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index 576034bf..b43f0e46 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -141,7 +141,7 @@ func (sinfo *searchInfo) doSearchStep() { if len(sinfo.toVisit) == 0 { // Dead end, do cleanup delete(sinfo.core.searches.searches, sinfo.dest) - go sinfo.callback(nil, errors.New("search reached dead end")) + sinfo.callback(nil, errors.New("search reached dead end")) return } // Send to the next search target @@ -205,7 +205,7 @@ func (sinfo *searchInfo) checkDHTRes(res *dhtRes) bool { sess = sinfo.core.sessions.createSession(&res.Key) if sess == nil { // nil if the DHT search finished but the session wasn't allowed - go sinfo.callback(nil, errors.New("session not allowed")) + sinfo.callback(nil, errors.New("session not allowed")) return true } _, isIn := sinfo.core.sessions.getByTheirPerm(&res.Key) @@ -217,7 +217,7 @@ func (sinfo *searchInfo) checkDHTRes(res *dhtRes) bool { sess.coords = res.Coords sess.packet = sinfo.packet sinfo.core.sessions.ping(sess) - go sinfo.callback(sess, nil) + sinfo.callback(sess, nil) // Cleanup delete(sinfo.core.searches.searches, res.Dest) return true From c808be514ffea600ec8cf3ef02b48dd1612e142a Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 28 Jun 2019 19:11:28 -0500 Subject: [PATCH 13/34] make tunAdapter.wrap return the right thing --- src/tuntap/tun.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index a21f8711..ed5d2d45 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -237,6 +237,7 @@ func (tun *TunAdapter) wrap(conn *yggdrasil.Conn) (c *tunConn, err error) { stop: make(chan struct{}), alive: make(chan struct{}, 1), } + c = &s // Get the remote address and subnet of the other side remoteNodeID := conn.RemoteAddr() s.addr = *address.AddrForNodeID(&remoteNodeID) From e7cb76cea3cad1ac735c712b1b32b2a7b1d34b53 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 28 Jun 2019 19:21:44 -0500 Subject: [PATCH 14/34] clean up unused old session maps --- src/yggdrasil/session.go | 60 +--------------------------------------- 1 file changed, 1 insertion(+), 59 deletions(-) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 22118476..55b0ed43 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -118,12 +118,8 @@ type sessions struct { isAllowedHandler func(pubkey *crypto.BoxPubKey, initiator bool) bool // Returns true or false if session setup is allowed isAllowedMutex sync.RWMutex // Protects the above permShared map[crypto.BoxPubKey]*crypto.BoxSharedKey // Maps known permanent keys to their shared key, used by DHT a lot - sinfos map[crypto.Handle]*sessionInfo // Maps (secret) handle onto session info - conns map[crypto.Handle]*Conn // Maps (secret) handle onto connections - byMySes map[crypto.BoxPubKey]*crypto.Handle // Maps mySesPub onto handle + sinfos map[crypto.Handle]*sessionInfo // Maps handle onto session info byTheirPerm map[crypto.BoxPubKey]*crypto.Handle // Maps theirPermPub onto handle - addrToPerm map[address.Address]*crypto.BoxPubKey - subnetToPerm map[address.Subnet]*crypto.BoxPubKey } // Initializes the session struct. @@ -149,10 +145,7 @@ func (ss *sessions) init(core *Core) { }() ss.permShared = make(map[crypto.BoxPubKey]*crypto.BoxSharedKey) ss.sinfos = make(map[crypto.Handle]*sessionInfo) - ss.byMySes = make(map[crypto.BoxPubKey]*crypto.Handle) ss.byTheirPerm = make(map[crypto.BoxPubKey]*crypto.Handle) - ss.addrToPerm = make(map[address.Address]*crypto.BoxPubKey) - ss.subnetToPerm = make(map[address.Subnet]*crypto.BoxPubKey) ss.lastCleanup = time.Now() } @@ -175,16 +168,6 @@ func (ss *sessions) getSessionForHandle(handle *crypto.Handle) (*sessionInfo, bo return sinfo, isIn } -// Gets a session corresponding to an ephemeral session key used by this node. -func (ss *sessions) getByMySes(key *crypto.BoxPubKey) (*sessionInfo, bool) { - h, isIn := ss.byMySes[*key] - if !isIn { - return nil, false - } - sinfo, isIn := ss.getSessionForHandle(h) - return sinfo, isIn -} - // Gets a session corresponding to a permanent key used by the remote node. func (ss *sessions) getByTheirPerm(key *crypto.BoxPubKey) (*sessionInfo, bool) { h, isIn := ss.byTheirPerm[*key] @@ -195,26 +178,6 @@ func (ss *sessions) getByTheirPerm(key *crypto.BoxPubKey) (*sessionInfo, bool) { return sinfo, isIn } -// Gets a session corresponding to an IPv6 address used by the remote node. -func (ss *sessions) getByTheirAddr(addr *address.Address) (*sessionInfo, bool) { - p, isIn := ss.addrToPerm[*addr] - if !isIn { - return nil, false - } - sinfo, isIn := ss.getByTheirPerm(p) - return sinfo, isIn -} - -// Gets a session corresponding to an IPv6 /64 subnet used by the remote node/network. -func (ss *sessions) getByTheirSubnet(snet *address.Subnet) (*sessionInfo, bool) { - p, isIn := ss.subnetToPerm[*snet] - if !isIn { - return nil, false - } - sinfo, isIn := ss.getByTheirPerm(p) - return sinfo, isIn -} - // Creates a new session and lazily cleans up old existing sessions. This // includse initializing session info to sane defaults (e.g. lowest supported // MTU). @@ -263,10 +226,7 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.worker = make(chan func(), 1) sinfo.recv = make(chan *wire_trafficPacket, 32) ss.sinfos[sinfo.myHandle] = &sinfo - ss.byMySes[sinfo.mySesPub] = &sinfo.myHandle ss.byTheirPerm[sinfo.theirPermPub] = &sinfo.myHandle - ss.addrToPerm[sinfo.theirAddr] = &sinfo.theirPermPub - ss.subnetToPerm[sinfo.theirSubnet] = &sinfo.theirPermPub go sinfo.workerMain() return &sinfo } @@ -291,36 +251,18 @@ func (ss *sessions) cleanup() { sinfos[k] = v } ss.sinfos = sinfos - byMySes := make(map[crypto.BoxPubKey]*crypto.Handle, len(ss.byMySes)) - for k, v := range ss.byMySes { - byMySes[k] = v - } - ss.byMySes = byMySes byTheirPerm := make(map[crypto.BoxPubKey]*crypto.Handle, len(ss.byTheirPerm)) for k, v := range ss.byTheirPerm { byTheirPerm[k] = v } ss.byTheirPerm = byTheirPerm - addrToPerm := make(map[address.Address]*crypto.BoxPubKey, len(ss.addrToPerm)) - for k, v := range ss.addrToPerm { - addrToPerm[k] = v - } - ss.addrToPerm = addrToPerm - subnetToPerm := make(map[address.Subnet]*crypto.BoxPubKey, len(ss.subnetToPerm)) - for k, v := range ss.subnetToPerm { - subnetToPerm[k] = v - } - ss.subnetToPerm = subnetToPerm ss.lastCleanup = time.Now() } // Closes a session, removing it from sessions maps and killing the worker goroutine. func (sinfo *sessionInfo) close() { delete(sinfo.core.sessions.sinfos, sinfo.myHandle) - delete(sinfo.core.sessions.byMySes, sinfo.mySesPub) delete(sinfo.core.sessions.byTheirPerm, sinfo.theirPermPub) - delete(sinfo.core.sessions.addrToPerm, sinfo.theirAddr) - delete(sinfo.core.sessions.subnetToPerm, sinfo.theirSubnet) close(sinfo.worker) } From e88bef35c0be4b60577fb344f2c27fc6b54e4196 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 28 Jun 2019 20:02:58 -0500 Subject: [PATCH 15/34] get rid of old buffered session packets --- src/yggdrasil/conn.go | 2 +- src/yggdrasil/search.go | 2 -- src/yggdrasil/session.go | 14 ++------------ 3 files changed, 3 insertions(+), 15 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 5c9a4136..a4036c78 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -215,7 +215,7 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { } switch { case !sinfo.init: - doSearch() + sinfo.core.sessions.ping(sinfo) case time.Since(sinfo.time) > 6*time.Second: if sinfo.time.Before(sinfo.pingTime) && time.Since(sinfo.pingTime) > 6*time.Second { // TODO double check that the above condition is correct diff --git a/src/yggdrasil/search.go b/src/yggdrasil/search.go index b43f0e46..d8c9049a 100644 --- a/src/yggdrasil/search.go +++ b/src/yggdrasil/search.go @@ -37,7 +37,6 @@ type searchInfo struct { dest crypto.NodeID mask crypto.NodeID time time.Time - packet []byte toVisit []*dhtInfo visited map[crypto.NodeID]bool callback func(*sessionInfo, error) @@ -215,7 +214,6 @@ func (sinfo *searchInfo) checkDHTRes(res *dhtRes) bool { } // FIXME (!) replay attacks could mess with coords? Give it a handle (tstamp)? sess.coords = res.Coords - sess.packet = sinfo.packet sinfo.core.sessions.ping(sess) sinfo.callback(sess, nil) // Cleanup diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 55b0ed43..dc3f01e8 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -39,7 +39,6 @@ type sessionInfo struct { pingTime time.Time // time the first ping was sent since the last received packet pingSend time.Time // time the last ping was sent coords []byte // coords of destination - packet []byte // a buffered packet, sent immediately on ping/pong init bool // Reset if coords change tstamp int64 // ATOMIC - tstamp from their last session ping, replay attack mitigation bytesSent uint64 // Bytes of real traffic sent in this session @@ -325,8 +324,8 @@ func (ss *sessions) sendPingPong(sinfo *sessionInfo, isPong bool) { } packet := p.encode() ss.core.router.out(packet) - if !isPong { - sinfo.pingSend = time.Now() + if sinfo.pingTime.Before(sinfo.time) { + sinfo.pingTime = time.Now() } } @@ -367,15 +366,6 @@ func (ss *sessions) handlePing(ping *sessionPing) { if !ping.IsPong { ss.sendPingPong(sinfo, true) } - if sinfo.packet != nil { - /* FIXME this needs to live in the net.Conn or something, needs work in Write - // send - var bs []byte - bs, sinfo.packet = sinfo.packet, nil - ss.core.router.sendPacket(bs) // FIXME this needs to live in the net.Conn or something, needs work in Write - */ - sinfo.packet = nil - } }) } From 784acba82398e411de2419231362e439f6f471e8 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 29 Jun 2019 12:14:44 -0500 Subject: [PATCH 16/34] I think this fixes the concurrent map read/write panic --- src/yggdrasil/conn.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index a4036c78..0357ccad 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -64,9 +64,11 @@ func (c *Conn) String() string { return fmt.Sprintf("conn=%p", c) } -// This should only be called from the router goroutine +// This should never be called from the router goroutine func (c *Conn) search() error { - sinfo, isIn := c.core.searches.searches[*c.nodeID] + var sinfo *searchInfo + var isIn bool + c.core.router.doAdmin(func() { sinfo, isIn = c.core.searches.searches[*c.nodeID] }) if !isIn { done := make(chan struct{}, 1) var sess *sessionInfo @@ -80,8 +82,10 @@ func (c *Conn) search() error { default: } } - sinfo = c.core.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) - sinfo.continueSearch() + c.core.router.doAdmin(func() { + sinfo = c.core.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) + sinfo.continueSearch() + }) <-done c.session = sess if c.session == nil && err == nil { From ca1f2bb0a27ec604067beb0d6361833194833e86 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 29 Jun 2019 12:33:00 -0500 Subject: [PATCH 17/34] add go-syslog to go.mod/go.sum --- go.mod | 1 + go.sum | 2 ++ 2 files changed, 3 insertions(+) diff --git a/go.mod b/go.mod index 995e54c2..eec583ae 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/yggdrasil-network/yggdrasil-go require ( github.com/docker/libcontainer v2.2.1+incompatible github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 + github.com/hashicorp/go-syslog v1.0.0 github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222 github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 github.com/mitchellh/mapstructure v1.1.2 diff --git a/go.sum b/go.sum index 92dfe88c..4be88b29 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/docker/libcontainer v2.2.1+incompatible h1:++SbbkCw+X8vAd4j2gOCzZ2Nn7 github.com/docker/libcontainer v2.2.1+incompatible/go.mod h1:osvj61pYsqhNCMLGX31xr7klUBhHb/ZBuXS0o1Fvwbw= github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 h1:WD8iJ37bRNwvETMfVTusVSAi0WdXTpfNVGY2aHycNKY= github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U= +github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222 h1:xmvkbxXDeN1ffWq8kvrhyqVYAO2aXuRBsbpxVTR+JyU= github.com/hjson/hjson-go v0.0.0-20181010104306-a25ecf6bd222/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio= github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 h1:YnZmFjg0Nvk8851WTVWlqMC1ecJH07Ctz+Ezxx4u54g= From 818eca90dbbcc973852dfd4274fa6cf3a71db00a Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 29 Jun 2019 16:10:02 -0500 Subject: [PATCH 18/34] fix nil pointer deref if searches fail, block dial until a search exceeds or a timeout passes (todo: replace timer with context) --- src/yggdrasil/conn.go | 14 ++++++++------ src/yggdrasil/dialer.go | 12 +++++++++++- src/yggdrasil/router.go | 2 +- src/yggdrasil/session.go | 17 +++++++++++++---- 4 files changed, 33 insertions(+), 12 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 0357ccad..53d2551e 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -89,11 +89,13 @@ func (c *Conn) search() error { <-done c.session = sess if c.session == nil && err == nil { - panic("search failed but returend no error") + panic("search failed but returned no error") } - c.nodeID = crypto.GetNodeID(&c.session.theirPermPub) - for i := range c.nodeMask { - c.nodeMask[i] = 0xFF + if c.session != nil { + c.nodeID = crypto.GetNodeID(&c.session.theirPermPub) + for i := range c.nodeMask { + c.nodeMask[i] = 0xFF + } } return err } else { @@ -218,8 +220,6 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { go func() { c.core.router.admin <- routerWork }() } switch { - case !sinfo.init: - sinfo.core.sessions.ping(sinfo) case time.Since(sinfo.time) > 6*time.Second: if sinfo.time.Before(sinfo.pingTime) && time.Since(sinfo.pingTime) > 6*time.Second { // TODO double check that the above condition is correct @@ -227,6 +227,8 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { } else { sinfo.core.sessions.ping(sinfo) } + case sinfo.reset && sinfo.pingTime.Before(sinfo.time): + sinfo.core.sessions.ping(sinfo) default: // Don't do anything, to keep traffic throttled } } diff --git a/src/yggdrasil/dialer.go b/src/yggdrasil/dialer.go index 1e3e0d6e..6b24cfb4 100644 --- a/src/yggdrasil/dialer.go +++ b/src/yggdrasil/dialer.go @@ -5,6 +5,7 @@ import ( "errors" "strconv" "strings" + "time" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" ) @@ -61,7 +62,16 @@ func (d *Dialer) Dial(network, address string) (*Conn, error) { func (d *Dialer) DialByNodeIDandMask(nodeID, nodeMask *crypto.NodeID) (*Conn, error) { conn := newConn(d.core, nodeID, nodeMask, nil) if err := conn.search(); err != nil { + conn.Close() return nil, err } - return conn, nil + t := time.NewTimer(6 * time.Second) // TODO use a context instead + defer t.Stop() + select { + case <-conn.session.init: + return conn, nil + case <-t.C: + conn.Close() + return nil, errors.New("session handshake timeout") + } } diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 2e32fb6b..514d14fa 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -119,7 +119,7 @@ func (r *router) mainLoop() { case info := <-r.core.dht.peers: r.core.dht.insertPeer(info) case <-r.reset: - r.core.sessions.resetInits() + r.core.sessions.reset() r.core.dht.reset() case <-ticker.C: { diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index dc3f01e8..98a12c7b 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -39,12 +39,13 @@ type sessionInfo struct { pingTime time.Time // time the first ping was sent since the last received packet pingSend time.Time // time the last ping was sent coords []byte // coords of destination - init bool // Reset if coords change + reset bool // reset if coords change tstamp int64 // ATOMIC - tstamp from their last session ping, replay attack mitigation bytesSent uint64 // Bytes of real traffic sent in this session bytesRecvd uint64 // Bytes of real traffic received in this session worker chan func() // Channel to send work to the session worker recv chan *wire_trafficPacket // Received packets go here, picked up by the associated Conn + init chan struct{} // Closed when the first session pong arrives, used to signal that the session is ready for initial use } func (sinfo *sessionInfo) doWorker(f func()) { @@ -101,7 +102,14 @@ func (s *sessionInfo) update(p *sessionPing) bool { } s.time = time.Now() s.tstamp = p.Tstamp - s.init = true + s.reset = false + defer func() { recover() }() // Recover if the below panics + select { + case <-s.init: + default: + // Unblock anything waiting for the session to initialize + close(s.init) + } return true } @@ -203,6 +211,7 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.mtuTime = now sinfo.pingTime = now sinfo.pingSend = now + sinfo.init = make(chan struct{}) higher := false for idx := range ss.core.boxPub { if ss.core.boxPub[idx] > sinfo.theirPermPub[idx] { @@ -410,10 +419,10 @@ func (sinfo *sessionInfo) updateNonce(theirNonce *crypto.BoxNonce) { // Resets all sessions to an uninitialized state. // Called after coord changes, so attemtps to use a session will trigger a new ping and notify the remote end of the coord change. -func (ss *sessions) resetInits() { +func (ss *sessions) reset() { for _, sinfo := range ss.sinfos { sinfo.doWorker(func() { - sinfo.init = false + sinfo.reset = true }) } } From 7d58a7ef3e8be7a0df386db7e79ed9c0de91ba62 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 29 Jun 2019 17:44:28 -0500 Subject: [PATCH 19/34] fix channel multiple close bug and concurrency bug in the way sessionInfo.close was being called --- src/yggdrasil/conn.go | 2 +- src/yggdrasil/session.go | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 53d2551e..38e4df9f 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -256,7 +256,7 @@ func (c *Conn) Close() error { defer c.mutex.Unlock() if c.session != nil { // Close the session, if it hasn't been closed already - c.session.close() + c.core.router.doAdmin(c.session.close) } // This can't fail yet - TODO? c.closed = true diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 98a12c7b..4cc059e6 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -269,8 +269,11 @@ func (ss *sessions) cleanup() { // Closes a session, removing it from sessions maps and killing the worker goroutine. func (sinfo *sessionInfo) close() { - delete(sinfo.core.sessions.sinfos, sinfo.myHandle) - delete(sinfo.core.sessions.byTheirPerm, sinfo.theirPermPub) + if s := sinfo.core.sessions.sinfos[sinfo.myHandle]; s == sinfo { + delete(sinfo.core.sessions.sinfos, sinfo.myHandle) + delete(sinfo.core.sessions.byTheirPerm, sinfo.theirPermPub) + } + defer func() { recover() }() close(sinfo.worker) } From 28db566b37b14224ffbf1505b89d8eab2a0d0f32 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 29 Jun 2019 18:44:24 -0500 Subject: [PATCH 20/34] fix concurrency bug in iface.go --- src/tuntap/iface.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index f6cfec9c..16a3b65d 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -225,11 +225,11 @@ func (tun *TunAdapter) reader() error { panic("Given empty dstNodeID and dstNodeIDMask - this shouldn't happen") } // Dial to the remote node + packet := append(util.GetBytes(), bs[:n]...) go func() { // FIXME just spitting out a goroutine to do this is kind of ugly and means we drop packets until the dial finishes tun.mutex.Lock() _, known := tun.dials[*dstNodeID] - packet := append(util.GetBytes(), bs[:n]...) tun.dials[*dstNodeID] = append(tun.dials[*dstNodeID], packet) for len(tun.dials[*dstNodeID]) > 32 { util.PutBytes(tun.dials[*dstNodeID][0]) From d39428735df66a39acef3da98c2ca3eac5237a15 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 29 Jun 2019 18:50:21 -0500 Subject: [PATCH 21/34] recover if we try to send to a closed session worker due to a race between a Conn.Write call and a Conn.Close call --- src/yggdrasil/conn.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 38e4df9f..b4f68e18 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -236,6 +236,11 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { timer := getDeadlineTimer(&c.writeDeadline) defer util.TimerStop(timer) // Hand over to the session worker + defer func() { + if recover() != nil { + err = errors.New("write failed") + } + }() // In case we're racing with a close select { // Send to worker case sinfo.worker <- workerFunc: case <-timer.C: From 40553a6a44e88c3df8e164f7b2386d92c3bc93cc Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 29 Jun 2019 18:56:26 -0500 Subject: [PATCH 22/34] make GetSessions use the session workers to avoid races --- src/yggdrasil/api.go | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 864e7f37..7270bc2e 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -211,16 +211,31 @@ func (c *Core) GetSessions() []Session { var sessions []Session getSessions := func() { for _, sinfo := range c.sessions.sinfos { - // TODO? skipped known but timed out sessions? - session := Session{ - Coords: append([]byte{}, sinfo.coords...), - MTU: sinfo.getMTU(), - BytesSent: sinfo.bytesSent, - BytesRecvd: sinfo.bytesRecvd, - Uptime: time.Now().Sub(sinfo.timeOpened), - WasMTUFixed: sinfo.wasMTUFixed, + var session Session + workerFunc := func() { + session := Session{ + Coords: append([]byte{}, sinfo.coords...), + MTU: sinfo.getMTU(), + BytesSent: sinfo.bytesSent, + BytesRecvd: sinfo.bytesRecvd, + Uptime: time.Now().Sub(sinfo.timeOpened), + WasMTUFixed: sinfo.wasMTUFixed, + } + copy(session.PublicKey[:], sinfo.theirPermPub[:]) } - copy(session.PublicKey[:], sinfo.theirPermPub[:]) + var skip bool + func() { + defer func() { + if recover() != nil { + skip = true + } + }() + sinfo.doWorker(workerFunc) + }() + if skip { + continue + } + // TODO? skipped known but timed out sessions? sessions = append(sessions, session) } } From fbe44ea97377fca5f8d5af7495b257e1abd954fa Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 29 Jun 2019 19:25:34 -0500 Subject: [PATCH 23/34] fix bug in session api code --- src/yggdrasil/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 7270bc2e..1bec9836 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -213,7 +213,7 @@ func (c *Core) GetSessions() []Session { for _, sinfo := range c.sessions.sinfos { var session Session workerFunc := func() { - session := Session{ + session = Session{ Coords: append([]byte{}, sinfo.coords...), MTU: sinfo.getMTU(), BytesSent: sinfo.bytesSent, From cd29fde178e554c190585d19fa3d12e18980601e Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 29 Jun 2019 19:32:15 -0500 Subject: [PATCH 24/34] temporary workaround to concurrency bug in sessions.getSharedKey --- src/yggdrasil/session.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 4cc059e6..53836c38 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -298,6 +298,8 @@ func (ss *sessions) getPing(sinfo *sessionInfo) sessionPing { // This comes up with dht req/res and session ping/pong traffic. func (ss *sessions) getSharedKey(myPriv *crypto.BoxPrivKey, theirPub *crypto.BoxPubKey) *crypto.BoxSharedKey { + return crypto.GetSharedKey(myPriv, theirPub) + // FIXME concurrency issues with the below, so for now we just burn the CPU every time if skey, isIn := ss.permShared[*theirPub]; isIn { return skey } From 86c30a1fc4f918463e70571befbc07bf6b10622b Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 1 Jul 2019 18:55:07 -0500 Subject: [PATCH 25/34] fix another panic from a send on a closed session worker channel, from races between Conn.Read/Write/Close --- src/yggdrasil/conn.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index b4f68e18..5d1e77ac 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -159,6 +159,12 @@ func (c *Conn) Read(b []byte) (int, error) { sinfo.bytesRecvd += uint64(len(b)) } // Hand over to the session worker + defer func() { + if recover() != nil { + err = errors.New("read failed, session already closed") + close(done) + } + }() // In case we're racing with a close select { // Send to worker case sinfo.worker <- workerFunc: case <-timer.C: @@ -238,7 +244,8 @@ func (c *Conn) Write(b []byte) (bytesWritten int, err error) { // Hand over to the session worker defer func() { if recover() != nil { - err = errors.New("write failed") + err = errors.New("write failed, session already closed") + close(done) } }() // In case we're racing with a close select { // Send to worker From 12486b055734f92c1622af9128f28f8376f75d02 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 6 Jul 2019 11:52:30 +0100 Subject: [PATCH 26/34] Try to more gracefully handle shutdowns on Windows --- cmd/yggdrasil/main.go | 16 +++++++++------- src/multicast/multicast.go | 7 +++++++ src/tuntap/iface.go | 6 ++++++ src/tuntap/tun.go | 10 ++++++++++ 4 files changed, 32 insertions(+), 7 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 6af27725..fd8828c8 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -231,11 +231,6 @@ func main() { } else { logger.Errorln("Unable to get Listener:", err) } - // The Stop function ensures that the TUN/TAP adapter is correctly shut down - // before the program exits. - defer func() { - n.core.Stop() - }() // Make some nice output that tells us what our IPv6 address and subnet are. // This is just logged to stdout for the user. address := n.core.Address() @@ -256,6 +251,8 @@ func main() { // deferred Stop function above will run which will shut down TUN/TAP. for { select { + case _ = <-c: + goto exit case _ = <-r: if *useconffile != "" { cfg = readConfig(useconf, useconffile, normaliseconf) @@ -265,11 +262,16 @@ func main() { } else { logger.Errorln("Reloading config at runtime is only possible with -useconffile") } - case _ = <-c: - goto exit } } exit: + // When gracefully shutting down we should try and clean up as much as + // possible, although not all of these functions are necessarily implemented + // yet + n.core.Stop() + n.admin.Stop() + n.multicast.Stop() + n.tuntap.Stop() } func (n *node) sessionFirewall(pubkey *crypto.BoxPubKey, initiator bool) bool { diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 3c0d8c0f..ba1f18fb 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -26,6 +26,7 @@ type Multicast struct { groupAddr string listeners map[string]*yggdrasil.TcpListener listenPort uint16 + isOpen bool } // Init prepares the multicast interface for use. @@ -61,6 +62,7 @@ func (m *Multicast) Start() error { // Windows can't set this flag, so we need to handle it in other ways } + m.isOpen = true go m.multicastStarted() go m.listen() go m.announce() @@ -70,6 +72,8 @@ func (m *Multicast) Start() error { // Stop is not implemented for multicast yet. func (m *Multicast) Stop() error { + m.isOpen = false + m.sock.Close() return nil } @@ -246,6 +250,9 @@ func (m *Multicast) listen() { for { nBytes, rcm, fromAddr, err := m.sock.ReadFrom(bs) if err != nil { + if !m.isOpen { + return + } panic(err) } if rcm != nil { diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 16a3b65d..60c814c2 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -98,6 +98,9 @@ func (tun *TunAdapter) writer() error { util.PutBytes(b) } if err != nil { + if !tun.isOpen { + return err + } tun.log.Errorln("TUN/TAP iface write error:", err) continue } @@ -114,6 +117,9 @@ func (tun *TunAdapter) reader() error { // Wait for a packet to be delivered to us through the TUN/TAP adapter n, err := tun.iface.Read(bs) if err != nil { + if !tun.isOpen { + return err + } panic(err) } if n == 0 { diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index ed5d2d45..b7b4cfa6 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -181,6 +181,16 @@ func (tun *TunAdapter) Start() error { return nil } +// Start the setup process for the TUN/TAP adapter. If successful, starts the +// read/write goroutines to handle packets on that interface. +func (tun *TunAdapter) Stop() error { + tun.isOpen = false + // TODO: we have nothing that cleanly stops all the various goroutines opened + // by TUN/TAP, e.g. readers/writers, sessions + tun.iface.Close() + return nil +} + // UpdateConfig updates the TUN/TAP module with the provided config.NodeConfig // and then signals the various module goroutines to reconfigure themselves if // needed. From 02c99d3e7d2ae9d66557533e365ecc5f79113ebe Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 6 Jul 2019 12:04:31 +0100 Subject: [PATCH 27/34] More directly define a minwinsvc exit handler --- cmd/yggdrasil/main.go | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index fd8828c8..79446849 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -172,7 +172,7 @@ func main() { logger = log.New(syslogger, "", log.Flags()) } default: - if logfd, err := os.Create(*logto); err == nil { + if logfd, err := os.OpenFile(*logto, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err == nil { logger = log.New(logfd, "", log.Flags()) } } @@ -242,11 +242,16 @@ func main() { r := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, syscall.SIGTERM) signal.Notify(r, os.Interrupt, syscall.SIGHUP) - // Create a function to capture the service being stopped on Windows. - winTerminate := func() { - c <- os.Interrupt + // Define what happens when we want to stop Yggdrasil. + terminate := func() { + n.core.Stop() + n.admin.Stop() + n.multicast.Stop() + n.tuntap.Stop() + os.Exit(0) } - minwinsvc.SetOnExit(winTerminate) + // Capture the service being stopped on Windows. + minwinsvc.SetOnExit(terminate) // Wait for the terminate/interrupt signal. Once a signal is received, the // deferred Stop function above will run which will shut down TUN/TAP. for { @@ -265,13 +270,7 @@ func main() { } } exit: - // When gracefully shutting down we should try and clean up as much as - // possible, although not all of these functions are necessarily implemented - // yet - n.core.Stop() - n.admin.Stop() - n.multicast.Stop() - n.tuntap.Stop() + terminate() } func (n *node) sessionFirewall(pubkey *crypto.BoxPubKey, initiator bool) bool { From 618d46a7b30bb7e491591ebeb3f15ad40e41021d Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 6 Jul 2019 12:12:30 +0100 Subject: [PATCH 28/34] Don't block on adding peers in case one is unreachable and we are forced to wait for timeout --- src/yggdrasil/core.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 3a7f9f1b..62d89a8b 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -88,14 +88,14 @@ func (c *Core) addPeerLoop() { // Add peers from the Peers section for _, peer := range current.Peers { - c.AddPeer(peer, "") + go c.AddPeer(peer, "") time.Sleep(time.Second) } // Add peers from the InterfacePeers section for intf, intfpeers := range current.InterfacePeers { for _, peer := range intfpeers { - c.AddPeer(peer, intf) + go c.AddPeer(peer, intf) time.Sleep(time.Second) } } From 4804ce39afb250e2a5890182007e3b66e2a620a3 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 6 Jul 2019 12:17:40 +0100 Subject: [PATCH 29/34] Tidy up the terminate path a bit --- cmd/yggdrasil/main.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 79446849..129b01d5 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -242,16 +242,9 @@ func main() { r := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, syscall.SIGTERM) signal.Notify(r, os.Interrupt, syscall.SIGHUP) - // Define what happens when we want to stop Yggdrasil. - terminate := func() { - n.core.Stop() - n.admin.Stop() - n.multicast.Stop() - n.tuntap.Stop() - os.Exit(0) - } // Capture the service being stopped on Windows. - minwinsvc.SetOnExit(terminate) + minwinsvc.SetOnExit(n.shutdown) + defer n.shutdown() // Wait for the terminate/interrupt signal. Once a signal is received, the // deferred Stop function above will run which will shut down TUN/TAP. for { @@ -270,7 +263,14 @@ func main() { } } exit: - terminate() +} + +func (n *node) shutdown() { + n.core.Stop() + n.admin.Stop() + n.multicast.Stop() + n.tuntap.Stop() + os.Exit(0) } func (n *node) sessionFirewall(pubkey *crypto.BoxPubKey, initiator bool) bool { From e8272926a422ce4deaae20f8fdf4ff8b4154740c Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 6 Jul 2019 15:08:17 +0100 Subject: [PATCH 30/34] Fix TAP mode --- src/tuntap/ckr.go | 3 ++ src/tuntap/icmpv6.go | 94 +++++++++++++++++++++++++++++++++----------- src/tuntap/iface.go | 25 ++++++------ src/tuntap/tun.go | 19 ++------- 4 files changed, 88 insertions(+), 53 deletions(-) diff --git a/src/tuntap/ckr.go b/src/tuntap/ckr.go index c9233e64..00320391 100644 --- a/src/tuntap/ckr.go +++ b/src/tuntap/ckr.go @@ -48,8 +48,11 @@ func (c *cryptokey) init(tun *TunAdapter) { } }() + c.tun.log.Debugln("Configuring CKR...") if err := c.configure(); err != nil { c.tun.log.Errorln("CKR configuration failed:", err) + } else { + c.tun.log.Debugln("CKR configured") } } diff --git a/src/tuntap/icmpv6.go b/src/tuntap/icmpv6.go index 8159e0f9..ea1a785b 100644 --- a/src/tuntap/icmpv6.go +++ b/src/tuntap/icmpv6.go @@ -13,6 +13,7 @@ import ( "encoding/binary" "errors" "net" + "sync" "time" "golang.org/x/net/icmp" @@ -21,19 +22,18 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/address" ) -type macAddress [6]byte - const len_ETHER = 14 type ICMPv6 struct { - tun *TunAdapter - mylladdr net.IP - mymac macAddress - peermacs map[address.Address]neighbor + tun *TunAdapter + mylladdr net.IP + mymac net.HardwareAddr + peermacs map[address.Address]neighbor + peermacsmutex sync.RWMutex } type neighbor struct { - mac macAddress + mac net.HardwareAddr learned bool lastadvertisement time.Time lastsolicitation time.Time @@ -61,10 +61,12 @@ func ipv6Header_Marshal(h *ipv6.Header) ([]byte, error) { // addresses. func (i *ICMPv6) Init(t *TunAdapter) { i.tun = t + i.peermacsmutex.Lock() i.peermacs = make(map[address.Address]neighbor) + i.peermacsmutex.Unlock() // Our MAC address and link-local address - i.mymac = macAddress{ + i.mymac = net.HardwareAddr{ 0x02, 0x00, 0x00, 0x00, 0x00, 0x02} i.mylladdr = net.IP{ 0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -181,16 +183,30 @@ func (i *ICMPv6) UnmarshalPacket(datain []byte, datamac *[]byte) ([]byte, error) if datamac != nil { var addr address.Address var target address.Address - var mac macAddress + mac := net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00} copy(addr[:], ipv6Header.Src[:]) copy(target[:], datain[48:64]) copy(mac[:], (*datamac)[:]) - // fmt.Printf("Learning peer MAC %x for %x\n", mac, target) + i.peermacsmutex.Lock() neighbor := i.peermacs[target] neighbor.mac = mac neighbor.learned = true neighbor.lastadvertisement = time.Now() i.peermacs[target] = neighbor + i.peermacsmutex.Unlock() + i.tun.log.Debugln("Learned peer MAC", mac.String(), "for", net.IP(target[:]).String()) + /* + i.tun.log.Debugln("Peer MAC table:") + i.peermacsmutex.RLock() + for t, n := range i.peermacs { + if n.learned { + i.tun.log.Debugln("- Target", net.IP(t[:]).String(), "has MAC", n.mac.String()) + } else { + i.tun.log.Debugln("- Target", net.IP(t[:]).String(), "is not learned yet") + } + } + i.peermacsmutex.RUnlock() + */ } return nil, errors.New("No response needed") } @@ -201,7 +217,7 @@ func (i *ICMPv6) UnmarshalPacket(datain []byte, datamac *[]byte) ([]byte, error) // Creates an ICMPv6 packet based on the given icmp.MessageBody and other // parameters, complete with ethernet and IP headers, which can be written // directly to a TAP adapter. -func (i *ICMPv6) CreateICMPv6L2(dstmac macAddress, dst net.IP, src net.IP, mtype ipv6.ICMPType, mcode int, mbody icmp.MessageBody) ([]byte, error) { +func (i *ICMPv6) CreateICMPv6L2(dstmac net.HardwareAddr, dst net.IP, src net.IP, mtype ipv6.ICMPType, mcode int, mbody icmp.MessageBody) ([]byte, error) { // Pass through to CreateICMPv6 ipv6packet, err := CreateICMPv6(dst, src, mtype, mcode, mbody) if err != nil { @@ -264,13 +280,46 @@ func CreateICMPv6(dst net.IP, src net.IP, mtype ipv6.ICMPType, mcode int, mbody return responsePacket, nil } -func (i *ICMPv6) CreateNDPL2(dst address.Address) ([]byte, error) { +func (i *ICMPv6) Solicit(addr address.Address) { + retries := 5 + for retries > 0 { + retries-- + i.peermacsmutex.RLock() + if n, ok := i.peermacs[addr]; ok && n.learned { + i.tun.log.Debugln("MAC learned for", net.IP(addr[:]).String()) + i.peermacsmutex.RUnlock() + return + } + i.peermacsmutex.RUnlock() + i.tun.log.Debugln("Sending neighbor solicitation for", net.IP(addr[:]).String()) + i.peermacsmutex.Lock() + if n, ok := i.peermacs[addr]; !ok { + i.peermacs[addr] = neighbor{ + lastsolicitation: time.Now(), + } + } else { + n.lastsolicitation = time.Now() + } + i.peermacsmutex.Unlock() + request, err := i.createNDPL2(addr) + if err != nil { + panic(err) + } + if _, err := i.tun.iface.Write(request); err != nil { + panic(err) + } + i.tun.log.Debugln("Sent neighbor solicitation for", net.IP(addr[:]).String()) + time.Sleep(time.Second) + } +} + +func (i *ICMPv6) createNDPL2(dst address.Address) ([]byte, error) { // Create the ND payload var payload [28]byte - copy(payload[:4], []byte{0x00, 0x00, 0x00, 0x00}) - copy(payload[4:20], dst[:]) - copy(payload[20:22], []byte{0x01, 0x01}) - copy(payload[22:28], i.mymac[:6]) + copy(payload[:4], []byte{0x00, 0x00, 0x00, 0x00}) // Flags + copy(payload[4:20], dst[:]) // Destination + copy(payload[20:22], []byte{0x01, 0x01}) // Type & length + copy(payload[22:28], i.mymac[:6]) // Link layer address // Create the ICMPv6 solicited-node address var dstaddr address.Address @@ -281,7 +330,7 @@ func (i *ICMPv6) CreateNDPL2(dst address.Address) ([]byte, error) { copy(dstaddr[13:], dst[13:16]) // Create the multicast MAC - var dstmac macAddress + dstmac := net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00} copy(dstmac[:2], []byte{0x33, 0x33}) copy(dstmac[2:6], dstaddr[12:16]) @@ -293,9 +342,6 @@ func (i *ICMPv6) CreateNDPL2(dst address.Address) ([]byte, error) { if err != nil { return nil, err } - neighbor := i.peermacs[dstaddr] - neighbor.lastsolicitation = time.Now() - i.peermacs[dstaddr] = neighbor return requestPacket, nil } @@ -319,10 +365,10 @@ func (i *ICMPv6) HandleNDP(in []byte) ([]byte, error) { // Create our NDP message body response body := make([]byte, 28) - binary.BigEndian.PutUint32(body[:4], uint32(0x20000000)) - copy(body[4:20], in[8:24]) // Target address - body[20] = uint8(2) - body[21] = uint8(1) + binary.BigEndian.PutUint32(body[:4], uint32(0x40000000)) // Flags + copy(body[4:20], in[8:24]) // Target address + body[20] = uint8(2) // Type: Target link-layer address + body[21] = uint8(1) // Length: 1x address (8 bytes) copy(body[22:28], i.mymac[:6]) // Send it back diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 60c814c2..be3988a6 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -3,6 +3,7 @@ package tuntap import ( "bytes" "errors" + "net" "time" "github.com/songgao/packets/ethernet" @@ -43,19 +44,10 @@ func (tun *TunAdapter) writer() error { neigh, known := tun.icmpv6.peermacs[dstAddr] known = known && (time.Since(neigh.lastsolicitation).Seconds() < 30) if !known { - request, err := tun.icmpv6.CreateNDPL2(dstAddr) - if err != nil { - panic(err) - } - if _, err := tun.iface.Write(request); err != nil { - panic(err) - } - tun.icmpv6.peermacs[dstAddr] = neighbor{ - lastsolicitation: time.Now(), - } + tun.icmpv6.Solicit(dstAddr) } } - var peermac macAddress + peermac := net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00} var peerknown bool if b[0]&0xf0 == 0x40 { dstAddr = tun.addr @@ -65,14 +57,19 @@ func (tun *TunAdapter) writer() error { } } if neighbor, ok := tun.icmpv6.peermacs[dstAddr]; ok && neighbor.learned { + // If we've learned the MAC of a 300::/7 address, for example, or a CKR + // address, use the MAC address of that peermac = neighbor.mac peerknown = true } else if neighbor, ok := tun.icmpv6.peermacs[tun.addr]; ok && neighbor.learned { + // Otherwise send directly to the MAC address of the host if that's + // known instead peermac = neighbor.mac peerknown = true - sendndp(dstAddr) } else { + // Nothing has been discovered, try to discover the destination sendndp(tun.addr) + } if peerknown { var proto ethernet.Ethertype @@ -92,6 +89,8 @@ func (tun *TunAdapter) writer() error { copy(frame[tun_ETHER_HEADER_LENGTH:], b[:n]) n += tun_ETHER_HEADER_LENGTH w, err = tun.iface.Write(frame[:n]) + } else { + tun.log.Errorln("TUN/TAP iface write error: no peer MAC known for", net.IP(dstAddr[:]).String(), "- dropping packet") } } else { w, err = tun.iface.Write(b[:n]) @@ -184,7 +183,7 @@ func (tun *TunAdapter) reader() error { // Unknown address length or protocol, so drop the packet and ignore it continue } - if !tun.ckr.isValidSource(srcAddr, addrlen) { + if tun.ckr.isEnabled() && !tun.ckr.isValidSource(srcAddr, addrlen) { // The packet had a source address that doesn't belong to us or our // configured crypto-key routing source subnets continue diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index b7b4cfa6..15530d23 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -14,7 +14,6 @@ import ( "fmt" "net" "sync" - "time" "github.com/gologme/log" "github.com/yggdrasil-network/water" @@ -152,21 +151,6 @@ func (tun *TunAdapter) Start() error { tun.send = make(chan []byte, 32) // TODO: is this a sensible value? tun.reconfigure = make(chan chan error) tun.mutex.Unlock() - if iftapmode { - go func() { - for { - if _, ok := tun.icmpv6.peermacs[tun.addr]; ok { - break - } - request, err := tun.icmpv6.CreateNDPL2(tun.addr) - if err != nil { - panic(err) - } - tun.send <- request - time.Sleep(time.Second) - } - }() - } go func() { for { e := <-tun.reconfigure @@ -177,6 +161,9 @@ func (tun *TunAdapter) Start() error { go tun.reader() go tun.writer() tun.icmpv6.Init(tun) + if iftapmode { + go tun.icmpv6.Solicit(tun.addr) + } tun.ckr.init(tun) return nil } From a10c141896d1fa292e0c74f7e6fd4df3a0eb7e93 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 6 Jul 2019 15:15:43 +0100 Subject: [PATCH 31/34] Fix data race on peermacs --- src/tuntap/icmpv6.go | 8 ++++++++ src/tuntap/iface.go | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/tuntap/icmpv6.go b/src/tuntap/icmpv6.go index ea1a785b..fe80dfbd 100644 --- a/src/tuntap/icmpv6.go +++ b/src/tuntap/icmpv6.go @@ -313,6 +313,14 @@ func (i *ICMPv6) Solicit(addr address.Address) { } } +func (i *ICMPv6) getNeighbor(addr address.Address) (neighbor, bool) { + i.peermacsmutex.RLock() + defer i.peermacsmutex.RUnlock() + + n, ok := i.peermacs[addr] + return n, ok +} + func (i *ICMPv6) createNDPL2(dst address.Address) ([]byte, error) { // Create the ND payload var payload [28]byte diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index be3988a6..9ffde85b 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -41,7 +41,7 @@ func (tun *TunAdapter) writer() error { return errors.New("Invalid address family") } sendndp := func(dstAddr address.Address) { - neigh, known := tun.icmpv6.peermacs[dstAddr] + neigh, known := tun.icmpv6.getNeighbor(dstAddr) known = known && (time.Since(neigh.lastsolicitation).Seconds() < 30) if !known { tun.icmpv6.Solicit(dstAddr) @@ -56,12 +56,12 @@ func (tun *TunAdapter) writer() error { dstAddr = tun.addr } } - if neighbor, ok := tun.icmpv6.peermacs[dstAddr]; ok && neighbor.learned { + if neighbor, ok := tun.icmpv6.getNeighbor(dstAddr); ok && neighbor.learned { // If we've learned the MAC of a 300::/7 address, for example, or a CKR // address, use the MAC address of that peermac = neighbor.mac peerknown = true - } else if neighbor, ok := tun.icmpv6.peermacs[tun.addr]; ok && neighbor.learned { + } else if neighbor, ok := tun.icmpv6.getNeighbor(tun.addr); ok && neighbor.learned { // Otherwise send directly to the MAC address of the host if that's // known instead peermac = neighbor.mac From 30c03369cdd34c9064df3050cc0e68e6bf6f3892 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 6 Jul 2019 20:08:32 +0100 Subject: [PATCH 32/34] Try to fix CKR setup deadlock, fix some Windows output formatting --- src/tuntap/ckr.go | 13 ++++++------- src/tuntap/tun.go | 11 +++++------ src/tuntap/tun_windows.go | 16 ++++++++-------- 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/src/tuntap/ckr.go b/src/tuntap/ckr.go index 00320391..52c11596 100644 --- a/src/tuntap/ckr.go +++ b/src/tuntap/ckr.go @@ -59,11 +59,10 @@ func (c *cryptokey) init(tun *TunAdapter) { // Configure the CKR routes - this must only ever be called from the router // goroutine, e.g. through router.doAdmin func (c *cryptokey) configure() error { - c.tun.config.Mutex.RLock() - defer c.tun.config.Mutex.RUnlock() + current, _ := c.tun.config.Get() // Set enabled/disabled state - c.setEnabled(c.tun.config.Current.TunnelRouting.Enable) + c.setEnabled(current.TunnelRouting.Enable) // Clear out existing routes c.mutexroutes.Lock() @@ -72,14 +71,14 @@ func (c *cryptokey) configure() error { c.mutexroutes.Unlock() // Add IPv6 routes - for ipv6, pubkey := range c.tun.config.Current.TunnelRouting.IPv6Destinations { + for ipv6, pubkey := range current.TunnelRouting.IPv6Destinations { if err := c.addRoute(ipv6, pubkey); err != nil { return err } } // Add IPv4 routes - for ipv4, pubkey := range c.tun.config.Current.TunnelRouting.IPv4Destinations { + for ipv4, pubkey := range current.TunnelRouting.IPv4Destinations { if err := c.addRoute(ipv4, pubkey); err != nil { return err } @@ -93,7 +92,7 @@ func (c *cryptokey) configure() error { // Add IPv6 sources c.ipv6sources = make([]net.IPNet, 0) - for _, source := range c.tun.config.Current.TunnelRouting.IPv6Sources { + for _, source := range current.TunnelRouting.IPv6Sources { if err := c.addSourceSubnet(source); err != nil { return err } @@ -101,7 +100,7 @@ func (c *cryptokey) configure() error { // Add IPv4 sources c.ipv4sources = make([]net.IPNet, 0) - for _, source := range c.tun.config.Current.TunnelRouting.IPv4Sources { + for _, source := range current.TunnelRouting.IPv4Sources { if err := c.addSourceSubnet(source); err != nil { return err } diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 15530d23..cc124971 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -119,13 +119,12 @@ func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, listener // Start the setup process for the TUN/TAP adapter. If successful, starts the // read/write goroutines to handle packets on that interface. func (tun *TunAdapter) Start() error { - tun.config.Mutex.RLock() - defer tun.config.Mutex.RUnlock() + current, _ := tun.config.Get() if tun.config == nil || tun.listener == nil || tun.dialer == nil { return errors.New("No configuration available to TUN/TAP") } var boxPub crypto.BoxPubKey - boxPubHex, err := hex.DecodeString(tun.config.Current.EncryptionPublicKey) + boxPubHex, err := hex.DecodeString(current.EncryptionPublicKey) if err != nil { return err } @@ -133,9 +132,9 @@ func (tun *TunAdapter) Start() error { nodeID := crypto.GetNodeID(&boxPub) tun.addr = *address.AddrForNodeID(nodeID) tun.subnet = *address.SubnetForNodeID(nodeID) - tun.mtu = tun.config.Current.IfMTU - ifname := tun.config.Current.IfName - iftapmode := tun.config.Current.IfTAPMode + tun.mtu = current.IfMTU + ifname := current.IfName + iftapmode := current.IfTAPMode addr := fmt.Sprintf("%s/%d", net.IP(tun.addr[:]).String(), 8*len(address.GetPrefix())-1) if ifname != "none" { if err := tun.setup(ifname, iftapmode, addr, tun.mtu); err != nil { diff --git a/src/tuntap/tun_windows.go b/src/tuntap/tun_windows.go index 8a66ac62..002c354e 100644 --- a/src/tuntap/tun_windows.go +++ b/src/tuntap/tun_windows.go @@ -31,18 +31,18 @@ func (tun *TunAdapter) setup(ifname string, iftapmode bool, addr string, mtu int } // Disable/enable the interface to resets its configuration (invalidating iface) cmd := exec.Command("netsh", "interface", "set", "interface", iface.Name(), "admin=DISABLED") - tun.log.Printf("netsh command: %v", strings.Join(cmd.Args, " ")) + tun.log.Debugln("netsh command:", strings.Join(cmd.Args, " ")) output, err := cmd.CombinedOutput() if err != nil { - tun.log.Errorf("Windows netsh failed: %v.", err) + tun.log.Errorln("Windows netsh failed:", err) tun.log.Traceln(string(output)) return err } cmd = exec.Command("netsh", "interface", "set", "interface", iface.Name(), "admin=ENABLED") - tun.log.Printf("netsh command: %v", strings.Join(cmd.Args, " ")) + tun.log.Debugln("netsh command:", strings.Join(cmd.Args, " ")) output, err = cmd.CombinedOutput() if err != nil { - tun.log.Errorf("Windows netsh failed: %v.", err) + tun.log.Errorln("Windows netsh failed:", err) tun.log.Traceln(string(output)) return err } @@ -71,10 +71,10 @@ func (tun *TunAdapter) setupMTU(mtu int) error { fmt.Sprintf("interface=%s", tun.iface.Name()), fmt.Sprintf("mtu=%d", mtu), "store=active") - tun.log.Debugln("netsh command: %v", strings.Join(cmd.Args, " ")) + tun.log.Debugln("netsh command:", strings.Join(cmd.Args, " ")) output, err := cmd.CombinedOutput() if err != nil { - tun.log.Errorf("Windows netsh failed: %v.", err) + tun.log.Errorln("Windows netsh failed:", err) tun.log.Traceln(string(output)) return err } @@ -88,10 +88,10 @@ func (tun *TunAdapter) setupAddress(addr string) error { fmt.Sprintf("interface=%s", tun.iface.Name()), fmt.Sprintf("addr=%s", addr), "store=active") - tun.log.Debugln("netsh command: %v", strings.Join(cmd.Args, " ")) + tun.log.Debugln("netsh command:", strings.Join(cmd.Args, " ")) output, err := cmd.CombinedOutput() if err != nil { - tun.log.Errorf("Windows netsh failed: %v.", err) + tun.log.Errorln("Windows netsh failed:", err) tun.log.Traceln(string(output)) return err } From ea9d5db16d68d32268a05c8d288f67a9c3c2a2d1 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 7 Jul 2019 19:41:53 +0100 Subject: [PATCH 33/34] Make admin socket output a bit friendlier (fixes #385) --- src/admin/admin.go | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/admin/admin.go b/src/admin/admin.go index c3f140ae..4c933dd5 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -381,11 +381,11 @@ func (a *AdminSocket) handleRequest(conn net.Conn) { if r != nil { send = Info{ "status": "error", - "error": "Unrecoverable error, possibly as a result of invalid input types or malformed syntax", + "error": "Check your syntax and input types", } - a.log.Errorln("Admin socket error:", r) + a.log.Debugln("Admin socket error:", r) if err := encoder.Encode(&send); err != nil { - a.log.Errorln("Admin socket JSON encode error:", err) + a.log.Debugln("Admin socket JSON encode error:", err) } conn.Close() } @@ -407,13 +407,14 @@ func (a *AdminSocket) handleRequest(conn net.Conn) { send["request"] = recv send["status"] = "error" + n := strings.ToLower(recv["request"].(string)) + if _, ok := recv["request"]; !ok { send["error"] = "No request sent" - break + goto respond } - n := strings.ToLower(recv["request"].(string)) - if h, ok := a.handlers[strings.ToLower(n)]; ok { + if h, ok := a.handlers[n]; ok { // Check that we have all the required arguments for _, arg := range h.args { // An argument in [square brackets] is optional and not required, @@ -428,7 +429,7 @@ func (a *AdminSocket) handleRequest(conn net.Conn) { "error": "Expected field missing: " + arg, "expecting": arg, } - break + goto respond } } @@ -439,16 +440,28 @@ func (a *AdminSocket) handleRequest(conn net.Conn) { send["error"] = err.Error() if response != nil { send["response"] = response + goto respond } } else { send["status"] = "success" if response != nil { send["response"] = response + goto respond } } + } else { + // Start with a clean response on each request, which defaults to an error + // state. If a handler is found below then this will be overwritten + send = Info{ + "request": recv, + "status": "error", + "error": fmt.Sprintf("Unknown action '%s', try 'list' for help", recv["request"].(string)), + } + goto respond } // Send the response back + respond: if err := encoder.Encode(&send); err != nil { return } From 99aac19f98d6b09a3e636ae1a469e371e51e18a6 Mon Sep 17 00:00:00 2001 From: Leon Knauer Date: Tue, 9 Jul 2019 12:30:29 +0200 Subject: [PATCH 34/34] Correcting typo in headline --- doc/Whitepaper.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/Whitepaper.md b/doc/Whitepaper.md index 26d49a53..d7f13279 100644 --- a/doc/Whitepaper.md +++ b/doc/Whitepaper.md @@ -1,4 +1,4 @@ -# Yggdasil +# Yggdrasil Note: This is a very rough early draft.