On 8/12/25 4:56 PM, Ales Musil via dev wrote:
> Add physical flows based on the EVPN bindings and multicast groups
> that ensure the proper flow of traffic that is going through the
> EVPN. In order for this to wrk the CMS needs to ensure that there
> is proper VXLAN tunnel port in br-int and also the
> "external_ids:ovn-evpn-local-ip" is set properly in the local OvS
> database.
> 
> All traffic will be flooded through the UNKNOWN group if there is
> unknown port at this stage. That's due to missing support for FDB
> learning.
> 
> Signed-off-by: Ales Musil <amu...@redhat.com>
> ---
> v3: Rebase on top of latest main.
>     Add an assert that mc groups have some bindings.
> 
> v2: Rebase on top of latest main.
>     Add WARN about mossing local_ip.
> ---
>  NEWS                            |   3 +
>  TODO.rst                        |   4 +
>  controller/chassis.c            |  18 +++
>  controller/lflow.h              |   7 +-
>  controller/ovn-controller.8.xml |   8 +
>  controller/ovn-controller.c     |  30 +++-
>  controller/physical.c           | 276 ++++++++++++++++++++++++++++----
>  controller/physical.h           |   8 +
>  include/ovn/logical-fields.h    |   3 +
>  tests/ovn-macros.at             |   5 +-
>  tests/ovn.at                    |   2 +-
>  11 files changed, 326 insertions(+), 38 deletions(-)
> 
> diff --git a/NEWS b/NEWS
> index ec52f0d90..6aef31fa4 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -49,6 +49,9 @@ Post v25.03.0
>       * Add the option "external_ids:ovn-evpn-vxlan-ports" into OvS datapath.
>         This option allows CMS to set list of UDP destination ports that will 
> be
>         used to create the VXLAN tunnels.
> +     * Add the option "external_ids:ovn-evpn-local-ip" into OvS datapath.

Same comment as for the previous NEWS entry.

