[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