When Logical Switch has dynamic-routing-vni and dynamic-routing-maintain-evpn=mvd set, ovn-controller automatically creates and manages the Linux kernel interfaces required for EVPN/BGP: a VRF (ovnvrf<vni>), a bridge (ovnbr<vni>), a VXLAN interface (ovnvxlan<vni>) and a dummy interface (ovnlo<vni>). For L3 VNI support, dynamic-routing-evpn-lrp must be set to the name of a Logical Router Port whose MAC and IP address will be applied to the bridge interface.
Signed-off-by: Alexandra Rukomoinikova <[email protected]> --- NEWS | 7 + controller/automake.mk | 2 + controller/host-if-monitor.c | 15 ++ controller/host-if-monitor.h | 2 +- controller/neighbor-exchange-netlink.c | 102 +++++++- controller/neighbor-exchange-netlink.h | 11 + controller/neighbor-exchange-stub.c | 10 + controller/neighbor-exchange.c | 308 +++++++++++++++++++++++-- controller/neighbor-exchange.h | 8 + controller/neighbor.c | 185 ++++++++++++--- controller/neighbor.h | 34 +++ controller/netlink-utils.c | 148 ++++++++++++ controller/netlink-utils.h | 42 ++++ controller/ovn-controller.c | 20 ++ controller/physical.c | 157 ------------- controller/route-exchange-netlink.c | 45 +--- controller/route-exchange-netlink.h | 8 - controller/route-exchange.c | 1 + lib/ovn-util.c | 157 +++++++++++++ lib/ovn-util.h | 26 ++- northd/en-datapath-logical-switch.c | 14 ++ tests/automake.mk | 2 + tests/system-ovn.at | 179 ++++++++++++++ 23 files changed, 1217 insertions(+), 266 deletions(-) create mode 100644 controller/netlink-utils.c create mode 100644 controller/netlink-utils.h diff --git a/NEWS b/NEWS index 68cca917e..060ab1359 100644 --- a/NEWS +++ b/NEWS @@ -61,6 +61,13 @@ Post v26.03.0 (type 11) and Parameter Problem (type 12) - generated by an external router are un-NATed correctly. This makes Path MTU discovery and traceroute work through stateless NAT. + - Add other_config "dynamic-routing-maintain-evpn" option for Logical + Switches. When set to "mvd", ovn-controller creates and maintains + Linux kernel interfaces required for EVPN/VXLAN itself: VRF, bridge, + VXLAN device and, for FDB redistribution, a dummy interface. + Other_config "dynamic-routing-maintain-evpn-lrp" option names a Logical + Router Port whose MAC applied to the bridge interface for router port + MAC-IP redistribution support. OVN v26.03.0 - xxx xx xxxx -------------------------- diff --git a/controller/automake.mk b/controller/automake.mk index a779250cb..dfbb1d4ae 100644 --- a/controller/automake.mk +++ b/controller/automake.mk @@ -73,6 +73,8 @@ controller_ovn_controller_SOURCES = \ if HAVE_NETLINK controller_ovn_controller_SOURCES += \ + controller/netlink-utils.h \ + controller/netlink-utils.c \ controller/host-if-monitor.c \ controller/ovn-netlink-notifier.c \ controller/neighbor-exchange-netlink.h \ diff --git a/controller/host-if-monitor.c b/controller/host-if-monitor.c index 65110d2a3..bbd86aaa5 100644 --- a/controller/host-if-monitor.c +++ b/controller/host-if-monitor.c @@ -65,6 +65,21 @@ host_if_monitor_run(void) return monitor.changes_detected; } +void +host_if_monitor_invalidate(const char *if_name) +{ + if (!sset_contains(&monitor.watched_interfaces, if_name)) { + return; + } + + int32_t ifindex = if_nametoindex(if_name); + if (ifindex) { + simap_put(&monitor.ifname_to_ifindex, if_name, ifindex); + } else { + simap_find_and_delete(&monitor.ifname_to_ifindex, if_name); + } +} + void host_if_monitor_update_watches(const struct sset *if_names) { diff --git a/controller/host-if-monitor.h b/controller/host-if-monitor.h index a41c5869c..f625d839c 100644 --- a/controller/host-if-monitor.h +++ b/controller/host-if-monitor.h @@ -22,7 +22,7 @@ void host_if_monitor_wait(void); bool host_if_monitor_run(void); - +void host_if_monitor_invalidate(const char *if_name); void host_if_monitor_update_watches(const struct sset *if_names); int32_t host_if_monitor_ifname_toindex(const char *if_name); diff --git a/controller/neighbor-exchange-netlink.c b/controller/neighbor-exchange-netlink.c index f01bfda5b..23df6d501 100644 --- a/controller/neighbor-exchange-netlink.c +++ b/controller/neighbor-exchange-netlink.c @@ -14,8 +14,11 @@ */ #include <config.h> +#include <errno.h> #include <stdbool.h> + #include <linux/if_ether.h> +#include <linux/if_link.h> #include <linux/rtnetlink.h> #include "hmapx.h" @@ -23,20 +26,13 @@ #include "lib/netlink-socket.h" #include "lib/packets.h" #include "openvswitch/vlog.h" +#include "netlink-utils.h" #include "neighbor-exchange-netlink.h" #include "neighbor.h" VLOG_DEFINE_THIS_MODULE(neighbor_exchange_netlink); -#define NETNL_REQ_BUFFER_SIZE 128 - -/* NTF_EXT_LEARNED was introduced in Linux v3.19, define it if - * not available. */ -#ifndef NTF_EXT_LEARNED -#define NTF_EXT_LEARNED (1 << 4) -#endif - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20); /* Inspired from route_table_dump_one_table() in OVS. */ @@ -535,3 +531,93 @@ ne_table_parse(struct ofpbuf *buf, void *change) return ne_table_parse__(buf, NLMSG_HDRLEN + sizeof *nd, nlmsg, nd, change); } + +int32_t +ne_nl_ifindex_get(const char *ifname) +{ + return nl_ifindex_get(ifname); +} + +int +ne_nl_create_bridge(const char *ifname) +{ + return nl_create_device(ifname, "bridge"); +} + +int +ne_nl_create_lo(const char *ifname) +{ + return nl_create_device(ifname, "dummy"); +} + +int +ne_nl_create_vxlan(const char *ifname, uint32_t vni, + const struct in6_addr *local_ip, uint16_t dst_port, + int32_t link_ifindex) +{ + int err = 0; + struct ifinfomsg *ifinfo; + struct ofpbuf request; + ofpbuf_init(&request, 0); + + nl_msg_put_nlmsghdr(&request, 0, RTM_NEWLINK, + NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL); + ifinfo = ofpbuf_put_zeros(&request, sizeof *ifinfo); + nl_msg_put_string(&request, IFLA_IFNAME, ifname); + + ifinfo->ifi_change = ifinfo->ifi_flags = IFF_UP; + size_t linkinfo_off = nl_msg_start_nested(&request, IFLA_LINKINFO); + nl_msg_put_string(&request, IFLA_INFO_KIND, "vxlan"); + size_t infodata_off = nl_msg_start_nested(&request, IFLA_INFO_DATA); + + nl_msg_put_u32(&request, IFLA_VXLAN_ID, vni); + nl_msg_put_u32(&request, IFLA_VXLAN_LINK, link_ifindex); + nl_msg_put_u8(&request, IFLA_VXLAN_LEARNING, 0); + nl_msg_put_be16(&request, IFLA_VXLAN_PORT, htons(dst_port)); + + if (IN6_IS_ADDR_V4MAPPED(local_ip)) { + ovs_be32 ipv4 = in6_addr_get_mapped_ipv4(local_ip); + nl_msg_put_be32(&request, IFLA_VXLAN_LOCAL, ipv4); + } else { + nl_msg_put_in6_addr(&request, IFLA_VXLAN_LOCAL6, local_ip); + } + + nl_msg_end_nested(&request, infodata_off); + nl_msg_end_nested(&request, linkinfo_off); + + err = nl_transact(NETLINK_ROUTE, &request, NULL); + ofpbuf_uninit(&request); + return err; +} + +int +ne_nl_set_master(const char *slave, const char *master) +{ + return nl_set_master(slave, master); +} + +int +ne_nl_set_iface_mac_addr(const char *ifname, const struct eth_addr *mac) +{ + return nl_set_iface_mac(ifname, mac); +} + +int +ne_nl_delete_iface(const char *ifname) +{ + return nl_delete_device(ifname); +} + +int +ne_nl_create_vrf(const char *ifname, uint32_t table_id) +{ + if (!TABLE_ID_VALID(table_id)) { + VLOG_WARN_RL(&rl, + "attempt to create VRF using invalid table id %"PRIu32, + table_id); + return EINVAL; + } + + return nl_create_vrf(ifname, table_id); +} + diff --git a/controller/neighbor-exchange-netlink.h b/controller/neighbor-exchange-netlink.h index 659fe72d8..37badb226 100644 --- a/controller/neighbor-exchange-netlink.h +++ b/controller/neighbor-exchange-netlink.h @@ -50,6 +50,17 @@ struct ne_table_msg { struct ne_nl_received_neigh nd; /* Data parsed from this message. */ }; +int32_t ne_nl_ifindex_get(const char *ifname); +int ne_nl_create_vxlan(const char *ifname, uint32_t vni, + const struct in6_addr *local_ip, uint16_t dst_port, + int32_t link_ifindex); +int ne_nl_create_bridge(const char *ifname); +int ne_nl_create_lo(const char *ifname); +int ne_nl_create_vrf(const char *ifname, uint32_t table_id); +int ne_nl_set_master(const char *slave, const char *master); +int ne_nl_set_iface_mac_addr(const char *ifname, const struct eth_addr *mac); +int ne_nl_delete_iface(const char *ifname); + int ne_nl_sync_neigh(uint8_t family, int32_t if_index, const struct hmap *neighbors, struct vector *learned_neighbors); diff --git a/controller/neighbor-exchange-stub.c b/controller/neighbor-exchange-stub.c index a1c89ed2b..5a0912b73 100644 --- a/controller/neighbor-exchange-stub.c +++ b/controller/neighbor-exchange-stub.c @@ -45,3 +45,13 @@ void evpn_static_entries_clear(struct hmap *static_entries OVS_UNUSED) { } + +void +neighbor_exchange_maintain_evpn_cleanup_all(void) +{ +} + +void +neighbor_exchange_maintain_evpn_destroy(void) +{ +} diff --git a/controller/neighbor-exchange.c b/controller/neighbor-exchange.c index 43626cc21..f271cdf67 100644 --- a/controller/neighbor-exchange.c +++ b/controller/neighbor-exchange.c @@ -15,12 +15,16 @@ #include <config.h> +#include <errno.h> #include <linux/neighbour.h> +#include <net/if.h> #include "host-if-monitor.h" +#include "lib/sset.h" #include "neighbor.h" #include "neighbor-exchange.h" #include "neighbor-exchange-netlink.h" +#include "route-exchange-netlink.h" #include "openvswitch/poll-loop.h" #include "openvswitch/vlog.h" #include "ovn-util.h" @@ -29,23 +33,7 @@ #include "unixctl.h" VLOG_DEFINE_THIS_MODULE(neighbor_exchange); - -static uint32_t evpn_remote_vtep_hash(const struct in6_addr *ip, - uint16_t port, uint32_t vni); -static void evpn_remote_vtep_add(struct hmap *remote_vteps, struct in6_addr ip, - uint16_t port, uint32_t vni); -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_entry_add(struct hmap *static_entries, - struct eth_addr mac, struct in6_addr ip, - uint32_t vni, uint32_t nh_id); -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 nh_id); -static uint32_t evpn_static_entry_hash(const struct eth_addr *mac, - const struct in6_addr *ip, - uint32_t vni, uint32_t nh_id); +static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20); /* Last neighbor_exchange netlink operation. */ static int neighbor_exchange_nl_status; @@ -65,10 +53,296 @@ static int neighbor_exchange_nl_status; } \ } while (0) +struct maintained_evpn_entry { + struct hmap_node node; + struct neighbor_ovn_maintain_entry entry; +}; + +static struct hmap maintained_evpn_entries = + HMAP_INITIALIZER(&maintained_evpn_entries); + +static struct maintained_evpn_entry * +maintained_evpn_entry_find(const struct hmap *entries, uint32_t vni) +{ + struct maintained_evpn_entry *me; + HMAP_FOR_EACH_WITH_HASH (me, node, hash_int(vni, 0), entries) { + if (me->entry.vni == vni) { + return me; + } + } + + return NULL; +} + +static void +evpn_delete_evpn_devices(struct neighbor_ovn_maintain_entry *entry) +{ + int error; + + error = ne_nl_delete_iface(entry->br_if_name); + if (error && error != ENODEV) { + VLOG_WARN_RL(&rl, "Unable to delete bridge interface %s: %s", + entry->br_if_name, ovs_strerror(error)); + SET_NEIGHBOR_EXCHANGE_NL_STATUS(error); + } + + error = ne_nl_delete_iface(entry->vxlan_if_name); + if (error && error != ENODEV) { + VLOG_WARN_RL(&rl, "Unable to delete VXLAN interface %s: %s", + entry->vxlan_if_name, ovs_strerror(error)); + SET_NEIGHBOR_EXCHANGE_NL_STATUS(error); + } + + error = ne_nl_delete_iface(entry->vrf_if_name); + if (error && error != ENODEV) { + VLOG_WARN_RL(&rl, "Unable to delete vrf interface %s: %s", + entry->vrf_if_name, ovs_strerror(error)); + SET_NEIGHBOR_EXCHANGE_NL_STATUS(error); + } + + error = ne_nl_delete_iface(entry->lo_if_name); + if (error && error != ENODEV) { + VLOG_WARN_RL(&rl, "Unable to delete dummy interface %s: %s", + entry->lo_if_name, ovs_strerror(error)); + SET_NEIGHBOR_EXCHANGE_NL_STATUS(error); + } + + /* Refresh the host-if-monitor ifindex cache for the interfaces we + * monitor for neighbor sync, so a lookup later in this same engine + * iteration doesn't use a stale ifindex. */ + host_if_monitor_invalidate(entry->br_if_name); + host_if_monitor_invalidate(entry->vxlan_if_name); + host_if_monitor_invalidate(entry->lo_if_name); +} + +static void +set_bridge_evpn_device_addr(struct neighbor_ovn_maintain_entry *entry) +{ + int err; + if (entry->br_config.has_addr) { + err = ne_nl_set_iface_mac_addr(entry->br_if_name, + &entry->br_config.lladdr); + if (err) { + VLOG_WARN_RL(&rl, "Unable set mac address on interface %s: %s", + entry->br_if_name, ovs_strerror(err)); + SET_NEIGHBOR_EXCHANGE_NL_STATUS(err); + } + } +} + +static void +evpn_create_devices(struct neighbor_ovn_maintain_entry *entry) +{ + static char *link_dev = "vxlan_sys_4789"; + int32_t link_ifindex = ne_nl_ifindex_get(link_dev); + int err; + + if (!link_ifindex) { + err = ENODEV; + VLOG_WARN_RL(&rl, + "Unable to find OVS system vxlan interface"); + SET_NEIGHBOR_EXCHANGE_NL_STATUS(err); + } + + if (entry->br_config.has_addr + && nrm_mode_IP_is_set(entry->redistribute_mode)) { + err = ne_nl_create_vrf(entry->vrf_if_name, entry->vni); + if (err && err != EEXIST) { + VLOG_WARN_RL(&rl, + "Unable to create VRF %s for datapath %s", + entry->vrf_if_name, ovs_strerror(err)); + SET_NEIGHBOR_EXCHANGE_NL_STATUS(err); + } + } + + err = ne_nl_create_vxlan(entry->vxlan_if_name, entry->vni, + &entry->local_ip, entry->fake_vxlan_port, + link_ifindex); + if (err && err != EEXIST) { + VLOG_WARN_RL(&rl, + "Unable to create VXLAN interface %s for " + "VNI %"PRIu32": %s", + entry->vxlan_if_name, entry->vni, ovs_strerror(err)); + SET_NEIGHBOR_EXCHANGE_NL_STATUS(err); + } + + err = ne_nl_create_bridge(entry->br_if_name); + if (err && err != EEXIST) { + VLOG_WARN_RL(&rl, + "Unable to create bridge interface %s for " + "VNI %"PRIu32": %s", + entry->br_if_name, entry->vni, ovs_strerror(err)); + SET_NEIGHBOR_EXCHANGE_NL_STATUS(err); + } + + set_bridge_evpn_device_addr(entry); + + if (entry->br_config.has_addr + && nrm_mode_IP_is_set(entry->redistribute_mode)) { + err = ne_nl_set_master(entry->br_if_name, entry->vrf_if_name); + if (err) { + VLOG_WARN_RL(&rl, "Unable to enslave %s to bridge %s: %s", + entry->br_if_name, entry->vrf_if_name, + ovs_strerror(err)); + SET_NEIGHBOR_EXCHANGE_NL_STATUS(err); + } + } + + if (nrm_mode_FDB_is_set(entry->redistribute_mode)) { + err = ne_nl_create_lo(entry->lo_if_name); + if (err && err != EEXIST) { + VLOG_WARN_RL(&rl, + "Unable to create dummy interface %s for " + "VNI %"PRIu32": %s", + entry->lo_if_name, entry->vni, ovs_strerror(err)); + SET_NEIGHBOR_EXCHANGE_NL_STATUS(err); + } + err = ne_nl_set_master(entry->lo_if_name, entry->br_if_name); + if (err) { + VLOG_WARN_RL(&rl, "Unable to enslave %s to bridge %s: %s", + entry->lo_if_name, entry->br_if_name, + ovs_strerror(err)); + SET_NEIGHBOR_EXCHANGE_NL_STATUS(err); + } + } + + err = ne_nl_set_master(entry->vxlan_if_name, entry->br_if_name); + if (err) { + VLOG_WARN_RL(&rl, "Unable to enslave %s to bridge %s: %s", + entry->vxlan_if_name, entry->br_if_name, + ovs_strerror(err)); + SET_NEIGHBOR_EXCHANGE_NL_STATUS(err); + } +} + +/* Returns true if a change between 'old_entry' and 'entry' requires + * deleting and recreating the whole VRF/bridge/VXLAN/lo interface stack, + * rather than updating it in place. This is the case when: + * + * - The VXLAN device's local IP or UDP destination port changed: Netlink + * offers no way to change either on an existing VXLAN interface. + * + * - The bridge gained or lost its L3 address ('has_addr'): this changes + * whether a VRF is needed at all. + * + * - The redistribution mode changed (FDB and/or IP bits): this changes + * whether the loopback advertise interface is needed at all. + * + * Recreating the whole stack in these cases is simpler and less + * error-prone than trying to patch each interface individually. */ +static bool +should_destroy_interfaces(const struct neighbor_ovn_maintain_entry *entry, + const struct neighbor_ovn_maintain_entry *old_entry) +{ + return !ipv6_addr_equals(&old_entry->local_ip, &entry->local_ip) || + old_entry->fake_vxlan_port != entry->fake_vxlan_port || + old_entry->br_config.has_addr != entry->br_config.has_addr || + old_entry->redistribute_mode != entry->redistribute_mode || + (entry->br_config.has_addr + && !eth_addr_equals(old_entry->br_config.lladdr, + entry->br_config.lladdr)); +} + +static void +maintain_evpn_devices(struct neighbor_ovn_maintain_entry *entry, + struct neighbor_ovn_maintain_entry *old_entry) +{ + if (!old_entry) { + evpn_create_devices(entry); + return; + } + + if (should_destroy_interfaces(entry, old_entry)) { + /* Something changed that cannot be updated without recreation: + * - local IP, + * - VXLAN port + * - has_addr var or redistribute_mode) + * tear down and recreate interfaces. */ + evpn_delete_evpn_devices(entry); + evpn_create_devices(entry); + return; + } +} + +static void +neighbor_exchange_maintain_evpn_run(const struct vector *maintain_evpn) +{ + CLEAR_NEIGHBOR_EXCHANGE_NL_STATUS(); + + struct hmap old_maintained_evpn_entries = + HMAP_INITIALIZER(&old_maintained_evpn_entries); + hmap_swap(&maintained_evpn_entries, &old_maintained_evpn_entries); + + struct neighbor_ovn_maintain_entry *entry; + VECTOR_FOR_EACH_PTR (maintain_evpn, entry) { + struct maintained_evpn_entry *old_me = + maintained_evpn_entry_find(&old_maintained_evpn_entries, + entry->vni); + + maintain_evpn_devices(entry, old_me ? &old_me->entry : NULL); + + struct maintained_evpn_entry *me; + if (old_me) { + hmap_remove(&old_maintained_evpn_entries, &old_me->node); + me = old_me; + } else { + me = xmalloc(sizeof *me); + } + me->entry = *entry; + hmap_insert(&maintained_evpn_entries, &me->node, + hash_int(entry->vni, 0)); + } + + struct maintained_evpn_entry *stale_me; + HMAP_FOR_EACH_POP (stale_me, node, &old_maintained_evpn_entries) { + evpn_delete_evpn_devices(&stale_me->entry); + free(stale_me); + } + hmap_destroy(&old_maintained_evpn_entries); +} + +void +neighbor_exchange_maintain_evpn_cleanup_all(void) +{ + struct maintained_evpn_entry *me; + HMAP_FOR_EACH (me, node, &maintained_evpn_entries) { + evpn_delete_evpn_devices(&me->entry); + } +} + +void +neighbor_exchange_maintain_evpn_destroy(void) +{ + struct maintained_evpn_entry *me; + HMAP_FOR_EACH_POP (me, node, &maintained_evpn_entries) { + free(me); + } + hmap_destroy(&maintained_evpn_entries); +} + +static uint32_t evpn_remote_vtep_hash(const struct in6_addr *ip, + uint16_t port, uint32_t vni); +static void evpn_remote_vtep_add(struct hmap *remote_vteps, struct in6_addr ip, + uint16_t port, uint32_t vni); +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_entry_add(struct hmap *static_entries, + struct eth_addr mac, struct in6_addr ip, + uint32_t vni, uint32_t nh_id); +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 nh_id); +static uint32_t evpn_static_entry_hash(const struct eth_addr *mac, + const struct in6_addr *ip, + uint32_t vni, uint32_t nh_id); + void neighbor_exchange_run(const struct neighbor_exchange_ctx_in *n_ctx_in, struct neighbor_exchange_ctx_out *n_ctx_out) { + neighbor_exchange_maintain_evpn_run(n_ctx_in->maintain_evpn); + struct neighbor_interface_monitor *nim; struct sset if_names = SSET_INITIALIZER(&if_names); diff --git a/controller/neighbor-exchange.h b/controller/neighbor-exchange.h index 456ff23df..e0098b2c7 100644 --- a/controller/neighbor-exchange.h +++ b/controller/neighbor-exchange.h @@ -27,6 +27,9 @@ struct unixctl_conn; struct neighbor_exchange_ctx_in { /* Contains struct neighbor_interface_monitor pointers. */ const struct vector *monitored_interfaces; + /* Contains struct neighbor_ovn_maintain_entry for VNIs whose kernel + * interfaces OVN must create/maintain. May be NULL. */ + const struct vector *maintain_evpn; }; struct neighbor_exchange_ctx_out { @@ -73,4 +76,9 @@ void evpn_remote_vtep_list(struct unixctl_conn *, int argc, const char *argv[], void *data_); void evpn_static_entries_clear(struct hmap *static_entries); +/* EVPN kernel interface lifecycle. Call cleanup_all on graceful shutdown, + * destroy to release module-level state. */ +void neighbor_exchange_maintain_evpn_cleanup_all(void); +void neighbor_exchange_maintain_evpn_destroy(void); + #endif /* NEIGHBOR_EXCHANGE_H */ diff --git a/controller/neighbor.c b/controller/neighbor.c index 72fabe205..05873c445 100644 --- a/controller/neighbor.c +++ b/controller/neighbor.c @@ -27,6 +27,7 @@ #include "neighbor.h" VLOG_DEFINE_THIS_MODULE(neighbor); +static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); static const char *neighbor_opt_name[] = { [NEIGH_IFACE_BRIDGE] = "dynamic-routing-bridge-ifname", @@ -87,13 +88,111 @@ neigh_parse_device_name(struct sset *device_names, struct local_datapath *ld, neighbor_opt_name[type], ""); sset_from_delimited_string(device_names, names, ","); if (sset_is_empty(device_names)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); VLOG_WARN_RL(&rl, "Datapath "UUID_FMT" misses %s", UUID_ARGS(&ld->datapath->header_.uuid), neighbor_opt_name[type]); } } +static bool +create_maintain_evpn_entry(const struct neighbor_ctx_in *n_ctx_in, + const struct local_datapath *ld, uint32_t vni, + enum neigh_redistribute_mode redistribute_mode, + enum neighbor_mainatain_mode mainatain_mode, + struct neighbor_ovn_maintain_entry *entry) +{ + if (vni > UINT16_MAX - 60000) { + VLOG_WARN_RL(&rl, "dynamic-routing-vni %"PRIu32" for datapath " + UUID_FMT" is too large for dynamic-routing-maintain-evpn " + "(max supported VNI is %u)", + vni, UUID_ARGS(&ld->datapath->header_.uuid), + UINT16_MAX - 60000); + return false; + } + + *entry = (struct neighbor_ovn_maintain_entry) { + .redistribute_mode = redistribute_mode, + .mainatain_mode = mainatain_mode, + .vni = vni, + .fake_vxlan_port = 60000 + vni, + }; + + snprintf(entry->vxlan_if_name, sizeof entry->vxlan_if_name, + "ovnvxlan%"PRIu32, vni); + snprintf(entry->br_if_name, sizeof entry->br_if_name, + "ovnbr%"PRIu32, vni); + snprintf(entry->lo_if_name, sizeof entry->lo_if_name, + "ovnlo%"PRIu32, vni); + snprintf(entry->vrf_if_name, sizeof entry->vrf_if_name, + "ovnvrf%"PRIu32, vni); + + const struct in6_addr *local_ip = NULL; + local_ip = evpn_local_ip_map_lookup(n_ctx_in->evpn_local_ip_map, + vni, true); + if (!local_ip) { + local_ip = evpn_local_ip_map_lookup(n_ctx_in->evpn_local_ip_map, + vni, false); + } + + if (local_ip) { + entry->local_ip = *local_ip; + } else { + VLOG_WARN_RL(&rl, "dynamic-routing-maintain-evpn set for datapath " + UUID_FMT" VNI %"PRIu32" but no ovn-evpn-local-ip " + "configured", + UUID_ARGS(&ld->datapath->header_.uuid), vni); + return false; + } + + if (nrm_mode_IP_is_set(redistribute_mode)) { + const char *lrp_name = smap_get(&ld->datapath->external_ids, + "dynamic-routing-maintain-evpn-lrp"); + if (!lrp_name) { + VLOG_WARN_RL(&rl, "dynamic-routing-maintain-evpn=mvd is set with " + "IP redistribution on datapath "UUID_FMT" but " + "dynamic-routing-maintain-evpn-lrp is not configured", + UUID_ARGS(&ld->datapath->header_.uuid)); + return false; + } + + const struct sbrec_port_binding *pb = + lport_lookup_by_name(n_ctx_in->sbrec_pb_by_name, lrp_name); + if (!pb) { + VLOG_WARN_RL(&rl, "dynamic-routing-evpn-lrp: port \"%s\" " + "not found", lrp_name); + return false; + } + + entry->br_config.has_addr = + extract_sbrec_binding_first_mac(pb, &entry->br_config.lladdr); + if (!entry->br_config.has_addr) { + VLOG_WARN_RL(&rl, "Unable to parse mac address on router " + "port %s for configuring EVPN devices", lrp_name); + return false; + } + } + + return true; +} + +static enum neighbor_mainatain_mode +parse_neigh_maintain_mode(const struct smap *external_ids) +{ + const char *val = smap_get(external_ids, "dynamic-routing-maintain-evpn"); + if (!val) { + return NEIGH_MAINTAIN_NONE; + } + + if (!strcmp(val, "mvd")) { + return NEIGH_MAINTAIN_MVD; + } + + VLOG_WARN_RL(&rl, "dynamic-routing-maintain-evpn: unknown value \"%s\", " + "expected \"mvd\"", val); + + return NEIGH_MAINTAIN_NONE; +} + void neighbor_run(struct neighbor_ctx_in *n_ctx_in, struct neighbor_ctx_out *n_ctx_out) @@ -115,26 +214,51 @@ neighbor_run(struct neighbor_ctx_in *n_ctx_in, continue; } + enum neigh_redistribute_mode redistribute_mode = + parse_neigh_dynamic_redistribute(&ld->datapath->external_ids); + + enum neighbor_mainatain_mode mainatain_mode = + parse_neigh_maintain_mode(&ld->datapath->external_ids); + + struct neighbor_ovn_maintain_entry maintain_entry; + if (mainatain_mode != NEIGH_MAINTAIN_NONE) { + if (create_maintain_evpn_entry(n_ctx_in, ld, (uint32_t) vni, + redistribute_mode, mainatain_mode, + &maintain_entry)) { + vector_push(n_ctx_out->maintain_evpn, &maintain_entry); + } else { + continue; + } + } + struct sset device_names; - neigh_parse_device_name(&device_names, ld, NEIGH_IFACE_VXLAN); + if (mainatain_mode != NEIGH_MAINTAIN_NONE) { + sset_init(&device_names); + sset_add(&device_names, maintain_entry.vxlan_if_name); + } else { + neigh_parse_device_name(&device_names, ld, NEIGH_IFACE_VXLAN); + } const char *name; SSET_FOR_EACH (name, &device_names) { - struct neighbor_interface_monitor *vxlan = + struct neighbor_interface_monitor *nim = neighbor_interface_monitor_alloc(NEIGH_AF_BRIDGE, NEIGH_IFACE_VXLAN, vni, name); - vector_push(n_ctx_out->monitored_interfaces, &vxlan); + vector_push(n_ctx_out->monitored_interfaces, &nim); } sset_destroy(&device_names); struct neighbor_interface_monitor *lo = NULL; - neigh_parse_device_name(&device_names, ld, NEIGH_IFACE_LOOPBACK); - if (sset_count(&device_names) > 1) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - VLOG_WARN_RL(&rl, "Datapath "UUID_FMT" too many names provided " - "for loopback device", - UUID_ARGS(&ld->datapath->header_.uuid)); + if (mainatain_mode != NEIGH_MAINTAIN_NONE) { + sset_init(&device_names); + sset_add(&device_names, maintain_entry.lo_if_name); + } else { + neigh_parse_device_name(&device_names, ld, NEIGH_IFACE_LOOPBACK); + if (sset_count(&device_names) > 1) { + VLOG_WARN_RL(&rl, "Datapath "UUID_FMT" too many names for " + "loopback device", + UUID_ARGS(&ld->datapath->header_.uuid)); + } } - if (!sset_is_empty(&device_names)) { lo = neighbor_interface_monitor_alloc(NEIGH_AF_BRIDGE, NEIGH_IFACE_LOOPBACK, vni, @@ -145,45 +269,42 @@ neighbor_run(struct neighbor_ctx_in *n_ctx_in, struct neighbor_interface_monitor *br_v4 = NULL; struct neighbor_interface_monitor *br_v6 = NULL; - neigh_parse_device_name(&device_names, ld, NEIGH_IFACE_BRIDGE); - if (sset_count(&device_names) > 1) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - VLOG_WARN_RL(&rl, "Datapath "UUID_FMT" too many names provided " - "for bridge device", - UUID_ARGS(&ld->datapath->header_.uuid)); + if (mainatain_mode != NEIGH_MAINTAIN_NONE) { + sset_init(&device_names); + sset_add(&device_names, maintain_entry.br_if_name); + } else { + neigh_parse_device_name(&device_names, ld, NEIGH_IFACE_BRIDGE); + if (sset_count(&device_names) > 1) { + VLOG_WARN_RL(&rl, "Datapath "UUID_FMT" too many names for " + "bridge device", + UUID_ARGS(&ld->datapath->header_.uuid)); + } } - if (!sset_is_empty(&device_names)) { - br_v4 = - neighbor_interface_monitor_alloc(NEIGH_AF_INET, - NEIGH_IFACE_BRIDGE, vni, - SSET_FIRST(&device_names)); + br_v4 = neighbor_interface_monitor_alloc( + NEIGH_AF_INET, NEIGH_IFACE_BRIDGE, vni, + SSET_FIRST(&device_names)); vector_push(n_ctx_out->monitored_interfaces, &br_v4); - - br_v6 = - neighbor_interface_monitor_alloc(NEIGH_AF_INET6, - NEIGH_IFACE_BRIDGE, vni, - SSET_FIRST(&device_names)); + br_v6 = neighbor_interface_monitor_alloc( + NEIGH_AF_INET6, NEIGH_IFACE_BRIDGE, vni, + SSET_FIRST(&device_names)); vector_push(n_ctx_out->monitored_interfaces, &br_v6); } sset_destroy(&device_names); - enum neigh_redistribute_mode mode = - parse_neigh_dynamic_redistribute(&ld->datapath->external_ids); - if (nrm_mode_FDB_is_set(mode) && lo) { + if (nrm_mode_FDB_is_set(redistribute_mode) && lo) { neighbor_collect_mac_to_advertise(n_ctx_in, &lo->announced_neighbors, n_ctx_out->advertised_pbs, ld->datapath); } - if (nrm_mode_IP_is_set(mode) && br_v4 && br_v6) { + if (nrm_mode_IP_is_set(redistribute_mode) && br_v4 && br_v6) { neighbor_collect_ip_mac_to_advertise(n_ctx_in, &br_v4->announced_neighbors, &br_v6->announced_neighbors, n_ctx_out->advertised_pbs, ld->datapath); } - } } diff --git a/controller/neighbor.h b/controller/neighbor.h index e3adc87d1..bb440535f 100644 --- a/controller/neighbor.h +++ b/controller/neighbor.h @@ -23,6 +23,7 @@ #include "lib/sset.h" #include "openvswitch/hmap.h" +#include "lib/ovn-util.h" #include "vec.h" @@ -49,6 +50,7 @@ struct neighbor_ctx_in { /* Index for Port Binding by name. */ struct ovsdb_idl_index *sbrec_pb_by_name; const struct sbrec_chassis *chassis; + struct evpn_local_ip_map *evpn_local_ip_map; }; struct neighbor_ctx_out { @@ -56,6 +58,10 @@ struct neighbor_ctx_out { struct vector *monitored_interfaces; /* Contains set of port binding names that are currently advertised. */ struct sset *advertised_pbs; + /* Contains struct neighbor_ovn_maintain_entry for VNIs that need + * maintained interfaces. May be NULL if caller does not need this. */ + struct vector *maintain_evpn; + struct evpn_local_ip_map *evpn_ip_map; }; enum neighbor_interface_type { @@ -64,6 +70,34 @@ enum neighbor_interface_type { NEIGH_IFACE_LOOPBACK, }; +enum neighbor_mainatain_mode { + NEIGH_MAINTAIN_NONE, + NEIGH_MAINTAIN_MVD, +}; + +/* IP/MAC to configure on the bridge interface so BGP can use it as next-hop. + * Filled from the LRP named by dynamic-routing-evpn-lrp. */ +struct neighbor_bridge_iface_config { + bool has_addr; + struct eth_addr lladdr; + struct in6_addr addr; /* IPv4 stored as mapped IPv4-in-IPv6. */ + uint8_t prefix_len; +}; + +struct neighbor_ovn_maintain_entry { + enum neigh_redistribute_mode redistribute_mode; + enum neighbor_mainatain_mode mainatain_mode; + char vxlan_if_name[IFNAMSIZ + 1]; + char vrf_if_name[IFNAMSIZ + 1]; + char br_if_name[IFNAMSIZ + 1]; + char lo_if_name[IFNAMSIZ + 1]; + struct neighbor_bridge_iface_config br_config; + struct in6_addr local_ip; + uint32_t vni; + /* Formula: 60000 + vni (valid for VNI <= 5535). */ + uint16_t fake_vxlan_port; +}; + struct neighbor_interface_monitor { enum neighbor_family family; char if_name[IFNAMSIZ + 1]; diff --git a/controller/netlink-utils.c b/controller/netlink-utils.c new file mode 100644 index 000000000..4055f8809 --- /dev/null +++ b/controller/netlink-utils.c @@ -0,0 +1,148 @@ +/* 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 <stdbool.h> + +#include <errno.h> +#include <linux/if_ether.h> +#include <linux/if_link.h> +#include <linux/rtnetlink.h> + +#include "lib/netlink.h" +#include "lib/netlink-socket.h" +#include "lib/packets.h" + +#include "openvswitch/ofpbuf.h" + +#include "netlink-utils.h" + +int32_t +nl_ifindex_get(const char *ifname) +{ + return (int32_t) if_nametoindex(ifname); +} + +int +nl_create_device(const char *ifname, const char *kind) +{ + struct ifinfomsg *ifinfo; + struct ofpbuf request; + ofpbuf_init(&request, 0); + + nl_msg_put_nlmsghdr(&request, 0, RTM_NEWLINK, + NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL); + ifinfo = ofpbuf_put_zeros(&request, sizeof *ifinfo); + nl_msg_put_string(&request, IFLA_IFNAME, ifname); + ifinfo->ifi_change = ifinfo->ifi_flags = IFF_UP; + + size_t linkinfo_off = nl_msg_start_nested(&request, IFLA_LINKINFO); + nl_msg_put_string(&request, IFLA_INFO_KIND, kind); + nl_msg_end_nested(&request, linkinfo_off); + + int err = nl_transact(NETLINK_ROUTE, &request, NULL); + ofpbuf_uninit(&request); + return err; +} + +int +nl_create_vrf(const char *ifname, uint32_t table_id) +{ + size_t linkinfo_off, infodata_off; + struct ifinfomsg *ifinfo; + int err; + + struct ofpbuf request; + uint8_t request_stub[NETNL_REQ_BUFFER_SIZE]; + ofpbuf_use_stub(&request, request_stub, sizeof(request_stub)); + + nl_msg_put_nlmsghdr(&request, 0, RTM_NEWLINK, + NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL); + ifinfo = ofpbuf_put_zeros(&request, sizeof *ifinfo); + nl_msg_put_string(&request, IFLA_IFNAME, ifname); + + ifinfo->ifi_change = ifinfo->ifi_flags = IFF_UP; + linkinfo_off = nl_msg_start_nested(&request, IFLA_LINKINFO); + nl_msg_put_string(&request, IFLA_INFO_KIND, "vrf"); + infodata_off = nl_msg_start_nested(&request, IFLA_INFO_DATA); + nl_msg_put_u32(&request, IFLA_VRF_TABLE, table_id); + nl_msg_end_nested(&request, infodata_off); + nl_msg_end_nested(&request, linkinfo_off); + + err = nl_transact(NETLINK_ROUTE, &request, NULL); + + ofpbuf_uninit(&request); + return err; +} + +int +nl_delete_device(const char *ifname) +{ + struct ifinfomsg *ifinfo; + int err; + + struct ofpbuf request; + uint8_t request_stub[NETNL_REQ_BUFFER_SIZE]; + ofpbuf_use_stub(&request, request_stub, sizeof(request_stub)); + + nl_msg_put_nlmsghdr(&request, 0, RTM_DELLINK, NLM_F_REQUEST | NLM_F_ACK); + ifinfo = ofpbuf_put_zeros(&request, sizeof *ifinfo); + nl_msg_put_string(&request, IFLA_IFNAME, ifname); + err = nl_transact(NETLINK_ROUTE, &request, NULL); + + ofpbuf_uninit(&request); + return err; +} + +int +nl_set_master(const char *slave, const char *master) +{ + int err = 0; + int32_t master_idx = nl_ifindex_get(master); + if (!master_idx) { + return ENODEV; + } + + struct ofpbuf request; + ofpbuf_init(&request, 0); + + nl_msg_put_nlmsghdr(&request, 0, RTM_NEWLINK, + NLM_F_REQUEST | NLM_F_ACK); + ofpbuf_put_zeros(&request, sizeof(struct ifinfomsg)); + nl_msg_put_string(&request, IFLA_IFNAME, slave); + nl_msg_put_u32(&request, IFLA_MASTER, master_idx); + + err = nl_transact(NETLINK_ROUTE, &request, NULL); + ofpbuf_uninit(&request); + return err; +} + +int +nl_set_iface_mac(const char *ifname, const struct eth_addr *mac) +{ + int err = 0; + struct ofpbuf request; + ofpbuf_init(&request, 0); + + nl_msg_put_nlmsghdr(&request, 0, RTM_NEWLINK, + NLM_F_REQUEST | NLM_F_ACK); + ofpbuf_put_zeros(&request, sizeof(struct ifinfomsg)); + nl_msg_put_string(&request, IFLA_IFNAME, ifname); + nl_msg_put_unspec(&request, IFLA_ADDRESS, mac, sizeof *mac); + + err = nl_transact(NETLINK_ROUTE, &request, NULL); + ofpbuf_uninit(&request); + return err; +} diff --git a/controller/netlink-utils.h b/controller/netlink-utils.h new file mode 100644 index 000000000..ab5424dfe --- /dev/null +++ b/controller/netlink-utils.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2025 Canonical, Ltd. + * Copyright (c) 2025, STACKIT GmbH & Co. KG + * + * 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 NETLINK_UTILS_H +#define NETLINK_UTILS_H 1 + +#include <stdint.h> +#include <linux/rtnetlink.h> +#include <netinet/in.h> +#include <net/if.h> + +#define NETNL_REQ_BUFFER_SIZE 128 + +#define TABLE_ID_VALID(table_id) (table_id != RT_TABLE_UNSPEC && \ + table_id != RT_TABLE_COMPAT && \ + table_id != RT_TABLE_DEFAULT && \ + table_id != RT_TABLE_MAIN && \ + table_id != RT_TABLE_LOCAL && \ + table_id != RT_TABLE_MAX) + +int32_t nl_ifindex_get(const char *ifname); +int nl_create_device(const char *ifname, const char *kind); +int nl_create_vrf(const char *ifname, uint32_t table_id); +int nl_delete_device(const char *ifname); +int nl_set_iface_mac(const char *ifname, + const struct eth_addr *mac); +int nl_set_master(const char *slave, const char *master); +#endif /* netlink-utils.h */ \ No newline at end of file diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c index 552e87b53..4b1fcec26 100644 --- a/controller/ovn-controller.c +++ b/controller/ovn-controller.c @@ -6074,6 +6074,9 @@ struct ed_type_neighbor { struct vector monitored_interfaces; /* Contains set of PB names that are currently advertised. */ struct sset advertised_pbs; + /* Contains struct neighbor_ovn_maintain_entry, one per VNI + * whose EVPN interfaces are auto-created and maintained. */ + struct vector maintain_evpn; }; static void * @@ -6086,6 +6089,8 @@ en_neighbor_init(struct engine_node *node OVS_UNUSED, .monitored_interfaces = VECTOR_EMPTY_INITIALIZER(struct neighbor_interface_monitor *), .advertised_pbs = SSET_INITIALIZER(&data->advertised_pbs), + .maintain_evpn = + VECTOR_EMPTY_INITIALIZER(struct neighbor_ovn_maintain_entry), }; return data; } @@ -6097,6 +6102,7 @@ en_neighbor_cleanup(void *data) neighbor_cleanup(&ne_data->monitored_interfaces); vector_destroy(&ne_data->monitored_interfaces); + vector_destroy(&ne_data->maintain_evpn); sset_destroy(&ne_data->advertised_pbs); } @@ -6131,22 +6137,32 @@ en_neighbor_run(struct engine_node *node OVS_UNUSED, void *data) chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id); ovs_assert(chassis); + struct evpn_local_ip_map evpn_ip_map = { + .vni_ip4 = HMAP_INITIALIZER(&evpn_ip_map.vni_ip4), + .vni_ip6 = HMAP_INITIALIZER(&evpn_ip_map.vni_ip6), + }; + evpn_local_ip_map_init(&evpn_ip_map, &chassis->other_config); + struct neighbor_ctx_in n_ctx_in = { .local_datapaths = &rt_data->local_datapaths, .sbrec_pb_by_dp = sbrec_port_binding_by_datapath, .sbrec_amb_by_dp = sbrec_advertised_mac_binding_by_datapath, .sbrec_pb_by_name = sbrec_port_binding_by_name, .chassis = chassis, + .evpn_local_ip_map = &evpn_ip_map, }; struct neighbor_ctx_out n_ctx_out = { .monitored_interfaces = &ne_data->monitored_interfaces, .advertised_pbs = &ne_data->advertised_pbs, + .maintain_evpn = &ne_data->maintain_evpn, }; neighbor_cleanup(&ne_data->monitored_interfaces); sset_clear(&ne_data->advertised_pbs); + vector_clear(&ne_data->maintain_evpn); neighbor_run(&n_ctx_in, &n_ctx_out); + evpn_local_ip_map_destroy(&evpn_ip_map); return EN_UPDATED; } @@ -6479,6 +6495,7 @@ en_neighbor_exchange_run(struct engine_node *node, void *data_) struct neighbor_exchange_ctx_in n_ctx_in = { .monitored_interfaces = &neighbor_data->monitored_interfaces, + .maintain_evpn = &neighbor_data->maintain_evpn, }; struct neighbor_exchange_ctx_out n_ctx_out = { .neighbor_table_watches = &nt_notify->watches, @@ -8458,6 +8475,8 @@ loop_done: route_exchange_cleanup_vrfs(); } + neighbor_exchange_maintain_evpn_cleanup_all(); + /* The engine cleanup should happen only after threads have been * destroyed and joined in case they are accessing engine data. */ pinctrl_destroy(); @@ -8494,6 +8513,7 @@ loop_done: dns_resolve_destroy(); route_exchange_destroy(); ovn_netlink_notifiers_destroy(); + neighbor_exchange_maintain_evpn_destroy(); exit(retval); } diff --git a/controller/physical.c b/controller/physical.c index 89ff2785e..0277d472d 100644 --- a/controller/physical.c +++ b/controller/physical.c @@ -3231,163 +3231,6 @@ physical_eval_remote_chassis_flows(const struct physical_ctx *ctx, ofpbuf_uninit(&ingress_ofpacts); } -struct vni_local_ip { - struct hmap_node hmap_node; - struct in6_addr ip; - uint32_t vni; -}; - -struct evpn_local_ip_map { - struct hmap vni_ip4; /* Per VNI local IPv4 vni_local_ips. */ - struct hmap vni_ip6; /* Per VNI local IPv6 vni_local_ips. */ - struct in6_addr default_ip4; /* Default local IPv4. */ - struct in6_addr default_ip6; /* Default local IPv6. */ -}; - -static const struct in6_addr * -evpn_local_ip_lookup(const struct hmap *map, uint32_t vni) -{ - struct vni_local_ip *e; - HMAP_FOR_EACH_WITH_HASH (e, hmap_node, hash_add(vni, 0), map) { - if (e->vni == vni) { - return &e->ip; - } - } - return NULL; -} - -static ovs_be32 -evpn_local_ip_find_v4(const struct evpn_local_ip_map *vni_ip_map, - uint32_t vni) -{ - const struct in6_addr *addr = evpn_local_ip_lookup(&vni_ip_map->vni_ip4, - vni); - if (addr) { - return in6_addr_get_mapped_ipv4(addr); - } - - if (ipv6_addr_is_set(&vni_ip_map->default_ip4)) { - return in6_addr_get_mapped_ipv4(&vni_ip_map->default_ip4); - } - - return 0; -} - -static const struct in6_addr * -evpn_local_ip_find_v6(const struct evpn_local_ip_map *vni_ip_map, - uint32_t vni) -{ - const struct in6_addr *addr = evpn_local_ip_lookup(&vni_ip_map->vni_ip6, - vni); - if (addr) { - return addr; - } - - if (ipv6_addr_is_set(&vni_ip_map->default_ip6)) { - return &vni_ip_map->default_ip6; - } - - return NULL; -} - -static void -evpn_local_ip_map_init(struct evpn_local_ip_map *vni_ip_map, - const struct smap *config) -{ - char *tokstr, *token, *ptr0 = NULL; - - const char *local_ip_str = smap_get_def(config, "ovn-evpn-local-ip", ""); - tokstr = xstrdup(local_ip_str); - for (token = strtok_r(tokstr, ",", &ptr0); token; - token = strtok_r(NULL, ",", &ptr0)) { - char *ptr1 = NULL, *vni_str = strtok_r(token, "-", &ptr1); - char *ip_str = strtok_r(NULL, "-", &ptr1); - struct in6_addr ip; - uint32_t vni; - - if (ptr1 && *ptr1) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "Malformed 'ovn-evpn-local-ip': %s", tokstr); - break; - } - - if (!ip_str) { /* default IP */ - if (!ip46_parse(vni_str, &ip)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "Invalid IP %s in 'ovn-evpn-local-ip'", - vni_str); - continue; - } - struct in6_addr *ip_ptr = IN6_IS_ADDR_V4MAPPED(&ip) - ? &vni_ip_map->default_ip4 - : &vni_ip_map->default_ip6; - if (ipv6_addr_is_set(ip_ptr)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "Default ovn-evpn-local-ip IP%d " - "already configured", - IN6_IS_ADDR_V4MAPPED(&ip) ? 4 : 6); - continue; - } - *ip_ptr = ip; - } else { - if (!ip46_parse(ip_str, &ip)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, - "EVPN enabled, but required 'ovn-evpn-local-ip' " - "is missing or invalid for vni %s", vni_str); - continue; - } - - if (!vni_str) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "Required 'ovn-evpn-local-ip' VNI not " - "configured for IP %s", - ip_str); - continue; - } - - if (!ovs_scan(vni_str, "%u", &vni) || !ovn_is_valid_vni(vni)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "Invalid VNI %s in 'ovn-evpn-local-ip'", - vni_str); - continue; - } - - struct hmap *map = IN6_IS_ADDR_V4MAPPED(&ip) - ? &vni_ip_map->vni_ip4 - : &vni_ip_map->vni_ip6; - if (evpn_local_ip_lookup(map, vni)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "Duplicated VNI entry: %s", vni_str); - continue; - } - - struct vni_local_ip *e = xmalloc(sizeof *e); - *e = (struct vni_local_ip) { - .vni = vni, - .ip = ip, - }; - hmap_insert(map, &e->hmap_node, hash_add(vni, 0)); - } - } - free(tokstr); -} - -static void -evpn_local_ip_map_destroy(struct evpn_local_ip_map *map) -{ - struct vni_local_ip *e; - HMAP_FOR_EACH_POP (e, hmap_node, &map->vni_ip4) { - free(e); - } - hmap_destroy(&map->vni_ip4); - - HMAP_FOR_EACH_POP (e, hmap_node, &map->vni_ip6) { - free(e); - } - hmap_destroy(&map->vni_ip6); -} - static void physical_consider_evpn_binding(const struct evpn_binding *binding, const struct evpn_local_ip_map *vni_ip_map, diff --git a/controller/route-exchange-netlink.c b/controller/route-exchange-netlink.c index 8f1615c4e..e280fae1d 100644 --- a/controller/route-exchange-netlink.c +++ b/controller/route-exchange-netlink.c @@ -31,13 +31,12 @@ #include "route-table.h" #include "route.h" #include "vec.h" +#include "netlink-utils.h" #include "route-exchange-netlink.h" VLOG_DEFINE_THIS_MODULE(route_exchange_netlink); -#define NETNL_REQ_BUFFER_SIZE 128 - static void re_nl_encode_nexthop(struct ofpbuf *, bool dst_is_ipv4, const struct in6_addr *); @@ -51,51 +50,13 @@ re_nl_create_vrf(const char *ifname, uint32_t table_id) table_id); return EINVAL; } - - size_t linkinfo_off, infodata_off; - struct ifinfomsg *ifinfo; - int err; - - struct ofpbuf request; - uint8_t request_stub[NETNL_REQ_BUFFER_SIZE]; - ofpbuf_use_stub(&request, request_stub, sizeof(request_stub)); - - nl_msg_put_nlmsghdr(&request, 0, RTM_NEWLINK, - NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL); - ifinfo = ofpbuf_put_zeros(&request, sizeof *ifinfo); - nl_msg_put_string(&request, IFLA_IFNAME, ifname); - - ifinfo->ifi_change = ifinfo->ifi_flags = IFF_UP; - linkinfo_off = nl_msg_start_nested(&request, IFLA_LINKINFO); - nl_msg_put_string(&request, IFLA_INFO_KIND, "vrf"); - infodata_off = nl_msg_start_nested(&request, IFLA_INFO_DATA); - nl_msg_put_u32(&request, IFLA_VRF_TABLE, table_id); - nl_msg_end_nested(&request, infodata_off); - nl_msg_end_nested(&request, linkinfo_off); - - err = nl_transact(NETLINK_ROUTE, &request, NULL); - - ofpbuf_uninit(&request); - return err; + return nl_create_vrf(ifname, table_id); } int re_nl_delete_vrf(const char *ifname) { - struct ifinfomsg *ifinfo; - int err; - - struct ofpbuf request; - uint8_t request_stub[NETNL_REQ_BUFFER_SIZE]; - ofpbuf_use_stub(&request, request_stub, sizeof(request_stub)); - - nl_msg_put_nlmsghdr(&request, 0, RTM_DELLINK, NLM_F_REQUEST | NLM_F_ACK); - ifinfo = ofpbuf_put_zeros(&request, sizeof *ifinfo); - nl_msg_put_string(&request, IFLA_IFNAME, ifname); - err = nl_transact(NETLINK_ROUTE, &request, NULL); - - ofpbuf_uninit(&request); - return err; + return nl_delete_device(ifname); } void diff --git a/controller/route-exchange-netlink.h b/controller/route-exchange-netlink.h index f79ae05db..af3985f81 100644 --- a/controller/route-exchange-netlink.h +++ b/controller/route-exchange-netlink.h @@ -29,13 +29,6 @@ #define RTPROT_OVN 84 #endif -#define TABLE_ID_VALID(table_id) (table_id != RT_TABLE_UNSPEC && \ - table_id != RT_TABLE_COMPAT && \ - table_id != RT_TABLE_DEFAULT && \ - table_id != RT_TABLE_MAIN && \ - table_id != RT_TABLE_LOCAL && \ - table_id != RT_TABLE_MAX) - struct in6_addr; struct hmap; struct vector; @@ -51,7 +44,6 @@ struct re_nl_received_route_node { int re_nl_create_vrf(const char *ifname, uint32_t table_id); int re_nl_delete_vrf(const char *ifname); - int re_nl_add_route(uint32_t table_id, const struct advertise_route_entry *); int re_nl_delete_route(uint32_t table_id, const struct advertise_route_entry *); diff --git a/controller/route-exchange.c b/controller/route-exchange.c index b86eb43bf..879be9940 100644 --- a/controller/route-exchange.c +++ b/controller/route-exchange.c @@ -34,6 +34,7 @@ #include "route.h" #include "route-exchange.h" #include "route-exchange-netlink.h" +#include "netlink-utils.h" VLOG_DEFINE_THIS_MODULE(route_exchange); static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20); diff --git a/lib/ovn-util.c b/lib/ovn-util.c index 80346d5bb..6da3ae8fb 100644 --- a/lib/ovn-util.c +++ b/lib/ovn-util.c @@ -1894,3 +1894,160 @@ port_contains_duplicate_ip(struct lport_addresses *laddrs1, return false; } + +const struct in6_addr * +evpn_local_ip_lookup(const struct hmap *map, uint32_t vni) +{ + struct vni_local_ip *e; + HMAP_FOR_EACH_WITH_HASH (e, hmap_node, hash_add(vni, 0), map) { + if (e->vni == vni) { + return &e->ip; + } + } + return NULL; +} + +ovs_be32 +evpn_local_ip_find_v4(const struct evpn_local_ip_map *vni_ip_map, + uint32_t vni) +{ + const struct in6_addr *addr = evpn_local_ip_lookup(&vni_ip_map->vni_ip4, + vni); + if (addr) { + return in6_addr_get_mapped_ipv4(addr); + } + + if (ipv6_addr_is_set(&vni_ip_map->default_ip4)) { + return in6_addr_get_mapped_ipv4(&vni_ip_map->default_ip4); + } + + return 0; +} + +const struct in6_addr * +evpn_local_ip_find_v6(const struct evpn_local_ip_map *vni_ip_map, + uint32_t vni) +{ + const struct in6_addr *addr = evpn_local_ip_lookup(&vni_ip_map->vni_ip6, + vni); + if (addr) { + return addr; + } + + if (ipv6_addr_is_set(&vni_ip_map->default_ip6)) { + return &vni_ip_map->default_ip6; + } + + return NULL; +} + +const struct in6_addr * +evpn_local_ip_map_lookup(const struct evpn_local_ip_map *map, + uint32_t vni, bool ipv4) +{ + const struct hmap *hmap = ipv4 ? &map->vni_ip4 : &map->vni_ip6; + const struct in6_addr *addr = evpn_local_ip_lookup(hmap, vni); + if (addr) { + return addr; + } + const struct in6_addr *def = ipv4 ? &map->default_ip4 : &map->default_ip6; + return ipv6_addr_is_set(def) ? def : NULL; +} + +void +evpn_local_ip_map_init(struct evpn_local_ip_map *vni_ip_map, + const struct smap *config) +{ + char *tokstr, *token, *ptr0 = NULL; + + const char *local_ip_str = smap_get_def(config, "ovn-evpn-local-ip", ""); + tokstr = xstrdup(local_ip_str); + for (token = strtok_r(tokstr, ",", &ptr0); token; + token = strtok_r(NULL, ",", &ptr0)) { + char *ptr1 = NULL, *vni_str = strtok_r(token, "-", &ptr1); + char *ip_str = strtok_r(NULL, "-", &ptr1); + struct in6_addr ip; + uint32_t vni; + + if (ptr1 && *ptr1) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "Malformed 'ovn-evpn-local-ip': %s", tokstr); + break; + } + + if (!ip_str) { /* default IP */ + if (!ip46_parse(vni_str, &ip)) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "Invalid IP %s in 'ovn-evpn-local-ip'", + vni_str); + continue; + } + struct in6_addr *ip_ptr = IN6_IS_ADDR_V4MAPPED(&ip) + ? &vni_ip_map->default_ip4 + : &vni_ip_map->default_ip6; + if (ipv6_addr_is_set(ip_ptr)) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "Default ovn-evpn-local-ip IP%d " + "already configured", + IN6_IS_ADDR_V4MAPPED(&ip) ? 4 : 6); + continue; + } + *ip_ptr = ip; + } else { + if (!ip46_parse(ip_str, &ip)) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, + "EVPN enabled, but required 'ovn-evpn-local-ip' " + "is missing or invalid for vni %s", vni_str); + continue; + } + + if (!vni_str) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "Required 'ovn-evpn-local-ip' VNI not " + "configured for IP %s", + ip_str); + continue; + } + + if (!ovs_scan(vni_str, "%u", &vni) || !ovn_is_valid_vni(vni)) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "Invalid VNI %s in 'ovn-evpn-local-ip'", + vni_str); + continue; + } + + struct hmap *map = IN6_IS_ADDR_V4MAPPED(&ip) + ? &vni_ip_map->vni_ip4 + : &vni_ip_map->vni_ip6; + if (evpn_local_ip_lookup(map, vni)) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "Duplicated VNI entry: %s", vni_str); + continue; + } + + struct vni_local_ip *e = xmalloc(sizeof *e); + *e = (struct vni_local_ip) { + .vni = vni, + .ip = ip, + }; + hmap_insert(map, &e->hmap_node, hash_add(vni, 0)); + } + } + free(tokstr); +} + +void +evpn_local_ip_map_destroy(struct evpn_local_ip_map *map) +{ + struct vni_local_ip *e; + HMAP_FOR_EACH_POP (e, hmap_node, &map->vni_ip4) { + free(e); + } + hmap_destroy(&map->vni_ip4); + + HMAP_FOR_EACH_POP (e, hmap_node, &map->vni_ip6) { + free(e); + } + hmap_destroy(&map->vni_ip6); +} diff --git a/lib/ovn-util.h b/lib/ovn-util.h index b911a59b0..55ab372cf 100644 --- a/lib/ovn-util.h +++ b/lib/ovn-util.h @@ -124,7 +124,6 @@ bool extract_sbrec_binding_first_mac(const struct sbrec_port_binding *binding, bool extract_lrp_networks__(char *mac, char **networks, size_t n_networks, struct lport_addresses *laddrs); - bool lport_addresses_is_empty(const struct lport_addresses *); void init_lport_addresses(struct lport_addresses *); void destroy_lport_addresses(struct lport_addresses *); @@ -830,4 +829,29 @@ parse_neigh_dynamic_redistribute(const struct smap *options); size_t *shuffled_range(size_t n); +struct vni_local_ip { + struct hmap_node hmap_node; + struct in6_addr ip; + uint32_t vni; +}; + +struct evpn_local_ip_map { + struct hmap vni_ip4; /* Per VNI local IPv4 vni_local_ips. */ + struct hmap vni_ip6; /* Per VNI local IPv6 vni_local_ips. */ + struct in6_addr default_ip4; /* Default local IPv4. */ + struct in6_addr default_ip6; /* Default local IPv6. */ +}; + +const struct in6_addr *evpn_local_ip_lookup(const struct hmap *map, + uint32_t vni); +ovs_be32 evpn_local_ip_find_v4(const struct evpn_local_ip_map *vni_ip_map, + uint32_t vni); +const struct in6_addr *evpn_local_ip_find_v6( + const struct evpn_local_ip_map *vni_ip_map, uint32_t vni); +void evpn_local_ip_map_init(struct evpn_local_ip_map *vni_ip_map, + const struct smap *config); +const struct in6_addr * +evpn_local_ip_map_lookup(const struct evpn_local_ip_map *map, + uint32_t vni, bool ipv4); +void evpn_local_ip_map_destroy(struct evpn_local_ip_map *map); #endif /* OVN_UTIL_H */ diff --git a/northd/en-datapath-logical-switch.c b/northd/en-datapath-logical-switch.c index ad6d042bc..a1ba217ba 100644 --- a/northd/en-datapath-logical-switch.c +++ b/northd/en-datapath-logical-switch.c @@ -180,6 +180,20 @@ gather_external_ids(const struct nbrec_logical_switch *nbs, smap_add(external_ids, "dynamic-routing-arp-prefer-local", prefer_evpn_arp_local); } + + const char *ovn_maintain_evpn_mode = + smap_get(&nbs->other_config, "dynamic-routing-maintain-evpn"); + if (ovn_maintain_evpn_mode) { + smap_add(external_ids, "dynamic-routing-maintain-evpn", + ovn_maintain_evpn_mode); + } + + const char *ovn_maintain_evpn_lrp = + smap_get(&nbs->other_config, "dynamic-routing-maintain-evpn-lrp"); + if (ovn_maintain_evpn_lrp) { + smap_add(external_ids, "dynamic-routing-maintain-evpn-lrp", + ovn_maintain_evpn_lrp); + } } /* For backwards-compatibility, also store the NB UUID in diff --git a/tests/automake.mk b/tests/automake.mk index 8084357b5..1aa9f45c2 100644 --- a/tests/automake.mk +++ b/tests/automake.mk @@ -291,6 +291,8 @@ tests_ovstest_SOURCES = \ if HAVE_NETLINK tests_ovstest_SOURCES += \ + controller/netlink-utils.h \ + controller/netlink-utils.c \ controller/host-if-monitor.c \ controller/host-if-monitor.h \ controller/neighbor-exchange-netlink.c \ diff --git a/tests/system-ovn.at b/tests/system-ovn.at index 87839f4a3..9a7e78263 100644 --- a/tests/system-ovn.at +++ b/tests/system-ovn.at @@ -22094,3 +22094,182 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d /connection dropped.*/d"]) AT_CLEANUP ]) + +OVN_FOR_EACH_NORTHD([ +AT_SETUP([dynamic-routing - EVPN maintain-evpn mvd]) +AT_KEYWORDS([dynamic-routing evpn maintain-evpn]) + +CHECK_VRF() + +vni=100 +VRF_RESERVE([$vni]) + +ovn_start +OVS_TRAFFIC_VSWITCHD_START() +ADD_BR([br-int]) + +check ovs-vsctl \ + -- set Open_vSwitch . external-ids:system-id=hv1 \ + -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ + -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \ + -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ + -- set Open_vSwitch . external-ids:ovn-evpn-local-ip=169.0.0.1 \ + -- set bridge br-int fail-mode=secure other-config:disable-in-band=true + +start_daemon ovn-controller +ovs-vsctl add-port br-int p1 -- \ + set Interface p1 external_ids:iface-id=ls-p1 -- \ + set Interface p1 type=internal +check ovn-nbctl ls-add ls-evpn +check ovn-nbctl lsp-add ls-evpn ls-p1 +check ovn-nbctl lsp-set-addresses ls-p1 "00:00:00:00:00:02 20.20.20.2" + +AS_BOX([Create EVPN interface stack - L2 VNI]) +check ovn-nbctl \ + -- set Logical_Switch ls-evpn \ + other_config:dynamic-routing-vni=$vni \ + other_config:dynamic-routing-maintain-evpn=mvd \ + other_config:dynamic-routing-redistribute=fdb +check ovn-nbctl --wait=hv sync + +# check no vrf exists since it's only fdb mode +AT_CHECK([ip a | grep -q ovnvrf100], [1]) +AT_CHECK([ip a | grep -q ovnbr100], [0]) +AT_CHECK([ip a | grep -q ovnvxlan100], [0]) +AT_CHECK([ip a | grep -q ovnlo100], [0]) + +dstport=$((60000 + $vni)) +AT_CHECK([ip -d link show ovnvxlan$vni | grep -oP ' id \K[[0-9]]+'], [0], [dnl +100 +], [ignore]) + +AT_CHECK([ip -d link show ovnvxlan$vni | grep -oP 'dstport \K[[0-9]]+'],[0], [dnl +60100 +], ignore) + +AT_CHECK([ip -d link show ovnvxlan$vni | grep -q 'local 169.0.0.1'], [0], [ignore], [ignore]) + +AT_CHECK([ip -d link show ovnvxlan$vni | grep -q 'nolearning'], [0], [ignore], [ignore]) + +AT_CHECK([ip link show ovnvxlan$vni | grep -oP 'master \K\S+'],[0], [dnl +ovnbr100 +]) +AT_CHECK([ip link show ovnlo$vni | grep -oP 'master \K\S+'],[0], [dnl +ovnbr100 +]) + + +AS_BOX([Bridge MAC via dynamic-routing-evpn-lrp]) +# Add LRP connected to ls-evpn; its MAC/IP will be applied to the bridge. +check ovn-nbctl \ + -- lr-add R-evpn \ + -- lrp-add R-evpn rp-evpn 00:00:00:aa:bb:01 192.168.100.1/24 \ + -- lsp-add ls-evpn ls-evpn-rp \ + -- set Logical_Switch_Port ls-evpn-rp type=router \ + options:router-port=rp-evpn \ + -- lsp-set-addresses ls-evpn-rp router \ + -- set Logical_Switch ls-evpn \ + other_config:dynamic-routing-maintain-evpn-lrp=rp-evpn \ + other_config:dynamic-routing-redistribute=fdb,ip +check ovn-nbctl --wait=hv sync + +# check vrf exists since it's fdb + ip mode +AT_CHECK([ip a | grep -q ovnvrf100], [0]) +AT_CHECK([ip a | grep -q ovnbr100], [0]) +AT_CHECK([ip a | grep -q ovnvxlan100], [0]) +AT_CHECK([ip a | grep -q ovnlo100], [0]) + +AT_CHECK([ip link show ovnlo$vni | grep -oP 'master \K\S+'], [0], [dnl +ovnbr100 +]) +AT_CHECK([ip link show ovnvxlan$vni | grep -oP 'master \K\S+'], [0], [dnl +ovnbr100 +]) +AT_CHECK([ip link show ovnbr$vni | grep -oP 'master \K\S+'], [0], [dnl +ovnvrf100 +]) + +AT_CHECK([ip link show ovnbr$vni | grep -oP 'link/ether \K\S+'], [0], [dnl +00:00:00:aa:bb:01 +]) + +AS_BOX([Bridge MAC address changes]) +check ovn-nbctl set Logical_Router_Port rp-evpn \ + mac='"00:00:00:00:00:08"' +check ovn-nbctl --wait=hv sync + +AT_CHECK([ip link show ovnbr$vni | grep -oP 'link/ether \K\S+'],[0], [dnl +00:00:00:00:00:08 +]) + +AS_BOX([Redistribute mode change fdb,ip -> fdb: VRF destroyed, lo kept]) +check ovn-nbctl set Logical_Switch ls-evpn \ + other_config:dynamic-routing-redistribute=fdb +check ovn-nbctl --wait=hv sync + +# check vrf exists since it's fdb + ip mode +AT_CHECK([ip a | grep -q ovnvrf100], [1]) +AT_CHECK([ip a | grep -q ovnbr100], [0]) +AT_CHECK([ip a | grep -q ovnvxlan100], [0]) +AT_CHECK([ip a | grep -q ovnlo100], [0]) + +AT_CHECK([ip link show ovnlo$vni | grep -oP 'master \K\S+'], [0], [dnl +ovnbr100 +]) +AT_CHECK([ip link show ovnvxlan$vni | grep -oP 'master \K\S+'], [0], [dnl +ovnbr100 +]) + +AS_BOX([Redistribute mode change fdb -> ip: lo destroyed, VRF recreated]) +check ovn-nbctl set Logical_Switch ls-evpn \ + other_config:dynamic-routing-redistribute=ip +check ovn-nbctl --wait=hv sync + +AT_CHECK([ip a | grep -q ovnvrf100], [0]) +AT_CHECK([ip a | grep -q ovnbr100], [0]) +AT_CHECK([ip a | grep -q ovnvxlan100], [0]) +AT_CHECK([ip a | grep -q ovnlo100], [1]) +AT_CHECK([ip link show ovnvxlan$vni | grep -oP 'master \K\S+'],[0], [dnl +ovnbr100 +]) +AT_CHECK([ip link show ovnbr$vni | grep -oP 'master \K\S+'],[0], [dnl +ovnvrf100 +]) + +AS_BOX([Redistribute mode change ip -> fdb,ip: lo and VRF both present again]) +check ovn-nbctl set Logical_Switch ls-evpn \ + other_config:dynamic-routing-redistribute=fdb,ip +check ovn-nbctl --wait=hv sync + +AT_CHECK([ip a | grep -q ovnvrf100], [0]) +AT_CHECK([ip a | grep -q ovnbr100], [0]) +AT_CHECK([ip a | grep -q ovnvxlan100], [0]) +AT_CHECK([ip a | grep -q ovnlo100], [0]) + +AT_CHECK([ip link show ovnlo$vni | grep -oP 'master \K\S+'], [0], [dnl +ovnbr100 +]) +AT_CHECK([ip link show ovnvxlan$vni | grep -oP 'master \K\S+'], [0], [dnl +ovnbr100 +]) +AT_CHECK([ip link show ovnbr$vni | grep -oP 'master \K\S+'],[0], [dnl +ovnvrf100 +]) + +AS_BOX([Remove maintain-evpn - full stack cleanup]) +check ovn-nbctl \ + -- remove Logical_Switch ls-evpn other_config dynamic-routing-maintain-evpn +check ovn-nbctl --wait=hv sync + +AT_CHECK([ip a | grep -q ovnvrf100], [1]) +AT_CHECK([ip a | grep -q ovnbr100], [1]) +AT_CHECK([ip a | grep -q ovnvxlan100], [1]) +AT_CHECK([ip a | grep -q ovnlo100], [1]) + +OVN_CLEANUP_CONTROLLER([hv1]) +OVN_CLEANUP_NORTHD +as +OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d +/connection dropped.*/d"]) +AT_CLEANUP +]) -- 2.48.1 _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
