[lxc-devel] [lxd/master] Network: DHCP static allocation package

tomponline on Github lxc-bot at linuxcontainers.org
Fri Jul 31 13:57:48 UTC 2020


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 1687 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20200731/14b81ac1/attachment-0001.bin>
-------------- next part --------------
From 2cfc0302744a521ed69ce40411da5e02c984898d Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 30 Jul 2020 13:13:02 +0100
Subject: [PATCH 01/19] lxd/networks: Validate network config before starting
 networks on startup

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

diff --git a/lxd/networks.go b/lxd/networks.go
index 7f7913e4ec..5f892d3bcf 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -943,10 +943,18 @@ func networkStartup(s *state.State) error {
 			return err
 		}
 
+		err = n.Validate(n.Config())
+		if err != nil {
+			// Don't cause LXD to fail to start entirely on network start up failure.
+			logger.Error("Failed to validate network", log.Ctx{"err": err, "name": name})
+			continue
+		}
+
 		err = n.Start()
 		if err != nil {
 			// Don't cause LXD to fail to start entirely on network start up failure.
 			logger.Error("Failed to bring up network", log.Ctx{"err": err, "name": name})
+			continue
 		}
 	}
 

From 3d05d05f685e6c5e41df5aa14b465d1ba50a88c1 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 30 Jul 2020 11:32:39 +0100
Subject: [PATCH 02/19] lxd/network/driver/common: Call init() in update() to
 consistency apply new internal state

