From aecc151baf5dd0d38cd06149d0e0dde4e1c3f3e9 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 25 Sep 2018 15:32:45 +0100 Subject: [PATCH 1/7] Add support for specifying TCP source interface, i.e. tcp://a.b.c.d:e/eth0, for multiple simultaneous peerings to the same node over different interfaces --- src/yggdrasil/admin.go | 8 ++++-- src/yggdrasil/multicast.go | 2 +- src/yggdrasil/tcp.go | 53 ++++++++++++++++++++++++++++++-------- 3 files changed, 49 insertions(+), 14 deletions(-) diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index b0d487a3..f9cb703d 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -395,7 +395,11 @@ func (a *admin) addPeer(addr string) error { if err == nil { switch strings.ToLower(u.Scheme) { case "tcp": - a.core.tcp.connect(u.Host) + if len(u.Path) > 1 { + a.core.tcp.connect(u.Host, u.Path[1:]) + } else { + a.core.tcp.connect(u.Host, "") + } case "socks": a.core.tcp.connectSOCKS(u.Host, u.Path[1:]) default: @@ -407,7 +411,7 @@ func (a *admin) addPeer(addr string) error { if strings.HasPrefix(addr, "tcp:") { addr = addr[4:] } - a.core.tcp.connect(addr) + a.core.tcp.connect(addr, "") return nil } return nil diff --git a/src/yggdrasil/multicast.go b/src/yggdrasil/multicast.go index d9d0ccc4..697744cb 100644 --- a/src/yggdrasil/multicast.go +++ b/src/yggdrasil/multicast.go @@ -153,6 +153,6 @@ func (m *multicast) listen() { } addr.Zone = from.Zone saddr := addr.String() - m.core.tcp.connect(saddr) + m.core.tcp.connect(saddr, "") } } diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index d0239184..4c710458 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -64,13 +64,13 @@ func (iface *tcpInterface) getAddr() *net.TCPAddr { } // Attempts to initiate a connection to the provided address. -func (iface *tcpInterface) connect(addr string) { - iface.call(addr, nil) +func (iface *tcpInterface) connect(addr string, intf string) { + iface.call(addr, nil, intf) } // Attempst to initiate a connection to the provided address, viathe provided socks proxy address. func (iface *tcpInterface) connectSOCKS(socksaddr, peeraddr string) { - iface.call(peeraddr, &socksaddr) + iface.call(peeraddr, &socksaddr, "") } // Initializes the struct. @@ -110,20 +110,21 @@ func (iface *tcpInterface) listener() { // 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 (iface *tcpInterface) call(saddr string, socksaddr *string) { +func (iface *tcpInterface) call(saddr string, socksaddr *string, sintf string) { go func() { + callname := fmt.Sprintf("%s/%s", saddr, sintf) quit := false iface.mutex.Lock() - if _, isIn := iface.calls[saddr]; isIn { + if _, isIn := iface.calls[callname]; isIn { quit = true } else { - iface.calls[saddr] = struct{}{} + iface.calls[callname] = struct{}{} defer func() { // Block new calls for a little while, to mitigate livelock scenarios time.Sleep(default_tcp_timeout) time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond) iface.mutex.Lock() - delete(iface.calls, saddr) + delete(iface.calls, callname) iface.mutex.Unlock() }() } @@ -151,7 +152,36 @@ func (iface *tcpInterface) call(saddr string, socksaddr *string) { }, } } else { - conn, err = net.Dial("tcp", saddr) + dialer := net.Dialer{} + if sintf != "" { + ief, err := net.InterfaceByName(sintf) + if err == nil { + addrs, err := ief.Addrs() + if err == nil { + dst, err := net.ResolveTCPAddr("tcp", saddr) + if err != nil { + return + } + for _, addr := range addrs { + src, _, err := net.ParseCIDR(addr.String()) + if err != nil { + continue + } + if (src.To4() != nil) == (dst.IP.To4() != nil) && src.IsGlobalUnicast() { + dialer.LocalAddr = &net.TCPAddr{ + IP: src, + Port: 0, + } + } + } + if dialer.LocalAddr == nil { + iface.core.log.Println("No valid source address found for interface", sintf) + return + } + } + } + } + conn, err = dialer.Dial("tcp", saddr) if err != nil { return } @@ -307,17 +337,18 @@ func (iface *tcpInterface) handler(sock net.Conn, incoming bool) { // Put all of our cleanup here... p.core.peers.removePeer(p.port) }() + us, _, _ := net.SplitHostPort(sock.LocalAddr().String()) them, _, _ := net.SplitHostPort(sock.RemoteAddr().String()) themNodeID := getNodeID(&info.box) themAddr := address_addrForNodeID(themNodeID) themAddrString := net.IP(themAddr[:]).String() themString := fmt.Sprintf("%s@%s", themAddrString, them) - iface.core.log.Println("Connected:", themString) + iface.core.log.Println("Connected:", themString, "source", us) err = iface.reader(sock, in) // In this goroutine, because of defers if err == nil { - iface.core.log.Println("Disconnected:", themString) + iface.core.log.Println("Disconnected:", themString, "source", us) } else { - iface.core.log.Println("Disconnected:", themString, "with error:", err) + iface.core.log.Println("Disconnected:", themString, "source", us, "with error:", err) } return } From 1796000b05abffc60c89435f7c1ccef7815cdb63 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 25 Sep 2018 16:55:57 +0100 Subject: [PATCH 2/7] Change to InterfacePeers instead of modifying the tcp:// URI format --- src/yggdrasil/admin.go | 14 ++++++-------- src/yggdrasil/config/config.go | 27 ++++++++++++++------------- src/yggdrasil/core.go | 4 ++-- yggdrasil.go | 13 ++++++++++--- 4 files changed, 32 insertions(+), 26 deletions(-) diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index f9cb703d..8c4944a7 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -112,17 +112,19 @@ func (a *admin) init(c *Core, listenaddr string) { } return admin_info{"sessions": sessions}, nil }) - a.addHandler("addPeer", []string{"uri"}, func(in admin_info) (admin_info, error) { - if a.addPeer(in["uri"].(string)) == nil { + a.addHandler("addPeer", []string{"uri", "[interface]"}, func(in admin_info) (admin_info, error) { + if a.addPeer(in["uri"].(string), in["interface"].(string)) == nil { return admin_info{ "added": []string{ in["uri"].(string), + in["interface"].(string), }, }, nil } else { return admin_info{ "not_added": []string{ in["uri"].(string), + in["interface"].(string), }, }, errors.New("Failed to add peer") } @@ -390,16 +392,12 @@ func (a *admin) printInfos(infos []admin_nodeInfo) string { } // addPeer triggers a connection attempt to a node. -func (a *admin) addPeer(addr string) error { +func (a *admin) addPeer(addr string, sintf string) error { u, err := url.Parse(addr) if err == nil { switch strings.ToLower(u.Scheme) { case "tcp": - if len(u.Path) > 1 { - a.core.tcp.connect(u.Host, u.Path[1:]) - } else { - a.core.tcp.connect(u.Host, "") - } + a.core.tcp.connect(u.Host, sintf) case "socks": a.core.tcp.connectSOCKS(u.Host, u.Path[1:]) default: diff --git a/src/yggdrasil/config/config.go b/src/yggdrasil/config/config.go index 2498193b..8c23c622 100644 --- a/src/yggdrasil/config/config.go +++ b/src/yggdrasil/config/config.go @@ -2,19 +2,20 @@ package config // NodeConfig defines all configuration values needed to run a signle yggdrasil node type NodeConfig struct { - Listen string `comment:"Listen address for peer connections. Default is to listen for all\nTCP connections over IPv4 and IPv6 with a random port."` - AdminListen string `comment:"Listen address for admin connections Default is to listen for local\nconnections either on TCP/9001 or a UNIX socket depending on your\nplatform. Use this value for yggdrasilctl -endpoint=X."` - Peers []string `comment:"List of connection strings for static peers in URI format, i.e.\ntcp://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j"` - ReadTimeout int32 `comment:"Read timeout for connections, specified in milliseconds. If less than 6000 and not negative, 6000 (the default) is used. If negative, reads won't time out."` - AllowedEncryptionPublicKeys []string `comment:"List of peer encryption public keys to allow or incoming TCP\nconnections from. If left empty/undefined then all connections\nwill be allowed by default."` - EncryptionPublicKey string `comment:"Your public encryption key. Your peers may ask you for this to put\ninto their AllowedEncryptionPublicKeys configuration."` - EncryptionPrivateKey string `comment:"Your private encryption key. DO NOT share this with anyone!"` - SigningPublicKey string `comment:"Your public signing key. You should not ordinarily need to share\nthis with anyone."` - SigningPrivateKey string `comment:"Your private signing key. DO NOT share this with anyone!"` - MulticastInterfaces []string `comment:"Regular expressions for which interfaces multicast peer discovery\nshould be enabled on. If none specified, multicast peer discovery is\ndisabled. The default value is .* which uses all interfaces."` - IfName string `comment:"Local network interface name for TUN/TAP adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN/TAP."` - IfTAPMode bool `comment:"Set local network interface to TAP mode rather than TUN mode if\nsupported by your platform - option will be ignored if not."` - IfMTU int `comment:"Maximux Transmission Unit (MTU) size for your local TUN/TAP interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."` + Listen string `comment:"Listen address for peer connections. Default is to listen for all\nTCP connections over IPv4 and IPv6 with a random port."` + AdminListen string `comment:"Listen address for admin connections Default is to listen for local\nconnections either on TCP/9001 or a UNIX socket depending on your\nplatform. Use this value for yggdrasilctl -endpoint=X."` + Peers []string `comment:"List of connection strings for static peers in URI format, i.e.\ntcp://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j"` + InterfacePeers map[string][]string `comment:"List of connection strings for static peers in URI format, arranged\nby source interface, i.e. { \"eth0\": [ tcp://a.b.c.d:e ] }"` + ReadTimeout int32 `comment:"Read timeout for connections, specified in milliseconds. If less\nthan 6000 and not negative, 6000 (the default) is used. If negative,\nreads won't time out."` + AllowedEncryptionPublicKeys []string `comment:"List of peer encryption public keys to allow or incoming TCP\nconnections from. If left empty/undefined then all connections\nwill be allowed by default."` + EncryptionPublicKey string `comment:"Your public encryption key. Your peers may ask you for this to put\ninto their AllowedEncryptionPublicKeys configuration."` + EncryptionPrivateKey string `comment:"Your private encryption key. DO NOT share this with anyone!"` + SigningPublicKey string `comment:"Your public signing key. You should not ordinarily need to share\nthis with anyone."` + SigningPrivateKey string `comment:"Your private signing key. DO NOT share this with anyone!"` + MulticastInterfaces []string `comment:"Regular expressions for which interfaces multicast peer discovery\nshould be enabled on. If none specified, multicast peer discovery is\ndisabled. The default value is .* which uses all interfaces."` + IfName string `comment:"Local network interface name for TUN/TAP adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN/TAP."` + IfTAPMode bool `comment:"Set local network interface to TAP mode rather than TUN mode if\nsupported by your platform - option will be ignored if not."` + IfMTU int `comment:"Maximux Transmission Unit (MTU) size for your local TUN/TAP interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."` //Net NetConfig `comment:"Extended options for connecting to peers over other networks."` } diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 35ba2ce3..224bad99 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -182,8 +182,8 @@ func (c *Core) SetLogger(log *log.Logger) { // Adds a peer. This should be specified in the peer URI format, i.e. // tcp://a.b.c.d:e, udp://a.b.c.d:e, socks://a.b.c.d:e/f.g.h.i:j -func (c *Core) AddPeer(addr string) error { - return c.admin.addPeer(addr) +func (c *Core) AddPeer(addr string, sintf string) error { + return c.admin.addPeer(addr, sintf) } // Adds an expression to select multicast interfaces for peer discovery. This diff --git a/yggdrasil.go b/yggdrasil.go index ab237f4c..71028249 100644 --- a/yggdrasil.go +++ b/yggdrasil.go @@ -60,6 +60,7 @@ func generateConfig(isAutoconf bool) *nodeConfig { cfg.SigningPublicKey = hex.EncodeToString(spub[:]) cfg.SigningPrivateKey = hex.EncodeToString(spriv[:]) cfg.Peers = []string{} + cfg.InterfacePeers = map[string][]string{} cfg.AllowedEncryptionPublicKeys = []string{} cfg.MulticastInterfaces = []string{".*"} cfg.IfName = defaults.GetDefaults().DefaultIfName @@ -231,14 +232,20 @@ func main() { // configure them. The loop ensures that disconnected peers will eventually // be reconnected with. go func() { - if len(cfg.Peers) == 0 { + if len(cfg.Peers) == 0 && len(cfg.InterfacePeers) == 0 { return } for { - for _, p := range cfg.Peers { - n.core.AddPeer(p) + for _, peer := range cfg.Peers { + n.core.AddPeer(peer, "") time.Sleep(time.Second) } + for intf, intfpeers := range cfg.InterfacePeers { + for _, peer := range intfpeers { + n.core.AddPeer(peer, intf) + time.Sleep(time.Second) + } + } time.Sleep(time.Minute) } }() From b368421dbddf6d3a9857df21d60aa04337fc3a93 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 25 Sep 2018 17:13:35 +0100 Subject: [PATCH 3/7] Fix addPeer to make interface= optional --- src/yggdrasil/admin.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index 8c4944a7..9f578815 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -113,18 +113,22 @@ func (a *admin) init(c *Core, listenaddr string) { return admin_info{"sessions": sessions}, nil }) a.addHandler("addPeer", []string{"uri", "[interface]"}, func(in admin_info) (admin_info, error) { - if a.addPeer(in["uri"].(string), in["interface"].(string)) == nil { + // Set sane defaults + intf := "" + // Has interface been specified? + if itf, ok := in["interface"]; ok { + intf = itf.(string) + } + if a.addPeer(in["uri"].(string), intf) == nil { return admin_info{ "added": []string{ in["uri"].(string), - in["interface"].(string), }, }, nil } else { return admin_info{ "not_added": []string{ in["uri"].(string), - in["interface"].(string), }, }, errors.New("Failed to add peer") } From 3f8a4ab17dc2a27aa54ec9fddae070bc9fd97120 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 25 Sep 2018 17:59:01 +0100 Subject: [PATCH 4/7] Add bytes_sent and bytes_recvd to getSwitchPorts --- src/yggdrasil/admin.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index 9f578815..46420c5c 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -510,6 +510,8 @@ func (a *admin) getData_getSwitchPeers() []admin_nodeInfo { {"ip", net.IP(addr[:]).String()}, {"coords", fmt.Sprint(coords)}, {"port", elem.port}, + {"bytes_sent", atomic.LoadUint64(&peer.bytesSent)}, + {"bytes_recvd", atomic.LoadUint64(&peer.bytesRecvd)}, } peerInfos = append(peerInfos, info) } From 387ae9ea6c78321f2c7c23693d0ab7ec6d2ea376 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 25 Sep 2018 18:05:57 +0100 Subject: [PATCH 5/7] Only replace call name with interface prefix when interface is set --- src/yggdrasil/tcp.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index 4c710458..4850899d 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -112,7 +112,10 @@ func (iface *tcpInterface) listener() { // This all happens in a separate goroutine that it spawns. func (iface *tcpInterface) call(saddr string, socksaddr *string, sintf string) { go func() { - callname := fmt.Sprintf("%s/%s", saddr, sintf) + callname := saddr + if sintf != "" { + callname = fmt.Sprintf("%s/%s", saddr, sintf) + } quit := false iface.mutex.Lock() if _, isIn := iface.calls[callname]; isIn { From 6844b9df513454297f036d70403f19c55847b84c Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 25 Sep 2018 18:17:00 +0100 Subject: [PATCH 6/7] Update comments in default config --- src/yggdrasil/config/config.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yggdrasil/config/config.go b/src/yggdrasil/config/config.go index 8c23c622..5a081b68 100644 --- a/src/yggdrasil/config/config.go +++ b/src/yggdrasil/config/config.go @@ -4,8 +4,8 @@ package config type NodeConfig struct { Listen string `comment:"Listen address for peer connections. Default is to listen for all\nTCP connections over IPv4 and IPv6 with a random port."` AdminListen string `comment:"Listen address for admin connections Default is to listen for local\nconnections either on TCP/9001 or a UNIX socket depending on your\nplatform. Use this value for yggdrasilctl -endpoint=X."` - Peers []string `comment:"List of connection strings for static peers in URI format, i.e.\ntcp://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j"` - InterfacePeers map[string][]string `comment:"List of connection strings for static peers in URI format, arranged\nby source interface, i.e. { \"eth0\": [ tcp://a.b.c.d:e ] }"` + Peers []string `comment:"List of connection strings for static peers in URI format, i.e.\ntcp://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j."` + InterfacePeers map[string][]string `comment:"List of connection strings for static peers in URI format, arranged\nby source interface, i.e. { \"eth0\": [ tcp://a.b.c.d:e ] }. Note that\nSOCKS peerings will NOT be affected by this option and should go in\nthe \"Peers\" section instead."` ReadTimeout int32 `comment:"Read timeout for connections, specified in milliseconds. If less\nthan 6000 and not negative, 6000 (the default) is used. If negative,\nreads won't time out."` AllowedEncryptionPublicKeys []string `comment:"List of peer encryption public keys to allow or incoming TCP\nconnections from. If left empty/undefined then all connections\nwill be allowed by default."` EncryptionPublicKey string `comment:"Your public encryption key. Your peers may ask you for this to put\ninto their AllowedEncryptionPublicKeys configuration."` From b7f2f8b55c3047d6ce4fc527f1d37fde45c848a0 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 25 Sep 2018 19:46:06 +0100 Subject: [PATCH 7/7] Ignore interfaces that are not up --- src/yggdrasil/tcp.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index 4850899d..8debba75 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -138,6 +138,9 @@ func (iface *tcpInterface) call(saddr string, socksaddr *string, sintf string) { var conn net.Conn var err error if socksaddr != nil { + if sintf != "" { + return + } var dialer proxy.Dialer dialer, err = proxy.SOCKS5("tcp", *socksaddr, nil, proxy.Direct) if err != nil { @@ -159,6 +162,9 @@ func (iface *tcpInterface) call(saddr string, socksaddr *string, sintf string) { if sintf != "" { ief, err := net.InterfaceByName(sintf) if err == nil { + if ief.Flags & net.FlagUp == 0 { + return + } addrs, err := ief.Addrs() if err == nil { dst, err := net.ResolveTCPAddr("tcp", saddr) @@ -175,10 +181,10 @@ func (iface *tcpInterface) call(saddr string, socksaddr *string, sintf string) { IP: src, Port: 0, } + break } } if dialer.LocalAddr == nil { - iface.core.log.Println("No valid source address found for interface", sintf) return } }