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. Signed-off-by: Frode Nordahl <[email protected]> --- controller/automake.mk | 4 +- controller/ovn-controller.c | 159 +++++++++++++++++++++++++ controller/route-exchange.c | 229 ++++++++++++++++++++++++++++++++++++ controller/route-exchange.h | 38 ++++++ 4 files changed, 429 insertions(+), 1 deletion(-) 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 70facdd72..a824aee28 100644 --- a/controller/automake.mk +++ b/controller/automake.mk @@ -52,7 +52,9 @@ controller_ovn_controller_SOURCES = \ controller/ct-zone.c \ controller/route-exchange-netlink.h \ controller/route-exchange-netlink-private.h \ - controller/route-exchange-netlink.c + controller/route-exchange-netlink.c \ + controller/route-exchange.h \ + controller/route-exchange.c controller_ovn_controller_LDADD = lib/libovn.la $(OVS_LIBDIR)/libopenvswitch.la man_MANS += controller/ovn-controller.8 diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c index f46edd22d..52deae439 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); @@ -4572,6 +4573,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 @@ -4595,6 +4604,142 @@ pflow_lflow_output_sb_chassis_handler(struct engine_node *node, return true; } +struct ed_type_route_exchange { +}; + +static void +en_route_exchange_run(struct engine_node *node, + void *data OVS_UNUSED) +{ + 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, + }; + + route_exchange_run(&r_ctx_in); + + 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) +{ + return NULL; +} + +static void +en_route_exchange_cleanup(void *data OVS_UNUSED) +{ +} + +static bool +route_exchange_runtime_data_handler(struct engine_node *node, + void *data OVS_UNUSED) +{ + struct ed_type_runtime_data *rt_data = + engine_get_input_data("runtime_data", node); + + if (!rt_data->tracked) { + return false; + } + + struct tracked_datapath *tdp; + HMAP_FOR_EACH (tdp, node, &rt_data->tracked_dp_bindings) { + struct shash_node *shash_node; + SHASH_FOR_EACH (shash_node, &tdp->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 OVS_UNUSED) +{ + 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; + } + + 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 *tdp; + HMAP_FOR_EACH (tdp, node, tracked_dp_bindings) { + if (tdp->tracked_type != TRACKED_RESOURCE_NEW) { + 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, tdp->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. */ @@ -4881,6 +5026,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 @@ -4903,6 +5049,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, @@ -5076,6 +5233,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, diff --git a/controller/route-exchange.c b/controller/route-exchange.c new file mode 100644 index 000000000..878e872ee --- /dev/null +++ b/controller/route-exchange.c @@ -0,0 +1,229 @@ +/* + * 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 <config.h> + +#include <net/if.h> + +#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 999999999 + +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) || + smap_get_bool(&pb->options, "maintain-vrf", 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; + } + } + /* XXX: nat_addresses can also contain the LRP IP without any + * association with a NB NAT record, for use by pinctrl ARP handling. + * + * We need to figure out a way to identify and ignore these */ + 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 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) { + continue; + } + + struct hmap local_host_routes_for_current_dp + = HMAP_INITIALIZER(&local_host_routes_for_current_dp); + uint32_t table_id = 0; + bool maintain_vrf = false; + if (ld->is_switch) { + /* The nat_addresses are stored in the LRP's LSP peer record. */ + for (size_t i = 0; i < ld->n_peer_ports; i++) { + const struct sbrec_port_binding *local_peer + = ld->peer_ports[i].local; + const struct sbrec_port_binding *remote_peer + = ld->peer_ports[i].remote; + if (!local_peer || !remote_peer) { + continue; + } + if (!route_exchange_relevant_port(remote_peer)) { + continue; + } + maintain_vrf |= smap_get_bool(&remote_peer->options, + "maintain-vrf", false); + table_id = remote_peer->datapath->tunnel_key; + extract_nat_addresses(local_peer, r_ctx_in, table_id, + &local_host_routes_for_current_dp); + } + } else { + /* Load Balancers are conveniently associated with the LR datapath. + */ + bool relevant = false; + 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) { + continue; + } + relevant |= route_exchange_relevant_port(local_peer); + maintain_vrf = smap_get_bool(&local_peer->options, + "maintain-vrf", false); + } + if (!relevant) { + goto out; + } + + table_id = ld->datapath->tunnel_key; + extract_lb_vips(ld->datapath, lbs_by_dp_hmap, r_ctx_in, + table_id, &local_host_routes_for_current_dp); + } + if (table_id > MAX_TABLE_ID) { + VLOG_WARN_RL(&rl, + "skip route sync for datapath "UUID_FMT", " + "tunnel_key %"PRIu32" would make VRF interface name " + "overflow.", + UUID_ARGS(&ld->datapath->header_.uuid), table_id); + goto out; + } + char vrf_name[IFNAMSIZ + 1]; + snprintf(vrf_name, sizeof vrf_name, "ovnvrf%"PRIu32, table_id); + + if (maintain_vrf) { + re_nl_create_vrf(vrf_name, table_id); + } + if (table_id) { + re_nl_sync_routes(table_id, vrf_name, + &local_host_routes_for_current_dp); + } + +out: + host_routes_destroy(&local_host_routes_for_current_dp); + } + load_balancers_by_dp_cleanup(lbs_by_dp_hmap); +} diff --git a/controller/route-exchange.h b/controller/route-exchange.h new file mode 100644 index 000000000..7798874be --- /dev/null +++ b/controller/route-exchange.h @@ -0,0 +1,38 @@ +/* + * 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; +}; + +bool route_exchange_relevant_port(const struct sbrec_port_binding *pb); +void route_exchange_run(struct route_exchange_ctx_in *); + +#endif /* ROUTE_EXCHANGE_H */ -- 2.45.2 _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
