package main import ( "errors" "fmt" "io" "os" "path/filepath" "strconv" "strings" ) const ( PathProcPCI = iota PathProcUSB ) var ( ErrInvalidPCIFormat = errors.New("Invalid PCI address format") ErrInvalidUSBFormat = errors.New("Invalid USB address format") ErrParseMac = errors.New("Failed to parse MAC address") ) var SYSFS = "/sys" // Sourced from include/uapi/linux/netdevice.h // as of v6.8 const ( NET_NAME_UNKNOWN = 0 /* unknown origin (not exposed to userspace) */ NET_NAME_ENUM = 1 /* enumerated by kernel */ NET_NAME_PREDICTABLE = 2 /* predictably named by the kernel */ NET_NAME_USER = 3 /* provided by user-space */ NET_NAME_RENAMED = 4 /* renamed by user-space */ ) const ( ClassEthernet = iota ClassWireless ) type iface struct { class int orig string pci, usb, mac string netpath string } func (i iface) String() (ret string) { var pfx string ret = i.orig switch i.class { case ClassEthernet: pfx = "en" case ClassWireless: pfx = "wl" } if i.pci != "" { ret = fmt.Sprintf("%s%s%s", pfx, i.pci, i.usb) } else if i.mac != "" { ret = fmt.Sprintf("%s%s", pfx, i.mac) } return } func New(ifname string) (i iface, err error) { i.orig = ifname i.netpath = strings.Join([]string{SYSFS, "class/net", i.orig}, "/") switch ifname[0] { case 'e': i.class = ClassEthernet case 'w': i.class = ClassWireless } return } func (i iface) shouldRun() (should bool, err error) { ifname_assign_type := strings.Join([]string{i.netpath, "name_assign_type"}, "/") should = true var ( file *os.File contents []byte ) if file, err = os.Open(ifname_assign_type); err != nil { err = fmt.Errorf("Failed to open interface rename property: %w", err) return } defer file.Close() if contents, err = io.ReadAll(file); err != nil { err = fmt.Errorf("Failed to read interface rename property: %w", err) return } num, _ := strings.CutSuffix(string(contents), "\n") var res int if res, err = strconv.Atoi(num); err != nil { err = fmt.Errorf("Failed to interpret interface rename property as number: %w", err) return } if res != NET_NAME_ENUM { should = false return } return } func (i iface) Process(phys bool) (nif string, err error) { var ( procmode int ) nif = i.orig if phys { spath := strings.Join([]string{i.netpath, "device"}, "/") var real string if real, err = filepath.EvalSymlinks(spath); err != nil { if errors.Is(err, os.ErrNotExist) { err = fmt.Errorf("Interface %s isn't a hardware device", i.orig) return } err = fmt.Errorf("Failed to resolve symlink to device: %w", err) return } var devpath string var ok bool sysdev := strings.Join([]string{SYSFS, "devices/"}, "/") if devpath, ok = strings.CutPrefix(real, sysdev); !ok { err = fmt.Errorf("Symlink doesn't point to %s\n", sysdev) return } for _, v := range strings.Split(devpath, "/") { if strings.HasPrefix(v, "pci") { procmode = PathProcPCI continue } else if strings.HasPrefix(v, "usb") { procmode = PathProcUSB continue } switch procmode { case PathProcPCI: { if err = i.ProcPCI(v); err != nil { continue } } case PathProcUSB: { if err = i.ProcUSB(v); err != nil { continue } } } } } err = i.ProcMAC() // It can error but as long as we get a identifier along the path it's okay if i.pci == "" && i.mac == "" { return } nif = i.String() return }