[lxc-devel] [lxd/master] Network: Add EUI64 guessing for bridged NIC state

tomponline on Github lxc-bot at linuxcontainers.org
Thu Oct 1 16:50:26 UTC 2020


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 301 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20201001/16a7acf2/attachment.bin>
-------------- next part --------------
From 9a0cf626779c141f59b358963cede5632c988563 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 1 Oct 2020 17:07:39 +0100
Subject: [PATCH 1/3] lxd/network/network/utils: Adds GetNeighbourV6Addresses
 function

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

diff --git a/lxd/network/network_utils.go b/lxd/network/network_utils.go
index c195228a3d..a4ead17f6f 100644
--- a/lxd/network/network_utils.go
+++ b/lxd/network/network_utils.go
@@ -805,6 +805,34 @@ func GetHostDevice(parent string, vlan string) string {
 	return defaultVlan
 }
 
+// GetNeighbourV6Addresses returns the IPv6 addresses in the neighbour cache for a particular interface and MAC.
+func GetNeighbourV6Addresses(interfaceName string, hwaddr string) ([]net.IP, error) {
+	addresses := []net.IP{}
+
+	// Look for neighbour entries for IPv6.
+	out, err := shared.RunCommand("ip", "-6", "neigh", "show", "dev", interfaceName)
+	if err == nil {
+		for _, line := range strings.Split(out, "\n") {
+			// Split fields and early validation.
+			fields := strings.Fields(line)
+			if len(fields) != 4 {
+				continue
+			}
+
+			if fields[2] != hwaddr {
+				continue
+			}
+
+			ip := net.ParseIP(fields[0])
+			if ip != nil {
+				addresses = append(addresses, ip)
+			}
+		}
+	}
+
+	return addresses, nil
+}
+
 // 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{}

From 6ba197ad54e2dff9416ff698cdbbc2c6e836aa1c Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 1 Oct 2020 17:46:42 +0100
Subject: [PATCH 2/3] lxd/network/network/utils: Updates GetLeaseAddresses to
 return only net.IP list

 - Removes IPv6 neighbour look as was out of scope for this function's name.
 - Returns []net.IP rather than []api.InstanceStateNetworkAddress.

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

diff --git a/lxd/network/network_utils.go b/lxd/network/network_utils.go
index a4ead17f6f..96d57465ed 100644
--- a/lxd/network/network_utils.go
+++ b/lxd/network/network_utils.go
@@ -834,48 +834,10 @@ func GetNeighbourV6Addresses(interfaceName string, hwaddr string) ([]net.IP, err
 }
 
 // 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{}
-
-	// Look for neighborhood entries for IPv6.
-	out, err := shared.RunCommand("ip", "-6", "neigh", "show", "dev", networkName)
-	if err == nil {
-		for _, line := range strings.Split(out, "\n") {
-			// Split fields and early validation.
-			fields := strings.Fields(line)
-			if len(fields) != 4 {
-				continue
-			}
-
-			if fields[2] != hwaddr {
-				continue
-			}
-
-			// Prepare the entry.
-			addr := api.InstanceStateNetworkAddress{}
-			addr.Address = fields[0]
-			addr.Family = "inet6"
-
-			if strings.HasPrefix(fields[0], "fe80::") {
-				addr.Scope = "link"
-			} else {
-				addr.Scope = "global"
-			}
-
-			addresses = append(addresses, addr)
-		}
-	}
-
-	// Look for DHCP leases.
+func GetLeaseAddresses(networkName string, hwaddr string) ([]net.IP, error) {
 	leaseFile := shared.VarPath("networks", networkName, "dnsmasq.leases")
 	if !shared.PathExists(leaseFile) {
-		return addresses, nil
-	}
-
-	// Pass project.Default here, as currently dnsmasq (bridged) networks do not support projects.
-	dbInfo, err := LoadByName(s, project.Default, networkName)
-	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("Leases file not found for network %q", networkName)
 	}
 
 	content, err := ioutil.ReadFile(leaseFile)
@@ -883,13 +845,15 @@ func GetLeaseAddresses(s *state.State, networkName string, hwaddr string) ([]api
 		return nil, err
 	}
 
+	addresses := []net.IP{}
+
 	for _, lease := range strings.Split(string(content), "\n") {
 		fields := strings.Fields(lease)
 		if len(fields) < 5 {
 			continue
 		}
 
-		// Parse the MAC
+		// Parse the MAC.
 		mac := GetMACSlice(fields[1])
 		macStr := strings.Join(mac, ":")
 
@@ -901,36 +865,11 @@ func GetLeaseAddresses(s *state.State, networkName string, hwaddr string) ([]api
 			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)
-			}
+		// Parse the IP.
+		ip := net.ParseIP(fields[2])
+		if ip != nil {
+			addresses = append(addresses, ip)
 		}
