From a115d18595e748dc1deddc5fd12a56bb0f747014 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 2 May 2020 22:26:41 +0100 Subject: [PATCH 01/35] Refactor the multicast code a bit --- src/multicast/multicast.go | 275 ++++++++++++++++++------------------- 1 file changed, 134 insertions(+), 141 deletions(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 93bf94bb..6ceef0aa 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -15,40 +15,57 @@ import ( "golang.org/x/net/ipv6" ) +const ( + // GroupAddr contains the multicast group and port used for multicast packets. + GroupAddr = "[ff02::114]:9001" +) + // Multicast represents the multicast advertisement and discovery mechanism used // by Yggdrasil to find peers on the same subnet. When a beacon is received on a // configured multicast interface, Yggdrasil will attempt to peer with that node // automatically. type Multicast struct { phony.Inbox - core *yggdrasil.Core - config *config.NodeState - log *log.Logger - sock *ipv6.PacketConn - groupAddr string - listeners map[string]*listenerInfo - listenPort uint16 - isOpen bool - announcer *time.Timer - platformhandler *time.Timer + core *yggdrasil.Core + config *config.NodeState + log *log.Logger + sock *ipv6.PacketConn + groupAddr *net.UDPAddr + listeners map[string]*multicastInterface + listenPort uint16 + isOpen bool + interfaceMonitor *time.Timer + announcer *time.Timer + platformhandler *time.Timer } -type listenerInfo struct { +type multicastInterface struct { + phony.Inbox + sock *ipv6.PacketConn + destAddr net.UDPAddr listener *yggdrasil.TcpListener - time time.Time + zone string + timer *time.Timer interval time.Duration + send chan<- beacon + stop chan interface{} +} + +type beacon struct { + llAddr string + zone string } // Init prepares the multicast interface for use. -func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) error { +func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) (err error) { m.core = core m.config = state m.log = log - m.listeners = make(map[string]*listenerInfo) + m.listeners = make(map[string]*multicastInterface) current := m.config.GetCurrent() m.listenPort = current.LinkLocalTCPPort - m.groupAddr = "[ff02::114]:9001" - return nil + m.groupAddr, err = net.ResolveUDPAddr("udp6", GroupAddr) + return } // Start starts the multicast interface. This launches goroutines which will @@ -71,7 +88,7 @@ func (m *Multicast) _start() error { return nil } m.log.Infoln("Starting multicast module") - addr, err := net.ResolveUDPAddr("udp", m.groupAddr) + addr, err := net.ResolveUDPAddr("udp", GroupAddr) if err != nil { return err } @@ -91,7 +108,7 @@ func (m *Multicast) _start() error { m.isOpen = true go m.listen() m.Act(m, m.multicastStarted) - m.Act(m, m.announce) + m.Act(m, m.monitorInterfaceChanges) return nil } @@ -118,9 +135,14 @@ func (m *Multicast) Stop() error { func (m *Multicast) _stop() error { m.log.Infoln("Stopping multicast module") m.isOpen = false - if m.announcer != nil { - m.announcer.Stop() - } + /* + if m.monitorInterfaceChanges != nil { + m.monitorInterfaceChanges.Stop() + } + if m.sendBeacons != nil { + m.sendBeacons.Stop() + } + */ if m.platformhandler != nil { m.platformhandler.Stop() } @@ -156,6 +178,83 @@ func (m *Multicast) _updateConfig(config *config.NodeConfig) { m.log.Debugln("Reloaded multicast configuration successfully") } +func (m *Multicast) monitorInterfaceChanges() { + interfaces := m.Interfaces() + + // Look for interfaces we don't know about yet. + for name, intf := range interfaces { + if _, ok := m.listeners[name]; !ok { + // Look up interface addresses. + addrs, err := intf.Addrs() + if err != nil { + continue + } + // Find the first link-local address. + for _, addr := range addrs { + addrIP, _, _ := net.ParseCIDR(addr.String()) + // Join the multicast group. + m.sock.JoinGroup(&intf, m.groupAddr) + // Construct a listener on this address. + listenaddr := fmt.Sprintf("[%s%%%s]:%d", addrIP, intf.Name, m.listenPort) + listener, err := m.core.ListenTCP(listenaddr) + if err != nil { + m.log.Warnln("Not multicasting on", name, "due to error:", err) + continue + } + // This is a new interface. Start an announcer for it. + multicastInterface := &multicastInterface{ + sock: m.sock, + destAddr: *m.groupAddr, + listener: listener, + stop: make(chan interface{}), + zone: name, + } + multicastInterface.Act(multicastInterface, multicastInterface.announce) + m.listeners[name] = multicastInterface + m.log.Infoln("Started multicasting on", name) + break + } + } + } + // Look for interfaces we knew about but are no longer there. + for name, intf := range m.listeners { + if _, ok := interfaces[name]; !ok { + // This is a disappeared interface. Stop the announcer. + close(intf.stop) + delete(m.listeners, name) + m.log.Infoln("Stopped multicasting on", name) + } + } + // Queue the next check. + m.interfaceMonitor = time.AfterFunc(time.Second, func() { + m.Act(m, m.monitorInterfaceChanges) + }) +} + +func (m *multicastInterface) announce() { + // Check if the multicast interface has been stopped. This will happen + // if it disappears from the system or goes down. + select { + case <-m.stop: + return + default: + } + // Send the beacon. + lladdr := m.listener.Listener.Addr().String() + if a, err := net.ResolveTCPAddr("tcp6", lladdr); err == nil { + a.Zone = "" + msg := []byte(a.String()) + m.sock.WriteTo(msg, nil, &m.destAddr) + } + // Queue the next beacon. + if m.interval.Seconds() < 15 { + m.interval += time.Second + } + m.timer = time.AfterFunc(m.interval, func() { + m.Act(m, m.announce) + }) +} + // GetInterfaces returns the currently known/enabled multicast interfaces. It is // expected that UpdateInterfaces has been called at least once before calling // this method. @@ -183,6 +282,19 @@ func (m *Multicast) Interfaces() map[string]net.Interface { // Ignore point-to-point interfaces continue } + addrs, _ := iface.Addrs() + hasLLAddr := false + for _, addr := range addrs { + addrIP, _, _ := net.ParseCIDR(addr.String()) + if addrIP.To4() == nil && addrIP.IsLinkLocalUnicast() { + hasLLAddr = true + break + } + } + if !hasLLAddr { + // Ignore interfaces without link-local addresses + continue + } for _, expr := range exprs { // Compile each regular expression e, err := regexp.Compile(expr) @@ -198,127 +310,8 @@ func (m *Multicast) Interfaces() map[string]net.Interface { return interfaces } -func (m *Multicast) announce() { - groupAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr) - if err != nil { - panic(err) - } - destAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr) - if err != nil { - panic(err) - } - 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, info := range m.listeners { - // Prepare our stop function! - stop := func() { - info.listener.Stop() - delete(m.listeners, name) - m.log.Debugln("No longer multicasting on", name) - } - // If the interface is no longer visible on the system then stop the - // listener, as another one will be started further down - if _, ok := interfaces[name]; !ok { - stop() - continue - } - // It's possible that the link-local listener address has changed so if - // that is the case then we should clean up the interface listener - found := false - listenaddr, err := net.ResolveTCPAddr("tcp6", info.listener.Listener.Addr().String()) - if err != nil { - stop() - continue - } - // Find the interface that matches the listener - if intf, err := net.InterfaceByName(name); err == nil { - if addrs, err := intf.Addrs(); err == nil { - // Loop through the addresses attached to that listener and see if any - // of them match the current address of the listener - for _, addr := range addrs { - if ip, _, err := net.ParseCIDR(addr.String()); err == nil { - // Does the interface address match our listener address? - if ip.Equal(listenaddr.IP) { - found = true - break - } - } - } - } - } - // If the address has not been found on the adapter then we should stop - // and clean up the TCP listener. A new one will be created below if a - // suitable link-local address is found - if !found { - stop() - } - } - // Now that we have a list of valid interfaces from the operating system, - // we can start checking if we can send multicasts on them - for _, iface := range interfaces { - // Find interface addresses - addrs, err := iface.Addrs() - if err != nil { - panic(err) - } - for _, addr := range addrs { - addrIP, _, _ := net.ParseCIDR(addr.String()) - // Ignore IPv4 addresses - if addrIP.To4() != nil { - continue - } - // Ignore non-link-local addresses - if !addrIP.IsLinkLocalUnicast() { - continue - } - // Join the multicast group - m.sock.JoinGroup(&iface, groupAddr) - // Try and see if we already have a TCP listener for this interface - var info *listenerInfo - if nfo, ok := m.listeners[iface.Name]; !ok || nfo.listener.Listener == nil { - // No listener was found - let's create one - listenaddr := fmt.Sprintf("[%s%%%s]:%d", addrIP, iface.Name, m.listenPort) - if li, err := m.core.ListenTCP(listenaddr); err == nil { - m.log.Debugln("Started multicasting on", iface.Name) - // Store the listener so that we can stop it later if needed - info = &listenerInfo{listener: li, time: time.Now()} - m.listeners[iface.Name] = info - } else { - m.log.Warnln("Not multicasting on", iface.Name, "due to error:", err) - } - } else { - // An existing listener was found - info = m.listeners[iface.Name] - } - // Make sure nothing above failed for some reason - if info == nil { - continue - } - if time.Since(info.time) < info.interval { - continue - } - // Get the listener details and construct the multicast beacon - lladdr := info.listener.Listener.Addr().String() - if a, err := net.ResolveTCPAddr("tcp6", lladdr); err == nil { - a.Zone = "" - destAddr.Zone = iface.Name - msg := []byte(a.String()) - m.sock.WriteTo(msg, nil, destAddr) - } - if info.interval.Seconds() < 15 { - info.interval += time.Second - } - break - } - } - m.announcer = time.AfterFunc(time.Second, func() { - m.Act(m, m.announce) - }) -} - func (m *Multicast) listen() { - groupAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr) + groupAddr, err := net.ResolveUDPAddr("udp6", GroupAddr) if err != nil { panic(err) } From 0c7cf65d27fe734bda4d9bc334e95c8a9fea2e13 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 2 May 2020 22:33:25 +0100 Subject: [PATCH 02/35] Move some logging back to debug --- src/multicast/multicast.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 6ceef0aa..d944b7ff 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -211,7 +211,7 @@ func (m *Multicast) monitorInterfaceChanges() { } multicastInterface.Act(multicastInterface, multicastInterface.announce) m.listeners[name] = multicastInterface - m.log.Infoln("Started multicasting on", name) + m.log.Debugln("Started multicasting on", name) break } } @@ -222,7 +222,7 @@ func (m *Multicast) monitorInterfaceChanges() { // This is a disappeared interface. Stop the announcer. close(intf.stop) delete(m.listeners, name) - m.log.Infoln("Stopped multicasting on", name) + m.log.Debugln("Stopped multicasting on", name) } } // Queue the next check. From 127b7e311c737fb23142941d16a1b7e8cdb1cf5f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 2 May 2020 22:37:12 +0100 Subject: [PATCH 03/35] Clean up a bit --- src/multicast/multicast.go | 41 ++++++++++++++------------------------ 1 file changed, 15 insertions(+), 26 deletions(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index d944b7ff..28c9b4ec 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -26,17 +26,16 @@ const ( // automatically. type Multicast struct { phony.Inbox - core *yggdrasil.Core - config *config.NodeState - log *log.Logger - sock *ipv6.PacketConn - groupAddr *net.UDPAddr - listeners map[string]*multicastInterface - listenPort uint16 - isOpen bool - interfaceMonitor *time.Timer - announcer *time.Timer - platformhandler *time.Timer + core *yggdrasil.Core + config *config.NodeState + log *log.Logger + sock *ipv6.PacketConn + groupAddr *net.UDPAddr + listeners map[string]*multicastInterface + listenPort uint16 + isOpen bool + monitor *time.Timer + platformhandler *time.Timer } type multicastInterface struct { @@ -47,15 +46,9 @@ type multicastInterface struct { zone string timer *time.Timer interval time.Duration - send chan<- beacon stop chan interface{} } -type beacon struct { - llAddr string - zone string -} - // Init prepares the multicast interface for use. func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) (err error) { m.core = core @@ -135,14 +128,10 @@ func (m *Multicast) Stop() error { func (m *Multicast) _stop() error { m.log.Infoln("Stopping multicast module") m.isOpen = false - /* - if m.monitorInterfaceChanges != nil { - m.monitorInterfaceChanges.Stop() - } - if m.sendBeacons != nil { - m.sendBeacons.Stop() - } - */ + for name := range m.listeners { + close(m.listeners[name].stop) + delete(m.listeners, name) + } if m.platformhandler != nil { m.platformhandler.Stop() } @@ -226,7 +215,7 @@ func (m *Multicast) monitorInterfaceChanges() { } } // Queue the next check. - m.interfaceMonitor = time.AfterFunc(time.Second, func() { + m.monitor = time.AfterFunc(time.Second, func() { m.Act(m, m.monitorInterfaceChanges) }) } From 02e1cb180d046e2a9d120de00556456f5c6887ad Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 2 May 2020 17:23:20 -0500 Subject: [PATCH 04/35] possibly reduce multicast cpu usage even more --- src/multicast/multicast.go | 43 +++++++++++++++++++----------- src/multicast/multicast_darwin.go | 4 +-- src/multicast/multicast_other.go | 2 +- src/multicast/multicast_unix.go | 2 +- src/multicast/multicast_windows.go | 2 +- 5 files changed, 32 insertions(+), 21 deletions(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 28c9b4ec..4f0a3ff9 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -36,6 +36,8 @@ type Multicast struct { isOpen bool monitor *time.Timer platformhandler *time.Timer + _interfaces map[string]net.Interface + _interfaceAddrs map[string][]net.Addr } type multicastInterface struct { @@ -100,8 +102,8 @@ func (m *Multicast) _start() error { m.isOpen = true go m.listen() - m.Act(m, m.multicastStarted) - m.Act(m, m.monitorInterfaceChanges) + m.Act(nil, m._multicastStarted) + m.Act(nil, m._monitorInterfaceChanges) return nil } @@ -145,7 +147,7 @@ func (m *Multicast) _stop() error { // and then signals the various module goroutines to reconfigure themselves if // needed. func (m *Multicast) UpdateConfig(config *config.NodeConfig) { - m.Act(m, func() { m._updateConfig(config) }) + m.Act(nil, func() { m._updateConfig(config) }) } func (m *Multicast) _updateConfig(config *config.NodeConfig) { @@ -167,17 +169,14 @@ func (m *Multicast) _updateConfig(config *config.NodeConfig) { m.log.Debugln("Reloaded multicast configuration successfully") } -func (m *Multicast) monitorInterfaceChanges() { - interfaces := m.Interfaces() +func (m *Multicast) _monitorInterfaceChanges() { + m._updateInterfaces() // update interfaces and interfaceAddrs // Look for interfaces we don't know about yet. - for name, intf := range interfaces { + for name, intf := range m._interfaces { if _, ok := m.listeners[name]; !ok { // Look up interface addresses. - addrs, err := intf.Addrs() - if err != nil { - continue - } + addrs := m._interfaceAddrs[intf.Name] // Find the first link-local address. for _, addr := range addrs { addrIP, _, _ := net.ParseCIDR(addr.String()) @@ -198,7 +197,7 @@ func (m *Multicast) monitorInterfaceChanges() { stop: make(chan interface{}), zone: name, } - multicastInterface.Act(multicastInterface, multicastInterface.announce) + multicastInterface.Act(m, multicastInterface._announce) m.listeners[name] = multicastInterface m.log.Debugln("Started multicasting on", name) break @@ -207,7 +206,7 @@ func (m *Multicast) monitorInterfaceChanges() { } // Look for interfaces we knew about but are no longer there. for name, intf := range m.listeners { - if _, ok := interfaces[name]; !ok { + if _, ok := m._interfaces[name]; !ok { // This is a disappeared interface. Stop the announcer. close(intf.stop) delete(m.listeners, name) @@ -216,11 +215,11 @@ func (m *Multicast) monitorInterfaceChanges() { } // Queue the next check. m.monitor = time.AfterFunc(time.Second, func() { - m.Act(m, m.monitorInterfaceChanges) + m.Act(nil, m._monitorInterfaceChanges) }) } -func (m *multicastInterface) announce() { +func (m *multicastInterface) _announce() { // Check if the multicast interface has been stopped. This will happen // if it disappears from the system or goes down. select { @@ -240,7 +239,7 @@ func (m *multicastInterface) announce() { m.interval += time.Second } m.timer = time.AfterFunc(m.interval, func() { - m.Act(m, m.announce) + m.Act(nil, m._announce) }) } @@ -248,6 +247,14 @@ func (m *multicastInterface) announce() { // expected that UpdateInterfaces has been called at least once before calling // this method. func (m *Multicast) Interfaces() map[string]net.Interface { + var interfaces map[string]net.Interface + phony.Block(m, func() { + interfaces = m._interfaces + }) + return interfaces +} + +func (m *Multicast) _updateInterfaces() { interfaces := make(map[string]net.Interface) // Get interface expressions from config current := m.config.GetCurrent() @@ -258,6 +265,7 @@ func (m *Multicast) Interfaces() map[string]net.Interface { panic(err) } // Work out which interfaces to announce on + interfaceAddrs := make(map[string][]net.Addr) for _, iface := range allifaces { if iface.Flags&net.FlagUp == 0 { // Ignore interfaces that are down @@ -293,10 +301,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 + interfaceAddrs[iface.Name] = addrs } } } - return interfaces + m._interfaces = interfaces + m._interfaceAddrs = interfaceAddrs } func (m *Multicast) listen() { @@ -333,6 +343,7 @@ func (m *Multicast) listen() { if addr.IP.String() != from.IP.String() { continue } + // Note that m.Interfaces would block if it was being run by the actor itself if _, ok := m.Interfaces()[from.Zone]; ok { addr.Zone = "" if err := m.core.CallPeer("tcp://"+addr.String(), from.Zone); err != nil { diff --git a/src/multicast/multicast_darwin.go b/src/multicast/multicast_darwin.go index 4cfef9e9..e020106b 100644 --- a/src/multicast/multicast_darwin.go +++ b/src/multicast/multicast_darwin.go @@ -31,7 +31,7 @@ import ( var awdlGoroutineStarted bool -func (m *Multicast) multicastStarted() { +func (m *Multicast) _multicastStarted() { C.StopAWDLBrowsing() for intf := range m.Interfaces() { if intf == "awdl0" { @@ -40,7 +40,7 @@ func (m *Multicast) multicastStarted() { } } m.platformhandler = time.AfterFunc(time.Minute, func() { - m.Act(m, m.multicastStarted) + m.Act(m, m._multicastStarted) }) } diff --git a/src/multicast/multicast_other.go b/src/multicast/multicast_other.go index 16ea15d6..dfcf625f 100644 --- a/src/multicast/multicast_other.go +++ b/src/multicast/multicast_other.go @@ -4,7 +4,7 @@ package multicast import "syscall" -func (m *Multicast) multicastStarted() { +func (m *Multicast) _multicastStarted() { } diff --git a/src/multicast/multicast_unix.go b/src/multicast/multicast_unix.go index 9d6a01a8..1ff48b17 100644 --- a/src/multicast/multicast_unix.go +++ b/src/multicast/multicast_unix.go @@ -5,7 +5,7 @@ package multicast import "syscall" import "golang.org/x/sys/unix" -func (m *Multicast) multicastStarted() { +func (m *Multicast) _multicastStarted() { } diff --git a/src/multicast/multicast_windows.go b/src/multicast/multicast_windows.go index 7a846b1e..3666faaa 100644 --- a/src/multicast/multicast_windows.go +++ b/src/multicast/multicast_windows.go @@ -5,7 +5,7 @@ package multicast import "syscall" import "golang.org/x/sys/windows" -func (m *Multicast) multicastStarted() { +func (m *Multicast) _multicastStarted() { } From de79401bb228695a78b6244f9e85037ef46c5062 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 3 May 2020 02:50:04 -0500 Subject: [PATCH 05/35] only call (net.Interface).Addrs() once per minute per interface --- src/multicast/multicast.go | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 4f0a3ff9..aaa21151 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -37,7 +37,12 @@ type Multicast struct { monitor *time.Timer platformhandler *time.Timer _interfaces map[string]net.Interface - _interfaceAddrs map[string][]net.Addr + _interfaceAddrs map[string]addrInfo +} + +type addrInfo struct { + addrs []net.Addr + time time.Time } type multicastInterface struct { @@ -176,7 +181,7 @@ func (m *Multicast) _monitorInterfaceChanges() { for name, intf := range m._interfaces { if _, ok := m.listeners[name]; !ok { // Look up interface addresses. - addrs := m._interfaceAddrs[intf.Name] + addrs := m._interfaceAddrs[intf.Name].addrs // Find the first link-local address. for _, addr := range addrs { addrIP, _, _ := net.ParseCIDR(addr.String()) @@ -265,7 +270,7 @@ func (m *Multicast) _updateInterfaces() { panic(err) } // Work out which interfaces to announce on - interfaceAddrs := make(map[string][]net.Addr) + interfaceAddrs := make(map[string]addrInfo) for _, iface := range allifaces { if iface.Flags&net.FlagUp == 0 { // Ignore interfaces that are down @@ -279,9 +284,16 @@ func (m *Multicast) _updateInterfaces() { // Ignore point-to-point interfaces continue } - addrs, _ := iface.Addrs() + var aInfo addrInfo + var isIn bool + if aInfo, isIn = m._interfaceAddrs[iface.Name]; isIn && time.Since(aInfo.time) < time.Minute { + // don't call iface.Addrs, it's unlikely things have changed + } else { + aInfo.addrs, _ = iface.Addrs() + aInfo.time = time.Now() + } hasLLAddr := false - for _, addr := range addrs { + for _, addr := range aInfo.addrs { addrIP, _, _ := net.ParseCIDR(addr.String()) if addrIP.To4() == nil && addrIP.IsLinkLocalUnicast() { hasLLAddr = true @@ -301,7 +313,7 @@ func (m *Multicast) _updateInterfaces() { // Does the interface match the regular expression? Store it if so if e.MatchString(iface.Name) { interfaces[iface.Name] = iface - interfaceAddrs[iface.Name] = addrs + interfaceAddrs[iface.Name] = aInfo } } } From 95f4ec52a40ad1e8d3d1c72d80dd50dbbb8553ce Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 3 May 2020 05:06:59 -0500 Subject: [PATCH 06/35] save only the link-local addresses for multicast --- src/multicast/multicast.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index aaa21151..fc7b1373 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -292,15 +292,15 @@ func (m *Multicast) _updateInterfaces() { aInfo.addrs, _ = iface.Addrs() aInfo.time = time.Now() } - hasLLAddr := false + lladdrs := aInfo.addrs[:0] for _, addr := range aInfo.addrs { addrIP, _, _ := net.ParseCIDR(addr.String()) if addrIP.To4() == nil && addrIP.IsLinkLocalUnicast() { - hasLLAddr = true - break + lladdrs = append(lladdrs, addr) } } - if !hasLLAddr { + aInfo.addrs = lladdrs + if len(lladdrs) == 0 { // Ignore interfaces without link-local addresses continue } From e849b3e1192f94f14f6ea0e0af50275956689171 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 8 May 2020 23:23:48 +0100 Subject: [PATCH 07/35] Initial support for pinning public keys in peering strings --- src/yggdrasil/link.go | 57 +++++++++++++++++++++++++++++++++++++------ src/yggdrasil/tcp.go | 42 ++++++++++++++++++------------- 2 files changed, 75 insertions(+), 24 deletions(-) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 157ea525..c0ae23b5 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -1,6 +1,7 @@ package yggdrasil import ( + "bytes" "encoding/hex" "errors" "fmt" @@ -50,6 +51,7 @@ type linkInterface struct { name string link *link peer *peer + options linkOptions msgIO linkInterfaceMsgIO info linkInfo incoming bool @@ -67,6 +69,10 @@ type linkInterface struct { unstalled bool // False if an idle notification to the switch hasn't been sent because we stalled (or are first starting up) } +type linkOptions struct { + pinningInfo *url.Userinfo +} + func (l *link) init(c *Core) error { l.core = c l.mutex.Lock() @@ -92,13 +98,19 @@ func (l *link) call(uri string, sintf string) error { return fmt.Errorf("peer %s is not correctly formatted (%s)", uri, err) } pathtokens := strings.Split(strings.Trim(u.Path, "/"), "/") + tcpOpts := tcpOptions{} + if u.User != nil { + tcpOpts.pinningInfo = u.User + } switch u.Scheme { case "tcp": - l.tcp.call(u.Host, nil, sintf, nil) + l.tcp.call(u.Host, tcpOpts, sintf) case "socks": - l.tcp.call(pathtokens[0], u.Host, sintf, nil) + tcpOpts.socksProxyAddr = u.Host + l.tcp.call(pathtokens[0], tcpOpts, sintf) case "tls": - l.tcp.call(u.Host, nil, sintf, l.tcp.tls.forDialer) + tcpOpts.upgrade = l.tcp.tls.forDialer + l.tcp.call(u.Host, tcpOpts, sintf) default: return errors.New("unknown call scheme: " + u.Scheme) } @@ -122,12 +134,13 @@ func (l *link) listen(uri string) error { } } -func (l *link) create(msgIO linkInterfaceMsgIO, name, linkType, local, remote string, incoming, force bool) (*linkInterface, error) { +func (l *link) create(msgIO linkInterfaceMsgIO, name, linkType, local, remote string, incoming, force bool, options linkOptions) (*linkInterface, error) { // Technically anything unique would work for names, but let's pick something human readable, just for debugging intf := linkInterface{ - name: name, - link: l, - msgIO: msgIO, + name: name, + link: l, + options: options, + msgIO: msgIO, info: linkInfo{ linkType: linkType, local: local, @@ -181,6 +194,36 @@ func (intf *linkInterface) handler() error { intf.link.core.log.Errorln("Failed to connect to node: " + intf.name + " version: " + fmt.Sprintf("%d.%d", meta.ver, meta.minorVer)) return errors.New("failed to connect: wrong version") } + // Check if the remote side matches the keys we expected. This is a bit of a weak + // check - in future versions we really should check a signature or something like that. + if pinning := intf.options.pinningInfo; pinning != nil { + allowed := true + keytype := pinning.Username() + if pubkey, ok := pinning.Password(); ok { + switch keytype { + case "curve25519": + boxPub, err := hex.DecodeString(pubkey) + if err != nil || len(boxPub) != crypto.BoxPubKeyLen { + allowed = false + break + } + allowed = bytes.Compare(boxPub, meta.box[:]) == 0 + case "ed25519": + sigPub, err := hex.DecodeString(pubkey) + if err != nil || len(sigPub) != crypto.SigPubKeyLen { + allowed = false + break + } + allowed = bytes.Compare(sigPub, meta.sig[:]) == 0 + } + } else { + allowed = false + } + if !allowed { + intf.link.core.log.Errorf("Failed to connect to node: %q sent key that does not match pinned %q key", intf.name, keytype) + return fmt.Errorf("failed to connect: host does not match pinned %q key", pinning.Username()) + } + } // Check if we're authorized to connect to this key / IP if intf.incoming && !intf.force && !intf.link.core.peers.isAllowedEncryptionPublicKey(&meta.box) { intf.link.core.log.Warnf("%s connection from %s forbidden: AllowedEncryptionPublicKeys does not contain key %s", diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index 9cca4193..a1b6402f 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -57,6 +57,12 @@ type TcpUpgrade struct { name string } +type tcpOptions struct { + linkOptions + upgrade *TcpUpgrade + socksProxyAddr string +} + func (l *TcpListener) Stop() { defer func() { recover() }() close(l.stop) @@ -221,7 +227,10 @@ func (t *tcp) listener(l *TcpListener, listenaddr string) { return } t.waitgroup.Add(1) - go t.handler(sock, true, nil, l.upgrade) + options := tcpOptions{ + upgrade: l.upgrade, + } + go t.handler(sock, true, options) } } @@ -239,12 +248,12 @@ func (t *tcp) startCalling(saddr string) bool { // If the dial is successful, it launches the handler. // When finished, it removes the outgoing call, so reconnection attempts can be made later. // This all happens in a separate goroutine that it spawns. -func (t *tcp) call(saddr string, options interface{}, sintf string, upgrade *TcpUpgrade) { +func (t *tcp) call(saddr string, options tcpOptions, sintf string) { go func() { callname := saddr callproto := "TCP" - if upgrade != nil { - callproto = strings.ToUpper(upgrade.name) + if options.upgrade != nil { + callproto = strings.ToUpper(options.upgrade.name) } if sintf != "" { callname = fmt.Sprintf("%s/%s/%s", callproto, saddr, sintf) @@ -263,12 +272,11 @@ func (t *tcp) call(saddr string, options interface{}, sintf string, upgrade *Tcp }() var conn net.Conn var err error - socksaddr, issocks := options.(string) - if issocks { + if options.socksProxyAddr != "" { if sintf != "" { return } - dialerdst, er := net.ResolveTCPAddr("tcp", socksaddr) + dialerdst, er := net.ResolveTCPAddr("tcp", options.socksProxyAddr) if er != nil { return } @@ -282,7 +290,7 @@ func (t *tcp) call(saddr string, options interface{}, sintf string, upgrade *Tcp return } t.waitgroup.Add(1) - t.handler(conn, false, saddr, nil) + t.handler(conn, false, options) } else { dst, err := net.ResolveTCPAddr("tcp", saddr) if err != nil { @@ -348,19 +356,19 @@ func (t *tcp) call(saddr string, options interface{}, sintf string, upgrade *Tcp return } t.waitgroup.Add(1) - t.handler(conn, false, nil, upgrade) + t.handler(conn, false, options) } }() } -func (t *tcp) handler(sock net.Conn, incoming bool, options interface{}, upgrade *TcpUpgrade) { +func (t *tcp) handler(sock net.Conn, incoming bool, options tcpOptions) { defer t.waitgroup.Done() // Happens after sock.close defer sock.Close() t.setExtraOptions(sock) var upgraded bool - if upgrade != nil { + if options.upgrade != nil { var err error - if sock, err = upgrade.upgrade(sock); err != nil { + if sock, err = options.upgrade.upgrade(sock); err != nil { t.link.core.log.Errorln("TCP handler upgrade failed:", err) return } else { @@ -370,14 +378,14 @@ func (t *tcp) handler(sock net.Conn, incoming bool, options interface{}, upgrade stream := stream{} stream.init(sock) var name, proto, local, remote string - if socksaddr, issocks := options.(string); issocks { - name = "socks://" + sock.RemoteAddr().String() + "/" + socksaddr + if options.socksProxyAddr != "" { + name = "socks://" + sock.RemoteAddr().String() + "/" + options.socksProxyAddr proto = "socks" local, _, _ = net.SplitHostPort(sock.LocalAddr().String()) - remote, _, _ = net.SplitHostPort(socksaddr) + remote, _, _ = net.SplitHostPort(options.socksProxyAddr) } else { if upgraded { - proto = upgrade.name + proto = options.upgrade.name name = proto + "://" + sock.RemoteAddr().String() } else { proto = "tcp" @@ -387,7 +395,7 @@ func (t *tcp) handler(sock net.Conn, incoming bool, options interface{}, upgrade remote, _, _ = net.SplitHostPort(sock.RemoteAddr().String()) } force := net.ParseIP(strings.Split(remote, "%")[0]).IsLinkLocalUnicast() - link, err := t.link.core.link.create(&stream, name, proto, local, remote, incoming, force) + link, err := t.link.core.link.create(&stream, name, proto, local, remote, incoming, force, options.linkOptions) if err != nil { t.link.core.log.Println(err) panic(err) From fbf59184ee4a7005fc5768a251826c1633637160 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 9 May 2020 00:43:19 +0100 Subject: [PATCH 08/35] Use query string instead, allow specifying multiple keys (might be useful for DNS RR) --- src/yggdrasil/link.go | 66 +++++++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 27 deletions(-) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index c0ae23b5..40d128e3 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -70,7 +70,8 @@ type linkInterface struct { } type linkOptions struct { - pinningInfo *url.Userinfo + pinnedCurve25519Keys []crypto.BoxPubKey + pinnedEd25519Keys []crypto.SigPubKey } func (l *link) init(c *Core) error { @@ -99,8 +100,27 @@ func (l *link) call(uri string, sintf string) error { } pathtokens := strings.Split(strings.Trim(u.Path, "/"), "/") tcpOpts := tcpOptions{} - if u.User != nil { - tcpOpts.pinningInfo = u.User + if pubkeys, ok := u.Query()["curve25519"]; ok && len(pubkeys) > 0 { + for _, pubkey := range pubkeys { + if boxPub, err := hex.DecodeString(pubkey); err != nil { + var boxPubKey crypto.BoxPubKey + copy(boxPubKey[:], boxPub) + tcpOpts.pinnedCurve25519Keys = append( + tcpOpts.pinnedCurve25519Keys, boxPubKey, + ) + } + } + } + if pubkeys, ok := u.Query()["ed25519"]; ok && len(pubkeys) > 0 { + for _, pubkey := range pubkeys { + if sigPub, err := hex.DecodeString(pubkey); err != nil { + var sigPubKey crypto.SigPubKey + copy(sigPubKey[:], sigPub) + tcpOpts.pinnedEd25519Keys = append( + tcpOpts.pinnedEd25519Keys, sigPubKey, + ) + } + } } switch u.Scheme { case "tcp": @@ -196,32 +216,24 @@ func (intf *linkInterface) handler() error { } // Check if the remote side matches the keys we expected. This is a bit of a weak // check - in future versions we really should check a signature or something like that. - if pinning := intf.options.pinningInfo; pinning != nil { - allowed := true - keytype := pinning.Username() - if pubkey, ok := pinning.Password(); ok { - switch keytype { - case "curve25519": - boxPub, err := hex.DecodeString(pubkey) - if err != nil || len(boxPub) != crypto.BoxPubKeyLen { - allowed = false - break - } - allowed = bytes.Compare(boxPub, meta.box[:]) == 0 - case "ed25519": - sigPub, err := hex.DecodeString(pubkey) - if err != nil || len(sigPub) != crypto.SigPubKeyLen { - allowed = false - break - } - allowed = bytes.Compare(sigPub, meta.sig[:]) == 0 - } - } else { - allowed = false + if pinned := intf.options.pinnedCurve25519Keys; len(pinned) > 0 { + allowed := false + for _, key := range pinned { + allowed = allowed || (bytes.Compare(key[:], meta.box[:]) == 0) } if !allowed { - intf.link.core.log.Errorf("Failed to connect to node: %q sent key that does not match pinned %q key", intf.name, keytype) - return fmt.Errorf("failed to connect: host does not match pinned %q key", pinning.Username()) + intf.link.core.log.Errorf("Failed to connect to node: %q sent curve25519 key that does not match pinned keys", intf.name) + return fmt.Errorf("failed to connect: host sent curve25519 key that does not match pinned keys") + } + } + if pinned := intf.options.pinnedEd25519Keys; len(pinned) > 0 { + allowed := false + for _, key := range pinned { + allowed = allowed || (bytes.Compare(key[:], meta.sig[:]) == 0) + } + if !allowed { + intf.link.core.log.Errorf("Failed to connect to node: %q sent ed25519 key that does not match pinned keys", intf.name) + return fmt.Errorf("failed to connect: host sent ed25519 key that does not match pinned keys") } } // Check if we're authorized to connect to this key / IP From 58345ac198acafbfb219a52a1e66358483347394 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 9 May 2020 10:53:58 +0100 Subject: [PATCH 09/35] Track proxy addr and real peer addr in SOCKS mode --- src/yggdrasil/tcp.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index a1b6402f..6f97da82 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -61,6 +61,7 @@ type tcpOptions struct { linkOptions upgrade *TcpUpgrade socksProxyAddr string + socksPeerAddr string } func (l *TcpListener) Stop() { @@ -290,6 +291,7 @@ func (t *tcp) call(saddr string, options tcpOptions, sintf string) { return } t.waitgroup.Add(1) + options.socksPeerAddr = saddr t.handler(conn, false, options) } else { dst, err := net.ResolveTCPAddr("tcp", saddr) @@ -379,10 +381,10 @@ func (t *tcp) handler(sock net.Conn, incoming bool, options tcpOptions) { stream.init(sock) var name, proto, local, remote string if options.socksProxyAddr != "" { - name = "socks://" + sock.RemoteAddr().String() + "/" + options.socksProxyAddr + name = "socks://" + sock.RemoteAddr().String() + "/" + options.socksPeerAddr proto = "socks" local, _, _ = net.SplitHostPort(sock.LocalAddr().String()) - remote, _, _ = net.SplitHostPort(options.socksProxyAddr) + remote, _, _ = net.SplitHostPort(options.socksPeerAddr) } else { if upgraded { proto = options.upgrade.name From 8b180e941aa7ccf178a7e82c5a11d172c23d6331 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 9 May 2020 11:24:32 +0100 Subject: [PATCH 10/35] Add SOCKS proxy auth (closes #423) --- src/yggdrasil/link.go | 6 ++++++ src/yggdrasil/tcp.go | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 40d128e3..5c4f0e68 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -17,6 +17,7 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/util" + "golang.org/x/net/proxy" "github.com/Arceliar/phony" ) @@ -127,6 +128,11 @@ func (l *link) call(uri string, sintf string) error { l.tcp.call(u.Host, tcpOpts, sintf) case "socks": tcpOpts.socksProxyAddr = u.Host + if u.User != nil { + tcpOpts.socksProxyAuth = &proxy.Auth{} + tcpOpts.socksProxyAuth.User = u.User.Username() + tcpOpts.socksProxyAuth.Password, _ = u.User.Password() + } l.tcp.call(pathtokens[0], tcpOpts, sintf) case "tls": tcpOpts.upgrade = l.tcp.tls.forDialer diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index 6f97da82..e137391a 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -61,6 +61,7 @@ type tcpOptions struct { linkOptions upgrade *TcpUpgrade socksProxyAddr string + socksProxyAuth *proxy.Auth socksPeerAddr string } @@ -282,7 +283,7 @@ func (t *tcp) call(saddr string, options tcpOptions, sintf string) { return } var dialer proxy.Dialer - dialer, err = proxy.SOCKS5("tcp", dialerdst.String(), nil, proxy.Direct) + dialer, err = proxy.SOCKS5("tcp", dialerdst.String(), options.socksProxyAuth, proxy.Direct) if err != nil { return } From 13a2d99fdc427753b3ccd433e0bdfee8fa5930a8 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 9 May 2020 11:26:09 +0100 Subject: [PATCH 11/35] Set SOCKS peer addr to resolved address --- src/yggdrasil/tcp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index e137391a..c81c4ddf 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -292,7 +292,7 @@ func (t *tcp) call(saddr string, options tcpOptions, sintf string) { return } t.waitgroup.Add(1) - options.socksPeerAddr = saddr + options.socksPeerAddr = conn.RemoteAddr().String() t.handler(conn, false, options) } else { dst, err := net.ResolveTCPAddr("tcp", saddr) From 7779d86c5b6ff1ccc531552ac5d0108f98aa5d66 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 9 May 2020 05:56:36 -0500 Subject: [PATCH 12/35] maybe fix multicast deadlock on darwin --- src/multicast/multicast_darwin.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/multicast/multicast_darwin.go b/src/multicast/multicast_darwin.go index e020106b..91cb5aee 100644 --- a/src/multicast/multicast_darwin.go +++ b/src/multicast/multicast_darwin.go @@ -33,14 +33,14 @@ var awdlGoroutineStarted bool func (m *Multicast) _multicastStarted() { C.StopAWDLBrowsing() - for intf := range m.Interfaces() { + for intf := range m._interfaces { if intf == "awdl0" { C.StartAWDLBrowsing() break } } m.platformhandler = time.AfterFunc(time.Minute, func() { - m.Act(m, m._multicastStarted) + m.Act(nil, m._multicastStarted) }) } From 9dfe0f4b4b7cf38b4c5e32ba20ee9d7de8cccdd6 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 9 May 2020 12:08:29 +0100 Subject: [PATCH 13/35] Fix hjson dependency? --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 04cd5b90..bf8e7571 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/cheggaaa/pb/v3 v3.0.4 github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 github.com/hashicorp/go-syslog v1.0.0 - github.com/hjson/hjson-go v3.0.1-0.20190209023717-9147687966d9+incompatible + github.com/hjson/hjson-go v3.0.2-0.20200316202735-d5d0e8b0617d+incompatible github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 github.com/mitchellh/mapstructure v1.1.2 github.com/vishvananda/netlink v1.0.0 diff --git a/go.sum b/go.sum index 6d08e865..f67b6806 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,8 @@ github.com/gologme/log v0.0.0-20181207131047-4e5d8ccb38e8 h1:WD8iJ37bRNwvETMfVTu 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 v3.0.1-0.20190209023717-9147687966d9+incompatible h1:bLQ2Ve+eW65id3b8xEMQiAwJT4qGZeywAEMLvXjznvw= -github.com/hjson/hjson-go v3.0.1-0.20190209023717-9147687966d9+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio= +github.com/hjson/hjson-go v3.0.2-0.20200316202735-d5d0e8b0617d+incompatible h1:v6BPcb9q9U6JDVsuizxBr/piVB/2Y1Q5GWoBybvZVWI= +github.com/hjson/hjson-go v3.0.2-0.20200316202735-d5d0e8b0617d+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio= github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0 h1:YnZmFjg0Nvk8851WTVWlqMC1ecJH07Ctz+Ezxx4u54g= github.com/kardianos/minwinsvc v0.0.0-20151122163309-cad6b2b879b0/go.mod h1:rUi0/YffDo1oXBOGn1KRq7Fr07LX48XEBecQnmwjsAo= github.com/lxn/walk v0.0.0-20191128110447-55ccb3a9f5c1 h1:/QwQcwWVOQXcoNuV9tHx30gQ3q7jCE/rKcGjwzsa5tg= From 2a2ad76479a58ca01842453d89d370b70cc24b4d Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 9 May 2020 12:38:20 +0100 Subject: [PATCH 14/35] Use maps instead of slices --- src/yggdrasil/link.go | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 5c4f0e68..03e60076 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -1,7 +1,6 @@ package yggdrasil import ( - "bytes" "encoding/hex" "errors" "fmt" @@ -71,8 +70,8 @@ type linkInterface struct { } type linkOptions struct { - pinnedCurve25519Keys []crypto.BoxPubKey - pinnedEd25519Keys []crypto.SigPubKey + pinnedCurve25519Keys map[crypto.BoxPubKey]struct{} + pinnedEd25519Keys map[crypto.SigPubKey]struct{} } func (l *link) init(c *Core) error { @@ -102,24 +101,22 @@ func (l *link) call(uri string, sintf string) error { pathtokens := strings.Split(strings.Trim(u.Path, "/"), "/") tcpOpts := tcpOptions{} if pubkeys, ok := u.Query()["curve25519"]; ok && len(pubkeys) > 0 { + tcpOpts.pinnedCurve25519Keys = make(map[crypto.BoxPubKey]struct{}) for _, pubkey := range pubkeys { if boxPub, err := hex.DecodeString(pubkey); err != nil { var boxPubKey crypto.BoxPubKey copy(boxPubKey[:], boxPub) - tcpOpts.pinnedCurve25519Keys = append( - tcpOpts.pinnedCurve25519Keys, boxPubKey, - ) + tcpOpts.pinnedCurve25519Keys[boxPubKey] = struct{}{} } } } if pubkeys, ok := u.Query()["ed25519"]; ok && len(pubkeys) > 0 { + tcpOpts.pinnedEd25519Keys = make(map[crypto.SigPubKey]struct{}) for _, pubkey := range pubkeys { if sigPub, err := hex.DecodeString(pubkey); err != nil { var sigPubKey crypto.SigPubKey copy(sigPubKey[:], sigPub) - tcpOpts.pinnedEd25519Keys = append( - tcpOpts.pinnedEd25519Keys, sigPubKey, - ) + tcpOpts.pinnedEd25519Keys[sigPubKey] = struct{}{} } } } @@ -222,22 +219,14 @@ func (intf *linkInterface) handler() error { } // Check if the remote side matches the keys we expected. This is a bit of a weak // check - in future versions we really should check a signature or something like that. - if pinned := intf.options.pinnedCurve25519Keys; len(pinned) > 0 { - allowed := false - for _, key := range pinned { - allowed = allowed || (bytes.Compare(key[:], meta.box[:]) == 0) - } - if !allowed { + if pinned := intf.options.pinnedCurve25519Keys; pinned != nil { + if _, allowed := pinned[meta.box]; !allowed { intf.link.core.log.Errorf("Failed to connect to node: %q sent curve25519 key that does not match pinned keys", intf.name) return fmt.Errorf("failed to connect: host sent curve25519 key that does not match pinned keys") } } - if pinned := intf.options.pinnedEd25519Keys; len(pinned) > 0 { - allowed := false - for _, key := range pinned { - allowed = allowed || (bytes.Compare(key[:], meta.sig[:]) == 0) - } - if !allowed { + if pinned := intf.options.pinnedEd25519Keys; pinned != nil { + if _, allowed := pinned[meta.sig]; !allowed { intf.link.core.log.Errorf("Failed to connect to node: %q sent ed25519 key that does not match pinned keys", intf.name) return fmt.Errorf("failed to connect: host sent ed25519 key that does not match pinned keys") } From f70b2ebceaff118e67541f3d9d0f714a00fe0dc7 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 9 May 2020 12:49:02 +0100 Subject: [PATCH 15/35] Fix bad check --- src/yggdrasil/link.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 03e60076..0b37eaa2 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -103,7 +103,7 @@ func (l *link) call(uri string, sintf string) error { if pubkeys, ok := u.Query()["curve25519"]; ok && len(pubkeys) > 0 { tcpOpts.pinnedCurve25519Keys = make(map[crypto.BoxPubKey]struct{}) for _, pubkey := range pubkeys { - if boxPub, err := hex.DecodeString(pubkey); err != nil { + if boxPub, err := hex.DecodeString(pubkey); err == nil { var boxPubKey crypto.BoxPubKey copy(boxPubKey[:], boxPub) tcpOpts.pinnedCurve25519Keys[boxPubKey] = struct{}{} @@ -113,7 +113,7 @@ func (l *link) call(uri string, sintf string) error { if pubkeys, ok := u.Query()["ed25519"]; ok && len(pubkeys) > 0 { tcpOpts.pinnedEd25519Keys = make(map[crypto.SigPubKey]struct{}) for _, pubkey := range pubkeys { - if sigPub, err := hex.DecodeString(pubkey); err != nil { + if sigPub, err := hex.DecodeString(pubkey); err == nil { var sigPubKey crypto.SigPubKey copy(sigPubKey[:], sigPub) tcpOpts.pinnedEd25519Keys[sigPubKey] = struct{}{} From bc48e4bb809aa875caabf711a58acf7911e81c24 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 23 May 2020 11:11:11 -0500 Subject: [PATCH 16/35] fix deadlock in conn (unsafe use of phony.Block) --- src/yggdrasil/conn.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index eef57683..a217d88e 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -153,7 +153,7 @@ func (c *Conn) doSearch() { // Nothing was found, so create a new search searchCompleted := func(sinfo *sessionInfo, e error) {} sinfo = c.core.router.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) - c.core.log.Debugf("%s DHT search started: %p", c.String(), sinfo) + c.core.log.Debugf("%s DHT search started: %p", fmt.Sprintf("conn=%p", c), sinfo) // Start the search sinfo.startSearch() } From 7063ddcc7369e95e219b373b353ea2cd1d6b234c Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 23 May 2020 11:16:03 -0500 Subject: [PATCH 17/35] slightly cleaner fix to conn String deadlock issue --- src/yggdrasil/conn.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index a217d88e..4cd8b205 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -145,7 +145,8 @@ func (c *Conn) search() error { } // Used in session keep-alive traffic -func (c *Conn) doSearch() { +func (c *Conn) _doSearch() { + s := fmt.Sprintf("conn=%p", c) routerWork := func() { // Check to see if there is a search already matching the destination sinfo, isIn := c.core.router.searches.searches[*c.nodeID] @@ -153,7 +154,7 @@ func (c *Conn) doSearch() { // Nothing was found, so create a new search searchCompleted := func(sinfo *sessionInfo, e error) {} sinfo = c.core.router.searches.newIterSearch(c.nodeID, c.nodeMask, searchCompleted) - c.core.log.Debugf("%s DHT search started: %p", fmt.Sprintf("conn=%p", c), sinfo) + c.core.log.Debugf("%s DHT search started: %p", s, sinfo) // Start the search sinfo.startSearch() } @@ -269,7 +270,7 @@ func (c *Conn) _write(msg FlowKeyMessage) error { case time.Since(c.session.time) > 6*time.Second: if c.session.time.Before(c.session.pingTime) && time.Since(c.session.pingTime) > 6*time.Second { // TODO double check that the above condition is correct - c.doSearch() + c._doSearch() } else { c.session.ping(c.session) // TODO send from self if this becomes an actor } From c2d6e9e8f1603f41c1aebaad5a6ec7023e34edc7 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 24 May 2020 14:09:06 -0500 Subject: [PATCH 18/35] close listener when a multicast interface is removed --- src/multicast/multicast.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index fc7b1373..ad097b86 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -136,6 +136,7 @@ func (m *Multicast) _stop() error { m.log.Infoln("Stopping multicast module") m.isOpen = false for name := range m.listeners { + m.listeners[name].listener.Listener.Close() close(m.listeners[name].stop) delete(m.listeners, name) } @@ -213,6 +214,7 @@ func (m *Multicast) _monitorInterfaceChanges() { for name, intf := range m.listeners { if _, ok := m._interfaces[name]; !ok { // This is a disappeared interface. Stop the announcer. + intf.listener.Listener.Close() close(intf.stop) delete(m.listeners, name) m.log.Debugln("Stopped multicasting on", name) From 1e471e3712d14d7434b14e6d21b99be169bfaec5 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 24 May 2020 14:43:38 -0500 Subject: [PATCH 19/35] back to master's version of multicast, lets try rewriting it again --- src/multicast/multicast.go | 292 ++++++++++++++++++------------------- 1 file changed, 140 insertions(+), 152 deletions(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index ad097b86..30f66151 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -15,57 +15,38 @@ import ( "golang.org/x/net/ipv6" ) -const ( - // GroupAddr contains the multicast group and port used for multicast packets. - GroupAddr = "[ff02::114]:9001" -) - // Multicast represents the multicast advertisement and discovery mechanism used // by Yggdrasil to find peers on the same subnet. When a beacon is received on a // configured multicast interface, Yggdrasil will attempt to peer with that node // automatically. type Multicast struct { phony.Inbox - core *yggdrasil.Core - config *config.NodeState - log *log.Logger - sock *ipv6.PacketConn - groupAddr *net.UDPAddr - listeners map[string]*multicastInterface - listenPort uint16 - isOpen bool - monitor *time.Timer - platformhandler *time.Timer - _interfaces map[string]net.Interface - _interfaceAddrs map[string]addrInfo + core *yggdrasil.Core + config *config.NodeState + log *log.Logger + sock *ipv6.PacketConn + groupAddr string + listeners map[string]*listenerInfo + listenPort uint16 + isOpen bool } -type addrInfo struct { - addrs []net.Addr - time time.Time -} - -type multicastInterface struct { - phony.Inbox - sock *ipv6.PacketConn - destAddr net.UDPAddr +type listenerInfo struct { listener *yggdrasil.TcpListener - zone string - timer *time.Timer + time time.Time interval time.Duration - stop chan interface{} } // Init prepares the multicast interface for use. -func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) (err error) { +func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) error { m.core = core m.config = state m.log = log - m.listeners = make(map[string]*multicastInterface) + m.listeners = make(map[string]*listenerInfo) current := m.config.GetCurrent() m.listenPort = current.LinkLocalTCPPort - m.groupAddr, err = net.ResolveUDPAddr("udp6", GroupAddr) - return + m.groupAddr = "[ff02::114]:9001" + return nil } // Start starts the multicast interface. This launches goroutines which will @@ -88,7 +69,7 @@ func (m *Multicast) _start() error { return nil } m.log.Infoln("Starting multicast module") - addr, err := net.ResolveUDPAddr("udp", GroupAddr) + addr, err := net.ResolveUDPAddr("udp", m.groupAddr) if err != nil { return err } @@ -108,7 +89,7 @@ func (m *Multicast) _start() error { m.isOpen = true go m.listen() m.Act(nil, m._multicastStarted) - m.Act(nil, m._monitorInterfaceChanges) + m.Act(nil, m._announce) return nil } @@ -135,14 +116,6 @@ func (m *Multicast) Stop() error { func (m *Multicast) _stop() error { m.log.Infoln("Stopping multicast module") m.isOpen = false - for name := range m.listeners { - m.listeners[name].listener.Listener.Close() - close(m.listeners[name].stop) - delete(m.listeners, name) - } - if m.platformhandler != nil { - m.platformhandler.Stop() - } if m.sock != nil { m.sock.Close() } @@ -175,93 +148,10 @@ func (m *Multicast) _updateConfig(config *config.NodeConfig) { m.log.Debugln("Reloaded multicast configuration successfully") } -func (m *Multicast) _monitorInterfaceChanges() { - m._updateInterfaces() // update interfaces and interfaceAddrs - - // Look for interfaces we don't know about yet. - for name, intf := range m._interfaces { - if _, ok := m.listeners[name]; !ok { - // Look up interface addresses. - addrs := m._interfaceAddrs[intf.Name].addrs - // Find the first link-local address. - for _, addr := range addrs { - addrIP, _, _ := net.ParseCIDR(addr.String()) - // Join the multicast group. - m.sock.JoinGroup(&intf, m.groupAddr) - // Construct a listener on this address. - listenaddr := fmt.Sprintf("[%s%%%s]:%d", addrIP, intf.Name, m.listenPort) - listener, err := m.core.ListenTCP(listenaddr) - if err != nil { - m.log.Warnln("Not multicasting on", name, "due to error:", err) - continue - } - // This is a new interface. Start an announcer for it. - multicastInterface := &multicastInterface{ - sock: m.sock, - destAddr: *m.groupAddr, - listener: listener, - stop: make(chan interface{}), - zone: name, - } - multicastInterface.Act(m, multicastInterface._announce) - m.listeners[name] = multicastInterface - m.log.Debugln("Started multicasting on", name) - break - } - } - } - // Look for interfaces we knew about but are no longer there. - for name, intf := range m.listeners { - if _, ok := m._interfaces[name]; !ok { - // This is a disappeared interface. Stop the announcer. - intf.listener.Listener.Close() - close(intf.stop) - delete(m.listeners, name) - m.log.Debugln("Stopped multicasting on", name) - } - } - // Queue the next check. - m.monitor = time.AfterFunc(time.Second, func() { - m.Act(nil, m._monitorInterfaceChanges) - }) -} - -func (m *multicastInterface) _announce() { - // Check if the multicast interface has been stopped. This will happen - // if it disappears from the system or goes down. - select { - case <-m.stop: - return - default: - } - // Send the beacon. - lladdr := m.listener.Listener.Addr().String() - if a, err := net.ResolveTCPAddr("tcp6", lladdr); err == nil { - a.Zone = "" - msg := []byte(a.String()) - m.sock.WriteTo(msg, nil, &m.destAddr) - } - // Queue the next beacon. - if m.interval.Seconds() < 15 { - m.interval += time.Second - } - m.timer = time.AfterFunc(m.interval, func() { - m.Act(nil, m._announce) - }) -} - // 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) Interfaces() map[string]net.Interface { - var interfaces map[string]net.Interface - phony.Block(m, func() { - interfaces = m._interfaces - }) - return interfaces -} - -func (m *Multicast) _updateInterfaces() { interfaces := make(map[string]net.Interface) // Get interface expressions from config current := m.config.GetCurrent() @@ -272,7 +162,6 @@ func (m *Multicast) _updateInterfaces() { panic(err) } // Work out which interfaces to announce on - interfaceAddrs := make(map[string]addrInfo) for _, iface := range allifaces { if iface.Flags&net.FlagUp == 0 { // Ignore interfaces that are down @@ -286,26 +175,6 @@ func (m *Multicast) _updateInterfaces() { // Ignore point-to-point interfaces continue } - var aInfo addrInfo - var isIn bool - if aInfo, isIn = m._interfaceAddrs[iface.Name]; isIn && time.Since(aInfo.time) < time.Minute { - // don't call iface.Addrs, it's unlikely things have changed - } else { - aInfo.addrs, _ = iface.Addrs() - aInfo.time = time.Now() - } - lladdrs := aInfo.addrs[:0] - for _, addr := range aInfo.addrs { - addrIP, _, _ := net.ParseCIDR(addr.String()) - if addrIP.To4() == nil && addrIP.IsLinkLocalUnicast() { - lladdrs = append(lladdrs, addr) - } - } - aInfo.addrs = lladdrs - if len(lladdrs) == 0 { - // Ignore interfaces without link-local addresses - continue - } for _, expr := range exprs { // Compile each regular expression e, err := regexp.Compile(expr) @@ -315,16 +184,136 @@ func (m *Multicast) _updateInterfaces() { // Does the interface match the regular expression? Store it if so if e.MatchString(iface.Name) { interfaces[iface.Name] = iface - interfaceAddrs[iface.Name] = aInfo } } } - m._interfaces = interfaces - m._interfaceAddrs = interfaceAddrs + return interfaces +} + +func (m *Multicast) _announce() { + if !m.isOpen { + return + } + groupAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr) + if err != nil { + panic(err) + } + destAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr) + if err != nil { + panic(err) + } + 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, info := range m.listeners { + // Prepare our stop function! + stop := func() { + info.listener.Stop() + delete(m.listeners, name) + m.log.Debugln("No longer multicasting on", name) + } + // If the interface is no longer visible on the system then stop the + // listener, as another one will be started further down + if _, ok := interfaces[name]; !ok { + stop() + continue + } + // It's possible that the link-local listener address has changed so if + // that is the case then we should clean up the interface listener + found := false + listenaddr, err := net.ResolveTCPAddr("tcp6", info.listener.Listener.Addr().String()) + if err != nil { + stop() + continue + } + // Find the interface that matches the listener + if intf, err := net.InterfaceByName(name); err == nil { + if addrs, err := intf.Addrs(); err == nil { + // Loop through the addresses attached to that listener and see if any + // of them match the current address of the listener + for _, addr := range addrs { + if ip, _, err := net.ParseCIDR(addr.String()); err == nil { + // Does the interface address match our listener address? + if ip.Equal(listenaddr.IP) { + found = true + break + } + } + } + } + } + // If the address has not been found on the adapter then we should stop + // and clean up the TCP listener. A new one will be created below if a + // suitable link-local address is found + if !found { + stop() + } + } + // Now that we have a list of valid interfaces from the operating system, + // we can start checking if we can send multicasts on them + for _, iface := range interfaces { + // Find interface addresses + addrs, err := iface.Addrs() + if err != nil { + panic(err) + } + for _, addr := range addrs { + addrIP, _, _ := net.ParseCIDR(addr.String()) + // Ignore IPv4 addresses + if addrIP.To4() != nil { + continue + } + // Ignore non-link-local addresses + if !addrIP.IsLinkLocalUnicast() { + continue + } + // Join the multicast group + m.sock.JoinGroup(&iface, groupAddr) + // Try and see if we already have a TCP listener for this interface + var info *listenerInfo + if nfo, ok := m.listeners[iface.Name]; !ok || nfo.listener.Listener == nil { + // No listener was found - let's create one + listenaddr := fmt.Sprintf("[%s%%%s]:%d", addrIP, iface.Name, m.listenPort) + if li, err := m.core.ListenTCP(listenaddr); err == nil { + m.log.Debugln("Started multicasting on", iface.Name) + // Store the listener so that we can stop it later if needed + info = &listenerInfo{listener: li, time: time.Now()} + m.listeners[iface.Name] = info + } else { + m.log.Warnln("Not multicasting on", iface.Name, "due to error:", err) + } + } else { + // An existing listener was found + info = m.listeners[iface.Name] + } + // Make sure nothing above failed for some reason + if info == nil { + continue + } + if time.Since(info.time) < info.interval { + continue + } + // Get the listener details and construct the multicast beacon + lladdr := info.listener.Listener.Addr().String() + if a, err := net.ResolveTCPAddr("tcp6", lladdr); err == nil { + a.Zone = "" + destAddr.Zone = iface.Name + msg := []byte(a.String()) + m.sock.WriteTo(msg, nil, destAddr) + } + if info.interval.Seconds() < 15 { + info.interval += time.Second + } + break + } + } + time.AfterFunc(time.Second, func() { + m.Act(nil, m._announce) + }) } func (m *Multicast) listen() { - groupAddr, err := net.ResolveUDPAddr("udp6", GroupAddr) + groupAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr) if err != nil { panic(err) } @@ -357,7 +346,6 @@ func (m *Multicast) listen() { if addr.IP.String() != from.IP.String() { continue } - // Note that m.Interfaces would block if it was being run by the actor itself if _, ok := m.Interfaces()[from.Zone]; ok { addr.Zone = "" if err := m.core.CallPeer("tcp://"+addr.String(), from.Zone); err != nil { From 98816f34b233bbe0a9d44fa8d3050fd2b9052606 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 24 May 2020 15:24:39 -0500 Subject: [PATCH 20/35] don't spam calls to net.Interfaces and net.Interface.Addrs (hopefully) --- src/multicast/multicast.go | 74 +++++++++++++++++++++++++++----------- 1 file changed, 53 insertions(+), 21 deletions(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 30f66151..7de3cfab 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -21,14 +21,20 @@ import ( // automatically. type Multicast struct { phony.Inbox - core *yggdrasil.Core - config *config.NodeState - log *log.Logger - sock *ipv6.PacketConn - groupAddr string - listeners map[string]*listenerInfo - listenPort uint16 - isOpen bool + core *yggdrasil.Core + config *config.NodeState + log *log.Logger + sock *ipv6.PacketConn + groupAddr string + listeners map[string]*listenerInfo + listenPort uint16 + isOpen bool + _interfaces map[string]interfaceInfo +} + +type interfaceInfo struct { + iface net.Interface + addrs []net.Addr } type listenerInfo struct { @@ -43,6 +49,7 @@ func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log m.config = state m.log = log m.listeners = make(map[string]*listenerInfo) + m._interfaces = make(map[string]interfaceInfo) current := m.config.GetCurrent() m.listenPort = current.LinkLocalTCPPort m.groupAddr = "[ff02::114]:9001" @@ -148,10 +155,35 @@ func (m *Multicast) _updateConfig(config *config.NodeConfig) { m.log.Debugln("Reloaded multicast configuration successfully") } -// 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) _updateInterfaces() { + interfaces := make(map[string]interfaceInfo) + intfs := m.getAllowedInterfaces() + for _, intf := range intfs { + addrs, err := intf.Addrs() + if err != nil { + m.log.Warnf("Failed up get addresses for interface %s: %s", intf.Name, err) + continue + } + interfaces[intf.Name] = interfaceInfo{ + iface: intf, + addrs: addrs, + } + } + m._interfaces = interfaces +} + func (m *Multicast) Interfaces() map[string]net.Interface { + interfaces := make(map[string]net.Interface) + phony.Block(m, func() { + for _, info := range m._interfaces { + interfaces[info.iface.Name] = info.iface + } + }) + return interfaces +} + +// getAllowedInterfaces returns the currently known/enabled multicast interfaces. +func (m *Multicast) getAllowedInterfaces() map[string]net.Interface { interfaces := make(map[string]net.Interface) // Get interface expressions from config current := m.config.GetCurrent() @@ -194,6 +226,7 @@ func (m *Multicast) _announce() { if !m.isOpen { return } + m._updateInterfaces() groupAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr) if err != nil { panic(err) @@ -202,7 +235,6 @@ func (m *Multicast) _announce() { if err != nil { panic(err) } - 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, info := range m.listeners { @@ -214,7 +246,7 @@ func (m *Multicast) _announce() { } // If the interface is no longer visible on the system then stop the // listener, as another one will be started further down - if _, ok := interfaces[name]; !ok { + if _, ok := m._interfaces[name]; !ok { stop() continue } @@ -251,13 +283,9 @@ func (m *Multicast) _announce() { } // Now that we have a list of valid interfaces from the operating system, // we can start checking if we can send multicasts on them - for _, iface := range interfaces { - // Find interface addresses - addrs, err := iface.Addrs() - if err != nil { - panic(err) - } - for _, addr := range addrs { + for _, info := range m._interfaces { + iface := info.iface + for _, addr := range info.addrs { addrIP, _, _ := net.ParseCIDR(addr.String()) // Ignore IPv4 addresses if addrIP.To4() != nil { @@ -346,7 +374,11 @@ func (m *Multicast) listen() { if addr.IP.String() != from.IP.String() { continue } - if _, ok := m.Interfaces()[from.Zone]; ok { + var interfaces map[string]interfaceInfo + phony.Block(m, func() { + interfaces = m._interfaces + }) + if _, ok := 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) From 7778a47a8f224cf4e16cf8008fbed62d8ee18ceb Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 24 May 2020 15:46:18 -0500 Subject: [PATCH 21/35] fix darwin compile problem --- src/multicast/multicast_darwin.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/multicast/multicast_darwin.go b/src/multicast/multicast_darwin.go index 91cb5aee..ceff5b44 100644 --- a/src/multicast/multicast_darwin.go +++ b/src/multicast/multicast_darwin.go @@ -32,6 +32,9 @@ import ( var awdlGoroutineStarted bool func (m *Multicast) _multicastStarted() { + if !m.isOpen { + return + } C.StopAWDLBrowsing() for intf := range m._interfaces { if intf == "awdl0" { @@ -39,7 +42,7 @@ func (m *Multicast) _multicastStarted() { break } } - m.platformhandler = time.AfterFunc(time.Minute, func() { + time.AfterFunc(time.Minute, func() { m.Act(nil, m._multicastStarted) }) } From 40bfd207f56c26159322b7dbc6bd7a9b443a0c2e Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 25 May 2020 12:23:38 -0500 Subject: [PATCH 22/35] don't store every node we hear from in the DHT, only ones we already know about or that are important --- src/yggdrasil/dht.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index 8efc549f..0b951c9d 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -260,7 +260,9 @@ func (t *dht) handleRes(res *dhtRes) { key: res.Key, coords: res.Coords, } - t.insert(&rinfo) + if _, isIn := t.table[*rinfo.getNodeID()]; isIn || t.isImportant(&rinfo) { + t.insert(&rinfo) + } for _, info := range res.Infos { if *info.getNodeID() == t.nodeID { continue From eefabb5f9fd128aa51350c9e81c6b771f2f32984 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 25 May 2020 12:44:06 -0500 Subject: [PATCH 23/35] disregard nodes if they're unimportant, even if they're already in the DHT --- src/yggdrasil/dht.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index 0b951c9d..013fd1e4 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -260,7 +260,7 @@ func (t *dht) handleRes(res *dhtRes) { key: res.Key, coords: res.Coords, } - if _, isIn := t.table[*rinfo.getNodeID()]; isIn || t.isImportant(&rinfo) { + if t.isImportant(&rinfo) { t.insert(&rinfo) } for _, info := range res.Infos { From 8cca565ac46a4e412ffe7c9089d1c4ecb86fb492 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 25 May 2020 22:08:53 +0100 Subject: [PATCH 24/35] Update go.mod/go.sum for yggdrasil-extras for iOS builds --- go.mod | 1 + go.sum | 2 ++ 2 files changed, 3 insertions(+) diff --git a/go.mod b/go.mod index bf8e7571..cea62b2b 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/mitchellh/mapstructure v1.1.2 github.com/vishvananda/netlink v1.0.0 github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f // indirect + github.com/yggdrasil-network/yggdrasil-extras v0.0.0-20200525205615-6c8a4a2e8855 // indirect golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d golang.org/x/net v0.0.0-20200301022130-244492dfa37a golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 diff --git a/go.sum b/go.sum index f67b6806..013abbeb 100644 --- a/go.sum +++ b/go.sum @@ -31,6 +31,8 @@ github.com/vishvananda/netlink v1.0.0 h1:bqNY2lgheFIu1meHUFSH3d7vG93AFyqg3oGbJCO github.com/vishvananda/netlink v1.0.0/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f h1:nBX3nTcmxEtHSERBJaIo1Qa26VwRaopnZmfDQUXsF4I= github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= +github.com/yggdrasil-network/yggdrasil-extras v0.0.0-20200525205615-6c8a4a2e8855 h1:xLQihK8bAKOEDii/Z39dHTgSJzetm2TQ1YKRPRX87R4= +github.com/yggdrasil-network/yggdrasil-extras v0.0.0-20200525205615-6c8a4a2e8855/go.mod h1:xQdsh08Io6nV4WRnOVTe6gI8/2iTvfLDQ0CYa5aMt+I= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw= From 8345ae1fa304b6f097903336fa5c37084b047832 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 25 May 2020 19:08:04 -0500 Subject: [PATCH 25/35] don't allow ygg tcp connections to/from a local ygg address --- src/yggdrasil/tcp.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index c81c4ddf..83305ab6 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -25,6 +25,7 @@ import ( "golang.org/x/net/proxy" + "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/util" ) @@ -397,6 +398,18 @@ func (t *tcp) handler(sock net.Conn, incoming bool, options tcpOptions) { local, _, _ = net.SplitHostPort(sock.LocalAddr().String()) remote, _, _ = net.SplitHostPort(sock.RemoteAddr().String()) } + localIP := net.ParseIP(local) + if localIP = localIP.To16(); localIP != nil { + var laddr address.Address + var lsubnet address.Subnet + copy(laddr[:], localIP) + copy(lsubnet[:], localIP) + if laddr.IsValid() || lsubnet.IsValid() { + // The local address is with the network address/prefix range + // This would route ygg over ygg, which we don't want + return + } + } force := net.ParseIP(strings.Split(remote, "%")[0]).IsLinkLocalUnicast() link, err := t.link.core.link.create(&stream, name, proto, local, remote, incoming, force, options.linkOptions) if err != nil { From 85eec5ba8e63116632a408aa6aa27937fc55b09a Mon Sep 17 00:00:00 2001 From: Arceliar Date: Mon, 25 May 2020 19:13:37 -0500 Subject: [PATCH 26/35] tcp ygg-over-ygg debug logging --- src/yggdrasil/tcp.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index 83305ab6..129fc0ee 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -407,6 +407,7 @@ func (t *tcp) handler(sock net.Conn, incoming bool, options tcpOptions) { if laddr.IsValid() || lsubnet.IsValid() { // The local address is with the network address/prefix range // This would route ygg over ygg, which we don't want + t.link.core.log.Debugln("Dropping ygg-tunneled connection", local, remote) return } } From 48f008a8e2ef2b36280e7dcbfac89bbc18d503f8 Mon Sep 17 00:00:00 2001 From: George <57254463+zhoreeq@users.noreply.github.com> Date: Mon, 6 Jul 2020 09:14:34 -0400 Subject: [PATCH 27/35] Implement Core.RemovePeer method (#699) (#709) Co-authored-by: George --- src/admin/admin.go | 27 ++++++++++++++++++++++++--- src/yggdrasil/api.go | 29 ++++++++++++++++++++++++----- 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/src/admin/admin.go b/src/admin/admin.go index 8028bcc6..5ea5ab89 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -189,22 +189,43 @@ func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) { }, errors.New("Failed to add peer") } }) - a.AddHandler("removePeer", []string{"port"}, func(in Info) (Info, error) { + a.AddHandler("disconnectPeer", []string{"port"}, func(in Info) (Info, error) { port, err := strconv.ParseInt(fmt.Sprint(in["port"]), 10, 64) if err != nil { return Info{}, err } if a.core.DisconnectPeer(uint64(port)) == nil { return Info{ - "removed": []string{ + "disconnected": []string{ fmt.Sprint(port), }, }, nil } else { return Info{ - "not_removed": []string{ + "not_disconnected": []string{ fmt.Sprint(port), }, + }, errors.New("Failed to disconnect peer") + } + }) + a.AddHandler("removePeer", []string{"uri", "[interface]"}, func(in Info) (Info, error) { + // Set sane defaults + intf := "" + // Has interface been specified? + if itf, ok := in["interface"]; ok { + intf = itf.(string) + } + if a.core.RemovePeer(in["uri"].(string), intf) == nil { + return Info{ + "removed": []string{ + in["uri"].(string), + }, + }, nil + } else { + return Info{ + "not_removed": []string{ + in["uri"].(string), + }, }, errors.New("Failed to remove peer") } }) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 82d0aa93..be905cbc 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -468,12 +468,31 @@ func (c *Core) AddPeer(addr string, sintf string) error { return nil } -// RemovePeer is not implemented yet. func (c *Core) RemovePeer(addr string, sintf string) error { - // TODO: Implement a reverse of AddPeer, where we look up the port number - // based on the addr and sintf, disconnect it and then remove it from the - // peers list so we don't reconnect to it later - return errors.New("not implemented") + if sintf == "" { + for i, peer := range c.config.Current.Peers { + if peer == addr { + c.config.Current.Peers = append(c.config.Current.Peers[:i], c.config.Current.Peers[i+1:]...) + break + } + } + } else if _, ok := c.config.Current.InterfacePeers[sintf]; ok { + for i, peer := range c.config.Current.InterfacePeers[sintf] { + if peer == addr { + c.config.Current.InterfacePeers[sintf] = append(c.config.Current.InterfacePeers[sintf][:i], c.config.Current.InterfacePeers[sintf][i+1:]...) + break + } + } + } + + ports := c.peers.ports.Load().(map[switchPort]*peer) + for p, peer := range ports { + if addr == peer.intf.name { + c.peers.removePeer(p) + } + } + + return nil } // CallPeer calls a peer once. This should be specified in the peer URI format, From 33e36794587305a578c4ebe83e79bae8c2f7ea77 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sun, 27 Sep 2020 07:16:51 -0500 Subject: [PATCH 28/35] multicast, use the prebuilt interface map when checking active listeners (#707) --- src/multicast/multicast.go | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 7de3cfab..33cb29ab 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -259,17 +259,13 @@ func (m *Multicast) _announce() { continue } // Find the interface that matches the listener - if intf, err := net.InterfaceByName(name); err == nil { - if addrs, err := intf.Addrs(); err == nil { - // Loop through the addresses attached to that listener and see if any - // of them match the current address of the listener - for _, addr := range addrs { - if ip, _, err := net.ParseCIDR(addr.String()); err == nil { - // Does the interface address match our listener address? - if ip.Equal(listenaddr.IP) { - found = true - break - } + if info, ok := m._interfaces[name]; ok { + for _, addr := range info.addrs { + if ip, _, err := net.ParseCIDR(addr.String()); err == nil { + // Does the interface address match our listener address? + if ip.Equal(listenaddr.IP) { + found = true + break } } } From fcb6f5ca365e7cb23ff89ee21fad3deb91b926c7 Mon Sep 17 00:00:00 2001 From: Ryan Westlund Date: Sun, 27 Sep 2020 08:22:49 -0400 Subject: [PATCH 29/35] Set default conf file on FreeBSD to /usr/local/etc/yggdrasil.conf (#717) --- src/defaults/defaults_freebsd.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/defaults/defaults_freebsd.go b/src/defaults/defaults_freebsd.go index b08d80d0..a57be2fa 100644 --- a/src/defaults/defaults_freebsd.go +++ b/src/defaults/defaults_freebsd.go @@ -10,7 +10,7 @@ func GetDefaults() platformDefaultParameters { DefaultAdminListen: "unix:///var/run/yggdrasil.sock", // Configuration (used for yggdrasilctl) - DefaultConfigFile: "/etc/yggdrasil.conf", + DefaultConfigFile: "/usr/local/etc/yggdrasil.conf", // Multicast interfaces DefaultMulticastInterfaces: []string{ From 7588a55e84c81a4d152e0f438db54511961028fa Mon Sep 17 00:00:00 2001 From: asymmetric Date: Sun, 27 Sep 2020 15:24:19 +0300 Subject: [PATCH 30/35] README: mention Nix package (#689) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dcc9bd91..aa4a46c1 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ some of the below: - Linux - `.deb` and `.rpm` packages are built by CI for Debian and Red Hat-based distributions - - Void and Arch packages also available within their respective repositories + - Arch, Nix, Void packages also available within their respective repositories - macOS - `.pkg` packages are built by CI - Ubiquiti EdgeOS From e09ca6a089cbb138e7743931f34fe766bf062bd6 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 6 Jul 2020 14:21:28 +0100 Subject: [PATCH 31/35] Fix build --- src/yggdrasil/api.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index be905cbc..4232f3c4 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -485,12 +485,14 @@ func (c *Core) RemovePeer(addr string, sintf string) error { } } - ports := c.peers.ports.Load().(map[switchPort]*peer) - for p, peer := range ports { - if addr == peer.intf.name { - c.peers.removePeer(p) + c.peers.Act(nil, func() { + ports := c.peers.ports + for _, peer := range ports { + if addr == peer.intf.name() { + c.peers._removePeer(peer) + } } - } + }) return nil } From 48bf0ce2109542d2fa010d6878bcd4a3f40307ba Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 27 Sep 2020 13:28:13 +0100 Subject: [PATCH 32/35] Revert "Fix build" This reverts commit e09ca6a089cbb138e7743931f34fe766bf062bd6. --- src/yggdrasil/api.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index 4232f3c4..be905cbc 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -485,14 +485,12 @@ func (c *Core) RemovePeer(addr string, sintf string) error { } } - c.peers.Act(nil, func() { - ports := c.peers.ports - for _, peer := range ports { - if addr == peer.intf.name() { - c.peers._removePeer(peer) - } + ports := c.peers.ports.Load().(map[switchPort]*peer) + for p, peer := range ports { + if addr == peer.intf.name { + c.peers.removePeer(p) } - }) + } return nil } From 1492738c9e05ad77accea0dbbdd275bc98d7d4b8 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 27 Sep 2020 14:28:25 +0100 Subject: [PATCH 33/35] golangci-lint in CI (#733) * golangci-lint in CI * Put CI in own job * Run verify job * Use go get * Fix typo * Name lint instead of verify * Read the config * Use debug tag * Tweaks --- .circleci/config.yml | 26 ++++++++- .golangci.yml | 10 ++++ contrib/config/yggdrasilconf.go | 97 --------------------------------- misc/sim/treesim.go | 28 ++++++---- 4 files changed, 49 insertions(+), 112 deletions(-) create mode 100644 .golangci.yml delete mode 100644 contrib/config/yggdrasilconf.go diff --git a/.circleci/config.yml b/.circleci/config.yml index 3f555df5..c1c8cf4a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,6 +3,19 @@ # Check https://circleci.com/docs/2.0/language-go/ for more details version: 2.1 jobs: + lint: + docker: + - image: circleci/golang:1.14.1 + + steps: + - checkout + + - run: + name: Run golangci-lint + command: | + go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.31.0 + golangci-lint run + build-linux: docker: - image: circleci/golang:1.14.1 @@ -201,9 +214,16 @@ workflows: version: 2.1 build: jobs: - - build-linux - - build-macos - - build-other + - lint + - build-linux: + requires: + - lint + - build-macos: + requires: + - lint + - build-other: + requires: + - lint - upload: requires: - build-linux diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 00000000..c35edee4 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,10 @@ +run: + build-tags: + - lint + issues-exit-code: 0 # TODO: change this to 1 when we want it to fail builds + skip-dirs: + - contrib/ + - misc/ +linters: + disable: + - gocyclo \ No newline at end of file diff --git a/contrib/config/yggdrasilconf.go b/contrib/config/yggdrasilconf.go deleted file mode 100644 index ad55e163..00000000 --- a/contrib/config/yggdrasilconf.go +++ /dev/null @@ -1,97 +0,0 @@ -package main - -/* -This is a small utility that is designed to accompany the vyatta-yggdrasil -package. It takes a HJSON configuration file, makes changes to it based on -the command line arguments, and then spits out an updated file. -*/ - -import ( - "bytes" - "encoding/json" - "flag" - "fmt" - "io/ioutil" - "strconv" - - "github.com/hjson/hjson-go" - "golang.org/x/text/encoding/unicode" - - "github.com/yggdrasil-network/yggdrasil-go/src/config" -) - -type nodeConfig = config.NodeConfig - -func main() { - useconffile := flag.String("useconffile", "/etc/yggdrasil.conf", "update config at specified file path") - flag.Parse() - cfg := nodeConfig{} - var config []byte - var err error - config, err = ioutil.ReadFile(*useconffile) - if err != nil { - panic(err) - } - if bytes.Compare(config[0:2], []byte{0xFF, 0xFE}) == 0 || - bytes.Compare(config[0:2], []byte{0xFE, 0xFF}) == 0 { - utf := unicode.UTF16(unicode.BigEndian, unicode.UseBOM) - decoder := utf.NewDecoder() - config, err = decoder.Bytes(config) - if err != nil { - panic(err) - } - } - var dat map[string]interface{} - if err := hjson.Unmarshal(config, &dat); err != nil { - panic(err) - } - confJson, err := json.Marshal(dat) - if err != nil { - panic(err) - } - json.Unmarshal(confJson, &cfg) - switch flag.Arg(0) { - case "setMTU": - cfg.IfMTU, err = strconv.Atoi(flag.Arg(1)) - if err != nil { - cfg.IfMTU = 1280 - } - if mtu, _ := strconv.Atoi(flag.Arg(1)); mtu < 1280 { - cfg.IfMTU = 1280 - } - case "setIfName": - cfg.IfName = flag.Arg(1) - case "setListen": - cfg.Listen = flag.Arg(1) - case "setAdminListen": - cfg.AdminListen = flag.Arg(1) - case "setIfTapMode": - if flag.Arg(1) == "true" { - cfg.IfTAPMode = true - } else { - cfg.IfTAPMode = false - } - case "addPeer": - found := false - for _, v := range cfg.Peers { - if v == flag.Arg(1) { - found = true - } - } - if !found { - cfg.Peers = append(cfg.Peers, flag.Arg(1)) - } - case "removePeer": - for k, v := range cfg.Peers { - if v == flag.Arg(1) { - cfg.Peers = append(cfg.Peers[:k], cfg.Peers[k+1:]...) - } - } - } - bs, err := hjson.Marshal(cfg) - if err != nil { - panic(err) - } - fmt.Println(string(bs)) - return -} diff --git a/misc/sim/treesim.go b/misc/sim/treesim.go index 2b155151..22cf881f 100644 --- a/misc/sim/treesim.go +++ b/misc/sim/treesim.go @@ -1,20 +1,24 @@ +// +build !lint + package main -import "fmt" -import "bufio" -import "os" -import "strings" -import "strconv" -import "time" +import ( + "bufio" + "flag" + "fmt" + "os" + "runtime" + "runtime/pprof" + "strconv" + "strings" + "time" -import "runtime" -import "runtime/pprof" -import "flag" + "github.com/gologme/log" -import "github.com/gologme/log" + . "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" -import . "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" -import . "github.com/yggdrasil-network/yggdrasil-go/src/crypto" + . "github.com/yggdrasil-network/yggdrasil-go/src/crypto" +) //////////////////////////////////////////////////////////////////////////////// From d6d2d9c19a9fddad0987252f33b471a25c4a7746 Mon Sep 17 00:00:00 2001 From: Ryan Westlund Date: Sun, 27 Sep 2020 09:42:46 -0400 Subject: [PATCH 34/35] Accept some golint suggestions (#690) * Fixed some linter issues * Simplified isBetter method * Accept some linter suggestions * Fix typo Co-authored-by: klesomik Co-authored-by: Neil Alexander --- cmd/genkeys/main.go | 21 +++++++-------- cmd/yggdrasil/main.go | 10 +++---- cmd/yggdrasilctl/main.go | 4 +-- src/admin/admin.go | 56 +++++++++++++++++++-------------------- src/crypto/crypto.go | 2 +- src/tuntap/admin.go | 12 +++------ src/tuntap/iface.go | 1 - src/util/cancellation.go | 9 +++---- src/util/util.go | 3 +-- src/yggdrasil/conn.go | 8 +++--- src/yggdrasil/link.go | 19 +++++++------ src/yggdrasil/nodeinfo.go | 6 ++--- src/yggdrasil/switch.go | 4 +-- src/yggdrasil/tcp.go | 8 +++--- src/yggdrasil/version.go | 6 ++--- 15 files changed, 75 insertions(+), 94 deletions(-) diff --git a/cmd/genkeys/main.go b/cmd/genkeys/main.go index 7b48cdc7..cfab7ec1 100644 --- a/cmd/genkeys/main.go +++ b/cmd/genkeys/main.go @@ -51,7 +51,7 @@ func main() { for { newKey := <-newKeys - if isBetter(currentBest[:], newKey.id[:]) || len(currentBest) == 0 { + if isBetter(currentBest, newKey.id[:]) || len(currentBest) == 0 { currentBest = newKey.id for _, channel := range threadChannels { select { @@ -61,13 +61,13 @@ func main() { fmt.Println("--------------------------------------------------------------------------------") switch { case *doSig: - fmt.Println("sigPriv:", hex.EncodeToString(newKey.priv[:])) - fmt.Println("sigPub:", hex.EncodeToString(newKey.pub[:])) - fmt.Println("TreeID:", hex.EncodeToString(newKey.id[:])) + fmt.Println("sigPriv:", hex.EncodeToString(newKey.priv)) + fmt.Println("sigPub:", hex.EncodeToString(newKey.pub)) + fmt.Println("TreeID:", hex.EncodeToString(newKey.id)) default: - fmt.Println("boxPriv:", hex.EncodeToString(newKey.priv[:])) - fmt.Println("boxPub:", hex.EncodeToString(newKey.pub[:])) - fmt.Println("NodeID:", hex.EncodeToString(newKey.id[:])) + fmt.Println("boxPriv:", hex.EncodeToString(newKey.priv)) + fmt.Println("boxPub:", hex.EncodeToString(newKey.pub)) + fmt.Println("NodeID:", hex.EncodeToString(newKey.id)) fmt.Println("IP:", newKey.ip) } } @@ -76,11 +76,8 @@ func main() { func isBetter(oldID, newID []byte) bool { for idx := range oldID { - if newID[idx] > oldID[idx] { - return true - } - if newID[idx] < oldID[idx] { - return false + if newID[idx] != oldID[idx] { + return newID[idx] > oldID[idx] } } return false diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 813e950b..14b76c34 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -60,8 +60,8 @@ func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *config // throwing everywhere when it's converting things into UTF-16 for the hell // of it - remove it and decode back down into UTF-8. This is necessary // because hjson doesn't know what to do with UTF-16 and will panic - if bytes.Compare(conf[0:2], []byte{0xFF, 0xFE}) == 0 || - bytes.Compare(conf[0:2], []byte{0xFE, 0xFF}) == 0 { + if bytes.Equal(conf[0:2], []byte{0xFF, 0xFE}) || + bytes.Equal(conf[0:2], []byte{0xFE, 0xFF}) { utf := unicode.UTF16(unicode.BigEndian, unicode.UseBOM) decoder := utf.NewDecoder() conf, err = decoder.Bytes(conf) @@ -222,7 +222,7 @@ func main() { getNodeID := func() *crypto.NodeID { if pubkey, err := hex.DecodeString(cfg.EncryptionPublicKey); err == nil { var box crypto.BoxPubKey - copy(box[:], pubkey[:]) + copy(box[:], pubkey) return crypto.GetNodeID(&box) } return nil @@ -328,9 +328,9 @@ func main() { // deferred Stop function above will run which will shut down TUN/TAP. for { select { - case _ = <-c: + case <-c: goto exit - case _ = <-r: + case <-r: if *useconffile != "" { cfg = readConfig(useconf, useconffile, normaliseconf) logger.Infoln("Reloading configuration from", *useconffile) diff --git a/cmd/yggdrasilctl/main.go b/cmd/yggdrasilctl/main.go index 697a3faa..38d935a6 100644 --- a/cmd/yggdrasilctl/main.go +++ b/cmd/yggdrasilctl/main.go @@ -78,8 +78,8 @@ func run() int { if *server == endpoint { if config, err := ioutil.ReadFile(defaults.GetDefaults().DefaultConfigFile); err == nil { - if bytes.Compare(config[0:2], []byte{0xFF, 0xFE}) == 0 || - bytes.Compare(config[0:2], []byte{0xFE, 0xFF}) == 0 { + if bytes.Equal(config[0:2], []byte{0xFF, 0xFE}) || + bytes.Equal(config[0:2], []byte{0xFE, 0xFF}) { utf := unicode.UTF16(unicode.BigEndian, unicode.UseBOM) decoder := utf.NewDecoder() config, err = decoder.Bytes(config) diff --git a/src/admin/admin.go b/src/admin/admin.go index 5ea5ab89..bdb7cd3a 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -53,7 +53,7 @@ func (a *AdminSocket) AddHandler(name string, args []string, handlerfunc func(In return nil } -// init runs the initial admin setup. +// Init runs the initial admin setup. func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) error { a.core = c a.log = log @@ -181,13 +181,12 @@ func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) { in["uri"].(string), }, }, nil - } else { - return Info{ - "not_added": []string{ - in["uri"].(string), - }, - }, errors.New("Failed to add peer") } + return Info{ + "not_added": []string{ + in["uri"].(string), + }, + }, errors.New("Failed to add peer") }) a.AddHandler("disconnectPeer", []string{"port"}, func(in Info) (Info, error) { port, err := strconv.ParseInt(fmt.Sprint(in["port"]), 10, 64) @@ -228,6 +227,11 @@ func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) { }, }, errors.New("Failed to remove peer") } + return Info{ + "not_removed": []string{ + in["uri"].(string), + }, + }, errors.New("Failed to remove peer") }) a.AddHandler("getAllowedEncryptionPublicKeys", []string{}, func(in Info) (Info, error) { return Info{"allowed_box_pubs": a.core.GetAllowedEncryptionPublicKeys()}, nil @@ -239,13 +243,12 @@ func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) { in["box_pub_key"].(string), }, }, nil - } else { - return Info{ - "not_added": []string{ - in["box_pub_key"].(string), - }, - }, errors.New("Failed to add allowed key") } + return Info{ + "not_added": []string{ + in["box_pub_key"].(string), + }, + }, errors.New("Failed to add allowed key") }) a.AddHandler("removeAllowedEncryptionPublicKey", []string{"box_pub_key"}, func(in Info) (Info, error) { if a.core.RemoveAllowedEncryptionPublicKey(in["box_pub_key"].(string)) == nil { @@ -254,13 +257,12 @@ func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) { in["box_pub_key"].(string), }, }, nil - } else { - return Info{ - "not_removed": []string{ - in["box_pub_key"].(string), - }, - }, errors.New("Failed to remove allowed key") } + return Info{ + "not_removed": []string{ + in["box_pub_key"].(string), + }, + }, errors.New("Failed to remove allowed key") }) a.AddHandler("dhtPing", []string{"box_pub_key", "coords", "[target]"}, func(in Info) (Info, error) { var reserr error @@ -271,10 +273,10 @@ func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) { coords := util.DecodeCoordString(in["coords"].(string)) var boxPubKey crypto.BoxPubKey if b, err := hex.DecodeString(in["box_pub_key"].(string)); err == nil { - copy(boxPubKey[:], b[:]) + copy(boxPubKey[:], b) if n, err := hex.DecodeString(in["target"].(string)); err == nil { var targetNodeID crypto.NodeID - copy(targetNodeID[:], n[:]) + copy(targetNodeID[:], n) result, reserr = a.core.DHTPing(boxPubKey, coords, &targetNodeID) } else { result, reserr = a.core.DHTPing(boxPubKey, coords, nil) @@ -308,14 +310,13 @@ func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) { var jsoninfo interface{} if err := json.Unmarshal(nodeinfo, &jsoninfo); err != nil { return Info{}, err - } else { - return Info{"nodeinfo": jsoninfo}, nil } + return Info{"nodeinfo": jsoninfo}, nil } else if in["box_pub_key"] == nil || in["coords"] == nil { return Info{}, errors.New("Expecting both box_pub_key and coords") } else { if b, err := hex.DecodeString(in["box_pub_key"].(string)); err == nil { - copy(boxPubKey[:], b[:]) + copy(boxPubKey[:], b) } else { return Info{}, err } @@ -326,12 +327,10 @@ func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) { var m map[string]interface{} if err = json.Unmarshal(result, &m); err == nil { return Info{"nodeinfo": m}, nil - } else { - return Info{}, err } - } else { return Info{}, err } + return Info{}, err }) } @@ -354,9 +353,8 @@ func (a *AdminSocket) Stop() error { if a.listener != nil { a.started = false return a.listener.Close() - } else { - return nil } + return nil } // listen is run by start and manages API connections. diff --git a/src/crypto/crypto.go b/src/crypto/crypto.go index 211a0e54..dd4013a7 100644 --- a/src/crypto/crypto.go +++ b/src/crypto/crypto.go @@ -272,7 +272,7 @@ func (n *BoxNonce) Increment() { n[len(n)-1] += 2 for i := len(n) - 2; i >= 0; i-- { if n[i+1] < oldNonce[i+1] { - n[i] += 1 + n[i]++ } } } diff --git a/src/tuntap/admin.go b/src/tuntap/admin.go index c7fc20b0..eb1eb69b 100644 --- a/src/tuntap/admin.go +++ b/src/tuntap/admin.go @@ -68,16 +68,14 @@ func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) { a.AddHandler("addLocalSubnet", []string{"subnet"}, func(in admin.Info) (admin.Info, error) { if err := t.ckr.addLocalSubnet(in["subnet"].(string)); err == nil { return admin.Info{"added": []string{in["subnet"].(string)}}, nil - } else { - return admin.Info{"not_added": []string{in["subnet"].(string)}}, errors.New("Failed to add source subnet") } + return admin.Info{"not_added": []string{in["subnet"].(string)}}, errors.New("Failed to add source subnet") }) a.AddHandler("addRemoteSubnet", []string{"subnet", "box_pub_key"}, func(in admin.Info) (admin.Info, error) { if err := t.ckr.addRemoteSubnet(in["subnet"].(string), in["box_pub_key"].(string)); err == nil { return admin.Info{"added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil - } else { - return admin.Info{"not_added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to add route") } + return admin.Info{"not_added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to add route") }) a.AddHandler("getSourceSubnets", []string{}, func(in admin.Info) (admin.Info, error) { var subnets []string @@ -104,15 +102,13 @@ func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) { a.AddHandler("removeLocalSubnet", []string{"subnet"}, func(in admin.Info) (admin.Info, error) { if err := t.ckr.removeLocalSubnet(in["subnet"].(string)); err == nil { return admin.Info{"removed": []string{in["subnet"].(string)}}, nil - } else { - return admin.Info{"not_removed": []string{in["subnet"].(string)}}, errors.New("Failed to remove source subnet") } + return admin.Info{"not_removed": []string{in["subnet"].(string)}}, errors.New("Failed to remove source subnet") }) a.AddHandler("removeRemoteSubnet", []string{"subnet", "box_pub_key"}, func(in admin.Info) (admin.Info, error) { if err := t.ckr.removeRemoteSubnet(in["subnet"].(string), in["box_pub_key"].(string)); err == nil { return admin.Info{"removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil - } else { - return admin.Info{"not_removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to remove route") } + return admin.Info{"not_removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to remove route") }) } diff --git a/src/tuntap/iface.go b/src/tuntap/iface.go index 1e5902e8..bd849c7f 100644 --- a/src/tuntap/iface.go +++ b/src/tuntap/iface.go @@ -199,7 +199,6 @@ func (tun *TunAdapter) _handlePacket(recvd []byte, err error) { tc.writeFrom(nil, packet) } }) - return }() } } diff --git a/src/util/cancellation.go b/src/util/cancellation.go index 1f6d1658..7fc83147 100644 --- a/src/util/cancellation.go +++ b/src/util/cancellation.go @@ -54,12 +54,11 @@ func (c *cancellation) Cancel(err error) error { defer c.mutex.Unlock() if c.done { return c.err - } else { - c.err = err - c.done = true - close(c.cancel) - return nil } + c.err = err + c.done = true + close(c.cancel) + return nil } // Error returns the error provided to Cancel, or nil if no error has been provided. diff --git a/src/util/util.go b/src/util/util.go index c20421d5..d7a7443d 100644 --- a/src/util/util.go +++ b/src/util/util.go @@ -30,9 +30,8 @@ func UnlockThread() { func ResizeBytes(bs []byte, length int) []byte { if cap(bs) >= length { return bs[:length] - } else { - return make([]byte, length) } + return make([]byte, length) } // TimerStop stops a timer and makes sure the channel is drained, returns true if the timer was stopped before firing. diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 4cd8b205..1b7a5a1e 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -167,10 +167,9 @@ func (c *Conn) _getDeadlineCancellation(t *time.Time) (util.Cancellation, bool) // A deadline is set, so return a Cancellation that uses it c := util.CancellationWithDeadline(c.session.cancel, *t) return c, true - } else { - // No deadline was set, so just return the existing cancellation and a dummy value - return c.session.cancel, false } + // No deadline was set, so just return the existing cancellation and a dummy value + return c.session.cancel, false } // SetReadCallback allows you to specify a function that will be called whenever @@ -225,9 +224,8 @@ func (c *Conn) readNoCopy() ([]byte, error) { case <-cancel.Finished(): if cancel.Error() == util.CancellationTimeoutError { return nil, ConnError{errors.New("read timeout"), true, false, false, 0} - } else { - return nil, ConnError{errors.New("session closed"), false, false, true, 0} } + return nil, ConnError{errors.New("session closed"), false, false, true, 0} case bs := <-c.readBuffer: return bs, nil } diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 0b37eaa2..01d4aff4 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -253,17 +253,16 @@ func (intf *linkInterface) handler() error { <-oldIntf.closed } return nil - } else { - intf.closed = make(chan struct{}) - intf.link.interfaces[intf.info] = intf - defer func() { - intf.link.mutex.Lock() - delete(intf.link.interfaces, intf.info) - intf.link.mutex.Unlock() - close(intf.closed) - }() - intf.link.core.log.Debugln("DEBUG: registered interface for", intf.name) } + intf.closed = make(chan struct{}) + intf.link.interfaces[intf.info] = intf + defer func() { + intf.link.mutex.Lock() + delete(intf.link.interfaces, intf.info) + intf.link.mutex.Unlock() + close(intf.closed) + }() + intf.link.core.log.Debugln("DEBUG: registered interface for", intf.name) intf.link.mutex.Unlock() // Create peer shared := crypto.GetSharedKey(myLinkPriv, &meta.link) diff --git a/src/yggdrasil/nodeinfo.go b/src/yggdrasil/nodeinfo.go index fc6250d6..8d502f41 100644 --- a/src/yggdrasil/nodeinfo.go +++ b/src/yggdrasil/nodeinfo.go @@ -136,15 +136,15 @@ func (m *nodeinfo) _setNodeInfo(given interface{}, privacy bool) error { newnodeinfo[key] = value } } - if newjson, err := json.Marshal(newnodeinfo); err == nil { + newjson, err := json.Marshal(newnodeinfo) + if err == nil { if len(newjson) > 16384 { return errors.New("NodeInfo exceeds max length of 16384 bytes") } m.myNodeInfo = newjson return nil - } else { - return err } + return err } // Add nodeinfo into the cache for a node diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 5e4d3e92..23298e76 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -532,7 +532,6 @@ func (t *switchTable) unlockedHandleMsg(msg *switchMsg, fromPort switchPort, rep if true || doUpdate { t.updater.Store(&sync.Once{}) } - return } //////////////////////////////////////////////////////////////////////////////// @@ -615,9 +614,8 @@ func (t *switchTable) portIsCloser(dest []byte, port switchPort) bool { theirDist := info.locator.dist(dest) myDist := table.self.dist(dest) return theirDist < myDist - } else { - return false } + return false } // Get the coords of a packet without decoding diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index 129fc0ee..2bc81de1 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -205,10 +205,9 @@ func (t *tcp) listener(l *TcpListener, listenaddr string) { t.mutex.Unlock() l.Listener.Close() return - } else { - t.listeners[listenaddr] = l - t.mutex.Unlock() } + t.listeners[listenaddr] = l + t.mutex.Unlock() // And here we go! defer func() { t.link.core.log.Infoln("Stopping TCP listener on:", l.Listener.Addr().String()) @@ -375,9 +374,8 @@ func (t *tcp) handler(sock net.Conn, incoming bool, options tcpOptions) { if sock, err = options.upgrade.upgrade(sock); err != nil { t.link.core.log.Errorln("TCP handler upgrade failed:", err) return - } else { - upgraded = true } + upgraded = true } stream := stream{} stream.init(sock) diff --git a/src/yggdrasil/version.go b/src/yggdrasil/version.go index 91fcc825..e0cb38e3 100644 --- a/src/yggdrasil/version.go +++ b/src/yggdrasil/version.go @@ -28,11 +28,11 @@ func version_getBaseMetadata() version_metadata { } } -// Gest the length of the metadata for this version, used to know how many bytes to read from the start of a connection. +// Gets the length of the metadata for this version, used to know how many bytes to read from the start of a connection. func version_getMetaLength() (mlen int) { mlen += 4 // meta - mlen += 1 // ver, as long as it's < 127, which it is in this version - mlen += 1 // minorVer, as long as it's < 127, which it is in this version + mlen++ // ver, as long as it's < 127, which it is in this version + mlen++ // minorVer, as long as it's < 127, which it is in this version mlen += crypto.BoxPubKeyLen // box mlen += crypto.SigPubKeyLen // sig mlen += crypto.BoxPubKeyLen // link From ba7be10a2f958ccd34f42bba21dde8b34d4042ae Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 27 Sep 2020 15:05:14 +0100 Subject: [PATCH 35/35] Update changelog --- CHANGELOG.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b0216eb..1017e8d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,27 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - in case of vulnerabilities. --> +## [0.3.15] - 2020-09-27 +### Added +- Support for pinning remote public keys in peering strings has been added, e.g. + - By signing public key: `tcp://host:port?ed25519=key` + - By encryption public key: `tcp://host:port?curve25519=key` + - By both: `tcp://host:port?ed25519=key&curve25519=key` + - By multiple, in case of DNS round-robin or similar: `tcp://host:port?curve25519=key&curve25519=key&ed25519=key&ed25519=key` +- Some checks to prevent Yggdrasil-over-Yggdrasil peerings have been added +- Added support for SOCKS proxy authentication, e.g. `socks://user@password:host/...` + +### Fixed +- Some bugs in the multicast code that could cause unnecessary CPU usage have been fixed +- A possible multicast deadlock on macOS when enumerating interfaces has been fixed +- A deadlock in the connection code has been fixed +- Updated HJSON dependency that caused some build problems + +### Changed +- `DisconnectPeer` and `RemovePeer` have been separated and implemented properly now +- Less nodes are stored in the DHT now, reducing ambient network traffic and possible instability +- Default config file for FreeBSD is now at `/usr/local/etc/yggdrasil.conf` instead of `/etc/yggdrasil.conf` + ## [0.3.14] - 2020-03-28 ### Fixed - Fixes a memory leak that may occur if packets are incorrectly never removed from a switch queue