The following pull request was submitted through Github.
It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/5895

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) ===
Adds support for using IP filtering with dynamically allocated IPs.

From 7f372ca362462410183643085f9f2d5a03d30535 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Thu, 27 Jun 2019 16:33:18 +0100
Subject: [PATCH 01/15] container/lxc: Ensure if bridge nic added/removed that
 dnsmasq config is refreshed

Fixes #5892

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/container_lxc.go | 13 ++++++++-----
 1 file changed, 8 insertions(+), 5 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 1471d401bd..5463cc2fae 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -5790,12 +5790,15 @@ func (c *containerLXC) Update(args db.ContainerArgs, 
userRequested bool) error {
                }
        }
 
-       // Update network leases
+       // Update network leases if a bridged device has changed.
        needsUpdate := false
-       for _, m := range updateDevices {
-               if m["type"] == "nic" && m["nictype"] == "bridged" {
-                       needsUpdate = true
-                       break
+       deviceLists := []map[string]types.Device{removeDevices, addDevices, 
updateDevices}
+       for _, deviceList := range deviceLists {
+               for _, m := range deviceList {
+                       if m["type"] == "nic" && m["nictype"] == "bridged" {
+                               needsUpdate = true
+                               break
+                       }
                }
        }
 

From 97b658573e682c9ffb23bf846cc29a3a401b615e Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Fri, 28 Jun 2019 10:30:42 +0100
Subject: [PATCH 02/15] test: Adds test for checking dnsmasq host config is
 refreshed when nic added/removed

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 test/suites/container_devices_nic_bridged.sh | 29 ++++++++++++++++++++
 1 file changed, 29 insertions(+)

diff --git a/test/suites/container_devices_nic_bridged.sh 
b/test/suites/container_devices_nic_bridged.sh
index 925d3052fa..b9ccac54dc 100644
--- a/test/suites/container_devices_nic_bridged.sh
+++ b/test/suites/container_devices_nic_bridged.sh
@@ -288,7 +288,36 @@ test_container_devices_nic_bridged() {
     false
   fi
 
+  # Check dnsmasq host file is updated on new device.
+  lxc init testimage "${ctName}" -p "${ctName}"
+  lxc config device add "${ctName}" eth0 nic nictype=bridged 
parent="${brName}" name=eth0 ipv4.address=192.0.2.200 ipv6.address=2001:db8::200
+
+  ls -lR "${LXD_DIR}/networks/${brName}/dnsmasq.hosts/"
+
+  if ! grep "192.0.2.200" 
"${LXD_DIR}/networks/${brName}/dnsmasq.hosts/${ctName}" ; then
+    echo "dnsmasq host config not updated with IPv4 address"
+    false
+  fi
+
+  if ! grep "2001:db8::200" 
"${LXD_DIR}/networks/${brName}/dnsmasq.hosts/${ctName}" ; then
+    echo "dnsmasq host config not updated with IPv6 address"
+    false
+  fi
+
+  lxc config device remove "${ctName}" eth0
+
+  if grep "192.0.2.200" 
"${LXD_DIR}/networks/${brName}/dnsmasq.hosts/${ctName}" ; then
+    echo "dnsmasq host config still has old IPv4 address"
+    false
+  fi
+
+  if grep "2001:db8::200" 
"${LXD_DIR}/networks/${brName}/dnsmasq.hosts/${ctName}" ; then
+    echo "dnsmasq host config still has old IPv6 address"
+    false
+  fi
+
   # Cleanup.
+  lxc delete "${ctName}" -f
   lxc network delete "${brName}"
   lxc profile delete "${ctName}"
 }

From c485e9c07304b5461964d0848ffb42581851ee25 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Thu, 27 Jun 2019 16:32:22 +0100
Subject: [PATCH 03/15] container: Relaxes validation for
 security.ipv4_filtering and security.ipv6_filtering

These no longer need a static IP defined, opening the way for automatic IP 
allocation.

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/container.go | 8 --------
 1 file changed, 8 deletions(-)

diff --git a/lxd/container.go b/lxd/container.go
index 29e4d06b1d..bec08a195e 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -443,20 +443,12 @@ func containerValidDevices(cluster *db.Cluster, devices 
types.Devices, profile b
                                if m["nictype"] != "bridged" {
                                        return fmt.Errorf("Bad nic type for 
security.ipv4_filtering: %s", m["nictype"])
                                }
-
-                               if m["ipv4.address"] == "" {
-                                       return fmt.Errorf("ipv4.address 
required for security.ipv4_filtering")
-                               }
                        }
 
                        if shared.IsTrue(m["security.ipv6_filtering"]) {
                                if m["nictype"] != "bridged" {
                                        return fmt.Errorf("Bad nic type for 
security.ipv6_filtering: %s", m["nictype"])
                                }
-
-                               if m["ipv6.address"] == "" {
-                                       return fmt.Errorf("ipv6.address 
required for security.ipv6_filtering")
-                               }
                        }
                } else if m["type"] == "infiniband" {
                        if m["nictype"] == "" {

From 0cc23fabe6eac3cbafeac6bb98d0ca84e0f14085 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Fri, 28 Jun 2019 11:44:42 +0100
Subject: [PATCH 04/15] shared/container: Adds volatile prefixes .ipv4_address
 and .ipv6_address

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 shared/container.go | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/shared/container.go b/shared/container.go
index d46ea943e1..dd13e2defa 100644
--- a/shared/container.go
+++ b/shared/container.go
@@ -351,6 +351,14 @@ func ConfigKeyChecker(key string) (func(value string) 
error, error) {
                if strings.HasSuffix(key, ".spoofcheck") {
                        return IsAny, nil
                }
+
+               if strings.HasSuffix(key, ".ipv4_address") {
+                       return IsAny, nil
+               }
+
+               if strings.HasSuffix(key, ".ipv6_address") {
+                       return IsAny, nil
+               }
        }
 
        if strings.HasPrefix(key, "environment.") {

From e3f2a68a90c6c613b423f899293f52356f723b18 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Thu, 27 Jun 2019 16:35:51 +0100
Subject: [PATCH 05/15] networks/utils: Adds networkDHCPAllocatedIPs and
 networkDHCPStaticContainerIPs functions

These functions trawl the static dnsmasq host config files and dynamic leases 
file for a list of all used IPs.

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/networks_utils.go | 147 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 147 insertions(+)

diff --git a/lxd/networks_utils.go b/lxd/networks_utils.go
index 3f84e55b23..619e1e97b4 100644
--- a/lxd/networks_utils.go
+++ b/lxd/networks_utils.go
@@ -879,6 +879,153 @@ func networkGetDnsmasqVersion() (*version.DottedVersion, 
error) {
        return version.NewDottedVersion(lines[2])
 }
 
+// DHCPAllocation represents an IP allocation from dnsmasq.
+type DHCPAllocation struct {
+       IP     net.IP
+       Name   string
+       MAC    net.HardwareAddr
+       Static bool
+}
+
+// networkDHCPStaticContainerIPs retrieves the dnsmasq statically allocated 
IPs for a container.
+// Returns IPv4 and IPv6 DHCPAllocation structs respectively.
+func networkDHCPStaticContainerIPs(network string, containerName string) 
(DHCPAllocation, DHCPAllocation, error) {
+       var IPv4, IPv6 DHCPAllocation
+
+       file, err := os.Open(shared.VarPath("networks", network, 
"dnsmasq.hosts") + "/" + containerName)
+       if err != nil {
+               return IPv4, IPv6, err
+       }
+       defer file.Close()
+
+       scanner := bufio.NewScanner(file)
+       for scanner.Scan() {
+               fields := strings.SplitN(scanner.Text(), ",", -1)
+               for _, field := range fields {
+                       // Check if field is IPv4 or IPv6 address.
+                       if strings.Count(field, ".") == 3 {
+                               IP := net.ParseIP(field)
+                               if IP.To4() == nil {
+                                       return IPv4, IPv6, fmt.Errorf("Error 
parsing IP address: %v", field)
+                               }
+                               IPv4 = DHCPAllocation{Name: containerName, 
Static: true, IP: IP.To4()}
+
+                       } else if strings.HasPrefix(field, "[") && 
strings.HasSuffix(field, "]") {
+                               IP := net.ParseIP(field[1 : len(field)-1])
+                               if IP == nil {
+                                       return IPv4, IPv6, fmt.Errorf("Error 
parsing IP address: %v", field)
+                               }
+                               IPv6 = DHCPAllocation{Name: containerName, 
Static: true, IP: IP}
+                       }
+               }
+       }
+       if err := scanner.Err(); err != nil {
+               return IPv4, IPv6, err
+       }
+
+       return IPv4, IPv6, nil
+}
+
+// networkDHCPAllocatedIPs returns a map of IPs currently allocated 
(statically and dynamically)
+// in dnsmasq for a specific network. The returned map is keyed by a 16 byte 
array representing
+// the net.IP format. The value of each map item is a DHCPAllocation struct 
containing at least
+// whether the allocation was static or dynamic and optionally container name 
or MAC address.
+// MAC addresses are only included for dynamic IPv4 allocations (where name is 
not reliable).
+// Static allocations are not overridden by dynamic allocations, allowing for 
container name to be
+// included for static IPv6 allocations. IPv6 addresses that are dynamically 
assigned cannot be
+// reliably linked to containers using either name or MAC because dnsmasq does 
not record the MAC
+// address for these records, and the recorded host name can be set by the 
container if the dns.mode
+// for the network is set to "dynamic" and so cannot be trusted, so in this 
case we do not return
+// any identifying info.
+func networkDHCPAllocatedIPs(network string) (map[[4]byte]DHCPAllocation, 
map[[16]byte]DHCPAllocation, error) {
+       IPv4s := make(map[[4]byte]DHCPAllocation)
+       IPv6s := make(map[[16]byte]DHCPAllocation)
+
+       // First read all statically allocated IPs.
+       files, err := ioutil.ReadDir(shared.VarPath("networks", network, 
"dnsmasq.hosts"))
+       if err != nil {
+               return IPv4s, IPv6s, err
+       }
+
+       for _, entry := range files {
+               IPv4, IPv6, err := networkDHCPStaticContainerIPs(network, 
entry.Name())
+               if err != nil {
+                       return IPv4s, IPv6s, err
+               }
+
+               if IPv4.IP != nil {
+                       var IPKey [4]byte
+                       copy(IPKey[:], IPv4.IP.To4())
+                       IPv4s[IPKey] = IPv4
+               }
+
+               if IPv6.IP != nil {
+                       var IPKey [16]byte
+                       copy(IPKey[:], IPv6.IP.To16())
+                       IPv6s[IPKey] = IPv6
+               }
+       }
+
+       // Next read all dynamic allocated IPs.
+       file, err := os.Open(shared.VarPath("networks", network, 
"dnsmasq.leases"))
+       if err != nil {
+               return IPv4s, IPv6s, err
+       }
+       defer file.Close()
+
+       scanner := bufio.NewScanner(file)
+       for scanner.Scan() {
+               fields := strings.Fields(scanner.Text())
+               if len(fields) == 5 {
+                       IP := net.ParseIP(fields[2])
+                       if IP == nil {
+                               return IPv4s, IPv6s, fmt.Errorf("Error parsing 
IP address: %v", fields[2])
+                       }
+
+                       // Handle IPv6 addresses.
+                       if IP.To4() == nil {
+                               var IPKey [16]byte
+                               copy(IPKey[:], IP.To16())
+
+                               // Don't replace IPs from static config as more 
reliable.
+                               if IPv6s[IPKey].Name != "" {
+                                       continue
+                               }
+
+                               IPv6s[IPKey] = DHCPAllocation{
+                                       Static: false,
+                                       IP:     IP.To16(),
+                               }
+                       } else {
+                               // MAC only available in IPv4 leases.
+                               MAC, err := net.ParseMAC(fields[1])
+                               if err != nil {
+                                       return IPv4s, IPv6s, err
+                               }
+
+                               var IPKey [4]byte
+                               copy(IPKey[:], IP.To4())
+
+                               // Don't replace IPs from static config as more 
reliable.
+                               if IPv4s[IPKey].Name != "" {
+                                       continue
+                               }
+
+                               IPv4s[IPKey] = DHCPAllocation{
+                                       MAC:    MAC,
+                                       Static: false,
+                                       IP:     IP.To4(),
+                               }
+                       }
+               }
+       }
+       if err := scanner.Err(); err != nil {
+               return IPv4s, IPv6s, err
+       }
+
+       return IPv4s, IPv6s, nil
+}
+
 func networkUpdateStatic(s *state.State, networkName string) error {
        // We don't want to race with ourselves here
        networkStaticLock.Lock()

From 962ed025b34dd937f6e11a105b2ba7671fe5bf07 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Thu, 27 Jun 2019 16:37:07 +0100
Subject: [PATCH 06/15] networks/utils: Adds networkDHCPFindFreeIPv4 and
 networkDHCPFindFreeIPv6 functions

These functions use the used IP lists from networkDHCPAllocatedIPs to figure 
out the next free IP.

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/networks_utils.go | 132 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 132 insertions(+)

diff --git a/lxd/networks_utils.go b/lxd/networks_utils.go
index 619e1e97b4..9461053840 100644
--- a/lxd/networks_utils.go
+++ b/lxd/networks_utils.go
@@ -22,6 +22,7 @@ import (
 
        "github.com/google/gopacket"
        "github.com/google/gopacket/layers"
+       "github.com/mdlayher/eui64"
        "golang.org/x/net/context"
        "golang.org/x/sys/unix"
 
@@ -1026,6 +1027,137 @@ func networkDHCPAllocatedIPs(network string) 
(map[[4]byte]DHCPAllocation, map[[1
        return IPv4s, IPv6s, nil
 }
 
+// networkDHCPFindFreeIPv6 attempts to find a free IPv6 address for the device.
+// It first checks whether there is an existing allocation for the container. 
Due to the limitations
+// of dnsmasq lease file format, we can only search for previous static 
allocations.
+// If no previous allocation, then if SLAAC (stateless) mode is enabled on the 
network, or if
+// DHCPv6 stateful mode is enabled without custom ranges, then an EUI64 IP is 
generated from the
+// device's MAC address. Finally if stateful custom ranges are enabled, then a 
free IP is picked
+// from the ranges configured.
+func networkDHCPFindFreeIPv6(usedIPs map[[16]byte]DHCPAllocation, netConfig 
map[string]string, ctName string, deviceMAC string) (net.IP, error) {
+       // Lets see if there is already an allocation for container on this 
network.
+       for _, DHCP := range usedIPs {
+               if ctName == DHCP.Name {
+                       return DHCP.IP, nil
+               }
+       }
+
+       // If no existing allocation found, try and find a free one in the 
subnet pool.
+       lxdIP, subnet, err := net.ParseCIDR(netConfig["ipv6.address"])
+       if err != nil {
+               return nil, err
+       }
+
+       // Try using an EUI64 IP when in either SLAAC or DHCPv6 stateful mode 
without custom ranges.
+       if !shared.IsTrue(netConfig["ipv6.dhcp.stateful"]) || 
netConfig["ipv6.dhcp.ranges"] == "" {
+               MAC, err := net.ParseMAC(deviceMAC)
+               if err != nil {
+                       return nil, err
+               }
+
+               IP, err := eui64.ParseMAC(subnet.IP, MAC)
+               if err != nil {
+                       return nil, err
+               }
+
+               // Check IP is not already allocated.
+               var IPKey [16]byte
+               copy(IPKey[:], IP.To16())
+               if _, inUse := usedIPs[IPKey]; !inUse {
+                       return IP, nil
+               }
+       }
+
+       // If we get here, then someone already has our SLAAC IP, or we are 
using custom ranges.
+       inc := big.NewInt(1)
+       startBig := big.NewInt(0)
+       startBig.SetBytes(networkGetIP(subnet, 1))
+       endBig := big.NewInt(0)
+       endBig.SetBytes(networkGetIP(subnet, -1))
+
+       for {
+               if startBig.Cmp(endBig) >= 0 {
+                       break
+               }
+
+               IP := net.IP(startBig.Bytes())
+
+               // Check IP generated is not LXD's IP.
+               if IP.Equal(lxdIP) {
+                       startBig.Add(startBig, inc)
+                       continue
+               }
+
+               // Check IP is not already allocated.
+               var IPKey [16]byte
+               copy(IPKey[:], IP.To16())
+               if _, inUse := usedIPs[IPKey]; inUse {
+                       startBig.Add(startBig, inc)
+                       continue
+               }
+
+               // Used by networkUpdateStatic temporarily to build new static 
allocations.
+               return IP, nil
+       }
+
+       return nil, fmt.Errorf("No available IP could not be found")
+}
+
+// networkDHCPFindFreeIPv4 attempts to find a free IPv4 address for the device.
+// It first checks whether there is an existing allocation for the container.
+// If no previous allocation, then a free IP is picked from the ranges 
configured.
+func networkDHCPFindFreeIPv4(usedIPs map[[4]byte]DHCPAllocation, netConfig 
map[string]string, ctName string, deviceMAC string) (net.IP, error) {
+       MAC, err := net.ParseMAC(deviceMAC)
+       if err != nil {
+               return nil, err
+       }
+
+       // Lets see if there is already an allocation for our device.
+       for _, DHCP := range usedIPs {
+               if ctName == DHCP.Name || bytes.Compare(MAC, DHCP.MAC) == 0 {
+                       return DHCP.IP, nil
+               }
+       }
+
+       // If no existing allocation found, try and find a free one in the 
subnet pool.
+       lxdIP, subnet, err := net.ParseCIDR(netConfig["ipv4.address"])
+       if err != nil {
+               return nil, err
+       }
+
+       inc := big.NewInt(1)
+       startBig := big.NewInt(0)
+       startBig.SetBytes(networkGetIP(subnet, 1))
+       endBig := big.NewInt(0)
+       endBig.SetBytes(networkGetIP(subnet, -2))
+
+       for {
+               if startBig.Cmp(endBig) >= 0 {
+                       break
+               }
+
+               IP := net.IP(startBig.Bytes())
+
+               // Check IP generated is not LXD's IP.
+               if IP.Equal(lxdIP) {
+                       startBig.Add(startBig, inc)
+                       continue
+               }
+
+               // Check IP is not already allocated.
+               var IPKey [4]byte
+               copy(IPKey[:], IP.To4())
+               if _, inUse := usedIPs[IPKey]; inUse {
+                       startBig.Add(startBig, inc)
+                       continue
+               }
+
+               return IP, nil
+       }
+
+       return nil, fmt.Errorf("No available IP could not be found")
+}
+
 func networkUpdateStatic(s *state.State, networkName string) error {
        // We don't want to race with ourselves here
        networkStaticLock.Lock()

From 2b8ba7332951c5aca2ad34a93687e3842c2a405e Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Thu, 27 Jun 2019 16:38:44 +0100
Subject: [PATCH 07/15] container/lxc: Adds networkDHCPAllocateIPsStatically
 function

This function decides whether static IPs need allocating and records the result.

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/container_lxc.go | 34 ++++++++++++++++++++++++++++++++++
 1 file changed, 34 insertions(+)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 5463cc2fae..8e2823fcef 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -8854,6 +8854,40 @@ func (c *containerLXC) matchEbtablesRule(activeRule 
[]string, matchRule []string
        return true
 }
 
+// networkDHCPAllocateIPsStatically allocates static IP addresses for a 
container device from the
+// DHCP pool of the parent network. Allocation of IPs is controlled by 
security.ipv4_filtering and
+// security.ipv6_filtering device keys.
+func (c *containerLXC) networkDHCPAllocateIPsStatically(deviceName string, m 
types.Device) error {
+       // Get a list of used IPs for all containers in device's network.
+       usedIPv4s, usedIPv6s, err := networkDHCPAllocatedIPs(m["parent"])
+       if err != nil {
+               return err
+       }
+
+       network, err := networkLoadByName(c.state, m["parent"])
+       if err != nil {
+               return nil
+       }
+
+       if shared.IsTrue(m["security.ipv6_filtering"]) && m["ipv6.address"] == 
"" && m["hwaddr"] != "" {
+               IP, err := networkDHCPFindFreeIPv6(usedIPv6s, network.config, 
c.Name(), m["hwaddr"])
+               if err != nil {
+                       return errors.Wrap(err, fmt.Sprintf("DHCP IPv6 static 
allocation failed for %s device %s", c.Name(), deviceName))
+               }
+               m["ipv6.address"] = IP.String() // Used by 
networkUpdateStatic() to generate dnsmasq config.
+       }
+
+       if shared.IsTrue(m["security.ipv4_filtering"]) && m["ipv4.address"] == 
"" && m["hwaddr"] != "" {
+               IP, err := networkDHCPFindFreeIPv4(usedIPv4s, network.config, 
c.Name(), m["hwaddr"])
+               if err != nil {
+                       return errors.Wrap(err, fmt.Sprintf("DHCP IPv4 static 
allocation failed for %s device %s", c.Name(), deviceName))
+               }
+               m["ipv4.address"] = IP.String() // Used by 
networkUpdateStatic() to generate dnsmasq config.
+       }
+
+       return nil
+}
+
 func (c *containerLXC) insertNetworkDevice(name string, m types.Device) 
(types.Device, error) {
        // Load the go-lxc struct
        err := c.initLXC(false)

From 39146a213d1d37377ee301427f12ff9a2e04eb46 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Thu, 27 Jun 2019 16:39:59 +0100
Subject: [PATCH 08/15] networks/utils: Adds c.networkDHCPAllocateIPsStatically
 to networkUpdateStatic

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/networks_utils.go | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/lxd/networks_utils.go b/lxd/networks_utils.go
index 9461053840..02f113904d 100644
--- a/lxd/networks_utils.go
+++ b/lxd/networks_utils.go
@@ -1203,6 +1203,13 @@ func networkUpdateStatic(s *state.State, networkName 
string) error {
                                entries[d["parent"]] = [][]string{}
                        }
 
+                       if shared.IsTrue(d["security.ipv4_filtering"]) || 
shared.IsTrue(d["security.ipv6_filtering"]) {
+                               err := 
c.(*containerLXC).networkDHCPAllocateIPsStatically(k, d)
+                               if err != nil {
+                                       return err
+                               }
+                       }
+
                        entries[d["parent"]] = append(entries[d["parent"]], 
[]string{d["hwaddr"], c.Project(), c.Name(), d["ipv4.address"], 
d["ipv6.address"]})
                }
        }

From 544b6badc0219ba42fe757766d7364d6be9fd9ba Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Fri, 28 Jun 2019 11:41:42 +0100
Subject: [PATCH 09/15] container/lxc: Consistent comment endings in
 setupHostVethDevice()

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/container_lxc.go | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 8e2823fcef..a6ab5875f4 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -3752,12 +3752,12 @@ func (c *containerLXC) setupHostVethDevice(deviceName 
string, device types.Devic
                return fmt.Errorf("Failed to find host side veth name for 
device \"%s\"", deviceName)
        }
 
-       // Remove any old network filters
+       // Remove any old network filters.
        if oldDevice["nictype"] == "bridged" && 
shared.IsTrue(oldDevice["security.mac_filtering"]) || 
shared.IsTrue(oldDevice["security.ipv4_filtering"]) || 
shared.IsTrue(oldDevice["security.ipv6_filtering"]) {
                c.removeNetworkFilters(deviceName, oldDevice)
        }
 
-       // Setup network filters
+       // Setup network filters.
        if device["nictype"] == "bridged" && 
shared.IsTrue(device["security.mac_filtering"]) || 
shared.IsTrue(device["security.ipv4_filtering"]) || 
shared.IsTrue(device["security.ipv6_filtering"]) {
                err := c.setNetworkFilters(deviceName, device)
                if err != nil {
@@ -3765,7 +3765,7 @@ func (c *containerLXC) setupHostVethDevice(deviceName 
string, device types.Devic
                }
        }
 
-       // Refresh tc limits
+       // Refresh tc limits.
        err := c.setNetworkLimits(device)
        if err != nil {
                return err
@@ -3776,7 +3776,7 @@ func (c *containerLXC) setupHostVethDevice(deviceName 
string, device types.Devic
                c.removeNetworkRoutes(deviceName, oldDevice)
        }
 
-       // Setup static routes to container
+       // Setup static routes to container.
        err = c.setNetworkRoutes(device)
        if err != nil {
                return err

From 5eb7397518ae770eabb3dd1acb6f167cf83b7d4c Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Fri, 28 Jun 2019 11:42:45 +0100
Subject: [PATCH 10/15] container/lxc: Updates
 generateNetworkFilterEbtablesRules to accept IP info as arguments

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/container_lxc.go | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index a6ab5875f4..14a4306336 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -8643,7 +8643,7 @@ func (c *containerLXC) getVolatileHwaddr(deviceName 
string) string {
 }
 
 // generateNetworkFilterEbtablesRules returns a customised set of ebtables 
filter rules based on the device.
-func (c *containerLXC) generateNetworkFilterEbtablesRules(m types.Device) 
[][]string {
+func (c *containerLXC) generateNetworkFilterEbtablesRules(m types.Device, IPv4 
net.IP, IPv6 net.IP) [][]string {
        // MAC source filtering rules. Blocks any packet coming from container 
with an incorrect Ethernet source MAC.
        // This is required for IP filtering too.
        rules := [][]string{
@@ -8651,30 +8651,30 @@ func (c *containerLXC) 
generateNetworkFilterEbtablesRules(m types.Device) [][]st
                {"ebtables", "-t", "filter", "-A", "FORWARD", "-s", "!", 
m["hwaddr"], "-i", m["host_name"], "-j", "DROP"},
        }
 
-       if shared.IsTrue(m["security.ipv4_filtering"]) && m["ipv4.address"] != 
"" {
+       if shared.IsTrue(m["security.ipv4_filtering"]) && IPv4 != nil {
                rules = append(rules,
                        // Prevent ARP MAC spoofing (prevents the container 
poisoning the ARP cache of its neighbours with a MAC address that isn't its 
own).
                        []string{"ebtables", "-t", "filter", "-A", "INPUT", 
"-p", "ARP", "-i", m["host_name"], "--arp-mac-src", "!", m["hwaddr"], "-j", 
"DROP"},
                        []string{"ebtables", "-t", "filter", "-A", "FORWARD", 
"-p", "ARP", "-i", m["host_name"], "--arp-mac-src", "!", m["hwaddr"], "-j", 
"DROP"},
                        // Prevent ARP IP spoofing (prevents the container 
redirecting traffic for IPs that are not its own).
-                       []string{"ebtables", "-t", "filter", "-A", "INPUT", 
"-p", "ARP", "-i", m["host_name"], "--arp-ip-src", "!", m["ipv4.address"], 
"-j", "DROP"},
-                       []string{"ebtables", "-t", "filter", "-A", "FORWARD", 
"-p", "ARP", "-i", m["host_name"], "--arp-ip-src", "!", m["ipv4.address"], 
"-j", "DROP"},
+                       []string{"ebtables", "-t", "filter", "-A", "INPUT", 
"-p", "ARP", "-i", m["host_name"], "--arp-ip-src", "!", IPv4.String(), "-j", 
"DROP"},
+                       []string{"ebtables", "-t", "filter", "-A", "FORWARD", 
"-p", "ARP", "-i", m["host_name"], "--arp-ip-src", "!", IPv4.String(), "-j", 
"DROP"},
                        // Allow DHCPv4 to the host only. This must come before 
the IP source filtering rules below.
                        []string{"ebtables", "-t", "filter", "-A", "INPUT", 
"-p", "IPv4", "-s", m["hwaddr"], "-i", m["host_name"], "--ip-src", "0.0.0.0", 
"--ip-dst", "255.255.255.255", "--ip-proto", "udp", "--ip-dport", "67", "-j", 
"ACCEPT"},
                        // IP source filtering rules. Blocks any packet coming 
from container with an incorrect IP source address.
-                       []string{"ebtables", "-t", "filter", "-A", "INPUT", 
"-p", "IPv4", "-i", m["host_name"], "--ip-src", "!", m["ipv4.address"], "-j", 
"DROP"},
-                       []string{"ebtables", "-t", "filter", "-A", "FORWARD", 
"-p", "IPv4", "-i", m["host_name"], "--ip-src", "!", m["ipv4.address"], "-j", 
"DROP"},
+                       []string{"ebtables", "-t", "filter", "-A", "INPUT", 
"-p", "IPv4", "-i", m["host_name"], "--ip-src", "!", IPv4.String(), "-j", 
"DROP"},
+                       []string{"ebtables", "-t", "filter", "-A", "FORWARD", 
"-p", "IPv4", "-i", m["host_name"], "--ip-src", "!", IPv4.String(), "-j", 
"DROP"},
                )
        }
 
-       if shared.IsTrue(m["security.ipv6_filtering"]) && m["ipv6.address"] != 
"" {
+       if shared.IsTrue(m["security.ipv6_filtering"]) && IPv6 != nil {
                rules = append(rules,
                        // Allow DHCPv6 and Router Solicitation to the host 
only. This must come before the IP source filtering rules below.
                        []string{"ebtables", "-t", "filter", "-A", "INPUT", 
"-p", "IPv6", "-s", m["hwaddr"], "-i", m["host_name"], "--ip6-src", 
"fe80::/ffc0::", "--ip6-dst", 
"ff02::1:2/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "--ip6-proto", "udp", 
"--ip6-dport", "547", "-j", "ACCEPT"},
                        []string{"ebtables", "-t", "filter", "-A", "INPUT", 
"-p", "IPv6", "-s", m["hwaddr"], "-i", m["host_name"], "--ip6-src", 
"fe80::/ffc0::", "--ip6-dst", 
"ff02::2/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "--ip6-proto", "ipv6-icmp", 
"--ip6-icmp-type", "router-solicitation", "-j", "ACCEPT"},
                        // IP source filtering rules. Blocks any packet coming 
from container with an incorrect IP source address.
-                       []string{"ebtables", "-t", "filter", "-A", "INPUT", 
"-p", "IPv6", "-i", m["host_name"], "--ip6-src", "!", 
fmt.Sprintf("%s/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", m["ipv6.address"]), 
"-j", "DROP"},
-                       []string{"ebtables", "-t", "filter", "-A", "FORWARD", 
"-p", "IPv6", "-i", m["host_name"], "--ip6-src", "!", 
fmt.Sprintf("%s/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", m["ipv6.address"]), 
"-j", "DROP"},
+                       []string{"ebtables", "-t", "filter", "-A", "INPUT", 
"-p", "IPv6", "-i", m["host_name"], "--ip6-src", "!", 
fmt.Sprintf("%s/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", IPv6.String()), "-j", 
"DROP"},
+                       []string{"ebtables", "-t", "filter", "-A", "FORWARD", 
"-p", "IPv6", "-i", m["host_name"], "--ip6-src", "!", 
fmt.Sprintf("%s/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", IPv6.String()), "-j", 
"DROP"},
                )
        }
 

From 1f95eee35299e27d84c7bb2160b3f3243bdcd284 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Fri, 28 Jun 2019 11:43:15 +0100
Subject: [PATCH 11/15] container/lxc: Updates
 generateNetworkFilterIptablesRules to accept IP info as arguments

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/container_lxc.go | 12 +++---------
 1 file changed, 3 insertions(+), 9 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 14a4306336..21b4723233 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -8682,7 +8682,7 @@ func (c *containerLXC) 
generateNetworkFilterEbtablesRules(m types.Device, IPv4 n
 }
 
 // generateNetworkFilterIptablesRules returns a customised set of iptables 
filter rules based on the device.
-func (c *containerLXC) generateNetworkFilterIptablesRules(m types.Device) 
(rules [][]string, err error) {
+func (c *containerLXC) generateNetworkFilterIptablesRules(m types.Device, IPv6 
net.IP) (rules [][]string, err error) {
        mac, err := net.ParseMAC(m["hwaddr"])
        if err != nil {
                return
@@ -8698,14 +8698,8 @@ func (c *containerLXC) 
generateNetworkFilterIptablesRules(m types.Device) (rules
        // not assigned to the container by sending a specially crafted 
gratuitous NDP packet with
        // correct source address and MAC at the IP & ethernet layers, but a 
fraudulent IP or MAC
        // inside the ICMPv6 NDP packet.
-       if shared.IsTrue(m["security.ipv6_filtering"]) && m["ipv6.address"] != 
"" {
-               ipv6 := net.ParseIP(m["ipv6.address"])
-               if ipv6 == nil {
-                       err = fmt.Errorf("Invalid IPv6 address")
-                       return
-               }
-
-               ipv6Hex := hex.EncodeToString(ipv6)
+       if shared.IsTrue(m["security.ipv6_filtering"]) && IPv6 != nil {
+               ipv6Hex := hex.EncodeToString(IPv6)
 
                rules = append(rules,
                        // Prevent Neighbor Advertisement IP spoofing (prevents 
the container redirecting traffic for IPs that are not its own).

From adfc682c4be08a7a7b611fa93d2f7336b3bfbc56 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Fri, 28 Jun 2019 11:44:00 +0100
Subject: [PATCH 12/15] container/lxc: Updates setNetworkFilters to read IP
 info from dnsmasq host config

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/container_lxc.go | 20 ++++++++++++++++++--
 1 file changed, 18 insertions(+), 2 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 21b4723233..3641e97ef4 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -8737,6 +8737,22 @@ func (c *containerLXC) setNetworkFilters(deviceName 
string, m types.Device) (err
                }
        }
 
+       // Read static IP configured from dnsmasq host config.
+       IPv4, IPv6, err := networkDHCPStaticContainerIPs(m["parent"], c.Name())
+       if err != nil {
+               return err
+       }
+       volatile := map[string]string{
+               fmt.Sprintf("volatile.%s.ipv4_address", deviceName): 
IPv4.IP.String(),
+               fmt.Sprintf("volatile.%s.ipv6_address", deviceName): 
IPv6.IP.String(),
+       }
+
+       // Store the IPs used for filter for removal later.
+       err = c.VolatileSet(volatile)
+       if err != nil {
+               return err
+       }
+
        // If anything goes wrong, clean up so we don't leave orphaned rules.
        defer func() {
                if err != nil {
@@ -8744,7 +8760,7 @@ func (c *containerLXC) setNetworkFilters(deviceName 
string, m types.Device) (err
                }
        }()
 
-       rules := c.generateNetworkFilterEbtablesRules(m)
+       rules := c.generateNetworkFilterEbtablesRules(m, IPv4.IP, IPv6.IP)
        for _, rule := range rules {
                _, err = shared.RunCommand(rule[0], rule[1:]...)
                if err != nil {
@@ -8752,7 +8768,7 @@ func (c *containerLXC) setNetworkFilters(deviceName 
string, m types.Device) (err
                }
        }
 
-       rules, err = c.generateNetworkFilterIptablesRules(m)
+       rules, err = c.generateNetworkFilterIptablesRules(m, IPv6.IP)
        if err != nil {
                return err
        }

From 3447602c9d9deca307e151b87c047508a433fdf5 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Fri, 28 Jun 2019 11:44:23 +0100
Subject: [PATCH 13/15] container/lxc: Updates removeNetworkFilters to read IP
 info from volatile data

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/container_lxc.go | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 3641e97ef4..eff53954f3 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -8801,6 +8801,9 @@ func (c *containerLXC) removeNetworkFilters(deviceName 
string, m types.Device) {
                logger.Error("Failed to clear ip6tables ipv6_filter rules: ", 
log.Ctx{"container": c.Name(), "device": deviceName, "err": err})
        }
 
+       IPv4 := 
net.ParseIP(c.localConfig[fmt.Sprintf("volatile.%s.ipv4_address", deviceName)])
+       IPv6 := 
net.ParseIP(c.localConfig[fmt.Sprintf("volatile.%s.ipv6_address", deviceName)])
+
        // Get a current list of rules active on the host.
        out, err := shared.RunCommand("ebtables", "-L", "--Lmac2", "--Lx")
        if err != nil {
@@ -8809,7 +8812,7 @@ func (c *containerLXC) removeNetworkFilters(deviceName 
string, m types.Device) {
        }
 
        // Get a list of rules that we would have applied on container start.
-       rules := c.generateNetworkFilterEbtablesRules(m)
+       rules := c.generateNetworkFilterEbtablesRules(m, IPv4, IPv6)
 
        // Iterate through each active rule on the host and try and match it to 
one the LXD rules.
        for _, line := range strings.Split(out, "\n") {

From b88e64ed786d7cb8e59d87237e6e37f26d87b949 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Fri, 28 Jun 2019 13:32:40 +0100
Subject: [PATCH 14/15] container/lxc: Queues requests for
 setupHostVethDevice() during Update()

Queues requests for setupHostVethDevice() during Update() until after dnsmasq 
config has been updated (including any new IP allocations), so that IP 
filtering can be configured correctly.

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/container_lxc.go | 32 ++++++++++++++++++++++++--------
 1 file changed, 24 insertions(+), 8 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index eff53954f3..60bea89a2f 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -5081,6 +5081,11 @@ func (c *containerLXC) Update(args db.ContainerArgs, 
userRequested bool) error {
                }
        }
 
+       // vethSetupQueue stores request to run veth setup functions after the 
dnsmasq config
+       // has been regenerated. This ensures that any dynamic IPs allocated 
for IP filtering are
+       // availble to the veth setup functions.
+       vethSetupQueue := make([]func() error, 0)
+
        // Apply the live changes
        if isRunning {
                // Live update the container config
@@ -5534,10 +5539,10 @@ func (c *containerLXC) Update(args db.ContainerArgs, 
userRequested bool) error {
                                                return err
                                        }
 
-                                       err = c.setupHostVethDevice(k, m, 
types.Device{})
-                                       if err != nil {
-                                               return err
-                                       }
+                                       // Add a call to setupHostVethDevice to 
do later after dnsmasq update.
+                                       vethSetupQueue = append(vethSetupQueue, 
func() error {
+                                               return c.setupHostVethDevice(k, 
m, types.Device{})
+                                       })
                                }
                        } else if m["type"] == "usb" {
                                if usbs == nil {
@@ -5650,10 +5655,10 @@ func (c *containerLXC) Update(args db.ContainerArgs, 
userRequested bool) error {
                                        return err
                                }
 
-                               err = c.setupHostVethDevice(k, m, 
oldExpandedDevices[k])
-                               if err != nil {
-                                       return err
-                               }
+                               // Add a call to setupHostVethDevice to do 
later after dnsmasq update.
+                               vethSetupQueue = append(vethSetupQueue, func() 
error {
+                                       return c.setupHostVethDevice(k, m, 
oldExpandedDevices[k])
+                               })
                        } else if m["type"] == "proxy" {
                                err = c.updateProxyDevice(k, m)
                                if err != nil {
@@ -5804,6 +5809,17 @@ func (c *containerLXC) Update(args db.ContainerArgs, 
userRequested bool) error {
 
        if needsUpdate {
                networkUpdateStatic(c.state, "")
+
+               // Once the dnsmasq config is updated (including allocating any 
new IPs), then if
+               // any of the devices that have changed earlier have queued a 
request to run
+               // veth host setup then run these now. This way we can be sure 
if IP filtering is
+               // being setup that it will have the correct IP to work with.
+               for _, f := range vethSetupQueue {
+                       err := f()
+                       if err != nil {
+                               return nil
+                       }
+               }
        }
 
        // Send devlxd notifications

From 62b0e644d35f5e9e7ec90430a706597cc7723e96 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Fri, 28 Jun 2019 13:44:29 +0100
Subject: [PATCH 15/15] container/lxc: Adds more prominent error about missing
 br_netfilter module on start

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/container_lxc.go | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 60bea89a2f..83c00bb1d2 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -2134,6 +2134,14 @@ func (c *containerLXC) startCommon() (string, error) {
                        if m["parent"] != "" && 
!shared.PathExists(fmt.Sprintf("/sys/class/net/%s", m["parent"])) {
                                return "", fmt.Errorf("Missing parent '%s' for 
nic '%s'", m["parent"], name)
                        }
+
+                       if shared.IsTrue(m["security.ipv6_filtering"]) {
+                               // Check br_netfilter is loaded and enabled for 
IPv6.
+                               sysctlVal, err := 
networkSysctlGet("bridge/bridge-nf-call-ip6tables")
+                               if err != nil || sysctlVal != "1\n" {
+                                       return "", errors.Wrapf(err, 
"security.ipv6_filtering requires br_netfilter and sysctl 
net.bridge.bridge-nf-call-ip6tables=1")
+                               }
+                       }
                case "unix-char", "unix-block":
                        srcPath, exist := m["source"]
                        if !exist {
_______________________________________________
lxc-devel mailing list
lxc-devel@lists.linuxcontainers.org
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to