[lxc-devel] [lxd/master] add IPVLAN support
s3rj1k on Github
lxc-bot at linuxcontainers.org
Wed Oct 17 10:42:29 UTC 2018
A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 403 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20181017/27598bf6/attachment.bin>
-------------- next part --------------
From 75e501bb1f0836412f909c379c5f8e853acf1617 Mon Sep 17 00:00:00 2001
From: s3rj1k <evasive.gyron at gmail.com>
Date: Wed, 17 Oct 2018 12:22:56 +0300
Subject: [PATCH 1/7] refactor networkValidAddress functions, for input
validation purposes
---
lxd/networks_config.go | 6 ++---
lxd/networks_utils.go | 53 +++++++++++++++++++++++++++++++++++++++++-
2 files changed, 55 insertions(+), 4 deletions(-)
diff --git a/lxd/networks_config.go b/lxd/networks_config.go
index bf0f0c5019..e2a31e4e2e 100644
--- a/lxd/networks_config.go
+++ b/lxd/networks_config.go
@@ -47,10 +47,10 @@ var networkConfigKeys = map[string]func(value string) error{
"tunnel.TARGET.protocol": func(value string) error {
return shared.IsOneOf(value, []string{"gre", "vxlan"})
},
- "tunnel.TARGET.local": networkValidAddressV4,
- "tunnel.TARGET.remote": networkValidAddressV4,
+ "tunnel.TARGET.local": networkValidAddress,
+ "tunnel.TARGET.remote": networkValidAddress,
"tunnel.TARGET.port": networkValidPort,
- "tunnel.TARGET.group": networkValidAddressV4,
+ "tunnel.TARGET.group": networkValidAddress,
"tunnel.TARGET.id": shared.IsInt64,
"tunnel.TARGET.interface": networkValidName,
"tunnel.TARGET.ttl": shared.IsUint8,
diff --git a/lxd/networks_utils.go b/lxd/networks_utils.go
index a0802b7d86..d3e8fbff4b 100644
--- a/lxd/networks_utils.go
+++ b/lxd/networks_utils.go
@@ -552,19 +552,70 @@ func networkValidAddressCIDRV4(value string) error {
return nil
}
-func networkValidAddressV4(value string) error {
+func networkValidAddress(value string) error {
if value == "" {
return nil
}
ip := net.ParseIP(value)
if ip == nil {
+ return fmt.Errorf("Not an IP address: %s", value)
+ }
+
+ return nil
+}
+
+// validate list of v4 IPs (space separated)
+func networkValidAddressV4List(value string) error {
+ for _, v := range strings.Fields(value) {
+ err := networkValidAddressV4(v)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// validate list of v6 IPs (space separated)
+func networkValidAddressV6List(value string) error {
+ for _, v := range strings.Fields(value) {
+ err := networkValidAddressV6(v)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func networkValidAddressV4(value string) error {
+ if value == "" {
+ return nil
+ }
+
+ ip := net.ParseIP(value)
+ if ip.To4() == nil {
return fmt.Errorf("Not an IPv4 address: %s", value)
}
return nil
}
+func networkValidAddressV6(value string) error {
+ if value == "" {
+ return nil
+ }
+
+ ip := net.ParseIP(value)
+ if ip == nil {
+ return fmt.Errorf("Not an IPv6 address: %s", value)
+ }
+ if ip.To4() != nil {
+ return fmt.Errorf("Not an IPv6 address: %s", value)
+ }
+
+ return nil
+}
+
func networkValidNetworkV4(value string) error {
if value == "" {
return nil
From 8a3b6ba5796066afca06e3f73a19f93b8b2015c6 Mon Sep 17 00:00:00 2001
From: s3rj1k <evasive.gyron at gmail.com>
Date: Wed, 17 Oct 2018 12:40:11 +0300
Subject: [PATCH 2/7] add IPVLAN support
---
lxd/container.go | 30 ++++++-
lxd/container_lxc.go | 200 ++++++++++++++++++++++++++++++++++++++++--
lxd/networks_utils.go | 121 ++++++++++++++++++++++++-
3 files changed, 341 insertions(+), 10 deletions(-)
diff --git a/lxd/container.go b/lxd/container.go
index ba8ac1edbc..12d6479ef9 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -356,13 +356,39 @@ func containerValidDevices(db *db.Cluster, devices types.Devices, profile bool,
return fmt.Errorf("Missing nic type")
}
- if !shared.StringInSlice(m["nictype"], []string{"bridged", "macvlan", "p2p", "physical", "sriov"}) {
+ if !shared.StringInSlice(m["nictype"], []string{"bridged", "macvlan", "ipvlan", "p2p", "physical", "sriov"}) {
return fmt.Errorf("Bad nic type: %s", m["nictype"])
}
- if shared.StringInSlice(m["nictype"], []string{"bridged", "macvlan", "physical", "sriov"}) && m["parent"] == "" {
+ if shared.StringInSlice(m["nictype"], []string{"bridged", "macvlan", "ipvlan", "physical", "sriov"}) && m["parent"] == "" {
return fmt.Errorf("Missing parent for %s type nic", m["nictype"])
}
+
+ if m["nictype"] == "ipvlan" {
+
+ var isV4, isV6 bool
+ var val string
+
+ if val, isV4 = m["ipv4.address"]; isV4 {
+ err := networkValidAddressV4List(val)
+ if err != nil {
+ return err
+ }
+ }
+
+ if val, isV6 = m["ipv6.address"]; isV6 {
+ err := networkValidAddressV6List(val)
+ if err != nil {
+ return err
+ }
+ }
+
+ if !isV4 && !isV6 {
+ return fmt.Errorf("Missing IPv4 or IPv6 address for %s nic", m["nictype"])
+ }
+
+ }
+
} else if m["type"] == "infiniband" {
if m["nictype"] == "" {
return fmt.Errorf("Missing nic type")
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 8b6c9d4fa6..1492be040a 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1567,6 +1567,13 @@ func (c *containerLXC) initLXC(config bool) error {
if err != nil {
return err
}
+ } else if m["nictype"] == "ipvlan" {
+ // go-lxc.v2 lacks support for IPVLAN, so we will create only loopback interface
+ // and at later stage we will move newly created IPVLAN interface from default ns to container ns
+ err = lxcSetConfigItem(cc, fmt.Sprintf("%s.%d.type", networkKeyPrefix, networkidx), "empty")
+ if err != nil {
+ return err
+ }
}
err = lxcSetConfigItem(cc, fmt.Sprintf("%s.%d.flags", networkKeyPrefix, networkidx), "up")
@@ -2319,6 +2326,66 @@ func (c *containerLXC) startCommon() (string, error) {
}
}
+ // Creating loopback device inside container namespace for IPVLAN network
+ if m["type"] == "nic" && m["nictype"] == "ipvlan" {
+ // go-lxc.v2 lacks support for IPVLAN, so we will create only loopback interface
+ // and at later stage we will move newly created IPVLAN interface from default ns to container ns
+ err = lxcSetConfigItem(c.c, fmt.Sprintf("%s.%d.type", networkKeyPrefix, networkidx), "empty")
+ if err != nil {
+ return "", err
+ }
+
+ if !shared.PathExists(fmt.Sprintf("/sys/class/net/%s", m["parent"])) {
+ return "", fmt.Errorf("Parent network device %s does not exist", m["parent"])
+ }
+
+ // enable IPv4 forwarding, sysctl -w net.ipv4.ip_forward=1
+ err = networkSysctl("ipv4/ip_forward", "1")
+ if err != nil {
+ return "", err
+ }
+ // enable IPv4 Proxy ARP, sysctl -w net.ipv4.conf.DEV.proxy_arp=1
+ err = networkSysctl(fmt.Sprintf("ipv4/conf/%s/proxy_arp", m["parent"]), "1")
+ if err != nil {
+ return "", err
+ }
+ // disable IPv4 RP Filter, sysctl -w net.ipv4.conf.DEV.rp_filter=0
+ err = networkSysctl(fmt.Sprintf("ipv4/conf/%s/rp_filter", m["parent"]), "0")
+ if err != nil {
+ return "", err
+ }
+
+ // enable IPv6, sysctl -w net.ipv6.conf.DEV.disable_ipv6=0
+ err = networkSysctl(fmt.Sprintf("ipv6/conf/%s/disable_ipv6", m["parent"]), "0")
+ if err == nil {
+ // enable IPv6 forwarding, sysctl -w net.ipv6.conf.all.forwarding=1
+ err = networkSysctl("ipv6/conf/all/forwarding", "1")
+ if err != nil {
+ return "", err
+ }
+ // enable IPv6 Proxy NDP, sysctl -w net.ipv6.conf.DEV.proxy_ndp=1
+ err = networkSysctl(fmt.Sprintf("ipv6/conf/%s/proxy_ndp", m["parent"]), "1")
+ if err != nil {
+ return "", err
+ }
+ // disable IPv6 autoconfiguration, sysctl -w net.ipv6.conf.DEV.autoconf=0
+ err = networkSysctl(fmt.Sprintf("ipv6/conf/%s/autoconf", m["parent"]), "0")
+ if err != nil {
+ return "", err
+ }
+ // disable IPv6 router advertisement, sysctl -w net.ipv6.conf.DEV.accept_ra=0
+ err = networkSysctl(fmt.Sprintf("ipv6/conf/%s/accept_ra", m["parent"]), "0")
+ if err != nil {
+ return "", err
+ }
+ // disable IPv6 router advertisement default route, sysctl -w net.ipv6.conf.DEV.accept_ra_defrtr=0
+ err = networkSysctl(fmt.Sprintf("ipv6/conf/%s/accept_ra_defrtr", m["parent"]), "0")
+ if err != nil {
+ return "", err
+ }
+ }
+ }
+
if m["nictype"] == "bridged" && shared.IsTrue(m["security.mac_filtering"]) {
// Read device name from config
vethName := ""
@@ -2587,6 +2654,37 @@ func (c *containerLXC) Start(stateful bool) error {
return err
}
+ // Inject IPVLAN interface inside container namespace
+ if c.IsRunning() {
+ for name, m := range c.ExpandedDevices() {
+ if m["type"] == "nic" && m["nictype"] == "ipvlan" {
+ // Creating temporary name for IPVLAN interface.
+ ifaceName, err := networkGenerateInterfaceName(c.Name(), name)
+ if err != nil {
+ return err
+ }
+
+ // Creating IPVLAN interface inside default namespace
+ _, err = shared.RunCommand("ip", "link", "add", ifaceName, "link", m["parent"], "type", "ipvlan", "mode", "l3s")
+ if err != nil {
+ return fmt.Errorf("Failed to create new IPVLAN interface: %s", err)
+ }
+
+ // Moving IPVLAN interface to container namespace
+ _, err = shared.RunCommand("ip", "link", "set", ifaceName, "netns", strconv.Itoa(c.InitPID()), "name", name, "up")
+ if err != nil {
+ return fmt.Errorf("Failed to assign IPVLAN interface to container: %s", err)
+ }
+
+ // Configuring IP addresses
+ err = c.addIPVLANConfiguration()
+ if err != nil {
+ return err
+ }
+ }
+ }
+ }
+
logger.Info("Started container", ctxMap)
eventSendLifecycle(c.project, "container-started",
fmt.Sprintf("/1.0/containers/%s", c.name), nil)
@@ -2913,6 +3011,12 @@ func (c *containerLXC) OnStop(target string) error {
// Wait for other post-stop actions to be done
c.IsRunning()
+ // remove IPVLAN configuration, if exists
+ err = c.removeIPVLANConfiguration()
+ if err != nil {
+ logger.Error("Failed to unconfigure IPVLAN interface", log.Ctx{"container": c.Name(), "err": err})
+ }
+
// Unload the apparmor profile
err = AADestroy(c)
if err != nil {
@@ -7321,7 +7425,7 @@ func (c *containerLXC) restartProxyDevices() error {
func (c *containerLXC) createNetworkDevice(name string, m types.Device) (string, error) {
var dev, n1 string
- if shared.StringInSlice(m["nictype"], []string{"bridged", "p2p", "macvlan"}) {
+ if shared.StringInSlice(m["nictype"], []string{"bridged", "p2p", "macvlan", "ipvlan"}) {
// Host Virtual NIC name
if m["host_name"] != "" {
n1 = m["host_name"]
@@ -7362,8 +7466,8 @@ func (c *containerLXC) createNetworkDevice(name string, m types.Device) (string,
dev = n2
}
- // Handle physical and macvlan
- if shared.StringInSlice(m["nictype"], []string{"macvlan", "physical"}) {
+ // Handle physical, macvlan, ipvlan
+ if shared.StringInSlice(m["nictype"], []string{"macvlan", "physical", "ipvlan"}) {
// Deal with VLAN
device := m["parent"]
if m["vlan"] != "" {
@@ -7393,10 +7497,20 @@ func (c *containerLXC) createNetworkDevice(name string, m types.Device) (string,
dev = n1
}
+
+ // Handle ipvlan
+ if m["nictype"] == "ipvlan" {
+ _, err := shared.RunCommand("ip", "link", "add", "dev", n1, "link", device, "type", "ipvlan", "mode", "l3s")
+ if err != nil {
+ return "", fmt.Errorf("Failed to create the new ipvlan interface: %s", err)
+ }
+
+ dev = n1
+ }
}
- // Set the MAC address
- if m["hwaddr"] != "" {
+ // Set the MAC address, skip for IPVLAN type
+ if m["hwaddr"] != "" && m["nictype"] != "ipvlan" {
_, err := shared.RunCommand("ip", "link", "set", "dev", dev, "address", m["hwaddr"])
if err != nil {
deviceRemoveInterface(dev)
@@ -7769,6 +7883,58 @@ func (c *containerLXC) removeNetworkFilter(hwaddr string, bridge string) error {
return nil
}
+func (c *containerLXC) removeIPVLANConfiguration() error {
+ for _, m := range c.ExpandedDevices() {
+ // not checking here for nictype == ipvlan,
+ // for network device update to work
+ if m["type"] == "nic" {
+ if val, ok := m["ipv4.address"]; ok {
+ for _, addr := range strings.Fields(val) {
+ err := networkUnConfigureIPVlanAddress(addr, m["parent"])
+ if err != nil {
+ return err
+ }
+ }
+ }
+ if val, ok := m["ipv6.address"]; ok {
+ for _, addr := range strings.Fields(val) {
+ err := networkUnConfigureIPVlanAddress(addr, m["parent"])
+ if err != nil {
+ return err
+ }
+ }
+ }
+ }
+ }
+
+ return nil
+}
+
+func (c *containerLXC) addIPVLANConfiguration() error {
+ for _, m := range c.ExpandedDevices() {
+ if m["type"] == "nic" && m["nictype"] == "ipvlan" {
+ if val, ok := m["ipv4.address"]; ok {
+ for _, addr := range strings.Fields(val) {
+ err := networkConfigureIPVlanAddress(addr, m["parent"])
+ if err != nil {
+ return err
+ }
+ }
+ }
+ if val, ok := m["ipv6.address"]; ok {
+ for _, addr := range strings.Fields(val) {
+ err := networkConfigureIPVlanAddress(addr, m["parent"])
+ if err != nil {
+ return err
+ }
+ }
+ }
+ }
+ }
+
+ return nil
+}
+
func (c *containerLXC) removeNetworkFilters() error {
for k, m := range c.expandedDevices {
if m["type"] != "nic" || m["nictype"] != "bridged" {
@@ -7829,6 +7995,12 @@ func (c *containerLXC) insertNetworkDevice(name string, m types.Device) (types.D
return nil, fmt.Errorf("Failed to attach interface: %s: %s", devName, err)
}
+ // Configuring IP addresses for IPVLAN, if exists
+ err = c.addIPVLANConfiguration()
+ if err != nil {
+ return nil, err
+ }
+
return m, nil
}
@@ -7862,12 +8034,26 @@ func (c *containerLXC) removeNetworkDevice(name string, m types.Device) error {
}
defer lxc.Release(cc)
- // Remove the interface from the container
- err = cc.DetachInterfaceRename(m["name"], hostName)
+ // Check if interface exists inside container namespace
+ ifaces, err := cc.Interfaces()
if err != nil {
return fmt.Errorf("Failed to detach interface: %s: %s", m["name"], err)
}
+ // remove IPVLAN configuration, if exists
+ err = c.removeIPVLANConfiguration()
+ if err != nil {
+ return err
+ }
+
+ // Remove the interface from the container if it exists
+ if shared.StringInSlice(m["name"], ifaces) {
+ err = cc.DetachInterfaceRename(m["name"], hostName)
+ if err != nil {
+ return fmt.Errorf("Failed to detach interface: %s: %s", m["name"], err)
+ }
+ }
+
// If a veth, destroy it
if m["nictype"] != "physical" && m["nictype"] != "sriov" {
deviceRemoveInterface(hostName)
diff --git a/lxd/networks_utils.go b/lxd/networks_utils.go
index d3e8fbff4b..7febd1d0cf 100644
--- a/lxd/networks_utils.go
+++ b/lxd/networks_utils.go
@@ -6,6 +6,7 @@ import (
"encoding/binary"
"encoding/hex"
"fmt"
+ "hash/fnv"
"io/ioutil"
"math"
"math/big"
@@ -111,7 +112,7 @@ func networkIsInUse(c container, name string) bool {
continue
}
- if !shared.StringInSlice(d["nictype"], []string{"bridged", "macvlan", "physical", "sriov"}) {
+ if !shared.StringInSlice(d["nictype"], []string{"bridged", "macvlan", "ipvlan", "physical", "sriov"}) {
continue
}
@@ -1183,3 +1184,121 @@ func networkGetState(netIf net.Interface) api.NetworkState {
network.Counters = shared.NetworkGetCounters(netIf.Name)
return network
}
+
+func networkConfigureIPVlanAddress(address, parentDevName string) error {
+
+ if address == "" {
+ return fmt.Errorf("Network address can not be empty for parent nic: %s", parentDevName)
+ }
+
+ // handle IPv4
+ if err := networkValidAddressV4(address); err == nil {
+ // add Local Route
+ _, err := shared.RunCommand("ip", "-4", "route", "add", "local", fmt.Sprintf("%s/32", address), "dev", parentDevName)
+ if err != nil {
+ switch {
+ case strings.Contains(err.Error(), "File exists"):
+ default:
+ return err
+ }
+ }
+ // add Proxy ARP record
+ _, err = shared.RunCommand("ip", "-4", "neigh", "add", "proxy", address, "dev", parentDevName)
+ if err != nil {
+ return err
+ }
+ }
+
+ // handle IPv6
+ if err := networkValidAddressV6(address); err == nil {
+ // add Local Route
+ _, err := shared.RunCommand("ip", "-6", "route", "add", "local", fmt.Sprintf("%s/128", address), "dev", parentDevName)
+ if err != nil {
+ switch {
+ case strings.Contains(err.Error(), "File exists"):
+ default:
+ return err
+ }
+ }
+ // add Proxy NDP record
+ _, err = shared.RunCommand("ip", "-6", "neigh", "add", "proxy", address, "dev", parentDevName)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func networkUnConfigureIPVlanAddress(address, parentDevName string) error {
+
+ if address == "" {
+ return fmt.Errorf("Network address can not be empty for parent nic: %s", parentDevName)
+ }
+
+ // handle IPv4
+ if err := networkValidAddressV4(address); err == nil {
+ // delete Local Route
+ _, err := shared.RunCommand("ip", "-4", "route", "del", "local", fmt.Sprintf("%s/32", address), "dev", parentDevName)
+ if err != nil {
+ switch {
+ case strings.Contains(err.Error(), "No such process"):
+ case strings.Contains(err.Error(), "No such file or directory"):
+ default:
+ return err
+ }
+ }
+ // delete Proxy ARP record
+ _, err = shared.RunCommand("ip", "-4", "neigh", "del", "proxy", address, "dev", parentDevName)
+ if err != nil {
+ switch {
+ case strings.Contains(err.Error(), "No such process"):
+ case strings.Contains(err.Error(), "No such file or directory"):
+ default:
+ return err
+ }
+ }
+ }
+
+ // handle IPv6
+ if err := networkValidAddressV6(address); err == nil {
+ // delete Local Route
+ _, err := shared.RunCommand("ip", "-6", "route", "del", "local", fmt.Sprintf("%s/128", address), "dev", parentDevName)
+ if err != nil {
+ switch {
+ case strings.Contains(err.Error(), "No such process"):
+ case strings.Contains(err.Error(), "No such file or directory"):
+ default:
+ return err
+ }
+ }
+ // delete Proxy NDP record
+ _, err = shared.RunCommand("ip", "-6", "neigh", "del", "proxy", address, "dev", parentDevName)
+ if err != nil {
+ switch {
+ case strings.Contains(err.Error(), "No such process"):
+ case strings.Contains(err.Error(), "No such file or directory"):
+ default:
+ return err
+ }
+ }
+ }
+
+ return nil
+}
+
+func networkGenerateInterfaceName(preffix, suffix string) (string, error) {
+ // Interface name is based upon hash (32-bit FNV-1a) of supplied preffix and suffix.
+ // Function can be used to make interface creation thread-safe and predictable.
+ // Interface name length is limited by kernel, so we use 32-bit sized hash.
+ h := fnv.New32a()
+ h.Write([]byte(fmt.Sprintf("%s-%s", preffix, suffix)))
+ name := fmt.Sprintf("eth%010d", h.Sum32())
+
+ err := networkValidName(name)
+ if err != nil {
+ return "", err
+ }
+
+ return name, nil
+}
From ebeeb923ddc547f06ae20ddfaf6c7eefd7e70dc8 Mon Sep 17 00:00:00 2001
From: s3rj1k <evasive.gyron at gmail.com>
Date: Wed, 17 Oct 2018 12:42:01 +0300
Subject: [PATCH 3/7] use case instead of if inside devlxdConfig
---
lxd/devlxd.go | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/lxd/devlxd.go b/lxd/devlxd.go
index eb7735b215..06f9346003 100644
--- a/lxd/devlxd.go
+++ b/lxd/devlxd.go
@@ -59,8 +59,11 @@ type devLxdHandler struct {
var devlxdConfigGet = devLxdHandler{"/1.0/config", func(d *Daemon, c container, w http.ResponseWriter, r *http.Request) *devLxdResponse {
filtered := []string{}
for k := range c.ExpandedConfig() {
- if strings.HasPrefix(k, "user.") {
+ switch {
+ case strings.HasPrefix(k, "user."):
filtered = append(filtered, fmt.Sprintf("/1.0/config/%s", k))
+ default:
+ continue
}
}
return okResponse(filtered, "json")
@@ -68,7 +71,10 @@ var devlxdConfigGet = devLxdHandler{"/1.0/config", func(d *Daemon, c container,
var devlxdConfigKeyGet = devLxdHandler{"/1.0/config/{key}", func(d *Daemon, c container, w http.ResponseWriter, r *http.Request) *devLxdResponse {
key := mux.Vars(r)["key"]
- if !strings.HasPrefix(key, "user.") {
+
+ switch {
+ case strings.HasPrefix(key, "user."):
+ default:
return &devLxdResponse{"not authorized", http.StatusForbidden, "raw"}
}
From f47c859d8915c9f259acc1ab4c488007054bd1f7 Mon Sep 17 00:00:00 2001
From: s3rj1k <evasive.gyron at gmail.com>
Date: Wed, 17 Oct 2018 12:43:39 +0300
Subject: [PATCH 4/7] expose IPVLAN device inside devLXD
---
lxd/devlxd.go | 46 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 46 insertions(+)
diff --git a/lxd/devlxd.go b/lxd/devlxd.go
index 06f9346003..064562f38e 100644
--- a/lxd/devlxd.go
+++ b/lxd/devlxd.go
@@ -86,6 +86,50 @@ var devlxdConfigKeyGet = devLxdHandler{"/1.0/config/{key}", func(d *Daemon, c co
return okResponse(value, "raw")
}}
+var devlxdDevicesGet = devLxdHandler{"/1.0/devices", func(d *Daemon, c container, w http.ResponseWriter, r *http.Request) *devLxdResponse {
+ filtered := []string{}
+ for name, m := range c.ExpandedDevices() {
+ switch {
+ case m["type"] == "nic" && m["nictype"] == "ipvlan":
+ filtered = append(filtered, fmt.Sprintf("/1.0/device/%s", name))
+ default:
+ continue
+ }
+ }
+ return okResponse(filtered, "json")
+}}
+
+var devlxdDeviceOptionsGet = devLxdHandler{"/1.0/device/{key}", func(d *Daemon, c container, w http.ResponseWriter, r *http.Request) *devLxdResponse {
+ key := mux.Vars(r)["key"]
+
+ m, ok := c.ExpandedDevices()[key]
+ if !ok {
+ return &devLxdResponse{"not found", http.StatusNotFound, "raw"}
+ }
+
+ switch {
+ case m["type"] == "nic" && m["nictype"] == "ipvlan":
+ filtered := make(map[string]string, len(m))
+ for k, v := range m {
+ switch k {
+ case "type":
+ filtered[k] = v
+ case "ipv4.address":
+ filtered[k] = v
+ case "ipv6.address":
+ filtered[k] = v
+ case "nictype":
+ filtered[k] = v
+ default:
+ continue
+ }
+ }
+ return okResponse(filtered, "json")
+ default:
+ return &devLxdResponse{"not authorized", http.StatusForbidden, "raw"}
+ }
+}}
+
var devlxdImageExport = devLxdHandler{"/1.0/images/{fingerprint}/export", func(d *Daemon, c container, w http.ResponseWriter, r *http.Request) *devLxdResponse {
if !shared.IsTrue(c.ExpandedConfig()["security.devlxd.images"]) {
return &devLxdResponse{"not authorized", http.StatusForbidden, "raw"}
@@ -214,6 +258,8 @@ var handlers = []devLxdHandler{
}},
devlxdConfigGet,
devlxdConfigKeyGet,
+ devlxdDevicesGet,
+ devlxdDeviceOptionsGet,
devlxdMetadataGet,
devlxdEventsGet,
devlxdImageExport,
From fca78fe7d454c6f3650b711c01370b9f018410e5 Mon Sep 17 00:00:00 2001
From: s3rj1k <evasive.gyron at gmail.com>
Date: Wed, 17 Oct 2018 12:58:32 +0300
Subject: [PATCH 5/7] add nameserver key configuration for nic (IPVLAN) device
---
lxd/container.go | 9 +++++++++
lxd/devlxd.go | 2 ++
lxd/networks_utils.go | 11 +++++++++++
3 files changed, 22 insertions(+)
diff --git a/lxd/container.go b/lxd/container.go
index 12d6479ef9..e631a7e5b6 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -135,6 +135,8 @@ func containerValidDeviceConfigKey(t, k string) bool {
return true
case "vlan":
return true
+ case "nameserver":
+ return true
case "ipv4.address":
return true
case "ipv6.address":
@@ -383,6 +385,13 @@ func containerValidDevices(db *db.Cluster, devices types.Devices, profile bool,
}
}
+ if val, ok = m["nameserver"]; ok {
+ err := networkValidAddressList(val)
+ if err != nil {
+ return err
+ }
+ }
+
if !isV4 && !isV6 {
return fmt.Errorf("Missing IPv4 or IPv6 address for %s nic", m["nictype"])
}
diff --git a/lxd/devlxd.go b/lxd/devlxd.go
index 064562f38e..d23bd25bed 100644
--- a/lxd/devlxd.go
+++ b/lxd/devlxd.go
@@ -118,6 +118,8 @@ var devlxdDeviceOptionsGet = devLxdHandler{"/1.0/device/{key}", func(d *Daemon,
filtered[k] = v
case "ipv6.address":
filtered[k] = v
+ case "nameserver":
+ filtered[k] = v
case "nictype":
filtered[k] = v
default:
diff --git a/lxd/networks_utils.go b/lxd/networks_utils.go
index 7febd1d0cf..ebc685d2fc 100644
--- a/lxd/networks_utils.go
+++ b/lxd/networks_utils.go
@@ -588,6 +588,17 @@ func networkValidAddressV6List(value string) error {
return nil
}
+// validate list of IPs (space separated)
+func networkValidAddressList(value string) error {
+ for _, v := range strings.Fields(value) {
+ err := networkValidAddress(v)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
func networkValidAddressV4(value string) error {
if value == "" {
return nil
From 7145b106b4d7b6939714158e61ce50c7465d4494 Mon Sep 17 00:00:00 2001
From: s3rj1k <evasive.gyron at gmail.com>
Date: Wed, 17 Oct 2018 13:09:43 +0300
Subject: [PATCH 6/7] fix nameserver key check
---
lxd/container.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lxd/container.go b/lxd/container.go
index e631a7e5b6..3d4dd8ae9b 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -385,7 +385,7 @@ func containerValidDevices(db *db.Cluster, devices types.Devices, profile bool,
}
}
- if val, ok = m["nameserver"]; ok {
+ if val, ok := m["nameserver"]; ok {
err := networkValidAddressList(val)
if err != nil {
return err
From e4db677765d114de95ee2573974eb1c827dab93f Mon Sep 17 00:00:00 2001
From: s3rj1k <evasive.gyron at gmail.com>
Date: Wed, 17 Oct 2018 13:39:53 +0300
Subject: [PATCH 7/7] update docs for network config, devlxd
---
doc/containers.md | 51 +++++++++++++++++++++++++++++++----------------
doc/dev-lxd.md | 34 +++++++++++++++++++++++++++++++
2 files changed, 68 insertions(+), 17 deletions(-)
diff --git a/doc/containers.md b/doc/containers.md
index 211c50febb..3c57a28610 100644
--- a/doc/containers.md
+++ b/doc/containers.md
@@ -225,28 +225,30 @@ LXD supports different kind of network devices:
- `physical`: Straight physical device passthrough from the host. The targeted device will vanish from the host and appear in the container.
- `bridged`: Uses an existing bridge on the host and creates a virtual device pair to connect the host bridge to the container.
- `macvlan`: Sets up a new network device based on an existing one but using a different MAC address.
+ - `ipvlan`: Sets up a new network device based on an existing one using a same MAC address.
- `p2p`: Creates a virtual device pair, putting one side in the container and leaving the other side on the host.
- `sriov`: Passes a virtual function of an SR-IOV enabled physical network device into the container.
Different network interface types have different additional properties, the current list is:
-Key | Type | Default | Required | Used by | API extension | Description
-:-- | :-- | :-- | :-- | :-- | :-- | :--
-nictype | string | - | yes | all | - | The device type, one of "bridged", "macvlan", "p2p", "physical", or "sriov"
-limits.ingress | string | - | no | bridged, p2p | - | I/O limit in bit/s for incoming traffic (supports kbit, Mbit, Gbit suffixes)
-limits.egress | string | - | no | bridged, p2p | - | I/O limit in bit/s for outgoing traffic (supports kbit, Mbit, Gbit suffixes)
-limits.max | string | - | no | bridged, p2p | - | Same as modifying both limits.ingress and limits.egress
-name | string | kernel assigned | no | all | - | The name of the interface inside the container
-host\_name | string | randomly assigned | no | bridged, macvlan, p2p, sriov | - | The name of the interface inside the host
-hwaddr | string | randomly assigned | no | all | - | The MAC address of the new interface
-mtu | integer | parent MTU | no | all | - | The MTU of the new interface
-parent | string | - | yes | bridged, macvlan, physical, sriov | - | The name of the host device or bridge
-vlan | integer | - | no | macvlan, physical | network\_vlan, network\_vlan\_physical | The VLAN ID to attach to
-ipv4.address | string | - | no | bridged | network | An IPv4 address to assign to the container through DHCP
-ipv6.address | string | - | no | bridged | network | An IPv6 address to assign to the container through DHCP
-security.mac\_filtering | boolean | false | no | bridged | network | Prevent the container from spoofing another's MAC address
-maas.subnet.ipv4 | string | - | no | bridged, macvlan, physical, sriov | maas\_network | MAAS IPv4 subnet to register the container in
-maas.subnet.ipv6 | string | - | no | bridged, macvlan, physical, sriov | maas\_network | MAAS IPv6 subnet to register the container in
+Key | Type | Default | Required | Used by | API extension | Description
+:-- | :-- | :-- | :-- | :-- | :-- | :--
+nictype | string | - | yes | all | - | The device type, one of "bridged", "macvlan", "p2p", "physical", or "sriov"
+limits.ingress | string | - | no | bridged, p2p | - | I/O limit in bit/s for incoming traffic (supports kbit, Mbit, Gbit suffixes)
+limits.egress | string | - | no | bridged, p2p | - | I/O limit in bit/s for outgoing traffic (supports kbit, Mbit, Gbit suffixes)
+limits.max | string | - | no | bridged, p2p | - | Same as modifying both limits.ingress and limits.egress
+name | string | kernel assigned | no | all | - | The name of the interface inside the container
+host\_name | string | randomly assigned | no | bridged, macvlan, ipvlan, p2p, sriov | - | The name of the interface inside the host
+hwaddr | string | randomly assigned | no | all | - | The MAC address of the new interface
+mtu | integer | parent MTU | no | all | - | The MTU of the new interface
+parent | string | - | yes | bridged, macvlan, ipvlan, physical, sriov | - | The name of the host device or bridge
+vlan | integer | - | no | macvlan, physical | network\_vlan, network\_vlan\_physical | The VLAN ID to attach to
+ipv4.address | string | - | no* | bridged, ipvlan | network | An IPv4 address to assign to the container through DHCP, for IPVLAN list of IPv4 for container (required at least one IPv4/IPv6)
+ipv6.address | string | - | no* | bridged, ipvlan | network | An IPv6 address to assign to the container through DHCP, for IPVLAN list of IPv6 for container (required at least one IPv4/IPv6)
+nameserver | string | - | no | ipvlan | network | A list of Nameserver IPs that will be exposed to container exposed through devLXD
+security.mac\_filtering | boolean | false | no | bridged | network | Prevent the container from spoofing another's MAC address
+maas.subnet.ipv4 | string | - | no | bridged, macvlan, physical, sriov | maas\_network | MAAS IPv4 subnet to register the container in
+maas.subnet.ipv6 | string | - | no | bridged, macvlan, physical, sriov | maas\_network | MAAS IPv6 subnet to register the container in
#### bridged or macvlan for connection to physical network
The `bridged` and `macvlan` interface types can both be used to connect
@@ -265,6 +267,21 @@ your containers to talk to the host itself.
In such case, a bridge is preferable. A bridge will also let you use mac
filtering and I/O limits which cannot be applied to a macvlan device.
+#### IPVLAN
+The `ipvlan` interface type can both be used to connect
+to an existing physical network without L2 support.
+
+This interface type is similar to `macvlan` but instead it uses parent MAC.
+This effectively saves MAC address table size of network devices.
+Also `ipvlan` in L3s mode support netfilter, so you can use iptables
+for container from hardware node.
+
+The downside to this is that `ipvlan` configuration is more cumbersome,
+lack of L2 forces you to use Proxy ARP and Proxy NDP, this issue is solved
+by LXD itself.
+
+To expose IP configuration inside container LXD uses superpowers of DevLXD.
+
#### SR-IOV
The `sriov` interface type supports SR-IOV enabled network devices. These
devices associate a set of virtual functions (VFs) with the single physical
diff --git a/doc/dev-lxd.md b/doc/dev-lxd.md
index 692f2513b4..7879007152 100644
--- a/doc/dev-lxd.md
+++ b/doc/dev-lxd.md
@@ -40,6 +40,8 @@ authentication support in the `/dev/lxd/sock` API.
* /1.0
* /1.0/config
* /1.0/config/{key}
+ * /1.0/devices
+ * /1.0/device/{key}
* /1.0/events
* /1.0/images/{fingerprint}/export
* /1.0/meta-data
@@ -98,6 +100,38 @@ Return value:
blah
+#### `/1.0/devices`
+##### GET
+ * Description: List of devices keys
+ * Return: list of devices keys URL
+
+Currently only `ipvlan` device keys are accessible to the container.
+
+Return value:
+
+```json
+[
+ "/1.0/device/eth0"
+]
+```
+
+#### `/1.0/device/<KEY>`
+##### GET
+ * Description: Value of that key
+ * Return: JSON multi-parameter value
+
+Return value:
+
+```json
+{
+ "ipv4.address": "80.80.90.60",
+ "ipv6.address": "2a02:2200:1:2eee:80:80:90:60",
+ "nameserver": "8.8.8.8 1.1.1.1",
+ "nictype": "ipvlan",
+ "type": "nic"
+}
+```
+
#### `/1.0/events`
##### GET
* Description: websocket upgrade
More information about the lxc-devel
mailing list