On distributed routers, the flows that track traffic of an SNAT network
also matched traffic of a stateless dnat_and_snat inside that network,
so it was committed to the SNAT CT zone.  Stateless NAT must bypass
conntrack.

Add higher priority flows for stateless dnat_and_snat that just skip
the tracking, reusing the same helper as stateful NAT.

Fixes: 40136a2f2c84 ("northd: Fix direct access to SNAT network.")
Fixes: 79f4cfd9c5d4 ("northd: Avoid committing DNAT traffic to SNAT zone.")
Signed-off-by: Alexandra Rukomoinikova <[email protected]>
---
 Documentation/ref/ovn-logical-flows.7.rst |  34 +++++-
 northd/northd.c                           | 128 +++++++++++++---------
 tests/ovn-northd.at                       |  68 ++++++++++++
 tests/system-ovn.at                       |  16 +++
 4 files changed, 191 insertions(+), 55 deletions(-)

diff --git a/Documentation/ref/ovn-logical-flows.7.rst 
b/Documentation/ref/ovn-logical-flows.7.rst
index 44fd560bf..194074642 100644
--- a/Documentation/ref/ovn-logical-flows.7.rst
+++ b/Documentation/ref/ovn-logical-flows.7.rst
@@ -3784,7 +3784,19 @@ Egress Table 2: Post UNDNAT
 
 - A priority-70 logical flow is added that initiates CT state for traffic that
   is configured to be SNATed on Distributed routers. This allows the next 
table,
-  ``lr_out_snat``, to effectively match on various CT states.
+  ``lr_out_snat``, to effectively match on various CT states. The flow matches
+  on ``ip && ip4.src == A && outport == GW && (!ct.trk || !ct.rpl)`` with an
+  action ``ct_next(snat);``, where *A* is the logical IP or network of the NAT
+  rule and *GW* is the logical router gateway port.
+
+  If the NAT rule is of type dnat_and_snat, the flow is added with priority 75
+  instead, so that the traffic of its logical IP is not tracked by a SNAT rule
+  covering the same network. The action is ``ct_next(dnat);``. If the
+  dnat_and_snat rule has ``stateless=true`` in the options, the flow does not
+  match on CT state and its action is ``next;``, so that the traffic bypasses
+  conntrack.
+
+  These flows are not added if ``options:ct-commit-all`` is set to ``true``.
 
 - A priority-50 logical flow is added that commits any untracked flows from the
   previous table :ref:`UNDNAT <lr-out-1>` for Gateway routers.  This flow
@@ -3923,7 +3935,17 @@ based on the configuration in the OVN Northbound 
database.
   ``ip4.src == A && outport == GW``, this flow matches on ``ip4.dst == A &&
   inport == GW``. A CT state is initiated for this traffic so that the 
following
   table, ``lr_out_post_snat``, can identify whether the traffic flow was
-  initiated from the internal or external network.
+  initiated from the internal or external network. The flow has priority *P*,
+  an additional match ``(!ct.trk || !ct.rpl)`` and an action ``ct_snat;``.
+
+  If the NAT rule is of type dnat_and_snat, the flow is added with priority
+  ``P + 5`` and an action ``ct_dnat;``, so that the traffic of its logical IP
+  is tracked in the DNAT CT zone instead of the SNAT CT zone of a SNAT rule
+  covering the same network. If the dnat_and_snat rule has ``stateless=true``
+  in the options, the flow does not match on CT state and its action is
+  ``next;``, so that the traffic bypasses conntrack.
+
+  This flow is not added if ``options:ct-commit-all`` is set to ``true``.
 
 - If the ``options:ct-commit-all`` is set to ``true`` the following two flows
   are configured matching on ``ip && (!ct.trk || !ct.rpl) && flags.unsnat_new 
==
@@ -3945,8 +3967,12 @@ Packets reaching this table are processed according to 
the flows below:
   routers, and was initiated from an external network (i.e. it matches
   ``ct.new``), is committed to the SNAT CT zone. This ensures that replies
   returning from the SNATed network do not have their source address 
