From 81839ad50d11ea0cf6d44f2eb5841022a38a1aa6 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 21 Oct 2022 19:49:15 +0100 Subject: [PATCH 01/25] Fix `InterfacePeers` --- src/core/link_tcp.go | 13 +++++-------- src/core/link_tls.go | 3 +-- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/core/link_tcp.go b/src/core/link_tcp.go index c5a73c9e..30749983 100644 --- a/src/core/link_tcp.go +++ b/src/core/link_tcp.go @@ -39,8 +39,7 @@ func (l *linkTCP) dial(url *url.URL, options linkOptions, sintf string) error { if err != nil { return err } - addr.Zone = sintf - dialer, err := l.dialerFor(addr.String(), sintf) + dialer, err := l.dialerFor(addr, sintf) if err != nil { return err } @@ -121,13 +120,11 @@ func (l *linkTCP) getAddr() *net.TCPAddr { return addr } -func (l *linkTCP) dialerFor(saddr, sintf string) (*net.Dialer, error) { - dst, err := net.ResolveTCPAddr("tcp", saddr) - if err != nil { - return nil, err - } +func (l *linkTCP) dialerFor(dst *net.TCPAddr, sintf string) (*net.Dialer, error) { if dst.IP.IsLinkLocalUnicast() { - dst.Zone = sintf + if sintf != "" { + dst.Zone = sintf + } if dst.Zone == "" { return nil, fmt.Errorf("link-local address requires a zone") } diff --git a/src/core/link_tls.go b/src/core/link_tls.go index 1e932b66..2dc2daf1 100644 --- a/src/core/link_tls.go +++ b/src/core/link_tls.go @@ -55,8 +55,7 @@ func (l *linkTLS) dial(url *url.URL, options linkOptions, sintf, sni string) err if err != nil { return err } - addr.Zone = sintf - dialer, err := l.tcp.dialerFor(addr.String(), sintf) + dialer, err := l.tcp.dialerFor(addr, sintf) if err != nil { return err } From 22caddef63b1e46a4fc6e8c9a93e425b391fa723 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 21 Oct 2022 19:49:49 +0100 Subject: [PATCH 02/25] Don't log `duplicate connection attempt` --- src/core/link_socks.go | 2 +- src/core/link_tcp.go | 2 +- src/core/link_tls.go | 2 +- src/core/link_unix.go | 3 +-- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/core/link_socks.go b/src/core/link_socks.go index ad5b8c98..036de992 100644 --- a/src/core/link_socks.go +++ b/src/core/link_socks.go @@ -23,7 +23,7 @@ func (l *links) newLinkSOCKS() *linkSOCKS { func (l *linkSOCKS) dial(url *url.URL, options linkOptions) error { info := linkInfoFor("socks", "", url.Path) if l.links.isConnectedTo(info) { - return fmt.Errorf("duplicate connection attempt") + return nil } proxyAuth := &proxy.Auth{} proxyAuth.User = url.User.Username() diff --git a/src/core/link_tcp.go b/src/core/link_tcp.go index 30749983..6a04bb35 100644 --- a/src/core/link_tcp.go +++ b/src/core/link_tcp.go @@ -33,7 +33,7 @@ func (l *links) newLinkTCP() *linkTCP { func (l *linkTCP) dial(url *url.URL, options linkOptions, sintf string) error { info := linkInfoFor("tcp", sintf, strings.SplitN(url.Host, "%", 2)[0]) if l.links.isConnectedTo(info) { - return fmt.Errorf("duplicate connection attempt") + return nil } addr, err := net.ResolveTCPAddr("tcp", url.Host) if err != nil { diff --git a/src/core/link_tls.go b/src/core/link_tls.go index 2dc2daf1..bc39b6c0 100644 --- a/src/core/link_tls.go +++ b/src/core/link_tls.go @@ -49,7 +49,7 @@ func (l *links) newLinkTLS(tcp *linkTCP) *linkTLS { func (l *linkTLS) dial(url *url.URL, options linkOptions, sintf, sni string) error { info := linkInfoFor("tls", sintf, strings.SplitN(url.Host, "%", 2)[0]) if l.links.isConnectedTo(info) { - return fmt.Errorf("duplicate connection attempt") + return nil } addr, err := net.ResolveTCPAddr("tcp", url.Host) if err != nil { diff --git a/src/core/link_unix.go b/src/core/link_unix.go index e71f9362..2d1b9398 100644 --- a/src/core/link_unix.go +++ b/src/core/link_unix.go @@ -2,7 +2,6 @@ package core import ( "context" - "fmt" "net" "net/url" "time" @@ -36,7 +35,7 @@ func (l *links) newLinkUNIX() *linkUNIX { func (l *linkUNIX) dial(url *url.URL, options linkOptions, _ string) error { info := linkInfoFor("unix", "", url.Path) if l.links.isConnectedTo(info) { - return fmt.Errorf("duplicate connection attempt") + return nil } addr, err := net.ResolveUnixAddr("unix", url.Path) if err != nil { From c55611a478bc725a462db241c56758eb613cd4cf Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 22 Oct 2022 14:56:11 +0100 Subject: [PATCH 03/25] Tweak logging for connections --- src/core/link.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/core/link.go b/src/core/link.go index b4515276..f8e5be22 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -286,7 +286,7 @@ func (intf *link) handler() error { } } if intf.incoming && !intf.force && !isallowed { - intf.links.core.log.Warnf("%s connection from %s forbidden: AllowedEncryptionPublicKeys does not contain key %s", + intf.links.core.log.Warnf("%s connection from %s forbidden: AllowedPublicKeys does not contain key %s", strings.ToUpper(intf.info.linkType), intf.info.remote, hex.EncodeToString(meta.key)) _ = intf.close() return fmt.Errorf("forbidden connection") @@ -302,15 +302,15 @@ func (intf *link) handler() error { intf.links.core.log.Infof("Connected %s: %s, source %s", strings.ToUpper(intf.info.linkType), remoteStr, localStr) - // TODO don't report an error if it's just a 'use of closed network connection' - if err = intf.links.core.HandleConn(meta.key, intf.conn); err != nil && err != io.EOF { - intf.links.core.log.Infof("Disconnected %s: %s, source %s; error: %s", - strings.ToUpper(intf.info.linkType), remoteStr, localStr, err) - } else { + err = intf.links.core.HandleConn(meta.key, intf.conn) + switch err { + case io.EOF, net.ErrClosed, nil: intf.links.core.log.Infof("Disconnected %s: %s, source %s", strings.ToUpper(intf.info.linkType), remoteStr, localStr) + default: + intf.links.core.log.Infof("Disconnected %s: %s, source %s; error: %s", + strings.ToUpper(intf.info.linkType), remoteStr, localStr, err) } - return nil } From 0a1a155e66ace9ee30841348a3eb1cdbafca2264 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 22 Oct 2022 14:56:29 +0100 Subject: [PATCH 04/25] Use `SO_REUSEADDR` instead of `SO_REUSEPORT` on Linux --- src/multicast/multicast_unix.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/multicast/multicast_unix.go b/src/multicast/multicast_unix.go index c59d876b..08230735 100644 --- a/src/multicast/multicast_unix.go +++ b/src/multicast/multicast_unix.go @@ -15,15 +15,19 @@ func (m *Multicast) _multicastStarted() { func (m *Multicast) multicastReuse(network string, address string, c syscall.RawConn) error { var control error - var reuseport error + var reuseaddr error control = c.Control(func(fd uintptr) { - reuseport = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1) + // Previously we used SO_REUSEPORT here, but that meant that machines running + // Yggdrasil nodes as different users would inevitably fail with EADDRINUSE. + // The behaviour for multicast is similar with both, so we'll use SO_REUSEADDR + // instead. + reuseaddr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1) }) switch { - case reuseport != nil: - return reuseport + case reuseaddr != nil: + return reuseaddr default: return control } From 63c4cb5c211aeca37bbe62534bb3675db03a96f6 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 22 Oct 2022 15:47:09 +0100 Subject: [PATCH 05/25] Fix reporting name for TCP --- src/core/link_tcp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/link_tcp.go b/src/core/link_tcp.go index 6a04bb35..a388fcdb 100644 --- a/src/core/link_tcp.go +++ b/src/core/link_tcp.go @@ -82,7 +82,7 @@ func (l *linkTCP) listen(url *url.URL, sintf string) (*Listener, error) { break } addr := conn.RemoteAddr().(*net.TCPAddr) - name := fmt.Sprintf("tls://%s", addr) + name := fmt.Sprintf("tcp://%s", addr) info := linkInfoFor("tcp", sintf, strings.SplitN(addr.IP.String(), "%", 2)[0]) if err = l.handler(name, info, conn, linkOptions{}, true); err != nil { l.core.log.Errorln("Failed to create inbound link:", err) From d66b3ffb7ae67b7996033acf5e54a9174e8856bd Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 22 Oct 2022 16:23:25 +0100 Subject: [PATCH 06/25] Always allow link-local peerings again --- src/core/link.go | 27 ++++++++++++--------------- src/core/link_tcp.go | 8 ++++---- src/core/link_tls.go | 8 ++++---- 3 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src/core/link.go b/src/core/link.go index f8e5be22..e822acaf 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -272,8 +272,7 @@ func (intf *link) handler() error { var key keyArray copy(key[:], meta.key) if _, allowed := pinned[key]; !allowed { - intf.links.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") + return fmt.Errorf("node public key that does not match pinned keys") } } // Check if we're authorized to connect to this key / IP @@ -286,30 +285,32 @@ func (intf *link) handler() error { } } if intf.incoming && !intf.force && !isallowed { - intf.links.core.log.Warnf("%s connection from %s forbidden: AllowedPublicKeys does not contain key %s", - strings.ToUpper(intf.info.linkType), intf.info.remote, hex.EncodeToString(meta.key)) _ = intf.close() - return fmt.Errorf("forbidden connection") + return fmt.Errorf("node public key %q is not in AllowedPublicKeys", hex.EncodeToString(meta.key)) } phony.Block(intf.links, func() { intf.links._links[intf.info] = intf }) + dir := "outbound" + if intf.incoming { + dir = "inbound" + } remoteAddr := net.IP(address.AddrForKey(meta.key)[:]).String() remoteStr := fmt.Sprintf("%s@%s", remoteAddr, intf.info.remote) localStr := intf.conn.LocalAddr() - intf.links.core.log.Infof("Connected %s: %s, source %s", - strings.ToUpper(intf.info.linkType), remoteStr, localStr) + intf.links.core.log.Infof("Connected %s %s: %s, source %s", + dir, strings.ToUpper(intf.info.linkType), remoteStr, localStr) err = intf.links.core.HandleConn(meta.key, intf.conn) switch err { case io.EOF, net.ErrClosed, nil: - intf.links.core.log.Infof("Disconnected %s: %s, source %s", - strings.ToUpper(intf.info.linkType), remoteStr, localStr) + intf.links.core.log.Infof("Disconnected %s %s: %s, source %s", + dir, strings.ToUpper(intf.info.linkType), remoteStr, localStr) default: - intf.links.core.log.Infof("Disconnected %s: %s, source %s; error: %s", - strings.ToUpper(intf.info.linkType), remoteStr, localStr, err) + intf.links.core.log.Infof("Disconnected %s %s: %s, source %s; error: %s", + dir, strings.ToUpper(intf.info.linkType), remoteStr, localStr, err) } return nil } @@ -318,10 +319,6 @@ func (intf *link) close() error { return intf.conn.Close() } -func (intf *link) name() string { - return intf.lname -} - func linkInfoFor(linkType, sintf, remote string) linkInfo { if h, _, err := net.SplitHostPort(remote); err == nil { remote = h diff --git a/src/core/link_tcp.go b/src/core/link_tcp.go index a388fcdb..986eda30 100644 --- a/src/core/link_tcp.go +++ b/src/core/link_tcp.go @@ -47,7 +47,7 @@ func (l *linkTCP) dial(url *url.URL, options linkOptions, sintf string) error { if err != nil { return err } - return l.handler(url.String(), info, conn, options, false) + return l.handler(url.String(), info, conn, options, false, false) } func (l *linkTCP) listen(url *url.URL, sintf string) (*Listener, error) { @@ -84,7 +84,7 @@ func (l *linkTCP) listen(url *url.URL, sintf string) (*Listener, error) { addr := conn.RemoteAddr().(*net.TCPAddr) name := fmt.Sprintf("tcp://%s", addr) info := linkInfoFor("tcp", sintf, strings.SplitN(addr.IP.String(), "%", 2)[0]) - if err = l.handler(name, info, conn, linkOptions{}, true); err != nil { + if err = l.handler(name, info, conn, linkOptions{}, true, addr.IP.IsLinkLocalUnicast()); err != nil { l.core.log.Errorln("Failed to create inbound link:", err) } } @@ -95,13 +95,13 @@ func (l *linkTCP) listen(url *url.URL, sintf string) (*Listener, error) { return entry, nil } -func (l *linkTCP) handler(name string, info linkInfo, conn net.Conn, options linkOptions, incoming bool) error { +func (l *linkTCP) handler(name string, info linkInfo, conn net.Conn, options linkOptions, incoming, force bool) error { return l.links.create( conn, // connection name, // connection name info, // connection info incoming, // not incoming - false, // not forced + force, // not forced options, // connection options ) } diff --git a/src/core/link_tls.go b/src/core/link_tls.go index bc39b6c0..9e7dda9b 100644 --- a/src/core/link_tls.go +++ b/src/core/link_tls.go @@ -69,7 +69,7 @@ func (l *linkTLS) dial(url *url.URL, options linkOptions, sintf, sni string) err if err != nil { return err } - return l.handler(url.String(), info, conn, options, false) + return l.handler(url.String(), info, conn, options, false, false) } func (l *linkTLS) listen(url *url.URL, sintf string) (*Listener, error) { @@ -107,7 +107,7 @@ func (l *linkTLS) listen(url *url.URL, sintf string) (*Listener, error) { addr := conn.RemoteAddr().(*net.TCPAddr) name := fmt.Sprintf("tls://%s", addr) info := linkInfoFor("tls", sintf, strings.SplitN(addr.IP.String(), "%", 2)[0]) - if err = l.handler(name, info, conn, linkOptions{}, true); err != nil { + if err = l.handler(name, info, conn, linkOptions{}, true, addr.IP.IsLinkLocalUnicast()); err != nil { l.core.log.Errorln("Failed to create inbound link:", err) } } @@ -165,6 +165,6 @@ func (l *linkTLS) generateConfig() (*tls.Config, error) { }, nil } -func (l *linkTLS) handler(name string, info linkInfo, conn net.Conn, options linkOptions, incoming bool) error { - return l.tcp.handler(name, info, conn, options, incoming) +func (l *linkTLS) handler(name string, info linkInfo, conn net.Conn, options linkOptions, incoming, force bool) error { + return l.tcp.handler(name, info, conn, options, incoming, force) } From 8fe1c41295e568df75d89cf2226f0260f32f7e9c Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 22 Oct 2022 16:59:25 +0100 Subject: [PATCH 07/25] Don't reject multiple genuine links from the same host --- src/core/link.go | 3 --- src/core/link_tcp.go | 13 +++++++------ src/core/link_tls.go | 13 +++++++------ 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/core/link.go b/src/core/link.go index e822acaf..67184625 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -320,9 +320,6 @@ func (intf *link) close() error { } func linkInfoFor(linkType, sintf, remote string) linkInfo { - if h, _, err := net.SplitHostPort(remote); err == nil { - remote = h - } return linkInfo{ linkType: linkType, local: sintf, diff --git a/src/core/link_tcp.go b/src/core/link_tcp.go index 986eda30..ee0dd001 100644 --- a/src/core/link_tcp.go +++ b/src/core/link_tcp.go @@ -31,14 +31,14 @@ func (l *links) newLinkTCP() *linkTCP { } func (l *linkTCP) dial(url *url.URL, options linkOptions, sintf string) error { - info := linkInfoFor("tcp", sintf, strings.SplitN(url.Host, "%", 2)[0]) - if l.links.isConnectedTo(info) { - return nil - } addr, err := net.ResolveTCPAddr("tcp", url.Host) if err != nil { return err } + info := linkInfoFor("tcp", sintf, addr.String()) + if l.links.isConnectedTo(info) { + return nil + } dialer, err := l.dialerFor(addr, sintf) if err != nil { return err @@ -47,7 +47,8 @@ func (l *linkTCP) dial(url *url.URL, options linkOptions, sintf string) error { if err != nil { return err } - return l.handler(url.String(), info, conn, options, false, false) + uri := strings.TrimRight(strings.SplitN(url.String(), "?", 2)[0], "/") + return l.handler(uri, info, conn, options, false, false) } func (l *linkTCP) listen(url *url.URL, sintf string) (*Listener, error) { @@ -83,7 +84,7 @@ func (l *linkTCP) listen(url *url.URL, sintf string) (*Listener, error) { } addr := conn.RemoteAddr().(*net.TCPAddr) name := fmt.Sprintf("tcp://%s", addr) - info := linkInfoFor("tcp", sintf, strings.SplitN(addr.IP.String(), "%", 2)[0]) + info := linkInfoFor("tcp", sintf, addr.String()) if err = l.handler(name, info, conn, linkOptions{}, true, addr.IP.IsLinkLocalUnicast()); err != nil { l.core.log.Errorln("Failed to create inbound link:", err) } diff --git a/src/core/link_tls.go b/src/core/link_tls.go index 9e7dda9b..ee3363ec 100644 --- a/src/core/link_tls.go +++ b/src/core/link_tls.go @@ -47,14 +47,14 @@ func (l *links) newLinkTLS(tcp *linkTCP) *linkTLS { } func (l *linkTLS) dial(url *url.URL, options linkOptions, sintf, sni string) error { - info := linkInfoFor("tls", sintf, strings.SplitN(url.Host, "%", 2)[0]) - if l.links.isConnectedTo(info) { - return nil - } addr, err := net.ResolveTCPAddr("tcp", url.Host) if err != nil { return err } + info := linkInfoFor("tls", sintf, addr.String()) + if l.links.isConnectedTo(info) { + return nil + } dialer, err := l.tcp.dialerFor(addr, sintf) if err != nil { return err @@ -69,7 +69,8 @@ func (l *linkTLS) dial(url *url.URL, options linkOptions, sintf, sni string) err if err != nil { return err } - return l.handler(url.String(), info, conn, options, false, false) + uri := strings.TrimRight(strings.SplitN(url.String(), "?", 2)[0], "/") + return l.handler(uri, info, conn, options, false, false) } func (l *linkTLS) listen(url *url.URL, sintf string) (*Listener, error) { @@ -106,7 +107,7 @@ func (l *linkTLS) listen(url *url.URL, sintf string) (*Listener, error) { } addr := conn.RemoteAddr().(*net.TCPAddr) name := fmt.Sprintf("tls://%s", addr) - info := linkInfoFor("tls", sintf, strings.SplitN(addr.IP.String(), "%", 2)[0]) + info := linkInfoFor("tls", sintf, addr.String()) if err = l.handler(name, info, conn, linkOptions{}, true, addr.IP.IsLinkLocalUnicast()); err != nil { l.core.log.Errorln("Failed to create inbound link:", err) } From 35ea66d651d3f186d8c33ede4568fd28a18bdc58 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 22 Oct 2022 17:45:09 +0100 Subject: [PATCH 08/25] Varying connection check strictness based on scope --- src/core/link_tcp.go | 30 ++++++++++++++++++++++-------- src/core/link_tls.go | 17 +++++++++-------- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/src/core/link_tcp.go b/src/core/link_tcp.go index ee0dd001..a8f437e9 100644 --- a/src/core/link_tcp.go +++ b/src/core/link_tcp.go @@ -35,14 +35,14 @@ func (l *linkTCP) dial(url *url.URL, options linkOptions, sintf string) error { if err != nil { return err } - info := linkInfoFor("tcp", sintf, addr.String()) - if l.links.isConnectedTo(info) { - return nil - } dialer, err := l.dialerFor(addr, sintf) if err != nil { return err } + info := linkInfoFor("tcp", sintf, tcpIDFor(dialer.LocalAddr, addr)) + if l.links.isConnectedTo(info) { + return nil + } conn, err := dialer.DialContext(l.core.ctx, "tcp", addr.String()) if err != nil { return err @@ -82,10 +82,11 @@ func (l *linkTCP) listen(url *url.URL, sintf string) (*Listener, error) { cancel() break } - addr := conn.RemoteAddr().(*net.TCPAddr) - name := fmt.Sprintf("tcp://%s", addr) - info := linkInfoFor("tcp", sintf, addr.String()) - if err = l.handler(name, info, conn, linkOptions{}, true, addr.IP.IsLinkLocalUnicast()); err != nil { + laddr := conn.LocalAddr().(*net.TCPAddr) + raddr := conn.RemoteAddr().(*net.TCPAddr) + name := fmt.Sprintf("tcp://%s", raddr) + info := linkInfoFor("tcp", sintf, tcpIDFor(laddr, raddr)) + if err = l.handler(name, info, conn, linkOptions{}, true, raddr.IP.IsLinkLocalUnicast()); err != nil { l.core.log.Errorln("Failed to create inbound link:", err) } } @@ -179,3 +180,16 @@ func (l *linkTCP) dialerFor(dst *net.TCPAddr, sintf string) (*net.Dialer, error) } return dialer, nil } + +func tcpIDFor(local net.Addr, remoteAddr *net.TCPAddr) string { + if localAddr, ok := local.(*net.TCPAddr); ok && localAddr.IP.Equal(remoteAddr.IP) { + // Nodes running on the same host — include both the IP and port. + return remoteAddr.String() + } + if remoteAddr.IP.IsLinkLocalUnicast() { + // Nodes discovered via multicast — include the IP only. + return remoteAddr.IP.String() + } + // Nodes connected remotely — include both the IP and port. + return remoteAddr.String() +} diff --git a/src/core/link_tls.go b/src/core/link_tls.go index ee3363ec..3af8fe2b 100644 --- a/src/core/link_tls.go +++ b/src/core/link_tls.go @@ -51,14 +51,14 @@ func (l *linkTLS) dial(url *url.URL, options linkOptions, sintf, sni string) err if err != nil { return err } - info := linkInfoFor("tls", sintf, addr.String()) - if l.links.isConnectedTo(info) { - return nil - } dialer, err := l.tcp.dialerFor(addr, sintf) if err != nil { return err } + info := linkInfoFor("tls", sintf, tcpIDFor(dialer.LocalAddr, addr)) + if l.links.isConnectedTo(info) { + return nil + } tlsconfig := l.config.Clone() tlsconfig.ServerName = sni tlsdialer := &tls.Dialer{ @@ -105,10 +105,11 @@ func (l *linkTLS) listen(url *url.URL, sintf string) (*Listener, error) { cancel() break } - addr := conn.RemoteAddr().(*net.TCPAddr) - name := fmt.Sprintf("tls://%s", addr) - info := linkInfoFor("tls", sintf, addr.String()) - if err = l.handler(name, info, conn, linkOptions{}, true, addr.IP.IsLinkLocalUnicast()); err != nil { + laddr := conn.LocalAddr().(*net.TCPAddr) + raddr := conn.RemoteAddr().(*net.TCPAddr) + name := fmt.Sprintf("tls://%s", raddr) + info := linkInfoFor("tls", sintf, tcpIDFor(laddr, raddr)) + if err = l.handler(name, info, conn, linkOptions{}, true, raddr.IP.IsLinkLocalUnicast()); err != nil { l.core.log.Errorln("Failed to create inbound link:", err) } } From 65e350153eeba16ab47386963a8e705a22dd2cc8 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 22 Oct 2022 18:05:14 +0100 Subject: [PATCH 09/25] Don't start multicast module if all `Beacon` and `Listen` are disabled --- src/multicast/multicast.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index d40bcfc0..84d5933a 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -77,7 +77,11 @@ func (m *Multicast) _start() error { if m._isOpen { return fmt.Errorf("multicast module is already started") } - if len(m.config._interfaces) == 0 { + var anyEnabled bool + for intf := range m.config._interfaces { + anyEnabled = anyEnabled || intf.Beacon || intf.Listen + } + if !anyEnabled { return nil } m.log.Debugln("Starting multicast module") From 9a9452dcc814931503a1267bc0ef52b6ab9837ba Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 25 Oct 2022 18:58:52 +0100 Subject: [PATCH 10/25] Fix panic in `GetPeers` that may happen mid-link setup --- src/core/api.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/core/api.go b/src/core/api.go index 0fa6dd33..8e9186ac 100644 --- a/src/core/api.go +++ b/src/core/api.go @@ -62,6 +62,9 @@ func (c *Core) GetPeers() []PeerInfo { names := make(map[net.Conn]string) phony.Block(&c.links, func() { for _, info := range c.links._links { + if info == nil { + continue + } names[info.conn] = info.lname } }) From f08dec822a80bd487649273cd4195cdce4ed619f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 26 Oct 2022 09:24:24 +0100 Subject: [PATCH 11/25] Priority support (#964) * Allow setting link priorities * Fix a bug * Allow setting priority on listeners and multicast interfaces * Update `yggdrasilctl` * Update to Arceliar/ironwood#5 --- cmd/yggdrasil/main.go | 9 +++++---- cmd/yggdrasilctl/main.go | 3 ++- contrib/mobile/mobile.go | 9 +++++---- go.mod | 2 +- go.sum | 15 +++------------ src/admin/getpeers.go | 5 +++++ src/config/config.go | 9 +++++---- src/core/api.go | 18 ++++++++++-------- src/core/link.go | 28 +++++++++++++++++++++++----- src/core/link_tcp.go | 2 +- src/core/link_tls.go | 2 +- src/core/link_unix.go | 2 +- src/multicast/multicast.go | 22 ++++++++++++---------- src/multicast/options.go | 9 +++++---- 14 files changed, 79 insertions(+), 56 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 3d5eab97..8185dee0 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -335,10 +335,11 @@ func run(args yggArgs, ctx context.Context) { options := []multicast.SetupOption{} for _, intf := range cfg.MulticastInterfaces { options = append(options, multicast.MulticastInterface{ - Regex: regexp.MustCompile(intf.Regex), - Beacon: intf.Beacon, - Listen: intf.Listen, - Port: intf.Port, + Regex: regexp.MustCompile(intf.Regex), + Beacon: intf.Beacon, + Listen: intf.Listen, + Port: intf.Port, + Priority: intf.Priority, }) } if n.multicast, err = multicast.New(n.core, logger, options...); err != nil { diff --git a/cmd/yggdrasilctl/main.go b/cmd/yggdrasilctl/main.go index 324550bd..c9b1522a 100644 --- a/cmd/yggdrasilctl/main.go +++ b/cmd/yggdrasilctl/main.go @@ -174,7 +174,7 @@ func run() int { if err := json.Unmarshal(recv.Response, &resp); err != nil { panic(err) } - table.SetHeader([]string{"Port", "Public Key", "IP Address", "Uptime", "RX", "TX", "URI"}) + table.SetHeader([]string{"Port", "Public Key", "IP Address", "Uptime", "RX", "TX", "Pr", "URI"}) for _, peer := range resp.Peers { table.Append([]string{ fmt.Sprintf("%d", peer.Port), @@ -183,6 +183,7 @@ func run() int { (time.Duration(peer.Uptime) * time.Second).String(), peer.RXBytes.String(), peer.TXBytes.String(), + fmt.Sprintf("%d", peer.Priority), peer.Remote, }) } diff --git a/contrib/mobile/mobile.go b/contrib/mobile/mobile.go index 0cf87180..78a3f506 100644 --- a/contrib/mobile/mobile.go +++ b/contrib/mobile/mobile.go @@ -83,10 +83,11 @@ func (m *Yggdrasil) StartJSON(configjson []byte) error { options := []multicast.SetupOption{} for _, intf := range m.config.MulticastInterfaces { options = append(options, multicast.MulticastInterface{ - Regex: regexp.MustCompile(intf.Regex), - Beacon: intf.Beacon, - Listen: intf.Listen, - Port: intf.Port, + Regex: regexp.MustCompile(intf.Regex), + Beacon: intf.Beacon, + Listen: intf.Listen, + Port: intf.Port, + Priority: intf.Priority, }) } m.multicast, err = multicast.New(m.core, logger, options...) diff --git a/go.mod b/go.mod index 4e73edea..156b42ce 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/yggdrasil-network/yggdrasil-go go 1.17 require ( - github.com/Arceliar/ironwood v0.0.0-20220924160422-ed4b6d4750b6 + github.com/Arceliar/ironwood v0.0.0-20221025225125-45b4281814c2 github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 github.com/cheggaaa/pb/v3 v3.0.8 github.com/gologme/log v1.2.0 diff --git a/go.sum b/go.sum index 9e0b7458..b6655087 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Arceliar/ironwood v0.0.0-20220924160422-ed4b6d4750b6 h1:iwL6nm2ibyuHXYimRNtFof7RJfe8JB+6CPDskV7K7gA= -github.com/Arceliar/ironwood v0.0.0-20220924160422-ed4b6d4750b6/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= +github.com/Arceliar/ironwood v0.0.0-20221025225125-45b4281814c2 h1:Usab30pNT2i/vZvpXcN9uOr5IO1RZPcUqoGH0DIAPnU= +github.com/Arceliar/ironwood v0.0.0-20221025225125-45b4281814c2/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 h1:WndgpSW13S32VLQ3ugUxx2EnnWmgba1kCqPkd4Gk1yQ= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -17,8 +17,6 @@ github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwM github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hjson/hjson-go v3.1.0+incompatible h1:DY/9yE8ey8Zv22bY+mHV1uk2yRy0h8tKhZ77hEdi0Aw= github.com/hjson/hjson-go v3.1.0+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio= -github.com/kardianos/minwinsvc v1.0.0 h1:+JfAi8IBJna0jY2dJGZqi7o15z13JelFIklJCAENALA= -github.com/kardianos/minwinsvc v1.0.0/go.mod h1:Bgd0oc+D0Qo3bBytmNtyRKVlp85dAloLKhfxanPFFRc= github.com/kardianos/minwinsvc v1.0.2 h1:JmZKFJQrmTGa/WiW+vkJXKmfzdjabuEW4Tirj5lLdR0= github.com/kardianos/minwinsvc v1.0.2/go.mod h1:LUZNYhNmxujx2tR7FbdxqYJ9XDDoCd3MQcl1o//FWl4= github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ= @@ -50,7 +48,6 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20221012134737-56aed061732a h1:NmSIgad6KjE6VvHciPZuNRTKxGhlPfD6OA87W/PLkqg= golang.org/x/crypto v0.0.0-20221012134737-56aed061732a/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -58,8 +55,6 @@ golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9t golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20220722155234-aaac322e2105 h1:3vUV5x5+3LfQbgk7paCM6INOaJG9xXQbn79xoNkwfIk= -golang.org/x/mobile v0.0.0-20220722155234-aaac322e2105/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ= golang.org/x/mobile v0.0.0-20221012134814-c746ac228303 h1:K4fp1rDuJBz0FCPAWzIJwnzwNEM7S6yobdZzMrZ/Zws= golang.org/x/mobile v0.0.0-20221012134814-c746ac228303/go.mod h1:M32cGdzp91A8Ex9qQtyZinr19EYxzkFqDjW2oyHzTDQ= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= @@ -73,7 +68,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210927181540-4e4d966f7476/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211011170408-caeb26a5c8c0/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20221014081412-f15817d10f9b h1:tvrvnPFcdzp294diPnrdZZZ8XUt2Tyj7svb7X52iDuU= golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= @@ -87,7 +82,6 @@ golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -97,9 +91,7 @@ golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 h1:OK7RB6t2WQX54srQQYSXMW8dF5C6/8+oA/s5QBmmto4= golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -109,7 +101,6 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b h1:NXqSWXSRUSCaFuvitrWtU169I3876zRTalMRbfd6LL0= golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0= golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= diff --git a/src/admin/getpeers.go b/src/admin/getpeers.go index 61d0937f..c1c9a6f9 100644 --- a/src/admin/getpeers.go +++ b/src/admin/getpeers.go @@ -19,6 +19,7 @@ type PeerEntry struct { IPAddress string `json:"address"` PublicKey string `json:"key"` Port uint64 `json:"port"` + Priority uint8 `json:"priority"` Coords []uint64 `json:"coords"` Remote string `json:"remote"` RXBytes DataUnit `json:"bytes_recvd"` @@ -35,6 +36,7 @@ func (a *AdminSocket) getPeersHandler(req *GetPeersRequest, res *GetPeersRespons IPAddress: net.IP(addr[:]).String(), PublicKey: hex.EncodeToString(p.Key), Port: p.Port, + Priority: p.Priority, Coords: p.Coords, Remote: p.Remote, RXBytes: DataUnit(p.RXBytes), @@ -43,6 +45,9 @@ func (a *AdminSocket) getPeersHandler(req *GetPeersRequest, res *GetPeersRespons }) } sort.Slice(res.Peers, func(i, j int) bool { + if res.Peers[i].Port == res.Peers[j].Port { + return res.Peers[i].Priority < res.Peers[j].Priority + } return res.Peers[i].Port < res.Peers[j].Port }) return nil diff --git a/src/config/config.go b/src/config/config.go index 5bdeec4b..3fc9c4ed 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -40,10 +40,11 @@ type NodeConfig struct { } type MulticastInterfaceConfig struct { - Regex string - Beacon bool - Listen bool - Port uint16 + Regex string + Beacon bool + Listen bool + Port uint16 + Priority uint8 } // NewSigningKeys replaces the signing keypair in the NodeConfig with a new diff --git a/src/core/api.go b/src/core/api.go index 8e9186ac..ae783156 100644 --- a/src/core/api.go +++ b/src/core/api.go @@ -20,14 +20,15 @@ type SelfInfo struct { } type PeerInfo struct { - Key ed25519.PublicKey - Root ed25519.PublicKey - Coords []uint64 - Port uint64 - Remote string - RXBytes uint64 - TXBytes uint64 - Uptime time.Duration + Key ed25519.PublicKey + Root ed25519.PublicKey + Coords []uint64 + Port uint64 + Priority uint8 + Remote string + RXBytes uint64 + TXBytes uint64 + Uptime time.Duration } type DHTEntryInfo struct { @@ -75,6 +76,7 @@ func (c *Core) GetPeers() []PeerInfo { info.Root = p.Root info.Coords = p.Coords info.Port = p.Port + info.Priority = p.Priority info.Remote = p.Conn.RemoteAddr().String() if name := names[p.Conn]; name != "" { info.Remote = name diff --git a/src/core/link.go b/src/core/link.go index 67184625..550d7ec2 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -8,6 +8,7 @@ import ( "io" "net" "net/url" + "strconv" "strings" "sync/atomic" "time" @@ -45,6 +46,7 @@ type link struct { type linkOptions struct { pinnedEd25519Keys map[keyArray]struct{} + priority uint8 } type Listener struct { @@ -120,17 +122,24 @@ func (l *links) call(u *url.URL, sintf string) (linkInfo, error) { copy(sigPubKey[:], sigPub) options.pinnedEd25519Keys[sigPubKey] = struct{}{} } + if p := u.Query().Get("priority"); p != "" { + pi, err := strconv.ParseUint(p, 10, 8) + if err != nil { + return info, fmt.Errorf("priority invalid: %w", err) + } + options.priority = uint8(pi) + } switch info.linkType { case "tcp": go func() { - if err := l.tcp.dial(u, options, sintf); err != nil { + if err := l.tcp.dial(u, options, sintf); err != nil && err != io.EOF { l.core.log.Warnf("Failed to dial TCP %s: %s\n", u.Host, err) } }() case "socks": go func() { - if err := l.socks.dial(u, options); err != nil { + if err := l.socks.dial(u, options); err != nil && err != io.EOF { l.core.log.Warnf("Failed to dial SOCKS %s: %s\n", u.Host, err) } }() @@ -154,14 +163,14 @@ func (l *links) call(u *url.URL, sintf string) (linkInfo, error) { } } go func() { - if err := l.tls.dial(u, options, sintf, tlsSNI); err != nil { + if err := l.tls.dial(u, options, sintf, tlsSNI); err != nil && err != io.EOF { l.core.log.Warnf("Failed to dial TLS %s: %s\n", u.Host, err) } }() case "unix": go func() { - if err := l.unix.dial(u, options, sintf); err != nil { + if err := l.unix.dial(u, options, sintf); err != nil && err != io.EOF { l.core.log.Warnf("Failed to dial UNIX %s: %s\n", u.Host, err) } }() @@ -303,7 +312,7 @@ func (intf *link) handler() error { intf.links.core.log.Infof("Connected %s %s: %s, source %s", dir, strings.ToUpper(intf.info.linkType), remoteStr, localStr) - err = intf.links.core.HandleConn(meta.key, intf.conn) + err = intf.links.core.HandleConn(meta.key, intf.conn, intf.options.priority) switch err { case io.EOF, net.ErrClosed, nil: intf.links.core.log.Infof("Disconnected %s %s: %s, source %s", @@ -347,3 +356,12 @@ func (c *linkConn) Write(p []byte) (n int, err error) { atomic.AddUint64(&c.tx, uint64(n)) return } + +func linkOptionsForListener(u *url.URL) (l linkOptions) { + if p := u.Query().Get("priority"); p != "" { + if pi, err := strconv.ParseUint(p, 10, 8); err == nil { + l.priority = uint8(pi) + } + } + return +} diff --git a/src/core/link_tcp.go b/src/core/link_tcp.go index a8f437e9..9c3c3290 100644 --- a/src/core/link_tcp.go +++ b/src/core/link_tcp.go @@ -86,7 +86,7 @@ func (l *linkTCP) listen(url *url.URL, sintf string) (*Listener, error) { raddr := conn.RemoteAddr().(*net.TCPAddr) name := fmt.Sprintf("tcp://%s", raddr) info := linkInfoFor("tcp", sintf, tcpIDFor(laddr, raddr)) - if err = l.handler(name, info, conn, linkOptions{}, true, raddr.IP.IsLinkLocalUnicast()); err != nil { + if err = l.handler(name, info, conn, linkOptionsForListener(url), true, raddr.IP.IsLinkLocalUnicast()); err != nil { l.core.log.Errorln("Failed to create inbound link:", err) } } diff --git a/src/core/link_tls.go b/src/core/link_tls.go index 3af8fe2b..4eeb8710 100644 --- a/src/core/link_tls.go +++ b/src/core/link_tls.go @@ -109,7 +109,7 @@ func (l *linkTLS) listen(url *url.URL, sintf string) (*Listener, error) { raddr := conn.RemoteAddr().(*net.TCPAddr) name := fmt.Sprintf("tls://%s", raddr) info := linkInfoFor("tls", sintf, tcpIDFor(laddr, raddr)) - if err = l.handler(name, info, conn, linkOptions{}, true, raddr.IP.IsLinkLocalUnicast()); err != nil { + if err = l.handler(name, info, conn, linkOptionsForListener(url), true, raddr.IP.IsLinkLocalUnicast()); err != nil { l.core.log.Errorln("Failed to create inbound link:", err) } } diff --git a/src/core/link_unix.go b/src/core/link_unix.go index 2d1b9398..50183a25 100644 --- a/src/core/link_unix.go +++ b/src/core/link_unix.go @@ -74,7 +74,7 @@ func (l *linkUNIX) listen(url *url.URL, _ string) (*Listener, error) { break } info := linkInfoFor("unix", "", url.String()) - if err = l.handler(url.String(), info, conn, linkOptions{}, true); err != nil { + if err = l.handler(url.String(), info, conn, linkOptionsForListener(url), true); err != nil { l.core.log.Errorln("Failed to create inbound link:", err) } } diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 84d5933a..8d7fbb7d 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -37,11 +37,12 @@ type Multicast struct { } type interfaceInfo struct { - iface net.Interface - addrs []net.Addr - beacon bool - listen bool - port uint16 + iface net.Interface + addrs []net.Addr + beacon bool + listen bool + port uint16 + priority uint8 } type listenerInfo struct { @@ -194,10 +195,11 @@ func (m *Multicast) _getAllowedInterfaces() map[string]*interfaceInfo { continue } interfaces[iface.Name] = &interfaceInfo{ - iface: iface, - beacon: ifcfg.Beacon, - listen: ifcfg.Listen, - port: ifcfg.Port, + iface: iface, + beacon: ifcfg.Beacon, + listen: ifcfg.Listen, + port: ifcfg.Port, + priority: ifcfg.Priority, } break } @@ -387,7 +389,7 @@ func (m *Multicast) listen() { }) if info, ok := interfaces[from.Zone]; ok && info.listen { addr.Zone = "" - pin := fmt.Sprintf("/?key=%s", hex.EncodeToString(key)) + pin := fmt.Sprintf("/?key=%s&priority=%d", hex.EncodeToString(key), info.priority) u, err := url.Parse("tls://" + addr.String() + pin) if err != nil { m.log.Debugln("Call from multicast failed, parse error:", addr.String(), err) diff --git a/src/multicast/options.go b/src/multicast/options.go index a03b0677..f36284ed 100644 --- a/src/multicast/options.go +++ b/src/multicast/options.go @@ -16,10 +16,11 @@ type SetupOption interface { } type MulticastInterface struct { - Regex *regexp.Regexp - Beacon bool - Listen bool - Port uint16 + Regex *regexp.Regexp + Beacon bool + Listen bool + Port uint16 + Priority uint8 } type GroupAddress string From cfa293d189fae9af9c025bf40a19e9ac69f8f839 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 26 Oct 2022 22:29:19 +0100 Subject: [PATCH 12/25] Fix bug in admin socket where requests fail unless `"arguments":{}` is specified in the JSON --- src/admin/admin.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/admin/admin.go b/src/admin/admin.go index b24bf0de..9dbcfdca 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -327,6 +327,7 @@ func (a *AdminSocket) handleRequest(conn net.Conn) { var buf json.RawMessage var req AdminSocketRequest var resp AdminSocketResponse + req.Arguments = []byte("{}") if err := func() error { if err = decoder.Decode(&buf); err != nil { return fmt.Errorf("Failed to find request") From ee33bd248f19945d83d1b84db65d97030739eb3e Mon Sep 17 00:00:00 2001 From: Revertron <105154+Revertron@users.noreply.github.com> Date: Tue, 1 Nov 2022 13:10:50 +0100 Subject: [PATCH 13/25] Added two new methods to `mobile` package (#974) * Added two new methods In order to implement https://github.com/yggdrasil-network/yggdrasil-android/issues/25 we need these new methods. * Renamed methods, changed comments --- contrib/mobile/mobile.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/contrib/mobile/mobile.go b/contrib/mobile/mobile.go index 78a3f506..9b146492 100644 --- a/contrib/mobile/mobile.go +++ b/contrib/mobile/mobile.go @@ -115,6 +115,18 @@ func (m *Yggdrasil) Send(p []byte) error { return nil } +// Send sends a packet from given buffer to Yggdrasil. From first byte up to length. +func (m *Yggdrasil) SendBuffer(p []byte, length int) error { + if m.iprwc == nil { + return nil + } + if len(p) < length { + return nil + } + _, _ = m.iprwc.Write(p[:length]) + return nil +} + // Recv waits for and reads a packet coming from Yggdrasil. It // will be a fully formed IPv6 packet func (m *Yggdrasil) Recv() ([]byte, error) { @@ -126,6 +138,15 @@ func (m *Yggdrasil) Recv() ([]byte, error) { return buf[:n], nil } +// Recv waits for and reads a packet coming from Yggdrasil to given buffer, returning size of packet +func (m *Yggdrasil) RecvBuffer(buf []byte) (int, error) { + if m.iprwc == nil { + return 0, nil + } + n, _ := m.iprwc.Read(buf) + return n, nil +} + // Stop the mobile Yggdrasil instance func (m *Yggdrasil) Stop() error { logger := log.New(m.log, "", 0) From 590d83aa9c90b151de471d13571c331075506481 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 1 Nov 2022 17:42:52 +0000 Subject: [PATCH 14/25] Fix #975 by not exporting `uint8` --- contrib/mobile/mobile.go | 4 ++-- go.mod | 2 +- go.sum | 5 ++--- src/admin/getpeers.go | 4 ++-- src/config/config.go | 2 +- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/contrib/mobile/mobile.go b/contrib/mobile/mobile.go index 9b146492..ff22b9d7 100644 --- a/contrib/mobile/mobile.go +++ b/contrib/mobile/mobile.go @@ -87,7 +87,7 @@ func (m *Yggdrasil) StartJSON(configjson []byte) error { Beacon: intf.Beacon, Listen: intf.Listen, Port: intf.Port, - Priority: intf.Priority, + Priority: uint8(intf.Priority), }) } m.multicast, err = multicast.New(m.core, logger, options...) @@ -138,7 +138,7 @@ func (m *Yggdrasil) Recv() ([]byte, error) { return buf[:n], nil } -// Recv waits for and reads a packet coming from Yggdrasil to given buffer, returning size of packet +// Recv waits for and reads a packet coming from Yggdrasil to given buffer, returning size of packet func (m *Yggdrasil) RecvBuffer(buf []byte) (int, error) { if m.iprwc == nil { return 0, nil diff --git a/go.mod b/go.mod index 156b42ce..adaf7cf8 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/kardianos/minwinsvc v1.0.2 github.com/mitchellh/mapstructure v1.4.1 github.com/vishvananda/netlink v1.1.0 - golang.org/x/mobile v0.0.0-20221012134814-c746ac228303 + golang.org/x/mobile v0.0.0-20221020085226-b36e6246172e golang.org/x/net v0.0.0-20221014081412-f15817d10f9b golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 golang.org/x/text v0.3.8 diff --git a/go.sum b/go.sum index b6655087..e398aca6 100644 --- a/go.sum +++ b/go.sum @@ -55,8 +55,8 @@ golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9t golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20221012134814-c746ac228303 h1:K4fp1rDuJBz0FCPAWzIJwnzwNEM7S6yobdZzMrZ/Zws= -golang.org/x/mobile v0.0.0-20221012134814-c746ac228303/go.mod h1:M32cGdzp91A8Ex9qQtyZinr19EYxzkFqDjW2oyHzTDQ= +golang.org/x/mobile v0.0.0-20221020085226-b36e6246172e h1:zSgtO19fpg781xknwqiQPmOHaASr6E7ZVlTseLd9Fx4= +golang.org/x/mobile v0.0.0-20221020085226-b36e6246172e/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= @@ -108,7 +108,6 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= -golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/src/admin/getpeers.go b/src/admin/getpeers.go index c1c9a6f9..d51b184d 100644 --- a/src/admin/getpeers.go +++ b/src/admin/getpeers.go @@ -19,7 +19,7 @@ type PeerEntry struct { IPAddress string `json:"address"` PublicKey string `json:"key"` Port uint64 `json:"port"` - Priority uint8 `json:"priority"` + Priority uint64 `json:"priority"` Coords []uint64 `json:"coords"` Remote string `json:"remote"` RXBytes DataUnit `json:"bytes_recvd"` @@ -36,7 +36,7 @@ func (a *AdminSocket) getPeersHandler(req *GetPeersRequest, res *GetPeersRespons IPAddress: net.IP(addr[:]).String(), PublicKey: hex.EncodeToString(p.Key), Port: p.Port, - Priority: p.Priority, + Priority: uint64(p.Priority), // can't be uint8 thanks to gobind Coords: p.Coords, Remote: p.Remote, RXBytes: DataUnit(p.RXBytes), diff --git a/src/config/config.go b/src/config/config.go index 3fc9c4ed..f7f0f6ba 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -44,7 +44,7 @@ type MulticastInterfaceConfig struct { Beacon bool Listen bool Port uint16 - Priority uint8 + Priority uint64 // really uint8, but gobind won't export it } // NewSigningKeys replaces the signing keypair in the NodeConfig with a new From 6112c9cf18929547229e55c9d364efec6f775f88 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 1 Nov 2022 18:34:49 +0000 Subject: [PATCH 15/25] Fix build --- cmd/yggdrasil/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 8185dee0..2d1da6b9 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -339,7 +339,7 @@ func run(args yggArgs, ctx context.Context) { Beacon: intf.Beacon, Listen: intf.Listen, Port: intf.Port, - Priority: intf.Priority, + Priority: uint8(intf.Priority), }) } if n.multicast, err = multicast.New(n.core, logger, options...); err != nil { From 6fed2a75d749c90471da8a8f10154af3de4e1772 Mon Sep 17 00:00:00 2001 From: majestrate Date: Tue, 8 Nov 2022 17:11:22 -0500 Subject: [PATCH 16/25] Make TLS certs never expire (#977) According to RFC5280 we can make TLS certs never expire by setting their `NotAfter` date to a value that is basically the end of time. Fixes #976. --- src/core/link_tls.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/core/link_tls.go b/src/core/link_tls.go index 4eeb8710..fbc6172e 100644 --- a/src/core/link_tls.go +++ b/src/core/link_tls.go @@ -120,20 +120,18 @@ func (l *linkTLS) listen(url *url.URL, sintf string) (*Listener, error) { return entry, nil } +// RFC5280 section 4.1.2.5 +var notAfterNeverExpires = time.Date(9999, time.December, 31, 23, 59, 59, 0, time.UTC) + func (l *linkTLS) generateConfig() (*tls.Config, error) { certBuf := &bytes.Buffer{} - - // TODO: because NotAfter is finite, we should add some mechanism to - // regenerate the certificate and restart the listeners periodically - // for nodes with very high uptimes. Perhaps regenerate certs and restart - // listeners every few months or so. cert := x509.Certificate{ SerialNumber: big.NewInt(1), Subject: pkix.Name{ CommonName: hex.EncodeToString(l.links.core.public[:]), }, NotBefore: time.Now(), - NotAfter: time.Now().Add(time.Hour * 24 * 365), + NotAfter: notAfterNeverExpires, KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, BasicConstraintsValid: true, From 0da871f5287addb555138c65cad93183411d8d84 Mon Sep 17 00:00:00 2001 From: solanav Date: Tue, 8 Nov 2022 23:19:43 +0100 Subject: [PATCH 17/25] Fix #884 (#916) * Fixed #884 * Remove yggdrasil and yggdrasilctl * Fixed #884 Co-authored-by: asolana Co-authored-by: solanav Co-authored-by: Neil Alexander --- cmd/yggdrasil/main.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 2d1da6b9..f85525d6 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -247,7 +247,12 @@ func run(args yggArgs, ctx context.Context) { return default: // No flags were provided, therefore print the list of flags to stdout. + fmt.Println("Usage:") flag.PrintDefaults() + + if args.getaddr || args.getsnet { + fmt.Println("\nError: You need to specify some config data using -useconf or -useconffile.") + } } // Have we got a working configuration? If we don't then it probably means // that neither -autoconf, -useconf or -useconffile were set above. Stop From 7efd66932f469c2e8ff6f695995d4008ac23c158 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 12 Nov 2022 11:30:03 +0000 Subject: [PATCH 18/25] Redial failed connections if possible (#983) --- contrib/mobile/mobile.go | 5 +++ src/core/api.go | 4 +-- src/core/core.go | 7 ++++ src/core/link.go | 67 +++++++++++++++++++++++++++++++++++--- src/core/link_socks.go | 20 +++++++----- src/core/link_tcp.go | 13 +++++--- src/core/link_tls.go | 14 +++++--- src/core/link_unix.go | 10 ++++-- src/multicast/multicast.go | 12 ++++++- 9 files changed, 124 insertions(+), 28 deletions(-) diff --git a/contrib/mobile/mobile.go b/contrib/mobile/mobile.go index ff22b9d7..3b3227bd 100644 --- a/contrib/mobile/mobile.go +++ b/contrib/mobile/mobile.go @@ -159,6 +159,11 @@ func (m *Yggdrasil) Stop() error { return nil } +// Retry resets the peer connection timer and tries to dial them immediately. +func (m *Yggdrasil) RetryPeersNow() { + m.core.RetryPeersNow() +} + // GenerateConfigJSON generates mobile-friendly configuration in JSON format func GenerateConfigJSON() []byte { nc := defaults.GenerateConfig() diff --git a/src/core/api.go b/src/core/api.go index ae783156..5accdeee 100644 --- a/src/core/api.go +++ b/src/core/api.go @@ -194,7 +194,7 @@ func (c *Core) AddPeer(uri string, sourceInterface string) error { if err != nil { return err } - info, err := c.links.call(u, sourceInterface) + info, err := c.links.call(u, sourceInterface, nil) if err != nil { return err } @@ -236,7 +236,7 @@ func (c *Core) RemovePeer(uri string, sourceInterface string) error { // This does not add the peer to the peer list, so if the connection drops, the // peer will not be called again automatically. func (c *Core) CallPeer(u *url.URL, sintf string) error { - _, err := c.links.call(u, sintf) + _, err := c.links.call(u, sintf, nil) return err } diff --git a/src/core/core.go b/src/core/core.go index 67f927a6..b096d1d7 100644 --- a/src/core/core.go +++ b/src/core/core.go @@ -121,6 +121,13 @@ func (c *Core) _addPeerLoop() { }) } +func (c *Core) RetryPeersNow() { + if c.addPeerTimer != nil && !c.addPeerTimer.Stop() { + <-c.addPeerTimer.C + } + c.Act(nil, c._addPeerLoop) +} + // Stop shuts down the Yggdrasil node. func (c *Core) Stop() { phony.Block(c, func() { diff --git a/src/core/link.go b/src/core/link.go index 550d7ec2..fa2e3325 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -34,6 +34,11 @@ type linkInfo struct { remote string // Remote name or address } +type linkDial struct { + url *url.URL + sintf string +} + type link struct { lname string links *links @@ -105,9 +110,10 @@ func (l *links) isConnectedTo(info linkInfo) bool { return isConnected } -func (l *links) call(u *url.URL, sintf string) (linkInfo, error) { - info := linkInfoFor(u.Scheme, sintf, u.Host) +func (l *links) call(u *url.URL, sintf string, errch chan<- error) (info linkInfo, err error) { + info = linkInfoFor(u.Scheme, sintf, u.Host) if l.isConnectedTo(info) { + close(errch) // already connected, no error return info, nil } options := linkOptions{ @@ -116,6 +122,7 @@ func (l *links) call(u *url.URL, sintf string) (linkInfo, error) { for _, pubkey := range u.Query()["key"] { sigPub, err := hex.DecodeString(pubkey) if err != nil { + close(errch) return info, fmt.Errorf("pinned key contains invalid hex characters") } var sigPubKey keyArray @@ -125,6 +132,7 @@ func (l *links) call(u *url.URL, sintf string) (linkInfo, error) { if p := u.Query().Get("priority"); p != "" { pi, err := strconv.ParseUint(p, 10, 8) if err != nil { + close(errch) return info, fmt.Errorf("priority invalid: %w", err) } options.priority = uint8(pi) @@ -132,15 +140,27 @@ func (l *links) call(u *url.URL, sintf string) (linkInfo, error) { switch info.linkType { case "tcp": go func() { + if errch != nil { + defer close(errch) + } if err := l.tcp.dial(u, options, sintf); err != nil && err != io.EOF { l.core.log.Warnf("Failed to dial TCP %s: %s\n", u.Host, err) + if errch != nil { + errch <- err + } } }() case "socks": go func() { + if errch != nil { + defer close(errch) + } if err := l.socks.dial(u, options); err != nil && err != io.EOF { l.core.log.Warnf("Failed to dial SOCKS %s: %s\n", u.Host, err) + if errch != nil { + errch <- err + } } }() @@ -163,19 +183,32 @@ func (l *links) call(u *url.URL, sintf string) (linkInfo, error) { } } go func() { + if errch != nil { + defer close(errch) + } if err := l.tls.dial(u, options, sintf, tlsSNI); err != nil && err != io.EOF { l.core.log.Warnf("Failed to dial TLS %s: %s\n", u.Host, err) + if errch != nil { + errch <- err + } } }() case "unix": go func() { + if errch != nil { + defer close(errch) + } if err := l.unix.dial(u, options, sintf); err != nil && err != io.EOF { l.core.log.Warnf("Failed to dial UNIX %s: %s\n", u.Host, err) + if errch != nil { + errch <- err + } } }() default: + close(errch) return info, errors.New("unknown call scheme: " + u.Scheme) } return info, nil @@ -197,7 +230,7 @@ func (l *links) listen(u *url.URL, sintf string) (*Listener, error) { return listener, err } -func (l *links) create(conn net.Conn, name string, info linkInfo, incoming, force bool, options linkOptions) error { +func (l *links) create(conn net.Conn, dial *linkDial, name string, info linkInfo, incoming, force bool, options linkOptions) error { intf := link{ conn: &linkConn{ Conn: conn, @@ -211,14 +244,14 @@ func (l *links) create(conn net.Conn, name string, info linkInfo, incoming, forc force: force, } go func() { - if err := intf.handler(); err != nil { + if err := intf.handler(dial); err != nil { l.core.log.Errorf("Link handler %s error (%s): %s", name, conn.RemoteAddr(), err) } }() return nil } -func (intf *link) handler() error { +func (intf *link) handler(dial *linkDial) error { defer intf.conn.Close() // nolint:errcheck // Don't connect to this link more than once. @@ -321,6 +354,30 @@ func (intf *link) handler() error { intf.links.core.log.Infof("Disconnected %s %s: %s, source %s; error: %s", dir, strings.ToUpper(intf.info.linkType), remoteStr, localStr, err) } + + if !intf.incoming && dial != nil { + // The connection was one that we dialled, so wait a second and try to + // dial it again. + var retry func(attempt int) + retry = func(attempt int) { + // intf.links.core.log.Infof("Retrying %s (attempt %d of 5)...", dial.url.String(), attempt) + errch := make(chan error, 1) + if _, err := intf.links.call(dial.url, dial.sintf, errch); err != nil { + return + } + if err := <-errch; err != nil { + if attempt < 3 { + time.AfterFunc(time.Second, func() { + retry(attempt + 1) + }) + } + } + } + time.AfterFunc(time.Second, func() { + retry(1) + }) + } + return nil } diff --git a/src/core/link_socks.go b/src/core/link_socks.go index 036de992..4cdffa51 100644 --- a/src/core/link_socks.go +++ b/src/core/link_socks.go @@ -37,16 +37,20 @@ func (l *linkSOCKS) dial(url *url.URL, options linkOptions) error { if err != nil { return err } - return l.handler(url.String(), info, conn, options, false) + dial := &linkDial{ + url: url, + } + return l.handler(dial, info, conn, options, false) } -func (l *linkSOCKS) handler(name string, info linkInfo, conn net.Conn, options linkOptions, incoming bool) error { +func (l *linkSOCKS) handler(dial *linkDial, info linkInfo, conn net.Conn, options linkOptions, incoming bool) error { return l.links.create( - conn, // connection - name, // connection name - info, // connection info - incoming, // not incoming - false, // not forced - options, // connection options + conn, // connection + dial, // connection URL + dial.url.String(), // connection name + info, // connection info + incoming, // not incoming + false, // not forced + options, // connection options ) } diff --git a/src/core/link_tcp.go b/src/core/link_tcp.go index 9c3c3290..714ea9d9 100644 --- a/src/core/link_tcp.go +++ b/src/core/link_tcp.go @@ -47,8 +47,12 @@ func (l *linkTCP) dial(url *url.URL, options linkOptions, sintf string) error { if err != nil { return err } - uri := strings.TrimRight(strings.SplitN(url.String(), "?", 2)[0], "/") - return l.handler(uri, info, conn, options, false, false) + name := strings.TrimRight(strings.SplitN(url.String(), "?", 2)[0], "/") + dial := &linkDial{ + url: url, + sintf: sintf, + } + return l.handler(dial, name, info, conn, options, false, false) } func (l *linkTCP) listen(url *url.URL, sintf string) (*Listener, error) { @@ -86,7 +90,7 @@ func (l *linkTCP) listen(url *url.URL, sintf string) (*Listener, error) { raddr := conn.RemoteAddr().(*net.TCPAddr) name := fmt.Sprintf("tcp://%s", raddr) info := linkInfoFor("tcp", sintf, tcpIDFor(laddr, raddr)) - if err = l.handler(name, info, conn, linkOptionsForListener(url), true, raddr.IP.IsLinkLocalUnicast()); err != nil { + if err = l.handler(nil, name, info, conn, linkOptionsForListener(url), true, raddr.IP.IsLinkLocalUnicast()); err != nil { l.core.log.Errorln("Failed to create inbound link:", err) } } @@ -97,9 +101,10 @@ func (l *linkTCP) listen(url *url.URL, sintf string) (*Listener, error) { return entry, nil } -func (l *linkTCP) handler(name string, info linkInfo, conn net.Conn, options linkOptions, incoming, force bool) error { +func (l *linkTCP) handler(dial *linkDial, name string, info linkInfo, conn net.Conn, options linkOptions, incoming, force bool) error { return l.links.create( conn, // connection + dial, // connection URL name, // connection name info, // connection info incoming, // not incoming diff --git a/src/core/link_tls.go b/src/core/link_tls.go index fbc6172e..dda0e2fe 100644 --- a/src/core/link_tls.go +++ b/src/core/link_tls.go @@ -69,8 +69,12 @@ func (l *linkTLS) dial(url *url.URL, options linkOptions, sintf, sni string) err if err != nil { return err } - uri := strings.TrimRight(strings.SplitN(url.String(), "?", 2)[0], "/") - return l.handler(uri, info, conn, options, false, false) + name := strings.TrimRight(strings.SplitN(url.String(), "?", 2)[0], "/") + dial := &linkDial{ + url: url, + sintf: sintf, + } + return l.handler(dial, name, info, conn, options, false, false) } func (l *linkTLS) listen(url *url.URL, sintf string) (*Listener, error) { @@ -109,7 +113,7 @@ func (l *linkTLS) listen(url *url.URL, sintf string) (*Listener, error) { raddr := conn.RemoteAddr().(*net.TCPAddr) name := fmt.Sprintf("tls://%s", raddr) info := linkInfoFor("tls", sintf, tcpIDFor(laddr, raddr)) - if err = l.handler(name, info, conn, linkOptionsForListener(url), true, raddr.IP.IsLinkLocalUnicast()); err != nil { + if err = l.handler(nil, name, info, conn, linkOptionsForListener(url), true, raddr.IP.IsLinkLocalUnicast()); err != nil { l.core.log.Errorln("Failed to create inbound link:", err) } } @@ -165,6 +169,6 @@ func (l *linkTLS) generateConfig() (*tls.Config, error) { }, nil } -func (l *linkTLS) handler(name string, info linkInfo, conn net.Conn, options linkOptions, incoming, force bool) error { - return l.tcp.handler(name, info, conn, options, incoming, force) +func (l *linkTLS) handler(dial *linkDial, name string, info linkInfo, conn net.Conn, options linkOptions, incoming, force bool) error { + return l.tcp.handler(dial, name, info, conn, options, incoming, force) } diff --git a/src/core/link_unix.go b/src/core/link_unix.go index 50183a25..7f782575 100644 --- a/src/core/link_unix.go +++ b/src/core/link_unix.go @@ -45,7 +45,10 @@ func (l *linkUNIX) dial(url *url.URL, options linkOptions, _ string) error { if err != nil { return err } - return l.handler(url.String(), info, conn, options, false) + dial := &linkDial{ + url: url, + } + return l.handler(dial, url.String(), info, conn, options, false) } func (l *linkUNIX) listen(url *url.URL, _ string) (*Listener, error) { @@ -74,7 +77,7 @@ func (l *linkUNIX) listen(url *url.URL, _ string) (*Listener, error) { break } info := linkInfoFor("unix", "", url.String()) - if err = l.handler(url.String(), info, conn, linkOptionsForListener(url), true); err != nil { + if err = l.handler(nil, url.String(), info, conn, linkOptionsForListener(url), true); err != nil { l.core.log.Errorln("Failed to create inbound link:", err) } } @@ -85,9 +88,10 @@ func (l *linkUNIX) listen(url *url.URL, _ string) (*Listener, error) { return entry, nil } -func (l *linkUNIX) handler(name string, info linkInfo, conn net.Conn, options linkOptions, incoming bool) error { +func (l *linkUNIX) handler(dial *linkDial, name string, info linkInfo, conn net.Conn, options linkOptions, incoming bool) error { return l.links.create( conn, // connection + dial, // connection URL name, // connection name info, // connection info incoming, // not incoming diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 8d7fbb7d..ec145230 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -30,6 +30,7 @@ type Multicast struct { _isOpen bool _listeners map[string]*listenerInfo _interfaces map[string]*interfaceInfo + _timer *time.Timer config struct { _groupAddr GroupAddress _interfaces map[MulticastInterface]struct{} @@ -207,6 +208,15 @@ func (m *Multicast) _getAllowedInterfaces() map[string]*interfaceInfo { return interfaces } +func (m *Multicast) AnnounceNow() { + phony.Block(m, func() { + if m._timer != nil && !m._timer.Stop() { + <-m._timer.C + } + m.Act(nil, m._announce) + }) +} + func (m *Multicast) _announce() { if !m._isOpen { return @@ -329,7 +339,7 @@ func (m *Multicast) _announce() { break } } - time.AfterFunc(time.Second, func() { + m._timer = time.AfterFunc(time.Second, func() { m.Act(nil, m._announce) }) } From e824c73e21bd06756189765ee5e39532a82a2a16 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 12 Nov 2022 11:56:50 +0000 Subject: [PATCH 19/25] Fix crash --- src/core/link.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/core/link.go b/src/core/link.go index fa2e3325..933e3983 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -113,7 +113,9 @@ func (l *links) isConnectedTo(info linkInfo) bool { func (l *links) call(u *url.URL, sintf string, errch chan<- error) (info linkInfo, err error) { info = linkInfoFor(u.Scheme, sintf, u.Host) if l.isConnectedTo(info) { - close(errch) // already connected, no error + if errch != nil { + close(errch) // already connected, no error + } return info, nil } options := linkOptions{ @@ -122,7 +124,9 @@ func (l *links) call(u *url.URL, sintf string, errch chan<- error) (info linkInf for _, pubkey := range u.Query()["key"] { sigPub, err := hex.DecodeString(pubkey) if err != nil { - close(errch) + if errch != nil { + close(errch) + } return info, fmt.Errorf("pinned key contains invalid hex characters") } var sigPubKey keyArray @@ -132,7 +136,9 @@ func (l *links) call(u *url.URL, sintf string, errch chan<- error) (info linkInf if p := u.Query().Get("priority"); p != "" { pi, err := strconv.ParseUint(p, 10, 8) if err != nil { - close(errch) + if errch != nil { + close(errch) + } return info, fmt.Errorf("priority invalid: %w", err) } options.priority = uint8(pi) @@ -208,7 +214,9 @@ func (l *links) call(u *url.URL, sintf string, errch chan<- error) (info linkInf }() default: - close(errch) + if errch != nil { + close(errch) + } return info, errors.New("unknown call scheme: " + u.Scheme) } return info, nil From 9df3bc0066d626d43d66588c70d14999e58f2d79 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 12 Nov 2022 15:26:43 +0000 Subject: [PATCH 20/25] Update to Arceliar/ironwood@846a97f5e5bb7b4002638c1c33790707a7060e8f --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index adaf7cf8..7c7da09c 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/yggdrasil-network/yggdrasil-go go 1.17 require ( - github.com/Arceliar/ironwood v0.0.0-20221025225125-45b4281814c2 + github.com/Arceliar/ironwood v0.0.0-20221112140720-846a97f5e5bb github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 github.com/cheggaaa/pb/v3 v3.0.8 github.com/gologme/log v1.2.0 @@ -12,7 +12,7 @@ require ( github.com/kardianos/minwinsvc v1.0.2 github.com/mitchellh/mapstructure v1.4.1 github.com/vishvananda/netlink v1.1.0 - golang.org/x/mobile v0.0.0-20221020085226-b36e6246172e + golang.org/x/mobile v0.0.0-20221110043201-43a038452099 golang.org/x/net v0.0.0-20221014081412-f15817d10f9b golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 golang.org/x/text v0.3.8 diff --git a/go.sum b/go.sum index e398aca6..69558e3a 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Arceliar/ironwood v0.0.0-20221025225125-45b4281814c2 h1:Usab30pNT2i/vZvpXcN9uOr5IO1RZPcUqoGH0DIAPnU= -github.com/Arceliar/ironwood v0.0.0-20221025225125-45b4281814c2/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= +github.com/Arceliar/ironwood v0.0.0-20221112140720-846a97f5e5bb h1:O/+9TuKlOuaVagYOApNdOgf+PUbgM+mZ+bpo0L0wddM= +github.com/Arceliar/ironwood v0.0.0-20221112140720-846a97f5e5bb/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 h1:WndgpSW13S32VLQ3ugUxx2EnnWmgba1kCqPkd4Gk1yQ= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -55,8 +55,8 @@ golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9t golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20221020085226-b36e6246172e h1:zSgtO19fpg781xknwqiQPmOHaASr6E7ZVlTseLd9Fx4= -golang.org/x/mobile v0.0.0-20221020085226-b36e6246172e/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY= +golang.org/x/mobile v0.0.0-20221110043201-43a038452099 h1:aIu0lKmfdgtn2uTj7JI2oN4TUrQvgB+wzTPO23bCKt8= +golang.org/x/mobile v0.0.0-20221110043201-43a038452099/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= From cba667f28da202ec1d1bbbe24d98b50eb0680afe Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 12 Nov 2022 16:47:20 +0000 Subject: [PATCH 21/25] Fix race conditions (update to Arceliar/ironwood@2c0740b) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 7c7da09c..a8517360 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/yggdrasil-network/yggdrasil-go go 1.17 require ( - github.com/Arceliar/ironwood v0.0.0-20221112140720-846a97f5e5bb + github.com/Arceliar/ironwood v0.0.0-20221112164410-2c0740b8ceca github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 github.com/cheggaaa/pb/v3 v3.0.8 github.com/gologme/log v1.2.0 diff --git a/go.sum b/go.sum index 69558e3a..04819a68 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Arceliar/ironwood v0.0.0-20221112140720-846a97f5e5bb h1:O/+9TuKlOuaVagYOApNdOgf+PUbgM+mZ+bpo0L0wddM= -github.com/Arceliar/ironwood v0.0.0-20221112140720-846a97f5e5bb/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= +github.com/Arceliar/ironwood v0.0.0-20221112164410-2c0740b8ceca h1:tWBRn22W6q4aEb+jPUZZ3vaJjZGbJ/6ON6QVmzJFRlE= +github.com/Arceliar/ironwood v0.0.0-20221112164410-2c0740b8ceca/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 h1:WndgpSW13S32VLQ3ugUxx2EnnWmgba1kCqPkd4Gk1yQ= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= From ae24f5de38885bc17adb63f5726459beb3fcaa1c Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 12 Nov 2022 16:55:23 +0000 Subject: [PATCH 22/25] Less aggressive key ratcheting (update to Arceliar/ironwood@bf5f12a) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a8517360..74b88aa4 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/yggdrasil-network/yggdrasil-go go 1.17 require ( - github.com/Arceliar/ironwood v0.0.0-20221112164410-2c0740b8ceca + github.com/Arceliar/ironwood v0.0.0-20221112165445-bf5f12ab2b26 github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 github.com/cheggaaa/pb/v3 v3.0.8 github.com/gologme/log v1.2.0 diff --git a/go.sum b/go.sum index 04819a68..f3904fc0 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Arceliar/ironwood v0.0.0-20221112164410-2c0740b8ceca h1:tWBRn22W6q4aEb+jPUZZ3vaJjZGbJ/6ON6QVmzJFRlE= -github.com/Arceliar/ironwood v0.0.0-20221112164410-2c0740b8ceca/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= +github.com/Arceliar/ironwood v0.0.0-20221112165445-bf5f12ab2b26 h1:RJIZ+FIOx4v3SYaR236RwmBOyK6FJNA/+6Sq1CCf9M4= +github.com/Arceliar/ironwood v0.0.0-20221112165445-bf5f12ab2b26/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 h1:WndgpSW13S32VLQ3ugUxx2EnnWmgba1kCqPkd4Gk1yQ= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= From 596f16aa6cf8e2cba07747ea2971c3ed4973caf8 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 15 Nov 2022 12:46:08 +0000 Subject: [PATCH 23/25] Reduce allocations in `encrypted` package (update to Arceliar/ironwood@ec61cea) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 74b88aa4..7d6b3bd5 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/yggdrasil-network/yggdrasil-go go 1.17 require ( - github.com/Arceliar/ironwood v0.0.0-20221112165445-bf5f12ab2b26 + github.com/Arceliar/ironwood v0.0.0-20221115123222-ec61cea2f439 github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 github.com/cheggaaa/pb/v3 v3.0.8 github.com/gologme/log v1.2.0 diff --git a/go.sum b/go.sum index f3904fc0..3c2f7310 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Arceliar/ironwood v0.0.0-20221112165445-bf5f12ab2b26 h1:RJIZ+FIOx4v3SYaR236RwmBOyK6FJNA/+6Sq1CCf9M4= -github.com/Arceliar/ironwood v0.0.0-20221112165445-bf5f12ab2b26/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= +github.com/Arceliar/ironwood v0.0.0-20221115123222-ec61cea2f439 h1:eOW6/XIs06TnUn9GPCnfv71CQZw8edP3u3mH3lZt6iM= +github.com/Arceliar/ironwood v0.0.0-20221115123222-ec61cea2f439/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 h1:WndgpSW13S32VLQ3ugUxx2EnnWmgba1kCqPkd4Gk1yQ= github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= From 48d278bd2a24021ff1ea3a990984b6a4d4ddaab7 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 15 Nov 2022 19:06:14 +0000 Subject: [PATCH 24/25] Version 0.4.7 changelog (#985) --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index af827b21..9a144c13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,24 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - in case of vulnerabilities. --> +## [0.4.7] - 2022-11-19 + +### Added + +- Dropped outbound peerings will now try to reconnect after a single second, rather than waiting up to 60 seconds for the normal peer timer + +### Changed + +- Session encryption keys are now rotated at most once per minute, which reduces CPU usage and improves throughput on fast low latency links +- Buffers are now reused in the session encryption handler, which improves session throughput and reduces memory allocations +- Buffers are now reused in the router for DHT and path traffic, which improves overall routing throughput and reduces memory allocations + +### Fixed + +- A bug in the admin socket where requests fail unless `arguments` is specified has been fixed +- Certificates on TLS listeners will no longer expire after a year +- The `-address` and `-subnet` command line options now return a useful warning when no configuration is specified + ## [0.4.6] - 2022-10-25 ### Added From b0f6544b079240c17157072dd3d3dc8d5400a0a4 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 20 Nov 2022 21:14:33 +0000 Subject: [PATCH 25/25] Update changelog date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a144c13..2a43a4d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - in case of vulnerabilities. --> -## [0.4.7] - 2022-11-19 +## [0.4.7] - 2022-11-20 ### Added