On Wed, Aug 6, 2025 at 12:54 PM Dumitru Ceara <dce...@redhat.com> wrote:

> On 7/25/25 8:03 AM, Ales Musil wrote:
> > Add an option to LS that is inline with existing LR option called
> > "dynamic-routing-redistribute". That option currently accepts only
> > "fdb" which will lead to advertisment of workloads connected to
> > the specified switch. This doesn't work without the switch having
> > "dynamic-routing-vni" option set.
> >
> > Currently ovn-controller will advertise VIFs, virtual ports,
> > container ports, DGPs and GW router ports bound to that chassis.
> > In other words only local workloads are advertised to avoid
> > the potential confusion of defining the same MAC multiple times
> > as the same EVPN might be connected to more than one chassis.
> >
> > Reported-at: https://issues.redhat.com/browse/FDP-1389
> > Signed-off-by: Ales Musil <amu...@redhat.com>
> > ---
>
> Hi Ales,
>
> I only have a few minor comments for now, otherwise this looks OK to me.
>


Hi Dumitru,

thank you for the review.


>
> >  NEWS                        |   2 +
> >  controller/neighbor.c       | 137 ++++++++++++++++++++++++++++++++++++
> >  controller/neighbor.h       |  13 ++++
> >  controller/ovn-controller.c |  92 +++++++++++++++++++++++-
> >  northd/northd.c             |  13 ++++
> >  northd/northd.h             |   3 +-
> >  ovn-nb.xml                  |  24 +++++++
> >  7 files changed, 282 insertions(+), 2 deletions(-)
> >
> > diff --git a/NEWS b/NEWS
> > index 2659e2b70..f3278628d 100644
> > --- a/NEWS
> > +++ b/NEWS
> > @@ -47,6 +47,8 @@ Post v25.03.0
> >         for the EVPN traffic egressing through the tunnel.
> >       * Add the "other_config:dynamic-routing-vni" to Logical Switches.
> If set
> >         to valid integer the LS is considered to be connected to EVPN L2
> domain.
> > +     * Allow the "other_config:dynamic-routing-redistribute" to Logical
>
> Maybe 'Add the "other_config:dynamic-routing-redistribute" ...'?
>
> > +      Switches. The only accept value at the moment is "fdb".
>
> Typo: s/accept/accepted/
>
> >
> >  OVN v25.03.0 - 07 Mar 2025
> >  --------------------------
> > diff --git a/controller/neighbor.c b/controller/neighbor.c
> > index ee77d02d6..9fe9ee405 100644
> > --- a/controller/neighbor.c
> > +++ b/controller/neighbor.c
> > @@ -19,6 +19,7 @@
> >  #include "lib/packets.h"
> >  #include "lib/sset.h"
> >  #include "local_data.h"
> > +#include "lport.h"
> >  #include "ovn-sb-idl.h"
> >
> >  #include "neighbor.h"
> > @@ -38,6 +39,17 @@ static struct neighbor_interface_monitor *
> >  neighbor_interface_monitor_alloc(enum neighbor_family family,
> >                                   enum neighbor_interface_type type,
> >                                   uint32_t vni);
> > +static void neighbor_collect_mac_to_advertise(
> > +    const struct neighbor_ctx_in *, struct hmap *neighbors,
> > +    struct sset *advertised_pbs, const struct sbrec_datapath_binding *);
> > +static const struct sbrec_port_binding
> *neighbor_get_relevant_port_binding(
> > +    struct ovsdb_idl_index *sbrec_pb_by_name,
> > +    const struct sbrec_port_binding *);
> > +static void advertise_neigh_add(struct hmap *neighbors, struct eth_addr
> mac,
> > +                                struct in6_addr ip);
> > +static struct advertise_neighbor_entry *advertise_neigh_find(
> > +    const struct hmap *neighbors, struct eth_addr mac,
> > +    const struct in6_addr *ip);
> >
> >  uint32_t
> >  advertise_neigh_hash(const struct eth_addr *eth, const struct in6_addr
> *ip)
> > @@ -85,6 +97,16 @@ neighbor_run(struct neighbor_ctx_in *n_ctx_in,
> >              neighbor_interface_monitor_alloc(NEIGH_AF_INET6,
> >                                               NEIGH_IFACE_BRIDGE, vni);
> >          vector_push(n_ctx_out->monitored_interfaces, &br_v6);
> > +
> > +
>
> Nit: too many empty lines.
>
> > +        if (!smap_get_bool(&ld->datapath->external_ids,
> > +                           "dynamic-routing-advertise-fdb", false)) {
> > +            continue;
> > +        }
> > +
> > +        neighbor_collect_mac_to_advertise(n_ctx_in,
> &lo->announced_neighbors,
> > +                                          n_ctx_out->advertised_pbs,
> > +                                          ld->datapath);
> >      }
> >  }
> >
> > @@ -98,6 +120,26 @@ neighbor_cleanup(struct vector *monitored_interfaces)
> >      vector_clear(monitored_interfaces);
> >  }
> >
> > +bool
> > +neighbor_is_relevant_port_updated(struct ovsdb_idl_index
> *sbrec_pb_by_name,
> > +                                  const struct sbrec_chassis *chassis,
> > +                                  struct sset *advertised_pbs,
> > +                                  const struct tracked_lport *lport)
> > +{
> > +    if (lport->tracked_type == TRACKED_RESOURCE_REMOVED &&
> > +        sset_contains(advertised_pbs, lport->pb->logical_port)) {
> > +        return true;
> > +    }
> > +
> > +    if (lport->tracked_type != TRACKED_RESOURCE_NEW) {
> > +        return false;
> > +    }
> > +
> > +    const struct sbrec_port_binding *pb =
> > +        neighbor_get_relevant_port_binding(sbrec_pb_by_name, lport->pb);
> > +    return  pb && lport_pb_is_chassis_resident(chassis, pb);
>
> Nit: too many spaces
>
> > +}
> > +
> >  static void
> >  neighbor_interface_monitor_destroy(struct neighbor_interface_monitor
> *nim)
> >  {
> > @@ -139,3 +181,98 @@ neighbor_interface_monitor_alloc(enum
> neighbor_family family,
> >               neighbor_interface_prefixes[type], vni);
> >      return nim;
> >  }
> > +
> > +static void
> > +neighbor_collect_mac_to_advertise(const struct neighbor_ctx_in
> *n_ctx_in,
> > +                                  struct hmap *neighbors,
> > +                                  struct sset *advertised_pbs,
> > +                                  const struct sbrec_datapath_binding
> *dp)
> > +{
> > +    struct sbrec_port_binding *target =
> > +        sbrec_port_binding_index_init_row(n_ctx_in->sbrec_pb_by_dp);
> > +    sbrec_port_binding_index_set_datapath(target, dp);
> > +
> > +    const struct sbrec_port_binding *dp_pb;
> > +    SBREC_PORT_BINDING_FOR_EACH_EQUAL (dp_pb, target,
> > +                                       n_ctx_in->sbrec_pb_by_dp) {
> > +        const struct sbrec_port_binding *pb =
> > +
> neighbor_get_relevant_port_binding(n_ctx_in->sbrec_pb_by_name,
> > +                                               dp_pb);
> > +        if (!(pb && lport_pb_is_chassis_resident(n_ctx_in->chassis,
> pb))) {
> > +            continue;
> > +        }
> > +
> > +        for (size_t i = 0; i < pb->n_mac; i++) {
> > +            struct lport_addresses addresses;
> > +            if (!extract_lsp_addresses(pb->mac[i], &addresses)) {
> > +                continue;
> > +            }
>
> It's a bit annoying we parse lsp addresses so many times in
> ovn-controller (even before this call).  I'm just ranting, we should
> maybe try to improve this in general in ovn-controller in the future.
>
> > +
> > +            if (!advertise_neigh_find(neighbors, addresses.ea,
> &in6addr_any)) {
> > +                advertise_neigh_add(neighbors, addresses.ea,
> in6addr_any);
> > +            }
> > +
> > +            destroy_lport_addresses(&addresses);
> > +        }
> > +
> > +        sset_add(advertised_pbs, pb->logical_port);
> > +    }
> > +
> > +    sbrec_port_binding_index_destroy_row(target);
> > +}
> > +
> > +static const struct sbrec_port_binding *
> > +neighbor_get_relevant_port_binding(struct ovsdb_idl_index
> *sbrec_pb_by_name,
> > +                                   const struct sbrec_port_binding *pb)
> > +{
> > +    enum en_lport_type type = get_lport_type(pb);
> > +    if (type == LP_VIF || type == LP_VIRTUAL || type == LP_CONTAINER) {
> > +        return pb;
> > +    }
> > +
> > +    if (type == LP_L3GATEWAY) {
> > +        return lport_get_peer(pb, sbrec_pb_by_name);
> > +    }
> > +
> > +    if (type == LP_PATCH) {
> > +        const struct sbrec_port_binding *peer =
> > +            lport_get_peer(pb, sbrec_pb_by_name);
> > +        if (!peer) {
> > +            return NULL;
> > +        }
> > +
> > +        return lport_get_cr_port(sbrec_pb_by_name, peer, NULL);
> > +    }
> > +
> > +    return NULL;
> > +}
> > +
> > +static void
> > +advertise_neigh_add(struct hmap *neighbors, struct eth_addr mac,
> > +                    struct in6_addr ip)
> > +{
> > +    struct advertise_neighbor_entry *ne = xmalloc(sizeof *ne);
> > +    *ne = (struct advertise_neighbor_entry) {
> > +        .lladdr = mac,
> > +        .addr = ip,
> > +    };
> > +
> > +    hmap_insert(neighbors, &ne->node, advertise_neigh_hash(&mac, &ip));
> > +}
> > +
> > +static struct advertise_neighbor_entry *
> > +advertise_neigh_find(const struct hmap *neighbors, struct eth_addr mac,
> > +                     const struct in6_addr *ip)
> > +{
> > +    uint32_t hash = advertise_neigh_hash(&mac, ip);
> > +
> > +    struct advertise_neighbor_entry *ne;
> > +    HMAP_FOR_EACH_WITH_HASH (ne, node, hash, neighbors) {
> > +        if (eth_addr_equals(ne->lladdr, mac) &&
> > +            ipv6_addr_equals(&ne->addr, ip)) {
> > +            return ne;
> > +        }
> > +    }
> > +
> > +    return NULL;
> > +}
> > diff --git a/controller/neighbor.h b/controller/neighbor.h
> > index 3dc21938b..c5d063ad5 100644
> > --- a/controller/neighbor.h
> > +++ b/controller/neighbor.h
> > @@ -33,6 +33,8 @@
> >  #endif
> >  #endif /* __APPLE__ */
> >
> > +struct tracked_lport;
> > +
> >  enum neighbor_family {
> >      NEIGH_AF_INET = AF_INET,
> >      NEIGH_AF_INET6 = AF_INET6,
> > @@ -42,11 +44,18 @@ enum neighbor_family {
> >  struct neighbor_ctx_in {
> >      /* Contains 'struct local_datapath'. */
> >      const struct hmap *local_datapaths;
> > +    /* Index for Port Binding by Datapath. */
> > +    struct ovsdb_idl_index *sbrec_pb_by_dp;
> > +    /* Index for Port Binding by name. */
> > +    struct ovsdb_idl_index *sbrec_pb_by_name;
> > +    const struct sbrec_chassis *chassis;
> >  };
> >
> >  struct neighbor_ctx_out {
> >      /* Contains struct neighbor_interface_monitor pointers. */
> >      struct vector *monitored_interfaces;
> > +    /* Contains set of PB names that are currently advertised. */
>
> Nit: I think I'd expand "PB" into "port binding".
>
> > +    struct sset *advertised_pbs;
> >  };
> >
> >  enum neighbor_interface_type {
> > @@ -79,5 +88,9 @@ uint32_t advertise_neigh_hash(const struct eth_addr *,
> >                                const struct in6_addr *);
> >  void neighbor_run(struct neighbor_ctx_in *, struct neighbor_ctx_out *);
> >  void neighbor_cleanup(struct vector *monitored_interfaces);
> > +bool neighbor_is_relevant_port_updated(
> > +    struct ovsdb_idl_index *sbrec_pb_by_name,
> > +    const struct sbrec_chassis *chassis, struct sset *advertised_pbs,
> > +    const struct tracked_lport *lport);
> >
> >  #endif /* NEIGHBOR_H */
> > diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> > index a3d4f28c6..5786542f6 100644
> > --- a/controller/ovn-controller.c
> > +++ b/controller/ovn-controller.c
> > @@ -5772,6 +5772,8 @@ en_host_if_monitor_run(struct engine_node *node
> OVS_UNUSED, void *data)
> >  struct ed_type_neighbor {
> >      /* Contains struct neighbor_interface_monitor pointers. */
> >      struct vector monitored_interfaces;
> > +    /* Contains set of PB names that are currently advertised. */
> > +    struct sset advertised_pbs;
> >  };
> >
> >  static void *
> > @@ -5783,6 +5785,7 @@ en_neighbor_init(struct engine_node *node
> OVS_UNUSED,
> >      *data = (struct ed_type_neighbor) {
> >          .monitored_interfaces =
> >              VECTOR_EMPTY_INITIALIZER(struct neighbor_interface_monitor
> *),
> > +        .advertised_pbs = SSET_INITIALIZER(&data->advertised_pbs),
> >      };
> >      return data;
> >  }
> > @@ -5794,6 +5797,7 @@ en_neighbor_cleanup(void *data)
> >
> >      neighbor_cleanup(&ne_data->monitored_interfaces);
> >      vector_destroy(&ne_data->monitored_interfaces);
> > +    sset_destroy(&ne_data->advertised_pbs);
> >  }
> >
> >  static enum engine_node_state
> > @@ -5802,25 +5806,68 @@ en_neighbor_run(struct engine_node *node
> OVS_UNUSED, void *data)
> >      struct ed_type_neighbor *ne_data = data;
> >      struct ed_type_runtime_data *rt_data =
> >          engine_get_input_data("runtime_data", node);
> > +    struct ovsdb_idl_index *sbrec_port_binding_by_datapath =
> > +        engine_ovsdb_node_get_index(
> > +            engine_get_input("SB_port_binding", node),
> > +            "datapath");
> > +    struct ovsdb_idl_index *sbrec_port_binding_by_name =
> > +        engine_ovsdb_node_get_index(
> > +            engine_get_input("SB_port_binding", node),
> > +            "name");
> > +    const struct ovsrec_open_vswitch_table *ovs_table =
> > +        EN_OVSDB_GET(engine_get_input("OVS_open_vswitch", node));
> > +    struct ovsdb_idl_index *sbrec_chassis_by_name =
> > +        engine_ovsdb_node_get_index(
> > +            engine_get_input("SB_chassis", node),
> > +            "name");
> > +
> > +    const char *chassis_id = get_ovs_chassis_id(ovs_table);
> > +    ovs_assert(chassis_id);
> > +    const struct sbrec_chassis *chassis =
> > +        chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
> > +    ovs_assert(chassis);
> > +
> >      struct neighbor_ctx_in n_ctx_in = {
> >          .local_datapaths = &rt_data->local_datapaths,
> > +        .sbrec_pb_by_dp = sbrec_port_binding_by_datapath,
> > +        .sbrec_pb_by_name = sbrec_port_binding_by_name,
> > +        .chassis = chassis,
> >      };
> >
> >      struct neighbor_ctx_out n_ctx_out = {
> >          .monitored_interfaces = &ne_data->monitored_interfaces,
> > +        .advertised_pbs = &ne_data->advertised_pbs,
> >      };
> >
> >      neighbor_cleanup(&ne_data->monitored_interfaces);
> > +    sset_clear(&ne_data->advertised_pbs);
> >      neighbor_run(&n_ctx_in, &n_ctx_out);
> >
> >      return EN_UPDATED;
> >  }
> >
> >  static enum engine_input_handler_result
> > -neighbor_runtime_data_handler(struct engine_node *node, void *data
> OVS_UNUSED)
> > +neighbor_runtime_data_handler(struct engine_node *node, void *data)
> >  {
> > +    struct ed_type_neighbor *ne_data = data;
> >      struct ed_type_runtime_data *rt_data =
> >          engine_get_input_data("runtime_data", node);
> > +    struct ovsdb_idl_index *sbrec_port_binding_by_name =
> > +        engine_ovsdb_node_get_index(
> > +            engine_get_input("SB_port_binding", node),
> > +            "name");
> > +    const struct ovsrec_open_vswitch_table *ovs_table =
> > +        EN_OVSDB_GET(engine_get_input("OVS_open_vswitch", node));
> > +    struct ovsdb_idl_index *sbrec_chassis_by_name =
> > +        engine_ovsdb_node_get_index(
> > +            engine_get_input("SB_chassis", node),
> > +            "name");
> > +
> > +    const char *chassis_id = get_ovs_chassis_id(ovs_table);
> > +    ovs_assert(chassis_id);
> > +    const struct sbrec_chassis *chassis =
> > +        chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
> > +    ovs_assert(chassis);
> >
> >      /* There are no tracked data. Fall back to full recompute. */
> >      if (!rt_data->tracked) {
> > @@ -5845,6 +5892,21 @@ neighbor_runtime_data_handler(struct engine_node
> *node, void *data OVS_UNUSED)
> >              tdp->tracked_type == TRACKED_RESOURCE_REMOVED) {
> >              return EN_UNHANDLED;
> >          }
> > +
> > +        if (!smap_get_bool(&ld->datapath->external_ids,
> > +                           "dynamic-routing-advertise-fdb", false)) {
> > +            continue;
> > +        }
> > +
> > +        const struct shash_node *shash_node;
> > +        SHASH_FOR_EACH (shash_node, &tdp->lports) {
> > +            if
> (neighbor_is_relevant_port_updated(sbrec_port_binding_by_name,
> > +                                                  chassis,
> > +
> &ne_data->advertised_pbs,
> > +                                                  shash_node->data)) {
> > +                return EN_UNHANDLED;
> > +            }
> > +        }
> >      }
> >
> >      return EN_HANDLED_UNCHANGED;
> > @@ -5882,6 +5944,30 @@ neighbor_sb_datapath_binding_handler(struct
> engine_node *node,
> >      return EN_HANDLED_UNCHANGED;
> >  }
> >
> > +static enum engine_input_handler_result
> > +neighbor_sb_port_binding_handler(struct engine_node *node, void *data)
> > +{
> > +    struct ed_type_neighbor *ne_data = data;
> > +    const struct sbrec_port_binding_table *pb_table =
> > +        EN_OVSDB_GET(engine_get_input("SB_port_binding", node));
> > +
> > +    const struct sbrec_port_binding *pb;
> > +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb, pb_table) {
> > +        if (sbrec_port_binding_is_new(pb) ||
> > +            sbrec_port_binding_is_deleted(pb)) {
> > +            /* The removal and addition is handled via runtime_data. */
> > +            return EN_HANDLED_UNCHANGED;
> > +        }
> > +
> > +        if (sbrec_port_binding_is_updated(pb,
> SBREC_PORT_BINDING_COL_MAC) &&
> > +            sset_contains(&ne_data->advertised_pbs, pb->logical_port)) {
> > +            return EN_UNHANDLED;
> > +        }
> > +    }
> > +
> > +    return EN_HANDLED_UNCHANGED;
> > +}
> > +
> >  struct ed_type_neighbor_table_notify {
> >      /* For incremental processing this could be tracked per interface in
> >       * the future. */
> > @@ -6775,10 +6861,14 @@ main(int argc, char *argv[])
> >      engine_add_input(&en_garp_rarp, &en_runtime_data,
> >                       garp_rarp_runtime_data_handler);
> >
> > +    engine_add_input(&en_neighbor, &en_ovs_open_vswitch, NULL);
> > +    engine_add_input(&en_neighbor, &en_sb_chassis, NULL);
> >      engine_add_input(&en_neighbor, &en_runtime_data,
> >                       neighbor_runtime_data_handler);
> >      engine_add_input(&en_neighbor, &en_sb_datapath_binding,
> >                       neighbor_sb_datapath_binding_handler);
> > +    engine_add_input(&en_neighbor, &en_sb_port_binding,
> > +                     neighbor_sb_port_binding_handler);
> >      engine_add_input(&en_neighbor_exchange, &en_neighbor, NULL);
> >      engine_add_input(&en_neighbor_exchange, &en_host_if_monitor, NULL);
> >      engine_add_input(&en_neighbor_exchange, &en_neighbor_table_notify,
> NULL);
> > diff --git a/northd/northd.c b/northd/northd.c
> > index b45cac3cf..cd9409023 100644
> > --- a/northd/northd.c
> > +++ b/northd/northd.c
> > @@ -830,6 +830,11 @@ ovn_datapath_update_external_ids(struct
> ovn_datapath *od)
> >                                         "dynamic-routing-vni");
> >              smap_add(&ids, "dynamic-routing-vni", vni);
> >          }
> > +
> > +        smap_add(&ids, "dynamic-routing-advertise-fdb",
> > +                 drr_mode_FDB_is_set(od->dynamic_routing_redistribute)
> > +                 ? "true"
> > +                 : "false");
> >      }
> >
> >      /* Set snat-ct-zone */
> > @@ -906,6 +911,10 @@ parse_dynamic_routing_redistribute(
> >              out |= DRRM_LB;
> >              continue;
> >          }
> > +        if (!strcmp(token, "fdb")) {
> > +            out |= DRRM_FDB;
> > +            continue;
> > +        }
>
> As parse_dynamic_routing_redistribute() is now used for both switches
> and routers (router ports) should we validate the parsed values in the
> callers?
>

