Adds the specified NAT to router.
@@ -1224,6 +1224,18 @@
external_port_range
is 1-65535
.
+
+ The --match
allows to specify the extra match condition.
+ The extra match is for more fine-grained control over the NAT rule.
+
+
+
+ The --priority
option allows to specify order of NAT
+ rule evaluation. Priority must be between 0
and
+ 32767
, inclusive and can be only specified together
+ with --match
.
+
+
When type is dnat
, the externally
visible IP address external_ip is DNATted to the
diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
index 618f3a18b..da31c45f1 100644
--- a/utilities/ovn-nbctl.c
+++ b/utilities/ovn-nbctl.c
@@ -4981,6 +4981,8 @@ nbctl_pre_lr_nat_add(struct ctl_context *ctx)
ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_logical_port);
ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_external_mac);
ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_gateway_port);
+ ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_match);
+ ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_priority);
ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_options);
ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_port_col_name);
@@ -5177,6 +5179,21 @@ nbctl_lr_nat_add(struct ctl_context *ctx)
}
}
+ int64_t priority = 0;
+ const char *match = shash_find_data(&ctx->options, "--match");
+ const char *priority_opt = shash_find_data(&ctx->options, "--priority");
+
+ if (!match && priority_opt) {
+ ctl_error(ctx, "priority can be set only with --match option.");
+ goto cleanup;
+ } else if (match && priority_opt) {
+ error = parse_priority(priority_opt, &priority);
+ if (error) {
+ ctx->error = error;
+ goto cleanup;
+ }
+ }
+
for (size_t i = 0; i < lr->n_nat; i++) {
const struct nbrec_nat *nat = lr->nat[i];
@@ -5207,6 +5224,10 @@ nbctl_lr_nat_add(struct ctl_context *ctx)
nbrec_nat_verify_external_mac(nat);
nbrec_nat_set_logical_port(nat, logical_port);
nbrec_nat_set_external_mac(nat, external_mac);
+ if (match) {
+ nbrec_nat_set_match(nat, match);
+ nbrec_nat_set_priority(nat, priority);
+ }
should_return = true;
} else {
ctl_error(ctx, "%s, %s: a NAT with this "
@@ -5215,12 +5236,19 @@ nbctl_lr_nat_add(struct ctl_context *ctx)
new_logical_ip);
should_return = true;
}
- } else {
- ctl_error(ctx, "a NAT with this type (%s), %s (%s) "
- "already exists",
- nat_type,
- is_snat ? "logical_ip" : "external_ip",
- is_snat ? new_logical_ip : new_external_ip);
+ } else if (!match || !strcmp(nat->match, match)) {
+ if (match) {
+ ctl_error(ctx, "a NAT with this type (%s), %s (%s) "
+ "and match \"%s\" already exists", nat_type,
+ is_snat ? "logical_ip" : "external_ip",
+ is_snat ? new_logical_ip : new_external_ip,
+ match);
+ } else {
+ ctl_error(ctx, "a NAT with this type (%s), %s (%s) "
+ "already exists", nat_type,
+ is_snat ? "logical_ip" : "external_ip",
+ is_snat ? new_logical_ip : new_external_ip);
+ }
should_return = true;
}
}
@@ -5273,6 +5301,11 @@ nbctl_lr_nat_add(struct ctl_context *ctx)
}
nbrec_nat_set_options(nat, &nat_options);
+ if (match) {
+ nbrec_nat_set_match(nat, match);
+ nbrec_nat_set_priority(nat, priority);
+ }
+
smap_destroy(&nat_options);
/* Insert the NAT into the logical router. */
@@ -5293,30 +5326,79 @@ nbctl_pre_lr_nat_del(struct ctl_context *ctx)
ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_logical_ip);
ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_external_ip);
ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_gateway_port);
+ ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_match);
ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_port_col_name);
}
+static size_t
+lr_nat_del_matching(const struct nbrec_logical_router *lr, const char *type,
+ const char *ip,
+ const struct nbrec_logical_router_port *dgw_port,
+ const char *match, bool is_snat)
+{
+ size_t n_deleted = 0;
+
+ for (size_t i = 0; i < lr->n_nat; i++) {
+ struct nbrec_nat *nat = lr->nat[i];
+ char *old_ip = normalize_prefix_str(is_snat
+ ? nat->logical_ip
+ : nat->external_ip);
+ if (!old_ip) {
+ continue;
+ }
+
+ bool delete = true;
+
+ if (type && strcmp(type, nat->type)) {
+ delete = false;
+ }
+
+ if (ip && strcmp(ip, old_ip)) {
+ delete = false;
+ }
+
+ if (dgw_port && nat->gateway_port != dgw_port) {
+ delete = false;
+ }
+
+ if (match && strcmp(match, nat->match)) {
+ delete = false;
+ }
+
+ if (delete) {
+ nbrec_logical_router_update_nat_delvalue(lr, nat);
+ n_deleted++;
+ }
+
+ free(old_ip);
+ }
+
+ return n_deleted;
+}
+
static void
nbctl_lr_nat_del(struct ctl_context *ctx)
{
const struct nbrec_logical_router *lr = NULL;
bool must_exist = !shash_find(&ctx->options, "--if-exists");
+ const char *match = shash_find_data(&ctx->options, "--match");
char *error = lr_by_name_or_uuid(ctx, ctx->argv[1], true, &lr);
if (error) {
ctx->error = error;
return;
}
- if (ctx->argc == 2) {
- /* If type, external_ip and logical_ip are not specified, delete
- * all NATs. */
+ if (ctx->argc == 2 && !match) {
+ /* If type, external_ip, logical_ip and match are not specified,
+ * delete all NATs. */
nbrec_logical_router_verify_nat(lr);
nbrec_logical_router_set_nat(lr, NULL, 0);
return;
}
const char *nat_type = ctx->argv[2];
+ int is_snat = !strcmp("snat", nat_type);
if (strcmp(nat_type, "dnat") && strcmp(nat_type, "snat")
&& strcmp(nat_type, "dnat_and_snat")) {
ctl_error(ctx, "%s: type must be one of \"dnat\", \"snat\" and "
@@ -5326,16 +5408,11 @@ nbctl_lr_nat_del(struct ctl_context *ctx)
if (ctx->argc == 3) {
/*Deletes all NATs with the specified type. */
- for (size_t i = 0; i < lr->n_nat; i++) {
- if (!strcmp(nat_type, lr->nat[i]->type)) {
- nbrec_logical_router_update_nat_delvalue(lr, lr->nat[i]);
- }
- }
+ lr_nat_del_matching(lr, nat_type, NULL, NULL, match, is_snat);
return;
}
char *nat_ip = normalize_prefix_str(ctx->argv[3]);
- int is_snat = !strcmp("snat", nat_type);
const struct nbrec_logical_router_port *dgw_port = NULL;
if (ctx->argc == 4) {
@@ -5346,32 +5423,9 @@ nbctl_lr_nat_del(struct ctl_context *ctx)
ctx->error = error;
return;
}
-
- /* Deletes all NATs matching the type and gateway_port
- * specified. */
- for (size_t i = 0; i < lr->n_nat; i++) {
- if (!strcmp(nat_type, lr->nat[i]->type) &&
- lr->nat[i]->gateway_port == dgw_port) {
- nbrec_logical_router_update_nat_delvalue(lr, lr->nat[i]);
- }
- }
- return;
}
- /* Remove NAT rules matching the type and IP (based on type). */
- for (size_t i = 0; i < lr->n_nat; i++) {
- struct nbrec_nat *nat = lr->nat[i];
- char *old_ip = normalize_prefix_str(is_snat
- ? nat->logical_ip
- : nat->external_ip);
- if (!old_ip) {
- continue;
- }
- if (!strcmp(nat_type, nat->type) && !strcmp(nat_ip, old_ip)) {
- nbrec_logical_router_update_nat_delvalue(lr, nat);
- }
- free(old_ip);
- }
+ lr_nat_del_matching(lr, nat_type, nat_ip, dgw_port, match, is_snat);
goto cleanup;
}
@@ -5386,32 +5440,22 @@ nbctl_lr_nat_del(struct ctl_context *ctx)
goto cleanup;
}
- /* Remove matching NAT. */
- for (size_t i = 0; i < lr->n_nat; i++) {
- struct nbrec_nat *nat = lr->nat[i];
- bool should_return = false;
- char *old_ip = normalize_prefix_str(is_snat
- ? nat->logical_ip
- : nat->external_ip);
- if (!old_ip) {
- continue;
- }
- if (!strcmp(nat_type, nat->type) && !strcmp(nat_ip, old_ip) &&
- nat->gateway_port == dgw_port) {
- nbrec_logical_router_update_nat_delvalue(lr, nat);
- should_return = true;
- }
- free(old_ip);
- if (should_return) {
- goto cleanup;
+ size_t n_deleted = lr_nat_del_matching(lr, nat_type, nat_ip, dgw_port,
+ match, is_snat);
+ if (must_exist && !n_deleted) {
+ struct ds ds = DS_EMPTY_INITIALIZER;
+ ds_put_format(&ds, "no matching NAT with the type (%s), %s (%s)",
+ nat_type, is_snat ? "logical_ip" : "external_ip",
+ nat_ip);
+ if (match) {
+ ds_put_format(&ds, ", gateway_port (%s) and match (%s)",
+ ctx->argv[4], match);
+ } else {
+ ds_put_format(&ds, " and gateway_port (%s)", ctx->argv[4]);
}
- }
- if (must_exist) {
- ctl_error(ctx, "no matching NAT with the type (%s), %s (%s) and "
- "gateway_port (%s)", nat_type,
- is_snat ? "logical_ip" : "external_ip", nat_ip,
- ctx->argv[4]);
+ ctx->error = xstrdup(ds_cstr_ro(&ds));
+ ds_destroy(&ds);
}
cleanup:
@@ -5431,6 +5475,7 @@ nbctl_pre_lr_nat_list(struct ctl_context *ctx)
ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_external_mac);
ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_logical_port);
ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_gateway_port);
+ ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_match);
ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_port_col_name);
}
@@ -5448,11 +5493,12 @@ nbctl_lr_nat_list(struct ctl_context *ctx)
struct smap lr_nats = SMAP_INITIALIZER(&lr_nats);
for (size_t i = 0; i < lr->n_nat; i++) {
const struct nbrec_nat *nat = lr->nat[i];
- char *key = xasprintf("%-17.13s%-22.18s%s",
+ char *key = xasprintf("%-17.13s%-22.18s%-22.18s%-19.15s",
nat->type,
nat->gateway_port
? nat->gateway_port->name
: "",
+ nat->match,
nat->external_ip);
if (nat->external_mac && nat->logical_port) {
smap_add_format(&lr_nats, key,
@@ -5472,13 +5518,13 @@ nbctl_lr_nat_list(struct ctl_context *ctx)
const struct smap_node **nodes = smap_sort(&lr_nats);
if (nodes) {
ds_put_format(&ctx->output,
- "%-17.13s%-22.18s%-19.15s%-17.13s%-20.16s%-21.17s%s\n",
- "TYPE", "GATEWAY_PORT", "EXTERNAL_IP", "EXTERNAL_PORT",
- "LOGICAL_IP", "EXTERNAL_MAC", "LOGICAL_PORT");
+ "%-17.13s%-22.18s%-22.18s%-19.15s%-17.13s%-20.16s%-21.17s%s\n",
+ "TYPE", "GATEWAY_PORT", "MATCH", "EXTERNAL_IP",
+ "EXTERNAL_PORT", "LOGICAL_IP", "EXTERNAL_MAC", "LOGICAL_PORT");
for (size_t i = 0; i < smap_count(&lr_nats); i++) {
const struct smap_node *node = nodes[i];
- ds_put_format(&ctx->output, "%-58.54s%s\n",
- node->key, node->value);
+ ds_put_format(&ctx->output, "%-80.86s%s\n",
+ node->key, node->value);
}
free(nodes);
}
@@ -8022,9 +8068,10 @@ static const struct ctl_command_syntax nbctl_commands[] = {
"[LOGICAL_PORT EXTERNAL_MAC] [EXTERNAL_PORT_RANGE]",
nbctl_pre_lr_nat_add, nbctl_lr_nat_add,
NULL, "--may-exist,--stateless,--portrange,--add-route,"
- "--gateway-port=", RW },
+ "--gateway-port=,--priority=,--match=", RW },
{ "lr-nat-del", 1, 4, "ROUTER [TYPE [IP] [GATEWAY_PORT]]",
- nbctl_pre_lr_nat_del, nbctl_lr_nat_del, NULL, "--if-exists", RW },
+ nbctl_pre_lr_nat_del, nbctl_lr_nat_del, NULL,
+ "--if-exists,--match=", RW },
{ "lr-nat-list", 1, 1, "ROUTER", nbctl_pre_lr_nat_list,
nbctl_lr_nat_list, NULL, "", RO },
{ "lr-nat-update-ext-ip", 4, 4, "ROUTER TYPE IP ADDRESS_SET",
From patchwork Fri May 3 07:26:22 2024
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
X-Patchwork-Submitter: Ales Musil
X-Patchwork-Id: 1930951
Return-Path:
X-Original-To: incoming@patchwork.ozlabs.org
Delivered-To: patchwork-incoming@legolas.ozlabs.org
Authentication-Results: legolas.ozlabs.org;
dkim=fail reason="signature verification failed" (1024-bit key;
unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256
header.s=mimecast20190719 header.b=DaNU/AT0;
dkim-atps=neutral
Authentication-Results: legolas.ozlabs.org;
spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org
(client-ip=140.211.166.133; helo=smtp2.osuosl.org;
envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org)
Received: from smtp2.osuosl.org (smtp2.osuosl.org [140.211.166.133])
(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384)
(No client certificate requested)
by legolas.ozlabs.org (Postfix) with ESMTPS id 4VW2Rl3Hmbz1ydX
for ; Fri, 3 May 2024 17:26:43 +1000 (AEST)
Received: from localhost (localhost [127.0.0.1])
by smtp2.osuosl.org (Postfix) with ESMTP id 9087141CA8;
Fri, 3 May 2024 07:26:41 +0000 (UTC)
X-Virus-Scanned: amavis at osuosl.org
Received: from smtp2.osuosl.org ([127.0.0.1])
by localhost (smtp2.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP
id sRMrQ99PXi63; Fri, 3 May 2024 07:26:37 +0000 (UTC)
X-Comment: SPF check N/A for local connections - client-ip=140.211.9.56;
helo=lists.linuxfoundation.org;
envelope-from=ovs-dev-bounces@openvswitch.org; receiver=
DKIM-Filter: OpenDKIM Filter v2.11.0 smtp2.osuosl.org CB10941CAA
Authentication-Results: smtp2.osuosl.org;
dkim=fail reason="signature verification failed" (1024-bit key)
header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256
header.s=mimecast20190719 header.b=DaNU/AT0
Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56])
by smtp2.osuosl.org (Postfix) with ESMTPS id CB10941CAA;
Fri, 3 May 2024 07:26:35 +0000 (UTC)
Received: from lf-lists.osuosl.org (localhost [127.0.0.1])
by lists.linuxfoundation.org (Postfix) with ESMTP id 02994C0DCE;
Fri, 3 May 2024 07:26:35 +0000 (UTC)
X-Original-To: dev@openvswitch.org
Delivered-To: ovs-dev@lists.linuxfoundation.org
Received: from smtp1.osuosl.org (smtp1.osuosl.org [140.211.166.138])
by lists.linuxfoundation.org (Postfix) with ESMTP id 3C971C0DCE
for ; Fri, 3 May 2024 07:26:32 +0000 (UTC)
Received: from localhost (localhost [127.0.0.1])
by smtp1.osuosl.org (Postfix) with ESMTP id 2C32583026
for ; Fri, 3 May 2024 07:26:32 +0000 (UTC)
X-Virus-Scanned: amavis at osuosl.org
Received: from smtp1.osuosl.org ([127.0.0.1])
by localhost (smtp1.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP
id JNjgRTR_8kRy for ;
Fri, 3 May 2024 07:26:30 +0000 (UTC)
Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=170.10.133.124;
helo=us-smtp-delivery-124.mimecast.com; envelope-from=amusil@redhat.com;
receiver=
DMARC-Filter: OpenDMARC Filter v1.4.2 smtp1.osuosl.org EAC5E82CAC
Authentication-Results: smtp1.osuosl.org;
dmarc=pass (p=none dis=none) header.from=redhat.com
DKIM-Filter: OpenDKIM Filter v2.11.0 smtp1.osuosl.org EAC5E82CAC
Authentication-Results: smtp1.osuosl.org;
dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com
header.a=rsa-sha256 header.s=mimecast20190719 header.b=DaNU/AT0
Received: from us-smtp-delivery-124.mimecast.com
(us-smtp-delivery-124.mimecast.com [170.10.133.124])
by smtp1.osuosl.org (Postfix) with ESMTPS id EAC5E82CAC
for ; Fri, 3 May 2024 07:26:29 +0000 (UTC)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;
s=mimecast20190719; t=1714721188;
h=from:from:reply-to:subject:subject:date:date:message-id:message-id:
to:to:cc:cc:mime-version:mime-version:content-type:content-type:
content-transfer-encoding:content-transfer-encoding:
in-reply-to:in-reply-to:references:references;
bh=ctSm8ugnbkCT6YPcgODdXKud1YLxZMJy/rgMpIJUirg=;
b=DaNU/AT0+9urdLGkAp/qR752Ujz7ZTFhiIZ+uvSpSFmjmqyAqhMhFk58U56QLkM/mHWHdL
LZRbvfRhK6MCopUyXcp9hN2cAhePBgGVp9TYfcYRlqXS5MvxnH2fhuP6o8Cpr65PbU+pxj
wPPegITnSlL4OEW4eTdeNvJnsIZh94o=
Received: from mimecast-mx02.redhat.com (mimecast-mx02.redhat.com
[66.187.233.88]) by relay.mimecast.com with ESMTP with STARTTLS
(version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id
us-mta-448-oh5kAst3Omi3oakq7X8mCw-1; Fri, 03 May 2024 03:26:27 -0400
X-MC-Unique: oh5kAst3Omi3oakq7X8mCw-1
Received: from smtp.corp.redhat.com (int-mx01.intmail.prod.int.rdu2.redhat.com
[10.11.54.1])
(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest
SHA256)
(No client certificate requested)
by mimecast-mx02.redhat.com (Postfix) with ESMTPS id BDEF518065AB
for ; Fri, 3 May 2024 07:26:26 +0000 (UTC)
Received: from amusil.redhat.com (unknown [10.45.224.48])
by smtp.corp.redhat.com (Postfix) with ESMTP id 3C9B6AC6B;
Fri, 3 May 2024 07:26:26 +0000 (UTC)
From: Ales Musil
To: dev@openvswitch.org
Date: Fri, 3 May 2024 09:26:22 +0200
Message-ID: <20240503072622.2111265-4-amusil@redhat.com>
In-Reply-To: <20240503072622.2111265-1-amusil@redhat.com>
References: <20240503072622.2111265-1-amusil@redhat.com>
MIME-Version: 1.0
X-Scanned-By: MIMEDefang 3.4.1 on 10.11.54.1
X-Mimecast-Spam-Score: 0
X-Mimecast-Originator: redhat.com
Subject: [ovs-dev] [PATCH ovn 3/3] northd: Use the NAT match column.
X-BeenThere: ovs-dev@openvswitch.org
X-Mailman-Version: 2.1.15
Precedence: list
List-Id:
List-Unsubscribe: ,
List-Archive:
List-Post:
List-Help:
List-Subscribe: ,
Errors-To: ovs-dev-bounces@openvswitch.org
Sender: "dev"
Use the newly added NAT match and priority column in logical flows.
This allows to differentiate between various scenarios and more
fine-grained control over the resulting translation. The flows with
the extra match have higher priority than regular flows as the
flows without match are subset of the flows with match, the priority
is calculated as 300 + priority column.
Reported-at: https://issues.redhat.com/browse/FDP-433
Signed-off-by: Ales Musil
Acked-by: Mark Michelson
---
northd/northd.c | 31 +++--
northd/ovn-northd.8.xml | 31 +++++
tests/ovn-northd.at | 79 ++++++++++++
tests/system-ovn.at | 272 ++++++++++++++++++++++++++++++++++++++++
4 files changed, 406 insertions(+), 7 deletions(-)
diff --git a/northd/northd.c b/northd/northd.c
index a883c3e08..a7e8c34c1 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -11544,9 +11544,14 @@ lrouter_dnat_and_snat_is_stateless(const struct nbrec_nat *nat)
}
static inline uint16_t
-lrouter_nat_get_priority(const struct ovn_datapath *od, bool is_dnat,
+lrouter_nat_get_priority(const struct ovn_datapath *od,
+ const struct nbrec_nat *nat, bool is_dnat,
uint16_t prefix_len)
{
+ if (nat->match[0]) {
+ return 300 + nat->priority;
+ }
+
if (is_dnat) {
return 100;
}
@@ -11608,7 +11613,7 @@ lrouter_nat_add_ext_ip_match(const struct ovn_datapath *od,
*
*/
uint16_t priority =
- lrouter_nat_get_priority(od, is_src, cidr_bits) + 2;
+ lrouter_nat_get_priority(od, nat, is_src, cidr_bits) + 2;
ds_clone(&match_exempt, match);
ds_put_format(&match_exempt, " && ip%s.%s == $%s",
@@ -14561,6 +14566,7 @@ build_lrouter_in_dnat_flow(struct lflow_table *lflows,
const char *nat_action = lrouter_use_common_zone(od)
? "ct_dnat_in_czone"
: "ct_dnat";
+ uint16_t priority = lrouter_nat_get_priority(od, nat, true, cidr_bits);
ds_put_format(match, "ip && ip%c.dst == %s", is_v6 ? '6' : '4',
nat->external_ip);
@@ -14607,8 +14613,11 @@ build_lrouter_in_dnat_flow(struct lflow_table *lflows,
ds_put_format(actions, ");");
}
- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT,
- lrouter_nat_get_priority(od, true, cidr_bits),
+ if (!lrouter_use_common_zone(od) && nat->match[0]) {
+ ds_put_format(match, " && (%s)", nat->match);
+ }
+
+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, priority,
ds_cstr(match), ds_cstr(actions),
&nat->header_, lflow_ref);
}
@@ -14751,7 +14760,7 @@ build_lrouter_out_snat_stateless_flow(struct lflow_table *lflows,
ds_clear(actions);
- uint16_t priority = lrouter_nat_get_priority(od, false, cidr_bits);
+ uint16_t priority = lrouter_nat_get_priority(od, nat, false, cidr_bits);
build_lrouter_out_snat_match(lflows, od, nat, match, distributed_nat,
cidr_bits, is_v6, l3dgw_port, lflow_ref,
false);
@@ -14764,6 +14773,10 @@ build_lrouter_out_snat_stateless_flow(struct lflow_table *lflows,
ds_put_format(actions, "ip%c.src=%s; next;",
is_v6 ? '6' : '4', nat->external_ip);
+ if (nat->match[0]) {
+ ds_put_format(match, " && (%s)", nat->match);
+ }
+
ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT,
priority, ds_cstr(match),
ds_cstr(actions), &nat->header_,
@@ -14786,7 +14799,7 @@ build_lrouter_out_snat_in_czone_flow(struct lflow_table *lflows,
ds_clear(actions);
- uint16_t priority = lrouter_nat_get_priority(od, false, cidr_bits);
+ uint16_t priority = lrouter_nat_get_priority(od, nat, false, cidr_bits);
struct ds zone_actions = DS_EMPTY_INITIALIZER;
build_lrouter_out_snat_match(lflows, od, nat, match, distributed_nat,
@@ -14845,7 +14858,7 @@ build_lrouter_out_snat_flow(struct lflow_table *lflows,
ds_clear(actions);
- uint16_t priority = lrouter_nat_get_priority(od, false, cidr_bits);
+ uint16_t priority = lrouter_nat_get_priority(od, nat, false, cidr_bits);
build_lrouter_out_snat_match(lflows, od, nat, match, distributed_nat,
cidr_bits, is_v6, l3dgw_port, lflow_ref,
@@ -14864,6 +14877,10 @@ build_lrouter_out_snat_flow(struct lflow_table *lflows,
}
ds_put_format(actions, ");");
+ if (nat->match[0]) {
+ ds_put_format(match, " && (%s)", nat->match);
+ }
+
ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT,
priority, ds_cstr(match),
ds_cstr(actions), &nat->header_,
diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
index 2e588e28f..f4e5028ec 100644
--- a/northd/ovn-northd.8.xml
+++ b/northd/ovn-northd.8.xml
@@ -3780,6 +3780,23 @@ next;
exempted_ext_ips
.
+
+ For each configuration in the OVN Northbound database, that asks
+ to change the destination IP address of a packet from A
+ to B, match M and priority P,
+ a logical flow that matches ip && ip4.dst ==
+ A
or ip && ip6.dst == A
+ && (M)
with an action
+ flags.loopback = 1; ct_dnat(B);
.
+ The priority of the flow is calculated based as
+ 300 + P
. If the Gateway router is
+ configured to force SNAT any DNATed packet, the above action will
+ be replaced by flags.force_snat_for_dnat = 1;
+ flags.loopback = 1; ct_dnat(B);
. If the NAT rule
+ is of type dnat_and_snat and has stateless=true
in the
+ options, then the action would be ip4/6.dst=
+ (B)
.
+
@@ -5148,6 +5165,20 @@ nd_ns {
options, then the action would be ip4/6.src=
(B)
.
+
+
+ For each configuration in the OVN Northbound database, that asks
+ to change the source IP address of a packet from an IP address of
+ A or to change the source IP address of a packet that
+ belongs to network A to B, match M
+ and priority P, a flow matches ip &&
+ ip4.src == A && (!ct.trk || !ct.rpl) &&
+ (M)
with an action ct_snat(B);
+
. The priority of the flow is calculated based as
+ 300 + P
. If the NAT rule is of type
+ dnat_and_snat and has stateless=true
in the options,
+ then the action would be ip4/6.src=(B)
.
+
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 680d96675..9a339095b 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -1024,22 +1024,46 @@ ovn-nbctl --wait=sb lr-nat-add R1 dnat_and_snat 172.16.1.1 50.0.0.11
check_flow_match_sets 2 2 2 0 0 0 0
ovn-nbctl lr-nat-del R1 dnat_and_snat 172.16.1.1
+echo
+echo "IPv4: stateful with match"
+ovn-nbctl --wait=sb --match="udp" lr-nat-add R1 dnat_and_snat 172.16.1.1 50.0.0.11
+check_flow_match_sets 2 2 2 0 0 0 0
+ovn-nbctl lr-nat-del R1 dnat_and_snat 172.16.1.1
+
echo
echo "IPv4: stateless"
ovn-nbctl --wait=sb --stateless lr-nat-add R1 dnat_and_snat 172.16.1.1 50.0.0.11
check_flow_match_sets 2 0 0 1 1 0 0
ovn-nbctl lr-nat-del R1 dnat_and_snat 172.16.1.1
+echo
+echo "IPv4: stateless with match"
+ovn-nbctl --wait=sb --match="udp" --stateless lr-nat-add R1 dnat_and_snat 172.16.1.1 50.0.0.11
+check_flow_match_sets 2 0 0 1 1 0 0
+ovn-nbctl lr-nat-del R1 dnat_and_snat 172.16.1.1
+
echo
echo "IPv6: stateful"
ovn-nbctl --wait=sb lr-nat-add R1 dnat_and_snat fd01::1 fd11::2
check_flow_match_sets 2 2 2 0 0 0 0
ovn-nbctl lr-nat-del R1 dnat_and_snat fd01::1
+echo
+echo "IPv6: stateful with match"
+ovn-nbctl --wait=sb --match="udp" lr-nat-add R1 dnat_and_snat fd01::1 fd11::2
+check_flow_match_sets 2 2 2 0 0 0 0
+ovn-nbctl lr-nat-del R1 dnat_and_snat fd01::1
+
echo
echo "IPv6: stateless"
ovn-nbctl --wait=sb --stateless lr-nat-add R1 dnat_and_snat fd01::1 fd11::2
check_flow_match_sets 2 0 0 0 0 1 1
+ovn-nbctl lr-nat-del R1 dnat_and_snat fd01::1
+
+echo
+echo "IPv6: stateless with match"
+ovn-nbctl --wait=sb --match="udp" --stateless lr-nat-add R1 dnat_and_snat fd01::1 fd11::2
+check_flow_match_sets 2 0 0 0 0 1 1
AT_CLEANUP
])
@@ -12544,3 +12568,58 @@ check_engine_stats northd recompute nocompute
check_engine_stats lflow recompute nocompute
AT_CLEANUP
+
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([NAT with match])
+ovn_start
+
+check ovn-sbctl chassis-add hv1 geneve 127.0.0.1
+
+check ovn-nbctl lr-add lr -- \
+ lrp-add lr lr-ls 02:ac:10:01:00:01 172.16.1.1/24
+
+check ovn-nbctl ls-add ls -- \
+ lsp-add ls ls-lr -- \
+ lsp-set-type ls-lr router -- \
+ lsp-set-addresses ls-lr router -- \
+ lsp-set-options ls-lr router-port=lr-ls
+
+check ovn-nbctl lr-nat-add lr snat 172.16.1.1 10.0.0.0/24
+check ovn-nbctl --match="udp" --priority=10 lr-nat-add lr snat 172.16.1.2 10.0.0.0/24
+
+check ovn-nbctl lr-nat-add lr dnat 10.0.0.100 10.0.0.10
+check ovn-nbctl --match="udp" --priority=20 lr-nat-add lr dnat 10.0.0.100 10.0.0.20
+
+check ovn-nbctl --wait=sb lrp-set-gateway-chassis lr-ls hv1
+
+AT_CHECK([ovn-sbctl dump-flows lr | grep lr_out_snat | 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.src == 10.0.0.0/24 && outport == "lr-ls" && is_chassis_resident("cr-lr-ls") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.16.1.1);)
+ table=??(lr_out_snat ), priority=310 , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr-ls" && is_chassis_resident("cr-lr-ls") && (!ct.trk || !ct.rpl) && (udp)), action=(ct_snat(172.16.1.2);)
+])
+
+AT_CHECK([ovn-sbctl dump-flows lr | grep lr_in_dnat | ovn_strip_lflows], [0], [dnl
+ table=??(lr_in_dnat ), priority=0 , match=(1), action=(next;)
+ table=??(lr_in_dnat ), priority=100 , match=(ip && ip4.dst == 10.0.0.100 && inport == "lr-ls" && is_chassis_resident("cr-lr-ls")), action=(ct_dnat(10.0.0.10);)
+ table=??(lr_in_dnat ), priority=320 , match=(ip && ip4.dst == 10.0.0.100 && inport == "lr-ls" && is_chassis_resident("cr-lr-ls") && (udp)), action=(ct_dnat(10.0.0.20);)
+])
+
+check ovn-nbctl lrp-del-gateway-chassis lr-ls hv1
+check ovn-nbctl --wait=sb set logical_router lr options:chassis=hv1
+
+AT_CHECK([ovn-sbctl dump-flows lr | grep lr_out_snat | 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=25 , match=(ip && ip4.src == 10.0.0.0/24 && (!ct.trk || !ct.rpl)), action=(ct_snat(172.16.1.1);)
+ table=??(lr_out_snat ), priority=310 , match=(ip && ip4.src == 10.0.0.0/24 && (!ct.trk || !ct.rpl) && (udp)), action=(ct_snat(172.16.1.2);)
+])
+
+AT_CHECK([ovn-sbctl dump-flows lr | grep lr_in_dnat | ovn_strip_lflows], [0], [dnl
+ table=??(lr_in_dnat ), priority=0 , match=(1), action=(next;)
+ table=??(lr_in_dnat ), priority=100 , match=(ip && ip4.dst == 10.0.0.100), action=(flags.loopback = 1; ct_dnat(10.0.0.10);)
+ table=??(lr_in_dnat ), priority=320 , match=(ip && ip4.dst == 10.0.0.100 && (udp)), action=(flags.loopback = 1; ct_dnat(10.0.0.20);)
+])
+
+AT_CLEANUP
+])
diff --git a/tests/system-ovn.at b/tests/system-ovn.at
index 86fd240d2..a658849e6 100644
--- a/tests/system-ovn.at
+++ b/tests/system-ovn.at
@@ -12736,3 +12736,275 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d
/.*terminating with signal 15.*/d"])
AT_CLEANUP
])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([NAT arbitrary match - IPv4])
+AT_KEYWORDS([ovnlb])
+
+CHECK_CONNTRACK()
+CHECK_CONNTRACK_NAT()
+
+ovn_start
+OVS_TRAFFIC_VSWITCHD_START()
+ADD_BR([br-int])
+ADD_BR([br-ext])
+
+check ovs-ofctl add-flow br-ext action=normal
+# Set external-ids in br-int needed for ovn-controller
+check ovs-vsctl \
+ -- set Open_vSwitch . external-ids:system-id=hv1 \
+ -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
+ -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
+ -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
+ -- set bridge br-int fail-mode=secure other-config:disable-in-band=true \
+ -- set Open_vSwitch . external-ids:ovn-bridge-mappings=phynet:br-ext
+
+# Start ovn-controller
+start_daemon ovn-controller
+
+check ovn-nbctl lr-add lr
+check ovn-nbctl ls-add internal
+check ovn-nbctl ls-add public
+
+check ovn-nbctl lrp-add lr lr-pub 00:00:01:01:02:03 192.168.100.1/24
+check ovn-nbctl lsp-add public pub-lr -- set Logical_Switch_Port pub-lr \
+ type=router options:router-port=lr-pub addresses=\"00:00:01:01:02:03\"
+
+check ovn-nbctl lrp-add lr lr-internal 00:00:01:01:02:04 192.168.200.1/24
+check ovn-nbctl lsp-add internal internal-lr -- set Logical_Switch_Port internal-lr \
+ type=router options:router-port=lr-internal addresses=\"00:00:01:01:02:04\"
+
+check ovn-nbctl lsp-add internal vm0 \
+ -- lsp-set-addresses vm0 "f0:00:0f:01:02:03 192.168.200.10"
+check ovn-nbctl lsp-add internal vm2 \
+ -- lsp-set-addresses vm2 "f0:00:0f:01:02:04 192.168.200.20"
+check ovn-nbctl lsp-add internal vm3 \
+ -- lsp-set-addresses vm3 "f0:00:0f:01:02:05 192.168.200.30"
+
+ovn-nbctl lsp-add public ln_port \
+ -- lsp-set-addresses ln_port unknown \
+ -- lsp-set-type ln_port localnet \
+ -- lsp-set-options ln_port network_name=phynet
+
+check ovn-nbctl --wait=hv sync
+
+ADD_NAMESPACES(vm0)
+ADD_VETH(vm0, vm0, br-int, "192.168.200.10/24", "f0:00:0f:01:02:03", "192.168.200.1")
+
+ADD_NAMESPACES(vm1)
+ADD_VETH(vm1, vm1, br-ext, "192.168.100.10/24", "f0:00:00:01:02:03", "192.168.100.1")
+
+ADD_NAMESPACES(vm2)
+ADD_VETH(vm2, vm2, br-int, "192.168.200.20/24", "f0:00:0f:01:02:04", "192.168.200.1")
+
+ADD_NAMESPACES(vm3)
+ADD_VETH(vm3, vm3, br-int, "192.168.200.30/24", "f0:00:0f:01:02:05", "192.168.200.1")
+
+NETNS_DAEMONIZE([vm0], [nc -l -k 192.168.200.10 4242], [server0.pid])
+NETNS_DAEMONIZE([vm1], [nc -l -k 192.168.100.10 4242], [server1.pid])
+NETNS_DAEMONIZE([vm2], [nc -l -k 192.168.200.20 4242], [server2.pid])
+NETNS_DAEMONIZE([vm3], [nc -l -k 192.168.200.30 4242], [server3.pid])
+
+check_snat() {
+ check ovn-nbctl lr-nat-del lr
+ check ovn-nbctl lr-nat-add lr snat 192.168.100.1 192.168.200.0/24
+ check ovn-nbctl --match="tcp && tcp.src == 2001" lr-nat-add lr snat 192.168.100.11 192.168.200.0/24
+ check ovn-nbctl --match="tcp && tcp.src == 2002" lr-nat-add lr snat 192.168.100.12 192.168.200.0/24
+ check ovn-nbctl --wait=hv sync
+
+ check ovs-appctl dpctl/flush-conntrack
+
+ NS_CHECK_EXEC([vm0], [nc -z 192.168.100.10 4242 -p 2000])
+ NS_CHECK_EXEC([vm0], [nc -z 192.168.100.10 4242 -p 2001])
+ NS_CHECK_EXEC([vm0], [nc -z 192.168.100.10 4242 -p 2002])
+
+ snat_id=$(ovn-appctl -t ovn-controller ct-zone-list | grep lr_snat | cut -d ' ' -f2)
+ AT_CHECK_UNQUOTED([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(192.168.100.10) | grep "zone=$snat_id"], [0], [dnl
+tcp,orig=(src=192.168.200.10,dst=192.168.100.10,sport=,dport=),reply=(src=192.168.100.10,dst=192.168.100.1,sport=,dport=),zone=$snat_id,protoinfo=(state=)
+tcp,orig=(src=192.168.200.10,dst=192.168.100.10,sport=,dport=),reply=(src=192.168.100.10,dst=192.168.100.11,sport=,dport=),zone=$snat_id,protoinfo=(state=)
+tcp,orig=(src=192.168.200.10,dst=192.168.100.10,sport=,dport=),reply=(src=192.168.100.10,dst=192.168.100.12,sport=,dport=),zone=$snat_id,protoinfo=(state=)
+])
+}
+
+check_dnat() {
+ check ovn-nbctl lr-nat-del lr
+ check ovn-nbctl lr-nat-add lr dnat 192.168.100.100 192.168.200.10
+ check ovn-nbctl --match="tcp && tcp.src == 2001" lr-nat-add lr dnat 192.168.100.100 192.168.200.20
+ check ovn-nbctl --match="tcp && tcp.src == 2002" lr-nat-add lr dnat 192.168.100.100 192.168.200.30
+ check ovn-nbctl --wait=hv sync
+
+ check ovs-appctl dpctl/flush-conntrack
+
+ NS_CHECK_EXEC([vm1], [nc -z 192.168.100.100 4242 -p 2000])
+ NS_CHECK_EXEC([vm1], [nc -z 192.168.100.100 4242 -p 2001])
+ NS_CHECK_EXEC([vm1], [nc -z 192.168.100.100 4242 -p 2002])
+
+ dnat_id=$(ovn-appctl -t ovn-controller ct-zone-list | grep lr_dnat | cut -d ' ' -f2)
+ AT_CHECK_UNQUOTED([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(192.168.100.100) | grep "zone=$dnat_id"], [0], [dnl
+tcp,orig=(src=192.168.100.10,dst=192.168.100.100,sport=,dport=),reply=(src=192.168.200.10,dst=192.168.100.10,sport=,dport=),zone=$dnat_id,protoinfo=(state=)
+tcp,orig=(src=192.168.100.10,dst=192.168.100.100,sport=,dport=),reply=(src=192.168.200.20,dst=192.168.100.10,sport=,dport=),zone=$dnat_id,protoinfo=(state=)
+tcp,orig=(src=192.168.100.10,dst=192.168.100.100,sport=,dport=),reply=(src=192.168.200.30,dst=192.168.100.10,sport=,dport=),zone=$dnat_id,protoinfo=(state=)
+])
+}
+
+check ovn-nbctl lrp-set-gateway-chassis lr-pub hv1
+check_snat
+check_dnat
+check ovn-nbctl lrp-del-gateway-chassis lr-pub hv1
+
+check ovn-nbctl set logical_router lr options:chassis=hv1
+check_snat
+check_dnat
+
+OVS_APP_EXIT_AND_WAIT([ovn-controller])
+
+as ovn-sb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as ovn-nb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as northd
+OVS_APP_EXIT_AND_WAIT([ovn-northd])
+
+as
+OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
+/connection dropped.*/d"])
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([NAT arbitrary match - IPv6])
+AT_KEYWORDS([ovnlb])
+
+CHECK_CONNTRACK()
+CHECK_CONNTRACK_NAT()
+
+ovn_start
+OVS_TRAFFIC_VSWITCHD_START()
+ADD_BR([br-int])
+ADD_BR([br-ext])
+
+check ovs-ofctl add-flow br-ext action=normal
+# Set external-ids in br-int needed for ovn-controller
+check ovs-vsctl \
+ -- set Open_vSwitch . external-ids:system-id=hv1 \
+ -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
+ -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
+ -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
+ -- set bridge br-int fail-mode=secure other-config:disable-in-band=true \
+ -- set Open_vSwitch . external-ids:ovn-bridge-mappings=phynet:br-ext
+
+# Start ovn-controller
+start_daemon ovn-controller
+
+check ovn-nbctl lr-add lr
+check ovn-nbctl ls-add internal
+check ovn-nbctl ls-add public
+
+check ovn-nbctl lrp-add lr lr-pub 00:00:01:01:02:03 1000::1/64
+check ovn-nbctl lsp-add public pub-lr -- set Logical_Switch_Port pub-lr \
+ type=router options:router-port=lr-pub addresses=\"00:00:01:01:02:03\"
+
+check ovn-nbctl lrp-add lr lr-internal 00:00:01:01:02:04 2000::1/64
+check ovn-nbctl lsp-add internal internal-lr -- set Logical_Switch_Port internal-lr \
+ type=router options:router-port=lr-internal addresses=\"00:00:01:01:02:04\"
+
+check ovn-nbctl lsp-add internal vm0 \
+ -- lsp-set-addresses vm0 "f0:00:0f:01:02:03 2000::10"
+check ovn-nbctl lsp-add internal vm2 \
+ -- lsp-set-addresses vm2 "f0:00:0f:01:02:04 2000::20"
+check ovn-nbctl lsp-add internal vm3 \
+ -- lsp-set-addresses vm3 "f0:00:0f:01:02:05 2000::30"
+
+ovn-nbctl lsp-add public ln_port \
+ -- lsp-set-addresses ln_port unknown \
+ -- lsp-set-type ln_port localnet \
+ -- lsp-set-options ln_port network_name=phynet
+
+check ovn-nbctl --wait=hv sync
+
+ADD_NAMESPACES(vm0)
+ADD_VETH(vm0, vm0, br-int, "2000::10/64", "f0:00:0f:01:02:03", "2000::1", "nodad")
+
+ADD_NAMESPACES(vm1)
+ADD_VETH(vm1, vm1, br-ext, "1000::10/64", "f0:00:00:01:02:03", "1000::1", "nodad")
+
+ADD_NAMESPACES(vm2)
+ADD_VETH(vm2, vm2, br-int, "2000::20/64", "f0:00:0f:01:02:04", "2000::1", "nodad")
+
+ADD_NAMESPACES(vm3)
+ADD_VETH(vm3, vm3, br-int, "2000::30/64", "f0:00:0f:01:02:05", "2000::1", "nodad")
+
+NETNS_DAEMONIZE([vm0], [nc -lk 2000::10 4242 > /dev/null], [server0.pid])
+NETNS_DAEMONIZE([vm1], [nc -lk 1000::10 4242 > /dev/null], [server1.pid])
+NETNS_DAEMONIZE([vm2], [nc -lk 2000::20 4242 > /dev/null], [server2.pid])
+NETNS_DAEMONIZE([vm3], [nc -lk 2000::30 4242 > /dev/null], [server3.pid])
+
+check_snat() {
+ check ovn-nbctl lr-nat-del lr
+ check ovn-nbctl lr-nat-add lr snat 1000::1 2000::/64
+ check ovn-nbctl --match="tcp && tcp.src == 2001" lr-nat-add lr snat 1000::11 2000::/64
+ check ovn-nbctl --match="tcp && tcp.src == 2002" lr-nat-add lr snat 1000::12 2000::/64
+ check ovn-nbctl --wait=hv sync
+
+ check ovs-appctl dpctl/flush-conntrack
+
+ NS_CHECK_EXEC([vm0], [nc -z 1000::10 4242 -p 2000])
+ NS_CHECK_EXEC([vm0], [nc -z 1000::10 4242 -p 2001])
+ NS_CHECK_EXEC([vm0], [nc -z 1000::10 4242 -p 2002])
+
+ snat_id=$(ovn-appctl -t ovn-controller ct-zone-list | grep lr_snat | cut -d ' ' -f2)
+ AT_CHECK_UNQUOTED([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(1000::10) | grep "zone=$snat_id"], [0], [dnl
+tcp,orig=(src=2000::10,dst=1000::10,sport=,dport=),reply=(src=1000::10,dst=1000::1,sport=,dport=),zone=$snat_id,protoinfo=(state=)
+tcp,orig=(src=2000::10,dst=1000::10,sport=,dport=),reply=(src=1000::10,dst=1000::11,sport=,dport=),zone=$snat_id,protoinfo=(state=)
+tcp,orig=(src=2000::10,dst=1000::10,sport=,dport=),reply=(src=1000::10,dst=1000::12,sport=,dport=),zone=$snat_id,protoinfo=(state=)
+])
+}
+
+check_dnat() {
+ check ovn-nbctl lr-nat-del lr
+ check ovn-nbctl lr-nat-add lr dnat 1000::100 2000::10
+ check ovn-nbctl --match="tcp && tcp.src == 2001" lr-nat-add lr dnat 1000::100 2000::20
+ check ovn-nbctl --match="tcp && tcp.src == 2002" lr-nat-add lr dnat 1000::100 2000::30
+ check ovn-nbctl --wait=hv sync
+
+ check ovs-appctl dpctl/flush-conntrack
+
+ NS_CHECK_EXEC([vm1], [nc -z 1000::100 4242 -p 2000])
+ NS_CHECK_EXEC([vm1], [nc -z 1000::100 4242 -p 2001])
+ NS_CHECK_EXEC([vm1], [nc -z 1000::100 4242 -p 2002])
+
+ dnat_id=$(ovn-appctl -t ovn-controller ct-zone-list | grep lr_dnat | cut -d ' ' -f2)
+ AT_CHECK_UNQUOTED([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(1000::100) | grep "zone=$dnat_id"], [0], [dnl
+tcp,orig=(src=1000::10,dst=1000::100,sport=,dport=),reply=(src=2000::10,dst=1000::10,sport=,dport=),zone=$dnat_id,protoinfo=(state=)
+tcp,orig=(src=1000::10,dst=1000::100,sport=,dport=),reply=(src=2000::20,dst=1000::10,sport=,dport=),zone=$dnat_id,protoinfo=(state=)
+tcp,orig=(src=1000::10,dst=1000::100,sport=,dport=),reply=(src=2000::30,dst=1000::10,sport=,dport=),zone=$dnat_id,protoinfo=(state=)
+])
+}
+
+check ovn-nbctl lrp-set-gateway-chassis lr-pub hv1
+check_snat
+check_dnat
+check ovn-nbctl lrp-del-gateway-chassis lr-pub hv1
+
+check ovn-nbctl set logical_router lr options:chassis=hv1
+check_snat
+check_dnat
+
+OVS_APP_EXIT_AND_WAIT([ovn-controller])
+
+as ovn-sb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as ovn-nb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as northd
+OVS_APP_EXIT_AND_WAIT([ovn-northd])
+
+as
+OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
+/connection dropped.*/d"])
+AT_CLEANUP
+])