diff --git a/platform/net/mac_address_detector.go b/platform/net/mac_address_detector.go index 5218441ce..d7c0dc515 100644 --- a/platform/net/mac_address_detector.go +++ b/platform/net/mac_address_detector.go @@ -4,6 +4,7 @@ import ( "encoding/json" gonet "net" "path" + "slices" "strings" bosherr "github.com/cloudfoundry/bosh-utils/errors" @@ -18,9 +19,10 @@ type MACAddressDetector interface { } const ( - ifaliasPrefix = "bosh-interface" - sriovVFIfalias = "sriov-vf" - macAddressDetectorLogTag = "MacAddressDetector" + ifaliasPrefix = "bosh-interface" + azureUnmanagedSRIOVUdevTag = "E:AZURE_UNMANAGED_SRIOV=1" + udevDataDir = "/run/udev/data" + macAddressDetectorLogTag = "MacAddressDetector" ) type linuxMacAddressDetector struct { @@ -75,16 +77,10 @@ func (d linuxMacAddressDetector) DetectMacAddresses() (map[string]string, error) hasBoshPrefix = strings.HasPrefix(ifalias, ifaliasPrefix) } - // SR-IOV VF interfaces share the same MAC address as synthetic interfaces and should be ignored - // They have an ifalias with the content "sriov-vf" - isSRIOVVF := false - if err == nil { - isSRIOVVF = strings.Contains(ifalias, sriovVFIfalias) - if isSRIOVVF { - interfaceName := path.Base(filePath) - d.logger.Debug(macAddressDetectorLogTag, "Ignoring SR-IOV VF interface: %s (ifalias: %s)", interfaceName, strings.TrimSpace(ifalias)) - continue - } + if d.isAzureUnmanagedSRIOV(filePath) { + interfaceName := path.Base(filePath) + d.logger.Debug(macAddressDetectorLogTag, "Ignoring SR-IOV VF interface: %s (AZURE_UNMANAGED_SRIOV=1)", interfaceName) + continue } if isPhysicalDevice || hasBoshPrefix { @@ -143,3 +139,19 @@ func adapterVisible(netAdapters []netAdapter, macAddress string, adapterName str } return false } + +// isAzureUnmanagedSRIOV returns true when the udev database tags the interface +// with AZURE_UNMANAGED_SRIOV=1. The azure-vm-utils package applies this property +// to SR-IOV VF devices transparently bonded under an hv_netvsc synthetic +// interface, signaling that the IP layer should not configure them. +func (d linuxMacAddressDetector) isAzureUnmanagedSRIOV(sysClassNetPath string) bool { + ifindex, err := d.fs.ReadFileString(path.Join(sysClassNetPath, "ifindex")) + if err != nil { + return false + } + contents, err := d.fs.ReadFileString(path.Join(udevDataDir, "n"+strings.TrimSpace(ifindex))) + if err != nil { + return false + } + return slices.Contains(strings.Split(contents, "\n"), azureUnmanagedSRIOVUdevTag) +} diff --git a/platform/net/mac_address_detector_test.go b/platform/net/mac_address_detector_test.go index be32b0605..660213a22 100644 --- a/platform/net/mac_address_detector_test.go +++ b/platform/net/mac_address_detector_test.go @@ -105,15 +105,25 @@ var _ = Describe("MacAddressDetector", func() { }) }) - Context("when there are SR-IOV VF interfaces", func() { + Context("when there are SR-IOV VF interfaces marked AZURE_UNMANAGED_SRIOV=1", func() { + writeUdevProperties := func(iface, ifindex, body string) { + err := fs.WriteFileString(fmt.Sprintf("/sys/class/net/%s/ifindex", iface), ifindex+"\n") + Expect(err).NotTo(HaveOccurred()) + err = fs.WriteFileString(fmt.Sprintf("/run/udev/data/n%s", ifindex), body) + Expect(err).NotTo(HaveOccurred()) + } + It("should ignore VF interfaces", func() { stubInterfacesWithVirtual(map[string]string{ "aa:bb": "eth0", "cc:dd": "eth2", }, nil, nil) - sriovVF1Path := writeNetworkDevice("eth1", "aa:bb", true, "sriov-vf") - sriovVF2Path := writeNetworkDevice("eth3", "ee:ff", true, "sriov-vf") + sriovVF1Path := writeNetworkDevice("eth1", "aa:bb", true, "") + writeUdevProperties("eth1", "3", "I:1\nE:AZURE_UNMANAGED_SRIOV=1\nE:ID_NET_DRIVER=mlx5_core\n") + + sriovVF2Path := writeNetworkDevice("eth3", "ee:ff", true, "") + writeUdevProperties("eth3", "5", "E:AZURE_UNMANAGED_SRIOV=1\n") existingPaths, err := fs.Glob("/sys/class/net/*") Expect(err).NotTo(HaveOccurred()) @@ -129,6 +139,50 @@ var _ = Describe("MacAddressDetector", func() { "cc:dd": "eth2", })) }) + + It("includes the interface when the ifindex file is missing", func() { + stubInterfacesWithVirtual(map[string]string{ + "aa:bb": "eth0", + }, nil, nil) + + interfacesByMacAddress, err := macAddressDetector.DetectMacAddresses() + Expect(err).ToNot(HaveOccurred()) + Expect(interfacesByMacAddress).To(HaveKeyWithValue("aa:bb", "eth0")) + }) + + It("includes the interface when the udev data file is missing", func() { + stubInterfacesWithVirtual(map[string]string{ + "aa:bb": "eth0", + }, nil, nil) + // ifindex present but no /run/udev/data/n file + err := fs.WriteFileString("/sys/class/net/eth0/ifindex", "2\n") + Expect(err).NotTo(HaveOccurred()) + + interfacesByMacAddress, err := macAddressDetector.DetectMacAddresses() + Expect(err).ToNot(HaveOccurred()) + Expect(interfacesByMacAddress).To(HaveKeyWithValue("aa:bb", "eth0")) + }) + + It("does not match a substring or differently-valued property", func() { + stubInterfacesWithVirtual(map[string]string{ + "aa:bb": "eth0", + }, nil, nil) + + vfPath := writeNetworkDevice("eth1", "cc:dd", true, "") + // AZURE_UNMANAGED_SRIOV=0 and a similarly-named property must not trigger the skip + writeUdevProperties("eth1", "3", "E:AZURE_UNMANAGED_SRIOV=0\nE:SOME_AZURE_UNMANAGED_SRIOV=1\n") + + existing, err := fs.Glob("/sys/class/net/*") + Expect(err).NotTo(HaveOccurred()) + fs.SetGlob("/sys/class/net/*", append(existing, vfPath)) + + interfacesByMacAddress, err := macAddressDetector.DetectMacAddresses() + Expect(err).ToNot(HaveOccurred()) + Expect(interfacesByMacAddress).To(Equal(map[string]string{ + "aa:bb": "eth0", + "cc:dd": "eth1", + })) + }) }) It("returns errors from glob /sys/class/net/", func() {