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. > 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? > 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. :) > + </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 _______________________________________________ dev mailing list d...@openvswitch.org https://mail.openvswitch.org/mailman/listinfo/ovs-dev