On 2/11/25 9:36 AM, Felix Huettner via dev wrote:
> Previously we just assumed that if a LR had multiple LRPs bound to the
> local chassis that all routes where valid for all of these LRPs.
> This commit handles a previous option in ovn-nb that allows the user to
> specify the interface name that a route needs to use to be acceptable
> for a given LRP.
> The user specifies a port name in the northbound database. If this port
> is bound locally we use the interface name associated with it for
> filtering routes.
> Additionally users can overwrite that interface name to also support
> cases where there is no referenceable port name in the northbound.
> 
> This can e.g. be used if a chassis has multiple uplinks. Each of these
> uplinks could be assigned to a different LRP of the same LR connected to
> the same localnet.
> The option allows then to map e.g. a learned default route to each
> individual LRP. If one of the links would go down just one of the
> learned routes would be removed, causing ECMP across multiple chassis to
> be correctly weighted.
> 
> Signed-off-by: Felix Huettner <[email protected]>
> ---

Hi Felix,

I applied this patch to main with the following minor style changes:

diff --git a/controller/route.c b/controller/route.c
index f83a7b2944..f94b673bfa 100644
--- a/controller/route.c
+++ b/controller/route.c
@@ -82,8 +82,8 @@ build_port_mapping(struct smap *mapping, const char 
*port_mapping)
     }
 
     char *tokstr, *orig, *key, *value;
-    orig = tokstr = xstrdup(port_mapping);
 
