[lxc-devel] [lxd/master] Network: OVN Ingress mode

tomponline on Github lxc-bot at linuxcontainers.org
Wed Dec 9 10:10:45 UTC 2020


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 480 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20201209/2ff5550e/attachment.bin>
-------------- next part --------------
From 4ec52f656bb1c178a38f1bcffb489f12ad644a10 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 9 Dec 2020 09:24:48 +0000
Subject: [PATCH 1/7] lxd/network/driver/ovn: Improve error message

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

diff --git a/lxd/network/driver_ovn.go b/lxd/network/driver_ovn.go
index e222099fee..3dc4db91bb 100644
--- a/lxd/network/driver_ovn.go
+++ b/lxd/network/driver_ovn.go
@@ -1411,7 +1411,7 @@ func (n *ovn) setup(update bool) error {
 		err := n.state.Cluster.Transaction(func(tx *db.ClusterTx) error {
 			err = tx.UpdateNetwork(n.id, n.description, n.config)
 			if err != nil {
-				return errors.Wrapf(err, "Failed saving optimal bridge MTU")
+				return errors.Wrapf(err, "Failed saving updated network config")
 			}
 
 			return nil

From 7cbdd62e06db2b2758a4d2b7ad620722fdc3f719 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 9 Dec 2020 10:04:37 +0000
Subject: [PATCH 2/7] lxd/network/driver/physical: Adds ovn.ingress_mode config
 key

Allows specifying how external OVN NIC IPs are advertised to the uplink; either "l2proxy" (default) or "routed".

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

diff --git a/lxd/network/driver_physical.go b/lxd/network/driver_physical.go
index 19d0001a57..99a8be7f11 100644
--- a/lxd/network/driver_physical.go
+++ b/lxd/network/driver_physical.go
@@ -34,18 +34,21 @@ func (n *physical) DBType() db.NetworkType {
 // Validate network config.
 func (n *physical) Validate(config map[string]string) error {
 	rules := map[string]func(value string) error{
-		"parent":                      validate.Required(validate.IsNotEmpty, validInterfaceName),
-		"mtu":                         validate.Optional(validate.IsNetworkMTU),
-		"vlan":                        validate.Optional(validate.IsNetworkVLAN),
-		"maas.subnet.ipv4":            validate.IsAny,
-		"maas.subnet.ipv6":            validate.IsAny,
-		"ipv4.gateway":                validate.Optional(validate.IsNetworkAddressCIDRV4),
-		"ipv6.gateway":                validate.Optional(validate.IsNetworkAddressCIDRV6),
-		"ipv4.ovn.ranges":             validate.Optional(validate.IsNetworkRangeV4List),
-		"ipv6.ovn.ranges":             validate.Optional(validate.IsNetworkRangeV6List),
-		"ipv4.routes":                 validate.Optional(validate.IsNetworkV4List),
-		"ipv6.routes":                 validate.Optional(validate.IsNetworkV6List),
-		"dns.nameservers":             validate.Optional(validate.IsNetworkAddressList),
+		"parent":           validate.Required(validate.IsNotEmpty, validInterfaceName),
+		"mtu":              validate.Optional(validate.IsNetworkMTU),
+		"vlan":             validate.Optional(validate.IsNetworkVLAN),
+		"maas.subnet.ipv4": validate.IsAny,
+		"maas.subnet.ipv6": validate.IsAny,
+		"ipv4.gateway":     validate.Optional(validate.IsNetworkAddressCIDRV4),
+		"ipv6.gateway":     validate.Optional(validate.IsNetworkAddressCIDRV6),
+		"ipv4.ovn.ranges":  validate.Optional(validate.IsNetworkRangeV4List),
+		"ipv6.ovn.ranges":  validate.Optional(validate.IsNetworkRangeV6List),
+		"ipv4.routes":      validate.Optional(validate.IsNetworkV4List),
+		"ipv6.routes":      validate.Optional(validate.IsNetworkV6List),
+		"dns.nameservers":  validate.Optional(validate.IsNetworkAddressList),
+		"ovn.ingress_mode": validate.Optional(func(value string) error {
+			return validate.IsOneOf(value, []string{"l2proxy", "routed"})
+		}),
 		"volatile.last_state.created": validate.Optional(validate.IsBool),
 	}
 

From 0e1537b591c9f317a47523eca3328c7bac5e058c Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 9 Dec 2020 10:05:39 +0000
Subject: [PATCH 3/7] lxd/network/driver/ovn: Updates uplinkRoutes to accept an
 *api.Network argument

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

diff --git a/lxd/network/driver_ovn.go b/lxd/network/driver_ovn.go
index 3dc4db91bb..a20cbcd9da 100644
--- a/lxd/network/driver_ovn.go
+++ b/lxd/network/driver_ovn.go
@@ -81,13 +81,9 @@ func (n *ovn) Info() Info {
 	}
 }
 
-// uplinkRoutes parses ipv4.routes and ipv6.routes settings for a named uplink network into a slice of *net.IPNet.
-func (n *ovn) uplinkRoutes(uplinkNetworkName string) ([]*net.IPNet, error) {
-	_, uplink, _, err := n.state.Cluster.GetNetworkInAnyState(project.Default, uplinkNetworkName)
-	if err != nil {
-		return nil, err
-	}
-
+// uplinkRoutes parses ipv4.routes and ipv6.routes settings for an uplink network into a slice of *net.IPNet.
+func (n *ovn) uplinkRoutes(uplink *api.Network) ([]*net.IPNet, error) {
+	var err error
 	var uplinkRoutes []*net.IPNet
 	for _, k := range []string{"ipv4.routes", "ipv6.routes"} {
 		if uplink.Config[k] == "" {

From dce5376a83b39d371907e397c6390617e12cc347 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 9 Dec 2020 10:06:07 +0000
Subject: [PATCH 4/7] lxd/network/driver/ovn: n.uplinkRoutes usage

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

diff --git a/lxd/network/driver_ovn.go b/lxd/network/driver_ovn.go
index a20cbcd9da..3b36606b9b 100644
--- a/lxd/network/driver_ovn.go
+++ b/lxd/network/driver_ovn.go
@@ -216,7 +216,12 @@ func (n *ovn) Validate(config map[string]string) error {
 	}
 
 	// Get uplink routes.
-	uplinkRoutes, err := n.uplinkRoutes(uplinkNetworkName)
+	_, uplink, _, err := n.state.Cluster.GetNetworkInAnyState(project.Default, uplinkNetworkName)
+	if err != nil {
+		return errors.Wrapf(err, "Failed to load uplink network %q", uplinkNetworkName)
+	}
+
+	uplinkRoutes, err := n.uplinkRoutes(uplink)
 	if err != nil {
 		return err
 	}

From 20ea6d892ecbc259d7c0e0f41bcb5988fb489dea Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 9 Dec 2020 10:06:34 +0000
Subject: [PATCH 5/7] lxd/network/driver/ovn: Moves subnet size validation into
 InstanceDevicePortValidateExternalRoutes

And makes subnet size validation conditional on uplink's `ovn.ingress_node` setting being in l2proxy mode.

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

diff --git a/lxd/network/driver_ovn.go b/lxd/network/driver_ovn.go
index 3b36606b9b..b6a444cd72 100644
--- a/lxd/network/driver_ovn.go
+++ b/lxd/network/driver_ovn.go
@@ -1974,11 +1974,28 @@ func (n *ovn) InstanceDevicePortValidateExternalRoutes(deviceInstance instance.I
 	var projectNetworks map[string]map[int64]api.Network
 
 	// Get uplink routes.
-	uplinkRoutes, err := n.uplinkRoutes(n.config["network"])
+	_, uplink, _, err := n.state.Cluster.GetNetworkInAnyState(project.Default, n.config["network"])
+	if err != nil {
+		return errors.Wrapf(err, "Failed to load uplink network %q", n.config["network"])
+	}
+
+	uplinkRoutes, err := n.uplinkRoutes(uplink)
 	if err != nil {
 		return err
 	}
 
+	// Check port's external routes are suffciently small when using l2proxy ingress mode on uplink.
+	if shared.StringInSlice(uplink.Config["ovn.ingress_mode"], []string{"l2proxy", ""}) {
+		for _, portExternalRoute := range portExternalRoutes {
+			rOnes, rBits := portExternalRoute.Mask.Size()
+			if rBits > 32 && rOnes < 122 {
+				return fmt.Errorf("External route %q is too large. Maximum size for IPv6 external route is /122", portExternalRoute.String())
+			} else if rOnes < 26 {
+				return fmt.Errorf("External route %q is too large. Maximum size for IPv4 external route is /26", portExternalRoute.String())
+			}
+		}
+	}
+
 	err = n.state.Cluster.Transaction(func(tx *db.ClusterTx) error {
 		// Load the project to get uplink network restrictions.
 		p, err = tx.GetProject(n.project)

From 04e8ff8310be9061be802815bf8f19e3a34da0b8 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 9 Dec 2020 10:07:43 +0000
Subject: [PATCH 6/7] lxd/network/driver/ovn: Updates InstanceDevicePortAdd to
 only publish external IPs using DNAT when uplink l2proxy mode enabled

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

diff --git a/lxd/network/driver_ovn.go b/lxd/network/driver_ovn.go
index b6a444cd72..0984080469 100644
--- a/lxd/network/driver_ovn.go
+++ b/lxd/network/driver_ovn.go
@@ -2087,6 +2087,12 @@ func (n *ovn) InstanceDevicePortAdd(instanceUUID string, instanceName string, de
 		return "", err
 	}
 
+	// Load uplink network config.
+	_, uplink, _, err := n.state.Cluster.GetNetworkInAnyState(project.Default, n.config["network"])
+	if err != nil {
+		return "", errors.Wrapf(err, "Failed to load uplink network %q", n.config["network"])
+	}
+
 	// Get DHCP options IDs.
 	if validate.IsOneOf(n.getRouterIntPortIPv4Net(), []string{"none", ""}) != nil {
 		_, routerIntPortIPv4Net, err := net.ParseCIDR(n.getRouterIntPortIPv4Net())
@@ -2180,30 +2186,32 @@ func (n *ovn) InstanceDevicePortAdd(instanceUUID string, instanceName string, de
 
 	revert.Add(func() { client.LogicalSwitchPortDeleteDNS(n.getIntSwitchName(), dnsUUID) })
 
-	// Publish NIC's IPs on uplink network if NAT is disabled.
-	for _, k := range []string{"ipv4.nat", "ipv6.nat"} {
-		if shared.IsTrue(n.config[k]) {
-			continue
-		}
+	// Publish NIC's IPs on uplink network if NAT is disabled and using l2proxy ingress mode on uplink.
+	if shared.StringInSlice(uplink.Config["ovn.ingress_mode"], []string{"l2proxy", ""}) {
+		for _, k := range []string{"ipv4.nat", "ipv6.nat"} {
+			if shared.IsTrue(n.config[k]) {
+				continue
+			}
 
-		// Select the correct destination IP from the DNS records.
-		var ip net.IP
-		if k == "ipv4.nat" {
-			ip = dnsIPv4
-		} else if k == "ipv6.nat" {
-			ip = dnsIPv6
-		}
+			// Select the correct destination IP from the DNS records.
+			var ip net.IP
+			if k == "ipv4.nat" {
+				ip = dnsIPv4
+			} else if k == "ipv6.nat" {
+				ip = dnsIPv6
+			}
 
-		if ip == nil {
-			continue //No qualifying target IP from DNS records.
-		}
+			if ip == nil {
+				continue //No qualifying target IP from DNS records.
+			}
 
-		err = client.LogicalRouterDNATSNATAdd(n.getRouterName(), ip, ip, true, true)
-		if err != nil {
-			return "", err
-		}
+			err = client.LogicalRouterDNATSNATAdd(n.getRouterName(), ip, ip, true, true)
+			if err != nil {
+				return "", err
+			}
 
-		revert.Add(func() { client.LogicalRouterDNATSNATDelete(n.getRouterName(), ip) })
+			revert.Add(func() { client.LogicalRouterDNATSNATDelete(n.getRouterName(), ip) })
+		}
 	}
 
 	// Add each internal route (using the IPs set for DNS as target).
@@ -2243,22 +2251,25 @@ func (n *ovn) InstanceDevicePortAdd(instanceUUID string, instanceName string, de
 
 		revert.Add(func() { client.LogicalRouterRouteDelete(n.getRouterName(), externalRoute, targetIP) })
 
-		// In order to advertise the external route to the uplink network using proxy ARP/NDP we need to
-		// add a stateless dnat_and_snat rule (as to my knowledge this is the only way to get the OVN
-		// router to respond to ARP/NDP requests for IPs that it doesn't actually have). However we have
-		// to add each IP in the external route individually as DNAT doesn't support whole subnets.
-		err = SubnetIterate(externalRoute, func(ip net.IP) error {
-			err = client.LogicalRouterDNATSNATAdd(n.getRouterName(), ip, ip, true, true)
-			if err != nil {
-				return err
-			}
+		// When using l2proxy ingress mode on uplink, in order to advertise the external route to the
+		// uplink network using proxy ARP/NDP we need to add a stateless dnat_and_snat rule (as to my
+		// knowledge this is the only way to get the OVN router to respond to ARP/NDP requests for IPs that
+		// it doesn't actually have). However we have to add each IP in the external route individually as
+		// DNAT doesn't support whole subnets.
+		if shared.StringInSlice(uplink.Config["ovn.ingress_mode"], []string{"l2proxy", ""}) {
+			err = SubnetIterate(externalRoute, func(ip net.IP) error {
+				err = client.LogicalRouterDNATSNATAdd(n.getRouterName(), ip, ip, true, true)
+				if err != nil {
+					return err
+				}
 
-			revert.Add(func() { client.LogicalRouterDNATSNATDelete(n.getRouterName(), ip) })
+				revert.Add(func() { client.LogicalRouterDNATSNATDelete(n.getRouterName(), ip) })
 
-			return nil
-		})
-		if err != nil {
-			return "", err
+				return nil
+			})
+			if err != nil {
+				return "", err
+			}
 		}
 	}
 

From dcfcb0ba11dcd2fea7a72b7a64b996af7f4e04b0 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 9 Dec 2020 10:08:21 +0000
Subject: [PATCH 7/7] lxd/device/nic/ovn: Removes external subnet validation

Now done by d.network.InstanceDevicePortValidateExternalRoutes.

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

diff --git a/lxd/device/nic_ovn.go b/lxd/device/nic_ovn.go
index 415f5271cc..a7cf93d04f 100644
--- a/lxd/device/nic_ovn.go
+++ b/lxd/device/nic_ovn.go
@@ -171,15 +171,6 @@ func (d *nicOVN) validateConfig(instConf instance.ConfigReader) error {
 	}
 
 	if len(externalRoutes) > 0 {
-		for _, externalRoute := range externalRoutes {
-			rOnes, rBits := externalRoute.Mask.Size()
-			if rBits > 32 && rOnes < 122 {
-				return fmt.Errorf("External route %q is too large. Maximum size for IPv6 external route is /122", externalRoute.String())
-			} else if rOnes < 26 {
-				return fmt.Errorf("External route %q is too large. Maximum size for IPv4 external route is /26", externalRoute.String())
-			}
-		}
-
 		err = d.network.InstanceDevicePortValidateExternalRoutes(d.inst, d.name, externalRoutes)
 		if err != nil {
 			return err


More information about the lxc-devel mailing list