On Mon, Apr 13, 2026 at 6:37 PM Lucas Vargas Dias via dev < [email protected]> wrote:
> Consider the scenario where there are 2 AZs using ovn-ic, each AZ has > one LR and its LS. The LRs are interconnected by a transit switch. > On each AZ, we can configure the dynamic routing and exchange routes > via BGP with external BGP speakers. > > AZ1 > LS1 - LR1 - BGP Speaker1 - Local subnet1 > \ > ----------------------- > transit switch > ----------------------- > AZ2 / > LS2 - LR2 - BGP Speaker2 - Local subnet2 > > The LR2 will learn the subnet1 prefix via ovn-ic but this prefix will > not be redistributed to BGP Speaker2 and it also happens with LR1. > This scenario uses the network's hub-and-spoke terminology where we can > enable the hub-spoke on the LR for such redistribution. > subnet1 and subnet2 are configured as ic-source-dynamic=true in ovn-ic. > Also, consider the same scenario, but LR1 and LR2 learn the same subnet > for redundancy, hub-spoke option is disabled to not adv the subnet learned > from BGP and advertised in ovn-ic in BGP speakers. > > Signed-off-by: Lucas Vargas Dias <[email protected]> > --- > Hi Lucas, thank you for the v4. I have some small comments down below. > NEWS | 2 + > ic/ovn-ic.c | 20 ++++-- > lib/ovn-util.h | 1 + > northd/en-advertised-route-sync.c | 2 + > northd/northd.c | 8 +++ > northd/northd.h | 5 +- > northd/ovn-northd.c | 1 - > ovn-ic-sb.ovsschema | 7 ++- > ovn-ic-sb.xml | 14 +++-- > ovn-nb.xml | 14 +++++ > tests/ovn-ic.at | 101 ++++++++++++++++++++++++++++++ > 11 files changed, 160 insertions(+), 15 deletions(-) > > diff --git a/NEWS b/NEWS > index 888946b54..5710716aa 100644 > --- a/NEWS > +++ b/NEWS > @@ -52,6 +52,8 @@ OVN v26.03.0 - xxx xx xxxx > learned routes. This option has priority over its router > counterpart. > * The EVPN support is now considered stable. Its "experimental" tag > has > been removed. > + * Add support for hub-and-spoke propagation via the "hub-spoke" > option > + in dynamic-routing-redistribute settings. > nit: This should be in the post 26.03.0 section. - Add support for Network Function insertion in OVN with stateful > traffic > redirection capability in Logical Switch datapath. The feature > introduces > three new NB database tables: > diff --git a/ic/ovn-ic.c b/ic/ovn-ic.c > index 1aa8af9b9..9d422e25b 100644 > --- a/ic/ovn-ic.c > +++ b/ic/ovn-ic.c > @@ -1283,6 +1283,7 @@ struct ic_route_info { > struct in6_addr prefix; > unsigned int plen; > struct in6_addr nexthop; > + bool is_src_dynamic; > nit: This is no longer used. > const char *origin; > const char *route_table; > const char *route_tag; > @@ -1671,7 +1672,8 @@ add_network_to_routes_ad(struct hmap *routes_ad, > const char *network, > const struct smap *nb_options, > const struct nbrec_logical_router *nb_lr, > const char *route_tag, > - const struct nbrec_logical_router_port *ts_lrp) > + const struct nbrec_logical_router_port *ts_lrp, > + bool is_src_dynamic) > { > struct in6_addr prefix, nexthop; > unsigned int plen; > @@ -1721,8 +1723,10 @@ add_network_to_routes_ad(struct hmap *routes_ad, > const char *network, > ds_destroy(&msg); > } > > + const char *origin = is_src_dynamic ? ROUTE_ORIGIN_CONNECTED_DYNAMIC : > + ROUTE_ORIGIN_CONNECTED; > /* directly-connected routes go to <main> route table */ > - add_to_routes_ad(routes_ad, prefix, plen, nexthop, > ROUTE_ORIGIN_CONNECTED, > + add_to_routes_ad(routes_ad, prefix, plen, nexthop, origin, > NULL, nb_lrp, NULL, nb_lr, NULL, route_tag); > } > > @@ -2169,6 +2173,14 @@ sync_learned_routes(struct ic_context *ctx, > isb_route->route_table, > &isb_route->header_.uuid, 0); > if (route_learned) { > + if (strcmp(route_learned->origin, isb_route->origin) != > 0) { > + VLOG_DBG("Update learned route %s -> %s with new > origin " > + "%s (old origin was %s)", > + isb_route->ip_prefix, isb_route->nexthop, > + isb_route->origin, route_learned->origin); > + > nbrec_logical_router_static_route_update_options_setkey( > + route_learned->nb_route, "origin", > isb_route->origin); > + } > I think this condition can actually never happen, because thei ic_route_find() is also checking the origin. I should have realized this in the previous review sorry. So we don't need this update because if they don't match we will just re-create the NB entry. hmap_remove(&ic_lr->routes_learned, &route_learned->node); > free(route_learned); > } else { > @@ -2374,7 +2386,7 @@ build_ts_routes_to_adv(struct ic_context *ctx, > add_network_to_routes_ad(routes_ad, lrp->networks[j], lrp, > ts_port_addrs, > &nb_global->options, > - lr, route_tag, ts_lrp); > + lr, route_tag, ts_lrp, false); > } > } else { > /* The router port of the TS port is ignored. */ > @@ -2429,7 +2441,7 @@ build_ts_routes_to_adv(struct ic_context *ctx, > add_network_to_routes_ad(routes_ad, sb_route->ip_prefix, NULL, > ts_port_addrs, > &nb_global->options, > - lr, route_tag, ts_lrp); > + lr, route_tag, ts_lrp, true); > } > sbrec_learned_route_index_destroy_row(filter); > } > diff --git a/lib/ovn-util.h b/lib/ovn-util.h > index 4c6623bef..4ccf6dc2d 100644 > --- a/lib/ovn-util.h > +++ b/lib/ovn-util.h > @@ -33,6 +33,7 @@ > #define ROUTE_ORIGIN_CONNECTED "connected" > #define ROUTE_ORIGIN_STATIC "static" > #define ROUTE_ORIGIN_LB "loadbalancer" > +#define ROUTE_ORIGIN_CONNECTED_DYNAMIC "connected-dynamic" > > #define ETH_CRC_LENGTH 4 > #define ETHERNET_OVERHEAD (ETH_HEADER_LEN + ETH_CRC_LENGTH) > diff --git a/northd/en-advertised-route-sync.c > b/northd/en-advertised-route-sync.c > index 8b73810c7..6ae9a9689 100644 > --- a/northd/en-advertised-route-sync.c > +++ b/northd/en-advertised-route-sync.c > @@ -683,6 +683,8 @@ should_advertise_route(const struct ovn_datapath > *advertising_od, > return drr_mode_LB_is_set(drr); > case ROUTE_SOURCE_CONNECTED_AS_HOST: > return drr_mode_CONNECTED_AS_HOST_is_set(drr); > + case ROUTE_SOURCE_IC_DYNAMIC: > + return drr_mode_IC_DYNAMIC_is_set(drr); > case ROUTE_SOURCE_LEARNED: > OVS_NOT_REACHED(); > default: > diff --git a/northd/northd.c b/northd/northd.c > index b7239f4e2..ce80639ea 100644 > --- a/northd/northd.c > +++ b/northd/northd.c > @@ -887,6 +887,10 @@ parse_dynamic_routing_redistribute( > out |= DRRM_LB; > continue; > } > + if (!strcmp(token, "hub-spoke")) { > + out |= DRRM_IC_DYNAMIC; > + continue; > + } > static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); > VLOG_WARN_RL(&rl, > "unknown dynamic-routing-redistribute option '%s' on > %s", > @@ -12342,6 +12346,9 @@ parsed_routes_add_static(const struct ovn_datapath > *od, > if (!strcmp(smap_get_def(&route->options, "origin", ""), > ROUTE_ORIGIN_CONNECTED)) { > source = ROUTE_SOURCE_CONNECTED; > + } else if (!strcmp(smap_get_def(&route->options, "origin", ""), > + ROUTE_ORIGIN_CONNECTED_DYNAMIC)) { > + source = ROUTE_SOURCE_IC_DYNAMIC; > nit: We are calling the smap_get_def multiple times. > } else { > source = ROUTE_SOURCE_STATIC; > } > @@ -12436,6 +12443,7 @@ route_source_to_offset(enum route_source source) > { > switch (source) { > case ROUTE_SOURCE_CONNECTED: > + case ROUTE_SOURCE_IC_DYNAMIC: > return ROUTE_PRIO_OFFSET_CONNECTED; > case ROUTE_SOURCE_STATIC: > return ROUTE_PRIO_OFFSET_STATIC; > diff --git a/northd/northd.h b/northd/northd.h > index 139519006..e86d39f9a 100644 > --- a/northd/northd.h > +++ b/northd/northd.h > @@ -365,7 +365,8 @@ struct mcast_port_info { > DRR_MODE(CONNECTED_AS_HOST, 1) \ > DRR_MODE(STATIC, 2) \ > DRR_MODE(NAT, 3) \ > - DRR_MODE(LB, 4) > + DRR_MODE(LB, 4) \ > + DRR_MODE(IC_DYNAMIC, 5) > > enum dynamic_routing_redistribute_mode_bits { > #define DRR_MODE(PROTOCOL, BIT) DRRM_##PROTOCOL##_BIT = BIT, > @@ -832,6 +833,8 @@ enum route_source { > ROUTE_SOURCE_LB, > /* The route is derived from out_port of connected logical router. */ > ROUTE_SOURCE_CONNECTED_AS_HOST, > + /* The route is derived from an ovn-controller and advertised to IC. > */ > + ROUTE_SOURCE_IC_DYNAMIC, > }; > > struct parsed_route { > diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c > index 0ed2eb17a..cfaf2f74b 100644 > --- a/northd/ovn-northd.c > +++ b/northd/ovn-northd.c > @@ -905,7 +905,6 @@ main(int argc, char *argv[]) > &nbrec_load_balancer_col_external_ids, > &nbrec_load_balancer_health_check_col_external_ids, > &nbrec_logical_router_policy_col_external_ids, > - &nbrec_logical_router_static_route_col_external_ids, > nit: Leftover. > &nbrec_meter_col_external_ids, > &nbrec_meter_band_col_external_ids, > &nbrec_mirror_col_external_ids, > diff --git a/ovn-ic-sb.ovsschema b/ovn-ic-sb.ovsschema > index 8981a4eef..e0e0fef5e 100644 > --- a/ovn-ic-sb.ovsschema > +++ b/ovn-ic-sb.ovsschema > @@ -1,7 +1,7 @@ > { > "name": "OVN_IC_Southbound", > - "version": "2.4.0", > - "cksum": "859073048 9662", > + "version": "2.5.0", > + "cksum": "1892994110 9713", > "tables": { > "IC_SB_Global": { > "columns": { > @@ -116,7 +116,8 @@ > "origin": {"type": {"key": { > "type": "string", > "enum": ["set", > - ["connected", "static", "loadbalancer"]]}}}, > + ["connected", "static", "loadbalancer", > + "connected-dynamic"]]}}}, > "external_ids": { > "type": {"key": "string", "value": "string", > "min": 0, "max": "unlimited"}}}, > diff --git a/ovn-ic-sb.xml b/ovn-ic-sb.xml > index b59fe1d03..3ca115073 100644 > --- a/ovn-ic-sb.xml > +++ b/ovn-ic-sb.xml > @@ -378,12 +378,14 @@ > </column> > > <column name="origin"> > - Can be one of <code>connected</code>, <code>static</code> or > - <code>loadbalancer</code>. Routes to directly-connected subnets - > - LRP's CIDRs are inserted to OVN IC SB DB with > <code>connected</code> > - value in <ref column="origin"/>. Static routes are inserted to > OVN IC > - SB DB with <code>static</code> value. Routes for LB VIPs are > inserted > - in OVN IC SB DB with <code>loadbalancer</code> value. > + Can be one of <code>connected</code>, <code>static</code>, > + <code>connected-dynamic</code> or <code>loadbalancer</code>. > Routes > + to directly-connected subnets - LRP's CIDRs are inserted to OVN > IC SB > + DB with <code>connected</code> value in <ref column="origin"/>. > + Static routes are inserted to OVN IC SB DB with > <code>static</code> > nit: Stray witespace. > + value. Routes for LB VIPs are inserted in OVN IC SB DB with > + <code>loadbalancer</code> value. Routes learned from dynamic > routing > + are inserted OVN IC SB DB with <code>connected-dynamic</code> > value. > nit: Missing "in". > Next when route is learned to another AZ NB DB by ovn-ic, route > origin > is synced to <ref table="Logical_Router_Static_Route" > column="options" > key="origin"/>. > diff --git a/ovn-nb.xml b/ovn-nb.xml > index f1cd89509..7324e8656 100644 > --- a/ovn-nb.xml > +++ b/ovn-nb.xml > @@ -3533,6 +3533,13 @@ or > Logical Switch. > </p> > > + <p> > + If <code>hub-spoke</code> is in the list then northd will > synchronize > + dynamic routes learned through OVN-IC from other routers into > the > + <ref table="Advertised_Route" db="OVN_Southbound"/> table, > enabling > + hub-and-spoke propagation. > + </p> > + > <p> > This value can be overwritten on a per LRP basis using > <ref column="options" key="dynamic-routing-redistribute" > @@ -4629,6 +4636,13 @@ or > via shared Logical Switch. > </p> > > + <p> > + If <code>hub-spoke</code> is in the list then northd will > synchronize > + dynamic routes learned through OVN-IC from other routers into > the > + <ref table="Advertised_Route" db="OVN_Southbound"/> table, > enabling > + hub-and-spoke propagation. > + </p> > + > <p> > If not set the value from <ref column="options" > key="dynamic-routing-redistribute" table="Logical_Router"/> on > the > diff --git a/tests/ovn-ic.at b/tests/ovn-ic.at > index 8bb5a4177..50270fdda 100644 > --- a/tests/ovn-ic.at > +++ b/tests/ovn-ic.at > @@ -4692,3 +4692,104 @@ OVS_WAIT_FOR_OUTPUT([ovn_as az2 ovn-nbctl > lr-route-list lr12 | grep 192.168 | > OVN_CLEANUP_IC([az1], [az2], [az3]) > AT_CLEANUP > ]) > + > +OVN_FOR_EACH_NORTHD([ > +AT_SETUP([ovn-ic -- Check ovn-ic adv and learn from SB Learned Route - > hub and spoke mode]) > + > +ovn_init_ic_db > + > +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 > + > +# Create new transit switches and LRs. Test topology is next: > +# > +# > +# logical router (lr11) - transit switch (ts11) - logical router (lr12) > +# > +# > + > +# Create lr11, lr12 and ts11 and connect them > +for i in 1 2; do > + ovn_as az$i > + > + lr=lr1$i > + check ovn-nbctl lr-add $lr > + > + ts=ts11 > + check ovn-ic-nbctl --wait=sb --may-exist ts-add $ts > + > + lrp=lrp-$lr-$ts > + lsp=lsp-$ts-$lr > + # Create LRP and connect to TS > + check ovn-nbctl lrp-add $lr $lrp aa:aa:aa:aa:a1:0$i 169.254.101.$i/24 > + check ovn-nbctl lsp-add-router-port $ts $lsp $lrp > +done > + > +# Create directly-connected route in lr12 > +check ovn_as az2 ovn-nbctl lrp-add lr12 lrp-lr12 aa:aa:aa:aa:bb:01 " > 192.168.0.1/24" > +OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep > 192.168 | > + grep learned | awk '{print $1, $2}' | sort ], [0], [dnl > +192.168.0.0/24 169.254.101.2 > +]) > + > +ovn_as az2 > +check ovn-nbctl --wait=sb set Logical_Router lr12 > option:dynamic-routing=true \ > + option:dynamic-routing-redistribute="connected,static" > +check ovn_as az2 ovn-nbctl --wait=sb lrp-add lr12 lr12-dr1 > 00:00:00:00:ff:01 10.0.0.1/24 > +dr1=$(fetch_column port_binding _uuid logical_port=lr12-dr1) > +datapath=$(fetch_column datapath_binding _uuid external_ids:name=lr12) > + > +check_uuid ovn-sbctl create Learned_Route \ > + datapath=$datapath \ > + logical_port=$dr1 \ > + ip_prefix=192.168.1.0/24 \ > + nexthop=10.0.0.20 > + > +wait_row_count ic-sb:Route 1 ip_prefix=192.168.1.0/24 > origin=connected-dynamic > + > +# Check Learned_Route adv in ovn-ic > +OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr11 | > + grep learned | awk '{print $1, $2}' | sort ], [0], [dnl > +10.0.0.0/24 169.254.101.2 > +192.168.0.0/24 169.254.101.2 > +192.168.1.0/24 169.254.101.2 > +]) > + > +ovn_as az1 > +check ovn-nbctl --wait=sb set Logical_Router lr11 > option:dynamic-routing=true \ > + option:dynamic-routing-redistribute="connected,static" > +# Advertise just 10.0.0.0/24 and 192.168.0.0/24 routes > +check_row_count Advertised_Route 1 ip_prefix=192.168.0.0/24 > +check_row_count Advertised_Route 1 ip_prefix=10.0.0.0/24 > +check_row_count Advertised_Route 0 ip_prefix=192.168.1.0/24 > + > + > +ovn_as az1 > +check ovn-nbctl --wait=sb set Logical_Router lr11 \ > + option:dynamic-routing-redistribute="connected,static,hub-spoke" > +# Advertise just 10.0.0.0/24, 192.168.0.0/24 and 192.168.1.0/24 routes > +# Route 192.168.1.0/24 is learned by DR from other logical routes (lr12) > +# And lr12 Advertised it in ovn-ic. Hub-spoke option enable re-routing in > lr11 > +check_row_count Advertised_Route 1 ip_prefix=192.168.0.0/24 > +check_row_count Advertised_Route 1 ip_prefix=10.0.0.0/24 > +check_row_count Advertised_Route 1 ip_prefix=192.168.1.0/24 > + > +ovn_as az1 > +check ovn-nbctl --wait=sb set Logical_Router lr11 \ > + option:dynamic-routing-redistribute="connected,static" > + > +check_row_count Advertised_Route 1 ip_prefix=192.168.0.0/24 > +check_row_count Advertised_Route 1 ip_prefix=10.0.0.0/24 > +check_row_count Advertised_Route 0 ip_prefix=192.168.1.0/24 > + > +OVN_CLEANUP_IC([az1], [az2]) > + > +AT_CLEANUP > +]) > -- > 2.43.0 > > > -- > > > > > _'Esta mensagem é direcionada apenas para os endereços constantes no > cabeçalho inicial. Se você não está listado nos endereços constantes no > cabeçalho, pedimos-lhe que desconsidere completamente o conteúdo dessa > mensagem e cuja cópia, encaminhamento e/ou execução das ações citadas > estão > imediatamente anuladas e proibidas'._ > > > * **'Apesar do Magazine Luiza tomar > todas as precauções razoáveis para assegurar que nenhum vírus esteja > presente nesse e-mail, a empresa não poderá aceitar a responsabilidade por > quaisquer perdas ou danos causados por esse e-mail ou por seus anexos'.* > > > > _______________________________________________ > dev mailing list > [email protected] > https://mail.openvswitch.org/mailman/listinfo/ovs-dev > > I addressed those comments and applied it to main. Regards, Ales _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
