diff mbox series

[ovs-dev,v5] northd: centralized reply lb traffic even if FIP is defined

Message ID feb19ca0c72ca22993446c67968bc06a9b9281c1.1685391886.git.lorenzo.bianconi@redhat.com
State Changes Requested
Headers show
Series [ovs-dev,v5] northd: centralized reply lb traffic even if FIP is defined | expand

Checks

Context Check Description
ovsrobot/apply-robot success apply and check: success
ovsrobot/github-robot-_Build_and_Test success github build: passed
ovsrobot/github-robot-_ovn-kubernetes fail github build: failed

Commit Message

Lorenzo Bianconi May 29, 2023, 8:27 p.m. UTC
In the current codebase for distributed gw router port use-case,
it is not possible to add a load balancer that redirects the traffic
to a back-end if it is used as internal IP of a FIP NAT rule since
the reply traffic is never centralized. Fix the issue centralizing the
traffic if it is the reply packet for the load balancer.

Reported-at: https://bugzilla.redhat.com/show_bug.cgi?id=2023609
Reviewed-by: Simon Horman <simon.horman@corigine.com>
Signed-off-by: Lorenzo Bianconi <lorenzo.bianconi@redhat.com>
---
Changes since v4:
- improve memory footprint
Changes since v3:
- add ovn unit-test and get rid of system-ovn unit-test.
- minor changes in ovn-northd unit-test.
Changes since v2:
- rebase on top of ovn main branch
- fix spelling
Changes since v1:
- add new system-test and unit-test
---
 northd/northd.c         |  20 ++++++-
 northd/ovn-northd.8.xml |  16 +++++
 tests/ovn-northd.at     |  50 ++++++++++++++++
 tests/ovn.at            | 125 ++++++++++++++++++++++++++++++++++++++++
 4 files changed, 210 insertions(+), 1 deletion(-)

Comments

Mark Michelson June 1, 2023, 2:50 p.m. UTC | #1
Hi Lorenzo,

THis looks good to me

Acked-by: Mark Michelson <mmichels@redhat.com>

