diff mbox series

[ovs-dev,v6,2/2] External IP based NAT: NORTHD changes to use allowed/exempted external ip

Message ID 1599512205-11041-3-git-send-email-svc.mail.git@nutanix.com
State Accepted
Headers show
Series External IP based NAT | expand

Commit Message

Ankur Sharma Sept. 7, 2020, 8:56 p.m. UTC
From: Ankur Sharma <ankur.sharma@nutanix.com>

This patch has northd changes which consumes allowed/exempted external ip
configuration per NAT rule in logical flow.

Allowed external ip range adds an additional match criteria in
snat/dnat logical flow rules.

For example, if an allowed_external_ip address set ("abcd")
is configured for following NAT rule.
TYPE             EXTERNAL_IP        LOGICAL_IP
snat             10.15.24.135       50.0.0.10

Then logical flow will look like following:
..(lr_out_snat)...match=(ip && .... && ip4.dst == $abcd), action=(ct_snat(...);)

Exempted external ip range adds an additional flow at priority+1
to bypass the NAT pipeline if external ip is in extempted external
ip address set.
For example, if the same NAT rule mentioned aboe has an
exempted_external_ip address set ("efgh"), then
logical flow will look like following:

..(lr_out_snat), priority=162...match=(ip && .... && ip4.dst == $efgh), action=(next;)
..(lr_out_snat), priority=161...match=(ip && ....), action=(ct_snat(10.15.24.135);)

Signed-off-by: Ankur Sharma <ankur.sharma@nutanix.com>
---
 northd/ovn-northd.8.xml |  67 +++++++++++++++
 northd/ovn-northd.c     | 102 +++++++++++++++++++++++
 tests/ovn-northd.at     | 210 ++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 379 insertions(+)

Comments

0-day Robot Sept. 7, 2020, 10:02 p.m. UTC | #1
Bleep bloop.  Greetings Ankur Sharma, I am a robot and I have tried out your patch.
Thanks for your contribution.

I encountered some error that I wasn't expecting.  See the details below.


checkpatch:
ERROR: Inappropriate bracing around statement
#197 FILE: northd/ovn-northd.c:8338:
            if (!is_gw_router)

Lines checked: 507, Warnings: 0, Errors: 1


Please check this out.  If you feel there has been an error, please email aconole@redhat.com

Thanks,
0-day Robot
diff mbox series

Patch

diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
index 989e364..d15245a 100644
--- a/northd/ovn-northd.8.xml
+++ b/northd/ovn-northd.8.xml
@@ -2471,6 +2471,23 @@  icmp6 {
         dnat_and_snat and has <code>stateless=true</code> in the
         options, then the action would be <code>ip4/6.dst=
         (<var>B</var>)</code>.
+
+        <p>
+          If the NAT rule has <code>allowed_ext_ips</code> configured, then
+          there is an additional match <code>ip4.src == <var>allowed_ext_ips
+          </var></code>. Similarly, for IPV6, match would be <code>ip6.src ==
+          <var>allowed_ext_ips</var></code>.
+        </p>
+
+        <p>
+          If the NAT rule has <code>exempted_ext_ips</code> set, then
+          there is an additional flow configured at priority 101.
+          The flow matches if source ip is an <code>exempted_ext_ip</code>
+          and the action is <code>next; </code>. This flow is used to
+          bypass the ct_dnat action for a packet originating from
+          <code>exempted_ext_ips</code>.
+        </p>
+
       </li>
 
       <li>
@@ -2516,6 +2533,22 @@  icmp6 {
         </p>
 
         <p>
+          If the NAT rule has <code>allowed_ext_ips</code> configured, then
+          there is an additional match <code>ip4.src == <var>allowed_ext_ips
+          </var></code>. Similarly, for IPV6, match would be <code>ip6.src ==
+          <var>allowed_ext_ips</var></code>.
+        </p>
+
+        <p>
+          If the NAT rule has <code>exempted_ext_ips</code> set, then
+          there is an additional flow configured at priority 101.
+          The flow matches if source ip is an <code>exempted_ext_ip</code>
+          and the action is <code>next; </code>. This flow is used to
+          bypass the ct_dnat action for a packet originating from
+          <code>exempted_ext_ips</code>.
+        </p>
+
+        <p>
           A priority-0 logical flow with match <code>1</code> has actions
           <code>next;</code>.
         </p>
@@ -3301,6 +3334,23 @@  nd_ns {
           options, then the action would be <code>ip4/6.src=
           (<var>B</var>)</code>.
         </p>
+
+        <p>
+          If the NAT rule has <code>allowed_ext_ips</code> configured, then
+          there is an additional match <code>ip4.dst == <var>allowed_ext_ips
+          </var></code>. Similarly, for IPV6, match would be <code>ip6.dst ==
+          <var>allowed_ext_ips</var></code>.
+        </p>
+
+        <p>
+          If the NAT rule has <code>exempted_ext_ips</code> set, then
+          there is an additional flow configured at the priority + 1 of
+          corresponding NAT rule. The flow matches if destination ip
+          is an <code>exempted_ext_ip</code> and the action is <code>next;
+          </code>. This flow is used to bypass the ct_snat action for a packet
+          which is destinted to <code>exempted_ext_ips</code>.
+        </p>
+
         <p>
           A priority-0 logical flow with match <code>1</code> has actions
           <code>next;</code>.
@@ -3343,6 +3393,23 @@  nd_ns {
           <var>A</var> in the NAT rule.  This allows upstream MAC
           learning to point to the correct chassis.
         </p>
+
+        <p>
+          If the NAT rule has <code>allowed_ext_ips</code> configured, then
+          there is an additional match <code>ip4.dst == <var>allowed_ext_ips
+          </var></code>. Similarly, for IPV6, match would be <code>ip6.dst ==
+          <var>allowed_ext_ips</var></code>.
+        </p>
+
+        <p>
+          If the NAT rule has <code>exempted_ext_ips</code> set, then
+          there is an additional flow configured at the priority + 1 of
+          corresponding NAT rule. The flow matches if destination ip
+          is an <code>exempted_ext_ip</code> and the action is <code>next;
+          </code>. This flow is used to bypass the ct_snat action for a flow
+          which is destinted to <code>exempted_ext_ips</code>.
+        </p>
+
       </li>
 
       <li>
diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
index 3de7161..8cfeb3a 100644
--- a/northd/ovn-northd.c
+++ b/northd/ovn-northd.c
@@ -8282,6 +8282,76 @@  lrouter_nat_is_stateless(const struct nbrec_nat *nat)
     return false;
 }
 
+/* Handles the match criteria and actions in logical flow
+ * based on external ip based NAT rule filter.
+ *
+ * For ALLOWED_EXT_IPs, we will add an additional match criteria
+ * of comparing ip*.src/dst with the allowed external ip address set.
+ *
+ * For EXEMPTED_EXT_IPs, we will have an additional logical flow
+ * where we compare ip*.src/dst with the exempted external ip address set
+ * and action says "next" instead of ct*.
+ */
+static inline void
+lrouter_nat_add_ext_ip_match(struct ovn_datapath *od,
+                             struct hmap *lflows, struct ds *match,
+                             const struct nbrec_nat *nat,
+                             bool is_v6, bool is_src, ovs_be32 mask)
+{
+    struct nbrec_address_set *allowed_ext_ips = nat->allowed_ext_ips;
+    struct nbrec_address_set *exempted_ext_ips = nat->exempted_ext_ips;
+    bool is_gw_router = !od->l3dgw_port;
+
+    ovs_assert(allowed_ext_ips || exempted_ext_ips);
+
+    if (allowed_ext_ips) {
+        ds_put_format(match, " && ip%s.%s == $%s",
+                      is_v6 ? "6" : "4",
+                      is_src ? "src" : "dst",
+                      allowed_ext_ips->name);
+    } else if (exempted_ext_ips) {
+        struct ds match_exempt = DS_EMPTY_INITIALIZER;
+        enum ovn_stage stage = is_src ? S_ROUTER_IN_DNAT : S_ROUTER_OUT_SNAT;
+        uint16_t priority;
+
+        /* Priority of logical flows corresponding to exempted_ext_ips is
+         * +1 of the corresponding regulr NAT rule.
+         * For example, if we have following NAT rule and we associate
+         * exempted external ips to it:
+         * "ovn-nbctl lr-nat-add router dnat_and_snat 10.15.24.139 50.0.0.11"
+         *
+         * And now we associate exempted external ip address set to it.
+         * Now corresponding to above rule we will have following logical
+         * flows:
+         * lr_out_snat...priority=162, match=(..ip4.dst == $exempt_range),
+         *                             action=(next;)
+         * lr_out_snat...priority=161, match=(..), action=(ct_snat(....);)
+         *
+         */
+        if (is_src) {
+            /* S_ROUTER_IN_DNAT uses priority 100 */
+            priority = 100 + 1;
+        } else {
+            /* S_ROUTER_OUT_SNAT uses priority (mask + 1 + 128 + 1) */
+            priority = count_1bits(ntohl(mask)) + 2;
+
+            if (!is_gw_router)
+                priority += 128;
+        }
+
+        ds_clone(&match_exempt, match);
+        ds_put_format(&match_exempt, " && ip%s.%s == $%s",
+                      is_v6 ? "6" : "4",
+                      is_src ? "src" : "dst",
+                      exempted_ext_ips->name);
+
+        ovn_lflow_add_with_hint(lflows, od, stage, priority,
+                                ds_cstr(&match_exempt), "next;",
+                                &nat->header_);
+        ds_destroy(&match_exempt);
+    }
+}
+
 /* Builds the logical flow that replies to ARP requests for an 'ip_address'
  * owned by the router. The flow is inserted in table S_ROUTER_IN_IP_INPUT
  * with the given priority.
@@ -9343,6 +9413,18 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
             struct in6_addr ipv6, mask_v6, v6_exact = IN6ADDR_EXACT_INIT;
             bool is_v6 = false;
             bool stateless = lrouter_nat_is_stateless(nat);
+            struct nbrec_address_set *allowed_ext_ips =
+                                      nat->allowed_ext_ips;
+            struct nbrec_address_set *exempted_ext_ips =
+                                      nat->exempted_ext_ips;
+
+            if (allowed_ext_ips && exempted_ext_ips) {
+                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+                VLOG_WARN_RL(&rl, "NAT rule: "UUID_FMT" not applied, since"
+                             "both allowed and exempt external ips set",
+                             UUID_ARGS(&(nat->header_.uuid)));
+                continue;
+            }
 
             char *error = ip_parse_masked(nat->external_ip, &ip, &mask);
             if (error || mask != OVS_BE32_MAX) {
@@ -9488,6 +9570,11 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
                                   is_v6 ? "6" : "4",
                                   nat->external_ip);
                     ds_clear(&actions);
+                    if (allowed_ext_ips || exempted_ext_ips) {
+                        lrouter_nat_add_ext_ip_match(od, lflows, &match, nat,
+                                                     is_v6, true, mask);
+                    }
+
                     if (dnat_force_snat_ip) {
                         /* Indicate to the future tables that a DNAT has taken
                          * place and a force SNAT needs to be done in the
@@ -9531,6 +9618,10 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
                                       od->l3redirect_port->json_key);
                     }
                     ds_clear(&actions);
+                    if (allowed_ext_ips || exempted_ext_ips) {
+                        lrouter_nat_add_ext_ip_match(od, lflows, &match, nat,
+                                                     is_v6, true, mask);
+                    }
 
                     if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
                         ds_put_format(&actions, "ip%s.dst=%s; next;",
@@ -9642,6 +9733,11 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
                                   nat->logical_ip);
                     ds_clear(&actions);
 
+                    if (allowed_ext_ips || exempted_ext_ips) {
+                        lrouter_nat_add_ext_ip_match(od, lflows, &match, nat,
+                                                     is_v6, false, mask);
+                    }
+
                     if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
                         ds_put_format(&actions, "ip%s.src=%s; next;",
                                       is_v6 ? "6" : "4", nat->external_ip);
@@ -9681,6 +9777,12 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
                                       od->l3redirect_port->json_key);
                     }
                     ds_clear(&actions);
+
+                    if (allowed_ext_ips || exempted_ext_ips) {
+                        lrouter_nat_add_ext_ip_match(od, lflows, &match, nat,
+                                                     is_v6, false, mask);
+                    }
+
                     if (distributed) {
                         ds_put_format(&actions, "eth.src = "ETH_ADDR_FMT"; ",
                                       ETH_ADDR_ARGS(mac));
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 8344c7f..c6d9ae11 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -1140,6 +1140,216 @@  ovn-nbctl lr-nat-del R1 dnat_and_snat  172.16.1.1
 
 AT_CLEANUP
 
+AT_SETUP([ovn -- check allowed/disallowed external dnat, snat and dnat_and_snat rules])
+ovn_start
+
+# Logical network:
+# 2 LRs - CR and DR
+# CR ==> Centralized router
+# DR ==> Distributed router
+#
+# DR is connected to S1 and CR is connected to S2
+
+ovn-sbctl chassis-add gw1 geneve 127.0.0.1
+
+ovn-nbctl lr-add DR
+ovn-nbctl lrp-add DR DR-S1 02:ac:10:01:00:01 172.16.1.1/24
+
+cr_uuid=$(ovn-nbctl create Logical_Router name=CR)
+ovn-nbctl lrp-add CR CR-S2 02:ac:10:01:00:01 172.16.1.1/24
+
+ovn-nbctl ls-add S1
+ovn-nbctl lsp-add S1 S1-DR
+ovn-nbctl lsp-set-type S1-DR router
+ovn-nbctl lsp-set-addresses S1-DR router
+ovn-nbctl --wait=sb lsp-set-options S1-DR router-port=DR-S1
+
+ovn-nbctl ls-add S2
+ovn-nbctl lsp-add S2 S2-CR
+ovn-nbctl lsp-set-type S2-CR router
+ovn-nbctl lsp-set-addresses S2-CR router
+ovn-nbctl --wait=sb lsp-set-options S2-CR router-port=CR-S2
+
+ovn-nbctl lrp-set-gateway-chassis DR-S1 gw1
+
+uuid=`ovn-sbctl --columns=_uuid --bare find Port_Binding logical_port=cr-DR-S1`
+echo "CR-LRP UUID is: " $uuid
+
+ovn-nbctl set Logical_Router $cr_uuid options:chassis=gw1
+ovn-nbctl --wait=hv sync
+
+ovn-nbctl create Address_Set name=allowed_range addresses=\"1.1.1.1\"
+ovn-nbctl create Address_Set name=disallowed_range addresses=\"2.2.2.2\"
+
+# SNAT with ALLOWED_IPs
+ovn-nbctl lr-nat-add DR snat  172.16.1.1 50.0.0.11
+ovn-nbctl lr-nat-update-ext-ip DR snat 50.0.0.11 allowed_range
+
+ovn-nbctl lr-nat-add CR snat  172.16.1.1 50.0.0.11
+ovn-nbctl lr-nat-update-ext-ip CR snat 50.0.0.11 allowed_range
+
+OVS_WAIT_UNTIL([test 3 = `ovn-sbctl dump-flows DR | grep lr_out_snat | wc -l`])
+OVS_WAIT_UNTIL([test 3 = `ovn-sbctl dump-flows CR | grep lr_out_snat | wc -l`])
+
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_out_snat | grep "ip4.src == 50.0.0.11" | grep "ip4.dst == $allowed_range" | wc -l], [0], [1
+])
+AT_CHECK([ovn-sbctl dump-flows CR | grep lr_out_snat | grep "ip4.src == 50.0.0.11" | grep "ip4.dst == $allowed_range" | wc -l], [0], [1
+])
+
+# SNAT with DISALLOWED_IPs
+ovn-nbctl lr-nat-del DR snat  50.0.0.11
+ovn-nbctl lr-nat-del CR snat  50.0.0.11
+
+ovn-nbctl lr-nat-add DR snat  172.16.1.1 50.0.0.11
+ovn-nbctl lr-nat-add CR snat  172.16.1.1 50.0.0.11
+
+ovn-nbctl --is-exempted lr-nat-update-ext-ip DR snat 50.0.0.11 disallowed_range
+ovn-nbctl --is-exempted lr-nat-update-ext-ip CR snat 50.0.0.11 disallowed_range
+
+ovn-sbctl dump-flows DR
+ovn-sbctl dump-flows CR
+
+OVS_WAIT_UNTIL([test 4 = `ovn-sbctl dump-flows DR | grep lr_out_snat | \
+wc -l`])
+OVS_WAIT_UNTIL([test 4 = `ovn-sbctl dump-flows CR | grep lr_out_snat | \
+wc -l`])
+
+ovn-nbctl show DR
+ovn-sbctl dump-flows DR
+
+ovn-nbctl show CR
+ovn-sbctl dump-flows CR
+
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_out_snat | grep "ip4.src == 50.0.0.11" | grep "ip4.dst == $disallowed_range" | grep "priority=162" | wc -l], [0], [1
+])
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_out_snat | grep "ip4.src == 50.0.0.11" | grep "priority=161" | wc -l], [0], [1
+])
+
+AT_CHECK([ovn-sbctl dump-flows CR | grep lr_out_snat | grep "ip4.src == 50.0.0.11" | grep "ip4.dst == $disallowed_range" | grep "priority=34" | wc -l], [0], [1
+])
+AT_CHECK([ovn-sbctl dump-flows CR | grep lr_out_snat | grep "ip4.src == 50.0.0.11" | grep "priority=33" | wc -l], [0], [1
+])
+
+# Stateful FIP with ALLOWED_IPs
+ovn-nbctl lr-nat-del DR snat  50.0.0.11
+ovn-nbctl lr-nat-del CR snat  50.0.0.11
+
+ovn-nbctl lr-nat-add DR dnat_and_snat  172.16.1.2 50.0.0.11
+ovn-nbctl lr-nat-add CR dnat_and_snat  172.16.1.2 50.0.0.11
+
+ovn-nbctl lr-nat-update-ext-ip DR dnat_and_snat 172.16.1.2 allowed_range
+ovn-nbctl lr-nat-update-ext-ip CR dnat_and_snat 172.16.1.2 allowed_range
+
+ovn-nbctl show DR
+ovn-sbctl dump-flows DR
+ovn-nbctl show CR
+ovn-sbctl dump-flows CR
+
+OVS_WAIT_UNTIL([test 3 = `ovn-sbctl dump-flows DR | grep lr_out_snat | \
+wc -l`])
+OVS_WAIT_UNTIL([test 3 = `ovn-sbctl dump-flows CR | grep lr_out_snat | \
+wc -l`])
+
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_out_snat | grep "ip4.src == 50.0.0.11" | grep "ip4.dst == $allowed_range" | wc -l], [0], [1
+])
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_dnat | grep "ip4.dst == 172.16.1.2" | grep "ip4.src == $allowed_range" | wc -l], [0], [1
+])
+AT_CHECK([ovn-sbctl dump-flows CR | grep lr_out_snat | grep "ip4.src == 50.0.0.11" | grep "ip4.dst == $allowed_range" | wc -l], [0], [1
+])
+AT_CHECK([ovn-sbctl dump-flows CR | grep lr_in_dnat | grep "ip4.dst == 172.16.1.2" | grep "ip4.src == $allowed_range" | wc -l], [0], [1
+])
+
+# Stateful FIP with DISALLOWED_IPs
+ovn-nbctl lr-nat-del DR dnat_and_snat  172.16.1.2
+ovn-nbctl lr-nat-del CR dnat_and_snat  172.16.1.2
+
+ovn-nbctl lr-nat-add DR dnat_and_snat  172.16.1.2 50.0.0.11
+ovn-nbctl lr-nat-add CR dnat_and_snat  172.16.1.2 50.0.0.11
+
+ovn-nbctl --is-exempted lr-nat-update-ext-ip DR dnat_and_snat 172.16.1.2 disallowed_range
+ovn-nbctl --is-exempted lr-nat-update-ext-ip CR dnat_and_snat 172.16.1.2 disallowed_range
+
+ovn-nbctl show DR
+ovn-sbctl dump-flows DR
+ovn-nbctl show CR
+ovn-sbctl dump-flows CR
+
+OVS_WAIT_UNTIL([test 4 = `ovn-sbctl dump-flows DR | grep lr_out_snat | \
+wc -l`])
+OVS_WAIT_UNTIL([test 4 = `ovn-sbctl dump-flows CR | grep lr_out_snat | \
+wc -l`])
+
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_out_snat | grep "ip4.src == 50.0.0.11" | grep "ip4.dst == $disallowed_range" | grep "priority=162" | wc -l], [0], [1
+])
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_dnat | grep "ip4.dst == 172.16.1.2" | grep "ip4.src == $disallowed_range" | grep "priority=101" | wc -l], [0], [1
+])
+
+AT_CHECK([ovn-sbctl dump-flows CR | grep lr_out_snat | grep "ip4.src == 50.0.0.11" | grep "ip4.dst == $disallowed_range" | grep "priority=34" | wc -l], [0], [1
+])
+AT_CHECK([ovn-sbctl dump-flows CR | grep lr_in_dnat | grep "ip4.dst == 172.16.1.2" | grep "ip4.src == $disallowed_range" | grep "priority=101" | wc -l], [0], [1
+])
+
+# Stateless FIP with DISALLOWED_IPs
+ovn-nbctl lr-nat-del DR dnat_and_snat  172.16.1.2
+ovn-nbctl lr-nat-del CR dnat_and_snat  172.16.1.2
+
+ovn-nbctl --stateless lr-nat-add DR dnat_and_snat  172.16.1.2 50.0.0.11
+ovn-nbctl --stateless lr-nat-add CR dnat_and_snat  172.16.1.2 50.0.0.11
+
+ovn-nbctl lr-nat-update-ext-ip DR dnat_and_snat 172.16.1.2 allowed_range
+ovn-nbctl lr-nat-update-ext-ip CR dnat_and_snat 172.16.1.2 allowed_range
+
+ovn-nbctl show DR
+ovn-sbctl dump-flows DR
+
+ovn-nbctl show CR
+ovn-sbctl dump-flows CR
+
+OVS_WAIT_UNTIL([test 3 = `ovn-sbctl dump-flows DR | grep lr_out_snat | \
+wc -l`])
+OVS_WAIT_UNTIL([test 3 = `ovn-sbctl dump-flows CR | grep lr_out_snat | \
+wc -l`])
+
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_out_snat | grep "ip4.src == 50.0.0.11" | grep "ip4.dst == $allowed_range" | wc -l], [0], [1
+])
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_dnat | grep "ip4.dst == 172.16.1.2" | grep "ip4.src == $allowed_range" | wc -l], [0], [1
+])
+AT_CHECK([ovn-sbctl dump-flows CR | grep lr_out_snat | grep "ip4.src == 50.0.0.11" | grep "ip4.dst == $allowed_range" | wc -l], [0], [1
+])
+AT_CHECK([ovn-sbctl dump-flows CR | grep lr_in_dnat | grep "ip4.dst == 172.16.1.2" | grep "ip4.src == $allowed_range" | wc -l], [0], [1
+])
+
+# Stateful FIP with DISALLOWED_IPs
+ovn-nbctl lr-nat-del DR dnat_and_snat  172.16.1.2
+ovn-nbctl lr-nat-del CR dnat_and_snat  172.16.1.2
+
+ovn-nbctl --stateless lr-nat-add DR dnat_and_snat  172.16.1.2 50.0.0.11
+ovn-nbctl --stateless lr-nat-add CR dnat_and_snat  172.16.1.2 50.0.0.11
+
+ovn-nbctl --is-exempted lr-nat-update-ext-ip DR dnat_and_snat 172.16.1.2 disallowed_range
+ovn-nbctl --is-exempted lr-nat-update-ext-ip CR dnat_and_snat 172.16.1.2 disallowed_range
+
+ovn-nbctl show DR
+ovn-sbctl dump-flows DR
+ovn-nbctl show CR
+ovn-sbctl dump-flows CR
+
+OVS_WAIT_UNTIL([test 4 = `ovn-sbctl dump-flows DR | grep lr_out_snat | \
+wc -l`])
+OVS_WAIT_UNTIL([test 4 = `ovn-sbctl dump-flows CR | grep lr_out_snat | \
+wc -l`])
+
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_out_snat | grep "ip4.src == 50.0.0.11" | grep "ip4.dst == $disallowed_range" | grep "priority=162" | wc -l], [0], [1
+])
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_dnat | grep "ip4.dst == 172.16.1.2" | grep "ip4.src == $disallowed_range" | grep "priority=101" | wc -l], [0], [1
+])
+AT_CHECK([ovn-sbctl dump-flows CR | grep lr_out_snat | grep "ip4.src == 50.0.0.11" | grep "ip4.dst == $disallowed_range" | grep "priority=34" | wc -l], [0], [1
+])
+AT_CHECK([ovn-sbctl dump-flows CR | grep lr_in_dnat | grep "ip4.dst == 172.16.1.2" | grep "ip4.src == $disallowed_range" | grep "priority=101" | wc -l], [0], [1
+])
+
+AT_CLEANUP
+
 AT_SETUP([ovn -- check Load balancer health check and Service Monitor sync])
 AT_SKIP_IF([test $HAVE_PYTHON = no])
 ovn_start