Sometimes we want to use individual host routes instead of the connected
routes of LRPs.
This allows the network fabric to know which addresses are actually in
use and e.g. drop traffic to addresses that are not used anyway.

Acked-by: Dumitru Ceara <dce...@redhat.com>
Signed-off-by: Felix Huettner <felix.huettner@stackit.cloud>
---
v7->v8:
  * addressed review comments
v6->v7:
  * switch the setting to a dynamic-routing-redistribute option
  * support lrp<->lrp peer connection for host routes
  * addressed review comments
v5->v6:
  * addressed review comments
v4->v5: skipped
v3->v4:
  * fix a memory leak
v2->v3:
  * A lot of minor review comments.

 NEWS                              |   3 +
 northd/en-advertised-route-sync.c | 249 ++++++++++++++++++++++++++++--
 northd/en-advertised-route-sync.h |  11 ++
 northd/inc-proc-northd.c          |   4 +
 northd/northd.c                   |  37 ++---
 northd/northd.h                   |  24 ++-
 ovn-nb.xml                        | 133 ++++++++++++----
 tests/ovn-northd.at               | 127 ++++++++++++++-
 8 files changed, 521 insertions(+), 67 deletions(-)

diff --git a/NEWS b/NEWS
index ef625931f..6d36a7222 100644
--- a/NEWS
+++ b/NEWS
@@ -66,6 +66,9 @@ Post v24.09.0
        fabric. Routes entered into the "Learned_Route" table in the southbound
        database will be learned by the respective LR. They are included in the
        route table with a lower priority than static routes.
+     * Add the option value "connected-as-host" to the
+       "dynamic-routing-redistribute" LR and LRP option. If set then connected
+       routes are announced as individual host routes.
 
 OVN v24.09.0 - 13 Sep 2024
 --------------------------
diff --git a/northd/en-advertised-route-sync.c 
b/northd/en-advertised-route-sync.c
index 7da76491d..d53ba2584 100644
--- a/northd/en-advertised-route-sync.c
+++ b/northd/en-advertised-route-sync.c
@@ -20,6 +20,7 @@
 #include "northd.h"
 
 #include "en-advertised-route-sync.h"
+#include "en-lr-stateful.h"
 #include "lib/stopwatch-names.h"
 #include "openvswitch/hmap.h"
 #include "ovn-util.h"