On 5/29/23 16:27, Lorenzo Bianconi wrote:
> In the current codebase for distributed gw router port use-case,
> it is not possible to add a load balancer that redirects the traffic
> to a back-end if it is used as internal IP of a FIP NAT rule since
> the reply traffic is never centralized. Fix the issue centralizing the
> traffic if it is the reply packet for the load balancer.
> 
> Reported-at: https://bugzilla.redhat.com/show_bug.cgi?id=2023609
> Reviewed-by: Simon Horman <simon.horman@corigine.com>
> Signed-off-by: Lorenzo Bianconi <lorenzo.bianconi@redhat.com>
> ---
> Changes since v4:
> - improve memory footprint
> Changes since v3:
> - add ovn unit-test and get rid of system-ovn unit-test.
> - minor changes in ovn-northd unit-test.
> Changes since v2:
> - rebase on top of ovn main branch
> - fix spelling
> Changes since v1:
> - add new system-test and unit-test
> ---
>   northd/northd.c         |  20 ++++++-
>   northd/ovn-northd.8.xml |  16 +++++
>   tests/ovn-northd.at     |  50 ++++++++++++++++
>   tests/ovn.at            | 125 ++++++++++++++++++++++++++++++++++++++++
>   4 files changed, 210 insertions(+), 1 deletion(-)
> 
> diff --git a/northd/northd.c b/northd/northd.c
> index a6eca916b..9634da6c0 100644
> --- a/northd/northd.c
> +++ b/northd/northd.c
> @@ -10717,6 +10717,7 @@ struct lrouter_nat_lb_flows_ctx {
>   
>       struct ds *new_match;
>       struct ds *undnat_match;
> +    struct ds *gw_redir_action;
>   
>       struct ovn_lb_vip *lb_vip;
>       struct ovn_northd_lb *lb;
> @@ -10784,6 +10785,20 @@ build_distr_lrouter_nat_flows_for_lb(struct lrouter_nat_lb_flows_ctx *ctx,
>           return;
>       }
>   
> +    /* We need to centralize the LB traffic to properly perform
> +     * the undnat stage.
> +     */
> +    ds_put_format(ctx->undnat_match, ") && outport == %s", dgp->json_key);
> +    ds_clear(ctx->gw_redir_action);
> +    ds_put_format(ctx->gw_redir_action, "outport = %s; next;",
> +                  dgp->cr_port->json_key);
> +
> +    ovn_lflow_add_with_hint(ctx->lflows, od, S_ROUTER_IN_GW_REDIRECT,
> +                            200, ds_cstr(ctx->undnat_match),
> +                            ds_cstr(ctx->gw_redir_action),
> +                            &ctx->lb->nlb->header_);
> +    ds_truncate(ctx->undnat_match, undnat_match_len);
> +
>       ds_put_format(ctx->undnat_match, ") && (inport == %s || outport == %s)"
>                     " && is_chassis_resident(%s)", dgp->json_key, dgp->json_key,
>                     dgp->cr_port->json_key);
> @@ -10854,6 +10869,7 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip,
>       struct ds force_snat_act = DS_EMPTY_INITIALIZER;
>       struct ds undnat_match = DS_EMPTY_INITIALIZER;
>       struct ds unsnat_match = DS_EMPTY_INITIALIZER;
> +    struct ds gw_redir_action = DS_EMPTY_INITIALIZER;
>   
>       ds_clear(match);
>       ds_clear(action);
> @@ -10912,7 +10928,8 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip,
>           .lflows = lflows,
>           .meter_groups = meter_groups,
>           .new_match = match,
> -        .undnat_match = &undnat_match
> +        .undnat_match = &undnat_match,
> +        .gw_redir_action = &gw_redir_action,
>       };
>   
>       ctx.new_action[LROUTER_NAT_LB_FLOW_NORMAL] = ds_cstr(action);
> @@ -11006,6 +11023,7 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip,
>       ds_destroy(&undnat_match);
>       ds_destroy(&skip_snat_act);
>       ds_destroy(&force_snat_act);
> +    ds_destroy(&gw_redir_action);
>   
>       for (size_t i = 0; i < LROUTER_NAT_LB_FLOW_MAX + 2; i++) {
>           bitmap_free(dp_bitmap[i]);
> diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
> index 540fe03bd..e4f971f4b 100644
> --- a/northd/ovn-northd.8.xml
> +++ b/northd/ovn-northd.8.xml
> @@ -4578,6 +4578,22 @@ icmp6 {
>       </p>
>   
>       <ul>
> +      <li>
> +        For all the configured load balancing rules that include an IPv4
> +        address <var>VIP</var>, and a list of IPv4 backend addresses
> +        <var>B0</var>, <var>B1</var> .. <var>Bn</var> defined for the
> +        <var>VIP</var> a priority-200 flow is added that matches <code>
> +        ip4 &amp;&amp; (ip4.src == <var>B0</var> || ip4.src == <var>B1</var>
> +        || ... || ip4.src == <var>Bn</var>)</code> with an action <code>
> +        outport = <var>CR</var>; next;</code> where <var>CR</var> is the
> +        <code>chassisredirect</code> port representing the instance of the
> +        logical router distributed gateway port on the gateway chassis.
> +        If the backend IPv4 address <var>Bx</var> is also configured with
> +        L4 port <var>PORT</var> of protocol <var>P</var>, then the match
> +        also includes <code>P.src</code> == <var>PORT</var>.
> +        Similar flows are added for IPv6.
> +      </li>
> +
>         <li>
>           For each NAT rule in the OVN Northbound database that can
>           be handled in a distributed manner, a priority-100 logical
> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> index e3669bdf5..aed6a9ea6 100644
> --- a/tests/ovn-northd.at
> +++ b/tests/ovn-northd.at
> @@ -9484,3 +9484,53 @@ AT_CHECK([ovn-sbctl lflow-list sw | grep ls_out_pre_lb | grep priority=110 | gre
>   
>   AT_CLEANUP
>   ])
> +
> +OVN_FOR_EACH_NORTHD_NO_HV([
> +AT_SETUP([check fip and lb flows])
> +AT_KEYWORDS([fip-lb-flows])
> +ovn_start
> +
> +check ovn-nbctl lr-add R1
> +check ovn-nbctl lrp-add R1 R1-S0 02:ac:10:01:00:01 10.0.0.1/24 1000::a/64
> +check ovn-nbctl lrp-add R1 R1-PUB 02:ac:20:01:01:01 172.16.0.1/24 3000::a/64
> +check ovn-nbctl lrp-set-gateway-chassis R1-PUB hv1 20
> +
> +check ovn-nbctl ls-add S0
> +check ovn-nbctl lsp-add S0 S0-R1
> +check ovn-nbctl lsp-set-type S0-R1 router
> +check ovn-nbctl lsp-set-addresses S0-R1 02:ac:10:01:00:01
> +check ovn-nbctl lsp-set-options S0-R1 router-port=R1-S0
> +check ovn-nbctl lsp-add S0 S0-P0
> +check ovn-nbctl lsp-set-addresses S0-P0 "50:54:00:00:00:03 10.0.0.3 1000::3"
> +
> +check ovn-nbctl lr-nat-add R1 dnat_and_snat 172.16.0.110 10.0.0.3 S0-P0 30:54:00:00:00:03
> +check ovn-nbctl lr-nat-add R1 dnat_and_snat 3000::c 1000::3 S0-P0 40:54:00:00:00:03
> +
> +ovn-sbctl dump-flows R1 > R1flows
> +AT_CAPTURE_FILE([R1flows])
> +
> +AT_CHECK([grep "lr_in_gw_redirect" R1flows | sed s'/table=../table=??/' |sort], [0], [dnl
> +  table=??(lr_in_gw_redirect  ), priority=0    , match=(1), action=(next;)
> +  table=??(lr_in_gw_redirect  ), priority=100  , match=(ip4.src == 10.0.0.3 && outport == "R1-PUB" && is_chassis_resident("S0-P0")), action=(eth.src = 30:54:00:00:00:03; reg1 = 172.16.0.110; next;)
> +  table=??(lr_in_gw_redirect  ), priority=100  , match=(ip6.src == 1000::3 && outport == "R1-PUB" && is_chassis_resident("S0-P0")), action=(eth.src = 40:54:00:00:00:03; xxreg1 = 3000::c; next;)
> +  table=??(lr_in_gw_redirect  ), priority=50   , match=(outport == "R1-PUB"), action=(outport = "cr-R1-PUB"; next;)
> +])
> +
> +check ovn-nbctl lb-add lb_tcp4 172.16.0.100:50001 10.0.0.2:50001,10.0.0.3:50001,10.0.0.4:50001 tcp
> +check ovn-nbctl lb-add lb_tcp6 [[1000::1]]:50001 [[1000::3]]:8080
> +check ovn-nbctl --wait=sb lr-lb-add R1 lb_tcp4
> +check ovn-nbctl --wait=sb lr-lb-add R1 lb_tcp6
> +
> +ovn-sbctl dump-flows R1 > R1flows
> +AT_CAPTURE_FILE([R1flows])
> +AT_CHECK([grep "lr_in_gw_redirect" R1flows |sed s'/table=../table=??/' |sort], [0], [dnl
> +  table=??(lr_in_gw_redirect  ), priority=0    , match=(1), action=(next;)
> +  table=??(lr_in_gw_redirect  ), priority=100  , match=(ip4.src == 10.0.0.3 && outport == "R1-PUB" && is_chassis_resident("S0-P0")), action=(eth.src = 30:54:00:00:00:03; reg1 = 172.16.0.110; next;)
> +  table=??(lr_in_gw_redirect  ), priority=100  , match=(ip6.src == 1000::3 && outport == "R1-PUB" && is_chassis_resident("S0-P0")), action=(eth.src = 40:54:00:00:00:03; xxreg1 = 3000::c; next;)
> +  table=??(lr_in_gw_redirect  ), priority=200  , match=(ip4 && ((ip4.src == 10.0.0.2 && tcp.src == 50001) || (ip4.src == 10.0.0.3 && tcp.src == 50001) || (ip4.src == 10.0.0.4 && tcp.src == 50001)) && outport == "R1-PUB"), action=(outport = "cr-R1-PUB"; next;)
> +  table=??(lr_in_gw_redirect  ), priority=200  , match=(ip6 && ((ip6.src == 1000::3 && tcp.src == 8080)) && outport == "R1-PUB"), action=(outport = "cr-R1-PUB"; next;)
> +  table=??(lr_in_gw_redirect  ), priority=50   , match=(outport == "R1-PUB"), action=(outport = "cr-R1-PUB"; next;)
> +])
> +
> +AT_CLEANUP
> +])
> diff --git a/tests/ovn.at b/tests/ovn.at
> index 6f9fbbfd2..92bfd78bd 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -35819,3 +35819,128 @@ OVS_WAIT_UNTIL([test $(as hv-1 ovs-vsctl list queue | grep -c 'burst="8000000000
>   
>   AT_CLEANUP
>   ])
> +
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([DNAT_SNAT and LB traffic])
> +AT_KEYWORDS([dnat-snat-lb])
> +ovn_start
> +
> +test_ip_req_packet() {
> +    local src_mac="505400000088"
> +    local dst_mac="$1"
> +    local src_ip=$(ip_to_hex 172 168 0 200)
> +    local dst_ip="$2"
> +    local cksum="$3"
> +
> +    local packet=${dst_mac}${src_mac}080045000072000000004011${cksum}
> +    local packet_l3=${src_ip}${dst_ip}0035111100080000
> +
> +    packet_l3=${packet_l3}000000000000000000000000000000000000
> +    packet_l3=${packet_l3}000000000000000000000000000000000000
> +    packet_l3=${packet_l3}000000000000000000000000000000000000
> +    packet_l3=${packet_l3}000000000000000000000000000000000000
> +    packet_l3=${packet_l3}0000000000000000000000000000
> +    packet=${packet}${packet_l3}
> +
> +    as hv1 reset_pcap_file hv1-vif1 hv1/vif1
> +    as hv2 reset_pcap_file hv2-vif1 hv2/vif1
> +    check as hv2 ovs-appctl netdev-dummy/receive hv2-vif1 $packet
> +}
> +
> +test_ip_rep_packet() {
> +    local src_mac="505400000001"
> +    local dst_mac="00000000ff01"
> +    local src_ip=$(ip_to_hex 10 0 0 2)
> +    local dst_ip=$(ip_to_hex 172 168 0 200)
> +
> +    local packet=${dst_mac}${src_mac}080045000072000000004011c309
> +    local packet_l3=${src_ip}${dst_ip}1111003500080000
> +
> +    packet_l3=${packet_l3}000000000000000000000000000000000000
> +    packet_l3=${packet_l3}000000000000000000000000000000000000
> +    packet_l3=${packet_l3}000000000000000000000000000000000000
> +    packet_l3=${packet_l3}000000000000000000000000000000000000
> +    packet_l3=${packet_l3}0000000000000000000000000000
> +    packet=${packet}${packet_l3}
> +
> +    check as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
> +}
> +
> +net_add n
> +
> +sim_add hv1
> +as hv1
> +check ovs-vsctl add-br br-phys
> +ovn_attach n br-phys 192.168.0.1
> +check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
> +check ovs-vsctl -- add-port br-int hv1-vif1 -- \
> +    set interface hv1-vif1 external-ids:iface-id=sw0-port1 \
> +    options:tx_pcap=hv1/vif1-tx.pcap \
> +    options:rxq_pcap=hv1/vif1-rx.pcap
> +
> +sim_add hv2
> +as hv2
> +check ovs-vsctl add-br br-phys
> +ovn_attach n br-phys 192.168.0.2
> +check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
> +check ovs-vsctl -- add-port br-int hv2-vif1 -- \
> +    set interface hv2-vif1 external-ids:iface-id=public-port1 \
> +    options:tx_pcap=hv2/vif1-tx.pcap \
> +    options:rxq_pcap=hv2/vif1-rx.pcap
> +
> +check ovn-nbctl ls-add sw0
> +check ovn-nbctl lsp-add sw0 sw0-port1
> +check ovn-nbctl lsp-set-addresses sw0-port1 "50:54:00:00:00:01 10.0.0.2"
> +
> +check ovn-nbctl ls-add public
> +check ovn-nbctl lsp-add public ln-public
> +check ovn-nbctl lsp-set-type ln-public localnet
> +check ovn-nbctl lsp-set-addresses ln-public unknown
> +check ovn-nbctl lsp-set-options ln-public network_name=phys
> +check ovn-nbctl lsp-add public public-port1
> +check ovn-nbctl lsp-set-addresses public-port1 "50:54:00:00:00:88 172.168.0.200"
> +
> +check ovn-nbctl lr-add lr0
> +check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24
> +check ovn-nbctl lsp-add sw0 sw0-lr0
> +check ovn-nbctl lsp-set-type sw0-lr0 router
> +check ovn-nbctl lsp-set-addresses sw0-lr0 router
> +check ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
> +check ovn-nbctl lrp-add lr0 lr0-public 00:00:20:20:12:13 172.168.0.1/24
> +check ovn-nbctl lsp-add public public-lr0
> +check ovn-nbctl lsp-set-type public-lr0 router
> +check ovn-nbctl lsp-set-addresses public-lr0 router
> +check ovn-nbctl lsp-set-options public-lr0 router-port=lr0-public
> +
> +check ovn-nbctl lrp-set-gateway-chassis lr0-public hv2 20
> +
> +check ovn-nbctl lr-nat-add lr0 snat 172.168.0.1 10.0.0.0/24
> +check ovn-nbctl lr-nat-add lr0 dnat_and_snat 172.168.0.110 10.0.0.2 sw0-port1 f0:00:00:01:02:03
> +
> +wait_for_ports_up
> +OVN_POPULATE_ARP
> +
> +# send UDP request to the FIP associated to sw0-port1
> +test_ip_req_packet "f00000010203" $(ip_to_hex 172 168 0 110) "1ff5"
> +OVS_WAIT_UNTIL([test $($PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap | wc -l) -ge 1])
> +# send UDP reply from sw0-port1
> +test_ip_rep_packet
> +# packet sent by the FIP
> +echo "505400000088f00000010203080045000072000000003f1120f5aca8006eaca800c811110035000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" > expected
> +
> +OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
> +
> +check ovn-nbctl lb-add lb0 172.168.0.1:4369 10.0.0.2:4369 udp
> +check ovn-nbctl lr-lb-add lr0 lb0
> +
> +# send UDP request to the load-balancer VIP
> +test_ip_req_packet "000020201213" $(ip_to_hex 172 168 0 1) "2062"
> +OVS_WAIT_UNTIL([test $($PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap | wc -l) -ge 1])
> +# send UDP reply from sw0-port1
> +test_ip_rep_packet
> +# packet sent by the load balancer VIP
> +echo "505400000088000020201213080045000072000000003f112162aca80001aca800c811110035000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" > expected
> +OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
> +
> +AT_CLEANUP
> +])
Dumitru Ceara June 8, 2023, 8:56 a.m. UTC | #2
On 6/1/23 16:50, Mark Michelson wrote:
> Hi Lorenzo,
> 
> THis looks good to me
> 
> Acked-by: Mark Michelson <mmichels@redhat.com>
> 
> On 5/29/23 16:27, Lorenzo Bianconi wrote:
>> In the current codebase for distributed gw router port use-case,
>> it is not possible to add a load balancer that redirects the traffic
>> to a back-end if it is used as internal IP of a FIP NAT rule since
>> the reply traffic is never centralized. Fix the issue centralizing the
>> traffic if it is the reply packet for the load balancer.
>>
>> Reported-at: https://bugzilla.redhat.com/show_bug.cgi?id=2023609
>> Reviewed-by: Simon Horman <simon.horman@corigine.com>
>> Signed-off-by: Lorenzo Bianconi <lorenzo.bianconi@redhat.com>
>> ---
>> Changes since v4:
>> - improve memory footprint
>> Changes since v3:
>> - add ovn unit-test and get rid of system-ovn unit-test.
>> - minor changes in ovn-northd unit-test.
>> Changes since v2:
>> - rebase on top of ovn main branch
>> - fix spelling
>> Changes since v1:
>> - add new system-test and unit-test
>> ---
>>   northd/northd.c         |  20 ++++++-
>>   northd/ovn-northd.8.xml |  16 +++++
>>   tests/ovn-northd.at     |  50 ++++++++++++++++
>>   tests/ovn.at            | 125 ++++++++++++++++++++++++++++++++++++++++
>>   4 files changed, 210 insertions(+), 1 deletion(-)
>>
>> diff --git a/northd/northd.c b/northd/northd.c
>> index a6eca916b..9634da6c0 100644
>> --- a/northd/northd.c
>> +++ b/northd/northd.c
>> @@ -10717,6 +10717,7 @@ struct lrouter_nat_lb_flows_ctx {
>>         struct ds *new_match;
>>       struct ds *undnat_match;
>> +    struct ds *gw_redir_action;
>>         struct ovn_lb_vip *lb_vip;
>>       struct ovn_northd_lb *lb;
>> @@ -10784,6 +10785,20 @@ build_distr_lrouter_nat_flows_for_lb(struct
>> lrouter_nat_lb_flows_ctx *ctx,
>>           return;
>>       }
>>   +    /* We need to centralize the LB traffic to properly perform
>> +     * the undnat stage.
>> +     */
>> +    ds_put_format(ctx->undnat_match, ") && outport == %s",
>> dgp->json_key);
>> +    ds_clear(ctx->gw_redir_action);
>> +    ds_put_format(ctx->gw_redir_action, "outport = %s; next;",
>> +                  dgp->cr_port->json_key);
>> +
>> +    ovn_lflow_add_with_hint(ctx->lflows, od, S_ROUTER_IN_GW_REDIRECT,
>> +                            200, ds_cstr(ctx->undnat_match),
>> +                            ds_cstr(ctx->gw_redir_action),
>> +                            &ctx->lb->nlb->header_);
>> +    ds_truncate(ctx->undnat_match, undnat_match_len);
>> +
>>       ds_put_format(ctx->undnat_match, ") && (inport == %s || outport
>> == %s)"
>>                     " && is_chassis_resident(%s)", dgp->json_key,
>> dgp->json_key,
>>                     dgp->cr_port->json_key);
>> @@ -10854,6 +10869,7 @@ build_lrouter_nat_flows_for_lb(struct
>> ovn_lb_vip *lb_vip,
>>       struct ds force_snat_act = DS_EMPTY_INITIALIZER;
>>       struct ds undnat_match = DS_EMPTY_INITIALIZER;
>>       struct ds unsnat_match = DS_EMPTY_INITIALIZER;
>> +    struct ds gw_redir_action = DS_EMPTY_INITIALIZER;
>>         ds_clear(match);
>>       ds_clear(action);
>> @@ -10912,7 +10928,8 @@ build_lrouter_nat_flows_for_lb(struct
>> ovn_lb_vip *lb_vip,
>>           .lflows = lflows,
>>           .meter_groups = meter_groups,
>>           .new_match = match,
>> -        .undnat_match = &undnat_match
>> +        .undnat_match = &undnat_match,
>> +        .gw_redir_action = &gw_redir_action,
>>       };
>>         ctx.new_action[LROUTER_NAT_LB_FLOW_NORMAL] = ds_cstr(action);
>> @@ -11006,6 +11023,7 @@ build_lrouter_nat_flows_for_lb(struct
>> ovn_lb_vip *lb_vip,
>>       ds_destroy(&undnat_match);
>>       ds_destroy(&skip_snat_act);
>>       ds_destroy(&force_snat_act);
>> +    ds_destroy(&gw_redir_action);
>>         for (size_t i = 0; i < LROUTER_NAT_LB_FLOW_MAX + 2; i++) {
>>           bitmap_free(dp_bitmap[i]);
>> diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
>> index 540fe03bd..e4f971f4b 100644
>> --- a/northd/ovn-northd.8.xml
>> +++ b/northd/ovn-northd.8.xml
>> @@ -4578,6 +4578,22 @@ icmp6 {
>>       </p>
>>         <ul>
>> +      <li>
>> +        For all the configured load balancing rules that include an IPv4
>> +        address <var>VIP</var>, and a list of IPv4 backend addresses
>> +        <var>B0</var>, <var>B1</var> .. <var>Bn</var> defined for the
>> +        <var>VIP</var> a priority-200 flow is added that matches <code>
>> +        ip4 &amp;&amp; (ip4.src == <var>B0</var> || ip4.src ==
>> <var>B1</var>
>> +        || ... || ip4.src == <var>Bn</var>)</code> with an action <code>
>> +        outport = <var>CR</var>; next;</code> where <var>CR</var> is the
>> +        <code>chassisredirect</code> port representing the instance
>> of the
>> +        logical router distributed gateway port on the gateway chassis.
>> +        If the backend IPv4 address <var>Bx</var> is also configured
>> with
>> +        L4 port <var>PORT</var> of protocol <var>P</var>, then the match
>> +        also includes <code>P.src</code> == <var>PORT</var>.
>> +        Similar flows are added for IPv6.
>> +      </li>
>> +
>>         <li>
>>           For each NAT rule in the OVN Northbound database that can
>>           be handled in a distributed manner, a priority-100 logical
>> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
>> index e3669bdf5..aed6a9ea6 100644
>> --- a/tests/ovn-northd.at
>> +++ b/tests/ovn-northd.at
>> @@ -9484,3 +9484,53 @@ AT_CHECK([ovn-sbctl lflow-list sw | grep
>> ls_out_pre_lb | grep priority=110 | gre
>>     AT_CLEANUP
>>   ])
>> +
>> +OVN_FOR_EACH_NORTHD_NO_HV([
>> +AT_SETUP([check fip and lb flows])
>> +AT_KEYWORDS([fip-lb-flows])
>> +ovn_start
>> +
>> +check ovn-nbctl lr-add R1
>> +check ovn-nbctl lrp-add R1 R1-S0 02:ac:10:01:00:01 10.0.0.1/24
>> 1000::a/64
>> +check ovn-nbctl lrp-add R1 R1-PUB 02:ac:20:01:01:01 172.16.0.1/24
>> 3000::a/64
>> +check ovn-nbctl lrp-set-gateway-chassis R1-PUB hv1 20
>> +
>> +check ovn-nbctl ls-add S0
>> +check ovn-nbctl lsp-add S0 S0-R1
>> +check ovn-nbctl lsp-set-type S0-R1 router
>> +check ovn-nbctl lsp-set-addresses S0-R1 02:ac:10:01:00:01
>> +check ovn-nbctl lsp-set-options S0-R1 router-port=R1-S0
>> +check ovn-nbctl lsp-add S0 S0-P0
>> +check ovn-nbctl lsp-set-addresses S0-P0 "50:54:00:00:00:03 10.0.0.3
>> 1000::3"
>> +
>> +check ovn-nbctl lr-nat-add R1 dnat_and_snat 172.16.0.110 10.0.0.3
>> S0-P0 30:54:00:00:00:03
>> +check ovn-nbctl lr-nat-add R1 dnat_and_snat 3000::c 1000::3 S0-P0
>> 40:54:00:00:00:03
>> +
>> +ovn-sbctl dump-flows R1 > R1flows
>> +AT_CAPTURE_FILE([R1flows])
>> +
>> +AT_CHECK([grep "lr_in_gw_redirect" R1flows | sed
>> s'/table=../table=??/' |sort], [0], [dnl
>> +  table=??(lr_in_gw_redirect  ), priority=0    , match=(1),
>> action=(next;)
>> +  table=??(lr_in_gw_redirect  ), priority=100  , match=(ip4.src ==
>> 10.0.0.3 && outport == "R1-PUB" && is_chassis_resident("S0-P0")),
>> action=(eth.src = 30:54:00:00:00:03; reg1 = 172.16.0.110; next;)
>> +  table=??(lr_in_gw_redirect  ), priority=100  , match=(ip6.src ==
>> 1000::3 && outport == "R1-PUB" && is_chassis_resident("S0-P0")),
>> action=(eth.src = 40:54:00:00:00:03; xxreg1 = 3000::c; next;)
>> +  table=??(lr_in_gw_redirect  ), priority=50   , match=(outport ==
>> "R1-PUB"), action=(outport = "cr-R1-PUB"; next;)
>> +])
>> +
>> +check ovn-nbctl lb-add lb_tcp4 172.16.0.100:50001
>> 10.0.0.2:50001,10.0.0.3:50001,10.0.0.4:50001 tcp
>> +check ovn-nbctl lb-add lb_tcp6 [[1000::1]]:50001 [[1000::3]]:8080
>> +check ovn-nbctl --wait=sb lr-lb-add R1 lb_tcp4
>> +check ovn-nbctl --wait=sb lr-lb-add R1 lb_tcp6
>> +
>> +ovn-sbctl dump-flows R1 > R1flows
>> +AT_CAPTURE_FILE([R1flows])
>> +AT_CHECK([grep "lr_in_gw_redirect" R1flows |sed
>> s'/table=../table=??/' |sort], [0], [dnl
>> +  table=??(lr_in_gw_redirect  ), priority=0    , match=(1),
>> action=(next;)
>> +  table=??(lr_in_gw_redirect  ), priority=100  , match=(ip4.src ==
>> 10.0.0.3 && outport == "R1-PUB" && is_chassis_resident("S0-P0")),
>> action=(eth.src = 30:54:00:00:00:03; reg1 = 172.16.0.110; next;)
>> +  table=??(lr_in_gw_redirect  ), priority=100  , match=(ip6.src ==
>> 1000::3 && outport == "R1-PUB" && is_chassis_resident("S0-P0")),
>> action=(eth.src = 40:54:00:00:00:03; xxreg1 = 3000::c; next;)
>> +  table=??(lr_in_gw_redirect  ), priority=200  , match=(ip4 &&
>> ((ip4.src == 10.0.0.2 && tcp.src == 50001) || (ip4.src == 10.0.0.3 &&
>> tcp.src == 50001) || (ip4.src == 10.0.0.4 && tcp.src == 50001)) &&
>> outport == "R1-PUB"), action=(outport = "cr-R1-PUB"; next;)
>> +  table=??(lr_in_gw_redirect  ), priority=200  , match=(ip6 &&
>> ((ip6.src == 1000::3 && tcp.src == 8080)) && outport == "R1-PUB"),
>> action=(outport = "cr-R1-PUB"; next;)
>> +  table=??(lr_in_gw_redirect  ), priority=50   , match=(outport ==
>> "R1-PUB"), action=(outport = "cr-R1-PUB"; next;)
>> +])
>> +
>> +AT_CLEANUP
>> +])
>> diff --git a/tests/ovn.at b/tests/ovn.at
>> index 6f9fbbfd2..92bfd78bd 100644
>> --- a/tests/ovn.at
>> +++ b/tests/ovn.at
>> @@ -35819,3 +35819,128 @@ OVS_WAIT_UNTIL([test $(as hv-1 ovs-vsctl
>> list queue | grep -c 'burst="8000000000
>>     AT_CLEANUP
>>   ])
>> +
>> +OVN_FOR_EACH_NORTHD([
>> +AT_SETUP([DNAT_SNAT and LB traffic])
>> +AT_KEYWORDS([dnat-snat-lb])
>> +ovn_start
>> +
>> +test_ip_req_packet() {
>> +    local src_mac="505400000088"
>> +    local dst_mac="$1"
>> +    local src_ip=$(ip_to_hex 172 168 0 200)
>> +    local dst_ip="$2"
>> +    local cksum="$3"
>> +
>> +    local packet=${dst_mac}${src_mac}080045000072000000004011${cksum}
>> +    local packet_l3=${src_ip}${dst_ip}0035111100080000
>> +
>> +    packet_l3=${packet_l3}000000000000000000000000000000000000
>> +    packet_l3=${packet_l3}000000000000000000000000000000000000
>> +    packet_l3=${packet_l3}000000000000000000000000000000000000
>> +    packet_l3=${packet_l3}000000000000000000000000000000000000
>> +    packet_l3=${packet_l3}0000000000000000000000000000
>> +    packet=${packet}${packet_l3}
>> +
>> +    as hv1 reset_pcap_file hv1-vif1 hv1/vif1
>> +    as hv2 reset_pcap_file hv2-vif1 hv2/vif1
>> +    check as hv2 ovs-appctl netdev-dummy/receive hv2-vif1 $packet
>> +}
>> +
>> +test_ip_rep_packet() {
>> +    local src_mac="505400000001"
>> +    local dst_mac="00000000ff01"
>> +    local src_ip=$(ip_to_hex 10 0 0 2)
>> +    local dst_ip=$(ip_to_hex 172 168 0 200)
>> +
>> +    local packet=${dst_mac}${src_mac}080045000072000000004011c309
>> +    local packet_l3=${src_ip}${dst_ip}1111003500080000
>> +
>> +    packet_l3=${packet_l3}000000000000000000000000000000000000
>> +    packet_l3=${packet_l3}000000000000000000000000000000000000
>> +    packet_l3=${packet_l3}000000000000000000000000000000000000
>> +    packet_l3=${packet_l3}000000000000000000000000000000000000
>> +    packet_l3=${packet_l3}0000000000000000000000000000
>> +    packet=${packet}${packet_l3}
>> +
>> +    check as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
>> +}
>> +
>> +net_add n
>> +
>> +sim_add hv1
>> +as hv1
>> +check ovs-vsctl add-br br-phys
>> +ovn_attach n br-phys 192.168.0.1
>> +check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
>> +check ovs-vsctl -- add-port br-int hv1-vif1 -- \
>> +    set interface hv1-vif1 external-ids:iface-id=sw0-port1 \
>> +    options:tx_pcap=hv1/vif1-tx.pcap \
>> +    options:rxq_pcap=hv1/vif1-rx.pcap
>> +
>> +sim_add hv2
>> +as hv2
>> +check ovs-vsctl add-br br-phys
>> +ovn_attach n br-phys 192.168.0.2
>> +check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
>> +check ovs-vsctl -- add-port br-int hv2-vif1 -- \
>> +    set interface hv2-vif1 external-ids:iface-id=public-port1 \
>> +    options:tx_pcap=hv2/vif1-tx.pcap \
>> +    options:rxq_pcap=hv2/vif1-rx.pcap
>> +
>> +check ovn-nbctl ls-add sw0
>> +check ovn-nbctl lsp-add sw0 sw0-port1
>> +check ovn-nbctl lsp-set-addresses sw0-port1 "50:54:00:00:00:01 10.0.0.2"
>> +
>> +check ovn-nbctl ls-add public
>> +check ovn-nbctl lsp-add public ln-public
>> +check ovn-nbctl lsp-set-type ln-public localnet
>> +check ovn-nbctl lsp-set-addresses ln-public unknown
>> +check ovn-nbctl lsp-set-options ln-public network_name=phys
>> +check ovn-nbctl lsp-add public public-port1
>> +check ovn-nbctl lsp-set-addresses public-port1 "50:54:00:00:00:88
>> 172.168.0.200"
>> +
>> +check ovn-nbctl lr-add lr0
>> +check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24
>> +check ovn-nbctl lsp-add sw0 sw0-lr0
>> +check ovn-nbctl lsp-set-type sw0-lr0 router
>> +check ovn-nbctl lsp-set-addresses sw0-lr0 router
>> +check ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
>> +check ovn-nbctl lrp-add lr0 lr0-public 00:00:20:20:12:13 172.168.0.1/24
>> +check ovn-nbctl lsp-add public public-lr0
>> +check ovn-nbctl lsp-set-type public-lr0 router
>> +check ovn-nbctl lsp-set-addresses public-lr0 router
>> +check ovn-nbctl lsp-set-options public-lr0 router-port=lr0-public
>> +
>> +check ovn-nbctl lrp-set-gateway-chassis lr0-public hv2 20
>> +
>> +check ovn-nbctl lr-nat-add lr0 snat 172.168.0.1 10.0.0.0/24
>> +check ovn-nbctl lr-nat-add lr0 dnat_and_snat 172.168.0.110 10.0.0.2
>> sw0-port1 f0:00:00:01:02:03
>> +
>> +wait_for_ports_up
>> +OVN_POPULATE_ARP
>> +
>> +# send UDP request to the FIP associated to sw0-port1
>> +test_ip_req_packet "f00000010203" $(ip_to_hex 172 168 0 110) "1ff5"
>> +OVS_WAIT_UNTIL([test $($PYTHON "$ovs_srcdir/utilities/ovs-pcap.in"
>> hv1/vif1-tx.pcap | wc -l) -ge 1])
>> +# send UDP reply from sw0-port1
>> +test_ip_rep_packet
>> +# packet sent by the FIP
>> +echo
>> "505400000088f00000010203080045000072000000003f1120f5aca8006eaca800c811110035000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" > expected

Hi Lorenzo,

I wanted to apply this patch but changed my mind at the last moment
because of this.  We now have 'fmt_pkt()' in ovn-macros.at and we use it
in a few tests.  It avoids raw packet contents and makes packet tests
way more readable.

Do you think you can find some time to use it in this patch too instead?

If not, I'll reconsider applying v5 and fixing this as a follow up.  But
I'd rather have it nice from the beginning. :)

Thanks,
Dumitru

>> +
>> +OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
>> +
>> +check ovn-nbctl lb-add lb0 172.168.0.1:4369 10.0.0.2:4369 udp
>> +check ovn-nbctl lr-lb-add lr0 lb0
>> +
>> +# send UDP request to the load-balancer VIP
>> +test_ip_req_packet "000020201213" $(ip_to_hex 172 168 0 1) "2062"
>> +OVS_WAIT_UNTIL([test $($PYTHON "$ovs_srcdir/utilities/ovs-pcap.in"
>> hv1/vif1-tx.pcap | wc -l) -ge 1])
>> +# send UDP reply from sw0-port1
>> +test_ip_rep_packet
>> +# packet sent by the load balancer VIP
>> +echo
>> "505400000088000020201213080045000072000000003f112162aca80001aca800c811110035000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" > expected
>> +OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
>> +
>> +AT_CLEANUP
>> +])
>
diff mbox series

Patch

diff --git a/northd/northd.c b/northd/northd.c
index a6eca916b..9634da6c0 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -10717,6 +10717,7 @@  struct lrouter_nat_lb_flows_ctx {
 
     struct ds *new_match;
     struct ds *undnat_match;
+    struct ds *gw_redir_action;
 
     struct ovn_lb_vip *lb_vip;
     struct ovn_northd_lb *lb;
@@ -10784,6 +10785,20 @@  build_distr_lrouter_nat_flows_for_lb(struct lrouter_nat_lb_flows_ctx *ctx,
         return;
     }
 
+    /* We need to centralize the LB traffic to properly perform
+     * the undnat stage.
+     */
+    ds_put_format(ctx->undnat_match, ") && outport == %s", dgp->json_key);
+    ds_clear(ctx->gw_redir_action);
+    ds_put_format(ctx->gw_redir_action, "outport = %s; next;",
+                  dgp->cr_port->json_key);
+
+    ovn_lflow_add_with_hint(ctx->lflows, od, S_ROUTER_IN_GW_REDIRECT,
+                            200, ds_cstr(ctx->undnat_match),
+                            ds_cstr(ctx->gw_redir_action),
+                            &ctx->lb->nlb->header_);
+    ds_truncate(ctx->undnat_match, undnat_match_len);
+
     ds_put_format(ctx->undnat_match, ") && (inport == %s || outport == %s)"
                   " && is_chassis_resident(%s)", dgp->json_key, dgp->json_key,
                   dgp->cr_port->json_key);
@@ -10854,6 +10869,7 @@  build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip,
     struct ds force_snat_act = DS_EMPTY_INITIALIZER;
     struct ds undnat_match = DS_EMPTY_INITIALIZER;
     struct ds unsnat_match = DS_EMPTY_INITIALIZER;
+    struct ds gw_redir_action = DS_EMPTY_INITIALIZER;
 
     ds_clear(match);
     ds_clear(action);
@@ -10912,7 +10928,8 @@  build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip,
         .lflows = lflows,
         .meter_groups = meter_groups,
         .new_match = match,
-        .undnat_match = &undnat_match
+        .undnat_match = &undnat_match,
+        .gw_redir_action = &gw_redir_action,
     };
 
     ctx.new_action[LROUTER_NAT_LB_FLOW_NORMAL] = ds_cstr(action);
@@ -11006,6 +11023,7 @@  build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip,
     ds_destroy(&undnat_match);
     ds_destroy(&skip_snat_act);
     ds_destroy(&force_snat_act);
+    ds_destroy(&gw_redir_action);
 
     for (size_t i = 0; i < LROUTER_NAT_LB_FLOW_MAX + 2; i++) {
         bitmap_free(dp_bitmap[i]);
diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
index 540fe03bd..e4f971f4b 100644
--- a/northd/ovn-northd.8.xml
+++ b/northd/ovn-northd.8.xml
@@ -4578,6 +4578,22 @@  icmp6 {
     </p>
 
     <ul>
+      <li>
+        For all the configured load balancing rules that include an IPv4
+        address <var>VIP</var>, and a list of IPv4 backend addresses
+        <var>B0</var>, <var>B1</var> .. <var>Bn</var> defined for the
+        <var>VIP</var> a priority-200 flow is added that matches <code>
+        ip4 &amp;&amp; (ip4.src == <var>B0</var> || ip4.src == <var>B1</var>
+        || ... || ip4.src == <var>Bn</var>)</code> with an action <code>
+        outport = <var>CR</var>; next;</code> where <var>CR</var> is the
+        <code>chassisredirect</code> port representing the instance of the
+        logical router distributed gateway port on the gateway chassis.
+        If the backend IPv4 address <var>Bx</var> is also configured with
+        L4 port <var>PORT</var> of protocol <var>P</var>, then the match
+        also includes <code>P.src</code> == <var>PORT</var>.
+        Similar flows are added for IPv6.
+      </li>
+
       <li>
         For each NAT rule in the OVN Northbound database that can
         be handled in a distributed manner, a priority-100 logical
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index e3669bdf5..aed6a9ea6 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -9484,3 +9484,53 @@  AT_CHECK([ovn-sbctl lflow-list sw | grep ls_out_pre_lb | grep priority=110 | gre
 
 AT_CLEANUP
 ])
+
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([check fip and lb flows])
+AT_KEYWORDS([fip-lb-flows])
+ovn_start
+
+check ovn-nbctl lr-add R1
+check ovn-nbctl lrp-add R1 R1-S0 02:ac:10:01:00:01 10.0.0.1/24 1000::a/64
+check ovn-nbctl lrp-add R1 R1-PUB 02:ac:20:01:01:01 172.16.0.1/24 3000::a/64
+check ovn-nbctl lrp-set-gateway-chassis R1-PUB hv1 20
+
+check ovn-nbctl ls-add S0
+check ovn-nbctl lsp-add S0 S0-R1
+check ovn-nbctl lsp-set-type S0-R1 router
+check ovn-nbctl lsp-set-addresses S0-R1 02:ac:10:01:00:01
+check ovn-nbctl lsp-set-options S0-R1 router-port=R1-S0
+check ovn-nbctl lsp-add S0 S0-P0
+check ovn-nbctl lsp-set-addresses S0-P0 "50:54:00:00:00:03 10.0.0.3 1000::3"
+
+check ovn-nbctl lr-nat-add R1 dnat_and_snat 172.16.0.110 10.0.0.3 S0-P0 30:54:00:00:00:03
+check ovn-nbctl lr-nat-add R1 dnat_and_snat 3000::c 1000::3 S0-P0 40:54:00:00:00:03
+
+ovn-sbctl dump-flows R1 > R1flows
+AT_CAPTURE_FILE([R1flows])
+
+AT_CHECK([grep "lr_in_gw_redirect" R1flows | sed s'/table=../table=??/' |sort], [0], [dnl
+  table=??(lr_in_gw_redirect  ), priority=0    , match=(1), action=(next;)
+  table=??(lr_in_gw_redirect  ), priority=100  , match=(ip4.src == 10.0.0.3 && outport == "R1-PUB" && is_chassis_resident("S0-P0")), action=(eth.src = 30:54:00:00:00:03; reg1 = 172.16.0.110; next;)
+  table=??(lr_in_gw_redirect  ), priority=100  , match=(ip6.src == 1000::3 && outport == "R1-PUB" && is_chassis_resident("S0-P0")), action=(eth.src = 40:54:00:00:00:03; xxreg1 = 3000::c; next;)
+  table=??(lr_in_gw_redirect  ), priority=50   , match=(outport == "R1-PUB"), action=(outport = "cr-R1-PUB"; next;)
+])
+
+check ovn-nbctl lb-add lb_tcp4 172.16.0.100:50001 10.0.0.2:50001,10.0.0.3:50001,10.0.0.4:50001 tcp
+check ovn-nbctl lb-add lb_tcp6 [[1000::1]]:50001 [[1000::3]]:8080
+check ovn-nbctl --wait=sb lr-lb-add R1 lb_tcp4
+check ovn-nbctl --wait=sb lr-lb-add R1 lb_tcp6
+
+ovn-sbctl dump-flows R1 > R1flows
+AT_CAPTURE_FILE([R1flows])
+AT_CHECK([grep "lr_in_gw_redirect" R1flows |sed s'/table=../table=??/' |sort], [0], [dnl
+  table=??(lr_in_gw_redirect  ), priority=0    , match=(1), action=(next;)
+  table=??(lr_in_gw_redirect  ), priority=100  , match=(ip4.src == 10.0.0.3 && outport == "R1-PUB" && is_chassis_resident("S0-P0")), action=(eth.src = 30:54:00:00:00:03; reg1 = 172.16.0.110; next;)
+  table=??(lr_in_gw_redirect  ), priority=100  , match=(ip6.src == 1000::3 && outport == "R1-PUB" && is_chassis_resident("S0-P0")), action=(eth.src = 40:54:00:00:00:03; xxreg1 = 3000::c; next;)
+  table=??(lr_in_gw_redirect  ), priority=200  , match=(ip4 && ((ip4.src == 10.0.0.2 && tcp.src == 50001) || (ip4.src == 10.0.0.3 && tcp.src == 50001) || (ip4.src == 10.0.0.4 && tcp.src == 50001)) && outport == "R1-PUB"), action=(outport = "cr-R1-PUB"; next;)
+  table=??(lr_in_gw_redirect  ), priority=200  , match=(ip6 && ((ip6.src == 1000::3 && tcp.src == 8080)) && outport == "R1-PUB"), action=(outport = "cr-R1-PUB"; next;)
+  table=??(lr_in_gw_redirect  ), priority=50   , match=(outport == "R1-PUB"), action=(outport = "cr-R1-PUB"; next;)
+])
+
+AT_CLEANUP
+])
diff --git a/tests/ovn.at b/tests/ovn.at
index 6f9fbbfd2..92bfd78bd 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -35819,3 +35819,128 @@  OVS_WAIT_UNTIL([test $(as hv-1 ovs-vsctl list queue | grep -c 'burst="8000000000
 
 AT_CLEANUP
 ])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([DNAT_SNAT and LB traffic])
+AT_KEYWORDS([dnat-snat-lb])
+ovn_start
+
+test_ip_req_packet() {
+    local src_mac="505400000088"
+    local dst_mac="$1"
+    local src_ip=$(ip_to_hex 172 168 0 200)
+    local dst_ip="$2"
+    local cksum="$3"
+
+    local packet=${dst_mac}${src_mac}080045000072000000004011${cksum}
+    local packet_l3=${src_ip}${dst_ip}0035111100080000
+
+    packet_l3=${packet_l3}000000000000000000000000000000000000
+    packet_l3=${packet_l3}000000000000000000000000000000000000
+    packet_l3=${packet_l3}000000000000000000000000000000000000
+    packet_l3=${packet_l3}000000000000000000000000000000000000
+    packet_l3=${packet_l3}0000000000000000000000000000
+    packet=${packet}${packet_l3}
+
+    as hv1 reset_pcap_file hv1-vif1 hv1/vif1
+    as hv2 reset_pcap_file hv2-vif1 hv2/vif1
+    check as hv2 ovs-appctl netdev-dummy/receive hv2-vif1 $packet
+}
+
+test_ip_rep_packet() {
+    local src_mac="505400000001"
+    local dst_mac="00000000ff01"
+    local src_ip=$(ip_to_hex 10 0 0 2)
+    local dst_ip=$(ip_to_hex 172 168 0 200)
+
+    local packet=${dst_mac}${src_mac}080045000072000000004011c309
+    local packet_l3=${src_ip}${dst_ip}1111003500080000
+
+    packet_l3=${packet_l3}000000000000000000000000000000000000
+    packet_l3=${packet_l3}000000000000000000000000000000000000
+    packet_l3=${packet_l3}000000000000000000000000000000000000
+    packet_l3=${packet_l3}000000000000000000000000000000000000
+    packet_l3=${packet_l3}0000000000000000000000000000
+    packet=${packet}${packet_l3}
+
+    check as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
+}
+
+net_add n
+
+sim_add hv1
+as hv1
+check ovs-vsctl add-br br-phys
+ovn_attach n br-phys 192.168.0.1
+check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+check ovs-vsctl -- add-port br-int hv1-vif1 -- \
+    set interface hv1-vif1 external-ids:iface-id=sw0-port1 \
+    options:tx_pcap=hv1/vif1-tx.pcap \
+    options:rxq_pcap=hv1/vif1-rx.pcap
+
+sim_add hv2
+as hv2
+check ovs-vsctl add-br br-phys
+ovn_attach n br-phys 192.168.0.2
+check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+check ovs-vsctl -- add-port br-int hv2-vif1 -- \
+    set interface hv2-vif1 external-ids:iface-id=public-port1 \
+    options:tx_pcap=hv2/vif1-tx.pcap \
+    options:rxq_pcap=hv2/vif1-rx.pcap
+
+check ovn-nbctl ls-add sw0
+check ovn-nbctl lsp-add sw0 sw0-port1
+check ovn-nbctl lsp-set-addresses sw0-port1 "50:54:00:00:00:01 10.0.0.2"
+
+check ovn-nbctl ls-add public
+check ovn-nbctl lsp-add public ln-public
+check ovn-nbctl lsp-set-type ln-public localnet
+check ovn-nbctl lsp-set-addresses ln-public unknown
+check ovn-nbctl lsp-set-options ln-public network_name=phys
+check ovn-nbctl lsp-add public public-port1
+check ovn-nbctl lsp-set-addresses public-port1 "50:54:00:00:00:88 172.168.0.200"
+
+check ovn-nbctl lr-add lr0
+check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24
+check ovn-nbctl lsp-add sw0 sw0-lr0
+check ovn-nbctl lsp-set-type sw0-lr0 router
+check ovn-nbctl lsp-set-addresses sw0-lr0 router
+check ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
+check ovn-nbctl lrp-add lr0 lr0-public 00:00:20:20:12:13 172.168.0.1/24
+check ovn-nbctl lsp-add public public-lr0
+check ovn-nbctl lsp-set-type public-lr0 router
+check ovn-nbctl lsp-set-addresses public-lr0 router
+check ovn-nbctl lsp-set-options public-lr0 router-port=lr0-public
+
+check ovn-nbctl lrp-set-gateway-chassis lr0-public hv2 20
+
+check ovn-nbctl lr-nat-add lr0 snat 172.168.0.1 10.0.0.0/24
+check ovn-nbctl lr-nat-add lr0 dnat_and_snat 172.168.0.110 10.0.0.2 sw0-port1 f0:00:00:01:02:03
+
+wait_for_ports_up
+OVN_POPULATE_ARP
+
+# send UDP request to the FIP associated to sw0-port1
+test_ip_req_packet "f00000010203" $(ip_to_hex 172 168 0 110) "1ff5"
+OVS_WAIT_UNTIL([test $($PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap | wc -l) -ge 1])
+# send UDP reply from sw0-port1
+test_ip_rep_packet
+# packet sent by the FIP
+echo "505400000088f00000010203080045000072000000003f1120f5aca8006eaca800c811110035000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" > expected
+
+OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
+
+check ovn-nbctl lb-add lb0 172.168.0.1:4369 10.0.0.2:4369 udp
+check ovn-nbctl lr-lb-add lr0 lb0
+
+# send UDP request to the load-balancer VIP
+test_ip_req_packet "000020201213" $(ip_to_hex 172 168 0 1) "2062"
+OVS_WAIT_UNTIL([test $($PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap | wc -l) -ge 1])
+# send UDP reply from sw0-port1
+test_ip_rep_packet
+# packet sent by the load balancer VIP
+echo "505400000088000020201213080045000072000000003f112162aca80001aca800c811110035000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" > expected
+OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
+
+AT_CLEANUP
+])