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

Reply via email to