On Thu, Jul 2, 2026 at 10:55 AM Dmitrii Shcherbakov via dev
<[email protected]> wrote:
>
> When an LRP carries dynamic-routing-redistribute=lb or =nat, northd
> already enumerates neighbouring LRs' LB VIPs and NAT external IPs and
> emits Advertised_Route entries for them.  The advertising LR, however,
> has no route to those addresses through the peer, so traffic forwarded
> via the advertising LR's VRF cannot reach the backend.
>
> Add a forwarding route on the advertising LR for each such IP: install
> a parsed_route pointing at the peer LRP as nexthop.  Forwarding routes
> use a dedicated route source (ROUTE_SOURCE_LB or ROUTE_SOURCE_NAT)
> and the same prefix and lose to operator-installed static routes.
>
> en_dynamic_routes_run rebuilds parsed_routes from scratch each cycle
> and diffs old vs new entries by exact field comparison. When an
> entry matches, the old parsed_route object is retained in the rebuilt
> hmap (preserving pointer identity for downstream nodes like
> group_ecmp_route that store const pointers) and the newly built
> duplicate is freed.
>
> The advertising LRP may be unnumbered (no IP in the nexthop's family),
> in which case lrp_addr_s is passed through as NULL. The emitted route
> omits REG_SRC_IPV{4,6} but ARP resolution still works: the LS-level
> ls_in_arp_rsp responder matches on arp.tpa alone.
>
> Signed-off-by: Dmitrii Shcherbakov <[email protected]>
> ---
>  northd/en-advertised-route-sync.c | 261 ++++++++++++-
>  northd/en-advertised-route-sync.h |  25 +-
>  northd/en-group-ecmp-route.c      |  76 +++-
>  northd/en-group-ecmp-route.h      |   4 +
>  northd/inc-proc-northd.c          |   5 +
>  northd/northd.c                   |  22 +-
>  northd/northd.h                   |   2 +
>  tests/ovn-inc-proc-graph-dump.at  |   7 +-
>  tests/ovn-northd.at               | 619 +++++++++++++++++++++++++++++-
>  9 files changed, 988 insertions(+), 33 deletions(-)
>
> diff --git a/northd/en-advertised-route-sync.c 
> b/northd/en-advertised-route-sync.c
> index 4a8d13232..8e02fdec1 100644
> --- a/northd/en-advertised-route-sync.c
> +++ b/northd/en-advertised-route-sync.c
> @@ -166,16 +166,105 @@ dynamic_routes_track_od(struct dynamic_routes_data 
> *data,
>      uuidset_insert(od->nbr ? &data->nb_lr : &data->nb_ls, &od->key);
>  }
>
> +/* Install a parsed_route on advertising_od that forwards ip_address (a
> + * LB VIP or NAT external IP) through advertising_op to tracked_port,
> + * where tracked_port must be a peer LRP on the shared LS so that its
> + * first matching-family network address is a valid nexthop.
> + *
> + * Used by the connected-neighbour redistribution paths
> + * (build_{lb,nat}_connected_routes) so the advertising LR can
> + * forward to the peer's VIPs and external IPs, not just advertise
> + * reachability for them.
> + *
> + * For distributed NAT the tracked_port is the backend's LSP (not an LRP) and
> + * doesn't carry lrp_networks - in that case this function is a no-op. Such
> + * deployments still rely on the existing ARP-resolved data path.
> + *
> + * Silently no-ops when:
> + *   - tracked_port is not an LRP (no nexthop derivable from this hop), or
> + *   - the prefix string fails to parse, or
> + *   - the peer LRP carries no address of the prefix's IP family.
> + *
> + * When advertising_op is unnumbered for the nexthop's family, lrp_addr_s
> + * is NULL. */
> +static void
> +add_redistribute_parsed_route(struct hmap *parsed_routes_out,
> +                              const struct ovn_datapath *advertising_od,
> +                              const struct ovn_port *advertising_op,
> +                              const struct ovn_port *tracked_port,
> +                              const char *ip_address,
> +                              enum route_source source,
> +                              const struct ovsdb_idl_row *source_hint)
> +{
> +    if (!tracked_port || !tracked_port->nbrp) {
> +        /* Not an LRP-typed tracked port (e.g. distributed NAT bound to a
> +         * specific LSP). No nexthop available from this hop. */
> +        return;
> +    }
> +
> +    /* Parse the prefix (the VIP/FIP). */
> +    struct in6_addr prefix;
> +    if (!ip46_parse(ip_address, &prefix)) {
> +        return;
> +    }
> +    bool is_v6 = !IN6_IS_ADDR_V4MAPPED(&prefix);
> +    unsigned int plen = is_v6 ? 128 : 32;
> +
> +    /* Choose the nexthop from the peer LRP's first matching-family address. 
> */
> +    const char *nexthop_s = NULL;
> +    if (!is_v6 && tracked_port->lrp_networks.n_ipv4_addrs) {
> +        nexthop_s = tracked_port->lrp_networks.ipv4_addrs[0].addr_s;
> +    } else if (is_v6 && tracked_port->lrp_networks.n_ipv6_addrs) {
> +        nexthop_s = tracked_port->lrp_networks.ipv6_addrs[0].addr_s;
> +    }
> +    if (!nexthop_s) {
> +        return;
> +    }
> +
> +    /* If advertising_op has an address in the nexthop's family, use it as
> +     * eth.src. Otherwise (unnumbered LRP) leave lrp_addr_s NULL so the
> +     * emitted route omits REG_SRC_IPV{4,6}. ARP resolution still works:
> +     * the LS-level ls_in_arp_rsp responder matches on arp.tpa alone. */
> +    const char *lrp_addr_s = lrp_find_member_ip(advertising_op, nexthop_s);
> +
> +    struct in6_addr *nexthop = xmalloc(sizeof *nexthop);
> +    if (!ip46_parse(nexthop_s, nexthop)) {
> +        free(nexthop);
> +        return;
> +    }
> +
> +    parsed_route_add(advertising_od, nexthop, &prefix, plen,
> +                     false,
> +                     lrp_addr_s, advertising_op,
> +                     0,
> +                     false,
> +                     false,
> +                     false,
> +                     NULL,
> +                     source,
> +                     false,
> +                     source_hint,
> +                     tracked_port,
> +                     parsed_routes_out);

It's not typical in OVN to place one argument per line like this if
there's room on the line for more parameters. Please change this to
put as many parameters as can fit in 80 columns.

> +}
> +
>  /* This function adds a new route for each entry in lr_nat record
>   * to "routes". Logical port of the route is set to "advertising_op" and
>   * tracked port is set to NAT's distributed gw port. If NAT doesn't have
>   * DGP (for example if it's set on gateway router), no tracked port will
> - * be set.*/
> + * be set.
> + *
> + * If parsed_routes_out is non-NULL, also installs a local forwarding
> + * parsed_route on advertising_op->od for each NAT external IP whose
> + * nexthop is available from tracked_port (i.e. a peer LRP). This is the
> + * connected-neighbour redistribution case where the advertising LR
> + * needs to forward to the peer's LR.*/
>  static void
>  build_nat_route_for_port(const struct ovn_port *advertising_op,
>                           const struct lr_nat_record *lr_nat,
>                           const struct hmap *ls_ports,
> -                         struct hmap *routes)
> +                         struct hmap *routes,
> +                         struct hmap *parsed_routes_out)
>  {
>      const struct ovn_datapath *advertising_od = advertising_op->od;
>
> @@ -203,11 +292,22 @@ build_nat_route_for_port(const struct ovn_port 
> *advertising_op,
>                           nat->nb->external_ip, tracked_port,
>                           ROUTE_SOURCE_NAT);
>          }
> +
> +        if (parsed_routes_out) {
> +            add_redistribute_parsed_route(parsed_routes_out, advertising_od,
> +                                          advertising_op, tracked_port,
> +                                          nat->nb->external_ip,
> +                                          ROUTE_SOURCE_NAT,
> +                                          &nat->nb->header_);
> +        }
>      }
>  }
>
>  /* Generate routes for NAT external IPs in lr_nat, for each ovn port
> - * in "od" that has enabled redistribution of NAT adresses.*/
> + * in "od" that has enabled redistribution of NAT addresses.
> + *
> + * No forwarding route is needed because the LR owns the NAT and
> + * its own NAT pipeline handles ingress for the external IP. */
>  static void
>  build_nat_routes(const struct ovn_datapath *od,
>                   const struct lr_nat_record *lr_nat,
> @@ -220,15 +320,16 @@ build_nat_routes(const struct ovn_datapath *od,
>              continue;
>          }
>
> -        build_nat_route_for_port(op, lr_nat, ls_ports, routes);
> +        build_nat_route_for_port(op, lr_nat, ls_ports, routes,
> +                                 NULL);
>      }
>  }
>
>  /* Similar to build_nat_routes, this function generates routes for nat 
> records
>   * in neighboring routers. For each ovn port in "od" that has enabled
> - * redistribution of NAT adresses, look up their neighbors (either directly
> + * redistribution of NAT addresses, look up their neighbors (either directly
>   * connected routers, or routers connected through common LS) and advertise
> - * thier external NAT IPs too.*/
> + * their external NAT IPs too.*/
>  static void
>  build_nat_connected_routes(
>      const struct ovn_datapath *od,
> @@ -260,9 +361,12 @@ build_nat_connected_routes(
>                  continue;
>              }
>
> -            /* Advertise peer's NAT routes via the local port too. */
> +            /* Advertise peer's NAT routes via the local port too, and
> +             * install forwarding routes so we can reach the
> +             * peer's external IPs. */
>              build_nat_route_for_port(op, peer_lr_stateful->lrnat_rec,
> -                                     ls_ports, &data->routes);
> +                                     ls_ports, &data->routes,
> +                                     &data->parsed_routes);
>              continue;
>          }
>
> @@ -282,9 +386,12 @@ build_nat_connected_routes(
>                  continue;
>              }
>
> -            /* Advertise peer's NAT routes via the local port too. */
> +            /* Advertise peer's NAT routes via the local port too, and
> +             * install forwarding routes so we can reach the
> +             * peer's external IPs. */
>              build_nat_route_for_port(op, peer_lr_stateful->lrnat_rec,
> -                                     ls_ports, &data->routes);
> +                                     ls_ports, &data->routes,
> +                                     &data->parsed_routes);
>              /* Track the LR datapath on the other side of LS
>               * for any changes. */
>              dynamic_routes_track_od(data, rp->peer->od);
> @@ -292,12 +399,19 @@ build_nat_connected_routes(
>      }
>  }
>
> -/* This function adds a new route for each IP in lb_ips to "routes".*/
> +/* Own-LR entry point used by the own-LR (gateway-router/DGP) path,
> + * which doesn't currently route through a peer LR's LBs. Emits one
> + * Advertised_Route per IP in lb_ips with tracked_port as-is.
> + *
> + * If parsed_routes_out is non-NULL, also installs one local forwarding
> + * parsed_route per VIP, used by the connected-neighbour redistribution
> + * case so the advertising LR can reach the peer's VIPs. */
>  static void
>  build_lb_route_for_port(const struct ovn_port *advertising_op,
>                          const struct ovn_port *tracked_port,
>                          const struct ovn_lb_ip_set *lb_ips,
> -                        struct hmap *routes)
> +                        struct hmap *routes,
> +                        struct hmap *parsed_routes_out)
>  {
>      const struct ovn_datapath *advertising_od = advertising_op->od;
>
> @@ -305,10 +419,22 @@ build_lb_route_for_port(const struct ovn_port 
> *advertising_op,
>      SSET_FOR_EACH (ip_address, &lb_ips->ips_v4_adv) {
>          ar_entry_add(routes, advertising_od, advertising_op,
>                       ip_address, tracked_port, ROUTE_SOURCE_LB);
> +        if (parsed_routes_out) {
> +            add_redistribute_parsed_route(parsed_routes_out, advertising_od,
> +                                          advertising_op, tracked_port,
> +                                          ip_address, ROUTE_SOURCE_LB,
> +                                          &advertising_op->nbrp->header_);
> +        }
>      }
>      SSET_FOR_EACH (ip_address, &lb_ips->ips_v6_adv) {
>          ar_entry_add(routes, advertising_od, advertising_op,
>                       ip_address, tracked_port, ROUTE_SOURCE_LB);
> +        if (parsed_routes_out) {
> +            add_redistribute_parsed_route(parsed_routes_out, advertising_od,
> +                                          advertising_op, tracked_port,
> +                                          ip_address, ROUTE_SOURCE_LB,
> +                                          &advertising_op->nbrp->header_);
> +        }
>      }
>  }
>
> @@ -343,7 +469,7 @@ build_lb_connected_routes(const struct ovn_datapath *od,
>              lr_stateful_rec = lr_stateful_table_find_by_uuid(
>                  lr_stateful_table, peer_od->key);
>              build_lb_route_for_port(op, op->peer, lr_stateful_rec->lb_ips,
> -                                    &data->routes);
> +                                    &data->routes, &data->parsed_routes);
>              continue;
>          }
>
> @@ -360,7 +486,7 @@ build_lb_connected_routes(const struct ovn_datapath *od,
>                  lr_stateful_table, rp->peer->od->key);
>
>              build_lb_route_for_port(op, rp->peer, lr_stateful_rec->lb_ips,
> -                                    &data->routes);
> +                                    &data->routes, &data->parsed_routes);
>              /* Track the LR datapath on the other side of LS
>               * for any changes. */
>              dynamic_routes_track_od(data, rp->peer->od);
> @@ -385,14 +511,16 @@ build_lb_routes(const struct ovn_datapath *od,
>           * - always redirected to a distributed gateway router port
>           *
>           * Advertise the LB IPs via all 'op' if this is a gateway router or
> -         * throuh all DGPs of this distributed router otherwise. */
> +         * through all DGPs of this distributed router otherwise. */
>
>          if (od->is_gw_router) {
> -            build_lb_route_for_port(op, NULL, lb_ips, routes);
> +            build_lb_route_for_port(op, NULL, lb_ips, routes,
> +                                    NULL);
>          } else {
>              struct ovn_port *dgp;
>              VECTOR_FOR_EACH (&od->l3dgw_ports, dgp) {
> -                build_lb_route_for_port(op, dgp, lb_ips, routes);
> +                build_lb_route_for_port(op, dgp, lb_ips, routes,
> +                                        NULL);
>              }
>          }
>      }
> @@ -528,13 +656,32 @@ en_dynamic_routes_init(struct engine_node *node 
> OVS_UNUSED,
>      struct dynamic_routes_data *data = xmalloc(sizeof *data);
>      *data = (struct dynamic_routes_data) {
>          .routes = HMAP_INITIALIZER(&data->routes),
> +        .parsed_routes = HMAP_INITIALIZER(&data->parsed_routes),
>          .nb_lr = UUIDSET_INITIALIZER(&data->nb_lr),
>          .nb_ls = UUIDSET_INITIALIZER(&data->nb_ls),
> +        .tracked = false,
> +        .trk_data.trk_created_parsed_routes =
> +            HMAPX_INITIALIZER(&data->trk_data.trk_created_parsed_routes),
> +        .trk_data.trk_deleted_parsed_routes =
> +            HMAPX_INITIALIZER(&data->trk_data.trk_deleted_parsed_routes),
>      };
>
>      return data;
>  }
>
> +static void
> +dynamic_routes_clear_tracked(struct dynamic_routes_data *data)
> +{
> +    hmapx_clear(&data->trk_data.trk_created_parsed_routes);
> +    struct hmapx_node *hmapx_node;
> +    HMAPX_FOR_EACH_SAFE (hmapx_node,
> +                         &data->trk_data.trk_deleted_parsed_routes) {
> +        parsed_route_free(hmapx_node->data);
> +        hmapx_delete(&data->trk_data.trk_deleted_parsed_routes, hmapx_node);
> +    }
> +    data->tracked = false;
> +}
> +
>  static void
>  en_dynamic_routes_clear(struct dynamic_routes_data *data)
>  {
> @@ -543,6 +690,40 @@ en_dynamic_routes_clear(struct dynamic_routes_data *data)
>          ar_entry_free(ar);
>      }
>
> +    struct parsed_route *pr;
> +    HMAP_FOR_EACH_POP (pr, key_node, &data->parsed_routes) {
> +        parsed_route_free(pr);
> +    }
> +
> +    dynamic_routes_clear_tracked(data);
> +
> +    uuidset_clear(&data->nb_lr);
> +    uuidset_clear(&data->nb_ls);
> +}
> +
> +static void
> +dynamic_routes_prepare_rebuild(struct dynamic_routes_data *data,
> +                               struct hmap *old_parsed_routes);
> +
> +static void
> +dynamic_routes_diff_parsed(struct dynamic_routes_data *data,
> +                           struct hmap *old_parsed_routes);
> +
> +/* Save current parsed_routes into *old_parsed_routes and reinitialise
> + * data->parsed_routes for a full rebuild. */
> +static void
> +dynamic_routes_prepare_rebuild(struct dynamic_routes_data *data,
> +                               struct hmap *old_parsed_routes)
> +{
> +    dynamic_routes_clear_tracked(data);
> +
> +    hmap_swap(old_parsed_routes, &data->parsed_routes);
> +    hmap_init(&data->parsed_routes);
> +
> +    struct ar_entry *ar;
> +    HMAP_FOR_EACH_POP (ar, hmap_node, &data->routes) {
> +        ar_entry_free(ar);
> +    }
>      uuidset_clear(&data->nb_lr);
>      uuidset_clear(&data->nb_ls);
>  }
> @@ -554,6 +735,9 @@ en_dynamic_routes_cleanup(void *data_)
>
>      en_dynamic_routes_clear(data);
>      hmap_destroy(&data->routes);
> +    hmap_destroy(&data->parsed_routes);
> +    hmapx_destroy(&data->trk_data.trk_created_parsed_routes);
> +    hmapx_destroy(&data->trk_data.trk_deleted_parsed_routes);
>      uuidset_destroy(&data->nb_lr);
>      uuidset_destroy(&data->nb_ls);
>  }
> @@ -566,7 +750,8 @@ en_dynamic_routes_run(struct engine_node *node, void 
> *data)
>      struct ed_type_lr_stateful *lr_stateful_data =
>          engine_get_input_data("lr_stateful", node);
>
> -    en_dynamic_routes_clear(data);
> +    struct hmap old_parsed_routes = HMAP_INITIALIZER(&old_parsed_routes);
> +    dynamic_routes_prepare_rebuild(dynamic_routes_data, &old_parsed_routes);
>
>      const struct ovn_datapath *od;
>      HMAP_FOR_EACH (od, key_node, &northd_data->lr_datapaths.datapaths) {
> @@ -598,9 +783,47 @@ en_dynamic_routes_run(struct engine_node *node, void 
> *data)
>          build_lb_connected_routes(od, &lr_stateful_data->table,
>                                    dynamic_routes_data);
>      }
> +
> +    dynamic_routes_diff_parsed(dynamic_routes_data, &old_parsed_routes);
> +    hmap_destroy(&old_parsed_routes);
> +
>      return EN_UPDATED;
>  }
>
> +static void
> +dynamic_routes_diff_parsed(struct dynamic_routes_data *data,
> +                           struct hmap *old_parsed_routes)
> +{
> +    struct parsed_route *pr;
> +
> +    HMAP_FOR_EACH_SAFE (pr, key_node, &data->parsed_routes) {
> +        size_t hash = parsed_route_hash(pr);
> +        struct parsed_route *old_pr = parsed_route_lookup(
> +            old_parsed_routes, hash, pr);
> +        if (old_pr) {
> +            /* Swap in the pre-existing route so that pointers held by
> +             * group_ecmp_route remain valid.  Detach from
> +             * old_parsed_routes first: hmap_node is intrusive and
> +             * cannot live in two maps. */

You've done something here that has always been possible with the
incremental engine, but nobody has been bold enough to try. That is,
you're supplying tracked data changes from a run() callback instead of
an input handler. This comment here illustrates why it's tough to do
this, though, since downstream incremental engine nodes will have
cached pointers to the data in this node.

I think that one way to make this feel a bit less odd is if the
tracking data were handled at the same time as the parsed routes are
being calculated, rather than afterward. As far as the mechanics of
the code are concerned, you won't really be doing anything different,
but you'll be doing it in a different place. The big advantage to
doing it this way, though, is that if/when "proper" incremental
processing is added into en_dynamic_routes, the code should
essentially just work without much change.

> +            hmap_remove(old_parsed_routes, &old_pr->key_node);
> +            hmap_remove(&data->parsed_routes, &pr->key_node);
> +            hmap_insert(&data->parsed_routes, &old_pr->key_node, hash);
> +            parsed_route_free(pr);
> +        } else {
> +            hmapx_add(&data->trk_data.trk_created_parsed_routes, pr);
> +        }
> +    }
> +
> +    HMAP_FOR_EACH_SAFE (pr, key_node, old_parsed_routes) {
> +        hmapx_add(&data->trk_data.trk_deleted_parsed_routes, pr);
> +    }
> +
> +    if (!hmapx_is_empty(&data->trk_data.trk_created_parsed_routes)
> +        || !hmapx_is_empty(&data->trk_data.trk_deleted_parsed_routes)) {
> +        data->tracked = true;
> +    }
> +}
> +
>  enum engine_input_handler_result
>  dynamic_routes_lr_stateful_change_handler(struct engine_node *node,
>                                            void *data_)
> @@ -801,7 +1024,7 @@ advertised_route_table_sync(
>          sbrec_advertised_route_set_datapath(sr, route_e->od->sdp->sb_dp);
>          sbrec_advertised_route_set_logical_port(sr, route_e->op->sb);
>          sbrec_advertised_route_set_ip_prefix(sr, route_e->ip_prefix);
> -        if (route_e->tracked_port) {
> +        if (route_e->tracked_port && route_e->tracked_port->sb) {
>              sbrec_advertised_route_set_tracked_port(sr,
>                                                      
> route_e->tracked_port->sb);
>          }
> diff --git a/northd/en-advertised-route-sync.h 
> b/northd/en-advertised-route-sync.h
> index 71cd417de..298062412 100644
> --- a/northd/en-advertised-route-sync.h
> +++ b/northd/en-advertised-route-sync.h
> @@ -18,16 +18,39 @@
>
>  #include "lib/inc-proc-eng.h"
>  #include "lib/uuidset.h"
> +#include "openvswitch/hmap.h"
> +#include "hmapx.h"
> +
> +/* Track what changed in the dynamic_routes engine node's parsed_routes.
> + * All hmapx node data are pointers to struct parsed_route. */
> +struct dynamic_routes_tracked_data {
> +    struct hmapx trk_created_parsed_routes;
> +    struct hmapx trk_deleted_parsed_routes;
> +};
>
>  struct dynamic_routes_data {
> -    /* Stores struct ar_entry, one for each dynamic route. */
> +    /* Stores struct ar_entry, one for each dynamic route. Fed only to
> +     * en_advertised_route_sync (SB Advertised_Route table). */
>      struct hmap routes;
> +    /* Stores struct parsed_route, one per VIP/NAT-external IP whose
> +     * advertisement was synthesized from a *connected-neighbour* LR (i.e.
> +     * dynamic-routing-redistribute=lb/nat for an LRP whose peer LS hosts
> +     * another LR that owns the LB/NAT). Without these the advertising LR
> +     * would claim reachability for a prefix it had no local forwarding
> +     * route to. Fed to en_group_ecmp_route alongside en_routes and
> +     * en_learned_route_sync. */
> +    struct hmap parsed_routes;
>      /* 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;
> +
> +    /* 'tracked' is set to true if there is information available for
> +     * incremental processing. If true then trk_data is valid. */
> +    bool tracked;
> +    struct dynamic_routes_tracked_data trk_data;
>  };
>
>  void *en_advertised_route_sync_init(struct engine_node *, struct engine_arg 
> *);
> diff --git a/northd/en-group-ecmp-route.c b/northd/en-group-ecmp-route.c
> index c4c93fd84..8af1dcc0b 100644
> --- a/northd/en-group-ecmp-route.c
> +++ b/northd/en-group-ecmp-route.c
> @@ -21,8 +21,10 @@
>  #include "openvswitch/vlog.h"
>  #include "northd.h"
>
> +#include "en-advertised-route-sync.h"
>  #include "en-group-ecmp-route.h"
>  #include "en-learned-route-sync.h"
> +#include "hmapx.h"
>  #include "openvswitch/hmap.h"
>
>  VLOG_DEFINE_THIS_MODULE(en_group_ecmp_route);
> @@ -356,7 +358,8 @@ add_route(struct group_ecmp_datapath *gn, const struct 
> parsed_route *pr)
>  static void
>  group_ecmp_route(struct group_ecmp_route_data *data,
>                   const struct routes_data *routes_data,
> -                 const struct learned_route_sync_data *learned_route_data)
> +                 const struct learned_route_sync_data *learned_route_data,
> +                 const struct dynamic_routes_data *dynamic_routes_data)
>  {
>      struct group_ecmp_datapath *gn;
>      const struct parsed_route *pr;
> @@ -369,6 +372,11 @@ group_ecmp_route(struct group_ecmp_route_data *data,
>          gn = group_ecmp_datapath_lookup_or_add(data, pr->od);
>          add_route(gn, pr);
>      }
> +
> +    HMAP_FOR_EACH (pr, key_node, &dynamic_routes_data->parsed_routes) {
> +        gn = group_ecmp_datapath_lookup_or_add(data, pr->od);
> +        add_route(gn, pr);
> +    }
>  }
>
>  enum engine_node_state
> @@ -381,8 +389,11 @@ en_group_ecmp_route_run(struct engine_node *node, void 
> *_data)
>          = engine_get_input_data("routes", node);
>      struct learned_route_sync_data *learned_route_data
>          = engine_get_input_data("learned_route_sync", node);
> +    struct dynamic_routes_data *dynamic_routes_data
> +        = engine_get_input_data("dynamic_routes", node);
>
> -    group_ecmp_route(data, routes_data, learned_route_data);
> +    group_ecmp_route(data, routes_data, learned_route_data,
> +                     dynamic_routes_data);
>
>      return EN_UPDATED;
>  }
> @@ -519,3 +530,64 @@ group_ecmp_route_learned_route_change_handler(struct 
> engine_node *eng_node,
>      }
>      return EN_HANDLED_UNCHANGED;
>  }
> +
> +/* When parsed_routes is empty, dynamic_routes has no new content for us.
> + * When tracked is false but parsed_routes is non-empty we fall back to a
> + * full recompute. Otherwise process tracked adds/deletes incrementally
> + * (see group_ecmp_route_learned_route_change_handler for the pattern). */
> +enum engine_input_handler_result
> +group_ecmp_route_dynamic_routes_change_handler(struct engine_node *eng_node,
> +                                                void *data)
> +{
> +    struct group_ecmp_route_data *gdata = data;
> +    struct dynamic_routes_data *dynamic_routes_data
> +        = engine_get_input_data("dynamic_routes", eng_node);
> +
> +    if (!dynamic_routes_data->tracked) {
> +        if (hmap_is_empty(&dynamic_routes_data->parsed_routes)) {
> +            return EN_HANDLED_UNCHANGED;
> +        }
> +        gdata->tracked = false;
> +        return EN_UNHANDLED;
> +    }
> +
> +    gdata->tracked = true;
> +
> +    struct hmapx updated_routes = HMAPX_INITIALIZER(&updated_routes);
> +
> +    const struct hmapx_node *hmapx_node;
> +    const struct parsed_route *pr;
> +    HMAPX_FOR_EACH (hmapx_node,
> +                    
> &dynamic_routes_data->trk_data.trk_deleted_parsed_routes) {
> +        pr = hmapx_node->data;
> +        if (!handle_deleted_route(gdata, pr, &updated_routes)) {
> +            hmapx_destroy(&updated_routes);
> +            return EN_UNHANDLED;
> +        }
> +    }
> +
> +    HMAPX_FOR_EACH (hmapx_node,
> +                    
> &dynamic_routes_data->trk_data.trk_created_parsed_routes) {
> +        pr = hmapx_node->data;
> +        handle_added_route(gdata, pr, &updated_routes);
> +    }
> +
> +    HMAPX_FOR_EACH (hmapx_node, &updated_routes) {
> +        struct group_ecmp_datapath *node = hmapx_node->data;
> +        if (hmap_is_empty(&node->unique_routes) &&
> +                hmap_is_empty(&node->ecmp_groups)) {
> +            hmapx_add(&gdata->trk_data.deleted_datapath_routes, node);
> +            hmap_remove(&gdata->datapaths, &node->hmap_node);
> +        } else {
> +            hmapx_add(&gdata->trk_data.crupdated_datapath_routes, node);
> +        }
> +    }
> +
> +    hmapx_destroy(&updated_routes);
> +
> +    if (!(hmapx_is_empty(&gdata->trk_data.crupdated_datapath_routes) &&
> +          hmapx_is_empty(&gdata->trk_data.deleted_datapath_routes))) {
> +        return EN_HANDLED_UPDATED;
> +    }
> +    return EN_HANDLED_UNCHANGED;
> +}
> diff --git a/northd/en-group-ecmp-route.h b/northd/en-group-ecmp-route.h
> index d4a3248d0..0ebfe7442 100644
> --- a/northd/en-group-ecmp-route.h
> +++ b/northd/en-group-ecmp-route.h
> @@ -98,6 +98,10 @@ enum engine_input_handler_result
>  group_ecmp_route_learned_route_change_handler(struct engine_node *,
>                                                void *data);
>
> +enum engine_input_handler_result
> +group_ecmp_route_dynamic_routes_change_handler(struct engine_node *,
> +                                               void *data);
> +
>  struct group_ecmp_datapath *group_ecmp_datapath_lookup(
>      const struct group_ecmp_route_data *data,
>      const struct ovn_datapath *od);
> diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
> index 8376c2e1d..6805f16ec 100644
> --- a/northd/inc-proc-northd.c
> +++ b/northd/inc-proc-northd.c
> @@ -378,6 +378,11 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
>      engine_add_input(&en_group_ecmp_route, &en_routes, NULL);
>      engine_add_input(&en_group_ecmp_route, &en_learned_route_sync,
>                       group_ecmp_route_learned_route_change_handler);
> +    /* Connected-neighbour redistribute={lb,nat} also emits forwarding
> +     * parsed_routes. Consume those to compose ECMP groups alongside
> +     * routes and learned_route_sync. */
> +    engine_add_input(&en_group_ecmp_route, &en_dynamic_routes,
> +                     group_ecmp_route_dynamic_routes_change_handler);
>
>      engine_add_input(&en_sync_meters, &en_nb_acl, 
> sync_meters_nb_acl_handler);
>      engine_add_input(&en_sync_meters, &en_nb_meter, NULL);
> diff --git a/northd/northd.c b/northd/northd.c
> index 0a0a7e425..2eea687fb 100644
> --- a/northd/northd.c
> +++ b/northd/northd.c
> @@ -382,13 +382,16 @@ static const char *reg_ct_state[] = {
>   *  2. ic-learned connected routes with route_table set.
>   *  3. connected routes, including ic-learned.
>   *  4. static routes, including ic-learned.
> - *  5. routes learned from the outside via ovn-controller (e.g. bgp)
> - *  6. (lowest priority) src-ip routes */
> + *  5. routes synthesized from connected-neighbour
> + *     dynamic-routing-redistribute={lb,nat}.
> + *  6. routes learned from the outside via ovn-controller (e.g. bgp)
> + *  7. (lowest priority) src-ip routes */
>  #define ROUTE_PRIO_OFFSET_MULTIPLIER 12
>  #define ROUTE_PRIO_OFFSET_PRIORITY_STATIC 10
>  #define ROUTE_PRIO_OFFSET_IC_LEARNED_CONNECTED_WITH_TABLEID 8
>  #define ROUTE_PRIO_OFFSET_CONNECTED 6
>  #define ROUTE_PRIO_OFFSET_STATIC 4
> +#define ROUTE_PRIO_OFFSET_REDISTRIBUTE 3
>  #define ROUTE_PRIO_OFFSET_LEARNED 2
>
>  #define ROUTE_PRIO_BASE_SHIFT ((MAX_PREFIX_LEN + 1) * \
> @@ -12400,7 +12403,7 @@ find_static_route_outport(const struct ovn_datapath 
> *od,
>  /* Parse and validate the route. Return the parsed route if successful.
>   * Otherwise return NULL. */
>
> -static struct parsed_route *
> +struct parsed_route *
>  parsed_route_lookup(struct hmap *routes, size_t hash,
>                      struct parsed_route *new_pr)
>  {
> @@ -12846,12 +12849,19 @@ get_route_offset(enum route_source source,
>                 ? ROUTE_PRIO_OFFSET_PRIORITY_STATIC
>                 : ROUTE_PRIO_OFFSET_STATIC;
>
> +    case ROUTE_SOURCE_NAT:
> +    case ROUTE_SOURCE_LB:
> +        /* Priority offset for forwarding routes installed by
> +         * redistribute={lb,nat}. Placed above LEARNED so dynamically
> +         * learned routes for the same prefix cannot displace the locally
> +         * known nexthop, and below STATIC so operator-installed routes
> +         * still win. */
> +        return ROUTE_PRIO_OFFSET_REDISTRIBUTE;
> +
>      case ROUTE_SOURCE_LEARNED:
>          return ROUTE_PRIO_OFFSET_LEARNED;
>
> -    /* Dynamic route types (NAT, LB, and connected-as-host) are not used. */
> -    case ROUTE_SOURCE_NAT:
> -    case ROUTE_SOURCE_LB:
> +    /* connected-as-host advertisements don't produce forwarding routes. */
>      case ROUTE_SOURCE_CONNECTED_AS_HOST:
>      default:
>          OVS_NOT_REACHED();
> diff --git a/northd/northd.h b/northd/northd.h
> index c524bc2ae..60e94956b 100644
> --- a/northd/northd.h
> +++ b/northd/northd.h
> @@ -869,6 +869,8 @@ struct parsed_route {
>  };
>
>  struct parsed_route *parsed_route_clone(const struct parsed_route *);
> +struct parsed_route *parsed_route_lookup(struct hmap *routes, size_t hash,
> +                                         struct parsed_route *new_pr);
>  struct parsed_route *parsed_route_lookup_by_source(
>      enum route_source source, const struct ovsdb_idl_row *source_hint,
>      const struct hmap *routes);
> diff --git a/tests/ovn-inc-proc-graph-dump.at 
> b/tests/ovn-inc-proc-graph-dump.at
> index a1fc1d289..264146301 100644
> --- a/tests/ovn-inc-proc-graph-dump.at
> +++ b/tests/ovn-inc-proc-graph-dump.at
> @@ -166,9 +166,13 @@ digraph "Incremental-Processing-Engine" {
>         learned_route_sync [[style=filled, shape=box, fillcolor=white, 
> label="learned_route_sync"]];
>         SB_learned_route -> learned_route_sync 
> [[label="learned_route_sync_sb_learned_route_change_handler"]];
>         northd -> learned_route_sync 
> [[label="learned_route_sync_northd_change_handler"]];
> +       dynamic_routes [[style=filled, shape=box, fillcolor=white, 
> label="dynamic_routes"]];
> +       lr_stateful -> dynamic_routes 
> [[label="dynamic_routes_lr_stateful_change_handler"]];
> +       northd -> dynamic_routes 
> [[label="dynamic_routes_northd_change_handler"]];
>         group_ecmp_route [[style=filled, shape=box, fillcolor=white, 
> label="group_ecmp_route"]];
>         routes -> group_ecmp_route [[label=""]];
>         learned_route_sync -> group_ecmp_route 
> [[label="group_ecmp_route_learned_route_change_handler"]];
> +       dynamic_routes -> group_ecmp_route 
> [[label="group_ecmp_route_dynamic_routes_change_handler"]];
>         ls_stateful [[style=filled, shape=box, fillcolor=white, 
> label="ls_stateful"]];
>         northd -> ls_stateful [[label="ls_stateful_northd_handler"]];
>         port_group -> ls_stateful [[label="ls_stateful_port_group_handler"]];
> @@ -216,9 +220,6 @@ digraph "Incremental-Processing-Engine" {
>         SB_ecmp_nexthop -> ecmp_nexthop [[label=""]];
>         SB_port_binding -> ecmp_nexthop [[label=""]];
>         SB_mac_binding -> ecmp_nexthop 
> [[label="ecmp_nexthop_mac_binding_handler"]];
> -       dynamic_routes [[style=filled, shape=box, fillcolor=white, 
> label="dynamic_routes"]];
> -       lr_stateful -> dynamic_routes 
> [[label="dynamic_routes_lr_stateful_change_handler"]];
> -       northd -> dynamic_routes 
> [[label="dynamic_routes_northd_change_handler"]];
>         SB_advertised_route [[style=filled, shape=box, fillcolor=white, 
> label="SB_advertised_route"]];
>         advertised_route_sync [[style=filled, shape=box, fillcolor=white, 
> label="advertised_route_sync"]];
>         routes -> advertised_route_sync [[label=""]];
> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> index df5876d93..da67d54a7 100644
> --- a/tests/ovn-northd.at
> +++ b/tests/ovn-northd.at
> @@ -17026,7 +17026,12 @@ check_engine_compute northd incremental
>  check_engine_compute routes incremental
>  check_engine_compute advertised_route_sync recompute
>  check_engine_compute learned_route_sync incremental
> -check_engine_compute group_ecmp_route unchanged
> +# dynamic_routes recomputes on every en_northd update. the handler on
> +# en_group_ecmp_route returns UNCHANGED when no connected-neighbour
> +# LB/NAT forwarding routes are produced (this LR uses =connected-as-host,
> +# not =lb/=nat), but the call itself increments compute_ct so the state
> +# is "incremental" rather than "unchanged".
> +check_engine_compute group_ecmp_route incremental
>  check_engine_compute lflow incremental
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>
> @@ -17050,7 +17055,8 @@ check_engine_compute northd incremental
>  check_engine_compute routes incremental
>  check_engine_compute advertised_route_sync recompute
>  check_engine_compute learned_route_sync incremental
> -check_engine_compute group_ecmp_route unchanged
> +# See comment above re group_ecmp_route incremental vs unchanged.
> +check_engine_compute group_ecmp_route incremental
>  check_engine_compute lflow incremental
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>
> @@ -17831,6 +17837,615 @@ OVN_CLEANUP_NORTHD
>  AT_CLEANUP
>  ])
>
> +OVN_FOR_EACH_NORTHD_NO_HV([
> +AT_SETUP([dynamic-routing - LB redistribute installs local forwarding route])
> +AT_KEYWORDS([dynamic-routing])
> +ovn_start
> +
> +# When dynamic-routing-redistribute=lb is set on an LRP whose peer LS
> +# hosts a neighbouring LR with an attached load balancer, northd emits
> +# both an SB Advertised_Route entry and a /32 forwarding parsed_route
> +# on the advertising LR so it can forward traffic to the peer's VIP.
> +
> +check ovn-nbctl lr-add lr0
> +check ovn-nbctl set Logical_Router lr0 \
> +    options:dynamic-routing=true       \
> +    options:chassis=hv1
> +
> +# lr0's transit LRP toward 'up' is unnumbered (no IPv4 address).
> +# dynamic-routing-redistribute=lb is set on this LRP.
> +check ovn-nbctl lrp-add lr0 lr0-up 00:00:00:00:00:01
> +check ovn-nbctl lrp-set-options lr0-up dynamic-routing-redistribute=lb
> +check ovn-nbctl ls-add up
> +check ovn-nbctl lsp-add-router-port up up-lr0 lr0-up
> +
> +# lr1 is the neighbouring router that owns the load balancer.
> +# Its LRP on 'up' carries an IPv4 address (10.0.0.1) which becomes the
> +# nexthop for the forwarding route synthesised on lr0.
> +check ovn-nbctl lr-add lr1
> +check ovn-nbctl lrp-add lr1 lr1-up 00:00:00:00:00:02 10.0.0.1/24
> +check ovn-nbctl lsp-add-router-port up up-lr1 lr1-up
> +
> +check ovn-nbctl \
> +    -- lb-add lb0 172.16.1.10:80 192.168.1.10:80,192.168.1.11:80 \
> +    -- lr-lb-add lr1 lb0
> +check ovn-nbctl --wait=sb sync
> +
> +# An Advertised_Route entry for lb0's VIP is emitted on lr0 with
> +# tracked_port pointing at lr1's LRP.
> +datapath_lr0=$(fetch_column Datapath_Binding _uuid external_ids:name=lr0)
> +pb_lr0_up=$(fetch_column Port_Binding _uuid logical_port=lr0-up)
> +pb_lr1_up=$(fetch_column Port_Binding _uuid logical_port=lr1-up)
> +check_row_count Advertised_Route 1
> +check_row_count Advertised_Route 1 \
> +    ip_prefix="172.16.1.10"        \
> +    datapath=$datapath_lr0         \
> +    logical_port=$pb_lr0_up        \
> +    tracked_port=$pb_lr1_up
> +
> +# lr0 also gets a /32 forwarding flow with reg0 = lr1's LRP IP
> +# (10.0.0.1) and outport = lr0-up. Because lr0-up is unnumbered
> +# (no IPv4 address) there is no REG_SRC_IPV4 clause in the action.
> +ovn-sbctl lflow-list lr0 > lr0_flows
> +AT_CHECK([grep 'lr_in_ip_routing.*172.16.1.10/32' lr0_flows | 
> ovn_strip_lflows], [0], [dnl
> +  table=??(lr_in_ip_routing   ), priority=1935 , match=(ip4.dst == 
> 172.16.1.10/32), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 10.0.0.1; 
> eth.src = 00:00:00:00:00:01; outport = "lr0-up"; flags.loopback = 1; 
> reg9[[9]] = 1; next;)
> +])
> +
> +# Removing the redistribution option also removes the forwarding route.
> +check ovn-nbctl --wait=sb remove Logical_Router_Port lr0-up options 
> dynamic-routing-redistribute
> +ovn-sbctl lflow-list lr0 > lr0_flows_after_remove
> +AT_CHECK([grep -c 'lr_in_ip_routing.*172.16.1.10/32' 
> lr0_flows_after_remove], [1], [0
> +])
> +
> +OVN_CLEANUP_NORTHD
> +AT_CLEANUP
> +])
> +
> +OVN_FOR_EACH_NORTHD_NO_HV([
> +AT_SETUP([dynamic-routing - LB redistribute forwarding route - numbered LRP])
> +AT_KEYWORDS([dynamic-routing])
> +ovn_start
> +
> +# Same as "LB redistribute installs local forwarding route" but the
> +# advertising LRP is numbered (has an IPv4 address), so the emitted
> +# forwarding flow includes reg5 = <src-ip> (REG_SRC_IPV4).
> +
> +check ovn-nbctl lr-add lr0
> +check ovn-nbctl set Logical_Router lr0 \
> +    options:dynamic-routing=true       \
> +    options:chassis=hv1
> +check ovn-nbctl lrp-add lr0 lr0-up 00:00:00:00:00:01 10.0.0.2/24
> +check ovn-nbctl lrp-set-options lr0-up dynamic-routing-redistribute=lb
> +check ovn-nbctl ls-add up
> +check ovn-nbctl lsp-add-router-port up up-lr0 lr0-up
> +
> +check ovn-nbctl lr-add lr1
> +check ovn-nbctl lrp-add lr1 lr1-up 00:00:00:00:00:02 10.0.0.1/24
> +check ovn-nbctl lsp-add-router-port up up-lr1 lr1-up
> +
> +check ovn-nbctl \
> +    -- lb-add lb0 172.16.1.10:80 192.168.1.10:80,192.168.1.11:80 \
> +    -- lr-lb-add lr1 lb0
> +check ovn-nbctl --wait=sb sync
> +
> +# lr0-up is numbered (10.0.0.2) so reg5 = 10.0.0.2 appears in the action.
> +ovn-sbctl lflow-list lr0 > lr0_flows
> +AT_CHECK([grep 'lr_in_ip_routing.*172.16.1.10/32' lr0_flows | 
> ovn_strip_lflows], [0], [dnl
> +  table=??(lr_in_ip_routing   ), priority=1935 , match=(ip4.dst == 
> 172.16.1.10/32), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 10.0.0.1; reg5 = 
> 10.0.0.2; eth.src = 00:00:00:00:00:01; outport = "lr0-up"; flags.loopback = 
> 1; reg9[[9]] = 1; next;)
> +])
> +
> +OVN_CLEANUP_NORTHD
> +AT_CLEANUP
> +])
> +
> +OVN_FOR_EACH_NORTHD_NO_HV([
> +AT_SETUP([dynamic-routing - NAT redistribute forwarding route IPv4])
> +AT_KEYWORDS([dynamic-routing])
> +ovn_start
> +
> +# When dynamic-routing-redistribute=nat is set on an LRP whose peer LS
> +# hosts a neighbouring LR with a NAT external IP, northd emits a /32
> +# forwarding parsed_route on the advertising LR.
> +
> +check ovn-nbctl lr-add lr0
> +check ovn-nbctl set Logical_Router lr0 \
> +    options:dynamic-routing=true       \
> +    options:chassis=hv1
> +check ovn-nbctl lrp-add lr0 lr0-up 00:00:00:00:00:01
> +check ovn-nbctl lrp-set-options lr0-up dynamic-routing-redistribute=nat
> +check ovn-nbctl ls-add up
> +check ovn-nbctl lsp-add-router-port up up-lr0 lr0-up
> +
> +check ovn-nbctl lr-add lr1
> +check ovn-nbctl lrp-add lr1 lr1-up 00:00:00:00:00:02 10.0.0.1/24
> +check ovn-nbctl lrp-set-gateway-chassis lr1-up hv1
> +check ovn-nbctl lsp-add-router-port up up-lr1 lr1-up
> +check ovn-nbctl --add-route lr-nat-add lr1 dnat_and_snat 172.16.1.10 
> 192.168.1.10
> +check ovn-nbctl --wait=sb sync
> +
> +datapath_lr0=$(fetch_column Datapath_Binding _uuid external_ids:name=lr0)
> +pb_lr0_up=$(fetch_column Port_Binding _uuid logical_port=lr0-up)
> +pb_lr1_up=$(fetch_column Port_Binding _uuid logical_port=lr1-up)
> +
> +# SB Advertised_Route for lr1's NAT external IP is emitted on lr0.
> +check_row_count Advertised_Route 1
> +check_row_count Advertised_Route 1 \
> +    ip_prefix="172.16.1.10"        \
> +    datapath=$datapath_lr0          \
> +    logical_port=$pb_lr0_up         \
> +    tracked_port=$pb_lr1_up
> +
> +# lr0 also gets a /32 forwarding flow to lr1-up (10.0.0.1).
> +# Grep for priority 1935 specifically to avoid matching the connected
> +# host route (priority 1938) that --add-route also produces.
> +ovn-sbctl lflow-list lr0 > lr0_flows
> +AT_CHECK([grep 'lr_in_ip_routing.*172.16.1.10/32' lr0_flows | grep 
> 'priority=1935' | ovn_strip_lflows], [0], [dnl
> +  table=??(lr_in_ip_routing   ), priority=1935 , match=(ip4.dst == 
> 172.16.1.10/32), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 10.0.0.1; 
> eth.src = 00:00:00:00:00:01; outport = "lr0-up"; flags.loopback = 1; 
> reg9[[9]] = 1; next;)
> +])
> +
> +OVN_CLEANUP_NORTHD
> +AT_CLEANUP
> +])
> +
> +OVN_FOR_EACH_NORTHD_NO_HV([
> +AT_SETUP([dynamic-routing - NAT redistribute forwarding route IPv6])
> +AT_KEYWORDS([dynamic-routing])
> +ovn_start
> +
> +# IPv6 variant of the NAT redistribute forwarding route test.
> +
> +check ovn-nbctl lr-add lr0
> +check ovn-nbctl set Logical_Router lr0 \
> +    options:dynamic-routing=true       \
> +    options:chassis=hv1
> +check ovn-nbctl lrp-add lr0 lr0-up 00:00:00:00:00:01
> +check ovn-nbctl lrp-set-options lr0-up dynamic-routing-redistribute=nat
> +check ovn-nbctl ls-add up
> +check ovn-nbctl lsp-add-router-port up up-lr0 lr0-up
> +
> +check ovn-nbctl lr-add lr1
> +check ovn-nbctl lrp-add lr1 lr1-up 00:00:00:00:00:02 2001:db8::1/64
> +check ovn-nbctl lrp-set-gateway-chassis lr1-up hv1
> +check ovn-nbctl lsp-add-router-port up up-lr1 lr1-up
> +check ovn-nbctl --add-route lr-nat-add lr1 dnat_and_snat 2001:db8:ffff::10 
> 2001:db8:1::10
> +check ovn-nbctl --wait=sb sync
> +
> +datapath_lr0=$(fetch_column Datapath_Binding _uuid external_ids:name=lr0)
> +pb_lr0_up=$(fetch_column Port_Binding _uuid logical_port=lr0-up)
> +pb_lr1_up=$(fetch_column Port_Binding _uuid logical_port=lr1-up)
> +
> +check_row_count Advertised_Route 1
> +check_row_count Advertised_Route 1 \
> +    ip_prefix="2001\:db8\:ffff\:\:10"  \
> +    datapath=$datapath_lr0              \
> +    logical_port=$pb_lr0_up             \
> +    tracked_port=$pb_lr1_up
> +
> +# /128 forwarding flow for v6 NAT external IP.
> +ovn-sbctl lflow-list lr0 > lr0_flows
> +AT_CHECK([grep 'lr_in_ip_routing.*2001:db8:ffff::10/128' lr0_flows | 
> ovn_strip_lflows], [0], [dnl
> +  table=??(lr_in_ip_routing   ), priority=3087 , match=(ip6.dst == 
> 2001:db8:ffff::10/128), action=(ip.ttl--; reg8[[0..15]] = 0; xxreg0 = 
> 2001:db8::1; eth.src = 00:00:00:00:00:01; outport = "lr0-up"; flags.loopback 
> = 1; reg9[[9]] = 0; next;)
> +])
> +
> +OVN_CLEANUP_NORTHD
> +AT_CLEANUP
> +])
> +
> +OVN_FOR_EACH_NORTHD_NO_HV([
> +AT_SETUP([dynamic-routing - NAT redistribute distributed NAT tracked_port])
> +AT_KEYWORDS([dynamic-routing])
> +ovn_start
> +
> +# When a neighbouring LR has a distributed NAT (logical_port +
> +# external_mac set), the connected-neighbour path must use the
> +# backend LSP as tracked_port rather than the DGP.  The DGP must
> +# not be the same port that peers with the advertising LR, because
> +# that creates a cr_port on the peer LSP and disables distributed
> +# NAT (en-lr-nat.c: lr_nat_entry_set_dgw_port).
> +
> +check ovn-nbctl lr-add lr0
> +check ovn-nbctl set Logical_Router lr0 \
> +    options:dynamic-routing=true       \
> +    options:chassis=hv1
> +check ovn-nbctl lrp-add lr0 lr0-up 00:00:00:00:00:01
> +check ovn-nbctl lrp-set-options lr0-up dynamic-routing-redistribute=nat
> +check ovn-nbctl ls-add up
> +check ovn-nbctl lsp-add-router-port up up-lr0 lr0-up
> +
> +check ovn-nbctl lr-add lr1
> +check ovn-nbctl lrp-add lr1 lr1-up 00:00:00:00:00:02 10.0.0.1/24
> +check ovn-nbctl lsp-add-router-port up up-lr1 lr1-up
> +
> +# DGP on a separate port (lr1-ext), so lr1-up's peer LSP does not
> +# get a cr_port and the NAT stays distributed.
> +check ovn-nbctl lrp-add lr1 lr1-ext 00:00:00:00:00:04 192.168.2.1/24
> +check ovn-nbctl lrp-set-gateway-chassis lr1-ext hv1
> +check ovn-nbctl ls-add ext
> +check ovn-nbctl lsp-add-router-port ext ext-lr1 lr1-ext
> +check ovn-nbctl lsp-add ext ln-ext
> +check ovn-nbctl lsp-set-type ln-ext localnet
> +check ovn-nbctl lsp-set-options ln-ext network_name=phys
> +
> +check ovn-nbctl lrp-add lr1 lr1-be 00:00:00:00:00:03 192.168.1.1/24
> +check ovn-nbctl ls-add be
> +check ovn-nbctl lsp-add-router-port be be-lr1 lr1-be
> +check ovn-nbctl lsp-add be be-vm1
> +check ovn-nbctl lsp-set-addresses be-vm1 "00:00:00:00:01:01 192.168.1.10"
> +# Distributed NAT: logical_port and external_mac point to the backend LSP.
> +check ovn-nbctl --add-route lr-nat-add lr1 dnat_and_snat 172.16.1.10 
> 192.168.1.10 be-vm1 00:00:00:00:01:01
> +check ovn-nbctl --wait=sb sync
> +
> +datapath_lr0=$(fetch_column Datapath_Binding _uuid external_ids:name=lr0)
> +pb_lr0_up=$(fetch_column Port_Binding _uuid logical_port=lr0-up)
> +pb_be_vm1=$(fetch_column Port_Binding _uuid logical_port=be-vm1)
> +
> +# The advertised route must carry the backend LSP as tracked_port,
> +# not the DGP (lr1-ext).
> +check_row_count Advertised_Route 1
> +check_row_count Advertised_Route 1 \
> +    ip_prefix="172.16.1.10"        \
> +    datapath=$datapath_lr0          \
> +    logical_port=$pb_lr0_up         \
> +    tracked_port=$pb_be_vm1
> +
> +# No forwarding parsed_route is installed for distributed NAT: the
> +# backend LSP has no lrp_networks, so add_redistribute_parsed_route
> +# returns early.  Verify that lr0 has no /32 routing flow for the
> +# NAT external IP.
> +ovn-sbctl lflow-list lr0 > lr0_flows
> +AT_CHECK([grep 'lr_in_ip_routing.*172.16.1.10/32' lr0_flows], [1])
> +
> +OVN_CLEANUP_NORTHD
> +AT_CLEANUP
> +])
> +
> +OVN_FOR_EACH_NORTHD_NO_HV([
> +AT_SETUP([dynamic-routing - NAT redistribute own-LR distributed NAT])
> +AT_KEYWORDS([dynamic-routing])
> +ovn_start
> +
> +# When a router with a distributed NAT advertises its own NAT routes
> +# (build_nat_routes path), the Advertised_Route must use the backend
> +# LSP as tracked_port.
> +
> +check ovn-nbctl lr-add lr1
> +check ovn-nbctl set Logical_Router lr1 \
> +    options:dynamic-routing=true
> +
> +check ovn-nbctl lrp-add lr1 lr1-up 00:00:00:00:00:01 10.0.0.1/24
> +check ovn-nbctl lrp-set-options lr1-up dynamic-routing-redistribute=nat
> +check ovn-nbctl lrp-set-gateway-chassis lr1-up hv1
> +check ovn-nbctl ls-add up
> +check ovn-nbctl lsp-add-router-port up up-lr1 lr1-up
> +check ovn-nbctl lsp-add up ln-up
> +check ovn-nbctl lsp-set-type ln-up localnet
> +check ovn-nbctl lsp-set-options ln-up network_name=phys
> +
> +check ovn-nbctl lrp-add lr1 lr1-be 00:00:00:00:00:03 192.168.1.1/24
> +check ovn-nbctl ls-add be
> +check ovn-nbctl lsp-add-router-port be be-lr1 lr1-be
> +check ovn-nbctl lsp-add be be-vm1
> +check ovn-nbctl lsp-set-addresses be-vm1 "00:00:00:00:01:01 192.168.1.10"
> +
> +# Distributed NAT: logical_port and external_mac point to the backend LSP.
> +check ovn-nbctl --add-route lr-nat-add lr1 dnat_and_snat 172.16.1.10 
> 192.168.1.10 be-vm1 00:00:00:00:01:01
> +check ovn-nbctl --wait=sb sync
> +
> +datapath_lr1=$(fetch_column Datapath_Binding _uuid external_ids:name=lr1)
> +pb_lr1_up=$(fetch_column Port_Binding _uuid logical_port=lr1-up)
> +pb_be_vm1=$(fetch_column Port_Binding _uuid logical_port=be-vm1)
> +
> +# The own-LR advertised route must carry the backend LSP as tracked_port.
> +check_row_count Advertised_Route 1
> +check_row_count Advertised_Route 1 \
> +    ip_prefix="172.16.1.10"        \
> +    datapath=$datapath_lr1          \
> +    logical_port=$pb_lr1_up         \
> +    tracked_port=$pb_be_vm1
> +
> +OVN_CLEANUP_NORTHD
> +AT_CLEANUP
> +])
> +
> +OVN_FOR_EACH_NORTHD_NO_HV([
> +AT_SETUP([dynamic-routing - NAT redistribute connected-neighbour distributed 
> NAT on localnet LS])
> +AT_KEYWORDS([dynamic-routing])
> +ovn_start
> +
> +# A provider LS backed by a localnet port connects two LRs.  The
> +# neighbour LR has a distributed NAT whose DGP is on a separate
> +# provider LS (also localnet-backed).  The advertising LR must emit
> +# an Advertised_Route with the backend LSP as tracked_port and must
> +# NOT install a forwarding parsed_route (the backend LSP has no
> +# lrp_networks for the nexthop).
> +
> +check ovn-nbctl lr-add lr0
> +check ovn-nbctl set Logical_Router lr0 \
> +    options:dynamic-routing=true       \
> +    options:chassis=hv1
> +check ovn-nbctl lrp-add lr0 lr0-up 00:00:00:00:00:01 10.0.0.2/24
> +check ovn-nbctl lrp-set-options lr0-up dynamic-routing-redistribute=nat
> +check ovn-nbctl ls-add provider
> +check ovn-nbctl lsp-add-router-port provider prov-lr0 lr0-up
> +check ovn-nbctl lsp-add provider ln-prov
> +check ovn-nbctl lsp-set-type ln-prov localnet
> +check ovn-nbctl lsp-set-options ln-prov network_name=physnet
> +
> +check ovn-nbctl lr-add lr1
> +check ovn-nbctl lrp-add lr1 lr1-prov 00:00:00:00:00:02 10.0.0.1/24
> +check ovn-nbctl lsp-add-router-port provider prov-lr1 lr1-prov
> +
> +# DGP on a separate provider LS, so lr1-prov's peer LSP does not get
> +# a cr_port and the NAT stays distributed.
> +check ovn-nbctl lrp-add lr1 lr1-ext 00:00:00:00:00:04 192.168.2.1/24
> +check ovn-nbctl lrp-set-gateway-chassis lr1-ext hv1
> +check ovn-nbctl ls-add ext
> +check ovn-nbctl lsp-add-router-port ext ext-lr1 lr1-ext
> +check ovn-nbctl lsp-add ext ln-ext
> +check ovn-nbctl lsp-set-type ln-ext localnet
> +check ovn-nbctl lsp-set-options ln-ext network_name=physnet2
> +
> +check ovn-nbctl lrp-add lr1 lr1-be 00:00:00:00:00:03 192.168.1.1/24
> +check ovn-nbctl ls-add be
> +check ovn-nbctl lsp-add-router-port be be-lr1 lr1-be
> +check ovn-nbctl lsp-add be be-vm1
> +check ovn-nbctl lsp-set-addresses be-vm1 "00:00:00:00:01:01 192.168.1.10"
> +
> +# Distributed NAT: logical_port and external_mac point to the backend LSP.
> +check ovn-nbctl --add-route lr-nat-add lr1 dnat_and_snat 172.16.1.10 
> 192.168.1.10 be-vm1 00:00:00:00:01:01
> +check ovn-nbctl --wait=sb sync
> +
> +datapath_lr0=$(fetch_column Datapath_Binding _uuid external_ids:name=lr0)
> +pb_lr0_up=$(fetch_column Port_Binding _uuid logical_port=lr0-up)
> +pb_be_vm1=$(fetch_column Port_Binding _uuid logical_port=be-vm1)
> +
> +# Advertised_Route uses the backend LSP as tracked_port.
> +check_row_count Advertised_Route 1
> +check_row_count Advertised_Route 1 \
> +    ip_prefix="172.16.1.10"        \
> +    datapath=$datapath_lr0          \
> +    logical_port=$pb_lr0_up         \
> +    tracked_port=$pb_be_vm1
> +
> +# No forwarding parsed_route: the backend LSP has no lrp_networks,
> +# so add_redistribute_parsed_route returns early.
> +ovn-sbctl lflow-list lr0 > lr0_flows
> +AT_CHECK([grep 'lr_in_ip_routing.*172.16.1.10/32' lr0_flows], [1])
> +
> +OVN_CLEANUP_NORTHD
> +AT_CLEANUP
> +])
> +
> +OVN_FOR_EACH_NORTHD_NO_HV([
> +AT_SETUP([dynamic-routing - LB redistribute forwarding route IPv6])
> +AT_KEYWORDS([dynamic-routing])
> +ovn_start
> +
> +# IPv6 variant of the LB forwarding route test.
> +
> +check ovn-nbctl lr-add lr0
> +check ovn-nbctl set Logical_Router lr0 \
> +    options:dynamic-routing=true       \
> +    options:chassis=hv1
> +check ovn-nbctl lrp-add lr0 lr0-up 00:00:00:00:00:01
> +check ovn-nbctl lrp-set-options lr0-up dynamic-routing-redistribute=lb
> +check ovn-nbctl ls-add up
> +check ovn-nbctl lsp-add-router-port up up-lr0 lr0-up
> +
> +check ovn-nbctl lr-add lr1
> +check ovn-nbctl lrp-add lr1 lr1-up 00:00:00:00:00:02 2001:db8::1/64
> +check ovn-nbctl lsp-add-router-port up up-lr1 lr1-up
> +
> +check ovn-nbctl \
> +    -- lb-add lb0 [[2001:db8:ffff::10]]:80 
> [[2001:db8:1::10]]:80,[[2001:db8:1::11]]:80 \
> +    -- lr-lb-add lr1 lb0
> +check ovn-nbctl --wait=sb sync
> +
> +datapath_lr0=$(fetch_column Datapath_Binding _uuid external_ids:name=lr0)
> +pb_lr0_up=$(fetch_column Port_Binding _uuid logical_port=lr0-up)
> +pb_lr1_up=$(fetch_column Port_Binding _uuid logical_port=lr1-up)
> +
> +check_row_count Advertised_Route 1
> +check_row_count Advertised_Route 1 \
> +    ip_prefix="2001\:db8\:ffff\:\:10"   \
> +    datapath=$datapath_lr0               \
> +    logical_port=$pb_lr0_up              \
> +    tracked_port=$pb_lr1_up
> +
> +# /128 forwarding flow for v6 LB VIP.
> +ovn-sbctl lflow-list lr0 > lr0_flows
> +AT_CHECK([grep 'lr_in_ip_routing.*2001:db8:ffff::10/128' lr0_flows | 
> ovn_strip_lflows], [0], [dnl
> +  table=??(lr_in_ip_routing   ), priority=3087 , match=(ip6.dst == 
> 2001:db8:ffff::10/128), action=(ip.ttl--; reg8[[0..15]] = 0; xxreg0 = 
> 2001:db8::1; eth.src = 00:00:00:00:00:01; outport = "lr0-up"; flags.loopback 
> = 1; reg9[[9]] = 0; next;)
> +])
> +
> +OVN_CLEANUP_NORTHD
> +AT_CLEANUP
> +])
> +
> +OVN_FOR_EACH_NORTHD_NO_HV([
> +AT_SETUP([dynamic-routing - LB redistribute forwarding route via LS])
> +AT_KEYWORDS([dynamic-routing])
> +ovn_start
> +
> +# Two LRs (lr1, lr2) connected to a shared LS "join", both with LBs.
> +# lr0 has redistribute=lb on its LRP toward "join". lr0's peer on "join"
> +# is an LSP (not a direct LR-LR peer), so northd discovers lr1 and lr2
> +# through the LS's router_ports. Both LB VIPs get forwarding
> +# routes on lr0.
> +
> +check ovn-nbctl lr-add lr0
> +check ovn-nbctl set Logical_Router lr0 \
> +    options:dynamic-routing=true       \
> +    options:chassis=hv1
> +check ovn-nbctl lrp-add lr0 lr0-join 00:00:00:00:00:01
> +check ovn-nbctl lrp-set-options lr0-join dynamic-routing-redistribute=lb
> +check ovn-nbctl ls-add join
> +check ovn-nbctl lsp-add-router-port join join-lr0 lr0-join
> +
> +check ovn-nbctl lr-add lr1
> +check ovn-nbctl lrp-add lr1 lr1-join 00:00:00:00:00:02 10.0.0.1/24
> +check ovn-nbctl lsp-add-router-port join join-lr1 lr1-join
> +check ovn-nbctl \
> +    -- lb-add lb1 172.16.1.10:80 192.168.1.10:80 \
> +    -- lr-lb-add lr1 lb1
> +
> +check ovn-nbctl lr-add lr2
> +check ovn-nbctl lrp-add lr2 lr2-join 00:00:00:00:00:03 10.0.0.2/24
> +check ovn-nbctl lsp-add-router-port join join-lr2 lr2-join
> +check ovn-nbctl \
> +    -- lb-add lb2 172.16.2.10:80 192.168.2.10:80 \
> +    -- lr-lb-add lr2 lb2
> +
> +check ovn-nbctl --wait=sb sync
> +
> +datapath_lr0=$(fetch_column Datapath_Binding _uuid external_ids:name=lr0)
> +pb_lr0_join=$(fetch_column Port_Binding _uuid logical_port=lr0-join)
> +pb_lr1_join=$(fetch_column Port_Binding _uuid logical_port=lr1-join)
> +pb_lr2_join=$(fetch_column Port_Binding _uuid logical_port=lr2-join)
> +
> +# Two advertised routes (one per neighbour LB VIP).
> +check_row_count Advertised_Route 2
> +check_row_count Advertised_Route 1 \
> +    ip_prefix="172.16.1.10"         \
> +    datapath=$datapath_lr0           \
> +    logical_port=$pb_lr0_join        \
> +    tracked_port=$pb_lr1_join
> +check_row_count Advertised_Route 1 \
> +    ip_prefix="172.16.2.10"         \
> +    datapath=$datapath_lr0           \
> +    logical_port=$pb_lr0_join        \
> +    tracked_port=$pb_lr2_join
> +
> +# Two forwarding flows on lr0, one per VIP, each with the correct
> +# nexthop pointing at the respective neighbour LRP IP.
> +ovn-sbctl lflow-list lr0 > lr0_flows
> +AT_CHECK([grep 'lr_in_ip_routing.*172.16.1.10/32' lr0_flows | 
> ovn_strip_lflows], [0], [dnl
> +  table=??(lr_in_ip_routing   ), priority=1935 , match=(ip4.dst == 
> 172.16.1.10/32), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 10.0.0.1; 
> eth.src = 00:00:00:00:00:01; outport = "lr0-join"; flags.loopback = 1; 
> reg9[[9]] = 1; next;)
> +])
> +AT_CHECK([grep 'lr_in_ip_routing.*172.16.2.10/32' lr0_flows | 
> ovn_strip_lflows], [0], [dnl
> +  table=??(lr_in_ip_routing   ), priority=1935 , match=(ip4.dst == 
> 172.16.2.10/32), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 10.0.0.2; 
> eth.src = 00:00:00:00:00:01; outport = "lr0-join"; flags.loopback = 1; 
> reg9[[9]] = 1; next;)
> +])
> +
> +OVN_CLEANUP_NORTHD
> +AT_CLEANUP
> +])
> +
> +OVN_FOR_EACH_NORTHD_NO_HV([
> +AT_SETUP([dynamic-routing - LB forwarding route updates on nexthop change])
> +AT_KEYWORDS([dynamic-routing])
> +ovn_start
> +
> +# Regression test: parsed_route_lookup must treat routes with different
> +# nexthops as distinct.  Create a forwarding route for an LB VIP with
> +# nexthop 10.0.0.1, then change the peer LRP address to 10.0.0.42 and
> +# verify the logical flow is updated.
> +
> +check ovn-nbctl lr-add lr0
> +check ovn-nbctl set Logical_Router lr0 \
> +    options:dynamic-routing=true       \
> +    options:chassis=hv1
> +check ovn-nbctl lrp-add lr0 lr0-up 00:00:00:00:00:01 10.0.0.2/24
> +check ovn-nbctl lrp-set-options lr0-up dynamic-routing-redistribute=lb
> +check ovn-nbctl ls-add up
> +check ovn-nbctl lsp-add-router-port up up-lr0 lr0-up
> +
> +check ovn-nbctl lr-add lr1
> +check ovn-nbctl lrp-add lr1 lr1-up 00:00:00:00:00:02 10.0.0.1/24
> +check ovn-nbctl lsp-add-router-port up up-lr1 lr1-up
> +
> +check ovn-nbctl \
> +    -- lb-add lb0 172.16.1.10:80 192.168.1.10:80 \
> +    -- lr-lb-add lr1 lb0
> +check ovn-nbctl --wait=sb sync
> +
> +# Forwarding flow on lr0 points at 10.0.0.1 (lr1-up's address).
> +ovn-sbctl lflow-list lr0 > lr0_flows
> +AT_CHECK([grep 'lr_in_ip_routing.*172.16.1.10/32' lr0_flows | 
> ovn_strip_lflows], [0], [dnl
> +  table=??(lr_in_ip_routing   ), priority=1935 , match=(ip4.dst == 
> 172.16.1.10/32), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 10.0.0.1; reg5 = 
> 10.0.0.2; eth.src = 00:00:00:00:00:01; outport = "lr0-up"; flags.loopback = 
> 1; reg9[[9]] = 1; next;)
> +])
> +
> +# Change lr1-up's address from 10.0.0.1 to 10.0.0.42.
> +check ovn-nbctl --wait=sb set Logical_Router_Port lr1-up 
> networks=\"10.0.0.42/24\"
> +
> +# The forwarding flow must now use 10.0.0.42 as nexthop.
> +ovn-sbctl lflow-list lr0 > lr0_flows_after
> +AT_CHECK([grep 'lr_in_ip_routing.*172.16.1.10/32' lr0_flows_after | 
> ovn_strip_lflows], [0], [dnl
> +  table=??(lr_in_ip_routing   ), priority=1935 , match=(ip4.dst == 
> 172.16.1.10/32), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 10.0.0.42; reg5 
> = 10.0.0.2; eth.src = 00:00:00:00:00:01; outport = "lr0-up"; flags.loopback = 
> 1; reg9[[9]] = 1; next;)
> +])
> +
> +# The old nexthop (10.0.0.1) must no longer appear in the flow.
> +AT_CHECK([grep 'lr_in_ip_routing.*172.16.1.10/32' lr0_flows_after | grep -c 
> 'reg0 = 10.0.0.1' || true], [0], [0
> +])
> +
> +OVN_CLEANUP_NORTHD
> +AT_CLEANUP
> +])
> +
> +OVN_FOR_EACH_NORTHD_NO_HV([
> +AT_SETUP([dynamic-routing - LB redistribute advertise=false skips forwarding 
> route])
> +AT_KEYWORDS([dynamic-routing])
> +ovn_start
> +
> +# When dynamic-routing-redistribute=lb is set on an LRP but the LB has
> +# options:dynamic-routing-advertise=false, both the Advertised_Route row
> +# and the forwarding parsed route / logical flow are not installed.
> +
> +check ovn-nbctl lr-add lr0
> +check ovn-nbctl set Logical_Router lr0 \
> +    options:dynamic-routing=true       \
> +    options:chassis=hv1
> +check ovn-nbctl lrp-add lr0 lr0-up 00:00:00:00:00:01 10.0.0.2/24
> +check ovn-nbctl lrp-set-options lr0-up dynamic-routing-redistribute=lb
> +check ovn-nbctl ls-add up
> +check ovn-nbctl lsp-add-router-port up up-lr0 lr0-up
> +
> +check ovn-nbctl lr-add lr1
> +check ovn-nbctl lrp-add lr1 lr1-up 00:00:00:00:00:02 10.0.0.1/24
> +check ovn-nbctl lsp-add-router-port up up-lr1 lr1-up
> +
> +check ovn-nbctl \
> +    -- lb-add lb0 172.16.1.10:80 192.168.1.10:80 \
> +    -- set Load_Balancer lb0 options:dynamic-routing-advertise=false \
> +    -- lr-lb-add lr1 lb0
> +check ovn-nbctl --wait=sb sync
> +
> +# No Advertised_Route should be emitted.
> +check_row_count Advertised_Route 0
> +
> +# No forwarding flow for the LB VIP.
> +ovn-sbctl lflow-list lr0 > lr0_flows
> +AT_CHECK([grep -c 'lr_in_ip_routing.*172.16.1.10/32' lr0_flows || true], 
> [0], [0
> +])
> +
> +# Enabling advertise should produce both.
> +check ovn-nbctl --wait=sb remove Load_Balancer lb0 options 
> dynamic-routing-advertise
> +
> +datapath_lr0=$(fetch_column Datapath_Binding _uuid external_ids:name=lr0)
> +pb_lr0_up=$(fetch_column Port_Binding _uuid logical_port=lr0-up)
> +pb_lr1_up=$(fetch_column Port_Binding _uuid logical_port=lr1-up)
> +check_row_count Advertised_Route 1 \
> +    ip_prefix="172.16.1.10"        \
> +    datapath=$datapath_lr0          \
> +    logical_port=$pb_lr0_up         \
> +    tracked_port=$pb_lr1_up
> +
> +ovn-sbctl lflow-list lr0 > lr0_flows_on
> +AT_CHECK([grep 'lr_in_ip_routing.*172.16.1.10/32' lr0_flows_on | 
> ovn_strip_lflows], [0], [dnl
> +  table=??(lr_in_ip_routing   ), priority=1935 , match=(ip4.dst == 
> 172.16.1.10/32), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 10.0.0.1; reg5 = 
> 10.0.0.2; eth.src = 00:00:00:00:00:01; outport = "lr0-up"; flags.loopback = 
> 1; reg9[[9]] = 1; next;)
> +])
> +
> +# Disabling again should withdraw both.
> +check ovn-nbctl --wait=sb set Load_Balancer lb0 
> options:dynamic-routing-advertise=false
> +check_row_count Advertised_Route 0
> +
> +ovn-sbctl lflow-list lr0 > lr0_flows_off
> +AT_CHECK([grep -c 'lr_in_ip_routing.*172.16.1.10/32' lr0_flows_off || true], 
> [0], [0
> +])
> +
> +OVN_CLEANUP_NORTHD
> +AT_CLEANUP
> +])
> +
>  OVN_FOR_EACH_NORTHD_NO_HV([
>  AT_SETUP([dynamic-routing - LB sync to sb IPv6])
>  AT_KEYWORDS([dynamic-routing])
> --
> 2.53.0
>
>
> _______________________________________________
> dev mailing list
> [email protected]
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to