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

tomponline on Github lxc-bot at linuxcontainers.org
Tue Jun 2 13:48:48 UTC 2020


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 343 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20200602/2c6722a6/attachment.bin>
-------------- next part --------------
From 59ccc7ca5463727c6b5684855c4a3d73a2f22bac Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Tue, 2 Jun 2020 10:50:08 +0100
Subject: [PATCH 1/4] lxd/network/utils: Adds IsNativeBridge function

To isolate the logic used for detecting a native linux bridge in a single function for re-use.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/network/network_utils.go | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/lxd/network/network_utils.go b/lxd/network/network_utils.go
index bb56b2b187..14fe9230df 100644
--- a/lxd/network/network_utils.go
+++ b/lxd/network/network_utils.go
@@ -92,9 +92,14 @@ func GetIP(subnet *net.IPNet, host int64) net.IP {
 	return newIP
 }
 
+// IsNativeBridge returns whether the bridge name specified is a Linux native bridge.
+func IsNativeBridge(bridgeName string) bool {
+	return shared.PathExists(fmt.Sprintf("/sys/class/net/%s/bridge", bridgeName))
+}
+
 // AttachInterface attaches an interface to a bridge.
 func AttachInterface(bridgeName string, devName string) error {
-	if shared.PathExists(fmt.Sprintf("/sys/class/net/%s/bridge", bridgeName)) {
+	if IsNativeBridge(bridgeName) {
 		_, err := shared.RunCommand("ip", "link", "set", "dev", devName, "master", bridgeName)
 		if err != nil {
 			return err
@@ -115,7 +120,7 @@ func AttachInterface(bridgeName string, devName string) error {
 
 // DetachInterface detaches an interface from a bridge.
 func DetachInterface(bridgeName string, devName string) error {
-	if shared.PathExists(fmt.Sprintf("/sys/class/net/%s/bridge", bridgeName)) {
+	if IsNativeBridge(bridgeName) {
 		_, err := shared.RunCommand("ip", "link", "set", "dev", devName, "nomaster")
 		if err != nil {
 			return err

From a6d92324db52ddf9541c06a29b7db4a418cae8cf Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Tue, 2 Jun 2020 14:39:30 +0100
Subject: [PATCH 2/4] lxd/device/device/utils/network: Allow VLAN ID 0 in
 networkValidVLAN

Openvswitch allows VLAN 0 for untagged frames.

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

diff --git a/lxd/device/device_utils_network.go b/lxd/device/device_utils_network.go
index 0558635f9c..6a15a345d8 100644
--- a/lxd/device/device_utils_network.go
+++ b/lxd/device/device_utils_network.go
@@ -699,8 +699,8 @@ func networkValidVLAN(value string) error {
 		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)
+	if vlanID < 0 || vlanID > 4094 {
+		return fmt.Errorf("Out of range (0-4094) VLAN ID: %s", value)
 	}
 
 	return nil

From 607888d4a827eaa2c9cffd74bcd51140396e8e94 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Tue, 2 Jun 2020 14:40:10 +0100
Subject: [PATCH 3/4] test: Updates bridged vlan ID range tests

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 test/suites/container_devices_nic_bridged_vlan.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/suites/container_devices_nic_bridged_vlan.sh b/test/suites/container_devices_nic_bridged_vlan.sh
index 1713e2c10f..9f717098b5 100644
--- a/test/suites/container_devices_nic_bridged_vlan.sh
+++ b/test/suites/container_devices_nic_bridged_vlan.sh
@@ -71,7 +71,7 @@ test_container_devices_nic_bridged_vlan() {
   ! lxc config device set "${prefix}-ctA" eth0 vlan = 4096 # Check out of range VLAN ID.
   ! lxc config device set "${prefix}-ctA" eth0 vlan = 0 # Check out of range VLAN ID.
   ! lxc config device set "${prefix}-ctA" eth0 vlan.tagged = 5,invalid, 6 # Check invalid VLAN ID list.
-  ! lxc config device set "${prefix}-ctA" eth0 vlan.tagged=0 # Check out of range VLAN ID list.
+  ! lxc config device set "${prefix}-ctA" eth0 vlan.tagged=-1 # Check out of range VLAN ID list.
   ! lxc config device set "${prefix}-ctA" eth0 vlan.tagged=4096 # Check out of range VLAN ID list.
   lxc config device remove "${prefix}-ctA" eth0
 

From ccf2c7e221dd8c91ab2b788f321741e1d8fd16f3 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Tue, 2 Jun 2020 14:41:32 +0100
Subject: [PATCH 4/4] lxd/device/nic/bridged: Adds openvswitch vlan support

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

diff --git a/lxd/device/nic_bridged.go b/lxd/device/nic_bridged.go
index 132c8944e9..a1c48f1e00 100644
--- a/lxd/device/nic_bridged.go
+++ b/lxd/device/nic_bridged.go
@@ -286,8 +286,13 @@ func (d *nicBridged) Start() (*deviceConfig.RunConfig, error) {
 		return nil, err
 	}
 
-	// Setup VLAN settings on bridge port.
-	err = d.setupBridgePortVLANs(saveData["host_name"])
+	// Detech bridge type and setup VLAN settings on bridge port.
+	if network.IsNativeBridge(d.config["parent"]) {
+		err = d.setupNativeBridgePortVLANs(saveData["host_name"])
+	} else {
+		err = d.setupOVSBridgePortVLANs(saveData["host_name"])
+	}
+
 	if err != nil {
 		return nil, err
 	}
@@ -1202,8 +1207,8 @@ func (d *nicBridged) networkDHCPv6CreateIAAddress(IP net.IP) []byte {
 	return data
 }
 
-// setupBridgePortVLANs configures the bridge port with the specified VLAN settings in device config.
-func (d *nicBridged) setupBridgePortVLANs(hostName string) error {
+// setupNativeBridgePortVLANs configures the bridge port with the specified VLAN settings on the native bridge.
+func (d *nicBridged) setupNativeBridgePortVLANs(hostName string) error {
 	// Check vlan_filtering is enabled on bridge if needed.
 	if d.config["vlan"] != "" || d.config["vlan.tagged"] != "" {
 		vlanFilteringStatus, err := network.BridgeVLANFilteringStatus(d.config["parent"])
@@ -1218,6 +1223,11 @@ func (d *nicBridged) setupBridgePortVLANs(hostName string) error {
 
 	// Set port on bridge to specified untagged PVID.
 	if d.config["vlan"] != "" {
+		// Reject VLAN ID 0 if specified (as main validation allows VLAN ID 0 to accomodate ovs).
+		if d.config["vlan"] == "0" {
+			return fmt.Errorf("VLAN ID 0 is not allowed for native Linux bridges")
+		}
+
 		// Get default PVID membership on port.
 		defaultPVID, err := network.BridgeVLANDefaultPVID(d.config["parent"])
 		if err != nil {
@@ -1246,6 +1256,12 @@ func (d *nicBridged) setupBridgePortVLANs(hostName string) error {
 	if d.config["vlan.tagged"] != "" {
 		for _, vlanID := range strings.Split(d.config["vlan.tagged"], ",") {
 			vlanID = strings.TrimSpace(vlanID)
+
+			// Reject VLAN ID 0 if specified (as main validation allows VLAN ID 0 to accomodate ovs).
+			if vlanID == "0" {
+				return fmt.Errorf("VLAN ID 0 is not allowed for native Linux bridges")
+			}
+
 			_, err := shared.RunCommand("bridge", "vlan", "add", "dev", hostName, "vid", vlanID)
 			if err != nil {
 				return err
@@ -1255,3 +1271,51 @@ func (d *nicBridged) setupBridgePortVLANs(hostName string) error {
 
 	return nil
 }
+
+// setupOVSBridgePortVLANs configures the bridge port with the specified VLAN settings on the openvswitch bridge.
+func (d *nicBridged) setupOVSBridgePortVLANs(hostName string) error {
+	// Set port on bridge to specified untagged PVID.
+	if d.config["vlan"] != "" {
+		if d.config["vlan"] == "none" && d.config["vlan.tagged"] == "" {
+			return fmt.Errorf("vlan=none is not supported with openvswitch bridges when not using vlan.tagged")
+		}
+
+		// Configure the untagged 'native' membership settings of the port if VLAN ID specified.
+		// Also set the vlan_mode=access, which will drop any tagged frames.
+		// Order is important here, as vlan_mode is set to "access", assuming that vlan.tagged is not used.
+		// If vlan.tagged is specified, then we expect it to also change the vlan_mode as needed.
+		if d.config["vlan"] != "none" {
+			_, err := shared.RunCommand("ovs-vsctl", "set", "port", hostName, "vlan_mode=access", fmt.Sprintf("tag=%s", d.config["vlan"]))
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	// Add any tagged VLAN memberships.
+	if d.config["vlan.tagged"] != "" {
+		vlanIDs := strings.Split(d.config["vlan.tagged"], ",")
+
+		// Remove any spaces from raw config string.
+		for i, vlanID := range vlanIDs {
+			vlanIDs[i] = strings.TrimSpace(vlanID)
+		}
+
+		vlanMode := "trunk" // Default to only allowing tagged frames (drop untagged frames).
+		if d.config["vlan"] != "none" {
+			// If untagged vlan mode isn't "none" then allow untagged frames for port's 'native' VLAN.
+			vlanMode = "native-untagged"
+		}
+
+		// Configure the tagged membership settings of the port if VLAN ID specified.
+		// Also set the vlan_mode as needed from above.
+		// Must come after the ovs-vsctl command used for setting "vlan" mode above so that the correct
+		// vlan_mode is retained.
+		_, err := shared.RunCommand("ovs-vsctl", "set", "port", hostName, fmt.Sprintf("vlan_mode=%s", vlanMode), fmt.Sprintf("trunks=%s", strings.Join(vlanIDs, ",")))
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}


More information about the lxc-devel mailing list