From patchwork Thu Jul 25 14:00:09 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Frode Nordahl X-Patchwork-Id: 1964789 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=140.211.166.137; helo=smtp4.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org) Received: from smtp4.osuosl.org (smtp4.osuosl.org [140.211.166.137]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4WVCG640g1z1yXx for ; Fri, 26 Jul 2024 00:00:46 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp4.osuosl.org (Postfix) with ESMTP id CB18440975; Thu, 25 Jul 2024 14:00:44 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp4.osuosl.org ([127.0.0.1]) by localhost (smtp4.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id BOrjvJYi8itb; Thu, 25 Jul 2024 14:00:38 +0000 (UTC) X-Comment: SPF check N/A for local connections - client-ip=2605:bc80:3010:104::8cd3:938; helo=lists.linuxfoundation.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver= DKIM-Filter: OpenDKIM Filter v2.11.0 smtp4.osuosl.org 38D834099F Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [IPv6:2605:bc80:3010:104::8cd3:938]) by smtp4.osuosl.org (Postfix) with ESMTPS id 38D834099F; Thu, 25 Jul 2024 14:00:34 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id A16F8C0033; Thu, 25 Jul 2024 14:00:33 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp4.osuosl.org (smtp4.osuosl.org [IPv6:2605:bc80:3010::137]) by lists.linuxfoundation.org (Postfix) with ESMTP id 225C8C0926 for ; Thu, 25 Jul 2024 14:00:31 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp4.osuosl.org (Postfix) with ESMTP id 53B6340994 for ; Thu, 25 Jul 2024 14:00:29 +0000 (UTC) X-Virus-Scanned: amavis at osuosl.org Received: from smtp4.osuosl.org ([127.0.0.1]) by localhost (smtp4.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP id MnQb9hbM0qCW for ; Thu, 25 Jul 2024 14:00:26 +0000 (UTC) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=209.85.208.171; helo=mail-lj1-f171.google.com; envelope-from=frode.nordahl@gmail.com; receiver= DMARC-Filter: OpenDMARC Filter v1.4.2 smtp4.osuosl.org 57C2240981 Authentication-Results: smtp4.osuosl.org; dmarc=fail (p=none dis=none) header.from=ubuntu.com DKIM-Filter: OpenDKIM Filter v2.11.0 smtp4.osuosl.org 57C2240981 Received: from mail-lj1-f171.google.com (mail-lj1-f171.google.com [209.85.208.171]) by smtp4.osuosl.org (Postfix) with ESMTPS id 57C2240981 for ; Thu, 25 Jul 2024 14:00:25 +0000 (UTC) Received: by mail-lj1-f171.google.com with SMTP id 38308e7fff4ca-2ef2cb7d562so2297041fa.3 for ; Thu, 25 Jul 2024 07:00:25 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1721916022; x=1722520822; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=Ik5W0sXJX5hbCSbIN7X3FRPIoy0DNtNYK9mDAiu4M1c=; b=wI1DeGfeoVJLnUP8lYibhwo5Pswb4sinq0iqzdRWmGiBnRx6UgpZ6cQcfkzFWu21wk /d9POkd50R8L/K6uRPicjrHmhtNBehl+8LBRH8WpJSbPgsBIpcKCqr+0h3E60KHxMDj4 h4eZQBoAd3h+ZaT66XKJRPCLM2lwzWkCTGf3kphplvZlr3VffOvcXw0O4SQl6iMpV/bB H+5MVdLvxRmVJCxQ8MhEAhpapXHuSzu+a1/TEnxMUj1TaQg5WgRUfIz4IezEuFzRKHkj tHlaRx5LzwQYhhysnRODuR26m50rvBd9XCjIU3KAz4+juG8JHJN0b7XHfnik9svzuNHH PZ/w== X-Gm-Message-State: AOJu0YysxQ9h0L/sBnuZiuCpJWvFGsI4NtDnmGROpxCmvFF0uB1gAPS0 eEW6vGUCxckI6+qebNCapVJKfvxRIqul4B04dL/Vx4FPVZm2UElGAJ2HZg== X-Google-Smtp-Source: AGHT+IHL1NCFYcfUJmysmaxwrpGq3Ju8J0PyxEAyC6uPMGHrQk9v0E2Aw6OO9zqhZ2CytY02TJGMqg== X-Received: by 2002:a2e:93c3:0:b0:2ef:284e:1d07 with SMTP id 38308e7fff4ca-2f03db7df5amr16108611fa.13.1721916021928; Thu, 25 Jul 2024 07:00:21 -0700 (PDT) Received: from localhost.localdomain ([2001:4643:d087:0:bd05:8094:92b2:c0a]) by smtp.gmail.com with ESMTPSA id 38308e7fff4ca-2f03cf0e36csm2027211fa.17.2024.07.25.07.00.20 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 25 Jul 2024 07:00:20 -0700 (PDT) From: Frode Nordahl To: dev@openvswitch.org Date: Thu, 25 Jul 2024 16:00:09 +0200 Message-ID: <20240725140009.413791-7-fnordahl@ubuntu.com> X-Mailer: git-send-email 2.45.2 In-Reply-To: <20240725140009.413791-1-fnordahl@ubuntu.com> References: <20240719020943.380924-1-fnordahl@ubuntu.com> <20240725140009.413791-1-fnordahl@ubuntu.com> MIME-Version: 1.0 Subject: [ovs-dev] [PATCH ovn v3 7/7] controller: Introduce route-exchange module. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" Introduce route-exchange module that depending on Logical Router Port options maintains a VRF in the system for redistribution of host routes to NAT addresses and LB VIPs attached to local gateway router datapaths. The route-exchange module requires input from both runtime_data and lb_data engine nodes. Consequently it needs its own I-P engine node. TODO: * E2E test together with the bgp-mirror patch. * E2E docs and NEWS items. Signed-off-by: Frode Nordahl --- controller/automake.mk | 9 +- controller/ovn-controller.c | 193 ++++++++++++++++ controller/route-exchange-stub.c | 44 ++++ controller/route-exchange.c | 274 ++++++++++++++++++++++ controller/route-exchange.h | 45 ++++ tests/system-ovn.at | 382 +++++++++++++++++++++++++++++++ 6 files changed, 945 insertions(+), 2 deletions(-) create mode 100644 controller/route-exchange-stub.c create mode 100644 controller/route-exchange.c create mode 100644 controller/route-exchange.h diff --git a/controller/automake.mk b/controller/automake.mk index 006e884dc..3e91e97e6 100644 --- a/controller/automake.mk +++ b/controller/automake.mk @@ -49,13 +49,18 @@ controller_ovn_controller_SOURCES = \ controller/statctrl.h \ controller/statctrl.c \ controller/ct-zone.h \ - controller/ct-zone.c + controller/ct-zone.c \ + controller/route-exchange.h if HAVE_NETLINK controller_ovn_controller_SOURCES += \ controller/route-exchange-netlink.h \ controller/route-exchange-netlink-private.h \ - controller/route-exchange-netlink.c + controller/route-exchange-netlink.c \ + controller/route-exchange.c +else +controller_ovn_controller_SOURCES += \ + controller/route-exchange-stub.c endif controller_ovn_controller_LDADD = lib/libovn.la $(OVS_LIBDIR)/libopenvswitch.la diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c index 805d29c81..7bc90da31 100644 --- a/controller/ovn-controller.c +++ b/controller/ovn-controller.c @@ -87,6 +87,7 @@ #include "statctrl.h" #include "lib/dns-resolve.h" #include "ct-zone.h" +#include "route-exchange.h" VLOG_DEFINE_THIS_MODULE(main); @@ -4576,6 +4577,14 @@ controller_output_mac_cache_handler(struct engine_node *node, return true; } +static bool +controller_output_route_exchange_handler(struct engine_node *node, + void *data OVS_UNUSED) +{ + engine_set_node_state(node, EN_UPDATED); + return true; +} + /* Handles sbrec_chassis changes. * If a new chassis is added or removed return false, so that * flows are recomputed. For any updates, there is no need for @@ -4599,6 +4608,174 @@ pflow_lflow_output_sb_chassis_handler(struct engine_node *node, return true; } +struct ed_type_route_exchange { + /* Contains struct tracked_datapath entries for local datapaths subject to + * route exchange. */ + struct hmap tracked_re_datapaths; +}; + +static void +en_route_exchange_run(struct engine_node *node, void *data) +{ + struct ed_type_route_exchange *re_data = data; + const struct ovsrec_open_vswitch_table *ovs_table = + EN_OVSDB_GET(engine_get_input("OVS_open_vswitch", node)); + const char *chassis_id = get_ovs_chassis_id(ovs_table); + ovs_assert(chassis_id); + + struct ovsdb_idl_index *sbrec_chassis_by_name = + engine_ovsdb_node_get_index( + engine_get_input("SB_chassis", node), + "name"); + const struct sbrec_chassis *chassis + = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id); + ovs_assert(chassis); + + struct ovsdb_idl_index *sbrec_port_binding_by_name = + engine_ovsdb_node_get_index( + engine_get_input("SB_port_binding", node), + "name"); + struct ed_type_runtime_data *rt_data = + engine_get_input_data("runtime_data", node); + + const struct sbrec_load_balancer_table *lb_table = + EN_OVSDB_GET(engine_get_input("SB_load_balancer", node)); + struct ed_type_lb_data *lb_data = + engine_get_input_data("lb_data", node); + + struct route_exchange_ctx_in r_ctx_in = { + .sbrec_port_binding_by_name = sbrec_port_binding_by_name, + .lb_table = lb_table, + .chassis_rec = chassis, + .active_tunnels = &rt_data->active_tunnels, + .local_datapaths = &rt_data->local_datapaths, + .local_lbs = &lb_data->local_lbs, + }; + + struct route_exchange_ctx_out r_ctx_out = { + .tracked_re_datapaths = &re_data->tracked_re_datapaths, + }; + + + route_exchange_run(&r_ctx_in, &r_ctx_out); + + engine_set_node_state(node, EN_UPDATED); +} + + +static void * +en_route_exchange_init(struct engine_node *node OVS_UNUSED, + struct engine_arg *arg OVS_UNUSED) +{ + struct ed_type_route_exchange *data = xzalloc(sizeof *data); + + hmap_init(&data->tracked_re_datapaths); + + return data; +} + +static void +en_route_exchange_cleanup(void *data) +{ + struct ed_type_route_exchange *re_data = data; + + tracked_datapaths_destroy(&re_data->tracked_re_datapaths); +} + +static bool +route_exchange_runtime_data_handler(struct engine_node *node, void *data) +{ + struct ed_type_route_exchange *re_data = data; + struct ed_type_runtime_data *rt_data = + engine_get_input_data("runtime_data", node); + + if (!rt_data->tracked) { + return false; + } + + struct tracked_datapath *t_dp; + HMAP_FOR_EACH (t_dp, node, &rt_data->tracked_dp_bindings) { + struct tracked_datapath *re_t_dp = + tracked_datapath_find(&re_data->tracked_re_datapaths, t_dp->dp); + + if (re_t_dp) { + /* Until we get I-P support for route exchange we need to request + * recompute. */ + return false; + } + + struct shash_node *shash_node; + SHASH_FOR_EACH (shash_node, &t_dp->lports) { + struct tracked_lport *lport = shash_node->data; + if (route_exchange_relevant_port(lport->pb)) { + /* Until we get I-P support for route exchange we need to + * request recompute. */ + return false; + } + } + } + + return true; +} + +static bool +route_exchange_lb_data_handler(struct engine_node *node, + void *data) +{ + struct ed_type_route_exchange *re_data = data; + struct ed_type_runtime_data *rt_data = + engine_get_input_data("runtime_data", node); + struct ed_type_lb_data *lb_data = + engine_get_input_data("lb_data", node); + const struct sbrec_load_balancer_table *lb_table = + EN_OVSDB_GET(engine_get_input("SB_load_balancer", node)); + + if (!lb_data->change_tracked) { + return false; + } + + if (!rt_data->tracked) { + return false; + } + + if (hmap_is_empty(&re_data->tracked_re_datapaths)) { + return true; + } + + struct hmap *tracked_dp_bindings = &rt_data->tracked_dp_bindings; + if (hmap_is_empty(tracked_dp_bindings)) { + return true; + } + + struct hmap *lbs = NULL; + + struct tracked_datapath *t_dp; + HMAP_FOR_EACH (t_dp, node, tracked_dp_bindings) { + struct tracked_datapath *re_t_dp = + tracked_datapath_find(&re_data->tracked_re_datapaths, t_dp->dp); + + if (!re_t_dp) { + continue; + } + + if (!lbs) { + lbs = load_balancers_by_dp_init(&rt_data->local_datapaths, + lb_table); + } + + struct load_balancers_by_dp *lbs_by_dp = + load_balancers_by_dp_find(lbs, re_t_dp->dp); + if (lbs_by_dp) { + /* Until we get I-P support for route exchange we need to + * request recompute. */ + load_balancers_by_dp_cleanup(lbs); + return false; + } + } + load_balancers_by_dp_cleanup(lbs); + return true; +} + /* Returns false if the northd internal version stored in SB_Global * and ovn-controller internal version don't match. */ @@ -4885,6 +5062,7 @@ main(int argc, char *argv[]) ENGINE_NODE(if_status_mgr, "if_status_mgr"); ENGINE_NODE_WITH_CLEAR_TRACK_DATA(lb_data, "lb_data"); ENGINE_NODE(mac_cache, "mac_cache"); + ENGINE_NODE(route_exchange, "route_exchange"); #define SB_NODE(NAME, NAME_STR) ENGINE_NODE_SB(NAME, NAME_STR); SB_NODES @@ -4907,6 +5085,17 @@ main(int argc, char *argv[]) engine_add_input(&en_lb_data, &en_runtime_data, lb_data_runtime_data_handler); + engine_add_input(&en_route_exchange, &en_ovs_open_vswitch, NULL); + engine_add_input(&en_route_exchange, &en_sb_chassis, NULL); + engine_add_input(&en_route_exchange, &en_sb_port_binding, + engine_noop_handler); + engine_add_input(&en_route_exchange, &en_runtime_data, + route_exchange_runtime_data_handler); + engine_add_input(&en_route_exchange, &en_sb_load_balancer, + engine_noop_handler); + engine_add_input(&en_route_exchange, &en_lb_data, + route_exchange_lb_data_handler); + engine_add_input(&en_addr_sets, &en_sb_address_set, addr_sets_sb_address_set_handler); engine_add_input(&en_port_groups, &en_sb_port_group, @@ -5081,6 +5270,8 @@ main(int argc, char *argv[]) controller_output_pflow_output_handler); engine_add_input(&en_controller_output, &en_mac_cache, controller_output_mac_cache_handler); + engine_add_input(&en_controller_output, &en_route_exchange, + controller_output_route_exchange_handler); struct engine_arg engine_arg = { .sb_idl = ovnsb_idl_loop.idl, @@ -5770,6 +5961,7 @@ loop_done: ovsdb_idl_loop_commit_and_wait(&ovs_idl_loop); poll_block(); } + route_exchange_cleanup(); } free(ovn_version); @@ -5799,6 +5991,7 @@ loop_done: service_stop(); ovsrcu_exit(); dns_resolve_destroy(); + route_exchange_destroy(); exit(retval); } diff --git a/controller/route-exchange-stub.c b/controller/route-exchange-stub.c new file mode 100644 index 000000000..839cbc077 --- /dev/null +++ b/controller/route-exchange-stub.c @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024 Canonical + * + * 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 + +#include + +#include "openvswitch/compiler.h" +#include "route-exchange.h" + +bool +route_exchange_relevant_port(const struct sbrec_port_binding *pb OVS_UNUSED) +{ + return false; +} + +void +route_exchange_run(struct route_exchange_ctx_in *r_ctx_in OVS_UNUSED, + struct route_exchange_ctx_out *r_ctx_out OVS_UNUSED) +{ +} + +void +route_exchange_cleanup(void) +{ +} + +void +route_exchange_destroy(void) +{ +} diff --git a/controller/route-exchange.c b/controller/route-exchange.c new file mode 100644 index 000000000..d3b8f0480 --- /dev/null +++ b/controller/route-exchange.c @@ -0,0 +1,274 @@ +/* + * Copyright (c) 2024 Canonical + * + * 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 + +#include +#include + +#include "openvswitch/vlog.h" + +#include "lib/ovn-sb-idl.h" + +#include "binding.h" +#include "ha-chassis.h" +#include "lb.h" +#include "local_data.h" +#include "route-exchange.h" +#include "route-exchange-netlink.h" + + +VLOG_DEFINE_THIS_MODULE(route_exchange); +static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20); + +/* While the linux kernel can handle 2^32 routing tables, only so many can fit + * in the corresponding VRF interface name. */ +#define MAX_TABLE_ID 1000000000 + +static struct sset _maintained_vrfs = SSET_INITIALIZER(&_maintained_vrfs); + +bool +route_exchange_relevant_port(const struct sbrec_port_binding *pb) { + return (pb && pb->type && !strcmp(pb->type, "l3gateway") && + (smap_get_bool(&pb->options, "redistribute-lb-vips", false) || + smap_get_bool(&pb->options, "redistribute-nat", false))); +} + +static void +extract_nat_addresses(const struct sbrec_port_binding *pb, + struct route_exchange_ctx_in *r_ctx_in, + uint32_t table_id, struct hmap *host_routes) +{ + if (!pb || !pb->n_nat_addresses) { + return; + } + VLOG_DBG("extract_nat_addresses: considering lport %s", pb->logical_port); + + for (size_t i = 0; i < pb->n_nat_addresses; i++) { + struct lport_addresses *laddrs = xzalloc(sizeof *laddrs); + char *lport = NULL; + + if (!extract_addresses_with_port( + pb->nat_addresses[i], laddrs, &lport)) { + VLOG_DBG("extract_nat_addresses: no addresses"); + goto cleanup; + } + if (lport) { + const struct sbrec_port_binding *lport_pb = lport_lookup_by_name( + r_ctx_in->sbrec_port_binding_by_name, lport); + if (!lport_pb || !lport_pb->chassis) { + VLOG_DBG("extract_nat_addresses: cannot find lport %s", + lport); + goto cleanup; + } + enum en_lport_type lport_pb_type = get_lport_type(lport_pb); + if (((lport_pb_type == LP_VIF || + lport_pb_type == LP_CHASSISREDIRECT) && + lport_pb->chassis != r_ctx_in->chassis_rec) || + !ha_chassis_group_is_active(lport_pb->ha_chassis_group, + r_ctx_in->active_tunnels, + r_ctx_in->chassis_rec)) { + VLOG_DBG("extract_nat_addresses: ignoring non-local lport %s", + lport); + goto cleanup; + } + } + for (size_t j = 0; j < laddrs->n_ipv4_addrs; j++) { + struct in6_addr addr; + in6_addr_set_mapped_ipv4(&addr, laddrs->ipv4_addrs[j].addr); + host_route_insert(host_routes, table_id, &addr); + } + for (size_t j = 0; j < laddrs->n_ipv6_addrs; j++) { + host_route_insert(host_routes, table_id, + &laddrs->ipv6_addrs[j].addr); + } + +cleanup: + destroy_lport_addresses(laddrs); + free(laddrs); + if (lport) { + free(lport); + } + } +} + +static void +extract_lb_vips(const struct sbrec_datapath_binding *dpb, + struct hmap *lbs_by_dp_hmap, + const struct route_exchange_ctx_in *r_ctx_in, + uint32_t table_id, struct hmap *host_routes) +{ + struct load_balancers_by_dp *lbs_by_dp + = load_balancers_by_dp_find(lbs_by_dp_hmap, dpb); + if (!lbs_by_dp) { + return; + } + + for (size_t i = 0; i < lbs_by_dp->n_dp_lbs; i++) { + const struct sbrec_load_balancer *sbrec_lb + = lbs_by_dp->dp_lbs[i]; + + if (!sbrec_lb) { + return; + } + + struct ovn_controller_lb *lb + = ovn_controller_lb_find(r_ctx_in->local_lbs, + &sbrec_lb->header_.uuid); + + if (!lb || !lb->slb) { + return; + } + + VLOG_DBG("considering lb for route leaking: %s", lb->slb->name); + for (i = 0; i < lb->n_vips; i++) { + VLOG_DBG("considering lb for route leaking: %s vip_str=%s", + lb->slb->name, lb->vips[i].vip_str); + host_route_insert(host_routes, table_id, &lb->vips[i].vip); + } + } +} + +void +route_exchange_run(struct route_exchange_ctx_in *r_ctx_in, + struct route_exchange_ctx_out *r_ctx_out) +{ + struct sset old_maintained_vrfs = SSET_INITIALIZER(&old_maintained_vrfs); + sset_swap(&_maintained_vrfs, &old_maintained_vrfs); + struct hmap *lbs_by_dp_hmap + = load_balancers_by_dp_init(r_ctx_in->local_datapaths, + r_ctx_in->lb_table); + + /* Extract all NAT- and LB VIP-addresses associated with lports resident on + * the current chassis to allow full sync of leaked routing tables. */ + const struct local_datapath *ld; + HMAP_FOR_EACH (ld, hmap_node, r_ctx_in->local_datapaths) { + if (!ld->n_peer_ports || ld->is_switch) { + continue; + } + + bool maintain_vrf = false; + bool lbs_sync = false; + struct hmap local_host_routes_for_current_dp + = HMAP_INITIALIZER(&local_host_routes_for_current_dp); + + /* This is a LR datapath, find LRPs with route exchange options. */ + for (size_t i = 0; i < ld->n_peer_ports; i++) { + const struct sbrec_port_binding *local_peer + = ld->peer_ports[i].local; + if (!local_peer || !route_exchange_relevant_port(local_peer)) { + continue; + } + + maintain_vrf |= smap_get_bool(&local_peer->options, + "maintain-vrf", false); + lbs_sync |= smap_get_bool(&local_peer->options, + "redistribute-lb-vips", + false); + if (smap_get_bool(&local_peer->options, + "redistribute-nat", + false)) { + extract_nat_addresses(local_peer, r_ctx_in, + ld->datapath->tunnel_key, + &local_host_routes_for_current_dp); + } + } + + if (lbs_sync) { + extract_lb_vips(ld->datapath, lbs_by_dp_hmap, r_ctx_in, + ld->datapath->tunnel_key, + &local_host_routes_for_current_dp); + } + + /* While tunnel_key would most likely never be negative, the compiler + * has opinions if we don't check before using it in snprintf below. */ + if (ld->datapath->tunnel_key < 0 || + ld->datapath->tunnel_key > MAX_TABLE_ID) { + VLOG_WARN_RL(&rl, + "skip route sync for datapath "UUID_FMT", " + "tunnel_key %"PRIi64" would make VRF interface name " + "overflow.", + UUID_ARGS(&ld->datapath->header_.uuid), + ld->datapath->tunnel_key); + goto out; + } + char vrf_name[IFNAMSIZ + 1]; + snprintf(vrf_name, sizeof vrf_name, "ovnvrf%"PRIi64, + ld->datapath->tunnel_key); + + if (maintain_vrf) { + int error = re_nl_create_vrf(vrf_name, ld->datapath->tunnel_key); + if (error && error != EEXIST) { + VLOG_WARN_RL(&rl, + "Unable to create VRF %s for datapath "UUID_FMT + ": %s.", + vrf_name, UUID_ARGS(&ld->datapath->header_.uuid), + ovs_strerror(error)); + goto out; + } + sset_add(&_maintained_vrfs, vrf_name); + } + if (!hmap_is_empty(&local_host_routes_for_current_dp)) { + tracked_datapath_add(ld->datapath, TRACKED_RESOURCE_NEW, + r_ctx_out->tracked_re_datapaths); + } + re_nl_sync_routes(ld->datapath->tunnel_key, vrf_name, + &local_host_routes_for_current_dp); + +out: + host_routes_destroy(&local_host_routes_for_current_dp); + } + + /* Remove VRFs previously maintained by us not found in the above loop. */ + const char *vrf_name; + SSET_FOR_EACH_SAFE (vrf_name, &old_maintained_vrfs) { + if (!sset_find(&_maintained_vrfs, vrf_name)) { + re_nl_delete_vrf(vrf_name); + } + sset_delete(&old_maintained_vrfs, SSET_NODE_FROM_NAME(vrf_name)); + } + sset_destroy(&old_maintained_vrfs); + + load_balancers_by_dp_cleanup(lbs_by_dp_hmap); +} + +static void +route_exchange_cleanup__(bool cleanup) +{ + const char *vrf_name; + SSET_FOR_EACH_SAFE (vrf_name, &_maintained_vrfs) { + if (cleanup) { + re_nl_delete_vrf(vrf_name); + } else { + sset_delete(&_maintained_vrfs, SSET_NODE_FROM_NAME(vrf_name)); + } + } + if (!cleanup) { + sset_destroy(&_maintained_vrfs); + } +} + +void +route_exchange_cleanup(void) +{ + route_exchange_cleanup__(true); +} + +void +route_exchange_destroy(void) +{ + route_exchange_cleanup__(false); +} diff --git a/controller/route-exchange.h b/controller/route-exchange.h new file mode 100644 index 000000000..de554f9b1 --- /dev/null +++ b/controller/route-exchange.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024 Canonical + * + * 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 ROUTE_EXCHANGE_H +#define ROUTE_EXCHANGE_H 1 + +struct hmap; +struct ovsdb_idl_index; +struct sbrec_chassis; +struct sbrec_port_binding; +struct sset; + +struct route_exchange_ctx_in { + struct ovsdb_idl_index *sbrec_port_binding_by_name; + const struct sbrec_load_balancer_table *lb_table; + const struct sbrec_chassis *chassis_rec; + const struct sset *active_tunnels; + struct hmap *local_datapaths; + struct hmap *local_lbs; +}; + +struct route_exchange_ctx_out { + struct hmap *tracked_re_datapaths; +}; + +bool route_exchange_relevant_port(const struct sbrec_port_binding *pb); +void route_exchange_run(struct route_exchange_ctx_in *, + struct route_exchange_ctx_out *); +void route_exchange_cleanup(void); +void route_exchange_destroy(void); + +#endif /* ROUTE_EXCHANGE_H */ diff --git a/tests/system-ovn.at b/tests/system-ovn.at index ddb3d14e9..2c410d555 100644 --- a/tests/system-ovn.at +++ b/tests/system-ovn.at @@ -13022,3 +13022,385 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d /connection dropped.*/d"]) AT_CLEANUP ]) + +OVN_FOR_EACH_NORTHD([ +AT_SETUP([route-exchange for LB VIPs with gateway router IPv4]) +AT_KEYWORDS([route-exchange]) + +CHECK_VRF() +CHECK_CONNTRACK() +CHECK_CONNTRACK_NAT() +ovn_start +OVS_TRAFFIC_VSWITCHD_START() +ADD_BR([br-int]) +ADD_BR([br-ext], [set Bridge br-ext fail-mode=standalone]) + +# Set external-ids in br-int needed for ovn-controller +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 bridge br-int fail-mode=secure other-config:disable-in-band=true + +# Start ovn-controller +start_daemon ovn-controller + +ovn-appctl vlog/set route_exchange +check ovn-nbctl -- lr-add R1 \ + -- set Logical_Router R1 options:requested-tnl-key=1000 + +check ovn-nbctl ls-add sw0 +check ovn-nbctl ls-add public + +check ovn-nbctl --wait=hv sync + +AT_CHECK([ip link | grep -q ovnvrf1000:.*UP], [1]) + +check ovn-nbctl lrp-add R1 rp-sw0 00:00:01:01:02:03 192.168.1.1/24 +check ovn-nbctl -- lrp-add R1 rp-public 00:00:02:01:02:03 172.16.1.1/24 \ + -- lrp-set-options rp-public \ + maintain-vrf=true \ + redistribute-lb-vips=true + +check ovn-nbctl set logical_router R1 options:chassis=hv1 + +check ovn-nbctl lsp-add sw0 sw0-rp -- set Logical_Switch_Port sw0-rp \ + type=router options:router-port=rp-sw0 \ + -- lsp-set-addresses sw0-rp router + +check ovn-nbctl lsp-add public public-rp -- set Logical_Switch_Port public-rp \ + type=router options:router-port=rp-public \ + -- lsp-set-addresses public-rp router + +check ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=phynet:br-ext + +check ovn-nbctl lsp-add public public1 \ + -- lsp-set-addresses public1 unknown \ + -- lsp-set-type public1 localnet \ + -- lsp-set-options public1 network_name=phynet + +check ovn-nbctl --wait=hv sync + +AT_CHECK([test `ip route show table 1000 | wc -l` -eq 1], [1]) + +# Create a load balancer and associate to R1 +check ovn-nbctl lb-add lb1 172.16.1.150:80 172.16.1.100:80 +check ovn-nbctl lr-lb-add R1 lb1 + +check ovn-nbctl --wait=hv sync + +AT_CHECK([ip link | grep -q ovnvrf1000:.*UP]) +AT_CHECK([test `ip route show table 1000 | wc -l` -eq 1]) +AT_CHECK([ip route show table 1000 | grep -q 172.16.1.150]) + + +OVS_APP_EXIT_AND_WAIT([ovn-controller]) + +# Ensure system resources are cleaned up +AT_CHECK([ip link | grep -q ovnvrf1000:.*UP], [1]) +AT_CHECK([test `ip route show table 1000 | wc -l` -eq 1], [1]) + +as ovn-sb +OVS_APP_EXIT_AND_WAIT([ovsdb-server]) + +as ovn-nb +OVS_APP_EXIT_AND_WAIT([ovsdb-server]) + +as northd +OVS_APP_EXIT_AND_WAIT([ovn-northd]) + +as +OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d +/Failed to acquire.*/d +/connection dropped.*/d"]) +AT_CLEANUP +]) + +OVN_FOR_EACH_NORTHD([ +AT_SETUP([route-exchange for LB VIPs with gateway router IPv6]) +AT_KEYWORDS([route-exchange]) + +CHECK_VRF() +CHECK_CONNTRACK() +CHECK_CONNTRACK_NAT() +ovn_start +OVS_TRAFFIC_VSWITCHD_START() +ADD_BR([br-int]) +ADD_BR([br-ext], [set Bridge br-ext fail-mode=standalone]) + +# Set external-ids in br-int needed for ovn-controller +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 bridge br-int fail-mode=secure other-config:disable-in-band=true + +# Start ovn-controller +start_daemon ovn-controller + +ovn-appctl vlog/set route_exchange +check ovn-nbctl -- lr-add R1 \ + -- set Logical_Router R1 options:requested-tnl-key=1001 + +check ovn-nbctl ls-add sw0 +check ovn-nbctl ls-add public + +check ovn-nbctl --wait=hv sync + +AT_CHECK([ip link | grep -q ovnvrf1001:.*UP], [1]) + +check ovn-nbctl lrp-add R1 rp-sw0 00:00:01:01:02:03 2001:db8:100::1/64 +check ovn-nbctl -- lrp-add R1 rp-public 00:00:02:01:02:03 2001:db8:1001::1/64 \ + -- lrp-set-options rp-public \ + maintain-vrf=true \ + redistribute-lb-vips=true + +check ovn-nbctl set logical_router R1 options:chassis=hv1 + +check ovn-nbctl lsp-add sw0 sw0-rp -- set Logical_Switch_Port sw0-rp \ + type=router options:router-port=rp-sw0 \ + -- lsp-set-addresses sw0-rp router + +check ovn-nbctl lsp-add public public-rp -- set Logical_Switch_Port public-rp \ + type=router options:router-port=rp-public \ + -- lsp-set-addresses public-rp router + +check ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=phynet:br-ext + +check ovn-nbctl lsp-add public public1 \ + -- lsp-set-addresses public1 unknown \ + -- lsp-set-type public1 localnet \ + -- lsp-set-options public1 network_name=phynet + +check ovn-nbctl --wait=hv sync + +AT_CHECK([test `ip -6 route show table 1001 | wc -l` -eq 1], [1]) + +# Create a load balancer and associate to R1 +check ovn-nbctl lb-add lb1 [[2001:db8:1001::150]]:80 [[2001:db8:1001::100]]:80 +check ovn-nbctl lr-lb-add R1 lb1 + +check ovn-nbctl --wait=hv sync + +AT_CHECK([ip link | grep -q ovnvrf1001:.*UP]) +AT_CHECK([test `ip -6 route show table 1001 | wc -l` -eq 1]) +AT_CHECK([ip -6 route show table 1001 | grep -q 2001:db8:1001::150]) + + +OVS_APP_EXIT_AND_WAIT([ovn-controller]) + +# Ensure system resources are cleaned up +AT_CHECK([ip link | grep -q ovnvrf1001:.*UP], [1]) +AT_CHECK([test `ip -6 route show table 1001 | wc -l` -eq 1], [1]) + +as ovn-sb +OVS_APP_EXIT_AND_WAIT([ovsdb-server]) + +as ovn-nb +OVS_APP_EXIT_AND_WAIT([ovsdb-server]) + +as northd +OVS_APP_EXIT_AND_WAIT([ovn-northd]) + +as +OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d +/Failed to acquire.*/d +/connection dropped.*/d"]) +AT_CLEANUP +]) + +OVN_FOR_EACH_NORTHD([ +AT_SETUP([route-exchange for DNAT and DNAT_AND_SNAT with gateway router IPv4]) +AT_KEYWORDS([route-exchange]) + +CHECK_VRF() +CHECK_CONNTRACK() +CHECK_CONNTRACK_NAT() +ovn_start +OVS_TRAFFIC_VSWITCHD_START() +ADD_BR([br-int]) +ADD_BR([br-ext], [set Bridge br-ext fail-mode=standalone]) + +# Set external-ids in br-int needed for ovn-controller +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 bridge br-int fail-mode=secure other-config:disable-in-band=true + +# Start ovn-controller +start_daemon ovn-controller + +ovn-appctl vlog/set route_exchange +check ovn-nbctl -- lr-add R1 \ + -- set Logical_Router R1 options:requested-tnl-key=1002 + +check ovn-nbctl ls-add sw0 +check ovn-nbctl ls-add public + +check ovn-nbctl --wait=hv sync + +AT_CHECK([ip link | grep -q ovnvrf1002:.*UP], [1]) + +check ovn-nbctl lrp-add R1 rp-sw0 00:00:01:01:02:03 192.168.1.1/24 +check ovn-nbctl -- lrp-add R1 rp-public 00:00:02:01:02:03 172.16.1.1/24 \ + -- lrp-set-options rp-public \ + maintain-vrf=true \ + redistribute-nat=true + +check ovn-nbctl set logical_router R1 options:chassis=hv1 + +check ovn-nbctl lsp-add sw0 sw0-rp -- set Logical_Switch_Port sw0-rp \ + type=router options:router-port=rp-sw0 \ + -- lsp-set-addresses sw0-rp router + +check ovn-nbctl lsp-add public public-rp -- set Logical_Switch_Port public-rp \ + type=router options:router-port=rp-public \ + -- lsp-set-addresses public-rp router + +check ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=phynet:br-ext + +check ovn-nbctl lsp-add public public1 \ + -- lsp-set-addresses public1 unknown \ + -- lsp-set-type public1 localnet \ + -- lsp-set-options public1 network_name=phynet + +check ovn-nbctl --wait=hv sync + +AT_CHECK([test `ip route show table 1002 | wc -l` -eq 2], [1]) + +# Create dnat_and_snat, dnat rules in R1 +check ovn-nbctl lr-nat-add R1 dnat_and_snat 172.16.1.10 192.168.1.10 +check ovn-nbctl lr-nat-add R1 dnat 172.16.1.11 192.168.1.11 + +check ovn-nbctl --wait=hv sync + +AT_CHECK([ip link | grep -q ovnvrf1002:.*UP]) +AT_CHECK([test `ip route show table 1002 | wc -l` -eq 2]) +AT_CHECK([ip route show table 1002 | grep -q 172.16.1.10]) +AT_CHECK([ip route show table 1002 | grep -q 172.16.1.11]) + + +OVS_APP_EXIT_AND_WAIT([ovn-controller]) + +# Ensure system resources are cleaned up +AT_CHECK([ip link | grep -q ovnvrf1000:.*UP], [1]) +AT_CHECK([test `ip route show table 1000 | wc -l` -eq 1], [1]) + +as ovn-sb +OVS_APP_EXIT_AND_WAIT([ovsdb-server]) + +as ovn-nb +OVS_APP_EXIT_AND_WAIT([ovsdb-server]) + +as northd +OVS_APP_EXIT_AND_WAIT([ovn-northd]) + +as +OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d +/Failed to acquire.*/d +/connection dropped.*/d"]) +AT_CLEANUP +]) + +OVN_FOR_EACH_NORTHD([ +AT_SETUP([route-exchange for DNAT and DNAT_AND_SNAT with gateway router IPv6]) +AT_KEYWORDS([route-exchange]) + +CHECK_VRF() +CHECK_CONNTRACK() +CHECK_CONNTRACK_NAT() +ovn_start +OVS_TRAFFIC_VSWITCHD_START() +ADD_BR([br-int]) +ADD_BR([br-ext], [set Bridge br-ext fail-mode=standalone]) + +# Set external-ids in br-int needed for ovn-controller +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 bridge br-int fail-mode=secure other-config:disable-in-band=true + +# Start ovn-controller +start_daemon ovn-controller + +ovn-appctl vlog/set route_exchange +check ovn-nbctl -- lr-add R1 \ + -- set Logical_Router R1 options:requested-tnl-key=1003 + +check ovn-nbctl ls-add sw0 +check ovn-nbctl ls-add public + +check ovn-nbctl --wait=hv sync + +AT_CHECK([ip link | grep -q ovnvrf1003:.*UP], [1]) + +check ovn-nbctl lrp-add R1 rp-sw0 00:00:01:01:02:03 2001:db8:100::1/64 +check ovn-nbctl -- lrp-add R1 rp-public 00:00:02:01:02:03 2001:db8:1003::1/64 \ + -- lrp-set-options rp-public \ + maintain-vrf=true \ + redistribute-nat=true + +check ovn-nbctl set logical_router R1 options:chassis=hv1 + +check ovn-nbctl lsp-add sw0 sw0-rp -- set Logical_Switch_Port sw0-rp \ + type=router options:router-port=rp-sw0 \ + -- lsp-set-addresses sw0-rp router + +check ovn-nbctl lsp-add public public-rp -- set Logical_Switch_Port public-rp \ + type=router options:router-port=rp-public \ + -- lsp-set-addresses public-rp router + +check ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=phynet:br-ext + +check ovn-nbctl lsp-add public public1 \ + -- lsp-set-addresses public1 unknown \ + -- lsp-set-type public1 localnet \ + -- lsp-set-options public1 network_name=phynet + +check ovn-nbctl --wait=hv sync + +AT_CHECK([test `ip -6 route show table 1003 | wc -l` -eq 2], [1]) + +# Create dnat_and_snat, dnat rules in R1 +check ovn-nbctl lr-nat-add R1 \ + dnat_and_snat 2001:db8:1003::150 2001:db8:100::100 +check ovn-nbctl lr-nat-add R1 \ + dnat 2001:db8:1003::151 2001:db8:100::100 + +check ovn-nbctl --wait=hv sync + +ovn-nbctl list nat +ovn-sbctl list port-binding + +AT_CHECK([ip link | grep -q ovnvrf1003:.*UP]) +AT_CHECK([test `ip -6 route show table 1003 | wc -l` -eq 2]) +AT_CHECK([ip -6 route show table 1003 | grep -q 2001:db8:1003::150]) +AT_CHECK([ip -6 route show table 1003 | grep -q 2001:db8:1003::151]) + +OVS_APP_EXIT_AND_WAIT([ovn-controller]) + +# Ensure system resources are cleaned up +AT_CHECK([ip link | grep -q ovnvrf1003:.*UP], [1]) +AT_CHECK([test `ip -6 route show table 1003 | wc -l` -eq 2], [1]) + +as ovn-sb +OVS_APP_EXIT_AND_WAIT([ovsdb-server]) + +as ovn-nb +OVS_APP_EXIT_AND_WAIT([ovsdb-server]) + +as northd +OVS_APP_EXIT_AND_WAIT([ovn-northd]) + +as +OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d +/Failed to acquire.*/d +/connection dropped.*/d"]) +AT_CLEANUP +])