Add a per-Logical_Router option "ic-route-ipv4-next-hop-ipv6" that makes
ovn-ic advertise a router's IPv4 routes using the interconnect port's IPv6
address as next hop instead of its IPv4 address ("IPv4 over IPv6").
This is useful when AZs are interconnected over a dual-stack transit switch
and the operator wants IPv4 traffic to be forwarded via the IPv6 transit
network.The option is read from the Logical_Router and can be overridden on the transit Logical_Router_Port. It only affects the advertising side; the learning side already resolves the next hop by its own address family, so a peer learns the IPv4 prefix with the IPv6 next hop without any extra config. It defaults to false, preserving the previous behavior, and is a no-op when the interconnect port has no IPv6 address (the IPv4 next hop is kept). The logical router datapath already supports an IPv4 prefix with an IPv6 next hop, so no ovn-northd changes are needed. While here, fix advertise_routes() formatting the IC-SB next hop based on the prefix's address family instead of the next hop's, which corrupted an IPv6 next hop advertised for an IPv4 prefix. Assisted-by: Claude Opus 4.8, Claude Code Signed-off-by: Lucas Vargas Dias <[email protected]> --- NEWS | 4 ++ ic/ovn-ic.c | 46 +++++++++++-- ovn-nb.xml | 31 +++++++++ tests/ovn-ic.at | 176 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 253 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index 5782df27d..2d3ff9336 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,9 @@ Post v26.03.0 ------------- + - Added the "ic-route-ipv4-next-hop-ipv6" option to Logical_Router (and, + as an override, to Logical_Router_Port) to advertise a router's IPv4 + routes through ovn-ic using the interconnect port's IPv6 address as next + hop ("IPv4 over IPv6"). - Added Transit Switch port support with new ovn-ic-nbctl 'tsp-add', 'tsp-del' and 'tsp-set-addr' commands. - Added ability to set any "ipsec_*" NB_Global option to configure the diff --git a/ic/ovn-ic.c b/ic/ovn-ic.c index f07e74866..359857306 100644 --- a/ic/ovn-ic.c +++ b/ic/ovn-ic.c @@ -1653,12 +1653,37 @@ add_to_routes_learned(struct hmap *routes_learned, return true; } +/* Returns true if IPv4 prefixes of 'nb_lr' should be advertised with the + * interconnect port's IPv6 address as next hop ("IPv4 over IPv6"). + * The option is read from the Logical_Router and may be overridden on the + * transit Logical_Router_Port. + */ +static bool +route_ipv4_next_hop_ipv6(const struct nbrec_logical_router *nb_lr, + const struct nbrec_logical_router_port *ts_lrp) +{ + if (ts_lrp) { + const char *lrp_opt = smap_get(&ts_lrp->options, + "ic-route-ipv4-next-hop-ipv6"); + if (lrp_opt) { + return !strcmp(lrp_opt, "true"); + } + } + return smap_get_bool(&nb_lr->options, "ic-route-ipv4-next-hop-ipv6", + false); +} + static bool -get_nexthop_from_lport_addresses(bool is_v4, +get_nexthop_from_lport_addresses(bool is_v4_prefix, const struct lport_addresses *laddr, + bool ipv4_next_hop_ipv6, struct in6_addr *nexthop) { - if (is_v4) { + /* For an IPv4 prefix, pick an IPv4 next hop by default. When + * 'ipv4_next_hop_ipv6' is enabled and the interconnect port has an IPv6 + * address, advertise the IPv4 prefix with that IPv6 address as next hop + * instead ("IPv4 over IPv6"). */ + if (is_v4_prefix && !(ipv4_next_hop_ipv6 && laddr->n_ipv6_addrs)) { if (!laddr->n_ipv4_addrs) { return false; } @@ -1880,6 +1905,8 @@ add_static_to_routes_ad( if (!get_nexthop_from_lport_addresses(IN6_IS_ADDR_V4MAPPED(&prefix), nexthop_addresses, + route_ipv4_next_hop_ipv6(nb_lr, + ts_lrp), &nexthop)) { return; } @@ -1944,6 +1971,8 @@ add_network_to_routes_ad(struct hmap *routes_ad, const char *network, if (!get_nexthop_from_lport_addresses(IN6_IS_ADDR_V4MAPPED(&prefix), nexthop_addresses, + route_ipv4_next_hop_ipv6(nb_lr, + ts_lrp), &nexthop)) { return; } @@ -2021,6 +2050,8 @@ add_lb_vip_to_routes_ad(struct hmap *routes_ad, const char *vip_key, } if (!get_nexthop_from_lport_addresses(IN6_IS_ADDR_V4MAPPED(&vip_ip), nexthop_addresses, + route_ipv4_next_hop_ipv6(nb_lr, + ts_lrp), &nexthop)) { VLOG_WARN_RL(&rl, "Route ad: failed to get nexthop for lb vip"); goto out; @@ -2573,17 +2604,24 @@ advertise_routes(struct ic_context *ctx, icsbrec_route_set_transit_switch(isb_route, ts_name); icsbrec_route_set_availability_zone(isb_route, az); + /* The prefix and the next hop are formatted independently: an IPv4 + * prefix may be advertised with an IPv6 next hop ("IPv4 over IPv6"). + */ char *prefix_s, *nexthop_s; if (IN6_IS_ADDR_V4MAPPED(&route_adv->prefix)) { ovs_be32 ipv4 = in6_addr_get_mapped_ipv4(&route_adv->prefix); - ovs_be32 nh = in6_addr_get_mapped_ipv4(&route_adv->nexthop); prefix_s = xasprintf(IP_FMT "/%d", IP_ARGS(ipv4), route_adv->plen); - nexthop_s = xasprintf(IP_FMT, IP_ARGS(nh)); } else { char network_s[INET6_ADDRSTRLEN]; inet_ntop(AF_INET6, &route_adv->prefix, network_s, INET6_ADDRSTRLEN); prefix_s = xasprintf("%s/%d", network_s, route_adv->plen); + } + if (IN6_IS_ADDR_V4MAPPED(&route_adv->nexthop)) { + ovs_be32 nh = in6_addr_get_mapped_ipv4(&route_adv->nexthop); + nexthop_s = xasprintf(IP_FMT, IP_ARGS(nh)); + } else { + char network_s[INET6_ADDRSTRLEN]; inet_ntop(AF_INET6, &route_adv->nexthop, network_s, INET6_ADDRSTRLEN); nexthop_s = xstrdup(network_s); diff --git a/ovn-nb.xml b/ovn-nb.xml index 15fb1d7e8..53bc8b748 100644 --- a/ovn-nb.xml +++ b/ovn-nb.xml @@ -3747,6 +3747,25 @@ or </p> </column> + <column name="options" key="ic-route-ipv4-next-hop-ipv6" + type='{"type": "boolean"}'> + <p> + A boolean value that, when <code>true</code>, advertises this Logical + Router's IPv4 routes into the global <ref db="OVN_IC_Southbound"/> + database using the IPv6 address of the interconnect + <ref table="Logical_Router_Port"/> as next hop instead of its IPv4 + address ("IPv4 over IPv6"). This requires the interconnect port to + have an IPv6 address; otherwise the IPv4 next hop is used as usual. + The default value is <code>false</code>. + </p> + + <p> + This option can be overridden per transit + <ref table="Logical_Router_Port"/> by setting the option of the same + name on that port. + </p> + </column> + <column name="options" key="disable_garp_rarp" type='{"type": "boolean"}'> <p> @@ -4830,6 +4849,18 @@ or </p> </column> + <column name="options" key="ic-route-ipv4-next-hop-ipv6" + type='{"type": "boolean"}'> + <p> + Overrides the <code>ic-route-ipv4-next-hop-ipv6</code> option of the + <ref table="Logical_Router"/> for routes advertised through this + transit port. When <code>true</code>, IPv4 routes are advertised + using this port's IPv6 address as next hop ("IPv4 over IPv6"); when + <code>false</code>, the IPv4 next hop is used. If unset, the + Logical Router option applies. + </p> + </column> + <column name="options" key="dynamic-routing-no-learning" type='{"type": "boolean"}'> <p> diff --git a/tests/ovn-ic.at b/tests/ovn-ic.at index eb3b7efef..db70b6ea9 100644 --- a/tests/ovn-ic.at +++ b/tests/ovn-ic.at @@ -1452,6 +1452,182 @@ OVN_CLEANUP_IC([az1], [az2]) AT_CLEANUP ]) +OVN_FOR_EACH_NORTHD([ +AT_SETUP([ovn-ic -- route sync -- IPv4 next hop over IPv6]) +AT_KEYWORDS([ic-route-ipv4-next-hop-ipv6]) + +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-ic-nbctl --wait=sb sync + # Enable route learning and advertising at AZ level. + check ovn-nbctl set nb_global . options:ic-route-learn=true + check ovn-nbctl set nb_global . options:ic-route-adv=true + + # Create a dual-stack interconnect port and connect it to the TS. + check ovn-nbctl lr-add lr$i + check ovn-nbctl lrp-add lr$i lrp-lr$i-ts1 aa:aa:aa:aa:aa:0$i \ + 169.254.100.$i/24 2001:db8:1::$i/64 + check ovn-nbctl lsp-add-router-port ts1 lsp-ts1-lr$i lrp-lr$i-ts1 +done + +# Advertise an IPv4 route from az1. +check ovn_as az1 ovn-nbctl --wait=sb lr-route-add lr1 10.11.1.0/24 169.254.0.1 +check ovn-ic-nbctl --wait=sb sync + +# By default the IPv4 route is advertised with an IPv4 next hop. +OVS_WAIT_UNTIL([ovn_as az2 ovn-nbctl lr-route-list lr2 | grep learned \ + | grep 10.11.1.0]) +AT_CHECK([ovn_as az2 ovn-nbctl lr-route-list lr2], [0], [dnl +IPv4 Routes +Route Table <main>: + 10.11.1.0/24 169.254.100.1 dst-ip (learned) +]) + +# Enable "IPv4 over IPv6" on the advertising LR: the IPv4 route is now +# advertised with the interconnect port's IPv6 address as next hop. +check ovn_as az1 ovn-nbctl set logical_router lr1 \ + options:ic-route-ipv4-next-hop-ipv6=true +OVS_WAIT_UNTIL([ovn_as az2 ovn-nbctl lr-route-list lr2 | grep 10.11.1.0 \ + | grep 2001:db8:1::1]) +AT_CHECK([ovn_as az2 ovn-nbctl lr-route-list lr2], [0], [dnl +IPv4 Routes +Route Table <main>: + 10.11.1.0/24 2001:db8:1::1 dst-ip (learned) +]) + +# The next hop stored in IC-SB is the IPv6 address. +AT_CHECK([ovn-ic-sbctl list route | grep 'ip_prefix.*10.11.1' -A 1], [0], [dnl +ip_prefix : "10.11.1.0/24" +nexthop : "2001:db8:1::1" +]) + +# A per-port "false" override on the transit LRP wins over the LR option, +# reverting to an IPv4 next hop. +check ovn_as az1 ovn-nbctl set logical_router_port lrp-lr1-ts1 \ + options:ic-route-ipv4-next-hop-ipv6=false +OVS_WAIT_UNTIL([ovn_as az2 ovn-nbctl lr-route-list lr2 | grep 10.11.1.0 \ + | grep 169.254.100.1]) +AT_CHECK([ovn_as az2 ovn-nbctl lr-route-list lr2], [0], [dnl +IPv4 Routes +Route Table <main>: + 10.11.1.0/24 169.254.100.1 dst-ip (learned) +]) + +OVN_CLEANUP_IC([az1], [az2]) + +AT_CLEANUP +]) + +OVN_FOR_EACH_NORTHD([ +AT_SETUP([ovn-ic -- route sync -- IPv4 next hop over IPv6 -- mixed config]) +AT_KEYWORDS([ic-route-ipv4-next-hop-ipv6]) + +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-ic-nbctl --wait=sb sync + # Enable route learning and advertising at AZ level. + check ovn-nbctl set nb_global . options:ic-route-learn=true + check ovn-nbctl set nb_global . options:ic-route-adv=true + + # Create a dual-stack interconnect port and connect it to the TS. + check ovn-nbctl lr-add lr$i + check ovn-nbctl lrp-add lr$i lrp-lr$i-ts1 aa:aa:aa:aa:aa:0$i \ + 169.254.100.$i/24 2001:db8:1::$i/64 + check ovn-nbctl lsp-add-router-port ts1 lsp-ts1-lr$i lrp-lr$i-ts1 + + # Each AZ advertises one IPv4 route. + check ovn-nbctl --wait=sb lr-route-add lr$i 10.11.$i.0/24 discard +done + +# Only az1 opts in to "IPv4 over IPv6". az2 keeps the default behavior. +check ovn_as az1 ovn-nbctl set logical_router lr1 \ + options:ic-route-ipv4-next-hop-ipv6=true +check ovn-ic-nbctl --wait=sb sync + +# az2 (no config) still learns az1's route with the advertised IPv6 next hop: +# the option only affects the advertising router. +OVS_WAIT_UNTIL([ovn_as az2 ovn-nbctl lr-route-list lr2 | grep 10.11.1.0 \ + | grep 2001:db8:1::1]) +AT_CHECK([ovn_as az2 ovn-nbctl lr-route-list lr2], [0], [dnl +IPv4 Routes +Route Table <main>: + 10.11.1.0/24 2001:db8:1::1 dst-ip (learned) + 10.11.2.0/24 discard dst-ip +]) + +# az1 learns az2's route with an IPv4 next hop, since lr2 has no config: the +# two directions are independent. +OVS_WAIT_UNTIL([ovn_as az1 ovn-nbctl lr-route-list lr1 | grep 10.11.2.0 \ + | grep 169.254.100.2]) +AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr1], [0], [dnl +IPv4 Routes +Route Table <main>: + 10.11.1.0/24 discard dst-ip + 10.11.2.0/24 169.254.100.2 dst-ip (learned) +]) + +OVN_CLEANUP_IC([az1], [az2]) + +AT_CLEANUP +]) + +OVN_FOR_EACH_NORTHD([ +AT_SETUP([ovn-ic -- route sync -- IPv4 next hop over IPv6 -- no IPv6 on LRP]) +AT_KEYWORDS([ic-route-ipv4-next-hop-ipv6]) + +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-ic-nbctl --wait=sb sync + # Enable route learning and advertising at AZ level. + check ovn-nbctl set nb_global . options:ic-route-learn=true + check ovn-nbctl set nb_global . options:ic-route-adv=true + + # Create an IPv4-only interconnect port and connect it to the TS. + check ovn-nbctl lr-add lr$i + check ovn-nbctl lrp-add lr$i lrp-lr$i-ts1 aa:aa:aa:aa:aa:0$i \ + 169.254.100.$i/24 + check ovn-nbctl lsp-add-router-port ts1 lsp-ts1-lr$i lrp-lr$i-ts1 +done + +# Enable "IPv4 over IPv6" on lr1, whose interconnect port has no IPv6 address. +check ovn_as az1 ovn-nbctl set logical_router lr1 \ + options:ic-route-ipv4-next-hop-ipv6=true +check ovn_as az1 ovn-nbctl --wait=sb lr-route-add lr1 10.11.1.0/24 169.254.0.1 +check ovn-ic-nbctl --wait=sb sync + +# Without an IPv6 address on the interconnect port, the IPv4 next hop is used +# as usual: the option has no effect. +OVS_WAIT_UNTIL([ovn_as az2 ovn-nbctl lr-route-list lr2 | grep learned \ + | grep 10.11.1.0]) +AT_CHECK([ovn_as az2 ovn-nbctl lr-route-list lr2], [0], [dnl +IPv4 Routes +Route Table <main>: + 10.11.1.0/24 169.254.100.1 dst-ip (learned) +]) + +# The next hop stored in IC-SB is the IPv4 address. +AT_CHECK([ovn-ic-sbctl list route | grep 'ip_prefix.*10.11.1' -A 1], [0], [dnl +ip_prefix : "10.11.1.0/24" +nexthop : "169.254.100.1" +]) + +OVN_CLEANUP_IC([az1], [az2]) + +AT_CLEANUP +]) + OVN_FOR_EACH_NORTHD([ AT_SETUP([ovn-ic -- route sync -- IPv6 route tables]) AT_KEYWORDS([IPv6-route-sync]) -- 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
