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