[lxc-devel] [lxd/master] NIC: Bridged VLAN support

tomponline on Github lxc-bot at linuxcontainers.org
Thu May 28 16:32:58 UTC 2020


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 304 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20200528/d50c5595/attachment-0001.bin>
-------------- next part --------------
From 979d50b45260307af029bb90a4554ec0b6c4165c Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 28 May 2020 15:17:56 +0100
Subject: [PATCH 1/6] lxd/device/device/utils/network: Adds networkValidVLAN
 and networkValidVLANList functions

Validates a VLAN ID is a number and in usable range.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/device_utils_network.go | 27 +++++++++++++++++++++++++++
 1 file changed, 27 insertions(+)

diff --git a/lxd/device/device_utils_network.go b/lxd/device/device_utils_network.go
index 24dea703b4..0558635f9c 100644
--- a/lxd/device/device_utils_network.go
+++ b/lxd/device/device_utils_network.go
@@ -692,6 +692,33 @@ func NetworkValidGateway(value string) error {
 	return fmt.Errorf("Invalid gateway: %s", value)
 }
 
+// networkValidVLAN validates a VLAN ID.
+func networkValidVLAN(value string) error {
+	vlanID, err := strconv.Atoi(value)
+	if err != nil {
+		return fmt.Errorf("Invalid VLAN ID: %s", value)
+	}
+
+	if vlanID < 1 || vlanID > 4094 {
+		return fmt.Errorf("Out of range (1-4094) VLAN ID: %s", value)
+	}
+
+	return nil
+}
+
+// networkValidVLANList validates a comma delimited list of VLAN IDs.
+func networkValidVLANList(value string) error {
+	for _, vlanID := range strings.Split(value, ",") {
+		vlanID = strings.TrimSpace(vlanID)
+		err := networkValidVLAN(vlanID)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
 // networkParsePortRange validates a port range in the form n-n.
 func networkParsePortRange(r string) (int64, int64, error) {
 	entries := strings.Split(r, "-")

From 80733e9ae48006719a8638499633bda84ce6dc70 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 28 May 2020 15:18:48 +0100
Subject: [PATCH 2/6] lxd/device/nic/bridged: Adds vlan.tagged config
 validation

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/nic_bridged.go | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)

diff --git a/lxd/device/nic_bridged.go b/lxd/device/nic_bridged.go
index 870155717e..b424fc1477 100644
--- a/lxd/device/nic_bridged.go
+++ b/lxd/device/nic_bridged.go
@@ -137,8 +137,19 @@ func (d *nicBridged) validateConfig(instConf instance.ConfigReader) error {
 		requiredFields = append(requiredFields, "parent")
 	}
 
+	rules := nicValidationRules(requiredFields, optionalFields)
+
+	// Add bridge specific vlan.tagged validation.
+	rules["vlan.tagged"] = func(value string) error {
+		if value == "" {
+			return nil
+		}
+
+		return networkValidVLANList(value)
+	}
+
 	// Now run normal validation.
-	err := d.config.Validate(nicValidationRules(requiredFields, optionalFields))
+	err := d.config.Validate(rules)
 	if err != nil {
 		return err
 	}

From 9baaa1a374d40f3e5a279275ef984da11065a0df Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 28 May 2020 15:30:52 +0100
Subject: [PATCH 3/6] lxd/device/nic: Changes nicValidationRules to properly
 validation vlan

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/nic.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/device/nic.go b/lxd/device/nic.go
index 2195cc12b0..01dc1918f8 100644
--- a/lxd/device/nic.go
+++ b/lxd/device/nic.go
@@ -33,7 +33,7 @@ func nicValidationRules(requiredFields []string, optionalFields []string) map[st
 		"parent":                  shared.IsAny,
 		"network":                 shared.IsAny,
 		"mtu":                     shared.IsAny,
-		"vlan":                    shared.IsAny,
+		"vlan":                    networkValidVLAN,
 		"hwaddr":                  networkValidMAC,
 		"host_name":               shared.IsAny,
 		"limits.ingress":          shared.IsAny,

From d8db1f69c82271697bb62cfe600ab2536812ca4e Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 28 May 2020 15:31:09 +0100
Subject: [PATCH 4/6] lxd/device/nic/bridged: Adds vlan validation

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/nic_bridged.go | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lxd/device/nic_bridged.go b/lxd/device/nic_bridged.go
index b424fc1477..873fae80d4 100644
--- a/lxd/device/nic_bridged.go
+++ b/lxd/device/nic_bridged.go
@@ -62,6 +62,7 @@ func (d *nicBridged) validateConfig(instConf instance.ConfigReader) error {
 		"maas.subnet.ipv4",
 		"maas.subnet.ipv6",
 		"boot.priority",
+		"vlan",
 	}
 
 	// Check that if network proeperty is set that conflicting keys are not present.

From 7f83ac5c27eaf095f234157fe8df1dee14c374f2 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 28 May 2020 15:43:22 +0100
Subject: [PATCH 5/6] lxd/device/nic/bridged: Adds revert for veth pair cleanup
 on error

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/nic_bridged.go | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/lxd/device/nic_bridged.go b/lxd/device/nic_bridged.go
index 873fae80d4..b6d09e6421 100644
--- a/lxd/device/nic_bridged.go
+++ b/lxd/device/nic_bridged.go
@@ -25,6 +25,7 @@ import (
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/instance/instancetype"
 	"github.com/lxc/lxd/lxd/network"
+	"github.com/lxc/lxd/lxd/revert"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	log "github.com/lxc/lxd/shared/log15"
@@ -195,6 +196,9 @@ func (d *nicBridged) Start() (*deviceConfig.RunConfig, error) {
 		return nil, err
 	}
 
+	revert := revert.New()
+	defer revert.Fail()
+
 	saveData := make(map[string]string)
 	saveData["host_name"] = d.config["host_name"]
 
@@ -218,10 +222,11 @@ func (d *nicBridged) Start() (*deviceConfig.RunConfig, error) {
 		return nil, err
 	}
 
+	revert.Add(func() { NetworkRemoveInterface(saveData["host_name"]) })
+
 	// Apply and host-side limits and routes.
 	err = networkSetupHostVethDevice(d.config, nil, saveData)
 	if err != nil {
-		NetworkRemoveInterface(saveData["host_name"])
 		return nil, err
 	}
 
@@ -235,21 +240,19 @@ func (d *nicBridged) Start() (*deviceConfig.RunConfig, error) {
 	// Apply and host-side network filters (uses enriched host_name from networkSetupHostVethDevice).
 	err = d.setupHostFilters(nil)
 	if err != nil {
-		NetworkRemoveInterface(saveData["host_name"])
 		return nil, err
 	}
+	revert.Add(func() { d.removeFilters(d.config) })
 
 	// Attach host side veth interface to bridge.
 	err = network.AttachInterface(d.config["parent"], saveData["host_name"])
 	if err != nil {
-		NetworkRemoveInterface(saveData["host_name"])
 		return nil, err
 	}
 
 	// Attempt to disable router advertisement acceptance.
 	err = util.SysctlSet(fmt.Sprintf("net/ipv6/conf/%s/accept_ra", saveData["host_name"]), "0")
 	if err != nil && !os.IsNotExist(err) {
-		NetworkRemoveInterface(saveData["host_name"])
 		return nil, err
 	}
 
@@ -274,6 +277,7 @@ func (d *nicBridged) Start() (*deviceConfig.RunConfig, error) {
 			}...)
 	}
 
+	revert.Success()
 	return &runConf, nil
 }
 

From 77b89b5c250cbf7ea0bb77352a34b6cabde70999 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 28 May 2020 17:10:56 +0100
Subject: [PATCH 6/6] lxd/device/nic/bridged: Adds support for untagged and
 tagged vlan membership

lxc config device set c1 eth0 vlan=2

Means:

 Remove from vlan 1 (or whatever the default vlan is for that bridge).
 Set PVID to vlan 2 for untagged ingress.
 Add to vlan 2 as untagged egress.

lxc config device set c1 eth0 vlan.tagged=3,4,5

Means:

 Add NIC port as trunk to bridge, with membership of VLANs 3, 4 and 5.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/nic_bridged.go | 81 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 81 insertions(+)

diff --git a/lxd/device/nic_bridged.go b/lxd/device/nic_bridged.go
index b6d09e6421..7c868deabf 100644
--- a/lxd/device/nic_bridged.go
+++ b/lxd/device/nic_bridged.go
@@ -6,6 +6,7 @@ import (
 	"encoding/binary"
 	"encoding/hex"
 	"fmt"
+	"io/ioutil"
 	"math/big"
 	"math/rand"
 	"net"
@@ -256,6 +257,12 @@ func (d *nicBridged) Start() (*deviceConfig.RunConfig, error) {
 		return nil, err
 	}
 
+	// Setup VLAN settings on bridge port.
+	err = d.setupBridgePortVLANs(saveData["host_name"])
+	if err != nil {
+		return nil, err
+	}
+
 	err = d.volatileSet(saveData)
 	if err != nil {
 		return nil, err
@@ -1159,3 +1166,77 @@ func (d *nicBridged) networkDHCPv6CreateIAAddress(IP net.IP) []byte {
 	binary.BigEndian.PutUint32(data[24:28], uint32(0))                    // Valid lifetime
 	return data
 }
+
+// setupBridgePortVLANs configures the bridge port with the specified VLAN settings in device config.
+func (d *nicBridged) setupBridgePortVLANs(hostName string) error {
+	// Enable vlan_filtering on bridge if needed.
+	if d.config["vlan"] != "" || d.config["vlan.tagged"] != "" {
+		vlanFilteringStatus, err := d.bridgeVLANFilteringStatus()
+		if err != nil {
+			return err
+		}
+
+		if vlanFilteringStatus != "1" {
+			err := ioutil.WriteFile(fmt.Sprintf("/sys/class/net/%s/bridge/vlan_filtering", d.config["parent"]), []byte("1"), 0)
+			if err != nil {
+				return errors.Wrapf(err, "Failed enabling VLAN filtering on bridge %q", d.config["parent"])
+			}
+		}
+	}
+
+	// Set port on bridge to specified untagged PVID.
+	if d.config["vlan"] != "" {
+		// Get default PVID membership on port.
+		defaultPVID, err := d.bridgeVLANDefaultPVID()
+		if err != nil {
+			return err
+		}
+
+		// If the default is different to the specified untagged VLAN remove its membership.
+		if defaultPVID != d.config["vlan"] {
+			_, err = shared.RunCommand("bridge", "vlan", "del", "dev", hostName, "vid", defaultPVID)
+			if err != nil {
+				return err
+			}
+		}
+
+		// Configure the untagged membership of the port.
+		_, err = shared.RunCommand("bridge", "vlan", "add", "dev", hostName, "vid", d.config["vlan"], "pvid", "untagged", "master")
+		if err != nil {
+			return err
+		}
+	}
+
+	// Add any tagged VLAN memberships.
+	if d.config["vlan.tagged"] != "" {
+		for _, vlanID := range strings.Split(d.config["vlan.tagged"], ",") {
+			vlanID = strings.TrimSpace(vlanID)
+			_, err := shared.RunCommand("bridge", "vlan", "add", "dev", hostName, "vid", vlanID)
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	return nil
+}
+
+// bridgeVLANFilteringStatus returns whether VLAN filtering is enabled on the bridge.
+func (d *nicBridged) bridgeVLANFilteringStatus() (string, error) {
+	content, err := ioutil.ReadFile(fmt.Sprintf("/sys/class/net/%s/bridge/vlan_filtering", d.config["parent"]))
+	if err != nil {
+		return "", errors.Wrapf(err, "Failed getting bridge VLAN status for %q", d.config["parent"])
+	}
+
+	return strings.TrimSpace(fmt.Sprintf("%s", content)), nil
+}
+
+// bridgeVLANDefaultPVID returns the VLAN default port VLAN ID (PVID).
+func (d *nicBridged) bridgeVLANDefaultPVID() (string, error) {
+	content, err := ioutil.ReadFile(fmt.Sprintf("/sys/class/net/%s/bridge/default_pvid", d.config["parent"]))
+	if err != nil {
+		return "", errors.Wrapf(err, "Failed getting bridge VLAN default PVID for %q", d.config["parent"])
+	}
+
+	return strings.TrimSpace(fmt.Sprintf("%s", content)), nil
+}


More information about the lxc-devel mailing list