Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions cmd/nerdctl/network/network_create_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,126 @@ func TestNetworkCreate(t *testing.T) {
}
},
},
{
Description: "with static IPv4 address",
Setup: func(data test.Data, helpers test.Helpers) {
networkName := data.Identifier()
staticIP := "172.19.0.100"
data.Labels().Set("networkName", networkName)
data.Labels().Set("staticIP", staticIP)
helpers.Ensure("network", "create", networkName, "--driver", "bridge", "--subnet", "172.19.0.0/24")
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("network", "rm", data.Labels().Get("networkName"))
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("run", "--rm", "--net", data.Labels().Get("networkName"), "--ip", data.Labels().Get("staticIP"), testutil.CommonImage, "ip", "addr", "show", "eth0")
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Output: func(stdout string, t tig.T) {
assert.Assert(t, strings.Contains(stdout, fmt.Sprintf("inet %s/24", data.Labels().Get("staticIP"))))
},
}
},
},
{
Description: "with static IPv6 address",
Require: nerdtest.OnlyIPv6,
Setup: func(data test.Data, helpers test.Helpers) {
networkName := data.Identifier()
staticIPv6 := "2001:db8:1::100"
data.Labels().Set("networkName", networkName)
data.Labels().Set("staticIPv6", staticIPv6)
helpers.Ensure("network", "create", networkName, "--driver", "bridge", "--ipv6", "--subnet", "2001:db8:1::/64")
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("network", "rm", data.Labels().Get("networkName"))
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("run", "--rm", "--net", data.Labels().Get("networkName"), "--ip", data.Labels().Get("staticIPv6"), testutil.CommonImage, "ip", "addr", "show", "eth0")
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Output: func(stdout string, t tig.T) {
assert.Assert(t, strings.Contains(stdout, fmt.Sprintf("inet6 %s/64", data.Labels().Get("staticIPv6"))))
},
}
},
},
{
Description: "with dual-stack static IP addresses",
Require: nerdtest.OnlyIPv6,
Setup: func(data test.Data, helpers test.Helpers) {
networkName := data.Identifier()
staticIPv4 := "172.20.0.100"
staticIPv6 := "2001:db8:2::100"
data.Labels().Set("networkName", networkName)
data.Labels().Set("staticIPv4", staticIPv4)
data.Labels().Set("staticIPv6", staticIPv6)
helpers.Ensure("network", "create", networkName, "--driver", "bridge", "--subnet", "172.20.0.0/24", "--ipv6", "--subnet", "2001:db8:2::/64")
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("network", "rm", data.Labels().Get("networkName"))
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("run", "--rm", "--net", data.Labels().Get("networkName"), "--ip", data.Labels().Get("staticIPv4"), "--ip", data.Labels().Get("staticIPv6"), testutil.CommonImage, "ip", "addr", "show", "eth0")
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Output: func(stdout string, t tig.T) {
assert.Assert(t, strings.Contains(stdout, fmt.Sprintf("inet %s/24", data.Labels().Get("staticIPv4"))))
assert.Assert(t, strings.Contains(stdout, fmt.Sprintf("inet6 %s/64", data.Labels().Get("staticIPv6"))))
},
}
},
},
{
Description: "with static IPv6 address on macvlan",
Require: nerdtest.OnlyIPv6,
Setup: func(data test.Data, helpers test.Helpers) {
dummyLinkName := "dummy-" + data.Identifier()
networkName := data.Identifier()
staticIPv6 := "2001:db8:3::100"
subnet := "2001:db8:3::/64"

data.Labels().Set("dummyLinkName", dummyLinkName)
data.Labels().Set("networkName", networkName)
data.Labels().Set("staticIPv6", staticIPv6)

// Create a dummy interface to be the parent of the macvlan network
helpers.Custom("ip", "link", "add", dummyLinkName, "type", "dummy").Run(&test.Expected{ExitCode: 0})
helpers.Custom("ip", "link", "set", dummyLinkName, "up").Run(&test.Expected{ExitCode: 0})

// Create the macvlan network
helpers.Ensure("network", "create", networkName,
"--driver", "macvlan",
"--parent", dummyLinkName,
"--ipv6",
"--subnet", subnet)
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("network", "rm", data.Labels().Get("networkName"))
helpers.Anyhow("ip", "link", "del", data.Labels().Get("dummyLinkName"))
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("run", "--rm",
"--net", data.Labels().Get("networkName"),
"--ip6", data.Labels().Get("staticIPv6"),
testutil.CommonImage, "ip", "addr", "show", "eth0")
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Output: func(stdout string, t tig.T) {
assert.Assert(t, strings.Contains(stdout, fmt.Sprintf("inet6 %s/64", data.Labels().Get("staticIPv6"))))
},
}
},
},
}