Same as is done in rename().

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/network/driver_common.go | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/lxd/network/driver_common.go b/lxd/network/driver_common.go
index 584d90ec7f..76a5675e9f 100644
--- a/lxd/network/driver_common.go
+++ b/lxd/network/driver_common.go
@@ -231,8 +231,7 @@ func (n *common) DHCPv6Ranges() []DHCPRange {
 func (n *common) update(applyNetwork api.NetworkPut, targetNode string, clusterNotification bool) error {
 	// Update internal config before database has been updated (so that if update is a notification we apply
 	// the config being supplied and not that in the database).
-	n.description = applyNetwork.Description
-	n.config = applyNetwork.Config
+	n.init(n.state, n.id, n.name, n.netType, applyNetwork.Description, applyNetwork.Config, n.status)
 
 	// If this update isn't coming via a cluster notification itself, then notify all nodes of change and then
 	// update the database.

From b3ae5837b5ef1829b2c2ca318654a85fafe669af Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 30 Jul 2020 18:06:56 +0100
Subject: [PATCH 03/19] lxd/device/device/utils/network: Removes
 networkDHCPValidIP

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/device_utils_network.go | 22 ----------------------
 1 file changed, 22 deletions(-)

diff --git a/lxd/device/device_utils_network.go b/lxd/device/device_utils_network.go
index 71295a098d..9556bfda9c 100644
--- a/lxd/device/device_utils_network.go
+++ b/lxd/device/device_utils_network.go
@@ -1,12 +1,10 @@
 package device
 
 import (
-	"bytes"
 	"crypto/rand"
 	"encoding/hex"
 	"fmt"
 	"io/ioutil"
-	"net"
 	"strconv"
 	"strings"
 	"sync"
@@ -596,23 +594,3 @@ func networkInterfaceBindWait(ifName string) error {
 
 	return fmt.Errorf("Bind of interface %q took too long", ifName)
 }
-
-// networkDHCPValidIP returns whether an IP fits inside one of the supplied DHCP ranges and subnet.
-func networkDHCPValidIP(subnet *net.IPNet, ranges []network.DHCPRange, IP net.IP) bool {
-	inSubnet := subnet.Contains(IP)
-	if !inSubnet {
-		return false
-	}
-
-	if len(ranges) > 0 {
-		for _, IPRange := range ranges {
-			if bytes.Compare(IP, IPRange.Start) >= 0 && bytes.Compare(IP, IPRange.End) <= 0 {
-				return true
-			}
-		}
-	} else if inSubnet {
-		return true
-	}
-
-	return false
-}

From c52ed3220cc134610143fff38e63bfde47ab3a15 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jul 2020 13:36:24 +0100
Subject: [PATCH 04/19] lxd/dnsmasq/dhcpalloc: Adds static DHCP allocation
 package for dnsmasq

This package allows one to allocate a static IP allocation (both IPv4 and IPV6) in dnsmasq's config for a particular hostname & mac combination.

This decouples the old allocation logic that was in device/nic_bridged.go from the IP filtering concepts for which it was originally added.

Now the plan is to use this same logic for OVN uplink port allocation on external LXD bridges.

This package provides a transaction style logic, to allow per-protocol allocation logic in the calling code, without incurring the overhead of having to parse the existing dnsmasq leases file once per protocol.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/dnsmasq/dhcpalloc/dhcpalloc.go | 408 +++++++++++++++++++++++++++++
 1 file changed, 408 insertions(+)
 create mode 100644 lxd/dnsmasq/dhcpalloc/dhcpalloc.go

diff --git a/lxd/dnsmasq/dhcpalloc/dhcpalloc.go b/lxd/dnsmasq/dhcpalloc/dhcpalloc.go
new file mode 100644
index 0000000000..736f2832b5
--- /dev/null
+++ b/lxd/dnsmasq/dhcpalloc/dhcpalloc.go
@@ -0,0 +1,408 @@
+package dhcpalloc
+
+import (
+	"bytes"
+	"encoding/binary"
+	"errors"
+	"fmt"
+	"math"
+	"math/big"
+	"net"
+	"os"
+
+	"github.com/mdlayher/netx/eui64"
+
+	"github.com/lxc/lxd/lxd/dnsmasq"
+	"github.com/lxc/lxd/shared"
+	log "github.com/lxc/lxd/shared/log15"
+	"github.com/lxc/lxd/shared/logger"
+	"github.com/lxc/lxd/shared/logging"
+)
+
+// ErrDHCPNotSupported indicates network doesn't support DHCP for this IP protocol.
+var ErrDHCPNotSupported error = errors.New("Network doesn't support DHCP")
+
+// DHCPRange represents a range of IPs from start to end.
+type DHCPRange struct {
+	Start net.IP
+	End   net.IP
+}
+
+// DHCPValidIP returns whether an IP fits inside one of the supplied DHCP ranges and subnet.
+func DHCPValidIP(subnet *net.IPNet, ranges []DHCPRange, IP net.IP) bool {
+	inSubnet := subnet.Contains(IP)
+	if !inSubnet {
+		return false
+	}
+
+	if len(ranges) > 0 {
+		for _, IPRange := range ranges {
+			if bytes.Compare(IP, IPRange.Start) >= 0 && bytes.Compare(IP, IPRange.End) <= 0 {
+				return true
+			}
+		}
+	} else if inSubnet {
+		return true
+	}
+
+	return false
+}
+
+// GetIP returns a net.IP representing the IP belonging to the subnet for the host number supplied.
+func GetIP(subnet *net.IPNet, host int64) net.IP {
+	// Convert IP to a big int.
+	bigIP := big.NewInt(0)
+	bigIP.SetBytes(subnet.IP.To16())
+
+	// Deal with negative offsets.
+	bigHost := big.NewInt(host)
+	bigCount := big.NewInt(host)
+	if host < 0 {
+		mask, size := subnet.Mask.Size()
+
+		bigHosts := big.NewFloat(0)
+		bigHosts.SetFloat64((math.Pow(2, float64(size-mask))))
+		bigHostsInt, _ := bigHosts.Int(nil)
+
+		bigCount.Set(bigHostsInt)
+		bigCount.Add(bigCount, bigHost)
+	}
+
+	// Get the new IP int.
+	bigIP.Add(bigIP, bigCount)
+
+	// Generate an IPv6.
+	if subnet.IP.To4() == nil {
+		newIP := bigIP.Bytes()
+		return newIP
+	}
+
+	// Generate an IPv4.
+	newIP := make(net.IP, 4)
+	binary.BigEndian.PutUint32(newIP, uint32(bigIP.Int64()))
+	return newIP
+}
+
+// Network represents a LXD network responsible for running dnsmasq.
+type Network interface {
+	Name() string
+	Type() string
+	Config() map[string]string
+	DHCPv4Subnet() *net.IPNet
+	DHCPv6Subnet() *net.IPNet
+	DHCPv4Ranges() []DHCPRange
+	DHCPv6Ranges() []DHCPRange
+}
+
+// Options to initialise the allocator with.
+type Options struct {
+	ProjectName string
+	HostName    string
+	HostMAC     net.HardwareAddr
+	Network     Network
+}
+
+// Transaction is a locked transaction of the dnsmasq config files that allows IP allocations for a host.
+type Transaction struct {
+	opts              *Options
+	currentDHCPMAC    net.HardwareAddr
+	currentDHCPv4     dnsmasq.DHCPAllocation
+	currentDHCPv6     dnsmasq.DHCPAllocation
+	allocationsDHCPv4 map[[4]byte]dnsmasq.DHCPAllocation
+	allocationsDHCPv6 map[[16]byte]dnsmasq.DHCPAllocation
+	allocatedIPv4     net.IP
+	allocatedIPv6     net.IP
+}
+
+// AllocateIPv4 allocate an IPv4 static DHCP allocation.
+func (t *Transaction) AllocateIPv4() (net.IP, error) {
+	var err error
+
+	// Should have a (at least empty) map if DHCP is supported
+	if t.allocationsDHCPv4 == nil {
+		return nil, ErrDHCPNotSupported
+	}
+
+	dhcpSubnet := t.opts.Network.DHCPv4Subnet()
+	if dhcpSubnet == nil {
+		return nil, ErrDHCPNotSupported
+	}
+
+	// Check the existing allocated IP is still valid in the network's subnet & ranges, if not then
+	// we'll need to generate a new one.
+	if t.allocatedIPv4 != nil {
+		ranges := t.opts.Network.DHCPv4Ranges()
+		if !DHCPValidIP(dhcpSubnet, ranges, t.allocatedIPv4.To4()) {
+			t.allocatedIPv4 = nil // We need a new IP allocated.
+		}
+	}
+
+	// Allocate a new IPv4 address if needed.
+	if t.allocatedIPv4 == nil {
+		t.allocatedIPv4, err = t.getDHCPFreeIPv4(t.allocationsDHCPv4, t.opts.HostName, t.opts.HostMAC)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return t.allocatedIPv4, nil
+}
+
+// AllocateIPv6 allocate an IPv6 static DHCP allocation.
+func (t *Transaction) AllocateIPv6() (net.IP, error) {
+	var err error
+
+	// Should have a (at least empty) map if DHCP is supported
+	if t.allocationsDHCPv6 == nil {
+		return nil, ErrDHCPNotSupported
+	}
+
+	dhcpSubnet := t.opts.Network.DHCPv6Subnet()
+	if dhcpSubnet == nil {
+		return nil, ErrDHCPNotSupported
+	}
+
+	// Check the existing allocated IP is still valid in the network's subnet & ranges, if not then
+	// we'll need to generate a new one.
+	if t.allocatedIPv6 != nil {
+		ranges := t.opts.Network.DHCPv6Ranges()
+		if !DHCPValidIP(dhcpSubnet, ranges, t.allocatedIPv6.To16()) {
+			t.allocatedIPv6 = nil // We need a new IP allocated.
+		}
+	}
+
+	// Allocate a new IPv6 address if needed.
+	if t.allocatedIPv6 == nil {
+		t.allocatedIPv6, err = t.getDHCPFreeIPv6(t.allocationsDHCPv6, t.opts.HostName, t.opts.HostMAC)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return t.allocatedIPv6, nil
+}
+
+// getDHCPFreeIPv4 attempts to find a free IPv4 address for the device.
+// It first checks whether there is an existing allocation for the instance.
+// If no previous allocation, then a free IP is picked from the ranges configured.
+func (t *Transaction) getDHCPFreeIPv4(usedIPs map[[4]byte]dnsmasq.DHCPAllocation, instName string, mac net.HardwareAddr) (net.IP, error) {
+	lxdIP, subnet, err := net.ParseCIDR(t.opts.Network.Config()["ipv4.address"])
+	if err != nil {
+		return nil, err
+	}
+
+	dhcpRanges := t.opts.Network.DHCPv4Ranges()
+
+	// Lets see if there is already an allocation for our device and that it sits within subnet.
+	// If there are custom DHCP ranges defined, check also that the IP falls within one of the ranges.
+	for _, DHCP := range usedIPs {
+		if (instName == DHCP.Name || bytes.Compare(mac, DHCP.MAC) == 0) && DHCPValidIP(subnet, dhcpRanges, DHCP.IP) {
+			return DHCP.IP, nil
+		}
+	}
+
+	// If no custom ranges defined, convert subnet pool to a range.
+	if len(dhcpRanges) <= 0 {
+		dhcpRanges = append(dhcpRanges, DHCPRange{
+			Start: GetIP(subnet, 1).To4(),
+			End:   GetIP(subnet, -2).To4()},
+		)
+	}
+
+	// If no valid existing allocation found, try and find a free one in the subnet pool/ranges.
+	for _, IPRange := range dhcpRanges {
+		inc := big.NewInt(1)
+		startBig := big.NewInt(0)
+		startBig.SetBytes(IPRange.Start)
+		endBig := big.NewInt(0)
+		endBig.SetBytes(IPRange.End)
+
+		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())
+
+			_, inUse := usedIPs[IPKey]
+			if inUse {
+				startBig.Add(startBig, inc)
+				continue
+			}
+
+			return IP, nil
+		}
+	}
+
+	return nil, fmt.Errorf("No available IP could not be found")
+}
+
+// getDHCPFreeIPv6 attempts to find a free IPv6 address for the device.
+// It first checks whether there is an existing allocation for the instance. 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 (t *Transaction) getDHCPFreeIPv6(usedIPs map[[16]byte]dnsmasq.DHCPAllocation, instName string, mac net.HardwareAddr) (net.IP, error) {
+	lxdIP, subnet, err := net.ParseCIDR(t.opts.Network.Config()["ipv6.address"])
+	if err != nil {
+		return nil, err
+	}
+
+	dhcpRanges := t.opts.Network.DHCPv6Ranges()
+
+	// Lets see if there is already an allocation for our device and that it sits within subnet.
+	// Because of dnsmasq's lease file format we can only match safely against static
+	// allocations using instance name. If there are custom DHCP ranges defined, check also
+	// that the IP falls within one of the ranges.
+	for _, DHCP := range usedIPs {
+		if instName == DHCP.Name && DHCPValidIP(subnet, dhcpRanges, DHCP.IP) {
+			return DHCP.IP, nil
+		}
+	}
+
+	netConfig := t.opts.Network.Config()
+
+	// 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"] == "" {
+		IP, err := eui64.ParseMAC(subnet.IP, mac)
+		if err != nil {
+			return nil, err
+		}
+
+		// Check IP is not already allocated and not the LXD IP.
+		var IPKey [16]byte
+		copy(IPKey[:], IP.To16())
+		_, inUse := usedIPs[IPKey]
+		if !inUse && !IP.Equal(lxdIP) {
+			return IP, nil
+		}
+	}
+
+	// If no custom ranges defined, convert subnet pool to a range.
+	if len(dhcpRanges) <= 0 {
+		dhcpRanges = append(dhcpRanges, DHCPRange{
+			Start: GetIP(subnet, 1).To16(),
+			End:   GetIP(subnet, -1).To16()},
+		)
+	}
+
+	// If we get here, then someone already has our SLAAC IP, or we are using custom ranges.
+	// Try and find a free one in the subnet pool/ranges.
+	for _, IPRange := range dhcpRanges {
+		inc := big.NewInt(1)
+		startBig := big.NewInt(0)
+		startBig.SetBytes(IPRange.Start)
+		endBig := big.NewInt(0)
+		endBig.SetBytes(IPRange.End)
+
+		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())
+
+			_, inUse := usedIPs[IPKey]
+			if inUse {
+				startBig.Add(startBig, inc)
+				continue
+			}
+
+			return IP, nil
+		}
+	}
+
+	return nil, fmt.Errorf("No available IP could not be found")
+}
+
+// AllocateTask initialises a new locked Transaction for a specific host and executes the supplied function on it.
+// The lock on the dnsmasq config is released when the function returns.
+func AllocateTask(opts *Options, f func(*Transaction) error) error {
+	logger := logging.AddContext(logger.Log, log.Ctx{"driver": opts.Network.Type(), "network": opts.Network.Name(), "project": opts.ProjectName, "host": opts.HostName})
+
+	dnsmasq.ConfigMutex.Lock()
+	defer dnsmasq.ConfigMutex.Unlock()
+
+	var err error
+	t := &Transaction{opts: opts}
+
+	// Read current static IP allocation configured from dnsmasq host config (if exists).
+	t.currentDHCPMAC, t.currentDHCPv4, t.currentDHCPv6, err = dnsmasq.DHCPStaticAllocation(opts.Network.Name(), opts.ProjectName, opts.HostName)
+	if err != nil && !os.IsNotExist(err) {
+		return err
+	}
+
+	// Set the current allocated IPs internally (these may be changed later), and if they are it will trigger
+	// a dnsmasq static host config file rebuild.
+	t.allocatedIPv4 = t.currentDHCPv4.IP
+	t.allocatedIPv6 = t.currentDHCPv6.IP
+
+	// Get all existing allocations in network if leases file exists. If not then we will detect this later
+	// due to the existing allocations maps being nil.
+	if shared.PathExists(shared.VarPath("networks", opts.Network.Name(), "dnsmasq.leases")) {
+		t.allocationsDHCPv4, t.allocationsDHCPv6, err = dnsmasq.DHCPAllAllocations(opts.Network.Name())
+		if err != nil {
+			return err
+		}
+	}
+
+	// Run the supplied allocation function.
+	err = f(t)
+	if err != nil {
+		return err
+	}
+
+	// If MAC or either IPv4 or IPv6 assigned is different than what is in dnsmasq config, rebuild config.
+	macChanged := bytes.Compare(opts.HostMAC, t.currentDHCPMAC) != 0
+	ipv4Changed := (t.allocatedIPv4 != nil && bytes.Compare(t.currentDHCPv4.IP, t.allocatedIPv4.To4()) != 0)
+	ipv6Changed := (t.allocatedIPv6 != nil && bytes.Compare(t.currentDHCPv6.IP, t.allocatedIPv6.To16()) != 0)
+
+	if macChanged || ipv4Changed || ipv6Changed {
+		var IPv4Str, IPv6Str string
+
+		if t.allocatedIPv4 != nil {
+			IPv4Str = t.allocatedIPv4.String()
+		}
+
+		if t.allocatedIPv6 != nil {
+			IPv6Str = t.allocatedIPv6.String()
+		}
+
+		// Write out new dnsmasq static host allocation config file.
+		err = dnsmasq.UpdateStaticEntry(opts.Network.Name(), opts.ProjectName, opts.HostName, opts.Network.Config(), opts.HostMAC.String(), IPv4Str, IPv6Str)
+		if err != nil {
+			return err
+		}
+		logger.Debug("Updated static DHCP entry", log.Ctx{"mac": opts.HostMAC.String(), "IPv4": IPv4Str, "IPv6": IPv6Str})
+
+		// Reload dnsmasq.
+		err = dnsmasq.Kill(opts.Network.Name(), true)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}

From b9c02d2a679cca01f58c707127df6d4dfebeacc9 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jul 2020 14:06:03 +0100
Subject: [PATCH 05/19] lxd/dnsmasq: Renames DHCPStaticIPs to
 DHCPStaticAllocation

Also adds MAC address of allocation to returned values.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/dnsmasq/dnsmasq.go | 28 ++++++++++++++++++----------
 1 file changed, 18 insertions(+), 10 deletions(-)

diff --git a/lxd/dnsmasq/dnsmasq.go b/lxd/dnsmasq/dnsmasq.go
index 82f7f5f7e0..58e0565ecb 100644
--- a/lxd/dnsmasq/dnsmasq.go
+++ b/lxd/dnsmasq/dnsmasq.go
@@ -110,14 +110,15 @@ func GetVersion() (*version.DottedVersion, error) {
 	return version.NewDottedVersion(lines[2])
 }
 
-// DHCPStaticIPs retrieves the dnsmasq statically allocated IPs for a container.
-// Returns IPv4 and IPv6 DHCPAllocation structs respectively.
-func DHCPStaticIPs(network, projectName, instanceName string) (DHCPAllocation, DHCPAllocation, error) {
+// DHCPStaticAllocation retrieves the dnsmasq statically allocated MAC and IPs for an instance.
+// Returns MAC, IPv4 and IPv6 DHCPAllocation structs respectively.
+func DHCPStaticAllocation(network, projectName, instanceName string) (net.HardwareAddr, DHCPAllocation, DHCPAllocation, error) {
 	var IPv4, IPv6 DHCPAllocation
+	var mac net.HardwareAddr
 
 	file, err := os.Open(shared.VarPath("networks", network, "dnsmasq.hosts", project.Instance(projectName, instanceName)))
 	if err != nil {
-		return IPv4, IPv6, err
+		return nil, IPv4, IPv6, err
 	}
 	defer file.Close()
 
@@ -129,24 +130,31 @@ func DHCPStaticIPs(network, projectName, instanceName string) (DHCPAllocation, D
 			if strings.Count(field, ".") == 3 {
 				IP := net.ParseIP(field)
 				if IP.To4() == nil {
-					return IPv4, IPv6, fmt.Errorf("Error parsing IP address: %v", field)
+					return nil, IPv4, IPv6, fmt.Errorf("Error parsing IP address %q", field)
 				}
-				IPv4 = DHCPAllocation{Name: instanceName, Static: true, IP: IP.To4()}
+				IPv4 = DHCPAllocation{Name: instanceName, Static: true, IP: IP.To4(), MAC: mac}
 
 			} 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)
+					return nil, IPv4, IPv6, fmt.Errorf("Error parsing IP address %q", field)
+				}
+				IPv6 = DHCPAllocation{Name: instanceName, Static: true, IP: IP, MAC: mac}
+			} else if strings.Count(field, ":") == 5 {
+				// This field is expected to come first, so that mac variable can be used with
+				// populating the DHCPAllocation structs too.
+				mac, err = net.ParseMAC(field)
+				if err != nil {
+					return nil, IPv4, IPv6, fmt.Errorf("Error parsing MAC address %q", field)
 				}
-				IPv6 = DHCPAllocation{Name: instanceName, Static: true, IP: IP}
 			}
 		}
 	}
 	if err := scanner.Err(); err != nil {
-		return IPv4, IPv6, err
+		return nil, IPv4, IPv6, err
 	}
 
