On 10/16/25 6:51 PM, Lorenzo Bianconi wrote:
> Introduce the capability to ovn-controller to announce local ips/macs
> bindings via EVPN route type2 messages in the EVPN domain.
> 
> Signed-off-by: Lorenzo Bianconi <[email protected]>
> ---

Hi Lorenzo,

Thanks for the patch!

>  controller/neighbor.c       | 62 ++++++++++++++++++++++++++++++++++---
>  controller/neighbor.h       |  2 ++
>  controller/ovn-controller.c | 23 +++++++++++++-
>  tests/system-ovn.at         | 34 ++++++++++++++++++++
>  4 files changed, 115 insertions(+), 6 deletions(-)
> 
> diff --git a/controller/neighbor.c b/controller/neighbor.c
> index 1870159bc..fadec7044 100644
> --- a/controller/neighbor.c
> +++ b/controller/neighbor.c
> @@ -20,6 +20,7 @@
>  #include "lib/sset.h"
>  #include "local_data.h"
>  #include "lport.h"
> +#include "openvswitch/ofp-parse.h"
>  #include "ovn-sb-idl.h"
>  
>  #include "neighbor.h"
> @@ -42,6 +43,10 @@ neighbor_interface_monitor_alloc(enum neighbor_family 
> family,
>  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 void neighbor_collect_route_type2_to_advertise(

As mentioned in patch 2/4, I think "type2" might be too specific.  Maybe
just "*_collect_ip_mac_to_advertise()"?

> +    const struct neighbor_ctx_in *,
> +    struct hmap *neighbors_v4, struct hmap *neighbors_v6,
> +    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 *);
> @@ -114,13 +119,20 @@ neighbor_run(struct neighbor_ctx_in *n_ctx_in,
>  
>          const char *redistribute = smap_get(&ld->datapath->external_ids,
>                                              "dynamic-routing-redistribute");
> -        if (!redistribute || strcmp(redistribute, "fdb")) {
> -            continue;
> +        if (redistribute && !strcmp(redistribute, "fdb")) {
> +            neighbor_collect_mac_to_advertise(n_ctx_in,
> +                                              &lo->announced_neighbors,
> +                                              n_ctx_out->advertised_pbs,
> +                                              ld->datapath);
> +        }
> +        if (redistribute && !strcmp(redistribute, "ip")) {
> +            neighbor_collect_route_type2_to_advertise(n_ctx_in,
> +                    &br_v4->announced_neighbors,
> +                    &br_v6->announced_neighbors,
> +                    n_ctx_out->advertised_pbs,
> +                    ld->datapath);
>          }
>  
> -        neighbor_collect_mac_to_advertise(n_ctx_in, &lo->announced_neighbors,
> -                                          n_ctx_out->advertised_pbs,
> -                                          ld->datapath);
>      }
>  }
>  
> @@ -236,6 +248,46 @@ neighbor_collect_mac_to_advertise(const struct 
> neighbor_ctx_in *n_ctx_in,
>      sbrec_port_binding_index_destroy_row(target);
>  }
>  
> +static void
> +neighbor_collect_route_type2_to_advertise(
> +        const struct neighbor_ctx_in *n_ctx_in,
> +        struct hmap *neighbors_v4,
> +        struct hmap *neighbors_v6,
> +        struct sset *advertised_pbs,
> +        const struct sbrec_datapath_binding *dp)
> +{
> +    struct sbrec_advertised_mac_binding *target =
> +        sbrec_advertised_mac_binding_index_init_row(
> +                n_ctx_in->sbrec_amb_by_dp);
> +    sbrec_advertised_mac_binding_index_set_datapath(target, dp);
> +
> +    const struct sbrec_advertised_mac_binding *adv_mb;
> +    SBREC_ADVERTISED_MAC_BINDING_FOR_EACH_EQUAL (adv_mb, target,
> +                                                 n_ctx_in->sbrec_amb_by_dp) {

We need a similar check to what we do in
neighbor_collect_mac_to_advertise().  We only want to advertise the
mac+ip binding if the port is local.  That's because we don't want to
generate suboptimal routing in the fabric.  The MAC+IP will be
advertised only on the chassis where the MAC+IP actually resides.  So
maybe something like:

if (!lport_pb_is_chassis_resident(n_ctx_in->chassis,
                                  adv_mb->logical_port)) {
    continue;
}

We might have to adjust the check for distributed patch ports though.

> +        struct in6_addr route;

s/route/ip/

> +        unsigned int plen;
> +        if (!ip46_parse_cidr(adv_mb->ip, &route, &plen)) {

It's not a route, it's an IP.  ip46_parse() is good enough.

> +            continue;
> +        }
> +
> +        struct eth_addr ea;
> +        char *err = str_to_mac(adv_mb->mac, &ea);
> +        if (err) {
> +            free(err);
> +            continue;
> +        }
> +
> +        struct hmap *neighbors = IN6_IS_ADDR_V4MAPPED(&route)
> +            ? neighbors_v4 : neighbors_v6;

Nit: according to our coding style guidelines, this should be indented
differently:

struct hmap *neighbors = IN6_IS_ADDR_V4MAPPED(&route)
                         ? neighbors_v4 : neighbors_v6;

> +        if (!advertise_neigh_find(neighbors, ea, &route)) {
> +            advertise_neigh_add(neighbors, ea, route);
> +        }
> +        sset_add(advertised_pbs, adv_mb->logical_port->logical_port);
> +    }
> +
> +    sbrec_advertised_mac_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)
> diff --git a/controller/neighbor.h b/controller/neighbor.h
> index ce73dd4c4..e3adc87d1 100644
> --- a/controller/neighbor.h
> +++ b/controller/neighbor.h
> @@ -44,6 +44,8 @@ struct neighbor_ctx_in {
>      const struct hmap *local_datapaths;
>      /* Index for Port Binding by Datapath. */
>      struct ovsdb_idl_index *sbrec_pb_by_dp;
> +    /* Index for Advertised_Mac_Binding by Datapath. */
> +    struct ovsdb_idl_index *sbrec_amb_by_dp;
>      /* Index for Port Binding by name. */
>      struct ovsdb_idl_index *sbrec_pb_by_name;
>      const struct sbrec_chassis *chassis;
> diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> index 6fbf3a227..ab6aba0e4 100644
> --- a/controller/ovn-controller.c
> +++ b/controller/ovn-controller.c
> @@ -267,6 +267,7 @@ update_sb_monitors(struct ovsdb_idl *ovnsb_idl,
>      struct ovsdb_idl_condition nh = OVSDB_IDL_CONDITION_INIT(&nh);
>      struct ovsdb_idl_condition ar = OVSDB_IDL_CONDITION_INIT(&ar);
>      struct ovsdb_idl_condition lr = OVSDB_IDL_CONDITION_INIT(&lr);
> +    struct ovsdb_idl_condition amb = OVSDB_IDL_CONDITION_INIT(&amb);
>  
>      /* Always monitor all logical datapath groups. Otherwise, DPG updates may
>       * be received *after* the lflows using it are seen by ovn-controller.
> @@ -301,6 +302,7 @@ update_sb_monitors(struct ovsdb_idl *ovnsb_idl,
>          ovsdb_idl_condition_add_clause_true(&tv);
>          ovsdb_idl_condition_add_clause_true(&nh);
>          ovsdb_idl_condition_add_clause_true(&ar);
> +        ovsdb_idl_condition_add_clause_true(&amb);
>          goto out;
>      }
>  
> @@ -400,6 +402,8 @@ update_sb_monitors(struct ovsdb_idl *ovnsb_idl,
>                                                     uuid);
>              sbrec_ecmp_nexthop_add_clause_datapath(&nh, OVSDB_F_EQ, uuid);
>              sbrec_advertised_route_add_clause_datapath(&ar, OVSDB_F_EQ, 
> uuid);
> +            sbrec_advertised_mac_binding_add_clause_datapath(&amb, 
> OVSDB_F_EQ,
> +                                                             uuid);
>          }
>  
>          /* Datapath groups are immutable, which means a new group record is
> @@ -430,6 +434,8 @@ out:;
>          sb_table_set_opt_mon_condition(ovnsb_idl, ecmp_nexthop, &nh),
>          sb_table_set_opt_mon_condition(ovnsb_idl, advertised_route, &ar),
>          sb_table_set_opt_mon_condition(ovnsb_idl, learned_route, &lr),
> +        sb_table_set_opt_mon_condition(ovnsb_idl, advertised_mac_binding,
> +                                       &amb),
>      };
>  
>      unsigned int expected_cond_seqno = 0;
> @@ -452,6 +458,7 @@ out:;
>      ovsdb_idl_condition_destroy(&nh);
>      ovsdb_idl_condition_destroy(&ar);
>      ovsdb_idl_condition_destroy(&lr);
> +    ovsdb_idl_condition_destroy(&amb);
>      return expected_cond_seqno;
>  }
>  
> @@ -1014,7 +1021,8 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
>      SB_NODE(chassis_template_var) \
>      SB_NODE(acl_id) \
>      SB_NODE(advertised_route) \
> -    SB_NODE(learned_route)
> +    SB_NODE(learned_route) \
> +    SB_NODE(advertised_mac_binding)
>  
>  enum sb_engine_node {
>  #define SB_NODE(NAME) SB_##NAME,
> @@ -5872,6 +5880,10 @@ en_neighbor_run(struct engine_node *node OVS_UNUSED, 
> void *data)
>          engine_ovsdb_node_get_index(
>              engine_get_input("SB_chassis", node),
>              "name");
> +    struct ovsdb_idl_index *sbrec_advertised_mac_binding_by_datapath =
> +        engine_ovsdb_node_get_index(
> +            engine_get_input("SB_advertised_mac_binding", node),
> +            "datapath");
>  
>      const char *chassis_id = get_ovs_chassis_id(ovs_table);
>      ovs_assert(chassis_id);
> @@ -5882,6 +5894,7 @@ en_neighbor_run(struct engine_node *node OVS_UNUSED, 
> void *data)
>      struct neighbor_ctx_in n_ctx_in = {
>          .local_datapaths = &rt_data->local_datapaths,
>          .sbrec_pb_by_dp = sbrec_port_binding_by_datapath,
> +        .sbrec_amb_by_dp = sbrec_advertised_mac_binding_by_datapath,
>          .sbrec_pb_by_name = sbrec_port_binding_by_name,
>          .chassis = chassis,
>      };
> @@ -6684,6 +6697,9 @@ main(int argc, char *argv[])
>      struct ovsdb_idl_index *sbrec_learned_route_index_by_datapath
>          = ovsdb_idl_index_create1(ovnsb_idl_loop.idl,
>                                    &sbrec_learned_route_col_datapath);
> +    struct ovsdb_idl_index *sbrec_advertised_mac_binding_index_by_dp
> +        = ovsdb_idl_index_create1(ovnsb_idl_loop.idl,
> +                                  
> &sbrec_advertised_mac_binding_col_datapath);
>  
>      ovsdb_idl_track_add_all(ovnsb_idl_loop.idl);
>      ovsdb_idl_omit_alert(ovnsb_idl_loop.idl,
> @@ -6718,6 +6734,8 @@ main(int argc, char *argv[])
>                     &sbrec_advertised_route_col_external_ids);
>      ovsdb_idl_omit(ovnsb_idl_loop.idl,
>                     &sbrec_learned_route_col_external_ids);
> +    ovsdb_idl_omit(ovnsb_idl_loop.idl,
> +                   &sbrec_advertised_mac_binding_col_external_ids);
>  
>      /* We don't want to monitor Connection table at all. So omit all the
>       * columns. */
> @@ -7019,6 +7037,7 @@ main(int argc, char *argv[])
>  
>      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_sb_advertised_mac_binding, NULL);
>      engine_add_input(&en_neighbor, &en_runtime_data,
>                       neighbor_runtime_data_handler);
>      engine_add_input(&en_neighbor, &en_sb_datapath_binding,
> @@ -7108,6 +7127,8 @@ main(int argc, char *argv[])
>                                  sbrec_chassis_template_var_index_by_chassis);
>      engine_ovsdb_node_add_index(&en_sb_learned_route, "datapath",
>                                  sbrec_learned_route_index_by_datapath);
> +    engine_ovsdb_node_add_index(&en_sb_advertised_mac_binding, "datapath",
> +                                sbrec_advertised_mac_binding_index_by_dp);
>      engine_ovsdb_node_add_index(&en_sb_mac_binding, "lport_ip",
>                                  sbrec_mac_binding_by_lport_ip);
>      engine_ovsdb_node_add_index(&en_ovs_flow_sample_collector_set, "id",
> diff --git a/tests/system-ovn.at b/tests/system-ovn.at
> index 973d2404e..99acae128 100644
> --- a/tests/system-ovn.at
> +++ b/tests/system-ovn.at
> @@ -18759,6 +18759,40 @@ AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int 
> table=OFTABLE_MAC_LOOKUP | grep p
>                     awk '{print $7, $8}' | sort], [0], [dnl
>  ])
>  
> +AS_BOX([L2 EVPN ARP advertising])
> +# Add a router connected to the EVPN logical switch.
> +check ovn-nbctl --wait=hv                                    \
> +    -- lr-add lr                                             \
> +    -- lrp-add lr lr-ls-evpn f0:00:0f:16:01:01 172.16.1.1/24
> +
> +ls_evpn_uuid=$(fetch_column Datapath_Binding _uuid external_ids:name=ls-evpn)
> +check ovn-nbctl --wait=hv set logical_switch ls-evpn 
> other_config:dynamic-routing-redistribute=ip
> +wait_row_count Advertised_Mac_Binding 1 ip=172.16.1.10/32 
> mac='f0\:00\:0f\:16\:01\:10' datapath=$ls_evpn_uuid
> +wait_row_count Advertised_Mac_Binding 1 ip=172.16.1.1/32 
> mac='f0\:00\:0f\:16\:01\:01' datapath=$ls_evpn_uuid

I think this can be check_row_count.

> +
> +AT_CHECK_UNQUOTED([ip neigh show dev br-10 nud noarp | grep -q '172.16.1.10 
> lladdr f0:00:0f:16:01:10 NOARP'])
> +AT_CHECK_UNQUOTED([ip neigh show dev br-10 nud noarp | grep -q '172.16.1.1 
> lladdr f0:00:0f:16:01:01 NOARP'])
> +
> +check ovn-nbctl --wait=hv lsp-add ls-evpn workload2         \
> +    -- lsp-set-addresses workload2 "f0:00:0f:16:01:20 172.16.1.20"
> +
> +wait_row_count Advertised_Mac_Binding 1 ip=172.16.1.20/32 
> mac='f0\:00\:0f\:16\:01\:20' datapath=$ls_evpn_uuid

This too.

> +AT_CHECK_UNQUOTED([ip neigh show dev br-10 nud noarp | grep -q '172.16.1.20 
> lladdr f0:00:0f:16:01:20 NOARP'])
> +
> +check ovn-nbctl --wait=hv lsp-del workload2
> +AT_CHECK_UNQUOTED([ip neigh show dev br-10 nud noarp | grep -q '172.16.1.20 
> lladdr f0:00:0f:16:01:20 NOARP'], [1])
> +
> +check ovn-nbctl --wait=hv lsp-del workload1
> +wait_row_count Advertised_Mac_Binding 1

This too?

> +AT_CHECK_UNQUOTED([ip neigh show dev br-10 nud noarp | grep -q '172.16.1.10 
> lladdr f0:00:0f:16:01:10 NOARP'], [1])
> +
> +check ovn-nbctl --wait=hv lrp-del lr-ls-evpn
> +wait_row_count Advertised_Mac_Binding 0
> +AT_CHECK_UNQUOTED([ip neigh show dev br-10 nud noarp | grep -q '172.16.1.1 
> lladdr f0:00:0f:16:01:01 NOARP'], [1])
> +
> +check ovn-nbctl --wait=hv ls-del ls-evpn
> +check ovn-nbctl --wait=hv lr-del lr
> +
>  OVN_CLEANUP_CONTROLLER([hv1])
>  
>  as ovn-sb

Regards,
Dumitru

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

Reply via email to