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

Reply via email to