pull/22776/merge
Nitish Agarwal 2026-03-25 05:07:39 +00:00 committed by GitHub
commit 968b2357e7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 46 additions and 11 deletions

View File

@ -188,8 +188,8 @@ func containerNetworkInspect(ociBin string, name string) (netInfo, error) {
type networkInspect struct {
Name string
Driver string
Subnet string
Gateway string
Subnets []string
Gateways []string
MTU int
ContainerIPs []string
}
@ -197,9 +197,11 @@ type networkInspect struct {
var dockerInspectGetter = func(name string) (*RunResult, error) {
// hack -- 'support ancient versions of docker again (template parsing issue) #10362' and resolve 'Template parsing error: template: :1: unexpected "=" in operand' / 'exit status 64'
// note: docker v18.09.7 and older use go v1.10.8 and older, whereas support for '=' operator in go templates came in go v1.11
cmd := exec.Command(Docker, "network", "inspect", name, "--format", `{"Name": "{{.Name}}","Driver": "{{.Driver}}","Subnet": "{{range .IPAM.Config}}{{.Subnet}}{{end}}","Gateway": "{{range .IPAM.Config}}{{.Gateway}}{{end}}","MTU": {{if (index .Options "com.docker.network.driver.mtu")}}{{(index .Options "com.docker.network.driver.mtu")}}{{else}}0{{end}}, "ContainerIPs": [{{range $k,$v := .Containers }}"{{$v.IPv4Address}}",{{end}}]}`)
// Subnets/Gateways are emitted as JSON arrays so that dual-stack networks (IPv4+IPv6) don't
// produce a concatenated invalid CIDR like "192.168.97.0/24fd07:b51a:cc66::/64". See #21435.
cmd := exec.Command(Docker, "network", "inspect", name, "--format", `{"Name": "{{.Name}}","Driver": "{{.Driver}}","Subnets": [{{range .IPAM.Config}}"{{.Subnet}}",{{end}}],"Gateways": [{{range .IPAM.Config}}"{{.Gateway}}",{{end}}],"MTU": {{if (index .Options "com.docker.network.driver.mtu")}}{{(index .Options "com.docker.network.driver.mtu")}}{{else}}0{{end}}, "ContainerIPs": [{{range $k,$v := .Containers }}"{{$v.IPv4Address}}",{{end}}]}`)
rr, err := runCmd(cmd)
// remove extra ',' after the last element in the ContainerIPs slice
// remove extra ',' after the last element in array slices (Subnets, Gateways, ContainerIPs)
rr.Stdout = *bytes.NewBuffer(bytes.ReplaceAll(rr.Stdout.Bytes(), []byte(",]"), []byte("]")))
return rr, err
}
@ -218,17 +220,34 @@ func dockerNetworkInspect(name string) (netInfo, error) {
return info, err
}
// results looks like {"Name": "bridge","Driver": "bridge","Subnet": "172.17.0.0/16","Gateway": "172.17.0.1","MTU": 1500, "ContainerIPs": ["172.17.0.3/16", "172.17.0.2/16"]}
// results looks like {"Name": "bridge","Driver": "bridge","Subnets": ["172.17.0.0/16"],"Gateways": ["172.17.0.1"],"MTU": 1500, "ContainerIPs": ["172.17.0.3/16", "172.17.0.2/16"]}
if err := json.Unmarshal(rr.Stdout.Bytes(), &vals); err != nil {
return info, fmt.Errorf("error parsing network inspect output: %q", rr.Stdout.String())
}
info.gateway = net.ParseIP(vals.Gateway)
info.mtu = vals.MTU
_, info.subnet, err = net.ParseCIDR(vals.Subnet)
if err != nil {
return info, fmt.Errorf("parse subnet for %s: %w", name, err)
// Select the first IPv4 gateway (IPv6 addresses contain ':')
for _, g := range vals.Gateways {
if g != "" && !strings.Contains(g, ":") {
info.gateway = net.ParseIP(g)
break
}
}
// Select the first IPv4 subnet (IPv6 CIDRs contain ':')
for _, s := range vals.Subnets {
if s != "" && !strings.Contains(s, ":") {
_, info.subnet, err = net.ParseCIDR(s)
if err != nil {
return info, fmt.Errorf("parse subnet for %s: %w", name, err)
}
break
}
}
if info.subnet == nil {
return info, fmt.Errorf("no IPv4 subnet found for %s", name)
}
return info, nil

View File

@ -41,18 +41,34 @@ func TestDockerInspect(t *testing.T) {
}{
{
name: "withMTU",
dockerInspectResponse: `{"Name": "m2","Driver": "bridge","Subnet": "172.19.0.0/16","Gateway": "172.19.0.1","MTU": 9216, "ContainerIPs": []}`,
dockerInspectResponse: `{"Name": "m2","Driver": "bridge","Subnets": ["172.19.0.0/16"],"Gateways": ["172.19.0.1"],"MTU": 9216, "ContainerIPs": []}`,
gateway: "172.19.0.1",
subnetIP: "172.19.0.0",
mtu: 9216,
},
{
name: "withoutMTU",
dockerInspectResponse: `{"Name": "m2","Driver": "bridge","Subnet": "172.19.0.0/16","Gateway": "172.19.0.1","MTU": 0, "ContainerIPs": []}`,
dockerInspectResponse: `{"Name": "m2","Driver": "bridge","Subnets": ["172.19.0.0/16"],"Gateways": ["172.19.0.1"],"MTU": 0, "ContainerIPs": []}`,
gateway: "172.19.0.1",
subnetIP: "172.19.0.0",
mtu: 0,
},
{
// dual-stack network: both IPv4 and IPv6 IPAM configs — only IPv4 should be used (#21435)
name: "dualStackIPv4IPv6",
dockerInspectResponse: `{"Name": "m2","Driver": "bridge","Subnets": ["192.168.97.0/24","fd07:b51a:cc66::/64"],"Gateways": ["192.168.97.1","fd07:b51a:cc66::1"],"MTU": 1500, "ContainerIPs": []}`,
gateway: "192.168.97.1",
subnetIP: "192.168.97.0",
mtu: 1500,
},
{
// IPv6-first ordering: IPv6 appears before IPv4 in the IPAM config list
name: "dualStackIPv6First",
dockerInspectResponse: `{"Name": "m2","Driver": "bridge","Subnets": ["fd07:b51a:cc66::/64","192.168.97.0/24"],"Gateways": ["fd07:b51a:cc66::1","192.168.97.1"],"MTU": 1500, "ContainerIPs": []}`,
gateway: "192.168.97.1",
subnetIP: "192.168.97.0",
mtu: 1500,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {