44 "context"
55 "net"
66 "net/netip"
7+ "slices"
78 "testing"
89
910 "github.com/docker/docker/api/types/network"
@@ -14,6 +15,7 @@ import (
1415 "github.com/vishvananda/netlink"
1516 "gotest.tools/v3/assert"
1617 is "gotest.tools/v3/assert/cmp"
18+ "gotest.tools/v3/poll"
1719 "gotest.tools/v3/skip"
1820)
1921
@@ -111,6 +113,29 @@ func TestDaemonDefaultBridgeIPAM_Docker0(t *testing.T) {
111113 {Subnet : "fdd1:8161:2d2c::/56" , IPRange : "fdd1:8161:2d2c::/64" , Gateway : "fdd1:8161:2d2c::8888" },
112114 },
113115 },
116+ {
117+ name : "link-local fixed-cidr-v6" ,
118+ daemonArgs : []string {
119+ "--fixed-cidr" , "192.168.176.0/24" ,
120+ "--fixed-cidr-v6" , "fe80::/64" ,
121+ },
122+ expIPAMConfig : []network.IPAMConfig {
123+ {Subnet : "192.168.176.0/24" , IPRange : "192.168.176.0/24" },
124+ {Subnet : "fe80::/64" , IPRange : "fe80::/64" , Gateway : llGwPlaceholder },
125+ },
126+ },
127+ {
128+ name : "nonstandard link-local fixed-cidr-v6" ,
129+ initialBridgeAddrs : []string {"192.168.176.88/20" , "fe80:1234::88/56" },
130+ daemonArgs : []string {
131+ "--fixed-cidr" , "192.168.176.0/24" ,
132+ "--fixed-cidr-v6" , "fe80:1234::/64" ,
133+ },
134+ expIPAMConfig : []network.IPAMConfig {
135+ {Subnet : "192.168.176.0/20" , IPRange : "192.168.176.0/24" , Gateway : "192.168.176.88" },
136+ {Subnet : "fe80:1234::/56" , IPRange : "fe80:1234::/64" , Gateway : "fe80:1234::88" },
137+ },
138+ },
114139 {
115140 name : "fixed-cidr within old bridge subnet with new bip" ,
116141 initialBridgeAddrs : []string {"192.168.176.88/20" , "fdd1:8161:2d2c::/56" },
@@ -233,6 +258,30 @@ func TestDaemonDefaultBridgeIPAM_UserBr(t *testing.T) {
233258 {Subnet : "fdd1:8161:2d2c:10::/60" , IPRange : "fdd1:8161:2d2c:11::/64" , Gateway : "fdd1:8161:2d2c:10::8888" },
234259 },
235260 },
261+ {
262+ name : "link-local fixed-cidr-v6" ,
263+ initialBridgeAddrs : []string {"192.168.176.88/20" },
264+ daemonArgs : []string {
265+ "--fixed-cidr" , "192.168.176.0/24" ,
266+ "--fixed-cidr-v6" , "fe80::/64" ,
267+ },
268+ expIPAMConfig : []network.IPAMConfig {
269+ {Subnet : "192.168.176.0/20" , IPRange : "192.168.176.0/24" , Gateway : "192.168.176.88" },
270+ {Subnet : "fe80::/64" , IPRange : "fe80::/64" , Gateway : llGwPlaceholder },
271+ },
272+ },
273+ {
274+ name : "nonstandard link-local fixed-cidr-v6" ,
275+ initialBridgeAddrs : []string {"192.168.176.88/20" , "fe80:1234::88/56" },
276+ daemonArgs : []string {
277+ "--fixed-cidr" , "192.168.176.0/24" ,
278+ "--fixed-cidr-v6" , "fe80:1234::/64" ,
279+ },
280+ expIPAMConfig : []network.IPAMConfig {
281+ {Subnet : "192.168.176.0/20" , IPRange : "192.168.176.0/24" , Gateway : "192.168.176.88" },
282+ {Subnet : "fe80:1234::/56" , IPRange : "fe80:1234::/64" , Gateway : "fe80:1234::88" },
283+ },
284+ },
236285 {
237286 name : "fixed-cidr bigger than bridge subnet" ,
238287 initialBridgeAddrs : []string {"192.168.176.88/24" },
@@ -310,6 +359,11 @@ func TestDaemonDefaultBridgeIPAM_UserBr(t *testing.T) {
310359 }
311360}
312361
362+ // llGwPlaceholder can be used as a value for "Gateway" in expected IPAM config,
363+ // before comparison with actual results it'll be replaced by the kernel assigned
364+ // link local IPv6 address for the bridge.
365+ const llGwPlaceholder = "ll-gateway-placeholder"
366+
313367type defaultBridgeIPAMTestCase struct {
314368 name string
315369 bridgeName string
@@ -333,7 +387,7 @@ func testDefaultBridgeIPAM(ctx context.Context, t *testing.T, tc defaultBridgeIP
333387 defer cleanup ()
334388
335389 host .Do (t , func () {
336- createBridge (t , tc .bridgeName , tc .initialBridgeAddrs )
390+ llAddr := createBridge (t , tc .bridgeName , tc .initialBridgeAddrs )
337391
338392 var dArgs []string
339393 if ! tc .ipv4Only {
@@ -365,7 +419,13 @@ func testDefaultBridgeIPAM(ctx context.Context, t *testing.T, tc defaultBridgeIP
365419
366420 insp , err := c .NetworkInspect (ctx , network .NetworkBridge , network.InspectOptions {})
367421 assert .NilError (t , err )
368- assert .Check (t , is .DeepEqual (insp .IPAM .Config , tc .expIPAMConfig ))
422+ expIPAMConfig := slices .Clone (tc .expIPAMConfig )
423+ for i := range expIPAMConfig {
424+ if expIPAMConfig [i ].Gateway == llGwPlaceholder {
425+ expIPAMConfig [i ].Gateway = llAddr .String ()
426+ }
427+ }
428+ assert .Check (t , is .DeepEqual (insp .IPAM .Config , expIPAMConfig ))
369429 })
370430 })
371431}
@@ -386,7 +446,10 @@ func newHostInL3Seg(t *testing.T, name, ip4, ip6 string) (networking.Host, func(
386446 return l3 .Hosts [hostname ], func () { l3 .Destroy (t ) }
387447}
388448
389- func createBridge (t * testing.T , ifName string , addrs []string ) {
449+ // createBridge creates a bridge device named ifName, brings it up, waits for
450+ // the kernel to assign a link-local IPv6 address, assigns addrs, and returns
451+ // the kernel-assigned LL address.
452+ func createBridge (t * testing.T , ifName string , addrs []string ) net.IP {
390453 t .Helper ()
391454
392455 // Get a netlink handle in this netns.
@@ -399,14 +462,35 @@ func createBridge(t *testing.T, ifName string, addrs []string) {
399462 Name : ifName ,
400463 },
401464 }
402-
403465 err = nlh .LinkAdd (link )
404466 assert .NilError (t , err )
467+
468+ // Bring the interface up, and wait for the kernel to assign its link-local
469+ // address (to cause maximum confusion - the LL address shouldn't be selected
470+ // as "bip6").
471+ brLink , err := nlh .LinkByName (ifName )
472+ assert .NilError (t , err )
473+ err = nlh .LinkSetUp (brLink )
474+ assert .NilError (t , err )
475+ var llAddr net.IP
476+ poll .WaitOn (t , func (t poll.LogT ) poll.Result {
477+ addrs , err := nlh .AddrList (brLink , netlink .FAMILY_V6 )
478+ if err != nil {
479+ return poll .Error (err )
480+ }
481+ if len (addrs ) == 0 {
482+ return poll .Continue ("no IPv6 addresses" )
483+ }
484+ llAddr = addrs [0 ].IP
485+ return poll .Success ()
486+ })
487+
405488 for _ , addr := range addrs {
406489 ip , ipNet , err := net .ParseCIDR (addr )
407490 assert .NilError (t , err )
408491 ipNet .IP = ip
409492 err = nlh .AddrAdd (link , & netlink.Addr {IPNet : ipNet })
410493 assert .NilError (t , err )
411494 }
495+ return llAddr
412496}
0 commit comments