From 834680045a9738df7940cfbab74ccc5e94ff6509 Mon Sep 17 00:00:00 2001 From: Klemens Nanni Date: Mon, 11 Nov 2024 22:27:02 +0300 Subject: [PATCH 01/15] Create admin socket synchronously before privdrop (#1201) Creating UNIX sockets the listen() goroutine that races against the main one dropping to an unprivileged user may cause startup failure when privdrop happens before privileged filesystem access. Setup or fail in New() and only do listen(2) in listen() to avoid this. ``` # yggdrasil -autoconf -user nobody 2024/11/03 21:15:27 Build name: yggdrasil-go 2024/11/03 21:15:27 Build version: 0.5.9 ... 2024/11/03 21:15:27 Admin socket failed to listen: listen unix /var/run/yggdrasil.sock: bind: permission denied ``` Rerun, now the order is flipped: ``` # yggdrasil -autoconf -user nobody 2024/11/03 21:15:34 Build name: yggdrasil-go 2024/11/03 21:15:34 Build version: 0.5.9 [...] 2024/11/03 21:15:34 UNIX admin socket listening on /var/run/yggdrasil.sock [...] ``` Fixes #927. --- src/admin/admin.go | 90 +++++++++++++++++++++++----------------------- 1 file changed, 46 insertions(+), 44 deletions(-) diff --git a/src/admin/admin.go b/src/admin/admin.go index 8823fc5e..996cabd5 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -83,6 +83,52 @@ func New(c *core.Core, log core.Logger, opts ...SetupOption) (*AdminSocket, erro if a.config.listenaddr == "none" || a.config.listenaddr == "" { return nil, nil } + + listenaddr := string(a.config.listenaddr) + u, err := url.Parse(listenaddr) + if err == nil { + switch strings.ToLower(u.Scheme) { + case "unix": + if _, err := os.Stat(u.Path); err == nil { + a.log.Debugln("Admin socket", u.Path, "already exists, trying to clean up") + if _, err := net.DialTimeout("unix", u.Path, time.Second*2); err == nil || err.(net.Error).Timeout() { + a.log.Errorln("Admin socket", u.Path, "already exists and is in use by another process") + os.Exit(1) + } else { + if err := os.Remove(u.Path); err == nil { + a.log.Debugln(u.Path, "was cleaned up") + } else { + a.log.Errorln(u.Path, "already exists and was not cleaned up:", err) + os.Exit(1) + } + } + } + a.listener, err = net.Listen("unix", u.Path) + if err == nil { + switch u.Path[:1] { + case "@": // maybe abstract namespace + default: + if err := os.Chmod(u.Path, 0660); err != nil { + a.log.Warnln("WARNING:", u.Path, "may have unsafe permissions!") + } + } + } + case "tcp": + a.listener, err = net.Listen("tcp", u.Host) + default: + a.listener, err = net.Listen("tcp", listenaddr) + } + } else { + a.listener, err = net.Listen("tcp", listenaddr) + } + if err != nil { + a.log.Errorf("Admin socket failed to listen: %v", err) + os.Exit(1) + } + a.log.Infof("%s admin socket listening on %s", + strings.ToUpper(a.listener.Addr().Network()), + a.listener.Addr().String()) + _ = a.AddHandler("list", "List available commands", []string{}, func(_ json.RawMessage) (interface{}, error) { res := &ListResponse{} for name, handler := range a.handlers { @@ -233,50 +279,6 @@ func (a *AdminSocket) Stop() error { // listen is run by start and manages API connections. func (a *AdminSocket) listen() { - listenaddr := string(a.config.listenaddr) - u, err := url.Parse(listenaddr) - if err == nil { - switch strings.ToLower(u.Scheme) { - case "unix": - if _, err := os.Stat(u.Path); err == nil { - a.log.Debugln("Admin socket", u.Path, "already exists, trying to clean up") - if _, err := net.DialTimeout("unix", u.Path, time.Second*2); err == nil || err.(net.Error).Timeout() { - a.log.Errorln("Admin socket", u.Path, "already exists and is in use by another process") - os.Exit(1) - } else { - if err := os.Remove(u.Path); err == nil { - a.log.Debugln(u.Path, "was cleaned up") - } else { - a.log.Errorln(u.Path, "already exists and was not cleaned up:", err) - os.Exit(1) - } - } - } - a.listener, err = net.Listen("unix", u.Path) - if err == nil { - switch u.Path[:1] { - case "@": // maybe abstract namespace - default: - if err := os.Chmod(u.Path, 0660); err != nil { - a.log.Warnln("WARNING:", u.Path, "may have unsafe permissions!") - } - } - } - case "tcp": - a.listener, err = net.Listen("tcp", u.Host) - default: - a.listener, err = net.Listen("tcp", listenaddr) - } - } else { - a.listener, err = net.Listen("tcp", listenaddr) - } - if err != nil { - a.log.Errorf("Admin socket failed to listen: %v", err) - os.Exit(1) - } - a.log.Infof("%s admin socket listening on %s", - strings.ToUpper(a.listener.Addr().Network()), - a.listener.Addr().String()) defer a.listener.Close() for { conn, err := a.listener.Accept() From 75d2080e53d8b2e98fb471bcc4e561d08ad7f7bc Mon Sep 17 00:00:00 2001 From: Klemens Nanni Date: Mon, 11 Nov 2024 22:28:28 +0300 Subject: [PATCH 02/15] Set groups when dropping privileges to not leak supplementary group access (#1202) Changing the real and effective user/group IDs and the saved set-user/group-ID is not enough to get rid of intial access permissions. The list of groups must be cleared also, otherwise a process changing from, e.g. `root:root` to `nobody:nobody` retains rights to access `:wheel` files (assuming `root` is a member of the `wheel` group). For example: ``` # id uid=0(root) gid=0(wheel) groups=0(wheel), 2(kmem), 3(sys), 4(tty), 5(operator), 20(staff), 31(guest) # ./yggdrasil -autoconf -logto /dev/null -user nobody & [1] 4337 # ps -o command,user,group,supgrp -U nobody COMMAND USER GROUP SUPGRP ./yggdrasil -aut nobody nobody wheel,kmem,sys,tty,operator,staff,guest ``` Fix that so the process runs as mere ``` COMMAND USER GROUP SUPGRP ./yggdrasil -aut nobody nobody nobody ``` Fixes #927. --- cmd/yggdrasil/chuser_unix.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cmd/yggdrasil/chuser_unix.go b/cmd/yggdrasil/chuser_unix.go index 6e802c69..69803773 100644 --- a/cmd/yggdrasil/chuser_unix.go +++ b/cmd/yggdrasil/chuser_unix.go @@ -53,6 +53,9 @@ func chuser(user string) error { gid, _ := strconv.ParseUint(g.Gid, 10, 32) var err error if gid < math.MaxInt { + if err := syscall.Setgroups([]int{int(gid)}); err != nil { + return fmt.Errorf("failed to setgroups %d: %v", gid, err) + } err = syscall.Setgid(int(gid)) } else { err = errors.New("gid too big") @@ -63,6 +66,9 @@ func chuser(user string) error { } } else if u != nil { gid, _ := strconv.ParseUint(u.Gid, 10, 32) + if err := syscall.Setgroups([]int{int(uint32(gid))}); err != nil { + return fmt.Errorf("failed to setgroups %d: %v", gid, err) + } err := syscall.Setgid(int(uint32(gid))) if err != nil { return fmt.Errorf("failed to setgid %d: %v", gid, err) From 42873be09b5912cfbff0053fa94a1cb03112e0cf Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 16 Nov 2024 22:59:03 +0000 Subject: [PATCH 03/15] Reusable peer lookup/dial logic --- cmd/yggdrasil/main.go | 7 ++++++ contrib/mobile/mobile.go | 10 +++++++- src/core/core.go | 1 + src/core/link.go | 38 ++++++++++++++++++++++++++++ src/core/link_quic.go | 29 ++++++++++++--------- src/core/link_socks.go | 46 ++++++++++++++++++++-------------- src/core/link_tcp.go | 54 +++++----------------------------------- src/core/link_tls.go | 36 +++++++++++++-------------- src/core/link_unix.go | 3 --- src/core/link_ws.go | 40 +++++++++++++++++++++-------- src/core/link_wss.go | 48 ++++++++++++++++++++++++++--------- src/core/options.go | 5 ++++ 12 files changed, 193 insertions(+), 124 deletions(-) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 0ae8ab42..3ec6414c 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -191,9 +191,16 @@ func main() { // Set up the Yggdrasil node itself. { + iprange := net.IPNet{ + IP: net.ParseIP("200::"), + Mask: net.CIDRMask(7, 128), + } options := []core.SetupOption{ core.NodeInfo(cfg.NodeInfo), core.NodeInfoPrivacy(cfg.NodeInfoPrivacy), + core.PeerFilter(func(ip net.IP) bool { + return !iprange.Contains(ip) + }), } for _, addr := range cfg.Listen { options = append(options, core.ListenAddress(addr)) diff --git a/contrib/mobile/mobile.go b/contrib/mobile/mobile.go index 06f48027..abc89f1c 100644 --- a/contrib/mobile/mobile.go +++ b/contrib/mobile/mobile.go @@ -53,7 +53,15 @@ func (m *Yggdrasil) StartJSON(configjson []byte) error { } // Set up the Yggdrasil node itself. { - options := []core.SetupOption{} + iprange := net.IPNet{ + IP: net.ParseIP("200::"), + Mask: net.CIDRMask(7, 128), + } + options := []core.SetupOption{ + core.PeerFilter(func(ip net.IP) bool { + return !iprange.Contains(ip) + }), + } for _, peer := range m.config.Peers { options = append(options, core.Peer{URI: peer}) } diff --git a/src/core/core.go b/src/core/core.go index 2b206ee1..a7f9fe96 100644 --- a/src/core/core.go +++ b/src/core/core.go @@ -40,6 +40,7 @@ type Core struct { tls *tls.Config // immutable after startup //_peers map[Peer]*linkInfo // configurable after startup _listeners map[ListenAddress]struct{} // configurable after startup + peerFilter func(ip net.IP) bool // immutable after startup nodeinfo NodeInfo // immutable after startup nodeinfoPrivacy NodeInfoPrivacy // immutable after startup _allowedPublicKeys map[[32]byte]struct{} // configurable after startup diff --git a/src/core/link.go b/src/core/link.go index c2267f24..d7e5b110 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -127,6 +127,7 @@ const ErrLinkPasswordInvalid = linkError("invalid password supplied") const ErrLinkUnrecognisedSchema = linkError("link schema unknown") const ErrLinkMaxBackoffInvalid = linkError("max backoff duration invalid") const ErrLinkSNINotSupported = linkError("SNI not supported on this link type") +const ErrLinkNoSuitableIPs = linkError("no suitable remote IPs") func (l *links) add(u *url.URL, sintf string, linkType linkType) error { var retErr error @@ -653,6 +654,43 @@ func (l *links) handler(linkType linkType, options linkOptions, conn net.Conn, s return err } +func (l *links) findSuitableIP(url *url.URL, fn func(hostname string, ip net.IP, port int) (net.Conn, error)) (net.Conn, error) { + host, p, err := net.SplitHostPort(url.Host) + if err != nil { + return nil, err + } + port, err := strconv.Atoi(p) + if err != nil { + return nil, err + } + resp, err := net.LookupIP(host) + if err != nil { + return nil, err + } + var _ips [64]net.IP + ips := _ips[:0] + for _, ip := range resp { + if l.core.config.peerFilter != nil && !l.core.config.peerFilter(ip) { + continue + } + ips = append(ips, ip) + } + if len(ips) == 0 { + return nil, ErrLinkNoSuitableIPs + } + for _, ip := range ips { + var conn net.Conn + if conn, err = fn(host, ip, port); err != nil { + url := *url + url.RawQuery = "" + l.core.log.Debugln("Dialling", url.Redacted(), "reported error:", err) + continue + } + return conn, nil + } + return nil, err +} + func urlForLinkInfo(u url.URL) url.URL { u.RawQuery = "" return u diff --git a/src/core/link_quic.go b/src/core/link_quic.go index 9ad5456d..ffb69a6d 100644 --- a/src/core/link_quic.go +++ b/src/core/link_quic.go @@ -51,18 +51,23 @@ func (l *links) newLinkQUIC() *linkQUIC { } func (l *linkQUIC) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) { - qc, err := quic.DialAddr(ctx, url.Host, l.tlsconfig, l.quicconfig) - if err != nil { - return nil, err - } - qs, err := qc.OpenStreamSync(ctx) - if err != nil { - return nil, err - } - return &linkQUICStream{ - Connection: qc, - Stream: qs, - }, nil + tlsconfig := l.tlsconfig.Clone() + return l.links.findSuitableIP(url, func(hostname string, ip net.IP, port int) (net.Conn, error) { + tlsconfig.ServerName = hostname + hostport := net.JoinHostPort(ip.String(), fmt.Sprintf("%d", port)) + qc, err := quic.DialAddr(ctx, hostport, l.tlsconfig, l.quicconfig) + if err != nil { + return nil, err + } + qs, err := qc.OpenStreamSync(ctx) + if err != nil { + return nil, err + } + return &linkQUICStream{ + Connection: qc, + Stream: qs, + }, nil + }) } func (l *linkQUIC) listen(ctx context.Context, url *url.URL, _ string) (net.Listener, error) { diff --git a/src/core/link_socks.go b/src/core/link_socks.go index 0f66661b..f33cd190 100644 --- a/src/core/link_socks.go +++ b/src/core/link_socks.go @@ -23,9 +23,6 @@ func (l *links) newLinkSOCKS() *linkSOCKS { } func (l *linkSOCKS) dial(_ context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) { - if url.Scheme != "sockstls" && options.tlsSNI != "" { - return nil, ErrLinkSNINotSupported - } var proxyAuth *proxy.Auth if url.User != nil && url.User.Username() != "" { proxyAuth = &proxy.Auth{ @@ -33,21 +30,34 @@ func (l *linkSOCKS) dial(_ context.Context, url *url.URL, info linkInfo, options } proxyAuth.Password, _ = url.User.Password() } - dialer, err := proxy.SOCKS5("tcp", url.Host, proxyAuth, proxy.Direct) - if err != nil { - return nil, fmt.Errorf("failed to configure proxy") - } - pathtokens := strings.Split(strings.Trim(url.Path, "/"), "/") - conn, err := dialer.Dial("tcp", pathtokens[0]) - if err != nil { - return nil, fmt.Errorf("failed to dial: %w", err) - } - if url.Scheme == "sockstls" { - tlsconfig := l.tls.config.Clone() - tlsconfig.ServerName = options.tlsSNI - conn = tls.Client(conn, tlsconfig) - } - return conn, nil + tlsconfig := l.tls.config.Clone() + return l.links.findSuitableIP(url, func(hostname string, ip net.IP, port int) (net.Conn, error) { + hostport := net.JoinHostPort(ip.String(), fmt.Sprintf("%d", port)) + dialer, err := l.tcp.dialerFor(&net.TCPAddr{ + IP: ip, + Port: port, + }, info.sintf) + if err != nil { + return nil, err + } + proxy, err := proxy.SOCKS5("tcp", hostport, proxyAuth, dialer) + if err != nil { + return nil, err + } + pathtokens := strings.Split(strings.Trim(url.Path, "/"), "/") + conn, err := proxy.Dial("tcp", pathtokens[0]) + if err != nil { + return nil, err + } + if url.Scheme == "sockstls" { + tlsconfig.ServerName = hostname + if sni := options.tlsSNI; sni != "" { + tlsconfig.ServerName = sni + } + conn = tls.Client(conn, tlsconfig) + } + return conn, nil + }) } func (l *linkSOCKS) listen(ctx context.Context, url *url.URL, _ string) (net.Listener, error) { diff --git a/src/core/link_tcp.go b/src/core/link_tcp.go index 38c42def..3315f5c7 100644 --- a/src/core/link_tcp.go +++ b/src/core/link_tcp.go @@ -5,7 +5,6 @@ import ( "fmt" "net" "net/url" - "strconv" "time" "github.com/Arceliar/phony" @@ -34,59 +33,18 @@ type tcpDialer struct { addr *net.TCPAddr } -func (l *linkTCP) dialersFor(url *url.URL, info linkInfo) ([]*tcpDialer, error) { - host, p, err := net.SplitHostPort(url.Host) - if err != nil { - return nil, err - } - port, err := strconv.Atoi(p) - if err != nil { - return nil, err - } - ips, err := net.LookupIP(host) - if err != nil { - return nil, err - } - dialers := make([]*tcpDialer, 0, len(ips)) - for _, ip := range ips { +func (l *linkTCP) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) { + return l.links.findSuitableIP(url, func(hostname string, ip net.IP, port int) (net.Conn, error) { addr := &net.TCPAddr{ IP: ip, Port: port, } - dialer, err := l.dialerFor(addr, info.sintf) + dialer, err := l.tcp.dialerFor(addr, info.sintf) if err != nil { - continue + return nil, err } - dialers = append(dialers, &tcpDialer{ - info: info, - dialer: dialer, - addr: addr, - }) - } - return dialers, nil -} - -func (l *linkTCP) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) { - if options.tlsSNI != "" { - return nil, ErrLinkSNINotSupported - } - dialers, err := l.dialersFor(url, info) - if err != nil { - return nil, err - } - if len(dialers) == 0 { - return nil, nil - } - for _, d := range dialers { - var conn net.Conn - conn, err = d.dialer.DialContext(ctx, "tcp", d.addr.String()) - if err != nil { - l.core.log.Warnf("Failed to connect to %s: %s", d.addr, err) - continue - } - return conn, nil - } - return nil, err + return dialer.DialContext(ctx, "tcp", addr.String()) + }) } func (l *linkTCP) listen(ctx context.Context, url *url.URL, sintf string) (net.Listener, error) { diff --git a/src/core/link_tls.go b/src/core/link_tls.go index d51d0ce5..da3c7791 100644 --- a/src/core/link_tls.go +++ b/src/core/link_tls.go @@ -32,28 +32,26 @@ func (l *links) newLinkTLS(tcp *linkTCP) *linkTLS { } func (l *linkTLS) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) { - dialers, err := l.tcp.dialersFor(url, info) - if err != nil { - return nil, err - } - if len(dialers) == 0 { - return nil, nil - } - for _, d := range dialers { - tlsconfig := l.config.Clone() - tlsconfig.ServerName = options.tlsSNI + tlsconfig := l.config.Clone() + return l.links.findSuitableIP(url, func(hostname string, ip net.IP, port int) (net.Conn, error) { + tlsconfig.ServerName = hostname + if sni := options.tlsSNI; sni != "" { + tlsconfig.ServerName = sni + } + addr := &net.TCPAddr{ + IP: ip, + Port: port, + } + dialer, err := l.tcp.dialerFor(addr, info.sintf) + if err != nil { + return nil, err + } tlsdialer := &tls.Dialer{ - NetDialer: d.dialer, + NetDialer: dialer, Config: tlsconfig, } - var conn net.Conn - conn, err = tlsdialer.DialContext(ctx, "tcp", d.addr.String()) - if err != nil { - continue - } - return conn, nil - } - return nil, err + return tlsdialer.DialContext(ctx, "tcp", addr.String()) + }) } func (l *linkTLS) listen(ctx context.Context, url *url.URL, sintf string) (net.Listener, error) { diff --git a/src/core/link_unix.go b/src/core/link_unix.go index 1da6931d..ddbfa0ad 100644 --- a/src/core/link_unix.go +++ b/src/core/link_unix.go @@ -31,9 +31,6 @@ func (l *links) newLinkUNIX() *linkUNIX { } func (l *linkUNIX) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) { - if options.tlsSNI != "" { - return nil, ErrLinkSNINotSupported - } addr, err := net.ResolveUnixAddr("unix", url.Path) if err != nil { return nil, err diff --git a/src/core/link_ws.go b/src/core/link_ws.go index 59816098..86f065a6 100644 --- a/src/core/link_ws.go +++ b/src/core/link_ws.go @@ -2,6 +2,7 @@ package core import ( "context" + "fmt" "net" "net/http" "net/url" @@ -87,18 +88,35 @@ func (l *links) newLinkWS() *linkWS { } func (l *linkWS) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) { - if options.tlsSNI != "" { - return nil, ErrLinkSNINotSupported - } - wsconn, _, err := websocket.Dial(ctx, url.String(), &websocket.DialOptions{ - Subprotocols: []string{"ygg-ws"}, + return l.links.findSuitableIP(url, func(hostname string, ip net.IP, port int) (net.Conn, error) { + u := *url + u.Host = net.JoinHostPort(ip.String(), fmt.Sprintf("%d", port)) + addr := &net.TCPAddr{ + IP: ip, + Port: port, + } + dialer, err := l.tcp.dialerFor(addr, info.sintf) + if err != nil { + return nil, err + } + wsconn, _, err := websocket.Dial(ctx, u.String(), &websocket.DialOptions{ + HTTPClient: &http.Client{ + Transport: &http.Transport{ + Proxy: http.ProxyFromEnvironment, + Dial: dialer.Dial, + DialContext: dialer.DialContext, + }, + }, + Subprotocols: []string{"ygg-ws"}, + Host: hostname, + }) + if err != nil { + return nil, err + } + return &linkWSConn{ + Conn: websocket.NetConn(ctx, wsconn, websocket.MessageBinary), + }, nil }) - if err != nil { - return nil, err - } - return &linkWSConn{ - Conn: websocket.NetConn(ctx, wsconn, websocket.MessageBinary), - }, nil } func (l *linkWS) listen(ctx context.Context, url *url.URL, _ string) (net.Listener, error) { diff --git a/src/core/link_wss.go b/src/core/link_wss.go index f09d7955..4d968c2f 100644 --- a/src/core/link_wss.go +++ b/src/core/link_wss.go @@ -2,8 +2,10 @@ package core import ( "context" + "crypto/tls" "fmt" "net" + "net/http" "net/url" "github.com/Arceliar/phony" @@ -13,6 +15,7 @@ import ( type linkWSS struct { phony.Inbox *links + tlsconfig *tls.Config } type linkWSSConn struct { @@ -21,24 +24,45 @@ type linkWSSConn struct { func (l *links) newLinkWSS() *linkWSS { lwss := &linkWSS{ - links: l, + links: l, + tlsconfig: l.core.config.tls.Clone(), } return lwss } func (l *linkWSS) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) { - if options.tlsSNI != "" { - return nil, ErrLinkSNINotSupported - } - wsconn, _, err := websocket.Dial(ctx, url.String(), &websocket.DialOptions{ - Subprotocols: []string{"ygg-ws"}, + tlsconfig := l.tlsconfig.Clone() + return l.links.findSuitableIP(url, func(hostname string, ip net.IP, port int) (net.Conn, error) { + tlsconfig.ServerName = hostname + u := *url + u.Host = net.JoinHostPort(ip.String(), fmt.Sprintf("%d", port)) + addr := &net.TCPAddr{ + IP: ip, + Port: port, + } + dialer, err := l.tcp.dialerFor(addr, info.sintf) + if err != nil { + return nil, err + } + wsconn, _, err := websocket.Dial(ctx, u.String(), &websocket.DialOptions{ + HTTPClient: &http.Client{ + Transport: &http.Transport{ + Proxy: http.ProxyFromEnvironment, + Dial: dialer.Dial, + DialContext: dialer.DialContext, + TLSClientConfig: tlsconfig, + }, + }, + Subprotocols: []string{"ygg-ws"}, + Host: hostname, + }) + if err != nil { + return nil, err + } + return &linkWSConn{ + Conn: websocket.NetConn(ctx, wsconn, websocket.MessageBinary), + }, nil }) - if err != nil { - return nil, err - } - return &linkWSSConn{ - Conn: websocket.NetConn(ctx, wsconn, websocket.MessageBinary), - }, nil } func (l *linkWSS) listen(ctx context.Context, url *url.URL, _ string) (net.Listener, error) { diff --git a/src/core/options.go b/src/core/options.go index 581c033b..7e67bfb4 100644 --- a/src/core/options.go +++ b/src/core/options.go @@ -3,6 +3,7 @@ package core import ( "crypto/ed25519" "fmt" + "net" "net/url" ) @@ -24,6 +25,8 @@ func (c *Core) _applyOption(opt SetupOption) (err error) { } case ListenAddress: c.config._listeners[v] = struct{}{} + case PeerFilter: + c.config.peerFilter = v case NodeInfo: c.config.nodeinfo = v case NodeInfoPrivacy: @@ -48,9 +51,11 @@ type Peer struct { type NodeInfo map[string]interface{} type NodeInfoPrivacy bool type AllowedPublicKey ed25519.PublicKey +type PeerFilter func(net.IP) bool func (a ListenAddress) isSetupOption() {} func (a Peer) isSetupOption() {} func (a NodeInfo) isSetupOption() {} func (a NodeInfoPrivacy) isSetupOption() {} func (a AllowedPublicKey) isSetupOption() {} +func (a PeerFilter) isSetupOption() {} From 67ec5a92b33089d1b821717d6e9f96e28f06fe26 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 17 Nov 2024 21:29:26 +0000 Subject: [PATCH 04/15] Fix some lint issues --- src/core/link_tcp.go | 6 ------ src/core/link_wss.go | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/core/link_tcp.go b/src/core/link_tcp.go index 3315f5c7..e50912d3 100644 --- a/src/core/link_tcp.go +++ b/src/core/link_tcp.go @@ -27,12 +27,6 @@ func (l *links) newLinkTCP() *linkTCP { return lt } -type tcpDialer struct { - info linkInfo - dialer *net.Dialer - addr *net.TCPAddr -} - func (l *linkTCP) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) { return l.links.findSuitableIP(url, func(hostname string, ip net.IP, port int) (net.Conn, error) { addr := &net.TCPAddr{ diff --git a/src/core/link_wss.go b/src/core/link_wss.go index 4d968c2f..1a8d571f 100644 --- a/src/core/link_wss.go +++ b/src/core/link_wss.go @@ -59,7 +59,7 @@ func (l *linkWSS) dial(ctx context.Context, url *url.URL, info linkInfo, options if err != nil { return nil, err } - return &linkWSConn{ + return &linkWSSConn{ Conn: websocket.NetConn(ctx, wsconn, websocket.MessageBinary), }, nil }) From c22a746a1d166b0060ce4b0b0496b82a1a96e29b Mon Sep 17 00:00:00 2001 From: Klemens Nanni Date: Mon, 18 Nov 2024 00:37:07 +0300 Subject: [PATCH 05/15] Rewrite chuser() for simplicity and correctness (#1203) - Use unambiguous variable names (w/o package name conflict). - Fail on invalid input such as the empty string or `:`. - Do not change group without user, i.e. fail on `:group`. - Parse input using mnemonic APIs. - Do not juggle between integer types. - Unset supplementary groups. - Use set[ug]id(2) to follow the idiom of OpenBSD base programs. (cannot use setres[ug]id(2) as macOS does not have them.) Includes/Supersedes #1202. Fixes #927. I only tested on OpenBSD (so far), but other systems should just work. --- cmd/yggdrasil/chuser_unix.go | 98 ++++++++++--------------------- cmd/yggdrasil/chuser_unix_test.go | 80 +++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 67 deletions(-) create mode 100644 cmd/yggdrasil/chuser_unix_test.go diff --git a/cmd/yggdrasil/chuser_unix.go b/cmd/yggdrasil/chuser_unix.go index 69803773..fc3e5c2c 100644 --- a/cmd/yggdrasil/chuser_unix.go +++ b/cmd/yggdrasil/chuser_unix.go @@ -4,89 +4,53 @@ package main import ( - "errors" "fmt" - "math" - osuser "os/user" + "os/user" "strconv" "strings" - "syscall" + + "golang.org/x/sys/unix" ) -func chuser(user string) error { - group := "" - if i := strings.IndexByte(user, ':'); i >= 0 { - user, group = user[:i], user[i+1:] - } +func chuser(input string) error { + givenUser, givenGroup, _ := strings.Cut(input, ":") - u := (*osuser.User)(nil) - g := (*osuser.Group)(nil) + var ( + err error + usr *user.User + grp *user.Group + uid, gid int + ) - if user != "" { - if _, err := strconv.ParseUint(user, 10, 32); err == nil { - u, err = osuser.LookupId(user) - if err != nil { - return fmt.Errorf("failed to lookup user by id %q: %v", user, err) - } - } else { - u, err = osuser.Lookup(user) - if err != nil { - return fmt.Errorf("failed to lookup user by name %q: %v", user, err) - } + if usr, err = user.LookupId(givenUser); err != nil { + if usr, err = user.Lookup(givenUser); err != nil { + return err } } - if group != "" { - if _, err := strconv.ParseUint(group, 10, 32); err == nil { - g, err = osuser.LookupGroupId(group) - if err != nil { - return fmt.Errorf("failed to lookup group by id %q: %v", user, err) - } - } else { - g, err = osuser.LookupGroup(group) - if err != nil { - return fmt.Errorf("failed to lookup group by name %q: %v", user, err) - } - } + if uid, err = strconv.Atoi(usr.Uid); err != nil { + return err } - if g != nil { - gid, _ := strconv.ParseUint(g.Gid, 10, 32) - var err error - if gid < math.MaxInt { - if err := syscall.Setgroups([]int{int(gid)}); err != nil { - return fmt.Errorf("failed to setgroups %d: %v", gid, err) + if givenGroup != "" { + if grp, err = user.LookupGroupId(givenGroup); err != nil { + if grp, err = user.LookupGroup(givenGroup); err != nil { + return err } - err = syscall.Setgid(int(gid)) - } else { - err = errors.New("gid too big") } - if err != nil { - return fmt.Errorf("failed to setgid %d: %v", gid, err) - } - } else if u != nil { - gid, _ := strconv.ParseUint(u.Gid, 10, 32) - if err := syscall.Setgroups([]int{int(uint32(gid))}); err != nil { - return fmt.Errorf("failed to setgroups %d: %v", gid, err) - } - err := syscall.Setgid(int(uint32(gid))) - if err != nil { - return fmt.Errorf("failed to setgid %d: %v", gid, err) - } + gid, _ = strconv.Atoi(grp.Gid) + } else { + gid, _ = strconv.Atoi(usr.Gid) } - if u != nil { - uid, _ := strconv.ParseUint(u.Uid, 10, 32) - var err error - if uid < math.MaxInt { - err = syscall.Setuid(int(uid)) - } else { - err = errors.New("uid too big") - } - - if err != nil { - return fmt.Errorf("failed to setuid %d: %v", uid, err) - } + if err := unix.Setgroups([]int{gid}); err != nil { + return fmt.Errorf("setgroups: %d: %v", gid, err) + } + if err := unix.Setgid(gid); err != nil { + return fmt.Errorf("setgid: %d: %v", gid, err) + } + if err := unix.Setuid(uid); err != nil { + return fmt.Errorf("setuid: %d: %v", uid, err) } return nil diff --git a/cmd/yggdrasil/chuser_unix_test.go b/cmd/yggdrasil/chuser_unix_test.go new file mode 100644 index 00000000..ad2e3517 --- /dev/null +++ b/cmd/yggdrasil/chuser_unix_test.go @@ -0,0 +1,80 @@ +//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris +// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris + +package main + +import ( + "testing" + "os/user" +) + +// Usernames must not contain a number sign. +func TestEmptyString (t *testing.T) { + if chuser("") == nil { + t.Fatal("the empty string is not a valid user") + } +} + +// Either omit delimiter and group, or omit both. +func TestEmptyGroup (t *testing.T) { + if chuser("0:") == nil { + t.Fatal("the empty group is not allowed") + } +} + +// Either user only or user and group. +func TestGroupOnly (t *testing.T) { + if chuser(":0") == nil { + t.Fatal("group only is not allowed") + } +} + +// Usenames must not contain the number sign. +func TestInvalidUsername (t *testing.T) { + const username = "#user" + if chuser(username) == nil { + t.Fatalf("'%s' is not a valid username", username) + } +} + +// User IDs must be non-negative. +func TestInvalidUserid (t *testing.T) { + if chuser("-1") == nil { + t.Fatal("User ID cannot be negative") + } +} + +// Change to the current user by ID. +func TestCurrentUserid (t *testing.T) { + usr, err := user.Current() + if err != nil { + t.Fatal(err) + } + + if usr.Uid != "0" { + t.Skip("setgroups(2): Only the superuser may set new groups.") + } + + if err = chuser(usr.Uid); err != nil { + t.Fatal(err) + } +} + +// Change to a common user by name. +func TestCommonUsername (t *testing.T) { + usr, err := user.Current() + if err != nil { + t.Fatal(err) + } + + if usr.Uid != "0" { + t.Skip("setgroups(2): Only the superuser may set new groups.") + } + + if err := chuser("nobody"); err != nil { + if _, ok := err.(user.UnknownUserError); ok { + t.Skip(err) + } + t.Fatal(err) + } +} From 9398cae230170990b5b048f63a22653a714e30ee Mon Sep 17 00:00:00 2001 From: Neil Date: Tue, 19 Nov 2024 08:42:27 +0000 Subject: [PATCH 06/15] Expose download/upload rate per peer (#1206) --- cmd/yggdrasilctl/main.go | 12 ++++++++++-- src/admin/admin.go | 16 +++++++++------- src/admin/getpeers.go | 4 ++++ src/core/api.go | 4 ++++ src/core/link.go | 37 ++++++++++++++++++++++++++++++++++--- 5 files changed, 61 insertions(+), 12 deletions(-) diff --git a/cmd/yggdrasilctl/main.go b/cmd/yggdrasilctl/main.go index 8a30f438..b73708e6 100644 --- a/cmd/yggdrasilctl/main.go +++ b/cmd/yggdrasilctl/main.go @@ -174,9 +174,9 @@ func run() int { if err := json.Unmarshal(recv.Response, &resp); err != nil { panic(err) } - table.SetHeader([]string{"URI", "State", "Dir", "IP Address", "Uptime", "RTT", "RX", "TX", "Pr", "Cost", "Last Error"}) + table.SetHeader([]string{"URI", "State", "Dir", "IP Address", "Uptime", "RTT", "RX", "TX", "Down", "Up", "Pr", "Cost", "Last Error"}) for _, peer := range resp.Peers { - state, lasterr, dir, rtt := "Up", "-", "Out", "-" + state, lasterr, dir, rtt, rxr, txr := "Up", "-", "Out", "-", "-", "-" if !peer.Up { state, lasterr = "Down", fmt.Sprintf("%s ago: %s", peer.LastErrorTime.Round(time.Second), peer.LastError) } else if rttms := float64(peer.Latency.Microseconds()) / 1000; rttms > 0 { @@ -190,6 +190,12 @@ func run() int { uri.RawQuery = "" uristring = uri.String() } + if peer.RXRate > 0 { + rxr = peer.RXRate.String() + "/s" + } + if peer.TXRate > 0 { + txr = peer.TXRate.String() + "/s" + } table.Append([]string{ uristring, state, @@ -199,6 +205,8 @@ func run() int { rtt, peer.RXBytes.String(), peer.TXBytes.String(), + rxr, + txr, fmt.Sprintf("%d", peer.Priority), fmt.Sprintf("%d", peer.Cost), lasterr, diff --git a/src/admin/admin.go b/src/admin/admin.go index 996cabd5..54c1a124 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -356,13 +356,15 @@ type DataUnit uint64 func (d DataUnit) String() string { switch { - case d > 1024*1024*1024*1024: - return fmt.Sprintf("%2.ftb", float64(d)/1024/1024/1024/1024) - case d > 1024*1024*1024: - return fmt.Sprintf("%2.fgb", float64(d)/1024/1024/1024) - case d > 1024*1024: - return fmt.Sprintf("%2.fmb", float64(d)/1024/1024) + case d >= 1024*1024*1024*1024: + return fmt.Sprintf("%2.1fTB", float64(d)/1024/1024/1024/1024) + case d >= 1024*1024*1024: + return fmt.Sprintf("%2.1fGB", float64(d)/1024/1024/1024) + case d >= 1024*1024: + return fmt.Sprintf("%2.1fMB", float64(d)/1024/1024) + case d >= 100: + return fmt.Sprintf("%2.1fKB", float64(d)/1024) default: - return fmt.Sprintf("%2.fkb", float64(d)/1024) + return fmt.Sprintf("%dB", d) } } diff --git a/src/admin/getpeers.go b/src/admin/getpeers.go index 2c2f8d8a..34eca243 100644 --- a/src/admin/getpeers.go +++ b/src/admin/getpeers.go @@ -28,6 +28,8 @@ type PeerEntry struct { Cost uint64 `json:"cost"` RXBytes DataUnit `json:"bytes_recvd,omitempty"` TXBytes DataUnit `json:"bytes_sent,omitempty"` + RXRate DataUnit `json:"rate_recvd,omitempty"` + TXRate DataUnit `json:"rate_sent,omitempty"` Uptime float64 `json:"uptime,omitempty"` Latency time.Duration `json:"latency_ms,omitempty"` LastErrorTime time.Duration `json:"last_error_time,omitempty"` @@ -47,6 +49,8 @@ func (a *AdminSocket) getPeersHandler(_ *GetPeersRequest, res *GetPeersResponse) URI: p.URI, RXBytes: DataUnit(p.RXBytes), TXBytes: DataUnit(p.TXBytes), + RXRate: DataUnit(p.RXRate), + TXRate: DataUnit(p.TXRate), Uptime: p.Uptime.Seconds(), } if p.Latency > 0 { diff --git a/src/core/api.go b/src/core/api.go index 2aa1ba87..cc1bde32 100644 --- a/src/core/api.go +++ b/src/core/api.go @@ -33,6 +33,8 @@ type PeerInfo struct { Cost uint64 RXBytes uint64 TXBytes uint64 + RXRate uint64 + TXRate uint64 Uptime time.Duration Latency time.Duration } @@ -87,6 +89,8 @@ func (c *Core) GetPeers() []PeerInfo { peerinfo.Inbound = state.linkType == linkTypeIncoming peerinfo.RXBytes = atomic.LoadUint64(&c.rx) peerinfo.TXBytes = atomic.LoadUint64(&c.tx) + peerinfo.RXRate = atomic.LoadUint64(&c.rxrate) + peerinfo.TXRate = atomic.LoadUint64(&c.txrate) peerinfo.Uptime = time.Since(c.up) } if p, ok := conns[conn]; ok { diff --git a/src/core/link.go b/src/core/link.go index d7e5b110..c21b8db5 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -99,9 +99,36 @@ func (l *links) init(c *Core) error { l._links = make(map[linkInfo]*link) l._listeners = make(map[*Listener]context.CancelFunc) + l.Act(nil, l._updateAverages) return nil } +func (l *links) _updateAverages() { + select { + case <-l.core.ctx.Done(): + return + default: + } + + for _, l := range l._links { + if l._conn == nil { + continue + } + rx := atomic.LoadUint64(&l._conn.rx) + tx := atomic.LoadUint64(&l._conn.tx) + lastrx := atomic.LoadUint64(&l._conn.lastrx) + lasttx := atomic.LoadUint64(&l._conn.lasttx) + atomic.StoreUint64(&l._conn.rxrate, rx-lastrx) + atomic.StoreUint64(&l._conn.txrate, tx-lasttx) + atomic.StoreUint64(&l._conn.lastrx, rx) + atomic.StoreUint64(&l._conn.lasttx, tx) + } + + time.AfterFunc(time.Second, func() { + l.Act(nil, l._updateAverages) + }) +} + func (l *links) shutdown() { phony.Block(l, func() { for listener := range l._listeners { @@ -699,9 +726,13 @@ func urlForLinkInfo(u url.URL) url.URL { type linkConn struct { // tx and rx are at the beginning of the struct to ensure 64-bit alignment // on 32-bit platforms, see https://pkg.go.dev/sync/atomic#pkg-note-BUG - rx uint64 - tx uint64 - up time.Time + rx uint64 + tx uint64 + rxrate uint64 + txrate uint64 + lastrx uint64 + lasttx uint64 + up time.Time net.Conn } From ff9e90c5aa2d7e177577f2d1044c25a278ae2036 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 22 Nov 2024 09:31:38 +0000 Subject: [PATCH 07/15] Update link cost calculation and next-hop selection (update to Arceliar/ironwood@75a6e82) --- go.mod | 2 +- go.sum | 47 ++--------------------------------------------- 2 files changed, 3 insertions(+), 46 deletions(-) diff --git a/go.mod b/go.mod index 76641a60..27fe1110 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/yggdrasil-network/yggdrasil-go go 1.21 require ( - github.com/Arceliar/ironwood v0.0.0-20241016082300-f6fb9da97a17 + github.com/Arceliar/ironwood v0.0.0-20241122002527-75a6e82fa380 github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d github.com/cheggaaa/pb/v3 v3.1.5 github.com/coder/websocket v1.8.12 diff --git a/go.sum b/go.sum index 89dd0c7b..19674559 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Arceliar/ironwood v0.0.0-20241016082300-f6fb9da97a17 h1:uOvHqPwu09ndYZQDUL6QvyDcz0M9kwooKYa/PEfLwIU= -github.com/Arceliar/ironwood v0.0.0-20241016082300-f6fb9da97a17/go.mod h1:6WP4799FX0OuWdENGQAh+0RXp9FLh0y7NZ7tM9cJyXk= +github.com/Arceliar/ironwood v0.0.0-20241122002527-75a6e82fa380 h1:WRLvBMWzs6NOiPUYA7fMu8XqZFg/clXKorUumfbJNv0= +github.com/Arceliar/ironwood v0.0.0-20241122002527-75a6e82fa380/go.mod h1:SrrElc3FFMpYCODSr11jWbLFeOM8WsY+DbDY/l2AXF0= github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d h1:UK9fsWbWqwIQkMCz1CP+v5pGbsGoWAw6g4AyvMpm1EM= github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d/go.mod h1:BCnxhRf47C/dy/e/D2pmB8NkB3dQVIrkD98b220rx5Q= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= @@ -73,74 +73,31 @@ github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1Y github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU= github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/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/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= -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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4= From b98f98318f9c9f0137f734adc5c6728cc4ae7a2e Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 22 Nov 2024 09:44:30 +0000 Subject: [PATCH 08/15] Tweaks to link handling --- src/core/link.go | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/core/link.go b/src/core/link.go index c21b8db5..899d73bd 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -154,7 +154,7 @@ const ErrLinkPasswordInvalid = linkError("invalid password supplied") const ErrLinkUnrecognisedSchema = linkError("link schema unknown") const ErrLinkMaxBackoffInvalid = linkError("max backoff duration invalid") const ErrLinkSNINotSupported = linkError("SNI not supported on this link type") -const ErrLinkNoSuitableIPs = linkError("no suitable remote IPs") +const ErrLinkNoSuitableIPs = linkError("peer has no suitable addresses") func (l *links) add(u *url.URL, sintf string, linkType linkType) error { var retErr error @@ -365,8 +365,12 @@ func (l *links) add(u *url.URL, sintf string, linkType linkType) error { // Give the connection to the handler. The handler will block // for the lifetime of the connection. - if err = l.handler(linkType, options, lc, resetBackoff, false); err != nil && err != io.EOF { - l.core.log.Debugf("Link %s error: %s\n", info.uri, err) + switch err = l.handler(linkType, options, lc, resetBackoff, false); { + case err == nil: + case errors.Is(err, io.EOF): + case errors.Is(err, net.ErrClosed): + default: + l.core.log.Debugf("Link %s error: %s\n", u.Host, err) } // The handler has stopped running so the connection is dead, @@ -697,7 +701,16 @@ func (l *links) findSuitableIP(url *url.URL, fn func(hostname string, ip net.IP, var _ips [64]net.IP ips := _ips[:0] for _, ip := range resp { - if l.core.config.peerFilter != nil && !l.core.config.peerFilter(ip) { + switch { + case ip.IsUnspecified(): + continue + case ip.IsMulticast(): + continue + case ip.IsLinkLocalMulticast(): + continue + case ip.IsInterfaceLocalMulticast(): + continue + case l.core.config.peerFilter != nil && !l.core.config.peerFilter(ip): continue } ips = append(ips, ip) From 2454970e4dbd55d8e9788e0022f4f0ca065e7af5 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 22 Nov 2024 09:47:33 +0000 Subject: [PATCH 09/15] Tweaks to configuration --- src/config/config.go | 6 +++--- src/config/defaults_darwin.go | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/config/config.go b/src/config/config.go index 64961547..5dd7b3d4 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -47,7 +47,7 @@ type NodeConfig struct { InterfacePeers map[string][]string `comment:"List of connection strings for outbound peer connections in URI format,\narranged by source interface, e.g. { \"eth0\": [ \"tls://a.b.c.d:e\" ] }.\nYou should only use this option if your machine is multi-homed and you\nwant to establish outbound peer connections on different interfaces.\nOtherwise you should use \"Peers\"."` Listen []string `comment:"Listen addresses for incoming connections. You will need to add\nlisteners in order to accept incoming peerings from non-local nodes.\nThis is not required if you wish to establish outbound peerings only.\nMulticast peer discovery will work regardless of any listeners set\nhere. Each listener should be specified in URI format as above, e.g.\ntls://0.0.0.0:0 or tls://[::]:0 to listen on all interfaces."` AdminListen string `json:",omitempty" 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. To disable\nthe admin socket, use the value \"none\" instead."` - MulticastInterfaces []MulticastInterfaceConfig `comment:"Configuration for which interfaces multicast peer discovery should be\nenabled on. Each entry in the list should be a json object which may\ncontain Regex, Beacon, Listen, and Port. Regex is a regular expression\nwhich is matched against an interface name, and interfaces use the\nfirst configuration that they match gainst. Beacon configures whether\nor not the node should send link-local multicast beacons to advertise\ntheir presence, while listening for incoming connections on Port.\nListen controls whether or not the node listens for multicast beacons\nand opens outgoing connections."` + MulticastInterfaces []MulticastInterfaceConfig `comment:"Configuration for which interfaces multicast peer discovery should be\nenabled on. Regex is a regular expression which is matched against an\ninterface name, and interfaces use the first configuration that they\nmatch against. Beacon controls whether or not your node advertises its\npresence to others, whereas Listen controls whether or not your node\nlistens out for and tries to connect to other advertising nodes. See\nhttps://yggdrasil-network.github.io/configurationref.html#multicastinterfaces\nfor more supported options."` AllowedPublicKeys []string `comment:"List of peer public keys to allow incoming peering connections\nfrom. If left empty/undefined then all connections will be allowed\nby default. This does not affect outgoing peerings, nor does it\naffect link-local peers discovered via multicast.\nWARNING: THIS IS NOT A FIREWALL and DOES NOT limit who can reach\nopen ports or services running on your machine!"` IfName string `comment:"Local network interface name for TUN adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN."` IfMTU uint64 `comment:"Maximum Transmission Unit (MTU) size for your local TUN interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."` @@ -60,8 +60,8 @@ type MulticastInterfaceConfig struct { Regex string Beacon bool Listen bool - Port uint16 - Priority uint64 // really uint8, but gobind won't export it + Port uint16 `json:",omitempty"` + Priority uint64 `json:",omitempty"` // really uint8, but gobind won't export it Password string } diff --git a/src/config/defaults_darwin.go b/src/config/defaults_darwin.go index e851d6b9..11da7484 100644 --- a/src/config/defaults_darwin.go +++ b/src/config/defaults_darwin.go @@ -8,7 +8,7 @@ package config func getDefaults() platformDefaultParameters { return platformDefaultParameters{ // Admin - DefaultAdminListen: "unix:///var/run/yggdrasil.sock", + DefaultAdminListen: "tcp://localhost:9001", // Configuration (used for yggdrasilctl) DefaultConfigFile: "/etc/yggdrasil.conf", @@ -17,6 +17,7 @@ func getDefaults() platformDefaultParameters { DefaultMulticastInterfaces: []MulticastInterfaceConfig{ {Regex: "en.*", Beacon: true, Listen: true}, {Regex: "bridge.*", Beacon: true, Listen: true}, + {Regex: "awdl0", Beacon: false, Listen: false}, }, // TUN From d3b4de46ea156b63e925909b957ff6138e6b07ba Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 23 Nov 2024 13:43:34 +0000 Subject: [PATCH 10/15] Improvements to how link shutdowns are handled --- src/core/link.go | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/core/link.go b/src/core/link.go index 899d73bd..f30016f9 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -131,8 +131,8 @@ func (l *links) _updateAverages() { func (l *links) shutdown() { phony.Block(l, func() { - for listener := range l._listeners { - _ = listener.listener.Close() + for _, cancel := range l._listeners { + cancel() } for _, link := range l._links { if link._conn != nil { @@ -429,7 +429,7 @@ func (l *links) remove(u *url.URL, sintf string, _ linkType) error { } func (l *links) listen(u *url.URL, sintf string, local bool) (*Listener, error) { - ctx, cancel := context.WithCancel(l.core.ctx) + ctx, ctxcancel := context.WithCancel(l.core.ctx) var protocol linkProtocol switch strings.ToLower(u.Scheme) { case "tcp": @@ -445,21 +445,25 @@ func (l *links) listen(u *url.URL, sintf string, local bool) (*Listener, error) case "wss": protocol = l.wss default: - cancel() + ctxcancel() return nil, ErrLinkUnrecognisedSchema } listener, err := protocol.listen(ctx, u, sintf) if err != nil { - cancel() + ctxcancel() return nil, err } + addr := listener.Addr() + cancel := func() { + ctxcancel() + if err := listener.Close(); err != nil && !errors.Is(err, net.ErrClosed) { + l.core.log.Warnf("Error closing %s listener %s: %s", strings.ToUpper(u.Scheme), addr, err) + } + } li := &Listener{ listener: listener, ctx: ctx, - Cancel: func() { - cancel() - _ = listener.Close() - }, + Cancel: cancel, } var options linkOptions @@ -482,10 +486,11 @@ func (l *links) listen(u *url.URL, sintf string, local bool) (*Listener, error) }) go func() { - l.core.log.Infof("%s listener started on %s", strings.ToUpper(u.Scheme), li.listener.Addr()) - defer l.core.log.Infof("%s listener stopped on %s", strings.ToUpper(u.Scheme), li.listener.Addr()) + l.core.log.Infof("%s listener started on %s", strings.ToUpper(u.Scheme), addr) defer phony.Block(l, func() { + cancel() delete(l._listeners, li) + l.core.log.Infof("%s listener stopped on %s", strings.ToUpper(u.Scheme), addr) }) for { conn, err := li.listener.Accept() From 7790a19e4c78da866bb29c653558a3bfba363ffe Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 23 Nov 2024 14:49:48 +0000 Subject: [PATCH 11/15] New detail in `getMulticastInterfaces` admin endpoint --- cmd/yggdrasilctl/main.go | 16 +++++++++++++-- src/config/defaults_darwin.go | 2 +- src/multicast/admin.go | 37 ++++++++++++++++++++++++++++++----- src/multicast/multicast.go | 18 +++++++++-------- 4 files changed, 57 insertions(+), 16 deletions(-) diff --git a/cmd/yggdrasilctl/main.go b/cmd/yggdrasilctl/main.go index b73708e6..ca0bce1a 100644 --- a/cmd/yggdrasilctl/main.go +++ b/cmd/yggdrasilctl/main.go @@ -281,9 +281,21 @@ func run() int { if err := json.Unmarshal(recv.Response, &resp); err != nil { panic(err) } - table.SetHeader([]string{"Interface"}) + fmtBool := func(b bool) string { + if b { + return "Yes" + } + return "-" + } + table.SetHeader([]string{"Name", "Listen Address", "Beacon", "Listen", "Password"}) for _, p := range resp.Interfaces { - table.Append([]string{p}) + table.Append([]string{ + p.Name, + p.Address, + fmtBool(p.Beacon), + fmtBool(p.Listen), + fmtBool(p.Password), + }) } table.Render() diff --git a/src/config/defaults_darwin.go b/src/config/defaults_darwin.go index 11da7484..5f44ef59 100644 --- a/src/config/defaults_darwin.go +++ b/src/config/defaults_darwin.go @@ -8,7 +8,7 @@ package config func getDefaults() platformDefaultParameters { return platformDefaultParameters{ // Admin - DefaultAdminListen: "tcp://localhost:9001", + DefaultAdminListen: "unix:///var/run/yggdrasil.sock", // Configuration (used for yggdrasilctl) DefaultConfigFile: "/etc/yggdrasil.conf", diff --git a/src/multicast/admin.go b/src/multicast/admin.go index 8b29716d..0042f519 100644 --- a/src/multicast/admin.go +++ b/src/multicast/admin.go @@ -2,20 +2,47 @@ package multicast import ( "encoding/json" + "slices" + "strings" + "github.com/Arceliar/phony" "github.com/yggdrasil-network/yggdrasil-go/src/admin" ) type GetMulticastInterfacesRequest struct{} type GetMulticastInterfacesResponse struct { - Interfaces []string `json:"multicast_interfaces"` + Interfaces []MulticastInterfaceState `json:"multicast_interfaces"` +} + +type MulticastInterfaceState struct { + Name string `json:"name"` + Address string `json:"address"` + Beacon bool `json:"beacon"` + Listen bool `json:"listen"` + Password bool `json:"password"` } func (m *Multicast) getMulticastInterfacesHandler(_ *GetMulticastInterfacesRequest, res *GetMulticastInterfacesResponse) error { - res.Interfaces = []string{} - for _, v := range m.Interfaces() { - res.Interfaces = append(res.Interfaces, v.Name) - } + res.Interfaces = []MulticastInterfaceState{} + phony.Block(m, func() { + for name, intf := range m._interfaces { + is := MulticastInterfaceState{ + Name: intf.iface.Name, + Beacon: intf.beacon, + Listen: intf.listen, + Password: len(intf.password) > 0, + } + if li := m._listeners[name]; li != nil && li.listener != nil { + is.Address = li.listener.Addr().String() + } else { + is.Address = "-" + } + res.Interfaces = append(res.Interfaces, is) + } + }) + slices.SortStableFunc(res.Interfaces, func(a, b MulticastInterfaceState) int { + return strings.Compare(a.Name, b.Name) + }) return nil } diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 77ea8a50..9134501c 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -156,7 +156,13 @@ func (m *Multicast) _updateInterfaces() { delete(interfaces, name) continue } - info.addrs = addrs + for _, addr := range addrs { + addrIP, _, err := net.ParseCIDR(addr.String()) + if err != nil || addrIP.To4() != nil || !addrIP.IsLinkLocalUnicast() { + continue + } + info.addrs = append(info.addrs, addr) + } interfaces[name] = info m.log.Debugf("Discovered addresses for interface %s: %s", name, addrs) } @@ -299,13 +305,9 @@ func (m *Multicast) _announce() { for _, info := range m._interfaces { iface := info.iface for _, addr := range info.addrs { - addrIP, _, _ := net.ParseCIDR(addr.String()) - // Ignore IPv4 addresses - if addrIP.To4() != nil { - continue - } - // Ignore non-link-local addresses - if !addrIP.IsLinkLocalUnicast() { + addrIP, _, err := net.ParseCIDR(addr.String()) + // Ignore IPv4 addresses or non-link-local addresses + if err != nil || addrIP.To4() != nil || !addrIP.IsLinkLocalUnicast() { continue } if info.listen { From bdb2d399c5776a80ea81164f1e9b8013fa934775 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 23 Nov 2024 14:55:14 +0000 Subject: [PATCH 12/15] Update dependencies --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 27fe1110..987a3554 100644 --- a/go.mod +++ b/go.mod @@ -14,10 +14,10 @@ require ( github.com/quic-go/quic-go v0.46.0 github.com/vishvananda/netlink v1.3.0 github.com/wlynxg/anet v0.0.5 - golang.org/x/crypto v0.28.0 - golang.org/x/net v0.30.0 - golang.org/x/sys v0.26.0 - golang.org/x/text v0.19.0 + golang.org/x/crypto v0.29.0 + golang.org/x/net v0.31.0 + golang.org/x/sys v0.27.0 + golang.org/x/text v0.20.0 golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 golang.zx2c4.com/wireguard/windows v0.5.3 @@ -34,7 +34,7 @@ require ( go.uber.org/mock v0.4.0 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect golang.org/x/mod v0.19.0 // indirect - golang.org/x/sync v0.8.0 // indirect + golang.org/x/sync v0.9.0 // indirect golang.org/x/tools v0.23.0 // indirect ) diff --git a/go.sum b/go.sum index 19674559..8531a2a3 100644 --- a/go.sum +++ b/go.sum @@ -75,25 +75,25 @@ github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU= github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= From 3ed4a92288ee47dc388af9bf23e0bf66bbab96ae Mon Sep 17 00:00:00 2001 From: Neil Date: Sun, 24 Nov 2024 12:56:24 +0000 Subject: [PATCH 13/15] Yggdrasil 0.5.10 (#1207) Changelog updates. Co-authored-by: Neil Alexander --- CHANGELOG.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39cfa960..c442353e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,31 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - in case of vulnerabilities. --> +## [0.5.10] - 2024-11-24 + +### Added + +* The `getPeers` admin endpoint will now report the current transmit/receive rate for each given peer +* The `getMulticastInterfaces` admin endpoint now reports much more useful information about each interface, rather than just a list of interface names + +### Changed + +* Minor tweaks to the routing algorithm: + * The next-hop selection will now prefer shorter paths when the costed distance is otherwise equal, tiebreaking on peering uptime to fall back to more stable paths + * Link cost calculations have been smoothed out, making the costs less sensitive to sudden spikes in latency +* Reusable name lookup and peer connection logic across different peering types for more consistent behaviour +* Some comments in the configuration file have been revised for clarity +* Upgrade dependencies + +### Fixed + +* Nodes with `IfName` set to `none` will now correctly respond to debug RPC requests +* The admin socket will now be created reliably before dropping privileges with `-user` +* Clear supplementary groups when providing a group ID as well as a user ID to `-user` +* SOCKS and WebSocket peerings should now use the correct source interface when specified in `InterfacePeers` +* `Peers` and `InterfacePeers` addresses that are obviously invalid (such as unspecified or multicast addresses) will now be correctly ignored +* Listeners should now shut down correctly, which should resolve issues where multicast listeners for specific interfaces would not come back up or would log errors + ## [0.5.9] - 2024-10-19 ### Added From b436052b2d7ff9f3cc3acd3c8a55396fa2308563 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 10 Dec 2024 19:02:13 +0000 Subject: [PATCH 14/15] Update to Arceliar/ironwood@9deb08d --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 987a3554..a2909018 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/yggdrasil-network/yggdrasil-go go 1.21 require ( - github.com/Arceliar/ironwood v0.0.0-20241122002527-75a6e82fa380 + github.com/Arceliar/ironwood v0.0.0-20241210120540-9deb08d9f8f9 github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d github.com/cheggaaa/pb/v3 v3.1.5 github.com/coder/websocket v1.8.12 diff --git a/go.sum b/go.sum index 8531a2a3..d62e2ddb 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Arceliar/ironwood v0.0.0-20241122002527-75a6e82fa380 h1:WRLvBMWzs6NOiPUYA7fMu8XqZFg/clXKorUumfbJNv0= -github.com/Arceliar/ironwood v0.0.0-20241122002527-75a6e82fa380/go.mod h1:SrrElc3FFMpYCODSr11jWbLFeOM8WsY+DbDY/l2AXF0= +github.com/Arceliar/ironwood v0.0.0-20241210120540-9deb08d9f8f9 h1:myI8fs7+Iw6g/ywvY9QNQOEzny51AklMz4sF0ErtTm8= +github.com/Arceliar/ironwood v0.0.0-20241210120540-9deb08d9f8f9/go.mod h1:SrrElc3FFMpYCODSr11jWbLFeOM8WsY+DbDY/l2AXF0= github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d h1:UK9fsWbWqwIQkMCz1CP+v5pGbsGoWAw6g4AyvMpm1EM= github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d/go.mod h1:BCnxhRf47C/dy/e/D2pmB8NkB3dQVIrkD98b220rx5Q= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= From 83ec58afc763ff89d9664876ed2e95fb5842985a Mon Sep 17 00:00:00 2001 From: Klemens Nanni Date: Thu, 12 Dec 2024 21:37:02 +0300 Subject: [PATCH 15/15] Use unveil(2) on OpenBSD (#1194) After #1175 removed ioctl(2) fallback code shelling out to ifconfig(8), there is no code left (compiled on OpenBSD) that would fork(2) or execve(2). Drop the ability to run any executable file to double down on this, thus reducing the attack surface of this this experimental, internet facing daemon running as root. pledge(2) is doable, but needs more polish. unveil(2), however, is as simple as it gets. On other systems, this code is a NOOP, but can still help to implement similar safety belts. --- cmd/yggdrasil/main.go | 16 ++++++++++++++++ go.mod | 1 + go.sum | 2 ++ 3 files changed, 19 insertions(+) diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 3ec6414c..b3cbecf0 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -14,6 +14,8 @@ import ( "strings" "syscall" + "suah.dev/protect" + "github.com/gologme/log" gsyslog "github.com/hashicorp/go-syslog" "github.com/hjson/hjson-go/v4" @@ -39,6 +41,20 @@ type node struct { // The main function is responsible for configuring and starting Yggdrasil. func main() { + // Not all operations are coverable with pledge(2), so immediately + // limit file system access with unveil(2), effectively preventing + // "proc exec" promises right from the start: + // + // - read arbitrary config file + // - create/write arbitrary log file + // - read/write/chmod/remove admin socket, if at all + if err := protect.Unveil("/", "rwc"); err != nil { + panic(fmt.Sprintf("unveil: / rwc: %v", err)) + } + if err := protect.UnveilBlock(); err != nil { + panic(fmt.Sprintf("unveil: %v", err)) + } + genconf := flag.Bool("genconf", false, "print a new config to stdout") useconf := flag.Bool("useconf", false, "read HJSON/JSON config from stdin") useconffile := flag.String("useconffile", "", "read HJSON/JSON config from specified file path") diff --git a/go.mod b/go.mod index a2909018..5608f588 100644 --- a/go.mod +++ b/go.mod @@ -45,4 +45,5 @@ require ( github.com/mattn/go-runewidth v0.0.15 // indirect github.com/olekukonko/tablewriter v0.0.5 github.com/vishvananda/netns v0.0.4 // indirect + suah.dev/protect v1.2.4 ) diff --git a/go.sum b/go.sum index d62e2ddb..061a0769 100644 --- a/go.sum +++ b/go.sum @@ -112,3 +112,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ= gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY= +suah.dev/protect v1.2.4 h1:iVZG/zQB63FKNpITDYM/cXoAeCTIjCiXHuFVByJFDzg= +suah.dev/protect v1.2.4/go.mod h1:vVrquYO3u1Ep9Ez2z8x+6N6/czm+TBmWKZfiXU2tb54=