diff mbox series

[ovs-dev,v2,2/2,ovn] OVN: Use ipv4.src and ipv4.dst actions for NAT rules

Message ID 1570220071-16483-3-git-send-email-ankur.sharma@nutanix.com
State Changes Requested
Headers show
Series ALLOW Stateless NAT operations | expand

Commit Message

Ankur Sharma Oct. 4, 2019, 8:13 p.m. UTC
For dnat_and_snat rules which are meant to be stateless
instead of using ct_snat/dnat OVN actions, we will use
ipv4.src/ipv4.dst.

This actions will do 1:1 mapping to inner ip to external ip,
while recalculating the checksums.

Signed-off-by: Ankur Sharma <ankur.sharma@nutanix.com>
---
 northd/ovn-northd.8.xml | 34 +++++++++++++++----
 northd/ovn-northd.c     | 86 +++++++++++++++++++++++++++++++++++++++++++------
 tests/ovn-northd.at     | 50 ++++++++++++++++++++++++++++
 3 files changed, 154 insertions(+), 16 deletions(-)

Comments

Numan Siddique Oct. 28, 2019, 1:40 p.m. UTC | #1
On Sat, Oct 5, 2019 at 1:45 AM Ankur Sharma <ankur.sharma@nutanix.com> wrote:
>
> For dnat_and_snat rules which are meant to be stateless
> instead of using ct_snat/dnat OVN actions, we will use
> ipv4.src/ipv4.dst.
>
> This actions will do 1:1 mapping to inner ip to external ip,
> while recalculating the checksums.
>
> Signed-off-by: Ankur Sharma <ankur.sharma@nutanix.com>


Hi Ankur,

There is a checkpatch warning - WARNING: Line is 80 characters long
(recommended limit is 79).
Please address it.

Please see below for few comments.

Thanks
Numan

