Skip to content

Commit

Permalink
Support IPv6 for network
Browse files Browse the repository at this point in the history
Signed-off-by: Hanchin Hsieh <me@yuchanns.xyz>
  • Loading branch information
yuchanns committed Feb 18, 2023
1 parent 34fb629 commit 99fd8ae
Show file tree
Hide file tree
Showing 13 changed files with 257 additions and 63 deletions.
10 changes: 8 additions & 2 deletions cmd/nerdctl/container_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,8 @@ func setCreateFlags(cmd *cobra.Command) {
cmd.Flags().StringSlice("dns-option", nil, "Set DNS options")
// publish is defined as StringSlice, not StringArray, to allow specifying "--publish=80:80,443:443" (compatible with Podman)
cmd.Flags().StringSliceP("publish", "p", nil, "Publish a container's port(s) to the host")
// FIXME: not support IPV6 yet
cmd.Flags().String("ip", "", "IPv4 address to assign to the container")
cmd.Flags().String("ip6", "", "IPv6 address to assign to the container")
cmd.Flags().StringP("hostname", "h", "", "Container host name")
cmd.Flags().String("mac-address", "", "MAC address to assign to the container")
// #endregion
Expand Down Expand Up @@ -608,12 +608,13 @@ func createContainer(ctx context.Context, cmd *cobra.Command, client *containerd
}
}

netOpts, netSlice, ipAddress, ports, macAddress, err := generateNetOpts(cmd, globalOptions, dataStore, stateDir, globalOptions.Namespace, id)
netOpts, netSlice, ipAddress, ports, macAddress, ip6Address, err := generateNetOpts(cmd, globalOptions, dataStore, stateDir, globalOptions.Namespace, id)
if err != nil {
return nil, nil, err
}
internalLabels.networks = netSlice
internalLabels.ipAddress = ipAddress
internalLabels.ip6Address = ip6Address
internalLabels.ports = ports
internalLabels.macAddress = macAddress
opts = append(opts, netOpts...)
Expand Down Expand Up @@ -1056,6 +1057,7 @@ type internalLabels struct {
// network
networks []string
ipAddress string
ip6Address string
ports []gocni.PortMapping
macAddress string
// volumn
Expand Down Expand Up @@ -1133,6 +1135,10 @@ func withInternalLabels(internalLabels internalLabels) (containerd.NewContainerO
m[labels.PIDContainer] = internalLabels.pidContainer
}

if internalLabels.ip6Address != "" {
m[labels.IP6Address] = internalLabels.ip6Address
}

return containerd.WithAdditionalContainerLabels(m), nil
}

Expand Down
42 changes: 23 additions & 19 deletions cmd/nerdctl/container_run_network.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,19 +113,23 @@ func withCustomHosts(src string) func(context.Context, oci.Client, *containers.C
}
}