@@ -28,34 +29,131 @@ static void
 advertised_route_table_sync(
     struct ovsdb_idl_txn *ovnsb_txn,
     const struct sbrec_advertised_route_table *sbrec_advertised_route_table,
-    const struct hmap *parsed_routes);
+    const struct lr_stateful_table *lr_stateful_table,
+    const struct hmap *parsed_routes,
+    struct advertised_route_sync_data *data);
+
+bool
+advertised_route_sync_lr_stateful_change_handler(struct engine_node *node,
+                                                 void *data_)
+{
+    /* We only actually use lr_stateful data if we expose individual host
+     * routes. In this case we for now just recompute.
+     * */
+    struct ed_type_lr_stateful *lr_stateful_data =
+        engine_get_input_data("lr_stateful", node);
+    struct advertised_route_sync_data *data = data_;
+
+    struct hmapx_node *hmapx_node;
+    const struct lr_stateful_record *lr_stateful_rec;
+    HMAPX_FOR_EACH (hmapx_node, &lr_stateful_data->trk_data.crupdated) {
+        lr_stateful_rec = hmapx_node->data;
+        if (uuidset_contains(&data->nb_lr,
+                             &lr_stateful_rec->nbr_uuid)) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool
+advertised_route_sync_northd_change_handler(struct engine_node *node,
+                                            void *data_)
+{
+    struct advertised_route_sync_data *data = data_;
+    struct northd_data *northd_data = engine_get_input_data("northd", node);
+    if (!northd_has_tracked_data(&northd_data->trk_data)) {
+        return false;
+    }
+
+    /* We indirectly use northd_data->ls_ports if we announce host routes.
+     * For now we just recompute on any change to lsps that are relevant to us.
+     */
+    struct hmapx_node *hmapx_node;
+    const struct ovn_port *op;
+    HMAPX_FOR_EACH (hmapx_node, &northd_data->trk_data.trk_lsps.created) {
+        op = hmapx_node->data;
+        if (uuidset_contains(&data->nb_ls,
+                             &op->od->nbs->header_.uuid)) {
+            return false;
+        }
+    }
+    HMAPX_FOR_EACH (hmapx_node, &northd_data->trk_data.trk_lsps.updated) {
+        op = hmapx_node->data;
+        if (uuidset_contains(&data->nb_ls,
+                             &op->od->nbs->header_.uuid)) {
+            return false;
+        }
+    }
+    HMAPX_FOR_EACH (hmapx_node, &northd_data->trk_data.trk_lsps.deleted) {
+        op = hmapx_node->data;
+        if (uuidset_contains(&data->nb_ls,
+                             &op->od->nbs->header_.uuid)) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+static void
+routes_sync_init(struct advertised_route_sync_data *data)
+{
+    uuidset_init(&data->nb_lr);
+    uuidset_init(&data->nb_ls);
+}
+
+static void
+routes_sync_clear(struct advertised_route_sync_data *data)
+{
+    uuidset_clear(&data->nb_lr);
+    uuidset_clear(&data->nb_ls);
+}
+
+static void
+routes_sync_destroy(struct advertised_route_sync_data *data)
+{
+    uuidset_destroy(&data->nb_lr);
+    uuidset_destroy(&data->nb_ls);
+}
 
 void
 *en_advertised_route_sync_init(struct engine_node *node OVS_UNUSED,
                                struct engine_arg *arg OVS_UNUSED)
 {
-    return NULL;
+    struct advertised_route_sync_data *data = xzalloc(sizeof *data);
+    routes_sync_init(data);
+    return data;
 }
 
 void
 en_advertised_route_sync_cleanup(void *data OVS_UNUSED)
 {
+    routes_sync_destroy(data);
 }
 
 void
 en_advertised_route_sync_run(struct engine_node *node, void *data OVS_UNUSED)
 {
+    routes_sync_clear(data);
+
+    struct advertised_route_sync_data *routes_sync_data = data;
     struct routes_data *routes_data
         = engine_get_input_data("routes", node);
     const struct engine_context *eng_ctx = engine_get_context();
     const struct sbrec_advertised_route_table *sbrec_advertised_route_table =
         EN_OVSDB_GET(engine_get_input("SB_advertised_route", node));
+    struct ed_type_lr_stateful *lr_stateful_data =
+        engine_get_input_data("lr_stateful", node);
 
     stopwatch_start(ADVERTISED_ROUTE_SYNC_RUN_STOPWATCH_NAME, time_msec());
 
     advertised_route_table_sync(eng_ctx->ovnsb_idl_txn,
                                 sbrec_advertised_route_table,
-                                &routes_data->parsed_routes);
+                                &lr_stateful_data->table,
+                                &routes_data->parsed_routes,
+                                routes_sync_data);
 
     stopwatch_stop(ADVERTISED_ROUTE_SYNC_RUN_STOPWATCH_NAME, time_msec());
     engine_set_node_state(node, EN_UPDATED);
@@ -68,19 +166,22 @@ struct ar_entry {
 
     const struct sbrec_port_binding *logical_port;
     char *ip_prefix;
+    const struct sbrec_port_binding *tracked_port;
 };
 
 /* Add a new entries to the to-be-advertised routes.
  * Takes ownership of ip_prefix. */
 static struct ar_entry *
 ar_add_entry(struct hmap *routes, const struct sbrec_datapath_binding *sb_db,
-             const struct sbrec_port_binding *logical_port, char *ip_prefix)
+             const struct sbrec_port_binding *logical_port, char *ip_prefix,
+             const struct sbrec_port_binding *tracked_port)
 {
     struct ar_entry *route_e = xzalloc(sizeof *route_e);
 
     route_e->sb_db = sb_db;
     route_e->logical_port = logical_port;
     route_e->ip_prefix = ip_prefix;
+    route_e->tracked_port = tracked_port;
     uint32_t hash = uuid_hash(&sb_db->header_.uuid);
     hash = hash_string(logical_port->logical_port, hash);
     hash = hash_string(ip_prefix, hash);
@@ -91,7 +192,8 @@ ar_add_entry(struct hmap *routes, const struct 
sbrec_datapath_binding *sb_db,
 
 static struct ar_entry *
 ar_find(struct hmap *route_map, const struct sbrec_datapath_binding *sb_db,
-        const struct sbrec_port_binding *logical_port, const char *ip_prefix)
+        const struct sbrec_port_binding *logical_port, const char *ip_prefix,
+        const struct sbrec_port_binding *tracked_port)
 {
     struct ar_entry *route_e;
     uint32_t hash;
@@ -114,6 +216,10 @@ ar_find(struct hmap *route_map, const struct 
sbrec_datapath_binding *sb_db,
             continue;
         }
 
+        if (tracked_port != route_e->tracked_port) {
+            continue;
+        }
+
         return route_e;
     }
 
@@ -127,13 +233,118 @@ ar_entry_free(struct ar_entry *route_e)
     free(route_e);
 }
 
+static void
+publish_lport_addresses(struct hmap *sync_routes,
+                        const struct sbrec_datapath_binding *sb_db,
+                        const struct ovn_port *logical_port,
+                        const struct lport_addresses *addresses,
+                        const struct ovn_port *tracking_port)
+{
+    for (size_t i = 0; i < addresses->n_ipv4_addrs; i++) {
+        const struct ipv4_netaddr *addr = &addresses->ipv4_addrs[i];
+        char *addr_s = xasprintf("%s/32", addr->addr_s);
+        ar_add_entry(sync_routes, sb_db, logical_port->sb,
+                       addr_s, tracking_port->sb);
+    }
+    for (size_t i = 0; i < addresses->n_ipv6_addrs; i++) {
+        if (in6_is_lla(&addresses->ipv6_addrs[i].network)) {
+            continue;
+        }
+        const struct ipv6_netaddr *addr = &addresses->ipv6_addrs[i];
+        char *addr_s = xasprintf("%s/128", addr->addr_s);
+        ar_add_entry(sync_routes, sb_db, logical_port->sb,
+                       addr_s, tracking_port->sb);
+    }
+}
+
+/* Collect all IP addresses connected via this LRP. */
+static void
+publish_host_routes_lrp(struct hmap *sync_routes,
+                        const struct lr_stateful_table *lr_stateful_table,
+                        const struct parsed_route *route,
+                        struct advertised_route_sync_data *data,
+                        const struct ovn_port *lrp)
+{
+    /* This is a LSP connected to an LRP */
+    const struct lport_addresses *addresses = &lrp->lrp_networks;
+    publish_lport_addresses(sync_routes, route->od->sb,
+                            route->out_port,
+                            addresses, lrp);
+
+    const struct lr_stateful_record *lr_stateful_rec;
+    lr_stateful_rec = lr_stateful_table_find_by_index(
+        lr_stateful_table, lrp->od->index);
+    /* We also need to track this LR as we need to recompute when
+     * any of its IPs change. */
+    uuidset_insert(&data->nb_lr,
+                   &lr_stateful_rec->nbr_uuid);
+    struct ovn_port_routable_addresses addrs = get_op_addresses(
+        lrp, lr_stateful_rec, false);
+    for (size_t i = 0; i < addrs.n_addrs; i++) {
+        publish_lport_addresses(sync_routes, route->od->sb,
+                                route->out_port,
+                                &addrs.laddrs[i],
+                                lrp);
+    }
+    destroy_routable_addresses(&addrs);
+}
+
+/* Collect all IP addresses connected to the out_port of a route.
+ * This traverses all LSPs on the LS connected to the out_port. */
+static void
+publish_host_routes(struct hmap *sync_routes,
+                    const struct lr_stateful_table *lr_stateful_table,
+                    const struct parsed_route *route,
+                    struct advertised_route_sync_data *data)
+{
+    if (!route->out_port->peer) {
+        return;
+    }
+
+    struct ovn_datapath *peer_od = route->out_port->peer->od;
+    if (!peer_od->nbs && !peer_od->nbr) {
+        return;
+    }
+
+    if (peer_od->nbr) {
+        /* This is a LRP directly connected to another LRP. */
+        publish_host_routes_lrp(sync_routes, lr_stateful_table, route,
+                                data, route->out_port->peer);
+        return;
+    }
+
+    /* We need to track the LS we are publishing routes from, so that we can
+     * recompute when any port on there changes. */
+    uuidset_insert(&data->nb_ls, &peer_od->nbs->header_.uuid);
+
+    struct ovn_port *port;
+    HMAP_FOR_EACH (port, dp_node, &peer_od->ports) {
+        if (port->peer) {
+            /* This is a LSP connected to an LRP */
+            publish_host_routes_lrp(sync_routes, lr_stateful_table, route,
+                                    data, port->peer);
+        } else {
+            /* This is just a plain LSP */
+            for (size_t i = 0; i < port->n_lsp_addrs; i++) {
+                publish_lport_addresses(sync_routes, route->od->sb,
+                                        route->out_port,
+                                        &port->lsp_addrs[i],
+                                        port);
+            }
+        }
+    }
+}
+
 static void
 advertised_route_table_sync(
     struct ovsdb_idl_txn *ovnsb_txn,
     const struct sbrec_advertised_route_table *sbrec_advertised_route_table,
-    const struct hmap *parsed_routes)
+    const struct lr_stateful_table *lr_stateful_table,
+    const struct hmap *parsed_routes,
+    struct advertised_route_sync_data *data)
 {
     struct hmap sync_routes = HMAP_INITIALIZER(&sync_routes);
+    struct uuidset host_route_lrps = UUIDSET_INITIALIZER(&host_route_lrps);
     const struct parsed_route *route;
 
     struct ar_entry *route_e;
@@ -151,9 +362,21 @@ advertised_route_table_sync(
 
         enum dynamic_routing_redistribute_mode drr =
             route->out_port->dynamic_routing_redistribute;
-        if (route->source == ROUTE_SOURCE_CONNECTED &&
-              !drr_mode_CONNECTED_is_set(drr)) {
-            continue;
+        if (route->source == ROUTE_SOURCE_CONNECTED) {
+            if (!drr_mode_CONNECTED_is_set(drr)) {
+                continue;
+            }
+            /* If we advertise host routes, we only need to do so once per
+             * LRP. */
+            const struct uuid *lrp_uuid =
+                &route->out_port->nbrp->header_.uuid;
+            if (drr_mode_CONNECTED_AS_HOST_is_set(drr) &&
+                  !uuidset_contains(&host_route_lrps, lrp_uuid)) {
+                uuidset_insert(&host_route_lrps, lrp_uuid);
+                publish_host_routes(&sync_routes, lr_stateful_table,
+                                    route, data);
+                continue;
+            }
         }
         if (route->source == ROUTE_SOURCE_STATIC &&
               !drr_mode_STATIC_is_set(drr)) {
@@ -162,14 +385,15 @@ advertised_route_table_sync(
 
         char *ip_prefix = normalize_v46_prefix(&route->prefix, route->plen);
         route_e = ar_add_entry(&sync_routes, route->od->sb,
-                               route->out_port->sb, ip_prefix);
+                               route->out_port->sb, ip_prefix, NULL);
     }
+    uuidset_destroy(&host_route_lrps);
 
     SBREC_ADVERTISED_ROUTE_TABLE_FOR_EACH_SAFE (sb_route,
                                                 sbrec_advertised_route_table) {
         route_e = ar_find(&sync_routes, sb_route->datapath,
-                          sb_route->logical_port,
-                          sb_route->ip_prefix);
+                          sb_route->logical_port, sb_route->ip_prefix,
+                          sb_route->tracked_port);
         if (route_e) {
           hmap_remove(&sync_routes, &route_e->hmap_node);
           ar_entry_free(route_e);
@@ -184,6 +408,7 @@ advertised_route_table_sync(
         sbrec_advertised_route_set_datapath(sr, route_e->sb_db);
         sbrec_advertised_route_set_logical_port(sr, route_e->logical_port);
         sbrec_advertised_route_set_ip_prefix(sr, route_e->ip_prefix);
+        sbrec_advertised_route_set_tracked_port(sr, route_e->tracked_port);
         ar_entry_free(route_e);
     }
 
diff --git a/northd/en-advertised-route-sync.h 
b/northd/en-advertised-route-sync.h
index 30e7cae1f..1f24fd329 100644
--- a/northd/en-advertised-route-sync.h
+++ b/northd/en-advertised-route-sync.h
@@ -17,10 +17,21 @@
 #define EN_ADVERTISED_ROUTE_SYNC_H 1
 
 #include "lib/inc-proc-eng.h"
+#include "lib/uuidset.h"
 
 struct advertised_route_sync_data {
+  /* Contains the uuids of all NB Logical Routers where we used a
+   * lr_stateful_record during computation. */
+  struct uuidset nb_lr;
+  /* Contains the uuids of all NB Logical Switches where we rely on port
+   * changes for host routes. */
+  struct uuidset nb_ls;
 };
 
+bool advertised_route_sync_lr_stateful_change_handler(struct engine_node *,
+                                                      void *data);
+bool advertised_route_sync_northd_change_handler(struct engine_node *,
+                                                 void *data);
 void *en_advertised_route_sync_init(struct engine_node *, struct engine_arg *);
 void en_advertised_route_sync_cleanup(void *data);
 void en_advertised_route_sync_run(struct engine_node *, void *data);
diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
index a251f7fd6..438daf1c6 100644
--- a/northd/inc-proc-northd.c
+++ b/northd/inc-proc-northd.c
@@ -292,6 +292,10 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
     engine_add_input(&en_advertised_route_sync, &en_routes, NULL);
     engine_add_input(&en_advertised_route_sync, &en_sb_advertised_route,
                      NULL);
+    engine_add_input(&en_advertised_route_sync, &en_lr_stateful,
+                     advertised_route_sync_lr_stateful_change_handler);
+    engine_add_input(&en_advertised_route_sync, &en_northd,
+                     advertised_route_sync_northd_change_handler);
 
     engine_add_input(&en_learned_route_sync, &en_routes, NULL);
     engine_add_input(&en_learned_route_sync, &en_sb_learned_route, NULL);
diff --git a/northd/northd.c b/northd/northd.c
index d1a91c8a5..08a3a1d23 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -839,6 +839,11 @@ parse_dynamic_routing_redistribute(
             out |= DRRM_CONNECTED;
             continue;
         }
+        if (!strcmp(token, "connected-as-host")) {
+            /* Setting connected-as-host implies connected. */
+            out |= DRRM_CONNECTED | DRRM_CONNECTED_AS_HOST;
+            continue;
+        }
         if (!strcmp(token, "static")) {
             out |= DRRM_STATIC;
             continue;
@@ -1137,19 +1142,6 @@ build_datapaths(struct ovsdb_idl_txn *ovnsb_txn,
     ods_build_array_index(lr_datapaths);
 }
 
-/* Structure representing logical router port
- * routable addresses. This includes DNAT and Load Balancer
- * addresses. This structure will only be filled in if the
- * router port is a gateway router port. Otherwise, all pointers
- * will be NULL and n_addrs will be 0.
- */
-struct ovn_port_routable_addresses {
-    /* The parsed routable addresses */
-    struct lport_addresses *laddrs;
-    /* Number of items in the laddrs array */
-    size_t n_addrs;
-};
-
 static bool lsp_can_be_inc_processed(const struct nbrec_logical_switch_port *);
 
 /* This function returns true if 'op' is a gateway router port.
@@ -1195,7 +1187,7 @@ is_transit_router_port(struct ovn_port *op)
            smap_get_bool(&op->sb->chassis->other_config, "is-remote", false);
 }
 
-static void
+void
 destroy_routable_addresses(struct ovn_port_routable_addresses *ra)
 {
     for (size_t i = 0; i < ra->n_addrs; i++) {
@@ -1208,12 +1200,14 @@ static char **get_nat_addresses(const struct ovn_port 
*op, size_t *n,
                                 bool routable_only, bool include_lb_ips,
                                 const struct lr_stateful_record *);
 
-static struct ovn_port_routable_addresses
-get_op_routable_addresses(struct ovn_port *op,
-                          const struct lr_stateful_record *lr_stateful_rec)
+struct ovn_port_routable_addresses
+get_op_addresses(const struct ovn_port *op,
+                 const struct lr_stateful_record *lr_stateful_rec,
+                 bool routable_only)
 {
     size_t n;
-    char **nats = get_nat_addresses(op, &n, true, true, lr_stateful_rec);
+    char **nats = get_nat_addresses(op, &n, routable_only, true,
+                                    lr_stateful_rec);
 
     if (!nats) {
         return (struct ovn_port_routable_addresses) {
@@ -1246,6 +1240,13 @@ get_op_routable_addresses(struct ovn_port *op,
     };
 }
 
+static struct ovn_port_routable_addresses
+get_op_routable_addresses(struct ovn_port *op,
+                          const struct lr_stateful_record *lr_stateful_rec)
+{
+    return get_op_addresses(op, lr_stateful_rec, true);
+}
+
 
 static void
 ovn_port_set_nb(struct ovn_port *op,
diff --git a/northd/northd.h b/northd/northd.h
index 0fbb718d8..6b4d9fdd0 100644
--- a/northd/northd.h
+++ b/northd/northd.h
@@ -25,6 +25,7 @@
 #include "openvswitch/hmap.h"
 #include "simap.h"
 #include "ovs-thread.h"
+#include "en-lr-stateful.h"
 
 struct northd_input {
     /* Northbound table references */
@@ -309,7 +310,8 @@ struct mcast_port_info {
 
 #define DRR_MODES          \
     DRR_MODE(CONNECTED, 0) \
-    DRR_MODE(STATIC,    1)
+    DRR_MODE(CONNECTED_AS_HOST, 1) \
+    DRR_MODE(STATIC,    2)
 
 enum dynamic_routing_redistribute_mode_bits {
 #define DRR_MODE(PROTOCOL, BIT) DRRM_##PROTOCOL##_BIT = BIT,
@@ -970,4 +972,24 @@ void build_igmp_lflows(struct hmap *igmp_groups,
                        const struct hmap *ls_datapaths,
                        struct lflow_table *lflows,
                        struct lflow_ref *lflow_ref);
+
+/* Structure representing logical router port routable addresses. This
+ * includes DNAT and Load Balancer addresses. This structure will only
+ * be filled in if the router port is a gateway router port. Otherwise,
+ * all pointers will be NULL and n_addrs will be 0.
+ */
+struct ovn_port_routable_addresses {
+    /* The parsed routable addresses */
+    struct lport_addresses *laddrs;
+    /* Number of items in the laddrs array */
+    size_t n_addrs;
+};
+
+struct ovn_port_routable_addresses get_op_addresses(
+    const struct ovn_port *op,
+    const struct lr_stateful_record *lr_stateful_rec,
+    bool routable_only);
+
+void destroy_routable_addresses(struct ovn_port_routable_addresses *ra);
+
 #endif /* NORTHD_H */
diff --git a/ovn-nb.xml b/ovn-nb.xml
index ae5e483d1..0addd09e6 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -3029,8 +3029,8 @@ or
         can participate in dynamic routing with components outside of OVN.
 
         It will synchronize all routes to the soutbound
-        <ref table="Advertised_Route" db="OVN_SB"/> table that are relevant
-        for the router. This includes:
+        <ref table="Advertised_Route" db="OVN_Southbound"/> table that are
+        relevant for the router. This includes:
         <ul>
           <li>
             all "connected" routes implicitly created by networks
@@ -3046,31 +3046,66 @@ or
         route types that should be advertised. See:
         <ul>
         <li><ref column="options" key="dynamic-routing-redistribute"
-                 table="Logical_Router"/></li>
+                 table="Logical_Router"/> on Logical_Router</li>
         <li><ref column="options" key="dynamic-routing-redistribute"
-                 table="Logical_Router_Port"/></li>
+                 table="Logical_Router_Port"/> on Logical_Router_Port</li>
         </ul>
       </column>
 
       <column name="options" key="dynamic-routing-redistribute"
               type='{"type": "string"}'>
-        Only relevant if <ref column="options" key="dynamic-routing"
-        table="Logical_Router"/> is set to <code>true</code>.
+        <p>
+          Only relevant if <ref column="options" key="dynamic-routing"
+          table="Logical_Router"/> is set to <code>true</code>.
+        </p>
 
-        This is a list of elements separated by <code>;</code>.
+        <p>
+          This is a list of elements separated by <code>;</code>.
+        </p>
 
-        If <code>connected</code> is in the list then northd will synchronize
-        all "connected" routes to the southbound <ref table="Route"
-        db="OVN_SB"/> table. "Connected" here means routes implicitly created
-        by networks associated with the LRPs.
+        <p>
+          If <code>connected</code> is in the list then northd will synchronize
+          all "connected" routes to the southbound <ref
+          table="Advertised_Route" db="OVN_Southbound"/> table. "Connected"
+          here means routes implicitly created by networks associated with
+          the LRPs.
+        </p>
 
-        If <code>static</code> is in the list then northd will synchronize all
-        <ref table="Logical_Router_Static_Route"/> to the southbound
-        <ref table="Route" db="OVN_SB"/> table.
+        <p>
+          If <code>connected-as-host</code> is in the list then northd will
+          enumerate all actively used individual IPs of a "connected" route and
+          announce these IPs as host routes instead of announcing the subnet.
+          This includes LSP and LRP addresses on the network as well as NAT
+          entries of remove Logical_Routers on this network.
+          Setting this implies the setting <code>connected</code>.
 
-        This value can be overwritten on a per LRP basis using
-        <ref column="options" key="dynamic-routing-redistribute"
-             table="Logical_Router_Port"/>.
+          This setting can be used to:
+          <ul>
+            <li>
+              allow the fabric outside of OVN to drop traffic towards IP
+              addresses that are not actually used. This traffic would
+              otherwise hit this LR and then be dropped.
+            </li>
+
+            <li>
+              If this LR has multiple LRPs connected to the fabric on different
+              chassis: allows the fabric outside of OVN to steer packets to the
+              chassis which already hosts this backing ip address.
+            </li>
+          </ul>
+        </p>
+
+        <p>
+          If <code>static</code> is in the list then northd will synchronize
+          all <ref table="Logical_Router_Static_Route"/> to the southbound
+          <ref table="Advertised_Route" db="OVN_Southbound"/> table.
+        </p>
+
+        <p>
+          This value can be overwritten on a per LRP basis using
+          <ref column="options" key="dynamic-routing-redistribute"
+               table="Logical_Router_Port"/> on the Logical_Router_Port.
+        </p>
       </column>
     </group>
 
@@ -3842,24 +3877,60 @@ or
 
       <column name="options" key="dynamic-routing-redistribute"
               type='{"type": "string"}'>
-        Only relevant if <ref column="options" key="dynamic-routing"
-        table="Logical_Router"/> on the respective Logical_Router is set
-        to <code>true</code>.
+        <p>
+          Only relevant if <ref column="options" key="dynamic-routing"
+          table="Logical_Router"/> on the respective Logical_Router is set
+          to <code>true</code>.
+        </p>
 
-        This is a list of elements separated by <code>;</code>.
+        <p>
+          This is a list of elements separated by <code>;</code>.
+        </p>
 
-        If <code>connected</code> is in the list then northd will synchronize
-        all "connected" routes to the southbound <ref table="Route"
-        db="OVN_SB"/> table. "Connected" here means routes implicitly created
-        by networks associated with the LRPs.
+        <p>
+          If <code>connected</code> is in the list then northd will synchronize
+          all "connected" routes to the southbound <ref
+          table="Advertised_Route" db="OVN_Southbound"/> table. "Connected"
+          here means routes implicitly created by networks associated with
+          the LRPs.
+        </p>
 
-        If <code>static</code> is in the list then northd will synchronize all
-        <ref table="Logical_Router_Static_Route"/> to the southbound
-        <ref table="Route" db="OVN_SB"/> table.
+        <p>
+          If <code>connected-as-host</code> is in the list then northd will
+          enumerate all actively used individual IPs of a "connected" route and
+          announce these IPs as host routes instead of announcing the subnet.
+          This includes LSP and LRP addresses on the network as well as NAT
+          entries of remove Logical_Routers on this network.
+          Setting this implies the setting <code>connected</code>.
+
+          This setting can be used to:
+          <ul>
+            <li>
+              allow the fabric outside of OVN to drop traffic towards IP
+              addresses that are not actually used. This traffic would
+              otherwise hit this LR and then be dropped.
+            </li>
+
+            <li>
+              If this LR has multiple LRPs connected to the fabric on different
+              chassis: allows the fabric outside of OVN to steer packets to the
+              chassis which already hosts this backing ip address.
+            </li>
+          </ul>
+        </p>
+
+        <p>
+          If <code>static</code> is in the list then northd will synchronize
+          all <ref table="Logical_Router_Static_Route"/> to the southbound
+          <ref table="Advertised_Route" db="OVN_Southbound"/> table.
+        </p>
+
+        <p>
+          If not set the value from <ref column="options"
+          key="dynamic-routing-redistribute" table="Logical_Router"/> on the
+          Logical_Router will be used.
+        </p>
 
-        If not set the value from <ref column="options"
-        key="dynamic-routing-redistribute" table="Logical_Router"/> will be
-        used.
       </column>
     </group>
 
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 826989671..44788bfdd 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -15183,6 +15183,80 @@ AT_CHECK([grep -w "lr_in_ip_routing" lr0flows | 
ovn_strip_lflows], [0], [dnl
 AT_CLEANUP
 ])
 
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([dynamic-routing - host routes])
+AT_KEYWORDS([dynamic-routing])
+ovn_start
+
+# We start with announcing routes on a lr with 2 lrps.
+# LRP lr0-sw0 is connected to ls sw0.
+check ovn-nbctl lr-add lr0
+check ovn-nbctl set Logical_Router lr0 option:dynamic-routing=true \
+                                 
option:dynamic-routing-redistribute="connected;static"
+check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24
+sw0=$(fetch_column port_binding _uuid logical_port=lr0-sw0)
+check ovn-nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 10.0.1.1/24
+sw1=$(fetch_column port_binding _uuid logical_port=lr0-sw1)
+check ovn-nbctl ls-add sw0
+check ovn-nbctl lsp-add sw0 sw0-lr0
+check ovn-nbctl --wait=sb set Logical_Switch_Port sw0-lr0 type=router 
options:router-port=lr0-sw0
+check_row_count Advertised_Route 2 tracked_port='[[]]'
+datapath=$(fetch_column datapath_binding _uuid external_ids:name=lr0)
+
+# Configuring the LRP lr0-sw0 to send host routes.
+# As sw0 is quite empty we will only see the addresses of lr0-sw0.
+check ovn-nbctl --wait=sb set Logical_Router_Port lr0-sw0 
options:dynamic-routing-redistribute="connected-as-host;static"
+check_row_count Advertised_Route 2
+check_column 10.0.0.1/32 Advertised_Route ip_prefix datapath=$datapath 
logical_port=$sw0
+check_column $sw0 Advertised_Route tracked_port datapath=$datapath 
logical_port=$sw0
+
+# Adding a VIF to the LS sw0 will advertise it as well.
+check ovn-nbctl lsp-add sw0 sw0-vif0
+check ovn-nbctl --wait=sb lsp-set-addresses sw0-vif0 "00:aa:bb:cc:dd:ee 
10.0.0.2"
+vif0=$(fetch_column port_binding _uuid logical_port=sw0-vif0)
+check_row_count Advertised_Route 3
+check_row_count Advertised_Route 2 tracked_port!='[[]]'
+check_column $vif0 Advertised_Route tracked_port datapath=$datapath 
logical_port=$sw0 ip_prefix=10.0.0.2/32
+
+# Adding a LR lr1 to the LS sw0 will advertise the LRP of the new router.
+check ovn-nbctl lr-add lr1
+check ovn-nbctl lrp-add lr1 lr1-sw0 00:00:00:01:ff:01 10.0.0.10/24
+check ovn-nbctl lsp-add sw0 sw0-lr1
+lr1=$(fetch_column port_binding _uuid logical_port=lr1-sw0)
+check ovn-nbctl --wait=sb set Logical_Switch_Port sw0-lr1 type=router 
options:router-port=lr1-sw0
+check_row_count Advertised_Route 4
+check_row_count Advertised_Route 3 tracked_port!='[[]]'
+check_column $lr1 Advertised_Route tracked_port datapath=$datapath 
logical_port=$sw0 ip_prefix=10.0.0.10/32
+
+# Adding a NAT rule to lr1 will advertise it as well.
+check ovn-nbctl --wait=sb lr-nat-add lr1 dnat_and_snat 10.0.0.100 192.168.0.1
+check_row_count Advertised_Route 5
+check_row_count Advertised_Route 4 tracked_port!='[[]]'
+check_column $lr1 Advertised_Route tracked_port datapath=$datapath 
logical_port=$sw0 ip_prefix=10.0.0.100/32
+
+# Adding a static route to lr1 will be advertised just normally.
+check ovn-nbctl --wait=sb lr-route-add lr0 172.16.0.0/24 10.0.0.200
+check_row_count Advertised_Route 6
+check_row_count Advertised_Route 4 tracked_port!='[[]]'
+check_row_count Advertised_Route 1 datapath=$datapath logical_port=$sw0 
ip_prefix=172.16.0.0/24
+
+# Adding lr2 directly connected using two LRPs will advertise it as well.
+# lr2 contains a NAT rule that will be advertised.
+check ovn-nbctl lr-add lr2
+check ovn-nbctl lrp-add lr0 lr0-lr2 00:00:00:02:ff:01 10.10.0.1/24 peer=lr2-lr0
+check ovn-nbctl set Logical_Router_Port lr0-lr2 
options:dynamic-routing-redistribute="connected-as-host;static"
+lr0lr2=$(fetch_column port_binding _uuid logical_port=lr0-lr2)
+check ovn-nbctl lrp-add lr2 lr2-lr0 00:00:00:02:ff:02 10.10.0.2/24 peer=lr0-lr2
+lr2lr0=$(fetch_column port_binding _uuid logical_port=lr2-lr0)
+check ovn-nbctl lrp-add lr2 lr2-sw1 00:00:00:02:ff:03 10.11.0.1/24
+check ovn-nbctl --wait=sb lr-nat-add lr2 dnat_and_snat 10.10.0.20 10.20.0.123
+check_row_count Advertised_Route 8
+check_row_count Advertised_Route 6 tracked_port!='[[]]'
+check_row_count Advertised_Route 1 datapath=$datapath logical_port=$lr0lr2 
ip_prefix=10.10.0.20/32 tracked_port=$lr2lr0
+
+AT_CLEANUP
+])
+
 OVN_FOR_EACH_NORTHD_NO_HV([
 AT_SETUP([dynamic-routing incremental processing])
 AT_KEYWORDS([dynamic-routing])
@@ -15206,13 +15280,16 @@ check_engine_stats lflow recompute nocompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
-check ovn-nbctl --wait=sb lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24
+check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24
+check ovn-nbctl ls-add sw0
+check ovn-nbctl lsp-add sw0 sw0-lr0
+check ovn-nbctl --wait=sb set Logical_Switch_Port sw0-lr0 type=router 
options:router-port=lr0-sw0
 sw0=$(fetch_column port_binding _uuid logical_port=lr0-sw0)
 check_engine_stats northd recompute compute
-check_engine_stats routes recompute nocompute
-check_engine_stats advertised_route_sync recompute nocompute
-check_engine_stats learned_route_sync recompute nocompute
-check_engine_stats lflow recompute nocompute
+check_engine_stats routes recompute compute
+check_engine_stats advertised_route_sync recompute compute
+check_engine_stats learned_route_sync recompute compute
+check_engine_stats lflow recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
@@ -15318,6 +15395,46 @@ check_engine_stats learned_route_sync recompute 
nocompute
 check_engine_stats lflow recompute nocompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb set Logical_Router_Port lr0-sw0 
options:dynamic-routing-redistribute="connected-as-host"
+check_engine_stats northd recompute nocompute
+check_engine_stats routes recompute nocompute
+check_engine_stats advertised_route_sync recompute nocompute
+check_engine_stats learned_route_sync recompute nocompute
+check_engine_stats lflow recompute nocompute
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl lsp-add sw0 sw0-vif0
+check ovn-nbctl --wait=sb lsp-set-addresses sw0-vif0 "00:aa:bb:cc:dd:ee 
10.0.0.2"
+check_engine_stats northd norecompute compute
+check_engine_stats routes norecompute compute
+check_engine_stats advertised_route_sync recompute nocompute
+check_engine_stats learned_route_sync norecompute compute
+check_engine_stats lflow norecompute compute
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl lr-add lr1
+check ovn-nbctl lrp-add lr1 lr1-sw0 00:00:00:01:ff:01 10.0.0.10/24
+check ovn-nbctl lsp-add sw0 sw0-lr1
+check ovn-nbctl --wait=sb set Logical_Switch_Port sw0-lr1 type=router 
options:router-port=lr1-sw0
+check_engine_stats northd recompute compute
+check_engine_stats routes recompute compute
+check_engine_stats advertised_route_sync recompute nocompute
+check_engine_stats learned_route_sync recompute compute
+check_engine_stats lflow recompute compute
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb lr-nat-add lr1 dnat_and_snat 10.0.0.100 192.168.0.1
+check_engine_stats northd norecompute compute
+check_engine_stats routes norecompute compute
+check_engine_stats advertised_route_sync recompute nocompute
+check_engine_stats learned_route_sync norecompute compute
+check_engine_stats lflow norecompute compute
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
 check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb lrp-del lr0-sw0
 check_engine_stats northd recompute compute
-- 
2.47.1


_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to