[lxc-devel] [lxd/master] NIC IPVLAN: Adds l2 mode support

tomponline on Github lxc-bot at linuxcontainers.org
Wed Apr 15 15:46:53 UTC 2020


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 1222 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20200415/c2764cc1/attachment-0001.bin>
-------------- next part --------------
From e1245418b5e590c0dbefebc31922bf1880e7cdc8 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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


More information about the lxc-devel mailing list