func generateNetOpts(cmd *cobra.Command, globalOptions types.GlobalCommandOptions, dataStore, stateDir, ns, id string) ([]oci.SpecOpts, []string, string, []gocni.PortMapping, string, error) {
func generateNetOpts(cmd *cobra.Command, globalOptions types.GlobalCommandOptions, dataStore, stateDir, ns, id string) ([]oci.SpecOpts, []string, string, []gocni.PortMapping, string, string, error) {
opts := []oci.SpecOpts{}
portSlice, err := cmd.Flags().GetStringSlice("publish")
if err != nil {
return nil, nil, "", nil, "", err
return nil, nil, "", nil, "", "", err
}
ipAddress, err := cmd.Flags().GetString("ip")
if err != nil {
return nil, nil, "", nil, "", err
return nil, nil, "", nil, "", "", err
}
ip6Address, err := cmd.Flags().GetString("ip6")
if err != nil {
return nil, nil, "", nil, "", "", err
}
netSlice, err := getNetworkSlice(cmd)
if err != nil {
return nil, nil, "", nil, "", err
return nil, nil, "", nil, "", "", err
}

if (len(netSlice) == 0) && (ipAddress != "") {
Expand All @@ -134,13 +138,13 @@ func generateNetOpts(cmd *cobra.Command, globalOptions types.GlobalCommandOption

macAddress, err := getMACAddress(cmd, netSlice)
if err != nil {
return nil, nil, "", nil, "", err
return nil, nil, "", nil, "", "", err
}

ports := make([]gocni.PortMapping, 0)
netType, err := nettype.Detect(netSlice)
if err != nil {
return nil, nil, "", nil, "", err
return nil, nil, "", nil, "", "", err
}

switch netType {
Expand All @@ -150,51 +154,51 @@ func generateNetOpts(cmd *cobra.Command, globalOptions types.GlobalCommandOption
// not work but run command will success
case nettype.Host:
if macAddress != "" {
return nil, nil, "", nil, "", errors.New("conflicting options: mac-address and the network mode")
return nil, nil, "", nil, "", "", errors.New("conflicting options: mac-address and the network mode")
}
opts = append(opts, oci.WithHostNamespace(specs.NetworkNamespace), oci.WithHostHostsFile, oci.WithHostResolvconf)
case nettype.CNI:
// We only verify flags and generate resolv.conf here.
// The actual network is configured in the oci hook.
if err := verifyCNINetwork(cmd, netSlice, macAddress, globalOptions); err != nil {
return nil, nil, "", nil, "", err
return nil, nil, "", nil, "", "", err
}

if runtime.GOOS == "linux" {
resolvConfPath := filepath.Join(stateDir, "resolv.conf")
if err := buildResolvConf(cmd, resolvConfPath); err != nil {
return nil, nil, "", nil, "", err
return nil, nil, "", nil, "", "", err
}

// the content of /etc/hosts is created in OCI Hook
etcHostsPath, err := hostsstore.AllocHostsFile(dataStore, ns, id)
if err != nil {
return nil, nil, "", nil, "", err
return nil, nil, "", nil, "", "", err
}
opts = append(opts, withCustomResolvConf(resolvConfPath), withCustomHosts(etcHostsPath))
for _, p := range portSlice {
pm, err := portutil.ParseFlagP(p)
if err != nil {
return nil, nil, "", pm, "", err
return nil, nil, "", pm, "", "", err
}
ports = append(ports, pm...)
}
}
case nettype.Container:
if macAddress != "" {
return nil, nil, "", nil, "", errors.New("conflicting options: mac-address and the network mode")
return nil, nil, "", nil, "", "", errors.New("conflicting options: mac-address and the network mode")
}
if err := verifyContainerNetwork(cmd, netSlice); err != nil {
return nil, nil, "", nil, "", err
return nil, nil, "", nil, "", "", err
}
network := strings.Split(netSlice[0], ":")
if len(network) != 2 {
return nil, nil, "", nil, "", fmt.Errorf("invalid network: %s, should be \"container:<id|name>\"", netSlice[0])
return nil, nil, "", nil, "", "", fmt.Errorf("invalid network: %s, should be \"container:<id|name>\"", netSlice[0])
}
containerName := network[1]
client, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address)
if err != nil {
return nil, nil, "", nil, "", err
return nil, nil, "", nil, "", "", err
}
defer cancel()

Expand Down Expand Up @@ -240,15 +244,15 @@ func generateNetOpts(cmd *cobra.Command, globalOptions types.GlobalCommandOption
}
n, err := walker.Walk(ctx, containerName)
if err != nil {
return nil, nil, "", nil, "", err
return nil, nil, "", nil, "", "", err
}
if n == 0 {
return nil, nil, "", nil, "", fmt.Errorf("no such container: %s", containerName)
return nil, nil, "", nil, "", "", fmt.Errorf("no such container: %s", containerName)
}
default:
return nil, nil, "", nil, "", fmt.Errorf("unexpected network type %v", netType)
return nil, nil, "", nil, "", "", fmt.Errorf("unexpected network type %v", netType)
}
return opts, netSlice, ipAddress, ports, macAddress, nil
return opts, netSlice, ipAddress, ports, macAddress, ip6Address, nil
}

func verifyCNINetwork(cmd *cobra.Command, netSlice []string, macAddress string, globalOptions types.GlobalCommandOptions) error {
Expand Down
70 changes: 70 additions & 0 deletions cmd/nerdctl/container_run_network_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -575,3 +575,73 @@ func TestRunContainerWithMACAddress(t *testing.T) {
}
}
}

func TestRunContainerWithStaticIP6(t *testing.T) {
if rootlessutil.IsRootless() {
t.Skip("Static IP6 assignment is not supported rootless mode yet.")
}
networkName := "test-network"
networkSubnet := "2001:db8:5::/64"
_, subnet, err := net.ParseCIDR(networkSubnet)
assert.Assert(t, err == nil)
base := testutil.NewBase(t)
base.Cmd("network", "create", networkName, "--subnet", networkSubnet, "--ipv6").AssertOK()
t.Cleanup(func() {
base.Cmd("network", "rm", networkName).Run()
})
testCases := []struct {
ip string
shouldSuccess bool
checkTheIPAddress bool
}{
{
ip: "",
shouldSuccess: true,
checkTheIPAddress: false,
},
{
ip: "2001:db8:5::6",
shouldSuccess: true,
checkTheIPAddress: true,
},
{
ip: "2001:db8:4::6",
shouldSuccess: false,
checkTheIPAddress: false,
},
}
tID := testutil.Identifier(t)
for i, tc := range testCases {
i := i
tc := tc
tcName := fmt.Sprintf("%+v", tc)
t.Run(tcName, func(t *testing.T) {
testContainerName := fmt.Sprintf("%s-%d", tID, i)
base := testutil.NewBase(t)
args := []string{
"run", "--rm", "--name", testContainerName, "--network", networkName,
}
if tc.ip != "" {
args = append(args, "--ip6", tc.ip)
}
args = append(args, []string{testutil.NginxAlpineImage, "ip", "addr", "show", "dev", "eth0"}...)
cmd := base.Cmd(args...)
if !tc.shouldSuccess {
cmd.AssertFail()
return
}
cmd.AssertOutWithFunc(func(stdout string) error {
ip := findIPv6(stdout)
if !subnet.Contains(ip) {
return fmt.Errorf("expected subnet %s include ip %s", subnet, ip)
}
if tc.checkTheIPAddress {
if ip.String() != tc.ip {
return fmt.Errorf("expected ip %s, got %s", tc.ip, ip)
}
}
return nil
})
})
}
}
12 changes: 9 additions & 3 deletions cmd/nerdctl/network_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,11 @@ func newNetworkCreateCommand() *cobra.Command {
networkCreateCommand.Flags().String("ipam-driver", "default", "IP Address Management Driver")
networkCreateCommand.RegisterFlagCompletionFunc("ipam-driver", shellCompleteIPAMDrivers)
networkCreateCommand.Flags().StringArray("ipam-opt", nil, "Set IPAM driver specific options")
networkCreateCommand.Flags().String("subnet", "", `Subnet in CIDR format that represents a network segment, e.g. "10.5.0.0/16"`)
networkCreateCommand.Flags().StringArray("subnet", nil, `Subnet in CIDR format that represents a network segment, e.g. "10.5.0.0/16"`)
networkCreateCommand.Flags().String("gateway", "", `Gateway for the master subnet`)
networkCreateCommand.Flags().String("ip-range", "", `Allocate container ip from a sub-range`)
networkCreateCommand.Flags().StringArray("label", nil, "Set metadata for a network")
networkCreateCommand.Flags().Bool("ipv6", false, "Enable IPv6 networking")
return networkCreateCommand
}

Expand Down Expand Up @@ -79,7 +80,7 @@ func networkCreateAction(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
subnetStr, err := cmd.Flags().GetString("subnet")
subnets, err := cmd.Flags().GetStringArray("subnet")
if err != nil {
return err
}
Expand All @@ -96,6 +97,10 @@ func networkCreateAction(cmd *cobra.Command, args []string) error {
return err
}
labels = strutil.DedupeStrSlice(labels)
ipv6, err := cmd.Flags().GetBool("ipv6")
if err != nil {
return err
}

return network.Create(types.NetworkCreateOptions{
GOptions: globalOptions,
Expand All @@ -105,10 +110,11 @@ func networkCreateAction(cmd *cobra.Command, args []string) error {
Options: strutil.ConvertKVStringsToMap(opts),
IPAMDriver: ipamDriver,
IPAMOptions: strutil.ConvertKVStringsToMap(ipamOpts),
Subnet: subnetStr,
Subnets: subnets,
Gateway: gatewayStr,
IPRange: ipRangeStr,
Labels: labels,
IPv6: ipv6,
},
}, cmd.OutOrStdout())
}
40 changes: 40 additions & 0 deletions cmd/nerdctl/network_create_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
package main

import (
"fmt"
"net"
"strings"
"testing"

"github.com/containerd/nerdctl/pkg/testutil"
Expand Down Expand Up @@ -54,3 +57,40 @@ func TestNetworkCreate(t *testing.T) {

base.Cmd("run", "--rm", "--net", testNetwork+"-1", testutil.CommonImage, "ip", "route").AssertNoOut(net.IPAM.Config[0].Subnet)
}

func TestNetworkCreateIPv6(t *testing.T) {
base := testutil.NewBase(t)
testNetwork := testutil.Identifier(t)

subnetStr := "2001:db8:1::/64"
_, subnet, err := net.ParseCIDR(subnetStr)
assert.Assert(t, err == nil)

base.Cmd("network", "create", "--ipv6", "--subnet", subnetStr, testNetwork).AssertOK()
t.Cleanup(func() {
base.Cmd("network", "rm", testNetwork).Run()
})

base.Cmd("run", "--rm", "--net", testNetwork, testutil.CommonImage, "ip", "addr", "show", "dev", "eth0").AssertOutWithFunc(func(stdout string) error {
ip := findIPv6(stdout)
if subnet.Contains(ip) {
return nil
}
return fmt.Errorf("expected subnet %s include ip %s", subnet, ip)
})
}

func findIPv6(output string) net.IP {
var ipv6 string
lines := strings.Split(output, "\n")
for _, line := range lines {
if strings.Contains(line, "inet6") {
fields := strings.Fields(line)
if len(fields) > 1 {
ipv6 = strings.Split(fields[1], "/")[0]
break
}
}
}
return net.ParseIP(ipv6)
}
2 changes: 2 additions & 0 deletions docs/command-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ Network flags:
- :whale: `-h, --hostname`: Container host name
- :whale: `--add-host`: Add a custom host-to-IP mapping (host:ip)
- :whale: `--ip`: Specific static IP address(es) to use
- :whale: `--ip6`: Specific static IP6 address(es) to use. Should be used with user networks
- :whale: `--mac-address`: Specific MAC address to use. Be aware that it does not
check if manually specified MAC addresses are unique. Supports network
type `bridge` and `macvlan`
Expand Down Expand Up @@ -949,6 +950,7 @@ Flags:
- :whale: `--gateway`: Gateway for the master subnet
- :whale: `--ip-range`: Allocate container ip from a sub-range
- :whale: `--label`: Set metadata on a network
- :whale: `--ipv6`: Enable IPv6. Should be used with a valid subnet.

Unimplemented `docker network create` flags: `--attachable`, `--aux-address`, `--config-from`, `--config-only`, `--ingress`, `--internal`, `--ipv6`, `--scope`

Expand Down
3 changes: 2 additions & 1 deletion pkg/cmd/network/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ import (
)

func Create(options types.NetworkCreateOptions, stdout io.Writer) error {
if options.CreateOptions.Subnet == "" {
if len(options.CreateOptions.Subnets) == 0 {
if options.CreateOptions.Gateway != "" || options.CreateOptions.IPRange != "" {
return fmt.Errorf("cannot set gateway or ip-range without subnet, specify --subnet manually")
}
options.CreateOptions.Subnets = []string{""}
}

e, err := netutil.NewCNIEnv(options.GOptions.CNIPath, options.GOptions.CNINetConfPath)
Expand Down
2 changes: 2 additions & 0 deletions pkg/labels/labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ const (
// Boolean value which can be parsed with strconv.ParseBool() is required.
// (like "nerdctl/default-network=true" or "nerdctl/default-network=false")
NerdctlDefaultNetwork = Prefix + "default-network"

IP6Address = Prefix + "ip6"
)

var ShellCompletions = []string{
Expand Down
Loading

0 comments on commit 99fd8ae

Please sign in to comment.