@@ -21,6 +21,7 @@ import (
2121 "net"
2222 "os"
2323 "strings"
24+ "sync"
2425
2526 "github.com/coreos/go-iptables/iptables"
2627 "github.com/networkplumbing/go-nft/nft"
@@ -407,6 +408,29 @@ func (tc testCase) expectedCIDRs() ([]*net.IPNet, []*net.IPNet) {
407408 return cidrsV4 , cidrsV6
408409}
409410
411+ // Attaches a veth port with an optional fixed MAC address to a bridge in the
412+ // current network namespace. To be cleaned up via the returned function.
413+ func attachVethPort (br netlink.Link , mac string ) (func () error , error ) {
414+ linkAttrs := netlink .NewLinkAttrs ()
415+ linkAttrs .Name = "testport0"
416+ if mac != "" {
417+ if hwAddr , err := net .ParseMAC (mac ); err != nil {
418+ return nil , err
419+ } else {
420+ linkAttrs .HardwareAddr = hwAddr
421+ }
422+ }
423+ veth := & netlink.Veth {LinkAttrs : linkAttrs , PeerName : "testport1" }
424+ if err := netlink .LinkAdd (veth ); err != nil {
425+ return nil , err
426+ }
427+ if err := netlink .LinkSetMaster (veth , br ); err != nil {
428+ return nil , err
429+ }
430+
431+ return sync .OnceValue (func () error { return netlink .LinkDel (veth ) }), nil
432+ }
433+
410434// delBridgeAddrs() deletes addresses from the bridge
411435func delBridgeAddrs (testNS ns.NetNS ) {
412436 err := testNS .Do (func (ns.NetNS ) error {
@@ -2377,6 +2401,48 @@ var _ = Describe("bridge Operations", func() {
23772401 })
23782402 Expect (err ).NotTo (HaveOccurred ())
23792403 })
2404+
2405+ It (fmt .Sprintf ("[%s] (%d) keeps a stable MAC even if the gateway address already exists" , ver , i ), func () {
2406+ err := originalNS .Do (func (ns.NetNS ) error {
2407+ defer GinkgoRecover ()
2408+
2409+ tc .cniVersion = ver
2410+ br , _ , err := setupBridge (tc .netConf ())
2411+ Expect (err ).NotTo (HaveOccurred ())
2412+ link , err := netlinksafe .LinkByName (BRNAME )
2413+ Expect (err ).NotTo (HaveOccurred ())
2414+ originalMAC := link .Attrs ().HardwareAddr
2415+
2416+ // Pre-add the gateway address the plugin is going to
2417+ // configure, as left behind by an ADD that failed midway.
2418+ _ , subnet , err := net .ParseCIDR (tc .subnet )
2419+ Expect (err ).NotTo (HaveOccurred ())
2420+ gwIP := calcGatewayIP (subnet )
2421+ err = netlink .AddrAdd (br , & netlink.Addr {
2422+ IPNet : & net.IPNet {IP : gwIP , Mask : subnet .Mask },
2423+ })
2424+ Expect (err ).NotTo (HaveOccurred ())
2425+
2426+ cmdAddDelTest (originalNS , targetNS , tc , dataDir )
2427+
2428+ // The MAC must have been pinned nevertheless: it neither
2429+ // got zeroed when the container veth was removed, nor
2430+ // does it change with port churn.
2431+ link , err = netlinksafe .LinkByName (BRNAME )
2432+ Expect (err ).NotTo (HaveOccurred ())
2433+ Expect (link .Attrs ().HardwareAddr ).To (Equal (originalMAC ))
2434+
2435+ cleanupVethPort , err := attachVethPort (br , "02:00:00:00:00:01" )
2436+ Expect (err ).NotTo (HaveOccurred ())
2437+ defer func () { Expect (cleanupVethPort ()).To (Succeed ()) }()
2438+
2439+ link , err = netlinksafe .LinkByName (BRNAME )
2440+ Expect (err ).NotTo (HaveOccurred ())
2441+ Expect (link .Attrs ().HardwareAddr ).To (Equal (originalMAC ))
2442+ return nil
2443+ })
2444+ Expect (err ).NotTo (HaveOccurred ())
2445+ })
23802446 }
23812447
23822448 It (fmt .Sprintf ("[%s] uses an explicit MAC addresses for the container iface (from CNI_ARGS)" , ver ), func () {
0 commit comments