[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