Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 25 additions & 13 deletions platform/net/mac_address_detector.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
gonet "net"
"path"
"slices"
"strings"

bosherr "github.com/cloudfoundry/bosh-utils/errors"
Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}
60 changes: 57 additions & 3 deletions platform/net/mac_address_detector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand All @@ -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<ifindex> 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"))
})
Comment thread
coderabbitai[bot] marked this conversation as resolved.

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() {
Expand Down
Loading