mirror of
https://github.com/yggdrasil-network/yggdrasil-go.git
synced 2025-04-28 06:05:06 +03:00
Merge branch 'develop' into pledge
This commit is contained in:
commit
f2c863ad2d
25 changed files with 562 additions and 340 deletions
25
CHANGELOG.md
25
CHANGELOG.md
|
@ -26,6 +26,31 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
||||||
- in case of vulnerabilities.
|
- 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
|
## [0.5.9] - 2024-10-19
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -4,83 +4,53 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"os/user"
|
||||||
osuser "os/user"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
func chuser(user string) error {
|
func chuser(input string) error {
|
||||||
group := ""
|
givenUser, givenGroup, _ := strings.Cut(input, ":")
|
||||||
if i := strings.IndexByte(user, ':'); i >= 0 {
|
|
||||||
user, group = user[:i], user[i+1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
u := (*osuser.User)(nil)
|
var (
|
||||||
g := (*osuser.Group)(nil)
|
err error
|
||||||
|
usr *user.User
|
||||||
|
grp *user.Group
|
||||||
|
uid, gid int
|
||||||
|
)
|
||||||
|
|
||||||
if user != "" {
|
if usr, err = user.LookupId(givenUser); err != nil {
|
||||||
if _, err := strconv.ParseUint(user, 10, 32); err == nil {
|
if usr, err = user.Lookup(givenUser); err != nil {
|
||||||
u, err = osuser.LookupId(user)
|
return err
|
||||||
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 uid, err = strconv.Atoi(usr.Uid); err != nil {
|
||||||
if _, err := strconv.ParseUint(group, 10, 32); err == nil {
|
return err
|
||||||
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 {
|
if givenGroup != "" {
|
||||||
gid, _ := strconv.ParseUint(g.Gid, 10, 32)
|
if grp, err = user.LookupGroupId(givenGroup); err != nil {
|
||||||
var err error
|
if grp, err = user.LookupGroup(givenGroup); err != nil {
|
||||||
if gid < math.MaxInt {
|
return err
|
||||||
err = syscall.Setgid(int(gid))
|
}
|
||||||
} else {
|
|
||||||
err = errors.New("gid too big")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
gid, _ = strconv.Atoi(grp.Gid)
|
||||||
return fmt.Errorf("failed to setgid %d: %v", gid, err)
|
} else {
|
||||||
}
|
gid, _ = strconv.Atoi(usr.Gid)
|
||||||
} 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 {
|
if err := unix.Setgroups([]int{gid}); err != nil {
|
||||||
uid, _ := strconv.ParseUint(u.Uid, 10, 32)
|
return fmt.Errorf("setgroups: %d: %v", gid, err)
|
||||||
var err error
|
}
|
||||||
if uid < math.MaxInt {
|
if err := unix.Setgid(gid); err != nil {
|
||||||
err = syscall.Setuid(int(uid))
|
return fmt.Errorf("setgid: %d: %v", gid, err)
|
||||||
} else {
|
}
|
||||||
err = errors.New("uid too big")
|
if err := unix.Setuid(uid); err != nil {
|
||||||
}
|
return fmt.Errorf("setuid: %d: %v", uid, err)
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to setuid %d: %v", uid, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
80
cmd/yggdrasil/chuser_unix_test.go
Normal file
80
cmd/yggdrasil/chuser_unix_test.go
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,6 +14,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"suah.dev/protect"
|
||||||
|
|
||||||
"github.com/gologme/log"
|
"github.com/gologme/log"
|
||||||
gsyslog "github.com/hashicorp/go-syslog"
|
gsyslog "github.com/hashicorp/go-syslog"
|
||||||
"github.com/hjson/hjson-go/v4"
|
"github.com/hjson/hjson-go/v4"
|
||||||
|
@ -39,6 +41,20 @@ type node struct {
|
||||||
|
|
||||||
// The main function is responsible for configuring and starting Yggdrasil.
|
// The main function is responsible for configuring and starting Yggdrasil.
|
||||||
func main() {
|
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")
|
genconf := flag.Bool("genconf", false, "print a new config to stdout")
|
||||||
useconf := flag.Bool("useconf", false, "read HJSON/JSON config from stdin")
|
useconf := flag.Bool("useconf", false, "read HJSON/JSON config from stdin")
|
||||||
useconffile := flag.String("useconffile", "", "read HJSON/JSON config from specified file path")
|
useconffile := flag.String("useconffile", "", "read HJSON/JSON config from specified file path")
|
||||||
|
@ -191,9 +207,16 @@ func main() {
|
||||||
|
|
||||||
// Set up the Yggdrasil node itself.
|
// Set up the Yggdrasil node itself.
|
||||||
{
|
{
|
||||||
|
iprange := net.IPNet{
|
||||||
|
IP: net.ParseIP("200::"),
|
||||||
|
Mask: net.CIDRMask(7, 128),
|
||||||
|
}
|
||||||
options := []core.SetupOption{
|
options := []core.SetupOption{
|
||||||
core.NodeInfo(cfg.NodeInfo),
|
core.NodeInfo(cfg.NodeInfo),
|
||||||
core.NodeInfoPrivacy(cfg.NodeInfoPrivacy),
|
core.NodeInfoPrivacy(cfg.NodeInfoPrivacy),
|
||||||
|
core.PeerFilter(func(ip net.IP) bool {
|
||||||
|
return !iprange.Contains(ip)
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
for _, addr := range cfg.Listen {
|
for _, addr := range cfg.Listen {
|
||||||
options = append(options, core.ListenAddress(addr))
|
options = append(options, core.ListenAddress(addr))
|
||||||
|
|
|
@ -186,9 +186,9 @@ func run() int {
|
||||||
if err := json.Unmarshal(recv.Response, &resp); err != nil {
|
if err := json.Unmarshal(recv.Response, &resp); err != nil {
|
||||||
panic(err)
|
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 {
|
for _, peer := range resp.Peers {
|
||||||
state, lasterr, dir, rtt := "Up", "-", "Out", "-"
|
state, lasterr, dir, rtt, rxr, txr := "Up", "-", "Out", "-", "-", "-"
|
||||||
if !peer.Up {
|
if !peer.Up {
|
||||||
state, lasterr = "Down", fmt.Sprintf("%s ago: %s", peer.LastErrorTime.Round(time.Second), peer.LastError)
|
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 {
|
} else if rttms := float64(peer.Latency.Microseconds()) / 1000; rttms > 0 {
|
||||||
|
@ -202,6 +202,12 @@ func run() int {
|
||||||
uri.RawQuery = ""
|
uri.RawQuery = ""
|
||||||
uristring = uri.String()
|
uristring = uri.String()
|
||||||
}
|
}
|
||||||
|
if peer.RXRate > 0 {
|
||||||
|
rxr = peer.RXRate.String() + "/s"
|
||||||
|
}
|
||||||
|
if peer.TXRate > 0 {
|
||||||
|
txr = peer.TXRate.String() + "/s"
|
||||||
|
}
|
||||||
table.Append([]string{
|
table.Append([]string{
|
||||||
uristring,
|
uristring,
|
||||||
state,
|
state,
|
||||||
|
@ -211,6 +217,8 @@ func run() int {
|
||||||
rtt,
|
rtt,
|
||||||
peer.RXBytes.String(),
|
peer.RXBytes.String(),
|
||||||
peer.TXBytes.String(),
|
peer.TXBytes.String(),
|
||||||
|
rxr,
|
||||||
|
txr,
|
||||||
fmt.Sprintf("%d", peer.Priority),
|
fmt.Sprintf("%d", peer.Priority),
|
||||||
fmt.Sprintf("%d", peer.Cost),
|
fmt.Sprintf("%d", peer.Cost),
|
||||||
lasterr,
|
lasterr,
|
||||||
|
@ -285,9 +293,21 @@ func run() int {
|
||||||
if err := json.Unmarshal(recv.Response, &resp); err != nil {
|
if err := json.Unmarshal(recv.Response, &resp); err != nil {
|
||||||
panic(err)
|
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 {
|
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()
|
table.Render()
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,15 @@ func (m *Yggdrasil) StartJSON(configjson []byte) error {
|
||||||
}
|
}
|
||||||
// Set up the Yggdrasil node itself.
|
// 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 {
|
for _, peer := range m.config.Peers {
|
||||||
options = append(options, core.Peer{URI: peer})
|
options = append(options, core.Peer{URI: peer})
|
||||||
}
|
}
|
||||||
|
|
12
go.mod
12
go.mod
|
@ -3,7 +3,7 @@ module github.com/yggdrasil-network/yggdrasil-go
|
||||||
go 1.21
|
go 1.21
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Arceliar/ironwood v0.0.0-20241016082300-f6fb9da97a17
|
github.com/Arceliar/ironwood v0.0.0-20241210120540-9deb08d9f8f9
|
||||||
github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d
|
github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d
|
||||||
github.com/cheggaaa/pb/v3 v3.1.5
|
github.com/cheggaaa/pb/v3 v3.1.5
|
||||||
github.com/coder/websocket v1.8.12
|
github.com/coder/websocket v1.8.12
|
||||||
|
@ -14,10 +14,10 @@ require (
|
||||||
github.com/quic-go/quic-go v0.46.0
|
github.com/quic-go/quic-go v0.46.0
|
||||||
github.com/vishvananda/netlink v1.3.0
|
github.com/vishvananda/netlink v1.3.0
|
||||||
github.com/wlynxg/anet v0.0.5
|
github.com/wlynxg/anet v0.0.5
|
||||||
golang.org/x/crypto v0.28.0
|
golang.org/x/crypto v0.29.0
|
||||||
golang.org/x/net v0.30.0
|
golang.org/x/net v0.31.0
|
||||||
golang.org/x/sys v0.26.0
|
golang.org/x/sys v0.27.0
|
||||||
golang.org/x/text v0.19.0
|
golang.org/x/text v0.20.0
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.3
|
golang.zx2c4.com/wireguard/windows v0.5.3
|
||||||
|
@ -34,7 +34,7 @@ require (
|
||||||
go.uber.org/mock v0.4.0 // indirect
|
go.uber.org/mock v0.4.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
|
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
|
||||||
golang.org/x/mod v0.19.0 // 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
|
golang.org/x/tools v0.23.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
67
go.sum
67
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-20241210120540-9deb08d9f8f9 h1:myI8fs7+Iw6g/ywvY9QNQOEzny51AklMz4sF0ErtTm8=
|
||||||
github.com/Arceliar/ironwood v0.0.0-20241016082300-f6fb9da97a17/go.mod h1:6WP4799FX0OuWdENGQAh+0RXp9FLh0y7NZ7tM9cJyXk=
|
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 h1:UK9fsWbWqwIQkMCz1CP+v5pGbsGoWAw6g4AyvMpm1EM=
|
||||||
github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d/go.mod h1:BCnxhRf47C/dy/e/D2pmB8NkB3dQVIrkD98b220rx5Q=
|
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=
|
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/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 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
|
||||||
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
|
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 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
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.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
|
||||||
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 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
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 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
|
||||||
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
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.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
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-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.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.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.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.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.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
|
||||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
|
||||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
|
||||||
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 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
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 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
|
||||||
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
|
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 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
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=
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
|
||||||
|
|
|
@ -83,6 +83,52 @@ func New(c *core.Core, log core.Logger, opts ...SetupOption) (*AdminSocket, erro
|
||||||
if a.config.listenaddr == "none" || a.config.listenaddr == "" {
|
if a.config.listenaddr == "none" || a.config.listenaddr == "" {
|
||||||
return nil, nil
|
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) {
|
_ = a.AddHandler("list", "List available commands", []string{}, func(_ json.RawMessage) (interface{}, error) {
|
||||||
res := &ListResponse{}
|
res := &ListResponse{}
|
||||||
for name, handler := range a.handlers {
|
for name, handler := range a.handlers {
|
||||||
|
@ -233,50 +279,6 @@ func (a *AdminSocket) Stop() error {
|
||||||
|
|
||||||
// listen is run by start and manages API connections.
|
// listen is run by start and manages API connections.
|
||||||
func (a *AdminSocket) listen() {
|
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()
|
defer a.listener.Close()
|
||||||
for {
|
for {
|
||||||
conn, err := a.listener.Accept()
|
conn, err := a.listener.Accept()
|
||||||
|
@ -354,13 +356,15 @@ type DataUnit uint64
|
||||||
|
|
||||||
func (d DataUnit) String() string {
|
func (d DataUnit) String() string {
|
||||||
switch {
|
switch {
|
||||||
case d > 1024*1024*1024*1024:
|
case d >= 1024*1024*1024*1024:
|
||||||
return fmt.Sprintf("%2.ftb", float64(d)/1024/1024/1024/1024)
|
return fmt.Sprintf("%2.1fTB", float64(d)/1024/1024/1024/1024)
|
||||||
case d > 1024*1024*1024:
|
case d >= 1024*1024*1024:
|
||||||
return fmt.Sprintf("%2.fgb", float64(d)/1024/1024/1024)
|
return fmt.Sprintf("%2.1fGB", float64(d)/1024/1024/1024)
|
||||||
case d > 1024*1024:
|
case d >= 1024*1024:
|
||||||
return fmt.Sprintf("%2.fmb", float64(d)/1024/1024)
|
return fmt.Sprintf("%2.1fMB", float64(d)/1024/1024)
|
||||||
|
case d >= 100:
|
||||||
|
return fmt.Sprintf("%2.1fKB", float64(d)/1024)
|
||||||
default:
|
default:
|
||||||
return fmt.Sprintf("%2.fkb", float64(d)/1024)
|
return fmt.Sprintf("%dB", d)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,8 @@ type PeerEntry struct {
|
||||||
Cost uint64 `json:"cost"`
|
Cost uint64 `json:"cost"`
|
||||||
RXBytes DataUnit `json:"bytes_recvd,omitempty"`
|
RXBytes DataUnit `json:"bytes_recvd,omitempty"`
|
||||||
TXBytes DataUnit `json:"bytes_sent,omitempty"`
|
TXBytes DataUnit `json:"bytes_sent,omitempty"`
|
||||||
|
RXRate DataUnit `json:"rate_recvd,omitempty"`
|
||||||
|
TXRate DataUnit `json:"rate_sent,omitempty"`
|
||||||
Uptime float64 `json:"uptime,omitempty"`
|
Uptime float64 `json:"uptime,omitempty"`
|
||||||
Latency time.Duration `json:"latency_ms,omitempty"`
|
Latency time.Duration `json:"latency_ms,omitempty"`
|
||||||
LastErrorTime time.Duration `json:"last_error_time,omitempty"`
|
LastErrorTime time.Duration `json:"last_error_time,omitempty"`
|
||||||
|
@ -47,6 +49,8 @@ func (a *AdminSocket) getPeersHandler(_ *GetPeersRequest, res *GetPeersResponse)
|
||||||
URI: p.URI,
|
URI: p.URI,
|
||||||
RXBytes: DataUnit(p.RXBytes),
|
RXBytes: DataUnit(p.RXBytes),
|
||||||
TXBytes: DataUnit(p.TXBytes),
|
TXBytes: DataUnit(p.TXBytes),
|
||||||
|
RXRate: DataUnit(p.RXRate),
|
||||||
|
TXRate: DataUnit(p.TXRate),
|
||||||
Uptime: p.Uptime.Seconds(),
|
Uptime: p.Uptime.Seconds(),
|
||||||
}
|
}
|
||||||
if p.Latency > 0 {
|
if p.Latency > 0 {
|
||||||
|
|
|
@ -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\"."`
|
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."`
|
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."`
|
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!"`
|
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."`
|
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."`
|
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
|
Regex string
|
||||||
Beacon bool
|
Beacon bool
|
||||||
Listen bool
|
Listen bool
|
||||||
Port uint16
|
Port uint16 `json:",omitempty"`
|
||||||
Priority uint64 // really uint8, but gobind won't export it
|
Priority uint64 `json:",omitempty"` // really uint8, but gobind won't export it
|
||||||
Password string
|
Password string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ func getDefaults() platformDefaultParameters {
|
||||||
DefaultMulticastInterfaces: []MulticastInterfaceConfig{
|
DefaultMulticastInterfaces: []MulticastInterfaceConfig{
|
||||||
{Regex: "en.*", Beacon: true, Listen: true},
|
{Regex: "en.*", Beacon: true, Listen: true},
|
||||||
{Regex: "bridge.*", Beacon: true, Listen: true},
|
{Regex: "bridge.*", Beacon: true, Listen: true},
|
||||||
|
{Regex: "awdl0", Beacon: false, Listen: false},
|
||||||
},
|
},
|
||||||
|
|
||||||
// TUN
|
// TUN
|
||||||
|
|
|
@ -33,6 +33,8 @@ type PeerInfo struct {
|
||||||
Cost uint64
|
Cost uint64
|
||||||
RXBytes uint64
|
RXBytes uint64
|
||||||
TXBytes uint64
|
TXBytes uint64
|
||||||
|
RXRate uint64
|
||||||
|
TXRate uint64
|
||||||
Uptime time.Duration
|
Uptime time.Duration
|
||||||
Latency time.Duration
|
Latency time.Duration
|
||||||
}
|
}
|
||||||
|
@ -87,6 +89,8 @@ func (c *Core) GetPeers() []PeerInfo {
|
||||||
peerinfo.Inbound = state.linkType == linkTypeIncoming
|
peerinfo.Inbound = state.linkType == linkTypeIncoming
|
||||||
peerinfo.RXBytes = atomic.LoadUint64(&c.rx)
|
peerinfo.RXBytes = atomic.LoadUint64(&c.rx)
|
||||||
peerinfo.TXBytes = atomic.LoadUint64(&c.tx)
|
peerinfo.TXBytes = atomic.LoadUint64(&c.tx)
|
||||||
|
peerinfo.RXRate = atomic.LoadUint64(&c.rxrate)
|
||||||
|
peerinfo.TXRate = atomic.LoadUint64(&c.txrate)
|
||||||
peerinfo.Uptime = time.Since(c.up)
|
peerinfo.Uptime = time.Since(c.up)
|
||||||
}
|
}
|
||||||
if p, ok := conns[conn]; ok {
|
if p, ok := conns[conn]; ok {
|
||||||
|
|
|
@ -40,6 +40,7 @@ type Core struct {
|
||||||
tls *tls.Config // immutable after startup
|
tls *tls.Config // immutable after startup
|
||||||
//_peers map[Peer]*linkInfo // configurable after startup
|
//_peers map[Peer]*linkInfo // configurable after startup
|
||||||
_listeners map[ListenAddress]struct{} // configurable after startup
|
_listeners map[ListenAddress]struct{} // configurable after startup
|
||||||
|
peerFilter func(ip net.IP) bool // immutable after startup
|
||||||
nodeinfo NodeInfo // immutable after startup
|
nodeinfo NodeInfo // immutable after startup
|
||||||
nodeinfoPrivacy NodeInfoPrivacy // immutable after startup
|
nodeinfoPrivacy NodeInfoPrivacy // immutable after startup
|
||||||
_allowedPublicKeys map[[32]byte]struct{} // configurable after startup
|
_allowedPublicKeys map[[32]byte]struct{} // configurable after startup
|
||||||
|
|
119
src/core/link.go
119
src/core/link.go
|
@ -99,13 +99,40 @@ func (l *links) init(c *Core) error {
|
||||||
l._links = make(map[linkInfo]*link)
|
l._links = make(map[linkInfo]*link)
|
||||||
l._listeners = make(map[*Listener]context.CancelFunc)
|
l._listeners = make(map[*Listener]context.CancelFunc)
|
||||||
|
|
||||||
|
l.Act(nil, l._updateAverages)
|
||||||
return nil
|
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() {
|
func (l *links) shutdown() {
|
||||||
phony.Block(l, func() {
|
phony.Block(l, func() {
|
||||||
for listener := range l._listeners {
|
for _, cancel := range l._listeners {
|
||||||
_ = listener.listener.Close()
|
cancel()
|
||||||
}
|
}
|
||||||
for _, link := range l._links {
|
for _, link := range l._links {
|
||||||
if link._conn != nil {
|
if link._conn != nil {
|
||||||
|
@ -127,6 +154,7 @@ const ErrLinkPasswordInvalid = linkError("invalid password supplied")
|
||||||
const ErrLinkUnrecognisedSchema = linkError("link schema unknown")
|
const ErrLinkUnrecognisedSchema = linkError("link schema unknown")
|
||||||
const ErrLinkMaxBackoffInvalid = linkError("max backoff duration invalid")
|
const ErrLinkMaxBackoffInvalid = linkError("max backoff duration invalid")
|
||||||
const ErrLinkSNINotSupported = linkError("SNI not supported on this link type")
|
const ErrLinkSNINotSupported = linkError("SNI not supported on this link type")
|
||||||
|
const ErrLinkNoSuitableIPs = linkError("peer has no suitable addresses")
|
||||||
|
|
||||||
func (l *links) add(u *url.URL, sintf string, linkType linkType) error {
|
func (l *links) add(u *url.URL, sintf string, linkType linkType) error {
|
||||||
var retErr error
|
var retErr error
|
||||||
|
@ -337,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
|
// Give the connection to the handler. The handler will block
|
||||||
// for the lifetime of the connection.
|
// for the lifetime of the connection.
|
||||||
if err = l.handler(linkType, options, lc, resetBackoff, false); err != nil && err != io.EOF {
|
switch err = l.handler(linkType, options, lc, resetBackoff, false); {
|
||||||
l.core.log.Debugf("Link %s error: %s\n", info.uri, err)
|
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,
|
// The handler has stopped running so the connection is dead,
|
||||||
|
@ -397,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) {
|
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
|
var protocol linkProtocol
|
||||||
switch strings.ToLower(u.Scheme) {
|
switch strings.ToLower(u.Scheme) {
|
||||||
case "tcp":
|
case "tcp":
|
||||||
|
@ -413,21 +445,25 @@ func (l *links) listen(u *url.URL, sintf string, local bool) (*Listener, error)
|
||||||
case "wss":
|
case "wss":
|
||||||
protocol = l.wss
|
protocol = l.wss
|
||||||
default:
|
default:
|
||||||
cancel()
|
ctxcancel()
|
||||||
return nil, ErrLinkUnrecognisedSchema
|
return nil, ErrLinkUnrecognisedSchema
|
||||||
}
|
}
|
||||||
listener, err := protocol.listen(ctx, u, sintf)
|
listener, err := protocol.listen(ctx, u, sintf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cancel()
|
ctxcancel()
|
||||||
return nil, err
|
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{
|
li := &Listener{
|
||||||
listener: listener,
|
listener: listener,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
Cancel: func() {
|
Cancel: cancel,
|
||||||
cancel()
|
|
||||||
_ = listener.Close()
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var options linkOptions
|
var options linkOptions
|
||||||
|
@ -450,10 +486,11 @@ func (l *links) listen(u *url.URL, sintf string, local bool) (*Listener, error)
|
||||||
})
|
})
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
l.core.log.Infof("%s listener started on %s", strings.ToUpper(u.Scheme), li.listener.Addr())
|
l.core.log.Infof("%s listener started on %s", strings.ToUpper(u.Scheme), addr)
|
||||||
defer l.core.log.Infof("%s listener stopped on %s", strings.ToUpper(u.Scheme), li.listener.Addr())
|
|
||||||
defer phony.Block(l, func() {
|
defer phony.Block(l, func() {
|
||||||
|
cancel()
|
||||||
delete(l._listeners, li)
|
delete(l._listeners, li)
|
||||||
|
l.core.log.Infof("%s listener stopped on %s", strings.ToUpper(u.Scheme), addr)
|
||||||
})
|
})
|
||||||
for {
|
for {
|
||||||
conn, err := li.listener.Accept()
|
conn, err := li.listener.Accept()
|
||||||
|
@ -653,6 +690,52 @@ func (l *links) handler(linkType linkType, options linkOptions, conn net.Conn, s
|
||||||
return err
|
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 {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
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 {
|
func urlForLinkInfo(u url.URL) url.URL {
|
||||||
u.RawQuery = ""
|
u.RawQuery = ""
|
||||||
return u
|
return u
|
||||||
|
@ -661,9 +744,13 @@ func urlForLinkInfo(u url.URL) url.URL {
|
||||||
type linkConn struct {
|
type linkConn struct {
|
||||||
// tx and rx are at the beginning of the struct to ensure 64-bit alignment
|
// 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
|
// on 32-bit platforms, see https://pkg.go.dev/sync/atomic#pkg-note-BUG
|
||||||
rx uint64
|
rx uint64
|
||||||
tx uint64
|
tx uint64
|
||||||
up time.Time
|
rxrate uint64
|
||||||
|
txrate uint64
|
||||||
|
lastrx uint64
|
||||||
|
lasttx uint64
|
||||||
|
up time.Time
|
||||||
net.Conn
|
net.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
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)
|
tlsconfig := l.tlsconfig.Clone()
|
||||||
if err != nil {
|
return l.links.findSuitableIP(url, func(hostname string, ip net.IP, port int) (net.Conn, error) {
|
||||||
return nil, err
|
tlsconfig.ServerName = hostname
|
||||||
}
|
hostport := net.JoinHostPort(ip.String(), fmt.Sprintf("%d", port))
|
||||||
qs, err := qc.OpenStreamSync(ctx)
|
qc, err := quic.DialAddr(ctx, hostport, l.tlsconfig, l.quicconfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &linkQUICStream{
|
qs, err := qc.OpenStreamSync(ctx)
|
||||||
Connection: qc,
|
if err != nil {
|
||||||
Stream: qs,
|
return nil, err
|
||||||
}, nil
|
}
|
||||||
|
return &linkQUICStream{
|
||||||
|
Connection: qc,
|
||||||
|
Stream: qs,
|
||||||
|
}, nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *linkQUIC) listen(ctx context.Context, url *url.URL, _ string) (net.Listener, error) {
|
func (l *linkQUIC) listen(ctx context.Context, url *url.URL, _ string) (net.Listener, error) {
|
||||||
|
|
|
@ -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) {
|
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
|
var proxyAuth *proxy.Auth
|
||||||
if url.User != nil && url.User.Username() != "" {
|
if url.User != nil && url.User.Username() != "" {
|
||||||
proxyAuth = &proxy.Auth{
|
proxyAuth = &proxy.Auth{
|
||||||
|
@ -33,21 +30,34 @@ func (l *linkSOCKS) dial(_ context.Context, url *url.URL, info linkInfo, options
|
||||||
}
|
}
|
||||||
proxyAuth.Password, _ = url.User.Password()
|
proxyAuth.Password, _ = url.User.Password()
|
||||||
}
|
}
|
||||||
dialer, err := proxy.SOCKS5("tcp", url.Host, proxyAuth, proxy.Direct)
|
tlsconfig := l.tls.config.Clone()
|
||||||
if err != nil {
|
return l.links.findSuitableIP(url, func(hostname string, ip net.IP, port int) (net.Conn, error) {
|
||||||
return nil, fmt.Errorf("failed to configure proxy")
|
hostport := net.JoinHostPort(ip.String(), fmt.Sprintf("%d", port))
|
||||||
}
|
dialer, err := l.tcp.dialerFor(&net.TCPAddr{
|
||||||
pathtokens := strings.Split(strings.Trim(url.Path, "/"), "/")
|
IP: ip,
|
||||||
conn, err := dialer.Dial("tcp", pathtokens[0])
|
Port: port,
|
||||||
if err != nil {
|
}, info.sintf)
|
||||||
return nil, fmt.Errorf("failed to dial: %w", err)
|
if err != nil {
|
||||||
}
|
return nil, err
|
||||||
if url.Scheme == "sockstls" {
|
}
|
||||||
tlsconfig := l.tls.config.Clone()
|
proxy, err := proxy.SOCKS5("tcp", hostport, proxyAuth, dialer)
|
||||||
tlsconfig.ServerName = options.tlsSNI
|
if err != nil {
|
||||||
conn = tls.Client(conn, tlsconfig)
|
return nil, err
|
||||||
}
|
}
|
||||||
return conn, nil
|
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) {
|
func (l *linkSOCKS) listen(ctx context.Context, url *url.URL, _ string) (net.Listener, error) {
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Arceliar/phony"
|
"github.com/Arceliar/phony"
|
||||||
|
@ -28,65 +27,18 @@ func (l *links) newLinkTCP() *linkTCP {
|
||||||
return lt
|
return lt
|
||||||
}
|
}
|
||||||
|
|
||||||
type tcpDialer struct {
|
func (l *linkTCP) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
|
||||||
info linkInfo
|
return l.links.findSuitableIP(url, func(hostname string, ip net.IP, port int) (net.Conn, error) {
|
||||||
dialer *net.Dialer
|
|
||||||
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 {
|
|
||||||
addr := &net.TCPAddr{
|
addr := &net.TCPAddr{
|
||||||
IP: ip,
|
IP: ip,
|
||||||
Port: port,
|
Port: port,
|
||||||
}
|
}
|
||||||
dialer, err := l.dialerFor(addr, info.sintf)
|
dialer, err := l.tcp.dialerFor(addr, info.sintf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
return nil, err
|
||||||
}
|
}
|
||||||
dialers = append(dialers, &tcpDialer{
|
return dialer.DialContext(ctx, "tcp", addr.String())
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *linkTCP) listen(ctx context.Context, url *url.URL, sintf string) (net.Listener, error) {
|
func (l *linkTCP) listen(ctx context.Context, url *url.URL, sintf string) (net.Listener, error) {
|
||||||
|
|
|
@ -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) {
|
func (l *linkTLS) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
|
||||||
dialers, err := l.tcp.dialersFor(url, info)
|
tlsconfig := l.config.Clone()
|
||||||
if err != nil {
|
return l.links.findSuitableIP(url, func(hostname string, ip net.IP, port int) (net.Conn, error) {
|
||||||
return nil, err
|
tlsconfig.ServerName = hostname
|
||||||
}
|
if sni := options.tlsSNI; sni != "" {
|
||||||
if len(dialers) == 0 {
|
tlsconfig.ServerName = sni
|
||||||
return nil, nil
|
}
|
||||||
}
|
addr := &net.TCPAddr{
|
||||||
for _, d := range dialers {
|
IP: ip,
|
||||||
tlsconfig := l.config.Clone()
|
Port: port,
|
||||||
tlsconfig.ServerName = options.tlsSNI
|
}
|
||||||
|
dialer, err := l.tcp.dialerFor(addr, info.sintf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
tlsdialer := &tls.Dialer{
|
tlsdialer := &tls.Dialer{
|
||||||
NetDialer: d.dialer,
|
NetDialer: dialer,
|
||||||
Config: tlsconfig,
|
Config: tlsconfig,
|
||||||
}
|
}
|
||||||
var conn net.Conn
|
return tlsdialer.DialContext(ctx, "tcp", addr.String())
|
||||||
conn, err = tlsdialer.DialContext(ctx, "tcp", d.addr.String())
|
})
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *linkTLS) listen(ctx context.Context, url *url.URL, sintf string) (net.Listener, error) {
|
func (l *linkTLS) listen(ctx context.Context, url *url.URL, sintf string) (net.Listener, error) {
|
||||||
|
|
|
@ -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) {
|
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)
|
addr, err := net.ResolveUnixAddr("unix", url.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -2,6 +2,7 @@ package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"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) {
|
func (l *linkWS) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
|
||||||
if options.tlsSNI != "" {
|
return l.links.findSuitableIP(url, func(hostname string, ip net.IP, port int) (net.Conn, error) {
|
||||||
return nil, ErrLinkSNINotSupported
|
u := *url
|
||||||
}
|
u.Host = net.JoinHostPort(ip.String(), fmt.Sprintf("%d", port))
|
||||||
wsconn, _, err := websocket.Dial(ctx, url.String(), &websocket.DialOptions{
|
addr := &net.TCPAddr{
|
||||||
Subprotocols: []string{"ygg-ws"},
|
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) {
|
func (l *linkWS) listen(ctx context.Context, url *url.URL, _ string) (net.Listener, error) {
|
||||||
|
|
|
@ -2,8 +2,10 @@ package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/Arceliar/phony"
|
"github.com/Arceliar/phony"
|
||||||
|
@ -13,6 +15,7 @@ import (
|
||||||
type linkWSS struct {
|
type linkWSS struct {
|
||||||
phony.Inbox
|
phony.Inbox
|
||||||
*links
|
*links
|
||||||
|
tlsconfig *tls.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
type linkWSSConn struct {
|
type linkWSSConn struct {
|
||||||
|
@ -21,24 +24,45 @@ type linkWSSConn struct {
|
||||||
|
|
||||||
func (l *links) newLinkWSS() *linkWSS {
|
func (l *links) newLinkWSS() *linkWSS {
|
||||||
lwss := &linkWSS{
|
lwss := &linkWSS{
|
||||||
links: l,
|
links: l,
|
||||||
|
tlsconfig: l.core.config.tls.Clone(),
|
||||||
}
|
}
|
||||||
return lwss
|
return lwss
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *linkWSS) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
|
func (l *linkWSS) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
|
||||||
if options.tlsSNI != "" {
|
tlsconfig := l.tlsconfig.Clone()
|
||||||
return nil, ErrLinkSNINotSupported
|
return l.links.findSuitableIP(url, func(hostname string, ip net.IP, port int) (net.Conn, error) {
|
||||||
}
|
tlsconfig.ServerName = hostname
|
||||||
wsconn, _, err := websocket.Dial(ctx, url.String(), &websocket.DialOptions{
|
u := *url
|
||||||
Subprotocols: []string{"ygg-ws"},
|
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 &linkWSSConn{
|
||||||
|
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) {
|
func (l *linkWSS) listen(ctx context.Context, url *url.URL, _ string) (net.Listener, error) {
|
||||||
|
|
|
@ -3,6 +3,7 @@ package core
|
||||||
import (
|
import (
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -24,6 +25,8 @@ func (c *Core) _applyOption(opt SetupOption) (err error) {
|
||||||
}
|
}
|
||||||
case ListenAddress:
|
case ListenAddress:
|
||||||
c.config._listeners[v] = struct{}{}
|
c.config._listeners[v] = struct{}{}
|
||||||
|
case PeerFilter:
|
||||||
|
c.config.peerFilter = v
|
||||||
case NodeInfo:
|
case NodeInfo:
|
||||||
c.config.nodeinfo = v
|
c.config.nodeinfo = v
|
||||||
case NodeInfoPrivacy:
|
case NodeInfoPrivacy:
|
||||||
|
@ -48,9 +51,11 @@ type Peer struct {
|
||||||
type NodeInfo map[string]interface{}
|
type NodeInfo map[string]interface{}
|
||||||
type NodeInfoPrivacy bool
|
type NodeInfoPrivacy bool
|
||||||
type AllowedPublicKey ed25519.PublicKey
|
type AllowedPublicKey ed25519.PublicKey
|
||||||
|
type PeerFilter func(net.IP) bool
|
||||||
|
|
||||||
func (a ListenAddress) isSetupOption() {}
|
func (a ListenAddress) isSetupOption() {}
|
||||||
func (a Peer) isSetupOption() {}
|
func (a Peer) isSetupOption() {}
|
||||||
func (a NodeInfo) isSetupOption() {}
|
func (a NodeInfo) isSetupOption() {}
|
||||||
func (a NodeInfoPrivacy) isSetupOption() {}
|
func (a NodeInfoPrivacy) isSetupOption() {}
|
||||||
func (a AllowedPublicKey) isSetupOption() {}
|
func (a AllowedPublicKey) isSetupOption() {}
|
||||||
|
func (a PeerFilter) isSetupOption() {}
|
||||||
|
|
|
@ -2,20 +2,47 @@ package multicast
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Arceliar/phony"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
|
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type GetMulticastInterfacesRequest struct{}
|
type GetMulticastInterfacesRequest struct{}
|
||||||
type GetMulticastInterfacesResponse 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 {
|
func (m *Multicast) getMulticastInterfacesHandler(_ *GetMulticastInterfacesRequest, res *GetMulticastInterfacesResponse) error {
|
||||||
res.Interfaces = []string{}
|
res.Interfaces = []MulticastInterfaceState{}
|
||||||
for _, v := range m.Interfaces() {
|
phony.Block(m, func() {
|
||||||
res.Interfaces = append(res.Interfaces, v.Name)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -156,7 +156,13 @@ func (m *Multicast) _updateInterfaces() {
|
||||||
delete(interfaces, name)
|
delete(interfaces, name)
|
||||||
continue
|
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
|
interfaces[name] = info
|
||||||
m.log.Debugf("Discovered addresses for interface %s: %s", name, addrs)
|
m.log.Debugf("Discovered addresses for interface %s: %s", name, addrs)
|
||||||
}
|
}
|
||||||
|
@ -299,13 +305,9 @@ func (m *Multicast) _announce() {
|
||||||
for _, info := range m._interfaces {
|
for _, info := range m._interfaces {
|
||||||
iface := info.iface
|
iface := info.iface
|
||||||
for _, addr := range info.addrs {
|
for _, addr := range info.addrs {
|
||||||
addrIP, _, _ := net.ParseCIDR(addr.String())
|
addrIP, _, err := net.ParseCIDR(addr.String())
|
||||||
// Ignore IPv4 addresses
|
// Ignore IPv4 addresses or non-link-local addresses
|
||||||
if addrIP.To4() != nil {
|
if err != nil || addrIP.To4() != nil || !addrIP.IsLinkLocalUnicast() {
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Ignore non-link-local addresses
|
|
||||||
if !addrIP.IsLinkLocalUnicast() {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if info.listen {
|
if info.listen {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue