@@ -2,13 +2,19 @@ package network // import "github.com/docker/docker/integration/network"
22
33import (
44 "bytes"
5+ "context"
56 "fmt"
67 "os/exec"
8+ "slices"
79 "strings"
10+ "syscall"
811 "testing"
12+ "time"
913
1014 containertypes "github.com/docker/docker/api/types/container"
1115 networktypes "github.com/docker/docker/api/types/network"
16+ "github.com/docker/docker/api/types/versions"
17+ "github.com/docker/docker/client"
1218 "github.com/docker/docker/integration/internal/container"
1319 "github.com/docker/docker/integration/internal/network"
1420 "github.com/docker/docker/internal/testutils/networking"
@@ -218,3 +224,172 @@ func TestHostGatewayFromDocker0(t *testing.T) {
218224 assert .Check (t , is .Contains (res .Stdout .String (), "192.168.50.1\t hg" ))
219225 assert .Check (t , is .Contains (res .Stdout .String (), "fddd:6ff4:6e08::1\t hg" ))
220226}
227+
228+ func TestCreateWithPriority (t * testing.T ) {
229+ // This feature should work on Windows, but the test is skipped because:
230+ // 1. Linux-specific tools are used here; 2. 'windows' IPAM driver doesn't
231+ // support static allocations.
232+ skip .If (t , testEnv .DaemonInfo .OSType == "windows" )
233+ skip .If (t , versions .LessThan (testEnv .DaemonAPIVersion (), "1.48" ), "requires API v1.48" )
234+
235+ ctx := setupTest (t )
236+ apiClient := testEnv .APIClient ()
237+
238+ network .CreateNoError (ctx , t , apiClient , "testnet1" ,
239+ network .WithIPv6 (),
240+ network .WithIPAM ("10.100.20.0/24" , "10.100.20.1" ),
241+ network .WithIPAM ("fd54:7a1b:8269::/64" , "fd54:7a1b:8269::1" ))
242+ defer network .RemoveNoError (ctx , t , apiClient , "testnet1" )
243+
244+ network .CreateNoError (ctx , t , apiClient , "testnet2" ,
245+ network .WithIPv6 (),
246+ network .WithIPAM ("10.100.30.0/24" , "10.100.30.1" ),
247+ network .WithIPAM ("fdff:6dfe:37d2::/64" , "fdff:6dfe:37d2::1" ))
248+ defer network .RemoveNoError (ctx , t , apiClient , "testnet2" )
249+
250+ ctrID := container .Run (ctx , t , apiClient ,
251+ container .WithCmd ("sleep" , "infinity" ),
252+ container .WithNetworkMode ("testnet1" ),
253+ container .WithEndpointSettings ("testnet1" , & networktypes.EndpointSettings {GwPriority : 10 }),
254+ container .WithEndpointSettings ("testnet2" , & networktypes.EndpointSettings {GwPriority : 100 }))
255+ defer container .Remove (ctx , t , apiClient , ctrID , containertypes.RemoveOptions {Force : true })
256+
257+ checkCtrRoutes (t , ctx , apiClient , ctrID , syscall .AF_INET , 3 , "default via 10.100.30.1 dev" )
258+ // IPv6 routing table will contain for each interface, one route for the LL
259+ // address, one for the ULA, and one multicast.
260+ checkCtrRoutes (t , ctx , apiClient , ctrID , syscall .AF_INET6 , 7 , "default via fdff:6dfe:37d2::1 dev" )
261+ }
262+
263+ func TestConnectWithPriority (t * testing.T ) {
264+ // This feature should work on Windows, but the test is skipped because:
265+ // 1. Linux-specific tools are used here; 2. 'windows' IPAM driver doesn't
266+ // support static allocations.
267+ skip .If (t , testEnv .DaemonInfo .OSType == "windows" )
268+ skip .If (t , versions .LessThan (testEnv .DaemonAPIVersion (), "1.48" ), "requires API v1.48" )
269+
270+ ctx := setupTest (t )
271+ apiClient := testEnv .APIClient ()
272+
273+ network .CreateNoError (ctx , t , apiClient , "testnet1" ,
274+ network .WithIPv6 (),
275+ network .WithIPAM ("10.100.10.0/24" , "10.100.10.1" ),
276+ network .WithIPAM ("fddd:4901:f594::/64" , "fddd:4901:f594::1" ))
277+ defer network .RemoveNoError (ctx , t , apiClient , "testnet1" )
278+
279+ network .CreateNoError (ctx , t , apiClient , "testnet2" ,
280+ network .WithIPv6 (),
281+ network .WithIPAM ("10.100.20.0/24" , "10.100.20.1" ),
282+ network .WithIPAM ("fd83:7683:7008::/64" , "fd83:7683:7008::1" ))
283+ defer network .RemoveNoError (ctx , t , apiClient , "testnet2" )
284+
285+ network .CreateNoError (ctx , t , apiClient , "testnet3" ,
286+ network .WithDriver ("bridge" ),
287+ network .WithIPv6 (),
288+ network .WithIPAM ("10.100.30.0/24" , "10.100.30.1" ),
289+ network .WithIPAM ("fd72:de0:adad::/64" , "fd72:de0:adad::1" ))
290+ defer network .RemoveNoError (ctx , t , apiClient , "testnet3" )
291+
292+ network .CreateNoError (ctx , t , apiClient , "testnet4" ,
293+ network .WithIPv6 (),
294+ network .WithIPAM ("10.100.40.0/24" , "10.100.40.1" ),
295+ network .WithIPAM ("fd4c:c927:7d90::/64" , "fd4c:c927:7d90::1" ))
296+ defer network .RemoveNoError (ctx , t , apiClient , "testnet4" )
297+
298+ network .CreateNoError (ctx , t , apiClient , "testnet5" ,
299+ network .WithIPv6 (),
300+ network .WithIPAM ("10.100.50.0/24" , "10.100.50.1" ),
301+ network .WithIPAM ("fd4c:364b:1110::/64" , "fd4c:364b:1110::1" ))
302+ defer network .RemoveNoError (ctx , t , apiClient , "testnet5" )
303+
304+ ctrID := container .Run (ctx , t , apiClient ,
305+ container .WithCmd ("sleep" , "infinity" ),
306+ container .WithNetworkMode ("testnet1" ),
307+ container .WithEndpointSettings ("testnet1" , & networktypes.EndpointSettings {}))
308+ defer container .Remove (ctx , t , apiClient , ctrID , containertypes.RemoveOptions {Force : true })
309+
310+ checkCtrRoutes (t , ctx , apiClient , ctrID , syscall .AF_INET , 2 , "default via 10.100.10.1 dev eth0" )
311+ checkCtrRoutes (t , ctx , apiClient , ctrID , syscall .AF_INET6 , 4 , "default via fddd:4901:f594::1 dev eth0" )
312+
313+ // testnet5 has a negative priority -- the default gateway should not change.
314+ err := apiClient .NetworkConnect (ctx , "testnet5" , ctrID , & networktypes.EndpointSettings {GwPriority : - 100 })
315+ assert .NilError (t , err )
316+ checkCtrRoutes (t , ctx , apiClient , ctrID , syscall .AF_INET , 3 , "default via 10.100.10.1 dev eth0" )
317+ checkCtrRoutes (t , ctx , apiClient , ctrID , syscall .AF_INET6 , 7 , "default via fddd:4901:f594::1 dev eth0" )
318+
319+ // testnet2 has a higher priority. It should now provide the default gateway.
320+ err = apiClient .NetworkConnect (ctx , "testnet2" , ctrID , & networktypes.EndpointSettings {GwPriority : 100 })
321+ assert .NilError (t , err )
322+ checkCtrRoutes (t , ctx , apiClient , ctrID , syscall .AF_INET , 4 , "default via 10.100.20.1 dev eth2" )
323+ checkCtrRoutes (t , ctx , apiClient , ctrID , syscall .AF_INET6 , 10 , "default via fd83:7683:7008::1 dev eth2" )
324+
325+ // testnet3 has a lower priority, so testnet2 should still provide the default gateway.
326+ err = apiClient .NetworkConnect (ctx , "testnet3" , ctrID , & networktypes.EndpointSettings {GwPriority : 10 })
327+ assert .NilError (t , err )
328+ checkCtrRoutes (t , ctx , apiClient , ctrID , syscall .AF_INET , 5 , "default via 10.100.20.1 dev eth2" )
329+ checkCtrRoutes (t , ctx , apiClient , ctrID , syscall .AF_INET6 , 13 , "default via fd83:7683:7008::1 dev eth2" )
330+
331+ // testnet4 has the same priority as testnet3, but it sorts after in
332+ // lexicographic order. For now, testnet2 stays the default gateway.
333+ err = apiClient .NetworkConnect (ctx , "testnet4" , ctrID , & networktypes.EndpointSettings {GwPriority : 10 })
334+ assert .NilError (t , err )
335+ checkCtrRoutes (t , ctx , apiClient , ctrID , syscall .AF_INET , 6 , "default via 10.100.20.1 dev eth2" )
336+ checkCtrRoutes (t , ctx , apiClient , ctrID , syscall .AF_INET6 , 16 , "default via fd83:7683:7008::1 dev eth2" )
337+
338+ inspect := container .Inspect (ctx , t , apiClient , ctrID )
339+ assert .Equal (t , inspect .NetworkSettings .Networks ["testnet1" ].GwPriority , 0 )
340+ assert .Equal (t , inspect .NetworkSettings .Networks ["testnet2" ].GwPriority , 100 )
341+ assert .Equal (t , inspect .NetworkSettings .Networks ["testnet3" ].GwPriority , 10 )
342+ assert .Equal (t , inspect .NetworkSettings .Networks ["testnet4" ].GwPriority , 10 )
343+ assert .Equal (t , inspect .NetworkSettings .Networks ["testnet5" ].GwPriority , - 100 )
344+
345+ // Disconnect testnet2, so testnet3 should now provide the default gateway.
346+ // When two endpoints have the same priority (eg. testnet3 vs testnet4),
347+ // the one that sorts first in lexicographic order is picked.
348+ err = apiClient .NetworkDisconnect (ctx , "testnet2" , ctrID , true )
349+ assert .NilError (t , err )
350+ checkCtrRoutes (t , ctx , apiClient , ctrID , syscall .AF_INET , 5 , "default via 10.100.30.1 dev eth3" )
351+ checkCtrRoutes (t , ctx , apiClient , ctrID , syscall .AF_INET6 , 13 , "default via fd72:de0:adad::1 dev eth3" )
352+
353+ // Disconnect testnet3, so testnet4 should now provide the default gateway.
354+ err = apiClient .NetworkDisconnect (ctx , "testnet3" , ctrID , true )
355+ assert .NilError (t , err )
356+ checkCtrRoutes (t , ctx , apiClient , ctrID , syscall .AF_INET , 4 , "default via 10.100.40.1 dev eth4" )
357+ checkCtrRoutes (t , ctx , apiClient , ctrID , syscall .AF_INET6 , 10 , "default via fd4c:c927:7d90::1 dev eth4" )
358+
359+ // Disconnect testnet4, so testnet1 should now provide the default gateway.
360+ err = apiClient .NetworkDisconnect (ctx , "testnet4" , ctrID , true )
361+ assert .NilError (t , err )
362+ checkCtrRoutes (t , ctx , apiClient , ctrID , syscall .AF_INET , 3 , "default via 10.100.10.1 dev eth0" )
363+ checkCtrRoutes (t , ctx , apiClient , ctrID , syscall .AF_INET6 , 7 , "default via fddd:4901:f594::1 dev eth0" )
364+ }
365+
366+ // checkCtrRoutes execute 'ip route show' in a container, and check that the
367+ // number of routes matches expRoutes. It also checks that the default route
368+ // matches expDefRoute. A substring match is used to avoid issues with
369+ // non-stable interface names.
370+ func checkCtrRoutes (t * testing.T , ctx context.Context , apiClient client.APIClient , ctrID string , af , expRoutes int , expDefRoute string ) {
371+ t .Helper ()
372+
373+ fam := "-4"
374+ if af == syscall .AF_INET6 {
375+ fam = "-6"
376+ }
377+
378+ ctx , cancel := context .WithTimeout (ctx , 1 * time .Second )
379+ defer cancel ()
380+ res , err := container .Exec (ctx , apiClient , ctrID , []string {"ip" , "-o" , fam , "route" , "show" })
381+ assert .NilError (t , err )
382+
383+ assert .Equal (t , res .ExitCode , 0 )
384+ assert .Equal (t , res .Stderr (), "" )
385+
386+ routes := slices .DeleteFunc (strings .Split (res .Stdout (), "\n " ), func (s string ) bool {
387+ return s == ""
388+ })
389+
390+ assert .Equal (t , len (routes ), expRoutes , "expected %d routes, got %d:\n %s" , expRoutes , len (routes ), strings .Join (routes , "\n " ))
391+ defFound := slices .ContainsFunc (routes , func (s string ) bool {
392+ return strings .Contains (s , expDefRoute )
393+ })
394+ assert .Assert (t , defFound , "default route %q not found:\n %s" , expDefRoute , strings .Join (routes , "\n " ))
395+ }
0 commit comments