> ---
>  northd/ovn-northd.8.xml | 34 +++++++++++++++----
>  northd/ovn-northd.c     | 86 +++++++++++++++++++++++++++++++++++++++++++------
>  tests/ovn-northd.at     | 50 ++++++++++++++++++++++++++++
>  3 files changed, 154 insertions(+), 16 deletions(-)
>
> diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
> index 0f4f1c1..7d5d102 100644
> --- a/northd/ovn-northd.8.xml
> +++ b/northd/ovn-northd.8.xml
> @@ -1718,7 +1718,9 @@ icmp6 {
>            to change the source IP address of a packet from <var>A</var> to
>            <var>B</var>, a priority-90 flow matches <code>ip &amp;&amp;
>            ip4.dst == <var>B</var></code> with an action
> -          <code>ct_snat; </code>.
> +          <code>ct_snat; </code>. If the NAT rule is of type dnat_and_snat
> +          and has <code>is_stateless=true</code> in the options, then the action
> +          would be <code>replace_dst_ip(<var>B</var>)</code>.

I think you need to update the documentation as you removed the action
- replace_dst_ip in v2.


>          </p>
>
>          <p>
> @@ -1738,7 +1740,10 @@ icmp6 {
>            <var>B</var>, a priority-100 flow matches <code>ip &amp;&amp;
>            ip4.dst == <var>B</var> &amp;&amp; inport == <var>GW</var></code>,
>            where <var>GW</var> is the logical router gateway port, with an
> -          action <code>ct_snat;</code>.
> +          action <code>ct_snat;</code>. If the NAT rule is of type
> +          dnat_and_snat and has <code>is_stateless=true</code> in the
> +          options, then the action would be <code>replace_dst_ip

Same here and other places too.

> +          (<var>B</var>)</code>.
>          </p>
>
>          <p>
> @@ -1858,7 +1863,10 @@ icmp6 {
>          Gateway router is configured to force SNAT any DNATed packet,
>          the above action will be replaced by
>          <code>flags.force_snat_for_dnat = 1; flags.loopback = 1;
> -        ct_dnat(<var>B</var>);</code>.
> +        ct_dnat(<var>B</var>);</code>. If the NAT rule is of type
> +        dnat_and_snat and has <code>is_stateless=true</code> in the
> +        options, then the action would be <code>replace_dst_ip
> +        (<var>B</var>)</code>.
>        </li>
>
>        <li>
> @@ -1890,7 +1898,10 @@ icmp6 {
>            <var>B</var>, a priority-100 flow matches <code>ip &amp;&amp;
>            ip4.dst == <var>B</var> &amp;&amp; inport == <var>GW</var></code>,
>            where <var>GW</var> is the logical router gateway port, with an
> -          action <code>ct_dnat(<var>B</var>);</code>.
> +          action <code>ct_dnat(<var>B</var>);</code>. If the NAT rule is of
> +          type dnat_and_snat and has <code>is_stateless=true</code> in the
> +          options, then the action would be <code>replace_dst_ip
> +          (<var>B</var>)</code>.
>          </p>
>
>          <p>
> @@ -2553,7 +2564,10 @@ nd_ns {
>            matches <code>ip &amp;&amp; ip4.src == <var>B</var>
>            &amp;&amp; outport == <var>GW</var></code>, where <var>GW</var>
>            is the logical router gateway port, with an action
> -          <code>ct_dnat;</code>.
> +          <code>ct_dnat;</code>. If the NAT rule is of type
> +          dnat_and_snat and has <code>is_stateless=true</code> in the
> +          options, then the action would be <code>replace_src_ip
> +          (<var>B</var>)</code>.
>          </p>
>
>          <p>
> @@ -2611,7 +2625,10 @@ nd_ns {
>            <code>ip &amp;&amp; ip4.src == <var>A</var></code> with an action
>            <code>ct_snat(<var>B</var>);</code>.  The priority of the flow
>            is calculated based on the mask of <var>A</var>, with matches
> -          having larger masks getting higher priorities.
> +          having larger masks getting higher priorities. If the NAT rule is
> +          of type dnat_and_snat and has <code>is_stateless=true</code> in the
> +          options, then the action would be <code>replace_src_ip
> +          (<var>B</var>)</code>.
>          </p>
>          <p>
>            A priority-0 logical flow with match <code>1</code> has actions
> @@ -2634,7 +2651,10 @@ nd_ns {
>            logical router gateway port, with an action
>            <code>ct_snat(<var>B</var>);</code>.  The priority of the flow
>            is calculated based on the mask of <var>A</var>, with matches
> -          having larger masks getting higher priorities.
> +          having larger masks getting higher priorities. If the NAT rule
> +          is of type dnat_and_snat and has <code>is_stateless=true</code>
> +          in the options, then the action would be <code>replace_src_ip
> +          (<var>B</var>)</code>.
>          </p>
>
>          <p>
> diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
> index f393ceb..4036392 100644
> --- a/northd/ovn-northd.c
> +++ b/northd/ovn-northd.c
> @@ -6314,6 +6314,18 @@ copy_ra_to_sb(struct ovn_port *op, const char *address_mode)
>      smap_destroy(&options);
>  }
>
> +static inline bool
> +lrouter_nat_is_stateless(const struct nbrec_nat *nat)
> +{
> +    const char *is_stateless = smap_get(&nat->options, "is_stateless");
> +
> +    if (is_stateless && !strcmp(is_stateless, "true")) {
> +        return true;
> +    }
> +
> +    return false;
> +}
> +
>  static void
>  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
>                      struct hmap *lflows, struct shash *meter_groups)
> @@ -7052,6 +7064,7 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
>              nat = od->nbr->nat[i];
>
>              ovs_be32 ip, mask;
> +            bool is_stateless = lrouter_nat_is_stateless(nat);
>
>              char *error = ip_parse_masked(nat->external_ip, &ip, &mask);
>              if (error || mask != OVS_BE32_MAX) {
> @@ -7117,15 +7130,26 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
>                  if (!od->l3dgw_port) {
>                      /* Gateway router. */
>                      ds_clear(&match);
> +                    ds_clear(&actions);
>                      ds_put_format(&match, "ip && ip4.dst == %s",
>                                    nat->external_ip);
> +
> +                    if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) {
> +                        ds_put_format(&actions, "ip4.dst=%s; next;",
> +                                      nat->logical_ip);
> +                    } else {
> +                        ds_put_cstr(&actions, "ct_snat;");
> +                    }
> +
>                      ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 90,
> -                                  ds_cstr(&match), "ct_snat;");
> +                                  ds_cstr(&match), ds_cstr(&actions));
>                  } else {
>                      /* Distributed router. */
>
>                      /* Traffic received on l3dgw_port is subject to NAT. */
>                      ds_clear(&match);
> +                    ds_clear(&actions);
> +
>                      ds_put_format(&match, "ip && ip4.dst == %s"
>                                            " && inport == %s",
>                                    nat->external_ip,
> @@ -7136,8 +7160,16 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
>                          ds_put_format(&match, " && is_chassis_resident(%s)",
>                                        od->l3redirect_port->json_key);
>                      }
> +
> +                    if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) {
> +                        ds_put_format(&actions, "ip4.dst=%s; next;",
> +                                      nat->logical_ip);
> +                    } else {
> +                        ds_put_cstr(&actions, "ct_snat;");
> +                    }
> +
>                      ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 100,
> -                                  ds_cstr(&match), "ct_snat;");
> +                                  ds_cstr(&match), ds_cstr(&actions));
>
>                      /* Traffic received on other router ports must be
>                       * redirected to the central instance of the l3dgw_port
> @@ -7172,8 +7204,16 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
>                          ds_put_format(&actions,
>                                        "flags.force_snat_for_dnat = 1; ");
>                      }
> -                    ds_put_format(&actions, "flags.loopback = 1; ct_dnat(%s);",
> -                                  nat->logical_ip);
> +
> +                    if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) {
> +                        ds_put_format(&actions, "flags.loopback = 1; "
> +                                      "ip4.dst=%s; next;",
> +                                      nat->logical_ip);
> +                    } else {
> +                        ds_put_format(&actions, "flags.loopback = 1; ct_dnat(%s);",
> +                                      nat->logical_ip);
> +                    }
> +
>                      ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 100,
>                                    ds_cstr(&match), ds_cstr(&actions));
>                  } else {
> @@ -7192,8 +7232,15 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
>                                        od->l3redirect_port->json_key);
>                      }
>                      ds_clear(&actions);
> -                    ds_put_format(&actions, "ct_dnat(%s);",
> -                                  nat->logical_ip);
> +
> +                    if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) {
> +                        ds_put_format(&actions, "ip4.dst=%s; next;",
> +                                      nat->logical_ip);
> +                    } else {
> +                        ds_put_format(&actions, "ct_dnat(%s);",
> +                                      nat->logical_ip);
> +                    }
> +
>                      ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 100,
>                                    ds_cstr(&match), ds_cstr(&actions));
>
> @@ -7235,7 +7282,14 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
>                      ds_put_format(&actions, "eth.src = "ETH_ADDR_FMT"; ",
>                                    ETH_ADDR_ARGS(mac));
>                  }
> -                ds_put_format(&actions, "ct_dnat;");
> +
> +                if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) {
> +                    ds_put_format(&actions, "ip4.src=%s; next;",
> +                                  nat->external_ip);
> +                } else {
> +                    ds_put_format(&actions, "ct_dnat;");
> +                }
> +
>                  ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 100,
>                                ds_cstr(&match), ds_cstr(&actions));
>              }
> @@ -7251,7 +7305,14 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
>                      ds_put_format(&match, "ip && ip4.src == %s",
>                                    nat->logical_ip);
>                      ds_clear(&actions);
> -                    ds_put_format(&actions, "ct_snat(%s);", nat->external_ip);
> +
> +                    if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) {
> +                        ds_put_format(&actions, "ip4.src=%s; next;",
> +                                      nat->external_ip);
> +                    } else {
> +                        ds_put_format(&actions, "ct_snat(%s);",
> +                                      nat->external_ip);
> +                    }
>
>                      /* The priority here is calculated such that the
>                       * nat->logical_ip with the longest mask gets a higher
> @@ -7280,7 +7341,14 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
>                          ds_put_format(&actions, "eth.src = "ETH_ADDR_FMT"; ",
>                                        ETH_ADDR_ARGS(mac));
>                      }
> -                    ds_put_format(&actions, "ct_snat(%s);", nat->external_ip);
> +
> +                    if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) {
> +                        ds_put_format(&actions, "ip4.src=%s; next;",
> +                                      nat->external_ip);
> +                    } else {
> +                        ds_put_format(&actions, "ct_snat(%s);",
> +                                      nat->external_ip);
> +                    }
>
>                      /* The priority here is calculated such that the
>                       * nat->logical_ip with the longest mask gets a higher
> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> index 42033d5..64511a9 100644
> --- a/tests/ovn-northd.at
> +++ b/tests/ovn-northd.at
> @@ -966,3 +966,53 @@ OVS_WAIT_UNTIL([ovn-sbctl get Port_Binding ${uuid} options:redirect-type], [0],
>  ])
>
>  AT_CLEANUP
> +

Can you add a new test case or modify an existing test case in ovn.at
to make use of this feature ?

> +AT_SETUP([ovn -- check stateless dnat_and_snat rule])
> +AT_SKIP_IF([test $HAVE_PYTHON = no])
> +ovn_start
> +
> +ovn-sbctl chassis-add gw1 geneve 127.0.0.1
> +
> +ovn-nbctl lr-add R1
> +ovn-nbctl lrp-add R1 R1-S1 02:ac:10:01:00:01 172.16.1.1/24
> +
> +ovn-nbctl ls-add S1
> +ovn-nbctl lsp-add S1 S1-R1
> +ovn-nbctl lsp-set-type S1-R1 router
> +ovn-nbctl lsp-set-addresses S1-R1 router
> +ovn-nbctl --wait=sb lsp-set-options S1-R1 router-port=R1-S1
> +
> +ovn-nbctl lrp-set-gateway-chassis R1-S1 gw1
> +
> +uuid=`ovn-sbctl --columns=_uuid --bare find Port_Binding logical_port=cr-R1-S1`
> +echo "CR-LRP UUID is: " $uuid
> +
> +ovn-nbctl lr-nat-add R1 dnat_and_snat  172.16.1.1 50.0.0.11
> +AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_snat | wc -l], [0], [2
> +])

There is a chance that the test case might fail on slower systems if
ovn-northd is not fast enough to
add the necessary logical flows.

For the above first check, I would suggest to use - OVS_WAIT_UNTIL.


> +
> +AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_dnat | wc -l], [0], [2
> +])
> +
> +AT_CHECK([ovn-sbctl dump-flows R1 | grep ip4.dst=| wc -l], [0], [0
> +])
> +
> +AT_CHECK([ovn-sbctl dump-flows R1 | grep ip4.src=| wc -l], [0], [0
> +])
> +
> +ovn-nbctl lr-nat-del R1 dnat_and_snat  172.16.1.1
> +
> +ovn-nbctl --stateless lr-nat-add R1 dnat_and_snat  172.16.1.1 50.0.0.11
> +AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_snat | wc -l], [0], [0
> +])
> +

Same here. Please use OVS_WAIT_UNTIL.


> +AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_dnat | wc -l], [0], [0
> +])
> +
> +AT_CHECK([ovn-sbctl dump-flows R1 | grep ip4.dst=| wc -l], [0], [2
> +])
> +
> +AT_CHECK([ovn-sbctl dump-flows R1 | grep ip4.src=| wc -l], [0], [2
> +])
> +
> +AT_CLEANUP
> --
> 1.8.3.1
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
Ankur Sharma Oct. 30, 2019, 8:31 p.m. UTC | #2
Hi Numan,

Submitted a v3 yesterday, addressing all the review comments.
Please take a look.

Regards,
Ankur

-----Original Message-----
From: Numan Siddique <numans@ovn.org> 
Sent: Monday, October 28, 2019 6:40 AM
To: Ankur Sharma <ankur.sharma@nutanix.com>
Cc: ovs-dev@openvswitch.org
Subject: Re: [ovs-dev] [PATCH v2 2/2 ovn] OVN: Use ipv4.src and ipv4.dst actions for NAT rules

On Sat, Oct 5, 2019 at 1:45 AM Ankur Sharma <ankur.sharma@nutanix.com> wrote:
>
> For dnat_and_snat rules which are meant to be stateless instead of 
> using ct_snat/dnat OVN actions, we will use ipv4.src/ipv4.dst.
>
> This actions will do 1:1 mapping to inner ip to external ip, while 
> recalculating the checksums.
>
> Signed-off-by: Ankur Sharma <ankur.sharma@nutanix.com>


Hi Ankur,

There is a checkpatch warning - WARNING: Line is 80 characters long (recommended limit is 79).
Please address it.

Please see below for few comments.

Thanks
Numan

> ---
>  northd/ovn-northd.8.xml | 34 +++++++++++++++----
>  northd/ovn-northd.c     | 86 +++++++++++++++++++++++++++++++++++++++++++------
>  tests/ovn-northd.at     | 50 ++++++++++++++++++++++++++++
>  3 files changed, 154 insertions(+), 16 deletions(-)
>
> diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml index 
> 0f4f1c1..7d5d102 100644
> --- a/northd/ovn-northd.8.xml
> +++ b/northd/ovn-northd.8.xml
> @@ -1718,7 +1718,9 @@ icmp6 {
>            to change the source IP address of a packet from <var>A</var> to
>            <var>B</var>, a priority-90 flow matches <code>ip &amp;&amp;
>            ip4.dst == <var>B</var></code> with an action
> -          <code>ct_snat; </code>.
> +          <code>ct_snat; </code>. If the NAT rule is of type dnat_and_snat
> +          and has <code>is_stateless=true</code> in the options, then the action
> +          would be <code>replace_dst_ip(<var>B</var>)</code>.

I think you need to update the documentation as you removed the action
- replace_dst_ip in v2.


>          </p>
>
>          <p>
> @@ -1738,7 +1740,10 @@ icmp6 {
>            <var>B</var>, a priority-100 flow matches <code>ip &amp;&amp;
>            ip4.dst == <var>B</var> &amp;&amp; inport == <var>GW</var></code>,
>            where <var>GW</var> is the logical router gateway port, with an
> -          action <code>ct_snat;</code>.
> +          action <code>ct_snat;</code>. If the NAT rule is of type
> +          dnat_and_snat and has <code>is_stateless=true</code> in the
> +          options, then the action would be <code>replace_dst_ip

Same here and other places too.

> +          (<var>B</var>)</code>.
>          </p>
>
>          <p>
> @@ -1858,7 +1863,10 @@ icmp6 {
>          Gateway router is configured to force SNAT any DNATed packet,
>          the above action will be replaced by
>          <code>flags.force_snat_for_dnat = 1; flags.loopback = 1;
> -        ct_dnat(<var>B</var>);</code>.
> +        ct_dnat(<var>B</var>);</code>. If the NAT rule is of type
> +        dnat_and_snat and has <code>is_stateless=true</code> in the
> +        options, then the action would be <code>replace_dst_ip
> +        (<var>B</var>)</code>.
>        </li>
>
>        <li>
> @@ -1890,7 +1898,10 @@ icmp6 {
>            <var>B</var>, a priority-100 flow matches <code>ip &amp;&amp;
>            ip4.dst == <var>B</var> &amp;&amp; inport == <var>GW</var></code>,
>            where <var>GW</var> is the logical router gateway port, with an
> -          action <code>ct_dnat(<var>B</var>);</code>.
> +          action <code>ct_dnat(<var>B</var>);</code>. If the NAT rule is of
> +          type dnat_and_snat and has <code>is_stateless=true</code> in the
> +          options, then the action would be <code>replace_dst_ip
> +          (<var>B</var>)</code>.
>          </p>
>
>          <p>
> @@ -2553,7 +2564,10 @@ nd_ns {
>            matches <code>ip &amp;&amp; ip4.src == <var>B</var>
>            &amp;&amp; outport == <var>GW</var></code>, where <var>GW</var>
>            is the logical router gateway port, with an action
> -          <code>ct_dnat;</code>.
> +          <code>ct_dnat;</code>. If the NAT rule is of type
> +          dnat_and_snat and has <code>is_stateless=true</code> in the
> +          options, then the action would be <code>replace_src_ip
> +          (<var>B</var>)</code>.
>          </p>
>
>          <p>
> @@ -2611,7 +2625,10 @@ nd_ns {
>            <code>ip &amp;&amp; ip4.src == <var>A</var></code> with an action
>            <code>ct_snat(<var>B</var>);</code>.  The priority of the flow
>            is calculated based on the mask of <var>A</var>, with matches
> -          having larger masks getting higher priorities.
> +          having larger masks getting higher priorities. If the NAT rule is
> +          of type dnat_and_snat and has <code>is_stateless=true</code> in the
> +          options, then the action would be <code>replace_src_ip
> +          (<var>B</var>)</code>.
>          </p>
>          <p>
>            A priority-0 logical flow with match <code>1</code> has 
> actions @@ -2634,7 +2651,10 @@ nd_ns {
>            logical router gateway port, with an action
>            <code>ct_snat(<var>B</var>);</code>.  The priority of the flow
>            is calculated based on the mask of <var>A</var>, with matches
> -          having larger masks getting higher priorities.
> +          having larger masks getting higher priorities. If the NAT rule
> +          is of type dnat_and_snat and has <code>is_stateless=true</code>
> +          in the options, then the action would be <code>replace_src_ip
> +          (<var>B</var>)</code>.
>          </p>
>
>          <p>
> diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c index 
> f393ceb..4036392 100644
> --- a/northd/ovn-northd.c
> +++ b/northd/ovn-northd.c
> @@ -6314,6 +6314,18 @@ copy_ra_to_sb(struct ovn_port *op, const char *address_mode)
>      smap_destroy(&options);
>  }
>
> +static inline bool
> +lrouter_nat_is_stateless(const struct nbrec_nat *nat) {
> +    const char *is_stateless = smap_get(&nat->options, 
> +"is_stateless");
> +
> +    if (is_stateless && !strcmp(is_stateless, "true")) {
> +        return true;
> +    }
> +
> +    return false;
> +}
> +
>  static void
>  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
>                      struct hmap *lflows, struct shash *meter_groups) 
> @@ -7052,6 +7064,7 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
>              nat = od->nbr->nat[i];
>
>              ovs_be32 ip, mask;
> +            bool is_stateless = lrouter_nat_is_stateless(nat);
>
>              char *error = ip_parse_masked(nat->external_ip, &ip, &mask);
>              if (error || mask != OVS_BE32_MAX) { @@ -7117,15 +7130,26 
> @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
>                  if (!od->l3dgw_port) {
>                      /* Gateway router. */
>                      ds_clear(&match);
> +                    ds_clear(&actions);
>                      ds_put_format(&match, "ip && ip4.dst == %s",
>                                    nat->external_ip);
> +
> +                    if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) {
> +                        ds_put_format(&actions, "ip4.dst=%s; next;",
> +                                      nat->logical_ip);
> +                    } else {
> +                        ds_put_cstr(&actions, "ct_snat;");
> +                    }
> +
>                      ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 90,
> -                                  ds_cstr(&match), "ct_snat;");
> +                                  ds_cstr(&match), 
> + ds_cstr(&actions));
>                  } else {
>                      /* Distributed router. */
>
>                      /* Traffic received on l3dgw_port is subject to NAT. */
>                      ds_clear(&match);
> +                    ds_clear(&actions);
> +
>                      ds_put_format(&match, "ip && ip4.dst == %s"
>                                            " && inport == %s",
>                                    nat->external_ip, @@ -7136,8 
> +7160,16 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
>                          ds_put_format(&match, " && is_chassis_resident(%s)",
>                                        od->l3redirect_port->json_key);
>                      }
> +
> +                    if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) {
> +                        ds_put_format(&actions, "ip4.dst=%s; next;",
> +                                      nat->logical_ip);
> +                    } else {
> +                        ds_put_cstr(&actions, "ct_snat;");
> +                    }
> +
>                      ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 100,
> -                                  ds_cstr(&match), "ct_snat;");
> +                                  ds_cstr(&match), 
> + ds_cstr(&actions));
>
>                      /* Traffic received on other router ports must be
>                       * redirected to the central instance of the 
> l3dgw_port @@ -7172,8 +7204,16 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
>                          ds_put_format(&actions,
>                                        "flags.force_snat_for_dnat = 1; ");
>                      }
> -                    ds_put_format(&actions, "flags.loopback = 1; ct_dnat(%s);",
> -                                  nat->logical_ip);
> +
> +                    if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) {
> +                        ds_put_format(&actions, "flags.loopback = 1; "
> +                                      "ip4.dst=%s; next;",
> +                                      nat->logical_ip);
> +                    } else {
> +                        ds_put_format(&actions, "flags.loopback = 1; ct_dnat(%s);",
> +                                      nat->logical_ip);
> +                    }
> +
>                      ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 100,
>                                    ds_cstr(&match), ds_cstr(&actions));
>                  } else {
> @@ -7192,8 +7232,15 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
>                                        od->l3redirect_port->json_key);
>                      }
>                      ds_clear(&actions);
> -                    ds_put_format(&actions, "ct_dnat(%s);",
> -                                  nat->logical_ip);
> +
> +                    if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) {
> +                        ds_put_format(&actions, "ip4.dst=%s; next;",
> +                                      nat->logical_ip);
> +                    } else {
> +                        ds_put_format(&actions, "ct_dnat(%s);",
> +                                      nat->logical_ip);
> +                    }
> +
>                      ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 100,
>                                    ds_cstr(&match), 
> ds_cstr(&actions));
>
> @@ -7235,7 +7282,14 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
>                      ds_put_format(&actions, "eth.src = "ETH_ADDR_FMT"; ",
>                                    ETH_ADDR_ARGS(mac));
>                  }
> -                ds_put_format(&actions, "ct_dnat;");
> +
> +                if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) {
> +                    ds_put_format(&actions, "ip4.src=%s; next;",
> +                                  nat->external_ip);
> +                } else {
> +                    ds_put_format(&actions, "ct_dnat;");
> +                }
> +
>                  ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 100,
>                                ds_cstr(&match), ds_cstr(&actions));
>              }
> @@ -7251,7 +7305,14 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
>                      ds_put_format(&match, "ip && ip4.src == %s",
>                                    nat->logical_ip);
>                      ds_clear(&actions);
> -                    ds_put_format(&actions, "ct_snat(%s);", nat->external_ip);
> +
> +                    if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) {
> +                        ds_put_format(&actions, "ip4.src=%s; next;",
> +                                      nat->external_ip);
> +                    } else {
> +                        ds_put_format(&actions, "ct_snat(%s);",
> +                                      nat->external_ip);
> +                    }
>
>                      /* The priority here is calculated such that the
>                       * nat->logical_ip with the longest mask gets a 
> higher @@ -7280,7 +7341,14 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
>                          ds_put_format(&actions, "eth.src = "ETH_ADDR_FMT"; ",
>                                        ETH_ADDR_ARGS(mac));
>                      }
> -                    ds_put_format(&actions, "ct_snat(%s);", nat->external_ip);
> +
> +                    if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) {
> +                        ds_put_format(&actions, "ip4.src=%s; next;",
> +                                      nat->external_ip);
> +                    } else {
> +                        ds_put_format(&actions, "ct_snat(%s);",
> +                                      nat->external_ip);
> +                    }
>
>                      /* The priority here is calculated such that the
>                       * nat->logical_ip with the longest mask gets a 
> higher diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at index 
> 42033d5..64511a9 100644
> --- a/tests/ovn-northd.at
> +++ b/tests/ovn-northd.at
> @@ -966,3 +966,53 @@ OVS_WAIT_UNTIL([ovn-sbctl get Port_Binding 
> ${uuid} options:redirect-type], [0],
>  ])
>
>  AT_CLEANUP
> +

Can you add a new test case or modify an existing test case in ovn.at to make use of this feature ?

> +AT_SETUP([ovn -- check stateless dnat_and_snat rule]) 
> +AT_SKIP_IF([test $HAVE_PYTHON = no]) ovn_start
> +
> +ovn-sbctl chassis-add gw1 geneve 127.0.0.1
> +
> +ovn-nbctl lr-add R1
> +ovn-nbctl lrp-add R1 R1-S1 02:ac:10:01:00:01 172.16.1.1/24
> +
> +ovn-nbctl ls-add S1
> +ovn-nbctl lsp-add S1 S1-R1
> +ovn-nbctl lsp-set-type S1-R1 router
> +ovn-nbctl lsp-set-addresses S1-R1 router ovn-nbctl --wait=sb 
> +lsp-set-options S1-R1 router-port=R1-S1
> +
> +ovn-nbctl lrp-set-gateway-chassis R1-S1 gw1
> +
> +uuid=`ovn-sbctl --columns=_uuid --bare find Port_Binding 
> +logical_port=cr-R1-S1` echo "CR-LRP UUID is: " $uuid
> +
> +ovn-nbctl lr-nat-add R1 dnat_and_snat  172.16.1.1 50.0.0.11 
> +AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_snat | wc -l], [0], [2
> +])

There is a chance that the test case might fail on slower systems if ovn-northd is not fast enough to add the necessary logical flows.

For the above first check, I would suggest to use - OVS_WAIT_UNTIL.


> +
> +AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_dnat | wc -l], [0], [2
> +])
> +
> +AT_CHECK([ovn-sbctl dump-flows R1 | grep ip4.dst=| wc -l], [0], [0
> +])
> +
> +AT_CHECK([ovn-sbctl dump-flows R1 | grep ip4.src=| wc -l], [0], [0
> +])
> +
> +ovn-nbctl lr-nat-del R1 dnat_and_snat  172.16.1.1
> +
> +ovn-nbctl --stateless lr-nat-add R1 dnat_and_snat  172.16.1.1 
> +50.0.0.11 AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_snat | wc -l], 
> +[0], [0
> +])
> +

Same here. Please use OVS_WAIT_UNTIL.


> +AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_dnat | wc -l], [0], [0
> +])
> +
> +AT_CHECK([ovn-sbctl dump-flows R1 | grep ip4.dst=| wc -l], [0], [2
> +])
> +
> +AT_CHECK([ovn-sbctl dump-flows R1 | grep ip4.src=| wc -l], [0], [2
> +])
> +
> +AT_CLEANUP
> --
> 1.8.3.1
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://urldefense.proofpoint.com/v2/url?u=https-3A__mail.openvswitch.
> org_mailman_listinfo_ovs-2Ddev&d=DwIBaQ&c=s883GpUCOChKOHiocYtGcg&r=mZw
> X9gFQgeJHzTg-68aCJgsODyUEVsHGFOfL90J6MJY&m=CouZqk7agKapXA85Hmee871NM2d
> rC0etOpAwwwF_Qvw&s=yGzdgaPfGMkQpXg_3NmyQCOqkqeJPvomW3QqTsWghPQ&e=
Numan Siddique Oct. 31, 2019, 5:38 p.m. UTC | #3
On Thu, Oct 31, 2019 at 2:02 AM Ankur Sharma <ankur.sharma@nutanix.com> wrote:
>
> Hi Numan,
>
> Submitted a v3 yesterday, addressing all the review comments.
> Please take a look.

Hi Ankur,

Can you please rebase your v3 patches ? They don't apply on top of master.

Thanks
Numan

>
> Regards,
> Ankur
>
> -----Original Message-----
> From: Numan Siddique <numans@ovn.org>
> Sent: Monday, October 28, 2019 6:40 AM
> To: Ankur Sharma <ankur.sharma@nutanix.com>
> Cc: ovs-dev@openvswitch.org
> Subject: Re: [ovs-dev] [PATCH v2 2/2 ovn] OVN: Use ipv4.src and ipv4.dst actions for NAT rules
>
> On Sat, Oct 5, 2019 at 1:45 AM Ankur Sharma <ankur.sharma@nutanix.com> wrote:
> >
> > For dnat_and_snat rules which are meant to be stateless instead of
> > using ct_snat/dnat OVN actions, we will use ipv4.src/ipv4.dst.
> >
> > This actions will do 1:1 mapping to inner ip to external ip, while
> > recalculating the checksums.
> >
> > Signed-off-by: Ankur Sharma <ankur.sharma@nutanix.com>
>
>
> Hi Ankur,
>
> There is a checkpatch warning - WARNING: Line is 80 characters long (recommended limit is 79).
> Please address it.
>
> Please see below for few comments.
>
> Thanks
> Numan
>
> > ---
> >  northd/ovn-northd.8.xml | 34 +++++++++++++++----
> >  northd/ovn-northd.c     | 86 +++++++++++++++++++++++++++++++++++++++++++------
> >  tests/ovn-northd.at     | 50 ++++++++++++++++++++++++++++
> >  3 files changed, 154 insertions(+), 16 deletions(-)
> >
> > diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml index
> > 0f4f1c1..7d5d102 100644
> > --- a/northd/ovn-northd.8.xml
> > +++ b/northd/ovn-northd.8.xml
> > @@ -1718,7 +1718,9 @@ icmp6 {
> >            to change the source IP address of a packet from <var>A</var> to
> >            <var>B</var>, a priority-90 flow matches <code>ip &amp;&amp;
> >            ip4.dst == <var>B</var></code> with an action
> > -          <code>ct_snat; </code>.
> > +          <code>ct_snat; </code>. If the NAT rule is of type dnat_and_snat
> > +          and has <code>is_stateless=true</code> in the options, then the action
> > +          would be <code>replace_dst_ip(<var>B</var>)</code>.
>
> I think you need to update the documentation as you removed the action
> - replace_dst_ip in v2.
>
>
> >          </p>
> >
> >          <p>
> > @@ -1738,7 +1740,10 @@ icmp6 {
> >            <var>B</var>, a priority-100 flow matches <code>ip &amp;&amp;
> >            ip4.dst == <var>B</var> &amp;&amp; inport == <var>GW</var></code>,
> >            where <var>GW</var> is the logical router gateway port, with an
> > -          action <code>ct_snat;</code>.
> > +          action <code>ct_snat;</code>. If the NAT rule is of type
> > +          dnat_and_snat and has <code>is_stateless=true</code> in the
> > +          options, then the action would be <code>replace_dst_ip
>
> Same here and other places too.
>
> > +          (<var>B</var>)</code>.
> >          </p>
> >
> >          <p>
> > @@ -1858,7 +1863,10 @@ icmp6 {
> >          Gateway router is configured to force SNAT any DNATed packet,
> >          the above action will be replaced by
> >          <code>flags.force_snat_for_dnat = 1; flags.loopback = 1;
> > -        ct_dnat(<var>B</var>);</code>.
> > +        ct_dnat(<var>B</var>);</code>. If the NAT rule is of type
> > +        dnat_and_snat and has <code>is_stateless=true</code> in the
> > +        options, then the action would be <code>replace_dst_ip
> > +        (<var>B</var>)</code>.
> >        </li>
> >
> >        <li>
> > @@ -1890,7 +1898,10 @@ icmp6 {
> >            <var>B</var>, a priority-100 flow matches <code>ip &amp;&amp;
> >            ip4.dst == <var>B</var> &amp;&amp; inport == <var>GW</var></code>,
> >            where <var>GW</var> is the logical router gateway port, with an
> > -          action <code>ct_dnat(<var>B</var>);</code>.
> > +          action <code>ct_dnat(<var>B</var>);</code>. If the NAT rule is of
> > +          type dnat_and_snat and has <code>is_stateless=true</code> in the
> > +          options, then the action would be <code>replace_dst_ip
> > +          (<var>B</var>)</code>.
> >          </p>
> >
> >          <p>
> > @@ -2553,7 +2564,10 @@ nd_ns {
> >            matches <code>ip &amp;&amp; ip4.src == <var>B</var>
> >            &amp;&amp; outport == <var>GW</var></code>, where <var>GW</var>
> >            is the logical router gateway port, with an action
> > -          <code>ct_dnat;</code>.
> > +          <code>ct_dnat;</code>. If the NAT rule is of type
> > +          dnat_and_snat and has <code>is_stateless=true</code> in the
> > +          options, then the action would be <code>replace_src_ip
> > +          (<var>B</var>)</code>.
> >          </p>
> >
> >          <p>
> > @@ -2611,7 +2625,10 @@ nd_ns {
> >            <code>ip &amp;&amp; ip4.src == <var>A</var></code> with an action
> >            <code>ct_snat(<var>B</var>);</code>.  The priority of the flow
> >            is calculated based on the mask of <var>A</var>, with matches
> > -          having larger masks getting higher priorities.
> > +          having larger masks getting higher priorities. If the NAT rule is
> > +          of type dnat_and_snat and has <code>is_stateless=true</code> in the
> > +          options, then the action would be <code>replace_src_ip
> > +          (<var>B</var>)</code>.
> >          </p>
> >          <p>
> >            A priority-0 logical flow with match <code>1</code> has
> > actions @@ -2634,7 +2651,10 @@ nd_ns {
> >            logical router gateway port, with an action
> >            <code>ct_snat(<var>B</var>);</code>.  The priority of the flow
> >            is calculated based on the mask of <var>A</var>, with matches
> > -          having larger masks getting higher priorities.
> > +          having larger masks getting higher priorities. If the NAT rule
> > +          is of type dnat_and_snat and has <code>is_stateless=true</code>
> > +          in the options, then the action would be <code>replace_src_ip
> > +          (<var>B</var>)</code>.
> >          </p>
> >
> >          <p>
> > diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c index
> > f393ceb..4036392 100644
> > --- a/northd/ovn-northd.c
> > +++ b/northd/ovn-northd.c
> > @@ -6314,6 +6314,18 @@ copy_ra_to_sb(struct ovn_port *op, const char *address_mode)
> >      smap_destroy(&options);
> >  }
> >
> > +static inline bool
> > +lrouter_nat_is_stateless(const struct nbrec_nat *nat) {
> > +    const char *is_stateless = smap_get(&nat->options,
> > +"is_stateless");
> > +
> > +    if (is_stateless && !strcmp(is_stateless, "true")) {
> > +        return true;
> > +    }
> > +
> > +    return false;
> > +}
> > +
> >  static void
> >  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
> >                      struct hmap *lflows, struct shash *meter_groups)
> > @@ -7052,6 +7064,7 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
> >              nat = od->nbr->nat[i];
> >
> >              ovs_be32 ip, mask;
> > +            bool is_stateless = lrouter_nat_is_stateless(nat);
> >
> >              char *error = ip_parse_masked(nat->external_ip, &ip, &mask);
> >              if (error || mask != OVS_BE32_MAX) { @@ -7117,15 +7130,26
> > @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
> >                  if (!od->l3dgw_port) {
> >                      /* Gateway router. */
> >                      ds_clear(&match);
> > +                    ds_clear(&actions);
> >                      ds_put_format(&match, "ip && ip4.dst == %s",
> >                                    nat->external_ip);
> > +
> > +                    if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) {
> > +                        ds_put_format(&actions, "ip4.dst=%s; next;",
> > +                                      nat->logical_ip);
> > +                    } else {
> > +                        ds_put_cstr(&actions, "ct_snat;");
> > +                    }
> > +
> >                      ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 90,
> > -                                  ds_cstr(&match), "ct_snat;");
> > +                                  ds_cstr(&match),
> > + ds_cstr(&actions));
> >                  } else {
> >                      /* Distributed router. */
> >
> >                      /* Traffic received on l3dgw_port is subject to NAT. */
> >                      ds_clear(&match);
> > +                    ds_clear(&actions);
> > +
> >                      ds_put_format(&match, "ip && ip4.dst == %s"
> >                                            " && inport == %s",
> >                                    nat->external_ip, @@ -7136,8
> > +7160,16 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
> >                          ds_put_format(&match, " && is_chassis_resident(%s)",
> >                                        od->l3redirect_port->json_key);
> >                      }
> > +
> > +                    if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) {
> > +                        ds_put_format(&actions, "ip4.dst=%s; next;",
> > +                                      nat->logical_ip);
> > +                    } else {
> > +                        ds_put_cstr(&actions, "ct_snat;");
> > +                    }
> > +
> >                      ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 100,
> > -                                  ds_cstr(&match), "ct_snat;");
> > +                                  ds_cstr(&match),
> > + ds_cstr(&actions));
> >
> >                      /* Traffic received on other router ports must be
> >                       * redirected to the central instance of the
> > l3dgw_port @@ -7172,8 +7204,16 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
> >                          ds_put_format(&actions,
> >                                        "flags.force_snat_for_dnat = 1; ");
> >                      }
> > -                    ds_put_format(&actions, "flags.loopback = 1; ct_dnat(%s);",
> > -                                  nat->logical_ip);
> > +
> > +                    if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) {
> > +                        ds_put_format(&actions, "flags.loopback = 1; "
> > +                                      "ip4.dst=%s; next;",
> > +                                      nat->logical_ip);
> > +                    } else {
> > +                        ds_put_format(&actions, "flags.loopback = 1; ct_dnat(%s);",
> > +                                      nat->logical_ip);
> > +                    }
> > +
> >                      ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 100,
> >                                    ds_cstr(&match), ds_cstr(&actions));
> >                  } else {
> > @@ -7192,8 +7232,15 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
> >                                        od->l3redirect_port->json_key);
> >                      }
> >                      ds_clear(&actions);
> > -                    ds_put_format(&actions, "ct_dnat(%s);",
> > -                                  nat->logical_ip);
> > +
> > +                    if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) {
> > +                        ds_put_format(&actions, "ip4.dst=%s; next;",
> > +                                      nat->logical_ip);
> > +                    } else {
> > +                        ds_put_format(&actions, "ct_dnat(%s);",
> > +                                      nat->logical_ip);
> > +                    }
> > +
> >                      ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 100,
> >                                    ds_cstr(&match),
> > ds_cstr(&actions));
> >
> > @@ -7235,7 +7282,14 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
> >                      ds_put_format(&actions, "eth.src = "ETH_ADDR_FMT"; ",
> >                                    ETH_ADDR_ARGS(mac));
> >                  }
> > -                ds_put_format(&actions, "ct_dnat;");
> > +
> > +                if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) {
> > +                    ds_put_format(&actions, "ip4.src=%s; next;",
> > +                                  nat->external_ip);
> > +                } else {
> > +                    ds_put_format(&actions, "ct_dnat;");
> > +                }
> > +
> >                  ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 100,
> >                                ds_cstr(&match), ds_cstr(&actions));
> >              }
> > @@ -7251,7 +7305,14 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
> >                      ds_put_format(&match, "ip && ip4.src == %s",
> >                                    nat->logical_ip);
> >                      ds_clear(&actions);
> > -                    ds_put_format(&actions, "ct_snat(%s);", nat->external_ip);
> > +
> > +                    if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) {
> > +                        ds_put_format(&actions, "ip4.src=%s; next;",
> > +                                      nat->external_ip);
> > +                    } else {
> > +                        ds_put_format(&actions, "ct_snat(%s);",
> > +                                      nat->external_ip);
> > +                    }
> >
> >                      /* The priority here is calculated such that the
> >                       * nat->logical_ip with the longest mask gets a
> > higher @@ -7280,7 +7341,14 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
> >                          ds_put_format(&actions, "eth.src = "ETH_ADDR_FMT"; ",
> >                                        ETH_ADDR_ARGS(mac));
> >                      }
> > -                    ds_put_format(&actions, "ct_snat(%s);", nat->external_ip);
> > +
> > +                    if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) {
> > +                        ds_put_format(&actions, "ip4.src=%s; next;",
> > +                                      nat->external_ip);
> > +                    } else {
> > +                        ds_put_format(&actions, "ct_snat(%s);",
> > +                                      nat->external_ip);
> > +                    }
> >
> >                      /* The priority here is calculated such that the
> >                       * nat->logical_ip with the longest mask gets a
> > higher diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at index
> > 42033d5..64511a9 100644
> > --- a/tests/ovn-northd.at
> > +++ b/tests/ovn-northd.at
> > @@ -966,3 +966,53 @@ OVS_WAIT_UNTIL([ovn-sbctl get Port_Binding
> > ${uuid} options:redirect-type], [0],
> >  ])
> >
> >  AT_CLEANUP
> > +
>
> Can you add a new test case or modify an existing test case in ovn.at to make use of this feature ?
>
> > +AT_SETUP([ovn -- check stateless dnat_and_snat rule])
> > +AT_SKIP_IF([test $HAVE_PYTHON = no]) ovn_start
> > +
> > +ovn-sbctl chassis-add gw1 geneve 127.0.0.1
> > +
> > +ovn-nbctl lr-add R1
> > +ovn-nbctl lrp-add R1 R1-S1 02:ac:10:01:00:01 172.16.1.1/24
> > +
> > +ovn-nbctl ls-add S1
> > +ovn-nbctl lsp-add S1 S1-R1
> > +ovn-nbctl lsp-set-type S1-R1 router
> > +ovn-nbctl lsp-set-addresses S1-R1 router ovn-nbctl --wait=sb
> > +lsp-set-options S1-R1 router-port=R1-S1
> > +
> > +ovn-nbctl lrp-set-gateway-chassis R1-S1 gw1
> > +
> > +uuid=`ovn-sbctl --columns=_uuid --bare find Port_Binding
> > +logical_port=cr-R1-S1` echo "CR-LRP UUID is: " $uuid
> > +
> > +ovn-nbctl lr-nat-add R1 dnat_and_snat  172.16.1.1 50.0.0.11
> > +AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_snat | wc -l], [0], [2
> > +])
>
> There is a chance that the test case might fail on slower systems if ovn-northd is not fast enough to add the necessary logical flows.
>
> For the above first check, I would suggest to use - OVS_WAIT_UNTIL.
>
>
> > +
> > +AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_dnat | wc -l], [0], [2
> > +])
> > +
> > +AT_CHECK([ovn-sbctl dump-flows R1 | grep ip4.dst=| wc -l], [0], [0
> > +])
> > +
> > +AT_CHECK([ovn-sbctl dump-flows R1 | grep ip4.src=| wc -l], [0], [0
> > +])
> > +
> > +ovn-nbctl lr-nat-del R1 dnat_and_snat  172.16.1.1
> > +
> > +ovn-nbctl --stateless lr-nat-add R1 dnat_and_snat  172.16.1.1
> > +50.0.0.11 AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_snat | wc -l],
> > +[0], [0
> > +])
> > +
>
> Same here. Please use OVS_WAIT_UNTIL.
>
>
> > +AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_dnat | wc -l], [0], [0
> > +])
> > +
> > +AT_CHECK([ovn-sbctl dump-flows R1 | grep ip4.dst=| wc -l], [0], [2
> > +])
> > +
> > +AT_CHECK([ovn-sbctl dump-flows R1 | grep ip4.src=| wc -l], [0], [2
> > +])
> > +
> > +AT_CLEANUP
> > --
> > 1.8.3.1
> >
> > _______________________________________________
> > dev mailing list
> > dev@openvswitch.org
> > https://urldefense.proofpoint.com/v2/url?u=https-3A__mail.openvswitch.
> > org_mailman_listinfo_ovs-2Ddev&d=DwIBaQ&c=s883GpUCOChKOHiocYtGcg&r=mZw
> > X9gFQgeJHzTg-68aCJgsODyUEVsHGFOfL90J6MJY&m=CouZqk7agKapXA85Hmee871NM2d
> > rC0etOpAwwwF_Qvw&s=yGzdgaPfGMkQpXg_3NmyQCOqkqeJPvomW3QqTsWghPQ&e=
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
Ankur Sharma Nov. 1, 2019, 1:30 a.m. UTC | #4
Hi Numan,

Just submitted V4, please take a look.

Regards,
Ankur

-----Original Message-----
From: Numan Siddique <numans@ovn.org> 
Sent: Thursday, October 31, 2019 10:39 AM
To: Ankur Sharma <ankur.sharma@nutanix.com>
Cc: ovs-dev@openvswitch.org
Subject: Re: [ovs-dev] [PATCH v2 2/2 ovn] OVN: Use ipv4.src and ipv4.dst actions for NAT rules

On Thu, Oct 31, 2019 at 2:02 AM Ankur Sharma <ankur.sharma@nutanix.com> wrote:
>
> Hi Numan,
>
> Submitted a v3 yesterday, addressing all the review comments.
> Please take a look.

Hi Ankur,

Can you please rebase your v3 patches ? They don't apply on top of master.

Thanks
Numan

>
> Regards,
> Ankur
>
> -----Original Message-----
> From: Numan Siddique <numans@ovn.org>
> Sent: Monday, October 28, 2019 6:40 AM
> To: Ankur Sharma <ankur.sharma@nutanix.com>
> Cc: ovs-dev@openvswitch.org
> Subject: Re: [ovs-dev] [PATCH v2 2/2 ovn] OVN: Use ipv4.src and 
> ipv4.dst actions for NAT rules
>
> On Sat, Oct 5, 2019 at 1:45 AM Ankur Sharma <ankur.sharma@nutanix.com> wrote:
> >
> > For dnat_and_snat rules which are meant to be stateless instead of 
> > using ct_snat/dnat OVN actions, we will use ipv4.src/ipv4.dst.
> >
> > This actions will do 1:1 mapping to inner ip to external ip, while 
> > recalculating the checksums.
> >
> > Signed-off-by: Ankur Sharma <ankur.sharma@nutanix.com>
>
>
> Hi Ankur,
>
> There is a checkpatch warning - WARNING: Line is 80 characters long (recommended limit is 79).
> Please address it.
>
> Please see below for few comments.
>
> Thanks
> Numan
>
> > ---
> >  northd/ovn-northd.8.xml | 34 +++++++++++++++----
> >  northd/ovn-northd.c     | 86 +++++++++++++++++++++++++++++++++++++++++++------
> >  tests/ovn-northd.at     | 50 ++++++++++++++++++++++++++++
> >  3 files changed, 154 insertions(+), 16 deletions(-)
> >
> > diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml index
> > 0f4f1c1..7d5d102 100644
> > --- a/northd/ovn-northd.8.xml
> > +++ b/northd/ovn-northd.8.xml
> > @@ -1718,7 +1718,9 @@ icmp6 {
> >            to change the source IP address of a packet from <var>A</var> to
> >            <var>B</var>, a priority-90 flow matches <code>ip &amp;&amp;
> >            ip4.dst == <var>B</var></code> with an action
> > -          <code>ct_snat; </code>.
> > +          <code>ct_snat; </code>. If the NAT rule is of type dnat_and_snat
> > +          and has <code>is_stateless=true</code> in the options, then the action
> > +          would be <code>replace_dst_ip(<var>B</var>)</code>.
>
> I think you need to update the documentation as you removed the action
> - replace_dst_ip in v2.
>
>
> >          </p>
> >
> >          <p>
> > @@ -1738,7 +1740,10 @@ icmp6 {
> >            <var>B</var>, a priority-100 flow matches <code>ip &amp;&amp;
> >            ip4.dst == <var>B</var> &amp;&amp; inport == <var>GW</var></code>,
> >            where <var>GW</var> is the logical router gateway port, with an
> > -          action <code>ct_snat;</code>.
> > +          action <code>ct_snat;</code>. If the NAT rule is of type
> > +          dnat_and_snat and has <code>is_stateless=true</code> in the
> > +          options, then the action would be <code>replace_dst_ip
>
> Same here and other places too.
>
> > +          (<var>B</var>)</code>.
> >          </p>
> >
> >          <p>
> > @@ -1858,7 +1863,10 @@ icmp6 {
> >          Gateway router is configured to force SNAT any DNATed packet,
> >          the above action will be replaced by
> >          <code>flags.force_snat_for_dnat = 1; flags.loopback = 1;
> > -        ct_dnat(<var>B</var>);</code>.
> > +        ct_dnat(<var>B</var>);</code>. If the NAT rule is of type
> > +        dnat_and_snat and has <code>is_stateless=true</code> in the
> > +        options, then the action would be <code>replace_dst_ip
> > +        (<var>B</var>)</code>.
> >        </li>
> >
> >        <li>
> > @@ -1890,7 +1898,10 @@ icmp6 {
> >            <var>B</var>, a priority-100 flow matches <code>ip &amp;&amp;
> >            ip4.dst == <var>B</var> &amp;&amp; inport == <var>GW</var></code>,
> >            where <var>GW</var> is the logical router gateway port, with an
> > -          action <code>ct_dnat(<var>B</var>);</code>.
> > +          action <code>ct_dnat(<var>B</var>);</code>. If the NAT rule is of
> > +          type dnat_and_snat and has <code>is_stateless=true</code> in the
> > +          options, then the action would be <code>replace_dst_ip
> > +          (<var>B</var>)</code>.
> >          </p>
> >
> >          <p>
> > @@ -2553,7 +2564,10 @@ nd_ns {
> >            matches <code>ip &amp;&amp; ip4.src == <var>B</var>
> >            &amp;&amp; outport == <var>GW</var></code>, where <var>GW</var>
> >            is the logical router gateway port, with an action
> > -          <code>ct_dnat;</code>.
> > +          <code>ct_dnat;</code>. If the NAT rule is of type
> > +          dnat_and_snat and has <code>is_stateless=true</code> in the
> > +          options, then the action would be <code>replace_src_ip
> > +          (<var>B</var>)</code>.
> >          </p>
> >
> >          <p>
> > @@ -2611,7 +2625,10 @@ nd_ns {
> >            <code>ip &amp;&amp; ip4.src == <var>A</var></code> with an action
> >            <code>ct_snat(<var>B</var>);</code>.  The priority of the flow
> >            is calculated based on the mask of <var>A</var>, with matches
> > -          having larger masks getting higher priorities.
> > +          having larger masks getting higher priorities. If the NAT rule is
> > +          of type dnat_and_snat and has <code>is_stateless=true</code> in the
> > +          options, then the action would be <code>replace_src_ip
> > +          (<var>B</var>)</code>.
> >          </p>
> >          <p>
> >            A priority-0 logical flow with match <code>1</code> has 
> > actions @@ -2634,7 +2651,10 @@ nd_ns {
> >            logical router gateway port, with an action
> >            <code>ct_snat(<var>B</var>);</code>.  The priority of the flow
> >            is calculated based on the mask of <var>A</var>, with matches
> > -          having larger masks getting higher priorities.
> > +          having larger masks getting higher priorities. If the NAT rule
> > +          is of type dnat_and_snat and has <code>is_stateless=true</code>
> > +          in the options, then the action would be <code>replace_src_ip
> > +          (<var>B</var>)</code>.
> >          </p>
> >
> >          <p>
> > diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c index
> > f393ceb..4036392 100644
> > --- a/northd/ovn-northd.c
> > +++ b/northd/ovn-northd.c
> > @@ -6314,6 +6314,18 @@ copy_ra_to_sb(struct ovn_port *op, const char *address_mode)
> >      smap_destroy(&options);
> >  }
> >
> > +static inline bool
> > +lrouter_nat_is_stateless(const struct nbrec_nat *nat) {
> > +    const char *is_stateless = smap_get(&nat->options, 
> > +"is_stateless");
> > +
> > +    if (is_stateless && !strcmp(is_stateless, "true")) {
> > +        return true;
> > +    }
> > +
> > +    return false;
> > +}
> > +
> >  static void
> >  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
> >                      struct hmap *lflows, struct shash 
> > *meter_groups) @@ -7052,6 +7064,7 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
> >              nat = od->nbr->nat[i];
> >
> >              ovs_be32 ip, mask;
> > +            bool is_stateless = lrouter_nat_is_stateless(nat);
> >
> >              char *error = ip_parse_masked(nat->external_ip, &ip, &mask);
> >              if (error || mask != OVS_BE32_MAX) { @@ -7117,15 
> > +7130,26 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
> >                  if (!od->l3dgw_port) {
> >                      /* Gateway router. */
> >                      ds_clear(&match);
> > +                    ds_clear(&actions);
> >                      ds_put_format(&match, "ip && ip4.dst == %s",
> >                                    nat->external_ip);
> > +
> > +                    if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) {
> > +                        ds_put_format(&actions, "ip4.dst=%s; next;",
> > +                                      nat->logical_ip);
> > +                    } else {
> > +                        ds_put_cstr(&actions, "ct_snat;");
> > +                    }
> > +
> >                      ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 90,
> > -                                  ds_cstr(&match), "ct_snat;");
> > +                                  ds_cstr(&match), 
> > + ds_cstr(&actions));
> >                  } else {
> >                      /* Distributed router. */
> >
> >                      /* Traffic received on l3dgw_port is subject to NAT. */
> >                      ds_clear(&match);
> > +                    ds_clear(&actions);
> > +
> >                      ds_put_format(&match, "ip && ip4.dst == %s"
> >                                            " && inport == %s",
> >                                    nat->external_ip, @@ -7136,8
> > +7160,16 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap 
> > +*ports,
> >                          ds_put_format(&match, " && is_chassis_resident(%s)",
> >                                        od->l3redirect_port->json_key);
> >                      }
> > +
> > +                    if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) {
> > +                        ds_put_format(&actions, "ip4.dst=%s; next;",
> > +                                      nat->logical_ip);
> > +                    } else {
> > +                        ds_put_cstr(&actions, "ct_snat;");
> > +                    }
> > +
> >                      ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 100,
> > -                                  ds_cstr(&match), "ct_snat;");
> > +                                  ds_cstr(&match), 
> > + ds_cstr(&actions));
> >
> >                      /* Traffic received on other router ports must be
> >                       * redirected to the central instance of the 
> > l3dgw_port @@ -7172,8 +7204,16 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
> >                          ds_put_format(&actions,
> >                                        "flags.force_snat_for_dnat = 1; ");
> >                      }
> > -                    ds_put_format(&actions, "flags.loopback = 1; ct_dnat(%s);",
> > -                                  nat->logical_ip);
> > +
> > +                    if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) {
> > +                        ds_put_format(&actions, "flags.loopback = 1; "
> > +                                      "ip4.dst=%s; next;",
> > +                                      nat->logical_ip);
> > +                    } else {
> > +                        ds_put_format(&actions, "flags.loopback = 1; ct_dnat(%s);",
> > +                                      nat->logical_ip);
> > +                    }
> > +
> >                      ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 100,
> >                                    ds_cstr(&match), ds_cstr(&actions));
> >                  } else {
> > @@ -7192,8 +7232,15 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
> >                                        od->l3redirect_port->json_key);
> >                      }
> >                      ds_clear(&actions);
> > -                    ds_put_format(&actions, "ct_dnat(%s);",
> > -                                  nat->logical_ip);
> > +
> > +                    if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) {
> > +                        ds_put_format(&actions, "ip4.dst=%s; next;",
> > +                                      nat->logical_ip);
> > +                    } else {
> > +                        ds_put_format(&actions, "ct_dnat(%s);",
> > +                                      nat->logical_ip);
> > +                    }
> > +
> >                      ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 100,
> >                                    ds_cstr(&match), 
> > ds_cstr(&actions));
> >
> > @@ -7235,7 +7282,14 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
> >                      ds_put_format(&actions, "eth.src = "ETH_ADDR_FMT"; ",
> >                                    ETH_ADDR_ARGS(mac));
> >                  }
> > -                ds_put_format(&actions, "ct_dnat;");
> > +
> > +                if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) {
> > +                    ds_put_format(&actions, "ip4.src=%s; next;",
> > +                                  nat->external_ip);
> > +                } else {
> > +                    ds_put_format(&actions, "ct_dnat;");
> > +                }
> > +
> >                  ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 100,
> >                                ds_cstr(&match), ds_cstr(&actions));
> >              }
> > @@ -7251,7 +7305,14 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
> >                      ds_put_format(&match, "ip && ip4.src == %s",
> >                                    nat->logical_ip);
> >                      ds_clear(&actions);
> > -                    ds_put_format(&actions, "ct_snat(%s);", nat->external_ip);
> > +
> > +                    if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) {
> > +                        ds_put_format(&actions, "ip4.src=%s; next;",
> > +                                      nat->external_ip);
> > +                    } else {
> > +                        ds_put_format(&actions, "ct_snat(%s);",
> > +                                      nat->external_ip);
> > +                    }
> >
> >                      /* The priority here is calculated such that the
> >                       * nat->logical_ip with the longest mask gets a 
> > higher @@ -7280,7 +7341,14 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
> >                          ds_put_format(&actions, "eth.src = "ETH_ADDR_FMT"; ",
> >                                        ETH_ADDR_ARGS(mac));
> >                      }
> > -                    ds_put_format(&actions, "ct_snat(%s);", nat->external_ip);
> > +
> > +                    if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) {
> > +                        ds_put_format(&actions, "ip4.src=%s; next;",
> > +                                      nat->external_ip);
> > +                    } else {
> > +                        ds_put_format(&actions, "ct_snat(%s);",
> > +                                      nat->external_ip);
> > +                    }
> >
> >                      /* The priority here is calculated such that the
> >                       * nat->logical_ip with the longest mask gets a 
> > higher diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at index
> > 42033d5..64511a9 100644
> > --- a/tests/ovn-northd.at
> > +++ b/tests/ovn-northd.at
> > @@ -966,3 +966,53 @@ OVS_WAIT_UNTIL([ovn-sbctl get Port_Binding 
> > ${uuid} options:redirect-type], [0],
> >  ])
> >
> >  AT_CLEANUP
> > +
>
> Can you add a new test case or modify an existing test case in ovn.at to make use of this feature ?
>
> > +AT_SETUP([ovn -- check stateless dnat_and_snat rule]) 
> > +AT_SKIP_IF([test $HAVE_PYTHON = no]) ovn_start
> > +
> > +ovn-sbctl chassis-add gw1 geneve 127.0.0.1
> > +
> > +ovn-nbctl lr-add R1
> > +ovn-nbctl lrp-add R1 R1-S1 02:ac:10:01:00:01 172.16.1.1/24
> > +
> > +ovn-nbctl ls-add S1
> > +ovn-nbctl lsp-add S1 S1-R1
> > +ovn-nbctl lsp-set-type S1-R1 router ovn-nbctl lsp-set-addresses 
> > +S1-R1 router ovn-nbctl --wait=sb lsp-set-options S1-R1 
> > +router-port=R1-S1
> > +
> > +ovn-nbctl lrp-set-gateway-chassis R1-S1 gw1
> > +
> > +uuid=`ovn-sbctl --columns=_uuid --bare find Port_Binding 
> > +logical_port=cr-R1-S1` echo "CR-LRP UUID is: " $uuid
> > +
> > +ovn-nbctl lr-nat-add R1 dnat_and_snat  172.16.1.1 50.0.0.11 
> > +AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_snat | wc -l], [0], [2
> > +])
>
> There is a chance that the test case might fail on slower systems if ovn-northd is not fast enough to add the necessary logical flows.
>
> For the above first check, I would suggest to use - OVS_WAIT_UNTIL.
>
>
> > +
> > +AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_dnat | wc -l], [0], [2
> > +])
> > +
> > +AT_CHECK([ovn-sbctl dump-flows R1 | grep ip4.dst=| wc -l], [0], [0
> > +])
> > +
> > +AT_CHECK([ovn-sbctl dump-flows R1 | grep ip4.src=| wc -l], [0], [0
> > +])
> > +
> > +ovn-nbctl lr-nat-del R1 dnat_and_snat  172.16.1.1
> > +
> > +ovn-nbctl --stateless lr-nat-add R1 dnat_and_snat  172.16.1.1
> > +50.0.0.11 AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_snat | wc 
> > +-l], [0], [0
> > +])
> > +
>
> Same here. Please use OVS_WAIT_UNTIL.
>
>
> > +AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_dnat | wc -l], [0], [0
> > +])
> > +
> > +AT_CHECK([ovn-sbctl dump-flows R1 | grep ip4.dst=| wc -l], [0], [2
> > +])
> > +
> > +AT_CHECK([ovn-sbctl dump-flows R1 | grep ip4.src=| wc -l], [0], [2
> > +])
> > +
> > +AT_CLEANUP
> > --
> > 1.8.3.1
> >
> > _______________________________________________
> > dev mailing list
> > dev@openvswitch.org
> > https://urldefense.proofpoint.com/v2/url?u=https-3A__mail.openvswitch.
> > org_mailman_listinfo_ovs-2Ddev&d=DwIBaQ&c=s883GpUCOChKOHiocYtGcg&r=m
> > Zw 
> > X9gFQgeJHzTg-68aCJgsODyUEVsHGFOfL90J6MJY&m=CouZqk7agKapXA85Hmee871NM
> > 2d rC0etOpAwwwF_Qvw&s=yGzdgaPfGMkQpXg_3NmyQCOqkqeJPvomW3QqTsWghPQ&e=
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://urldefense.proofpoint.com/v2/url?u=https-3A__mail.openvswitch.
> org_mailman_listinfo_ovs-2Ddev&d=DwIBaQ&c=s883GpUCOChKOHiocYtGcg&r=mZw
> X9gFQgeJHzTg-68aCJgsODyUEVsHGFOfL90J6MJY&m=FoT09vI9qbn00jTWguXBPw_kYud
> y-Wq8BfPkXLWJOa4&s=W5wu37wMH2lS-OF3c4rTe7_ZNJWutAlxru5RWqyeCwQ&e=
diff mbox series

Patch

diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
index 0f4f1c1..7d5d102 100644
--- a/northd/ovn-northd.8.xml
+++ b/northd/ovn-northd.8.xml
@@ -1718,7 +1718,9 @@  icmp6 {
           to change the source IP address of a packet from <var>A</var> to
           <var>B</var>, a priority-90 flow matches <code>ip &amp;&amp;
           ip4.dst == <var>B</var></code> with an action
-          <code>ct_snat; </code>.
+          <code>ct_snat; </code>. If the NAT rule is of type dnat_and_snat
+          and has <code>is_stateless=true</code> in the options, then the action
+          would be <code>replace_dst_ip(<var>B</var>)</code>.
         </p>
 
         <p>
@@ -1738,7 +1740,10 @@  icmp6 {
           <var>B</var>, a priority-100 flow matches <code>ip &amp;&amp;
           ip4.dst == <var>B</var> &amp;&amp; inport == <var>GW</var></code>,
           where <var>GW</var> is the logical router gateway port, with an
-          action <code>ct_snat;</code>.
+          action <code>ct_snat;</code>. If the NAT rule is of type
+          dnat_and_snat and has <code>is_stateless=true</code> in the
+          options, then the action would be <code>replace_dst_ip
+          (<var>B</var>)</code>.
         </p>
 
         <p>
@@ -1858,7 +1863,10 @@  icmp6 {
         Gateway router is configured to force SNAT any DNATed packet,
         the above action will be replaced by
         <code>flags.force_snat_for_dnat = 1; flags.loopback = 1;
-        ct_dnat(<var>B</var>);</code>.
+        ct_dnat(<var>B</var>);</code>. If the NAT rule is of type
+        dnat_and_snat and has <code>is_stateless=true</code> in the
+        options, then the action would be <code>replace_dst_ip
+        (<var>B</var>)</code>.
       </li>
 
       <li>
@@ -1890,7 +1898,10 @@  icmp6 {
           <var>B</var>, a priority-100 flow matches <code>ip &amp;&amp;
           ip4.dst == <var>B</var> &amp;&amp; inport == <var>GW</var></code>,
           where <var>GW</var> is the logical router gateway port, with an
-          action <code>ct_dnat(<var>B</var>);</code>.
+          action <code>ct_dnat(<var>B</var>);</code>. If the NAT rule is of
+          type dnat_and_snat and has <code>is_stateless=true</code> in the
+          options, then the action would be <code>replace_dst_ip
+          (<var>B</var>)</code>.
         </p>
 
         <p>
@@ -2553,7 +2564,10 @@  nd_ns {
           matches <code>ip &amp;&amp; ip4.src == <var>B</var>
           &amp;&amp; outport == <var>GW</var></code>, where <var>GW</var>
           is the logical router gateway port, with an action
-          <code>ct_dnat;</code>.
+          <code>ct_dnat;</code>. If the NAT rule is of type
+          dnat_and_snat and has <code>is_stateless=true</code> in the
+          options, then the action would be <code>replace_src_ip
+          (<var>B</var>)</code>.
         </p>
 
         <p>
@@ -2611,7 +2625,10 @@  nd_ns {
           <code>ip &amp;&amp; ip4.src == <var>A</var></code> with an action
           <code>ct_snat(<var>B</var>);</code>.  The priority of the flow
           is calculated based on the mask of <var>A</var>, with matches
-          having larger masks getting higher priorities.
+          having larger masks getting higher priorities. If the NAT rule is
+          of type dnat_and_snat and has <code>is_stateless=true</code> in the
+          options, then the action would be <code>replace_src_ip
+          (<var>B</var>)</code>.
         </p>
         <p>
           A priority-0 logical flow with match <code>1</code> has actions
@@ -2634,7 +2651,10 @@  nd_ns {
           logical router gateway port, with an action
           <code>ct_snat(<var>B</var>);</code>.  The priority of the flow
           is calculated based on the mask of <var>A</var>, with matches
-          having larger masks getting higher priorities.
+          having larger masks getting higher priorities. If the NAT rule
+          is of type dnat_and_snat and has <code>is_stateless=true</code>
+          in the options, then the action would be <code>replace_src_ip
+          (<var>B</var>)</code>.
         </p>
 
         <p>
diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
index f393ceb..4036392 100644
--- a/northd/ovn-northd.c
+++ b/northd/ovn-northd.c
@@ -6314,6 +6314,18 @@  copy_ra_to_sb(struct ovn_port *op, const char *address_mode)
     smap_destroy(&options);
 }
 
+static inline bool
+lrouter_nat_is_stateless(const struct nbrec_nat *nat)
+{
+    const char *is_stateless = smap_get(&nat->options, "is_stateless");
+
+    if (is_stateless && !strcmp(is_stateless, "true")) {
+        return true;
+    }
+
+    return false;
+}
+
 static void
 build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
                     struct hmap *lflows, struct shash *meter_groups)
@@ -7052,6 +7064,7 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
             nat = od->nbr->nat[i];
 
             ovs_be32 ip, mask;
+            bool is_stateless = lrouter_nat_is_stateless(nat);
 
             char *error = ip_parse_masked(nat->external_ip, &ip, &mask);
             if (error || mask != OVS_BE32_MAX) {
@@ -7117,15 +7130,26 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
                 if (!od->l3dgw_port) {
                     /* Gateway router. */
                     ds_clear(&match);
