[lxc-devel] [lxd/master] lxd/device/nic/routed: Adds veth routed NIC device

tomponline on Github lxc-bot at linuxcontainers.org
Sun Nov 10 16:37:42 UTC 2019


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 508 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20191110/74ced9cf/attachment.bin>
-------------- next part --------------
From a800a1276a62e46649c1b863038d300bc6564136 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Sun, 10 Nov 2019 16:35:25 +0000
Subject: [PATCH] lxd/device/nic/routed: Adds veth routed NIC device

Example usage:

 lxc config device add c1 eth0 nic nictype=routed parent=eth0 ipv4.address=192.168.1.200 ipv6.address=2a02:xxx:xxx:3::200/128

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/daemon.go            |   1 +
 lxd/device/nic.go        |   1 +
 lxd/device/nic_routed.go | 240 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 242 insertions(+)
 create mode 100644 lxd/device/nic_routed.go

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 0de75b098c..2ad1a2d318 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -635,6 +635,7 @@ func (d *Daemon) init() error {
 		"network_l2proxy",
 		"network_gateway_device_route",
 		"network_phys_macvlan_mtu",
+		"network_veth_router",
 	}
 	for _, extension := range lxcExtensions {
 		d.os.LXCFeatures[extension] = lxc.HasApiExtension(extension)
diff --git a/lxd/device/nic.go b/lxd/device/nic.go
index f3b3f80330..7f7f942ebc 100644
--- a/lxd/device/nic.go
+++ b/lxd/device/nic.go
@@ -11,6 +11,7 @@ var nicTypes = map[string]func() device{
 	"ipvlan":   func() device { return &nicIPVLAN{} },
 	"p2p":      func() device { return &nicP2P{} },
 	"bridged":  func() device { return &nicBridged{} },
+	"routed":   func() device { return &nicRouted{} },
 	"macvlan":  func() device { return &nicMACVLAN{} },
 	"sriov":    func() device { return &nicSRIOV{} },
 }
diff --git a/lxd/device/nic_routed.go b/lxd/device/nic_routed.go
new file mode 100644
index 0000000000..f838e49e42
--- /dev/null
+++ b/lxd/device/nic_routed.go
@@ -0,0 +1,240 @@
+package device
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/lxc/lxd/lxd/instance/instancetype"
+	"github.com/lxc/lxd/shared"
+)
+
+type nicRouted struct {
+	deviceCommon
+}
+
+func (d *nicRouted) CanHotPlug() (bool, []string) {
+	return false, []string{}
+}
+
+// validateConfig checks the supplied config for correctness.
+func (d *nicRouted) validateConfig() error {
+	if d.instance.Type() != instancetype.Container {
+		return ErrUnsupportedDevType
+	}
+
+	requiredFields := []string{"parent"}
+	optionalFields := []string{
+		"name",
+		"mtu",
+		"hwaddr",
+		"host_name",
+		"vlan",
+	}
+
+	rules := nicValidationRules(requiredFields, optionalFields)
+	rules["ipv4.address"] = func(value string) error {
+		if value == "" {
+			return nil
+		}
+
+		return NetworkValidAddressV4List(value)
+	}
+	rules["ipv6.address"] = func(value string) error {
+		if value == "" {
+			return nil
+		}
+
+		return NetworkValidAddressV6List(value)
+	}
+
+	err := d.config.Validate(rules)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// validateEnvironment checks the runtime environment for correctness.
+func (d *nicRouted) validateEnvironment() error {
+	if d.config["name"] == "" {
+		return fmt.Errorf("Requires name property to start")
+	}
+
+	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_veth_router"] || !extensions["network_l2proxy"] {
+		return fmt.Errorf("Requires liblxc has following API extensions: network_veth_router, network_l2proxy")
+	}
+
+	if d.config["ipv4.address"] != "" {
+		// Check necessary sysctls are configured for use with l2proxy parent for routed mode.
+		ipv4FwdPath := fmt.Sprintf("ipv4/conf/%s/forwarding", d.config["parent"])
+		sysctlVal, err := NetworkSysctlGet(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("Routed mode requires sysctl net.ipv4.conf.%s.forwarding=1", d.config["parent"])
+		}
+	}
+
+	if d.config["ipv6.address"] != "" {
+		// Check necessary sysctls are configured for use with l2proxy parent for routed mode.
+		ipv6FwdPath := fmt.Sprintf("ipv6/conf/%s/forwarding", d.config["parent"])
+		sysctlVal, err := NetworkSysctlGet(ipv6FwdPath)
+		if err != nil {
+			return fmt.Errorf("Error reading net sysctl %s: %v", ipv6FwdPath, err)
+		}
+		if sysctlVal != "1\n" {
+			return fmt.Errorf("Routed mode requires sysctl net.ipv6.conf.%s.forwarding=1", d.config["parent"])
+		}
+
+		ipv6ProxyNdpPath := fmt.Sprintf("ipv6/conf/%s/proxy_ndp", d.config["parent"])
+		sysctlVal, err = NetworkSysctlGet(ipv6ProxyNdpPath)
+		if err != nil {
+			return fmt.Errorf("Error reading net sysctl %s: %v", ipv6ProxyNdpPath, err)
+		}
+		if sysctlVal != "1\n" {
+			return fmt.Errorf("Routed mode requires sysctl net.ipv6.conf.%s.proxy_ndp=1", d.config["parent"])
+		}
+	}
+
+	return nil
+}
+
+// Start is run when the instance is starting up (Routed mode doesn't support hot plugging).
+func (d *nicRouted) Start() (*RunConfig, error) {
+	err := d.validateEnvironment()
+	if err != nil {
+		return nil, err
+	}
+
+	// Lock to avoid issues with containers starting in parallel.
+	networkCreateSharedDeviceLock.Lock()
+	defer networkCreateSharedDeviceLock.Unlock()
+
+	saveData := make(map[string]string)
+
+	// Decide which parent we should use based on VLAN setting.
+	parentName := NetworkGetHostDevice(d.config["parent"], d.config["vlan"])
+
+	statusDev, err := NetworkCreateVlanDeviceIfNeeded(d.state, d.config["parent"], parentName, d.config["vlan"])
+	if err != nil {
+		return nil, err
+	}
+
+	// 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" {
+		err := d.setupParentSysctls(parentName)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	err = d.volatileSet(saveData)
+	if err != nil {
+		return nil, err
+	}
+
+	runConf := RunConfig{}
+	nic := []RunConfigItem{
+		{Key: "name", Value: d.config["name"]},
+		{Key: "type", Value: "veth"},
+		{Key: "flags", Value: "up"},
+		{Key: "veth.mode", Value: "router"},
+		{Key: "l2proxy", Value: "1"},
+		{Key: "link", Value: parentName},
+	}
+
+	if d.config["mtu"] != "" {
+		nic = append(nic, RunConfigItem{Key: "mtu", Value: d.config["mtu"]})
+	}
+
+	if d.config["ipv4.address"] != "" {
+		for _, addr := range strings.Split(d.config["ipv4.address"], ",") {
+			addr = strings.TrimSpace(addr)
+			nic = append(nic, RunConfigItem{Key: "ipv4.address", Value: fmt.Sprintf("%s/32", addr)})
+		}
+
+		nic = append(nic, RunConfigItem{Key: "ipv4.gateway", Value: "auto"})
+	}
+
+	if d.config["ipv6.address"] != "" {
+		for _, addr := range strings.Split(d.config["ipv6.address"], ",") {
+			addr = strings.TrimSpace(addr)
+			nic = append(nic, RunConfigItem{Key: "ipv6.address", Value: fmt.Sprintf("%s/128", addr)})
+		}
+
+		nic = append(nic, RunConfigItem{Key: "ipv6.gateway", Value: "auto"})
+	}
+
+	runConf.NetworkInterface = nic
+	return &runConf, nil
+}
+
+// setupParentSysctls configures the required sysctls on the parent to allow l2proxy to work.
+// Because of our policy not to modify sysctls on existing interfaces, this should only be called
+// if we created the parent interface.
+func (d *nicRouted) setupParentSysctls(parentName string) error {
+	if d.config["ipv4.address"] != "" {
+		// Set necessary sysctls for use with l2proxy parent in routed mode.
+		ipv4FwdPath := fmt.Sprintf("ipv4/conf/%s/forwarding", parentName)
+		err := NetworkSysctlSet(ipv4FwdPath, "1")
+		if err != nil {
+			return fmt.Errorf("Error setting net sysctl %s: %v", ipv4FwdPath, err)
+		}
+	}
+
+	if d.config["ipv6.address"] != "" {
+		// Set necessary sysctls use with l2proxy parent in routed mode.
+		ipv6FwdPath := fmt.Sprintf("ipv6/conf/%s/forwarding", parentName)
+		err := NetworkSysctlSet(ipv6FwdPath, "1")
+		if err != nil {
+			return fmt.Errorf("Error reading net sysctl %s: %v", ipv6FwdPath, err)
+		}
+
+		ipv6ProxyNdpPath := fmt.Sprintf("ipv6/conf/%s/proxy_ndp", parentName)
+		err = NetworkSysctlSet(ipv6ProxyNdpPath, "1")
+		if err != nil {
+			return fmt.Errorf("Error reading net sysctl %s: %v", ipv6ProxyNdpPath, err)
+		}
+	}
+
+	return nil
+}
+
+// Stop is run when the device is removed from the instance.
+func (d *nicRouted) Stop() (*RunConfig, error) {
+	runConf := RunConfig{
+		PostHooks: []func() error{d.postStop},
+	}
+
+	return &runConf, nil
+}
+
+// postStop is run after the device is removed from the instance.
+func (d *nicRouted) postStop() error {
+	defer d.volatileSet(map[string]string{
+		"last_state.created": "",
+	})
+
+	v := d.volatileGet()
+
+	// This will delete the parent interface if we created it for VLAN parent.
+	if shared.IsTrue(v["last_state.created"]) {
+		parentName := NetworkGetHostDevice(d.config["parent"], d.config["vlan"])
+		err := NetworkRemoveInterfaceIfNeeded(d.state, parentName, d.instance, d.config["parent"], d.config["vlan"])
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}


More information about the lxc-devel mailing list