translated.
-  For details about match rules and priority see section :ref:`SNAT on
-  Distributed Routers <lr-out-3>`.
+  The action is ``ct_commit_to_zone(snat);``. If the NAT rule is of type
+  dnat_and_snat, the traffic is committed to the DNAT CT zone with an action
+  ``ct_commit_to_zone(dnat);`` instead. Traffic of a dnat_and_snat rule that
+  has ``stateless=true`` in the options is not committed. For details about
+  match rules and priority see section :ref:`SNAT on Distributed Routers
+  <lr-out-3>`.
 
 - A priority-0 logical flow that matches all packets not already handled (match
   ``1``) and action ``next;``.
diff --git a/northd/northd.c b/northd/northd.c
index f37040b57..4e4849f8c 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -18373,6 +18373,69 @@ build_lrouter_out_snat_match(struct lflow_table 
*lflows,
     }
 }
 
+static void
+build_lrouter_out_snat_track_flows(struct lflow_table *lflows,
+                                   const struct ovn_datapath *od,
+                                   const struct ovn_nat *nat_entry,
+                                   struct ds *match, struct ds *actions,
+                                   bool distributed_nat, int cidr_bits,
+                                   bool is_v6, struct ovn_port *l3dgw_port,
+                                   struct lflow_ref *lflow_ref,
+                                   bool commit_all,
+                                   const struct chassis_features *features,
+                                   bool stateless)
+{
+    if (!features->ct_commit_to_zone || !features->ct_next_zone ||
+        od->is_gw_router || commit_all || lrouter_use_common_zone(od)) {
+        return;
+    }
+
+    const struct nbrec_nat *nat = nat_entry->nb;
+    uint16_t priority = lrouter_nat_get_priority(od, nat, false, cidr_bits);
+    const char *zone = nat_entry->type == SNAT ? "snat" : "dnat";
+    uint16_t prio_offset = nat_entry->type == SNAT ? 0 : 5;
+
+    build_lrouter_out_snat_match(lflows, od, nat, match, distributed_nat,
+                                 cidr_bits, is_v6, l3dgw_port, lflow_ref,
+                                 false);
+    ds_clear(actions);
+    if (stateless) {
+        ds_put_cstr(actions, "next;");
+    } else {
+        ds_put_cstr(match, " && (!ct.trk || !ct.rpl)");
+        ds_put_format(actions, "ct_next(%s);", zone);
+    }
+    ovn_lflow_add(lflows, od, S_ROUTER_OUT_POST_UNDNAT, 70 + prio_offset,
+                  ds_cstr(match), ds_cstr(actions), lflow_ref,
+                  WITH_HINT(&nat->header_));
+
+    build_lrouter_out_snat_match(lflows, od, nat, match, distributed_nat,
+                                 cidr_bits, is_v6, l3dgw_port, lflow_ref,
+                                 true);
+    if (stateless) {
+        ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, priority + prio_offset,
+                      ds_cstr(match), "next;", lflow_ref,
+                      WITH_HINT(&nat->header_));
+        return;
+    }
+
+    size_t match_any_state_len = match->length;
+    ds_put_cstr(match, " && (!ct.trk || !ct.rpl)");
+    ds_clear(actions);
+    ds_put_format(actions, "ct_%s;", zone);
+    ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, priority + prio_offset,
+                  ds_cstr(match), ds_cstr(actions), lflow_ref,
+                  WITH_HINT(&nat->header_));
+
+    ds_truncate(match, match_any_state_len);
+    ds_put_cstr(match, " && ct.new");
+    ds_clear(actions);
+    ds_put_format(actions, "ct_commit_to_zone(%s);", zone);
+    ovn_lflow_add(lflows, od, S_ROUTER_OUT_POST_SNAT, priority + prio_offset,
+                  ds_cstr(match), ds_cstr(actions), lflow_ref,
+                  WITH_HINT(&nat->header_));
+}
+
 static void
 build_lrouter_out_snat_stateless_flow(struct lflow_table *lflows,
                                       const struct ovn_datapath *od,
@@ -18381,7 +18444,9 @@ build_lrouter_out_snat_stateless_flow(struct 
lflow_table *lflows,
                                       bool distributed_nat,
                                       struct eth_addr mac, int cidr_bits,
                                       bool is_v6, struct ovn_port *l3dgw_port,
-                                      struct lflow_ref *lflow_ref)
+                                      struct lflow_ref *lflow_ref,
+                                      bool commit_all,
+                                      const struct chassis_features *features)
 {
     if (!(nat_entry->type == SNAT || nat_entry->type == DNAT_AND_SNAT)) {
         return;
@@ -18405,6 +18470,11 @@ build_lrouter_out_snat_stateless_flow(struct 
lflow_table *lflows,
 
     ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, priority, ds_cstr(match),
                   ds_cstr(actions), lflow_ref, WITH_HINT(&nat->header_));
+
+    build_lrouter_out_snat_track_flows(lflows, od, nat_entry, match, actions,
+                                       distributed_nat, cidr_bits, is_v6,
+                                       l3dgw_port, lflow_ref, commit_all,
+                                       features, true);
 }
 
 static void
@@ -18501,55 +18571,10 @@ build_lrouter_out_snat_flow(struct lflow_table 
*lflows,
     ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, priority, ds_cstr(match),
                   ds_cstr(actions), lflow_ref, WITH_HINT(&nat->header_));
 
-    /* For the SNAT networks, we need to make sure that connections are
-     * properly tracked so we can decide whether to perform SNAT on traffic
-     * exiting the network. */
-    if (features->ct_commit_to_zone && features->ct_next_zone &&
-        !od->is_gw_router && !commit_all) {
-        const char *zone;
-        uint16_t prio_offset;
-        if (nat_entry->type == SNAT) {
-            /* Traffic to/from hosts behind SNAT is tracked through the
-             * SNAT CT zone.*/
-            zone = "snat";
-            prio_offset = 0;
-        } else {
-            /* Traffic to/from hosts behind DNAT_AND_SNAT is tracked through
-             * the DNAT CT zone with slightly higher priority flows.*/
-            zone = "dnat";
-            prio_offset = 5;
-        }
-
-        /* For traffic that comes from the SNAT network, initiate CT state
-         * from the correct zone, before entering S_ROUTER_OUT_SNAT to allow
-         * matching on various CT states.*/
-        ds_clear(actions);
-        ds_put_format(actions, "ct_next(%s);", zone);
-        ovn_lflow_add(lflows, od, S_ROUTER_OUT_POST_UNDNAT, 70 + prio_offset,
-                      ds_cstr(match), ds_cstr(actions),
-                      lflow_ref);
-
-        build_lrouter_out_snat_match(lflows, od, nat, match,
-                                     distributed_nat, cidr_bits, is_v6,
-                                     l3dgw_port, lflow_ref, true);
-        size_t match_any_state_len = match->length;
-        ds_put_cstr(match, " && (!ct.trk || !ct.rpl)");
-        ds_clear(actions);
-        ds_put_format(actions, "ct_%s;", zone);
-        ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, priority + prio_offset,
-                      ds_cstr(match), ds_cstr(actions),
-                      lflow_ref);
-
-        /* New traffic that goes into the SNAT network is committed to the
-         * correct CT zone to avoid SNAT-ing replies.*/
-        ds_truncate(match, match_any_state_len);
-        ds_put_cstr(match, " && ct.new");
-        ds_clear(actions);
-        ds_put_format(actions, "ct_commit_to_zone(%s);", zone);
-        ovn_lflow_add(lflows, od, S_ROUTER_OUT_POST_SNAT,
-                      priority + prio_offset, ds_cstr(match), ds_cstr(actions),
-                      lflow_ref);
-    }
+    build_lrouter_out_snat_track_flows(lflows, od, nat_entry, match, actions,
+                                       distributed_nat, cidr_bits, is_v6,
+                                       l3dgw_port, lflow_ref, commit_all,
+                                       features, false);
 }
 
 static void
