diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a3f34789..57a0d2a7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,10 +17,10 @@ jobs: steps: - uses: actions/setup-go@v5 with: - go-version: 1.21 + go-version: stable - uses: actions/checkout@v4 - name: golangci-lint - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@v6 with: args: --issues-exit-code=1 @@ -51,7 +51,7 @@ jobs: strategy: fail-fast: false matrix: - goversion: ["1.21", "1.22"] + goversion: ["1.21", "1.22", "1.23"] name: Build & Test (Linux, Go ${{ matrix.goversion }}) needs: [lint] @@ -75,7 +75,7 @@ jobs: strategy: fail-fast: false matrix: - goversion: ["1.21", "1.22"] + goversion: ["1.21", "1.22", "1.23"] name: Build & Test (Windows, Go ${{ matrix.goversion }}) needs: [lint] @@ -99,7 +99,7 @@ jobs: strategy: fail-fast: false matrix: - goversion: ["1.21", "1.22"] + goversion: ["1.21", "1.22", "1.23"] name: Build & Test (macOS, Go ${{ matrix.goversion }}) needs: [lint] @@ -123,7 +123,7 @@ jobs: strategy: fail-fast: false matrix: - goversion: ["1.21", "1.22"] + goversion: ["1.21", "1.22", "1.23"] goos: - freebsd - openbsd diff --git a/.golangci.yml b/.golangci.yml index c35edee4..836af618 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -2,9 +2,10 @@ run: build-tags: - lint issues-exit-code: 0 # TODO: change this to 1 when we want it to fail builds - skip-dirs: +issues: + exclude-dirs: - contrib/ - misc/ linters: disable: - - gocyclo \ No newline at end of file + - gocyclo diff --git a/cmd/yggdrasil/chuser_other.go b/cmd/yggdrasil/chuser_other.go new file mode 100644 index 00000000..702f3715 --- /dev/null +++ b/cmd/yggdrasil/chuser_other.go @@ -0,0 +1,10 @@ +//go:build !aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris +// +build !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris + +package main + +import "errors" + +func chuser(user string) error { + return errors.New("setting uid/gid is not supported on this platform") +} diff --git a/cmd/yggdrasil/chuser_unix.go b/cmd/yggdrasil/chuser_unix.go new file mode 100644 index 00000000..6e802c69 --- /dev/null +++ b/cmd/yggdrasil/chuser_unix.go @@ -0,0 +1,87 @@ +//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris +// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris + +package main + +import ( + "errors" + "fmt" + "math" + osuser "os/user" + "strconv" + "strings" + "syscall" +) + +func chuser(user string) error { + group := "" + if i := strings.IndexByte(user, ':'); i >= 0 { + user, group = user[:i], user[i+1:] + } + + u := (*osuser.User)(nil) + g := (*osuser.Group)(nil) + + 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 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 g != nil { + gid, _ := strconv.ParseUint(g.Gid, 10, 32) + var err error + if gid < math.MaxInt { + 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) + err := syscall.Setgid(int(uint32(gid))) + if err != nil { + return fmt.Errorf("failed to setgid %d: %v", gid, err) + } + } + + 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) + } + } + + return nil +} diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 6dbd5f25..ad6fce78 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -53,6 +53,7 @@ func main() { getsnet := flag.Bool("subnet", false, "use in combination with either -useconf or -useconffile, outputs your IPv6 subnet") getpkey := flag.Bool("publickey", false, "use in combination with either -useconf or -useconffile, outputs your public key") loglevel := flag.String("loglevel", "info", "loglevel to enable") + chuserto := flag.String("user", "", "user (and, optionally, group) to set UID/GID to") flag.Parse() done := make(chan struct{}) @@ -286,6 +287,14 @@ func main() { <-done }) + // Change user if requested + if *chuserto != "" { + err = chuser(*chuserto) + if err != nil { + panic(err) + } + } + // Block until we are told to shut down. <-ctx.Done() diff --git a/cmd/yggdrasilctl/cmd_line_env.go b/cmd/yggdrasilctl/cmd_line_env.go index b350b7e7..e929b0ba 100644 --- a/cmd/yggdrasilctl/cmd_line_env.go +++ b/cmd/yggdrasilctl/cmd_line_env.go @@ -38,7 +38,6 @@ func (cmdLineEnv *CmdLineEnv) parseFlagsAndArgs() { fmt.Println("Examples:") fmt.Println(" - ", os.Args[0], "list") fmt.Println(" - ", os.Args[0], "getPeers") - fmt.Println(" - ", os.Args[0], "setTunTap name=auto mtu=1500 tap_mode=false") fmt.Println(" - ", os.Args[0], "-endpoint=tcp://localhost:9001 getPeers") fmt.Println(" - ", os.Args[0], "-endpoint=unix:///var/run/ygg.sock getPeers") } diff --git a/cmd/yggdrasilctl/main.go b/cmd/yggdrasilctl/main.go index 2a1d70b1..8a30f438 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{"URI", "State", "Dir", "IP Address", "Uptime", "RTT", "RX", "TX", "Pr", "Last Error"}) + table.SetHeader([]string{"URI", "State", "Dir", "IP Address", "Uptime", "RTT", "RX", "TX", "Pr", "Cost", "Last Error"}) for _, peer := range resp.Peers { state, lasterr, dir, rtt := "Up", "-", "Out", "-" if !peer.Up { @@ -200,6 +200,7 @@ func run() int { peer.RXBytes.String(), peer.TXBytes.String(), fmt.Sprintf("%d", peer.Priority), + fmt.Sprintf("%d", peer.Cost), lasterr, }) } diff --git a/contrib/mobile/build b/contrib/mobile/build index 3f6b9bfc..9be9529b 100755 --- a/contrib/mobile/build +++ b/contrib/mobile/build @@ -7,6 +7,7 @@ set -ef PKGSRC=${PKGSRC:-github.com/yggdrasil-network/yggdrasil-go/src/version} PKGNAME=${PKGNAME:-$(sh contrib/semver/name.sh)} PKGVER=${PKGVER:-$(sh contrib/semver/version.sh --bare)} +GOVER=$(go version | { read _ _ version _; echo ${version#go}; }) LDFLAGS="-X $PKGSRC.buildName=$PKGNAME -X $PKGSRC.buildVersion=$PKGVER" ARGS="-v" @@ -33,6 +34,15 @@ if [ ! $IOS ] && [ ! $ANDROID ]; then exit 1 fi +ver_le() { + printf "$1\n$2\n" | sort -VC +} + +if [ $ANDROID ] && ver_le 1.23.0 $GOVER ; then + # github.com/wlynxg/anet library relies on //go:linkname + LDFLAGS="$LDFLAGS -checklinkname=0" +fi + if [ $IOS ]; then echo "Building framework for iOS" go get golang.org/x/mobile/bind diff --git a/contrib/mobile/mobile.go b/contrib/mobile/mobile.go index 82e73485..06f48027 100644 --- a/contrib/mobile/mobile.go +++ b/contrib/mobile/mobile.go @@ -15,8 +15,6 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/multicast" "github.com/yggdrasil-network/yggdrasil-go/src/tun" "github.com/yggdrasil-network/yggdrasil-go/src/version" - - _ "golang.org/x/mobile/bind" ) // Yggdrasil mobile package is meant to "plug the gap" for mobile support, as diff --git a/go.mod b/go.mod index 52b699d8..76641a60 100644 --- a/go.mod +++ b/go.mod @@ -3,24 +3,24 @@ module github.com/yggdrasil-network/yggdrasil-go go 1.21 require ( - github.com/Arceliar/ironwood v0.0.0-20240529054413-b8e59574e2b2 + github.com/Arceliar/ironwood v0.0.0-20241016082300-f6fb9da97a17 github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d github.com/cheggaaa/pb/v3 v3.1.5 + github.com/coder/websocket v1.8.12 github.com/gologme/log v1.3.0 github.com/hashicorp/go-syslog v1.0.0 github.com/hjson/hjson-go/v4 v4.4.0 github.com/kardianos/minwinsvc v1.0.2 - github.com/quic-go/quic-go v0.45.1 - github.com/vishvananda/netlink v1.1.0 - github.com/wlynxg/anet v0.0.4-0.20240806025826-e684438fc7c6 - golang.org/x/crypto v0.25.0 - golang.org/x/mobile v0.0.0-20240716161057-1ad2df20a8b6 - golang.org/x/net v0.27.0 - golang.org/x/sys v0.22.0 - golang.org/x/text v0.16.0 + 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.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 - nhooyr.io/websocket v1.8.11 ) require ( @@ -34,9 +34,8 @@ 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.7.0 // indirect + golang.org/x/sync v0.8.0 // indirect golang.org/x/tools v0.23.0 // indirect - golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect ) require ( @@ -45,5 +44,5 @@ require ( github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect github.com/olekukonko/tablewriter v0.0.5 - github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect + github.com/vishvananda/netns v0.0.4 // indirect ) diff --git a/go.sum b/go.sum index a3c2888e..89dd0c7b 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Arceliar/ironwood v0.0.0-20240529054413-b8e59574e2b2 h1:SBdYBKeXYUUFef5wi2CMhYmXFVGiYaRpTvbki0Bu+JQ= -github.com/Arceliar/ironwood v0.0.0-20240529054413-b8e59574e2b2/go.mod h1:6WP4799FX0OuWdENGQAh+0RXp9FLh0y7NZ7tM9cJyXk= +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/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= @@ -14,6 +14,8 @@ github.com/cheggaaa/pb/v3 v3.1.5/go.mod h1:CrxkeghYTXi1lQBEI7jSn+3svI3cuc19haAj6 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo= +github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -56,8 +58,8 @@ github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/quic-go/quic-go v0.45.1 h1:tPfeYCk+uZHjmDRwHHQmvHRYL2t44ROTujLeFVBmjCA= -github.com/quic-go/quic-go v0.45.1/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI= +github.com/quic-go/quic-go v0.46.0 h1:uuwLClEEyk1DNvchH8uCByQVjo3yKL9opKulExNDs7Y= +github.com/quic-go/quic-go v0.46.0/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -65,13 +67,12 @@ github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg= github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= -github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0= -github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= -github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= -github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f h1:p4VB7kIXpOQvVn1ZaTIVp+3vuYAXFe3OJEvjbUYJLaA= -github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= -github.com/wlynxg/anet v0.0.4-0.20240806025826-e684438fc7c6 h1:c/wkXIJvpg2oot7iFqPESTBAO9UvhWTBnW97y9aPgyU= -github.com/wlynxg/anet v0.0.4-0.20240806025826-e684438fc7c6/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= +github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk= +github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs= +github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= +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= @@ -79,12 +80,10 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk 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.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +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/mobile v0.0.0-20240716161057-1ad2df20a8b6 h1:/VlmIrkuLf2wzPjkZ8imSpckHoW7Y71h66dxbLHSpi8= -golang.org/x/mobile v0.0.0-20240716161057-1ad2df20a8b6/go.mod h1:TCsc78+c4cqb8IKEosz2LwJ6YRNkIjMuAYeHYjchGDE= 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= @@ -95,29 +94,29 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug 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.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +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.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +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-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200217220822-9197077df867/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.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.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= @@ -131,8 +130,8 @@ 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.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +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= @@ -156,5 +155,3 @@ 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= -nhooyr.io/websocket v1.8.11 h1:f/qXNc2/3DpoSZkHt1DQu6rj4zGC8JmkkLkWss0MgN0= -nhooyr.io/websocket v1.8.11/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= diff --git a/src/admin/admin.go b/src/admin/admin.go index 7cca1bbb..8823fc5e 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -238,27 +238,27 @@ func (a *AdminSocket) listen() { if err == nil { switch strings.ToLower(u.Scheme) { case "unix": - if _, err := os.Stat(listenaddr[7:]); err == nil { - a.log.Debugln("Admin socket", listenaddr[7:], "already exists, trying to clean up") - if _, err := net.DialTimeout("unix", listenaddr[7:], time.Second*2); err == nil || err.(net.Error).Timeout() { - a.log.Errorln("Admin socket", listenaddr[7:], "already exists and is in use by another process") + 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(listenaddr[7:]); err == nil { - a.log.Debugln(listenaddr[7:], "was cleaned up") + if err := os.Remove(u.Path); err == nil { + a.log.Debugln(u.Path, "was cleaned up") } else { - a.log.Errorln(listenaddr[7:], "already exists and was not cleaned up:", err) + a.log.Errorln(u.Path, "already exists and was not cleaned up:", err) os.Exit(1) } } } - a.listener, err = net.Listen("unix", listenaddr[7:]) + a.listener, err = net.Listen("unix", u.Path) if err == nil { - switch listenaddr[7:8] { + switch u.Path[:1] { case "@": // maybe abstract namespace default: - if err := os.Chmod(listenaddr[7:], 0660); err != nil { - a.log.Warnln("WARNING:", listenaddr[:7], "may have unsafe permissions!") + if err := os.Chmod(u.Path, 0660); err != nil { + a.log.Warnln("WARNING:", u.Path, "may have unsafe permissions!") } } } diff --git a/src/admin/getpaths.go b/src/admin/getpaths.go index 34de4532..250f4c6a 100644 --- a/src/admin/getpaths.go +++ b/src/admin/getpaths.go @@ -3,7 +3,7 @@ package admin import ( "encoding/hex" "net" - "sort" + "slices" "strings" "github.com/yggdrasil-network/yggdrasil-go/src/address" @@ -35,8 +35,8 @@ func (a *AdminSocket) getPathsHandler(_ *GetPathsRequest, res *GetPathsResponse) Sequence: p.Sequence, }) } - sort.SliceStable(res.Paths, func(i, j int) bool { - return strings.Compare(res.Paths[i].PublicKey, res.Paths[j].PublicKey) < 0 + slices.SortStableFunc(res.Paths, func(a, b PathEntry) int { + return strings.Compare(a.PublicKey, b.PublicKey) }) return nil } diff --git a/src/admin/getpeers.go b/src/admin/getpeers.go index e44428c3..2c2f8d8a 100644 --- a/src/admin/getpeers.go +++ b/src/admin/getpeers.go @@ -3,7 +3,8 @@ package admin import ( "encoding/hex" "net" - "sort" + "slices" + "strings" "time" "github.com/yggdrasil-network/yggdrasil-go/src/address" @@ -24,6 +25,7 @@ type PeerEntry struct { PublicKey string `json:"key"` Port uint64 `json:"port"` Priority uint64 `json:"priority"` + Cost uint64 `json:"cost"` RXBytes DataUnit `json:"bytes_recvd,omitempty"` TXBytes DataUnit `json:"bytes_sent,omitempty"` Uptime float64 `json:"uptime,omitempty"` @@ -41,6 +43,7 @@ func (a *AdminSocket) getPeersHandler(_ *GetPeersRequest, res *GetPeersResponse) Up: p.Up, Inbound: p.Inbound, Priority: uint64(p.Priority), // can't be uint8 thanks to gobind + Cost: p.Cost, URI: p.URI, RXBytes: DataUnit(p.RXBytes), TXBytes: DataUnit(p.TXBytes), @@ -59,17 +62,26 @@ func (a *AdminSocket) getPeersHandler(_ *GetPeersRequest, res *GetPeersResponse) } res.Peers = append(res.Peers, peer) } - sort.Slice(res.Peers, func(i, j int) bool { - if res.Peers[i].Inbound == res.Peers[j].Inbound { - if res.Peers[i].PublicKey == res.Peers[j].PublicKey { - if res.Peers[i].Priority == res.Peers[j].Priority { - return res.Peers[i].Uptime > res.Peers[j].Uptime - } - return res.Peers[i].Priority < res.Peers[j].Priority - } - return res.Peers[i].PublicKey < res.Peers[j].PublicKey + slices.SortStableFunc(res.Peers, func(a, b PeerEntry) int { + if !a.Inbound && b.Inbound { + return -1 } - return !res.Peers[i].Inbound && res.Peers[j].Inbound + if a.Inbound && !b.Inbound { + return 1 + } + if d := strings.Compare(a.PublicKey, b.PublicKey); d != 0 { + return d + } + if d := a.Priority - b.Priority; d != 0 { + return int(d) + } + if d := a.Cost - b.Cost; d != 0 { + return int(d) + } + if d := a.Uptime - b.Uptime; d != 0 { + return int(d) + } + return 0 }) return nil } diff --git a/src/admin/getsessions.go b/src/admin/getsessions.go index e6702f88..2d76a35b 100644 --- a/src/admin/getsessions.go +++ b/src/admin/getsessions.go @@ -3,7 +3,7 @@ package admin import ( "encoding/hex" "net" - "sort" + "slices" "strings" "github.com/yggdrasil-network/yggdrasil-go/src/address" @@ -36,8 +36,8 @@ func (a *AdminSocket) getSessionsHandler(_ *GetSessionsRequest, res *GetSessions Uptime: s.Uptime.Seconds(), }) } - sort.SliceStable(res.Sessions, func(i, j int) bool { - return strings.Compare(res.Sessions[i].PublicKey, res.Sessions[j].PublicKey) < 0 + slices.SortStableFunc(res.Sessions, func(a, b SessionEntry) int { + return strings.Compare(a.PublicKey, b.PublicKey) }) return nil } diff --git a/src/admin/gettree.go b/src/admin/gettree.go index 4b6f32a8..993827d9 100644 --- a/src/admin/gettree.go +++ b/src/admin/gettree.go @@ -3,7 +3,7 @@ package admin import ( "encoding/hex" "net" - "sort" + "slices" "strings" "github.com/yggdrasil-network/yggdrasil-go/src/address" @@ -34,8 +34,8 @@ func (a *AdminSocket) getTreeHandler(_ *GetTreeRequest, res *GetTreeResponse) er Sequence: d.Sequence, }) } - sort.SliceStable(res.Tree, func(i, j int) bool { - return strings.Compare(res.Tree[i].PublicKey, res.Tree[j].PublicKey) < 0 + slices.SortStableFunc(res.Tree, func(a, b TreeEntry) int { + return strings.Compare(a.PublicKey, b.PublicKey) }) return nil } diff --git a/src/core/api.go b/src/core/api.go index 875d7bf2..2aa1ba87 100644 --- a/src/core/api.go +++ b/src/core/api.go @@ -30,6 +30,7 @@ type PeerInfo struct { Coords []uint64 Port uint64 Priority uint8 + Cost uint64 RXBytes uint64 TXBytes uint64 Uptime time.Duration @@ -94,6 +95,7 @@ func (c *Core) GetPeers() []PeerInfo { peerinfo.Port = p.Port peerinfo.Priority = p.Priority peerinfo.Latency = p.Latency + peerinfo.Cost = p.Cost } peers = append(peers, peerinfo) } @@ -148,7 +150,14 @@ func (c *Core) GetSessions() []SessionInfo { // parsed from a string of the form e.g. "tcp://a.b.c.d:e". In the case of a // link-local address, the interface should be provided as the second argument. func (c *Core) Listen(u *url.URL, sintf string) (*Listener, error) { - return c.links.listen(u, sintf) + return c.links.listen(u, sintf, false) +} + +// ListenLocal starts a listener, like the Listen function, but is used for +// more trustworthy situations where you want to ignore AllowedPublicKeys, i.e. +// with multicast listeners. +func (c *Core) ListenLocal(u *url.URL, sintf string) (*Listener, error) { + return c.links.listen(u, sintf, true) } // Address gets the IPv6 address of the Yggdrasil node. This is always a /128 diff --git a/src/core/core.go b/src/core/core.go index 41858cb1..2b206ee1 100644 --- a/src/core/core.go +++ b/src/core/core.go @@ -127,7 +127,7 @@ func New(cert *tls.Certificate, logger Logger, opts ...SetupOption) (*Core, erro c.log.Errorf("Invalid listener URI %q specified, ignoring\n", listenaddr) continue } - if _, err = c.links.listen(u, ""); err != nil { + if _, err = c.links.listen(u, "", false); err != nil { c.log.Errorf("Failed to start listener %q: %s\n", listenaddr, err) } } diff --git a/src/core/core_test.go b/src/core/core_test.go index cece33c2..f186f43f 100644 --- a/src/core/core_test.go +++ b/src/core/core_test.go @@ -25,6 +25,27 @@ func GetLoggerWithPrefix(prefix string, verbose bool) *log.Logger { return l } +func require_NoError(t *testing.T, err error) { + t.Helper() + if err != nil { + t.Fatal(err) + } +} + +func require_Equal[T comparable](t *testing.T, a, b T) { + t.Helper() + if a != b { + t.Fatalf("%v != %v", a, b) + } +} + +func require_True(t *testing.T, a bool) { + t.Helper() + if !a { + t.Fatal("expected true") + } +} + // CreateAndConnectTwo creates two nodes. nodeB connects to nodeA. // Verbosity flag is passed to logger. func CreateAndConnectTwo(t testing.TB, verbose bool) (nodeA *Core, nodeB *Core) { @@ -201,3 +222,69 @@ func BenchmarkCore_Start_Transfer(b *testing.B) { } <-done } + +func TestAllowedPublicKeys(t *testing.T) { + logger := GetLoggerWithPrefix("", false) + cfgA, cfgB := config.GenerateConfig(), config.GenerateConfig() + require_NoError(t, cfgA.GenerateSelfSignedCertificate()) + require_NoError(t, cfgB.GenerateSelfSignedCertificate()) + + nodeA, err := New(cfgA.Certificate, logger, AllowedPublicKey("abcdef")) + require_NoError(t, err) + defer nodeA.Stop() + + nodeB, err := New(cfgB.Certificate, logger) + require_NoError(t, err) + defer nodeB.Stop() + + u, err := url.Parse("tcp://localhost:0") + require_NoError(t, err) + + l, err := nodeA.Listen(u, "") + require_NoError(t, err) + + u, err = url.Parse("tcp://" + l.Addr().String()) + require_NoError(t, err) + + require_NoError(t, nodeB.AddPeer(u, "")) + + time.Sleep(time.Second) + + peers := nodeB.GetPeers() + require_Equal(t, len(peers), 1) + require_True(t, !peers[0].Up) + require_True(t, peers[0].LastError != nil) +} + +func TestAllowedPublicKeysLocal(t *testing.T) { + logger := GetLoggerWithPrefix("", false) + cfgA, cfgB := config.GenerateConfig(), config.GenerateConfig() + require_NoError(t, cfgA.GenerateSelfSignedCertificate()) + require_NoError(t, cfgB.GenerateSelfSignedCertificate()) + + nodeA, err := New(cfgA.Certificate, logger, AllowedPublicKey("abcdef")) + require_NoError(t, err) + defer nodeA.Stop() + + nodeB, err := New(cfgB.Certificate, logger) + require_NoError(t, err) + defer nodeB.Stop() + + u, err := url.Parse("tcp://localhost:0") + require_NoError(t, err) + + l, err := nodeA.ListenLocal(u, "") + require_NoError(t, err) + + u, err = url.Parse("tcp://" + l.Addr().String()) + require_NoError(t, err) + + require_NoError(t, nodeB.AddPeer(u, "")) + + time.Sleep(time.Second) + + peers := nodeB.GetPeers() + require_Equal(t, len(peers), 1) + require_True(t, peers[0].Up) + require_True(t, peers[0].LastError == nil) +} diff --git a/src/core/link.go b/src/core/link.go index 1ead4e32..04fe0266 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -108,7 +108,9 @@ func (l *links) shutdown() { _ = listener.listener.Close() } for _, link := range l._links { - _ = link._conn.Close() + if link._conn != nil { + _ = link._conn.Close() + } } }) } @@ -334,7 +336,7 @@ 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); err != nil && err != io.EOF { + 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) } @@ -357,8 +359,9 @@ func (l *links) add(u *url.URL, sintf string, linkType linkType) error { if backoffNow() { continue } - return } + // Ephemeral or incoming connections don't reconnect. + return } }() }) @@ -392,7 +395,7 @@ func (l *links) remove(u *url.URL, sintf string, _ linkType) error { return retErr } -func (l *links) listen(u *url.URL, sintf string) (*Listener, error) { +func (l *links) listen(u *url.URL, sintf string, local bool) (*Listener, error) { ctx, cancel := context.WithCancel(l.core.ctx) var protocol linkProtocol switch strings.ToLower(u.Scheme) { @@ -420,7 +423,10 @@ func (l *links) listen(u *url.URL, sintf string) (*Listener, error) { li := &Listener{ listener: listener, ctx: ctx, - Cancel: cancel, + Cancel: func() { + cancel() + _ = listener.Close() + }, } var options linkOptions @@ -516,7 +522,7 @@ func (l *links) listen(u *url.URL, sintf string) (*Listener, error) { // Give the connection to the handler. The handler will block // for the lifetime of the connection. - switch err = l.handler(linkTypeIncoming, options, lc, nil); { + switch err = l.handler(linkTypeIncoming, options, lc, nil, local); { case err == nil: case errors.Is(err, io.EOF): case errors.Is(err, net.ErrClosed): @@ -557,7 +563,7 @@ func (l *links) connect(ctx context.Context, u *url.URL, info linkInfo, options return dialer.dial(ctx, u, info, options) } -func (l *links) handler(linkType linkType, options linkOptions, conn net.Conn, success func()) error { +func (l *links) handler(linkType linkType, options linkOptions, conn net.Conn, success func(), local bool) error { meta := version_getBaseMetadata() meta.publicKey = l.core.public meta.priority = options.priority @@ -600,19 +606,21 @@ func (l *links) handler(linkType linkType, options linkOptions, conn net.Conn, s } } // Check if we're authorized to connect to this key / IP - var allowed map[[32]byte]struct{} - phony.Block(l.core, func() { - allowed = l.core.config._allowedPublicKeys - }) - isallowed := len(allowed) == 0 - for k := range allowed { - if bytes.Equal(k[:], meta.publicKey) { - isallowed = true - break + if !local { + var allowed map[[32]byte]struct{} + phony.Block(l.core, func() { + allowed = l.core.config._allowedPublicKeys + }) + isallowed := len(allowed) == 0 + for k := range allowed { + if bytes.Equal(k[:], meta.publicKey) { + isallowed = true + break + } + } + if linkType == linkTypeIncoming && !isallowed { + return fmt.Errorf("node public key %q is not in AllowedPublicKeys", hex.EncodeToString(meta.publicKey)) } - } - if linkType == linkTypeIncoming && !isallowed { - return fmt.Errorf("node public key %q is not in AllowedPublicKeys", hex.EncodeToString(meta.publicKey)) } dir := "outbound" diff --git a/src/core/link_ws.go b/src/core/link_ws.go index 7a7d66f7..0602ed28 100644 --- a/src/core/link_ws.go +++ b/src/core/link_ws.go @@ -8,7 +8,7 @@ import ( "time" "github.com/Arceliar/phony" - "nhooyr.io/websocket" + "github.com/coder/websocket" ) type linkWS struct { diff --git a/src/core/link_wss.go b/src/core/link_wss.go index a9a8df24..0bdb4f3a 100644 --- a/src/core/link_wss.go +++ b/src/core/link_wss.go @@ -7,7 +7,7 @@ import ( "net/url" "github.com/Arceliar/phony" - "nhooyr.io/websocket" + "github.com/coder/websocket" ) type linkWSS struct { diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 902d7729..77ea8a50 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -190,6 +190,8 @@ func (m *Multicast) _getAllowedInterfaces() map[string]*interfaceInfo { switch { case iface.Flags&net.FlagUp == 0: continue // Ignore interfaces that are down + case iface.Flags&net.FlagRunning == 0: + continue // Ignore interfaces that are not running case iface.Flags&net.FlagMulticast == 0: continue // Ignore non-multicast interfaces case iface.Flags&net.FlagPointToPoint != 0: @@ -325,7 +327,7 @@ func (m *Multicast) _announce() { Host: net.JoinHostPort(addrIP.String(), fmt.Sprintf("%d", info.port)), RawQuery: v.Encode(), } - if li, err := m.core.Listen(u, iface.Name); err == nil { + if li, err := m.core.ListenLocal(u, iface.Name); err == nil { m.log.Debugln("Started multicasting on", iface.Name) // Store the listener so that we can stop it later if needed linfo = &listenerInfo{listener: li, time: time.Now(), port: info.port} diff --git a/src/tun/iface.go b/src/tun/iface.go index 3a4c55f4..f1898281 100644 --- a/src/tun/iface.go +++ b/src/tun/iface.go @@ -1,5 +1,11 @@ package tun +import ( + "errors" + + wgtun "golang.zx2c4.com/wireguard/tun" +) + const TUN_OFFSET_BYTES = 80 // sizeof(virtio_net_hdr) func (tun *TunAdapter) read() { @@ -12,6 +18,10 @@ func (tun *TunAdapter) read() { for { n, err := tun.iface.Read(bufs, sizes, TUN_OFFSET_BYTES) if err != nil { + if errors.Is(err, wgtun.ErrTooManySegments) { + tun.log.Debugln("TUN segments dropped: %v", err) + continue + } tun.log.Errorln("Error reading TUN:", err) return } diff --git a/src/tun/tun_bsd.go b/src/tun/tun_freebsd.go similarity index 81% rename from src/tun/tun_bsd.go rename to src/tun/tun_freebsd.go index da5b3297..7b8ab50c 100644 --- a/src/tun/tun_bsd.go +++ b/src/tun/tun_freebsd.go @@ -1,5 +1,5 @@ -//go:build openbsd || freebsd -// +build openbsd freebsd +//go:build freebsd +// +build freebsd package tun @@ -54,11 +54,6 @@ struct in6_ifreq { 290 }; */ -type in6_ifreq_mtu struct { - ifr_name [syscall.IFNAMSIZ]byte - ifru_mtu int -} - type in6_ifreq_addr struct { ifr_name [syscall.IFNAMSIZ]byte ifru_addr sockaddr_in6 @@ -112,26 +107,6 @@ func (tun *TunAdapter) setupAddress(addr string) error { tun.log.Infof("Interface IPv6: %s", addr) tun.log.Infof("Interface MTU: %d", tun.mtu) - // Create the MTU request - var ir in6_ifreq_mtu - copy(ir.ifr_name[:], tun.Name()) - ir.ifru_mtu = int(tun.mtu) - - // Set the MTU - if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(sfd), uintptr(syscall.SIOCSIFMTU), uintptr(unsafe.Pointer(&ir))); errno != 0 { - err = errno - tun.log.Errorf("Error in SIOCSIFMTU: %v", errno) - - // Fall back to ifconfig to set the MTU - cmd := exec.Command("ifconfig", tun.Name(), "mtu", string(tun.mtu)) - tun.log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " ")) - output, err := cmd.CombinedOutput() - if err != nil { - tun.log.Errorf("SIOCSIFMTU fallback failed: %v.", err) - tun.log.Traceln(string(output)) - } - } - // Create the address request // FIXME: I don't work! var ar in6_ifreq_addr diff --git a/src/tun/tun_openbsd.go b/src/tun/tun_openbsd.go new file mode 100644 index 00000000..714db3a8 --- /dev/null +++ b/src/tun/tun_openbsd.go @@ -0,0 +1,122 @@ +//go:build openbsd +// +build openbsd + +package tun + +import ( + "fmt" + "net" + "syscall" + "unsafe" + + "golang.org/x/sys/unix" + + wgtun "golang.zx2c4.com/wireguard/tun" +) + +const ( + SIOCAIFADDR_IN6 = 0x8080691a + ND6_INFINITE_LIFETIME = 0xffffffff +) + +type in6_addrlifetime struct { + ia6t_expire int64 + ia6t_preferred int64 + ia6t_vltime uint32 + ia6t_pltime uint32 +} + +// Match types from the net package, effectively being [16]byte for IPv6 addresses. +type in6_addr [16]uint8 + +type sockaddr_in6 struct { + sin6_len uint8 + sin6_family uint8 + sin6_port uint16 + sin6_flowinfo uint32 + sin6_addr in6_addr + sin6_scope_id uint32 +} + +func (sa6 *sockaddr_in6) setSockaddr(addr [/*16*/]byte /* net.IP or net.IPMask */) { + sa6.sin6_len = uint8(unsafe.Sizeof(*sa6)) + sa6.sin6_family = unix.AF_INET6 + + for i := range sa6.sin6_addr { + sa6.sin6_addr[i] = addr[i] + } +} + +type in6_aliasreq struct { + ifra_name [syscall.IFNAMSIZ]byte + ifra_addr sockaddr_in6 + ifra_dstaddr sockaddr_in6 + ifra_prefixmask sockaddr_in6 + ifra_flags int32 + ifra_lifetime in6_addrlifetime +} + +// Configures the TUN adapter with the correct IPv6 address and MTU. +func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error { + iface, err := wgtun.CreateTUN(ifname, int(mtu)) + if err != nil { + return fmt.Errorf("failed to create TUN: %w", err) + } + tun.iface = iface + if mtu, err := iface.MTU(); err == nil { + tun.mtu = getSupportedMTU(uint64(mtu)) + } else { + tun.mtu = 0 + } + if addr != "" { + return tun.setupAddress(addr) + } + return nil +} + +// Configures the "utun" adapter from an existing file descriptor. +func (tun *TunAdapter) setupFD(fd int32, addr string, mtu uint64) error { + return fmt.Errorf("setup via FD not supported on this platform") +} + +func (tun *TunAdapter) setupAddress(addr string) error { + var sfd int + var err error + + ip, prefix, err := net.ParseCIDR(addr) + if err != nil { + tun.log.Errorf("Error in ParseCIDR: %v", err) + return err + } + + // Create system socket + if sfd, err = unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, 0); err != nil { + tun.log.Printf("Create AF_INET6 socket failed: %v", err) + return err + } + + // Friendly output + tun.log.Infof("Interface name: %s", tun.Name()) + tun.log.Infof("Interface IPv6: %s", addr) + tun.log.Infof("Interface MTU: %d", tun.mtu) + + // Create the address request + var ar in6_aliasreq + copy(ar.ifra_name[:], tun.Name()) + + ar.ifra_addr.setSockaddr(ip) + + prefixmask := net.CIDRMask(prefix.Mask.Size()) + ar.ifra_prefixmask.setSockaddr(prefixmask) + + ar.ifra_lifetime.ia6t_vltime = ND6_INFINITE_LIFETIME + ar.ifra_lifetime.ia6t_pltime = ND6_INFINITE_LIFETIME + + // Set the interface address + if err = unix.IoctlSetInt(sfd, SIOCAIFADDR_IN6, int(uintptr(unsafe.Pointer(&ar)))); err != nil { + tun.log.Errorf("Error in SIOCAIFADDR_IN6: %v", err) + return err + } + + return nil +}