ovn-ic always advertises the full network of each Logical Router Port of a router attached to a transit switch. There is no way to advertise only part of a subnet, e.g. when only a portion of it must be reachable from other availability zones.
Add the "ic-route-adv-cidrs" option to the Logical_Router_Port connected to the transit switch. It takes a comma-separated list of CIDRs. When advertising the directly-connected networks of the router through that port, the listed CIDRs contained in each network are advertised instead of the network itself. A network containing none of the listed CIDRs is advertised as before, so a dual-stack network can be narrowed down in one address family only. CIDRs outside of the router's networks are ignored, invalid ones are logged, and host bits are cleared before advertising. Each transit switch port of a router can advertise a different set of CIDRs. The advertised CIDRs still go through the existing advertisement filters (ic-route-filter-adv, ic-route-deny-adv and the global denylist). If all of them are filtered, the whole network is not advertised as a fallback. Changes to the option are handled incrementally by the existing Logical_Router_Port route handler. Assisted-by: Claude Opus 5.5, Claude Code Signed-off-by: Lucas Vargas Dias <[email protected]> --- NEWS | 4 ++ ic/en-route.c | 103 +++++++++++++++++++++++++++++++++--- ovn-nb.xml | 22 ++++++++ tests/ovn-ic.at | 137 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 260 insertions(+), 6 deletions(-) diff --git a/NEWS b/NEWS index 4176f8d7f..eec912ddb 100644 --- a/NEWS +++ b/NEWS @@ -37,6 +37,10 @@ Post v26.09.0 now creates the VRF on every member of the port's HA chassis group instead of only on the active one, so the routing daemon can bring up its session in the VRF before a failover. + - Added the "ic-route-adv-cidrs" option to Logical_Router_Port. When set + on a port connected to a transit switch, ovn-ic advertises through it + the listed CIDRs contained in a directly-connected network of the router + instead of that whole network. - Removed OVN's ovs-bugtool plugin and helper scripts. - Removed ovn-sim utility scripts. - Removed ovn-docker utility scripts. diff --git a/ic/en-route.c b/ic/en-route.c index e974600dd..e7c3e70f9 100644 --- a/ic/en-route.c +++ b/ic/en-route.c @@ -556,6 +556,96 @@ add_network_to_routes_ad(struct hmap *routes_ad, const char *network, NULL, nb_lrp, NULL, nb_lr, NULL, route_tag, false); } +#define IC_ROUTE_ADV_CIDRS "ic-route-adv-cidrs" + +/* Returns true if 'prefix'/'plen' is a subnet of 'net'/'net_plen'. */ +static bool +prefix_in_network(const struct in6_addr *prefix, unsigned int plen, + const struct in6_addr *net, unsigned int net_plen) +{ + if (IN6_IS_ADDR_V4MAPPED(prefix) != IN6_IS_ADDR_V4MAPPED(net)) { + return false; + } + + /* IPv4 prefix lengths are relative to the mapped address. */ + unsigned int offset = IN6_IS_ADDR_V4MAPPED(prefix) ? 96 : 0; + if (plen < net_plen) { + return false; + } + + struct in6_addr mask = ipv6_create_mask(net_plen + offset); + struct in6_addr a = ipv6_addr_bitand(prefix, &mask); + struct in6_addr b = ipv6_addr_bitand(net, &mask); + return ipv6_addr_equals(&a, &b); +} + +/* Advertises the CIDRs listed in the "ic-route-adv-cidrs" option of 'ts_lrp' + * that are contained in 'network' of 'nb_lrp', instead of 'network' itself. + * If the option is not set or none of its CIDRs is contained in 'network', + * the whole 'network' is advertised. */ +static void +add_lrp_network_to_routes_ad(struct hmap *routes_ad, const char *network, + const struct nbrec_logical_router_port *nb_lrp, + const struct lport_addresses *nexthop_addresses, + const struct smap *nb_options, + const struct nbrec_logical_router *nb_lr, + const struct nbrec_logical_router_port *ts_lrp, + const char *route_tag, + const char *ts_route_table) +{ + const char *adv_cidrs = ts_lrp ? smap_get(&ts_lrp->options, + IC_ROUTE_ADV_CIDRS) + : NULL; + struct in6_addr net_prefix; + unsigned int net_plen; + + if (!adv_cidrs || !ip46_parse_cidr(network, &net_prefix, &net_plen)) { + add_network_to_routes_ad(routes_ad, network, nb_lrp, + nexthop_addresses, nb_options, nb_lr, + ts_lrp, route_tag, ts_route_table, false); + return; + } + + struct sset cidrs = SSET_INITIALIZER(&cidrs); + sset_from_delimited_string(&cidrs, adv_cidrs, ","); + + bool found = false; + const char *cidr; + SSET_FOR_EACH (cidr, &cidrs) { + struct in6_addr prefix; + unsigned int plen; + if (!ip46_parse_cidr(cidr, &prefix, &plen)) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "Bad format in %s of lrp %s: %s", + IC_ROUTE_ADV_CIDRS, ts_lrp->name, cidr); + continue; + } + if (!prefix_in_network(&prefix, plen, &net_prefix, net_plen)) { + continue; + } + + struct ds masked = DS_EMPTY_INITIALIZER; + struct in6_addr mask = ipv6_create_mask( + plen + (IN6_IS_ADDR_V4MAPPED(&prefix) ? 96 : 0)); + prefix = ipv6_addr_bitand(&prefix, &mask); + ipv6_format_mapped(&prefix, &masked); + ds_put_format(&masked, "/%u", plen); + + add_network_to_routes_ad(routes_ad, ds_cstr(&masked), nb_lrp, + nexthop_addresses, nb_options, nb_lr, + ts_lrp, route_tag, ts_route_table, false); + ds_destroy(&masked); + found = true; + } + sset_destroy(&cidrs); + + if (!found) { + add_network_to_routes_ad(routes_ad, network, nb_lrp, + nexthop_addresses, nb_options, nb_lr, + ts_lrp, route_tag, ts_route_table, false); + } +} + static void add_lb_vip_to_routes_ad(struct hmap *routes_ad, const char *vip_key, const struct nbrec_load_balancer *nb_lb, @@ -1398,11 +1488,11 @@ build_ts_routes_to_adv(struct ic_context *ctx, const struct nbrec_logical_router_port *lrp = lr->ports[i]; if (!lrp_is_ts_port(ctx, ic_lr, lrp->name)) { for (int j = 0; j < lrp->n_networks; j++) { - add_network_to_routes_ad(routes_ad, lrp->networks[j], lrp, - ts_port_addrs, - &nb_global->options, - lr, ts_lrp, route_tag, - ts_route_table, false); + add_lrp_network_to_routes_ad(routes_ad, lrp->networks[j], + lrp, ts_port_addrs, + &nb_global->options, + lr, ts_lrp, route_tag, + ts_route_table); } } else { /* The router port of the TS port is ignored. */ @@ -2226,7 +2316,8 @@ route_nb_logical_router_handler(struct engine_node *node, } /* NB Logical_Router_Port: networks and options (route_table, ic-route-tag, - * ic-route-learn-tag-rules, and the deprecated ic-route-filter-tag) affect + * ic-route-adv-cidrs, ic-route-learn-tag-rules, and the deprecated + * ic-route-filter-tag) affect * connected-route advertising and learning for the owning router. Port * creation/deletion changes the router's 'ports' column and is handled by * route_nb_logical_router_handler; here only column updates to an existing diff --git a/ovn-nb.xml b/ovn-nb.xml index d3f3cc197..e72c1f8c3 100644 --- a/ovn-nb.xml +++ b/ovn-nb.xml @@ -4707,6 +4707,28 @@ or </p> </column> + <column name="options" key="ic-route-adv-cidrs" + type='{"type": "string"}'> + <p> + This option applies to a Logical Router Port connected to a transit + switch. It expects a comma-separated list of CIDRs, for example + <code>10.0.0.0/25,2001:db8::/80</code>. When advertising, through + this port, the directly-connected networks of the other ports of + the router, <code>ovn-ic</code> advertises the listed CIDRs + contained in each network instead of the network itself. A network + that contains none of the listed CIDRs is advertised as usual. + CIDRs that are not contained in any of the router's networks, as + well as invalid ones, are ignored. + </p> + + <p> + The advertised CIDRs are still subject to the other advertisement + filters, such as <code>ic-route-filter-adv</code> and + <code>ic-route-deny-adv</code>. If all the CIDRs contained in a + network are filtered, the network itself is not advertised either. + </p> + </column> + <column name="options" key="ic-route-filter-tag" type='{"type": "string"}'> <p> diff --git a/tests/ovn-ic.at b/tests/ovn-ic.at index 646026817..bd4e8d5f4 100644 --- a/tests/ovn-ic.at +++ b/tests/ovn-ic.at @@ -5424,6 +5424,143 @@ OVN_CLEANUP_IC([az1], [az2]) AT_CLEANUP ]) +OVN_FOR_EACH_NORTHD([ +AT_SETUP([ovn-ic -- route adv -- specific CIDRs of connected networks]) +ovn_init_ic_db +ovn-ic-nbctl ts-add ts1 +for i in 1 2; do + ovn_start az$i + ovn_as az$i + check ovn-nbctl set nb_global . options:ic-route-learn=true + check ovn-nbctl set nb_global . options:ic-route-adv=true +done + +OVS_WAIT_FOR_OUTPUT([ovn-ic-sbctl show], [0], [dnl +availability-zone az1 +availability-zone az2 +]) +check ovn-ic-nbctl --wait=sb sync + +# logical router (lr11) - transit switch (ts1) - logical router (lr12) +for i in 1 2; do + ovn_as az$i + lr=lr1$i + check ovn-nbctl lr-add $lr + lrp=lrp-$lr-ts1 + lsp=lsp-ts1-$lr + check ovn-nbctl lrp-add $lr $lrp aa:aa:aa:aa:ab:0$i 169.254.101.$i/24 fe80:10::$i/64 + check ovn-nbctl lsp-add-router-port ts1 $lsp $lrp +done + +ovn_as az2 check ovn-nbctl lrp-add lr12 lrp-lr12-1 aa:aa:aa:aa:bb:01 "192.168.12.1/24" "2001:db12::1/64" +ovn_as az2 check ovn-nbctl --wait=sb lrp-add lr12 lrp-lr12-2 aa:aa:aa:aa:bb:02 "192.168.2.1/24" +check ovn-ic-nbctl --wait=sb sync + +OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr11 | + grep learned | awk '{print $1, $2}' | sort ], [0], [dnl +192.168.12.0/24 169.254.101.2 +192.168.2.0/24 169.254.101.2 +2001:db12::/64 fe80:10::2 +]) + +# Advertise through lrp-lr12-ts1 only parts of the IPv4 network of +# lrp-lr12-1. The CIDRs outside of the router's networks are ignored and the +# networks that contain none of the listed CIDRs are still advertised as a +# whole. Host bits are cleared. +ovn_as az2 check ovn-nbctl set logical_router_port lrp-lr12-ts1 \ + options:ic-route-adv-cidrs="\"192.168.12.0/26,192.168.12.130/25,10.0.0.0/24,192.168.0.0/16,invalid\"" +OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr11 | + grep learned | awk '{print $1, $2}' | sort ], [0], [dnl +192.168.12.0/26 169.254.101.2 +192.168.12.128/25 169.254.101.2 +192.168.2.0/24 169.254.101.2 +2001:db12::/64 fe80:10::2 +]) + +# IPv6 CIDRs work the same way. +ovn_as az2 check ovn-nbctl set logical_router_port lrp-lr12-ts1 \ + options:ic-route-adv-cidrs="\"192.168.12.0/26,2001:db12::/80\"" +OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr11 | + grep learned | awk '{print $1, $2}' | sort ], [0], [dnl +192.168.12.0/26 169.254.101.2 +192.168.2.0/24 169.254.101.2 +2001:db12::/80 fe80:10::2 +]) + +# The advertised CIDRs are still subject to the advertisement filters. +ovn_as az2 check ovn-nbctl set logical_router_port lrp-lr12-ts1 \ + options:ic-route-deny-adv=192.168.12.0/26 +OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr11 | + grep learned | awk '{print $1, $2}' | sort ], [0], [dnl +192.168.2.0/24 169.254.101.2 +2001:db12::/80 fe80:10::2 +]) +ovn_as az2 check ovn-nbctl remove logical_router_port lrp-lr12-ts1 \ + options ic-route-deny-adv + +# ic-route-filter-adv is applied to each advertised CIDR. A filter entry +# containing the CIDRs lets all of them through. +ovn_as az2 check ovn-nbctl set logical_router_port lrp-lr12-ts1 \ + options:ic-route-adv-cidrs="\"192.168.12.0/26,192.168.12.128/25\"" +ovn_as az2 check ovn-nbctl set logical_router_port lrp-lr12-ts1 \ + options:ic-route-filter-adv=192.168.12.0/24 +OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr11 | + grep learned | awk '{print $1, $2}' | sort ], [0], [dnl +192.168.12.0/26 169.254.101.2 +192.168.12.128/25 169.254.101.2 +]) + +# A filter disjoint from the CIDRs filters all of them, and the whole +# network is not advertised as a fallback. +ovn_as az2 check ovn-nbctl set logical_router_port lrp-lr12-ts1 \ + options:ic-route-filter-adv="\"192.168.12.64/26,192.168.2.0/24\"" +OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr11 | + grep learned | awk '{print $1, $2}' | sort ], [0], [dnl +192.168.2.0/24 169.254.101.2 +]) + +# A filter equal to one of the CIDRs lets only that one through. +ovn_as az2 check ovn-nbctl set logical_router_port lrp-lr12-ts1 \ + options:ic-route-filter-adv=192.168.12.0/26 +OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr11 | + grep learned | awk '{print $1, $2}' | sort ], [0], [dnl +192.168.12.0/26 169.254.101.2 +]) + +# A filter more specific than a CIDR does not split it: 192.168.12.0/26 is +# not contained in 192.168.12.0/27, so it is filtered. +ovn_as az2 check ovn-nbctl set logical_router_port lrp-lr12-ts1 \ + options:ic-route-filter-adv="\"192.168.12.0/27,192.168.2.0/24\"" +OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr11 | + grep learned | awk '{print $1, $2}' | sort ], [0], [dnl +192.168.2.0/24 169.254.101.2 +]) + +# Without ic-route-adv-cidrs, the same filter as the network lets the +# whole network through. +ovn_as az2 check ovn-nbctl set logical_router_port lrp-lr12-ts1 \ + options:ic-route-filter-adv=192.168.12.0/24 +ovn_as az2 check ovn-nbctl remove logical_router_port lrp-lr12-ts1 \ + options ic-route-adv-cidrs +OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr11 | + grep learned | awk '{print $1, $2}' | sort ], [0], [dnl +192.168.12.0/24 169.254.101.2 +]) + +# Removing the filter too restores advertising all the networks. +ovn_as az2 check ovn-nbctl remove logical_router_port lrp-lr12-ts1 \ + options ic-route-filter-adv +OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr11 | + grep learned | awk '{print $1, $2}' | sort ], [0], [dnl +192.168.12.0/24 169.254.101.2 +192.168.2.0/24 169.254.101.2 +2001:db12::/64 fe80:10::2 +]) + +OVN_CLEANUP_IC([az1], [az2]) +AT_CLEANUP +]) + OVN_FOR_EACH_NORTHD([ AT_SETUP([ovn-ic -- Check ovn-ic adv and learn from SB Learned Route]) -- 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
