diff --git a/internal/netInterface.go b/internal/netInterface.go new file mode 100644 index 0000000..5e6e0a3 --- /dev/null +++ b/internal/netInterface.go @@ -0,0 +1,74 @@ +package internal + +import ( + "fmt" + "net" + "strings" +) + +type InterfaceInfo struct { + Name string + IP net.IP +} + +// AvailableInterfaces returns a list of active network interfaces +// with their IPv4 addresses that can connect to the outside world +func AvailableInterfaces(interfaces []net.Interface) ([]InterfaceInfo, error) { + if len(interfaces) == 0 { + return nil, fmt.Errorf("no interfaces provided") + } + var activeInterfaces []InterfaceInfo + for _, iface := range interfaces { + if iface.Flags&net.FlagLoopback != 0 || + iface.Flags&net.FlagUp == 0 || + isVirtualInterface(iface.Name) { + continue + } + + addrs, err := iface.Addrs() + if err != nil { + fmt.Printf( + "Warning: Error fetching addresses for interface %s: %v\n", + iface.Name, + err, + ) + continue + } + + var ipV4 net.IP + + for _, addr := range addrs { + parsedIP, _, err := net.ParseCIDR(addr.String()) + if err != nil { + continue + } + + if parsedIP.To4() != nil && !parsedIP.IsLoopback() { + ipV4 = parsedIP + break + } + } + + activeInterfaces = append(activeInterfaces, InterfaceInfo{ + Name: iface.Name, + IP: ipV4, + }) + } + + return activeInterfaces, nil +} + +func isVirtualInterface(name string) bool { + virtualPrefixes := []string{ + "virbr", "vnet", "docker", "br-", "tun", "tap", + "vmnet", "veth", "vbox", "wg", + "kube", "cali", "flannel", + "vmx", "vlan", "bond", "teredo", + } + for _, prefix := range virtualPrefixes { + if strings.HasPrefix(name, prefix) { + return true + } + } + return false +} diff --git a/internal/netInterface_test.go b/internal/netInterface_test.go new file mode 100644 index 0000000..f904cff --- /dev/null +++ b/internal/netInterface_test.go @@ -0,0 +1,82 @@ +package internal + +import ( + "net" + "testing" +) + +// Tests the [AvailableInterfaces] function +func TestAvailableInterfaces(t *testing.T) { + testCases := []struct { + name string + interfaces []net.Interface + expectedInterfaces int + }{ + { + name: "Mixed Interfaces", + interfaces: []net.Interface{ + { + Name: "lo", + Flags: net.FlagLoopback | net.FlagUp, + }, + { + Name: "eth0", + Flags: 0, + }, + { + Name: "docker0", + Flags: net.FlagUp, + }, + { + Name: "ens192", + Flags: net.FlagUp, + }, + { + Name: "ens224", + Flags: net.FlagUp, + }, + }, + expectedInterfaces: 2, + }, + { + name: "Multiple Active Interfaces", + interfaces: []net.Interface{ + { + Name: "ens192", + Flags: net.FlagUp, + }, + { + Name: "eth0", + Flags: net.FlagUp, + }, + }, + expectedInterfaces: 2, + }, + { + name: "No Interfaces", + interfaces: []net.Interface{}, + expectedInterfaces: 0, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + interfaces, err := AvailableInterfaces(tc.interfaces) + if err != nil { + if tc.expectedInterfaces == 0 && len(tc.interfaces) == 0 { + return + } + t.Fatalf("Unexpected error: %v", err) + } + + if len(interfaces) != tc.expectedInterfaces { + t.Errorf( + "Expected %d interfaces, got %d", + tc.expectedInterfaces, + len(interfaces), + ) + } + + }) + } +}