On Mon, Aug 24, 2026 at 3:25 PM Tim Rozet <[email protected]> wrote:
>
> Northd adds a priority-120 UNSNAT bypass when a load balancer VIP
> also appears as a NAT external IP. This prevents packets for the VIP
> from repeatedly entering an SNAT zone lookup that never commits the
> pre-DNAT tuple. Such packets remain ct.new and cannot be offloaded.
>
> Gateway routers using lb_force_snat_ip=router_ip may use a router
> port IP as the VIP while their NAT external IP is a different
> masquerade address. Northd then omits the bypass even though it
> creates an UNSNAT flow for the router port IP.
>
> Generate the bypass when the VIP also matches an explicit DNAT or
> load balancer force-SNAT address, or a router port address selected
> by lb_force_snat_ip=router_ip.
>
> Add coverage using a NodePort-style VIP with a different masquerade
> SNAT address. A single-stream OVN-Kubernetes DPU test improved from
> 8.8 Gbit/s to 19.8 Gbit/s.
>
> Reported-at: https://github.com/ovn-kubernetes/ovn-kubernetes/issues/6422
> Assisted-by: GPT-5, OpenAI Codex
> Signed-off-by: Tim Rozet <[email protected]>
> ---
>  northd/northd.c     | 33 +++++++++++++++++++++++++--------
>  tests/ovn-northd.at | 40 ++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 65 insertions(+), 8 deletions(-)
>
> diff --git a/northd/northd.c b/northd/northd.c
> index 88e3ece88..59791d694 100644
> --- a/northd/northd.c
> +++ b/northd/northd.c
> @@ -13761,6 +13761,23 @@ build_gw_lrouter_nat_flows_for_lb(struct
lrouter_nat_lb_flows_ctx *ctx,
>      bitmap_free(dp_non_meter);
>  }
>
> +static bool
> +lrouter_lb_vip_is_unsnat_ip(const struct ovn_datapath *od,
> +                            const struct lr_nat_record *lrnat_rec,
> +                            const char *vip)
> +{
> +    if (sset_contains(&lrnat_rec->external_ips, vip)
> +        || lport_addresses_contains_ip(
> +               &lrnat_rec->dnat_force_snat_addrs, 1, vip)
> +        || lport_addresses_contains_ip(
> +               &lrnat_rec->lb_force_snat_addrs, 1, vip)) {
> +        return true;
> +    }
> +
> +    return lrnat_rec->lb_force_snat_router_ip
> +           && sset_contains(&od->router_ips, vip);
> +}
> +
>  static void
>  build_lrouter_nat_flows_for_lb(
>      struct ovn_lb_vip *lb_vip,
> @@ -13910,16 +13927,16 @@ build_lrouter_nat_flows_for_lb(
>              bitmap_set1(aff_dp_bitmap[type], index);
>          }
>
> -        if (sset_contains(&lrnat_rec->external_ips, lb_vip->vip_str)) {
> -            /* The load balancer vip is also present in the NAT entries.
> -             * So add a high priority lflow to advance the the packet
> -             * destined to the vip (and the vip port if defined)
> -             * in the S_ROUTER_IN_UNSNAT stage.
> +        if (lrouter_lb_vip_is_unsnat_ip(od, lrnat_rec,
> +                                        lb_vip->vip_str)) {
> +            /* The load balancer VIP is also present in an UNSNAT flow.
> +             * Add a high priority lflow to advance packets destined to
the
> +             * VIP (and the VIP port if defined) in S_ROUTER_IN_UNSNAT.
>               * There seems to be an issue with ovs-vswitchd. When the new
> -             * connection packet destined for the lb vip is received,
> -             * it is dnat'ed in the S_ROUTER_IN_DNAT stage in the dnat
> +             * connection packet destined for the LB VIP is received,
> +             * it is DNATed in the S_ROUTER_IN_DNAT stage in the DNAT
>               * conntrack zone. For the next packet, if it goes through
> -             * unsnat stage, the conntrack flags are not set properly,
and
> +             * UNSNAT stage, the conntrack flags are not set properly,
and
>               * it doesn't hit the established state flows in
>               * S_ROUTER_IN_DNAT stage. */
>              ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 120,

Thanks Tim for the fix.

For a port-less VIP, `unsnat_match` ends up holding just the protocol, so
on a gateway router with `lb_force_snat_ip=router_ip` we get:

priority=120, match=(ip4 && ip4.dst == 192.0.2.1 && tcp), action=(next;)
priority=110, match=(inport == "lr0-public" && ip4.dst == 192.0.2.1),
action=(ct_snat;)

The 120 flow now covers all TCP to that address and hides the 110 flow,
which is the only thing that reverses the force-SNAT for replies. Backend
replies never get unSNATed.

Could we require `lb_vip->port_str` for the force-SNAT cases? The
`external_ips` case is probably better left to a separate patch.

Best,
Han

> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> index 6d191c1a0..a60e5d012 100644
> --- a/tests/ovn-northd.at
> +++ b/tests/ovn-northd.at
> @@ -1882,6 +1882,46 @@ OVN_CLEANUP_NORTHD
>  AT_CLEANUP
>  ])
>
> +OVN_FOR_EACH_NORTHD_NO_HV([
> +AT_SETUP([Load balancer VIP in force-SNAT addresses])
> +ovn_start
> +
> +check ovn-nbctl ls-add public
> +check ovn-nbctl lr-add lr0
> +check ovn-nbctl set logical_router lr0 options:chassis=ch1
> +check ovn-nbctl lrp-add lr0 lr0-public 00:00:00:00:00:01 \
> +    192.0.2.1/24
> +check ovn-nbctl lsp-add-router-port public public-lr0 lr0-public
> +
> +check ovn-nbctl lb-add lb0 192.0.2.1:30663 198.51.100.10:5201
> +check ovn-nbctl lr-lb-add lr0 lb0
> +check ovn-nbctl lr-nat-add lr0 snat 169.254.0.47 198.51.100.0/24
> +check ovn-nbctl --wait=sb sync
> +
> +ovn-sbctl dump-flows lr0 > sbflows
> +AT_CAPTURE_FILE([sbflows])
> +AT_CHECK([grep "lr_in_unsnat.*priority=120" sbflows], [1])
> +
> +check ovn-nbctl --wait=sb set logical_router lr0 \
> +    options:lb_force_snat_ip=192.0.2.1
> +
> +AT_CHECK([ovn-sbctl dump-flows lr0 | \
> +    grep "lr_in_unsnat.*priority=120" | ovn_strip_lflows], [0], [dnl
> +  table=??(lr_in_unsnat       ), priority=120  , match=(ip4 && ip4.dst
== 192.0.2.1 && tcp && tcp.dst == 30663), action=(next;)
> +])
> +
> +check ovn-nbctl --wait=sb set logical_router lr0 \
> +    options:lb_force_snat_ip=router_ip
> +
> +AT_CHECK([ovn-sbctl dump-flows lr0 | \
> +    grep "lr_in_unsnat.*priority=120" | ovn_strip_lflows], [0], [dnl
> +  table=??(lr_in_unsnat       ), priority=120  , match=(ip4 && ip4.dst
== 192.0.2.1 && tcp && tcp.dst == 30663), action=(next;)
> +])
> +
> +OVN_CLEANUP_NORTHD
> +AT_CLEANUP
> +])
> +
>  OVN_FOR_EACH_NORTHD_NO_HV([
>  AT_SETUP([LRP same IP as VIP or SNAT])
>  ovn_start
> --
> 2.55.0
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to