On 9/29/25 10:40 AM, Ales Musil wrote: > 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, >
Hi Ales, > thank you for the patch. I have one comment down below. > Thanks for the review! > >> 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. > Good catch! I had copied these lines from evpn-fdb.c without thinking too much. > >> + >> + 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]> > I applied the patch to main with the following incremental squashed in: diff --git a/controller/evpn-arp.c b/controller/evpn-arp.c index f73e09504a..f4c3514f4e 100644 --- a/controller/evpn-arp.c +++ b/controller/evpn-arp.c @@ -35,6 +35,8 @@ static struct evpn_arp *evpn_arp_find(const struct hmap *evpn_arps, struct eth_addr, const struct in6_addr *, uint32_t vni); +static uint32_t evpn_arp_hash(const struct eth_addr *, const struct in6_addr *, + uint32_t vni); void evpn_arp_run(const struct evpn_arp_ctx_in *arp_ctx_in, @@ -150,8 +152,7 @@ evpn_arp_add(struct hmap *evpn_arps, struct eth_addr mac, .vni = vni, }; - uint32_t hash = hash_bytes(&mac, sizeof mac, 0); - hmap_insert(evpn_arps, &arp->hmap_node, hash); + hmap_insert(evpn_arps, &arp->hmap_node, evpn_arp_hash(&mac, ip, vni)); return arp; } @@ -160,7 +161,7 @@ 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); + uint32_t hash = evpn_arp_hash(&mac, ip, vni); struct evpn_arp *arp; HMAP_FOR_EACH_WITH_HASH (arp, hmap_node, hash, evpn_arps) { @@ -172,3 +173,15 @@ evpn_arp_find(const struct hmap *evpn_arps, struct eth_addr mac, return NULL; } + +static uint32_t +evpn_arp_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); + hash = hash_add_in6_addr(hash, ip); + hash = hash_add(hash, vni); + + return hash_finish(hash, 26); +} diff --git a/controller/neighbor-exchange.h b/controller/neighbor-exchange.h index 281733d905..b4257f14c3 100644 --- a/controller/neighbor-exchange.h +++ b/controller/neighbor-exchange.h @@ -34,10 +34,10 @@ struct neighbor_exchange_ctx_out { struct hmap neighbor_table_watches; /* Contains 'struct evpn_remote_vtep'. */ struct hmap *remote_vteps; - /* Contains 'struct evpn_static_entry', remote FDB entries learnt through + /* Contains 'struct evpn_static_entry', remote FDB entries learned through * EVPN. */ struct hmap *static_fdbs; - /* Contains 'struct evpn_static_entry', remote ARP entries learnt through + /* Contains 'struct evpn_static_entry', remote ARP entries learned through * EVPN. */ struct hmap *static_arps; }; diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c index 09bb83e3a2..ae76c8b224 100644 --- a/controller/ovn-controller.c +++ b/controller/ovn-controller.c @@ -6070,10 +6070,10 @@ 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_entry', remote FDB entries learnt through + /* Contains 'struct evpn_static_entry', remote FDB entries learned through * EVPN. */ struct hmap static_fdbs; - /* Contains 'struct evpn_static_entry', remote ARP entries learnt through + /* Contains 'struct evpn_static_entry', remote ARP entries learned through * EVPN. */ struct hmap static_arps; }; --- Regards, Dumitru _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
