mirror of
https://github.com/fosrl/newt.git
synced 2026-09-18 20:09:07 +02:00
Scrape and send local endpoints
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"net"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/fosrl/newt/logger"
|
||||
)
|
||||
|
||||
// Interface name patterns used to rank candidate local endpoints. Interfaces
|
||||
// matching physicalInterfacePatterns are tried first, interfaces matching
|
||||
// virtualInterfacePatterns (container/VPN/hypervisor bridges and the like)
|
||||
// are tried last, and everything else falls in between.
|
||||
var (
|
||||
physicalInterfacePatterns = []*regexp.Regexp{
|
||||
regexp.MustCompile(`(?i)^eth\d+$`),
|
||||
regexp.MustCompile(`(?i)^en\d+$`),
|
||||
regexp.MustCompile(`(?i)^eno\d+$`),
|
||||
regexp.MustCompile(`(?i)^ens\d+$`),
|
||||
regexp.MustCompile(`(?i)^enp\d+s\d+`),
|
||||
regexp.MustCompile(`(?i)^wlan\d*$`),
|
||||
regexp.MustCompile(`(?i)^wlp\d+s\d+`),
|
||||
regexp.MustCompile(`(?i)^wl\d+$`),
|
||||
regexp.MustCompile(`(?i)ethernet`),
|
||||
regexp.MustCompile(`(?i)wi-?fi`),
|
||||
regexp.MustCompile(`(?i)wireless`),
|
||||
}
|
||||
|
||||
virtualInterfacePatterns = []*regexp.Regexp{
|
||||
regexp.MustCompile(`(?i)docker`),
|
||||
regexp.MustCompile(`(?i)podman`),
|
||||
regexp.MustCompile(`(?i)^veth`),
|
||||
regexp.MustCompile(`(?i)^virbr`),
|
||||
regexp.MustCompile(`(?i)vmnet`),
|
||||
regexp.MustCompile(`(?i)vboxnet`),
|
||||
regexp.MustCompile(`(?i)virtualbox`),
|
||||
regexp.MustCompile(`(?i)^vbox`),
|
||||
regexp.MustCompile(`(?i)vmware`),
|
||||
regexp.MustCompile(`(?i)hyper-?v`),
|
||||
regexp.MustCompile(`(?i)vethernet`),
|
||||
regexp.MustCompile(`(?i)npcap`),
|
||||
regexp.MustCompile(`(?i)^tun\d*$`),
|
||||
regexp.MustCompile(`(?i)^tap\d*$`),
|
||||
regexp.MustCompile(`(?i)^wg\d*$`),
|
||||
regexp.MustCompile(`(?i)^utun\d*$`),
|
||||
regexp.MustCompile(`(?i)zerotier`),
|
||||
regexp.MustCompile(`(?i)^zt`),
|
||||
regexp.MustCompile(`(?i)tailscale`),
|
||||
regexp.MustCompile(`(?i)^ppp\d*$`),
|
||||
regexp.MustCompile(`(?i)bridge`),
|
||||
regexp.MustCompile(`(?i)^br-`),
|
||||
regexp.MustCompile(`(?i)^br\d+$`),
|
||||
regexp.MustCompile(`(?i)^cni`),
|
||||
regexp.MustCompile(`(?i)flannel`),
|
||||
regexp.MustCompile(`(?i)weave`),
|
||||
regexp.MustCompile(`(?i)kube`),
|
||||
regexp.MustCompile(`(?i)isatap`),
|
||||
regexp.MustCompile(`(?i)teredo`),
|
||||
regexp.MustCompile(`(?i)bluetooth`),
|
||||
regexp.MustCompile(`(?i)^awdl\d*$`),
|
||||
regexp.MustCompile(`(?i)^llw\d*$`),
|
||||
regexp.MustCompile(`(?i)p2p`),
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
scorePhysical = 0
|
||||
scoreUnknown = 10
|
||||
scoreVirtual = 20
|
||||
scoreLinkLocal = 1000
|
||||
)
|
||||
|
||||
// interfaceScore ranks an interface name by how likely it is to be a
|
||||
// real, usable network interface. Lower scores are tried first.
|
||||
func interfaceScore(name string) int {
|
||||
for _, re := range physicalInterfacePatterns {
|
||||
if re.MatchString(name) {
|
||||
return scorePhysical
|
||||
}
|
||||
}
|
||||
for _, re := range virtualInterfacePatterns {
|
||||
if re.MatchString(name) {
|
||||
return scoreVirtual
|
||||
}
|
||||
}
|
||||
return scoreUnknown
|
||||
}
|
||||
|
||||
// GetLocalEndpoints returns "ip:port" strings (bracketed for IPv6, e.g.
|
||||
// "[fe80::1]:51820") for every usable, non-loopback IP address bound to a
|
||||
// network interface on this host. The list is ordered with interfaces most
|
||||
// likely to be a genuine host network (wired/Wi-Fi) first, and interfaces
|
||||
// that are typically synthetic (Docker, VPN tunnels, hypervisor bridges,
|
||||
// etc.) last, so callers should try the results roughly in order.
|
||||
//
|
||||
// excludeInterface, if non-empty, is skipped entirely - this is normally the
|
||||
// name of our own WireGuard/TUN interface, whose address is the tunnel IP
|
||||
// and not a useful endpoint to advertise.
|
||||
//
|
||||
// If interfaces cannot be enumerated (e.g. insufficient OS permissions),
|
||||
// an info message is logged and an empty slice is returned.
|
||||
func GetLocalEndpoints(port uint16, excludeInterface string) []string {
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
logger.Info("Unable to enumerate local network interfaces, localEndpoints will not be reported: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
type candidate struct {
|
||||
score int
|
||||
ip string
|
||||
}
|
||||
var candidates []candidate
|
||||
|
||||
for _, iface := range ifaces {
|
||||
if excludeInterface != "" && iface.Name == excludeInterface {
|
||||
continue
|
||||
}
|
||||
if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
addrs, err := iface.Addrs()
|
||||
if err != nil {
|
||||
logger.Debug("Unable to read addresses for interface %s: %v", iface.Name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
baseScore := interfaceScore(iface.Name)
|
||||
|
||||
for _, addr := range addrs {
|
||||
var ip net.IP
|
||||
switch v := addr.(type) {
|
||||
case *net.IPNet:
|
||||
ip = v.IP
|
||||
case *net.IPAddr:
|
||||
ip = v.IP
|
||||
}
|
||||
if ip == nil || ip.IsLoopback() || ip.IsUnspecified() {
|
||||
continue
|
||||
}
|
||||
|
||||
score := baseScore
|
||||
if ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
|
||||
score += scoreLinkLocal
|
||||
}
|
||||
|
||||
candidates = append(candidates, candidate{score: score, ip: ip.String()})
|
||||
}
|
||||
}
|
||||
|
||||
sort.SliceStable(candidates, func(i, j int) bool {
|
||||
return candidates[i].score < candidates[j].score
|
||||
})
|
||||
|
||||
portStr := strconv.Itoa(int(port))
|
||||
endpoints := make([]string, 0, len(candidates))
|
||||
for _, c := range candidates {
|
||||
endpoints = append(endpoints, net.JoinHostPort(c.ip, portStr))
|
||||
}
|
||||
return endpoints
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
wgnetstack "github.com/fosrl/newt/clients"
|
||||
"github.com/fosrl/newt/clients/permissions"
|
||||
"github.com/fosrl/newt/logger"
|
||||
"github.com/fosrl/newt/network"
|
||||
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||
)
|
||||
|
||||
@@ -102,6 +103,13 @@ func (n *Newt) clientsOnConnect() {
|
||||
}
|
||||
}
|
||||
|
||||
// localEndpoints returns "ip:port" candidates on this host that could
|
||||
// potentially be used to reach our WireGuard listen port, ranked with the
|
||||
// most likely genuine host interfaces first.
|
||||
func (n *Newt) localEndpoints() []string {
|
||||
return network.GetLocalEndpoints(n.config.Port, n.config.InterfaceName)
|
||||
}
|
||||
|
||||
func (n *Newt) clientsStartDirectRelay(tunnelIP string) {
|
||||
if !n.ready {
|
||||
return
|
||||
|
||||
+11
-8
@@ -167,10 +167,11 @@ func (n *Newt) registerHandlers(ctx context.Context) {
|
||||
chainId := generateChainId()
|
||||
n.pendingRegisterChainId = chainId
|
||||
n.stopFunc = n.client.SendMessageInterval(topicWGRegister, map[string]interface{}{
|
||||
"publicKey": n.publicKey.String(),
|
||||
"pingResults": pingResults,
|
||||
"newtVersion": n.config.Version,
|
||||
"chainId": chainId,
|
||||
"publicKey": n.publicKey.String(),
|
||||
"pingResults": pingResults,
|
||||
"newtVersion": n.config.Version,
|
||||
"chainId": chainId,
|
||||
"localEndpoints": n.localEndpoints(),
|
||||
}, 2*time.Second)
|
||||
|
||||
return
|
||||
@@ -267,10 +268,11 @@ func (n *Newt) registerHandlers(ctx context.Context) {
|
||||
chainId := generateChainId()
|
||||
n.pendingRegisterChainId = chainId
|
||||
n.stopFunc = n.client.SendMessageInterval(topicWGRegister, map[string]interface{}{
|
||||
"publicKey": n.publicKey.String(),
|
||||
"pingResults": pingResults,
|
||||
"newtVersion": n.config.Version,
|
||||
"chainId": chainId,
|
||||
"publicKey": n.publicKey.String(),
|
||||
"pingResults": pingResults,
|
||||
"newtVersion": n.config.Version,
|
||||
"chainId": chainId,
|
||||
"localEndpoints": n.localEndpoints(),
|
||||
}, 2*time.Second)
|
||||
|
||||
logger.Debug("Sent exit node ping results to cloud for selection: pingResults=%+v", pingResults)
|
||||
@@ -1016,6 +1018,7 @@ func (n *Newt) registerHandlers(ctx context.Context) {
|
||||
"newtVersion": n.config.Version,
|
||||
"backwardsCompatible": true,
|
||||
"chainId": bcChainId,
|
||||
"localEndpoints": n.localEndpoints(),
|
||||
}); err != nil {
|
||||
logger.Error("Failed to send registration message: %v", err)
|
||||
return err
|
||||
|
||||
Reference in New Issue
Block a user