+    orig = tokstr = xstrdup(port_mapping);
     while (ofputil_parse_key_value(&tokstr, &key, &value)) {
         if (!*value) {
           static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
diff --git a/tests/system-ovn.at b/tests/system-ovn.at
index ea3d3b1812..cb32ff0159 100644
--- a/tests/system-ovn.at
+++ b/tests/system-ovn.at
@@ -16384,7 +16384,7 @@ check_row_count Learned_Route 0
 # By setting a learning interface filter will prevent us from learning the
 # route again. The Port referenced by the name does not even exist.
 check ovn-nbctl --wait=hv set Logical_Router_Port internet-phys \
-      options:dynamic-routing-port-name=thisportdoesnotexist
+    options:dynamic-routing-port-name=thisportdoesnotexist
 check ip route add 233.252.0.0/24 via 192.168.10.10 dev lo onlink vrf 
ovnvrf1337
 # For now we trigger a recompute as route watching is not yet implemented.
 check ovn-appctl -t ovn-controller inc-engine/recompute
@@ -16406,7 +16406,7 @@ check ip link set hv1-mll up
 wait_for_ports_up mylearninglsp
 
 check ovn-nbctl --wait=hv set Logical_Router_Port internet-phys \
-      options:dynamic-routing-port-name=mylearninglsp
+    options:dynamic-routing-port-name=mylearninglsp
 
 check ip route add 233.253.0.0/24 via 192.168.20.20 dev hv1-mll onlink vrf 
ovnvrf1337
 # For now we trigger a recompute as route watching is not yet implemented.
--

Regards,
Dumitru

> v7->v8:
>   * Rework implementation to only parse the port-mapping setting once
>     per run.
> v6->v7:
>   * Reworked implementation based on review comments.
>     We now have a layer of indirection to allow different chassis to
>     have different interface names.
> v5->v6:
>   * addressed review comments
> v3->v4:
>   - addressed review comments.
>   - updated commit message to be more descriptive.
> 
>  controller/ovn-controller.8.xml     |  8 +++
>  controller/ovn-controller.c         |  7 +++
>  controller/route-exchange-netlink.c |  2 +
>  controller/route-exchange-netlink.h |  3 +
>  controller/route-exchange.c         | 20 ++++--
>  controller/route.c                  | 81 ++++++++++++++++++++++---
>  controller/route.h                  | 11 +++-
>  tests/system-ovn.at                 | 94 ++++++++++++++++++++++++++++-
>  8 files changed, 209 insertions(+), 17 deletions(-)
> 
> diff --git a/controller/ovn-controller.8.xml b/controller/ovn-controller.8.xml
> index f27b32118..a6a6d6f4e 100644
> --- a/controller/ovn-controller.8.xml
> +++ b/controller/ovn-controller.8.xml
> @@ -414,6 +414,14 @@
>            If the value is zero, it disables the inactivity probe.
>          </p>
>        </dd>
> +      <dt><code>external_ids:dynamic-routing-port-mapping</code></dt>
> +      <dd>
> +        <p>
> +          This setting works together with the Northbound
> +          dynamic-routing-port-name option on Logical_Router_Ports.
> +          See the <code>ovn-nb</code>(5) for more details.
> +        </p>
> +      </dd>
>      </dl>
>  
>      <p>
> diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> index cf83ea6e3..bc5467c0f 100644
> --- a/controller/ovn-controller.c
> +++ b/controller/ovn-controller.c
> @@ -5005,13 +5005,20 @@ en_route_run(struct engine_node *node, void *data)
>      const struct sbrec_advertised_route_table *advertised_route_table =
>          EN_OVSDB_GET(engine_get_input("SB_advertised_route", node));
>  
> +    const struct ovsrec_open_vswitch *cfg
> +        = ovsrec_open_vswitch_table_first(ovs_table);
> +    const char *dynamic_routing_port_mapping =
> +        smap_get(&cfg->external_ids, "dynamic-routing-port-mapping");
> +
>      struct route_ctx_in r_ctx_in = {
>          .advertised_route_table = advertised_route_table,
>          .sbrec_port_binding_by_name = sbrec_port_binding_by_name,
>          .chassis = chassis,
> +        .dynamic_routing_port_mapping = dynamic_routing_port_mapping,
>          .active_tunnels = &rt_data->active_tunnels,
>          .local_datapaths = &rt_data->local_datapaths,
>          .local_lports = &rt_data->local_lports,
> +        .local_bindings = &rt_data->lbinding_data.bindings,
>      };
>  
>      struct route_ctx_out r_ctx_out = {
> diff --git a/controller/route-exchange-netlink.c 
> b/controller/route-exchange-netlink.c
> index 07c4ab0d0..bcd48c028 100644
> --- a/controller/route-exchange-netlink.c
> +++ b/controller/route-exchange-netlink.c
> @@ -227,6 +227,8 @@ handle_route_msg(const struct route_table_msg *msg, void 
> *data)
>              rr->prefix = rd->rta_dst;
>              rr->plen = rd->rtm_dst_len;
>              rr->nexthop = nexthop->addr;
> +            memcpy(rr->ifname, nexthop->ifname, IFNAMSIZ);
> +            rr->ifname[IFNAMSIZ] = 0;
>              ovs_list_push_back(handle_data->learned_routes, &rr->list_node);
>          }
>          return;
> diff --git a/controller/route-exchange-netlink.h 
> b/controller/route-exchange-netlink.h
> index 6b26103d5..e46ae048e 100644
> --- a/controller/route-exchange-netlink.h
> +++ b/controller/route-exchange-netlink.h
> @@ -21,6 +21,7 @@
>  #include <stdint.h>
>  #include "openvswitch/list.h"
>  #include <netinet/in.h>
> +#include <net/if.h>
>  
>  /* This value is arbitrary but currently unused.
>   * See the kernel rtnetlink UAPI at
> @@ -37,6 +38,8 @@ struct re_nl_received_route_node {
>      struct in6_addr prefix;
>      unsigned int plen;
>      struct in6_addr nexthop;
> +    /* Adding 1 to this to be sure we actually have a terminating '\0' */
> +    char ifname[IFNAMSIZ + 1];
>  };
>  
>  int re_nl_create_vrf(const char *ifname, uint32_t table_id);
> diff --git a/controller/route-exchange.c b/controller/route-exchange.c
> index b0ebc4fda..7b91d3adb 100644
> --- a/controller/route-exchange.c
> +++ b/controller/route-exchange.c
> @@ -94,7 +94,7 @@ route_lookup(struct hmap *route_map,
>  static void
>  sb_sync_learned_routes(const struct ovs_list *learned_routes,
>                         const struct sbrec_datapath_binding *datapath,
> -                       const struct sset *bound_ports,
> +                       const struct smap *bound_ports,
>                         struct ovsdb_idl_txn *ovnsb_idl_txn,
>                         struct ovsdb_idl_index *sbrec_port_binding_by_name,
>                         struct ovsdb_idl_index 
> *sbrec_learned_route_by_datapath)
> @@ -109,8 +109,9 @@ sb_sync_learned_routes(const struct ovs_list 
> *learned_routes,
>      SBREC_LEARNED_ROUTE_FOR_EACH_EQUAL (sb_route, filter,
>                                          sbrec_learned_route_by_datapath) {
>          /* If the port is not local we don't care about it.
> -         * Some other ovn-controller will handle it. */
> -        if (!sset_contains(bound_ports,
> +         * Some other ovn-controller will handle it.
> +         * We may not use smap_get since the value might be validly NULL. */
> +        if (!smap_get_node(bound_ports,
>                             sb_route->logical_port->logical_port)) {
>              continue;
>          }
> @@ -124,11 +125,18 @@ sb_sync_learned_routes(const struct ovs_list 
> *learned_routes,
>                                                 learned_route->plen);
>          char *nexthop = normalize_v46(&learned_route->nexthop);
>  
> -        const char *logical_port_name;
> -        SSET_FOR_EACH (logical_port_name, bound_ports) {
> +        struct smap_node *port_node;
> +        SMAP_FOR_EACH (port_node, bound_ports) {
> +            /* The user specified an ifname, but we learned it on a different
> +             * port. */
> +            if (port_node->value && strcmp(port_node->value,
> +                                           learned_route->ifname)) {
> +                continue;
> +            }
> +
>              const struct sbrec_port_binding *logical_port =
>                  lport_lookup_by_name(sbrec_port_binding_by_name,
> -                                     logical_port_name);
> +                                     port_node->key);
>              if (!logical_port) {
>                  continue;
>              }
> diff --git a/controller/route.c b/controller/route.c
> index 108435bad..4f3f26838 100644
> --- a/controller/route.c
> +++ b/controller/route.c
> @@ -19,8 +19,10 @@
>  
>  #include <net/if.h>
>  
> +#include "vswitch-idl.h"
>  #include "openvswitch/hmap.h"
>  #include "openvswitch/vlog.h"
> +#include "openvswitch/ofp-parse.h"
>  
>  #include "lib/ovn-sb-idl.h"
>  
> @@ -30,7 +32,6 @@
>  #include "route.h"
>  
>  VLOG_DEFINE_THIS_MODULE(exchange);
> -static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
>  
>  bool
>  route_exchange_relevant_port(const struct sbrec_port_binding *pb)
> @@ -73,6 +74,49 @@ find_route_exchange_pb(struct ovsdb_idl_index 
> *sbrec_port_binding_by_name,
>      return NULL;
>  }
>  
> +static void
> +build_port_mapping(struct smap *mapping, const char *port_mapping)
> +{
> +    if (!port_mapping) {
> +        return;
> +    }
> +
> +    char *tokstr, *orig, *key, *value;
> +    orig = tokstr = xstrdup(port_mapping);
> +
> +    while (ofputil_parse_key_value(&tokstr, &key, &value)) {
> +        if (!*value) {
> +          static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
> +          VLOG_WARN_RL(&rl, "dynamic-routing-port-mapping setting '%s' is "
> +                            "not valid.", tokstr);
> +          free(orig);
> +          break;
> +        }
> +        smap_add(mapping, key, value);
> +    }
> +    free(orig);
> +}
> +
> +static const char *
> +ifname_from_port_name(const struct smap *port_mapping,
> +                      struct shash *local_bindings,
> +                      const struct sbrec_chassis *chassis,
> +                      const char *port_name)
> +{
> +    const char *iface = smap_get(port_mapping, port_name);
> +    if (iface) {
> +        return iface;
> +    }
> +
> +    if (!local_binding_is_up(local_bindings, port_name, chassis)) {
> +        return NULL;
> +    }
> +
> +    struct local_binding *binding = local_binding_find(local_bindings,
> +                                                       port_name);
> +    return binding->iface->name;
> +}
> +
>  static void
>  advertise_datapath_cleanup(struct advertise_datapath_entry *ad)
>  {
> @@ -82,7 +126,7 @@ advertise_datapath_cleanup(struct advertise_datapath_entry 
> *ad)
>          free(ar);
>      }
>      hmap_destroy(&ad->routes);
> -    sset_destroy(&ad->bound_ports);
> +    smap_destroy(&ad->bound_ports);
>      free(ad);
>  }
>  
> @@ -105,6 +149,9 @@ route_run(struct route_ctx_in *r_ctx_in,
>  {
>      struct advertise_datapath_entry *ad;
>      const struct local_datapath *ld;
> +    struct smap port_mapping = SMAP_INITIALIZER(&port_mapping);
> +
> +    build_port_mapping(&port_mapping, 
> r_ctx_in->dynamic_routing_port_mapping);
>  
>      HMAP_FOR_EACH (ld, hmap_node, r_ctx_in->local_datapaths) {
>          if (!ld->n_peer_ports || ld->is_switch) {
> @@ -114,7 +161,7 @@ route_run(struct route_ctx_in *r_ctx_in,
>          ad = xzalloc(sizeof(*ad));
>          ad->db = ld->datapath;
>          hmap_init(&ad->routes);
> -        sset_init(&ad->bound_ports);
> +        smap_init(&ad->bound_ports);
>  
>          /* This is a LR datapath, find LRPs with route exchange options
>           * that are bound locally. */
> @@ -132,13 +179,28 @@ route_run(struct route_ctx_in *r_ctx_in,
>  
>              ad->maintain_vrf |= smap_get_bool(
>                  &repb->options, "dynamic-routing-maintain-vrf", false);
> -            sset_add(&ad->bound_ports, local_peer->logical_port);
> -        }
>  
> -        if (sset_is_empty(&ad->bound_ports)) {
> -            advertise_datapath_cleanup(ad);
> -            continue;
> +            const char *port_name = smap_get(&repb->options,
> +                                            "dynamic-routing-port-name");
> +            if (!port_name) {
> +                /* No port-name set, so we learn routes from all ports. */
> +                smap_add_nocopy(&ad->bound_ports,
> +                                xstrdup(local_peer->logical_port), NULL);
> +            } else {
> +                /* If a port_name is set the we filter for the name as set in
> +                 * the port-mapping or the interface name of the local
> +                 * binding. If the port is not in the port_mappings and not
> +                 * bound locally we will not learn routes for this port. */
> +                const char *ifname = ifname_from_port_name(
> +                    &port_mapping, r_ctx_in->local_bindings,
> +                    r_ctx_in->chassis, port_name);
> +                if (ifname) {
> +                    smap_add(&ad->bound_ports, local_peer->logical_port,
> +                             ifname);
> +                }
> +            }
>          }
> +
>          tracked_datapath_add(ld->datapath, TRACKED_RESOURCE_NEW,
>                               r_ctx_out->tracked_re_datapaths);
>  
> @@ -157,6 +219,7 @@ route_run(struct route_ctx_in *r_ctx_in,
>          struct in6_addr prefix;
>          unsigned int plen;
>          if (!ip46_parse_cidr(route->ip_prefix, &prefix, &plen)) {
> +            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
>              VLOG_WARN_RL(&rl, "bad 'ip_prefix' %s in route "
>                           UUID_FMT, route->ip_prefix,
>                           UUID_ARGS(&route->header_.uuid));
> @@ -169,6 +232,8 @@ route_run(struct route_ctx_in *r_ctx_in,
>          hmap_insert(&ad->routes, &ar->node,
>                      advertise_route_hash(&prefix, plen));
>      }
> +
> +    smap_destroy(&port_mapping);
>  }
>  
>  void
> diff --git a/controller/route.h b/controller/route.h
> index 448643ead..d3e5b3eff 100644
> --- a/controller/route.h
> +++ b/controller/route.h
> @@ -22,6 +22,7 @@
>  #include <netinet/in.h>
>  #include "openvswitch/hmap.h"
>  #include "sset.h"
> +#include "smap.h"
>  
>  struct hmap;
>  struct ovsdb_idl_index;
> @@ -32,9 +33,11 @@ struct route_ctx_in {
>      const struct sbrec_advertised_route_table *advertised_route_table;
>      struct ovsdb_idl_index *sbrec_port_binding_by_name;
>      const struct sbrec_chassis *chassis;
> +    const char *dynamic_routing_port_mapping;
>      const struct sset *active_tunnels;
>      const struct hmap *local_datapaths;
>      const struct sset *local_lports;
> +    struct shash *local_bindings;
>  };
>  
>  struct route_ctx_out {
> @@ -50,8 +53,12 @@ struct advertise_datapath_entry {
>      struct hmap routes;
>  
>      /* The name of the port bindings locally bound for this datapath and
> -     * running route exchange logic. */
> -    struct sset bound_ports;
> +     * running route exchange logic.
> +     * The key is the port name and the value is the ifname if set. This may
> +     * be empty if the all ports specified dynamic-routing-port-name, but no
> +     * such referenced port is local. In this case we should only advertise
> +     * routes but not learn them. */
> +    struct smap bound_ports;
>  };
>  
>  struct advertise_route_entry {
> diff --git a/tests/system-ovn.at b/tests/system-ovn.at
> index 88e37613e..bd15e0fa5 100644
> --- a/tests/system-ovn.at
> +++ b/tests/system-ovn.at
> @@ -16101,6 +16101,11 @@ blackhole 192.0.2.3 proto 84
>  blackhole 192.0.2.10 proto 84
>  blackhole 198.51.100.0/24 proto 84])
>  
> +# Disable maintaining the vrf as some tests below would cause the vrfs to be
> +# removed.
> +check ovn-nbctl set Logical_Router_Port internet-phys \
> +    options:dynamic-routing-maintain-vrf=false
> +
>  # Now we test route learning.
>  check_row_count Learned_Route 0
>  check ip route add 233.252.0.0/24 via 192.168.10.10 dev lo onlink vrf 
> ovnvrf1337
> @@ -16111,6 +16116,46 @@ check_row_count Learned_Route 1
>  lp=$(fetch_column port_binding _uuid logical_port=internet-phys)
>  check_row_count Learned_Route 1 logical_port=$lp ip_prefix=233.252.0.0/24 
> nexthop=192.168.10.10
>  
> +# If we remove the route it is also gone.
> +check ip route del 233.252.0.0/24 via 192.168.10.10 dev lo onlink vrf 
> ovnvrf1337
> +# For now we trigger a recompute as route watching is not yet implemented.
> +check ovn-appctl -t ovn-controller inc-engine/recompute
> +check ovn-nbctl --wait=hv sync
> +check_row_count Learned_Route 0
> +
> +# By setting a learning interface filter will prevent us from learning the
> +# route again. The Port referenced by the name does not even exist.
> +check ovn-nbctl --wait=hv set Logical_Router_Port internet-phys \
> +      options:dynamic-routing-port-name=thisportdoesnotexist
> +check ip route add 233.252.0.0/24 via 192.168.10.10 dev lo onlink vrf 
> ovnvrf1337
> +# For now we trigger a recompute as route watching is not yet implemented.
> +check ovn-appctl -t ovn-controller inc-engine/recompute
> +check ovn-nbctl --wait=hv sync
> +check_row_count Learned_Route 0
> +
> +# Setting the local ovsdb to map this port to "lo" will make route learning
> +# work again.
> +check ovs-vsctl set Open_vSwitch . \
> +    
> external-ids:dynamic-routing-port-mapping="thisisirrelevant=andjustfortesting,thisportdoesnotexist=lo"
> +check ovn-nbctl --wait=hv sync
> +check_row_count Learned_Route 1
> +
> +# Now we try the interface filter with an existing port.
> +check ovn-nbctl lsp-add phys mylearninglsp
> +check ovs-vsctl -- add-port br-int hv1-mll -- \
> +    set interface hv1-mll type=internal external-ids:iface-id=mylearninglsp
> +check ip link set hv1-mll up
> +wait_for_ports_up mylearninglsp
> +
> +check ovn-nbctl --wait=hv set Logical_Router_Port internet-phys \
> +      options:dynamic-routing-port-name=mylearninglsp
> +
> +check ip route add 233.253.0.0/24 via 192.168.20.20 dev hv1-mll onlink vrf 
> ovnvrf1337
> +# For now we trigger a recompute as route watching is not yet implemented.
> +check ovn-appctl -t ovn-controller inc-engine/recompute
> +check ovn-nbctl --wait=hv sync
> +check_row_count Learned_Route 1 ip_prefix=233.253.0.0/24 
> nexthop=192.168.20.20
> +
>  OVS_APP_EXIT_AND_WAIT([ovn-controller])
>  
>  as ovn-sb
> @@ -16314,16 +16359,63 @@ blackhole 192.0.2.3 proto 84
>  blackhole 192.0.2.10 proto 84
>  blackhole 198.51.100.0/24 proto 84])
>  
> +# Disable maintaining the vrf as some tests below would cause the vrfs to be
> +# removed.
> +check ovn-nbctl set Logical_Router_Port internet-phys \
> +    options:dynamic-routing-maintain-vrf=false
> +
>  # Now we test route learning.
>  check_row_count Learned_Route 0
>  check ip route add 233.252.0.0/24 via 192.168.10.10 dev lo onlink vrf 
> ovnvrf1337
>  # For now we trigger a recompute as route watching is not yet implemented.
>  check ovn-appctl -t ovn-controller inc-engine/recompute
>  check ovn-nbctl --wait=hv sync
> -check_row_count Learned_Route 2
> +check_row_count Learned_Route 1
>  lp=$(fetch_column port_binding _uuid logical_port=internet-phys)
>  check_row_count Learned_Route 1 logical_port=$lp ip_prefix=233.252.0.0/24 
> nexthop=192.168.10.10
>  
> +# If we remove the route it is also gone.
> +check ip route del 233.252.0.0/24 via 192.168.10.10 dev lo onlink vrf 
> ovnvrf1337
> +# For now we trigger a recompute as route watching is not yet implemented.
> +check ovn-appctl -t ovn-controller inc-engine/recompute
> +check ovn-nbctl --wait=hv sync
> +check_row_count Learned_Route 0
> +
> +# By setting a learning interface filter will prevent us from learning the
> +# route again. The Port referenced by the name does not even exist.
> +check ovn-nbctl --wait=hv set Logical_Router_Port internet-phys \
> +      options:dynamic-routing-port-name=thisportdoesnotexist
> +check ip route add 233.252.0.0/24 via 192.168.10.10 dev lo onlink vrf 
> ovnvrf1337
> +# For now we trigger a recompute as route watching is not yet implemented.
> +check ovn-appctl -t ovn-controller inc-engine/recompute
> +check ovn-nbctl --wait=hv sync
> +check_row_count Learned_Route 0
> +
> +# Setting the local ovsdb to map this port to "lo" will make route learning
> +# work again.
> +check ovs-vsctl set Open_vSwitch . \
> +    
> external-ids:dynamic-routing-port-mapping="thisisirrelevant=andjustfortesting,thisportdoesnotexist=lo"
> +check ovn-nbctl --wait=hv sync
> +check_row_count Learned_Route 1
> +
> +# Now we try the interface filter with an existing port.
> +check ovn-nbctl lsp-add phys mylearninglsp
> +check ovs-vsctl -- add-port br-int hv1-mll -- \
> +    set interface hv1-mll type=internal external-ids:iface-id=mylearninglsp
> +check ip link set hv1-mll up
> +wait_for_ports_up mylearninglsp
> +
> +check ovn-nbctl --wait=hv set Logical_Router_Port internet-phys \
> +      options:dynamic-routing-port-name=mylearninglsp
> +
> +check ip route add 233.253.0.0/24 via 192.168.20.20 dev hv1-mll onlink vrf 
> ovnvrf1337
> +# For now we trigger a recompute as route watching is not yet implemented.
> +check ovn-appctl -t ovn-controller inc-engine/recompute
> +check ovn-nbctl --wait=hv sync
> +check_row_count Learned_Route 1 ip_prefix=233.253.0.0/24 
> nexthop=192.168.20.20
> +
> +OVS_APP_EXIT_AND_WAIT([ovn-controller])
> +
>  as ovn-sb
>  OVS_APP_EXIT_AND_WAIT([ovsdb-server])
>  

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to