mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-07-04 01:07:49 +02:00
refactor: consolidate network interface detection logic
Move platform-specific network interface detection from cmd/cli/ to root package as ValidInterfaces function. This eliminates code duplication and provides a consistent interface for determining valid physical network interfaces across all platforms. - Remove duplicate validInterfacesMap functions from platform-specific files - Add context parameter to virtualInterfaces for proper logging - Update all callers to use ctrld.ValidInterfaces instead of local functions - Improve error handling in virtual interface detection on Linux
This commit is contained in:
committed by
Cuong Manh Le
parent
f24059885f
commit
650e47a504
@@ -1435,7 +1435,7 @@ func (p *prog) monitorNetworkChanges(ctx context.Context) error {
|
|||||||
|
|
||||||
mon.RegisterChangeCallback(func(delta *netmon.ChangeDelta) {
|
mon.RegisterChangeCallback(func(delta *netmon.ChangeDelta) {
|
||||||
// Get map of valid interfaces
|
// Get map of valid interfaces
|
||||||
validIfaces := validInterfacesMap(ctrld.LoggerCtx(ctx, p.logger.Load()))
|
validIfaces := ctrld.ValidInterfaces(ctrld.LoggerCtx(ctx, p.logger.Load()))
|
||||||
|
|
||||||
isMajorChange := mon.IsMajorChangeFrom(delta.Old, delta.New)
|
isMajorChange := mon.IsMajorChangeFrom(delta.Old, delta.New)
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package cli
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -50,28 +49,3 @@ func validInterface(iface *net.Interface, validIfacesMap map[string]struct{}) bo
|
|||||||
_, ok := validIfacesMap[iface.Name]
|
_, ok := validIfacesMap[iface.Name]
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// validInterfacesMap returns a set of all valid hardware ports.
|
|
||||||
func validInterfacesMap(ctx context.Context) map[string]struct{} {
|
|
||||||
b, err := exec.Command("networksetup", "-listallhardwareports").Output()
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return parseListAllHardwarePorts(bytes.NewReader(b))
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseListAllHardwarePorts parses output of "networksetup -listallhardwareports"
|
|
||||||
// and returns map presents all hardware ports.
|
|
||||||
func parseListAllHardwarePorts(r io.Reader) map[string]struct{} {
|
|
||||||
m := make(map[string]struct{})
|
|
||||||
scanner := bufio.NewScanner(r)
|
|
||||||
for scanner.Scan() {
|
|
||||||
line := scanner.Text()
|
|
||||||
after, ok := strings.CutPrefix(line, "Device: ")
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
m[after] = struct{}{}
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,15 +1,7 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"tailscale.com/net/netmon"
|
|
||||||
|
|
||||||
"github.com/Control-D-Inc/ctrld"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// patchNetIfaceName patches network interface names on Linux
|
// patchNetIfaceName patches network interface names on Linux
|
||||||
@@ -23,45 +15,3 @@ func validInterface(iface *net.Interface, validIfacesMap map[string]struct{}) bo
|
|||||||
_, ok := validIfacesMap[iface.Name]
|
_, ok := validIfacesMap[iface.Name]
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// validInterfacesMap returns a set containing non virtual interfaces.
|
|
||||||
// This filters out virtual interfaces to ensure DNS is only configured on physical interfaces
|
|
||||||
func validInterfacesMap(ctx context.Context) map[string]struct{} {
|
|
||||||
m := make(map[string]struct{})
|
|
||||||
vis := virtualInterfaces(ctx)
|
|
||||||
netmon.ForeachInterface(func(i netmon.Interface, prefixes []netip.Prefix) {
|
|
||||||
if _, existed := vis[i.Name]; existed {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
m[i.Name] = struct{}{}
|
|
||||||
})
|
|
||||||
// Fallback to the default route interface if found nothing.
|
|
||||||
// This ensures we always have at least one interface to configure
|
|
||||||
if len(m) == 0 {
|
|
||||||
defaultRoute, err := netmon.DefaultRoute()
|
|
||||||
if err != nil {
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
m[defaultRoute.InterfaceName] = struct{}{}
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// virtualInterfaces returns a map of virtual interfaces on the current machine.
|
|
||||||
// This reads from /sys/devices/virtual/net to identify virtual network interfaces
|
|
||||||
// Virtual interfaces should not have DNS configured as they don't represent physical network connections
|
|
||||||
func virtualInterfaces(ctx context.Context) map[string]struct{} {
|
|
||||||
logger := ctrld.LoggerFromCtx(ctx)
|
|
||||||
s := make(map[string]struct{})
|
|
||||||
entries, err := os.ReadDir("/sys/devices/virtual/net")
|
|
||||||
if err != nil {
|
|
||||||
logger.Error().Err(err).Msg("Failed to read /sys/devices/virtual/net")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for _, entry := range entries {
|
|
||||||
if entry.IsDir() {
|
|
||||||
s[strings.TrimSpace(entry.Name())] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,10 +3,7 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"tailscale.com/net/netmon"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// patchNetIfaceName patches network interface names on non-Linux/Darwin platforms
|
// patchNetIfaceName patches network interface names on non-Linux/Darwin platforms
|
||||||
@@ -14,12 +11,3 @@ func patchNetIfaceName(iface *net.Interface) (bool, error) { return true, nil }
|
|||||||
|
|
||||||
// validInterface checks if an interface is valid on non-Linux/Darwin platforms
|
// validInterface checks if an interface is valid on non-Linux/Darwin platforms
|
||||||
func validInterface(iface *net.Interface, validIfacesMap map[string]struct{}) bool { return true }
|
func validInterface(iface *net.Interface, validIfacesMap map[string]struct{}) bool { return true }
|
||||||
|
|
||||||
// validInterfacesMap returns a set containing only default route interfaces.
|
|
||||||
func validInterfacesMap(ctx context.Context) map[string]struct{} {
|
|
||||||
defaultRoute, err := netmon.DefaultRoute()
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return map[string]struct{}{defaultRoute.InterfaceName: {}}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/Control-D-Inc/ctrld"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func patchNetIfaceName(iface *net.Interface) (bool, error) {
|
func patchNetIfaceName(iface *net.Interface) (bool, error) {
|
||||||
@@ -17,12 +14,3 @@ func validInterface(iface *net.Interface, validIfacesMap map[string]struct{}) bo
|
|||||||
_, ok := validIfacesMap[iface.Name]
|
_, ok := validIfacesMap[iface.Name]
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// validInterfacesMap returns a set of all physical interfaces.
|
|
||||||
func validInterfacesMap(ctx context.Context) map[string]struct{} {
|
|
||||||
m := make(map[string]struct{})
|
|
||||||
for ifaceName := range ctrld.ValidInterfaces(ctx) {
|
|
||||||
m[ifaceName] = struct{}{}
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|||||||
+1
-1
@@ -1291,7 +1291,7 @@ func canBeLocalUpstream(addr string) bool {
|
|||||||
// the interface that matches excludeIfaceName. The context is used to clarify the
|
// the interface that matches excludeIfaceName. The context is used to clarify the
|
||||||
// log message when error happens.
|
// log message when error happens.
|
||||||
func withEachPhysicalInterfaces(excludeIfaceName, contextStr string, f func(i *net.Interface) error) {
|
func withEachPhysicalInterfaces(excludeIfaceName, contextStr string, f func(i *net.Interface) error) {
|
||||||
validIfacesMap := validInterfacesMap(ctrld.LoggerCtx(context.Background(), mainLog.Load()))
|
validIfacesMap := ctrld.ValidInterfaces(ctrld.LoggerCtx(context.Background(), mainLog.Load()))
|
||||||
netmon.ForeachInterface(func(i netmon.Interface, prefixes []netip.Prefix) {
|
netmon.ForeachInterface(func(i netmon.Interface, prefixes []netip.Prefix) {
|
||||||
// Skip loopback/virtual/down interface.
|
// Skip loopback/virtual/down interface.
|
||||||
if i.IsLoopback() || len(i.HardwareAddr) == 0 {
|
if i.IsLoopback() || len(i.HardwareAddr) == 0 {
|
||||||
|
|||||||
+21
-26
@@ -24,7 +24,7 @@ func dnsFns() []dnsFn {
|
|||||||
return []dnsFn{dnsFromResolvConf, dns4, dns6, dnsFromSystemdResolver}
|
return []dnsFn{dnsFromResolvConf, dns4, dns6, dnsFromSystemdResolver}
|
||||||
}
|
}
|
||||||
|
|
||||||
func dns4(_ context.Context) []string {
|
func dns4(ctx context.Context) []string {
|
||||||
f, err := os.Open(v4RouteFile)
|
f, err := os.Open(v4RouteFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -33,7 +33,7 @@ func dns4(_ context.Context) []string {
|
|||||||
|
|
||||||
var dns []string
|
var dns []string
|
||||||
seen := make(map[string]bool)
|
seen := make(map[string]bool)
|
||||||
vis := virtualInterfaces()
|
vis := virtualInterfaces(ctx)
|
||||||
s := bufio.NewScanner(f)
|
s := bufio.NewScanner(f)
|
||||||
first := true
|
first := true
|
||||||
for s.Scan() {
|
for s.Scan() {
|
||||||
@@ -46,7 +46,7 @@ func dns4(_ context.Context) []string {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Skip virtual interfaces.
|
// Skip virtual interfaces.
|
||||||
if vis.contains(string(bytes.TrimSpace(fields[0]))) {
|
if _, ok := vis[string(bytes.TrimSpace(fields[0]))]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
gw := make([]byte, net.IPv4len)
|
gw := make([]byte, net.IPv4len)
|
||||||
@@ -64,7 +64,7 @@ func dns4(_ context.Context) []string {
|
|||||||
return dns
|
return dns
|
||||||
}
|
}
|
||||||
|
|
||||||
func dns6(_ context.Context) []string {
|
func dns6(ctx context.Context) []string {
|
||||||
f, err := os.Open(v6RouteFile)
|
f, err := os.Open(v6RouteFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -72,7 +72,7 @@ func dns6(_ context.Context) []string {
|
|||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
var dns []string
|
var dns []string
|
||||||
vis := virtualInterfaces()
|
vis := virtualInterfaces(ctx)
|
||||||
s := bufio.NewScanner(f)
|
s := bufio.NewScanner(f)
|
||||||
for s.Scan() {
|
for s.Scan() {
|
||||||
fields := bytes.Fields(s.Bytes())
|
fields := bytes.Fields(s.Bytes())
|
||||||
@@ -80,7 +80,7 @@ func dns6(_ context.Context) []string {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Skip virtual interfaces.
|
// Skip virtual interfaces.
|
||||||
if vis.contains(string(bytes.TrimSpace(fields[len(fields)-1]))) {
|
if _, ok := vis[string(bytes.TrimSpace(fields[len(fields)-1]))]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,34 +110,29 @@ func dnsFromSystemdResolver(_ context.Context) []string {
|
|||||||
return ns
|
return ns
|
||||||
}
|
}
|
||||||
|
|
||||||
type set map[string]struct{}
|
// virtualInterfaces returns a map of virtual interfaces on the current machine.
|
||||||
|
// This reads from /sys/devices/virtual/net to identify virtual network interfaces
|
||||||
func (s *set) add(e string) {
|
// Virtual interfaces should not have DNS configured as they don't represent physical network connections
|
||||||
(*s)[e] = struct{}{}
|
func virtualInterfaces(ctx context.Context) map[string]struct{} {
|
||||||
}
|
logger := LoggerFromCtx(ctx)
|
||||||
|
s := make(map[string]struct{})
|
||||||
func (s *set) contains(e string) bool {
|
entries, err := os.ReadDir("/sys/devices/virtual/net")
|
||||||
_, ok := (*s)[e]
|
if err != nil {
|
||||||
return ok
|
logger.Error().Err(err).Msg("Failed to read /sys/devices/virtual/net")
|
||||||
}
|
return nil
|
||||||
|
}
|
||||||
// virtualInterfaces returns a set of virtual interfaces on current machine.
|
|
||||||
func virtualInterfaces() set {
|
|
||||||
s := make(set)
|
|
||||||
entries, _ := os.ReadDir("/sys/devices/virtual/net")
|
|
||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
if entry.IsDir() {
|
if entry.IsDir() {
|
||||||
s.add(strings.TrimSpace(entry.Name()))
|
s[strings.TrimSpace(entry.Name())] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// validInterfacesMap returns a set containing non virtual interfaces.
|
// ValidInterfaces returns a set containing non virtual interfaces.
|
||||||
// TODO: deduplicated with cmd/cli/net_linux.go in v2.
|
func ValidInterfaces(ctx context.Context) map[string]struct{} {
|
||||||
func validInterfaces() set {
|
|
||||||
m := make(map[string]struct{})
|
m := make(map[string]struct{})
|
||||||
vis := virtualInterfaces()
|
vis := virtualInterfaces(ctx)
|
||||||
netmon.ForeachInterface(func(i netmon.Interface, prefixes []netip.Prefix) {
|
netmon.ForeachInterface(func(i netmon.Interface, prefixes []netip.Prefix) {
|
||||||
if _, existed := vis[i.Name]; existed {
|
if _, existed := vis[i.Name]; existed {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
package ctrld
|
package ctrld
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_virtualInterfaces(t *testing.T) {
|
func Test_virtualInterfaces(t *testing.T) {
|
||||||
vis := virtualInterfaces()
|
vis := virtualInterfaces(context.Background())
|
||||||
t.Log(vis)
|
t.Log(vis)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -444,7 +444,3 @@ func ValidInterfaces(ctx context.Context) map[string]struct{} {
|
|||||||
}
|
}
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
func validInterfaces() map[string]struct{} {
|
|
||||||
return ValidInterfaces(context.Background())
|
|
||||||
}
|
|
||||||
|
|||||||
+3
-3
@@ -3,14 +3,14 @@ package ctrld
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// validInterfaces returns a set of all valid hardware ports.
|
// ValidInterfaces returns a set of all valid hardware ports.
|
||||||
// TODO: deduplicated with cmd/cli/net_darwin.go in v2.
|
func ValidInterfaces(_ context.Context) map[string]struct{} {
|
||||||
func validInterfaces() map[string]struct{} {
|
|
||||||
b, err := exec.Command("networksetup", "-listallhardwareports").Output()
|
b, err := exec.Command("networksetup", "-listallhardwareports").Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+7
-4
@@ -2,11 +2,14 @@
|
|||||||
|
|
||||||
package ctrld
|
package ctrld
|
||||||
|
|
||||||
import "tailscale.com/net/netmon"
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
// validInterfaces returns a set containing only default route interfaces.
|
"tailscale.com/net/netmon"
|
||||||
// TODO: deuplicated with cmd/cli/net_others.go in v2.
|
)
|
||||||
func validInterfaces() map[string]struct{} {
|
|
||||||
|
// ValidInterfaces returns a set containing only default route interfaces.
|
||||||
|
func ValidInterfaces(_ context.Context) map[string]struct{} {
|
||||||
defaultRoute, err := netmon.DefaultRoute()
|
defaultRoute, err := netmon.DefaultRoute()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+1
-1
@@ -711,7 +711,7 @@ func newResolverWithNameserver(nameservers []string) *osResolver {
|
|||||||
|
|
||||||
// Rfc1918Addresses returns the list of local physical interfaces private IP addresses
|
// Rfc1918Addresses returns the list of local physical interfaces private IP addresses
|
||||||
func Rfc1918Addresses() []string {
|
func Rfc1918Addresses() []string {
|
||||||
vis := validInterfaces()
|
vis := ValidInterfaces(context.Background())
|
||||||
var res []string
|
var res []string
|
||||||
netmon.ForeachInterface(func(i netmon.Interface, prefixes []netip.Prefix) {
|
netmon.ForeachInterface(func(i netmon.Interface, prefixes []netip.Prefix) {
|
||||||
// Skip virtual interfaces.
|
// Skip virtual interfaces.
|
||||||
|
|||||||
Reference in New Issue
Block a user