-
-		addresses = append(addresses, addr)
 	}
 
 	return addresses, nil

From 5d2b5543d6a62aa4d77e4a3a8fedc59765045fc6 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 1 Oct 2020 17:21:54 +0100
Subject: [PATCH 3/3] lxd/device/nic/bridged: Updates State() to return partial
 data

 - Only tries to parse dnsmasq leases file if parent network is managed.
 - If parent network is not managed, no longer treat this as complete failure.
 - Try and find IPv6 addresses from IP neighbour cache using network.GetNeighbourV6Addresses.
 - Load interface MTU using NIC's `host_name` rather than parent interface (should be the same but more consistent with how OVN NIC does it).
 - Also means that if the NIC's host veth interface is missing, then this call will not return any results.
 - If interface is available, will return interface stats even with no IPs found.
 - Guess link-local address netmask size based on IP family.

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

diff --git a/lxd/device/nic_bridged.go b/lxd/device/nic_bridged.go
index f8a255e4d8..47b0d67379 100644
--- a/lxd/device/nic_bridged.go
+++ b/lxd/device/nic_bridged.go
@@ -1091,24 +1091,81 @@ func (d *nicBridged) State() (*api.InstanceStateNetwork, error) {
 	// Populate device config with volatile fields if needed.
 	networkVethFillFromVolatile(d.config, v)
 
-	if d.config["hwaddr"] == "" {
-		return nil, nil
+	ips := []net.IP{}
+
+	// Check if parent is managed network and load config.
+	// Pass project.Default here, as currently dnsmasq (bridged) networks do not support projects.
+	n, err := network.LoadByName(d.state, project.Default, d.config["parent"])
+	if err == nil && d.config["hwaddr"] != "" {
+		// Parse the leases file if parent network is managed.
+		leaseIPs, err := network.GetLeaseAddresses(n.Name(), d.config["hwaddr"])
+		if err == nil {
+			for _, leaseIP := range leaseIPs {
+				ips = append(ips, leaseIP)
+			}
+		}
 	}
 
-	// Parse the leases file.
-	addresses, err := network.GetLeaseAddresses(d.state, d.config["parent"], d.config["hwaddr"])
-	if err != nil {
-		return nil, err
+	// Get IPv6 addresses from IP neighbour cache if present.
+	neighIPs, err := network.GetNeighbourV6Addresses(d.config["parent"], d.config["hwaddr"])
+	if err == nil {
+		for _, neighIP := range neighIPs {
+			ips = append(ips, neighIP)
+		}
+	}
+
+	// Extract subnet sizes from bridge addresses if available.
+	var v4mask string
+	var v6mask string
+
+	if n != nil {
+		netConfig := n.Config()
+		_, v4subnet, _ := net.ParseCIDR(netConfig["ipv4.address"])
+		_, v6subnet, _ := net.ParseCIDR(netConfig["ipv6.address"])
+
+		if v4subnet != nil {
+			mask, _ := v4subnet.Mask.Size()
+			v4mask = fmt.Sprintf("%d", mask)
+		}
+
+		if v6subnet != nil {
+			mask, _ := v6subnet.Mask.Size()
+			v6mask = fmt.Sprintf("%d", mask)
+		}
 	}
 
-	if len(addresses) == 0 {
-		return nil, nil
+	// Convert IPs to InstanceStateNetworkAddresses.
+	addresses := []api.InstanceStateNetworkAddress{}
+	for _, ip := range ips {
+		addr := api.InstanceStateNetworkAddress{}
+		addr.Address = ip.String()
+		addr.Family = "inet"
+		addr.Netmask = v4mask
+
+		if ip.To4() == nil {
+			addr.Family = "inet6"
+			addr.Netmask = v6mask
+		}
+
+		if ip.IsLinkLocalUnicast() {
+			addr.Scope = "link"
+
+			if addr.Family == "inet6" {
+				addr.Netmask = "64" // Link-local IPv6 addresses are /64.
+			} else {
+				addr.Netmask = "16" // Local-local IPv4 addresses are /16.
+			}
+		} else {
+			addr.Scope = "global"
+		}
+
+		addresses = append(addresses, addr)
 	}
 
 	// Get MTU.
-	iface, err := net.InterfaceByName(d.config["parent"])
+	iface, err := net.InterfaceByName(d.config["host_name"])
 	if err != nil {
-		return nil, err
+		return nil, errors.Wrapf(err, "Failed getting host interface state")
 	}
 
 	// Retrieve the host counters, as we report the values from the instance's point of view,


More information about the lxc-devel mailing list