diff --git a/cmd/cli/net_darwin.go b/cmd/cli/net_darwin.go index fdee1f8..b58a0bf 100644 --- a/cmd/cli/net_darwin.go +++ b/cmd/cli/net_darwin.go @@ -43,22 +43,32 @@ func networkServiceName(ifaceName string, r io.Reader) string { return "" } -// validInterface reports whether the *net.Interface is a valid one, which includes en0 -> en10. -// - en0: physical wireless -// - en1: Thunderbolt 1 -// - en2: Thunderbolt 2 -// - en3: Thunderbolt 3 -// - en4: Thunderbolt 4 -// - ... -// - en10: iPad -// -// For full list, see: https://unix.stackexchange.com/questions/603506/what-are-these-ifconfig-interfaces-on-macos -func validInterface(iface *net.Interface) bool { - switch iface.Name { - case "en0", "en1", "en2", "en3", "en4", "en5", - "en6", "en7", "en8", "en9", "en10": - return true - default: - return false - } +// validInterface reports whether the *net.Interface is a valid one. +func validInterface(iface *net.Interface, validIfacesMap map[string]struct{}) bool { + _, ok := validIfacesMap[iface.Name] + return ok +} + +func validInterfacesMap() 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 } diff --git a/cmd/cli/net_darwin_test.go b/cmd/cli/net_darwin_test.go index 443a9d1..9ef1906 100644 --- a/cmd/cli/net_darwin_test.go +++ b/cmd/cli/net_darwin_test.go @@ -1,6 +1,7 @@ package cli import ( + "maps" "strings" "testing" @@ -57,3 +58,47 @@ func Test_networkServiceName(t *testing.T) { }) } } + +const listallhardwareportsOutput = ` +Hardware Port: Ethernet Adapter (en6) +Device: en6 +Ethernet Address: 3a:3e:fc:1e:ab:41 + +Hardware Port: Ethernet Adapter (en7) +Device: en7 +Ethernet Address: 3a:3e:fc:1e:ab:42 + +Hardware Port: Thunderbolt Bridge +Device: bridge0 +Ethernet Address: 36:21:bb:3a:7a:40 + +Hardware Port: Wi-Fi +Device: en0 +Ethernet Address: a0:78:17:68:56:3f + +Hardware Port: Thunderbolt 1 +Device: en1 +Ethernet Address: 36:21:bb:3a:7a:40 + +Hardware Port: Thunderbolt 2 +Device: en2 +Ethernet Address: 36:21:bb:3a:7a:44 + +VLAN Configurations +=================== +` + +func Test_parseListAllHardwarePorts(t *testing.T) { + expected := map[string]struct{}{ + "en0": {}, + "en1": {}, + "en2": {}, + "en6": {}, + "en7": {}, + "bridge0": {}, + } + m := parseListAllHardwarePorts(strings.NewReader(listallhardwareportsOutput)) + if !maps.Equal(m, expected) { + t.Errorf("unexpected output, want: %v, got: %v", expected, m) + } +} diff --git a/cmd/cli/net_others.go b/cmd/cli/net_others.go index ebe7ba0..5a66e82 100644 --- a/cmd/cli/net_others.go +++ b/cmd/cli/net_others.go @@ -6,4 +6,6 @@ import "net" func patchNetIfaceName(iface *net.Interface) error { return nil } -func validInterface(iface *net.Interface) bool { return true } +func validInterface(iface *net.Interface, validIfacesMap map[string]struct{}) bool { return true } + +func validInterfacesMap() map[string]struct{} { return nil } diff --git a/cmd/cli/net_windows.go b/cmd/cli/net_windows.go index c75ee32..8ec5a5f 100644 --- a/cmd/cli/net_windows.go +++ b/cmd/cli/net_windows.go @@ -10,7 +10,7 @@ func patchNetIfaceName(iface *net.Interface) error { // validInterface reports whether the *net.Interface is a valid one. // On Windows, only physical interfaces are considered valid. -func validInterface(iface *net.Interface) bool { +func validInterface(iface *net.Interface, validIfacesMap map[string]struct{}) bool { if iface == nil { return false } @@ -19,3 +19,5 @@ func validInterface(iface *net.Interface) bool { } return false } + +func validInterfacesMap() map[string]struct{} { return nil } diff --git a/cmd/cli/prog.go b/cmd/cli/prog.go index 37647e4..54ade0d 100644 --- a/cmd/cli/prog.go +++ b/cmd/cli/prog.go @@ -868,13 +868,14 @@ func canBeLocalUpstream(addr string) bool { // the interface that matches excludeIfaceName. The context is used to clarify the // log message when error happens. func withEachPhysicalInterfaces(excludeIfaceName, context string, f func(i *net.Interface) error) { + validIfacesMap := validInterfacesMap() interfaces.ForeachInterface(func(i interfaces.Interface, prefixes []netip.Prefix) { // Skip loopback/virtual interface. if i.IsLoopback() || len(i.HardwareAddr) == 0 { return } // Skip invalid interface. - if !validInterface(i.Interface) { + if !validInterface(i.Interface, validIfacesMap) { return } netIface := i.Interface