@@ -19,6 +19,7 @@ package network
1919import (
2020 "encoding/json"
2121 "errors"
22+ "net"
2223 "os/exec"
2324 "runtime"
2425 "strings"
@@ -397,6 +398,164 @@ func TestNetworkInspect(t *testing.T) {
397398 }
398399 },
399400 },
401+ {
402+ Description : "Test container network details" ,
403+ Setup : func (data test.Data , helpers test.Helpers ) {
404+ helpers .Ensure ("network" , "create" , data .Identifier ("test-network" ))
405+
406+ // See https://github.com/containerd/nerdctl/issues/4322
407+ if runtime .GOOS == "windows" {
408+ time .Sleep (time .Second )
409+ }
410+
411+ // Create and start a container on this network
412+ helpers .Ensure ("run" , "-d" , "--name" , data .Identifier ("test-container" ),
413+ "--network" , data .Identifier ("test-network" ),
414+ testutil .CommonImage , "sleep" , nerdtest .Infinity )
415+
416+ // Get container ID for later use
417+ containerID := strings .Trim (helpers .Capture ("inspect" , data .Identifier ("test-container" ), "--format" , "{{.Id}}" ), "\n " )
418+ data .Labels ().Set ("containerID" , containerID )
419+ },
420+ Cleanup : func (data test.Data , helpers test.Helpers ) {
421+ helpers .Anyhow ("rm" , "-f" , data .Identifier ("test-container" ))
422+ helpers .Anyhow ("network" , "remove" , data .Identifier ("test-network" ))
423+ },
424+ Command : func (data test.Data , helpers test.Helpers ) test.TestableCommand {
425+ return helpers .Command ("network" , "inspect" , data .Identifier ("test-network" ))
426+ },
427+ Expected : func (data test.Data , helpers test.Helpers ) * test.Expected {
428+ return & test.Expected {
429+ Output : func (stdout string , t tig.T ) {
430+ var dc []dockercompat.Network
431+ err := json .Unmarshal ([]byte (stdout ), & dc )
432+ assert .NilError (t , err , "Unable to unmarshal output" )
433+ assert .Equal (t , 1 , len (dc ), "Expected exactly one network" )
434+
435+ network := dc [0 ]
436+ assert .Equal (t , network .Name , data .Identifier ("test-network" ))
437+ assert .Equal (t , 1 , len (network .Containers ), "Expected exactly one container" )
438+
439+ // Get the container details
440+ containerID := data .Labels ().Get ("containerID" )
441+ container := network .Containers [containerID ]
442+
443+ // Test container name
444+ assert .Equal (t , container .Name , data .Identifier ("test-container" ))
445+
446+ // Windows InspectNetNS is not implemented
447+ if runtime .GOOS != "windows" {
448+ // Verify IPv4Address is not empty and has CIDR notation
449+ assert .Assert (t , container .IPv4Address != "" , "IPv4Address should not be empty" )
450+ assert .Assert (t , strings .Contains (container .IPv4Address , "/" ), "IPv4Address should contain CIDR notation with /" )
451+
452+ // Verify IPv4Address is within the network's subnet
453+ if len (network .IPAM .Config ) > 0 && network .IPAM .Config [0 ].Subnet != "" {
454+ _ , subnet , err := net .ParseCIDR (network .IPAM .Config [0 ].Subnet )
455+ assert .NilError (t , err , "Failed to parse network subnet" )
456+
457+ containerIP , _ , err := net .ParseCIDR (container .IPv4Address )
458+ assert .NilError (t , err , "Failed to parse container IPv4Address" )
459+ assert .Assert (t , subnet .Contains (containerIP ), "IPv4Address should be within the network's subnet" )
460+ }
461+
462+ // Test MacAddress is present and has valid format
463+ assert .Assert (t , container .MacAddress != "" , "MacAddress should not be empty" )
464+
465+ // Test IPv6Address is empty for IPv4-only network
466+ assert .Equal (t , "" , container .IPv6Address , "IPv6Address should be empty for IPv4-only network" )
467+ }
468+ },
469+ }
470+ },
471+ },
472+ {
473+ Description : "Test dual-stack network with both IPv4 and IPv6" ,
474+ Require : require .Not (require .Windows ), // NetNS not implemented on Windows
475+ Setup : func (data test.Data , helpers test.Helpers ) {
476+ helpers .Ensure ("network" , "create" ,
477+ "--ipv6" ,
478+ "--subnet" , "10.1.0.0/24" ,
479+ "--subnet" , "fd00::/64" ,
480+ data .Identifier ("test-dual-stack" ))
481+
482+ // See https://github.com/containerd/nerdctl/issues/4322
483+ if runtime .GOOS == "windows" {
484+ time .Sleep (time .Second )
485+ }
486+
487+ // Create and start a container on this dual-stack network
488+ helpers .Ensure ("run" , "-d" ,
489+ "--name" , data .Identifier ("test-container" ),
490+ "--network" , data .Identifier ("test-dual-stack" ),
491+ testutil .CommonImage , "sleep" , nerdtest .Infinity )
492+
493+ // Get container ID for later use
494+ containerID := strings .Trim (helpers .Capture ("inspect" , data .Identifier ("test-container" ), "--format" , "{{.Id}}" ), "\n " )
495+ data .Labels ().Set ("containerID" , containerID )
496+ },
497+ Cleanup : func (data test.Data , helpers test.Helpers ) {
498+ helpers .Anyhow ("rm" , "-f" , data .Identifier ("test-container" ))
499+ helpers .Anyhow ("network" , "remove" , data .Identifier ("test-dual-stack" ))
500+ },
501+ Command : func (data test.Data , helpers test.Helpers ) test.TestableCommand {
502+ return helpers .Command ("network" , "inspect" , data .Identifier ("test-dual-stack" ))
503+ },
504+ Expected : func (data test.Data , helpers test.Helpers ) * test.Expected {
505+ return & test.Expected {
506+ Output : func (stdout string , t tig.T ) {
507+ var dc []dockercompat.Network
508+ err := json .Unmarshal ([]byte (stdout ), & dc )
509+ assert .NilError (t , err , "Unable to unmarshal output" )
510+ assert .Equal (t , 1 , len (dc ), "Expected exactly one network" )
511+
512+ network := dc [0 ]
513+ assert .Equal (t , network .Name , data .Identifier ("test-dual-stack" ))
514+ assert .Equal (t , 2 , len (network .IPAM .Config ), "Expected two subnets (IPv4 and IPv6)" )
515+
516+ // Get the container details
517+ containerID := data .Labels ().Get ("containerID" )
518+ container := network .Containers [containerID ]
519+
520+ // Test container name
521+ assert .Equal (t , container .Name , data .Identifier ("test-container" ))
522+
523+ // Parse both subnets
524+ var ipv4Subnet , ipv6Subnet * net.IPNet
525+ for _ , config := range network .IPAM .Config {
526+ if config .Subnet != "" {
527+ _ , subnet , err := net .ParseCIDR (config .Subnet )
528+ assert .NilError (t , err , "Failed to parse subnet" )
529+ if subnet .IP .To4 () != nil {
530+ ipv4Subnet = subnet
531+ } else {
532+ ipv6Subnet = subnet
533+ }
534+ }
535+ }
536+
537+ // Verify IPv4 address is present and within subnet
538+ assert .Assert (t , container .IPv4Address != "" , "IPv4Address should not be empty in dual-stack network" )
539+ ipv4 , _ , err := net .ParseCIDR (container .IPv4Address )
540+ assert .NilError (t , err , "Failed to parse IPv4Address" )
541+ if ipv4Subnet != nil {
542+ assert .Assert (t , ipv4Subnet .Contains (ipv4 ), "IPv4 address should be within the IPv4 subnet" )
543+ }
544+
545+ // Verify IPv6 address is present and within subnet
546+ assert .Assert (t , container .IPv6Address != "" , "IPv6Address should not be empty in dual-stack network" )
547+ ipv6 , _ , err := net .ParseCIDR (container .IPv6Address )
548+ assert .NilError (t , err , "Failed to parse IPv6Address" )
549+ if ipv6Subnet != nil {
550+ assert .Assert (t , ipv6Subnet .Contains (ipv6 ), "IPv6 address should be within the IPv6 subnet" )
551+ }
552+
553+ // Verify MAC address is present
554+ assert .Assert (t , container .MacAddress != "" , "MacAddress should not be empty" )
555+ },
556+ }
557+ },
558+ },
400559 }
401560
402561 testCase .Run (t )
0 commit comments