Recheck-request: github-robot-_Build_and_Test

On Friday, June 12th, 2026 at 6:35 AM, Dmitrii Shcherbakov 
<[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.
> 
> Also compare tracked_port in parsed_route_lookup() so that forwarding
> routes with different tracked_port values are not treated as identical.
> 
> Signed-off-by: Dmitrii Shcherbakov <[email protected]>
> ---
>  northd/en-advertised-route-sync.c | 263 ++++++++++++-
>  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                   |  26 +-
>  northd/northd.h                   |   2 +
>  tests/ovn-inc-proc-graph-dump.at  |   7 +-
>  tests/ovn-northd.at               | 619 +++++++++++++++++++++++++++++-
>  9 files changed, 994 insertions(+), 33 deletions(-)
> 
> diff --git a/northd/en-advertised-route-sync.c 
> b/northd/en-advertised-route-sync.c
> index 4a8d13232..fa8bcd697 100644
> --- a/northd/en-advertised-route-sync.c
> +++ b/northd/en-advertised-route-sync.c
> @@ -166,16 +166,104 @@ 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)
> +{
> +    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,
> +                     NULL,
> +                     tracked_port,
> +                     parsed_routes_out);
> +}
> +
>  /* 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 +291,21 @@ 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);
> +        }
>      }
>  }
>  
>  /* 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 +318,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 +359,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 +384,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 +397,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 +417,20 @@ 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);
> +        }
>      }
>      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);
> +        }
>      }
>  }
>  
> @@ -343,7 +465,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 +482,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 +507,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 +652,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 +686,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 +731,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 +746,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 +779,53 @@ 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 (pr, key_node, old_parsed_routes) {
> +        pr->stale = true;
> +    }
> +
> +    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) {
> +            old_pr->stale = false;
> +            /* 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. */
> +            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) {
> +        if (pr->stale) {
> +            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 +1026,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 a2b464411..62ddf4207 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 264cdd7a6..652c8ac8a 100644
> --- a/northd/northd.c
> +++ b/northd/northd.c
> @@ -381,13 +381,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) * \
> @@ -12295,7 +12298,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)
>  {
> @@ -12351,6 +12354,10 @@ parsed_route_lookup(struct hmap *routes, size_t hash,
>              continue;
>          }
>  
> +        if (pr->tracked_port != new_pr->tracked_port) {
> +            continue;
> +        }
> +
>          if (!nullable_string_is_equal(pr->lrp_addr_s,
>                                        new_pr->lrp_addr_s)) {
>              continue;
> @@ -12741,12 +12748,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 b1168c207..3d67381cc 100644
> --- a/northd/northd.h
> +++ b/northd/northd.h
> @@ -861,6 +861,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(
>      const struct ovn_datapath *od, 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 3750339d0..b81352657 100644
> --- a/tests/ovn-inc-proc-graph-dump.at
> +++ b/tests/ovn-inc-proc-graph-dump.at
> @@ -167,9 +167,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"]];
> @@ -217,9 +221,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 51c5b486a..aa3e14410 100644
> --- a/tests/ovn-northd.at
> +++ b/tests/ovn-northd.at
> @@ -16945,7 +16945,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
>  
> @@ -16969,7 +16974,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
>  
> @@ -17750,6 +17756,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

Reply via email to