[lxc-devel] [lxd/master] Network: Adds "network" property to bridged NIC device

tomponline on Github lxc-bot at linuxcontainers.org
Fri Jan 31 17:56:24 UTC 2020


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 346 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20200131/8501c4a8/attachment-0001.bin>
-------------- next part --------------
From 36f9705d1c5c924cdc01a7040b8820345786a974 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jan 2020 14:00:37 +0000
Subject: [PATCH 01/30] lxd/instance/drivers/load: Pass copy of device config
 to device.Validate

Ensures a copy of the instance's device config is passed to device.Validate to ensure devices cannot modify the source config map.

This way any internal modifications a device may make to its config map are not reflected outside of the device.

This mirrors the existing usage of device.New().

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

diff --git a/lxd/instance/drivers/load.go b/lxd/instance/drivers/load.go
index a77e55f8a0..548112c7b2 100644
--- a/lxd/instance/drivers/load.go
+++ b/lxd/instance/drivers/load.go
@@ -66,7 +66,8 @@ func validDevices(state *state.State, cluster *db.Cluster, instanceType instance
 	}
 
 	// Check each device individually using the device package.
-	for name, config := range devices {
+	// Use instConf.localDevices so that the cloned config is passed into the driver, so it cannot modify it.
+	for name, config := range instConf.localDevices {
 		err := device.Validate(instConf, state, name, config)
 		if err != nil {
 			return errors.Wrapf(err, "Device validation failed %q", name)

From a400a42274c91b3e2a13fb1307982b9b36960877 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jan 2020 16:19:42 +0000
Subject: [PATCH 02/30] lxd/device/nic/bridged: Updates use of network pkg
 functions

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

diff --git a/lxd/device/nic_bridged.go b/lxd/device/nic_bridged.go
index eb93964ffd..a83488905a 100644
--- a/lxd/device/nic_bridged.go
+++ b/lxd/device/nic_bridged.go
@@ -7,7 +7,6 @@ import (
 	"encoding/hex"
 	"fmt"
 	"io/ioutil"
-	"math"
 	"math/big"
 	"math/rand"
 	"net"
@@ -25,6 +24,7 @@ import (
 	firewallConsts "github.com/lxc/lxd/lxd/firewall/consts"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/instance/instancetype"
+	"github.com/lxc/lxd/lxd/network"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/logger"
@@ -157,7 +157,7 @@ func (d *nicBridged) Start() (*deviceConfig.RunConfig, error) {
 	}
 
 	// Attach host side veth interface to bridge.
-	err = NetworkAttachInterface(d.config["parent"], saveData["host_name"])
+	err = network.AttachInterface(d.config["parent"], saveData["host_name"])
 	if err != nil {
 		NetworkRemoveInterface(saveData["host_name"])
 		return nil, err
@@ -839,7 +839,7 @@ func (d *nicBridged) getDHCPFreeIPv4(usedIPs map[[4]byte]dhcpAllocation, netConf
 
 	// If no custom ranges defined, convert subnet pool to a range.
 	if len(dhcpRanges) <= 0 {
-		dhcpRanges = append(dhcpRanges, dhcpRange{Start: d.networkGetIP(subnet, 1).To4(), End: d.networkGetIP(subnet, -2).To4()})
+		dhcpRanges = append(dhcpRanges, 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.
@@ -928,7 +928,7 @@ func (d *nicBridged) getDHCPFreeIPv6(usedIPs map[[16]byte]dhcpAllocation, netCon
 
 	// If no custom ranges defined, convert subnet pool to a range.
 	if len(dhcpRanges) <= 0 {
-		dhcpRanges = append(dhcpRanges, dhcpRange{Start: d.networkGetIP(subnet, 1).To16(), End: d.networkGetIP(subnet, -1).To16()})
+		dhcpRanges = append(dhcpRanges, 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.
@@ -970,40 +970,6 @@ func (d *nicBridged) getDHCPFreeIPv6(usedIPs map[[16]byte]dhcpAllocation, netCon
 	return nil, fmt.Errorf("No available IP could not be found")
 }
 
-func (d *nicBridged) networkGetIP(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
-}
-
 const (
 	clearLeaseAll = iota
 	clearLeaseIPv4Only

From 38f8ce141faea40309c3cafa1d0262432c45e290 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jan 2020 17:25:09 +0000
Subject: [PATCH 03/30] lxd/device/nic/bridged: Uses network.LoadByName to
 access n.HasDHCPvX() helpers

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

diff --git a/lxd/device/nic_bridged.go b/lxd/device/nic_bridged.go
index a83488905a..10bc33b68f 100644
--- a/lxd/device/nic_bridged.go
+++ b/lxd/device/nic_bridged.go
@@ -493,22 +493,22 @@ func (d *nicBridged) setFilters() (err error) {
 
 	// Check if the parent is managed and load config. If parent is unmanaged continue anyway.
 	var IPv4, IPv6 net.IP
-	_, netInfo, err := d.state.Cluster.NetworkGet(d.config["parent"])
+	net, err := network.LoadByName(d.state, d.config["parent"])
 	if err != nil && err != db.ErrNoSuchObject {
 		return err
 	}
 
 	// If parent bridge is unmanaged check that IP filtering isn't enabled.
-	if err == db.ErrNoSuchObject || netInfo == nil {
+	if err == db.ErrNoSuchObject || net == nil {
 		if shared.IsTrue(d.config["security.ipv4_filtering"]) || shared.IsTrue(d.config["security.ipv6_filtering"]) {
 			return fmt.Errorf("IP filtering requires using a managed parent bridge")
 		}
 	}
 
 	// If parent bridge is unmanaged we cannot allocate static IPs.
-	if netInfo != nil {
+	if net != nil {
 		// Retrieve existing IPs, or allocate new ones if needed.
-		IPv4, IPv6, err = d.allocateFilterIPs(netInfo.Config)
+		IPv4, IPv6, err = d.allocateFilterIPs(net)
 		if err != nil {
 			return err
 		}
@@ -527,7 +527,7 @@ func (d *nicBridged) setFilters() (err error) {
 // 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(netConfig map[string]string) (net.IP, net.IP, error) {
+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.
@@ -546,9 +546,11 @@ func (d *nicBridged) allocateFilterIPs(netConfig map[string]string) (net.IP, net
 		}
 	}
 
+	netConfig := n.Config()
+
 	// Check the conditions required to dynamically allocated IPs.
-	canIPv4Allocate := netConfig["ipv4.address"] != "" && netConfig["ipv4.address"] != "none" && (netConfig["ipv4.dhcp"] == "" || shared.IsTrue(netConfig["ipv4.dhcp"]))
-	canIPv6Allocate := netConfig["ipv6.address"] != "" && netConfig["ipv6.address"] != "none" && (netConfig["ipv6.dhcp"] == "" || shared.IsTrue(netConfig["ipv6.dhcp"]))
+	canIPv4Allocate := netConfig["ipv4.address"] != "" && netConfig["ipv4.address"] != "none" && n.HasDHCPv4()
+	canIPv6Allocate := netConfig["ipv6.address"] != "" && netConfig["ipv6.address"] != "none" && n.HasDHCPv6()
 
 	// Check DHCPv4 is enabled on parent if dynamic IPv4 allocation is needed.
 	if shared.IsTrue(d.config["security.ipv4_filtering"]) && IPv4 == nil && !canIPv4Allocate {

From 0deb9d24beb625a367e58db9a48babb73828526d Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jan 2020 16:20:12 +0000
Subject: [PATCH 04/30] lxd/device: networkRandomDevName 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 10bc33b68f..b30248fe33 100644
--- a/lxd/device/nic_bridged.go
+++ b/lxd/device/nic_bridged.go
@@ -127,12 +127,12 @@ func (d *nicBridged) Start() (*deviceConfig.RunConfig, error) {
 	// Create veth pair and configure the peer end with custom hwaddr and mtu if supplied.
 	if d.inst.Type() == instancetype.Container {
 		if saveData["host_name"] == "" {
-			saveData["host_name"] = NetworkRandomDevName("veth")
+			saveData["host_name"] = networkRandomDevName("veth")
 		}
 		peerName, err = networkCreateVethPair(saveData["host_name"], d.config)
 	} else if d.inst.Type() == instancetype.VM {
 		if saveData["host_name"] == "" {
-			saveData["host_name"] = NetworkRandomDevName("tap")
+			saveData["host_name"] = networkRandomDevName("tap")
 		}
 		peerName = saveData["host_name"] // VMs use the host_name to link to the TAP FD.
 		err = networkCreateTap(saveData["host_name"], d.config)

From 3e9ec05d8a2a778f4c5f771c7a8dec5c6162a66e Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jan 2020 16:23:35 +0000
Subject: [PATCH 05/30] lxd/network/network/load: Adds LoadByName function

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

diff --git a/lxd/network/network_load.go b/lxd/network/network_load.go
new file mode 100644
index 0000000000..cf8d94e029
--- /dev/null
+++ b/lxd/network/network_load.go
@@ -0,0 +1,23 @@
+package network
+
+import (
+	"github.com/lxc/lxd/lxd/state"
+)
+
+// LoadByName loads the network info from the database by name.
+func LoadByName(s *state.State, name string) (*Network, error) {
+	id, dbInfo, err := s.Cluster.NetworkGet(name)
+	if err != nil {
+		return nil, err
+	}
+
+	n := &Network{
+		state:       s,
+		id:          id,
+		name:        name,
+		description: dbInfo.Description,
+		config:      dbInfo.Config,
+	}
+
+	return n, nil
+}

From e739ff2cb6a17b1b81df82c4aeb2a3aea662c2e3 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jan 2020 16:24:06 +0000
Subject: [PATCH 06/30] lxd/network: Adds network type in network pkg

Also adds n.HasDHCPv4() and n.HasDHCPv6() functions to indicate whether DHCP server is enabled for the network.

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

diff --git a/lxd/network/network.go b/lxd/network/network.go
new file mode 100644
index 0000000000..3d6b3272d0
--- /dev/null
+++ b/lxd/network/network.go
@@ -0,0 +1,1600 @@
+package network
+
+import (
+	"bufio"
+	"encoding/binary"
+	"fmt"
+	"io/ioutil"
+	"net"
+	"os"
+	"os/exec"
+	"reflect"
+	"strings"
+	"sync"
+
+	"github.com/pkg/errors"
+
+	lxd "github.com/lxc/lxd/client"
+	"github.com/lxc/lxd/lxd/cluster"
+	"github.com/lxc/lxd/lxd/daemon"
+	"github.com/lxc/lxd/lxd/dnsmasq"
+	firewallConsts "github.com/lxc/lxd/lxd/firewall/consts"
+	"github.com/lxc/lxd/lxd/instance"
+	"github.com/lxc/lxd/lxd/node"
+	"github.com/lxc/lxd/lxd/state"
+	"github.com/lxc/lxd/lxd/util"
+	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
+	"github.com/lxc/lxd/shared/logger"
+	"github.com/lxc/lxd/shared/subprocess"
+	"github.com/lxc/lxd/shared/version"
+)
+
+// ForkdnsServersListPath defines the path that contains the forkdns server candidate file.
+const ForkdnsServersListPath = "forkdns.servers"
+
+// ForkdnsServersListFile file that contains the server candidates list.
+const ForkdnsServersListFile = "servers.conf"
+
+var forkdnsServersLock sync.Mutex
+
+// Network represents a LXD network.
+type Network struct {
+	// Properties
+	state       *state.State
+	id          int64
+	name        string
+	description string
+
+	// config
+	config map[string]string
+}
+
+// Name returns the network name.
+func (n *Network) Name() string {
+	return n.name
+}
+
+// Config returns the network config.
+func (n *Network) Config() map[string]string {
+	return n.config
+}
+
+// IsRunning returns whether the network is up.
+func (n *Network) IsRunning() bool {
+	return shared.PathExists(fmt.Sprintf("/sys/class/net/%s", n.name))
+}
+
+// IsUsed returns whether the network is used by any instances.
+func (n *Network) IsUsed() bool {
+	// Look for instances using the interface
+	insts, err := instance.LoadFromAllProjects(n.state)
+	if err != nil {
+		return true
+	}
+
+	for _, inst := range insts {
+		if IsInUse(inst, n.name) {
+			return true
+		}
+	}
+
+	return false
+}
+
+// Delete deletes a network.
+func (n *Network) Delete(withDatabase bool) error {
+	// Bring the network down
+	if n.IsRunning() {
+		err := n.Stop()
+		if err != nil {
+			return err
+		}
+	}
+
+	// If withDatabase is false, this is a cluster notification, and we
+	// don't want to perform any database work.
+	if !withDatabase {
+		return nil
+	}
+
+	// Remove the network from the database
+	err := n.state.Cluster.NetworkDelete(n.name)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// Rename renames a network.
+func (n *Network) Rename(name string) error {
+	// Sanity checks
+	if n.IsUsed() {
+		return fmt.Errorf("The network is currently in use")
+	}
+
+	// Bring the network down
+	if n.IsRunning() {
+		err := n.Stop()
+		if err != nil {
+			return err
+		}
+	}
+
+	// Rename directory
+	if shared.PathExists(shared.VarPath("networks", name)) {
+		os.RemoveAll(shared.VarPath("networks", name))
+	}
+
+	if shared.PathExists(shared.VarPath("networks", n.name)) {
+		err := os.Rename(shared.VarPath("networks", n.name), shared.VarPath("networks", name))
+		if err != nil {
+			return err
+		}
+	}
+
+	forkDNSLogPath := fmt.Sprintf("forkdns.%s.log", n.name)
+	if shared.PathExists(shared.LogPath(forkDNSLogPath)) {
+		err := os.Rename(forkDNSLogPath, shared.LogPath(fmt.Sprintf("forkdns.%s.log", name)))
+		if err != nil {
+			return err
+		}
+	}
+
+	// Rename the database entry
+	err := n.state.Cluster.NetworkRename(n.name, name)
+	if err != nil {
+		return err
+	}
+	n.name = name
+
+	// Bring the network up
+	err = n.Start()
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// Start starts the network.
+func (n *Network) Start() error {
+	return n.setup(nil)
+}
+
+// setup restarts the network.
+func (n *Network) setup(oldConfig map[string]string) error {
+	// If we are in mock mode, just no-op.
+	if n.state.OS.MockMode {
+		return nil
+	}
+
+	// Create directory
+	if !shared.PathExists(shared.VarPath("networks", n.name)) {
+		err := os.MkdirAll(shared.VarPath("networks", n.name), 0711)
+		if err != nil {
+			return err
+		}
+	}
+
+	// Create the bridge interface
+	if !n.IsRunning() {
+		if n.config["bridge.driver"] == "openvswitch" {
+			_, err := exec.LookPath("ovs-vsctl")
+			if err != nil {
+				return fmt.Errorf("Open vSwitch isn't installed on this system")
+			}
+
+			_, err = shared.RunCommand("ovs-vsctl", "add-br", n.name)
+			if err != nil {
+				return err
+			}
+		} else {
+			_, err := shared.RunCommand("ip", "link", "add", "dev", n.name, "type", "bridge")
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	// Get a list of tunnels
+	tunnels := n.getTunnels()
+
+	// IPv6 bridge configuration
+	if !shared.StringInSlice(n.config["ipv6.address"], []string{"", "none"}) {
+		if !shared.PathExists("/proc/sys/net/ipv6") {
+			return fmt.Errorf("Network has ipv6.address but kernel IPv6 support is missing")
+		}
+
+		err := util.SysctlSet(fmt.Sprintf("net/ipv6/conf/%s/autoconf", n.name), "0")
+		if err != nil {
+			return err
+		}
+
+		err = util.SysctlSet(fmt.Sprintf("net/ipv6/conf/%s/accept_dad", n.name), "0")
+		if err != nil {
+			return err
+		}
+	}
+
+	// Get a list of interfaces
+	ifaces, err := net.Interfaces()
+	if err != nil {
+		return err
+	}
+
+	// Cleanup any existing tunnel device
+	for _, iface := range ifaces {
+		if strings.HasPrefix(iface.Name, fmt.Sprintf("%s-", n.name)) {
+			_, err = shared.RunCommand("ip", "link", "del", "dev", iface.Name)
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	// Set the MTU
+	mtu := ""
+	if n.config["bridge.mtu"] != "" {
+		mtu = n.config["bridge.mtu"]
+	} else if len(tunnels) > 0 {
+		mtu = "1400"
+	} else if n.config["bridge.mode"] == "fan" {
+		if n.config["fan.type"] == "ipip" {
+			mtu = "1480"
+		} else {
+			mtu = "1450"
+		}
+	}
+
+	// Attempt to add a dummy device to the bridge to force the MTU
+	if mtu != "" && n.config["bridge.driver"] != "openvswitch" {
+		_, err = shared.RunCommand("ip", "link", "add", "dev", fmt.Sprintf("%s-mtu", n.name), "mtu", mtu, "type", "dummy")
+		if err == nil {
+			_, err = shared.RunCommand("ip", "link", "set", "dev", fmt.Sprintf("%s-mtu", n.name), "up")
+			if err == nil {
+				AttachInterface(n.name, fmt.Sprintf("%s-mtu", n.name))
+			}
+		}
+	}
+
+	// Now, set a default MTU
+	if mtu == "" {
+		mtu = "1500"
+	}
+
+	_, err = shared.RunCommand("ip", "link", "set", "dev", n.name, "mtu", mtu)
+	if err != nil {
+		return err
+	}
+
+	// Set the MAC address
+	if n.config["bridge.hwaddr"] != "" {
+		_, err = shared.RunCommand("ip", "link", "set", "dev", n.name, "address", n.config["bridge.hwaddr"])
+		if err != nil {
+			return err
+		}
+	}
+
+	// Bring it up
+	_, err = shared.RunCommand("ip", "link", "set", "dev", n.name, "up")
+	if err != nil {
+		return err
+	}
+
+	// Add any listed existing external interface
+	if n.config["bridge.external_interfaces"] != "" {
+		for _, entry := range strings.Split(n.config["bridge.external_interfaces"], ",") {
+			entry = strings.TrimSpace(entry)
+			iface, err := net.InterfaceByName(entry)
+			if err != nil {
+				continue
+			}
+
+			unused := true
+			addrs, err := iface.Addrs()
+			if err == nil {
+				for _, addr := range addrs {
+					ip, _, err := net.ParseCIDR(addr.String())
+					if ip != nil && err == nil && ip.IsGlobalUnicast() {
+						unused = false
+						break
+					}
+				}
+			}
+
+			if !unused {
+				return fmt.Errorf("Only unconfigured network interfaces can be bridged")
+			}
+
+			err = AttachInterface(n.name, entry)
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	// Remove any existing IPv4 iptables rules
+	if n.config["ipv4.firewall"] == "" || shared.IsTrue(n.config["ipv4.firewall"]) || (oldConfig != nil && (oldConfig["ipv4.firewall"] == "" || shared.IsTrue(oldConfig["ipv4.firewall"]))) {
+		err = n.state.Firewall.NetworkClear(firewallConsts.FamilyIPv4, firewallConsts.TableAll, n.name)
+		if err != nil {
+			return err
+		}
+
+		err = n.state.Firewall.NetworkClear(firewallConsts.FamilyIPv4, firewallConsts.TableMangle, n.name)
+		if err != nil {
+			return err
+		}
+	}
+
+	if shared.IsTrue(n.config["ipv4.nat"]) || (oldConfig != nil && shared.IsTrue(oldConfig["ipv4.nat"])) {
+		err = n.state.Firewall.NetworkClear(firewallConsts.FamilyIPv4, firewallConsts.TableNat, n.name)
+		if err != nil {
+			return err
+		}
+	}
+
+	// Snapshot container specific IPv4 routes (added with boot proto) before removing IPv4 addresses.
+	// This is because the kernel removes any static routes on an interface when all addresses removed.
+	ctRoutes, err := n.bootRoutesV4()
+	if err != nil {
+		return err
+	}
+
+	// Flush all IPv4 addresses and routes
+	_, err = shared.RunCommand("ip", "-4", "addr", "flush", "dev", n.name, "scope", "global")
+	if err != nil {
+		return err
+	}
+
+	_, err = shared.RunCommand("ip", "-4", "route", "flush", "dev", n.name, "proto", "static")
+	if err != nil {
+		return err
+	}
+
+	// Configure IPv4 firewall (includes fan)
+	if n.config["bridge.mode"] == "fan" || !shared.StringInSlice(n.config["ipv4.address"], []string{"", "none"}) {
+		if n.HasDHCPv4() && (n.config["ipv4.firewall"] == "" || shared.IsTrue(n.config["ipv4.firewall"])) {
+			// Setup basic iptables overrides for DHCP/DNS
+			n.state.Firewall.NetworkSetupIPv4DNSOverrides(n.name)
+		}
+
+		// Attempt a workaround for broken DHCP clients
+		if n.config["ipv4.firewall"] == "" || shared.IsTrue(n.config["ipv4.firewall"]) {
+			n.state.Firewall.NetworkSetupIPv4DHCPWorkaround(n.name)
+		}
+
+		// Allow forwarding
+		if n.config["bridge.mode"] == "fan" || n.config["ipv4.routing"] == "" || shared.IsTrue(n.config["ipv4.routing"]) {
+			err = util.SysctlSet("net/ipv4/ip_forward", "1")
+			if err != nil {
+				return err
+			}
+
+			if n.config["ipv4.firewall"] == "" || shared.IsTrue(n.config["ipv4.firewall"]) {
+				err = n.state.Firewall.NetworkSetupAllowForwarding(firewallConsts.FamilyIPv4, n.name, firewallConsts.ActionAccept)
+				if err != nil {
+					return err
+				}
+			}
+		} else {
+			if n.config["ipv4.firewall"] == "" || shared.IsTrue(n.config["ipv4.firewall"]) {
+				err = n.state.Firewall.NetworkSetupAllowForwarding(firewallConsts.FamilyIPv4, n.name, firewallConsts.ActionReject)
+				if err != nil {
+					return err
+				}
+			}
+		}
+	}
+
+	// Start building process using subprocess package
+	command := "dnsmasq"
+	dnsmasqCmd := []string{"--keep-in-foreground", "--strict-order", "--bind-interfaces",
+		"--except-interface=lo",
+		"--no-ping", // --no-ping is very important to prevent delays to lease file updates.
+		fmt.Sprintf("--interface=%s", n.name)}
+
+	dnsmasqVersion, err := dnsmasq.GetVersion()
+	if err != nil {
+		return err
+	}
+
+	// --dhcp-rapid-commit option is only supported on >2.79
+	minVer, _ := version.NewDottedVersion("2.79")
+	if dnsmasqVersion.Compare(minVer) > 0 {
+		dnsmasqCmd = append(dnsmasqCmd, "--dhcp-rapid-commit")
+	}
+
+	if !daemon.Debug {
+		// --quiet options are only supported on >2.67
+		minVer, _ := version.NewDottedVersion("2.67")
+
+		if err == nil && dnsmasqVersion.Compare(minVer) > 0 {
+			dnsmasqCmd = append(dnsmasqCmd, []string{"--quiet-dhcp", "--quiet-dhcp6", "--quiet-ra"}...)
+		}
+	}
+
+	// Configure IPv4
+	if !shared.StringInSlice(n.config["ipv4.address"], []string{"", "none"}) {
+		// Parse the subnet
+		ip, subnet, err := net.ParseCIDR(n.config["ipv4.address"])
+		if err != nil {
+			return err
+		}
+
+		// Update the dnsmasq config
+		dnsmasqCmd = append(dnsmasqCmd, fmt.Sprintf("--listen-address=%s", ip.String()))
+		if n.HasDHCPv4() {
+			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"))}...)
+			}
+
+			if n.config["ipv4.dhcp.gateway"] != "" {
+				dnsmasqCmd = append(dnsmasqCmd, fmt.Sprintf("--dhcp-option=3,%s", n.config["ipv4.dhcp.gateway"]))
+			}
+
+			expiry := "1h"
+			if n.config["ipv4.dhcp.expiry"] != "" {
+				expiry = n.config["ipv4.dhcp.expiry"]
+			}
+
+			if n.config["ipv4.dhcp.ranges"] != "" {
+				for _, dhcpRange := range strings.Split(n.config["ipv4.dhcp.ranges"], ",") {
+					dhcpRange = strings.TrimSpace(dhcpRange)
+					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)}...)
+			}
+		}
+
+		// Add the address
+		_, err = shared.RunCommand("ip", "-4", "addr", "add", "dev", n.name, n.config["ipv4.address"])
+		if err != nil {
+			return err
+		}
+
+		// Configure NAT
+		if shared.IsTrue(n.config["ipv4.nat"]) {
+			//If a SNAT source address is specified, use that, otherwise default to using MASQUERADE mode.
+			args := []string{"-s", subnet.String(), "!", "-d", subnet.String(), "-j", "MASQUERADE"}
+			if n.config["ipv4.nat.address"] != "" {
+				args = []string{"-s", subnet.String(), "!", "-d", subnet.String(), "-j", "SNAT", "--to", n.config["ipv4.nat.address"]}
+			}
+
+			if n.config["ipv4.nat.order"] == "after" {
+				err = n.state.Firewall.NetworkSetupNAT(firewallConsts.FamilyIPv4, n.name, firewallConsts.LocationAppend, args...)
+				if err != nil {
+					return err
+				}
+			} else {
+				err = n.state.Firewall.NetworkSetupNAT(firewallConsts.FamilyIPv4, n.name, firewallConsts.LocationPrepend, args...)
+				if err != nil {
+					return err
+				}
+			}
+		}
+
+		// Add additional routes
+		if n.config["ipv4.routes"] != "" {
+			for _, route := range strings.Split(n.config["ipv4.routes"], ",") {
+				route = strings.TrimSpace(route)
+				_, err = shared.RunCommand("ip", "-4", "route", "add", "dev", n.name, route, "proto", "static")
+				if err != nil {
+					return err
+				}
+			}
+		}
+
+		// Restore container specific IPv4 routes to interface.
+		err = n.applyBootRoutesV4(ctRoutes)
+		if err != nil {
+			return err
+		}
+	}
+
+	// Remove any existing IPv6 iptables rules
+	if n.config["ipv6.firewall"] == "" || shared.IsTrue(n.config["ipv6.firewall"]) || (oldConfig != nil && (oldConfig["ipv6.firewall"] == "" || shared.IsTrue(oldConfig["ipv6.firewall"]))) {
+		err = n.state.Firewall.NetworkClear(firewallConsts.FamilyIPv6, firewallConsts.TableAll, n.name)
+		if err != nil {
+			return err
+		}
+	}
+
+	if shared.IsTrue(n.config["ipv6.nat"]) || (oldConfig != nil && shared.IsTrue(oldConfig["ipv6.nat"])) {
+		err = n.state.Firewall.NetworkClear(firewallConsts.FamilyIPv6, firewallConsts.TableNat, n.name)
+		if err != nil {
+			return err
+		}
+	}
+
+	// Snapshot container specific IPv6 routes (added with boot proto) before removing IPv6 addresses.
+	// This is because the kernel removes any static routes on an interface when all addresses removed.
+	ctRoutes, err = n.bootRoutesV6()
+	if err != nil {
+		return err
+	}
+
+	// Flush all IPv6 addresses and routes
+	_, err = shared.RunCommand("ip", "-6", "addr", "flush", "dev", n.name, "scope", "global")
+	if err != nil {
+		return err
+	}
+
+	_, err = shared.RunCommand("ip", "-6", "route", "flush", "dev", n.name, "proto", "static")
+	if err != nil {
+		return err
+	}
+
+	// Configure IPv6
+	if !shared.StringInSlice(n.config["ipv6.address"], []string{"", "none"}) {
+		// Enable IPv6 for the subnet
+		err := util.SysctlSet(fmt.Sprintf("net/ipv6/conf/%s/disable_ipv6", n.name), "0")
+		if err != nil {
+			return err
+		}
+
+		// Parse the subnet
+		ip, subnet, err := net.ParseCIDR(n.config["ipv6.address"])
+		if err != nil {
+			return err
+		}
+
+		// Update the dnsmasq config
+		dnsmasqCmd = append(dnsmasqCmd, []string{fmt.Sprintf("--listen-address=%s", ip.String()), "--enable-ra"}...)
+		if n.config["ipv6.dhcp"] == "" || shared.IsTrue(n.config["ipv6.dhcp"]) {
+			if n.config["ipv6.firewall"] == "" || shared.IsTrue(n.config["ipv6.firewall"]) {
+				// Setup basic iptables overrides for DHCP/DNS
+				n.state.Firewall.NetworkSetupIPv6DNSOverrides(n.name)
+			}
+
+			// Build DHCP configuration
+			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"))}...)
+			}
+
+			expiry := "1h"
+			if n.config["ipv6.dhcp.expiry"] != "" {
+				expiry = n.config["ipv6.dhcp.expiry"]
+			}
+
+			if shared.IsTrue(n.config["ipv6.dhcp.stateful"]) {
+				subnetSize, _ := subnet.Mask.Size()
+				if n.config["ipv6.dhcp.ranges"] != "" {
+					for _, dhcpRange := range strings.Split(n.config["ipv6.dhcp.ranges"], ",") {
+						dhcpRange = strings.TrimSpace(dhcpRange)
+						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)}...)
+				}
+			} else {
+				dnsmasqCmd = append(dnsmasqCmd, []string{"--dhcp-range", fmt.Sprintf("::,constructor:%s,ra-stateless,ra-names", n.name)}...)
+			}
+		} else {
+			dnsmasqCmd = append(dnsmasqCmd, []string{"--dhcp-range", fmt.Sprintf("::,constructor:%s,ra-only", n.name)}...)
+		}
+
+		// Allow forwarding
+		if n.config["ipv6.routing"] == "" || shared.IsTrue(n.config["ipv6.routing"]) {
+			// Get a list of proc entries
+			entries, err := ioutil.ReadDir("/proc/sys/net/ipv6/conf/")
+			if err != nil {
+				return err
+			}
+
+			// First set accept_ra to 2 for everything
+			for _, entry := range entries {
+				content, err := ioutil.ReadFile(fmt.Sprintf("/proc/sys/net/ipv6/conf/%s/accept_ra", entry.Name()))
+				if err == nil && string(content) != "1\n" {
+					continue
+				}
+
+				err = util.SysctlSet(fmt.Sprintf("net/ipv6/conf/%s/accept_ra", entry.Name()), "2")
+				if err != nil && !os.IsNotExist(err) {
+					return err
+				}
+			}
+
+			// Then set forwarding for all of them
+			for _, entry := range entries {
+				err = util.SysctlSet(fmt.Sprintf("net/ipv6/conf/%s/forwarding", entry.Name()), "1")
+				if err != nil && !os.IsNotExist(err) {
+					return err
+				}
+			}
+
+			if n.config["ipv6.firewall"] == "" || shared.IsTrue(n.config["ipv6.firewall"]) {
+				err = n.state.Firewall.NetworkSetupAllowForwarding(firewallConsts.FamilyIPv6, n.name, firewallConsts.ActionAccept)
+				if err != nil {
+					return err
+				}
+			}
+		} else {
+			if n.config["ipv6.firewall"] == "" || shared.IsTrue(n.config["ipv6.firewall"]) {
+				err = n.state.Firewall.NetworkSetupAllowForwarding(firewallConsts.FamilyIPv6, n.name, firewallConsts.ActionReject)
+				if err != nil {
+					return err
+				}
+			}
+		}
+
+		// Add the address
+		_, err = shared.RunCommand("ip", "-6", "addr", "add", "dev", n.name, n.config["ipv6.address"])
+		if err != nil {
+			return err
+		}
+
+		// Configure NAT
+		if shared.IsTrue(n.config["ipv6.nat"]) {
+			args := []string{"-s", subnet.String(), "!", "-d", subnet.String(), "-j", "MASQUERADE"}
+			if n.config["ipv6.nat.address"] != "" {
+				args = []string{"-s", subnet.String(), "!", "-d", subnet.String(), "-j", "SNAT", "--to", n.config["ipv6.nat.address"]}
+			}
+
+			if n.config["ipv6.nat.order"] == "after" {
+				err = n.state.Firewall.NetworkSetupNAT(firewallConsts.FamilyIPv6, n.name, firewallConsts.LocationAppend, args...)
+				if err != nil {
+					return err
+				}
+			} else {
+				err = n.state.Firewall.NetworkSetupNAT(firewallConsts.FamilyIPv6, n.name, firewallConsts.LocationPrepend, args...)
+				if err != nil {
+					return err
+				}
+			}
+		}
+
+		// Add additional routes
+		if n.config["ipv6.routes"] != "" {
+			for _, route := range strings.Split(n.config["ipv6.routes"], ",") {
+				route = strings.TrimSpace(route)
+				_, err = shared.RunCommand("ip", "-6", "route", "add", "dev", n.name, route, "proto", "static")
+				if err != nil {
+					return err
+				}
+			}
+		}
+
+		// Restore container specific IPv6 routes to interface.
+		err = n.applyBootRoutesV6(ctRoutes)
+		if err != nil {
+			return err
+		}
+	}
+
+	// Configure the fan
+	dnsClustered := false
+	dnsClusteredAddress := ""
+	var overlaySubnet *net.IPNet
+	if n.config["bridge.mode"] == "fan" {
+		tunName := fmt.Sprintf("%s-fan", n.name)
+
+		// Parse the underlay
+		underlay := n.config["fan.underlay_subnet"]
+		_, underlaySubnet, err := net.ParseCIDR(underlay)
+		if err != nil {
+			return nil
+		}
+
+		// Parse the overlay
+		overlay := n.config["fan.overlay_subnet"]
+		if overlay == "" {
+			overlay = "240.0.0.0/8"
+		}
+
+		_, overlaySubnet, err = net.ParseCIDR(overlay)
+		if err != nil {
+			return err
+		}
+
+		// Get the address
+		fanAddress, devName, devAddr, err := n.fanAddress(underlaySubnet, overlaySubnet)
+		if err != nil {
+			return err
+		}
+
+		addr := strings.Split(fanAddress, "/")
+		if n.config["fan.type"] == "ipip" {
+			fanAddress = fmt.Sprintf("%s/24", addr[0])
+		}
+
+		// Update the MTU based on overlay device (if available)
+		fanMtuInt, err := GetDevMTU(devName)
+		if err == nil {
+			// Apply overhead
+			if n.config["fan.type"] == "ipip" {
+				fanMtuInt = fanMtuInt - 20
+			} else {
+				fanMtuInt = fanMtuInt - 50
+			}
+
+			// Apply changes
+			fanMtu := fmt.Sprintf("%d", fanMtuInt)
+			if fanMtu != mtu {
+				mtu = fanMtu
+				if n.config["bridge.driver"] != "openvswitch" {
+					_, err = shared.RunCommand("ip", "link", "set", "dev", fmt.Sprintf("%s-mtu", n.name), "mtu", mtu)
+					if err != nil {
+						return err
+					}
+				}
+
+				_, err = shared.RunCommand("ip", "link", "set", "dev", n.name, "mtu", mtu)
+				if err != nil {
+					return err
+				}
+			}
+		}
+
+		// Parse the host subnet
+		_, hostSubnet, err := net.ParseCIDR(fmt.Sprintf("%s/24", addr[0]))
+		if err != nil {
+			return err
+		}
+
+		// Add the address
+		_, err = shared.RunCommand("ip", "-4", "addr", "add", "dev", n.name, fanAddress)
+		if err != nil {
+			return err
+		}
+
+		// Update the dnsmasq config
+		expiry := "1h"
+		if n.config["ipv4.dhcp.expiry"] != "" {
+			expiry = n.config["ipv4.dhcp.expiry"]
+		}
+
+		dnsmasqCmd = append(dnsmasqCmd, []string{
+			fmt.Sprintf("--listen-address=%s", addr[0]),
+			"--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)}...)
+
+		// Setup the tunnel
+		if n.config["fan.type"] == "ipip" {
+			_, err = shared.RunCommand("ip", "-4", "route", "flush", "dev", "tunl0")
+			if err != nil {
+				return err
+			}
+
+			_, err = shared.RunCommand("ip", "link", "set", "dev", "tunl0", "up")
+			if err != nil {
+				return err
+			}
+
+			// Fails if the map is already set
+			shared.RunCommand("ip", "link", "change", "dev", "tunl0", "type", "ipip", "fan-map", fmt.Sprintf("%s:%s", overlay, underlay))
+
+			_, err = shared.RunCommand("ip", "route", "add", overlay, "dev", "tunl0", "src", addr[0])
+			if err != nil {
+				return err
+			}
+		} else {
+			vxlanID := fmt.Sprintf("%d", binary.BigEndian.Uint32(overlaySubnet.IP.To4())>>8)
+
+			_, err = shared.RunCommand("ip", "link", "add", tunName, "type", "vxlan", "id", vxlanID, "dev", devName, "dstport", "0", "local", devAddr, "fan-map", fmt.Sprintf("%s:%s", overlay, underlay))
+			if err != nil {
+				return err
+			}
+
+			err = AttachInterface(n.name, tunName)
+			if err != nil {
+				return err
+			}
+
+			_, err = shared.RunCommand("ip", "link", "set", "dev", tunName, "mtu", mtu, "up")
+			if err != nil {
+				return err
+			}
+
+			_, err = shared.RunCommand("ip", "link", "set", "dev", n.name, "up")
+			if err != nil {
+				return err
+			}
+		}
+
+		// Configure NAT
+		if n.config["ipv4.nat"] == "" || shared.IsTrue(n.config["ipv4.nat"]) {
+			if n.config["ipv4.nat.order"] == "after" {
+				err = n.state.Firewall.NetworkSetupTunnelNAT(n.name, firewallConsts.LocationAppend, *overlaySubnet)
+				if err != nil {
+					return err
+				}
+			} else {
+				err = n.state.Firewall.NetworkSetupTunnelNAT(n.name, firewallConsts.LocationPrepend, *overlaySubnet)
+				if err != nil {
+					return err
+				}
+			}
+		}
+
+		// Setup clustered DNS
+		clusterAddress, err := node.ClusterAddress(n.state.Node)
+		if err != nil {
+			return err
+		}
+
+		// If clusterAddress is non-empty, this indicates the intention for this node to be
+		// part of a cluster and so we should ensure that dnsmasq and forkdns are started
+		// in cluster mode. Note: During LXD initialisation the cluster may not actually be
+		// setup yet, but we want the DNS processes to be ready for when it is.
+		if clusterAddress != "" {
+			dnsClustered = true
+		}
+
+		dnsClusteredAddress = strings.Split(fanAddress, "/")[0]
+	}
+
+	// Configure tunnels
+	for _, tunnel := range tunnels {
+		getConfig := func(key string) string {
+			return n.config[fmt.Sprintf("tunnel.%s.%s", tunnel, key)]
+		}
+
+		tunProtocol := getConfig("protocol")
+		tunLocal := getConfig("local")
+		tunRemote := getConfig("remote")
+		tunName := fmt.Sprintf("%s-%s", n.name, tunnel)
+
+		// Configure the tunnel
+		cmd := []string{"ip", "link", "add", "dev", tunName}
+		if tunProtocol == "gre" {
+			// Skip partial configs
+			if tunProtocol == "" || tunLocal == "" || tunRemote == "" {
+				continue
+			}
+
+			cmd = append(cmd, []string{"type", "gretap", "local", tunLocal, "remote", tunRemote}...)
+		} else if tunProtocol == "vxlan" {
+			tunGroup := getConfig("group")
+			tunInterface := getConfig("interface")
+
+			// Skip partial configs
+			if tunProtocol == "" {
+				continue
+			}
+
+			cmd = append(cmd, []string{"type", "vxlan"}...)
+
+			if tunLocal != "" && tunRemote != "" {
+				cmd = append(cmd, []string{"local", tunLocal, "remote", tunRemote}...)
+			} else {
+				if tunGroup == "" {
+					tunGroup = "239.0.0.1"
+				}
+
+				devName := tunInterface
+				if devName == "" {
+					_, devName, err = DefaultGatewaySubnetV4()
+					if err != nil {
+						return err
+					}
+				}
+
+				cmd = append(cmd, []string{"group", tunGroup, "dev", devName}...)
+			}
+
+			tunPort := getConfig("port")
+			if tunPort == "" {
+				tunPort = "0"
+			}
+			cmd = append(cmd, []string{"dstport", tunPort}...)
+
+			tunID := getConfig("id")
+			if tunID == "" {
+				tunID = "1"
+			}
+			cmd = append(cmd, []string{"id", tunID}...)
+
+			tunTTL := getConfig("ttl")
+			if tunTTL == "" {
+				tunTTL = "1"
+			}
+			cmd = append(cmd, []string{"ttl", tunTTL}...)
+		}
+
+		// Create the interface
+		_, err = shared.RunCommand(cmd[0], cmd[1:]...)
+		if err != nil {
+			return err
+		}
+
+		// Bridge it and bring up
+		err = AttachInterface(n.name, tunName)
+		if err != nil {
+			return err
+		}
+
+		_, err = shared.RunCommand("ip", "link", "set", "dev", tunName, "mtu", mtu, "up")
+		if err != nil {
+			return err
+		}
+
+		_, err = shared.RunCommand("ip", "link", "set", "dev", n.name, "up")
+		if err != nil {
+			return err
+		}
+	}
+
+	// Kill any existing dnsmasq and forkdns daemon for this network
+	err = dnsmasq.Kill(n.name, false)
+	if err != nil {
+		return err
+	}
+
+	err = n.killForkDNS()
+	if err != nil {
+		return err
+	}
+
+	// Configure dnsmasq
+	if n.config["bridge.mode"] == "fan" || !shared.StringInSlice(n.config["ipv4.address"], []string{"", "none"}) || !shared.StringInSlice(n.config["ipv6.address"], []string{"", "none"}) {
+		// Setup the dnsmasq domain
+		dnsDomain := n.config["dns.domain"]
+		if dnsDomain == "" {
+			dnsDomain = "lxd"
+		}
+
+		if n.config["dns.mode"] != "none" {
+			if dnsClustered {
+				dnsmasqCmd = append(dnsmasqCmd, "-s", dnsDomain)
+				dnsmasqCmd = append(dnsmasqCmd, "-S", fmt.Sprintf("/%s/%s#1053", dnsDomain, dnsClusteredAddress))
+				dnsmasqCmd = append(dnsmasqCmd, fmt.Sprintf("--rev-server=%s,%s#1053", overlaySubnet, dnsClusteredAddress))
+			} else {
+				dnsmasqCmd = append(dnsmasqCmd, []string{"-s", dnsDomain, "-S", fmt.Sprintf("/%s/", dnsDomain)}...)
+			}
+		}
+
+		// Create a config file to contain additional config (and to prevent dnsmasq from reading /etc/dnsmasq.conf)
+		err = ioutil.WriteFile(shared.VarPath("networks", n.name, "dnsmasq.raw"), []byte(fmt.Sprintf("%s\n", n.config["raw.dnsmasq"])), 0644)
+		if err != nil {
+			return err
+		}
+		dnsmasqCmd = append(dnsmasqCmd, fmt.Sprintf("--conf-file=%s", shared.VarPath("networks", n.name, "dnsmasq.raw")))
+
+		// Attempt to drop privileges
+		if n.state.OS.UnprivUser != "" {
+			dnsmasqCmd = append(dnsmasqCmd, []string{"-u", n.state.OS.UnprivUser}...)
+		}
+
+		// Create DHCP hosts directory
+		if !shared.PathExists(shared.VarPath("networks", n.name, "dnsmasq.hosts")) {
+			err = os.MkdirAll(shared.VarPath("networks", n.name, "dnsmasq.hosts"), 0755)
+			if err != nil {
+				return err
+			}
+		}
+
+		// Check for dnsmasq
+		_, err := exec.LookPath("dnsmasq")
+		if err != nil {
+			return fmt.Errorf("dnsmasq is required for LXD managed bridges")
+		}
+
+		// Update the static leases
+		err = UpdateDNSMasqStatic(n.state, n.name)
+		if err != nil {
+			return err
+		}
+
+		// Create subprocess object dnsmasq (occasionally races, try a few times)
+		p, err := subprocess.NewProcess(command, dnsmasqCmd, "", "")
+		if err != nil {
+			return fmt.Errorf("Failed to create subprocess: %s", err)
+		}
+
+		err = p.Start()
+		if err != nil {
+			return fmt.Errorf("Failed to run: %s %s: %v", command, strings.Join(dnsmasqCmd, " "), err)
+		}
+
+		err = p.Save(shared.VarPath("networks", n.name, "dnsmasq.pid"))
+		if err != nil {
+			// Kill Process if started, but could not save the file
+			err2 := p.Stop()
+			if err != nil {
+				return fmt.Errorf("Could not kill subprocess while handling saving error: %s: %s", err, err2)
+			}
+
+			return fmt.Errorf("Failed to save subprocess details: %s", err)
+		}
+
+		// Spawn DNS forwarder if needed (backgrounded to avoid deadlocks during cluster boot)
+		if dnsClustered {
+			// Create forkdns servers directory
+			if !shared.PathExists(shared.VarPath("networks", n.name, ForkdnsServersListPath)) {
+				err = os.MkdirAll(shared.VarPath("networks", n.name, ForkdnsServersListPath), 0755)
+				if err != nil {
+					return err
+				}
+			}
+
+			// Create forkdns servers.conf file if doesn't exist
+			f, err := os.OpenFile(shared.VarPath("networks", n.name, ForkdnsServersListPath+"/"+ForkdnsServersListFile), os.O_RDONLY|os.O_CREATE, 0666)
+			if err != nil {
+				return err
+			}
+			f.Close()
+
+			err = n.spawnForkDNS(dnsClusteredAddress)
+			if err != nil {
+				return err
+			}
+		}
+	} else {
+		// Clean up old dnsmasq config if exists and we are not starting dnsmasq.
+		leasesPath := shared.VarPath("networks", n.name, "dnsmasq.leases")
+		if shared.PathExists(leasesPath) {
+			err := os.Remove(leasesPath)
+			if err != nil {
+				return errors.Wrapf(err, "Failed to remove old dnsmasq leases file '%s'", leasesPath)
+			}
+		}
+
+		// And same for our PID file.
+		pidPath := shared.VarPath("networks", n.name, "dnsmasq.pid")
+		if shared.PathExists(pidPath) {
+			err := os.Remove(pidPath)
+			if err != nil {
+				return errors.Wrapf(err, "Failed to remove old dnsmasq pid file '%s'", pidPath)
+			}
+		}
+	}
+
+	return nil
+}
+
+// Stop stops the network.
+func (n *Network) Stop() error {
+	if !n.IsRunning() {
+		return fmt.Errorf("The network is already stopped")
+	}
+
+	// Destroy the bridge interface
+	if n.config["bridge.driver"] == "openvswitch" {
+		_, err := shared.RunCommand("ovs-vsctl", "del-br", n.name)
+		if err != nil {
+			return err
+		}
+	} else {
+		_, err := shared.RunCommand("ip", "link", "del", "dev", n.name)
+		if err != nil {
+			return err
+		}
+	}
+
+	// Cleanup iptables
+	if n.config["ipv4.firewall"] == "" || shared.IsTrue(n.config["ipv4.firewall"]) {
+		err := n.state.Firewall.NetworkClear(firewallConsts.FamilyIPv4, firewallConsts.TableAll, n.name)
+		if err != nil {
+			return err
+		}
+
+		err = n.state.Firewall.NetworkClear(firewallConsts.FamilyIPv4, firewallConsts.TableMangle, n.name)
+		if err != nil {
+			return err
+		}
+	}
+
+	if shared.IsTrue(n.config["ipv4.nat"]) {
+		err := n.state.Firewall.NetworkClear(firewallConsts.FamilyIPv4, firewallConsts.TableNat, n.name)
+		if err != nil {
+			return err
+		}
+	}
+
+	if n.config["ipv6.firewall"] == "" || shared.IsTrue(n.config["ipv6.firewall"]) {
+		err := n.state.Firewall.NetworkClear(firewallConsts.FamilyIPv6, firewallConsts.TableAll, n.name)
+		if err != nil {
+			return err
+		}
+	}
+
+	if shared.IsTrue(n.config["ipv6.nat"]) {
+		err := n.state.Firewall.NetworkClear(firewallConsts.FamilyIPv6, firewallConsts.TableNat, n.name)
+		if err != nil {
+			return err
+		}
+	}
+
+	// Kill any existing dnsmasq and forkdns daemon for this network
+	err := dnsmasq.Kill(n.name, false)
+	if err != nil {
+		return err
+	}
+
+	err = n.killForkDNS()
+	if err != nil {
+		return err
+	}
+
+	// Get a list of interfaces
+	ifaces, err := net.Interfaces()
+	if err != nil {
+		return err
+	}
+
+	// Cleanup any existing tunnel device
+	for _, iface := range ifaces {
+		if strings.HasPrefix(iface.Name, fmt.Sprintf("%s-", n.name)) {
+			_, err = shared.RunCommand("ip", "link", "del", "dev", iface.Name)
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	return nil
+}
+
+// Update updates the network.
+func (n *Network) Update(newNetwork api.NetworkPut, notify bool) error {
+	err := fillAuto(newNetwork.Config)
+	if err != nil {
+		return err
+	}
+	newConfig := newNetwork.Config
+
+	// Backup the current state
+	oldConfig := map[string]string{}
+	oldDescription := n.description
+	err = shared.DeepCopy(&n.config, &oldConfig)
+	if err != nil {
+		return err
+	}
+
+	// Define a function which reverts everything.  Defer this function
+	// so that it doesn't need to be explicitly called in every failing
+	// return path.  Track whether or not we want to undo the changes
+	// using a closure.
+	undoChanges := true
+	defer func() {
+		if undoChanges {
+			// Revert changes to the struct
+			n.config = oldConfig
+			n.description = oldDescription
+
+			// Update the database
+			n.state.Cluster.NetworkUpdate(n.name, n.description, n.config)
+
+			// Reset any change that was made to the bridge
+			n.setup(newConfig)
+		}
+	}()
+
+	// Diff the configurations
+	changedConfig := []string{}
+	userOnly := true
+	for key := range oldConfig {
+		if oldConfig[key] != newConfig[key] {
+			if !strings.HasPrefix(key, "user.") {
+				userOnly = false
+			}
+
+			if !shared.StringInSlice(key, changedConfig) {
+				changedConfig = append(changedConfig, key)
+			}
+		}
+	}
+
+	for key := range newConfig {
+		if oldConfig[key] != newConfig[key] {
+			if !strings.HasPrefix(key, "user.") {
+				userOnly = false
+			}
+
+			if !shared.StringInSlice(key, changedConfig) {
+				changedConfig = append(changedConfig, key)
+			}
+		}
+	}
+
+	// Skip on no change
+	if len(changedConfig) == 0 && newNetwork.Description == n.description {
+		return nil
+	}
+
+	// Update the network
+	if !userOnly {
+		if shared.StringInSlice("bridge.driver", changedConfig) && n.IsRunning() {
+			err = n.Stop()
+			if err != nil {
+				return err
+			}
+		}
+
+		if shared.StringInSlice("bridge.external_interfaces", changedConfig) && n.IsRunning() {
+			devices := []string{}
+			for _, dev := range strings.Split(newConfig["bridge.external_interfaces"], ",") {
+				dev = strings.TrimSpace(dev)
+				devices = append(devices, dev)
+			}
+
+			for _, dev := range strings.Split(oldConfig["bridge.external_interfaces"], ",") {
+				dev = strings.TrimSpace(dev)
+				if dev == "" {
+					continue
+				}
+
+				if !shared.StringInSlice(dev, devices) && shared.PathExists(fmt.Sprintf("/sys/class/net/%s", dev)) {
+					err = DetachInterface(n.name, dev)
+					if err != nil {
+						return err
+					}
+				}
+			}
+		}
+	}
+
+	// Apply changes
+	n.config = newConfig
+	n.description = newNetwork.Description
+
+	// Update the database
+	if !notify {
+		// Notify all other nodes to update the network.
+		notifier, err := cluster.NewNotifier(n.state, n.state.Endpoints.NetworkCert(), cluster.NotifyAll)
+		if err != nil {
+			return err
+		}
+
+		err = notifier(func(client lxd.InstanceServer) error {
+			return client.UpdateNetwork(n.name, newNetwork, "")
+		})
+		if err != nil {
+			return err
+		}
+
+		// Update the database.
+		err = n.state.Cluster.NetworkUpdate(n.name, n.description, n.config)
+		if err != nil {
+			return err
+		}
+	}
+
+	// Restart the network
+	if !userOnly {
+		err = n.setup(oldConfig)
+		if err != nil {
+			return err
+		}
+	}
+
+	// Success, update the closure to mark that the changes should be kept.
+	undoChanges = false
+
+	return nil
+}
+
+func (n *Network) spawnForkDNS(listenAddress string) error {
+	// Setup the dnsmasq domain
+	dnsDomain := n.config["dns.domain"]
+	if dnsDomain == "" {
+		dnsDomain = "lxd"
+	}
+
+	// Spawn the daemon using subprocess
+	command := n.state.OS.ExecPath
+	forkdnsargs := []string{"forkdns",
+		fmt.Sprintf("%s:1053", listenAddress),
+		dnsDomain,
+		n.name}
+
+	logPath := shared.LogPath(fmt.Sprintf("forkdns.%s.log", n.name))
+
+	p, err := subprocess.NewProcess(command, forkdnsargs, logPath, logPath)
+	if err != nil {
+		return fmt.Errorf("Failed to create subprocess: %s", err)
+	}
+
+	err = p.Start()
+	if err != nil {
+		return fmt.Errorf("Failed to run: %s %s: %v", command, strings.Join(forkdnsargs, " "), err)
+	}
+
+	err = p.Save(shared.VarPath("networks", n.name, "forkdns.pid"))
+	if err != nil {
+		// Kill Process if started, but could not save the file
+		err2 := p.Stop()
+		if err != nil {
+			return fmt.Errorf("Could not kill subprocess while handling saving error: %s: %s", err, err2)
+		}
+
+		return fmt.Errorf("Failed to save subprocess details: %s", err)
+	}
+
+	return nil
+}
+
+// RefreshForkdnsServerAddresses retrieves the IPv4 address of each cluster node (excluding ourselves)
+// for this network. It then updates the forkdns server list file if there are changes.
+func (n *Network) RefreshForkdnsServerAddresses(heartbeatData *cluster.APIHeartbeat) error {
+	addresses := []string{}
+	localAddress, err := node.HTTPSAddress(n.state.Node)
+	if err != nil {
+		return err
+	}
+
+	logger.Infof("Refreshing forkdns peers for %v", n.name)
+
+	cert := n.state.Endpoints.NetworkCert()
+	for _, node := range heartbeatData.Members {
+		if node.Address == localAddress {
+			// No need to query ourselves.
+			continue
+		}
+
+		client, err := cluster.Connect(node.Address, cert, true)
+		if err != nil {
+			return err
+		}
+
+		state, err := client.GetNetworkState(n.name)
+		if err != nil {
+			return err
+		}
+
+		for _, addr := range state.Addresses {
+			// Only get IPv4 addresses of nodes on network.
+			if addr.Family != "inet" || addr.Scope != "global" {
+				continue
+			}
+
+			addresses = append(addresses, addr.Address)
+			break
+		}
+	}
+
+	// Compare current stored list to retrieved list and see if we need to update.
+	curList, err := ForkdnsServersList(n.name)
+	if err != nil {
+		// Only warn here, but continue on to regenerate the servers list from cluster info.
+		logger.Warnf("Failed to load existing forkdns server list: %v", err)
+	}
+
+	// If current list is same as cluster list, nothing to do.
+	if err == nil && reflect.DeepEqual(curList, addresses) {
+		return nil
+	}
+
+	err = n.updateForkdnsServersFile(addresses)
+	if err != nil {
+		return err
+	}
+
+	logger.Infof("Updated forkdns server list for '%s': %v", n.name, addresses)
+	return nil
+}
+
+func (n *Network) getTunnels() []string {
+	tunnels := []string{}
+
+	for k := range n.config {
+		if !strings.HasPrefix(k, "tunnel.") {
+			continue
+		}
+
+		fields := strings.Split(k, ".")
+		if !shared.StringInSlice(fields[1], tunnels) {
+			tunnels = append(tunnels, fields[1])
+		}
+	}
+
+	return tunnels
+}
+
+// bootRoutesV4 returns a list of IPv4 boot routes on the network's device.
+func (n *Network) bootRoutesV4() ([]string, error) {
+	routes := []string{}
+	cmd := exec.Command("ip", "-4", "route", "show", "dev", n.name, "proto", "boot")
+	ipOut, err := cmd.StdoutPipe()
+	if err != nil {
+		return routes, err
+	}
+	cmd.Start()
+	scanner := bufio.NewScanner(ipOut)
+	for scanner.Scan() {
+		route := strings.Replace(scanner.Text(), "linkdown", "", -1)
+		routes = append(routes, route)
+	}
+	cmd.Wait()
+	return routes, nil
+}
+
+// bootRoutesV6 returns a list of IPv6 boot routes on the network's device.
+func (n *Network) bootRoutesV6() ([]string, error) {
+	routes := []string{}
+	cmd := exec.Command("ip", "-6", "route", "show", "dev", n.name, "proto", "boot")
+	ipOut, err := cmd.StdoutPipe()
+	if err != nil {
+		return routes, err
+	}
+	cmd.Start()
+	scanner := bufio.NewScanner(ipOut)
+	for scanner.Scan() {
+		route := strings.Replace(scanner.Text(), "linkdown", "", -1)
+		routes = append(routes, route)
+	}
+	cmd.Wait()
+	return routes, nil
+}
+
+// applyBootRoutesV4 applies a list of IPv4 boot routes to the network's device.
+func (n *Network) applyBootRoutesV4(routes []string) error {
+	for _, route := range routes {
+		cmd := []string{"-4", "route", "replace", "dev", n.name, "proto", "boot"}
+		cmd = append(cmd, strings.Fields(route)...)
+		_, err := shared.RunCommand("ip", cmd...)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+// applyBootRoutesV6 applies a list of IPv6 boot routes to the network's device.
+func (n *Network) applyBootRoutesV6(routes []string) error {
+	for _, route := range routes {
+		cmd := []string{"-6", "route", "replace", "dev", n.name, "proto", "boot"}
+		cmd = append(cmd, strings.Fields(route)...)
+		_, err := shared.RunCommand("ip", cmd...)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (n *Network) fanAddress(underlay *net.IPNet, overlay *net.IPNet) (string, string, string, error) {
+	// Sanity checks
+	underlaySize, _ := underlay.Mask.Size()
+	if underlaySize != 16 && underlaySize != 24 {
+		return "", "", "", fmt.Errorf("Only /16 or /24 underlays are supported at this time")
+	}
+
+	overlaySize, _ := overlay.Mask.Size()
+	if overlaySize != 8 && overlaySize != 16 {
+		return "", "", "", fmt.Errorf("Only /8 or /16 overlays are supported at this time")
+	}
+
+	if overlaySize+(32-underlaySize)+8 > 32 {
+		return "", "", "", fmt.Errorf("Underlay or overlay networks too large to accommodate the FAN")
+	}
+
+	// Get the IP
+	ip, dev, err := n.addressForSubnet(underlay)
+	if err != nil {
+		return "", "", "", err
+	}
+	ipStr := ip.String()
+
+	// Force into IPv4 format
+	ipBytes := ip.To4()
+	if ipBytes == nil {
+		return "", "", "", fmt.Errorf("Invalid IPv4: %s", ip)
+	}
+
+	// Compute the IP
+	ipBytes[0] = overlay.IP[0]
+	if overlaySize == 16 {
+		ipBytes[1] = overlay.IP[1]
+		ipBytes[2] = ipBytes[3]
+	} else if underlaySize == 24 {
+		ipBytes[1] = ipBytes[3]
+		ipBytes[2] = 0
+	} else if underlaySize == 16 {
+		ipBytes[1] = ipBytes[2]
+		ipBytes[2] = ipBytes[3]
+	}
+
+	ipBytes[3] = 1
+
+	return fmt.Sprintf("%s/%d", ipBytes.String(), overlaySize), dev, ipStr, err
+}
+
+func (n *Network) addressForSubnet(subnet *net.IPNet) (net.IP, string, error) {
+	ifaces, err := net.Interfaces()
+	if err != nil {
+		return net.IP{}, "", err
+	}
+
+	for _, iface := range ifaces {
+		addrs, err := iface.Addrs()
+		if err != nil {
+			continue
+		}
+
+		for _, addr := range addrs {
+			ip, _, err := net.ParseCIDR(addr.String())
+			if err != nil {
+				continue
+			}
+
+			if subnet.Contains(ip) {
+				return ip, iface.Name, nil
+			}
+		}
+	}
+
+	return net.IP{}, "", fmt.Errorf("No address found in subnet")
+}
+
+func (n *Network) killForkDNS() error {
+	// Check if we have a running forkdns at all
+	pidPath := shared.VarPath("networks", n.name, "forkdns.pid")
+
+	// If the pid file doesn't exist, there is no process to kill.
+	if !shared.PathExists(pidPath) {
+		return nil
+	}
+
+	p, err := subprocess.ImportProcess(pidPath)
+	if err != nil {
+		return fmt.Errorf("Could not read pid file: %s", err)
+	}
+
+	err = p.Stop()
+	if err != nil && err != subprocess.ErrNotRunning {
+		return fmt.Errorf("Unable to kill dnsmasq: %s", err)
+	}
+
+	return nil
+}
+
+// updateForkdnsServersFile takes a list of node addresses and writes them atomically to
+// the forkdns.servers file ready for forkdns to notice and re-apply its config.
+func (n *Network) updateForkdnsServersFile(addresses []string) error {
+	// We don't want to race with ourselves here
+	forkdnsServersLock.Lock()
+	defer forkdnsServersLock.Unlock()
+
+	permName := shared.VarPath("networks", n.name, ForkdnsServersListPath+"/"+ForkdnsServersListFile)
+	tmpName := permName + ".tmp"
+
+	// Open tmp file and truncate
+	tmpFile, err := os.Create(tmpName)
+	if err != nil {
+		return err
+	}
+	defer tmpFile.Close()
+
+	for _, address := range addresses {
+		_, err := tmpFile.WriteString(address + "\n")
+		if err != nil {
+			return err
+		}
+	}
+
+	tmpFile.Close()
+
+	// Atomically rename finished file into permanent location so forkdns can pick it up.
+	err = os.Rename(tmpName, permName)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// HasDHCPv4 indicates whether the network has DHCPv4 enabled.
+func (n *Network) HasDHCPv4() bool {
+	if n.config["ipv4.dhcp"] == "" || shared.IsTrue(n.config["ipv4.dhcp"]) {
+		return true
+	}
+
+	return false
+}
+
+// HasDHCPv6 indicates whether the network has DHCPv6 enabled.
+func (n *Network) HasDHCPv6() bool {
+	if n.config["ipv6.dhcp"] == "" || shared.IsTrue(n.config["ipv6.dhcp"]) {
+		return true
+	}
+
+	return false
+}

From 637f93d5e7979ede443bf5e5c854fb99b0c9872b Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jan 2020 16:24:56 +0000
Subject: [PATCH 07/30] lxd/network/network/utils: Moves network utils from
 main pkg

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

diff --git a/lxd/network/network_utils.go b/lxd/network/network_utils.go
new file mode 100644
index 0000000000..679e6ab94b
--- /dev/null
+++ b/lxd/network/network_utils.go
@@ -0,0 +1,677 @@
+package network
+
+import (
+	"bufio"
+	"encoding/binary"
+	"encoding/hex"
+	"fmt"
+	"io/ioutil"
+	"math"
+	"math/big"
+	"math/rand"
+	"net"
+	"os"
+	"strconv"
+	"strings"
+	"sync"
+	"time"
+
+	"github.com/lxc/lxd/lxd/dnsmasq"
+	"github.com/lxc/lxd/lxd/instance"
+	"github.com/lxc/lxd/lxd/instance/instancetype"
+	"github.com/lxc/lxd/lxd/project"
+	"github.com/lxc/lxd/lxd/state"
+	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
+	"github.com/lxc/lxd/shared/logger"
+)
+
+// IsInUse indicates if network is reference by any instance's NIC devices.
+// Checks if the device's parent or network properties match the network name.
+func IsInUse(c instance.Instance, networkName string) bool {
+	for _, d := range c.ExpandedDevices() {
+		if d["type"] != "nic" {
+			continue
+		}
+
+		if !shared.StringInSlice(d["nictype"], []string{"bridged", "macvlan", "ipvlan", "physical", "sriov"}) {
+			continue
+		}
+
+		if d["parent"] == "" {
+			continue
+		}
+
+		if GetHostDevice(d["parent"], d["vlan"]) == networkName || d["network"] == networkName {
+			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
+}
+
+// AttachInterface attaches an interface to a bridge.
+func AttachInterface(netName string, devName string) error {
+	if shared.PathExists(fmt.Sprintf("/sys/class/net/%s/bridge", netName)) {
+		_, err := shared.RunCommand("ip", "link", "set", "dev", devName, "master", netName)
+		if err != nil {
+			return err
+		}
+	} else {
+		_, err := shared.RunCommand("ovs-vsctl", "port-to-br", devName)
+		if err != nil {
+			_, err := shared.RunCommand("ovs-vsctl", "add-port", netName, devName)
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	return nil
+}
+
+// DetachInterface detaches an interface from a bridge.
+func DetachInterface(netName string, devName string) error {
+	if shared.PathExists(fmt.Sprintf("/sys/class/net/%s/bridge", netName)) {
+		_, err := shared.RunCommand("ip", "link", "set", "dev", devName, "nomaster")
+		if err != nil {
+			return err
+		}
+	} else {
+		_, err := shared.RunCommand("ovs-vsctl", "port-to-br", devName)
+		if err == nil {
+			_, err := shared.RunCommand("ovs-vsctl", "del-port", netName, devName)
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	return nil
+}
+
+// GetDevMTU retrieves the current MTU setting for a named network device.
+func GetDevMTU(devName string) (uint64, error) {
+	content, err := ioutil.ReadFile(fmt.Sprintf("/sys/class/net/%s/mtu", devName))
+	if err != nil {
+		return 0, err
+	}
+
+	// Parse value
+	mtu, err := strconv.ParseUint(strings.TrimSpace(string(content)), 10, 32)
+	if err != nil {
+		return 0, err
+	}
+
+	return mtu, nil
+}
+
+// DefaultGatewaySubnetV4 returns subnet of default gateway interface.
+func DefaultGatewaySubnetV4() (*net.IPNet, string, error) {
+	file, err := os.Open("/proc/net/route")
+	if err != nil {
+		return nil, "", err
+	}
+	defer file.Close()
+
+	ifaceName := ""
+
+	scanner := bufio.NewReader(file)
+	for {
+		line, _, err := scanner.ReadLine()
+		if err != nil {
+			break
+		}
+
+		fields := strings.Fields(string(line))
+
+		if fields[1] == "00000000" && fields[7] == "00000000" {
+			ifaceName = fields[0]
+			break
+		}
+	}
+
+	if ifaceName == "" {
+		return nil, "", fmt.Errorf("No default gateway for IPv4")
+	}
+
+	iface, err := net.InterfaceByName(ifaceName)
+	if err != nil {
+		return nil, "", err
+	}
+
+	addrs, err := iface.Addrs()
+	if err != nil {
+		return nil, "", err
+	}
+
+	var subnet *net.IPNet
+
+	for _, addr := range addrs {
+		addrIP, addrNet, err := net.ParseCIDR(addr.String())
+		if err != nil {
+			return nil, "", err
+		}
+
+		if addrIP.To4() == nil {
+			continue
+		}
+
+		if subnet != nil {
+			return nil, "", fmt.Errorf("More than one IPv4 subnet on default interface")
+		}
+
+		subnet = addrNet
+	}
+
+	if subnet == nil {
+		return nil, "", fmt.Errorf("No IPv4 subnet on default interface")
+	}
+
+	return subnet, ifaceName, nil
+}
+
+// UpdateDNSMasqStatic rebuilds the DNSMasq static allocations.
+func UpdateDNSMasqStatic(s *state.State, networkName string) error {
+	// We don't want to race with ourselves here
+	dnsmasq.ConfigMutex.Lock()
+	defer dnsmasq.ConfigMutex.Unlock()
+
+	// Get all the networks
+	var networks []string
+	if networkName == "" {
+		var err error
+		networks, err = s.Cluster.Networks()
+		if err != nil {
+			return err
+		}
+	} else {
+		networks = []string{networkName}
+	}
+
+	// Get all the instances
+	insts, err := instance.LoadNodeAll(s, instancetype.Any)
+	if err != nil {
+		return err
+	}
+
+	// Build a list of dhcp host entries
+	entries := map[string][][]string{}
+	for _, inst := range insts {
+		// Go through all its devices (including profiles
+		for k, d := range inst.ExpandedDevices() {
+			// Skip uninteresting entries
+			if d["type"] != "nic" || d["nictype"] != "bridged" || !shared.StringInSlice(d["parent"], networks) {
+				continue
+			}
+
+			// Fill in the hwaddr from volatile
+			d, err = inst.FillNetworkDevice(k, d)
+			if err != nil {
+				continue
+			}
+
+			// Add the new host entries
+			_, ok := entries[d["parent"]]
+			if !ok {
+				entries[d["parent"]] = [][]string{}
+			}
+
+			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.Name())
+				if err != nil && !os.IsNotExist(err) {
+					return err
+				}
+
+				if d["ipv4.address"] == "" && curIPv4.IP != nil {
+					d["ipv4.address"] = curIPv4.IP.String()
+				}
+
+				if d["ipv6.address"] == "" && curIPv6.IP != nil {
+					d["ipv6.address"] = curIPv6.IP.String()
+				}
+			}
+
+			entries[d["parent"]] = append(entries[d["parent"]], []string{d["hwaddr"], inst.Project(), inst.Name(), d["ipv4.address"], d["ipv6.address"]})
+		}
+	}
+
+	// Update the host files
+	for _, network := range networks {
+		entries, _ := entries[network]
+
+		// Skip networks we don't manage (or don't have DHCP enabled)
+		if !shared.PathExists(shared.VarPath("networks", network, "dnsmasq.pid")) {
+			continue
+		}
+
+		n, err := LoadByName(s, network)
+		if err != nil {
+			return err
+		}
+		config := n.Config()
+
+		// Wipe everything clean
+		files, err := ioutil.ReadDir(shared.VarPath("networks", network, "dnsmasq.hosts"))
+		if err != nil {
+			return err
+		}
+
+		for _, entry := range files {
+			err = os.Remove(shared.VarPath("networks", network, "dnsmasq.hosts", entry.Name()))
+			if err != nil {
+				return err
+			}
+		}
+
+		// Apply the changes
+		for entryIdx, entry := range entries {
+			hwaddr := entry[0]
+			projectName := entry[1]
+			cName := entry[2]
+			ipv4Address := entry[3]
+			ipv6Address := entry[4]
+			line := hwaddr
+
+			// Look for duplicates
+			duplicate := false
+			for iIdx, i := range entries {
+				if project.Prefix(entry[1], entry[2]) == project.Prefix(i[1], i[2]) {
+					// Skip ourselves
+					continue
+				}
+
+				if entry[0] == i[0] {
+					// Find broken configurations
+					logger.Errorf("Duplicate MAC detected: %s and %s", project.Prefix(entry[1], entry[2]), project.Prefix(i[1], i[2]))
+				}
+
+				if i[3] == "" && i[4] == "" {
+					// Skip unconfigured
+					continue
+				}
+
+				if entry[3] == i[3] && entry[4] == i[4] {
+					// Find identical containers (copies with static configuration)
+					if entryIdx > iIdx {
+						duplicate = true
+					} else {
+						line = fmt.Sprintf("%s,%s", line, i[0])
+						logger.Debugf("Found containers with duplicate IPv4/IPv6: %s and %s", project.Prefix(entry[1], entry[2]), project.Prefix(i[1], i[2]))
+					}
+				}
+			}
+
+			if duplicate {
+				continue
+			}
+
+			// Generate the dhcp-host line
+			err := dnsmasq.UpdateStaticEntry(network, projectName, cName, config, hwaddr, ipv4Address, ipv6Address)
+			if err != nil {
+				return err
+			}
+		}
+
+		// Signal dnsmasq
+		err = dnsmasq.Kill(network, true)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+// ForkdnsServersList reads the server list file and returns the list as a slice.
+func ForkdnsServersList(networkName string) ([]string, error) {
+	servers := []string{}
+	file, err := os.Open(shared.VarPath("networks", networkName, ForkdnsServersListPath, "/", ForkdnsServersListFile))
+	if err != nil {
+		return servers, err
+	}
+	defer file.Close()
+
+	scanner := bufio.NewScanner(file)
+	for scanner.Scan() {
+		fields := strings.Fields(scanner.Text())
+		if len(fields) > 0 {
+			servers = append(servers, fields[0])
+		}
+	}
+	if err := scanner.Err(); err != nil {
+		return servers, err
+	}
+
+	return servers, nil
+}
+
+// FillConfig populates the supplied api.NetworkPost with automatically populated values.
+func FillConfig(req *api.NetworksPost) error {
+	// Set some default values where needed
+	if req.Config["bridge.mode"] == "fan" {
+		if req.Config["fan.underlay_subnet"] == "" {
+			req.Config["fan.underlay_subnet"] = "auto"
+		}
+	} else {
+		if req.Config["ipv4.address"] == "" {
+			req.Config["ipv4.address"] = "auto"
+		}
+		if req.Config["ipv4.address"] == "auto" && req.Config["ipv4.nat"] == "" {
+			req.Config["ipv4.nat"] = "true"
+		}
+
+		if req.Config["ipv6.address"] == "" {
+			content, err := ioutil.ReadFile("/proc/sys/net/ipv6/conf/default/disable_ipv6")
+			if err == nil && string(content) == "0\n" {
+				req.Config["ipv6.address"] = "auto"
+			}
+		}
+		if req.Config["ipv6.address"] == "auto" && req.Config["ipv6.nat"] == "" {
+			req.Config["ipv6.nat"] = "true"
+		}
+	}
+
+	// Replace "auto" by actual values
+	err := fillAuto(req.Config)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func fillAuto(config map[string]string) error {
+	if config["ipv4.address"] == "auto" {
+		subnet, err := randomSubnetV4()
+		if err != nil {
+			return err
+		}
+
+		config["ipv4.address"] = subnet
+	}
+
+	if config["ipv6.address"] == "auto" {
+		subnet, err := randomSubnetV6()
+		if err != nil {
+			return err
+		}
+
+		config["ipv6.address"] = subnet
+	}
+
+	if config["fan.underlay_subnet"] == "auto" {
+		subnet, _, err := DefaultGatewaySubnetV4()
+		if err != nil {
+			return err
+		}
+
+		config["fan.underlay_subnet"] = subnet.String()
+	}
+
+	return nil
+}
+
+func randomSubnetV4() (string, error) {
+	for i := 0; i < 100; i++ {
+		cidr := fmt.Sprintf("10.%d.%d.1/24", rand.Intn(255), rand.Intn(255))
+		_, subnet, err := net.ParseCIDR(cidr)
+		if err != nil {
+			continue
+		}
+
+		if inRoutingTable(subnet) {
+			continue
+		}
+
+		if pingSubnet(subnet) {
+			continue
+		}
+
+		return cidr, nil
+	}
+
+	return "", fmt.Errorf("Failed to automatically find an unused IPv4 subnet, manual configuration required")
+}
+
+func randomSubnetV6() (string, error) {
+	for i := 0; i < 100; i++ {
+		cidr := fmt.Sprintf("fd42:%x:%x:%x::1/64", rand.Intn(65535), rand.Intn(65535), rand.Intn(65535))
+		_, subnet, err := net.ParseCIDR(cidr)
+		if err != nil {
+			continue
+		}
+
+		if inRoutingTable(subnet) {
+			continue
+		}
+
+		if pingSubnet(subnet) {
+			continue
+		}
+
+		return cidr, nil
+	}
+
+	return "", fmt.Errorf("Failed to automatically find an unused IPv6 subnet, manual configuration required")
+}
+
+func inRoutingTable(subnet *net.IPNet) bool {
+	filename := "route"
+	if subnet.IP.To4() == nil {
+		filename = "ipv6_route"
+	}
+
+	file, err := os.Open(fmt.Sprintf("/proc/net/%s", filename))
+	if err != nil {
+		return false
+	}
+	defer file.Close()
+
+	scanner := bufio.NewReader(file)
+	for {
+		line, _, err := scanner.ReadLine()
+		if err != nil {
+			break
+		}
+
+		fields := strings.Fields(string(line))
+
+		// Get the IP
+		var ip net.IP
+		if filename == "ipv6_route" {
+			ip, err = hex.DecodeString(fields[0])
+			if err != nil {
+				continue
+			}
+		} else {
+			bytes, err := hex.DecodeString(fields[1])
+			if err != nil {
+				continue
+			}
+
+			ip = net.IPv4(bytes[3], bytes[2], bytes[1], bytes[0])
+		}
+
+		// Get the mask
+		var mask net.IPMask
+		if filename == "ipv6_route" {
+			size, err := strconv.ParseInt(fmt.Sprintf("0x%s", fields[1]), 0, 64)
+			if err != nil {
+				continue
+			}
+
+			mask = net.CIDRMask(int(size), 128)
+		} else {
+			bytes, err := hex.DecodeString(fields[7])
+			if err != nil {
+				continue
+			}
+
+			mask = net.IPv4Mask(bytes[3], bytes[2], bytes[1], bytes[0])
+		}
+
+		// Generate a new network
+		lineNet := net.IPNet{IP: ip, Mask: mask}
+
+		// Ignore default gateway
+		if lineNet.IP.Equal(net.ParseIP("::")) {
+			continue
+		}
+
+		if lineNet.IP.Equal(net.ParseIP("0.0.0.0")) {
+			continue
+		}
+
+		// Check if we have a route to our new subnet
+		if lineNet.Contains(subnet.IP) {
+			return true
+		}
+	}
+
+	return false
+}
+
+func pingSubnet(subnet *net.IPNet) bool {
+	var fail bool
+	var failLock sync.Mutex
+	var wgChecks sync.WaitGroup
+
+	ping := func(ip net.IP) {
+		defer wgChecks.Done()
+
+		cmd := "ping"
+		if ip.To4() == nil {
+			cmd = "ping6"
+		}
+
+		_, err := shared.RunCommand(cmd, "-n", "-q", ip.String(), "-c", "1", "-W", "1")
+		if err != nil {
+			// Remote didn't answer
+			return
+		}
+
+		// Remote answered
+		failLock.Lock()
+		fail = true
+		failLock.Unlock()
+	}
+
+	poke := func(ip net.IP) {
+		defer wgChecks.Done()
+
+		addr := fmt.Sprintf("%s:22", ip.String())
+		if ip.To4() == nil {
+			addr = fmt.Sprintf("[%s]:22", ip.String())
+		}
+
+		_, err := net.DialTimeout("tcp", addr, time.Second)
+		if err == nil {
+			// Remote answered
+			failLock.Lock()
+			fail = true
+			failLock.Unlock()
+			return
+		}
+	}
+
+	// Ping first IP
+	wgChecks.Add(1)
+	go ping(GetIP(subnet, 1))
+
+	// Poke port on first IP
+	wgChecks.Add(1)
+	go poke(GetIP(subnet, 1))
+
+	// Ping check
+	if subnet.IP.To4() != nil {
+		// Ping last IP
+		wgChecks.Add(1)
+		go ping(GetIP(subnet, -2))
+
+		// Poke port on last IP
+		wgChecks.Add(1)
+		go poke(GetIP(subnet, -2))
+	}
+
+	wgChecks.Wait()
+
+	return fail
+}
+
+// GetHostDevice returns the interface name to use for a combination of parent device name and VLAN ID.
+// If no vlan ID supplied, parent name is returned unmodified. If non-empty VLAN ID is supplied then it will look
+// for an existing VLAN device and return that, otherwise it will return the default "parent.vlan" format as name.
+func GetHostDevice(parent string, vlan string) string {
+	// If no VLAN, just use the raw device
+	if vlan == "" {
+		return parent
+	}
+
+	// If no VLANs are configured, use the default pattern
+	defaultVlan := fmt.Sprintf("%s.%s", parent, vlan)
+	if !shared.PathExists("/proc/net/vlan/config") {
+		return defaultVlan
+	}
+
+	// Look for an existing VLAN
+	f, err := os.Open("/proc/net/vlan/config")
+	if err != nil {
+		return defaultVlan
+	}
+	defer f.Close()
+
+	scanner := bufio.NewScanner(f)
+	for scanner.Scan() {
+		// Only grab the lines we're interested in
+		s := strings.Split(scanner.Text(), "|")
+		if len(s) != 3 {
+			continue
+		}
+
+		vlanIface := strings.TrimSpace(s[0])
+		vlanID := strings.TrimSpace(s[1])
+		vlanParent := strings.TrimSpace(s[2])
+
+		if vlanParent == parent && vlanID == vlan {
+			return vlanIface
+		}
+	}
+
+	// Return the default pattern
+	return defaultVlan
+}

From 6ed3aa5ccd55698bc03b773bee84de18ad7c6a30 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jan 2020 16:25:36 +0000
Subject: [PATCH 08/30] lxd/instance/instance/utils: Removes
 NetworkUpdateStatic function link

This will be moved to its own network package.

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

diff --git a/lxd/instance/instance_utils.go b/lxd/instance/instance_utils.go
index b418f90dac..e83e412e45 100644
--- a/lxd/instance/instance_utils.go
+++ b/lxd/instance/instance_utils.go
@@ -45,10 +45,6 @@ var Create func(s *state.State, args db.InstanceArgs) (Instance, error)
 // network related functions into their own package at this time.
 var NetworkGetLeaseAddresses func(s *state.State, network string, hwaddr string) ([]api.InstanceStateNetworkAddress, error)
 
-// NetworkUpdateStatic is linked to main.networkUpdateStatic to limit scope of moving
-// network related functions into their own package at this time.
-var NetworkUpdateStatic func(s *state.State, network string) error
-
 // CompareSnapshots returns a list of snapshots to sync to the target and a list of
 // snapshots to remove from the target. A snapshot will be marked as "to sync" if it either doesn't
 // exist in the target or its creation date is different to the source. A snapshot will be marked

From 1ec4503eead1d132fd3ee1cc7e233d8662af9c82 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jan 2020 16:26:01 +0000
Subject: [PATCH 09/30] lxd/instance/instance/utils: Adds more instance load
 functions

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

diff --git a/lxd/instance/instance_utils.go b/lxd/instance/instance_utils.go
index e83e412e45..bf69bb5baf 100644
--- a/lxd/instance/instance_utils.go
+++ b/lxd/instance/instance_utils.go
@@ -513,6 +513,75 @@ func LoadAllInternal(s *state.State, dbInstances []db.Instance) ([]Instance, err
 	return instances, nil
 }
 
+// LoadByProject loads all instances in a project.
+func LoadByProject(s *state.State, project string) ([]Instance, error) {
+	// Get all the instances.
+	var cts []db.Instance
+	err := s.Cluster.Transaction(func(tx *db.ClusterTx) error {
+		filter := db.InstanceFilter{
+			Project: project,
+			Type:    instancetype.Any,
+		}
+		var err error
+		cts, err = tx.InstanceList(filter)
+		if err != nil {
+			return err
+		}
+
+		return nil
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	return LoadAllInternal(s, cts)
+}
+
+// LoadFromAllProjects loads all instances across all projects.
+func LoadFromAllProjects(s *state.State) ([]Instance, error) {
+	var projects []string
+
+	err := s.Cluster.Transaction(func(tx *db.ClusterTx) error {
+		var err error
+		projects, err = tx.ProjectNames()
+		return err
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	instances := []Instance{}
+	for _, project := range projects {
+		projectInstances, err := LoadByProject(s, project)
+		if err != nil {
+			return nil, errors.Wrapf(nil, "Load instances in project %s", project)
+		}
+		instances = append(instances, projectInstances...)
+	}
+
+	return instances, nil
+}
+
+// LoadNodeAll loads all instances of this nodes.
+func LoadNodeAll(s *state.State, instanceType instancetype.Type) ([]Instance, error) {
+	// Get all the container arguments
+	var insts []db.Instance
+	err := s.Cluster.Transaction(func(tx *db.ClusterTx) error {
+		var err error
+		insts, err = tx.ContainerNodeProjectList("", instanceType)
+		if err != nil {
+			return err
+		}
+
+		return nil
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	return LoadAllInternal(s, insts)
+}
+
 // WriteBackupFile writes instance's config to a file. Deprecated, use inst.UpdateBackupFile().
 func WriteBackupFile(state *state.State, inst Instance) error {
 	// We only write backup files out for actual instances.

From 37c2271a5b84ba3be0cbc3d738508d5d7bb9304d Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jan 2020 16:27:41 +0000
Subject: [PATCH 10/30] lxd/container: Removes instance load functions moved to
 instance pkg

Updates usage.

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

diff --git a/lxd/container.go b/lxd/container.go
index 5283d13ec3..f0d4b8d549 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -19,7 +19,6 @@ import (
 	"github.com/lxc/lxd/lxd/backup"
 	"github.com/lxc/lxd/lxd/cluster"
 	"github.com/lxc/lxd/lxd/db"
-	"github.com/lxc/lxd/lxd/device"
 	deviceConfig "github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/instance/instancetype"
@@ -37,20 +36,6 @@ import (
 	"github.com/lxc/lxd/shared/units"
 )
 
-func init() {
-	// Expose instanceLoadNodeAll to the device package converting the response to a slice of Instances.
-	// This is because container types are defined in the main package and are not importable.
-	device.InstanceLoadNodeAll = func(s *state.State) ([]instance.Instance, error) {
-		return instanceLoadNodeAll(s, instancetype.Any)
-	}
-
-	// Expose instance.LoadByProjectAndName to the device package converting the response to an Instance.
-	// This is because container types are defined in the main package and are not importable.
-	device.InstanceLoadByProjectAndName = func(s *state.State, project, name string) (instance.Instance, error) {
-		return instance.LoadByProjectAndName(s, project, name)
-	}
-}
-
 // Helper functions
 
 // instanceCreateAsEmpty creates an empty instance.
@@ -927,77 +912,9 @@ func instanceConfigureInternal(state *state.State, c instance.Instance) error {
 	return nil
 }
 
-func instanceLoadByProject(s *state.State, project string) ([]instance.Instance, error) {
-	// Get all the containers
-	var cts []db.Instance
-	err := s.Cluster.Transaction(func(tx *db.ClusterTx) error {
-		filter := db.InstanceFilter{
-			Project: project,
-			Type:    instancetype.Any,
-		}
-		var err error
-		cts, err = tx.InstanceList(filter)
-		if err != nil {
-			return err
-		}
-
-		return nil
-	})
-	if err != nil {
-		return nil, err
-	}
-
-	return instance.LoadAllInternal(s, cts)
-}
-
-// Load all instances across all projects.
-func instanceLoadFromAllProjects(s *state.State) ([]instance.Instance, error) {
-	var projects []string
-
-	err := s.Cluster.Transaction(func(tx *db.ClusterTx) error {
-		var err error
-		projects, err = tx.ProjectNames()
-		return err
-	})
-	if err != nil {
-		return nil, err
-	}
-
-	instances := []instance.Instance{}
-	for _, project := range projects {
-		projectInstances, err := instanceLoadByProject(s, project)
-		if err != nil {
-			return nil, errors.Wrapf(nil, "Load instances in project %s", project)
-		}
-		instances = append(instances, projectInstances...)
-	}
-
-	return instances, nil
-}
-
 // Legacy interface.
 func instanceLoadAll(s *state.State) ([]instance.Instance, error) {
-	return instanceLoadByProject(s, "default")
-}
-
-// Load all instances of this nodes.
-func instanceLoadNodeAll(s *state.State, instanceType instancetype.Type) ([]instance.Instance, error) {
-	// Get all the container arguments
-	var insts []db.Instance
-	err := s.Cluster.Transaction(func(tx *db.ClusterTx) error {
-		var err error
-		insts, err = tx.ContainerNodeProjectList("", instanceType)
-		if err != nil {
-			return err
-		}
-
-		return nil
-	})
-	if err != nil {
-		return nil, err
-	}
-
-	return instance.LoadAllInternal(s, insts)
+	return instance.LoadByProject(s, "default")
 }
 
 // Load all instances of this nodes under the given project.
@@ -1023,7 +940,7 @@ func instanceLoadNodeProjectAll(s *state.State, project string, instanceType ins
 func autoCreateContainerSnapshotsTask(d *Daemon) (task.Func, task.Schedule) {
 	f := func(ctx context.Context) {
 		// Load all local instances
-		allContainers, err := instanceLoadNodeAll(d.State(), instancetype.Any)
+		allContainers, err := instance.LoadNodeAll(d.State(), instancetype.Any)
 		if err != nil {
 			logger.Error("Failed to load containers for scheduled snapshots", log.Ctx{"err": err})
 			return
@@ -1166,7 +1083,7 @@ func autoCreateContainerSnapshots(ctx context.Context, d *Daemon, instances []in
 func pruneExpiredContainerSnapshotsTask(d *Daemon) (task.Func, task.Schedule) {
 	f := func(ctx context.Context) {
 		// Load all local instances
-		allInstances, err := instanceLoadNodeAll(d.State(), instancetype.Any)
+		allInstances, err := instance.LoadNodeAll(d.State(), instancetype.Any)
 		if err != nil {
 			logger.Error("Failed to load instances for snapshot expiry", log.Ctx{"err": err})
 			return

From 850c9f6d68fb1c4501e2b4cd48bec1f2691205e4 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jan 2020 16:28:24 +0000
Subject: [PATCH 11/30] container/lxc: network.UpdateDNSMasqStatic usage

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

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 95a6a939fc..c262730689 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -38,6 +38,7 @@ import (
 	"github.com/lxc/lxd/lxd/instance/instancetype"
 	"github.com/lxc/lxd/lxd/instance/operationlock"
 	"github.com/lxc/lxd/lxd/maas"
+	"github.com/lxc/lxd/lxd/network"
 	"github.com/lxc/lxd/lxd/operations"
 	"github.com/lxc/lxd/lxd/project"
 	"github.com/lxc/lxd/lxd/seccomp"
@@ -3799,7 +3800,7 @@ func (c *containerLXC) Rename(newName string) error {
 	c.cConfig = false
 
 	// Update lease files.
-	networkUpdateStatic(c.state, "")
+	network.UpdateDNSMasqStatic(c.state, "")
 
 	logger.Info("Renamed container", ctxMap)
 

From 6aa858d9ffe79cbbcf3570f99e28b199076d030b Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jan 2020 16:28:42 +0000
Subject: [PATCH 12/30] lxd: instance.LoadNodeAll usage

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/containers.go                   | 6 +++---
 lxd/daemon.go                       | 3 ++-
 lxd/device/device_utils_instance.go | 8 +-------
 lxd/device/device_utils_network.go  | 5 +++--
 lxd/devices.go                      | 6 +++---
 lxd/devlxd.go                       | 2 +-
 6 files changed, 13 insertions(+), 17 deletions(-)

diff --git a/lxd/containers.go b/lxd/containers.go
index fcf0ffbaa9..b43f67c64c 100644
--- a/lxd/containers.go
+++ b/lxd/containers.go
@@ -205,7 +205,7 @@ func (slice containerAutostartList) Swap(i, j int) {
 
 func containersRestart(s *state.State) error {
 	// Get all the instances
-	result, err := instanceLoadNodeAll(s, instancetype.Any)
+	result, err := instance.LoadNodeAll(s, instancetype.Any)
 	if err != nil {
 		return err
 	}
@@ -248,7 +248,7 @@ func containersRestart(s *state.State) error {
 
 func vmMonitor(s *state.State) error {
 	// Get all the instances
-	insts, err := instanceLoadNodeAll(s, instancetype.VM)
+	insts, err := instance.LoadNodeAll(s, instancetype.VM)
 	if err != nil {
 		return err
 	}
@@ -318,7 +318,7 @@ func containersShutdown(s *state.State) error {
 	dbAvailable := true
 
 	// Get all the instances
-	instances, err := instanceLoadNodeAll(s, instancetype.Any)
+	instances, err := instance.LoadNodeAll(s, instancetype.Any)
 	if err != nil {
 		// Mark database as offline
 		dbAvailable = false
diff --git a/lxd/daemon.go b/lxd/daemon.go
index 646f1d82df..fd0e4b57fc 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -35,6 +35,7 @@ import (
 	"github.com/lxc/lxd/lxd/endpoints"
 	"github.com/lxc/lxd/lxd/events"
 	"github.com/lxc/lxd/lxd/firewall"
+	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/instance/instancetype"
 	"github.com/lxc/lxd/lxd/maas"
 	"github.com/lxc/lxd/lxd/node"
@@ -1047,7 +1048,7 @@ func (d *Daemon) Ready() error {
 }
 
 func (d *Daemon) numRunningContainers() (int, error) {
-	results, err := instanceLoadNodeAll(d.State(), instancetype.Container)
+	results, err := instance.LoadNodeAll(d.State(), instancetype.Container)
 	if err != nil {
 		return 0, err
 	}
diff --git a/lxd/device/device_utils_instance.go b/lxd/device/device_utils_instance.go
index 2d808c3055..c0793f06a6 100644
--- a/lxd/device/device_utils_instance.go
+++ b/lxd/device/device_utils_instance.go
@@ -10,12 +10,6 @@ import (
 	"github.com/lxc/lxd/lxd/state"
 )
 
-// InstanceLoadNodeAll returns all local instance configs.
-var InstanceLoadNodeAll func(s *state.State) ([]instance.Instance, error)
-
-// InstanceLoadByProjectAndName returns instance config by project and name.
-var InstanceLoadByProjectAndName func(s *state.State, project, name string) (instance.Instance, error)
-
 // reservedDevicesMutex used to coordinate access for checking reserved devices.
 var reservedDevicesMutex sync.Mutex
 
@@ -25,7 +19,7 @@ func instanceGetReservedDevices(s *state.State, m deviceConfig.Device) (map[stri
 	reservedDevicesMutex.Lock()
 	defer reservedDevicesMutex.Unlock()
 
-	instances, err := InstanceLoadNodeAll(s)
+	instances, err := instance.LoadNodeAll(s, instancetype.Any)
 	if err != nil {
 		return nil, err
 	}
diff --git a/lxd/device/device_utils_network.go b/lxd/device/device_utils_network.go
index 9abd4babc1..7c83d680f0 100644
--- a/lxd/device/device_utils_network.go
+++ b/lxd/device/device_utils_network.go
@@ -18,6 +18,7 @@ import (
 
 	deviceConfig "github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/lxd/instance"
+	"github.com/lxc/lxd/lxd/instance/instancetype"
 	"github.com/lxc/lxd/lxd/revert"
 	"github.com/lxc/lxd/lxd/state"
 	"github.com/lxc/lxd/lxd/util"
@@ -142,7 +143,7 @@ func NetworkRemoveInterface(nic string) error {
 // networkRemoveInterfaceIfNeeded removes a network interface by name but only if no other instance is using it.
 func networkRemoveInterfaceIfNeeded(state *state.State, nic string, current instance.Instance, parent string, vlanID string) error {
 	// Check if it's used by another instance.
-	instances, err := InstanceLoadNodeAll(state)
+	instances, err := instance.LoadNodeAll(state, instancetype.Any)
 	if err != nil {
 		return err
 	}
@@ -191,7 +192,7 @@ func NetworkCreateVlanDeviceIfNeeded(state *state.State, parent string, vlanDevi
 		}
 
 		// Check if it was created for another running instance.
-		instances, err := InstanceLoadNodeAll(state)
+		instances, err := instance.LoadNodeAll(state, instancetype.Any)
 		if err != nil {
 			return "", err
 		}
diff --git a/lxd/devices.go b/lxd/devices.go
index 4ac021d002..497960f2af 100644
--- a/lxd/devices.go
+++ b/lxd/devices.go
@@ -392,7 +392,7 @@ func deviceTaskBalance(s *state.State) {
 	}
 
 	// Iterate through the instances
-	instances, err := instanceLoadNodeAll(s, instancetype.Container)
+	instances, err := instance.LoadNodeAll(s, instancetype.Container)
 	if err != nil {
 		logger.Error("Problem loading instances list", log.Ctx{"err": err})
 		return
@@ -514,7 +514,7 @@ func deviceNetworkPriority(s *state.State, netif string) {
 		return
 	}
 
-	instances, err := instanceLoadNodeAll(s, instancetype.Container)
+	instances, err := instance.LoadNodeAll(s, instancetype.Container)
 	if err != nil {
 		return
 	}
@@ -594,7 +594,7 @@ func deviceEventListener(s *state.State) {
 
 // devicesRegister calls the Register() function on all supported devices so they receive events.
 func devicesRegister(s *state.State) {
-	instances, err := instanceLoadNodeAll(s, instancetype.Container)
+	instances, err := instance.LoadNodeAll(s, instancetype.Container)
 	if err != nil {
 		logger.Error("Problem loading containers list", log.Ctx{"err": err})
 		return
diff --git a/lxd/devlxd.go b/lxd/devlxd.go
index 57970454c4..f56c2e234c 100644
--- a/lxd/devlxd.go
+++ b/lxd/devlxd.go
@@ -394,7 +394,7 @@ func findContainerForPid(pid int32, s *state.State) (*containerLXC, error) {
 		return nil, err
 	}
 
-	instances, err := instanceLoadNodeAll(s, instancetype.Container)
+	instances, err := instance.LoadNodeAll(s, instancetype.Container)
 	if err != nil {
 		return nil, err
 	}

From 920fa7fc187599fd840a25aa591dd49be8cc4d6a Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jan 2020 16:36:49 +0000
Subject: [PATCH 13/30] lxd: instance.LoadByProject usage

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

diff --git a/lxd/storage_volumes_utils.go b/lxd/storage_volumes_utils.go
index fe80e7d9da..ce0c5e753f 100644
--- a/lxd/storage_volumes_utils.go
+++ b/lxd/storage_volumes_utils.go
@@ -6,6 +6,7 @@ import (
 	"strings"
 
 	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/state"
 	storagePools "github.com/lxc/lxd/lxd/storage"
 	"github.com/lxc/lxd/shared"
@@ -215,7 +216,7 @@ func storagePoolVolumeUpdate(state *state.State, poolName string, volumeName str
 }
 
 func storagePoolVolumeUsedByInstancesGet(s *state.State, project, poolName string, volumeName string) ([]string, error) {
-	insts, err := instanceLoadByProject(s, project)
+	insts, err := instance.LoadByProject(s, project)
 	if err != nil {
 		return []string{}, err
 	}

From ad099613c8cf874add47b3d86b164d7b462b1b1f Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jan 2020 16:41:16 +0000
Subject: [PATCH 14/30] lxd: instance.LoadByProjectAndName usage

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/device_utils_unix_events.go         | 2 +-
 lxd/device/device_utils_unix_hotplug_events.go | 2 +-
 lxd/device/device_utils_usb_events.go          | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/lxd/device/device_utils_unix_events.go b/lxd/device/device_utils_unix_events.go
index ec12643683..a85095f7b6 100644
--- a/lxd/device/device_utils_unix_events.go
+++ b/lxd/device/device_utils_unix_events.go
@@ -135,7 +135,7 @@ func unixRunHandlers(state *state.State, event *UnixEvent) {
 		// If runConf supplied, load instance and call its Unix event handler function so
 		// any instance specific device actions can occur.
 		if runConf != nil {
-			instance, err := InstanceLoadByProjectAndName(state, projectName, instanceName)
+			instance, err := instance.LoadByProjectAndName(state, projectName, instanceName)
 			if err != nil {
 				logger.Error("Unix event loading instance failed", log.Ctx{"err": err, "project": projectName, "instance": instanceName, "device": deviceName})
 				continue
diff --git a/lxd/device/device_utils_unix_hotplug_events.go b/lxd/device/device_utils_unix_hotplug_events.go
index 15ea424ff3..aa7ef17697 100644
--- a/lxd/device/device_utils_unix_hotplug_events.go
+++ b/lxd/device/device_utils_unix_hotplug_events.go
@@ -79,7 +79,7 @@ func UnixHotplugRunHandlers(state *state.State, event *UnixHotplugEvent) {
 		// If runConf supplied, load instance and call its Unix hotplug event handler function so
 		// any instance specific device actions can occur.
 		if runConf != nil {
-			instance, err := InstanceLoadByProjectAndName(state, projectName, instanceName)
+			instance, err := instance.LoadByProjectAndName(state, projectName, instanceName)
 			if err != nil {
 				logger.Error("Unix hotplug event loading instance failed", log.Ctx{"err": err, "project": projectName, "instance": instanceName, "device": deviceName})
 				continue
diff --git a/lxd/device/device_utils_usb_events.go b/lxd/device/device_utils_usb_events.go
index dfa02ba796..a0a2f01e11 100644
--- a/lxd/device/device_utils_usb_events.go
+++ b/lxd/device/device_utils_usb_events.go
@@ -79,7 +79,7 @@ func USBRunHandlers(state *state.State, event *USBEvent) {
 		// If runConf supplied, load instance and call its USB event handler function so
 		// any instance specific device actions can occur.
 		if runConf != nil {
-			instance, err := InstanceLoadByProjectAndName(state, projectName, instanceName)
+			instance, err := instance.LoadByProjectAndName(state, projectName, instanceName)
 			if err != nil {
 				logger.Error("USB event loading instance failed", log.Ctx{"err": err, "project": projectName, "instance": instanceName, "device": deviceName})
 				continue

From b842dd3219971aeca36412e37f2b22d9f3f280a5 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jan 2020 16:35:10 +0000
Subject: [PATCH 15/30] lxd/device/device/utils/network: Updates network
 package usage

Some functions have been moved to the network package.

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

diff --git a/lxd/device/device_utils_network.go b/lxd/device/device_utils_network.go
index 7c83d680f0..88b60cac54 100644
--- a/lxd/device/device_utils_network.go
+++ b/lxd/device/device_utils_network.go
@@ -19,6 +19,7 @@ import (
 	deviceConfig "github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/instance/instancetype"
+	"github.com/lxc/lxd/lxd/network"
 	"github.com/lxc/lxd/lxd/revert"
 	"github.com/lxc/lxd/lxd/state"
 	"github.com/lxc/lxd/lxd/util"
@@ -30,25 +31,9 @@ import (
 // Instances can be started in parallel, so lock the creation of VLANs.
 var networkCreateSharedDeviceLock sync.Mutex
 
-// NetworkGetDevMTU retrieves the current MTU setting for a named network device.
-func NetworkGetDevMTU(devName string) (uint64, error) {
-	content, err := ioutil.ReadFile(fmt.Sprintf("/sys/class/net/%s/mtu", devName))
-	if err != nil {
-		return 0, err
-	}
-
-	// Parse value
-	mtu, err := strconv.ParseUint(strings.TrimSpace(string(content)), 10, 32)
-	if err != nil {
-		return 0, err
-	}
-
-	return mtu, nil
-}
-
 // NetworkSetDevMTU sets the MTU setting for a named network device if different from current.
 func NetworkSetDevMTU(devName string, mtu uint64) error {
-	curMTU, err := NetworkGetDevMTU(devName)
+	curMTU, err := network.GetDevMTU(devName)
 	if err != nil {
 		return err
 	}
@@ -92,48 +77,6 @@ func NetworkSetDevMAC(devName string, mac string) error {
 	return nil
 }
 
-// NetworkGetHostDevice figures out whether there is an existing interface for the supplied
-// parent device and VLAN ID and returns it. Otherwise just returns the parent device name.
-func NetworkGetHostDevice(parent string, vlan string) string {
-	// If no VLAN, just use the raw device
-	if vlan == "" {
-		return parent
-	}
-
-	// If no VLANs are configured, use the default pattern
-	defaultVlan := fmt.Sprintf("%s.%s", parent, vlan)
-	if !shared.PathExists("/proc/net/vlan/config") {
-		return defaultVlan
-	}
-
-	// Look for an existing VLAN
-	f, err := os.Open("/proc/net/vlan/config")
-	if err != nil {
-		return defaultVlan
-	}
-	defer f.Close()
-
-	scanner := bufio.NewScanner(f)
-	for scanner.Scan() {
-		// Only grab the lines we're interested in
-		s := strings.Split(scanner.Text(), "|")
-		if len(s) != 3 {
-			continue
-		}
-
-		vlanIface := strings.TrimSpace(s[0])
-		vlanID := strings.TrimSpace(s[1])
-		vlanParent := strings.TrimSpace(s[2])
-
-		if vlanParent == parent && vlanID == vlan {
-			return vlanIface
-		}
-	}
-
-	// Return the default pattern
-	return defaultVlan
-}
-
 // NetworkRemoveInterface removes a network interface by name.
 func NetworkRemoveInterface(nic string) error {
 	_, err := shared.RunCommand("ip", "link", "del", "dev", nic)
@@ -217,7 +160,7 @@ func NetworkCreateVlanDeviceIfNeeded(state *state.State, parent string, vlanDevi
 // networkSnapshotPhysicalNic records properties of the NIC to volatile so they can be restored later.
 func networkSnapshotPhysicalNic(hostName string, volatile map[string]string) error {
 	// Store current MTU for restoration on detach.
-	mtu, err := NetworkGetDevMTU(hostName)
+	mtu, err := network.GetDevMTU(hostName)
 	if err != nil {
 		return err
 	}
@@ -284,26 +227,6 @@ func NetworkRandomDevName(prefix string) string {
 	return iface
 }
 
-// NetworkAttachInterface attaches an interface to a bridge.
-func NetworkAttachInterface(netName string, devName string) error {
-	if shared.PathExists(fmt.Sprintf("/sys/class/net/%s/bridge", netName)) {
-		_, err := shared.RunCommand("ip", "link", "set", "dev", devName, "master", netName)
-		if err != nil {
-			return err
-		}
-	} else {
-		_, err := shared.RunCommand("ovs-vsctl", "port-to-br", devName)
-		if err != nil {
-			_, err := shared.RunCommand("ovs-vsctl", "add-port", netName, devName)
-			if err != nil {
-				return err
-			}
-		}
-	}
-
-	return nil
-}
-
 // networkCreateVethPair creates and configures a veth pair. It will set the hwaddr and mtu settings
 // in the supplied config to the newly created peer interface. If mtu is not specified, but parent
 // is supplied in config, then the MTU of the new peer interface will inherit the parent MTU.
@@ -350,7 +273,7 @@ func networkCreateVethPair(hostName string, m deviceConfig.Device) (string, erro
 			return "", fmt.Errorf("Failed to set the MTU: %v", err)
 		}
 	} else if m["parent"] != "" {
-		parentMTU, err := NetworkGetDevMTU(m["parent"])
+		parentMTU, err := network.GetDevMTU(m["parent"])
 		if err != nil {
 			return "", fmt.Errorf("Failed to get the parent MTU: %v", err)
 		}
@@ -399,7 +322,7 @@ func networkCreateTap(hostName string, m deviceConfig.Device) error {
 			return errors.Wrap(err, "Failed to set the MTU")
 		}
 	} else if m["parent"] != "" {
-		parentMTU, err := NetworkGetDevMTU(m["parent"])
+		parentMTU, err := network.GetDevMTU(m["parent"])
 		if err != nil {
 			return errors.Wrap(err, "Failed to get the parent MTU")
 		}

From 0be1b04407f330088c2c2cc7fa01d6b61053e330 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jan 2020 16:35:40 +0000
Subject: [PATCH 16/30] lxd/device/device/utils/network: Unexports some
 non-shared functions

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

diff --git a/lxd/device/device_utils_network.go b/lxd/device/device_utils_network.go
index 88b60cac54..4c9c5e757d 100644
--- a/lxd/device/device_utils_network.go
+++ b/lxd/device/device_utils_network.go
@@ -111,8 +111,8 @@ func networkRemoveInterfaceIfNeeded(state *state.State, nic string, current inst
 	return NetworkRemoveInterface(nic)
 }
 
-// NetworkCreateVlanDeviceIfNeeded creates a VLAN device if doesn't already exist.
-func NetworkCreateVlanDeviceIfNeeded(state *state.State, parent string, vlanDevice string, vlanID string) (string, error) {
+// networkCreateVlanDeviceIfNeeded creates a VLAN device if doesn't already exist.
+func networkCreateVlanDeviceIfNeeded(state *state.State, parent string, vlanDevice string, vlanID string) (string, error) {
 	if vlanID != "" {
 		if !shared.PathExists(fmt.Sprintf("/sys/class/net/%s", vlanDevice)) {
 			// Bring the parent interface up so we can add a vlan to it.
@@ -212,10 +212,10 @@ func networkRestorePhysicalNic(hostName string, volatile map[string]string) erro
 	return nil
 }
 
-// NetworkRandomDevName returns a random device name with prefix.
+// networkRandomDevName returns a random device name with prefix.
 // If the random string combined with the prefix exceeds 13 characters then empty string is returned.
 // This is to ensure we support buggy dhclient applications: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=858580
-func NetworkRandomDevName(prefix string) string {
+func networkRandomDevName(prefix string) string {
 	// Return a new random veth device name
 	randBytes := make([]byte, 4)
 	rand.Read(randBytes)
@@ -232,7 +232,7 @@ func NetworkRandomDevName(prefix string) string {
 // is supplied in config, then the MTU of the new peer interface will inherit the parent MTU.
 // Accepts the name of the host side interface as a parameter and returns the peer interface name.
 func networkCreateVethPair(hostName string, m deviceConfig.Device) (string, error) {
-	peerName := NetworkRandomDevName("veth")
+	peerName := networkRandomDevName("veth")
 
 	_, err := shared.RunCommand("ip", "link", "add", "dev", hostName, "type", "veth", "peer", "name", peerName)
 	if err != nil {

From bae719581e5b615ebed99dcf633481fc017d46a3 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jan 2020 16:37:45 +0000
Subject: [PATCH 17/30] lxd/network/utils: Removes network utils functions used
 by network type

These are moved into network package.

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

diff --git a/lxd/networks_utils.go b/lxd/networks_utils.go
index 70601dbf6c..97124b4b91 100644
--- a/lxd/networks_utils.go
+++ b/lxd/networks_utils.go
@@ -1,39 +1,20 @@
 package main
 
 import (
-	"bufio"
-	"encoding/binary"
-	"encoding/hex"
 	"fmt"
-	"io/ioutil"
-	"math"
-	"math/big"
-	"math/rand"
 	"net"
-	"os"
-	"os/exec"
 	"regexp"
 	"strconv"
 	"strings"
-	"sync"
-	"time"
 
 	"github.com/lxc/lxd/lxd/cluster"
 	"github.com/lxc/lxd/lxd/db"
-	"github.com/lxc/lxd/lxd/device"
-	"github.com/lxc/lxd/lxd/dnsmasq"
-	"github.com/lxc/lxd/lxd/instance"
-	"github.com/lxc/lxd/lxd/instance/instancetype"
-	"github.com/lxc/lxd/lxd/project"
+	"github.com/lxc/lxd/lxd/network"
 	"github.com/lxc/lxd/lxd/state"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
-	"github.com/lxc/lxd/shared/logger"
-	"github.com/lxc/lxd/shared/subprocess"
 )
 
-var forkdnsServersLock sync.Mutex
-
 func networkAutoAttach(cluster *db.Cluster, devName string) error {
 	_, dbInfo, err := cluster.NetworkGetInterface(devName)
 	if err != nil {
@@ -41,26 +22,7 @@ func networkAutoAttach(cluster *db.Cluster, devName string) error {
 		return nil
 	}
 
-	return device.NetworkAttachInterface(dbInfo.Name, devName)
-}
-
-func networkDetachInterface(netName string, devName string) error {
-	if shared.PathExists(fmt.Sprintf("/sys/class/net/%s/bridge", netName)) {
-		_, err := shared.RunCommand("ip", "link", "set", "dev", devName, "nomaster")
-		if err != nil {
-			return err
-		}
-	} else {
-		_, err := shared.RunCommand("ovs-vsctl", "port-to-br", devName)
-		if err == nil {
-			_, err := shared.RunCommand("ovs-vsctl", "del-port", netName, devName)
-			if err != nil {
-				return err
-			}
-		}
-	}
-
-	return nil
+	return network.AttachInterface(dbInfo.Name, devName)
 }
 
 func networkGetInterfaces(cluster *db.Cluster) ([]string, error) {
@@ -89,330 +51,6 @@ func networkGetInterfaces(cluster *db.Cluster) ([]string, error) {
 	return networks, nil
 }
 
-func networkIsInUse(c instance.Instance, name string) bool {
-	for _, d := range c.ExpandedDevices() {
-		if d["type"] != "nic" {
-			continue
-		}
-
-		if !shared.StringInSlice(d["nictype"], []string{"bridged", "macvlan", "ipvlan", "physical", "sriov"}) {
-			continue
-		}
-
-		if d["parent"] == "" {
-			continue
-		}
-
-		if device.NetworkGetHostDevice(d["parent"], d["vlan"]) == name {
-			return true
-		}
-	}
-
-	return false
-}
-
-func networkGetIP(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
-}
-
-func networkGetTunnels(config map[string]string) []string {
-	tunnels := []string{}
-
-	for k := range config {
-		if !strings.HasPrefix(k, "tunnel.") {
-			continue
-		}
-
-		fields := strings.Split(k, ".")
-		if !shared.StringInSlice(fields[1], tunnels) {
-			tunnels = append(tunnels, fields[1])
-		}
-	}
-
-	return tunnels
-}
-
-func networkPingSubnet(subnet *net.IPNet) bool {
-	var fail bool
-	var failLock sync.Mutex
-	var wgChecks sync.WaitGroup
-
-	ping := func(ip net.IP) {
-		defer wgChecks.Done()
-
-		cmd := "ping"
-		if ip.To4() == nil {
-			cmd = "ping6"
-		}
-
-		_, err := shared.RunCommand(cmd, "-n", "-q", ip.String(), "-c", "1", "-W", "1")
-		if err != nil {
-			// Remote didn't answer
-			return
-		}
-
-		// Remote answered
-		failLock.Lock()
-		fail = true
-		failLock.Unlock()
-	}
-
-	poke := func(ip net.IP) {
-		defer wgChecks.Done()
-
-		addr := fmt.Sprintf("%s:22", ip.String())
-		if ip.To4() == nil {
-			addr = fmt.Sprintf("[%s]:22", ip.String())
-		}
-
-		_, err := net.DialTimeout("tcp", addr, time.Second)
-		if err == nil {
-			// Remote answered
-			failLock.Lock()
-			fail = true
-			failLock.Unlock()
-			return
-		}
-	}
-
-	// Ping first IP
-	wgChecks.Add(1)
-	go ping(networkGetIP(subnet, 1))
-
-	// Poke port on first IP
-	wgChecks.Add(1)
-	go poke(networkGetIP(subnet, 1))
-
-	// Ping check
-	if subnet.IP.To4() != nil {
-		// Ping last IP
-		wgChecks.Add(1)
-		go ping(networkGetIP(subnet, -2))
-
-		// Poke port on last IP
-		wgChecks.Add(1)
-		go poke(networkGetIP(subnet, -2))
-	}
-
-	wgChecks.Wait()
-
-	return fail
-}
-
-func networkInRoutingTable(subnet *net.IPNet) bool {
-	filename := "route"
-	if subnet.IP.To4() == nil {
-		filename = "ipv6_route"
-	}
-
-	file, err := os.Open(fmt.Sprintf("/proc/net/%s", filename))
-	if err != nil {
-		return false
-	}
-	defer file.Close()
-
-	scanner := bufio.NewReader(file)
-	for {
-		line, _, err := scanner.ReadLine()
-		if err != nil {
-			break
-		}
-
-		fields := strings.Fields(string(line))
-
-		// Get the IP
-		var ip net.IP
-		if filename == "ipv6_route" {
-			ip, err = hex.DecodeString(fields[0])
-			if err != nil {
-				continue
-			}
-		} else {
-			bytes, err := hex.DecodeString(fields[1])
-			if err != nil {
-				continue
-			}
-
-			ip = net.IPv4(bytes[3], bytes[2], bytes[1], bytes[0])
-		}
-
-		// Get the mask
-		var mask net.IPMask
-		if filename == "ipv6_route" {
-			size, err := strconv.ParseInt(fmt.Sprintf("0x%s", fields[1]), 0, 64)
-			if err != nil {
-				continue
-			}
-
-			mask = net.CIDRMask(int(size), 128)
-		} else {
-			bytes, err := hex.DecodeString(fields[7])
-			if err != nil {
-				continue
-			}
-
-			mask = net.IPv4Mask(bytes[3], bytes[2], bytes[1], bytes[0])
-		}
-
-		// Generate a new network
-		lineNet := net.IPNet{IP: ip, Mask: mask}
-
-		// Ignore default gateway
-		if lineNet.IP.Equal(net.ParseIP("::")) {
-			continue
-		}
-
-		if lineNet.IP.Equal(net.ParseIP("0.0.0.0")) {
-			continue
-		}
-
-		// Check if we have a route to our new subnet
-		if lineNet.Contains(subnet.IP) {
-			return true
-		}
-	}
-
-	return false
-}
-
-func networkRandomSubnetV4() (string, error) {
-	for i := 0; i < 100; i++ {
-		cidr := fmt.Sprintf("10.%d.%d.1/24", rand.Intn(255), rand.Intn(255))
-		_, subnet, err := net.ParseCIDR(cidr)
-		if err != nil {
-			continue
-		}
-
-		if networkInRoutingTable(subnet) {
-			continue
-		}
-
-		if networkPingSubnet(subnet) {
-			continue
-		}
-
-		return cidr, nil
-	}
-
-	return "", fmt.Errorf("Failed to automatically find an unused IPv4 subnet, manual configuration required")
-}
-
-func networkRandomSubnetV6() (string, error) {
-	for i := 0; i < 100; i++ {
-		cidr := fmt.Sprintf("fd42:%x:%x:%x::1/64", rand.Intn(65535), rand.Intn(65535), rand.Intn(65535))
-		_, subnet, err := net.ParseCIDR(cidr)
-		if err != nil {
-			continue
-		}
-
-		if networkInRoutingTable(subnet) {
-			continue
-		}
-
-		if networkPingSubnet(subnet) {
-			continue
-		}
-
-		return cidr, nil
-	}
-
-	return "", fmt.Errorf("Failed to automatically find an unused IPv6 subnet, manual configuration required")
-}
-
-func networkDefaultGatewaySubnetV4() (*net.IPNet, string, error) {
-	file, err := os.Open("/proc/net/route")
-	if err != nil {
-		return nil, "", err
-	}
-	defer file.Close()
-
-	ifaceName := ""
-
-	scanner := bufio.NewReader(file)
-	for {
-		line, _, err := scanner.ReadLine()
-		if err != nil {
-			break
-		}
-
-		fields := strings.Fields(string(line))
-
-		if fields[1] == "00000000" && fields[7] == "00000000" {
-			ifaceName = fields[0]
-			break
-		}
-	}
-
-	if ifaceName == "" {
-		return nil, "", fmt.Errorf("No default gateway for IPv4")
-	}
-
-	iface, err := net.InterfaceByName(ifaceName)
-	if err != nil {
-		return nil, "", err
-	}
-
-	addrs, err := iface.Addrs()
-	if err != nil {
-		return nil, "", err
-	}
-
-	var subnet *net.IPNet
-
-	for _, addr := range addrs {
-		addrIP, addrNet, err := net.ParseCIDR(addr.String())
-		if err != nil {
-			return nil, "", err
-		}
-
-		if addrIP.To4() == nil {
-			continue
-		}
-
-		if subnet != nil {
-			return nil, "", fmt.Errorf("More than one IPv4 subnet on default interface")
-		}
-
-		subnet = addrNet
-	}
-
-	if subnet == nil {
-		return nil, "", fmt.Errorf("No IPv4 subnet on default interface")
-	}
-
-	return subnet, ifaceName, nil
-}
-
 func networkValidName(value string) error {
 	// Not a veth-liked name
 	if strings.HasPrefix(value, "veth") {
@@ -496,288 +134,6 @@ func networkValidAddressCIDRV4(value string) error {
 	return nil
 }
 
-func networkAddressForSubnet(subnet *net.IPNet) (net.IP, string, error) {
-	ifaces, err := net.Interfaces()
-	if err != nil {
-		return net.IP{}, "", err
-	}
-
-	for _, iface := range ifaces {
-		addrs, err := iface.Addrs()
-		if err != nil {
-			continue
-		}
-
-		for _, addr := range addrs {
-			ip, _, err := net.ParseCIDR(addr.String())
-			if err != nil {
-				continue
-			}
-
-			if subnet.Contains(ip) {
-				return ip, iface.Name, nil
-			}
-		}
-	}
-
-	return net.IP{}, "", fmt.Errorf("No address found in subnet")
-}
-
-func networkFanAddress(underlay *net.IPNet, overlay *net.IPNet) (string, string, string, error) {
-	// Sanity checks
-	underlaySize, _ := underlay.Mask.Size()
-	if underlaySize != 16 && underlaySize != 24 {
-		return "", "", "", fmt.Errorf("Only /16 or /24 underlays are supported at this time")
-	}
-
-	overlaySize, _ := overlay.Mask.Size()
-	if overlaySize != 8 && overlaySize != 16 {
-		return "", "", "", fmt.Errorf("Only /8 or /16 overlays are supported at this time")
-	}
-
-	if overlaySize+(32-underlaySize)+8 > 32 {
-		return "", "", "", fmt.Errorf("Underlay or overlay networks too large to accommodate the FAN")
-	}
-
-	// Get the IP
-	ip, dev, err := networkAddressForSubnet(underlay)
-	if err != nil {
-		return "", "", "", err
-	}
-	ipStr := ip.String()
-
-	// Force into IPv4 format
-	ipBytes := ip.To4()
-	if ipBytes == nil {
-		return "", "", "", fmt.Errorf("Invalid IPv4: %s", ip)
-	}
-
-	// Compute the IP
-	ipBytes[0] = overlay.IP[0]
-	if overlaySize == 16 {
-		ipBytes[1] = overlay.IP[1]
-		ipBytes[2] = ipBytes[3]
-	} else if underlaySize == 24 {
-		ipBytes[1] = ipBytes[3]
-		ipBytes[2] = 0
-	} else if underlaySize == 16 {
-		ipBytes[1] = ipBytes[2]
-		ipBytes[2] = ipBytes[3]
-	}
-
-	ipBytes[3] = 1
-
-	return fmt.Sprintf("%s/%d", ipBytes.String(), overlaySize), dev, ipStr, err
-}
-
-func networkKillForkDNS(name string) error {
-	// Check if we have a running forkdns at all
-	pidPath := shared.VarPath("networks", name, "forkdns.pid")
-
-	// If the pid file doesn't exist, there is no process to kill.
-	if !shared.PathExists(pidPath) {
-		return nil
-	}
-
-	p, err := subprocess.ImportProcess(pidPath)
-	if err != nil {
-		return fmt.Errorf("Could not read pid file: %s", err)
-	}
-
-	err = p.Stop()
-	if err != nil && err != subprocess.ErrNotRunning {
-		return fmt.Errorf("Unable to kill dnsmasq: %s", err)
-	}
-
-	return nil
-}
-
-func networkUpdateStatic(s *state.State, networkName string) error {
-	// We don't want to race with ourselves here
-	dnsmasq.ConfigMutex.Lock()
-	defer dnsmasq.ConfigMutex.Unlock()
-
-	// Get all the networks
-	var networks []string
-	if networkName == "" {
-		var err error
-		networks, err = s.Cluster.Networks()
-		if err != nil {
-			return err
-		}
-	} else {
-		networks = []string{networkName}
-	}
-
-	// Get all the instances
-	insts, err := instanceLoadNodeAll(s, instancetype.Any)
-	if err != nil {
-		return err
-	}
-
-	// Build a list of dhcp host entries
-	entries := map[string][][]string{}
-	for _, inst := range insts {
-		// Go through all its devices (including profiles
-		for k, d := range inst.ExpandedDevices() {
-			// Skip uninteresting entries
-			if d["type"] != "nic" || d["nictype"] != "bridged" || !shared.StringInSlice(d["parent"], networks) {
-				continue
-			}
-
-			// Fill in the hwaddr from volatile
-			d, err = inst.FillNetworkDevice(k, d)
-			if err != nil {
-				continue
-			}
-
-			// Add the new host entries
-			_, ok := entries[d["parent"]]
-			if !ok {
-				entries[d["parent"]] = [][]string{}
-			}
-
-			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.Name())
-				if err != nil && !os.IsNotExist(err) {
-					return err
-				}
-
-				if d["ipv4.address"] == "" && curIPv4.IP != nil {
-					d["ipv4.address"] = curIPv4.IP.String()
-				}
-
-				if d["ipv6.address"] == "" && curIPv6.IP != nil {
-					d["ipv6.address"] = curIPv6.IP.String()
-				}
-			}
-
-			entries[d["parent"]] = append(entries[d["parent"]], []string{d["hwaddr"], inst.Project(), inst.Name(), d["ipv4.address"], d["ipv6.address"]})
-		}
-	}
-
-	// Update the host files
-	for _, network := range networks {
-		entries, _ := entries[network]
-
-		// Skip networks we don't manage (or don't have DHCP enabled)
-		if !shared.PathExists(shared.VarPath("networks", network, "dnsmasq.pid")) {
-			continue
-		}
-
-		n, err := networkLoadByName(s, network)
-		if err != nil {
-			return err
-		}
-		config := n.Config()
-
-		// Wipe everything clean
-		files, err := ioutil.ReadDir(shared.VarPath("networks", network, "dnsmasq.hosts"))
-		if err != nil {
-			return err
-		}
-
-		for _, entry := range files {
-			err = os.Remove(shared.VarPath("networks", network, "dnsmasq.hosts", entry.Name()))
-			if err != nil {
-				return err
-			}
-		}
-
-		// Apply the changes
-		for entryIdx, entry := range entries {
-			hwaddr := entry[0]
-			projectName := entry[1]
-			cName := entry[2]
-			ipv4Address := entry[3]
-			ipv6Address := entry[4]
-			line := hwaddr
-
-			// Look for duplicates
-			duplicate := false
-			for iIdx, i := range entries {
-				if project.Prefix(entry[1], entry[2]) == project.Prefix(i[1], i[2]) {
-					// Skip ourselves
-					continue
-				}
-
-				if entry[0] == i[0] {
-					// Find broken configurations
-					logger.Errorf("Duplicate MAC detected: %s and %s", project.Prefix(entry[1], entry[2]), project.Prefix(i[1], i[2]))
-				}
-
-				if i[3] == "" && i[4] == "" {
-					// Skip unconfigured
-					continue
-				}
-
-				if entry[3] == i[3] && entry[4] == i[4] {
-					// Find identical containers (copies with static configuration)
-					if entryIdx > iIdx {
-						duplicate = true
-					} else {
-						line = fmt.Sprintf("%s,%s", line, i[0])
-						logger.Debugf("Found containers with duplicate IPv4/IPv6: %s and %s", project.Prefix(entry[1], entry[2]), project.Prefix(i[1], i[2]))
-					}
-				}
-			}
-
-			if duplicate {
-				continue
-			}
-
-			// Generate the dhcp-host line
-			err := dnsmasq.UpdateStaticEntry(network, projectName, cName, config, hwaddr, ipv4Address, ipv6Address)
-			if err != nil {
-				return err
-			}
-		}
-
-		// Signal dnsmasq
-		err = dnsmasq.Kill(network, true)
-		if err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
-// networkUpdateForkdnsServersFile takes a list of node addresses and writes them atomically to
-// the forkdns.servers file ready for forkdns to notice and re-apply its config.
-func networkUpdateForkdnsServersFile(networkName string, addresses []string) error {
-	// We don't want to race with ourselves here
-	forkdnsServersLock.Lock()
-	defer forkdnsServersLock.Unlock()
-
-	permName := shared.VarPath("networks", networkName, forkdnsServersListPath+"/"+forkdnsServersListFile)
-	tmpName := permName + ".tmp"
-
-	// Open tmp file and truncate
-	tmpFile, err := os.Create(tmpName)
-	if err != nil {
-		return err
-	}
-	defer tmpFile.Close()
-
-	for _, address := range addresses {
-		_, err := tmpFile.WriteString(address + "\n")
-		if err != nil {
-			return err
-		}
-	}
-
-	tmpFile.Close()
-
-	// Atomically rename finished file into permanent location so forkdns can pick it up.
-	err = os.Rename(tmpName, permName)
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
-
 // networkUpdateForkdnsServersTask runs every 30s and refreshes the forkdns servers list.
 func networkUpdateForkdnsServersTask(s *state.State, heartbeatData *cluster.APIHeartbeat) error {
 	// Get a list of managed networks
@@ -787,13 +143,13 @@ func networkUpdateForkdnsServersTask(s *state.State, heartbeatData *cluster.APIH
 	}
 
 	for _, name := range networks {
-		n, err := networkLoadByName(s, name)
+		n, err := network.LoadByName(s, name)
 		if err != nil {
 			return err
 		}
 
-		if n.config["bridge.mode"] == "fan" {
-			err := n.refreshForkdnsServerAddresses(heartbeatData)
+		if n.Config()["bridge.mode"] == "fan" {
+			err := n.RefreshForkdnsServerAddresses(heartbeatData)
 			if err != nil {
 				return err
 			}
@@ -803,29 +159,6 @@ func networkUpdateForkdnsServersTask(s *state.State, heartbeatData *cluster.APIH
 	return nil
 }
 
-// networksGetForkdnsServersList reads the server list file and returns the list as a slice.
-func networksGetForkdnsServersList(networkName string) ([]string, error) {
-	servers := []string{}
-	file, err := os.Open(shared.VarPath("networks", networkName, forkdnsServersListPath, "/", forkdnsServersListFile))
-	if err != nil {
-		return servers, err
-	}
-	defer file.Close()
-
-	scanner := bufio.NewScanner(file)
-	for scanner.Scan() {
-		fields := strings.Fields(scanner.Text())
-		if len(fields) > 0 {
-			servers = append(servers, fields[0])
-		}
-	}
-	if err := scanner.Err(); err != nil {
-		return servers, err
-	}
-
-	return servers, nil
-}
-
 func networkGetMacSlice(hwaddr string) []string {
 	var buf []string
 
@@ -922,67 +255,3 @@ func networkGetState(netIf net.Interface) api.NetworkState {
 	network.Counters = shared.NetworkGetCounters(netIf.Name)
 	return network
 }
-
-// networkListBootRoutesV4 returns a list of IPv4 boot routes on a named network device.
-func networkListBootRoutesV4(devName string) ([]string, error) {
-	routes := []string{}
-	cmd := exec.Command("ip", "-4", "route", "show", "dev", devName, "proto", "boot")
-	ipOut, err := cmd.StdoutPipe()
-	if err != nil {
-		return routes, err
-	}
-	cmd.Start()
-	scanner := bufio.NewScanner(ipOut)
-	for scanner.Scan() {
-		route := strings.Replace(scanner.Text(), "linkdown", "", -1)
-		routes = append(routes, route)
-	}
-	cmd.Wait()
-	return routes, nil
-}
-
-// networkListBootRoutesV6 returns a list of IPv6 boot routes on a named network device.
-func networkListBootRoutesV6(devName string) ([]string, error) {
-	routes := []string{}
-	cmd := exec.Command("ip", "-6", "route", "show", "dev", devName, "proto", "boot")
-	ipOut, err := cmd.StdoutPipe()
-	if err != nil {
-		return routes, err
-	}
-	cmd.Start()
-	scanner := bufio.NewScanner(ipOut)
-	for scanner.Scan() {
-		route := strings.Replace(scanner.Text(), "linkdown", "", -1)
-		routes = append(routes, route)
-	}
-	cmd.Wait()
-	return routes, nil
-}
-
-// networkApplyBootRoutesV4 applies a list of IPv4 boot routes to a named network device.
-func networkApplyBootRoutesV4(devName string, routes []string) error {
-	for _, route := range routes {
-		cmd := []string{"-4", "route", "replace", "dev", devName, "proto", "boot"}
-		cmd = append(cmd, strings.Fields(route)...)
-		_, err := shared.RunCommand("ip", cmd...)
-		if err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
-// networkApplyBootRoutesV6 applies a list of IPv6 boot routes to a named network device.
-func networkApplyBootRoutesV6(devName string, routes []string) error {
-	for _, route := range routes {
-		cmd := []string{"-6", "route", "replace", "dev", devName, "proto", "boot"}
-		cmd = append(cmd, strings.Fields(route)...)
-		_, err := shared.RunCommand("ip", cmd...)
-		if err != nil {
-			return err
-		}
-	}
-
-	return nil
-}

From 85427d165c169bde9c3213159418227c2e4a1ed1 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jan 2020 16:38:21 +0000
Subject: [PATCH 18/30] lxd/networks/config: Removes networkFillAuto function

This is moved into network package.

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

diff --git a/lxd/networks_config.go b/lxd/networks_config.go
index a4c95dc135..8960bea960 100644
--- a/lxd/networks_config.go
+++ b/lxd/networks_config.go
@@ -190,34 +190,3 @@ func networkValidateConfig(name string, config map[string]string) error {
 
 	return nil
 }
-
-func networkFillAuto(config map[string]string) error {
-	if config["ipv4.address"] == "auto" {
-		subnet, err := networkRandomSubnetV4()
-		if err != nil {
-			return err
-		}
-
-		config["ipv4.address"] = subnet
-	}
-
-	if config["ipv6.address"] == "auto" {
-		subnet, err := networkRandomSubnetV6()
-		if err != nil {
-			return err
-		}
-
-		config["ipv6.address"] = subnet
-	}
-
-	if config["fan.underlay_subnet"] == "auto" {
-		subnet, _, err := networkDefaultGatewaySubnetV4()
-		if err != nil {
-			return err
-		}
-
-		config["fan.underlay_subnet"] = subnet.String()
-	}
-
-	return nil
-}

From 2c21a3f13bdbe85e848dbe9f342773827a8d0eb5 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jan 2020 16:40:13 +0000
Subject: [PATCH 19/30] lxd/networks: Removes network type and
 networkLoadByName function

Updates to use functions in network package.

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

diff --git a/lxd/networks.go b/lxd/networks.go
index be5fd0cb72..77aaa7810e 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -1,15 +1,12 @@
 package main
 
 import (
-	"encoding/binary"
 	"encoding/json"
 	"fmt"
 	"io/ioutil"
 	"net"
 	"net/http"
 	"os"
-	"os/exec"
-	"reflect"
 	"strings"
 	"sync"
 
@@ -19,29 +16,21 @@ import (
 
 	lxd "github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/lxd/cluster"
-	"github.com/lxc/lxd/lxd/daemon"
 	"github.com/lxc/lxd/lxd/db"
-	"github.com/lxc/lxd/lxd/device"
-	"github.com/lxc/lxd/lxd/dnsmasq"
-	firewallConsts "github.com/lxc/lxd/lxd/firewall/consts"
 	"github.com/lxc/lxd/lxd/instance"
-	"github.com/lxc/lxd/lxd/node"
+	"github.com/lxc/lxd/lxd/network"
 	"github.com/lxc/lxd/lxd/response"
 	"github.com/lxc/lxd/lxd/state"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/logger"
-	"github.com/lxc/lxd/shared/subprocess"
 	"github.com/lxc/lxd/shared/version"
 )
 
 func init() {
 	// Link networkGetLeaseAddresses into instance package.
 	instance.NetworkGetLeaseAddresses = networkGetLeaseAddresses
-
-	// Link networkUpdateStatic into instance package.
-	instance.NetworkUpdateStatic = networkUpdateStatic
 }
 
 // Lock to prevent concurent networks creation
@@ -192,7 +181,7 @@ func networksPost(d *Daemon, r *http.Request) response.Response {
 		return resp
 	}
 
-	err = networkFillConfig(&req)
+	err = network.FillConfig(&req)
 	if err != nil {
 		return response.SmartError(err)
 	}
@@ -245,7 +234,7 @@ func networksPostCluster(d *Daemon, req api.NetworksPost) error {
 	}
 
 	// Add default values.
-	err = networkFillConfig(&req)
+	err = network.FillConfig(&req)
 	if err != nil {
 		return err
 	}
@@ -322,44 +311,11 @@ func networksPostCluster(d *Daemon, req api.NetworksPost) error {
 	return notifyErr
 }
 
-func networkFillConfig(req *api.NetworksPost) error {
-	// Set some default values where needed
-	if req.Config["bridge.mode"] == "fan" {
-		if req.Config["fan.underlay_subnet"] == "" {
-			req.Config["fan.underlay_subnet"] = "auto"
-		}
-	} else {
-		if req.Config["ipv4.address"] == "" {
-			req.Config["ipv4.address"] = "auto"
-		}
-		if req.Config["ipv4.address"] == "auto" && req.Config["ipv4.nat"] == "" {
-			req.Config["ipv4.nat"] = "true"
-		}
-
-		if req.Config["ipv6.address"] == "" {
-			content, err := ioutil.ReadFile("/proc/sys/net/ipv6/conf/default/disable_ipv6")
-			if err == nil && string(content) == "0\n" {
-				req.Config["ipv6.address"] = "auto"
-			}
-		}
-		if req.Config["ipv6.address"] == "auto" && req.Config["ipv6.nat"] == "" {
-			req.Config["ipv6.nat"] = "true"
-		}
-	}
-
-	// Replace "auto" by actual values
-	err := networkFillAuto(req.Config)
-	if err != nil {
-		return err
-	}
-	return nil
-}
-
 // Create the network on the system. The withDatabase flag is used to decide
 // whether to cleanup the database if an error occurs.
 func doNetworksCreate(d *Daemon, req api.NetworksPost, withDatabase bool) error {
 	// Start the network
-	n, err := networkLoadByName(d.State(), req.Name)
+	n, err := network.LoadByName(d.State(), req.Name)
 	if err != nil {
 		return err
 	}
@@ -455,13 +411,13 @@ func doNetworkGet(d *Daemon, name string) (api.Network, error) {
 
 	// Look for containers using the interface
 	if n.Type != "loopback" {
-		insts, err := instanceLoadFromAllProjects(d.State())
+		insts, err := instance.LoadFromAllProjects(d.State())
 		if err != nil {
 			return api.Network{}, err
 		}
 
 		for _, inst := range insts {
-			if networkIsInUse(inst, n.Name) {
+			if network.IsInUse(inst, n.Name) {
 				uri := fmt.Sprintf("/%s/instances/%s", version.APIVersion, inst.Name())
 				if inst.Project() != "default" {
 					uri += fmt.Sprintf("?project=%s", inst.Project())
@@ -485,11 +441,11 @@ func networkDelete(d *Daemon, r *http.Request) response.Response {
 
 	// Check if the network is pending, if so we just need to delete it from
 	// the database.
-	_, network, err := d.cluster.NetworkGet(name)
+	_, dbNetwork, err := d.cluster.NetworkGet(name)
 	if err != nil {
 		return response.SmartError(err)
 	}
-	if network.Status == "Pending" {
+	if dbNetwork.Status == "Pending" {
 		err := d.cluster.NetworkDelete(name)
 		if err != nil {
 			return response.SmartError(err)
@@ -498,7 +454,7 @@ func networkDelete(d *Daemon, r *http.Request) response.Response {
 	}
 
 	// Get the existing network
-	n, err := networkLoadByName(state, name)
+	n, err := network.LoadByName(state, name)
 	if err != nil {
 		return response.NotFound(err)
 	}
@@ -532,8 +488,8 @@ func networkDelete(d *Daemon, r *http.Request) response.Response {
 	}
 
 	// Cleanup storage
-	if shared.PathExists(shared.VarPath("networks", n.name)) {
-		os.RemoveAll(shared.VarPath("networks", n.name))
+	if shared.PathExists(shared.VarPath("networks", n.Name())) {
+		os.RemoveAll(shared.VarPath("networks", n.Name()))
 	}
 
 	return response.EmptySyncResponse
@@ -566,7 +522,7 @@ func networkPost(d *Daemon, r *http.Request) response.Response {
 	}
 
 	// Get the existing network
-	n, err := networkLoadByName(state, name)
+	n, err := network.LoadByName(state, name)
 	if err != nil {
 		return response.NotFound(err)
 	}
@@ -705,7 +661,7 @@ func doNetworkUpdate(d *Daemon, name string, oldConfig map[string]string, req ap
 	}
 
 	// Load the network
-	n, err := networkLoadByName(d.State(), name)
+	n, err := network.LoadByName(d.State(), name)
 	if err != nil {
 		return response.NotFound(err)
 	}
@@ -739,7 +695,7 @@ func networkLeasesGet(d *Daemon, r *http.Request) response.Response {
 	// Get all static leases
 	if !isClusterNotification(r) {
 		// Get all the instances
-		instances, err := instanceLoadByProject(d.State(), project)
+		instances, err := instance.LoadByProject(d.State(), project)
 		if err != nil {
 			return response.SmartError(err)
 		}
@@ -878,15 +834,15 @@ func networkLeasesGet(d *Daemon, r *http.Request) response.Response {
 	return response.SyncResponse(true, leases)
 }
 
-func networkGetLeaseAddresses(s *state.State, network string, hwaddr string) ([]api.InstanceStateNetworkAddress, error) {
+func networkGetLeaseAddresses(s *state.State, networkName string, hwaddr string) ([]api.InstanceStateNetworkAddress, error) {
 	addresses := []api.InstanceStateNetworkAddress{}
 
-	leaseFile := shared.VarPath("networks", network, "dnsmasq.leases")
+	leaseFile := shared.VarPath("networks", networkName, "dnsmasq.leases")
 	if !shared.PathExists(leaseFile) {
 		return addresses, nil
 	}
 
-	dbInfo, err := networkLoadByName(s, network)
+	dbInfo, err := network.LoadByName(s, networkName)
 	if err != nil {
 		return nil, err
 	}
@@ -949,18 +905,6 @@ func networkGetLeaseAddresses(s *state.State, network string, hwaddr string) ([]
 	return addresses, nil
 }
 
-// The network structs and functions
-func networkLoadByName(s *state.State, name string) (*network, error) {
-	id, dbInfo, err := s.Cluster.NetworkGet(name)
-	if err != nil {
-		return nil, err
-	}
-
-	n := network{state: s, id: id, name: name, description: dbInfo.Description, config: dbInfo.Config}
-
-	return &n, nil
-}
-
 func networkStartup(s *state.State) error {
 	// Get a list of managed networks
 	networks, err := s.Cluster.NetworksNotPending()
@@ -970,7 +914,7 @@ func networkStartup(s *state.State) error {
 
 	// Bring them all up
 	for _, name := range networks {
-		n, err := networkLoadByName(s, name)
+		n, err := network.LoadByName(s, name)
 		if err != nil {
 			return err
 		}
@@ -994,7 +938,7 @@ func networkShutdown(s *state.State) error {
 
 	// Bring them all up
 	for _, name := range networks {
-		n, err := networkLoadByName(s, name)
+		n, err := network.LoadByName(s, name)
 		if err != nil {
 			return err
 		}
@@ -1031,1319 +975,3 @@ func networkStateGet(d *Daemon, r *http.Request) response.Response {
 
 	return response.SyncResponse(true, networkGetState(*osInfo))
 }
-
-type network struct {
-	// Properties
-	state       *state.State
-	id          int64
-	name        string
-	description string
-
-	// config
-	config map[string]string
-}
-
-func (n *network) Config() map[string]string {
-	return n.config
-}
-
-func (n *network) IsRunning() bool {
-	return shared.PathExists(fmt.Sprintf("/sys/class/net/%s", n.name))
-}
-
-func (n *network) IsUsed() bool {
-	// Look for instances using the interface
-	insts, err := instanceLoadFromAllProjects(n.state)
-	if err != nil {
-		return true
-	}
-
-	for _, inst := range insts {
-		if networkIsInUse(inst, n.name) {
-			return true
-		}
-	}
-
-	return false
-}
-
-func (n *network) Delete(withDatabase bool) error {
-	// Bring the network down
-	if n.IsRunning() {
-		err := n.Stop()
-		if err != nil {
-			return err
-		}
-	}
-
-	// If withDatabase is false, this is a cluster notification, and we
-	// don't want to perform any database work.
-	if !withDatabase {
-		return nil
-	}
-
-	// Remove the network from the database
-	err := n.state.Cluster.NetworkDelete(n.name)
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func (n *network) Rename(name string) error {
-	// Sanity checks
-	if n.IsUsed() {
-		return fmt.Errorf("The network is currently in use")
-	}
-
-	// Bring the network down
-	if n.IsRunning() {
-		err := n.Stop()
-		if err != nil {
-			return err
-		}
-	}
-
-	// Rename directory
-	if shared.PathExists(shared.VarPath("networks", name)) {
-		os.RemoveAll(shared.VarPath("networks", name))
-	}
-
-	if shared.PathExists(shared.VarPath("networks", n.name)) {
-		err := os.Rename(shared.VarPath("networks", n.name), shared.VarPath("networks", name))
-		if err != nil {
-			return err
-		}
-	}
-
-	forkDNSLogPath := fmt.Sprintf("forkdns.%s.log", n.name)
-	if shared.PathExists(shared.LogPath(forkDNSLogPath)) {
-		err := os.Rename(forkDNSLogPath, shared.LogPath(fmt.Sprintf("forkdns.%s.log", name)))
-		if err != nil {
-			return err
-		}
-	}
-
-	// Rename the database entry
-	err := n.state.Cluster.NetworkRename(n.name, name)
-	if err != nil {
-		return err
-	}
-	n.name = name
-
-	// Bring the network up
-	err = n.Start()
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func (n *network) Start() error {
-	return n.Setup(nil)
-}
-
-func (n *network) Setup(oldConfig map[string]string) error {
-	// If we are in mock mode, just no-op.
-	if n.state.OS.MockMode {
-		return nil
-	}
-
-	// Create directory
-	if !shared.PathExists(shared.VarPath("networks", n.name)) {
-		err := os.MkdirAll(shared.VarPath("networks", n.name), 0711)
-		if err != nil {
-			return err
-		}
-	}
-
-	// Create the bridge interface
-	if !n.IsRunning() {
-		if n.config["bridge.driver"] == "openvswitch" {
-			_, err := exec.LookPath("ovs-vsctl")
-			if err != nil {
-				return fmt.Errorf("Open vSwitch isn't installed on this system")
-			}
-
-			_, err = shared.RunCommand("ovs-vsctl", "add-br", n.name)
-			if err != nil {
-				return err
-			}
-		} else {
-			_, err := shared.RunCommand("ip", "link", "add", "dev", n.name, "type", "bridge")
-			if err != nil {
-				return err
-			}
-		}
-	}
-
-	// Get a list of tunnels
-	tunnels := networkGetTunnels(n.config)
-
-	// IPv6 bridge configuration
-	if !shared.StringInSlice(n.config["ipv6.address"], []string{"", "none"}) {
-		if !shared.PathExists("/proc/sys/net/ipv6") {
-			return fmt.Errorf("Network has ipv6.address but kernel IPv6 support is missing")
-		}
-
-		err := util.SysctlSet(fmt.Sprintf("net/ipv6/conf/%s/autoconf", n.name), "0")
-		if err != nil {
-			return err
-		}
-
-		err = util.SysctlSet(fmt.Sprintf("net/ipv6/conf/%s/accept_dad", n.name), "0")
-		if err != nil {
-			return err
-		}
-	}
-
-	// Get a list of interfaces
-	ifaces, err := net.Interfaces()
-	if err != nil {
-		return err
-	}
-
-	// Cleanup any existing tunnel device
-	for _, iface := range ifaces {
-		if strings.HasPrefix(iface.Name, fmt.Sprintf("%s-", n.name)) {
-			_, err = shared.RunCommand("ip", "link", "del", "dev", iface.Name)
-			if err != nil {
-				return err
-			}
-		}
-	}
-
-	// Set the MTU
-	mtu := ""
-	if n.config["bridge.mtu"] != "" {
-		mtu = n.config["bridge.mtu"]
-	} else if len(tunnels) > 0 {
-		mtu = "1400"
-	} else if n.config["bridge.mode"] == "fan" {
-		if n.config["fan.type"] == "ipip" {
-			mtu = "1480"
-		} else {
-			mtu = "1450"
-		}
-	}
-
-	// Attempt to add a dummy device to the bridge to force the MTU
-	if mtu != "" && n.config["bridge.driver"] != "openvswitch" {
-		_, err = shared.RunCommand("ip", "link", "add", "dev", fmt.Sprintf("%s-mtu", n.name), "mtu", mtu, "type", "dummy")
-		if err == nil {
-			_, err = shared.RunCommand("ip", "link", "set", "dev", fmt.Sprintf("%s-mtu", n.name), "up")
-			if err == nil {
-				device.NetworkAttachInterface(n.name, fmt.Sprintf("%s-mtu", n.name))
-			}
-		}
-	}
-
-	// Now, set a default MTU
-	if mtu == "" {
-		mtu = "1500"
-	}
-
-	_, err = shared.RunCommand("ip", "link", "set", "dev", n.name, "mtu", mtu)
-	if err != nil {
-		return err
-	}
-
-	// Set the MAC address
-	if n.config["bridge.hwaddr"] != "" {
-		_, err = shared.RunCommand("ip", "link", "set", "dev", n.name, "address", n.config["bridge.hwaddr"])
-		if err != nil {
-			return err
-		}
-	}
-
-	// Bring it up
-	_, err = shared.RunCommand("ip", "link", "set", "dev", n.name, "up")
-	if err != nil {
-		return err
-	}
-
-	// Add any listed existing external interface
-	if n.config["bridge.external_interfaces"] != "" {
-		for _, entry := range strings.Split(n.config["bridge.external_interfaces"], ",") {
-			entry = strings.TrimSpace(entry)
-			iface, err := net.InterfaceByName(entry)
-			if err != nil {
-				continue
-			}
-
-			unused := true
-			addrs, err := iface.Addrs()
-			if err == nil {
-				for _, addr := range addrs {
-					ip, _, err := net.ParseCIDR(addr.String())
-					if ip != nil && err == nil && ip.IsGlobalUnicast() {
-						unused = false
-						break
-					}
-				}
-			}
-
-			if !unused {
-				return fmt.Errorf("Only unconfigured network interfaces can be bridged")
-			}
-
-			err = device.NetworkAttachInterface(n.name, entry)
-			if err != nil {
-				return err
-			}
-		}
-	}
-
-	// Remove any existing IPv4 iptables rules
-	if n.config["ipv4.firewall"] == "" || shared.IsTrue(n.config["ipv4.firewall"]) || (oldConfig != nil && (oldConfig["ipv4.firewall"] == "" || shared.IsTrue(oldConfig["ipv4.firewall"]))) {
-		err = n.state.Firewall.NetworkClear(firewallConsts.FamilyIPv4, firewallConsts.TableAll, n.name)
-		if err != nil {
-			return err
-		}
-
-		err = n.state.Firewall.NetworkClear(firewallConsts.FamilyIPv4, firewallConsts.TableMangle, n.name)
-		if err != nil {
-			return err
-		}
-	}
-
-	if shared.IsTrue(n.config["ipv4.nat"]) || (oldConfig != nil && shared.IsTrue(oldConfig["ipv4.nat"])) {
-		err = n.state.Firewall.NetworkClear(firewallConsts.FamilyIPv4, firewallConsts.TableNat, n.name)
-		if err != nil {
-			return err
-		}
-	}
-
-	// Snapshot container specific IPv4 routes (added with boot proto) before removing IPv4 addresses.
-	// This is because the kernel removes any static routes on an interface when all addresses removed.
-	ctRoutes, err := networkListBootRoutesV4(n.name)
-	if err != nil {
-		return err
-	}
-
-	// Flush all IPv4 addresses and routes
-	_, err = shared.RunCommand("ip", "-4", "addr", "flush", "dev", n.name, "scope", "global")
-	if err != nil {
-		return err
-	}
-
-	_, err = shared.RunCommand("ip", "-4", "route", "flush", "dev", n.name, "proto", "static")
-	if err != nil {
-		return err
-	}
-
-	// Configure IPv4 firewall (includes fan)
-	if n.config["bridge.mode"] == "fan" || !shared.StringInSlice(n.config["ipv4.address"], []string{"", "none"}) {
-		if (n.config["ipv4.dhcp"] == "" || shared.IsTrue(n.config["ipv4.dhcp"])) && (n.config["ipv4.firewall"] == "" || shared.IsTrue(n.config["ipv4.firewall"])) {
-			// Setup basic iptables overrides for DHCP/DNS
-			n.state.Firewall.NetworkSetupIPv4DNSOverrides(n.name)
-		}
-
-		// Attempt a workaround for broken DHCP clients
-		if n.config["ipv4.firewall"] == "" || shared.IsTrue(n.config["ipv4.firewall"]) {
-			n.state.Firewall.NetworkSetupIPv4DHCPWorkaround(n.name)
-		}
-
-		// Allow forwarding
-		if n.config["bridge.mode"] == "fan" || n.config["ipv4.routing"] == "" || shared.IsTrue(n.config["ipv4.routing"]) {
-			err = util.SysctlSet("net/ipv4/ip_forward", "1")
-			if err != nil {
-				return err
-			}
-
-			if n.config["ipv4.firewall"] == "" || shared.IsTrue(n.config["ipv4.firewall"]) {
-				err = n.state.Firewall.NetworkSetupAllowForwarding(firewallConsts.FamilyIPv4, n.name, firewallConsts.ActionAccept)
-				if err != nil {
-					return err
-				}
-			}
-		} else {
-			if n.config["ipv4.firewall"] == "" || shared.IsTrue(n.config["ipv4.firewall"]) {
-				err = n.state.Firewall.NetworkSetupAllowForwarding(firewallConsts.FamilyIPv4, n.name, firewallConsts.ActionReject)
-				if err != nil {
-					return err
-				}
-			}
-		}
-	}
-
-	// Start building process using subprocess package
-	command := "dnsmasq"
-	dnsmasqCmd := []string{"--keep-in-foreground", "--strict-order", "--bind-interfaces",
-		"--except-interface=lo",
-		"--no-ping", // --no-ping is very important to prevent delays to lease file updates.
-		fmt.Sprintf("--interface=%s", n.name)}
-
-	dnsmasqVersion, err := dnsmasq.GetVersion()
-	if err != nil {
-		return err
-	}
-
-	// --dhcp-rapid-commit option is only supported on >2.79
-	minVer, _ := version.NewDottedVersion("2.79")
-	if dnsmasqVersion.Compare(minVer) > 0 {
-		dnsmasqCmd = append(dnsmasqCmd, "--dhcp-rapid-commit")
-	}
-
-	if !daemon.Debug {
-		// --quiet options are only supported on >2.67
-		minVer, _ := version.NewDottedVersion("2.67")
-
-		if err == nil && dnsmasqVersion.Compare(minVer) > 0 {
-			dnsmasqCmd = append(dnsmasqCmd, []string{"--quiet-dhcp", "--quiet-dhcp6", "--quiet-ra"}...)
-		}
-	}
-
-	// Configure IPv4
-	if !shared.StringInSlice(n.config["ipv4.address"], []string{"", "none"}) {
-		// Parse the subnet
-		ip, subnet, err := net.ParseCIDR(n.config["ipv4.address"])
-		if err != nil {
-			return err
-		}
-
-		// Update the dnsmasq config
-		dnsmasqCmd = append(dnsmasqCmd, fmt.Sprintf("--listen-address=%s", ip.String()))
-		if n.config["ipv4.dhcp"] == "" || shared.IsTrue(n.config["ipv4.dhcp"]) {
-			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"))}...)
-			}
-
-			if n.config["ipv4.dhcp.gateway"] != "" {
-				dnsmasqCmd = append(dnsmasqCmd, fmt.Sprintf("--dhcp-option=3,%s", n.config["ipv4.dhcp.gateway"]))
-			}
-
-			expiry := "1h"
-			if n.config["ipv4.dhcp.expiry"] != "" {
-				expiry = n.config["ipv4.dhcp.expiry"]
-			}
-
-			if n.config["ipv4.dhcp.ranges"] != "" {
-				for _, dhcpRange := range strings.Split(n.config["ipv4.dhcp.ranges"], ",") {
-					dhcpRange = strings.TrimSpace(dhcpRange)
-					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", networkGetIP(subnet, 2).String(), networkGetIP(subnet, -2).String(), expiry)}...)
-			}
-		}
-
-		// Add the address
-		_, err = shared.RunCommand("ip", "-4", "addr", "add", "dev", n.name, n.config["ipv4.address"])
-		if err != nil {
-			return err
-		}
-
-		// Configure NAT
-		if shared.IsTrue(n.config["ipv4.nat"]) {
-			//If a SNAT source address is specified, use that, otherwise default to using MASQUERADE mode.
-			args := []string{"-s", subnet.String(), "!", "-d", subnet.String(), "-j", "MASQUERADE"}
-			if n.config["ipv4.nat.address"] != "" {
-				args = []string{"-s", subnet.String(), "!", "-d", subnet.String(), "-j", "SNAT", "--to", n.config["ipv4.nat.address"]}
-			}
-
-			if n.config["ipv4.nat.order"] == "after" {
-				err = n.state.Firewall.NetworkSetupNAT(firewallConsts.FamilyIPv4, n.name, firewallConsts.LocationAppend, args...)
-				if err != nil {
-					return err
-				}
-			} else {
-				err = n.state.Firewall.NetworkSetupNAT(firewallConsts.FamilyIPv4, n.name, firewallConsts.LocationPrepend, args...)
-				if err != nil {
-					return err
-				}
-			}
-		}
-
-		// Add additional routes
-		if n.config["ipv4.routes"] != "" {
-			for _, route := range strings.Split(n.config["ipv4.routes"], ",") {
-				route = strings.TrimSpace(route)
-				_, err = shared.RunCommand("ip", "-4", "route", "add", "dev", n.name, route, "proto", "static")
-				if err != nil {
-					return err
-				}
-			}
-		}
-
-		// Restore container specific IPv4 routes to interface.
-		err = networkApplyBootRoutesV4(n.name, ctRoutes)
-		if err != nil {
-			return err
-		}
-	}
-
-	// Remove any existing IPv6 iptables rules
-	if n.config["ipv6.firewall"] == "" || shared.IsTrue(n.config["ipv6.firewall"]) || (oldConfig != nil && (oldConfig["ipv6.firewall"] == "" || shared.IsTrue(oldConfig["ipv6.firewall"]))) {
-		err = n.state.Firewall.NetworkClear(firewallConsts.FamilyIPv6, firewallConsts.TableAll, n.name)
-		if err != nil {
-			return err
-		}
-	}
-
-	if shared.IsTrue(n.config["ipv6.nat"]) || (oldConfig != nil && shared.IsTrue(oldConfig["ipv6.nat"])) {
-		err = n.state.Firewall.NetworkClear(firewallConsts.FamilyIPv6, firewallConsts.TableNat, n.name)
-		if err != nil {
-			return err
-		}
-	}
-
-	// Snapshot container specific IPv6 routes (added with boot proto) before removing IPv6 addresses.
-	// This is because the kernel removes any static routes on an interface when all addresses removed.
-	ctRoutes, err = networkListBootRoutesV6(n.name)
-	if err != nil {
-		return err
-	}
-
-	// Flush all IPv6 addresses and routes
-	_, err = shared.RunCommand("ip", "-6", "addr", "flush", "dev", n.name, "scope", "global")
-	if err != nil {
-		return err
-	}
-
-	_, err = shared.RunCommand("ip", "-6", "route", "flush", "dev", n.name, "proto", "static")
-	if err != nil {
-		return err
-	}
-
-	// Configure IPv6
-	if !shared.StringInSlice(n.config["ipv6.address"], []string{"", "none"}) {
-		// Enable IPv6 for the subnet
-		err := util.SysctlSet(fmt.Sprintf("net/ipv6/conf/%s/disable_ipv6", n.name), "0")
-		if err != nil {
-			return err
-		}
-
-		// Parse the subnet
-		ip, subnet, err := net.ParseCIDR(n.config["ipv6.address"])
-		if err != nil {
-			return err
-		}
-
-		// Update the dnsmasq config
-		dnsmasqCmd = append(dnsmasqCmd, []string{fmt.Sprintf("--listen-address=%s", ip.String()), "--enable-ra"}...)
-		if n.config["ipv6.dhcp"] == "" || shared.IsTrue(n.config["ipv6.dhcp"]) {
-			if n.config["ipv6.firewall"] == "" || shared.IsTrue(n.config["ipv6.firewall"]) {
-				// Setup basic iptables overrides for DHCP/DNS
-				n.state.Firewall.NetworkSetupIPv6DNSOverrides(n.name)
-			}
-
-			// Build DHCP configuration
-			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"))}...)
-			}
-
-			expiry := "1h"
-			if n.config["ipv6.dhcp.expiry"] != "" {
-				expiry = n.config["ipv6.dhcp.expiry"]
-			}
-
-			if shared.IsTrue(n.config["ipv6.dhcp.stateful"]) {
-				subnetSize, _ := subnet.Mask.Size()
-				if n.config["ipv6.dhcp.ranges"] != "" {
-					for _, dhcpRange := range strings.Split(n.config["ipv6.dhcp.ranges"], ",") {
-						dhcpRange = strings.TrimSpace(dhcpRange)
-						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", networkGetIP(subnet, 2), networkGetIP(subnet, -1), subnetSize, expiry)}...)
-				}
-			} else {
-				dnsmasqCmd = append(dnsmasqCmd, []string{"--dhcp-range", fmt.Sprintf("::,constructor:%s,ra-stateless,ra-names", n.name)}...)
-			}
-		} else {
-			dnsmasqCmd = append(dnsmasqCmd, []string{"--dhcp-range", fmt.Sprintf("::,constructor:%s,ra-only", n.name)}...)
-		}
-
-		// Allow forwarding
-		if n.config["ipv6.routing"] == "" || shared.IsTrue(n.config["ipv6.routing"]) {
-			// Get a list of proc entries
-			entries, err := ioutil.ReadDir("/proc/sys/net/ipv6/conf/")
-			if err != nil {
-				return err
-			}
-
-			// First set accept_ra to 2 for everything
-			for _, entry := range entries {
-				content, err := ioutil.ReadFile(fmt.Sprintf("/proc/sys/net/ipv6/conf/%s/accept_ra", entry.Name()))
-				if err == nil && string(content) != "1\n" {
-					continue
-				}
-
-				err = util.SysctlSet(fmt.Sprintf("net/ipv6/conf/%s/accept_ra", entry.Name()), "2")
-				if err != nil && !os.IsNotExist(err) {
-					return err
-				}
-			}
-
-			// Then set forwarding for all of them
-			for _, entry := range entries {
-				err = util.SysctlSet(fmt.Sprintf("net/ipv6/conf/%s/forwarding", entry.Name()), "1")
-				if err != nil && !os.IsNotExist(err) {
-					return err
-				}
-			}
-
-			if n.config["ipv6.firewall"] == "" || shared.IsTrue(n.config["ipv6.firewall"]) {
-				err = n.state.Firewall.NetworkSetupAllowForwarding(firewallConsts.FamilyIPv6, n.name, firewallConsts.ActionAccept)
-				if err != nil {
-					return err
-				}
-			}
-		} else {
-			if n.config["ipv6.firewall"] == "" || shared.IsTrue(n.config["ipv6.firewall"]) {
-				err = n.state.Firewall.NetworkSetupAllowForwarding(firewallConsts.FamilyIPv6, n.name, firewallConsts.ActionReject)
-				if err != nil {
-					return err
-				}
-			}
-		}
-
-		// Add the address
-		_, err = shared.RunCommand("ip", "-6", "addr", "add", "dev", n.name, n.config["ipv6.address"])
-		if err != nil {
-			return err
-		}
-
-		// Configure NAT
-		if shared.IsTrue(n.config["ipv6.nat"]) {
-			args := []string{"-s", subnet.String(), "!", "-d", subnet.String(), "-j", "MASQUERADE"}
-			if n.config["ipv6.nat.address"] != "" {
-				args = []string{"-s", subnet.String(), "!", "-d", subnet.String(), "-j", "SNAT", "--to", n.config["ipv6.nat.address"]}
-			}
-
-			if n.config["ipv6.nat.order"] == "after" {
-				err = n.state.Firewall.NetworkSetupNAT(firewallConsts.FamilyIPv6, n.name, firewallConsts.LocationAppend, args...)
-				if err != nil {
-					return err
-				}
-			} else {
-				err = n.state.Firewall.NetworkSetupNAT(firewallConsts.FamilyIPv6, n.name, firewallConsts.LocationPrepend, args...)
-				if err != nil {
-					return err
-				}
-			}
-		}
-
-		// Add additional routes
-		if n.config["ipv6.routes"] != "" {
-			for _, route := range strings.Split(n.config["ipv6.routes"], ",") {
-				route = strings.TrimSpace(route)
-				_, err = shared.RunCommand("ip", "-6", "route", "add", "dev", n.name, route, "proto", "static")
-				if err != nil {
-					return err
-				}
-			}
-		}
-
-		// Restore container specific IPv6 routes to interface.
-		err = networkApplyBootRoutesV6(n.name, ctRoutes)
-		if err != nil {
-			return err
-		}
-	}
-
-	// Configure the fan
-	dnsClustered := false
-	dnsClusteredAddress := ""
-	var overlaySubnet *net.IPNet
-	if n.config["bridge.mode"] == "fan" {
-		tunName := fmt.Sprintf("%s-fan", n.name)
-
-		// Parse the underlay
-		underlay := n.config["fan.underlay_subnet"]
-		_, underlaySubnet, err := net.ParseCIDR(underlay)
-		if err != nil {
-			return nil
-		}
-
-		// Parse the overlay
-		overlay := n.config["fan.overlay_subnet"]
-		if overlay == "" {
-			overlay = "240.0.0.0/8"
-		}
-
-		_, overlaySubnet, err = net.ParseCIDR(overlay)
-		if err != nil {
-			return err
-		}
-
-		// Get the address
-		fanAddress, devName, devAddr, err := networkFanAddress(underlaySubnet, overlaySubnet)
-		if err != nil {
-			return err
-		}
-
-		addr := strings.Split(fanAddress, "/")
-		if n.config["fan.type"] == "ipip" {
-			fanAddress = fmt.Sprintf("%s/24", addr[0])
-		}
-
-		// Update the MTU based on overlay device (if available)
-		fanMtuInt, err := device.NetworkGetDevMTU(devName)
-		if err == nil {
-			// Apply overhead
-			if n.config["fan.type"] == "ipip" {
-				fanMtuInt = fanMtuInt - 20
-			} else {
-				fanMtuInt = fanMtuInt - 50
-			}
-
-			// Apply changes
-			fanMtu := fmt.Sprintf("%d", fanMtuInt)
-			if fanMtu != mtu {
-				mtu = fanMtu
-				if n.config["bridge.driver"] != "openvswitch" {
-					_, err = shared.RunCommand("ip", "link", "set", "dev", fmt.Sprintf("%s-mtu", n.name), "mtu", mtu)
-					if err != nil {
-						return err
-					}
-				}
-
-				_, err = shared.RunCommand("ip", "link", "set", "dev", n.name, "mtu", mtu)
-				if err != nil {
-					return err
-				}
-			}
-		}
-
-		// Parse the host subnet
-		_, hostSubnet, err := net.ParseCIDR(fmt.Sprintf("%s/24", addr[0]))
-		if err != nil {
-			return err
-		}
-
-		// Add the address
-		_, err = shared.RunCommand("ip", "-4", "addr", "add", "dev", n.name, fanAddress)
-		if err != nil {
-			return err
-		}
-
-		// Update the dnsmasq config
-		expiry := "1h"
-		if n.config["ipv4.dhcp.expiry"] != "" {
-			expiry = n.config["ipv4.dhcp.expiry"]
-		}
-
-		dnsmasqCmd = append(dnsmasqCmd, []string{
-			fmt.Sprintf("--listen-address=%s", addr[0]),
-			"--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", networkGetIP(hostSubnet, 2).String(), networkGetIP(hostSubnet, -2).String(), expiry)}...)
-
-		// Setup the tunnel
-		if n.config["fan.type"] == "ipip" {
-			_, err = shared.RunCommand("ip", "-4", "route", "flush", "dev", "tunl0")
-			if err != nil {
-				return err
-			}
-
-			_, err = shared.RunCommand("ip", "link", "set", "dev", "tunl0", "up")
-			if err != nil {
-				return err
-			}
-
-			// Fails if the map is already set
-			shared.RunCommand("ip", "link", "change", "dev", "tunl0", "type", "ipip", "fan-map", fmt.Sprintf("%s:%s", overlay, underlay))
-
-			_, err = shared.RunCommand("ip", "route", "add", overlay, "dev", "tunl0", "src", addr[0])
-			if err != nil {
-				return err
-			}
-		} else {
-			vxlanID := fmt.Sprintf("%d", binary.BigEndian.Uint32(overlaySubnet.IP.To4())>>8)
-
-			_, err = shared.RunCommand("ip", "link", "add", tunName, "type", "vxlan", "id", vxlanID, "dev", devName, "dstport", "0", "local", devAddr, "fan-map", fmt.Sprintf("%s:%s", overlay, underlay))
-			if err != nil {
-				return err
-			}
-
-			err = device.NetworkAttachInterface(n.name, tunName)
-			if err != nil {
-				return err
-			}
-
-			_, err = shared.RunCommand("ip", "link", "set", "dev", tunName, "mtu", mtu, "up")
-			if err != nil {
-				return err
-			}
-
-			_, err = shared.RunCommand("ip", "link", "set", "dev", n.name, "up")
-			if err != nil {
-				return err
-			}
-		}
-
-		// Configure NAT
-		if n.config["ipv4.nat"] == "" || shared.IsTrue(n.config["ipv4.nat"]) {
-			if n.config["ipv4.nat.order"] == "after" {
-				err = n.state.Firewall.NetworkSetupTunnelNAT(n.name, firewallConsts.LocationAppend, *overlaySubnet)
-				if err != nil {
-					return err
-				}
-			} else {
-				err = n.state.Firewall.NetworkSetupTunnelNAT(n.name, firewallConsts.LocationPrepend, *overlaySubnet)
-				if err != nil {
-					return err
-				}
-			}
-		}
-
-		// Setup clustered DNS
-		clusterAddress, err := node.ClusterAddress(n.state.Node)
-		if err != nil {
-			return err
-		}
-
-		// If clusterAddress is non-empty, this indicates the intention for this node to be
-		// part of a cluster and so we should ensure that dnsmasq and forkdns are started
-		// in cluster mode. Note: During LXD initialisation the cluster may not actually be
-		// setup yet, but we want the DNS processes to be ready for when it is.
-		if clusterAddress != "" {
-			dnsClustered = true
-		}
-
-		dnsClusteredAddress = strings.Split(fanAddress, "/")[0]
-	}
-
-	// Configure tunnels
-	for _, tunnel := range tunnels {
-		getConfig := func(key string) string {
-			return n.config[fmt.Sprintf("tunnel.%s.%s", tunnel, key)]
-		}
-
-		tunProtocol := getConfig("protocol")
-		tunLocal := getConfig("local")
-		tunRemote := getConfig("remote")
-		tunName := fmt.Sprintf("%s-%s", n.name, tunnel)
-
-		// Configure the tunnel
-		cmd := []string{"ip", "link", "add", "dev", tunName}
-		if tunProtocol == "gre" {
-			// Skip partial configs
-			if tunProtocol == "" || tunLocal == "" || tunRemote == "" {
-				continue
-			}
-
-			cmd = append(cmd, []string{"type", "gretap", "local", tunLocal, "remote", tunRemote}...)
-		} else if tunProtocol == "vxlan" {
-			tunGroup := getConfig("group")
-			tunInterface := getConfig("interface")
-
-			// Skip partial configs
-			if tunProtocol == "" {
-				continue
-			}
-
-			cmd = append(cmd, []string{"type", "vxlan"}...)
-
-			if tunLocal != "" && tunRemote != "" {
-				cmd = append(cmd, []string{"local", tunLocal, "remote", tunRemote}...)
-			} else {
-				if tunGroup == "" {
-					tunGroup = "239.0.0.1"
-				}
-
-				devName := tunInterface
-				if devName == "" {
-					_, devName, err = networkDefaultGatewaySubnetV4()
-					if err != nil {
-						return err
-					}
-				}
-
-				cmd = append(cmd, []string{"group", tunGroup, "dev", devName}...)
-			}
-
-			tunPort := getConfig("port")
-			if tunPort == "" {
-				tunPort = "0"
-			}
-			cmd = append(cmd, []string{"dstport", tunPort}...)
-
-			tunId := getConfig("id")
-			if tunId == "" {
-				tunId = "1"
-			}
-			cmd = append(cmd, []string{"id", tunId}...)
-
-			tunTtl := getConfig("ttl")
-			if tunTtl == "" {
-				tunTtl = "1"
-			}
-			cmd = append(cmd, []string{"ttl", tunTtl}...)
-		}
-
-		// Create the interface
-		_, err = shared.RunCommand(cmd[0], cmd[1:]...)
-		if err != nil {
-			return err
-		}
-
-		// Bridge it and bring up
-		err = device.NetworkAttachInterface(n.name, tunName)
-		if err != nil {
-			return err
-		}
-
-		_, err = shared.RunCommand("ip", "link", "set", "dev", tunName, "mtu", mtu, "up")
-		if err != nil {
-			return err
-		}
-
-		_, err = shared.RunCommand("ip", "link", "set", "dev", n.name, "up")
-		if err != nil {
-			return err
-		}
-	}
-
-	// Kill any existing dnsmasq and forkdns daemon for this network
-	err = dnsmasq.Kill(n.name, false)
-	if err != nil {
-		return err
-	}
-
-	err = networkKillForkDNS(n.name)
-	if err != nil {
-		return err
-	}
-
-	// Configure dnsmasq
-	if n.config["bridge.mode"] == "fan" || !shared.StringInSlice(n.config["ipv4.address"], []string{"", "none"}) || !shared.StringInSlice(n.config["ipv6.address"], []string{"", "none"}) {
-		// Setup the dnsmasq domain
-		dnsDomain := n.config["dns.domain"]
-		if dnsDomain == "" {
-			dnsDomain = "lxd"
-		}
-
-		if n.config["dns.mode"] != "none" {
-			if dnsClustered {
-				dnsmasqCmd = append(dnsmasqCmd, "-s", dnsDomain)
-				dnsmasqCmd = append(dnsmasqCmd, "-S", fmt.Sprintf("/%s/%s#1053", dnsDomain, dnsClusteredAddress))
-				dnsmasqCmd = append(dnsmasqCmd, fmt.Sprintf("--rev-server=%s,%s#1053", overlaySubnet, dnsClusteredAddress))
-			} else {
-				dnsmasqCmd = append(dnsmasqCmd, []string{"-s", dnsDomain, "-S", fmt.Sprintf("/%s/", dnsDomain)}...)
-			}
-		}
-
-		// Create a config file to contain additional config (and to prevent dnsmasq from reading /etc/dnsmasq.conf)
-		err = ioutil.WriteFile(shared.VarPath("networks", n.name, "dnsmasq.raw"), []byte(fmt.Sprintf("%s\n", n.config["raw.dnsmasq"])), 0644)
-		if err != nil {
-			return err
-		}
-		dnsmasqCmd = append(dnsmasqCmd, fmt.Sprintf("--conf-file=%s", shared.VarPath("networks", n.name, "dnsmasq.raw")))
-
-		// Attempt to drop privileges
-		if n.state.OS.UnprivUser != "" {
-			dnsmasqCmd = append(dnsmasqCmd, []string{"-u", n.state.OS.UnprivUser}...)
-		}
-
-		// Create DHCP hosts directory
-		if !shared.PathExists(shared.VarPath("networks", n.name, "dnsmasq.hosts")) {
-			err = os.MkdirAll(shared.VarPath("networks", n.name, "dnsmasq.hosts"), 0755)
-			if err != nil {
-				return err
-			}
-		}
-
-		// Check for dnsmasq
-		_, err := exec.LookPath("dnsmasq")
-		if err != nil {
-			return fmt.Errorf("dnsmasq is required for LXD managed bridges")
-		}
-
-		// Update the static leases
-		err = networkUpdateStatic(n.state, n.name)
-		if err != nil {
-			return err
-		}
-
-		// Create subprocess object dnsmasq (occasionally races, try a few times)
-		p, err := subprocess.NewProcess(command, dnsmasqCmd, "", "")
-		if err != nil {
-			return fmt.Errorf("Failed to create subprocess: %s", err)
-		}
-
-		err = p.Start()
-		if err != nil {
-			return fmt.Errorf("Failed to run: %s %s: %v", command, strings.Join(dnsmasqCmd, " "), err)
-		}
-
-		err = p.Save(shared.VarPath("networks", n.name, "dnsmasq.pid"))
-		if err != nil {
-			// Kill Process if started, but could not save the file
-			err2 := p.Stop()
-			if err != nil {
-				return fmt.Errorf("Could not kill subprocess while handling saving error: %s: %s", err, err2)
-			}
-
-			return fmt.Errorf("Failed to save subprocess details: %s", err)
-		}
-
-		// Spawn DNS forwarder if needed (backgrounded to avoid deadlocks during cluster boot)
-		if dnsClustered {
-			// Create forkdns servers directory
-			if !shared.PathExists(shared.VarPath("networks", n.name, forkdnsServersListPath)) {
-				err = os.MkdirAll(shared.VarPath("networks", n.name, forkdnsServersListPath), 0755)
-				if err != nil {
-					return err
-				}
-			}
-
-			// Create forkdns servers.conf file if doesn't exist
-			f, err := os.OpenFile(shared.VarPath("networks", n.name, forkdnsServersListPath+"/"+forkdnsServersListFile), os.O_RDONLY|os.O_CREATE, 0666)
-			if err != nil {
-				return err
-			}
-			f.Close()
-
-			err = n.spawnForkDNS(dnsClusteredAddress)
-			if err != nil {
-				return err
-			}
-		}
-	} else {
-		// Clean up old dnsmasq config if exists and we are not starting dnsmasq.
-		leasesPath := shared.VarPath("networks", n.name, "dnsmasq.leases")
-		if shared.PathExists(leasesPath) {
-			err := os.Remove(leasesPath)
-			if err != nil {
-				return errors.Wrapf(err, "Failed to remove old dnsmasq leases file '%s'", leasesPath)
-			}
-		}
-
-		// And same for our PID file.
-		pidPath := shared.VarPath("networks", n.name, "dnsmasq.pid")
-		if shared.PathExists(pidPath) {
-			err := os.Remove(pidPath)
-			if err != nil {
-				return errors.Wrapf(err, "Failed to remove old dnsmasq pid file '%s'", pidPath)
-			}
-		}
-	}
-
-	return nil
-}
-
-func (n *network) Stop() error {
-	if !n.IsRunning() {
-		return fmt.Errorf("The network is already stopped")
-	}
-
-	// Destroy the bridge interface
-	if n.config["bridge.driver"] == "openvswitch" {
-		_, err := shared.RunCommand("ovs-vsctl", "del-br", n.name)
-		if err != nil {
-			return err
-		}
-	} else {
-		_, err := shared.RunCommand("ip", "link", "del", "dev", n.name)
-		if err != nil {
-			return err
-		}
-	}
-
-	// Cleanup iptables
-	if n.config["ipv4.firewall"] == "" || shared.IsTrue(n.config["ipv4.firewall"]) {
-		err := n.state.Firewall.NetworkClear(firewallConsts.FamilyIPv4, firewallConsts.TableAll, n.name)
-		if err != nil {
-			return err
-		}
-
-		err = n.state.Firewall.NetworkClear(firewallConsts.FamilyIPv4, firewallConsts.TableMangle, n.name)
-		if err != nil {
-			return err
-		}
-	}
-
-	if shared.IsTrue(n.config["ipv4.nat"]) {
-		err := n.state.Firewall.NetworkClear(firewallConsts.FamilyIPv4, firewallConsts.TableNat, n.name)
-		if err != nil {
-			return err
-		}
-	}
-
-	if n.config["ipv6.firewall"] == "" || shared.IsTrue(n.config["ipv6.firewall"]) {
-		err := n.state.Firewall.NetworkClear(firewallConsts.FamilyIPv6, firewallConsts.TableAll, n.name)
-		if err != nil {
-			return err
-		}
-	}
-
-	if shared.IsTrue(n.config["ipv6.nat"]) {
-		err := n.state.Firewall.NetworkClear(firewallConsts.FamilyIPv6, firewallConsts.TableNat, n.name)
-		if err != nil {
-			return err
-		}
-	}
-
-	// Kill any existing dnsmasq and forkdns daemon for this network
-	err := dnsmasq.Kill(n.name, false)
-	if err != nil {
-		return err
-	}
-
-	err = networkKillForkDNS(n.name)
-	if err != nil {
-		return err
-	}
-
-	// Get a list of interfaces
-	ifaces, err := net.Interfaces()
-	if err != nil {
-		return err
-	}
-
-	// Cleanup any existing tunnel device
-	for _, iface := range ifaces {
-		if strings.HasPrefix(iface.Name, fmt.Sprintf("%s-", n.name)) {
-			_, err = shared.RunCommand("ip", "link", "del", "dev", iface.Name)
-			if err != nil {
-				return err
-			}
-		}
-	}
-
-	return nil
-}
-
-func (n *network) Update(newNetwork api.NetworkPut, notify bool) error {
-	err := networkFillAuto(newNetwork.Config)
-	if err != nil {
-		return err
-	}
-	newConfig := newNetwork.Config
-
-	// Backup the current state
-	oldConfig := map[string]string{}
-	oldDescription := n.description
-	err = shared.DeepCopy(&n.config, &oldConfig)
-	if err != nil {
-		return err
-	}
-
-	// Define a function which reverts everything.  Defer this function
-	// so that it doesn't need to be explicitly called in every failing
-	// return path.  Track whether or not we want to undo the changes
-	// using a closure.
-	undoChanges := true
-	defer func() {
-		if undoChanges {
-			// Revert changes to the struct
-			n.config = oldConfig
-			n.description = oldDescription
-
-			// Update the database
-			n.state.Cluster.NetworkUpdate(n.name, n.description, n.config)
-
-			// Reset any change that was made to the bridge
-			n.Setup(newConfig)
-		}
-	}()
-
-	// Diff the configurations
-	changedConfig := []string{}
-	userOnly := true
-	for key := range oldConfig {
-		if oldConfig[key] != newConfig[key] {
-			if !strings.HasPrefix(key, "user.") {
-				userOnly = false
-			}
-
-			if !shared.StringInSlice(key, changedConfig) {
-				changedConfig = append(changedConfig, key)
-			}
-		}
-	}
-
-	for key := range newConfig {
-		if oldConfig[key] != newConfig[key] {
-			if !strings.HasPrefix(key, "user.") {
-				userOnly = false
-			}
-
-			if !shared.StringInSlice(key, changedConfig) {
-				changedConfig = append(changedConfig, key)
-			}
-		}
-	}
-
-	// Skip on no change
-	if len(changedConfig) == 0 && newNetwork.Description == n.description {
-		return nil
-	}
-
-	// Update the network
-	if !userOnly {
-		if shared.StringInSlice("bridge.driver", changedConfig) && n.IsRunning() {
-			err = n.Stop()
-			if err != nil {
-				return err
-			}
-		}
-
-		if shared.StringInSlice("bridge.external_interfaces", changedConfig) && n.IsRunning() {
-			devices := []string{}
-			for _, dev := range strings.Split(newConfig["bridge.external_interfaces"], ",") {
-				dev = strings.TrimSpace(dev)
-				devices = append(devices, dev)
-			}
-
-			for _, dev := range strings.Split(oldConfig["bridge.external_interfaces"], ",") {
-				dev = strings.TrimSpace(dev)
-				if dev == "" {
-					continue
-				}
-
-				if !shared.StringInSlice(dev, devices) && shared.PathExists(fmt.Sprintf("/sys/class/net/%s", dev)) {
-					err = networkDetachInterface(n.name, dev)
-					if err != nil {
-						return err
-					}
-				}
-			}
-		}
-	}
-
-	// Apply changes
-	n.config = newConfig
-	n.description = newNetwork.Description
-
-	// Update the database
-	if !notify {
-		// Notify all other nodes to update the network.
-		notifier, err := cluster.NewNotifier(n.state, n.state.Endpoints.NetworkCert(), cluster.NotifyAll)
-		if err != nil {
-			return err
-		}
-
-		err = notifier(func(client lxd.InstanceServer) error {
-			return client.UpdateNetwork(n.name, newNetwork, "")
-		})
-		if err != nil {
-			return err
-		}
-
-		// Update the database.
-		err = n.state.Cluster.NetworkUpdate(n.name, n.description, n.config)
-		if err != nil {
-			return err
-		}
-	}
-
-	// Restart the network
-	if !userOnly {
-		err = n.Setup(oldConfig)
-		if err != nil {
-			return err
-		}
-	}
-
-	// Success, update the closure to mark that the changes should be kept.
-	undoChanges = false
-
-	return nil
-}
-
-func (n *network) spawnForkDNS(listenAddress string) error {
-	// Setup the dnsmasq domain
-	dnsDomain := n.config["dns.domain"]
-	if dnsDomain == "" {
-		dnsDomain = "lxd"
-	}
-
-	// Spawn the daemon using subprocess
-	command := n.state.OS.ExecPath
-	forkdnsargs := []string{"forkdns",
-		fmt.Sprintf("%s:1053", listenAddress),
-		dnsDomain,
-		n.name}
-
-	logPath := shared.LogPath(fmt.Sprintf("forkdns.%s.log", n.name))
-
-	p, err := subprocess.NewProcess(command, forkdnsargs, logPath, logPath)
-	if err != nil {
-		return fmt.Errorf("Failed to create subprocess: %s", err)
-	}
-
-	err = p.Start()
-	if err != nil {
-		return fmt.Errorf("Failed to run: %s %s: %v", command, strings.Join(forkdnsargs, " "), err)
-	}
-
-	err = p.Save(shared.VarPath("networks", n.name, "forkdns.pid"))
-	if err != nil {
-		// Kill Process if started, but could not save the file
-		err2 := p.Stop()
-		if err != nil {
-			return fmt.Errorf("Could not kill subprocess while handling saving error: %s: %s", err, err2)
-		}
-
-		return fmt.Errorf("Failed to save subprocess details: %s", err)
-	}
-
-	return nil
-}
-
-// refreshForkdnsServerAddresses retrieves the IPv4 address of each cluster node (excluding ourselves)
-// for this network. It then updates the forkdns server list file if there are changes.
-func (n *network) refreshForkdnsServerAddresses(heartbeatData *cluster.APIHeartbeat) error {
-	addresses := []string{}
-	localAddress, err := node.HTTPSAddress(n.state.Node)
-	if err != nil {
-		return err
-	}
-
-	logger.Infof("Refreshing forkdns peers for %v", n.name)
-
-	cert := n.state.Endpoints.NetworkCert()
-	for _, node := range heartbeatData.Members {
-		if node.Address == localAddress {
-			// No need to query ourselves.
-			continue
-		}
-
-		client, err := cluster.Connect(node.Address, cert, true)
-		if err != nil {
-			return err
-		}
-
-		state, err := client.GetNetworkState(n.name)
-		if err != nil {
-			return err
-		}
-
-		for _, addr := range state.Addresses {
-			// Only get IPv4 addresses of nodes on network.
-			if addr.Family != "inet" || addr.Scope != "global" {
-				continue
-			}
-
-			addresses = append(addresses, addr.Address)
-			break
-		}
-	}
-
-	// Compare current stored list to retrieved list and see if we need to update.
-	curList, err := networksGetForkdnsServersList(n.name)
-	if err != nil {
-		// Only warn here, but continue on to regenerate the servers list from cluster info.
-		logger.Warnf("Failed to load existing forkdns server list: %v", err)
-	}
-
-	// If current list is same as cluster list, nothing to do.
-	if err == nil && reflect.DeepEqual(curList, addresses) {
-		return nil
-	}
-
-	err = networkUpdateForkdnsServersFile(n.name, addresses)
-	if err != nil {
-		return err
-	}
-
-	logger.Infof("Updated forkdns server list for '%s': %v", n.name, addresses)
-	return nil
-}

From 765c23ed262975ab90cd8733b375d4964eb38b99 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jan 2020 16:44:25 +0000
Subject: [PATCH 20/30] lxd/device: networkCreateVlanDeviceIfNeeded and
 networkRandomDevName usage

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/nic_ipvlan.go   | 2 +-
 lxd/device/nic_macvlan.go  | 4 ++--
 lxd/device/nic_p2p.go      | 4 ++--
 lxd/device/nic_physical.go | 2 +-
 lxd/device/nic_routed.go   | 4 ++--
 5 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/lxd/device/nic_ipvlan.go b/lxd/device/nic_ipvlan.go
index 92ae30174e..7dd9b212d0 100644
--- a/lxd/device/nic_ipvlan.go
+++ b/lxd/device/nic_ipvlan.go
@@ -124,7 +124,7 @@ func (d *nicIPVLAN) Start() (*deviceConfig.RunConfig, error) {
 	// Decide which parent we should use based on VLAN setting.
 	parentName := NetworkGetHostDevice(d.config["parent"], d.config["vlan"])
 
-	statusDev, err := NetworkCreateVlanDeviceIfNeeded(d.state, d.config["parent"], parentName, d.config["vlan"])
+	statusDev, err := networkCreateVlanDeviceIfNeeded(d.state, d.config["parent"], parentName, d.config["vlan"])
 	if err != nil {
 		return nil, err
 	}
diff --git a/lxd/device/nic_macvlan.go b/lxd/device/nic_macvlan.go
index 46d67dd429..91495e3a93 100644
--- a/lxd/device/nic_macvlan.go
+++ b/lxd/device/nic_macvlan.go
@@ -71,10 +71,10 @@ func (d *nicMACVLAN) Start() (*deviceConfig.RunConfig, error) {
 	actualParentName := NetworkGetHostDevice(d.config["parent"], d.config["vlan"])
 
 	// Record the temporary device name used for deletion later.
-	saveData["host_name"] = NetworkRandomDevName("mac")
+	saveData["host_name"] = networkRandomDevName("mac")
 
 	// Create VLAN parent device if needed.
-	statusDev, err := NetworkCreateVlanDeviceIfNeeded(d.state, d.config["parent"], actualParentName, d.config["vlan"])
+	statusDev, err := networkCreateVlanDeviceIfNeeded(d.state, d.config["parent"], actualParentName, d.config["vlan"])
 	if err != nil {
 		return nil, err
 	}
diff --git a/lxd/device/nic_p2p.go b/lxd/device/nic_p2p.go
index e0882a8b27..ea2e2bae4c 100644
--- a/lxd/device/nic_p2p.go
+++ b/lxd/device/nic_p2p.go
@@ -69,12 +69,12 @@ func (d *nicP2P) Start() (*deviceConfig.RunConfig, error) {
 	// Create veth pair and configure the peer end with custom hwaddr and mtu if supplied.
 	if d.inst.Type() == instancetype.Container {
 		if saveData["host_name"] == "" {
-			saveData["host_name"] = NetworkRandomDevName("veth")
+			saveData["host_name"] = networkRandomDevName("veth")
 		}
 		peerName, err = networkCreateVethPair(saveData["host_name"], d.config)
 	} else if d.inst.Type() == instancetype.VM {
 		if saveData["host_name"] == "" {
-			saveData["host_name"] = NetworkRandomDevName("tap")
+			saveData["host_name"] = networkRandomDevName("tap")
 		}
 		peerName = saveData["host_name"] // VMs use the host_name to link to the TAP FD.
 		err = networkCreateTap(saveData["host_name"], d.config)
diff --git a/lxd/device/nic_physical.go b/lxd/device/nic_physical.go
index f8627373d7..c44ab3f6e1 100644
--- a/lxd/device/nic_physical.go
+++ b/lxd/device/nic_physical.go
@@ -87,7 +87,7 @@ func (d *nicPhysical) Start() (*deviceConfig.RunConfig, error) {
 	saveData["host_name"] = NetworkGetHostDevice(d.config["parent"], d.config["vlan"])
 
 	if d.inst.Type() == instancetype.Container {
-		statusDev, err := NetworkCreateVlanDeviceIfNeeded(d.state, d.config["parent"], saveData["host_name"], d.config["vlan"])
+		statusDev, err := networkCreateVlanDeviceIfNeeded(d.state, d.config["parent"], saveData["host_name"], d.config["vlan"])
 		if err != nil {
 			return nil, err
 		}
diff --git a/lxd/device/nic_routed.go b/lxd/device/nic_routed.go
index 6198c1fb9c..416111ce67 100644
--- a/lxd/device/nic_routed.go
+++ b/lxd/device/nic_routed.go
@@ -157,7 +157,7 @@ func (d *nicRouted) Start() (*deviceConfig.RunConfig, error) {
 	if d.config["parent"] != "" {
 		parentName = NetworkGetHostDevice(d.config["parent"], d.config["vlan"])
 
-		statusDev, err := NetworkCreateVlanDeviceIfNeeded(d.state, d.config["parent"], parentName, d.config["vlan"])
+		statusDev, err := networkCreateVlanDeviceIfNeeded(d.state, d.config["parent"], parentName, d.config["vlan"])
 		if err != nil {
 			return nil, err
 		}
@@ -176,7 +176,7 @@ func (d *nicRouted) Start() (*deviceConfig.RunConfig, error) {
 
 	hostName := d.config["host_name"]
 	if hostName == "" {
-		hostName = NetworkRandomDevName("veth")
+		hostName = networkRandomDevName("veth")
 	}
 	saveData["host_name"] = hostName
 

From d47c793e91c1ca9ccecd80a1183257130707cbfc Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jan 2020 16:41:44 +0000
Subject: [PATCH 21/30] lxd: network package usage

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/nic_ipvlan.go            |  5 +++--
 lxd/device/nic_macvlan.go           |  5 +++--
 lxd/device/nic_physical.go          |  5 +++--
 lxd/device/nic_routed.go            |  5 +++--
 lxd/instance/drivers/driver_qemu.go |  3 ++-
 lxd/main_forkdns.go                 | 10 ++++------
 lxd/main_init_interactive.go        | 13 +++++++------
 7 files changed, 25 insertions(+), 21 deletions(-)

diff --git a/lxd/device/nic_ipvlan.go b/lxd/device/nic_ipvlan.go
index 7dd9b212d0..74a0f8c345 100644
--- a/lxd/device/nic_ipvlan.go
+++ b/lxd/device/nic_ipvlan.go
@@ -7,6 +7,7 @@ import (
 	deviceConfig "github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/instance/instancetype"
+	"github.com/lxc/lxd/lxd/network"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 )
@@ -122,7 +123,7 @@ func (d *nicIPVLAN) Start() (*deviceConfig.RunConfig, error) {
 	saveData := make(map[string]string)
 
 	// Decide which parent we should use based on VLAN setting.
-	parentName := NetworkGetHostDevice(d.config["parent"], d.config["vlan"])
+	parentName := network.GetHostDevice(d.config["parent"], d.config["vlan"])
 
 	statusDev, err := networkCreateVlanDeviceIfNeeded(d.state, d.config["parent"], parentName, d.config["vlan"])
 	if err != nil {
@@ -232,7 +233,7 @@ func (d *nicIPVLAN) postStop() error {
 
 	// This will delete the parent interface if we created it for VLAN parent.
 	if shared.IsTrue(v["last_state.created"]) {
-		parentName := NetworkGetHostDevice(d.config["parent"], d.config["vlan"])
+		parentName := network.GetHostDevice(d.config["parent"], d.config["vlan"])
 		err := networkRemoveInterfaceIfNeeded(d.state, parentName, d.inst, d.config["parent"], d.config["vlan"])
 		if err != nil {
 			return err
diff --git a/lxd/device/nic_macvlan.go b/lxd/device/nic_macvlan.go
index 91495e3a93..6050e763d5 100644
--- a/lxd/device/nic_macvlan.go
+++ b/lxd/device/nic_macvlan.go
@@ -6,6 +6,7 @@ import (
 	deviceConfig "github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/instance/instancetype"
+	"github.com/lxc/lxd/lxd/network"
 	"github.com/lxc/lxd/lxd/revert"
 	"github.com/lxc/lxd/shared"
 )
@@ -68,7 +69,7 @@ func (d *nicMACVLAN) Start() (*deviceConfig.RunConfig, error) {
 	saveData := make(map[string]string)
 
 	// Decide which parent we should use based on VLAN setting.
-	actualParentName := NetworkGetHostDevice(d.config["parent"], d.config["vlan"])
+	actualParentName := network.GetHostDevice(d.config["parent"], d.config["vlan"])
 
 	// Record the temporary device name used for deletion later.
 	saveData["host_name"] = networkRandomDevName("mac")
@@ -188,7 +189,7 @@ func (d *nicMACVLAN) postStop() error {
 
 	// This will delete the parent interface if we created it for VLAN parent.
 	if shared.IsTrue(v["last_state.created"]) {
-		actualParentName := NetworkGetHostDevice(d.config["parent"], d.config["vlan"])
+		actualParentName := network.GetHostDevice(d.config["parent"], d.config["vlan"])
 		err := networkRemoveInterfaceIfNeeded(d.state, actualParentName, d.inst, d.config["parent"], d.config["vlan"])
 		if err != nil {
 			errs = append(errs, err)
diff --git a/lxd/device/nic_physical.go b/lxd/device/nic_physical.go
index c44ab3f6e1..1205baf9e8 100644
--- a/lxd/device/nic_physical.go
+++ b/lxd/device/nic_physical.go
@@ -8,6 +8,7 @@ import (
 	deviceConfig "github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/instance/instancetype"
+	"github.com/lxc/lxd/lxd/network"
 	"github.com/lxc/lxd/lxd/revert"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
@@ -84,7 +85,7 @@ func (d *nicPhysical) Start() (*deviceConfig.RunConfig, error) {
 	}
 
 	// Record the host_name device used for restoration later.
-	saveData["host_name"] = NetworkGetHostDevice(d.config["parent"], d.config["vlan"])
+	saveData["host_name"] = network.GetHostDevice(d.config["parent"], d.config["vlan"])
 
 	if d.inst.Type() == instancetype.Container {
 		statusDev, err := networkCreateVlanDeviceIfNeeded(d.state, d.config["parent"], saveData["host_name"], d.config["vlan"])
@@ -238,7 +239,7 @@ func (d *nicPhysical) postStop() error {
 			return err
 		}
 	} else if d.inst.Type() == instancetype.Container {
-		hostName := NetworkGetHostDevice(d.config["parent"], d.config["vlan"])
+		hostName := network.GetHostDevice(d.config["parent"], d.config["vlan"])
 
 		// This will delete the parent interface if we created it for VLAN parent.
 		if shared.IsTrue(v["last_state.created"]) {
diff --git a/lxd/device/nic_routed.go b/lxd/device/nic_routed.go
index 416111ce67..8cca5851d9 100644
--- a/lxd/device/nic_routed.go
+++ b/lxd/device/nic_routed.go
@@ -7,6 +7,7 @@ import (
 	deviceConfig "github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/instance/instancetype"
+	"github.com/lxc/lxd/lxd/network"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 )
@@ -155,7 +156,7 @@ func (d *nicRouted) Start() (*deviceConfig.RunConfig, error) {
 	// Decide which parent we should use based on VLAN setting.
 	parentName := ""
 	if d.config["parent"] != "" {
-		parentName = NetworkGetHostDevice(d.config["parent"], d.config["vlan"])
+		parentName = network.GetHostDevice(d.config["parent"], d.config["vlan"])
 
 		statusDev, err := networkCreateVlanDeviceIfNeeded(d.state, d.config["parent"], parentName, d.config["vlan"])
 		if err != nil {
@@ -310,7 +311,7 @@ func (d *nicRouted) postStop() error {
 
 	// This will delete the parent interface if we created it for VLAN parent.
 	if shared.IsTrue(v["last_state.created"]) {
-		parentName := NetworkGetHostDevice(d.config["parent"], d.config["vlan"])
+		parentName := network.GetHostDevice(d.config["parent"], d.config["vlan"])
 		err := networkRemoveInterfaceIfNeeded(d.state, parentName, d.inst, d.config["parent"], d.config["vlan"])
 		if err != nil {
 			return err
diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go
index 94403192d2..341304ec16 100644
--- a/lxd/instance/drivers/driver_qemu.go
+++ b/lxd/instance/drivers/driver_qemu.go
@@ -34,6 +34,7 @@ import (
 	"github.com/lxc/lxd/lxd/instance/drivers/qmp"
 	"github.com/lxc/lxd/lxd/instance/operationlock"
 	"github.com/lxc/lxd/lxd/maas"
+	"github.com/lxc/lxd/lxd/network"
 	"github.com/lxc/lxd/lxd/operations"
 	"github.com/lxc/lxd/lxd/project"
 	"github.com/lxc/lxd/lxd/revert"
@@ -1938,7 +1939,7 @@ func (vm *qemu) Rename(newName string) error {
 	vm.name = newName
 
 	// Update lease files.
-	instance.NetworkUpdateStatic(vm.state, "")
+	network.UpdateDNSMasqStatic(vm.state, "")
 
 	logger.Info("Renamed instance", ctxMap)
 
diff --git a/lxd/main_forkdns.go b/lxd/main_forkdns.go
index f355d30a50..69406b7ff4 100644
--- a/lxd/main_forkdns.go
+++ b/lxd/main_forkdns.go
@@ -14,6 +14,7 @@ import (
 	"github.com/spf13/cobra"
 	"gopkg.in/fsnotify.v0"
 
+	"github.com/lxc/lxd/lxd/network"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/dnsutil"
 	"github.com/lxc/lxd/shared/logger"
@@ -29,9 +30,6 @@ type dnsHandler struct {
 	leaseFile string
 }
 
-const forkdnsServersListPath = "forkdns.servers"
-const forkdnsServersListFile = "servers.conf"
-
 var dnsServersFileLock sync.Mutex
 var dnsServersList []string
 
@@ -47,7 +45,7 @@ func serversFileMonitor(watcher *fsnotify.Watcher, networkName string) {
 		select {
 		case ev := <-watcher.Event:
 			// Ignore files events that dont concern the servers list file.
-			if !strings.HasSuffix(ev.Name, forkdnsServersListPath+"/"+forkdnsServersListFile) {
+			if !strings.HasSuffix(ev.Name, network.ForkdnsServersListPath+"/"+network.ForkdnsServersListFile) {
 				continue
 			}
 			err := loadServersList(networkName)
@@ -63,7 +61,7 @@ func serversFileMonitor(watcher *fsnotify.Watcher, networkName string) {
 
 // loadServersList reads the server list path and updates the internal servers list slice.
 func loadServersList(networkName string) error {
-	servers, err := networksGetForkdnsServersList(networkName)
+	servers, err := network.ForkdnsServersList(networkName)
 	if err != nil {
 		return err
 	}
@@ -353,7 +351,7 @@ func (c *cmdForkDNS) Run(cmd *cobra.Command, args []string) error {
 	}
 
 	networkName := args[2]
-	path := shared.VarPath("networks", networkName, forkdnsServersListPath)
+	path := shared.VarPath("networks", networkName, network.ForkdnsServersListPath)
 	err = watcher.Watch(path)
 	if err != nil {
 		return fmt.Errorf("Unable to setup fsnotify watch on %s: %s", path, err)
diff --git a/lxd/main_init_interactive.go b/lxd/main_init_interactive.go
index e93b575c5d..9845e58eeb 100644
--- a/lxd/main_init_interactive.go
+++ b/lxd/main_init_interactive.go
@@ -17,6 +17,7 @@ import (
 
 	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/lxd/cluster"
+	"github.com/lxc/lxd/lxd/network"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
@@ -284,20 +285,20 @@ func (c *cmdInit) askNetworking(config *cmdInitData, d lxd.InstanceServer) error
 			}
 		} else if config.Cluster != nil && fanKernel && cli.AskBool("Would you like to create a new Fan overlay network? (yes/no) [default=yes]: ", "yes") {
 			// Define the network
-			network := api.NetworksPost{}
-			network.Name = "lxdfan0"
-			network.Config = map[string]string{
+			networkPost := api.NetworksPost{}
+			networkPost.Name = "lxdfan0"
+			networkPost.Config = map[string]string{
 				"bridge.mode": "fan",
 			}
 
 			// Select the underlay
-			network.Config["fan.underlay_subnet"] = cli.AskString("What subnet should be used as the Fan underlay? [default=auto]: ", "auto", func(value string) error {
+			networkPost.Config["fan.underlay_subnet"] = cli.AskString("What subnet should be used as the Fan underlay? [default=auto]: ", "auto", func(value string) error {
 				var err error
 				var subnet *net.IPNet
 
 				// Handle auto
 				if value == "auto" {
-					subnet, _, err = networkDefaultGatewaySubnetV4()
+					subnet, _, err = network.DefaultGatewaySubnetV4()
 					if err != nil {
 						return err
 					}
@@ -317,7 +318,7 @@ func (c *cmdInit) askNetworking(config *cmdInitData, d lxd.InstanceServer) error
 			})
 
 			// Add the new network
-			config.Node.Networks = append(config.Node.Networks, network)
+			config.Node.Networks = append(config.Node.Networks, networkPost)
 
 			// Add to the default profile
 			config.Node.Profiles[0].Devices["eth0"] = map[string]string{

From 3a3f172b84b7ed092075a29473d9eaa35319259f Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jan 2020 17:24:20 +0000
Subject: [PATCH 22/30] test: static analysis of network pkg

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 test/suites/static_analysis.sh | 1 +
 1 file changed, 1 insertion(+)

diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh
index 291918e640..ecebf149d6 100644
--- a/test/suites/static_analysis.sh
+++ b/test/suites/static_analysis.sh
@@ -104,6 +104,7 @@ test_static_analysis() {
       golint -set_exit_status lxd/apparmor/...
       golint -set_exit_status lxd/daemon/...
       golint -set_exit_status lxd/rsync/...
+      golint -set_exit_status lxd/network/...
 
       golint -set_exit_status shared/api/
       golint -set_exit_status shared/cancel/

From 0d2b8bf075c4dc36ec7a5886e718a79c7a4a47c4 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jan 2020 17:45:48 +0000
Subject: [PATCH 23/30] lxd/instance/drivers/driver/qemu:
 network.GetLeaseAddresses usage

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

diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go
index 341304ec16..7929990120 100644
--- a/lxd/instance/drivers/driver_qemu.go
+++ b/lxd/instance/drivers/driver_qemu.go
@@ -3105,7 +3105,7 @@ func (vm *qemu) RenderState() (*api.InstanceState, error) {
 				}
 
 				// Parse the lease file.
-				addresses, err := instance.NetworkGetLeaseAddresses(vm.state, m["parent"], m["hwaddr"])
+				addresses, err := network.GetLeaseAddresses(vm.state, m["parent"], m["hwaddr"])
 				if err != nil {
 					return nil, err
 				}

From 061de0afda0b5bae0763ff0989cb67089d7b7ae3 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jan 2020 17:46:06 +0000
Subject: [PATCH 24/30] lxd/instance/instance/utils: Removes linked function
 NetworkGetLeaseAddresses var

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

diff --git a/lxd/instance/instance_utils.go b/lxd/instance/instance_utils.go
index bf69bb5baf..7dcc748da9 100644
--- a/lxd/instance/instance_utils.go
+++ b/lxd/instance/instance_utils.go
@@ -41,10 +41,6 @@ var Load func(s *state.State, args db.InstanceArgs, profiles []api.Profile) (Ins
 // Create is linked from instance/drivers.create to allow difference instance types to be created.
 var Create func(s *state.State, args db.InstanceArgs) (Instance, error)
 
-// NetworkGetLeaseAddresses is linked from main.networkGetLeaseAddresses to limit scope of moving
-// network related functions into their own package at this time.
-var NetworkGetLeaseAddresses func(s *state.State, network string, hwaddr string) ([]api.InstanceStateNetworkAddress, error)
-
 // CompareSnapshots returns a list of snapshots to sync to the target and a list of
 // snapshots to remove from the target. A snapshot will be marked as "to sync" if it either doesn't
 // exist in the target or its creation date is different to the source. A snapshot will be marked

From f4391662ee6f0f98a232e8773f7f58b34dedbc43 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jan 2020 17:46:31 +0000
Subject: [PATCH 25/30] lxd/network/network/utils: Adds GetMACSlice and
 GetLeaseAddresses functions

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

diff --git a/lxd/network/network_utils.go b/lxd/network/network_utils.go
index 679e6ab94b..5868310881 100644
--- a/lxd/network/network_utils.go
+++ b/lxd/network/network_utils.go
@@ -675,3 +675,98 @@ func GetHostDevice(parent string, vlan string) string {
 	// Return the default pattern
 	return defaultVlan
 }
+
+// GetLeaseAddresses returns the lease addresses for a network and hwaddr.
+func GetLeaseAddresses(s *state.State, networkName string, hwaddr string) ([]api.InstanceStateNetworkAddress, error) {
+	addresses := []api.InstanceStateNetworkAddress{}
+
+	leaseFile := shared.VarPath("networks", networkName, "dnsmasq.leases")
+	if !shared.PathExists(leaseFile) {
+		return addresses, nil
+	}
+
+	dbInfo, err := LoadByName(s, networkName)
+	if err != nil {
+		return nil, err
+	}
+
+	content, err := ioutil.ReadFile(leaseFile)
+	if err != nil {
+		return nil, err
+	}
+
+	for _, lease := range strings.Split(string(content), "\n") {
+		fields := strings.Fields(lease)
+		if len(fields) < 5 {
+			continue
+		}
+
+		// Parse the MAC
+		mac := GetMACSlice(fields[1])
+		macStr := strings.Join(mac, ":")
+
+		if len(macStr) < 17 && fields[4] != "" {
+			macStr = fields[4][len(fields[4])-17:]
+		}
+
+		if macStr != hwaddr {
+			continue
+		}
+
+		// Parse the IP
+		addr := api.InstanceStateNetworkAddress{
+			Address: fields[2],
+			Scope:   "global",
+		}
+
+		ip := net.ParseIP(addr.Address)
+		if ip == nil {
+			continue
+		}
+
+		if ip.To4() != nil {
+			addr.Family = "inet"
+
+			_, subnet, _ := net.ParseCIDR(dbInfo.Config()["ipv4.address"])
+			if subnet != nil {
+				mask, _ := subnet.Mask.Size()
+				addr.Netmask = fmt.Sprintf("%d", mask)
+			}
+		} else {
+			addr.Family = "inet6"
+
+			_, subnet, _ := net.ParseCIDR(dbInfo.Config()["ipv6.address"])
+			if subnet != nil {
+				mask, _ := subnet.Mask.Size()
+				addr.Netmask = fmt.Sprintf("%d", mask)
+			}
+		}
+
+		addresses = append(addresses, addr)
+	}
+
+	return addresses, nil
+}
+
+// GetMACSlice parses MAC address.
+func GetMACSlice(hwaddr string) []string {
+	var buf []string
+
+	if !strings.Contains(hwaddr, ":") {
+		if s, err := strconv.ParseUint(hwaddr, 10, 64); err == nil {
+			hwaddr = fmt.Sprintln(fmt.Sprintf("%x", s))
+			var tuple string
+			for i, r := range hwaddr {
+				tuple = tuple + string(r)
+				if i > 0 && (i+1)%2 == 0 {
+					buf = append(buf, tuple)
+					tuple = ""
+				}
+			}
+		}
+	} else {
+		buf = strings.Split(strings.ToLower(hwaddr), ":")
+	}
+
+	return buf
+}

From 6a19b2518fe1283b6596057661c5d888f6a506dc Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jan 2020 17:47:01 +0000
Subject: [PATCH 26/30] lxd/networks: Removes networkGetLeaseAddresses
 functions

Moved to network pkg.

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

diff --git a/lxd/networks.go b/lxd/networks.go
index 77aaa7810e..4c71abd70c 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -28,11 +28,6 @@ import (
 	"github.com/lxc/lxd/shared/version"
 )
 
-func init() {
-	// Link networkGetLeaseAddresses into instance package.
-	instance.NetworkGetLeaseAddresses = networkGetLeaseAddresses
-}
-
 // Lock to prevent concurent networks creation
 var networkCreateLock sync.Mutex
 
@@ -767,7 +762,7 @@ func networkLeasesGet(d *Daemon, r *http.Request) response.Response {
 		fields := strings.Fields(lease)
 		if len(fields) >= 5 {
 			// Parse the MAC
-			mac := networkGetMacSlice(fields[1])
+			mac := network.GetMACSlice(fields[1])
 			macStr := strings.Join(mac, ":")
 
 			if len(macStr) < 17 && fields[4] != "" {
@@ -834,77 +829,6 @@ func networkLeasesGet(d *Daemon, r *http.Request) response.Response {
 	return response.SyncResponse(true, leases)
 }
 
-func networkGetLeaseAddresses(s *state.State, networkName string, hwaddr string) ([]api.InstanceStateNetworkAddress, error) {
-	addresses := []api.InstanceStateNetworkAddress{}
-
-	leaseFile := shared.VarPath("networks", networkName, "dnsmasq.leases")
-	if !shared.PathExists(leaseFile) {
-		return addresses, nil
-	}
-
-	dbInfo, err := network.LoadByName(s, networkName)
-	if err != nil {
-		return nil, err
-	}
-
-	content, err := ioutil.ReadFile(leaseFile)
-	if err != nil {
-		return nil, err
-	}
-
-	for _, lease := range strings.Split(string(content), "\n") {
-		fields := strings.Fields(lease)
-		if len(fields) < 5 {
-			continue
-		}
-
-		// Parse the MAC
-		mac := networkGetMacSlice(fields[1])
-		macStr := strings.Join(mac, ":")
-
-		if len(macStr) < 17 && fields[4] != "" {
-			macStr = fields[4][len(fields[4])-17:]
-		}
-
-		if macStr != hwaddr {
-			continue
-		}
-
-		// Parse the IP
-		addr := api.InstanceStateNetworkAddress{
-			Address: fields[2],
-			Scope:   "global",
-		}
-
-		ip := net.ParseIP(addr.Address)
-		if ip == nil {
-			continue
-		}
-
-		if ip.To4() != nil {
-			addr.Family = "inet"
-
-			_, subnet, _ := net.ParseCIDR(dbInfo.Config()["ipv4.address"])
-			if subnet != nil {
-				mask, _ := subnet.Mask.Size()
-				addr.Netmask = fmt.Sprintf("%d", mask)
-			}
-		} else {
-			addr.Family = "inet6"
-
-			_, subnet, _ := net.ParseCIDR(dbInfo.Config()["ipv6.address"])
-			if subnet != nil {
-				mask, _ := subnet.Mask.Size()
-				addr.Netmask = fmt.Sprintf("%d", mask)
-			}
-		}
-
-		addresses = append(addresses, addr)
-	}
-
-	return addresses, nil
-}
-
 func networkStartup(s *state.State) error {
 	// Get a list of managed networks
 	networks, err := s.Cluster.NetworksNotPending()

From aa1a4f8af327068b73f302d3e2b2cdfeb770c5cb Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jan 2020 17:47:20 +0000
Subject: [PATCH 27/30] lxd/networks/utils: Removes networkGetMacSlice function

Moved to network pkg.

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

diff --git a/lxd/networks_utils.go b/lxd/networks_utils.go
index 97124b4b91..52210d4a44 100644
--- a/lxd/networks_utils.go
+++ b/lxd/networks_utils.go
@@ -159,28 +159,6 @@ func networkUpdateForkdnsServersTask(s *state.State, heartbeatData *cluster.APIH
 	return nil
 }
 
-func networkGetMacSlice(hwaddr string) []string {
-	var buf []string
-
-	if !strings.Contains(hwaddr, ":") {
-		if s, err := strconv.ParseUint(hwaddr, 10, 64); err == nil {
-			hwaddr = fmt.Sprintln(fmt.Sprintf("%x", s))
-			var tuple string
-			for i, r := range hwaddr {
-				tuple = tuple + string(r)
-				if i > 0 && (i+1)%2 == 0 {
-					buf = append(buf, tuple)
-					tuple = ""
-				}
-			}
-		}
-	} else {
-		buf = strings.Split(strings.ToLower(hwaddr), ":")
-	}
-
-	return buf
-}
-
 func networkGetState(netIf net.Interface) api.NetworkState {
 	netState := "down"
 	netType := "unknown"

From c45795c318980be40e2cf11b9754ecebca5610b4 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jan 2020 14:02:42 +0000
Subject: [PATCH 28/30] lxd/networks/configs: Adds maas.subnet.ipv[4,6] to
 allowed network keys

So these keys can be inherited by the NIC devices using the "network" property.

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

diff --git a/lxd/networks_config.go b/lxd/networks_config.go
index 8960bea960..26c6b4c7a7 100644
--- a/lxd/networks_config.go
+++ b/lxd/networks_config.go
@@ -102,6 +102,9 @@ var networkConfigKeys = map[string]func(value string) error{
 	},
 
 	"raw.dnsmasq": shared.IsAny,
+
+	"maas.subnet.ipv4": shared.IsAny,
+	"maas.subnet.ipv6": shared.IsAny,
 }
 
 func networkValidateConfig(name string, config map[string]string) error {

From 331cd9467a368ed79c10b7d2d05ce1443fce370d Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jan 2020 13:59:47 +0000
Subject: [PATCH 29/30] lxd/device/nic: Adds network as valid nic property

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

diff --git a/lxd/device/nic.go b/lxd/device/nic.go
index 562ee7a08a..f8b5bd3522 100644
--- a/lxd/device/nic.go
+++ b/lxd/device/nic.go
@@ -31,6 +31,7 @@ func nicValidationRules(requiredFields []string, optionalFields []string) map[st
 	defaultValidators := map[string]func(value string) error{
 		"name":                    shared.IsAny,
 		"parent":                  shared.IsAny,
+		"network":                 shared.IsAny,
 		"mtu":                     shared.IsAny,
 		"vlan":                    shared.IsAny,
 		"hwaddr":                  networkValidMAC,

From d486c9cd12e3f9f7227ed3e2faf96deab0643513 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 31 Jan 2020 16:20:28 +0000
Subject: [PATCH 30/30] lxd/device/nic/bridged: Adds support for network
 property

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

diff --git a/lxd/device/nic_bridged.go b/lxd/device/nic_bridged.go
index b30248fe33..faac6423f5 100644
--- a/lxd/device/nic_bridged.go
+++ b/lxd/device/nic_bridged.go
@@ -17,6 +17,7 @@ import (
 	"github.com/google/gopacket"
 	"github.com/google/gopacket/layers"
 	"github.com/mdlayher/eui64"
+	"github.com/pkg/errors"
 
 	"github.com/lxc/lxd/lxd/db"
 	deviceConfig "github.com/lxc/lxd/lxd/device/config"
@@ -54,9 +55,11 @@ func (d *nicBridged) validateConfig(instConf instance.ConfigReader) error {
 		return ErrUnsupportedDevType
 	}
 
-	requiredFields := []string{"parent"}
+	var requiredFields []string
 	optionalFields := []string{
 		"name",
+		"network",
+		"parent",
 		"mtu",
 		"hwaddr",
 		"host_name",
@@ -74,6 +77,56 @@ func (d *nicBridged) validateConfig(instConf instance.ConfigReader) error {
 		"maas.subnet.ipv6",
 		"boot.priority",
 	}
+
+	// Check that if network proeperty is set that conflicting keys are not present.
+	if d.config["network"] != "" {
+		requiredFields = append(requiredFields, "network")
+
+		bannedKeys := []string{"parent", "mtu", "maas.subnet.ipv4", "maas.subnet.ipv6"}
+		for _, bannedKey := range bannedKeys {
+			if d.config[bannedKey] != "" {
+				return fmt.Errorf("Cannot use %q property in conjunction with %q property", bannedKey, "network")
+			}
+		}
+
+		// If network property is specified, lookup network settings and apply them to the device's config.
+		net, err := network.LoadByName(d.state, d.config["network"])
+		if err != nil {
+			return errors.Wrapf(err, "Error loading network config for %q", d.config["network"])
+		}
+		netConfig := net.Config()
+
+		// Link device to network bridge.
+		d.config["parent"] = d.config["network"]
+
+		// Apply network level config options to device config before validation.
+		if netConfig["bridge.mtu"] != "" {
+			d.config["mtu"] = netConfig["bridge.mtu"]
+		}
+
+		inheritKeys := []string{"maas.subnet.ipv4", "maas.subnet.ipv6"}
+		for _, inheritKey := range inheritKeys {
+			if _, found := netConfig[inheritKey]; found {
+				d.config[inheritKey] = netConfig[inheritKey]
+			}
+		}
+
+		if d.config["ipv4.address"] != "" {
+			// Check that DHCPv4 is enabled on parent network (needed to use static assigned IPs).
+			if net.HasDHCPv4() {
+				return fmt.Errorf("Cannot specify %q when %q is disabled on network %q", "ipv4.address", "ipv4.dhcp", d.config["network"])
+			}
+
+		}
+
+		// Check if static IPs are supplied that they are valid for the linked network.
+
+	} else {
+		// If no network property supplied, then parent property is required.
+		requiredFields = append(requiredFields, "parent")
+	}
+
+	// Now run normal validation.
 	err := d.config.Validate(nicValidationRules(requiredFields, optionalFields))
 	if err != nil {
 		return err


More information about the lxc-devel mailing list