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