On Sat, Sep 27, 2025 at 12:20 AM Dumitru Ceara <[email protected]> wrote:

> For logical switches that are EVPN-enabled (with "dynamic-routing-vni"
> set), automatically learn IP MAC bindings that were advertised by remote
> EVPN peers as Type-2 MAC+IP routes.
>
> As with FDB, these are learned by monitoring through NetLink the VRF
> linux bridge associated to the EVPN logical switch.  The IP <-> MAC
> mappings are stored in memory in ovn-controller.  The OpenFlow pipelines
> of the adjacent logical routers are automatically updated by
> ovn-controller so that the neighbor entries are handled as if they were
> learned as regular Southbound MAC_Bindings.
>
> NOTE: By default the EVPN learned neighbors have a higher priority than
> the ones learned through the SB MAC_Binding table.  This can be tweaked
> by setting the new Logical_Switch "dynamic-routing-arp-prefer-local"
> option to "true" (default "false").  If set to true, SB MAC_Binding
> records have precedence.
>
> Reported-at: https://issues.redhat.com/browse/FDP-1388
> Signed-off-by: Dumitru Ceara <[email protected]>
> ---
>

Hi Dumitru,

thank you for the patch. I have one comment down below.


>  NEWS                                   |   8 ++
>  controller/automake.mk                 |   2 +
>  controller/evpn-arp.c                  | 174 +++++++++++++++++++++++++
>  controller/evpn-arp.h                  |  65 +++++++++
>  controller/evpn-fdb.c                  |   2 +-
>  controller/evpn-fdb.h                  |   2 +-
>  controller/neighbor-exchange-netlink.c |   9 ++
>  controller/neighbor-exchange-netlink.h |   1 +
>  controller/neighbor-exchange-stub.c    |   2 +-
>  controller/neighbor-exchange.c         |  91 ++++++++-----
>  controller/neighbor-exchange.h         |  12 +-
>  controller/neighbor-of.h               |  11 +-
>  controller/ovn-controller.c            | 132 ++++++++++++++++++-
>  controller/physical.c                  |  53 +++++++-
>  controller/physical.h                  |   5 +
>  northd/en-datapath-logical-switch.c    |   7 +
>  ovn-nb.xml                             |  23 ++++
>  tests/multinode.at                     | 104 +++++++++++++--
>  tests/system-ovn.at                    | 147 +++++++++++++++++++++
>  19 files changed, 792 insertions(+), 58 deletions(-)
>  create mode 100644 controller/evpn-arp.c
>  create mode 100644 controller/evpn-arp.h
>
> diff --git a/NEWS b/NEWS
> index dc5d49a94d..f9ad8ae75e 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -6,6 +6,14 @@ Post v25.09.0
>     - Added "ic-route-deny-adv" and "ic-route-deny-learn" options to
>       the Logical_Router/Logical_Router_Port tables to allow users to
>       deny filter advertised/learned IC routes.
> +   - Dynamic Routing:
> +     * Extend the Logical Switch EVPN support to now automatically learn
> +       IP neighbors (Type-2 MAC+IP EVPN routes) and automatically inject
> them
> +       into the pipelines of the adjacent logical routers.
> +     * Add the "other_config:dynamic-routing-arp-prefer-local" to Logical
> +       Switches. If set to "true" ovn-controller will give preference to
> SB
> +       (Static_)MAC_Bindings of adjacent logical routers over ARPs learned
> +       through EVPN on the switch.
>
>  OVN v25.09.0 - xxx xx xxxx
>  --------------------------
> diff --git a/controller/automake.mk b/controller/automake.mk
> index b3c6293fa9..fb27f2ae98 100644
> --- a/controller/automake.mk
> +++ b/controller/automake.mk
> @@ -10,6 +10,8 @@ controller_ovn_controller_SOURCES = \
>         controller/chassis.h \
>         controller/encaps.c \
>         controller/encaps.h \
> +       controller/evpn-arp.c \
> +       controller/evpn-arp.h \
>         controller/evpn-binding.c \
>         controller/evpn-binding.h \
>         controller/evpn-fdb.c \
> diff --git a/controller/evpn-arp.c b/controller/evpn-arp.c
> new file mode 100644
> index 0000000000..f73e09504a
> --- /dev/null
> +++ b/controller/evpn-arp.c
> @@ -0,0 +1,174 @@
> +/* Copyright (c) 2025, Red Hat, Inc.
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
> implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#include <config.h>
> +
> +#include "evpn-binding.h"
> +#include "neighbor-exchange.h"
> +#include "openvswitch/dynamic-string.h"
> +#include "openvswitch/vlog.h"
> +#include "ovn-sb-idl.h"
> +#include "packets.h"
> +#include "unixctl.h"
> +
> +#include "evpn-arp.h"
> +
> +VLOG_DEFINE_THIS_MODULE(evpn_arp);
> +
> +static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
> +
> +static struct evpn_arp *evpn_arp_add(struct hmap *evpn_arps, struct
> eth_addr,
> +                                     const struct in6_addr *, uint32_t
> vni);
> +static struct evpn_arp *evpn_arp_find(const struct hmap *evpn_arps,
> +                                      struct eth_addr,
> +                                      const struct in6_addr *,
> +                                      uint32_t vni);
> +
> +void
> +evpn_arp_run(const struct evpn_arp_ctx_in *arp_ctx_in,
> +             struct evpn_arp_ctx_out *arp_ctx_out)
> +{
> +    struct hmapx stale_arps = HMAPX_INITIALIZER(&stale_arps);
> +
> +    struct evpn_arp *arp;
> +    HMAP_FOR_EACH (arp, hmap_node, arp_ctx_out->arps) {
> +        hmapx_add(&stale_arps, arp);
> +    }
> +
> +    const struct evpn_static_entry *static_arp;
> +    HMAP_FOR_EACH (static_arp, hmap_node, arp_ctx_in->static_arps) {
> +        const struct evpn_datapath *edp =
> +            evpn_datapath_find(arp_ctx_in->datapaths, static_arp->vni);
> +        if (!edp) {
> +            char addr_s[INET6_ADDRSTRLEN + 1];
> +            VLOG_WARN_RL(&rl, "Couldn't find EVPN datapath for ARP entry:
> "
> +                              "VNI: %"PRIu32" MAC: "ETH_ADDR_FMT" IP:
> %s.",
> +                         static_arp->vni, ETH_ADDR_ARGS(static_arp->mac),
> +                         ipv6_string_mapped(addr_s, &static_arp->ip)
> +                         ? addr_s : "(invalid)");
> +            continue;
> +        }
> +
> +        arp = evpn_arp_find(arp_ctx_out->arps, static_arp->mac,
> +                            &static_arp->ip, static_arp->vni);
> +        if (!arp) {
> +            arp = evpn_arp_add(arp_ctx_out->arps, static_arp->mac,
> +                               &static_arp->ip, static_arp->vni);
> +        }
> +
> +        bool updated = false;
> +        if (arp->ldp != edp->ldp) {
> +            arp->ldp = edp->ldp;
> +            updated = true;
> +        }
> +
> +        enum neigh_of_rule_prio priority =
> +            smap_get_bool(&arp->ldp->datapath->external_ids,
> +                          "dynamic-routing-arp-prefer-local",
> +                          false)
> +            ? NEIGH_OF_EVPN_MAC_BINDING_LOW_PRIO
> +            : NEIGH_OF_EVPN_MAC_BINDING_HIGH_PRIO;
> +        if (arp->priority != priority) {
> +            arp->priority = priority;
> +            updated = true;
> +        }
> +
> +        if (updated) {
> +            hmapx_add(arp_ctx_out->updated_arps, arp);
> +        }
> +
> +        hmapx_find_and_delete(&stale_arps, arp);
> +    }
> +
> +    struct hmapx_node *node;
> +    HMAPX_FOR_EACH (node, &stale_arps) {
> +        arp = node->data;
> +
> +        uuidset_insert(arp_ctx_out->removed_arps, &arp->flow_uuid);
> +        hmap_remove(arp_ctx_out->arps, &arp->hmap_node);
> +        free(arp);
> +    }
> +
> +    hmapx_destroy(&stale_arps);
> +}
> +
> +void
> +evpn_arps_destroy(struct hmap *arps)
> +{
> +    struct evpn_arp *arp;
> +    HMAP_FOR_EACH_POP (arp, hmap_node, arps) {
> +        free(arp);
> +    }
> +    hmap_destroy(arps);
> +}
> +
> +void
> +evpn_arp_list(struct unixctl_conn *conn, int argc OVS_UNUSED,
> +              const char *argv[] OVS_UNUSED, void *data_)
> +{
> +    struct hmap *arps = data_;
> +    struct ds ds = DS_EMPTY_INITIALIZER;
> +
> +    const struct evpn_arp *arp;
> +    HMAP_FOR_EACH (arp, hmap_node, arps) {
> +        char addr_s[INET6_ADDRSTRLEN + 1];
> +        ds_put_format(&ds, "UUID: "UUID_FMT", VNI: %"PRIu32", "
> +                           "MAC: "ETH_ADDR_FMT", IP: %s, "
> +                           "dp_key: %"PRId64"\n",
> +                      UUID_ARGS(&arp->flow_uuid), arp->vni,
> +                      ETH_ADDR_ARGS(arp->mac),
> +                      ipv6_string_mapped(addr_s, &arp->ip)
> +                      ? addr_s : "(invalid)",
> +                      arp->ldp->datapath->tunnel_key);
> +    }
> +
> +    unixctl_command_reply(conn, ds_cstr_ro(&ds));
> +    ds_destroy(&ds);
> +}
> +
> +static struct evpn_arp *
> +evpn_arp_add(struct hmap *evpn_arps, struct eth_addr mac,
> +             const struct in6_addr *ip, uint32_t vni)
> +{
> +    struct evpn_arp *arp = xmalloc(sizeof *arp);
> +    *arp = (struct evpn_arp) {
> +        .flow_uuid = uuid_random(),
> +        .mac = mac,
> +        .ip = *ip,
> +        .vni = vni,
> +    };
> +
> +    uint32_t hash = hash_bytes(&mac, sizeof mac, 0);
> +    hmap_insert(evpn_arps, &arp->hmap_node, hash);
> +
> +    return arp;
> +}
> +
> +static struct evpn_arp *
> +evpn_arp_find(const struct hmap *evpn_arps, struct eth_addr mac,
> +              const struct in6_addr *ip, uint32_t vni)
> +{
> +    uint32_t hash = hash_bytes(&mac, sizeof mac, 0);
>

We should have separate hash function that hashes
all three keys (ip + mac + vni) instead of only the mac.


> +
> +    struct evpn_arp *arp;
> +    HMAP_FOR_EACH_WITH_HASH (arp, hmap_node, hash, evpn_arps) {
> +        if (arp->vni == vni && eth_addr_equals(arp->mac, mac) &&
> +                ipv6_addr_equals(&arp->ip, ip)) {
> +            return arp;
> +        }
> +    }
> +
> +    return NULL;
> +}
> diff --git a/controller/evpn-arp.h b/controller/evpn-arp.h
> new file mode 100644
> index 0000000000..7f3a4c0e44
> --- /dev/null
> +++ b/controller/evpn-arp.h
> @@ -0,0 +1,65 @@
> +/* Copyright (c) 2025, Red Hat, Inc.
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
> implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#ifndef EVPN_ARP_H
> +#define EVPN_ARP_H 1
> +
> +#include <stdint.h>
> +
> +#include "hmapx.h"
> +#include "local_data.h"
> +#include "neighbor-of.h"
> +#include "openvswitch/hmap.h"
> +#include "uuidset.h"
> +
> +struct unixctl_conn;
> +
> +struct evpn_arp_ctx_in {
> +    /* Contains 'struct evpn_datapath'. */
> +    const struct hmap *datapaths;
> +    /* Contains 'struct evpn_static_entry' one for each ARP. */
> +    const struct hmap *static_arps;
> +};
> +
> +struct evpn_arp_ctx_out {
> +    /* Contains 'struct evpn_arp'. */
> +    struct hmap *arps;
> +    /* Contains pointers to 'struct evpn_binding'. */
> +    struct hmapx *updated_arps;
> +    /* Contains 'flow_uuid' from removed 'struct evpn_binding'. */
> +    struct uuidset *removed_arps;
> +};
> +
> +struct evpn_arp {
> +    struct hmap_node hmap_node;
> +    /* UUID used to identify physical flows related to this ARP entry. */
> +    struct uuid flow_uuid;
> +    /* MAC address of the remote workload. */
> +    struct eth_addr mac;
> +    /* IP address of the remote workload. */
> +    struct in6_addr ip;
> +    uint32_t vni;
> +    /* Logical datapath of the switch this was learned on. */
> +    const struct local_datapath *ldp;
> +    /* Priority to use for this ARP entry at OpenFlow level. */
> +    enum neigh_of_rule_prio priority;
> +};
> +
> +void evpn_arp_run(const struct evpn_arp_ctx_in *, struct evpn_arp_ctx_out
> *);
> +void evpn_arps_destroy(struct hmap *arps);
> +void evpn_arp_list(struct unixctl_conn *conn, int argc,
> +                   const char *argv[], void *data_);
> +
> +#endif /* EVPN_ARP_H */
> diff --git a/controller/evpn-fdb.c b/controller/evpn-fdb.c
> index 53312fbb2c..acef8f0a42 100644
> --- a/controller/evpn-fdb.c
> +++ b/controller/evpn-fdb.c
> @@ -43,7 +43,7 @@ evpn_fdb_run(const struct evpn_fdb_ctx_in *f_ctx_in,
>          hmapx_add(&stale_fdbs, fdb);
>      }
>
> -    const struct evpn_static_fdb *static_fdb;
> +    const struct evpn_static_entry *static_fdb;
>      HMAP_FOR_EACH (static_fdb, hmap_node, f_ctx_in->static_fdbs) {
>          const struct evpn_binding *binding =
>              evpn_binding_find(f_ctx_in->bindings, &static_fdb->ip,
> diff --git a/controller/evpn-fdb.h b/controller/evpn-fdb.h
> index a38718cd93..de58df813e 100644
> --- a/controller/evpn-fdb.h
> +++ b/controller/evpn-fdb.h
> @@ -27,7 +27,7 @@ struct unixctl_conn;
>  struct evpn_fdb_ctx_in {
>      /* Contains 'struct evpn_binding'. */
>      const struct hmap *bindings;
> -    /* Contains 'struct evpn_static_fdb'. */
> +    /* Contains 'struct evpn_static_entry', one for each FDB. */
>      const struct hmap *static_fdbs;
>  };
>
> diff --git a/controller/neighbor-exchange-netlink.c
> b/controller/neighbor-exchange-netlink.c
> index 66711d4980..fa2444e2f3 100644
> --- a/controller/neighbor-exchange-netlink.c
> +++ b/controller/neighbor-exchange-netlink.c
> @@ -189,6 +189,15 @@ ne_is_valid_static_fdb(struct ne_nl_received_neigh
> *ne)
>             ipv6_addr_is_set(&ne->addr) && ne->flags & NTF_EXT_LEARNED;
>  }
>
> +/* OVN expects that the ARP entry has an IP address, a MAC address,
> + * the entry is marked as "extern learned" and "static" (noarp). */
> +bool
> +ne_is_valid_static_arp(struct ne_nl_received_neigh *ne)
> +{
> +    return !eth_addr_is_zero(ne->lladdr) && ipv6_addr_is_set(&ne->addr) &&
> +           ne->state & NUD_NOARP && ne->flags & NTF_EXT_LEARNED;
> +}
> +
>  static void
>  ne_table_dump_one_ifindex(unsigned char address_family, int32_t if_index,
>                            ne_table_handle_msg_callback *handle_msg_cb,
> diff --git a/controller/neighbor-exchange-netlink.h
> b/controller/neighbor-exchange-netlink.h
> index ee04691ebd..6d907938eb 100644
> --- a/controller/neighbor-exchange-netlink.h
> +++ b/controller/neighbor-exchange-netlink.h
> @@ -56,6 +56,7 @@ int ne_nl_sync_neigh(uint8_t family, int32_t if_index,
>  bool ne_is_ovn_owned(const struct ne_nl_received_neigh *nd);
>  bool ne_is_valid_remote_vtep(struct ne_nl_received_neigh *ne);
>  bool ne_is_valid_static_fdb(struct ne_nl_received_neigh *ne);
> +bool ne_is_valid_static_arp(struct ne_nl_received_neigh *ne);
>
>  int ne_table_parse(struct ofpbuf *, void *change);
>
> diff --git a/controller/neighbor-exchange-stub.c
> b/controller/neighbor-exchange-stub.c
> index 272c14007f..a1c89ed2ba 100644
> --- a/controller/neighbor-exchange-stub.c
> +++ b/controller/neighbor-exchange-stub.c
> @@ -42,6 +42,6 @@ evpn_remote_vtep_list(struct unixctl_conn *conn
> OVS_UNUSED,
>  }
>
>  void
> -evpn_static_fdbs_clear(struct hmap *static_fdbs OVS_UNUSED)
> +evpn_static_entries_clear(struct hmap *static_entries OVS_UNUSED)
>  {
>  }
> diff --git a/controller/neighbor-exchange.c
> b/controller/neighbor-exchange.c
> index 2c436b4141..ea6c287b1f 100644
> --- a/controller/neighbor-exchange.c
> +++ b/controller/neighbor-exchange.c
> @@ -38,13 +38,15 @@ static void evpn_remote_vtep_add(struct hmap
> *remote_vteps, struct in6_addr ip,
>  static struct evpn_remote_vtep *evpn_remote_vtep_find(
>      const struct hmap *remote_vteps, const struct in6_addr *ip,
>      uint16_t port, uint32_t vni);
> -static void evpn_static_fdb_add(struct hmap *static_fdbs, struct eth_addr
> mac,
> -                                struct in6_addr ip, uint32_t vni);
> -static struct evpn_static_fdb *evpn_static_fdb_find(
> -    const struct hmap *static_fdbs, struct eth_addr mac,
> +static void evpn_static_entry_add(struct hmap *static_entries,
> +                                  struct eth_addr mac, struct in6_addr ip,
> +                                  uint32_t vni);
> +static struct evpn_static_entry *evpn_static_entry_find(
> +    const struct hmap *static_entries, struct eth_addr mac,
>      struct in6_addr ip, uint32_t vni);
> -static uint32_t evpn_static_fdb_hash(const struct eth_addr *mac,
> -                                     const struct in6_addr *ip, uint32_t
> vni);
> +static uint32_t evpn_static_entry_hash(const struct eth_addr *mac,
> +                                       const struct in6_addr *ip,
> +                                       uint32_t vni);
>
>  /* Last neighbor_exchange netlink operation. */
>  static int neighbor_exchange_nl_status;
> @@ -92,8 +94,22 @@ neighbor_exchange_run(const struct
> neighbor_exchange_ctx_in *n_ctx_in,
>                               &received_neighbors)
>          );
>
> -        if (nim->type == NEIGH_IFACE_VXLAN) {
> -            struct ne_nl_received_neigh *ne;
> +        struct ne_nl_received_neigh *ne;
> +        switch (nim->type) {
> +        case NEIGH_IFACE_BRIDGE:
> +            VECTOR_FOR_EACH_PTR (&received_neighbors, ne) {
> +                if (ne_is_valid_static_arp(ne)) {
> +                    if (!evpn_static_entry_find(n_ctx_out->static_arps,
> +                                                ne->lladdr, ne->addr,
> +                                                nim->vni)) {
> +                        evpn_static_entry_add(n_ctx_out->static_arps,
> +                                              ne->lladdr, ne->addr,
> +                                              nim->vni);
> +                    }
> +                }
> +            }
> +            break;
> +        case NEIGH_IFACE_VXLAN:
>              VECTOR_FOR_EACH_PTR (&received_neighbors, ne) {
>                  if (ne_is_valid_remote_vtep(ne)) {
>                      uint16_t port = ne->port ? ne->port :
> DEFAULT_VXLAN_PORT;
> @@ -103,14 +119,19 @@ neighbor_exchange_run(const struct
> neighbor_exchange_ctx_in *n_ctx_in,
>                                               port, nim->vni);
>                      }
>                  } else if (ne_is_valid_static_fdb(ne)) {
> -                    if (!evpn_static_fdb_find(n_ctx_out->static_fdbs,
> +                    if (!evpn_static_entry_find(n_ctx_out->static_fdbs,
> +                                                ne->lladdr, ne->addr,
> +                                                nim->vni)) {
> +                        evpn_static_entry_add(n_ctx_out->static_fdbs,
>                                                ne->lladdr, ne->addr,
> -                                              nim->vni)) {
> -                        evpn_static_fdb_add(n_ctx_out->static_fdbs,
> ne->lladdr,
> -                                            ne->addr, nim->vni);
> +                                              nim->vni);
>                      }
>                  }
>              }
> +            break;
> +        case NEIGH_IFACE_LOOPBACK:
> +            /* No learning from the loopback interface required. */
> +            break;
>          }
>
>
>  neighbor_table_add_watch_request(&n_ctx_out->neighbor_table_watches,
> @@ -154,11 +175,11 @@ evpn_remote_vtep_list(struct unixctl_conn *conn, int
> argc OVS_UNUSED,
>  }
>
>  void
> -evpn_static_fdbs_clear(struct hmap *static_fdbs)
> +evpn_static_entries_clear(struct hmap *static_entries)
>  {
> -    struct evpn_static_fdb *fdb;
> -    HMAP_FOR_EACH_POP (fdb, hmap_node, static_fdbs) {
> -        free(fdb);
> +    struct evpn_static_entry *e;
> +    HMAP_FOR_EACH_POP (e, hmap_node, static_entries) {
> +        free(e);
>      }
>  }
>
> @@ -208,32 +229,32 @@ evpn_remote_vtep_hash(const struct in6_addr *ip,
> uint16_t port,
>  }
>
>  static void
> -evpn_static_fdb_add(struct hmap *static_fdbs, struct eth_addr mac,
> -                    struct in6_addr ip, uint32_t vni)
> +evpn_static_entry_add(struct hmap *static_entries, struct eth_addr mac,
> +                      struct in6_addr ip, uint32_t vni)
>  {
> -    struct evpn_static_fdb *fdb = xmalloc(sizeof *fdb);
> -    *fdb = (struct evpn_static_fdb) {
> +    struct evpn_static_entry *e = xmalloc(sizeof *e);
> +    *e = (struct evpn_static_entry) {
>          .mac = mac,
>          .ip = ip,
>          .vni = vni,
>      };
>
> -    hmap_insert(static_fdbs, &fdb->hmap_node,
> -                evpn_static_fdb_hash(&mac, &ip, vni));
> +    hmap_insert(static_entries, &e->hmap_node,
> +                evpn_static_entry_hash(&mac, &ip, vni));
>  }
>
> -static struct evpn_static_fdb *
> -evpn_static_fdb_find(const struct hmap *static_fdbs, struct eth_addr mac,
> -                     struct in6_addr ip, uint32_t vni)
> +static struct evpn_static_entry *
> +evpn_static_entry_find(const struct hmap *static_entries, struct eth_addr
> mac,
> +                       struct in6_addr ip, uint32_t vni)
>  {
> -    uint32_t hash = evpn_static_fdb_hash(&mac, &ip, vni);
> -
> -    struct evpn_static_fdb *fdb;
> -    HMAP_FOR_EACH_WITH_HASH (fdb, hmap_node, hash, static_fdbs) {
> -        if (eth_addr_equals(fdb->mac, mac) &&
> -            ipv6_addr_equals(&fdb->ip, &ip) &&
> -            fdb->vni == vni) {
> -            return fdb;
> +    uint32_t hash = evpn_static_entry_hash(&mac, &ip, vni);
> +
> +    struct evpn_static_entry *e;
> +    HMAP_FOR_EACH_WITH_HASH (e, hmap_node, hash, static_entries) {
> +        if (eth_addr_equals(e->mac, mac) &&
> +            ipv6_addr_equals(&e->ip, &ip) &&
> +            e->vni == vni) {
> +            return e;
>          }
>      }
>
> @@ -241,8 +262,8 @@ evpn_static_fdb_find(const struct hmap *static_fdbs,
> struct eth_addr mac,
>  }
>
>  static uint32_t
> -evpn_static_fdb_hash(const struct eth_addr *mac, const struct in6_addr
> *ip,
> -                     uint32_t vni)
> +evpn_static_entry_hash(const struct eth_addr *mac, const struct in6_addr
> *ip,
> +                       uint32_t vni)
>  {
>      uint32_t hash = 0;
>      hash = hash_bytes(mac, sizeof *mac, hash);
> diff --git a/controller/neighbor-exchange.h
> b/controller/neighbor-exchange.h
> index 6122d8dee5..281733d905 100644
> --- a/controller/neighbor-exchange.h
> +++ b/controller/neighbor-exchange.h
> @@ -34,8 +34,12 @@ struct neighbor_exchange_ctx_out {
>      struct hmap neighbor_table_watches;
>      /* Contains 'struct evpn_remote_vtep'. */
>      struct hmap *remote_vteps;
> -    /* Contains 'struct evpn_static_fdb'. */
> +    /* Contains 'struct evpn_static_entry', remote FDB entries learnt
> through
> +     * EVPN. */
>      struct hmap *static_fdbs;
> +    /* Contains 'struct evpn_static_entry', remote ARP entries learnt
> through
> +     * EVPN. */
> +    struct hmap *static_arps;
>  };
>
>  struct evpn_remote_vtep {
> @@ -48,11 +52,11 @@ struct evpn_remote_vtep {
>      uint32_t vni;
>  };
>
> -struct evpn_static_fdb {
> +struct evpn_static_entry {
>      struct hmap_node hmap_node;
>      /* MAC address of the remote workload. */
>      struct eth_addr mac;
> -    /* Destination ip of the remote tunnel. */
> +    /* Destination ip of the remote tunnel or remote IP. */
>      struct in6_addr ip;
>      /* VNI of the VTEP. */
>      uint32_t vni;
> @@ -64,6 +68,6 @@ int neighbor_exchange_status_run(void);
>  void evpn_remote_vteps_clear(struct hmap *remote_vteps);
>  void evpn_remote_vtep_list(struct unixctl_conn *, int argc,
>                             const char *argv[], void *data_);
> -void evpn_static_fdbs_clear(struct hmap *static_fdbs);
> +void evpn_static_entries_clear(struct hmap *static_entries);
>
>  #endif  /* NEIGHBOR_EXCHANGE_H */
> diff --git a/controller/neighbor-of.h b/controller/neighbor-of.h
> index 874200e8be..4138d1577e 100644
> --- a/controller/neighbor-of.h
> +++ b/controller/neighbor-of.h
> @@ -23,15 +23,20 @@
>
>  /* Priorities of ovn-controller generated flows for various types of MAC
>   * Bindings in different situations.  Valid preference orders, based on
> - * the SB.Static_MAC_Binding.override_dynamic_mac value are:
> + * the "dynamic-routing-arp-prefer-local" logical switch config and the
> + * SB.Static_MAC_Binding.override_dynamic_mac value are:
>   *
> - * - static-mac-binding < dynamic-mac-binding
> - * - dynamic-mac-binding < static-mac-binding
> + * - EVPN-learned < static-mac-binding < dynamic-mac-binding
> + * - EVPN-learned < dynamic-mac-binding < static-mac-binding
> + * - static-mac-binding < dynamic-mac-binding < EVPN-learned
> + * - dynamic-mac-binding < static-mac-binding < EVPN-learned
>   */
>  enum neigh_of_rule_prio {
> +    NEIGH_OF_EVPN_MAC_BINDING_LOW_PRIO    = 20,
>      NEIGH_OF_STATIC_MAC_BINDING_LOW_PRIO  = 50,
>      NEIGH_OF_DYNAMIC_MAC_BINDING_PRIO     = 100,
>      NEIGH_OF_STATIC_MAC_BINDING_HIGH_PRIO = 150,
> +    NEIGH_OF_EVPN_MAC_BINDING_HIGH_PRIO   = 200,
>  };
>
>  void
> diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> index c8b76feb52..09bb83e3a2 100644
> --- a/controller/ovn-controller.c
> +++ b/controller/ovn-controller.c
> @@ -98,6 +98,7 @@
>  #include "neighbor.h"
>  #include "neighbor-exchange.h"
>  #include "neighbor-table-notify.h"
> +#include "evpn-arp.h"
>  #include "evpn-binding.h"
>  #include "evpn-fdb.h"
>
> @@ -4589,6 +4590,15 @@ struct ed_type_evpn_fdb {
>      struct uuidset removed_fdbs;
>  };
>
> +struct ed_type_evpn_arp {
> +    /* Contains 'struct evpn_arp'. */
> +    struct hmap arps;
> +    /* Contains pointers to 'struct evpn_arp'. */
> +    struct hmapx updated_arps;
> +    /* Contains 'flow_uuid' from removed 'struct evpn_arps'. */
> +    struct uuidset removed_arps;
> +};
> +
>  static void init_physical_ctx(struct engine_node *node,
>                                struct ed_type_runtime_data *rt_data,
>                                struct ed_type_non_vif_data *non_vif_data,
> @@ -4649,6 +4659,9 @@ static void init_physical_ctx(struct engine_node
> *node,
>      struct ed_type_evpn_fdb *efdb_data =
>          engine_get_input_data("evpn_fdb", node);
>
> +    struct ed_type_evpn_arp *earp_data =
> +        engine_get_input_data("evpn_arp", 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;
> @@ -4669,6 +4682,7 @@ static void init_physical_ctx(struct engine_node
> *node,
>      p_ctx->evpn_bindings = &eb_data->bindings;
>      p_ctx->evpn_multicast_groups = &eb_data->multicast_groups;
>      p_ctx->evpn_fdbs = &efdb_data->fdbs;
> +    p_ctx->evpn_arps = &earp_data->arps;
>
>      struct controller_engine_ctx *ctrl_ctx =
> engine_get_context()->client_ctx;
>      p_ctx->if_mgr = ctrl_ctx->if_mgr;
> @@ -4993,6 +5007,22 @@ pflow_output_fdb_handler(struct engine_node *node,
> void *data)
>      return EN_HANDLED_UPDATED;
>  }
>
> +static enum engine_input_handler_result
> +pflow_output_arp_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_evpn_arp *ea_data =
> +        engine_get_input_data("evpn_arp", node);
> +
> +    physical_handle_evpn_arp_changes(&rt_data->local_datapaths,
> +                                     &pfo->flow_table,
> +                                     &ea_data->updated_arps,
> +                                     &ea_data->removed_arps);
> +    return EN_HANDLED_UPDATED;
> +}
> +
>  static void *
>  en_controller_output_init(struct engine_node *node OVS_UNUSED,
>                            struct engine_arg *arg OVS_UNUSED)
> @@ -6040,8 +6070,12 @@ en_neighbor_table_notify_run(struct engine_node
> *node OVS_UNUSED,
>  struct ed_type_neighbor_exchange {
>      /* Contains 'struct evpn_remote_vtep'. */
>      struct hmap remote_vteps;
> -    /* Contains 'struct evpn_static_fdb'. */
> +    /* Contains 'struct evpn_static_entry', remote FDB entries learnt
> through
> +     * EVPN. */
>      struct hmap static_fdbs;
> +    /* Contains 'struct evpn_static_entry', remote ARP entries learnt
> through
> +     * EVPN. */
> +    struct hmap static_arps;
>  };
>
>  static void *
> @@ -6052,6 +6086,7 @@ en_neighbor_exchange_init(struct engine_node *node
> OVS_UNUSED,
>      *data = (struct ed_type_neighbor_exchange) {
>          .remote_vteps = HMAP_INITIALIZER(&data->remote_vteps),
>          .static_fdbs = HMAP_INITIALIZER(&data->static_fdbs),
> +        .static_arps = HMAP_INITIALIZER(&data->static_arps),
>      };
>
>      return data;
> @@ -6062,9 +6097,11 @@ en_neighbor_exchange_cleanup(void *data_)
>  {
>      struct ed_type_neighbor_exchange *data = data_;
>      evpn_remote_vteps_clear(&data->remote_vteps);
> -    evpn_static_fdbs_clear(&data->static_fdbs);
> +    evpn_static_entries_clear(&data->static_fdbs);
> +    evpn_static_entries_clear(&data->static_arps);
>      hmap_destroy(&data->remote_vteps);
>      hmap_destroy(&data->static_fdbs);
> +    hmap_destroy(&data->static_arps);
>  }
>
>  static enum engine_node_state
> @@ -6075,7 +6112,8 @@ en_neighbor_exchange_run(struct engine_node *node,
> void *data_)
>          engine_get_input_data("neighbor", node);
>
>      evpn_remote_vteps_clear(&data->remote_vteps);
> -    evpn_static_fdbs_clear(&data->static_fdbs);
> +    evpn_static_entries_clear(&data->static_fdbs);
> +    evpn_static_entries_clear(&data->static_arps);
>
>      struct neighbor_exchange_ctx_in n_ctx_in = {
>          .monitored_interfaces = &neighbor_data->monitored_interfaces,
> @@ -6085,6 +6123,7 @@ en_neighbor_exchange_run(struct engine_node *node,
> void *data_)
>              HMAP_INITIALIZER(&n_ctx_out.neighbor_table_watches),
>          .remote_vteps = &data->remote_vteps,
>          .static_fdbs = &data->static_fdbs,
> +        .static_arps = &data->static_arps,
>      };
>
>      neighbor_exchange_run(&n_ctx_in, &n_ctx_out);
> @@ -6355,6 +6394,81 @@ evpn_fdb_vtep_binding_handler(struct engine_node
> *node, void *data OVS_UNUSED)
>      return EN_UNHANDLED;
>  }
>
> +static void *
> +en_evpn_arp_init(struct engine_node *node OVS_UNUSED,
> +                 struct engine_arg *arg OVS_UNUSED)
> +{
> +    struct ed_type_evpn_arp *data = xmalloc(sizeof *data);
> +    *data = (struct ed_type_evpn_arp) {
> +        .arps = HMAP_INITIALIZER(&data->arps),
> +        .updated_arps = HMAPX_INITIALIZER(&data->updated_arps),
> +        .removed_arps = UUIDSET_INITIALIZER(&data->removed_arps),
> +    };
> +
> +    return data;
> +}
> +
> +static void
> +en_evpn_arp_clear_tracked_data(void *data_)
> +{
> +    struct ed_type_evpn_arp *data = data_;
> +    hmapx_clear(&data->updated_arps);
> +    uuidset_clear(&data->removed_arps);
> +}
> +
> +static void
> +en_evpn_arp_cleanup(void *data_)
> +{
> +    struct ed_type_evpn_arp *data = data_;
> +    evpn_arps_destroy(&data->arps);
> +    hmapx_destroy(&data->updated_arps);
> +    uuidset_destroy(&data->removed_arps);
> +}
> +
> +static enum engine_node_state
> +en_evpn_arp_run(struct engine_node *node, void *data_)
> +{
> +    struct ed_type_evpn_arp *data = data_;
> +    const struct ed_type_neighbor_exchange *ne_data =
> +        engine_get_input_data("neighbor_exchange", node);
> +    const struct ed_type_evpn_vtep_binding *eb_data =
> +        engine_get_input_data("evpn_vtep_binding", node);
> +
> +    struct evpn_arp_ctx_in f_ctx_in = {
> +        .datapaths = &eb_data->datapaths,
> +        .static_arps = &ne_data->static_arps,
> +    };
> +
> +    struct evpn_arp_ctx_out f_ctx_out = {
> +        .arps = &data->arps,
> +        .updated_arps = &data->updated_arps,
> +        .removed_arps = &data->removed_arps,
> +    };
> +
> +    evpn_arp_run(&f_ctx_in, &f_ctx_out);
> +
> +    if (hmapx_count(&data->updated_arps) ||
> +        uuidset_count(&data->removed_arps)) {
> +        return EN_UPDATED;
> +    }
> +
> +    return EN_UNCHANGED;
> +}
> +
> +static enum engine_input_handler_result
> +evpn_arp_vtep_binding_handler(struct engine_node *node, void *data
> OVS_UNUSED)
> +{
> +    const struct ed_type_evpn_vtep_binding *eb_data =
> +        engine_get_input_data("evpn_vtep_binding", node);
> +
> +    if (hmapx_is_empty(&eb_data->updated_bindings) &&
> +        uuidset_is_empty(&eb_data->removed_bindings)) {
> +        return EN_HANDLED_UNCHANGED;
> +    }
> +
> +    return EN_UNHANDLED;
> +}
> +
>  /* Returns false if the northd internal version stored in SB_Global
>   * and ovn-controller internal version don't match.
>   */
> @@ -6681,6 +6795,7 @@ main(int argc, char *argv[])
>      ENGINE_NODE(neighbor_exchange_status);
>      ENGINE_NODE(evpn_vtep_binding, CLEAR_TRACKED_DATA);
>      ENGINE_NODE(evpn_fdb, CLEAR_TRACKED_DATA);
> +    ENGINE_NODE(evpn_arp, CLEAR_TRACKED_DATA);
>
>  #define SB_NODE(NAME) ENGINE_NODE_SB(NAME);
>      SB_NODES
> @@ -6938,10 +7053,16 @@ main(int argc, char *argv[])
>      engine_add_input(&en_evpn_fdb, &en_evpn_vtep_binding,
>                       evpn_fdb_vtep_binding_handler);
>
> +    engine_add_input(&en_evpn_arp, &en_neighbor_exchange, NULL);
> +    engine_add_input(&en_evpn_arp, &en_evpn_vtep_binding,
> +                     evpn_arp_vtep_binding_handler);
> +
>      engine_add_input(&en_pflow_output, &en_evpn_vtep_binding,
>                       pflow_output_evpn_binding_handler);
>      engine_add_input(&en_pflow_output, &en_evpn_fdb,
>                       pflow_output_fdb_handler);
> +    engine_add_input(&en_pflow_output, &en_evpn_arp,
> +                     pflow_output_arp_handler);
>
>      engine_add_input(&en_controller_output, &en_dns_cache,
>                       NULL);
> @@ -7025,6 +7146,8 @@ main(int argc, char *argv[])
>          engine_get_internal_data(&en_evpn_vtep_binding);
>      struct ed_type_evpn_fdb *efdb_data =
>          engine_get_internal_data(&en_evpn_fdb);
> +    struct ed_type_evpn_arp *earp_data =
> +        engine_get_internal_data(&en_evpn_arp);
>
>      ofctrl_init(&lflow_output_data->group_table,
>                  &lflow_output_data->meter_table);
> @@ -7053,6 +7176,9 @@ main(int argc, char *argv[])
>      unixctl_command_register("evpn/vtep-fdb-list", "", 0, 0,
>                               evpn_fdb_list,
>                               &efdb_data->fdbs);
> +    unixctl_command_register("evpn/vtep-arp-list", "", 0, 0,
> +                             evpn_arp_list,
> +                             &earp_data->arps);
>
>      struct pending_pkt pending_pkt = { .conn = NULL };
>      unixctl_command_register("inject-pkt", "MICROFLOW", 1, 1, inject_pkt,
> diff --git a/controller/physical.c b/controller/physical.c
> index 9ca535a6cd..23a579f857 100644
> --- a/controller/physical.c
> +++ b/controller/physical.c
> @@ -20,6 +20,7 @@
>  #include "ct-zone.h"
>  #include "encaps.h"
>  #include "evpn-binding.h"
> +#include "evpn-arp.h"
>  #include "evpn-fdb.h"
>  #include "flow.h"
>  #include "ha-chassis.h"
> @@ -54,6 +55,7 @@
>  #include "util.h"
>  #include "vswitch-idl.h"
>  #include "hmapx.h"
> +#include "neighbor-of.h"
>
>  VLOG_DEFINE_THIS_MODULE(physical);
>
> @@ -2778,6 +2780,28 @@ physical_consider_evpn_fdb(const struct evpn_fdb
> *fdb,
>                      match, ofpacts, &fdb->flow_uuid);
>  }
>
> +static void
> +physical_consider_evpn_arp(const struct hmap *local_datapaths,
> +                           const struct evpn_arp *arp,
> +                           struct ovn_desired_flow_table *flow_table)
> +{
> +    /* Walk connected OVN routers and install neighbor flows for the ARPs
> +     * learned on EVPN datapaths.*/
> +    const struct peer_ports *peers;
> +    VECTOR_FOR_EACH_PTR (&arp->ldp->peer_ports, peers) {
> +        const struct sbrec_port_binding *remote_pb = peers->remote;
> +        struct local_datapath *peer_ld =
> +            get_local_datapath(local_datapaths,
> +                               remote_pb->datapath->tunnel_key);
> +        if (!peer_ld || peer_ld->is_switch) {
> +            continue;
> +        }
> +
> +        consider_neighbor_flow(remote_pb, &arp->flow_uuid, &arp->ip,
> arp->mac,
> +                               flow_table, arp->priority, false);
> +    }
> +}
> +
>  static void
>  physical_eval_evpn_flows(const struct physical_ctx *ctx,
>                           struct ofpbuf *ofpacts,
> @@ -2785,7 +2809,8 @@ physical_eval_evpn_flows(const struct physical_ctx
> *ctx,
>  {
>      if (hmap_is_empty(ctx->evpn_bindings) &&
>          hmap_is_empty(ctx->evpn_multicast_groups) &&
> -        hmap_is_empty(ctx->evpn_fdbs)) {
> +        hmap_is_empty(ctx->evpn_fdbs) &&
> +        hmap_is_empty(ctx->evpn_arps)) {
>          return;
>      }
>
> @@ -2818,6 +2843,11 @@ physical_eval_evpn_flows(const struct physical_ctx
> *ctx,
>      HMAP_FOR_EACH (fdb, hmap_node, ctx->evpn_fdbs) {
>          physical_consider_evpn_fdb(fdb, ofpacts, &match, flow_table);
>      }
> +
> +    const struct evpn_arp *arp;
> +    HMAP_FOR_EACH (arp, hmap_node, ctx->evpn_arps) {
> +        physical_consider_evpn_arp(ctx->local_datapaths, arp, flow_table);
> +    }
>  }
>
>  static void
> @@ -3001,6 +3031,27 @@ physical_handle_evpn_fdb_changes(struct
> ovn_desired_flow_table *flow_table,
>      }
>  }
>
> +void
> +physical_handle_evpn_arp_changes(const struct hmap *local_datapaths,
> +                                 struct ovn_desired_flow_table
> *flow_table,
> +                                 const struct hmapx *updated_arps,
> +                                 const struct uuidset *removed_arps)
> +{
> +
> +    const struct hmapx_node *node;
> +    HMAPX_FOR_EACH (node, updated_arps) {
> +        const struct evpn_arp *arp = node->data;
> +
> +        ofctrl_remove_flows(flow_table, &arp->flow_uuid);
> +        physical_consider_evpn_arp(local_datapaths, arp, flow_table);
> +    }
> +
> +    const struct uuidset_node *uuidset_node;
> +    UUIDSET_FOR_EACH (uuidset_node, removed_arps) {
> +        ofctrl_remove_flows(flow_table, &uuidset_node->uuid);
> +    }
> +}
> +
>  void
>  physical_run(struct physical_ctx *p_ctx,
>               struct ovn_desired_flow_table *flow_table)
> diff --git a/controller/physical.h b/controller/physical.h
> index 37be46380e..0dc544823a 100644
> --- a/controller/physical.h
> +++ b/controller/physical.h
> @@ -72,6 +72,7 @@ struct physical_ctx {
>      const struct hmap *evpn_bindings;
>      const struct hmap *evpn_multicast_groups;
>      const struct hmap *evpn_fdbs;
> +    const struct hmap *evpn_arps;
>
>      /* Set of port binding names that have been already reprocessed during
>       * the I-P run. */
> @@ -99,4 +100,8 @@ void physical_handle_evpn_binding_changes(
>  void physical_handle_evpn_fdb_changes(struct ovn_desired_flow_table *,
>                                        const struct hmapx *updated_fdbs,
>                                        const struct uuidset *removed_fdbs);
> +void physical_handle_evpn_arp_changes(const struct hmap *local_datapaths,
> +                                      struct ovn_desired_flow_table *,
> +                                      const struct hmapx *updated_arps,
> +                                      const struct uuidset *removed_arps);
>  #endif /* controller/physical.h */
> diff --git a/northd/en-datapath-logical-switch.c
> b/northd/en-datapath-logical-switch.c
> index 0527239cec..c3fefd100a 100644
> --- a/northd/en-datapath-logical-switch.c
> +++ b/northd/en-datapath-logical-switch.c
> @@ -98,6 +98,13 @@ gather_external_ids(const struct nbrec_logical_switch
> *nbs,
>          smap_add(external_ids, "dynamic-routing-redistribute",
> redistribute);
>      }
>
> +    const char *prefer_evpn_arp_local =
> +        smap_get(&nbs->other_config, "dynamic-routing-arp-prefer-local");
> +    if (prefer_evpn_arp_local) {
> +        smap_add(external_ids, "dynamic-routing-arp-prefer-local",
> +                 prefer_evpn_arp_local);
> +    }
> +
>      /* For backwards-compatibility, also store the NB UUID in
>       * external-ids:logical-switch. This is useful if ovn-controller
>       * has not updated and expects this to be where to find the
> diff --git a/ovn-nb.xml b/ovn-nb.xml
> index ea7164e6cf..97da0177ed 100644
> --- a/ovn-nb.xml
> +++ b/ovn-nb.xml
> @@ -908,6 +908,29 @@
>          </p>
>        </column>
>
> +      <column name="other_config" key="dynamic-routing-arp-prefer-local"
> +              type='{"type": "boolean"}'>
> +        <p>
> +          This option defines the preference of ARP/ND lookup.  If set to
> +          true OVN routers connected to EVPN Logical Switches on which
> remote
> +          neighbor entries have been learned (Type-2 MAC+IP EVPN routes)
> will
> +          give precedence to any ARP/ND entries they might have in the SB
> +          <code>Mac_Binding</code> table before trying to resolve the MAC
> +          address via the <code>ovn-controller</code> local EVPN ARP/ND
> cache.
> +          The option defaults to false.
> +        </p>
> +
> +        <p>
> +          Only relevant if <ref column="other_config"
> key="dynamic-routing-vni"
> +                                table="Logical_switch"/> is set to valid
> VNI.
> +        </p>
> +
> +        <p>
> +          NOTE: this feature is experimental and may be subject to
> +          removal/change in the future.
> +        </p>
> +      </column>
> +
>        <column name="other_config" key="dynamic-routing-redistribute"
>                type='{"type": "string"}'>
>          <p>
> diff --git a/tests/multinode.at b/tests/multinode.at
> index 959c5ba8a8..b5d3df5b33 100644
> --- a/tests/multinode.at
> +++ b/tests/multinode.at
> @@ -3667,6 +3667,7 @@ check multinode_nbctl set logical_switch ls       \
>      other_config:dynamic-routing-vni=$vni         \
>      other_config:dynamic-routing-redistribute=fdb
>  check multinode_nbctl --wait=hv sync
> +dp_key=$(m_fetch_column Datapath_Binding tunnel_key external_ids:name=ls)
>
>  OVS_WAIT_UNTIL([m_as ovn-gw-1 bridge fdb | grep vxlan-10 | grep -q
> "00:00:00:00:00:00"])
>  OVS_WAIT_UNTIL([m_as ovn-gw-2 bridge fdb | grep vxlan-10 | grep -q
> "00:00:00:00:00:00"])
> @@ -3680,23 +3681,33 @@ IP: $ext_bgp_ip_gw2, port: 4789, vni: 10
>  ])
>
>  AS_BOX([Check traffic to "fabric" hosts - simulate external workloads])
> +check m_as ovn-gw-1 ip netns add fabric_workload
> +on_exit "m_as ovn-gw-1 ip netns del fabric_workload"
>  check m_as ovn-gw-1 ip link add evpn_host type veth peer evpn_host_peer
>  on_exit "m_as ovn-gw-1 ip link del evpn_host"
>  check m_as ovn-gw-1 ip link set netns frr-ns evpn_host_peer
>  check m_as ovn-gw-1 ip netns exec frr-ns ip link set evpn_host_peer
> master br-10
>  check m_as ovn-gw-1 ip netns exec frr-ns ip link set evpn_host_peer up
> -check m_as ovn-gw-1 ip link set evpn_host addr 00:00:00:00:01:00
> -check m_as ovn-gw-1 ip addr add dev evpn_host 10.0.0.41/24
> -check m_as ovn-gw-1 ip link set evpn_host up
> -
> +check m_as ovn-gw-1 ip link set netns fabric_workload evpn_host
> +check m_as ovn-gw-1 ip netns exec fabric_workload ip link set evpn_host
> addr 00:00:00:00:01:00
> +check m_as ovn-gw-1 ip netns exec fabric_workload ip addr add dev
> evpn_host 10.0.0.41/24
> +check m_as ovn-gw-1 ip netns exec fabric_workload ip link set evpn_host up
> +check m_as ovn-gw-1 ip netns exec fabric_workload ip r a default via
> 10.0.0.1
> +check m_as ovn-gw-1 ip netns exec frr-ns ip a a dev br-10 10.0.0.81/24
> +
> +check m_as ovn-gw-2 ip netns add fabric_workload
> +on_exit "m_as ovn-gw-2 ip netns del fabric_workload"
>  check m_as ovn-gw-2 ip link add evpn_host type veth peer evpn_host_peer
>  on_exit "m_as ovn-gw-2 ip link del evpn_host"
>  check m_as ovn-gw-2 ip link set netns frr-ns evpn_host_peer
>  check m_as ovn-gw-2 ip netns exec frr-ns ip link set evpn_host_peer
> master br-10
>  check m_as ovn-gw-2 ip netns exec frr-ns ip link set evpn_host_peer up
> -check m_as ovn-gw-2 ip link set evpn_host addr 00:00:00:00:02:00
> -check m_as ovn-gw-2 ip addr add dev evpn_host 10.0.0.42/24
> -check m_as ovn-gw-2 ip link set evpn_host up
> +check m_as ovn-gw-2 ip link set netns fabric_workload evpn_host
> +check m_as ovn-gw-2 ip netns exec fabric_workload ip link set evpn_host
> addr 00:00:00:00:02:00
> +check m_as ovn-gw-2 ip netns exec fabric_workload ip addr add dev
> evpn_host 10.0.0.42/24
> +check m_as ovn-gw-2 ip netns exec fabric_workload ip link set evpn_host up
> +check m_as ovn-gw-2 ip netns exec fabric_workload ip r a default via
> 10.0.0.1
> +check m_as ovn-gw-2 ip netns exec frr-ns ip a a dev br-10 10.0.0.82/24
>
>  AS_BOX([Checking EVPN MACs on External BGP host])
>  OVS_WAIT_FOR_OUTPUT([m_as ovn-gw-1 ip netns exec frr-ns vtysh
> --vty_socket /run/frr/frr-ns -c 'show evpn mac vni all'], [0], [dnl
> @@ -3720,8 +3731,83 @@ MAC               Type   Flags Intf/Remote ES/VTEP
>           VLAN  Seq #'s
>  ])
>
>  AS_BOX([Check traffic to "fabric" hosts - ping from fabric])
> -OVS_WAIT_UNTIL([m_as ovn-gw-1 ping -W 1 -c 1 10.0.0.11])
> -OVS_WAIT_UNTIL([m_as ovn-gw-2 ping -W 1 -c 1 10.0.0.12])
> +OVS_WAIT_UNTIL([m_as ovn-gw-1 ip netns exec fabric_workload ping -W 1 -c
> 1 10.0.0.11])
> +OVS_WAIT_UNTIL([m_as ovn-gw-2 ip netns exec fabric_workload ping -W 1 -c
> 1 10.0.0.12])
> +
> +AS_BOX([Check type-2 MAC+IP EVPN route advertisements])
> +# Ping from the frr-ns to the fabric workload so that its IP is learned on
> +# the fabric EVPN peer (and advertised to OVN).
> +OVS_WAIT_UNTIL([m_as ovn-gw-1 ip netns exec frr-ns ip vrf exec vrf-10
> ping -W 1 -c 1 10.0.0.41])
> +OVS_WAIT_UNTIL([m_as ovn-gw-2 ip netns exec frr-ns ip vrf exec vrf-10
> ping -W 1 -c 1 10.0.0.42])
> +
> +# Check that OVN learned the ARPs.
> +OVS_WAIT_FOR_OUTPUT_UNQUOTED([m_as ovn-gw-1 ovn-appctl evpn/vtep-arp-list
> | cut -d',' -f2- | sort], [0], [dnl
> + VNI: 10, MAC: 00:00:00:00:01:00, IP: 10.0.0.41, dp_key: $dp_key
> +])
> +OVS_WAIT_FOR_OUTPUT_UNQUOTED([m_as ovn-gw-2 ovn-appctl evpn/vtep-arp-list
> | cut -d',' -f2- | sort], [0], [dnl
> + VNI: 10, MAC: 00:00:00:00:02:00, IP: 10.0.0.42, dp_key: $dp_key
> +])
> +
> +AS_BOX([Check that OVN routers used ARP entries learned through type-2
> EVPN MAC+IP routes])
> +# Add an OVN router with an internal switch and internal workload.
> +check multinode_nbctl --wait=hv                               \
> +    -- lr-add lr                                              \
> +    -- lrp-add lr lr-ls 00:00:00:01:00:00 10.0.0.1/24         \
> +    -- lrp-add lr lr-ls-int 00:00:00:02:00:00 20.0.0.1/24     \
> +    -- lsp-add ls ls-lr                                       \
> +    -- lsp-set-type ls-lr router                              \
> +    -- lsp-set-options ls-lr router-port=lr-ls                \
> +    -- lsp-set-addresses ls-lr router                         \
> +    -- ls-add ls-int                                          \
> +    -- lsp-add ls-int ls-int-lr                               \
> +    -- lsp-set-type ls-int-lr router                          \
> +    -- lsp-set-options ls-int-lr router-port=lr-ls-int        \
> +    -- lsp-set-addresses ls-int-lr router                     \
> +    -- lsp-add ls-int w-int1                                  \
> +    -- lsp-set-addresses w-int1 "00:00:00:02:00:01 20.0.0.11" \
> +    -- lsp-add ls-int w-int2                                  \
> +    -- lsp-set-addresses w-int2 "00:00:00:02:00:02 20.0.0.12"
> +
> +rtr_dp_key=$(m_fetch_column Datapath tunnel_key external_ids:name=lr)
> +rtr_port_key=$(m_fetch_column Port_Binding tunnel_key logical_port=lr-ls)
> +
> +check m_as ovn-gw-1 /data/create_fake_vm.sh w-int1 w-int1
> 00:00:00:02:00:01 1500 20.0.0.11 24 20.0.0.1 2000::11/64 2000::1
> +check m_as ovn-gw-2 /data/create_fake_vm.sh w-int2 w-int2
> 00:00:00:02:00:02 1500 20.0.0.12 24 20.0.0.1 2000::12/64 2000::1
> +m_wait_for_ports_up
> +
> +# Check that flows are created for the type-2 EVPN MAC+IP routes, in the
> +# router pipeline.
> +AT_CHECK_UNQUOTED([m_as ovn-gw-1 ovs-ofctl dump-flows br-int
> table=OFTABLE_MAC_BINDING | grep priority | \
> +                   awk '{print $7, $8}' | sort], [0], [dnl
> +priority=100,reg0=0xa00000b,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key
> actions=mod_dl_dst:00:00:00:00:00:01,load:0x1->NXM_NX_REG10[[6]]
> +priority=100,reg0=0xa00000c,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key
> actions=mod_dl_dst:00:00:00:00:00:02,load:0x1->NXM_NX_REG10[[6]]
> +priority=200,reg0=0xa000029,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key
> actions=mod_dl_dst:00:00:00:00:01:00,load:0x1->NXM_NX_REG10[[6]]
> +])
> +
> +AT_CHECK_UNQUOTED([m_as ovn-gw-1 ovs-ofctl dump-flows br-int
> table=OFTABLE_MAC_LOOKUP | grep priority | \
> +                   awk '{print $7, $8}' | sort], [0], [dnl
> +priority=100,arp,reg0=0xa00000b,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=00:00:00:00:00:01
> actions=load:0x1->NXM_NX_REG10[[6]]
> +priority=100,arp,reg0=0xa00000c,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=00:00:00:00:00:02
> actions=load:0x1->NXM_NX_REG10[[6]]
> +priority=200,arp,reg0=0xa000029,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=00:00:00:00:01:00
> actions=load:0x1->NXM_NX_REG10[[6]]
> +])
> +
> +AT_CHECK_UNQUOTED([m_as ovn-gw-2 ovs-ofctl dump-flows br-int
> table=OFTABLE_MAC_BINDING | grep priority | \
> +                   awk '{print $7, $8}' | sort], [0], [dnl
> +priority=100,reg0=0xa00000b,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key
> actions=mod_dl_dst:00:00:00:00:00:01,load:0x1->NXM_NX_REG10[[6]]
> +priority=100,reg0=0xa00000c,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key
> actions=mod_dl_dst:00:00:00:00:00:02,load:0x1->NXM_NX_REG10[[6]]
> +priority=200,reg0=0xa00002a,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key
> actions=mod_dl_dst:00:00:00:00:02:00,load:0x1->NXM_NX_REG10[[6]]
> +])
> +
> +AT_CHECK_UNQUOTED([m_as ovn-gw-2 ovs-ofctl dump-flows br-int
> table=OFTABLE_MAC_LOOKUP | grep priority | \
> +                   awk '{print $7, $8}' | sort], [0], [dnl
> +priority=100,arp,reg0=0xa00000b,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=00:00:00:00:00:01
> actions=load:0x1->NXM_NX_REG10[[6]]
> +priority=100,arp,reg0=0xa00000c,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=00:00:00:00:00:02
> actions=load:0x1->NXM_NX_REG10[[6]]
> +priority=200,arp,reg0=0xa00002a,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=00:00:00:00:02:00
> actions=load:0x1->NXM_NX_REG10[[6]]
> +])
> +
> +AS_BOX([Check traffic to "fabric" hosts - ping from internal hosts])
> +OVS_WAIT_UNTIL([m_as ovn-gw-1 ip netns exec w-int1 ping -W 1 -c 1
> 10.0.0.41])
> +OVS_WAIT_UNTIL([m_as ovn-gw-2 ip netns exec w-int2 ping -W 1 -c 1
> 10.0.0.42])
>
>  # Remove "workloads" (VIF LSPs) on both chassis.
>  check multinode_nbctl --wait=hv lsp-del w1 -- lsp-del w2
> diff --git a/tests/system-ovn.at b/tests/system-ovn.at
> index 85e432e75d..4737a804b3 100644
> --- a/tests/system-ovn.at
> +++ b/tests/system-ovn.at
> @@ -18337,6 +18337,8 @@ on_exit "ip link del lo-$vni"
>  check ip link set lo-$vni master br-$vni
>  check ip link set lo-$vni up
>
> +AS_BOX([L2 EVPN VTEP and FDB learning])
> +
>  check ovn-nbctl --wait=hv set logical_switch ls-evpn
> other_config:dynamic-routing-vni=$vni
>  ofport=$(ovs-vsctl --bare --columns ofport find Interface
> name="ovn-evpn-4789")
>  dp_key=$(fetch_column Datapath tunnel_key external_ids:name=ls-evpn)
> @@ -18450,6 +18452,8 @@ check diff -q bindings_before bindings_after
>  check diff -q mc_groups_before mc_groups_after
>  check diff -q fdb_before fdb_after
>
> +AS_BOX([L2 EVPN FDB advertising])
> +
>  check ovn-nbctl --wait=hv set logical_switch ls-evpn
> other_config:dynamic-routing-redistribute=fdb
>  OVS_WAIT_FOR_OUTPUT([bridge fdb show | grep "lo-10" | grep
> "f0:00:0f:16:01" | sort], [0], [dnl
>  f0:00:0f:16:01:10 dev lo-10 master br-10 static
> @@ -18468,6 +18472,149 @@ check ovn-nbctl --wait=hv remove logical_switch
> ls-evpn other_config dynamic-rou
>  OVS_WAIT_FOR_OUTPUT([bridge fdb show | grep "lo-10" | grep
> "f0:00:0f:16:01" | sort], [0], [dnl
>  ])
>
> +AS_BOX([L2 EVPN ARP learning])
> +# 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 \
> +    -- lsp-add ls-evpn ls-evpn-lr                            \
> +    -- lsp-set-type ls-evpn-lr router                        \
> +    -- lsp-set-options ls-evpn-lr router-port=lr-ls-evpn     \
> +    -- lsp-set-addresses ls-evpn-lr router
> +
> +rtr_dp_key=$(fetch_column Datapath tunnel_key external_ids:name=lr)
> +rtr_port_key=$(fetch_column Port_Binding tunnel_key
> logical_port=lr-ls-evpn)
> +
> +# Simulate remote workload ARPs (type-2 MAC+IP EVPN route).
> +# ovn-controller needs to add OF rules for ARP lookup but no rules for
> +# MAC_CACHE use.  These entries do not age out automatically, their
> lifetime
> +# is controlled by the BGP-EVPN control plane.
> +check ip neigh add dev br-10 172.16.1.50 lladdr f0:00:0f:16:10:50 nud
> noarp extern_learn
> +check ip neigh add dev br-10 172.16.1.60 lladdr f0:00:0f:16:10:60 nud
> noarp extern_learn
> +
> +OVS_WAIT_FOR_OUTPUT_UNQUOTED([ovn-appctl evpn/vtep-arp-list | cut -d','
> -f2- | sort], [0], [dnl
> + VNI: 10, MAC: f0:00:0f:16:10:50, IP: 172.16.1.50, dp_key: $dp_key
> + VNI: 10, MAC: f0:00:0f:16:10:60, IP: 172.16.1.60, dp_key: $dp_key
> +])
> +
> +AS_BOX([Check dynamic-routing-arp-prefer-local=true])
> +check ovn-nbctl --wait=hv set Logical_Switch ls-evpn
> other_config:dynamic-routing-arp-prefer-local=true
> +
> +AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=OFTABLE_MAC_BINDING
> | grep priority | \
> +                   awk '{print $7, $8}' | sort], [0], [dnl
> +priority=100,reg0=0xac10010a,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key
> actions=mod_dl_dst:f0:00:0f:16:01:10,load:0x1->NXM_NX_REG10[[6]]
> +priority=20,reg0=0xac100132,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key
> actions=mod_dl_dst:f0:00:0f:16:10:50,load:0x1->NXM_NX_REG10[[6]]
> +priority=20,reg0=0xac10013c,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key
> actions=mod_dl_dst:f0:00:0f:16:10:60,load:0x1->NXM_NX_REG10[[6]]
> +])
> +
> +AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=OFTABLE_MAC_LOOKUP |
> grep priority | \
> +                   awk '{print $7, $8}' | sort], [0], [dnl
> +priority=100,arp,reg0=0xac10010a,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:01:10
> actions=load:0x1->NXM_NX_REG10[[6]]
> +priority=20,arp,reg0=0xac100132,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50
> actions=load:0x1->NXM_NX_REG10[[6]]
> +priority=20,arp,reg0=0xac10013c,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60
> actions=load:0x1->NXM_NX_REG10[[6]]
> +])
> +
> +AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int
> table=OFTABLE_MAC_CACHE_USE | grep priority | \
> +                   awk '{print $7, $8}' | sort], [0], [dnl
> +priority=100,arp,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:01:10,arp_spa=172.16.1.10,arp_op=2
> actions=drop
> +priority=100,ip,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:01:10,nw_src=172.16.1.10
> actions=drop
> +])
> +
> +AS_BOX([Check dynamic-routing-arp-prefer-local=false])
> +check ovn-nbctl --wait=hv set Logical_Switch ls-evpn
> other_config:dynamic-routing-arp-prefer-local=false
> +
> +AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=OFTABLE_MAC_BINDING
> | grep priority | \
> +                   awk '{print $7, $8}' | sort], [0], [dnl
> +priority=100,reg0=0xac10010a,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key
> actions=mod_dl_dst:f0:00:0f:16:01:10,load:0x1->NXM_NX_REG10[[6]]
> +priority=200,reg0=0xac100132,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key
> actions=mod_dl_dst:f0:00:0f:16:10:50,load:0x1->NXM_NX_REG10[[6]]
> +priority=200,reg0=0xac10013c,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key
> actions=mod_dl_dst:f0:00:0f:16:10:60,load:0x1->NXM_NX_REG10[[6]]
> +])
> +
> +AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=OFTABLE_MAC_LOOKUP |
> grep priority | \
> +                   awk '{print $7, $8}' | sort], [0], [dnl
> +priority=100,arp,reg0=0xac10010a,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:01:10
> actions=load:0x1->NXM_NX_REG10[[6]]
> +priority=200,arp,reg0=0xac100132,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50
> actions=load:0x1->NXM_NX_REG10[[6]]
> +priority=200,arp,reg0=0xac10013c,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60
> actions=load:0x1->NXM_NX_REG10[[6]]
> +])
> +
> +AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int
> table=OFTABLE_MAC_CACHE_USE | grep priority | \
> +                   awk '{print $7, $8}' | sort], [0], [dnl
> +priority=100,arp,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:01:10,arp_spa=172.16.1.10,arp_op=2
> actions=drop
> +priority=100,ip,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:01:10,nw_src=172.16.1.10
> actions=drop
> +])
> +
> +# Check that the recompute won't change the UUIDs and flows.
> +ovn-appctl evpn/vtep-arp-list > arp_before
> +
> +check ovn-appctl inc-engine/recompute
> +check ovn-nbctl --wait=hv sync
> +
> +ovn-appctl evpn/vtep-arp-list > arp_after
> +
> +check diff -q arp_before arp_after
> +
> +# Remove remote workload ARP entries and check ovn-controller's state.
> +check ip neigh del dev br-10 172.16.1.50
> +check ip neigh del dev br-10 172.16.1.60
> +
> +OVS_WAIT_FOR_OUTPUT_UNQUOTED([ovn-appctl evpn/vtep-arp-list | cut -d','
> -f2- | sort], [0], [dnl
> +])
> +
> +AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=OFTABLE_MAC_BINDING
> | grep priority | \
> +                   awk '{print $7, $8}' | sort], [0], [dnl
> +priority=100,reg0=0xac10010a,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key
> actions=mod_dl_dst:f0:00:0f:16:01:10,load:0x1->NXM_NX_REG10[[6]]
> +])
> +
> +AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=OFTABLE_MAC_LOOKUP |
> grep priority | \
> +                   awk '{print $7, $8}' | sort], [0], [dnl
> +priority=100,arp,reg0=0xac10010a,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:01:10
> actions=load:0x1->NXM_NX_REG10[[6]]
> +])
> +
> +AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int
> table=OFTABLE_MAC_CACHE_USE | grep priority | \
> +                   awk '{print $7, $8}' | sort], [0], [dnl
> +priority=100,arp,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:01:10,arp_spa=172.16.1.10,arp_op=2
> actions=drop
> +priority=100,ip,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:01:10,nw_src=172.16.1.10
> actions=drop
> +])
> +
> +# Re-add the remote workload ARPs, remove the router, check that flows are
> +# removed (vtep-arp-list should still list the ARPs as they're learned on
> +# the logical switch that still exists).
> +check ip neigh add dev br-10 172.16.1.50 lladdr f0:00:0f:16:10:50 nud
> noarp extern_learn
> +check ip neigh add dev br-10 172.16.1.60 lladdr f0:00:0f:16:10:60 nud
> noarp extern_learn
> +
> +OVS_WAIT_FOR_OUTPUT_UNQUOTED([ovn-appctl evpn/vtep-arp-list | cut -d','
> -f2- | sort], [0], [dnl
> + VNI: 10, MAC: f0:00:0f:16:10:50, IP: 172.16.1.50, dp_key: $dp_key
> + VNI: 10, MAC: f0:00:0f:16:10:60, IP: 172.16.1.60, dp_key: $dp_key
> +])
> +
> +AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=OFTABLE_MAC_BINDING
> | grep priority | \
> +                   awk '{print $7, $8}' | sort], [0], [dnl
> +priority=100,reg0=0xac10010a,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key
> actions=mod_dl_dst:f0:00:0f:16:01:10,load:0x1->NXM_NX_REG10[[6]]
> +priority=200,reg0=0xac100132,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key
> actions=mod_dl_dst:f0:00:0f:16:10:50,load:0x1->NXM_NX_REG10[[6]]
> +priority=200,reg0=0xac10013c,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key
> actions=mod_dl_dst:f0:00:0f:16:10:60,load:0x1->NXM_NX_REG10[[6]]
> +])
> +
> +AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=OFTABLE_MAC_LOOKUP |
> grep priority | \
> +                   awk '{print $7, $8}' | sort], [0], [dnl
> +priority=100,arp,reg0=0xac10010a,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:01:10
> actions=load:0x1->NXM_NX_REG10[[6]]
> +priority=200,arp,reg0=0xac100132,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50
> actions=load:0x1->NXM_NX_REG10[[6]]
> +priority=200,arp,reg0=0xac10013c,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60
> actions=load:0x1->NXM_NX_REG10[[6]]
> +])
> +
> +check ovn-nbctl --wait=hv lr-del lr
> +AT_CHECK_UNQUOTED([ovn-appctl evpn/vtep-arp-list | cut -d',' -f2- |
> sort], [0], [dnl
> + VNI: 10, MAC: f0:00:0f:16:10:50, IP: 172.16.1.50, dp_key: $dp_key
> + VNI: 10, MAC: f0:00:0f:16:10:60, IP: 172.16.1.60, dp_key: $dp_key
> +])
> +
> +AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=OFTABLE_MAC_BINDING
> | grep priority | \
> +                   awk '{print $7, $8}' | sort], [0], [dnl
> +])
> +
> +AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=OFTABLE_MAC_LOOKUP |
> grep priority | \
> +                   awk '{print $7, $8}' | sort], [0], [dnl
> +])
> +
>  OVN_CLEANUP_CONTROLLER([hv1])
>
>  as ovn-sb
> --
> 2.51.0
>
>
With that addressed:
Acked-by: Ales Musil <[email protected]>

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

Reply via email to