Hi Alexandra, thanks for the patch!

I have a couple of small notes below. They're so minor, that I think
that a maintainer can fix them when merging, so there is no need for a
v4.

Acked-by: Mark Michelson <[email protected]>

On Mon, Mar 2, 2026 at 7:35 AM Alexandra Rukomoinikova
<[email protected]> wrote:
>
> The learned routes hash previously only used route parameters (prefix,
> nexthop, origin, route_table). This caused collisions when identical
> routes were learned through different transit switches, leading to
> infinite route re-learning.
>
> While such configurations are invalid due to overlapping IP addressing
> - ICshould handle them without looping.
>
> Fix by including the IC route UUID in the hash to distinguish routes
> with identical parameters but different origins. As a result, routers
> will now learn the correct number of prefixes as ECMP routes, making
> the misconfiguration visible to the user.
>
> Signed-off-by: Alexandra Rukomoinikova <[email protected]>
> ---
> v1 --> v2: fixed typo in the tests
> v2 --> v3: fixed hash calculation
> ---
>  ic/ovn-ic.c     | 56 ++++++++++++++--------------
>  tests/ovn-ic.at | 98 +++++++++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 127 insertions(+), 27 deletions(-)
>
> diff --git a/ic/ovn-ic.c b/ic/ovn-ic.c
> index 95d73cb4b..7d2f9442a 100644
> --- a/ic/ovn-ic.c
> +++ b/ic/ovn-ic.c
> @@ -1286,6 +1286,7 @@ struct ic_route_info {
>      const char *origin;
>      const char *route_table;
>      const char *route_tag;
> +    struct uuid ic_route_uuid;
>
>      const struct nbrec_logical_router *nb_lr;
>
> @@ -1305,9 +1306,11 @@ struct ic_route_info {
>  static uint32_t
>  ic_route_hash(const struct in6_addr *prefix, unsigned int plen,
>                const struct in6_addr *nexthop, const char *origin,
> -              const char *route_table)
> +              const char *route_table, const struct uuid *ic_route_uuid)
>  {
> -    uint32_t basis = hash_bytes(prefix, sizeof *prefix, (uint32_t)plen);
> +    uint32_t basis = ic_route_uuid ? uuid_hash(ic_route_uuid) : 0;
> +    basis = hash_bytes(prefix, sizeof *prefix, basis);
> +    basis = hash_int((uint32_t) plen, basis);
>      basis = hash_string(origin, basis);
>      basis = hash_string(route_table, basis);
>      return hash_bytes(nexthop, sizeof *nexthop, basis);
> @@ -1316,18 +1319,22 @@ ic_route_hash(const struct in6_addr *prefix, unsigned 
> int plen,
>  static struct ic_route_info *
>  ic_route_find(struct hmap *routes, const struct in6_addr *prefix,
>                unsigned int plen, const struct in6_addr *nexthop,
> -              const char *origin, const char *route_table, uint32_t hash)
> +              const char *origin, const char *route_table,
> +              const struct uuid *ic_route_uuid, uint32_t hash)
>  {
>      struct ic_route_info *r;
>      if (!hash) {
> -        hash = ic_route_hash(prefix, plen, nexthop, origin, route_table);
> +        hash = ic_route_hash(prefix, plen, nexthop, origin, route_table,
> +                             ic_route_uuid);
>      }
>      HMAP_FOR_EACH_WITH_HASH (r, node, hash, routes) {
>          if (ipv6_addr_equals(&r->prefix, prefix) &&
>              r->plen == plen &&
>              ipv6_addr_equals(&r->nexthop, nexthop) &&
>              !strcmp(r->origin, origin) &&
> -            !strcmp(r->route_table ? r->route_table : "", route_table)) {
> +            !strcmp(r->route_table ? r->route_table : "", route_table) &&
> +            (!ic_route_uuid || uuid_equals(&r->ic_route_uuid,
> +                                           ic_route_uuid))) {
>              return r;
>          }
>      }
> @@ -1370,7 +1377,8 @@ parse_route(const char *s_prefix, const char *s_nexthop,
>  static bool
>  add_to_routes_learned(struct hmap *routes_learned,
>                        const struct nbrec_logical_router_static_route 
> *nb_route,
> -                      const struct nbrec_logical_router *nb_lr)
> +                      const struct nbrec_logical_router *nb_lr,
> +                      const struct uuid *ic_route_uuid)
>  {
>      struct in6_addr prefix, nexthop;
>      unsigned int plen;
> @@ -1379,8 +1387,11 @@ add_to_routes_learned(struct hmap *routes_learned,
>          return false;
>      }
>      const char *origin = smap_get_def(&nb_route->options, "origin", "");
> +
> +    uint32_t hash = ic_route_hash(&prefix, plen, &nexthop, origin,
> +                                  nb_route->route_table, ic_route_uuid);
>      if (ic_route_find(routes_learned, &prefix, plen, &nexthop, origin,
> -                      nb_route->route_table, 0)) {
> +                      nb_route->route_table, ic_route_uuid, hash)) {
>          /* Route was added to learned on previous iteration. */
>          return true;
>      }
> @@ -1393,9 +1404,9 @@ add_to_routes_learned(struct hmap *routes_learned,
>      ic_route->origin = origin;
>      ic_route->route_table = nb_route->route_table;
>      ic_route->nb_lr = nb_lr;
> -    hmap_insert(routes_learned, &ic_route->node,
> -                ic_route_hash(&prefix, plen, &nexthop, origin,
> -                              nb_route->route_table));
> +    memcpy(&ic_route->ic_route_uuid, ic_route_uuid, sizeof(struct uuid));

There's no need for memcpy here. You can just do a struct assignment:

ic_route->ic_route_uuid = *ic_route_uuid;

> +    hmap_insert(routes_learned, &ic_route->node, hash);
> +
>      return true;
>  }
>
> @@ -1562,10 +1573,11 @@ add_to_routes_ad(struct hmap *routes_ad, const struct 
> in6_addr prefix,
>          route_table = "";
>      }
>
> -    uint hash = ic_route_hash(&prefix, plen, &nexthop, origin, route_table);
> +    uint hash = ic_route_hash(&prefix, plen, &nexthop, origin,
> +                              route_table, NULL);
>
>      if (!ic_route_find(routes_ad, &prefix, plen, &nexthop, origin, 
> route_table,
> -                       hash)) {
> +                       NULL, hash)) {
>          struct ic_route_info *ic_route = xzalloc(sizeof *ic_route);
>          ic_route->prefix = prefix;
>          ic_route->plen = plen;
> @@ -2154,20 +2166,9 @@ sync_learned_routes(struct ic_context *ctx,
>              struct ic_route_info *route_learned
>                  = ic_route_find(&ic_lr->routes_learned, &prefix, plen,
>                                  &nexthop, isb_route->origin,
> -                                isb_route->route_table, 0);
> +                                isb_route->route_table,
> +                                &isb_route->header_.uuid, 0);
>              if (route_learned) {
> -                /* Sync external-ids */
> -                struct uuid ext_id;
> -                smap_get_uuid(&route_learned->nb_route->external_ids,
> -                              "ic-learned-route", &ext_id);
> -                if (!uuid_equals(&ext_id, &isb_route->header_.uuid)) {
> -                    char *uuid_s =
> -                        xasprintf(UUID_FMT,
> -                                  UUID_ARGS(&isb_route->header_.uuid));
> -                    
> nbrec_logical_router_static_route_update_external_ids_setkey(
> -                        route_learned->nb_route, "ic-learned-route", uuid_s);
> -                    free(uuid_s);
> -                }
>                  hmap_remove(&ic_lr->routes_learned, &route_learned->node);
>                  free(route_learned);
>              } else {
> @@ -2275,7 +2276,7 @@ advertise_routes(struct ic_context *ctx,
>          }
>          struct ic_route_info *route_adv =
>              ic_route_find(routes_ad, &prefix, plen, &nexthop,
> -                          isb_route->origin, isb_route->route_table, 0);
> +                          isb_route->origin, isb_route->route_table, NULL, 
> 0);
>          if (!route_adv) {
>              /* Delete the extra route from IC-SB. */
>              VLOG_DBG("Delete route %s -> %s from IC-SB, which is not found"
> @@ -2349,7 +2350,8 @@ build_ts_routes_to_adv(struct ic_context *ctx,
>          if (smap_get_uuid(&nb_route->external_ids, "ic-learned-route",
>                            &isb_uuid)) {
>              /* It is a learned route */
> -            if (!add_to_routes_learned(&ic_lr->routes_learned, nb_route, 
> lr)) {
> +            if (!add_to_routes_learned(&ic_lr->routes_learned, nb_route, lr,
> +                                       &isb_uuid)) {
>                  static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 
> 1);
>                  VLOG_WARN_RL(&rl, "Bad format of learned route in NB: "
>                               "%s -> %s. Delete it.", nb_route->ip_prefix,
> diff --git a/tests/ovn-ic.at b/tests/ovn-ic.at
> index 1a826aa1c..c633cdeb4 100644
> --- a/tests/ovn-ic.at
> +++ b/tests/ovn-ic.at
> @@ -4594,3 +4594,101 @@ OVN_CLEANUP_IC([az1], [az2])
>
>  AT_CLEANUP
>  ])
> +
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([ovn-ic -- check loop with annonce duplicated prefixes - multiple 
> ts])

s/annonce/announce/

The same mistake happens a few other times in this test as well.



> +
> +ovn_init_ic_db
> +ovn-ic-nbctl ts-add ts11
> +ovn-ic-nbctl ts-add ts12
> +
> +for i in 1 2; do
> +    ovn_start az$i
> +    ovn_as az$i
> +
> +    # Enable route learning at AZ level
> +    check ovn-nbctl set nb_global . options:ic-route-learn=true
> +    # Enable route advertising at AZ level
> +    check ovn-nbctl set nb_global . options:ic-route-adv=true
> +done
> +
> +for i in 1 2; do
> +    ovn_as az$i
> +
> +    check ovn-nbctl lr-add lr$i
> +
> +    check ovn-nbctl lrp-add lr$i lr-lrp-$i aa:aa:aa:aa:a1:0$i 
> 169.254.101.1/24
> +    check ovn-nbctl lsp-add-router-port ts11 lsp-az$i lr-lrp-$i
> +
> +    # Create subnet for annonce.
> +    check ovn-nbctl lrp-add lr$i lr-lrp-subnet-$i aa:aa:aa:aa:a2:0$i 
> 192.168.0.$i/24
> +
> +    # Create one more lrp-ts port through another transit switch.
> +    check ovn-nbctl lrp-add lr$i lr-lrp-dup$i aa:aa:aa:aa:a3:01 
> 169.254.101.1/24
> +    check ovn-nbctl lsp-add-router-port ts12 lsp-dup-az$i lr-lrp-dup$i
> +done
> +
> +OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr1 | grep 192.168 |
> +                     grep learned | awk '{print $1, $2}' | sort ], [0], [dnl
> +192.168.0.0/24 169.254.101.1
> +192.168.0.0/24 169.254.101.1
> +])
> +
> +OVN_CLEANUP_IC([az1], [az2])
> +AT_CLEANUP
> +])
> +
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([ovn-ic -- check loop with annonce duplicated prefixes - single ts])
> +
> +ovn_init_ic_db
> +
> +for i in 1 2 3; do
> +    ovn_start az$i
> +    ovn_as az$i
> +
> +    # Enable route learning at AZ level
> +    check ovn-nbctl set nb_global . options:ic-route-learn=true
> +    # Enable route advertising at AZ level
> +    check ovn-nbctl set nb_global . options:ic-route-adv=true
> +done
> +
> +# Create new transit switches and LRs. Test topology is next:
> +#                            logical router (lr12)
> +#                                   |
> +# logical router (lr11) -  / transit switch (ts11) \ - logical router (lr13)
> +#
> +
> +# Create lr11, lr13, ts11 and connect them
> +for i in 1 3; do
> +    ovn_as az$i
> +
> +    check ovn-nbctl lr-add lr1$i
> +    check ovn-ic-nbctl --wait=sb --may-exist ts-add ts11
> +
> +    # Create LRP and connect to TS
> +    check ovn-nbctl lrp-add lr1$i lrp-lr1$i-ts11 aa:aa:aa:aa:a1:0$i 
> 169.254.101.1/24
> +    check ovn-nbctl lsp-add-router-port ts11 lsp-ts11-lr1$i lrp-lr1$i-ts11
> +done
> +
> +# Create lr12 and connect it to ts11
> +ovn_as az2
> +check ovn-nbctl lr-add lr12
> +
> +# Create LRP and connect to TS
> +check ovn-nbctl lrp-add lr12 lrp-lr12-ts11 aa:aa:aa:aa:a1:03 169.254.101.2/24
> +check ovn-nbctl lsp-add-router-port ts11 lsp-lr12-ts11 lrp-lr12-ts11
> +
> +# Create directly-connected route in lr11
> +check ovn_as az1 ovn-nbctl lrp-add lr11 lrp-lr11 aa:aa:aa:aa:bb:01 
> "192.168.0.1/24"
> +check ovn_as az3 ovn-nbctl lrp-add lr13 lrp-lr13 aa:aa:aa:aa:bb:03 
> "192.168.0.1/24"
> +
> +OVS_WAIT_FOR_OUTPUT([ovn_as az2 ovn-nbctl lr-route-list lr12 | grep 192.168 |
> +                     grep learned | awk '{print $1, $2}' | sort ], [0], [dnl
> +192.168.0.0/24 169.254.101.1
> +192.168.0.0/24 169.254.101.1
> +])
> +
> +OVN_CLEANUP_IC([az1], [az2], [az3])
> +AT_CLEANUP
> +])
> --
> 2.48.1
>
> _______________________________________________
> 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