testCase.Run(t)
Expand Down
3 changes: 3 additions & 0 deletions pkg/composer/serviceparser/serviceparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,9 @@ func newContainer(project *types.Project, parsed *Service, i int) (*Container, e
if value != nil && value.Ipv4Address != "" {
c.RunArgs = append(c.RunArgs, "--ip="+value.Ipv4Address)
}
if value != nil && value.Ipv6Address != "" {
c.RunArgs = append(c.RunArgs, "--ip6="+value.Ipv6Address)
}
if value != nil && value.MacAddress != "" {
c.RunArgs = append(c.RunArgs, "--mac-address="+value.MacAddress)
}
Expand Down
38 changes: 38 additions & 0 deletions pkg/composer/serviceparser/serviceparser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,44 @@ services:

}

func TestParseDualStackAddress(t *testing.T) {
t.Parallel()
const dockerComposeYAML = `
services:
foo:
image: nginx:alpine
networks:
default:
ipv4_address: "172.30.0.100"
ipv6_address: "2001:db8:abc:123::42"
networks:
default:
driver: bridge
ipam:
driver: default
config:
- subnet: "172.30.0.0/24"
- subnet: "2001:db8:abc:123::/64"
`
comp := testutil.NewComposeDir(t, dockerComposeYAML)
defer comp.CleanUp()

project, err := testutil.LoadProject(comp.YAMLFullPath(), comp.ProjectName(), nil)
assert.NilError(t, err)

fooSvc, err := project.GetService("foo")
assert.NilError(t, err)

foo, err := Parse(project, fooSvc)
assert.NilError(t, err)

t.Logf("foo: %+v", foo)
for _, c := range foo.Containers {
assert.Assert(t, in(c.RunArgs, "--ip=172.30.0.100"))
assert.Assert(t, in(c.RunArgs, "--ip6=2001:db8:abc:123::42"))
}
}

func TestParseConfigs(t *testing.T) {
t.Parallel()
if runtime.GOOS == "windows" {
Expand Down
11 changes: 6 additions & 5 deletions pkg/netutil/cni_plugin_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,12 @@ func (*tuningConfig) GetPluginType() string {

// https://github.com/containernetworking/plugins/blob/v1.0.1/plugins/ipam/host-local/backend/allocator/config.go#L47-L56
type hostLocalIPAMConfig struct {
Type string `json:"type"`
Routes []IPAMRoute `json:"routes,omitempty"`
ResolveConf string `json:"resolveConf,omitempty"`
DataDir string `json:"dataDir,omitempty"`
Ranges [][]IPAMRange `json:"ranges,omitempty"`
Type string `json:"type"`
Routes []IPAMRoute `json:"routes,omitempty"`
ResolveConf string `json:"resolveConf,omitempty"`
DataDir string `json:"dataDir,omitempty"`
Ranges [][]IPAMRange `json:"ranges,omitempty"`
Capabilities map[string]bool `json:"capabilities,omitempty"`
}

func newHostLocalIPAMConfig() *hostLocalIPAMConfig {
Expand Down
13 changes: 13 additions & 0 deletions pkg/netutil/netutil_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ func (e *CNIEnv) generateCNIPlugins(driver string, name string, ipam map[string]
bridge.HairpinMode = true
if ipv6 {
bridge.Capabilities["ips"] = true
// Explicitly declare capabilities that are implicitly lost when
// the bridge.Capabilities map becomes non-empty.
bridge.Capabilities["dns"] = true
bridge.Capabilities["portMappings"] = true
}

// Determine the appropriate firewall ingress policy based on icc setting
Expand Down Expand Up @@ -207,6 +211,10 @@ func (e *CNIEnv) generateCNIPlugins(driver string, name string, ipam map[string]
vlan.IPAM = ipam
if ipv6 {
vlan.Capabilities["ips"] = true
// Explicitly declare capabilities that are implicitly lost when
// the vlan.Capabilities map becomes non-empty.
vlan.Capabilities["dns"] = true
vlan.Capabilities["portMappings"] = true
}
plugins = []CNIPlugin{vlan}
default:
Expand All @@ -225,6 +233,11 @@ func (e *CNIEnv) generateIPAM(driver string, subnets []string, gatewayStr, ipRan
{Dst: "0.0.0.0/0"},
}
}
// Explicitly declare "ips" capability for host-local IPAM
if ipamConf.Capabilities == nil {
ipamConf.Capabilities = make(map[string]bool)
}
ipamConf.Capabilities["ips"] = true
ranges, findIPv4, err := e.parseIPAMRanges(subnets, gatewayStr, ipRangeStr, ipv6)
if err != nil {
return nil, err
Expand Down
Loading