[lxc-devel] [lxd/master] Network: Adds support for external subnets for OVN networks

tomponline on Github lxc-bot at linuxcontainers.org
Fri Oct 16 13:57:55 UTC 2020


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 1031 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20201016/d13b1a1d/attachment-0001.bin>
-------------- next part --------------
From 05044a7c2567cd52534bc48ac61e207363f56556 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 16 Oct 2020 14:31:22 +0100
Subject: [PATCH 1/6] lxd/network/openvswitch/ovn: Adds LogicalSwitchPortGetDNS
 to return switch port DNS info

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

diff --git a/lxd/network/openvswitch/ovn.go b/lxd/network/openvswitch/ovn.go
index bb08c505af..475398d23e 100644
--- a/lxd/network/openvswitch/ovn.go
+++ b/lxd/network/openvswitch/ovn.go
@@ -791,6 +791,41 @@ func (o *OVN) LogicalSwitchPortSetDNS(switchName OVNSwitch, portName OVNSwitchPo
 	return dnsIPv4, dnsIPv6, nil
 }
 
+// LogicalSwitchPortGetDNS returns the logical switch port DNS info (UUID, name and IPs).
+func (o *OVN) LogicalSwitchPortGetDNS(portName OVNSwitchPort) (string, string, []net.IP, error) {
+	// Get UUID and DNS IPs for a switch port in the format: "<DNS UUID>,<DNS NAME>=<IP> <IP>"
+	output, err := o.nbctl("--format=csv", "--no-headings", "--data=bare", "--colum=_uuid,records", "find", "dns",
+		fmt.Sprintf("external_ids:lxd_switch_port=%s", string(portName)),
+	)
+	if err != nil {
+		return "", "", nil, err
+	}
+
+	parts := strings.Split(strings.TrimSpace(output), ",")
+	dnsUUID := strings.TrimSpace(parts[0])
+
+	var dnsName string
+	var ips []net.IP
+
+	// Try and parse the DNS name and IPs.
+	if len(parts) > 1 {
+		dnsParts := strings.SplitN(strings.TrimSpace(parts[1]), "=", 2)
+		if len(dnsParts) == 2 {
+			dnsName = strings.TrimSpace(dnsParts[0])
+			ipParts := strings.Split(dnsParts[1], " ")
+			for _, ipPart := range ipParts {
+				ip := net.ParseIP(strings.TrimSpace(ipPart))
+				if ip != nil {
+					ips = append(ips, ip)
+				}
+			}
+		}
+
+	}
+
+	return dnsUUID, dnsName, ips, nil
+}
+
 // LogicalSwitchPortDeleteDNS removes DNS records for a switch port.
 func (o *OVN) LogicalSwitchPortDeleteDNS(switchName OVNSwitch, portName OVNSwitchPort) error {
 	// Check if existing DNS record exists for switch port.

From 835968cbbdc2e9f104422e93102cea9f8c063408 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 16 Oct 2020 14:30:47 +0100
Subject: [PATCH 2/6] lxd/network/openvswitch/ovn: Updates
 LogicalSwitchPortDeleteDNS to only accept DNS UUID rather than port name

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

diff --git a/lxd/network/openvswitch/ovn.go b/lxd/network/openvswitch/ovn.go
index 475398d23e..b6a05abf18 100644
--- a/lxd/network/openvswitch/ovn.go
+++ b/lxd/network/openvswitch/ovn.go
@@ -827,28 +827,17 @@ func (o *OVN) LogicalSwitchPortGetDNS(portName OVNSwitchPort) (string, string, [
 }
 
 // LogicalSwitchPortDeleteDNS removes DNS records for a switch port.
-func (o *OVN) LogicalSwitchPortDeleteDNS(switchName OVNSwitch, portName OVNSwitchPort) error {
-	// Check if existing DNS record exists for switch port.
-	dnsUUID, err := o.nbctl("--format=csv", "--no-headings", "--data=bare", "--colum=_uuid", "find", "dns",
-		fmt.Sprintf("external_ids:lxd_switch_port=%s", string(portName)),
-	)
+func (o *OVN) LogicalSwitchPortDeleteDNS(switchName OVNSwitch, dnsUUID string) error {
+	// Remove DNS record association from switch.
+	_, err := o.nbctl("remove", "logical_switch", string(switchName), "dns_records", dnsUUID)
 	if err != nil {
 		return err
 	}
 
-	dnsUUID = strings.TrimSpace(dnsUUID)
-	if dnsUUID != "" {
-		// Remove DNS record association from switch.
-		_, err = o.nbctl("remove", "logical_switch", string(switchName), "dns_records", dnsUUID)
-		if err != nil {
-			return err
-		}
-
-		// Remove DNS record entry itself.
-		_, err = o.nbctl("destroy", "dns", dnsUUID)
-		if err != nil {
-			return err
-		}
+	// Remove DNS record entry itself.
+	_, err = o.nbctl("destroy", "dns", dnsUUID)
+	if err != nil {
+		return err
 	}
 
 	return nil

From cf1670c565cb62f9d22348fe81078fcc55fcdf22 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 16 Oct 2020 14:28:49 +0100
Subject: [PATCH 3/6] lxd/network/openvswitch/ovn: Updates
 LogicalSwitchPortSetDNS to return the DNS UUID record ID

Uses for for reverting a record created when LogicalSwitchPortSetDNS is updated to only accept DNS UUID.

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

diff --git a/lxd/network/openvswitch/ovn.go b/lxd/network/openvswitch/ovn.go
index b6a05abf18..a52cc5e595 100644
--- a/lxd/network/openvswitch/ovn.go
+++ b/lxd/network/openvswitch/ovn.go
@@ -681,8 +681,8 @@ func (o *OVN) LogicalSwitchPortDynamicIPs(portName OVNSwitchPort) ([]net.IP, err
 
 // LogicalSwitchPortSetDNS sets up the switch DNS records for the DNS name resolving to the IPs of the switch port.
 // Attempts to find at most one IP for each IP protocol, preferring static addresses over dynamic.
-// Returns the IPv4 and IPv6 addresses used for DNS records.
-func (o *OVN) LogicalSwitchPortSetDNS(switchName OVNSwitch, portName OVNSwitchPort, dnsName string) (net.IP, net.IP, error) {
+// Returns the DNS record UUID, IPv4 and IPv6 addresses used for DNS records.
+func (o *OVN) LogicalSwitchPortSetDNS(switchName OVNSwitch, portName OVNSwitchPort, dnsName string) (string, net.IP, net.IP, error) {
 	var dnsIPv4, dnsIPv6 net.IP
 
 	// checkAndStoreIP checks if the supplied IP is valid and can be used for a missing DNS IP variable.
@@ -705,7 +705,7 @@ func (o *OVN) LogicalSwitchPortSetDNS(switchName OVNSwitch, portName OVNSwitchPo
 	// Get static and dynamic IPs for switch port.
 	staticAddressesRaw, err := o.nbctl("lsp-get-addresses", string(portName))
 	if err != nil {
-		return nil, nil, err
+		return "", nil, nil, err
 	}
 
 	staticAddresses := strings.Split(strings.TrimSpace(staticAddressesRaw), " ")
@@ -729,7 +729,7 @@ func (o *OVN) LogicalSwitchPortSetDNS(switchName OVNSwitch, portName OVNSwitchPo
 	if hasDynamic && (dnsIPv4 == nil || dnsIPv6 == nil) {
 		dynamicIPs, err := o.LogicalSwitchPortDynamicIPs(portName)
 		if err != nil {
-			return nil, nil, err
+			return "", nil, nil, err
 		}
 
 		for _, dynamicIP := range dynamicIPs {
@@ -757,7 +757,7 @@ func (o *OVN) LogicalSwitchPortSetDNS(switchName OVNSwitch, portName OVNSwitchPo
 		fmt.Sprintf("external_ids:lxd_switch_port=%s", string(portName)),
 	)
 	if err != nil {
-		return nil, nil, err
+		return "", nil, nil, err
 	}
 
 	cmdArgs := []string{
@@ -771,13 +771,13 @@ func (o *OVN) LogicalSwitchPortSetDNS(switchName OVNSwitch, portName OVNSwitchPo
 		// Update existing record if exists.
 		_, err = o.nbctl(append([]string{"set", "dns", dnsUUID}, cmdArgs...)...)
 		if err != nil {
-			return nil, nil, err
+			return "", nil, nil, err
 		}
 	} else {
 		// Create new record if needed.
 		dnsUUID, err = o.nbctl(append([]string{"create", "dns"}, cmdArgs...)...)
 		if err != nil {
-			return nil, nil, err
+			return "", nil, nil, err
 		}
 		dnsUUID = strings.TrimSpace(dnsUUID)
 	}
@@ -785,10 +785,10 @@ func (o *OVN) LogicalSwitchPortSetDNS(switchName OVNSwitch, portName OVNSwitchPo
 	// Add DNS record to switch DNS records.
 	_, err = o.nbctl("add", "logical_switch", string(switchName), "dns_records", dnsUUID)
 	if err != nil {
-		return nil, nil, err
+		return "", nil, nil, err
 	}
 
-	return dnsIPv4, dnsIPv6, nil
+	return dnsUUID, dnsIPv4, dnsIPv6, nil
 }
 
 // LogicalSwitchPortGetDNS returns the logical switch port DNS info (UUID, name and IPs).

From 631c1e64f0dd47ac61aaa93b655da9b00f3fc17a Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 16 Oct 2020 14:32:48 +0100
Subject: [PATCH 4/6] lxd/network/driver/ovn: Generates static EUI64 IPv6
 address for instance switch ports in instanceDevicePortAdd

When only static IPv4 addresses have been added to a logical switch port.

This ensures that the switch port has an IPv6 address, as OVN has a limitation that prevents a port from being statically addressed for IPv4 and dynamically allocated for IPv6.

This in turn meant that if using the `ipv4.address` key without an associated `ipv6.address` key, then AAAA DNS record would not be created.

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

diff --git a/lxd/network/driver_ovn.go b/lxd/network/driver_ovn.go
index a6dc816a7a..47f1c0daf9 100644
--- a/lxd/network/driver_ovn.go
+++ b/lxd/network/driver_ovn.go
@@ -1873,6 +1873,30 @@ func (n *ovn) instanceDevicePortAdd(instanceID int, instanceName string, deviceN
 		if err != nil {
 			return "", err
 		}
+
+		// If port isn't going to have fully dynamic IPs allocated by OVN, and instead only static IPv4
+		// addresses have been added, then add an EUI64 static IPv6 address so that the switch port has an
+		// IPv6 address that will be used to generate a DNS record. This works around a limitation in OVN
+		// that prevents us requesting dynamic IPv6 address allocation when static IPv4 allocation is used.
+		if len(ips) > 0 {
+			hasIPv6 := false
+			for _, ip := range ips {
+				if ip.To4() == nil {
+					hasIPv6 = true
+					break
+				}
+			}
+
+			if !hasIPv6 {
+				eui64IP, err := eui64.ParseMAC(routerIntPortIPv6Net.IP, mac)
+				if err != nil {
+					return "", errors.Wrapf(err, "Failed generating EUI64 for instance port %q", mac.String())
+				}
+
+				// Add EUI64 to list of static IPs for instance port.
+				ips = append(ips, eui64IP)
+			}
+		}
 	}
 
 	instancePortName := n.getInstanceDevicePortName(instanceID, deviceName)

From 512fc6bad083c6ba698e8f28b15f44b624bb9ee1 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 16 Oct 2020 14:27:44 +0100
Subject: [PATCH 5/6] lxd/network/driver/ovn: Adds support for publishing
 instance port IPs to uplink network

Uses the IPs in the DNS record for a switch port for publishing.

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

diff --git a/lxd/network/driver_ovn.go b/lxd/network/driver_ovn.go
index 47f1c0daf9..9c41271966 100644
--- a/lxd/network/driver_ovn.go
+++ b/lxd/network/driver_ovn.go
@@ -1922,12 +1922,50 @@ func (n *ovn) instanceDevicePortAdd(instanceID int, instanceName string, deviceN
 		return "", err
 	}
 
-	dnsIPv4, dnsIPv6, err := client.LogicalSwitchPortSetDNS(n.getIntSwitchName(), instancePortName, fmt.Sprintf("%s.%s", instanceName, n.getDomainName()))
+	dnsUUID, dnsIPv4, dnsIPv6, err := client.LogicalSwitchPortSetDNS(n.getIntSwitchName(), instancePortName, fmt.Sprintf("%s.%s", instanceName, n.getDomainName()))
 	if err != nil {
 		return "", err
 	}
 
-	revert.Add(func() { client.LogicalSwitchPortDeleteDNS(n.getIntSwitchName(), instancePortName) })
+	revert.Add(func() { client.LogicalSwitchPortDeleteDNS(n.getIntSwitchName(), dnsUUID) })
+
+	// Parse the network's external routes so we can check if the port's IPs fall within them and should be
+	// published to the uplink network.
+	for _, k := range []string{"ipv4.routes.external", "ipv6.routes.external"} {
+		if n.config[k] == "" {
+			continue
+		}
+
+		var ip net.IP
+
+		// Select the correct destination IP and check that NAT is disabled on the network.
+		if k == "ipv4.routes.external" && !shared.IsTrue(n.config["ipv4.nat"]) {
+			ip = dnsIPv4
+		} else if k == "ipv6.routes.external" && !shared.IsTrue(n.config["ipv6.nat"]) {
+			ip = dnsIPv6
+		}
+
+		if ip == nil {
+			continue //No qualifying target IP to check.
+		}
+
+		netExternalRoutes, err := SubnetParseAppend([]*net.IPNet{}, strings.Split(n.config[k], ",")...)
+		if err != nil {
+			return "", err
+		}
+
+		for _, netExternalRoute := range netExternalRoutes {
+			if netExternalRoute.Contains(ip) {
+				err = client.LogicalRouterDNATSNATAdd(n.getRouterName(), ip, ip, true, true)
+				if err != nil {
+					return "", err
+				}
+
+				revert.Add(func() { client.LogicalRouterDNATSNATDelete(n.getRouterName(), ip) })
+				break // Confirmed IP should be published on uplink, no need to look further.
+			}
+		}
+	}
 
 	// Add each internal route (using the IPs set for DNS as target).
 	for _, internalRoute := range internalRoutes {
@@ -2015,11 +2053,30 @@ func (n *ovn) instanceDevicePortDelete(instanceID int, deviceName string, intern
 		return err
 	}
 
-	err = client.LogicalSwitchPortDeleteDNS(n.getIntSwitchName(), instancePortName)
+	// Delete DNS records.
+	dnsUUID, _, dnsIPs, err := client.LogicalSwitchPortGetDNS(instancePortName)
+	if err != nil {
+		return err
+	}
+
+	err = client.LogicalSwitchPortDeleteDNS(n.getIntSwitchName(), dnsUUID)
 	if err != nil {
 		return err
 	}
 
+	// Delete any associated external IP DNAT rules for the DNS IPs (if NAT disabled).
+	for _, dnsIP := range dnsIPs {
+		isV6 := dnsIP.To4() == nil
+
+		// Atempt to remove any externally published IP rules if the associated IP NAT setting is disabled.
+		if (!isV6 && !shared.IsTrue(n.config["ipv4.nat"])) || (isV6 && !shared.IsTrue(n.config["ipv6.nat"])) {
+			err = client.LogicalRouterDNATSNATDelete(n.getRouterName(), dnsIP)
+			if err != nil {
+				return err
+			}
+		}
+	}
+
 	// Delete each internal route.
 	for _, internalRoute := range internalRoutes {
 		err = client.LogicalRouterRouteDelete(n.getRouterName(), internalRoute, nil)

From 13613e869eb58f30225b93285bbc62f8f71814d7 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 16 Oct 2020 10:18:27 +0100
Subject: [PATCH 6/6] lxd/device/nic/ovn: Improved error messages

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

diff --git a/lxd/device/nic_ovn.go b/lxd/device/nic_ovn.go
index 766adbcdf0..091888ab60 100644
--- a/lxd/device/nic_ovn.go
+++ b/lxd/device/nic_ovn.go
@@ -300,7 +300,7 @@ func (d *nicOVN) Start() (*deviceConfig.RunConfig, error) {
 
 		internalRoutes, err = network.SubnetParseAppend(internalRoutes, strings.Split(d.config[key], ",")...)
 		if err != nil {
-			return nil, errors.Wrapf(err, "Invalid %s", key)
+			return nil, errors.Wrapf(err, "Invalid %q value", key)
 		}
 	}
 
@@ -312,7 +312,7 @@ func (d *nicOVN) Start() (*deviceConfig.RunConfig, error) {
 
 		externalRoutes, err = network.SubnetParseAppend(externalRoutes, strings.Split(d.config[key], ",")...)
 		if err != nil {
-			return nil, errors.Wrapf(err, "Invalid %s", key)
+			return nil, errors.Wrapf(err, "Invalid %q value", key)
 		}
 	}
 
@@ -439,7 +439,7 @@ func (d *nicOVN) Stop() (*deviceConfig.RunConfig, error) {
 
 		internalRoutes, err = network.SubnetParseAppend(internalRoutes, strings.Split(d.config[key], ",")...)
 		if err != nil {
-			return nil, errors.Wrapf(err, "Invalid %s", key)
+			return nil, errors.Wrapf(err, "Invalid %q value", key)
 		}
 	}
 
@@ -451,7 +451,7 @@ func (d *nicOVN) Stop() (*deviceConfig.RunConfig, error) {
 
 		externalRoutes, err = network.SubnetParseAppend(externalRoutes, strings.Split(d.config[key], ",")...)
 		if err != nil {
-			return nil, errors.Wrapf(err, "Invalid %s", key)
+			return nil, errors.Wrapf(err, "Invalid %q value", key)
 		}
 	}
 


More information about the lxc-devel mailing list