The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/7196
This e-mail was sent by the LXC bot, direct replies will not reach the author unless they happen to be subscribed to this list. === Description (from pull-request) === Adds l2 mode support for `ipvlan` NIC. This mode allows a container to define its own IPs and avoid the need to use proxy ARP/NDP. Although l2 mode removes the ability for the host to firewall the container traffic. Adds a new NIC config key `mode`, which can be either `l3s` or `l2` (defaults to `l3s` which is current behaviour). In `l2` mode, the `ipv4.address` and `ipv6.address` keys allow addresses to be specified in CIDR format, e.g. `192.168.0.1/27` allowing for the container to join the same layer 2 subnet as the wider network. If a singular address is specified, such as `192.168.0.1` then the subnet is assumed to be `/24` for IPv4 and `/64` for IPv6. In `l2` mode the `ipv4.gateway` and `ipv6.gateway` keys require a specific IP address if used, as the host is not used as a router unlike `l3s` mode. Fixes https://github.com/lxc/lxd/issues/6964 Includes https://github.com/lxc/lxd/pull/7193
From e1245418b5e590c0dbefebc31922bf1880e7cdc8 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Wed, 15 Apr 2020 10:45:35 +0100 Subject: [PATCH 1/9] lxd/device/nic/ipvlan: Improve validation of sysctl settings when vlan setting used Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/device/nic_ipvlan.go | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/lxd/device/nic_ipvlan.go b/lxd/device/nic_ipvlan.go index e51af7d96f..2ed0cf9fcd 100644 --- a/lxd/device/nic_ipvlan.go +++ b/lxd/device/nic_ipvlan.go @@ -66,45 +66,61 @@ func (d *nicIPVLAN) validateEnvironment() error { return fmt.Errorf("Requires name property to start") } + extensions := d.state.OS.LXCFeatures + if !extensions["network_ipvlan"] || !extensions["network_l2proxy"] || !extensions["network_gateway_device_route"] { + return fmt.Errorf("Requires liblxc has following API extensions: network_ipvlan, network_l2proxy, network_gateway_device_route") + } + if !shared.PathExists(fmt.Sprintf("/sys/class/net/%s", d.config["parent"])) { return fmt.Errorf("Parent device '%s' doesn't exist", d.config["parent"]) } - extensions := d.state.OS.LXCFeatures - if !extensions["network_ipvlan"] || !extensions["network_l2proxy"] || !extensions["network_gateway_device_route"] { - return fmt.Errorf("Requires liblxc has following API extensions: network_ipvlan, network_l2proxy, network_gateway_device_route") + if d.config["parent"] == "" && d.config["vlan"] != "" { + return fmt.Errorf("The vlan setting can only be used when combined with a parent interface") + } + + // Generate effective parent name, including the VLAN part if option used. + effectiveParentName := network.GetHostDevice(d.config["parent"], d.config["vlan"]) + + // If the effective parent doesn't exist and the vlan option is specified, it means we are going to create + // the VLAN parent at start, and we will configure the needed sysctls so don't need to check them yet. + if d.config["vlan"] != "" && !shared.PathExists(fmt.Sprintf("/sys/class/net/%s", effectiveParentName)) { + return nil } if d.config["ipv4.address"] != "" { // Check necessary sysctls are configured for use with l2proxy parent in IPVLAN l3s mode. - ipv4FwdPath := fmt.Sprintf("net/ipv4/conf/%s/forwarding", d.config["parent"]) + ipv4FwdPath := fmt.Sprintf("net/ipv4/conf/%s/forwarding", effectiveParentName) sysctlVal, err := util.SysctlGet(ipv4FwdPath) if err != nil || sysctlVal != "1\n" { return fmt.Errorf("Error reading net sysctl %s: %v", ipv4FwdPath, err) } if sysctlVal != "1\n" { - return fmt.Errorf("IPVLAN in L3S mode requires sysctl net.ipv4.conf.%s.forwarding=1", d.config["parent"]) + // Replace . in parent name with / for sysctl formatting. + return fmt.Errorf("IPVLAN in L3S mode requires sysctl net.ipv4.conf.%s.forwarding=1", strings.Replace(effectiveParentName, ".", "/", -1)) } } if d.config["ipv6.address"] != "" { // Check necessary sysctls are configured for use with l2proxy parent in IPVLAN l3s mode. - ipv6FwdPath := fmt.Sprintf("net/ipv6/conf/%s/forwarding", d.config["parent"]) + ipv6FwdPath := fmt.Sprintf("net/ipv6/conf/%s/forwarding", effectiveParentName) sysctlVal, err := util.SysctlGet(ipv6FwdPath) if err != nil { return fmt.Errorf("Error reading net sysctl %s: %v", ipv6FwdPath, err) } if sysctlVal != "1\n" { - return fmt.Errorf("IPVLAN in L3S mode requires sysctl net.ipv6.conf.%s.forwarding=1", d.config["parent"]) + // Replace . in parent name with / for sysctl formatting. + return fmt.Errorf("IPVLAN in L3S mode requires sysctl net.ipv6.conf.%s.forwarding=1", strings.Replace(effectiveParentName, ".", "/", -1)) } - ipv6ProxyNdpPath := fmt.Sprintf("net/ipv6/conf/%s/proxy_ndp", d.config["parent"]) + ipv6ProxyNdpPath := fmt.Sprintf("net/ipv6/conf/%s/proxy_ndp", effectiveParentName) sysctlVal, err = util.SysctlGet(ipv6ProxyNdpPath) if err != nil { return fmt.Errorf("Error reading net sysctl %s: %v", ipv6ProxyNdpPath, err) } if sysctlVal != "1\n" { - return fmt.Errorf("IPVLAN in L3S mode requires sysctl net.ipv6.conf.%s.proxy_ndp=1", d.config["parent"]) + // Replace . in parent name with / for sysctl formatting. + return fmt.Errorf("IPVLAN in L3S mode requires sysctl net.ipv6.conf.%s.proxy_ndp=1", strings.Replace(effectiveParentName, ".", "/", -1)) } } @@ -207,13 +223,13 @@ func (d *nicIPVLAN) setupParentSysctls(parentName string) error { ipv6FwdPath := fmt.Sprintf("net/ipv6/conf/%s/forwarding", parentName) err := util.SysctlSet(ipv6FwdPath, "1") if err != nil { - return fmt.Errorf("Error reading net sysctl %s: %v", ipv6FwdPath, err) + return fmt.Errorf("Error setting net sysctl %s: %v", ipv6FwdPath, err) } ipv6ProxyNdpPath := fmt.Sprintf("net/ipv6/conf/%s/proxy_ndp", parentName) err = util.SysctlSet(ipv6ProxyNdpPath, "1") if err != nil { - return fmt.Errorf("Error reading net sysctl %s: %v", ipv6ProxyNdpPath, err) + return fmt.Errorf("Error setting net sysctl %s: %v", ipv6ProxyNdpPath, err) } } From 468b144f1028f9392c86e9a09217969f8e7f5955 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Wed, 15 Apr 2020 11:48:22 +0100 Subject: [PATCH 2/9] lxd/device/nic/ipvlan: Adds host_table setting support Fixes #7123 Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/device/nic_ipvlan.go | 62 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/lxd/device/nic_ipvlan.go b/lxd/device/nic_ipvlan.go index 2ed0cf9fcd..6d55826f89 100644 --- a/lxd/device/nic_ipvlan.go +++ b/lxd/device/nic_ipvlan.go @@ -34,6 +34,8 @@ func (d *nicIPVLAN) validateConfig(instConf instance.ConfigReader) error { "vlan", "ipv4.gateway", "ipv6.gateway", + "ipv4.host_table", + "ipv6.host_table", } rules := nicValidationRules(requiredFields, optionalFields) @@ -202,6 +204,7 @@ func (d *nicIPVLAN) Start() (*deviceConfig.RunConfig, error) { } runConf.NetworkInterface = nic + runConf.PostHooks = append(runConf.PostHooks, d.postStart) return &runConf, nil } @@ -236,6 +239,39 @@ func (d *nicIPVLAN) setupParentSysctls(parentName string) error { return nil } +// postStart is run after the instance is started. +func (d *nicIPVLAN) postStart() error { + if d.config["ipv4.address"] != "" { + // Add static routes to instance IPs to custom routing tables if specified. + // This is in addition to the static route added by liblxc to the main routing table. + if d.config["ipv4.host_table"] != "" { + for _, addr := range strings.Split(d.config["ipv4.address"], ",") { + addr = strings.TrimSpace(addr) + _, err := shared.RunCommand("ip", "-4", "route", "add", "table", d.config["ipv4.host_table"], fmt.Sprintf("%s/32", addr), "dev", "lo") + if err != nil { + return err + } + } + } + } + + if d.config["ipv6.address"] != "" { + // Add static routes to instance IPs to custom routing tables if specified. + // This is in addition to the static route added by liblxc to the main routing table. + if d.config["ipv6.host_table"] != "" { + for _, addr := range strings.Split(d.config["ipv6.address"], ",") { + addr = strings.TrimSpace(addr) + _, err := shared.RunCommand("ip", "-6", "route", "add", "table", d.config["ipv6.host_table"], fmt.Sprintf("%s/128", addr), "dev", "lo") + if err != nil { + return err + } + } + } + } + + return nil +} + // Stop is run when the device is removed from the instance. func (d *nicIPVLAN) Stop() (*deviceConfig.RunConfig, error) { runConf := deviceConfig.RunConfig{ @@ -253,6 +289,32 @@ func (d *nicIPVLAN) postStop() error { v := d.volatileGet() + if d.config["ipv4.address"] != "" { + // Remove static routes to instance IPs to custom routing tables if specified. + if d.config["ipv4.host_table"] != "" { + for _, addr := range strings.Split(d.config["ipv4.address"], ",") { + addr = strings.TrimSpace(addr) + _, err := shared.RunCommand("ip", "-4", "route", "delete", "table", d.config["ipv4.host_table"], fmt.Sprintf("%s/32", addr), "dev", "lo") + if err != nil { + return err + } + } + } + } + + if d.config["ipv6.address"] != "" { + // Remove static routes to instance IPs to custom routing tables if specified. + if d.config["ipv6.host_table"] != "" { + for _, addr := range strings.Split(d.config["ipv6.address"], ",") { + addr = strings.TrimSpace(addr) + _, err := shared.RunCommand("ip", "-6", "route", "delete", "table", d.config["ipv6.host_table"], fmt.Sprintf("%s/128", addr), "dev", "lo") + if err != nil { + return err + } + } + } + } + // This will delete the parent interface if we created it for VLAN parent. if shared.IsTrue(v["last_state.created"]) { parentName := network.GetHostDevice(d.config["parent"], d.config["vlan"]) From 1abd0dce0572f9e1ad3a1add7124a0a77ac68a0c Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Wed, 15 Apr 2020 11:50:44 +0100 Subject: [PATCH 3/9] api: Adds container_nic_ipvlan_host_table extension Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- doc/api-extensions.md | 4 ++++ shared/version/api.go | 1 + 2 files changed, 5 insertions(+) diff --git a/doc/api-extensions.md b/doc/api-extensions.md index bdd514d9b3..15ab193712 100644 --- a/doc/api-extensions.md +++ b/doc/api-extensions.md @@ -1017,3 +1017,7 @@ Those are taken from the os-release data on the system. ## container\_nic\_routed\_host\_table This introduces the `ipv4.host_table` and `ipv6.host_table` NIC config keys that can be used to add static routes for the instance's IPs to a custom policy routing table by ID. + +## container\_nic\_ipvlan\_host\_table +This introduces the `ipv4.host_table` and `ipv6.host_table` NIC config keys that can be used to add static routes +for the instance's IPs to a custom policy routing table by ID. diff --git a/shared/version/api.go b/shared/version/api.go index 6c914d1aeb..9673f9cce5 100644 --- a/shared/version/api.go +++ b/shared/version/api.go @@ -206,6 +206,7 @@ var APIExtensions = []string{ "resources_cpu_core_die", "api_os", "container_nic_routed_host_table", + "container_nic_ipvlan_host_table", } // APIExtensionsCount returns the number of available API extensions. From cdb44209fa6094a4078a188f6dccd0429f39771a Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Wed, 15 Apr 2020 11:51:36 +0100 Subject: [PATCH 4/9] doc: Adds documentation for ipvlan NIC host_table setting Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- doc/instances.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/instances.md b/doc/instances.md index 0fa86f16a5..75da20da02 100644 --- a/doc/instances.md +++ b/doc/instances.md @@ -371,8 +371,10 @@ mtu | integer | parent MTU | no | The MTU of hwaddr | string | randomly assigned | no | The MAC address of the new interface ipv4.address | string | - | no | Comma delimited list of IPv4 static addresses to add to the instance ipv4.gateway | string | auto | no | Whether to add an automatic default IPv4 gateway, can be "auto" or "none" +ipv4.host\_table | integer | - | no | The custom policy routing table ID to add IPv4 static routes to (in addition to main routing table). ipv6.address | string | - | no | Comma delimited list of IPv6 static addresses to add to the instance ipv6.gateway | string | auto | no | Whether to add an automatic default IPv6 gateway, can be "auto" or "none" +ipv6.host\_table | integer | - | no | The custom policy routing table ID to add IPv6 static routes to (in addition to main routing table). vlan | integer | - | no | The VLAN ID to attach to #### nictype: p2p From c7f991d03c6102bf1e738ffbdd2be8e988698088 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Wed, 15 Apr 2020 16:01:58 +0100 Subject: [PATCH 5/9] test/suites/container/devices/nic/ipvlan: Adds tests for custom routing tables Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- test/suites/container_devices_nic_ipvlan.sh | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/test/suites/container_devices_nic_ipvlan.sh b/test/suites/container_devices_nic_ipvlan.sh index 3e82618ea1..23473801e7 100644 --- a/test/suites/container_devices_nic_ipvlan.sh +++ b/test/suites/container_devices_nic_ipvlan.sh @@ -64,9 +64,11 @@ test_container_devices_nic_ipvlan() { lxc exec "${ctName}2" -- ping6 -c2 -W1 "2001:db8::1${ipRand}" lxc stop -f "${ctName}2" - # Check IPVLAN ontop of VLAN parent. + # Check IPVLAN ontop of VLAN parent with custom routing tables. lxc stop -f "${ctName}" lxc config device set "${ctName}" eth0 vlan 1234 + lxc config device set "${ctName}" eth0 ipv4.host_table=100 + lxc config device set "${ctName}" eth0 ipv6.host_table=101 lxc start "${ctName}" # Check VLAN interface created @@ -75,6 +77,10 @@ test_container_devices_nic_ipvlan() { false fi + # Check static routes added to custom routing table + ip -4 route show table 100 | grep "192.0.2.1${ipRand}" + ip -6 route show table 101 | grep "2001:db8::1${ipRand}" + # Check volatile cleanup on stop. lxc stop -f "${ctName}" if lxc config show "${ctName}" | grep volatile.eth0 | grep -v volatile.eth0.hwaddr | grep -v volatile.eth0.name ; then @@ -88,6 +94,10 @@ test_container_devices_nic_ipvlan() { false fi + # Check static routes are removed from custom routing table + ! ip -4 route show table 100 | grep "192.0.2.1${ipRand}" + ! ip -6 route show table 101 | grep "2001:db8::1${ipRand}" + # Check we haven't left any NICS lying around. endNicCount=$(find /sys/class/net | wc -l) if [ "$startNicCount" != "$endNicCount" ]; then From dd96e8558053f697a4b91abf5d2250a3821adcf9 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Wed, 15 Apr 2020 14:59:07 +0100 Subject: [PATCH 6/9] api: Adds container_nic_ipvlan_mode extension Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- doc/api-extensions.md | 9 +++++++++ shared/version/api.go | 1 + 2 files changed, 10 insertions(+) diff --git a/doc/api-extensions.md b/doc/api-extensions.md index 15ab193712..60779580ee 100644 --- a/doc/api-extensions.md +++ b/doc/api-extensions.md @@ -1021,3 +1021,12 @@ for the instance's IPs to a custom policy routing table by ID. ## container\_nic\_ipvlan\_host\_table This introduces the `ipv4.host_table` and `ipv6.host_table` NIC config keys that can be used to add static routes for the instance's IPs to a custom policy routing table by ID. + +## container\_nic\_ipvlan\_mode +This introduces the `mode` NIC config key that can be used to switch the `ipvlan` mode into either `l2` or `l3s`. +If not specified, the default value is `l3s` (which is the old behavior). + +In `l2` mode the `ipv4.address` and `ipv6.address` keys will accept addresses in either CIDR or singular formats. +If singular format is used, the default subnet size is taken to be /24 and /64 for IPv4 and IPv6 respectively. + +In `l2` mode the `ipv4.gateway` and `ipv6.gateway` keys accept only a singular IP address. diff --git a/shared/version/api.go b/shared/version/api.go index 9673f9cce5..e7a86874cf 100644 --- a/shared/version/api.go +++ b/shared/version/api.go @@ -207,6 +207,7 @@ var APIExtensions = []string{ "api_os", "container_nic_routed_host_table", "container_nic_ipvlan_host_table", + "container_nic_ipvlan_mode", } // APIExtensionsCount returns the number of available API extensions. From 9639d283ef3f44c42f59bf63de3b24515efe2eed Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Wed, 15 Apr 2020 14:59:38 +0100 Subject: [PATCH 7/9] lxd/device/nic/ipvlan: Adds support for l2 mode Fixes #6964 Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/device/nic_ipvlan.go | 144 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 136 insertions(+), 8 deletions(-) diff --git a/lxd/device/nic_ipvlan.go b/lxd/device/nic_ipvlan.go index 6d55826f89..86dda86fa7 100644 --- a/lxd/device/nic_ipvlan.go +++ b/lxd/device/nic_ipvlan.go @@ -2,6 +2,7 @@ package device import ( "fmt" + "net" "strings" deviceConfig "github.com/lxc/lxd/lxd/device/config" @@ -12,6 +13,9 @@ import ( "github.com/lxc/lxd/shared" ) +const ipvlanModeL3S = "l3s" +const ipvlanModeL2 = "l2" + type nicIPVLAN struct { deviceCommon } @@ -44,6 +48,28 @@ func (d *nicIPVLAN) validateConfig(instConf instance.ConfigReader) error { return nil } + if d.config["mode"] == ipvlanModeL2 { + for _, v := range strings.Split(value, ",") { + v = strings.TrimSpace(v) + + // If valid non-CIDR address specified, append a /24 subnet. + if NetworkValidAddressV4(v) == nil { + v = fmt.Sprintf("%s/24", v) + } + + ip, _, err := net.ParseCIDR(v) + if err != nil { + return err + } + + if ip.To4() == nil { + return fmt.Errorf("Not an IPv4 CIDR address: %s", v) + } + } + + return nil + } + return NetworkValidAddressV4List(value) } rules["ipv6.address"] = func(value string) error { @@ -51,14 +77,70 @@ func (d *nicIPVLAN) validateConfig(instConf instance.ConfigReader) error { return nil } + if d.config["mode"] == ipvlanModeL2 { + for _, v := range strings.Split(value, ",") { + v = strings.TrimSpace(v) + + // If valid non-CIDR address specified, append a /64 subnet. + if NetworkValidAddressV6(v) == nil { + v = fmt.Sprintf("%s/64", v) + } + + ip, _, err := net.ParseCIDR(v) + if err != nil { + return err + } + + if ip == nil || ip.To4() != nil { + return fmt.Errorf("Not an IPv6 CIDR address: %s", v) + } + } + + return nil + } + return NetworkValidAddressV6List(value) } + rules["mode"] = func(value string) error { + if value == "" { + return nil + } + + validModes := []string{ipvlanModeL3S, ipvlanModeL2} + if !shared.StringInSlice(value, validModes) { + return fmt.Errorf("Must be one of: %v", strings.Join(validModes, ", ")) + } + + return nil + } + + if d.config["mode"] == ipvlanModeL2 { + rules["ipv4.gateway"] = func(value string) error { + if value == "" { + return nil + } + + return NetworkValidAddressV4(value) + } + + rules["ipv6.gateway"] = func(value string) error { + if value == "" { + return nil + } + + return NetworkValidAddressV6(value) + } + } err := d.config.Validate(rules) if err != nil { return err } + if d.config["mode"] == ipvlanModeL2 && d.config["host_table"] != "" { + return fmt.Errorf("host_table option cannot be used in l2 mode") + } + return nil } @@ -81,6 +163,11 @@ func (d *nicIPVLAN) validateEnvironment() error { return fmt.Errorf("The vlan setting can only be used when combined with a parent interface") } + // Only check sysctls for l2proxy if mode is l3s. + if d.mode() != ipvlanModeL3S { + return nil + } + // Generate effective parent name, including the VLAN part if option used. effectiveParentName := network.GetHostDevice(d.config["parent"], d.config["vlan"]) @@ -153,8 +240,10 @@ func (d *nicIPVLAN) Start() (*deviceConfig.RunConfig, error) { // Record whether we created this device or not so it can be removed on stop. saveData["last_state.created"] = fmt.Sprintf("%t", statusDev != "existing") - // If we created a VLAN interface, we need to setup the sysctls on that interface. - if statusDev == "created" { + mode := d.mode() + + // If we created a VLAN interface, we need to setup the sysctls on that interface for l3s mode l2proxy. + if statusDev == "created" && mode == ipvlanModeL3S { err := d.setupParentSysctls(parentName) if err != nil { return nil, err @@ -171,12 +260,16 @@ func (d *nicIPVLAN) Start() (*deviceConfig.RunConfig, error) { {Key: "name", Value: d.config["name"]}, {Key: "type", Value: "ipvlan"}, {Key: "flags", Value: "up"}, - {Key: "ipvlan.mode", Value: "l3s"}, + {Key: "ipvlan.mode", Value: mode}, {Key: "ipvlan.isolation", Value: "bridge"}, - {Key: "l2proxy", Value: "1"}, {Key: "link", Value: parentName}, } + // Enable l2proxy for l3s mode. + if mode == ipvlanModeL3S { + nic = append(nic, deviceConfig.RunConfigItem{Key: "l2proxy", Value: "1"}) + } + if d.config["mtu"] != "" { nic = append(nic, deviceConfig.RunConfigItem{Key: "mtu", Value: d.config["mtu"]}) } @@ -184,23 +277,49 @@ func (d *nicIPVLAN) Start() (*deviceConfig.RunConfig, error) { if d.config["ipv4.address"] != "" { for _, addr := range strings.Split(d.config["ipv4.address"], ",") { addr = strings.TrimSpace(addr) - nic = append(nic, deviceConfig.RunConfigItem{Key: "ipv4.address", Value: fmt.Sprintf("%s/32", addr)}) + + if mode == ipvlanModeL3S { + addr = fmt.Sprintf("%s/32", addr) + } + + if mode == ipvlanModeL2 && NetworkValidAddressV4(addr) == nil { + addr = fmt.Sprintf("%s/24", addr) + } + + nic = append(nic, deviceConfig.RunConfigItem{Key: "ipv4.address", Value: addr}) } - if nicHasAutoGateway(d.config["ipv4.gateway"]) { + if mode == ipvlanModeL3S && nicHasAutoGateway(d.config["ipv4.gateway"]) { nic = append(nic, deviceConfig.RunConfigItem{Key: "ipv4.gateway", Value: "dev"}) } + + if mode == ipvlanModeL2 && d.config["ipv4.gateway"] != "" { + nic = append(nic, deviceConfig.RunConfigItem{Key: "ipv4.gateway", Value: d.config["ipv4.gateway"]}) + } } if d.config["ipv6.address"] != "" { for _, addr := range strings.Split(d.config["ipv6.address"], ",") { addr = strings.TrimSpace(addr) - nic = append(nic, deviceConfig.RunConfigItem{Key: "ipv6.address", Value: fmt.Sprintf("%s/128", addr)}) + + if mode == ipvlanModeL3S { + addr = fmt.Sprintf("%s/128", addr) + } + + if mode == "l2" && NetworkValidAddressV6(addr) == nil { + addr = fmt.Sprintf("%s/64", addr) + } + + nic = append(nic, deviceConfig.RunConfigItem{Key: "ipv6.address", Value: addr}) } - if nicHasAutoGateway(d.config["ipv6.gateway"]) { + if mode == ipvlanModeL3S && nicHasAutoGateway(d.config["ipv6.gateway"]) { nic = append(nic, deviceConfig.RunConfigItem{Key: "ipv6.gateway", Value: "dev"}) } + + if mode == ipvlanModeL2 && d.config["ipv6.gateway"] != "" { + nic = append(nic, deviceConfig.RunConfigItem{Key: "ipv6.gateway", Value: d.config["ipv6.gateway"]}) + } } runConf.NetworkInterface = nic @@ -326,3 +445,12 @@ func (d *nicIPVLAN) postStop() error { return nil } + +// mode returns the ipvlan mode to use. +func (d *nicIPVLAN) mode() string { + if d.config["mode"] == ipvlanModeL2 { + return ipvlanModeL2 + } + + return ipvlanModeL3S +} From c4187cc593d689ef6b25b8e544b73dcd241a1cf1 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Wed, 15 Apr 2020 15:02:33 +0100 Subject: [PATCH 8/9] doc/instances: Documents ipvlan l2 mode Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- doc/instances.md | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/doc/instances.md b/doc/instances.md index 75da20da02..7736100693 100644 --- a/doc/instances.md +++ b/doc/instances.md @@ -363,19 +363,20 @@ net.ipv6.conf.<parent>.proxy_ndp=1 Device configuration properties: -Key | Type | Default | Required | Description -:-- | :-- | :-- | :-- | :-- -parent | string | - | yes | The name of the host device -name | string | kernel assigned | no | The name of the interface inside the instance -mtu | integer | parent MTU | no | The MTU of the new interface -hwaddr | string | randomly assigned | no | The MAC address of the new interface -ipv4.address | string | - | no | Comma delimited list of IPv4 static addresses to add to the instance -ipv4.gateway | string | auto | no | Whether to add an automatic default IPv4 gateway, can be "auto" or "none" -ipv4.host\_table | integer | - | no | The custom policy routing table ID to add IPv4 static routes to (in addition to main routing table). -ipv6.address | string | - | no | Comma delimited list of IPv6 static addresses to add to the instance -ipv6.gateway | string | auto | no | Whether to add an automatic default IPv6 gateway, can be "auto" or "none" -ipv6.host\_table | integer | - | no | The custom policy routing table ID to add IPv6 static routes to (in addition to main routing table). -vlan | integer | - | no | The VLAN ID to attach to +Key | Type | Default | Required | Description +:-- | :-- | :-- | :-- | :-- +parent | string | - | yes | The name of the host device +name | string | kernel assigned | no | The name of the interface inside the instance +mtu | integer | parent MTU | no | The MTU of the new interface +mode | string | l3s | no | The IPVLAN mode (either `l2` or `l3s`) +hwaddr | string | randomly assigned | no | The MAC address of the new interface +ipv4.address | string | - | no | Comma delimited list of IPv4 static addresses to add to the instance. In `l2` mode these can be specified as CIDR values or singular addresses (if singular a subnet of /24 is used). +ipv4.gateway | string | auto | no | In `l3s` mode, whether to add an automatic default IPv4 gateway, can be `auto` or `none`. In `l2` mode specifies the IPv4 address of the gateway. +ipv4.host\_table | integer | - | no | The custom policy routing table ID to add IPv4 static routes to (in addition to main routing table). +ipv6.address | string | - | no | Comma delimited list of IPv6 static addresses to add to the instance. In `l2` mode these can be specified as CIDR values or singular addresses (if singular a subnet of /64 is used). +ipv6.gateway | string | auto (l3s), - (l2) | no | In `l3s` mode, whether to add an automatic default IPv6 gateway, can be `auto` or `none`. In `l2` mode specifies the IPv6 address of the gateway. +ipv6.host\_table | integer | - | no | The custom policy routing table ID to add IPv6 static routes to (in addition to main routing table). +vlan | integer | - | no | The VLAN ID to attach to #### nictype: p2p From 98cee553e03d49a3da0ad49895d756962d14fe08 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Wed, 15 Apr 2020 16:37:42 +0100 Subject: [PATCH 9/9] test/suites/container/devices/nic/ipvlan: Adds l2 mode tests Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- test/suites/container_devices_nic_ipvlan.sh | 46 +++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/test/suites/container_devices_nic_ipvlan.sh b/test/suites/container_devices_nic_ipvlan.sh index 23473801e7..eec9cdbeda 100644 --- a/test/suites/container_devices_nic_ipvlan.sh +++ b/test/suites/container_devices_nic_ipvlan.sh @@ -26,6 +26,8 @@ test_container_devices_nic_ipvlan() { parent=${ctName} \ ipv4.address="192.0.2.1${ipRand}" \ ipv6.address="2001:db8::1${ipRand}" \ + ipv4.gateway=auto \ + ipv6.gateway=auto \ mtu=1400 lxc start "${ctName}" @@ -69,6 +71,11 @@ test_container_devices_nic_ipvlan() { lxc config device set "${ctName}" eth0 vlan 1234 lxc config device set "${ctName}" eth0 ipv4.host_table=100 lxc config device set "${ctName}" eth0 ipv6.host_table=101 + + # Check gateway settings don't accept IPs in default l3s mode. + ! lxc config device set "${ctName}" eth0 ipv4.gateway=192.0.2.254 + ! lxc config device set "${ctName}" eth0 ipv6.gateway=2001:db8::FFFF + lxc start "${ctName}" # Check VLAN interface created @@ -98,6 +105,45 @@ test_container_devices_nic_ipvlan() { ! ip -4 route show table 100 | grep "192.0.2.1${ipRand}" ! ip -6 route show table 101 | grep "2001:db8::1${ipRand}" + # Check ipvlan l2 mode with mixture of singular and CIDR IPs, and gateway IPs. + lxc config device remove "${ctName}" eth0 + lxc config device add "${ctName}" eth0 nic \ + nictype=ipvlan \ + mode=l2 \ + parent=${ctName} \ + ipv4.address="192.0.2.1${ipRand},192.0.2.2${ipRand}/32" \ + ipv6.address="2001:db8::1${ipRand},2001:db8::2${ipRand}/128" \ + ipv4.gateway=192.0.2.254 \ + ipv6.gateway=2001:db8::FFFF \ + mtu=1400 + lxc start "${ctName}" + + lxc config device remove "${ctName}2" eth0 + lxc config device add "${ctName}2" eth0 nic \ + nictype=ipvlan \ + parent=${ctName} \ + ipv4.address="192.0.2.3${ipRand}" \ + ipv6.address="2001:db8::3${ipRand}" \ + mtu=1400 + lxc start "${ctName}2" + + # Add an internally configured address (only possible in l2 mode). + lxc exec "${ctName}2" -- ip -4 addr add "192.0.2.4${ipRand}/32" dev eth0 + lxc exec "${ctName}2" -- ip -6 addr add "2001:db8::4${ipRand}/128" dev eth0 + + # Check comms between containers. + lxc exec "${ctName}" -- ping -c2 -W1 "192.0.2.3${ipRand}" + lxc exec "${ctName}" -- ping -c2 -W1 "192.0.2.4${ipRand}" + lxc exec "${ctName}" -- ping6 -c2 -W1 "2001:db8::3${ipRand}" + lxc exec "${ctName}" -- ping6 -c2 -W1 "2001:db8::4${ipRand}" + lxc exec "${ctName}2" -- ping -c2 -W1 "192.0.2.1${ipRand}" + lxc exec "${ctName}2" -- ping -c2 -W1 "192.0.2.2${ipRand}" + lxc exec "${ctName}2" -- ping6 -c2 -W1 "2001:db8::1${ipRand}" + lxc exec "${ctName}2" -- ping6 -c2 -W1 "2001:db8::2${ipRand}" + + lxc stop -f "${ctName}" + lxc stop -f "${ctName}2" + # Check we haven't left any NICS lying around. endNicCount=$(find /sys/class/net | wc -l) if [ "$startNicCount" != "$endNicCount" ]; then
_______________________________________________ lxc-devel mailing list lxc-devel@lists.linuxcontainers.org http://lists.linuxcontainers.org/listinfo/lxc-devel