On Tue, Jun 30, 2026 at 6:24 PM Lucas Vargas Dias <[email protected]>
wrote:
> 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]>
> ---
>
Hi Lucas,
thank you for the patch. There is small nit down below.
> 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);
> +}
>
This can be simplified into the following:
const char *opt_name = "ic-route-ipv4-next-hop-ipv6";
bool lr_opt = smap_get_bool(&nb_lr->options, opt_name, false);
return ts_lrp ? smap_get_bool(&ts_lrp->options, opt_name, lr_opt) :
lr_opt;
> +
> 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
>
>
With that adjustment applied to main.
Regards,
Ales
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev