From patchwork Mon Mar 4 10:55:41 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Martin Kalcok X-Patchwork-Id: 1907505 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" (2048-bit key; unprotected) header.d=canonical.com header.i=@canonical.com header.a=rsa-sha256 header.s=20210705 header.b=u0OZbRP+; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=2605:bc80:3010::137; helo=smtp4.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org) Received: from smtp4.osuosl.org (smtp4.osuosl.org [IPv6:2605:bc80:3010::137]) (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 4TpFxB3LBjz23hX for ; Mon, 4 Mar 2024 21:56:14 +1100 (AEDT) Received: from localhost (localhost [127.0.0.1]) by smtp4.osuosl.org (Postfix) with ESMTP id 2FCB540815; Mon, 4 Mar 2024 10:56:12 +0000 (UTC) X-Virus-Scanned: amavisd-new at osuosl.org Received: from smtp4.osuosl.org ([127.0.0.1]) by localhost (smtp4.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id 8jC793sDyGqg; Mon, 4 Mar 2024 10:56:11 +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 smtp4.osuosl.org EA9A3407F9 Authentication-Results: smtp4.osuosl.org; dkim=fail reason="signature verification failed" (2048-bit key) header.d=canonical.com header.i=@canonical.com header.a=rsa-sha256 header.s=20210705 header.b=u0OZbRP+ Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56]) by smtp4.osuosl.org (Postfix) with ESMTPS id EA9A3407F9; Mon, 4 Mar 2024 10:56:10 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id C1652C0077; Mon, 4 Mar 2024 10:56:10 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp3.osuosl.org (smtp3.osuosl.org [140.211.166.136]) by lists.linuxfoundation.org (Postfix) with ESMTP id 7AF97C0037 for ; Mon, 4 Mar 2024 10:56:09 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp3.osuosl.org (Postfix) with ESMTP id 6B0DF60B7B for ; Mon, 4 Mar 2024 10:56:09 +0000 (UTC) X-Virus-Scanned: amavisd-new at osuosl.org Received: from smtp3.osuosl.org ([127.0.0.1]) by localhost (smtp3.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id XG3RtwE1HaY5 for ; Mon, 4 Mar 2024 10:56:05 +0000 (UTC) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=185.125.188.121; helo=smtp-relay-canonical-1.canonical.com; envelope-from=martin.kalcok@canonical.com; receiver= DMARC-Filter: OpenDMARC Filter v1.4.2 smtp3.osuosl.org 3F23F60B71 Authentication-Results: smtp3.osuosl.org; dmarc=pass (p=none dis=none) header.from=canonical.com DKIM-Filter: OpenDKIM Filter v2.11.0 smtp3.osuosl.org 3F23F60B71 Authentication-Results: smtp3.osuosl.org; dkim=pass (2048-bit key, unprotected) header.d=canonical.com header.i=@canonical.com header.a=rsa-sha256 header.s=20210705 header.b=u0OZbRP+ Received: from smtp-relay-canonical-1.canonical.com (smtp-relay-canonical-1.canonical.com [185.125.188.121]) by smtp3.osuosl.org (Postfix) with ESMTPS id 3F23F60B71 for ; Mon, 4 Mar 2024 10:56:04 +0000 (UTC) Received: from omen-desktop.. (178-143-45-10.static.orange.sk [178.143.45.10]) (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 smtp-relay-canonical-1.canonical.com (Postfix) with ESMTPSA id 367E73FB61; Mon, 4 Mar 2024 10:56:02 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=canonical.com; s=20210705; t=1709549762; bh=vszT8gbhHeftGVZD4iuqtWqboLt5SXV9TtkCPYLTv5Q=; h=From:To:Cc:Subject:Date:Message-Id:MIME-Version; b=u0OZbRP+FQ/GVF9SFc01vr5rTCiS1l6TvfqEqLFigAXmcFwlnthetStVh8HVhCbJ1 2qOfv8xhoQUKq0DNReqOg+w9jbwdqEuGY16VK6rI0Q/cUXEA6FPZKJQwQIgk3cCRsH Uloal0kyJtma/KJayD6jr4c6CrPCu4JKNhtubY3t78wcAQnq2ddNwX4janKO6aiMdk Gv+3Y8iFKEuMBMVN4wXgRlkbUT7RupKNkQJ43Aau7UoTemz7h8Xzp/6B5Wo+ayZoD1 MBEwisUpmbnQmB4HnxR7Z54RFrwU1P7XJL7WAWH/kcLE99DuE11qeh7TXblYEddvyT 73NqIWMjKl/oQ== From: Martin Kalcok To: dev@openvswitch.org Date: Mon, 4 Mar 2024 11:55:41 +0100 Message-Id: <20240304105542.349690-1-martin.kalcok@canonical.com> X-Mailer: git-send-email 2.40.1 MIME-Version: 1.0 Subject: [ovs-dev] [Patch ovn 1/2] actions.c/h: Enable specifying zone for ct_commit. 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" Action `ct_commit` currently does not allow specifying zone into which connection is committed. For example, in LR datapath, the `ct_commit` will always use the DNAT zone. This change adds option to use `ct_commit(snat)` or `ct_commit(dnat)` to explicitly specify the zone into which the connection will be committed. Original behavior of `ct_commit` without the arguments remains unchanged. Signed-off-by: Martin Kalcok --- include/ovn/actions.h | 9 +++++++++ lib/actions.c | 20 +++++++++++++++++++- ovn-sb.xml | 9 +++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/include/ovn/actions.h b/include/ovn/actions.h index 49fb96fc6..ce9597cf5 100644 --- a/include/ovn/actions.h +++ b/include/ovn/actions.h @@ -259,11 +259,20 @@ struct ovnact_ct_next { uint8_t ltable; /* Logical table ID of next table. */ }; +/* Conntrack zone to be used for commiting CT entries by the action. + * DEFAULT uses default zone for given datapath. */ +enum ovnact_ct_zone { + OVNACT_CT_ZONE_DEFAULT, + OVNACT_CT_ZONE_SNAT, + OVNACT_CT_ZONE_DNAT, +}; + /* OVNACT_CT_COMMIT_V1. */ struct ovnact_ct_commit_v1 { struct ovnact ovnact; uint32_t ct_mark, ct_mark_mask; ovs_be128 ct_label, ct_label_mask; + enum ovnact_ct_zone zone; }; /* Type of NAT used for the particular action. diff --git a/lib/actions.c b/lib/actions.c index a45874dfb..319e65b6f 100644 --- a/lib/actions.c +++ b/lib/actions.c @@ -707,6 +707,7 @@ static void parse_ct_commit_v1_arg(struct action_context *ctx, struct ovnact_ct_commit_v1 *cc) { + cc->zone = OVNACT_CT_ZONE_DEFAULT; if (lexer_match_id(ctx->lexer, "ct_mark")) { if (!lexer_force_match(ctx->lexer, LEX_T_EQUALS)) { return; @@ -737,6 +738,10 @@ parse_ct_commit_v1_arg(struct action_context *ctx, return; } lexer_get(ctx->lexer); + } else if (lexer_match_id(ctx->lexer, "snat")) { + cc->zone = OVNACT_CT_ZONE_SNAT; + } else if (lexer_match_id(ctx->lexer, "dnat")) { + cc->zone = OVNACT_CT_ZONE_DNAT; } else { lexer_syntax_error(ctx->lexer, NULL); } @@ -814,7 +819,20 @@ encode_CT_COMMIT_V1(const struct ovnact_ct_commit_v1 *cc, struct ofpact_conntrack *ct = ofpact_put_CT(ofpacts); ct->flags = NX_CT_F_COMMIT; ct->recirc_table = NX_CT_RECIRC_NONE; - ct->zone_src.field = mf_from_id(MFF_LOG_CT_ZONE); + switch (cc->zone) { + case OVNACT_CT_ZONE_SNAT: + ct->zone_src.field = mf_from_id(MFF_LOG_SNAT_ZONE); + break; + + case OVNACT_CT_ZONE_DNAT: + ct->zone_src.field = mf_from_id(MFF_LOG_DNAT_ZONE); + break; + + case OVNACT_CT_ZONE_DEFAULT: + default: + ct->zone_src.field = mf_from_id(MFF_LOG_CT_ZONE); + break; + } ct->zone_src.ofs = 0; ct->zone_src.n_bits = 16; diff --git a/ovn-sb.xml b/ovn-sb.xml index ac4e585f2..66cb9747d 100644 --- a/ovn-sb.xml +++ b/ovn-sb.xml @@ -1405,6 +1405,8 @@
ct_commit { ct_mark=value[/mask]; };
ct_commit { ct_label=value[/mask]; };
ct_commit { ct_mark=value[/mask]; ct_label=value[/mask]; };
+
ct_commit(snat);
+
ct_commit(dnat);

Commit the flow to the connection tracking entry associated with it @@ -1421,6 +1423,13 @@ in order to have specific bits set.

+

+ Parameters ct_commit(snat) or ct_commit(dnat) + can be used to explicitly specify into which zone should be + connection committed. Without this parameter, the connection is + committed to the default zone for the Datapath. +

+

Note that if you want processing to continue in the next table, you must execute the next action after From patchwork Mon Mar 4 10:55:42 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Martin Kalcok X-Patchwork-Id: 1907506 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" (2048-bit key; unprotected) header.d=canonical.com header.i=@canonical.com header.a=rsa-sha256 header.s=20210705 header.b=bB0EQoZj; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=2605:bc80:3010::133; helo=smtp2.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org) Received: from smtp2.osuosl.org (smtp2.osuosl.org [IPv6:2605:bc80:3010::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 4TpFxG1lqsz23hX for ; Mon, 4 Mar 2024 21:56:18 +1100 (AEDT) Received: from localhost (localhost [127.0.0.1]) by smtp2.osuosl.org (Postfix) with ESMTP id 50CD640BAC; Mon, 4 Mar 2024 10:56:16 +0000 (UTC) X-Virus-Scanned: amavisd-new at osuosl.org Received: from smtp2.osuosl.org ([127.0.0.1]) by localhost (smtp2.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id IX0LBq5eD0W9; Mon, 4 Mar 2024 10:56:13 +0000 (UTC) X-Comment: SPF check N/A for local connections - client-ip=2605:bc80:3010:104::8cd3:938; helo=lists.linuxfoundation.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver= DKIM-Filter: OpenDKIM Filter v2.11.0 smtp2.osuosl.org AE46F40B9D Authentication-Results: smtp2.osuosl.org; dkim=fail reason="signature verification failed" (2048-bit key) header.d=canonical.com header.i=@canonical.com header.a=rsa-sha256 header.s=20210705 header.b=bB0EQoZj Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [IPv6:2605:bc80:3010:104::8cd3:938]) by smtp2.osuosl.org (Postfix) with ESMTPS id AE46F40B9D; Mon, 4 Mar 2024 10:56:12 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 8829DC0077; Mon, 4 Mar 2024 10:56:12 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp3.osuosl.org (smtp3.osuosl.org [140.211.166.136]) by lists.linuxfoundation.org (Postfix) with ESMTP id 2CC60C0037 for ; Mon, 4 Mar 2024 10:56:11 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp3.osuosl.org (Postfix) with ESMTP id 12F5960B8A for ; Mon, 4 Mar 2024 10:56:11 +0000 (UTC) X-Virus-Scanned: amavisd-new at osuosl.org Received: from smtp3.osuosl.org ([127.0.0.1]) by localhost (smtp3.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id Pd7fcU_EtdJw for ; Mon, 4 Mar 2024 10:56:09 +0000 (UTC) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=185.125.188.121; helo=smtp-relay-canonical-1.canonical.com; envelope-from=martin.kalcok@canonical.com; receiver= DMARC-Filter: OpenDMARC Filter v1.4.2 smtp3.osuosl.org E379D60B74 Authentication-Results: smtp3.osuosl.org; dmarc=pass (p=none dis=none) header.from=canonical.com DKIM-Filter: OpenDKIM Filter v2.11.0 smtp3.osuosl.org E379D60B74 Authentication-Results: smtp3.osuosl.org; dkim=pass (2048-bit key) header.d=canonical.com header.i=@canonical.com header.a=rsa-sha256 header.s=20210705 header.b=bB0EQoZj Received: from smtp-relay-canonical-1.canonical.com (smtp-relay-canonical-1.canonical.com [185.125.188.121]) by smtp3.osuosl.org (Postfix) with ESMTPS id E379D60B74 for ; Mon, 4 Mar 2024 10:56:08 +0000 (UTC) Received: from omen-desktop.. (178-143-45-10.static.orange.sk [178.143.45.10]) (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 smtp-relay-canonical-1.canonical.com (Postfix) with ESMTPSA id 944A94192C; Mon, 4 Mar 2024 10:56:06 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=canonical.com; s=20210705; t=1709549766; bh=zRKqTbeQ38D7cAGk1NpdS0rsfyzbYWtGxSCv+upOVN8=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=bB0EQoZja8Keek+PIEMD0z7633yl3fykKBECGz7NrVmfQd/1S2mLcpGLhZ7rerbq5 tqEkbMkzsMVysZhewX7JRGF3OwTTx9bs9jMWenYxvNqHYAwfnhBGSjQ7ASe/l3zWqb q/10Q/iCe8mUVKjOCR4YEXPsrj/JiACFigCzqXddSgIsR5s86k370Jhlmv5sG0hCDV hYQbUO2PXU7ainra581JVNsvmtJxnjvFhbGvBThdHb5Oej5Ssliuoq1mFaNe/EtwHZ LQHY6/PVj4trfpFW9CImKA5YGV72TvJUvzlkol/1GUz86wsMO1dQK6NSSThfH0Pu6I vN6uIBtZC00iQ== From: Martin Kalcok To: dev@openvswitch.org Date: Mon, 4 Mar 2024 11:55:42 +0100 Message-Id: <20240304105542.349690-2-martin.kalcok@canonical.com> X-Mailer: git-send-email 2.40.1 In-Reply-To: <20240304105542.349690-1-martin.kalcok@canonical.com> References: <20240304105542.349690-1-martin.kalcok@canonical.com> MIME-Version: 1.0 Subject: [ovs-dev] [Patch ovn 2/2] northd.c: Fix direct access to SNAT network on DR. 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" This change fixes bug that breaks ability of machines from external networks to communicate with machines in SNATed networks (specifically when using a Distributed router). Currently when a machine (S1) on an external network tries to talk over TCP with a machine (A1) in a network that has enabled SNAT, the connection is established successfully. However after the three-way handshake, any packets that come from the A1 machine will have their source address translated by the Distributed router, breaking the communication. Existing rule in `build_lrouter_out_snat_flow` that decides which packets should be SNATed already tries to avoid SNATing packets in reply direction with `(!ct.trk || !ct.rpl)`. However, previous stages in the distributed LR egress pipeline do not initiate the CT state. Additionally we need to commit new connections that originate from external networks into CT, so that the packets in the reply direction can be properly identified. Rationale: In my original RFC [0], there were questions about the motivation for fixing this issue. I'll try to summarize why I think this is a bug that should be fixed. 1. Current implementation for Distributed router already tries to avoid SNATing packets in the reply direction, it's just missing initialized CT states to make proper decisions. 2. This same scenario works with Gateway Router. I tested with following setup: foo -- R1 -- join - R3 -- alice | bar ----------R2 R1 is a Distributed router with SNAT for foo. R2 is a Gateway router with SNAT for bar. R3 is a Gateway router with no SNAT. Using 'alice1' as a client I was able to talk over TCP with 'bar1' but connection with 'foo1' failed. 3. Regarding security and "leaking" of internal IPs. Reading through RFC 4787 [1], 5382 [2] and their update in 7857 [3], the specifications do not seem to mandate that SNAT implementations must filter incoming traffic destined directly to the internal network. Sections like "5. Filtering Behavior" in 4787 and "4.3 Externally Initiated Connections" in 5382 describe only behavior for traffic destined to external IP/Port associated with NAT on the device that performs NAT. Besides, with the current implementation, it's already possible to scan the internal network with pings and TCP syn scanning. If an additional security is needed, ACLs can be used to filter out incomming traffic from external networks. 4. We do have customers/clouds that depend on this functionality. This is a scenario that used to work in Openstack with ML2/OVS and migrating those clouds to ML2/OVN would break it. [0]https://mail.openvswitch.org/pipermail/ovs-dev/2024-February/411670.html [1]https://datatracker.ietf.org/doc/html/rfc4787 [2]https://datatracker.ietf.org/doc/html/rfc5382 [3]https://datatracker.ietf.org/doc/html/rfc7857 Signed-off-by: Martin Kalcok --- northd/northd.c | 82 ++++++++++++++++++++++++++++++++++++----- northd/ovn-northd.8.xml | 29 +++++++++++++++ tests/ovn-northd.at | 15 +++++++- tests/system-ovn.at | 69 ++++++++++++++++++++++++++++++++++ 4 files changed, 185 insertions(+), 10 deletions(-) diff --git a/northd/northd.c b/northd/northd.c index 2c3560ce2..4b79b357c 100644 --- a/northd/northd.c +++ b/northd/northd.c @@ -14437,21 +14437,28 @@ build_lrouter_out_is_dnat_local(struct lflow_table *lflows, } static void -build_lrouter_out_snat_match(struct lflow_table *lflows, - const struct ovn_datapath *od, - const struct nbrec_nat *nat, struct ds *match, - bool distributed_nat, int cidr_bits, bool is_v6, - struct ovn_port *l3dgw_port, - struct lflow_ref *lflow_ref) +build_lrouter_out_snat_direction_match__(struct lflow_table *lflows, + const struct ovn_datapath *od, + const struct nbrec_nat *nat, + struct ds *match, + bool distributed_nat, int cidr_bits, + bool is_v6, + struct ovn_port *l3dgw_port, + struct lflow_ref *lflow_ref, + bool is_reverse) { ds_clear(match); - ds_put_format(match, "ip && ip%c.src == %s", is_v6 ? '6' : '4', + ds_put_format(match, "ip && ip%c.%s == %s", + is_v6 ? '6' : '4', + is_reverse ? "dst" : "src", nat->logical_ip); if (!od->is_gw_router) { /* Distributed router. */ - ds_put_format(match, " && outport == %s", l3dgw_port->json_key); + ds_put_format(match, " && %s == %s", + is_reverse ? "inport" : "outport", + l3dgw_port->json_key); if (od->n_l3dgw_ports) { ds_put_format(match, " && is_chassis_resident(\"%s\")", distributed_nat @@ -14462,11 +14469,37 @@ build_lrouter_out_snat_match(struct lflow_table *lflows, if (nat->allowed_ext_ips || nat->exempted_ext_ips) { lrouter_nat_add_ext_ip_match(od, lflows, match, nat, - is_v6, false, cidr_bits, + is_v6, is_reverse, cidr_bits, lflow_ref); } } +static void +build_lrouter_out_snat_match(struct lflow_table *lflows, + const struct ovn_datapath *od, + const struct nbrec_nat *nat, struct ds *match, + bool distributed_nat, int cidr_bits, bool is_v6, + struct ovn_port *l3dgw_port, + struct lflow_ref *lflow_ref) +{ + build_lrouter_out_snat_direction_match__(lflows, od, nat, match, + distributed_nat, cidr_bits, is_v6, + l3dgw_port, lflow_ref, false); +} + +static void +build_lrouter_out_snat_reverse_match(struct lflow_table *lflows, + const struct ovn_datapath *od, + const struct nbrec_nat *nat, struct ds *match, + bool distributed_nat, int cidr_bits, bool is_v6, + struct ovn_port *l3dgw_port, + struct lflow_ref *lflow_ref) +{ + build_lrouter_out_snat_direction_match__(lflows, od, nat, match, + distributed_nat, cidr_bits, is_v6, + l3dgw_port, lflow_ref, true); +} + static void build_lrouter_out_snat_stateless_flow(struct lflow_table *lflows, const struct ovn_datapath *od, @@ -14591,6 +14624,7 @@ build_lrouter_out_snat_flow(struct lflow_table *lflows, return; } + struct ds match_all_from_snat = DS_EMPTY_INITIALIZER; ds_clear(actions); /* The priority here is calculated such that the @@ -14600,6 +14634,7 @@ build_lrouter_out_snat_flow(struct lflow_table *lflows, build_lrouter_out_snat_match(lflows, od, nat, match, distributed_nat, cidr_bits, is_v6, l3dgw_port, lflow_ref); + ds_clone(&match_all_from_snat, match); if (!od->is_gw_router) { /* Distributed router. */ @@ -14624,6 +14659,35 @@ build_lrouter_out_snat_flow(struct lflow_table *lflows, priority, ds_cstr(match), ds_cstr(actions), &nat->header_, lflow_ref); + + /* 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 (!strcmp(nat->type, "snat") && !od->is_gw_router) { + /* For traffic that comes from SNAT network, initiate CT state before + * entering S_ROUTER_OUT_SNAT to allow matching on various CT states. + */ + ovn_lflow_add(lflows, od, S_ROUTER_OUT_POST_UNDNAT, 70, + ds_cstr(&match_all_from_snat), "ct_snat; ", + lflow_ref); + + build_lrouter_out_snat_reverse_match(lflows, od, nat, match, + distributed_nat, cidr_bits, is_v6, + l3dgw_port, lflow_ref); + + /* New traffic that goes into SNAT network is committed to CT to avoid + * SNAT-ing replies.*/ + ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, priority, + ds_cstr(match), "ct_snat;", + lflow_ref); + + ds_put_cstr(match, "&& ct.new"); + ovn_lflow_add(lflows, od, S_ROUTER_OUT_POST_SNAT, priority, + ds_cstr(match), "ct_commit(snat); next; ", + lflow_ref); + } + + ds_destroy(&match_all_from_snat); } static void diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml index 17b414144..ce6fd1270 100644 --- a/northd/ovn-northd.8.xml +++ b/northd/ovn-northd.8.xml @@ -4869,6 +4869,13 @@ nd_ns {

    +
  • + 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. +
  • +
  • A priority-50 logical flow is added that commits any untracked flows from the previous table lr_out_undnat for Gateway @@ -5061,6 +5068,18 @@ nd_ns {
  • +
  • + An additional flow is added for traffic that goes in opposite + direction (i.e. it enters a network with configured SNAT). Where the + flow above matched on 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. + +
  • +
  • A priority-0 logical flow with match 1 has actions next;. @@ -5072,6 +5091,16 @@ nd_ns {

    Packets reaching this table are processed according to the flows below:

      +
    • + Traffic that goes directly into a network configured with SNAT on + Distributed 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 "Egress Table 3: SNAT on Distributed + Routers". +
    • +
    • A priority-0 logical flow that matches all packets not already handled (match 1) and action next;. diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at index 0732486f3..a7597a975 100644 --- a/tests/ovn-northd.at +++ b/tests/ovn-northd.at @@ -1155,6 +1155,7 @@ AT_CAPTURE_FILE([crflows]) 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=161 , match=(ip && ip4.dst == 50.0.0.11 && inport == "DR-S1" && is_chassis_resident("cr-DR-S1") && ip4.src == $allowed_range), action=(ct_snat;) 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 && (!ct.trk || !ct.rpl)), action=(ct_snat(172.16.1.1);) ]) @@ -1185,6 +1186,7 @@ AT_CAPTURE_FILE([crflows2]) 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=120 , match=(nd_ns), action=(next;) + table=??(lr_out_snat ), priority=161 , match=(ip && ip4.dst == 50.0.0.11 && inport == "DR-S1" && is_chassis_resident("cr-DR-S1")), action=(ct_snat;) table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.16.1.1);) 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;) ]) @@ -1279,7 +1281,7 @@ AT_CHECK([grep -e "lr_out_snat" crflows5 | ovn_strip_lflows], [0], [dnl table=??(lr_out_snat ), priority=33 , match=(ip && ip4.src == 50.0.0.11 && ip4.dst == $allowed_range), action=(ip4.src=172.16.1.2; next;) ]) -# Stateful FIP with DISALLOWED_IPs +# 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 @@ -5590,12 +5592,16 @@ AT_CHECK([grep "lr_out_undnat" lr0flows | ovn_strip_lflows], [0], [dnl AT_CHECK([grep "lr_out_post_undnat" lr0flows | 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 == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat; ) + table=??(lr_out_post_undnat ), priority=70 , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat; ) ]) AT_CHECK([grep "lr_out_snat" lr0flows | 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 == 10.0.0.0/24 && inport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat;) table=??(lr_out_snat ), priority=153 , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.10);) + table=??(lr_out_snat ), priority=161 , match=(ip && ip4.dst == 10.0.0.10 && inport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat;) table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.30);) table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.20);) ]) @@ -5738,12 +5744,16 @@ AT_CHECK([grep "lr_out_undnat" lr0flows | ovn_strip_lflows], [0], [dnl AT_CHECK([grep "lr_out_post_undnat" lr0flows | 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 == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat; ) + table=??(lr_out_post_undnat ), priority=70 , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat; ) ]) AT_CHECK([grep "lr_out_snat" lr0flows | 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 == 10.0.0.0/24 && inport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat;) table=??(lr_out_snat ), priority=153 , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.10);) + table=??(lr_out_snat ), priority=161 , match=(ip && ip4.dst == 10.0.0.10 && inport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat;) table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.30);) table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.20);) ]) @@ -7643,6 +7653,9 @@ AT_CHECK([grep lr_in_unsnat lrflows | grep ct_snat | ovn_strip_lflows], [0], [dn ]) AT_CHECK([grep lr_out_snat lrflows | grep ct_snat | ovn_strip_lflows], [0], [dnl + table=??(lr_out_snat ), priority=161 , match=(ip && ip4.dst == 20.0.0.10 && inport == "DR-S1" && is_chassis_resident("cr-DR-S1")), action=(ct_snat;) + table=??(lr_out_snat ), priority=161 , match=(ip && ip4.dst == 20.0.0.10 && inport == "DR-S2" && is_chassis_resident("cr-DR-S2")), action=(ct_snat;) + table=??(lr_out_snat ), priority=161 , match=(ip && ip4.dst == 20.0.0.10 && inport == "DR-S3" && is_chassis_resident("cr-DR-S3")), action=(ct_snat;) table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.16.1.10);) table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S2" && is_chassis_resident("cr-DR-S2") && (!ct.trk || !ct.rpl)), action=(ct_snat(10.0.0.10);) table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S3" && is_chassis_resident("cr-DR-S3") && (!ct.trk || !ct.rpl)), action=(ct_snat(192.168.0.10);) diff --git a/tests/system-ovn.at b/tests/system-ovn.at index c22c7882f..80e9b7d0b 100644 --- a/tests/system-ovn.at +++ b/tests/system-ovn.at @@ -3572,6 +3572,40 @@ NS_CHECK_EXEC([foo1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.1 | FORMAT_PING], \ 3 packets transmitted, 3 received, 0% packet loss, time 0ms ]) + +# test_connectivity_from_ext takes parameters 'vm' and 'ip'. It tests +# icmp, udp and tcp connectivity from external network to the 'vm' on +# the specified 'ip'. +test_connectivity_from_ext() { + local vm=$1; shift + local ip=$1; shift + + # Start listening daemons for UDP and TCP connections + NETNS_DAEMONIZE($vm, [nc -l -u 1234], [nc-$vm-$ip-udp.pid]) + NETNS_DAEMONIZE($vm, [nc -l -k 1235], [nc-$vm-$ip-tcp.pid]) + + # Ensure that vm can be pinged on the specified IP + NS_CHECK_EXEC([alice1], [ping -q -c 3 -i 0.3 -w 2 $ip | FORMAT_PING], \ + [0], [dnl +3 packets transmitted, 3 received, 0% packet loss, time 0ms +]) + + # Perform two consecutive UDP connections to the specified IP + NS_CHECK_EXEC([alice1], [nc -u $ip 1234 -p 2000 -z]) + NS_CHECK_EXEC([alice1], [nc -u $ip 1234 -p 2000 -z]) + + # Send data over TCP connection to the specified IP + NS_CHECK_EXEC([alice1], [echo "TCP test" | nc --send-only $ip 1235]) +} + +# Test access from external network to the internal IP of a VM that +# has also configured DNAT +test_connectivity_from_ext foo1 192.168.1.2 + +# Test access from external network to the internal IP of a VM that +# does not have DNAT +test_connectivity_from_ext bar1 192.168.2.2 + OVS_WAIT_UNTIL([ total_pkts=$(cat ext-net.pcap | wc -l) test "${total_pkts}" = "3" @@ -3738,6 +3772,39 @@ sed -e 's/zone=[[0-9]]*/zone=/'], [0], [dnl icmpv6,orig=(src=fd12::2,dst=fd20::2,id=,type=128,code=0),reply=(src=fd20::2,dst=fd20::1,id=,type=129,code=0),zone= ]) +# test_connectivity_from_ext takes parameters 'vm' and 'ip'. It tests +# icmp, udp and tcp connectivity from external network to the 'vm' on +# the specified 'ip'. +test_connectivity_from_ext() { + local vm=$1; shift + local ip=$1; shift + + # Start listening daemons for UDP and TCP connections + NETNS_DAEMONIZE($vm, [nc -l -u 1234], [nc-$vm-$ip-udp.pid]) + NETNS_DAEMONIZE($vm, [nc -l -k 1235], [nc-$vm-$ip-tcp.pid]) + + # Ensure that vm can be pinged on the specified IP + NS_CHECK_EXEC([alice1], [ping -q -c 3 -i 0.3 -w 2 $ip | FORMAT_PING], \ + [0], [dnl +3 packets transmitted, 3 received, 0% packet loss, time 0ms +]) + + # Perform two consecutive UDP connections to the specified IP + NS_CHECK_EXEC([alice1], [nc -u $ip 1234 -p 2000 -z]) + NS_CHECK_EXEC([alice1], [nc -u $ip 1234 -p 2000 -z]) + + # Send data over TCP connection to the specified IP + NS_CHECK_EXEC([alice1], [echo "TCP test" | nc --send-only $ip 1235]) +} + +# Test access from external network to the internal IP of a VM that +# has also configured DNAT +test_connectivity_from_ext foo1 fd11::2 + +# Test access from external network to the internal IP of a VM that +# does not have DNAT +test_connectivity_from_ext bar1 fd12::2 + OVS_APP_EXIT_AND_WAIT([ovn-controller]) as ovn-sb @@ -3918,6 +3985,7 @@ NS_CHECK_EXEC([foo2], [ping -q -c 3 -i 0.3 -w 2 172.16.1.4 | FORMAT_PING], \ AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep icmp | FORMAT_CT(172.16.1.1) | \ sed -e 's/zone=[[0-9]]*/zone=/'], [0], [dnl icmp,orig=(src=172.16.1.1,dst=172.16.1.4,id=,type=8,code=0),reply=(src=192.168.2.2,dst=172.16.1.1,id=,type=0,code=0),zone= +icmp,orig=(src=172.16.1.1,dst=192.168.2.2,id=,type=8,code=0),reply=(src=192.168.2.2,dst=172.16.1.1,id=,type=0,code=0),zone= icmp,orig=(src=192.168.1.3,dst=172.16.1.4,id=,type=8,code=0),reply=(src=172.16.1.4,dst=172.16.1.1,id=,type=0,code=0),zone= ]) @@ -4086,6 +4154,7 @@ NS_CHECK_EXEC([foo2], [ping -q -c 3 -i 0.3 -w 2 fd20::4 | FORMAT_PING], \ AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd20::1) | \ sed -e 's/zone=[[0-9]]*/zone=/'], [0], [dnl icmpv6,orig=(src=fd11::3,dst=fd20::4,id=,type=128,code=0),reply=(src=fd20::4,dst=fd20::1,id=,type=129,code=0),zone= +icmpv6,orig=(src=fd20::1,dst=fd12::2,id=,type=128,code=0),reply=(src=fd12::2,dst=fd20::1,id=,type=129,code=0),zone= icmpv6,orig=(src=fd20::1,dst=fd20::4,id=,type=128,code=0),reply=(src=fd12::2,dst=fd20::1,id=,type=129,code=0),zone= ])