+                    ds_clear(&actions);
                     ds_put_format(&match, "ip && ip4.dst == %s",
                                   nat->external_ip);
+
+                    if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) {
+                        ds_put_format(&actions, "ip4.dst=%s; next;",
+                                      nat->logical_ip);
+                    } else {
+                        ds_put_cstr(&actions, "ct_snat;");
+                    }
+
                     ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 90,
-                                  ds_cstr(&match), "ct_snat;");
+                                  ds_cstr(&match), ds_cstr(&actions));
                 } else {
                     /* Distributed router. */
 
                     /* Traffic received on l3dgw_port is subject to NAT. */
                     ds_clear(&match);
+                    ds_clear(&actions);
+
                     ds_put_format(&match, "ip && ip4.dst == %s"
                                           " && inport == %s",
                                   nat->external_ip,
@@ -7136,8 +7160,16 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
                         ds_put_format(&match, " && is_chassis_resident(%s)",
                                       od->l3redirect_port->json_key);
                     }
+
+                    if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) {
+                        ds_put_format(&actions, "ip4.dst=%s; next;",
+                                      nat->logical_ip);
+                    } else {
+                        ds_put_cstr(&actions, "ct_snat;");
+                    }
+
                     ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 100,
-                                  ds_cstr(&match), "ct_snat;");
+                                  ds_cstr(&match), ds_cstr(&actions));
 
                     /* Traffic received on other router ports must be
                      * redirected to the central instance of the l3dgw_port
@@ -7172,8 +7204,16 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
                         ds_put_format(&actions,
                                       "flags.force_snat_for_dnat = 1; ");
                     }
-                    ds_put_format(&actions, "flags.loopback = 1; ct_dnat(%s);",
-                                  nat->logical_ip);
+
+                    if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) {
+                        ds_put_format(&actions, "flags.loopback = 1; "
+                                      "ip4.dst=%s; next;",
+                                      nat->logical_ip);
+                    } else {
+                        ds_put_format(&actions, "flags.loopback = 1; ct_dnat(%s);",
+                                      nat->logical_ip);
+                    }
+
                     ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 100,
                                   ds_cstr(&match), ds_cstr(&actions));
                 } else {
@@ -7192,8 +7232,15 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
                                       od->l3redirect_port->json_key);
                     }
                     ds_clear(&actions);
-                    ds_put_format(&actions, "ct_dnat(%s);",
-                                  nat->logical_ip);
+
+                    if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) {
+                        ds_put_format(&actions, "ip4.dst=%s; next;",
+                                      nat->logical_ip);
+                    } else {
+                        ds_put_format(&actions, "ct_dnat(%s);",
+                                      nat->logical_ip);
+                    }
+
                     ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 100,
                                   ds_cstr(&match), ds_cstr(&actions));
 
@@ -7235,7 +7282,14 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
                     ds_put_format(&actions, "eth.src = "ETH_ADDR_FMT"; ",
                                   ETH_ADDR_ARGS(mac));
                 }
-                ds_put_format(&actions, "ct_dnat;");
+
+                if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) {
+                    ds_put_format(&actions, "ip4.src=%s; next;",
+                                  nat->external_ip);
+                } else {
+                    ds_put_format(&actions, "ct_dnat;");
+                }
+
                 ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 100,
                               ds_cstr(&match), ds_cstr(&actions));
             }
@@ -7251,7 +7305,14 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
                     ds_put_format(&match, "ip && ip4.src == %s",
                                   nat->logical_ip);
                     ds_clear(&actions);
-                    ds_put_format(&actions, "ct_snat(%s);", nat->external_ip);
+
+                    if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) {
+                        ds_put_format(&actions, "ip4.src=%s; next;",
+                                      nat->external_ip);
+                    } else {
+                        ds_put_format(&actions, "ct_snat(%s);",
+                                      nat->external_ip);
+                    }
 
                     /* The priority here is calculated such that the
                      * nat->logical_ip with the longest mask gets a higher
@@ -7280,7 +7341,14 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
                         ds_put_format(&actions, "eth.src = "ETH_ADDR_FMT"; ",
                                       ETH_ADDR_ARGS(mac));
                     }
-                    ds_put_format(&actions, "ct_snat(%s);", nat->external_ip);
+
+                    if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) {
+                        ds_put_format(&actions, "ip4.src=%s; next;",
+                                      nat->external_ip);
+                    } else {
+                        ds_put_format(&actions, "ct_snat(%s);",
+                                      nat->external_ip);
+                    }
 
                     /* The priority here is calculated such that the
                      * nat->logical_ip with the longest mask gets a higher
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 42033d5..64511a9 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -966,3 +966,53 @@  OVS_WAIT_UNTIL([ovn-sbctl get Port_Binding ${uuid} options:redirect-type], [0],
 ])
 
 AT_CLEANUP
+
+AT_SETUP([ovn -- check stateless dnat_and_snat rule])
+AT_SKIP_IF([test $HAVE_PYTHON = no])
+ovn_start
+
+ovn-sbctl chassis-add gw1 geneve 127.0.0.1
+
+ovn-nbctl lr-add R1
+ovn-nbctl lrp-add R1 R1-S1 02:ac:10:01:00:01 172.16.1.1/24
+
+ovn-nbctl ls-add S1
+ovn-nbctl lsp-add S1 S1-R1
+ovn-nbctl lsp-set-type S1-R1 router
+ovn-nbctl lsp-set-addresses S1-R1 router
+ovn-nbctl --wait=sb lsp-set-options S1-R1 router-port=R1-S1
+
+ovn-nbctl lrp-set-gateway-chassis R1-S1 gw1
+
+uuid=`ovn-sbctl --columns=_uuid --bare find Port_Binding logical_port=cr-R1-S1`
+echo "CR-LRP UUID is: " $uuid
+
+ovn-nbctl lr-nat-add R1 dnat_and_snat  172.16.1.1 50.0.0.11
+AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_snat | wc -l], [0], [2
+])
+
+AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_dnat | wc -l], [0], [2
+])
+
+AT_CHECK([ovn-sbctl dump-flows R1 | grep ip4.dst=| wc -l], [0], [0
+])
+
+AT_CHECK([ovn-sbctl dump-flows R1 | grep ip4.src=| wc -l], [0], [0
+])
+
+ovn-nbctl lr-nat-del R1 dnat_and_snat  172.16.1.1
+
+ovn-nbctl --stateless lr-nat-add R1 dnat_and_snat  172.16.1.1 50.0.0.11
+AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_snat | wc -l], [0], [0
+])
+
+AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_dnat | wc -l], [0], [0
+])
+
+AT_CHECK([ovn-sbctl dump-flows R1 | grep ip4.dst=| wc -l], [0], [2
+])
+
+AT_CHECK([ovn-sbctl dump-flows R1 | grep ip4.src=| wc -l], [0], [2
+])
+
+AT_CLEANUP