The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/6432
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) === 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.parr...@canonical.com>
From a800a1276a62e46649c1b863038d300bc6564136 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@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.parr...@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 +}
_______________________________________________ lxc-devel mailing list lxc-devel@lists.linuxcontainers.org http://lists.linuxcontainers.org/listinfo/lxc-devel