The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/8226
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) === Add `ovn.ingress_mode` (either `l2proxy` (default) or `routed`) on `physical` networks to allow OVN NICs to change the way they advertise their external IPs on the uplink network.
From 4ec52f656bb1c178a38f1bcffb489f12ad644a10 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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.parr...@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
_______________________________________________ lxc-devel mailing list lxc-devel@lists.linuxcontainers.org http://lists.linuxcontainers.org/listinfo/lxc-devel