-	return IPv4, IPv6, nil
+	return mac, IPv4, IPv6, nil
 }
 
 // DHCPAllocatedIPs returns a map of IPs currently allocated (statically and dynamically)

From 02dc9beff9ba84c3f4ebc6b029871b9d093888ec Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jul 2020 14:08:18 +0100
Subject: [PATCH 06/19] lxd/dnsmasq: Renames DHCPAllocatedIPs to
 DHCPAllAllocations

- Removes container references.
- Returns nil allocations on error.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/dnsmasq/dnsmasq.go | 28 ++++++++++++++--------------
 1 file changed, 14 insertions(+), 14 deletions(-)

diff --git a/lxd/dnsmasq/dnsmasq.go b/lxd/dnsmasq/dnsmasq.go
index 58e0565ecb..1e26638640 100644
--- a/lxd/dnsmasq/dnsmasq.go
+++ b/lxd/dnsmasq/dnsmasq.go
@@ -157,32 +157,32 @@ func DHCPStaticAllocation(network, projectName, instanceName string) (net.Hardwa
 	return mac, IPv4, IPv6, nil
 }
 
-// DHCPAllocatedIPs returns a map of IPs currently allocated (statically and dynamically)
+// DHCPAllAllocations 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.
+// whether the allocation was static or dynamic and optionally instance 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
+// Static allocations are not overridden by dynamic allocations, allowing for instance 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
+// reliably linked to instances 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 instance 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 DHCPAllocatedIPs(network string) (map[[4]byte]DHCPAllocation, map[[16]byte]DHCPAllocation, error) {
+func DHCPAllAllocations(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
+	if err != nil && os.IsNotExist(err) {
+		return nil, nil, err
 	}
 
 	for _, entry := range files {
 		projectName, instanceName := project.InstanceParts(entry.Name())
-		IPv4, IPv6, err := DHCPStaticIPs(network, projectName, instanceName)
+		_, IPv4, IPv6, err := DHCPStaticAllocation(network, projectName, instanceName)
 		if err != nil {
-			return IPv4s, IPv6s, err
+			return nil, nil, err
 		}
 
 		if IPv4.IP != nil {
@@ -201,7 +201,7 @@ func DHCPAllocatedIPs(network string) (map[[4]byte]DHCPAllocation, map[[16]byte]
 	// Next read all dynamic allocated IPs.
 	file, err := os.Open(shared.VarPath("networks", network, "dnsmasq.leases"))
 	if err != nil {
-		return IPv4s, IPv6s, err
+		return nil, nil, err
 	}
 	defer file.Close()
 
@@ -211,7 +211,7 @@ func DHCPAllocatedIPs(network string) (map[[4]byte]DHCPAllocation, map[[16]byte]
 		if len(fields) == 5 {
 			IP := net.ParseIP(fields[2])
 			if IP == nil {
-				return IPv4s, IPv6s, fmt.Errorf("Error parsing IP address: %v", fields[2])
+				return nil, nil, fmt.Errorf("Error parsing IP address: %v", fields[2])
 			}
 
 			// Handle IPv6 addresses.
@@ -232,7 +232,7 @@ func DHCPAllocatedIPs(network string) (map[[4]byte]DHCPAllocation, map[[16]byte]
 				// MAC only available in IPv4 leases.
 				MAC, err := net.ParseMAC(fields[1])
 				if err != nil {
-					return IPv4s, IPv6s, err
+					return nil, nil, err
 				}
 
 				var IPKey [4]byte
@@ -252,7 +252,7 @@ func DHCPAllocatedIPs(network string) (map[[4]byte]DHCPAllocation, map[[16]byte]
 		}
 	}
 	if err := scanner.Err(); err != nil {
-		return IPv4s, IPv6s, err
+		return nil, nil, err
 	}
 
 	return IPv4s, IPv6s, nil

From 82b9c1f098e9dbb281c730735c02b07e24fe6dcc Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jul 2020 14:10:00 +0100
Subject: [PATCH 07/19] lxd/network/network/utils: Removes GetIP

Moved to dnsmasq/dhcpalloc package.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/network/network_utils.go | 38 ------------------------------------
 1 file changed, 38 deletions(-)

diff --git a/lxd/network/network_utils.go b/lxd/network/network_utils.go
index cbceca041f..6f5f382207 100644
--- a/lxd/network/network_utils.go
+++ b/lxd/network/network_utils.go
@@ -2,12 +2,9 @@ package network
 
 import (
 	"bufio"
-	"encoding/binary"
 	"encoding/hex"
 	"fmt"
 	"io/ioutil"
-	"math"
-	"math/big"
 	"math/rand"
 	"net"
 	"os"
@@ -117,41 +114,6 @@ func isInUseByDevices(s *state.State, devices deviceConfig.Devices, networkName
 	return false, nil
 }
 
-// GetIP returns a net.IP representing the IP belonging to the subnet for the host number supplied.
-func GetIP(subnet *net.IPNet, host int64) net.IP {
-	// Convert IP to a big int.
-	bigIP := big.NewInt(0)
-	bigIP.SetBytes(subnet.IP.To16())
-
-	// Deal with negative offsets.
-	bigHost := big.NewInt(host)
-	bigCount := big.NewInt(host)
-	if host < 0 {
-		mask, size := subnet.Mask.Size()
-
-		bigHosts := big.NewFloat(0)
-		bigHosts.SetFloat64((math.Pow(2, float64(size-mask))))
-		bigHostsInt, _ := bigHosts.Int(nil)
-
-		bigCount.Set(bigHostsInt)
-		bigCount.Add(bigCount, bigHost)
-	}
-
-	// Get the new IP int.
-	bigIP.Add(bigIP, bigCount)
-
-	// Generate an IPv6.
-	if subnet.IP.To4() == nil {
-		newIP := bigIP.Bytes()
-		return newIP
-	}
-
-	// Generate an IPv4.
-	newIP := make(net.IP, 4)
-	binary.BigEndian.PutUint32(newIP, uint32(bigIP.Int64()))
-	return newIP
-}
-
 // IsNativeBridge returns whether the bridge name specified is a Linux native bridge.
 func IsNativeBridge(bridgeName string) bool {
 	return shared.PathExists(fmt.Sprintf("/sys/class/net/%s/bridge", bridgeName))

From ec469999ac18e8faec0186acf75dab533ae41757 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jul 2020 14:10:39 +0100
Subject: [PATCH 08/19] lxd/network/network/utils: dhcpalloc.GetIP usage

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/network/network_utils.go | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/lxd/network/network_utils.go b/lxd/network/network_utils.go
index 6f5f382207..f881f9f300 100644
--- a/lxd/network/network_utils.go
+++ b/lxd/network/network_utils.go
@@ -19,6 +19,7 @@ import (
 	deviceConfig "github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/lxd/device/nictype"
 	"github.com/lxc/lxd/lxd/dnsmasq"
+	"github.com/lxc/lxd/lxd/dnsmasq/dhcpalloc"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/instance/instancetype"
 	"github.com/lxc/lxd/lxd/network/openvswitch"
@@ -622,21 +623,21 @@ func pingSubnet(subnet *net.IPNet) bool {
 
 	// Ping first IP
 	wgChecks.Add(1)
-	go ping(GetIP(subnet, 1))
+	go ping(dhcpalloc.GetIP(subnet, 1))
 
 	// Poke port on first IP
 	wgChecks.Add(1)
-	go poke(GetIP(subnet, 1))
+	go poke(dhcpalloc.GetIP(subnet, 1))
 
 	// Ping check
 	if subnet.IP.To4() != nil {
 		// Ping last IP
 		wgChecks.Add(1)
-		go ping(GetIP(subnet, -2))
+		go ping(dhcpalloc.GetIP(subnet, -2))
 
 		// Poke port on last IP
 		wgChecks.Add(1)
-		go poke(GetIP(subnet, -2))
+		go poke(dhcpalloc.GetIP(subnet, -2))
 	}
 
 	wgChecks.Wait()

From 2a288c060e0b7361eba15f71e8b0fdbfad65c671 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jul 2020 14:10:51 +0100
Subject: [PATCH 09/19] lxd/network/network/utils: dnsmasq.DHCPStaticAllocation
 usage

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/network/network_utils.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/network/network_utils.go b/lxd/network/network_utils.go
index f881f9f300..ae2e4977d2 100644
--- a/lxd/network/network_utils.go
+++ b/lxd/network/network_utils.go
@@ -299,7 +299,7 @@ func UpdateDNSMasqStatic(s *state.State, networkName string) error {
 			}
 
 			if (shared.IsTrue(d["security.ipv4_filtering"]) && d["ipv4.address"] == "") || (shared.IsTrue(d["security.ipv6_filtering"]) && d["ipv6.address"] == "") {
-				curIPv4, curIPv6, err := dnsmasq.DHCPStaticIPs(d["parent"], inst.Project(), inst.Name())
+				_, curIPv4, curIPv6, err := dnsmasq.DHCPStaticAllocation(d["parent"], inst.Project(), inst.Name())
 				if err != nil && !os.IsNotExist(err) {
 					return err
 				}

From dc3a9541e90a36b31e0e7a35ac2b08ad2268914b Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jul 2020 14:11:19 +0100
Subject: [PATCH 10/19] lxd/network/network/interface: Changes of functions to
 accomodate dhcpalloc package

- Removes HasDHCPvN functions and replaces with DHCPv4Subnet and DHCPv6Subnet - the thinking being that different networks may store their dynamic IP subnet in different config keys, so this accomodates static allocation from different network types, whilst still allowing DHCP functionality to be detected by a nil return value.
- Updates DHCPvNRanges functions to return dhcpalloc.DHCPRange slice as this has been moved.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/network/network_interface.go | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/lxd/network/network_interface.go b/lxd/network/network_interface.go
index 0bba1fd637..514f3e4d1c 100644
--- a/lxd/network/network_interface.go
+++ b/lxd/network/network_interface.go
@@ -1,7 +1,10 @@
 package network
 
 import (
+	"net"
+
 	"github.com/lxc/lxd/lxd/cluster"
+	"github.com/lxc/lxd/lxd/dnsmasq/dhcpalloc"
 	"github.com/lxc/lxd/lxd/state"
 	"github.com/lxc/lxd/shared/api"
 )
@@ -19,10 +22,10 @@ type Network interface {
 	Status() string
 	Config() map[string]string
 	IsUsed() (bool, error)
-	HasDHCPv4() bool
-	HasDHCPv6() bool
-	DHCPv4Ranges() []DHCPRange
-	DHCPv6Ranges() []DHCPRange
+	DHCPv4Subnet() *net.IPNet
+	DHCPv6Subnet() *net.IPNet
+	DHCPv4Ranges() []dhcpalloc.DHCPRange
+	DHCPv6Ranges() []dhcpalloc.DHCPRange
 
 	// Actions.
 	Create(clusterNotification bool) error

From 80273ca0b9bf23f05e70274fcdd18808120d48e9 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jul 2020 14:14:46 +0100
Subject: [PATCH 11/19] lxd/network/driver/common: Implements default no-op
 function for non-dhcp enabled networks

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/network/driver_common.go | 23 ++++++-----------------
 1 file changed, 6 insertions(+), 17 deletions(-)

diff --git a/lxd/network/driver_common.go b/lxd/network/driver_common.go
index 76a5675e9f..60cd79f4f2 100644
--- a/lxd/network/driver_common.go
+++ b/lxd/network/driver_common.go
@@ -166,25 +166,14 @@ func (n *common) IsUsed() (bool, error) {
 	return false, nil
 }
 
-// HasDHCPv4 indicates whether the network has DHCPv4 enabled.
-func (n *common) HasDHCPv4() bool {
-	if n.config["ipv4.dhcp"] == "" || shared.IsTrue(n.config["ipv4.dhcp"]) {
-		return true
-	}
-
-	return false
+// DHCPv4Subnet returns nil always.
+func (n *common) DHCPv4Subnet() *net.IPNet {
+	return nil
 }
 
-// HasDHCPv6 indicates whether the network has DHCPv6 enabled (includes stateless SLAAC router advertisement mode).
-// Technically speaking stateless SLAAC RA mode isn't DHCPv6, but for consistency with LXD's config paradigm, DHCP
-// here means "an ability to automatically allocate IPs and routes", rather than stateful DHCP with leases.
-// To check if true stateful DHCPv6 is enabled check the "ipv6.dhcp.stateful" config key.
-func (n *common) HasDHCPv6() bool {
-	if n.config["ipv6.dhcp"] == "" || shared.IsTrue(n.config["ipv6.dhcp"]) {
-		return true
-	}
-
-	return false
+// DHCPv6Subnet returns nil always.
+func (n *common) DHCPv6Subnet() *net.IPNet {
+	return nil
 }
 
 // DHCPv4Ranges returns a parsed set of DHCPv4 ranges for this network.

From cdc920e7240207564003d75d0276482e2938a93d Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jul 2020 14:15:16 +0100
Subject: [PATCH 12/19] lxd/network/driver/common: dhcpalloc.DHCPRange usage

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/network/driver_common.go | 19 +++++++------------
 1 file changed, 7 insertions(+), 12 deletions(-)

diff --git a/lxd/network/driver_common.go b/lxd/network/driver_common.go
index 60cd79f4f2..b96a90f994 100644
--- a/lxd/network/driver_common.go
+++ b/lxd/network/driver_common.go
@@ -11,6 +11,7 @@ import (
 	lxd "github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/lxd/cluster"
 	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/dnsmasq/dhcpalloc"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/state"
 	"github.com/lxc/lxd/shared"
@@ -20,12 +21,6 @@ import (
 	"github.com/lxc/lxd/shared/logging"
 )
 
-// DHCPRange represents a range of IPs from start to end.
-type DHCPRange struct {
-	Start net.IP
-	End   net.IP
-}
-
 // common represents a generic LXD network.
 type common struct {
 	logger      logger.Logger
@@ -177,15 +172,15 @@ func (n *common) DHCPv6Subnet() *net.IPNet {
 }
 
 // DHCPv4Ranges returns a parsed set of DHCPv4 ranges for this network.
-func (n *common) DHCPv4Ranges() []DHCPRange {
-	dhcpRanges := make([]DHCPRange, 0)
+func (n *common) DHCPv4Ranges() []dhcpalloc.DHCPRange {
+	dhcpRanges := make([]dhcpalloc.DHCPRange, 0)
 	if n.config["ipv4.dhcp.ranges"] != "" {
 		for _, r := range strings.Split(n.config["ipv4.dhcp.ranges"], ",") {
 			parts := strings.SplitN(strings.TrimSpace(r), "-", 2)
 			if len(parts) == 2 {
 				startIP := net.ParseIP(parts[0])
 				endIP := net.ParseIP(parts[1])
-				dhcpRanges = append(dhcpRanges, DHCPRange{
+				dhcpRanges = append(dhcpRanges, dhcpalloc.DHCPRange{
 					Start: startIP.To4(),
 					End:   endIP.To4(),
 				})
@@ -197,15 +192,15 @@ func (n *common) DHCPv4Ranges() []DHCPRange {
 }
 
 // DHCPv6Ranges returns a parsed set of DHCPv6 ranges for this network.
-func (n *common) DHCPv6Ranges() []DHCPRange {
-	dhcpRanges := make([]DHCPRange, 0)
+func (n *common) DHCPv6Ranges() []dhcpalloc.DHCPRange {
+	dhcpRanges := make([]dhcpalloc.DHCPRange, 0)
 	if n.config["ipv6.dhcp.ranges"] != "" {
 		for _, r := range strings.Split(n.config["ipv6.dhcp.ranges"], ",") {
 			parts := strings.SplitN(strings.TrimSpace(r), "-", 2)
 			if len(parts) == 2 {
 				startIP := net.ParseIP(parts[0])
 				endIP := net.ParseIP(parts[1])
-				dhcpRanges = append(dhcpRanges, DHCPRange{
+				dhcpRanges = append(dhcpRanges, dhcpalloc.DHCPRange{
 					Start: startIP.To16(),
 					End:   endIP.To16(),
 				})

From ce8f4746a66df7a801ccc16a837b5055123fe41b Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jul 2020 14:16:18 +0100
Subject: [PATCH 13/19] lxd/network/driver/bridge: dhcpalloc package function
 usage

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/network/driver_bridge.go | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/lxd/network/driver_bridge.go b/lxd/network/driver_bridge.go
index e92772fdd9..be83119ece 100644
--- a/lxd/network/driver_bridge.go
+++ b/lxd/network/driver_bridge.go
@@ -19,6 +19,7 @@ import (
 	"github.com/lxc/lxd/lxd/cluster"
 	"github.com/lxc/lxd/lxd/daemon"
 	"github.com/lxc/lxd/lxd/dnsmasq"
+	"github.com/lxc/lxd/lxd/dnsmasq/dhcpalloc"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/network/openvswitch"
 	"github.com/lxc/lxd/lxd/node"
@@ -626,7 +627,7 @@ func (n *bridge) setup(oldConfig map[string]string) error {
 
 	// Configure IPv4 firewall (includes fan).
 	if n.config["bridge.mode"] == "fan" || !shared.StringInSlice(n.config["ipv4.address"], []string{"", "none"}) {
-		if n.HasDHCPv4() && n.hasIPv4Firewall() {
+		if n.DHCPv4Subnet() != nil && n.hasIPv4Firewall() {
 			// Setup basic iptables overrides for DHCP/DNS.
 			err = n.state.Firewall.NetworkSetupDHCPDNSAccess(n.name, 4)
 			if err != nil {
@@ -703,7 +704,7 @@ func (n *bridge) setup(oldConfig map[string]string) error {
 
 		// Update the dnsmasq config.
 		dnsmasqCmd = append(dnsmasqCmd, fmt.Sprintf("--listen-address=%s", ip.String()))
-		if n.HasDHCPv4() {
+		if n.DHCPv4Subnet() != nil {
 			if !shared.StringInSlice("--dhcp-no-override", dnsmasqCmd) {
 				dnsmasqCmd = append(dnsmasqCmd, []string{"--dhcp-no-override", "--dhcp-authoritative", fmt.Sprintf("--dhcp-leasefile=%s", shared.VarPath("networks", n.name, "dnsmasq.leases")), fmt.Sprintf("--dhcp-hostsfile=%s", shared.VarPath("networks", n.name, "dnsmasq.hosts"))}...)
 			}
@@ -732,7 +733,7 @@ func (n *bridge) setup(oldConfig map[string]string) error {
 					dnsmasqCmd = append(dnsmasqCmd, []string{"--dhcp-range", fmt.Sprintf("%s,%s", strings.Replace(dhcpRange, "-", ",", -1), expiry)}...)
 				}
 			} else {
-				dnsmasqCmd = append(dnsmasqCmd, []string{"--dhcp-range", fmt.Sprintf("%s,%s,%s", GetIP(subnet, 2).String(), GetIP(subnet, -2).String(), expiry)}...)
+				dnsmasqCmd = append(dnsmasqCmd, []string{"--dhcp-range", fmt.Sprintf("%s,%s,%s", dhcpalloc.GetIP(subnet, 2).String(), dhcpalloc.GetIP(subnet, -2).String(), expiry)}...)
 			}
 		}
 
@@ -825,7 +826,7 @@ func (n *bridge) setup(oldConfig map[string]string) error {
 
 		// Update the dnsmasq config.
 		dnsmasqCmd = append(dnsmasqCmd, []string{fmt.Sprintf("--listen-address=%s", ip.String()), "--enable-ra"}...)
-		if n.HasDHCPv6() {
+		if n.DHCPv6Subnet() != nil {
 			if n.config["ipv6.firewall"] == "" || shared.IsTrue(n.config["ipv6.firewall"]) {
 				// Setup basic iptables overrides for DHCP/DNS.
 				err = n.state.Firewall.NetworkSetupDHCPDNSAccess(n.name, 6)
@@ -851,7 +852,7 @@ func (n *bridge) setup(oldConfig map[string]string) error {
 						dnsmasqCmd = append(dnsmasqCmd, []string{"--dhcp-range", fmt.Sprintf("%s,%d,%s", strings.Replace(dhcpRange, "-", ",", -1), subnetSize, expiry)}...)
 					}
 				} else {
-					dnsmasqCmd = append(dnsmasqCmd, []string{"--dhcp-range", fmt.Sprintf("%s,%s,%d,%s", GetIP(subnet, 2), GetIP(subnet, -1), subnetSize, expiry)}...)
+					dnsmasqCmd = append(dnsmasqCmd, []string{"--dhcp-range", fmt.Sprintf("%s,%s,%d,%s", dhcpalloc.GetIP(subnet, 2), dhcpalloc.GetIP(subnet, -1), subnetSize, expiry)}...)
 				}
 			} else {
 				dnsmasqCmd = append(dnsmasqCmd, []string{"--dhcp-range", fmt.Sprintf("::,constructor:%s,ra-stateless,ra-names", n.name)}...)
@@ -1032,7 +1033,7 @@ func (n *bridge) setup(oldConfig map[string]string) error {
 			"--dhcp-no-override", "--dhcp-authoritative",
 			fmt.Sprintf("--dhcp-leasefile=%s", shared.VarPath("networks", n.name, "dnsmasq.leases")),
 			fmt.Sprintf("--dhcp-hostsfile=%s", shared.VarPath("networks", n.name, "dnsmasq.hosts")),
-			"--dhcp-range", fmt.Sprintf("%s,%s,%s", GetIP(hostSubnet, 2).String(), GetIP(hostSubnet, -2).String(), expiry)}...)
+			"--dhcp-range", fmt.Sprintf("%s,%s,%s", dhcpalloc.GetIP(hostSubnet, 2).String(), dhcpalloc.GetIP(hostSubnet, -2).String(), expiry)}...)
 
 		// Setup the tunnel.
 		if n.config["fan.type"] == "ipip" {

From 4db60e042b29f7883c5e810805466764ea92b005 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jul 2020 14:16:34 +0100
Subject: [PATCH 14/19] lxd/network/driver/bridge: DHCPv4Subnet and
 DHCPv6Subnet implementations

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/network/driver_bridge.go | 30 ++++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/lxd/network/driver_bridge.go b/lxd/network/driver_bridge.go
index be83119ece..d0afd95f55 100644
--- a/lxd/network/driver_bridge.go
+++ b/lxd/network/driver_bridge.go
@@ -1828,3 +1828,33 @@ func (n *bridge) hasIPv6Firewall() bool {
 
 	return false
 }
+
+// DHCPv4Subnet returns the DHCPv4 subnet (if DHCP is enabled on network).
+func (n *bridge) DHCPv4Subnet() *net.IPNet {
+	// DHCP is disabled on this network (an empty ipv4.dhcp setting indicates enabled by default).
+	if n.config["ipv4.dhcp"] != "" && !shared.IsTrue(n.config["ipv4.dhcp"]) {
+		return nil
+	}
+
+	_, subnet, err := net.ParseCIDR(n.config["ipv4.address"])
+	if err != nil {
+		return nil
+	}
+
+	return subnet
+}
+
+// DHCPv6Subnet returns the DHCPv6 subnet (if DHCP or SLAAC is enabled on network).
+func (n *bridge) DHCPv6Subnet() *net.IPNet {
+	// DHCP is disabled on this network (an empty ipv6.dhcp setting indicates enabled by default).
+	if n.config["ipv6.dhcp"] != "" && !shared.IsTrue(n.config["ipv6.dhcp"]) {
+		return nil
+	}
+
+	_, subnet, err := net.ParseCIDR(n.config["ipv6.address"])
+	if err != nil {
+		return nil
+	}
+
+	return subnet
+}

From 1e55cf1b84809fcbe47cc76dc0604f31358474f1 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jul 2020 14:18:05 +0100
Subject: [PATCH 15/19] lxd/device/nic/bridged: Comment correction

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/nic_bridged.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/device/nic_bridged.go b/lxd/device/nic_bridged.go
index 1f77dd13b9..f596dcdf8d 100644
--- a/lxd/device/nic_bridged.go
+++ b/lxd/device/nic_bridged.go
@@ -288,7 +288,7 @@ func (d *nicBridged) Start() (*deviceConfig.RunConfig, error) {
 		return nil, err
 	}
 
-	// Apply and host-side network filters (uses enriched host_name from networkSetupHostVethDevice).
+	// Apply and host-side network filters (uses enriched host_name from networkVethFillFromVolatile).
 	err = d.setupHostFilters(nil)
 	if err != nil {
 		return nil, err
@@ -383,7 +383,7 @@ func (d *nicBridged) Update(oldDevices deviceConfig.Devices, isRunning bool) err
 			return err
 		}
 
-		// Apply and host-side network filters (uses enriched host_name from networkSetupHostVethDevice).
+		// Apply and host-side network filters (uses enriched host_name from networkVethFillFromVolatile).
 		err = d.setupHostFilters(oldConfig)
 		if err != nil {
 			return err

From 061e8f56a752dd911373539203f2480cd06521dc Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jul 2020 14:18:46 +0100
Subject: [PATCH 16/19] lxd/device/nic/bridged: n.DHCPv4Subnet and
 n.DHCPv6Subnet usage

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/nic_bridged.go | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/lxd/device/nic_bridged.go b/lxd/device/nic_bridged.go
index f596dcdf8d..d2ebe249d1 100644
--- a/lxd/device/nic_bridged.go
+++ b/lxd/device/nic_bridged.go
@@ -98,8 +98,8 @@ func (d *nicBridged) validateConfig(instConf instance.ConfigReader) error {
 
 		if d.config["ipv4.address"] != "" {
 			// Check that DHCPv4 is enabled on parent network (needed to use static assigned IPs).
-			if !n.HasDHCPv4() {
-				return fmt.Errorf("Cannot specify %q when %q is disabled on network %q", "ipv4.address", "ipv4.dhcp", d.config["network"])
+			if n.DHCPv4Subnet() == nil {
+				return fmt.Errorf("Cannot specify %q when DHCP is disabled on network %q", "ipv4.address", d.config["network"])
 			}
 
 			_, subnet, err := net.ParseCIDR(netConfig["ipv4.address"])
@@ -109,15 +109,15 @@ func (d *nicBridged) validateConfig(instConf instance.ConfigReader) error {
 
 			// Check the static IP supplied is valid for the linked network. It should be part of the
 			// network's subnet, but not necessarily part of the dynamic allocation ranges.
-			if !networkDHCPValidIP(subnet, nil, net.ParseIP(d.config["ipv4.address"])) {
+			if !dhcpalloc.DHCPValidIP(subnet, nil, net.ParseIP(d.config["ipv4.address"])) {
 				return fmt.Errorf("Device IP address %q not within network %q subnet", d.config["ipv4.address"], d.config["network"])
 			}
 		}
 
 		if d.config["ipv6.address"] != "" {
 			// Check that DHCPv6 is enabled on parent network (needed to use static assigned IPs).
-			if !n.HasDHCPv6() || !shared.IsTrue(netConfig["ipv6.dhcp.stateful"]) {
-				return fmt.Errorf("Cannot specify %q when %q or %q are disabled on network %q", "ipv6.address", "ipv6.dhcp", "ipv6.dhcp.stateful", d.config["network"])
+			if n.DHCPv6Subnet() == nil || !shared.IsTrue(netConfig["ipv6.dhcp.stateful"]) {
+				return fmt.Errorf("Cannot specify %q when DHCP or %q are disabled on network %q", "ipv6.address", "ipv6.dhcp.stateful", d.config["network"])
 			}
 
 			_, subnet, err := net.ParseCIDR(netConfig["ipv6.address"])

From ffc435153acf3e286e37c73b79020b168b814b3c Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jul 2020 14:19:30 +0100
Subject: [PATCH 17/19] lxd/device/nic/bridged: dnsmasq.DHCPStaticAllocation
 usage

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/nic_bridged.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/device/nic_bridged.go b/lxd/device/nic_bridged.go
index d2ebe249d1..1893cb3e58 100644
--- a/lxd/device/nic_bridged.go
+++ b/lxd/device/nic_bridged.go
@@ -503,7 +503,7 @@ func (d *nicBridged) rebuildDnsmasqEntry() error {
 	// If IP filtering is enabled, and no static IP in config, check if there is already a
 	// dynamically assigned static IP in dnsmasq config and write that back out in new config.
 	if (shared.IsTrue(d.config["security.ipv4_filtering"]) && ipv4Address == "") || (shared.IsTrue(d.config["security.ipv6_filtering"]) && ipv6Address == "") {
-		curIPv4, curIPv6, err := dnsmasq.DHCPStaticIPs(d.config["parent"], d.inst.Project(), d.inst.Name())
+		_, curIPv4, curIPv6, err := dnsmasq.DHCPStaticAllocation(d.config["parent"], d.inst.Project(), d.inst.Name())
 		if err != nil && !os.IsNotExist(err) {
 			return err
 		}
@@ -591,7 +591,7 @@ func (d *nicBridged) removeFilters(m deviceConfig.Device) {
 
 	// Read current static DHCP IP allocation configured from dnsmasq host config (if exists).
 	// This covers the case when IPs are not defined in config, but have been assigned in managed DHCP.
-	IPv4Alloc, IPv6Alloc, err := dnsmasq.DHCPStaticIPs(m["parent"], d.inst.Project(), d.inst.Name())
+	_, IPv4Alloc, IPv6Alloc, err := dnsmasq.DHCPStaticAllocation(m["parent"], d.inst.Project(), d.inst.Name())
 	if err != nil {
 		if os.IsNotExist(err) {
 			return

From 1f0b160159fbff66f1a89de87f8e77a0c2bc95e8 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jul 2020 14:20:36 +0100
Subject: [PATCH 18/19] lxd/device/nic/bridged: dhcpalloc.DHCPValidIP usage

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/nic_bridged.go | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lxd/device/nic_bridged.go b/lxd/device/nic_bridged.go
index 1893cb3e58..3d92edc2b5 100644
--- a/lxd/device/nic_bridged.go
+++ b/lxd/device/nic_bridged.go
@@ -21,6 +21,7 @@ import (
 	"github.com/lxc/lxd/lxd/db"
 	deviceConfig "github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/lxd/dnsmasq"
+	"github.com/lxc/lxd/lxd/dnsmasq/dhcpalloc"
 	firewallDrivers "github.com/lxc/lxd/lxd/firewall/drivers"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/instance/instancetype"
@@ -127,7 +128,7 @@ func (d *nicBridged) validateConfig(instConf instance.ConfigReader) error {
 
 			// Check the static IP supplied is valid for the linked network. It should be part of the
 			// network's subnet, but not necessarily part of the dynamic allocation ranges.
-			if !networkDHCPValidIP(subnet, nil, net.ParseIP(d.config["ipv6.address"])) {
+			if !dhcpalloc.DHCPValidIP(subnet, nil, net.ParseIP(d.config["ipv6.address"])) {
 				return fmt.Errorf("Device IP address %q not within network %q subnet", d.config["ipv6.address"], d.config["network"])
 			}
 		}

From e3a0cef1c372ed016c3b171dd7034ae503fedca8 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jul 2020 14:21:19 +0100
Subject: [PATCH 19/19] lxd/device/nic/bridged: Switches static DHCP allocation
 for IP filtering to dnsmasq/dhcpalloc

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/nic_bridged.go | 339 ++++++--------------------------------
 1 file changed, 46 insertions(+), 293 deletions(-)

diff --git a/lxd/device/nic_bridged.go b/lxd/device/nic_bridged.go
index 3d92edc2b5..50124614dd 100644
--- a/lxd/device/nic_bridged.go
+++ b/lxd/device/nic_bridged.go
@@ -2,11 +2,9 @@ package device
 
 import (
 	"bufio"
-	"bytes"
 	"encoding/binary"
 	"encoding/hex"
 	"fmt"
-	"math/big"
 	"math/rand"
 	"net"
 	"os"
@@ -15,7 +13,6 @@ import (
 
 	"github.com/google/gopacket"
 	"github.com/google/gopacket/layers"
-	"github.com/mdlayher/netx/eui64"
 	"github.com/pkg/errors"
 
 	"github.com/lxc/lxd/lxd/db"
@@ -638,8 +635,17 @@ func (d *nicBridged) setFilters() (err error) {
 		}
 	}
 
+	// Parse device config.
+	mac, err := net.ParseMAC(d.config["hwaddr"])
+	if err != nil {
+		return errors.Wrapf(err, "Invalid hwaddr")
+	}
+
+	// Parse static IPs, relies on invalid IPs being set to nil.
+	IPv4 := net.ParseIP(d.config["ipv4.address"])
+	IPv6 := net.ParseIP(d.config["ipv6.address"])
+
 	// Check if the parent is managed and load config. If parent is unmanaged continue anyway.
-	var IPv4, IPv6 net.IP
 	n, err := network.LoadByName(d.state, d.config["parent"])
 	if err != nil && err != db.ErrNoSuchObject {
 		return err
@@ -652,21 +658,45 @@ func (d *nicBridged) setFilters() (err error) {
 		}
 	}
 
-	// If parent bridge is unmanaged we cannot allocate static IPs.
+	// If parent bridge is managed, allocate the static IPs needed.
 	if n != nil {
-		// Retrieve existing IPs, or allocate new ones if needed.
-		IPv4, IPv6, err = d.allocateFilterIPs(n)
+		opts := &dhcpalloc.Options{
+			ProjectName: d.inst.Project(),
+			HostName:    d.inst.Name(),
+			HostMAC:     mac,
+			Network:     n,
+		}
+
+		err = dhcpalloc.AllocateTask(opts, func(t *dhcpalloc.Transaction) error {
+			if shared.IsTrue(d.config["security.ipv4_filtering"]) && IPv4 == nil {
+				IPv4, err = t.AllocateIPv4()
+
+				// If DHCP not supported, skip error, and will result in total protocol filter.
+				if err != nil && err != dhcpalloc.ErrDHCPNotSupported {
+					return err
+				}
+			}
+
+			if shared.IsTrue(d.config["security.ipv6_filtering"]) && IPv6 == nil {
+				IPv6, err = t.AllocateIPv6()
+
+				// If DHCP not supported, skip error, and will result in total protocol filter.
+				if err != nil && err != dhcpalloc.ErrDHCPNotSupported {
+					return err
+				}
+			}
+
+			return nil
+		})
 		if err != nil {
 			return err
 		}
 	}
 
 	// If anything goes wrong, clean up so we don't leave orphaned rules.
-	defer func() {
-		if err != nil {
-			d.removeFilters(d.config)
-		}
-	}()
+	revert := revert.New()
+	defer revert.Fail()
+	revert.Add(func() { d.removeFilters(d.config) })
 
 	// If no allocated IPv4 address for filtering and filtering enabled, then block all IPv4 traffic.
 	if shared.IsTrue(d.config["security.ipv4_filtering"]) && IPv4 == nil {
@@ -678,290 +708,13 @@ func (d *nicBridged) setFilters() (err error) {
 		IPv6 = net.ParseIP(firewallDrivers.FilterIPv6All)
 	}
 
-	return d.state.Firewall.InstanceSetupBridgeFilter(d.inst.Project(), d.inst.Name(), d.name, d.config["parent"], d.config["host_name"], d.config["hwaddr"], IPv4, IPv6)
-}
-
-// networkAllocateVethFilterIPs retrieves previously allocated IPs, or allocate new ones if needed.
-// This function only works with LXD managed networks, and as such, requires the managed network's
-// config to be supplied.
-func (d *nicBridged) allocateFilterIPs(n network.Network) (net.IP, net.IP, error) {
-	var IPv4, IPv6 net.IP
-
-	// Check if there is a valid static IPv4 address defined.
-	if d.config["ipv4.address"] != "" {
-		IPv4 = net.ParseIP(d.config["ipv4.address"])
-		if IPv4 == nil {
-			return nil, nil, fmt.Errorf("Invalid static IPv4 address %s", d.config["ipv4.address"])
-		}
-	}
-
-	// Check if there is a valid static IPv6 address defined.
-	if d.config["ipv6.address"] != "" {
-		IPv6 = net.ParseIP(d.config["ipv6.address"])
-		if IPv6 == nil {
-			return nil, nil, fmt.Errorf("Invalid static IPv6 address %s", d.config["ipv6.address"])
-		}
-	}
-
-	netConfig := n.Config()
-
-	// Check the conditions required to dynamically allocated IPs.
-	canIPv4Allocate := netConfig["ipv4.address"] != "" && netConfig["ipv4.address"] != "none" && n.HasDHCPv4()
-	canIPv6Allocate := netConfig["ipv6.address"] != "" && netConfig["ipv6.address"] != "none" && n.HasDHCPv6()
-
-	dnsmasq.ConfigMutex.Lock()
-	defer dnsmasq.ConfigMutex.Unlock()
-
-	// Read current static IP allocation configured from dnsmasq host config (if exists).
-	curIPv4, curIPv6, err := dnsmasq.DHCPStaticIPs(d.config["parent"], d.inst.Project(), d.inst.Name())
-	if err != nil && !os.IsNotExist(err) {
-		return nil, nil, err
-	}
-
-	// If no static IPv4, then check if there is a valid static DHCP IPv4 address defined.
-	if IPv4 == nil && curIPv4.IP != nil && canIPv4Allocate {
-		_, subnet, err := net.ParseCIDR(netConfig["ipv4.address"])
-		if err != nil {
-			return nil, nil, err
-		}
-
-		// Check the existing static DHCP IP is still valid in the subnet & ranges, if not
-		// then we'll need to generate a new one.
-		ranges := n.DHCPv4Ranges()
-		if networkDHCPValidIP(subnet, ranges, curIPv4.IP.To4()) {
-			IPv4 = curIPv4.IP.To4()
-		}
-	}
-
-	// If no static IPv6, then check if there is a valid static DHCP IPv6 address defined.
-	if IPv6 == nil && curIPv6.IP != nil && canIPv6Allocate {
-		_, subnet, err := net.ParseCIDR(netConfig["ipv6.address"])
-		if err != nil {
-			return IPv4, IPv6, err
-		}
-
-		// Check the existing static DHCP IP is still valid in the subnet & ranges, if not
-		// then we'll need to generate a new one.
-		ranges := n.DHCPv6Ranges()
-		if networkDHCPValidIP(subnet, ranges, curIPv6.IP.To16()) {
-			IPv6 = curIPv6.IP.To16()
-		}
-	}
-
-	// If we need to generate either a new IPv4 or IPv6, load existing IPs used in network.
-	if (IPv4 == nil && canIPv4Allocate) || (IPv6 == nil && canIPv6Allocate) {
-		// Get existing allocations in network.
-		IPv4Allocs, IPv6Allocs, err := dnsmasq.DHCPAllocatedIPs(d.config["parent"])
-		if err != nil {
-			return nil, nil, err
-		}
-
-		// Allocate a new IPv4 address if IPv4 filtering enabled.
-		if IPv4 == nil && canIPv4Allocate && shared.IsTrue(d.config["security.ipv4_filtering"]) {
-			IPv4, err = d.getDHCPFreeIPv4(IPv4Allocs, n, d.inst.Name(), d.config["hwaddr"])
-			if err != nil {
-				return nil, nil, err
-			}
-		}
-
-		// Allocate a new IPv6 address if IPv6 filtering enabled.
-		if IPv6 == nil && canIPv6Allocate && shared.IsTrue(d.config["security.ipv6_filtering"]) {
-			IPv6, err = d.getDHCPFreeIPv6(IPv6Allocs, n, d.inst.Name(), d.config["hwaddr"])
-			if err != nil {
-				return nil, nil, err
-			}
-		}
-	}
-
-	// If parent is a DHCP enabled managed network and either IPv4 or IPv6 assigned is different than what is in dnsmasq config, rebuild config.
-	if shared.PathExists(shared.VarPath("networks", d.config["parent"], "dnsmasq.pid")) &&
-		((IPv4 != nil && bytes.Compare(curIPv4.IP, IPv4.To4()) != 0) || (IPv6 != nil && bytes.Compare(curIPv6.IP, IPv6.To16()) != 0)) {
-		var IPv4Str, IPv6Str string
-
-		if IPv4 != nil {
-			IPv4Str = IPv4.String()
-		}
-
-		if IPv6 != nil {
-			IPv6Str = IPv6.String()
-		}
-
-		err = dnsmasq.UpdateStaticEntry(d.config["parent"], d.inst.Project(), d.inst.Name(), netConfig, d.config["hwaddr"], IPv4Str, IPv6Str)
-		if err != nil {
-			return nil, nil, err
-		}
-
-		err = dnsmasq.Kill(d.config["parent"], true)
-		if err != nil {
-			return nil, nil, err
-		}
-	}
-
-	return IPv4, IPv6, nil
-}
-
-// getDHCPFreeIPv4 attempts to find a free IPv4 address for the device.
-// It first checks whether there is an existing allocation for the instance.
-// If no previous allocation, then a free IP is picked from the ranges configured.
-func (d *nicBridged) getDHCPFreeIPv4(usedIPs map[[4]byte]dnsmasq.DHCPAllocation, n network.Network, ctName string, deviceMAC string) (net.IP, error) {
-	MAC, err := net.ParseMAC(deviceMAC)
-	if err != nil {
-		return nil, err
-	}
-
-	lxdIP, subnet, err := net.ParseCIDR(n.Config()["ipv4.address"])
-	if err != nil {
-		return nil, err
-	}
-
-	dhcpRanges := n.DHCPv4Ranges()
-
-	// Lets see if there is already an allocation for our device and that it sits within subnet.
-	// If there are custom DHCP ranges defined, check also that the IP falls within one of the ranges.
-	for _, DHCP := range usedIPs {
-		if (ctName == DHCP.Name || bytes.Compare(MAC, DHCP.MAC) == 0) && networkDHCPValidIP(subnet, dhcpRanges, DHCP.IP) {
-			return DHCP.IP, nil
-		}
-	}
-
-	// If no custom ranges defined, convert subnet pool to a range.
-	if len(dhcpRanges) <= 0 {
-		dhcpRanges = append(dhcpRanges, network.DHCPRange{
-			Start: network.GetIP(subnet, 1).To4(),
-			End:   network.GetIP(subnet, -2).To4()},
-		)
-	}
-
-	// If no valid existing allocation found, try and find a free one in the subnet pool/ranges.
-	for _, IPRange := range dhcpRanges {
-		inc := big.NewInt(1)
-		startBig := big.NewInt(0)
-		startBig.SetBytes(IPRange.Start)
-		endBig := big.NewInt(0)
-		endBig.SetBytes(IPRange.End)
-
-		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())
-
-			_, inUse := usedIPs[IPKey]
-			if inUse {
-				startBig.Add(startBig, inc)
-				continue
-			}
-
-			return IP, nil
-		}
-	}
-
-	return nil, fmt.Errorf("No available IP could not be found")
-}
-
-// getDHCPFreeIPv6 attempts to find a free IPv6 address for the device.
-// It first checks whether there is an existing allocation for the instance. 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 (d *nicBridged) getDHCPFreeIPv6(usedIPs map[[16]byte]dnsmasq.DHCPAllocation, n network.Network, ctName string, deviceMAC string) (net.IP, error) {
-	netConfig := n.Config()
-	lxdIP, subnet, err := net.ParseCIDR(netConfig["ipv6.address"])
+	err = d.state.Firewall.InstanceSetupBridgeFilter(d.inst.Project(), d.inst.Name(), d.name, d.config["parent"], d.config["host_name"], d.config["hwaddr"], IPv4, IPv6)
 	if err != nil {
-		return nil, err
-	}
-
-	dhcpRanges := n.DHCPv6Ranges()
-
-	// Lets see if there is already an allocation for our device and that it sits within subnet.
-	// Because of dnsmasq's lease file format we can only match safely against static
-	// allocations using instance name. If there are custom DHCP ranges defined, check also
-	// that the IP falls within one of the ranges.
-	for _, DHCP := range usedIPs {
-		if ctName == DHCP.Name && networkDHCPValidIP(subnet, dhcpRanges, DHCP.IP) {
-			return DHCP.IP, nil
-		}
-	}
-
-	// 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 and not the LXD IP.
-		var IPKey [16]byte
-		copy(IPKey[:], IP.To16())
-		_, inUse := usedIPs[IPKey]
-		if !inUse && !IP.Equal(lxdIP) {
-			return IP, nil
-		}
-	}
-
-	// If no custom ranges defined, convert subnet pool to a range.
-	if len(dhcpRanges) <= 0 {
-		dhcpRanges = append(dhcpRanges, network.DHCPRange{
-			Start: network.GetIP(subnet, 1).To16(),
-			End:   network.GetIP(subnet, -1).To16()},
-		)
-	}
-
-	// If we get here, then someone already has our SLAAC IP, or we are using custom ranges.
-	// Try and find a free one in the subnet pool/ranges.
-	for _, IPRange := range dhcpRanges {
-		inc := big.NewInt(1)
-		startBig := big.NewInt(0)
-		startBig.SetBytes(IPRange.Start)
-		endBig := big.NewInt(0)
-		endBig.SetBytes(IPRange.End)
-
-		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())
-
-			_, inUse := usedIPs[IPKey]
-			if inUse {
-				startBig.Add(startBig, inc)
-				continue
-			}
-
-			return IP, nil
-		}
+		return err
 	}
 
-	return nil, fmt.Errorf("No available IP could not be found")
+	revert.Success()
+	return nil
 }
 
 const (


More information about the lxc-devel mailing list