> +       This option allows CMS to set IP address that will be used as source
> +       for the EVPN traffic egressing through the tunnel.
>  
>  OVN v25.03.0 - 07 Mar 2025
>  --------------------------
> diff --git a/TODO.rst b/TODO.rst
> index 85208bfe3..9a9df78f2 100644
> --- a/TODO.rst
> +++ b/TODO.rst
> @@ -153,3 +153,7 @@ OVN To-do List
>      monitoring conditions to update before we actually try to learn routes.
>      Otherwise we could try to add duplicated Learned_Routes and the ovnsb
>      commit would fail.
> +
> +  * Allow ovn-evpn-local-ip to accept list of
> +    $VNI1:$LOCAL_IP1,$VNI2:$LOCAL_IP2 combinations which will be properly
> +    reflected in physical flows for given LS with VNI.
> diff --git a/controller/chassis.c b/controller/chassis.c
> index 34adc98be..772a57c7f 100644
> --- a/controller/chassis.c
> +++ b/controller/chassis.c
> @@ -59,6 +59,7 @@ struct ovs_chassis_cfg {
>      const char *trim_wmark_perc_lflow_cache;
>      const char *trim_timeout_ms;
>      const char *evpn_vxlan_port;
> +    const char *evpn_local_ip;
>  
>      /* Set of encap types parsed from the 'ovn-encap-type' external-id. */
>      struct sset encap_type_set;
> @@ -206,6 +207,13 @@ get_evpn_vxlan_port(const struct smap *ext_ids, const 
> char *chassis_id)
>                                           "ovn-evpn-vxlan-ports", "");
>  }
>  
> +static const char *
> +get_evpn_local_ip(const struct smap *ext_ids, const char *chassis_id)
> +{
> +    return get_chassis_external_id_value(ext_ids, chassis_id,
> +                                         "ovn-evpn-local-ip", "true");

Why IP defaults to "true"?

> +}
> +
>  static const char *
>  get_datapath_type(const struct ovsrec_bridge *br_int)
>  {
> @@ -342,6 +350,8 @@ chassis_parse_ovs_config(const struct 
> ovsrec_open_vswitch_table *ovs_table,
>          get_trim_timeout(&cfg->external_ids, chassis_id);
>      ovs_cfg->evpn_vxlan_port =
>          get_evpn_vxlan_port(&cfg->external_ids, chassis_id);
> +    ovs_cfg->evpn_local_ip =
> +        get_evpn_local_ip(&cfg->external_ids, chassis_id);
>  
>      chassis_parse_ovs_encap_type(encap_type, &ovs_cfg->encap_type_set);
>  
> @@ -395,6 +405,7 @@ chassis_build_other_config(const struct ovs_chassis_cfg 
> *ovs_cfg,
>      smap_replace(config, "iface-types", ds_cstr_ro(&ovs_cfg->iface_types));
>      smap_replace(config, "ovn-chassis-mac-mappings", ovs_cfg->chassis_macs);
>      smap_replace(config, "ovn-evpn-vxlan-ports", ovs_cfg->evpn_vxlan_port);
> +    smap_replace(config, "ovn-evpn-local-ip", ovs_cfg->evpn_local_ip);
>      smap_replace(config, "is-interconn",
>                   ovs_cfg->is_interconn ? "true" : "false");
>      smap_replace(config, OVN_FEATURE_PORT_UP_NOTIF, "true");
> @@ -521,6 +532,12 @@ chassis_other_config_changed(const struct 
> ovs_chassis_cfg *ovs_cfg,
>          return true;
>      }
>  
> +    const char *chassis_evpn_local_ip =
> +        get_evpn_local_ip(&chassis_rec->other_config, chassis_rec->name);
> +    if (strcmp(ovs_cfg->evpn_local_ip, chassis_evpn_local_ip)) {
> +        return true;
> +    }
> +
>  
>      if (!smap_get_bool(&chassis_rec->other_config, OVN_FEATURE_PORT_UP_NOTIF,
>                         false)) {
> @@ -741,6 +758,7 @@ update_supported_sset(struct sset *supported)
>      sset_add(supported, "ovn-chassis-mac-mappings");
>      sset_add(supported, "is-interconn");
>      sset_add(supported, "ovn-evpn-vxlan-ports");
> +    sset_add(supported, "ovn-evpn-local-ip");
>  
>      /* Internal options. */
>      sset_add(supported, "is-vtep");
> diff --git a/controller/lflow.h b/controller/lflow.h
> index fa9e795e0..c2d277078 100644
> --- a/controller/lflow.h
> +++ b/controller/lflow.h
> @@ -70,14 +70,15 @@ struct uuid;
>  #define OFTABLE_OUTPUT_LARGE_PKT_DETECT  41
>  #define OFTABLE_OUTPUT_LARGE_PKT_PROCESS 42
>  #define OFTABLE_REMOTE_OUTPUT            43
> -#define OFTABLE_LOCAL_OUTPUT             44
> -#define OFTABLE_CHECK_LOOPBACK           45
> +#define OFTABLE_REMOTE_VTEP_OUTPUT       44
> +#define OFTABLE_LOCAL_OUTPUT             45
> +#define OFTABLE_CHECK_LOOPBACK           46
>  
>  /* Start of the OUTPUT section of the pipeline. */
>  #define OFTABLE_OUTPUT_INIT OFTABLE_OUTPUT_LARGE_PKT_DETECT
>  
>  /* Start of LOG_PIPELINE_LEN tables. */
> -#define OFTABLE_LOG_EGRESS_PIPELINE      46
> +#define OFTABLE_LOG_EGRESS_PIPELINE      47
>  #define OFTABLE_SAVE_INPORT              64
>  #define OFTABLE_LOG_TO_PHY               65
>  #define OFTABLE_MAC_BINDING              66
> diff --git a/controller/ovn-controller.8.xml b/controller/ovn-controller.8.xml
> index e602ebca8..c6fd0780c 100644
> --- a/controller/ovn-controller.8.xml
> +++ b/controller/ovn-controller.8.xml
> @@ -399,6 +399,14 @@
>          the EVPN tunnels created by this ovn-controller. NOTE: this feature 
> is
>          experimental and may be subject to removal/change in the future.
>        </dd>
> +
> +      <dt><code>external_ids:ovn-evpn-local-ip</code></dt>
> +      <dd>
> +        IP address used as a source address for the EVPN traffic leaving this
> +        ovn-controller. There is currently support only for single IP address

s/ovn-controller/OVN setup/ ?

> +        being specified. NOTE: this feature is experimental and may be 
> subject
> +        to removal/change in the future.
> +      </dd>
>      </dl>
>  
>      <p>
> diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> index 1cda8e71e..86cf5f537 100644
> --- a/controller/ovn-controller.c
> +++ b/controller/ovn-controller.c
> @@ -4630,6 +4630,9 @@ static void init_physical_ctx(struct engine_node *node,
>      struct ed_type_northd_options *n_opts =
>          engine_get_input_data("northd_options", node);
>  
> +    struct ed_type_evpn_vtep_binding *eb_data =
> +        engine_get_input_data("evpn_vtep_binding", node);
> +
>      parse_encap_ips(ovs_table, &p_ctx->n_encap_ips, &p_ctx->encap_ips);
>      p_ctx->sbrec_port_binding_by_name = sbrec_port_binding_by_name;
>      p_ctx->sbrec_port_binding_by_datapath = sbrec_port_binding_by_datapath;
> @@ -4647,6 +4650,8 @@ static void init_physical_ctx(struct engine_node *node,
>      p_ctx->patch_ofports = &non_vif_data->patch_ofports;
>      p_ctx->chassis_tunnels = &non_vif_data->chassis_tunnels;
>      p_ctx->always_tunnel = n_opts->always_tunnel;
> +    p_ctx->evpn_bindings = &eb_data->bindings;
> +    p_ctx->evpn_multicast_groups = &eb_data->multicast_groups;
>  
>      struct controller_engine_ctx *ctrl_ctx = 
> engine_get_context()->client_ctx;
>      p_ctx->if_mgr = ctrl_ctx->if_mgr;
> @@ -4936,6 +4941,28 @@ pflow_output_debug_handler(struct engine_node *node, 
> void *data)
>      }
>      return EN_HANDLED_UPDATED;
>  }
> +static enum engine_input_handler_result
> +pflow_output_evpn_binding_handler(struct engine_node *node, void *data)
> +{
> +    struct ed_type_pflow_output *pfo = data;
> +    struct ed_type_runtime_data *rt_data =
> +        engine_get_input_data("runtime_data", node);
> +    struct ed_type_non_vif_data *non_vif_data =
> +        engine_get_input_data("non_vif_data", node);
> +    struct ed_type_evpn_vtep_binding *eb_data =
> +        engine_get_input_data("evpn_vtep_binding", node);
> +
> +    struct physical_ctx ctx;
> +    init_physical_ctx(node, rt_data, non_vif_data, &ctx);
> +
> +    physical_handle_evpn_binding_changes(&ctx, &pfo->flow_table,
> +                                         &eb_data->updated_bindings,
> +                                         &eb_data->updated_multicast_groups,
> +                                         &eb_data->removed_bindings,
> +                                         &eb_data->removed_multicast_groups);
> +    destroy_physical_ctx(&ctx);
> +    return EN_HANDLED_UPDATED;
> +}
>  
>  static void *
>  en_controller_output_init(struct engine_node *node OVS_UNUSED,
> @@ -6672,9 +6699,8 @@ main(int argc, char *argv[])
>      engine_add_input(&en_evpn_vtep_binding, &en_sb_datapath_binding,
>                       evpn_vtep_binding_datapath_binding_handler);
>  
> -    /* TODO Add real handler to process all bindings. */
>      engine_add_input(&en_pflow_output, &en_evpn_vtep_binding,
> -                     engine_noop_handler);
> +                     pflow_output_evpn_binding_handler);
>  
>      engine_add_input(&en_controller_output, &en_dns_cache,
>                       NULL);
> diff --git a/controller/physical.c b/controller/physical.c
> index c5f74010a..78cd98b61 100644
> --- a/controller/physical.c
> +++ b/controller/physical.c
> @@ -19,6 +19,7 @@
>  #include "byte-order.h"
>  #include "ct-zone.h"
>  #include "encaps.h"
> +#include "evpn-binding.h"
>  #include "flow.h"
>  #include "ha-chassis.h"
>  #include "lflow.h"
> @@ -475,7 +476,7 @@ put_remote_port_redirect_overlay(const struct 
> sbrec_port_binding *binding,
>                                    port_key, is_vtep_port, ofpacts_clone);
>                  ofpact_put_OUTPUT(ofpacts_clone)->port = tun->ofport;
>              }
> -            put_resubmit(OFTABLE_LOCAL_OUTPUT, ofpacts_clone);
> +            put_resubmit(OFTABLE_REMOTE_VTEP_OUTPUT, ofpacts_clone);
>              ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 100,
>                              binding->header_.uuid.parts[0], match,
>                              ofpacts_clone, &binding->header_.uuid);
> @@ -973,12 +974,12 @@ put_local_common_flows(uint32_t dp_key,
>  
>      uint32_t port_key = pb->tunnel_key;
>  
> -    /* Table 43, priority 100.
> +    /* Table 45, priority 100.
>       * =======================
>       *
>       * Implements output to local hypervisor.  Each flow matches a
>       * logical output port on the local hypervisor, and resubmits to
> -     * table 44.
> +     * table 46.
>       */
>  
>      ofpbuf_clear(ofpacts_p);
> @@ -988,13 +989,13 @@ put_local_common_flows(uint32_t dp_key,
>  
>      put_zones_ofpacts(zone_ids, ofpacts_p);
>  
> -    /* Resubmit to table 44. */
> +    /* Resubmit to table 46. */
>      put_resubmit(OFTABLE_CHECK_LOOPBACK, ofpacts_p);
>      ofctrl_add_flow(flow_table, OFTABLE_LOCAL_OUTPUT, 100,
>                      pb->header_.uuid.parts[0], &match, ofpacts_p,
>                      &pb->header_.uuid);
>  
> -    /* Table 44, Priority 100.
> +    /* Table 46, Priority 100.
>       * =======================
>       *
>       * Drop packets whose logical inport and outport are the same
> @@ -1772,12 +1773,12 @@ consider_port_binding(const struct physical_ctx *ctx,
>              ha_chassis_group_is_active(binding->ha_chassis_group,
>                                         ctx->active_tunnels, ctx->chassis))) {
>  
> -        /* Table 43, priority 100.
> +        /* Table 45, priority 100.
>           * =======================
>           *
>           * Implements output to local hypervisor.  Each flow matches a
>           * logical output port on the local hypervisor, and resubmits to
> -         * table 41.  For ports of type "chassisredirect", the logical
> +         * table 46.  For ports of type "chassisredirect", the logical
>           * output port is changed from the "chassisredirect" port to the
>           * underlying distributed port. */
>  
> @@ -1818,7 +1819,7 @@ consider_port_binding(const struct physical_ctx *ctx,
>               * go out from the same tunnel inport. */
>              put_load(ofp_to_u16(OFPP_NONE), MFF_IN_PORT, 0, 16, ofpacts_p);
>  
> -            /* Resubmit to table 41. */
> +            /* Resubmit to table 46. */
>              put_resubmit(OFTABLE_CHECK_LOOPBACK, ofpacts_p);
>          }
>  
> @@ -2049,7 +2050,7 @@ consider_port_binding(const struct physical_ctx *ctx,
>                                                ofport, flow_table);
>          }
>  
> -        /* Table 41, priority 160.
> +        /* Table 46, priority 160.
>           * =======================
>           *
>           * Do not forward local traffic from a localport to a localnet port.
> @@ -2120,13 +2121,13 @@ consider_port_binding(const struct physical_ctx *ctx,
>              }
>          }
>  
> -        /* Table 42, priority 150.
> +        /* Table 43, priority 150.
>           * =======================
>           *
>           * Handles packets received from ports of type "localport".  These
>           * ports are present on every hypervisor.  Traffic that originates at
>           * one should never go over a tunnel to a remote hypervisor,
> -         * so resubmit them to table 40 for local delivery. */
> +         * so resubmit them to table 45 for local delivery. */
>          if (type == LP_LOCALPORT) {
>              ofpbuf_clear(ofpacts_p);
>              put_resubmit(OFTABLE_LOCAL_OUTPUT, ofpacts_p);
> @@ -2140,7 +2141,7 @@ consider_port_binding(const struct physical_ctx *ctx,
>          }
>      } else if (access_type == PORT_LOCALNET && !ctx->always_tunnel) {
>          /* Remote port connected by localnet port */
> -        /* Table 43, priority 100.
> +        /* Table 45, priority 100.
>           * =======================
>           *
>           * Implements switching to localnet port. Each flow matches a
> @@ -2160,7 +2161,7 @@ consider_port_binding(const struct physical_ctx *ctx,
>  
>          put_load(localnet_port->tunnel_key, MFF_LOG_OUTPORT, 0, 32, 
> ofpacts_p);
>  
> -        /* Resubmit to table 43. */
> +        /* Resubmit to table 45. */
>          put_resubmit(OFTABLE_LOCAL_OUTPUT, ofpacts_p);
>          ofctrl_add_flow(flow_table, OFTABLE_LOCAL_OUTPUT, 100,
>                          binding->header_.uuid.parts[0],
> @@ -2516,15 +2517,13 @@ consider_mc_group(const struct physical_ctx *ctx,
>  
>      remote_ports = remote_ctx->ofpacts.size > 0;
>      if (remote_ports) {
> -        if (local_lports) {
> -            put_resubmit(OFTABLE_LOCAL_OUTPUT, &remote_ctx->ofpacts);
> -        }
> +        put_resubmit(OFTABLE_REMOTE_VTEP_OUTPUT, &remote_ctx->ofpacts);
>          mc_ofctrl_add_flow(mc, remote_ctx, false, flow_table);
>      }
>  
>      if (ramp_ports && has_vtep) {
>          put_load(mc->tunnel_key, MFF_LOG_OUTPORT, 0, 32, &ramp_ctx->ofpacts);
> -        put_resubmit(OFTABLE_LOCAL_OUTPUT, &ramp_ctx->ofpacts);
> +        put_resubmit(OFTABLE_REMOTE_VTEP_OUTPUT, &ramp_ctx->ofpacts);
>          mc_ofctrl_add_flow(mc, ramp_ctx, false, flow_table);
>      }
>  
> @@ -2641,6 +2640,162 @@ physical_eval_remote_chassis_flows(const struct 
> physical_ctx *ctx,
>      ofpbuf_uninit(&ingress_ofpacts);
>  }
>  
> +static void
> +physical_consider_evpn_binding(const struct evpn_binding *binding,
> +                               const struct in6_addr *local_ip,
> +                               struct ofpbuf *ofpacts, struct match *match,
> +                               struct ovn_desired_flow_table *flow_table,
> +                               bool ipv4)
> +{
> +    /* Ingress flows. */
> +    ofpbuf_clear(ofpacts);
> +    match_init_catchall(match);
> +
> +    match_set_in_port(match, binding->tunnel_ofport);
> +    match_set_tun_id(match, htonll(binding->vni));
> +    if (ipv4) {
> +        match_set_tun_src(match,
> +                          in6_addr_get_mapped_ipv4(&binding->remote_ip));
> +        match_set_tun_dst(match, in6_addr_get_mapped_ipv4(local_ip));
> +    } else {
> +        match_set_tun_ipv6_src(match, &binding->remote_ip);
> +        match_set_tun_ipv6_dst(match, local_ip);
> +    }
> +
> +    put_load(binding->dp_key, MFF_LOG_DATAPATH, 0, 32, ofpacts);
> +    put_load(binding->binding_key, MFF_LOG_INPORT, 0, 32, ofpacts);
> +    put_resubmit(OFTABLE_LOG_INGRESS_PIPELINE, ofpacts);
> +
> +    ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, 1050,
> +                    binding->flow_uuid.parts[0],
> +                    match, ofpacts, &binding->flow_uuid);
> +
> +    /* Egress flows. */
> +    ofpbuf_clear(ofpacts);
> +    match_init_catchall(match);
> +
> +    match_outport_dp_and_port_keys(match, binding->dp_key,
> +                                   binding->binding_key);
> +
> +    if (ipv4) {
> +        ovs_be32 ip4 = in6_addr_get_mapped_ipv4(local_ip);
> +        put_load_bytes(&ip4, sizeof ip4, MFF_TUN_SRC, 0, 32, ofpacts);
> +        ip4 = in6_addr_get_mapped_ipv4(&binding->remote_ip);
> +        put_load_bytes(&ip4, sizeof ip4, MFF_TUN_DST, 0, 32, ofpacts);
> +    } else {
> +        put_load_bytes(&local_ip, sizeof local_ip, MFF_TUN_IPV6_SRC,
> +                       0, 128, ofpacts);
> +        put_load_bytes(&binding->remote_ip, sizeof binding->remote_ip,
> +                       MFF_TUN_IPV6_DST, 0, 128, ofpacts);
> +    }
> +    put_load(binding->vni, MFF_TUN_ID, 0, 24, ofpacts);
> +
> +    size_t ofpacts_size = ofpacts->size;
> +    ofpact_put_OUTPUT(ofpacts)->port = binding->tunnel_ofport;
> +
> +    ofctrl_add_flow(flow_table, OFTABLE_REMOTE_VTEP_OUTPUT, 50,
> +                    binding->flow_uuid.parts[0],
> +                    match, ofpacts, &binding->flow_uuid);
> +
> +    /* Add flow that will match on LOOPBACK flag, in that case set
> +     * in_port to none otherwise the hairpin traffic would be rejected
> +     * by ovs. */
> +    match_set_reg_masked(match, MFF_LOG_FLAGS - MFF_REG0,
> +                         MLF_ALLOW_LOOPBACK, MLF_ALLOW_LOOPBACK);
> +
> +    ofpbuf_truncate(ofpacts, ofpacts_size);
> +    put_load(ofp_to_u16(OFPP_NONE), MFF_IN_PORT, 0, 16, ofpacts);
> +    ofpact_put_OUTPUT(ofpacts)->port = binding->tunnel_ofport;
> +
> +    ofctrl_add_flow(flow_table, OFTABLE_REMOTE_VTEP_OUTPUT, 55,
> +                    binding->flow_uuid.parts[0],
> +                    match, ofpacts, &binding->flow_uuid);
> +}
> +
> +static void
> +physical_consider_evpn_multicast(const struct evpn_multicast_group *mc_group,
> +                                 const struct in6_addr *local_ip,
> +                                 struct ofpbuf *ofpacts, struct match *match,
> +                                 struct ovn_desired_flow_table *flow_table,
> +                                 bool ipv4)
> +{
> +    const struct evpn_binding *binding = NULL;
> +
> +    ofpbuf_clear(ofpacts);
> +    uint32_t multicast_tunnel_keys[] = {OVN_MCAST_FLOOD_TUNNEL_KEY,
> +                                        OVN_MCAST_UNKNOWN_TUNNEL_KEY,
> +                                        OVN_MCAST_FLOOD_L2_TUNNEL_KEY};
> +    if (ipv4) {
> +        ovs_be32 ip4 = in6_addr_get_mapped_ipv4(local_ip);
> +        put_load_bytes(&ip4, sizeof ip4, MFF_TUN_SRC, 0, 32, ofpacts);
> +    } else {
> +        put_load_bytes(&local_ip, sizeof local_ip, MFF_TUN_IPV6_SRC,
> +                       0, 128, ofpacts);
> +    }
> +    put_load(mc_group->vni, MFF_TUN_ID, 0, 24, ofpacts);
> +
> +    const struct hmapx_node *node;
> +    HMAPX_FOR_EACH (node, &mc_group->bindings) {
> +        binding = node->data;
> +        if (ipv4) {
> +            ovs_be32 ip4 = in6_addr_get_mapped_ipv4(&binding->remote_ip);
> +            put_load_bytes(&ip4, sizeof ip4, MFF_TUN_DST, 0, 32, ofpacts);
> +        } else {
> +            put_load_bytes(&binding->remote_ip, sizeof binding->remote_ip,
> +                           MFF_TUN_IPV6_DST, 0, 128, ofpacts);
> +        }
> +        ofpact_put_OUTPUT(ofpacts)->port = binding->tunnel_ofport;
> +    }
> +    put_resubmit(OFTABLE_LOCAL_OUTPUT, ofpacts);
> +
> +    ovs_assert(!hmapx_is_empty(&mc_group->bindings));
> +    for (size_t i = 0; i < ARRAY_SIZE(multicast_tunnel_keys); i++) {
> +        match_init_catchall(match);
> +        match_outport_dp_and_port_keys(match, binding->dp_key,
> +                                       multicast_tunnel_keys[i]);
> +
> +        ofctrl_add_flow(flow_table, OFTABLE_REMOTE_VTEP_OUTPUT, 50,
> +                        mc_group->flow_uuid.parts[0],
> +                        match, ofpacts, &mc_group->flow_uuid);
> +    }
> +}
> +
> +static void
> +physical_eval_evpn_flows(const struct physical_ctx *ctx,
> +                         struct ofpbuf *ofpacts,
> +                         struct ovn_desired_flow_table *flow_table)
> +{
> +    if (hmap_is_empty(ctx->evpn_bindings) &&
> +        hmap_is_empty(ctx->evpn_multicast_groups)) {
> +        return;
> +    }
> +
> +    const char *local_ip_str = smap_get_def(&ctx->chassis->other_config,
> +                                            "ovn-evpn-local-ip", "");
> +    struct in6_addr local_ip;
> +    if (!ip46_parse(local_ip_str, &local_ip)) {
> +        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
> +        VLOG_WARN_RL(&rl, "EVPN enabled, but required 'evpn-local-ip' is "
> +                           "missing or invalid %s ", local_ip_str);

nit: slightly misaligned.

> +        return;
> +    }
> +
> +    struct match match = MATCH_CATCHALL_INITIALIZER;
> +    bool ipv4 = IN6_IS_ADDR_V4MAPPED(&local_ip);
> +
> +    const struct evpn_binding *binding;
> +    HMAP_FOR_EACH (binding, hmap_node, ctx->evpn_bindings) {
> +        physical_consider_evpn_binding(binding, &local_ip, ofpacts,
> +                                       &match, flow_table, ipv4);
> +    }
> +
> +    const struct evpn_multicast_group *mc_group;
> +    HMAP_FOR_EACH (mc_group, hmap_node, ctx->evpn_multicast_groups) {
> +        physical_consider_evpn_multicast(mc_group, &local_ip, ofpacts,
> +                                         &match, flow_table, ipv4);
> +    }
> +}
> +
>  static void
>  physical_eval_port_binding(struct physical_ctx *p_ctx,
>                             const struct sbrec_port_binding *pb,
> @@ -2748,6 +2903,55 @@ physical_handle_mc_group_changes(struct physical_ctx 
> *p_ctx,
>      }
>  }
>  
> +void
> +physical_handle_evpn_binding_changes(
> +    struct physical_ctx *ctx, struct ovn_desired_flow_table *flow_table,
> +    const struct hmapx *updated_bindings,
> +    const struct hmapx *updated_multicast_groups,
> +    const struct uuidset *removed_bindings,
> +    const struct uuidset *removed_multicast_groups)
> +{
> +    const char *local_ip_str = smap_get_def(&ctx->chassis->other_config,
> +                                            "ovn-evpn-local-ip", "");
> +    struct in6_addr local_ip;
> +    if (!ip46_parse(local_ip_str, &local_ip)) {
> +        return;
> +    }
> +
> +    struct ofpbuf ofpacts;
> +    ofpbuf_init(&ofpacts, 0);
> +    struct match match = MATCH_CATCHALL_INITIALIZER;
> +    bool ipv4 = IN6_IS_ADDR_V4MAPPED(&local_ip);
> +
> +    const struct hmapx_node *node;
> +    HMAPX_FOR_EACH (node, updated_bindings) {
> +        const struct evpn_binding *binding = node->data;
> +
> +        ofctrl_remove_flows(flow_table, &binding->flow_uuid);
> +        physical_consider_evpn_binding(binding, &local_ip, &ofpacts,
> +                                       &match, flow_table, ipv4);
> +    }
> +
> +    HMAPX_FOR_EACH (node, updated_multicast_groups) {
> +        const struct evpn_multicast_group *mc_group = node->data;
> +
> +        ofctrl_remove_flows(flow_table, &mc_group->flow_uuid);
> +        physical_consider_evpn_multicast(mc_group, &local_ip, &ofpacts,
> +                                         &match, flow_table, ipv4);
> +    }
> +
> +    ofpbuf_uninit(&ofpacts);
> +
> +    const struct uuidset_node *uuidset_node;
> +    UUIDSET_FOR_EACH (uuidset_node, removed_bindings) {
> +        ofctrl_remove_flows(flow_table, &uuidset_node->uuid);
> +    }
> +
> +    UUIDSET_FOR_EACH (uuidset_node, removed_multicast_groups) {
> +        ofctrl_remove_flows(flow_table, &uuidset_node->uuid);
> +    }
> +}
> +
>  void
>  physical_run(struct physical_ctx *p_ctx,
>               struct ovn_desired_flow_table *flow_table)
> @@ -2797,7 +3001,7 @@ physical_run(struct physical_ctx *p_ctx,
>       * have metadata about the ingress and egress logical ports.
>       * VXLAN encapsulations have metadata about the egress logical port only.
>       * We set MFF_LOG_DATAPATH, MFF_LOG_INPORT, and MFF_LOG_OUTPORT from the
> -     * tunnel key data where possible, then resubmit to table 40 to handle
> +     * tunnel key data where possible, then resubmit to table 45 to handle
>       * packets to the local hypervisor. */
>      struct chassis_tunnel *tun;
>      HMAP_FOR_EACH (tun, hmap_node, p_ctx->chassis_tunnels) {
> @@ -2910,7 +3114,7 @@ physical_run(struct physical_ctx *p_ctx,
>       */
>      add_default_drop_flow(p_ctx, OFTABLE_PHY_TO_LOG, flow_table);
>  
> -    /* Table 40-43, priority 0.
> +    /* Table 41-42, priority 0.
>       * ========================
>       *
>       * Default resubmit actions for OFTABLE_OUTPUT_LARGE_PKT_* tables.
> @@ -2936,7 +3140,7 @@ physical_run(struct physical_ctx *p_ctx,
>      ofctrl_add_flow(flow_table, OFTABLE_OUTPUT_LARGE_PKT_PROCESS, 0, 0, 
> &match,
>                      &ofpacts, hc_uuid);
>  
> -    /* Table 42, priority 150.
> +    /* Table 43, priority 150.
>       * =======================
>       *
>       * Handles packets received from a VXLAN tunnel which get resubmitted to
> @@ -2949,13 +3153,13 @@ physical_run(struct physical_ctx *p_ctx,
>      match_set_reg_masked(&match, MFF_LOG_FLAGS - MFF_REG0, MLF_RCV_FROM_RAMP,
>                           MLF_RCV_FROM_RAMP | MLF_ALLOW_LOOPBACK);
>  
> -    /* Resubmit to table 40. */
> +    /* Resubmit to table 45. */
>      ofpbuf_clear(&ofpacts);
>      put_resubmit(OFTABLE_LOCAL_OUTPUT, &ofpacts);
>      ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 150, 0,
>                      &match, &ofpacts, hc_uuid);
>  
> -    /* Table 42, priority 150.
> +    /* Table 43, priority 150.
>       * =======================
>       *
>       * Packets that should not be sent to other hypervisors.
> @@ -2963,35 +3167,46 @@ physical_run(struct physical_ctx *p_ctx,
>      match_init_catchall(&match);
>      match_set_reg_masked(&match, MFF_LOG_FLAGS - MFF_REG0,
>                           MLF_LOCAL_ONLY, MLF_LOCAL_ONLY);
> -    /* Resubmit to table 40. */
> +    /* Resubmit to table 45. */
>      ofpbuf_clear(&ofpacts);
>      put_resubmit(OFTABLE_LOCAL_OUTPUT, &ofpacts);
>      ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 150, 0,
>                      &match, &ofpacts, hc_uuid);
>  
> -    /* Table 42, Priority 0.
> +    /* Table 43, Priority 0.
>       * =======================
>       *
> -     * Resubmit packets that are not directed at tunnels or part of a
> -     * multicast group to the local output table. */
> +     * Resubmit packets that are not directed at OVN tunnels or part of a
> +     * multicast group to the VTEP output table. */
>      match_init_catchall(&match);
>      ofpbuf_clear(&ofpacts);
> -    put_resubmit(OFTABLE_LOCAL_OUTPUT, &ofpacts);
> +    put_resubmit(OFTABLE_REMOTE_VTEP_OUTPUT, &ofpacts);
>      ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 0, 0, &match,
>                      &ofpacts, hc_uuid);
>  
> -    /* Table 40, priority 0.
> +    /* Table 44, Priority 0.
> +     * =======================
> +     *
> +     * Resubmit packets that are not directed to remote VTEP to the local
> +     * output table. */
> +    match_init_catchall(&match);
> +    ofpbuf_clear(&ofpacts);
> +    put_resubmit(OFTABLE_LOCAL_OUTPUT, &ofpacts);
> +    ofctrl_add_flow(flow_table, OFTABLE_REMOTE_VTEP_OUTPUT, 0, 0, &match,
> +                    &ofpacts, hc_uuid);
> +
> +    /* Table 45, priority 0.
>       * ======================
>       *
>       * Drop packets that do not match previous flows.
>       */
>      add_default_drop_flow(p_ctx, OFTABLE_LOCAL_OUTPUT, flow_table);
>  
> -    /* Table 41, Priority 0.
> +    /* Table 46, Priority 0.
>       * =======================
>       *
>       * Resubmit packets that don't output to the ingress port (already 
> checked
> -     * in table 40) to the logical egress pipeline, clearing the logical
> +     * in table 44) to the logical egress pipeline, clearing the logical
>       * registers (for consistent behavior with packets that get tunneled). */
>      match_init_catchall(&match);
>      ofpbuf_clear(&ofpacts);
> @@ -3119,6 +3334,7 @@ physical_run(struct physical_ctx *p_ctx,
>                      &match, &ofpacts, hc_uuid);
>  
>      physical_eval_remote_chassis_flows(p_ctx, &ofpacts, flow_table);
> +    physical_eval_evpn_flows(p_ctx, &ofpacts, flow_table);
>  
>      ofpbuf_uninit(&ofpacts);
>  }
> diff --git a/controller/physical.h b/controller/physical.h
> index 0420251c6..e0443924b 100644
> --- a/controller/physical.h
> +++ b/controller/physical.h
> @@ -69,6 +69,8 @@ struct physical_ctx {
>      const char **encap_ips;
>      struct physical_debug debug;
>      bool always_tunnel;
> +    const struct hmap *evpn_bindings;
> +    const struct hmap *evpn_multicast_groups;
>  
>      /* Set of port binding names that have been already reprocessed during
>       * the I-P run. */
> @@ -87,4 +89,10 @@ bool physical_handle_flows_for_lport(const struct 
> sbrec_port_binding *,
>  void physical_multichassis_reprocess(const struct sbrec_port_binding *,
>                                       struct physical_ctx *,
>                                       struct ovn_desired_flow_table *);
> +void physical_handle_evpn_binding_changes(
> +    struct physical_ctx *, struct ovn_desired_flow_table *,
> +    const struct hmapx *updated_bindings,
> +    const struct hmapx *updated_multicast_groups,
> +    const struct uuidset *removed_bindings,
> +    const struct uuidset *removed_multicast_groups);
>  #endif /* controller/physical.h */
> diff --git a/include/ovn/logical-fields.h b/include/ovn/logical-fields.h
> index 8b1c1221e..9a8bf4874 100644
> --- a/include/ovn/logical-fields.h
> +++ b/include/ovn/logical-fields.h
> @@ -63,6 +63,9 @@ enum ovn_controller_event {
>  
>  #define MFF_LOG_CT_SAVED_STATE        MFF_REG4
>  
> +#define MFF_LOG_REMOTE_OUTPORT MFF_REG1  /* Logical remote output
> +                                          * port (32 bits). */
> +
>  /* Logical registers that are needed for backwards
>   * compatibility with older northd versions.
>   * XXX: All of them can be removed in 26.09. */
> diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at
> index 3fb56d4be..d82c98e35 100644
> --- a/tests/ovn-macros.at
> +++ b/tests/ovn-macros.at
> @@ -1480,8 +1480,9 @@ m4_define([OFTABLE_LOG_INGRESS_PIPELINE], [8])
>  m4_define([OFTABLE_OUTPUT_LARGE_PKT_DETECT], [41])
>  m4_define([OFTABLE_OUTPUT_LARGE_PKT_PROCESS], [42])
>  m4_define([OFTABLE_REMOTE_OUTPUT], [43])
> -m4_define([OFTABLE_LOCAL_OUTPUT], [44])
> -m4_define([OFTABLE_LOG_EGRESS_PIPELINE], [46])
> +m4_define([OFTABLE_REMOTE_VTEP_OUTPUT], [44])
> +m4_define([OFTABLE_LOCAL_OUTPUT], [45])
> +m4_define([OFTABLE_LOG_EGRESS_PIPELINE], [47])
>  m4_define([OFTABLE_SAVE_INPORT], [64])
>  m4_define([OFTABLE_LOG_TO_PHY], [65])
>  m4_define([OFTABLE_MAC_BINDING], [66])
> diff --git a/tests/ovn.at b/tests/ovn.at
> index bcf352ab3..6fbfc7fa9 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -40910,7 +40910,7 @@ check_output_flows_tunnelled() {
>    dp_key=$2
>    dp_rport=$3
>    AT_CHECK_UNQUOTED([as $hv ovs-ofctl dump-flows br-int 
> table=OFTABLE_REMOTE_OUTPUT,metadata=0x${dp_key},reg15=0x${dp_rport} | 
> ofctl_strip_all | grep -v NXST_FLOW], [0], [dnl
> - table=OFTABLE_REMOTE_OUTPUT, 
> priority=100,reg13=0/0xffff0000,reg15=0x${dp_rport},metadata=0x${dp_key} 
> actions=load:0x${dp_key}->NXM_NX_TUN_ID[[0..23]],set_field:0x${dp_rport}->tun_metadata0,move:NXM_NX_REG14[[0..14]]->NXM_NX_TUN_METADATA0[[16..30]],output:1,resubmit(,OFTABLE_LOCAL_OUTPUT)
> + table=OFTABLE_REMOTE_OUTPUT, 
> priority=100,reg13=0/0xffff0000,reg15=0x${dp_rport},metadata=0x${dp_key} 
> actions=load:0x${dp_key}->NXM_NX_TUN_ID[[0..23]],set_field:0x${dp_rport}->tun_metadata0,move:NXM_NX_REG14[[0..14]]->NXM_NX_TUN_METADATA0[[16..30]],output:1,resubmit(,OFTABLE_REMOTE_VTEP_OUTPUT)
>  ])
>  }
>  

_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to