@@ -19072,7 +19097,8 @@ build_lrouter_nat_defrag_and_lb(
                                                   nat_entry->is_distributed,
                                                   nat_entry->mac, cidr_bits,
                                                   is_v6, nat_entry->l3dgw_port,
-                                                  lflow_ref);
+                                                  lflow_ref, commit_all,
+                                                  features);
         } else if (lrouter_use_common_zone(od)) {
             build_lrouter_out_snat_in_czone_flow(lflows, od, nat_entry, match,
                                                  actions,
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index f8c144918..c41617fd4 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -1431,6 +1431,7 @@ AT_CHECK([grep -e "lr_out_snat" drflows5 | 
ovn_strip_lflows], [0], [dnl
   table=??(lr_out_snat        ), priority=0    , match=(1), action=(next;)
   table=??(lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
   table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 
50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && ip4.dst 
== $allowed_range), action=(ip4.src=172.16.1.2; next;)
+  table=??(lr_out_snat        ), priority=166  , match=(ip && ip4.dst == 
50.0.0.11 && inport == "DR-S1" && is_chassis_resident("cr-DR-S1") && ip4.src == 
$allowed_range), action=(next;)
 ])
 
 AT_CHECK([grep -e "lr_out_snat" crflows5 | ovn_strip_lflows], [0], [dnl
@@ -1461,6 +1462,7 @@ AT_CHECK([grep -e "lr_out_snat" drflows6 | 
ovn_strip_lflows], [0], [dnl
   table=??(lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
   table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 
50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1")), 
action=(ip4.src=172.16.1.2; next;)
   table=??(lr_out_snat        ), priority=163  , match=(ip && ip4.src == 
50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && ip4.dst 
== $disallowed_range), action=(next;)
+  table=??(lr_out_snat        ), priority=166  , match=(ip && ip4.dst == 
50.0.0.11 && inport == "DR-S1" && is_chassis_resident("cr-DR-S1")), 
action=(next;)
 ])
 
 AT_CHECK([grep -e "lr_out_snat" crflows6 | ovn_strip_lflows], [0], [dnl
@@ -1474,6 +1476,72 @@ OVN_CLEANUP_NORTHD
 AT_CLEANUP
 ])
 
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([Stateless dnat_and_snat inside SNAT network on distributed router])
+ovn_start
+
+check ovn-sbctl chassis-add gw1 geneve 127.0.0.1 \
+  -- set chassis gw1 other_config:ct-commit-to-zone="true" \
+  -- set chassis gw1 other_config:ct-next-zone="true"
+
+check ovn-nbctl lr-add DR
+check ovn-nbctl lrp-add DR DR-public 02:ac:10:01:00:01 172.16.1.1/24
+check ovn-nbctl lrp-add DR DR-S1 02:ac:10:01:00:02 50.0.0.1/24
+check ovn-nbctl lrp-set-gateway-chassis DR-public gw1
+
+check ovn-nbctl lr-nat-add DR snat 172.16.1.10 50.0.0.0/24
+check ovn-nbctl --stateless lr-nat-add DR dnat_and_snat 172.16.1.2 50.0.0.11
+check ovn-nbctl --wait=sb sync
+
+dnl Traffic to/from the stateless NAT logical IP must not be tracked by
+dnl the flows of the SNAT covering its network.
+ovn-sbctl dump-flows DR > drflows
+AT_CAPTURE_FILE([drflows])
+
+AT_CHECK([grep -e "lr_out_post_undnat" drflows | ovn_strip_lflows], [0], [dnl
+  table=??(lr_out_post_undnat ), priority=0    , match=(1), action=(next;)
+  table=??(lr_out_post_undnat ), priority=70   , match=(ip && ip4.src == 
50.0.0.0/24 && outport == "DR-public" && is_chassis_resident("cr-DR-public") && 
(!ct.trk || !ct.rpl)), action=(ct_next(snat);)
+  table=??(lr_out_post_undnat ), priority=75   , match=(ip && ip4.src == 
50.0.0.11 && outport == "DR-public" && is_chassis_resident("cr-DR-public")), 
action=(next;)
+])
+
+AT_CHECK([grep -e "lr_out_snat" drflows | ovn_strip_lflows], [0], [dnl
+  table=??(lr_out_snat        ), priority=0    , match=(1), action=(next;)
+  table=??(lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
+  table=??(lr_out_snat        ), priority=153  , match=(ip && ip4.dst == 
50.0.0.0/24 && inport == "DR-public" && is_chassis_resident("cr-DR-public") && 
(!ct.trk || !ct.rpl)), action=(ct_snat;)
+  table=??(lr_out_snat        ), priority=153  , match=(ip && ip4.src == 
50.0.0.0/24 && outport == "DR-public" && is_chassis_resident("cr-DR-public") && 
(!ct.trk || !ct.rpl)), action=(ct_snat(172.16.1.10);)
+  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 
50.0.0.11 && outport == "DR-public" && is_chassis_resident("cr-DR-public")), 
action=(ip4.src=172.16.1.2; next;)
+  table=??(lr_out_snat        ), priority=166  , match=(ip && ip4.dst == 
50.0.0.11 && inport == "DR-public" && is_chassis_resident("cr-DR-public")), 
action=(next;)
+])
+
+AT_CHECK([grep -e "lr_out_post_snat" drflows | ovn_strip_lflows], [0], [dnl
+  table=??(lr_out_post_snat   ), priority=0    , match=(1), action=(next;)
+  table=??(lr_out_post_snat   ), priority=153  , match=(ip && ip4.dst == 
50.0.0.0/24 && inport == "DR-public" && is_chassis_resident("cr-DR-public") && 
ct.new), action=(ct_commit_to_zone(snat);)
+])
+
+dnl With ct-commit-all the SNAT network is not tracked by these flows.
+check ovn-nbctl --wait=sb set logical_router DR options:ct-commit-all="true"
+ovn-sbctl dump-flows DR > drflows2
+AT_CAPTURE_FILE([drflows2])
+
+AT_CHECK([grep -e "lr_out_post_undnat" drflows2 | ovn_strip_lflows], [0], [dnl
+  table=??(lr_out_post_undnat ), priority=0    , match=(1), action=(next;)
+  table=??(lr_out_post_undnat ), priority=10   , match=(ip && (!ct.trk || 
!ct.rpl) && flags.unsnat_not_tracked == 1 && outport == "DR-public" && 
is_chassis_resident("cr-DR-public")), action=(ct_next(snat);)
+  table=??(lr_out_post_undnat ), priority=10   , match=(ip && flags.unsnat_new 
== 1 && outport == "DR-public" && is_chassis_resident("cr-DR-public")), 
action=(next;)
+])
+
+AT_CHECK([grep -e "lr_out_snat" drflows2 | ovn_strip_lflows], [0], [dnl
+  table=??(lr_out_snat        ), priority=0    , match=(1), action=(next;)
+  table=??(lr_out_snat        ), priority=10   , match=(ip && (!ct.trk || 
!ct.rpl) && flags.unsnat_new == 1 && outport == "DR-public" && 
is_chassis_resident("cr-DR-public")), action=(ct_commit_to_zone(snat);)
+  table=??(lr_out_snat        ), priority=10   , match=(ip && ct.new && 
outport == "DR-public" && is_chassis_resident("cr-DR-public")), 
action=(ct_commit_to_zone(snat);)
+  table=??(lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
+  table=??(lr_out_snat        ), priority=153  , match=(ip && ip4.src == 
50.0.0.0/24 && outport == "DR-public" && is_chassis_resident("cr-DR-public") && 
(!ct.trk || !ct.rpl)), action=(ct_snat(172.16.1.10);)
+  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 
50.0.0.11 && outport == "DR-public" && is_chassis_resident("cr-DR-public")), 
action=(ip4.src=172.16.1.2; next;)
+])
+
+OVN_CLEANUP_NORTHD
+AT_CLEANUP
+])
+
 OVN_FOR_EACH_NORTHD_NO_HV([
 AT_SETUP([check Load balancer health check and Service Monitor sync])
 ovn_start
diff --git a/tests/system-ovn.at b/tests/system-ovn.at
index 13e62bf9b..d9c33b03a 100644
--- a/tests/system-ovn.at
+++ b/tests/system-ovn.at
@@ -3677,6 +3677,22 @@ NS_CHECK_CONNECTIVITY([alice1], [foo1], [192.168.1.2])
 # North-South Direct (Bypassing SNAT): 'alice1' reaches 'bar1' using 
192.168.2.2
 NS_CHECK_CONNECTIVITY([alice1], [bar1], [192.168.2.2])
 
+AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+
+# Stateless DNAT_AND_SNAT inside the SNAT network: its traffic must not be
+# tracked by the flows of the SNAT covering the network.
+check ovn-nbctl --wait=hv --stateless lr-nat-add R1 dnat_and_snat 172.16.1.5 
192.168.2.2
+
+# North-South stateless DNAT: 'alice1' reaches 'bar1' on 172.16.1.5.
+NS_CHECK_CONNECTIVITY([alice1], [bar1], [172.16.1.5])
+
+# South-North stateless SNAT: 'bar1' reaches 'alice1' from 172.16.1.5.
+NS_CHECK_CONNECTIVITY([bar1], [alice1], [172.16.1.2])
+
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(192.168.2.2) | \
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+])
+
 # Try to ping external network
 NETNS_START_TCPDUMP([ext-net], [-n -c 3 -i ext-veth dst 172.16.1.3 and icmp], 
[ext-net])
 AT_CHECK([ovn-nbctl lr-nat-del R1 snat])
-- 
2.48.1

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to