I have added an extra argument to the function.


>
> >          static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
> >          VLOG_WARN_RL(&rl,
> >                       "unknown dynamic-routing-redistribute option '%s'
> on %s",
> > @@ -991,6 +1000,10 @@ join_datapaths(const struct
> nbrec_logical_switch_table *nbrec_ls_table,
> >          if (ovn_is_valid_vni(vni)) {
> >              od->has_evpn_vni = true;
> >          }
> > +
> > +        od->dynamic_routing_redistribute =
> > +            parse_dynamic_routing_redistribute(&od->nbs->other_config,
> > +                                               DRRM_NONE,
> od->nbs->name);
> >      }
> >
> >      const struct nbrec_logical_router *nbr;
> > diff --git a/northd/northd.h b/northd/northd.h
> > index fbd76704d..08730d4f5 100644
> > --- a/northd/northd.h
> > +++ b/northd/northd.h
> > @@ -322,7 +322,8 @@ struct mcast_port_info {
> >      DRR_MODE(CONNECTED_AS_HOST, 1) \
> >      DRR_MODE(STATIC,            2) \
> >      DRR_MODE(NAT,               3) \
> > -    DRR_MODE(LB,                4)
> > +    DRR_MODE(LB,                4) \
> > +    DRR_MODE(FDB,               5)
> >
> >  enum dynamic_routing_redistribute_mode_bits {
> >  #define DRR_MODE(PROTOCOL, BIT) DRRM_##PROTOCOL##_BIT = BIT,
> > diff --git a/ovn-nb.xml b/ovn-nb.xml
> > index 065bffd3f..a3e8da0fb 100644
> > --- a/ovn-nb.xml
> > +++ b/ovn-nb.xml
> > @@ -889,6 +889,30 @@
> >            removal/change in the future.
> >          </p>
> >        </column>
> > +
> > +      <column name="other_config" key="dynamic-routing-redistribute"
> > +              type='{"type": "string"}'>
> > +        <p>
> > +          Only relevant if <ref column="other_config"
> key="dynamic-routing-vni"
> > +          table="Logical_switch"/> is set to valid VNI.
> > +        </p>
> > +
> > +        <p>
> > +          This is a list of elements separated by <code>,</code>.
>
> This sounds a bit weird as we only allow one value. :)
>

True. I have updated that in v2.


>
> > +        </p>
> > +
> > +        <p>
> > +          If <code>fdb</code> is in the list then ovn-controller will
> > +          all workloads that are local to the chassis. The applies to
> VIFs,
> > +          container ports, virtual ports, connected DGPs and connected
> GW
> > +          routers.
> > +        </p>
> > +
> > +        <p>
> > +          NOTE: this feature is experimental and may be subject to
> > +          removal/change in the future.
> > +        </p>
> > +      </column>
> >      </group>
> >
> >      <group title="IP Multicast Snooping Options">
>
> Regards,
> Dumitru
>
>
Thanks,
Ales
_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to