[lxc-devel] [lxd/master] Network: OVN Instance port DNS entries

tomponline on Github lxc-bot at linuxcontainers.org
Wed Aug 19 11:44:24 UTC 2020


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 393 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20200819/3391e489/attachment-0001.bin>
-------------- next part --------------
From fb50fc4a964a1bfdd46b1abff82251a17c9fe0dc Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 19 Aug 2020 09:24:26 +0100
Subject: [PATCH 1/8] doc/networks: Adds link to OVN network

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 doc/networks.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/doc/networks.md b/doc/networks.md
index 2887aad829..c44dca2abf 100644
--- a/doc/networks.md
+++ b/doc/networks.md
@@ -5,6 +5,7 @@ LXD supports the following network types:
  - [bridge](#network-bridge): Creates an L2 bridge for connecting instances to (can provide local DHCP and DNS). This is the default.
  - [macvlan](#network-macvlan): Provides preset configuration to use when connecting instances to a parent macvlan interface.
  - [sriov](#network-sriov): Provides preset configuration to use when connecting instances to a parent SR-IOV interface.
+ - [ovn](#network-ovn): Creates a logical network using the OVN software defined networking system.
 
 The desired type can be specified using the `--type` argument, e.g.
 

From 5398036a31ed17b0a31b13c60454ea80c698a72d Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 19 Aug 2020 09:34:48 +0100
Subject: [PATCH 2/8] lxd/network/network/utils: Adds pingIP function

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

diff --git a/lxd/network/network_utils.go b/lxd/network/network_utils.go
index c8d05367e7..b4757742d3 100644
--- a/lxd/network/network_utils.go
+++ b/lxd/network/network_utils.go
@@ -543,6 +543,22 @@ func inRoutingTable(subnet *net.IPNet) bool {
 	return false
 }
 
+// pingIP sends a single ping packet to the specified IP, returns true if responds, false if not.
+func pingIP(ip net.IP) bool {
+	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 false
+	}
+
+	return true
+}
+
 func pingSubnet(subnet *net.IPNet) bool {
 	var fail bool
 	var failLock sync.Mutex
@@ -551,14 +567,7 @@ func pingSubnet(subnet *net.IPNet) bool {
 	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
+		if !pingIP(ip) {
 			return
 		}
 

From 522cedb58fd94649606a888b2e7e5be8c17a3c9e Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 19 Aug 2020 10:07:12 +0100
Subject: [PATCH 3/8] lxd/network/driver/ovn: Pings OVN external IPv6 router IP
 on bridge port start

This is to workaround an issue in older versions of OVN where the router will not perform neighbour discovery for the IPv6 gateway when using IPv6 SNAT.

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

diff --git a/lxd/network/driver_ovn.go b/lxd/network/driver_ovn.go
index bf7f12b5d8..2beb096fd9 100644
--- a/lxd/network/driver_ovn.go
+++ b/lxd/network/driver_ovn.go
@@ -581,6 +581,31 @@ func (n *ovn) startParentPortBridge(parentNet Network) error {
 		return errors.Wrapf(err, "Failed to associate parent OVS bridge %q to OVN provider %q", vars.ovsBridge, parentNet.Name())
 	}
 
+	routerExtPortIPv6 := net.ParseIP(n.config[ovnVolatileParentIPv6])
+	if routerExtPortIPv6 != nil {
+		// Now that the OVN router is connected to the uplink parent bridge, attempt to ping the OVN
+		// router's external IPv6 from the LXD host running the parent bridge in an attempt to trigger the
+		// OVN router to learn the parent uplink gateway's MAC address. This is to work around a bug in
+		// older versions of OVN that meant that the OVN router would not attempt to learn the external
+		// uplink IPv6 gateway MAC address when using SNAT, meaning that external IPv6 connectivity
+		// wouldn't work until the next router advertisement was sent (which could be several minutes).
+		// By pinging the OVN router's external IP this will trigger an NDP request from the parent bridge
+		// which will cause the OVN router to learn its MAC address.
+		func() {
+			// Try several attempts as it can take a few seconds for the network to come up.
+			for i := 0; i < 5; i++ {
+				if pingIP(routerExtPortIPv6) {
+					n.logger.Debug("OVN router external IPv6 address reachable", log.Ctx{"ip": routerExtPortIPv6.String()})
+					return
+				}
+
+				time.Sleep(time.Second)
+			}
+
+			n.logger.Warn("OVN router external IPv6 address unreachable", log.Ctx{"ip": routerExtPortIPv6.String()})
+		}()
+	}
+
 	revert.Success()
 	return nil
 }

From 8b539a5a09ea25db45e875468215786f4c59462e Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 19 Aug 2020 12:40:21 +0100
Subject: [PATCH 4/8] lxd/network/openvswitch/dns: Adds LogicalSwitchPortSetDNS
 and LogicalSwitchPortDeleteDNS functions

Populates switch port's DNS records based on port's assigned static and dynamic IPs.

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

diff --git a/lxd/network/openvswitch/ovn.go b/lxd/network/openvswitch/ovn.go
index 656ada439d..aab9fb1980 100644
--- a/lxd/network/openvswitch/ovn.go
+++ b/lxd/network/openvswitch/ovn.go
@@ -3,6 +3,7 @@ package openvswitch
 import (
 	"fmt"
 	"net"
+	"strconv"
 	"strings"
 	"time"
 
@@ -542,6 +543,152 @@ func (o *OVN) LogicalSwitchPortSet(portName OVNSwitchPort, opts *OVNSwitchPortOp
 	return nil
 }
 
+// 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.
+func (o *OVN) LogicalSwitchPortSetDNS(switchName OVNSwitch, portName OVNSwitchPort, dnsName string) error {
+	var dnsIPv4, dnsIPv6 net.IP
+
+	// parseAndStoreIP checks if the supplied IP string is valid and can be used for a missing DNS IP variable.
+	// If the found IP is needed, stores into the relevant dnsIPvP{X} variable and returns true.
+	parseAndStoreIP := func(ipRaw string) bool {
+		ip := net.ParseIP(ipRaw)
+		if ip != nil {
+			isV4 := ip.To4() != nil
+			if dnsIPv4 == nil && isV4 {
+				dnsIPv4 = ip
+				return true
+			} else if dnsIPv6 == nil && !isV4 {
+				dnsIPv6 = ip
+				return true
+			}
+		}
+
+		return false
+	}
+
+	// Get static and dynamic IPs for switch port.
+	staticAddressesRaw, err := o.nbctl("lsp-get-addresses", string(portName))
+	if err != nil {
+		return err
+	}
+
+	staticAddresses := strings.Split(strings.TrimSpace(staticAddressesRaw), " ")
+	hasDynamic := false
+	for _, staticAddress := range staticAddresses {
+		// Record that there should be at least one dynamic address (may be a MAC address though).
+		if staticAddress == "dynamic" {
+			hasDynamic = true
+			continue
+		}
+
+		// Try and find the first IPv4 and IPv6 addresses from the static address list.
+		if parseAndStoreIP(staticAddress) {
+			if dnsIPv4 != nil && dnsIPv6 != nil {
+				break // We've found all we wanted.
+			}
+		}
+	}
+
+	// Get dynamic IPs for switch port if indicated and needed.
+	if hasDynamic && (dnsIPv4 == nil || dnsIPv6 == nil) {
+		dynamicAddressesRaw, err := o.nbctl("get", "logical_switch_port", string(portName), "dynamic_addresses")
+		if err != nil {
+			return err
+		}
+
+		dynamicAddressesRaw, err = strconv.Unquote(strings.TrimSpace(dynamicAddressesRaw))
+		if err != nil {
+			return err
+		}
+
+		dynamicAddresses := strings.Split(strings.TrimSpace(dynamicAddressesRaw), " ")
+		for _, dynamicAddress := range dynamicAddresses {
+			// Try and find the first IPv4 and IPv6 addresses from the dynamic address list.
+			if parseAndStoreIP(dynamicAddress) {
+				if dnsIPv4 != nil && dnsIPv6 != nil {
+					break // We've found all we wanted.
+				}
+			}
+		}
+	}
+
+	// Create a list of IPs for the DNS record.
+	dnsIPs := make([]string, 0, 2)
+	if dnsIPv4 != nil {
+		dnsIPs = append(dnsIPs, dnsIPv4.String())
+	}
+
+	if dnsIPv6 != nil {
+		dnsIPs = append(dnsIPs, dnsIPv6.String())
+	}
+
+	// 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)),
+	)
+	if err != nil {
+		return err
+	}
+
+	cmdArgs := []string{
+		fmt.Sprintf(`records={"%s"="%s"}`, dnsName, strings.Join(dnsIPs, " ")),
+		fmt.Sprintf("external_ids:lxd_switch=%s", string(switchName)),
+		fmt.Sprintf("external_ids:lxd_switch_port=%s", string(portName)),
+	}
+
+	dnsUUID = strings.TrimSpace(dnsUUID)
+	if dnsUUID != "" {
+		// Update existing record if exists.
+		_, err = o.nbctl(append([]string{"set", "dns", dnsUUID}, cmdArgs...)...)
+		if err != nil {
+			return err
+		}
+	} else {
+		// Create new record if needed.
+		dnsUUID, err = o.nbctl(append([]string{"create", "dns"}, cmdArgs...)...)
+		if err != nil {
+			return err
+		}
+		dnsUUID = strings.TrimSpace(dnsUUID)
+	}
+
+	// Add DNS record to switch DNS records.
+	_, err = o.nbctl("add", "logical_switch", string(switchName), "dns_records", dnsUUID)
+	if err != nil {
+		return err
+	}
+
+	return 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.
+	dnsUUID, err := o.nbctl("--format=csv", "--no-headings", "--data=bare", "--colum=_uuid", "find", "dns",
+		fmt.Sprintf("external_ids:lxd_switch_port=%s", string(portName)),
+	)
+	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
+		}
+	}
+
+	return nil
+}
+
 // LogicalSwitchPortDelete deletes a named logical switch port.
 func (o *OVN) LogicalSwitchPortDelete(portName OVNSwitchPort) error {
 	_, err := o.nbctl("--if-exists", "lsp-del", string(portName))

From e6cb1ece6c6c8974dfceb7347235b659be4986a6 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 19 Aug 2020 12:41:26 +0100
Subject: [PATCH 5/8] lxd/network/openvswitch/ovn: Updates LogicalSwitchDelete
 to clear any remaining DNS records

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

diff --git a/lxd/network/openvswitch/ovn.go b/lxd/network/openvswitch/ovn.go
index aab9fb1980..e5bb67b062 100644
--- a/lxd/network/openvswitch/ovn.go
+++ b/lxd/network/openvswitch/ovn.go
@@ -268,7 +268,17 @@ func (o *OVN) LogicalSwitchDelete(switchName OVNSwitch) error {
 		return err
 	}
 
-	return o.logicalSwitchDHCPOptionsDelete(switchName)
+	err = o.logicalSwitchDHCPOptionsDelete(switchName)
+	if err != nil {
+		return err
+	}
+
+	err = o.logicalSwitchDNSRecordsDelete(switchName)
+	if err != nil {
+		return err
+	}
+
+	return nil
 }
 
 // LogicalSwitchSetIPAllocation sets the IP allocation config on the logical switch.
@@ -486,6 +496,28 @@ func (o *OVN) logicalSwitchDHCPOptionsDelete(switchName OVNSwitch) error {
 	return nil
 }
 
+// logicalSwitchDNSRecordsDelete deletes any DNS records defined for a switch.
+func (o *OVN) logicalSwitchDNSRecordsDelete(switchName OVNSwitch) error {
+	existingOpts, err := o.nbctl("--format=csv", "--no-headings", "--data=bare", "--colum=_uuid", "find", "dns",
+		fmt.Sprintf("external_ids:lxd_switch=%s", string(switchName)),
+	)
+	if err != nil {
+		return err
+	}
+
+	existingOpts = strings.TrimSpace(existingOpts)
+	if existingOpts != "" {
+		for _, uuid := range strings.Split(existingOpts, "\n") {
+			_, err = o.nbctl("destroy", "dns", uuid)
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	return nil
+}
+
 // LogicalSwitchPortAdd adds a named logical switch port to a logical switch.
 // If mayExist is true, then an existing resource of the same name is not treated as an error.
 func (o *OVN) LogicalSwitchPortAdd(switchName OVNSwitch, portName OVNSwitchPort, mayExist bool) error {

From a4c2ef37b148f91496f48c0d7fca669801f45b44 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 19 Aug 2020 12:41:54 +0100
Subject: [PATCH 6/8] lxd/network/network/utils/ovn: Updates
 OVNInstanceDevicePortAdd to take instanceName for DNS records

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

diff --git a/lxd/network/network_utils_ovn.go b/lxd/network/network_utils_ovn.go
index 03123de306..34ad39b59e 100644
--- a/lxd/network/network_utils_ovn.go
+++ b/lxd/network/network_utils_ovn.go
@@ -9,14 +9,14 @@ import (
 
 // OVNInstanceDevicePortAdd adds a logical port to the OVN network's internal switch and returns the logical
 // port name for use linking an OVS port on the integration bridge to the logical switch port.
-func OVNInstanceDevicePortAdd(network Network, instanceID int, deviceName string, mac net.HardwareAddr, ips []net.IP) (openvswitch.OVNSwitchPort, error) {
+func OVNInstanceDevicePortAdd(network Network, instanceID int, instanceName string, deviceName string, mac net.HardwareAddr, ips []net.IP) (openvswitch.OVNSwitchPort, error) {
 	// Check network is of type OVN.
 	n, ok := network.(*ovn)
 	if !ok {
 		return "", fmt.Errorf("Network is not OVN type")
 	}
 
-	return n.instanceDevicePortAdd(instanceID, deviceName, mac, ips)
+	return n.instanceDevicePortAdd(instanceID, instanceName, deviceName, mac, ips)
 }
 
 // OVNInstanceDevicePortDelete deletes a logical port from the OVN network's internal switch.

From aa22aa7c6ebc90e51423db60eaf5783f364d5bea Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 19 Aug 2020 12:42:22 +0100
Subject: [PATCH 7/8] lxd/network/driver/ovn: Updates instance port functions
 to setup and remove DNS records

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

diff --git a/lxd/network/driver_ovn.go b/lxd/network/driver_ovn.go
index 2beb096fd9..1c6d914674 100644
--- a/lxd/network/driver_ovn.go
+++ b/lxd/network/driver_ovn.go
@@ -1217,7 +1217,7 @@ func (n *ovn) getInstanceDevicePortName(instanceID int, deviceName string) openv
 }
 
 // instanceDevicePortAdd adds an instance device port to the internal logical switch and returns the port name.
-func (n *ovn) instanceDevicePortAdd(instanceID int, deviceName string, mac net.HardwareAddr, ips []net.IP) (openvswitch.OVNSwitchPort, error) {
+func (n *ovn) instanceDevicePortAdd(instanceID int, instanceName string, deviceName string, mac net.HardwareAddr, ips []net.IP) (openvswitch.OVNSwitchPort, error) {
 	var dhcpV4ID, dhcpv6ID string
 
 	revert := revert.New()
@@ -1276,6 +1276,11 @@ func (n *ovn) instanceDevicePortAdd(instanceID int, deviceName string, mac net.H
 		return "", err
 	}
 
+	err = client.LogicalSwitchPortSetDNS(n.getIntSwitchName(), instancePortName, fmt.Sprintf("%s.%s", instanceName, n.getDomainName()))
+	if err != nil {
+		return "", err
+	}
+
 	revert.Success()
 	return instancePortName, nil
 }
@@ -1294,5 +1299,10 @@ func (n *ovn) instanceDevicePortDelete(instanceID int, deviceName string) error
 		return err
 	}
 
+	err = client.LogicalSwitchPortDeleteDNS(n.getIntSwitchName(), instancePortName)
+	if err != nil {
+		return err
+	}
+
 	return nil
 }

From 9e0702680bc66af2312070350615363c22121278 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 19 Aug 2020 12:42:49 +0100
Subject: [PATCH 8/8] lxd/device/nic/ovn: Updates usage of
 network.OVNInstanceDevicePortAdd to supply instance name for DNS records

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

diff --git a/lxd/device/nic_ovn.go b/lxd/device/nic_ovn.go
index 5631b02a97..4b6db95bcd 100644
--- a/lxd/device/nic_ovn.go
+++ b/lxd/device/nic_ovn.go
@@ -233,7 +233,7 @@ func (d *nicOVN) Start() (*deviceConfig.RunConfig, error) {
 	}
 
 	// Add new OVN logical switch port for instance.
-	logicalPortName, err := network.OVNInstanceDevicePortAdd(d.network, d.inst.ID(), d.name, mac, ips)
+	logicalPortName, err := network.OVNInstanceDevicePortAdd(d.network, d.inst.ID(), d.inst.Name(), d.name, mac, ips)
 	if err != nil {
 		return nil, err
 	}


More information about the lxc-devel mailing list