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

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) ===
We need to perform static DHCP allocation against the dnsmasq config on a managed LXD bridge in order to allocate an IP for the OVN uplink ports.

This is very similar to what we already do in the bridged NIC when automatically allocating  static IP for use with the `security.ipN_filtering` features.

The existing implementation was tightly coupled to said IP filtering features and the bridged NIC type. Also I was not happy with the clarity of the existing code (I wrote it) as the way it dealt with the ability to selectively generate IP allocations for each protocol dependent on which IP filtering feature was enabled, whilst still only parsing the dnsmasq leases file once, was not particularly clear and had to handle many edge cases in multiple places.

In hind site what would have made it clearer was to introduce the concept of a dnsmasq allocation 'transaction', whereby the dnsmasq config could be locked, the leases file read in one go, and then allow the caller to provide a function to perform the desired allocations against that config, then finally write the generated host config file back to disk at the end.

This allows the caller to choose to only allocate certain protocol IPs based on their specific config/situation.

This PR introduces a new package called `dnsmasq/dhcpalloc` which provides just that, and then updates the bridged NIC to use it.
From 2cfc0302744a521ed69ce40411da5e02c984898d Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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 (
_______________________________________________
lxc-devel mailing list
lxc-devel@lists.linuxcontainers.org
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to