The following pull request was submitted through Github.
It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/8044

This e-mail was sent by the LXC bot, direct replies will not reach the author
unless they happen to be subscribed to this list.

=== Description (from pull-request) ===
- Adds support for using subnets from the OVN network's `ipv4.routes.external` and `ipv6.routes.external` for OVN networ's main subnet, as long as the associated `ipv4.nat` or `ipv6.nat` setting(s) are disabled.
- Allows for instance IPs on OVN networks to be published to the external uplink network (no NAT mode).
- Fixes an issue where if a static IPv4 address was set for a NIC using `ipv4.address`, OVN does not allow a mixture of static and dynamic IPs on a port, so this would prevent a dynamic IPv6 address from being added to the port, which in turn prevented a DNS name from being created. We now populate the logical port with an EUI64 address if only static IPv4 addresses are set, and IPv6 is enabled on the bridge.
From 05044a7c2567cd52534bc48ac61e207363f56556 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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)
                }
        }
 
_______________________________________________
lxc-devel mailing list
lxc-devel@lists.linuxcontainers.org
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to