Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/1.1/patches/2221006/?format=api
{ "id": 2221006, "url": "http://patchwork.ozlabs.org/api/1.1/patches/2221006/?format=api", "web_url": "http://patchwork.ozlabs.org/project/openvswitch/patch/20260408170613.587902-4-aconole@redhat.com/", "project": { "id": 47, "url": "http://patchwork.ozlabs.org/api/1.1/projects/47/?format=api", "name": "Open vSwitch", "link_name": "openvswitch", "list_id": "ovs-dev.openvswitch.org", "list_email": "ovs-dev@openvswitch.org", "web_url": "http://openvswitch.org/", "scm_url": "git@github.com:openvswitch/ovs.git", "webscm_url": "https://github.com/openvswitch/ovs" }, "msgid": "<20260408170613.587902-4-aconole@redhat.com>", "date": "2026-04-08T17:05:59", "name": "[ovs-dev,RFC,03/12] conntrack: Split the FTP and TFTP handling into separate files.", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "e7bdd184cb4ed3133a2986c7d839bb5cf089f822", "submitter": { "id": 67184, "url": "http://patchwork.ozlabs.org/api/1.1/people/67184/?format=api", "name": "Aaron Conole", "email": "aconole@redhat.com" }, "delegate": null, "mbox": "http://patchwork.ozlabs.org/project/openvswitch/patch/20260408170613.587902-4-aconole@redhat.com/mbox/", "series": [ { "id": 499163, "url": "http://patchwork.ozlabs.org/api/1.1/series/499163/?format=api", "web_url": "http://patchwork.ozlabs.org/project/openvswitch/list/?series=499163", "date": "2026-04-08T17:05:56", "name": "ct-offload: Introduce a conntrack offload infrastructure.", "version": 1, "mbox": "http://patchwork.ozlabs.org/series/499163/mbox/" } ], "comments": "http://patchwork.ozlabs.org/api/patches/2221006/comments/", "check": "success", "checks": "http://patchwork.ozlabs.org/api/patches/2221006/checks/", "tags": {}, "headers": { "Return-Path": "<ovs-dev-bounces@openvswitch.org>", "X-Original-To": [ "incoming@patchwork.ozlabs.org", "dev@openvswitch.org" ], "Delivered-To": [ "patchwork-incoming@legolas.ozlabs.org", "ovs-dev@lists.linuxfoundation.org" ], "Authentication-Results": [ "legolas.ozlabs.org;\n\tdkim=fail reason=\"signature verification failed\" (1024-bit key;\n unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256\n header.s=mimecast20190719 header.b=BbJgsaac;\n\tdkim-atps=neutral", "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org\n (client-ip=2605:bc80:3010::133; helo=smtp2.osuosl.org;\n envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org)", "smtp2.osuosl.org;\n\tdkim=fail reason=\"signature verification failed\" (1024-bit key)\n header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256\n header.s=mimecast20190719 header.b=BbJgsaac", "smtp1.osuosl.org; dmarc=pass (p=quarantine dis=none)\n header.from=redhat.com", "smtp1.osuosl.org;\n dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com\n header.a=rsa-sha256 header.s=mimecast20190719 header.b=BbJgsaac" ], "Received": [ "from smtp2.osuosl.org (smtp2.osuosl.org [IPv6:2605:bc80:3010::133])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519 server-signature ECDSA (secp384r1) server-digest SHA384)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4frTxb4HFBz1xv0\n\tfor <incoming@patchwork.ozlabs.org>; Thu, 09 Apr 2026 03:06:43 +1000 (AEST)", "from localhost (localhost [127.0.0.1])\n\tby smtp2.osuosl.org (Postfix) with ESMTP id 28E904052C;\n\tWed, 8 Apr 2026 17:06:42 +0000 (UTC)", "from smtp2.osuosl.org ([127.0.0.1])\n by localhost (smtp2.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP\n id jYiIFYIyM84k; Wed, 8 Apr 2026 17:06:36 +0000 (UTC)", "from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56])\n\tby smtp2.osuosl.org (Postfix) with ESMTPS id 8DB9E404AE;\n\tWed, 8 Apr 2026 17:06:36 +0000 (UTC)", "from lf-lists.osuosl.org (localhost [127.0.0.1])\n\tby lists.linuxfoundation.org (Postfix) with ESMTP id 84C41C054A;\n\tWed, 8 Apr 2026 17:06:36 +0000 (UTC)", "from smtp1.osuosl.org (smtp1.osuosl.org [140.211.166.138])\n by lists.linuxfoundation.org (Postfix) with ESMTP id 01B2EC0549\n for <dev@openvswitch.org>; Wed, 8 Apr 2026 17:06:35 +0000 (UTC)", "from localhost (localhost [127.0.0.1])\n by smtp1.osuosl.org (Postfix) with ESMTP id 6C17A8266A\n for <dev@openvswitch.org>; Wed, 8 Apr 2026 17:06:33 +0000 (UTC)", "from smtp1.osuosl.org ([127.0.0.1])\n by localhost (smtp1.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP\n id x8_cEeoRP0Ac for <dev@openvswitch.org>;\n Wed, 8 Apr 2026 17:06:28 +0000 (UTC)", "from us-smtp-delivery-124.mimecast.com\n (us-smtp-delivery-124.mimecast.com [170.10.133.124])\n by smtp1.osuosl.org (Postfix) with ESMTPS id D81D0824DF\n for <dev@openvswitch.org>; Wed, 8 Apr 2026 17:06:27 +0000 (UTC)", "from mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com\n (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by\n relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3,\n cipher=TLS_AES_256_GCM_SHA384) id us-mta-634-T5ijwdhjNl-6eCzsyRDGLw-1; Wed,\n 08 Apr 2026 13:06:24 -0400", "from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com\n (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4])\n (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest\n SHA256)\n (No client certificate requested)\n by mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS\n id 51AEB18005B0; Wed, 8 Apr 2026 17:06:23 +0000 (UTC)", "from RHTRH0061144.redhat.com (unknown [10.22.89.172])\n by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP\n id 08C66300019F; Wed, 8 Apr 2026 17:06:20 +0000 (UTC)" ], "X-Virus-Scanned": [ "amavis at osuosl.org", "amavis at osuosl.org" ], "X-Comment": "SPF check N/A for local connections - client-ip=140.211.9.56;\n helo=lists.linuxfoundation.org;\n envelope-from=ovs-dev-bounces@openvswitch.org; receiver=<UNKNOWN> ", "DKIM-Filter": [ "OpenDKIM Filter v2.11.0 smtp2.osuosl.org 8DB9E404AE", "OpenDKIM Filter v2.11.0 smtp1.osuosl.org D81D0824DF" ], "Received-SPF": "Pass (mailfrom) identity=mailfrom; client-ip=170.10.133.124;\n helo=us-smtp-delivery-124.mimecast.com; envelope-from=aconole@redhat.com;\n receiver=<UNKNOWN>", "DMARC-Filter": "OpenDMARC Filter v1.4.2 smtp1.osuosl.org D81D0824DF", "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n s=mimecast20190719; t=1775667985;\n h=from:from:reply-to:subject:subject:date:date:message-id:message-id:\n to:to:cc:cc:mime-version:mime-version:content-type:content-type:\n content-transfer-encoding:content-transfer-encoding:\n in-reply-to:in-reply-to:references:references;\n bh=o4aZV4mhAAH0vGNNjfTJOSw+BFfad3311g7d++pW9z0=;\n b=BbJgsaacSb8bvoDGbrbK5o50d62tDSP1Ig0+iqISb5JKQpAtlfd3A/b08y9hVyNS6GqnVn\n Ne2jxGsMbTOcZ3x0jh9waJNGgVKOxcF+mAZrBc6Ycsig3xhggpUk4yCRaOrEtiSWRSW+Lo\n dvkIXT1A1pK0ECSu9Q8G64/uqHaa9yU=", "X-MC-Unique": "T5ijwdhjNl-6eCzsyRDGLw-1", "X-Mimecast-MFC-AGG-ID": "T5ijwdhjNl-6eCzsyRDGLw_1775667983", "To": "dev@openvswitch.org", "Date": "Wed, 8 Apr 2026 13:05:59 -0400", "Message-ID": "<20260408170613.587902-4-aconole@redhat.com>", "In-Reply-To": "<20260408170613.587902-1-aconole@redhat.com>", "References": "<20260408170613.587902-1-aconole@redhat.com>", "MIME-Version": "1.0", "X-Scanned-By": "MIMEDefang 3.4.1 on 10.30.177.4", "X-Mimecast-Spam-Score": "0", "X-Mimecast-MFC-PROC-ID": "AUlXwVd2lPq9vyipe6d9ji5KqwyYVUikpTYpLF-EK9I_1775667983", "X-Mimecast-Originator": "redhat.com", "Subject": "[ovs-dev] [RFC 03/12] conntrack: Split the FTP and TFTP handling\n into separate files.", "X-BeenThere": "ovs-dev@openvswitch.org", "X-Mailman-Version": "2.1.30", "Precedence": "list", "List-Id": "<ovs-dev.openvswitch.org>", "List-Unsubscribe": "<https://mail.openvswitch.org/mailman/options/ovs-dev>,\n <mailto:ovs-dev-request@openvswitch.org?subject=unsubscribe>", "List-Archive": "<http://mail.openvswitch.org/pipermail/ovs-dev/>", "List-Post": "<mailto:ovs-dev@openvswitch.org>", "List-Help": "<mailto:ovs-dev-request@openvswitch.org?subject=help>", "List-Subscribe": "<https://mail.openvswitch.org/mailman/listinfo/ovs-dev>,\n <mailto:ovs-dev-request@openvswitch.org?subject=subscribe>", "From": "Aaron Conole via dev <ovs-dev@openvswitch.org>", "Reply-To": "Aaron Conole <aconole@redhat.com>", "Cc": "Eli Britstein <elibr@nvidia.com>, Florian Westphal <fwestpha@redhat.com>,\n Flavio Leitner <fbl@redhat.com>", "Content-Type": "text/plain; charset=\"us-ascii\"", "Content-Transfer-Encoding": "7bit", "Errors-To": "ovs-dev-bounces@openvswitch.org", "Sender": "\"dev\" <ovs-dev-bounces@openvswitch.org>" }, "content": "The FTP and TFTP helpers were scattered all over the conntrack TU making\nreading the individual FTP parts a bit difficult. Now that the handling\nis more modular, split them out into their own files.\n\nSigned-off-by: Aaron Conole <aconole@redhat.com>\n---\n lib/automake.mk | 2 +\n lib/conntrack-ftp.c | 689 ++++++++++++++++++++++++++++++++++++++\n lib/conntrack-private.h | 42 +++\n lib/conntrack-tftp.c | 47 +++\n lib/conntrack.c | 718 +---------------------------------------\n 5 files changed, 786 insertions(+), 712 deletions(-)\n create mode 100644 lib/conntrack-ftp.c\n create mode 100644 lib/conntrack-tftp.c", "diff": "diff --git a/lib/automake.mk b/lib/automake.mk\nindex c6e988906f..933b71226b 100644\n--- a/lib/automake.mk\n+++ b/lib/automake.mk\n@@ -86,9 +86,11 @@ lib_libopenvswitch_la_SOURCES = \\\n \tlib/compiler.h \\\n \tlib/connectivity.c \\\n \tlib/connectivity.h \\\n+\tlib/conntrack-ftp.c \\\n \tlib/conntrack-icmp.c \\\n \tlib/conntrack-private.h \\\n \tlib/conntrack-tcp.c \\\n+\tlib/conntrack-tftp.c \\\n \tlib/conntrack-tp.c \\\n \tlib/conntrack-tp.h \\\n \tlib/conntrack-other.c \\\ndiff --git a/lib/conntrack-ftp.c b/lib/conntrack-ftp.c\nnew file mode 100644\nindex 0000000000..6ce17c9efe\n--- /dev/null\n+++ b/lib/conntrack-ftp.c\n@@ -0,0 +1,689 @@\n+/*\n+ * Copyright (c) 2015-2019 Nicira, Inc.\n+ * Copyright (c) 2026 Red Hat, Inc.\n+ *\n+ * Licensed under the Apache License, Version 2.0 (the \"License\");\n+ * you may not use this file except in compliance with the License.\n+ * You may obtain a copy of the License at:\n+ *\n+ * http://www.apache.org/licenses/LICENSE-2.0\n+ *\n+ * Unless required by applicable law or agreed to in writing, software\n+ * distributed under the License is distributed on an \"AS IS\" BASIS,\n+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+ * See the License for the specific language governing permissions and\n+ * limitations under the License.\n+ */\n+\n+#include <config.h>\n+\n+#include <ctype.h>\n+#include <sys/types.h>\n+#include <netinet/in.h>\n+#include <string.h>\n+\n+#include \"conntrack-private.h\"\n+#include \"csum.h\"\n+#include \"dp-packet.h\"\n+#include \"openvswitch/vlog.h\"\n+#include \"packets.h\"\n+#include \"unaligned.h\"\n+#include \"util.h\"\n+\n+VLOG_DEFINE_THIS_MODULE(conntrack_ftp);\n+\n+/* FTP ALG mode: whether the data connection is initiated by the client\n+ * (active) or the server (passive), and whether the session uses IPv6\n+ * extensions (EPRT/EPSV). */\n+enum ct_alg_mode {\n+ CT_FTP_MODE_ACTIVE,\n+ CT_FTP_MODE_PASSIVE,\n+ CT_TFTP_MODE,\n+};\n+\n+/* String buffer used for parsing FTP string messages.\n+ * This is sized about twice what is needed to leave some\n+ * margin of error. */\n+#define LARGEST_FTP_MSG_OF_INTEREST 128\n+/* FTP port string used in active mode. */\n+#define FTP_PORT_CMD \"PORT\"\n+/* FTP pasv string used in passive mode. */\n+#define FTP_PASV_REPLY_CODE \"227\"\n+/* FTP epsv string used in passive mode. */\n+#define FTP_EPSV_REPLY_CODE \"229\"\n+/* Maximum decimal digits for port in FTP command.\n+ * The port is represented as two 3 digit numbers with the\n+ * high part a multiple of 256. */\n+#define MAX_FTP_PORT_DGTS 3\n+\n+/* FTP extension EPRT string used for active mode. */\n+#define FTP_EPRT_CMD \"EPRT\"\n+/* FTP extension EPSV string used for passive mode. */\n+#define FTP_EPSV_REPLY \"EXTENDED PASSIVE\"\n+/* Maximum decimal digits for port in FTP extended command. */\n+#define MAX_EXT_FTP_PORT_DGTS 5\n+/* FTP extended command code for IPv4. */\n+#define FTP_AF_V4 '1'\n+/* FTP extended command code for IPv6. */\n+#define FTP_AF_V6 '2'\n+\n+static bool\n+is_ftp_ctl(const enum ct_alg_ctl_type ct_alg_ctl)\n+{\n+ return ct_alg_ctl == CT_ALG_CTL_FTP;\n+}\n+\n+static void\n+replace_substring(char *substr, size_t substr_size,\n+ size_t total_size, char *rep_str,\n+ size_t rep_str_size)\n+{\n+ memmove(substr + rep_str_size, substr + substr_size,\n+ total_size - substr_size);\n+ memcpy(substr, rep_str, rep_str_size);\n+}\n+\n+static void\n+repl_bytes(char *str, char c1, char c2, int max)\n+{\n+ while (*str) {\n+ if (*str == c1) {\n+ *str = c2;\n+\n+ if (--max == 0) {\n+ break;\n+ }\n+ }\n+ str++;\n+ }\n+}\n+\n+/* Replaces a substring in the packet and rewrites the packet\n+ * size to match. This function assumes the caller has verified\n+ * the lengths to prevent under/over flow. */\n+static void\n+modify_packet(struct dp_packet *pkt, char *pkt_str, size_t size,\n+ char *repl_str, size_t repl_size,\n+ uint32_t orig_used_size)\n+{\n+ replace_substring(pkt_str, size,\n+ (const char *) dp_packet_tail(pkt) - pkt_str,\n+ repl_str, repl_size);\n+ dp_packet_set_size(pkt, orig_used_size + (int) repl_size - (int) size);\n+}\n+\n+/* Replace IPV4 address in FTP message with NATed address. */\n+static int\n+repl_ftp_v4_addr(struct dp_packet *pkt, ovs_be32 v4_addr_rep,\n+ char *ftp_data_start,\n+ size_t addr_offset_from_ftp_data_start,\n+ size_t addr_size)\n+{\n+ enum { MAX_FTP_V4_NAT_DELTA = 8 };\n+\n+ /* EPSV mode. */\n+ if (addr_offset_from_ftp_data_start == 0 &&\n+ addr_size == 0) {\n+ return 0;\n+ }\n+\n+ /* Do conservative check for pathological MTU usage. */\n+ uint32_t orig_used_size = dp_packet_size(pkt);\n+ if (orig_used_size + MAX_FTP_V4_NAT_DELTA >\n+ dp_packet_get_allocated(pkt)) {\n+\n+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5);\n+ VLOG_WARN_RL(&rl, \"Unsupported effective MTU %u used with FTP V4\",\n+ dp_packet_get_allocated(pkt));\n+ return 0;\n+ }\n+\n+ char v4_addr_str[INET_ADDRSTRLEN] = {0};\n+ ovs_assert(inet_ntop(AF_INET, &v4_addr_rep, v4_addr_str,\n+ sizeof v4_addr_str));\n+ repl_bytes(v4_addr_str, '.', ',', 0);\n+ modify_packet(pkt, ftp_data_start + addr_offset_from_ftp_data_start,\n+ addr_size, v4_addr_str, strlen(v4_addr_str),\n+ orig_used_size);\n+ return (int) strlen(v4_addr_str) - (int) addr_size;\n+}\n+\n+static char *\n+skip_non_digits(char *str)\n+{\n+ while (!isdigit(*str) && *str != 0) {\n+ str++;\n+ }\n+ return str;\n+}\n+\n+static char *\n+terminate_number_str(char *str, uint8_t max_digits)\n+{\n+ uint8_t digits_found = 0;\n+ while (isdigit(*str) && digits_found <= max_digits) {\n+ str++;\n+ digits_found++;\n+ }\n+\n+ *str = 0;\n+ return str;\n+}\n+\n+static void\n+get_ftp_ctl_msg(struct dp_packet *pkt, char *ftp_msg)\n+{\n+ struct tcp_header *th = dp_packet_l4(pkt);\n+ char *tcp_hdr = (char *) th;\n+ uint32_t tcp_payload_len = dp_packet_get_tcp_payload_length(pkt);\n+ size_t tcp_payload_of_interest = MIN(tcp_payload_len,\n+ LARGEST_FTP_MSG_OF_INTEREST);\n+ size_t tcp_hdr_len = TCP_OFFSET(th->tcp_ctl) * 4;\n+\n+ ovs_strlcpy(ftp_msg, tcp_hdr + tcp_hdr_len,\n+ tcp_payload_of_interest);\n+}\n+\n+static enum ftp_ctl_pkt\n+detect_ftp_ctl_type(const struct conn_lookup_ctx *ctx,\n+ struct dp_packet *pkt)\n+{\n+ char ftp_msg[LARGEST_FTP_MSG_OF_INTEREST + 1] = {0};\n+ get_ftp_ctl_msg(pkt, ftp_msg);\n+\n+ if (ctx->key.dl_type == htons(ETH_TYPE_IPV6)) {\n+ if (strncasecmp(ftp_msg, FTP_EPRT_CMD, strlen(FTP_EPRT_CMD)) &&\n+ !strcasestr(ftp_msg, FTP_EPSV_REPLY)) {\n+ return CT_FTP_CTL_OTHER;\n+ }\n+ } else {\n+ if (strncasecmp(ftp_msg, FTP_PORT_CMD, strlen(FTP_PORT_CMD)) &&\n+ strncasecmp(ftp_msg, FTP_EPRT_CMD, strlen(FTP_EPRT_CMD)) &&\n+ strncasecmp(ftp_msg, FTP_PASV_REPLY_CODE,\n+ strlen(FTP_PASV_REPLY_CODE)) &&\n+ strncasecmp(ftp_msg, FTP_EPSV_REPLY_CODE,\n+ strlen(FTP_EPSV_REPLY_CODE))) {\n+ return CT_FTP_CTL_OTHER;\n+ }\n+ }\n+\n+ return CT_FTP_CTL_INTEREST;\n+}\n+\n+static enum ftp_ctl_pkt\n+process_ftp_ctl_v4(struct conntrack *ct,\n+ struct dp_packet *pkt,\n+ const struct conn *conn_for_expectation,\n+ ovs_be32 *v4_addr_rep,\n+ char **ftp_data_v4_start,\n+ size_t *addr_offset_from_ftp_data_start,\n+ size_t *addr_size)\n+{\n+ struct tcp_header *th = dp_packet_l4(pkt);\n+ size_t tcp_hdr_len = TCP_OFFSET(th->tcp_ctl) * 4;\n+ char *tcp_hdr = (char *) th;\n+ *ftp_data_v4_start = tcp_hdr + tcp_hdr_len;\n+ char ftp_msg[LARGEST_FTP_MSG_OF_INTEREST + 1] = {0};\n+ get_ftp_ctl_msg(pkt, ftp_msg);\n+ char *ftp = ftp_msg;\n+ struct in_addr ip_addr;\n+ enum ct_alg_mode mode;\n+ bool extended = false;\n+\n+ if (!strncasecmp(ftp, FTP_PORT_CMD, strlen(FTP_PORT_CMD))) {\n+ ftp = ftp_msg + strlen(FTP_PORT_CMD);\n+ mode = CT_FTP_MODE_ACTIVE;\n+ } else if (!strncasecmp(ftp, FTP_EPRT_CMD, strlen(FTP_EPRT_CMD))) {\n+ ftp = ftp_msg + strlen(FTP_EPRT_CMD);\n+ mode = CT_FTP_MODE_ACTIVE;\n+ extended = true;\n+ } else if (!strncasecmp(ftp, FTP_EPSV_REPLY_CODE,\n+ strlen(FTP_EPSV_REPLY_CODE))) {\n+ ftp = ftp_msg + strlen(FTP_EPSV_REPLY_CODE);\n+ mode = CT_FTP_MODE_PASSIVE;\n+ extended = true;\n+ } else {\n+ ftp = ftp_msg + strlen(FTP_PASV_REPLY_CODE);\n+ mode = CT_FTP_MODE_PASSIVE;\n+ }\n+\n+ /* Find first space. */\n+ ftp = strchr(ftp, ' ');\n+ if (!ftp) {\n+ return CT_FTP_CTL_INVALID;\n+ }\n+\n+ /* Find the first digit, after space. */\n+ ftp = skip_non_digits(ftp);\n+ if (*ftp == 0) {\n+ return CT_FTP_CTL_INVALID;\n+ }\n+\n+ /* EPRT, verify address family. */\n+ if (extended && mode == CT_FTP_MODE_ACTIVE) {\n+ if (ftp[0] != FTP_AF_V4 || isdigit(ftp[1])) {\n+ return CT_FTP_CTL_INVALID;\n+ }\n+\n+ ftp = skip_non_digits(ftp + 1);\n+ if (*ftp == 0) {\n+ return CT_FTP_CTL_INVALID;\n+ }\n+ }\n+\n+ if (!extended || mode == CT_FTP_MODE_ACTIVE) {\n+ char *ip_addr_start = ftp;\n+ *addr_offset_from_ftp_data_start = ip_addr_start - ftp_msg;\n+ repl_bytes(ftp, ',', '.', 3);\n+\n+ /* Advance to end of IP address, to terminate it. */\n+ while (*ftp) {\n+ if (!isdigit(*ftp) && *ftp != '.') {\n+ break;\n+ }\n+ ftp++;\n+ }\n+ *ftp = 0;\n+ ftp++;\n+\n+ int rc2 = inet_pton(AF_INET, ip_addr_start, &ip_addr);\n+ if (rc2 != 1) {\n+ return CT_FTP_CTL_INVALID;\n+ }\n+\n+ *addr_size = ftp - ip_addr_start - 1;\n+ } else {\n+ *addr_size = 0;\n+ *addr_offset_from_ftp_data_start = 0;\n+ }\n+\n+ char *save_ftp = ftp;\n+ uint16_t port_hs;\n+\n+ if (!extended) {\n+ ftp = terminate_number_str(ftp, MAX_FTP_PORT_DGTS);\n+ if (!ftp) {\n+ return CT_FTP_CTL_INVALID;\n+ }\n+ int value;\n+ if (!str_to_int(save_ftp, 10, &value)) {\n+ return CT_FTP_CTL_INVALID;\n+ }\n+\n+ /* This is derived from the L4 port maximum is 65535. */\n+ if (value > 255) {\n+ return CT_FTP_CTL_INVALID;\n+ }\n+\n+ port_hs = value;\n+ port_hs <<= 8;\n+\n+ /* Skip over comma. */\n+ ftp++;\n+ save_ftp = ftp;\n+ bool digit_found = false;\n+ while (isdigit(*ftp)) {\n+ ftp++;\n+ digit_found = true;\n+ }\n+ if (!digit_found) {\n+ return CT_FTP_CTL_INVALID;\n+ }\n+ *ftp = 0;\n+ if (!str_to_int(save_ftp, 10, &value)) {\n+ return CT_FTP_CTL_INVALID;\n+ }\n+\n+ if (value > 255) {\n+ return CT_FTP_CTL_INVALID;\n+ }\n+\n+ port_hs |= value;\n+ } else {\n+ ftp = terminate_number_str(ftp, MAX_EXT_FTP_PORT_DGTS);\n+ if (!ftp) {\n+ return CT_FTP_CTL_INVALID;\n+ }\n+ int value;\n+ if (!str_to_int(save_ftp, 10, &value)) {\n+ return CT_FTP_CTL_INVALID;\n+ }\n+ if (value > UINT16_MAX) {\n+ return CT_FTP_CTL_INVALID;\n+ }\n+ port_hs = (uint16_t) value;\n+ }\n+\n+ ovs_be16 port = htons(port_hs);\n+ ovs_be32 conn_ipv4_addr;\n+\n+ switch (mode) {\n+ case CT_FTP_MODE_ACTIVE:\n+ *v4_addr_rep =\n+ conn_for_expectation->key_node[CT_DIR_REV].key.dst.addr.ipv4;\n+ conn_ipv4_addr =\n+ conn_for_expectation->key_node[CT_DIR_FWD].key.src.addr.ipv4;\n+ break;\n+ case CT_FTP_MODE_PASSIVE:\n+ *v4_addr_rep =\n+ conn_for_expectation->key_node[CT_DIR_FWD].key.dst.addr.ipv4;\n+ conn_ipv4_addr =\n+ conn_for_expectation->key_node[CT_DIR_REV].key.src.addr.ipv4;\n+ break;\n+ case CT_TFTP_MODE:\n+ default:\n+ OVS_NOT_REACHED();\n+ }\n+\n+ if (!extended || mode == CT_FTP_MODE_ACTIVE) {\n+ ovs_be32 ftp_ipv4_addr;\n+ ftp_ipv4_addr = ip_addr.s_addr;\n+ /* Although most servers will block this exploit, there may be some\n+ * less well managed. */\n+ if (ftp_ipv4_addr != conn_ipv4_addr && ftp_ipv4_addr != *v4_addr_rep) {\n+ return CT_FTP_CTL_INVALID;\n+ }\n+ }\n+\n+ expectation_create(ct, port, conn_for_expectation,\n+ !!(pkt->md.ct_state & CS_REPLY_DIR), false, false);\n+ return CT_FTP_CTL_INTEREST;\n+}\n+\n+static char *\n+skip_ipv6_digits(char *str)\n+{\n+ while (isxdigit(*str) || *str == ':' || *str == '.') {\n+ str++;\n+ }\n+ return str;\n+}\n+\n+static enum ftp_ctl_pkt\n+process_ftp_ctl_v6(struct conntrack *ct,\n+ struct dp_packet *pkt,\n+ const struct conn *conn_for_exp,\n+ union ct_addr *v6_addr_rep, char **ftp_data_start,\n+ size_t *addr_offset_from_ftp_data_start,\n+ size_t *addr_size, enum ct_alg_mode *mode)\n+{\n+ struct tcp_header *th = dp_packet_l4(pkt);\n+ size_t tcp_hdr_len = TCP_OFFSET(th->tcp_ctl) * 4;\n+ char *tcp_hdr = (char *) th;\n+ char ftp_msg[LARGEST_FTP_MSG_OF_INTEREST + 1] = {0};\n+ get_ftp_ctl_msg(pkt, ftp_msg);\n+ *ftp_data_start = tcp_hdr + tcp_hdr_len;\n+ char *ftp = ftp_msg;\n+ struct in6_addr ip6_addr;\n+\n+ if (!strncasecmp(ftp, FTP_EPRT_CMD, strlen(FTP_EPRT_CMD))) {\n+ ftp = ftp_msg + strlen(FTP_EPRT_CMD);\n+ ftp = skip_non_digits(ftp);\n+ if (*ftp != FTP_AF_V6 || isdigit(ftp[1])) {\n+ return CT_FTP_CTL_INVALID;\n+ }\n+ /* Jump over delimiter. */\n+ ftp += 2;\n+\n+ memset(&ip6_addr, 0, sizeof ip6_addr);\n+ char *ip_addr_start = ftp;\n+ *addr_offset_from_ftp_data_start = ip_addr_start - ftp_msg;\n+ ftp = skip_ipv6_digits(ftp);\n+ *ftp = 0;\n+ *addr_size = ftp - ip_addr_start;\n+ int rc2 = inet_pton(AF_INET6, ip_addr_start, &ip6_addr);\n+ if (rc2 != 1) {\n+ return CT_FTP_CTL_INVALID;\n+ }\n+ ftp++;\n+ *mode = CT_FTP_MODE_ACTIVE;\n+ } else {\n+ ftp = ftp_msg + strcspn(ftp_msg, \"(\");\n+ ftp = skip_non_digits(ftp);\n+ if (!isdigit(*ftp)) {\n+ return CT_FTP_CTL_INVALID;\n+ }\n+\n+ /* Not used for passive mode. */\n+ *addr_offset_from_ftp_data_start = 0;\n+ *addr_size = 0;\n+\n+ *mode = CT_FTP_MODE_PASSIVE;\n+ }\n+\n+ char *save_ftp = ftp;\n+ ftp = terminate_number_str(ftp, MAX_EXT_FTP_PORT_DGTS);\n+ if (!ftp) {\n+ return CT_FTP_CTL_INVALID;\n+ }\n+\n+ int value;\n+ if (!str_to_int(save_ftp, 10, &value)) {\n+ return CT_FTP_CTL_INVALID;\n+ }\n+ if (value > CT_MAX_L4_PORT) {\n+ return CT_FTP_CTL_INVALID;\n+ }\n+\n+ uint16_t port_hs = value;\n+ ovs_be16 port = htons(port_hs);\n+\n+ switch (*mode) {\n+ case CT_FTP_MODE_ACTIVE:\n+ *v6_addr_rep = conn_for_exp->key_node[CT_DIR_REV].key.dst.addr;\n+ /* Although most servers will block this exploit, there may be some\n+ * less well managed. */\n+ if (memcmp(&ip6_addr, &v6_addr_rep->ipv6, sizeof ip6_addr) &&\n+ memcmp(&ip6_addr,\n+ &conn_for_exp->key_node[CT_DIR_FWD].key.src.addr.ipv6,\n+ sizeof ip6_addr)) {\n+ return CT_FTP_CTL_INVALID;\n+ }\n+ break;\n+ case CT_FTP_MODE_PASSIVE:\n+ *v6_addr_rep = conn_for_exp->key_node[CT_DIR_FWD].key.dst.addr;\n+ break;\n+ case CT_TFTP_MODE:\n+ default:\n+ OVS_NOT_REACHED();\n+ }\n+\n+ expectation_create(ct, port, conn_for_exp,\n+ !!(pkt->md.ct_state & CS_REPLY_DIR), false, false);\n+ return CT_FTP_CTL_INTEREST;\n+}\n+\n+static int\n+repl_ftp_v6_addr(struct dp_packet *pkt, union ct_addr v6_addr_rep,\n+ char *ftp_data_start,\n+ size_t addr_offset_from_ftp_data_start,\n+ size_t addr_size, enum ct_alg_mode mode)\n+{\n+ /* This is slightly bigger than really possible. */\n+ enum { MAX_FTP_V6_NAT_DELTA = 45 };\n+\n+ if (mode == CT_FTP_MODE_PASSIVE) {\n+ return 0;\n+ }\n+\n+ /* Do conservative check for pathological MTU usage. */\n+ uint32_t orig_used_size = dp_packet_size(pkt);\n+ if (orig_used_size + MAX_FTP_V6_NAT_DELTA >\n+ dp_packet_get_allocated(pkt)) {\n+\n+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5);\n+ VLOG_WARN_RL(&rl, \"Unsupported effective MTU %u used with FTP V6\",\n+ dp_packet_get_allocated(pkt));\n+ return 0;\n+ }\n+\n+ char v6_addr_str[INET6_ADDRSTRLEN] = {0};\n+ ovs_assert(inet_ntop(AF_INET6, &v6_addr_rep.ipv6, v6_addr_str,\n+ sizeof v6_addr_str));\n+ modify_packet(pkt, ftp_data_start + addr_offset_from_ftp_data_start,\n+ addr_size, v6_addr_str, strlen(v6_addr_str),\n+ orig_used_size);\n+ return (int) strlen(v6_addr_str) - (int) addr_size;\n+}\n+\n+/* Increment/decrement a TCP sequence number. */\n+static void\n+adj_seqnum(ovs_16aligned_be32 *val, int32_t inc)\n+{\n+ put_16aligned_be32(val, htonl(ntohl(get_16aligned_be32(val)) + inc));\n+}\n+\n+static void\n+handle_ftp_ctl(struct conntrack *ct, const struct conn_lookup_ctx *ctx,\n+ struct dp_packet *pkt, struct conn *ec, long long now,\n+ enum ftp_ctl_pkt ftp_ctl, bool nat)\n+{\n+ struct ip_header *l3_hdr = dp_packet_l3(pkt);\n+ ovs_be32 v4_addr_rep = 0;\n+ union ct_addr v6_addr_rep;\n+ size_t addr_offset_from_ftp_data_start = 0;\n+ size_t addr_size = 0;\n+ char *ftp_data_start;\n+ enum ct_alg_mode mode = CT_FTP_MODE_ACTIVE;\n+\n+ if (detect_ftp_ctl_type(ctx, pkt) != ftp_ctl) {\n+ return;\n+ }\n+\n+ struct ovs_16aligned_ip6_hdr *nh6 = dp_packet_l3(pkt);\n+ int64_t seq_skew = 0;\n+\n+ if (ftp_ctl == CT_FTP_CTL_INTEREST) {\n+ enum ftp_ctl_pkt rc;\n+ if (ctx->key.dl_type == htons(ETH_TYPE_IPV6)) {\n+ rc = process_ftp_ctl_v6(ct, pkt, ec,\n+ &v6_addr_rep, &ftp_data_start,\n+ &addr_offset_from_ftp_data_start,\n+ &addr_size, &mode);\n+ } else {\n+ rc = process_ftp_ctl_v4(ct, pkt, ec,\n+ &v4_addr_rep, &ftp_data_start,\n+ &addr_offset_from_ftp_data_start,\n+ &addr_size);\n+ }\n+ if (rc == CT_FTP_CTL_INVALID) {\n+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5);\n+ VLOG_WARN_RL(&rl, \"Invalid FTP control packet format\");\n+ pkt->md.ct_state |= CS_TRACKED | CS_INVALID;\n+ return;\n+ } else if (rc == CT_FTP_CTL_INTEREST) {\n+ uint16_t ip_len;\n+\n+ if (ctx->key.dl_type == htons(ETH_TYPE_IPV6)) {\n+ if (nat) {\n+ seq_skew = repl_ftp_v6_addr(pkt, v6_addr_rep,\n+ ftp_data_start,\n+ addr_offset_from_ftp_data_start,\n+ addr_size, mode);\n+ }\n+\n+ if (seq_skew) {\n+ ip_len = ntohs(nh6->ip6_ctlun.ip6_un1.ip6_un1_plen) +\n+ seq_skew;\n+ nh6->ip6_ctlun.ip6_un1.ip6_un1_plen = htons(ip_len);\n+ }\n+ } else {\n+ if (nat) {\n+ seq_skew = repl_ftp_v4_addr(pkt, v4_addr_rep,\n+ ftp_data_start,\n+ addr_offset_from_ftp_data_start,\n+ addr_size);\n+ }\n+ if (seq_skew) {\n+ ip_len = ntohs(l3_hdr->ip_tot_len) + seq_skew;\n+ if (dp_packet_ip_checksum_valid(pkt)) {\n+ dp_packet_ip_checksum_set_partial(pkt);\n+ } else {\n+ l3_hdr->ip_csum = recalc_csum16(l3_hdr->ip_csum,\n+ l3_hdr->ip_tot_len,\n+ htons(ip_len));\n+ }\n+ l3_hdr->ip_tot_len = htons(ip_len);\n+ }\n+ }\n+ } else {\n+ OVS_NOT_REACHED();\n+ }\n+ }\n+\n+ struct tcp_header *th = dp_packet_l4(pkt);\n+\n+ if (nat && ec->seq_skew != 0) {\n+ ctx->reply != ec->seq_skew_dir ?\n+ adj_seqnum(&th->tcp_ack, -ec->seq_skew) :\n+ adj_seqnum(&th->tcp_seq, ec->seq_skew);\n+ }\n+\n+ if (dp_packet_l4_checksum_valid(pkt)) {\n+ dp_packet_l4_checksum_set_partial(pkt);\n+ } else {\n+ th->tcp_csum = 0;\n+ if (ctx->key.dl_type == htons(ETH_TYPE_IPV6)) {\n+ th->tcp_csum = packet_csum_upperlayer6(nh6, th, ctx->key.nw_proto,\n+ dp_packet_l4_size(pkt));\n+ } else {\n+ uint32_t tcp_csum = packet_csum_pseudoheader(l3_hdr);\n+ th->tcp_csum = csum_finish(\n+ csum_continue(tcp_csum, th, dp_packet_l4_size(pkt)));\n+ }\n+ }\n+\n+ if (seq_skew) {\n+ conn_seq_skew_set(ct, ec, now, seq_skew + ec->seq_skew,\n+ ctx->reply);\n+ }\n+}\n+\n+/* FTP requires sequence-number tracking to stay in sync with the source of\n+ * any sequence skew introduced by address/port rewriting. This hook\n+ * interleaves handle_ftp_ctl() calls with conn_update_state() depending on\n+ * packet direction so that the skew accounting is always correct. */\n+static bool\n+ftp_conn_update_state_hook(struct conntrack *ct, struct dp_packet *pkt,\n+ struct conn_lookup_ctx *ctx, struct conn *conn,\n+ const struct nat_action_info_t *nat_action_info,\n+ enum ct_alg_ctl_type ct_alg_ctl, long long now,\n+ bool *create_new_conn)\n+{\n+ if (!is_ftp_ctl(ct_alg_ctl)) {\n+ return false;\n+ }\n+\n+ /* Keep sequence tracking in sync with the source of the sequence skew. */\n+ ovs_mutex_lock(&conn->lock);\n+ if (ctx->reply != conn->seq_skew_dir) {\n+ handle_ftp_ctl(ct, ctx, pkt, conn, now, CT_FTP_CTL_OTHER,\n+ !!nat_action_info);\n+ /* conn_update_state acquires conn->lock for unrelated fields. */\n+ ovs_mutex_unlock(&conn->lock);\n+ *create_new_conn = conn_update_state(ct, pkt, ctx, conn, now);\n+ } else {\n+ ovs_mutex_unlock(&conn->lock);\n+ *create_new_conn = conn_update_state(ct, pkt, ctx, conn, now);\n+ ovs_mutex_lock(&conn->lock);\n+ if (!*create_new_conn) {\n+ handle_ftp_ctl(ct, ctx, pkt, conn, now, CT_FTP_CTL_OTHER,\n+ !!nat_action_info);\n+ }\n+ ovs_mutex_unlock(&conn->lock);\n+ }\n+ return true;\n+}\n+\n+void\n+conntrack_ftp_init(void)\n+{\n+ static struct ovsthread_once once = OVSTHREAD_ONCE_INITIALIZER;\n+\n+ if (ovsthread_once_start(&once)) {\n+ conn_update_state_hook_register(CT_HOOK_PRI_NORMAL,\n+ ftp_conn_update_state_hook);\n+ alg_helpers[CT_ALG_CTL_FTP] = handle_ftp_ctl;\n+ ovsthread_once_done(&once);\n+ }\n+}\ndiff --git a/lib/conntrack-private.h b/lib/conntrack-private.h\nindex a5bf1bb519..8eab1d3703 100644\n--- a/lib/conntrack-private.h\n+++ b/lib/conntrack-private.h\n@@ -177,6 +177,9 @@ enum ct_ephemeral_range {\n MAX_NAT_EPHEMERAL_PORT = 65535\n };\n \n+/* The maximum TCP or UDP port number. */\n+#define CT_MAX_L4_PORT 65535\n+\n #define IN_RANGE(curr, min, max) \\\n (curr >= min && curr <= max)\n \n@@ -261,6 +264,9 @@ enum ct_alg_ctl_type {\n /* SIP is not enabled through OpenFlow and is present only as an example\n * of an ALG that allows a wildcard source IP address. */\n CT_ALG_CTL_SIP,\n+\n+ /* MAX ALG */\n+ CT_ALG_CTL_MAX,\n };\n \n extern struct ct_l4_proto ct_proto_tcp;\n@@ -289,6 +295,28 @@ struct conn_lookup_ctx {\n bool icmp_related;\n };\n \n+/* FTP control-packet classification used by ALG helpers.\n+ * CT_FTP_CTL_INTEREST carries an address/port specifier (PORT, PASV, EPRT,\n+ * EPSV); CT_FTP_CTL_OTHER does not; CT_FTP_CTL_INVALID is malformed. */\n+enum ftp_ctl_pkt {\n+ CT_FTP_CTL_INTEREST,\n+ CT_FTP_CTL_OTHER,\n+ CT_FTP_CTL_INVALID,\n+};\n+\n+/* ALG helper callback signature. Each registered helper receives the\n+ * classified control-packet type so it can decide whether to act. */\n+typedef void (*alg_helper)(struct conntrack *ct,\n+ const struct conn_lookup_ctx *ctx,\n+ struct dp_packet *pkt,\n+ struct conn *conn_for_expectation,\n+ long long now, enum ftp_ctl_pkt ftp_ctl,\n+ bool nat);\n+\n+/* Array indexed by ct_alg_ctl_type; populated by per-module init functions\n+ * (conntrack_ftp_init, conntrack_tftp_init, ...) before first use. */\n+extern alg_helper alg_helpers[];\n+\n /* conn_update_state_dist() hook\n *\n * Modules may register a hook to intercept connection state transitions.\n@@ -323,6 +351,20 @@ void conn_update_state_hook_register(int priority,\n conn_update_state_hook_fn);\n void conn_update_state_hook_unregister(conn_update_state_hook_fn);\n \n+/* Functions in conntrack.c that ALG modules need. */\n+bool conn_update_state(struct conntrack *ct, struct dp_packet *pkt,\n+ struct conn_lookup_ctx *ctx, struct conn *conn,\n+ long long now);\n+void conn_seq_skew_set(struct conntrack *ct, const struct conn *conn_in,\n+ long long now, int seq_skew, bool seq_skew_dir);\n+void expectation_create(struct conntrack *ct, ovs_be16 dst_port,\n+ const struct conn *parent_conn, bool reply,\n+ bool src_ip_wc, bool skip_nat);\n+\n+/* ALG module initialization functions. */\n+void conntrack_ftp_init(void);\n+void conntrack_tftp_init(void);\n+\n /* conn_private_get() / conn_private_set()\n *\n * Fast-path accessors for per-connection private storage slots. Both\ndiff --git a/lib/conntrack-tftp.c b/lib/conntrack-tftp.c\nnew file mode 100644\nindex 0000000000..61297f7240\n--- /dev/null\n+++ b/lib/conntrack-tftp.c\n@@ -0,0 +1,47 @@\n+/*\n+ * Copyright (c) 2015-2019 Nicira, Inc.\n+ * Copyright (c) 2026 Red Hat, Inc.\n+ *\n+ * Licensed under the Apache License, Version 2.0 (the \"License\");\n+ * you may not use this file except in compliance with the License.\n+ * You may obtain a copy of the License at:\n+ *\n+ * http://www.apache.org/licenses/LICENSE-2.0\n+ *\n+ * Unless required by applicable law or agreed to in writing, software\n+ * distributed under the License is distributed on an \"AS IS\" BASIS,\n+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+ * See the License for the specific language governing permissions and\n+ * limitations under the License.\n+ */\n+\n+#include <config.h>\n+\n+#include \"conntrack-private.h\"\n+#include \"dp-packet.h\"\n+#include \"ovs-thread.h\"\n+#include \"packets.h\"\n+\n+static void\n+handle_tftp_ctl(struct conntrack *ct,\n+ const struct conn_lookup_ctx *ctx OVS_UNUSED,\n+ struct dp_packet *pkt, struct conn *conn_for_expectation,\n+ long long now OVS_UNUSED, enum ftp_ctl_pkt ftp_ctl OVS_UNUSED,\n+ bool nat OVS_UNUSED)\n+{\n+ expectation_create(ct,\n+ conn_for_expectation->key_node[CT_DIR_FWD].key.src.port,\n+ conn_for_expectation,\n+ !!(pkt->md.ct_state & CS_REPLY_DIR), false, false);\n+}\n+\n+void\n+conntrack_tftp_init(void)\n+{\n+ static struct ovsthread_once once = OVSTHREAD_ONCE_INITIALIZER;\n+\n+ if (ovsthread_once_start(&once)) {\n+ alg_helpers[CT_ALG_CTL_TFTP] = handle_tftp_ctl;\n+ ovsthread_once_done(&once);\n+ }\n+}\ndiff --git a/lib/conntrack.c b/lib/conntrack.c\nindex d81abe456a..462c0e0ad1 100644\n--- a/lib/conntrack.c\n+++ b/lib/conntrack.c\n@@ -55,20 +55,6 @@ COVERAGE_DEFINE(conntrack_l4csum_err);\n COVERAGE_DEFINE(conntrack_lookup_natted_miss);\n COVERAGE_DEFINE(conntrack_zone_full);\n \n-enum ftp_ctl_pkt {\n- /* Control packets with address and/or port specifiers. */\n- CT_FTP_CTL_INTEREST,\n- /* Control packets without address and/or port specifiers. */\n- CT_FTP_CTL_OTHER,\n- CT_FTP_CTL_INVALID,\n-};\n-\n-enum ct_alg_mode {\n- CT_FTP_MODE_ACTIVE,\n- CT_FTP_MODE_PASSIVE,\n- CT_TFTP_MODE,\n-};\n-\n struct zone_limit {\n struct cmap_node node;\n struct conntrack_zone_limit czl;\n@@ -117,24 +103,6 @@ static struct alg_exp_node *\n expectation_lookup(struct hmap *alg_expectations, const struct conn_key *key,\n uint32_t basis, bool src_ip_wc);\n \n-static int\n-repl_ftp_v4_addr(struct dp_packet *pkt, ovs_be32 v4_addr_rep,\n- char *ftp_data_v4_start,\n- size_t addr_offset_from_ftp_data_start, size_t addr_size);\n-\n-static enum ftp_ctl_pkt\n-process_ftp_ctl_v4(struct conntrack *ct,\n- struct dp_packet *pkt,\n- const struct conn *conn_for_expectation,\n- ovs_be32 *v4_addr_rep,\n- char **ftp_data_v4_start,\n- size_t *addr_offset_from_ftp_data_start,\n- size_t *addr_size);\n-\n-static enum ftp_ctl_pkt\n-detect_ftp_ctl_type(const struct conn_lookup_ctx *ctx,\n- struct dp_packet *pkt);\n-\n static void\n expectation_clean(struct conntrack *ct, const struct conn_key *parent_key);\n \n@@ -170,64 +138,8 @@ struct ct_update_hook {\n static struct ct_update_hook ct_update_hooks[CT_UPDATE_STATE_HOOKS_MAX];\n static size_t n_ct_update_hooks;\n \n-static bool ftp_conn_update_state_hook(struct conntrack *, struct dp_packet *,\n- struct conn_lookup_ctx *, struct conn *,\n- const struct nat_action_info_t *,\n- enum ct_alg_ctl_type, long long,\n- bool *);\n-\n-static void\n-handle_ftp_ctl(struct conntrack *ct, const struct conn_lookup_ctx *ctx,\n- struct dp_packet *pkt, struct conn *ec, long long now,\n- enum ftp_ctl_pkt ftp_ctl, bool nat);\n-\n-static void\n-handle_tftp_ctl(struct conntrack *ct,\n- const struct conn_lookup_ctx *ctx OVS_UNUSED,\n- struct dp_packet *pkt, struct conn *conn_for_expectation,\n- long long now OVS_UNUSED, enum ftp_ctl_pkt ftp_ctl OVS_UNUSED,\n- bool nat OVS_UNUSED);\n-\n-typedef void (*alg_helper)(struct conntrack *ct,\n- const struct conn_lookup_ctx *ctx,\n- struct dp_packet *pkt,\n- struct conn *conn_for_expectation,\n- long long now, enum ftp_ctl_pkt ftp_ctl,\n- bool nat);\n-\n-static alg_helper alg_helpers[] = {\n- [CT_ALG_CTL_NONE] = NULL,\n- [CT_ALG_CTL_FTP] = handle_ftp_ctl,\n- [CT_ALG_CTL_TFTP] = handle_tftp_ctl,\n-};\n+alg_helper alg_helpers[CT_ALG_CTL_MAX];\n \n-/* The maximum TCP or UDP port number. */\n-#define CT_MAX_L4_PORT 65535\n-/* String buffer used for parsing FTP string messages.\n- * This is sized about twice what is needed to leave some\n- * margin of error. */\n-#define LARGEST_FTP_MSG_OF_INTEREST 128\n-/* FTP port string used in active mode. */\n-#define FTP_PORT_CMD \"PORT\"\n-/* FTP pasv string used in passive mode. */\n-#define FTP_PASV_REPLY_CODE \"227\"\n-/* FTP epsv string used in passive mode. */\n-#define FTP_EPSV_REPLY_CODE \"229\"\n-/* Maximum decimal digits for port in FTP command.\n- * The port is represented as two 3 digit numbers with the\n- * high part a multiple of 256. */\n-#define MAX_FTP_PORT_DGTS 3\n-\n-/* FTP extension EPRT string used for active mode. */\n-#define FTP_EPRT_CMD \"EPRT\"\n-/* FTP extension EPSV string used for passive mode. */\n-#define FTP_EPSV_REPLY \"EXTENDED PASSIVE\"\n-/* Maximum decimal digits for port in FTP extended command. */\n-#define MAX_EXT_FTP_PORT_DGTS 5\n-/* FTP extended command code for IPv4. */\n-#define FTP_AF_V4 '1'\n-/* FTP extended command code for IPv6. */\n-#define FTP_AF_V6 '2'\n /* Used to indicate a wildcard L4 source port number for ALGs.\n * This is used for port numbers that we cannot predict in\n * expectations. */\n@@ -311,8 +223,8 @@ conntrack_init(void)\n l4_protos[IPPROTO_ICMP] = &ct_proto_icmp4;\n l4_protos[IPPROTO_ICMPV6] = &ct_proto_icmp6;\n \n- conn_update_state_hook_register(CT_HOOK_PRI_NORMAL,\n- ftp_conn_update_state_hook);\n+ conntrack_ftp_init();\n+ conntrack_tftp_init();\n \n ovsthread_once_done(&setup_l4_once);\n }\n@@ -835,12 +747,6 @@ get_ip_proto(const struct dp_packet *pkt)\n return ip_proto;\n }\n \n-static bool\n-is_ftp_ctl(const enum ct_alg_ctl_type ct_alg_ctl)\n-{\n- return ct_alg_ctl == CT_ALG_CTL_FTP;\n-}\n-\n static enum ct_alg_ctl_type\n get_alg_ctl_type(const struct dp_packet *pkt, const char *helper)\n {\n@@ -1044,7 +950,7 @@ nat_packet(struct dp_packet *pkt, struct conn *conn, bool reply, bool related)\n }\n }\n \n-static void\n+void\n conn_seq_skew_set(struct conntrack *ct, const struct conn *conn_in,\n long long now, int seq_skew, bool seq_skew_dir)\n {\n@@ -1202,7 +1108,7 @@ nat_res_exhaustion:\n return NULL;\n }\n \n-static bool\n+bool\n conn_update_state(struct conntrack *ct, struct dp_packet *pkt,\n struct conn_lookup_ctx *ctx, struct conn *conn,\n long long now)\n@@ -1322,38 +1228,6 @@ check_orig_tuple(struct conntrack *ct, struct dp_packet *pkt,\n return *conn ? true : false;\n }\n \n-static bool\n-ftp_conn_update_state_hook(struct conntrack *ct, struct dp_packet *pkt,\n- struct conn_lookup_ctx *ctx, struct conn *conn,\n- const struct nat_action_info_t *nat_action_info,\n- enum ct_alg_ctl_type ct_alg_ctl, long long now,\n- bool *create_new_conn)\n-{\n- if (!is_ftp_ctl(ct_alg_ctl)) {\n- return false;\n- }\n-\n- /* Keep sequence tracking in sync with the source of the sequence skew. */\n- ovs_mutex_lock(&conn->lock);\n- if (ctx->reply != conn->seq_skew_dir) {\n- handle_ftp_ctl(ct, ctx, pkt, conn, now, CT_FTP_CTL_OTHER,\n- !!nat_action_info);\n- /* conn_update_state acquires conn->lock for unrelated fields. */\n- ovs_mutex_unlock(&conn->lock);\n- *create_new_conn = conn_update_state(ct, pkt, ctx, conn, now);\n- } else {\n- ovs_mutex_unlock(&conn->lock);\n- *create_new_conn = conn_update_state(ct, pkt, ctx, conn, now);\n- ovs_mutex_lock(&conn->lock);\n- if (!*create_new_conn) {\n- handle_ftp_ctl(ct, ctx, pkt, conn, now, CT_FTP_CTL_OTHER,\n- !!nat_action_info);\n- }\n- ovs_mutex_unlock(&conn->lock);\n- }\n- return true;\n-}\n-\n /* Distribute a connection state-transition event to registered hooks.\n * Returns true if a hook handled the update (and set *create_new_conn),\n * false if the caller should fall through to default conn_update_state(). */\n@@ -3238,7 +3112,7 @@ expectation_clean(struct conntrack *ct, const struct conn_key *parent_key)\n ovs_rwlock_unlock(&ct->resources_lock);\n }\n \n-static void\n+void\n expectation_create(struct conntrack *ct, ovs_be16 dst_port,\n const struct conn *parent_conn, bool reply, bool src_ip_wc,\n bool skip_nat)\n@@ -3312,467 +3186,6 @@ expectation_create(struct conntrack *ct, ovs_be16 dst_port,\n ovs_rwlock_unlock(&ct->resources_lock);\n }\n \n-static void\n-replace_substring(char *substr, size_t substr_size,\n- size_t total_size, char *rep_str,\n- size_t rep_str_size)\n-{\n- memmove(substr + rep_str_size, substr + substr_size,\n- total_size - substr_size);\n- memcpy(substr, rep_str, rep_str_size);\n-}\n-\n-static void\n-repl_bytes(char *str, char c1, char c2, int max)\n-{\n- while (*str) {\n- if (*str == c1) {\n- *str = c2;\n-\n- if (--max == 0) {\n- break;\n- }\n- }\n- str++;\n- }\n-}\n-\n-/* Replaces a substring in the packet and rewrites the packet\n- * size to match. This function assumes the caller has verified\n- * the lengths to prevent under/over flow. */\n-static void\n-modify_packet(struct dp_packet *pkt, char *pkt_str, size_t size,\n- char *repl_str, size_t repl_size,\n- uint32_t orig_used_size)\n-{\n- replace_substring(pkt_str, size,\n- (const char *) dp_packet_tail(pkt) - pkt_str,\n- repl_str, repl_size);\n- dp_packet_set_size(pkt, orig_used_size + (int) repl_size - (int) size);\n-}\n-\n-/* Replace IPV4 address in FTP message with NATed address. */\n-static int\n-repl_ftp_v4_addr(struct dp_packet *pkt, ovs_be32 v4_addr_rep,\n- char *ftp_data_start,\n- size_t addr_offset_from_ftp_data_start,\n- size_t addr_size)\n-{\n- enum { MAX_FTP_V4_NAT_DELTA = 8 };\n-\n- /* EPSV mode. */\n- if (addr_offset_from_ftp_data_start == 0 &&\n- addr_size == 0) {\n- return 0;\n- }\n-\n- /* Do conservative check for pathological MTU usage. */\n- uint32_t orig_used_size = dp_packet_size(pkt);\n- if (orig_used_size + MAX_FTP_V4_NAT_DELTA >\n- dp_packet_get_allocated(pkt)) {\n-\n- static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5);\n- VLOG_WARN_RL(&rl, \"Unsupported effective MTU %u used with FTP V4\",\n- dp_packet_get_allocated(pkt));\n- return 0;\n- }\n-\n- char v4_addr_str[INET_ADDRSTRLEN] = {0};\n- ovs_assert(inet_ntop(AF_INET, &v4_addr_rep, v4_addr_str,\n- sizeof v4_addr_str));\n- repl_bytes(v4_addr_str, '.', ',', 0);\n- modify_packet(pkt, ftp_data_start + addr_offset_from_ftp_data_start,\n- addr_size, v4_addr_str, strlen(v4_addr_str),\n- orig_used_size);\n- return (int) strlen(v4_addr_str) - (int) addr_size;\n-}\n-\n-static char *\n-skip_non_digits(char *str)\n-{\n- while (!isdigit(*str) && *str != 0) {\n- str++;\n- }\n- return str;\n-}\n-\n-static char *\n-terminate_number_str(char *str, uint8_t max_digits)\n-{\n- uint8_t digits_found = 0;\n- while (isdigit(*str) && digits_found <= max_digits) {\n- str++;\n- digits_found++;\n- }\n-\n- *str = 0;\n- return str;\n-}\n-\n-\n-static void\n-get_ftp_ctl_msg(struct dp_packet *pkt, char *ftp_msg)\n-{\n- struct tcp_header *th = dp_packet_l4(pkt);\n- char *tcp_hdr = (char *) th;\n- uint32_t tcp_payload_len = dp_packet_get_tcp_payload_length(pkt);\n- size_t tcp_payload_of_interest = MIN(tcp_payload_len,\n- LARGEST_FTP_MSG_OF_INTEREST);\n- size_t tcp_hdr_len = TCP_OFFSET(th->tcp_ctl) * 4;\n-\n- ovs_strlcpy(ftp_msg, tcp_hdr + tcp_hdr_len,\n- tcp_payload_of_interest);\n-}\n-\n-static enum ftp_ctl_pkt\n-detect_ftp_ctl_type(const struct conn_lookup_ctx *ctx,\n- struct dp_packet *pkt)\n-{\n- char ftp_msg[LARGEST_FTP_MSG_OF_INTEREST + 1] = {0};\n- get_ftp_ctl_msg(pkt, ftp_msg);\n-\n- if (ctx->key.dl_type == htons(ETH_TYPE_IPV6)) {\n- if (strncasecmp(ftp_msg, FTP_EPRT_CMD, strlen(FTP_EPRT_CMD)) &&\n- !strcasestr(ftp_msg, FTP_EPSV_REPLY)) {\n- return CT_FTP_CTL_OTHER;\n- }\n- } else {\n- if (strncasecmp(ftp_msg, FTP_PORT_CMD, strlen(FTP_PORT_CMD)) &&\n- strncasecmp(ftp_msg, FTP_EPRT_CMD, strlen(FTP_EPRT_CMD)) &&\n- strncasecmp(ftp_msg, FTP_PASV_REPLY_CODE,\n- strlen(FTP_PASV_REPLY_CODE)) &&\n- strncasecmp(ftp_msg, FTP_EPSV_REPLY_CODE,\n- strlen(FTP_EPSV_REPLY_CODE))) {\n- return CT_FTP_CTL_OTHER;\n- }\n- }\n-\n- return CT_FTP_CTL_INTEREST;\n-}\n-\n-static enum ftp_ctl_pkt\n-process_ftp_ctl_v4(struct conntrack *ct,\n- struct dp_packet *pkt,\n- const struct conn *conn_for_expectation,\n- ovs_be32 *v4_addr_rep,\n- char **ftp_data_v4_start,\n- size_t *addr_offset_from_ftp_data_start,\n- size_t *addr_size)\n-{\n- struct tcp_header *th = dp_packet_l4(pkt);\n- size_t tcp_hdr_len = TCP_OFFSET(th->tcp_ctl) * 4;\n- char *tcp_hdr = (char *) th;\n- *ftp_data_v4_start = tcp_hdr + tcp_hdr_len;\n- char ftp_msg[LARGEST_FTP_MSG_OF_INTEREST + 1] = {0};\n- get_ftp_ctl_msg(pkt, ftp_msg);\n- char *ftp = ftp_msg;\n- struct in_addr ip_addr;\n- enum ct_alg_mode mode;\n- bool extended = false;\n-\n- if (!strncasecmp(ftp, FTP_PORT_CMD, strlen(FTP_PORT_CMD))) {\n- ftp = ftp_msg + strlen(FTP_PORT_CMD);\n- mode = CT_FTP_MODE_ACTIVE;\n- } else if (!strncasecmp(ftp, FTP_EPRT_CMD, strlen(FTP_EPRT_CMD))) {\n- ftp = ftp_msg + strlen(FTP_EPRT_CMD);\n- mode = CT_FTP_MODE_ACTIVE;\n- extended = true;\n- } else if (!strncasecmp(ftp, FTP_EPSV_REPLY_CODE,\n- strlen(FTP_EPSV_REPLY_CODE))) {\n- ftp = ftp_msg + strlen(FTP_EPSV_REPLY_CODE);\n- mode = CT_FTP_MODE_PASSIVE;\n- extended = true;\n- } else {\n- ftp = ftp_msg + strlen(FTP_PASV_REPLY_CODE);\n- mode = CT_FTP_MODE_PASSIVE;\n- }\n-\n- /* Find first space. */\n- ftp = strchr(ftp, ' ');\n- if (!ftp) {\n- return CT_FTP_CTL_INVALID;\n- }\n-\n- /* Find the first digit, after space. */\n- ftp = skip_non_digits(ftp);\n- if (*ftp == 0) {\n- return CT_FTP_CTL_INVALID;\n- }\n-\n- /* EPRT, verify address family. */\n- if (extended && mode == CT_FTP_MODE_ACTIVE) {\n- if (ftp[0] != FTP_AF_V4 || isdigit(ftp[1])) {\n- return CT_FTP_CTL_INVALID;\n- }\n-\n- ftp = skip_non_digits(ftp + 1);\n- if (*ftp == 0) {\n- return CT_FTP_CTL_INVALID;\n- }\n- }\n-\n- if (!extended || mode == CT_FTP_MODE_ACTIVE) {\n- char *ip_addr_start = ftp;\n- *addr_offset_from_ftp_data_start = ip_addr_start - ftp_msg;\n- repl_bytes(ftp, ',', '.', 3);\n-\n- /* Advance to end of IP address, to terminate it. */\n- while (*ftp) {\n- if (!isdigit(*ftp) && *ftp != '.') {\n- break;\n- }\n- ftp++;\n- }\n- *ftp = 0;\n- ftp++;\n-\n- int rc2 = inet_pton(AF_INET, ip_addr_start, &ip_addr);\n- if (rc2 != 1) {\n- return CT_FTP_CTL_INVALID;\n- }\n-\n- *addr_size = ftp - ip_addr_start - 1;\n- } else {\n- *addr_size = 0;\n- *addr_offset_from_ftp_data_start = 0;\n- }\n-\n- char *save_ftp = ftp;\n- uint16_t port_hs;\n-\n- if (!extended) {\n- ftp = terminate_number_str(ftp, MAX_FTP_PORT_DGTS);\n- if (!ftp) {\n- return CT_FTP_CTL_INVALID;\n- }\n- int value;\n- if (!str_to_int(save_ftp, 10, &value)) {\n- return CT_FTP_CTL_INVALID;\n- }\n-\n- /* This is derived from the L4 port maximum is 65535. */\n- if (value > 255) {\n- return CT_FTP_CTL_INVALID;\n- }\n-\n- port_hs = value;\n- port_hs <<= 8;\n-\n- /* Skip over comma. */\n- ftp++;\n- save_ftp = ftp;\n- bool digit_found = false;\n- while (isdigit(*ftp)) {\n- ftp++;\n- digit_found = true;\n- }\n- if (!digit_found) {\n- return CT_FTP_CTL_INVALID;\n- }\n- *ftp = 0;\n- if (!str_to_int(save_ftp, 10, &value)) {\n- return CT_FTP_CTL_INVALID;\n- }\n-\n- if (value > 255) {\n- return CT_FTP_CTL_INVALID;\n- }\n-\n- port_hs |= value;\n- } else {\n- ftp = terminate_number_str(ftp, MAX_EXT_FTP_PORT_DGTS);\n- if (!ftp) {\n- return CT_FTP_CTL_INVALID;\n- }\n- int value;\n- if (!str_to_int(save_ftp, 10, &value)) {\n- return CT_FTP_CTL_INVALID;\n- }\n- if (value > UINT16_MAX) {\n- return CT_FTP_CTL_INVALID;\n- }\n- port_hs = (uint16_t) value;\n- }\n-\n- ovs_be16 port = htons(port_hs);\n- ovs_be32 conn_ipv4_addr;\n-\n- switch (mode) {\n- case CT_FTP_MODE_ACTIVE:\n- *v4_addr_rep =\n- conn_for_expectation->key_node[CT_DIR_REV].key.dst.addr.ipv4;\n- conn_ipv4_addr =\n- conn_for_expectation->key_node[CT_DIR_FWD].key.src.addr.ipv4;\n- break;\n- case CT_FTP_MODE_PASSIVE:\n- *v4_addr_rep =\n- conn_for_expectation->key_node[CT_DIR_FWD].key.dst.addr.ipv4;\n- conn_ipv4_addr =\n- conn_for_expectation->key_node[CT_DIR_REV].key.src.addr.ipv4;\n- break;\n- case CT_TFTP_MODE:\n- default:\n- OVS_NOT_REACHED();\n- }\n-\n- if (!extended || mode == CT_FTP_MODE_ACTIVE) {\n- ovs_be32 ftp_ipv4_addr;\n- ftp_ipv4_addr = ip_addr.s_addr;\n- /* Although most servers will block this exploit, there may be some\n- * less well managed. */\n- if (ftp_ipv4_addr != conn_ipv4_addr && ftp_ipv4_addr != *v4_addr_rep) {\n- return CT_FTP_CTL_INVALID;\n- }\n- }\n-\n- expectation_create(ct, port, conn_for_expectation,\n- !!(pkt->md.ct_state & CS_REPLY_DIR), false, false);\n- return CT_FTP_CTL_INTEREST;\n-}\n-\n-static char *\n-skip_ipv6_digits(char *str)\n-{\n- while (isxdigit(*str) || *str == ':' || *str == '.') {\n- str++;\n- }\n- return str;\n-}\n-\n-static enum ftp_ctl_pkt\n-process_ftp_ctl_v6(struct conntrack *ct,\n- struct dp_packet *pkt,\n- const struct conn *conn_for_exp,\n- union ct_addr *v6_addr_rep, char **ftp_data_start,\n- size_t *addr_offset_from_ftp_data_start,\n- size_t *addr_size, enum ct_alg_mode *mode)\n-{\n- struct tcp_header *th = dp_packet_l4(pkt);\n- size_t tcp_hdr_len = TCP_OFFSET(th->tcp_ctl) * 4;\n- char *tcp_hdr = (char *) th;\n- char ftp_msg[LARGEST_FTP_MSG_OF_INTEREST + 1] = {0};\n- get_ftp_ctl_msg(pkt, ftp_msg);\n- *ftp_data_start = tcp_hdr + tcp_hdr_len;\n- char *ftp = ftp_msg;\n- struct in6_addr ip6_addr;\n-\n- if (!strncasecmp(ftp, FTP_EPRT_CMD, strlen(FTP_EPRT_CMD))) {\n- ftp = ftp_msg + strlen(FTP_EPRT_CMD);\n- ftp = skip_non_digits(ftp);\n- if (*ftp != FTP_AF_V6 || isdigit(ftp[1])) {\n- return CT_FTP_CTL_INVALID;\n- }\n- /* Jump over delimiter. */\n- ftp += 2;\n-\n- memset(&ip6_addr, 0, sizeof ip6_addr);\n- char *ip_addr_start = ftp;\n- *addr_offset_from_ftp_data_start = ip_addr_start - ftp_msg;\n- ftp = skip_ipv6_digits(ftp);\n- *ftp = 0;\n- *addr_size = ftp - ip_addr_start;\n- int rc2 = inet_pton(AF_INET6, ip_addr_start, &ip6_addr);\n- if (rc2 != 1) {\n- return CT_FTP_CTL_INVALID;\n- }\n- ftp++;\n- *mode = CT_FTP_MODE_ACTIVE;\n- } else {\n- ftp = ftp_msg + strcspn(ftp_msg, \"(\");\n- ftp = skip_non_digits(ftp);\n- if (!isdigit(*ftp)) {\n- return CT_FTP_CTL_INVALID;\n- }\n-\n- /* Not used for passive mode. */\n- *addr_offset_from_ftp_data_start = 0;\n- *addr_size = 0;\n-\n- *mode = CT_FTP_MODE_PASSIVE;\n- }\n-\n- char *save_ftp = ftp;\n- ftp = terminate_number_str(ftp, MAX_EXT_FTP_PORT_DGTS);\n- if (!ftp) {\n- return CT_FTP_CTL_INVALID;\n- }\n-\n- int value;\n- if (!str_to_int(save_ftp, 10, &value)) {\n- return CT_FTP_CTL_INVALID;\n- }\n- if (value > CT_MAX_L4_PORT) {\n- return CT_FTP_CTL_INVALID;\n- }\n-\n- uint16_t port_hs = value;\n- ovs_be16 port = htons(port_hs);\n-\n- switch (*mode) {\n- case CT_FTP_MODE_ACTIVE:\n- *v6_addr_rep = conn_for_exp->key_node[CT_DIR_REV].key.dst.addr;\n- /* Although most servers will block this exploit, there may be some\n- * less well managed. */\n- if (memcmp(&ip6_addr, &v6_addr_rep->ipv6, sizeof ip6_addr) &&\n- memcmp(&ip6_addr,\n- &conn_for_exp->key_node[CT_DIR_FWD].key.src.addr.ipv6,\n- sizeof ip6_addr)) {\n- return CT_FTP_CTL_INVALID;\n- }\n- break;\n- case CT_FTP_MODE_PASSIVE:\n- *v6_addr_rep = conn_for_exp->key_node[CT_DIR_FWD].key.dst.addr;\n- break;\n- case CT_TFTP_MODE:\n- default:\n- OVS_NOT_REACHED();\n- }\n-\n- expectation_create(ct, port, conn_for_exp,\n- !!(pkt->md.ct_state & CS_REPLY_DIR), false, false);\n- return CT_FTP_CTL_INTEREST;\n-}\n-\n-static int\n-repl_ftp_v6_addr(struct dp_packet *pkt, union ct_addr v6_addr_rep,\n- char *ftp_data_start,\n- size_t addr_offset_from_ftp_data_start,\n- size_t addr_size, enum ct_alg_mode mode)\n-{\n- /* This is slightly bigger than really possible. */\n- enum { MAX_FTP_V6_NAT_DELTA = 45 };\n-\n- if (mode == CT_FTP_MODE_PASSIVE) {\n- return 0;\n- }\n-\n- /* Do conservative check for pathological MTU usage. */\n- uint32_t orig_used_size = dp_packet_size(pkt);\n- if (orig_used_size + MAX_FTP_V6_NAT_DELTA >\n- dp_packet_get_allocated(pkt)) {\n-\n- static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5);\n- VLOG_WARN_RL(&rl, \"Unsupported effective MTU %u used with FTP V6\",\n- dp_packet_get_allocated(pkt));\n- return 0;\n- }\n-\n- char v6_addr_str[INET6_ADDRSTRLEN] = {0};\n- ovs_assert(inet_ntop(AF_INET6, &v6_addr_rep.ipv6, v6_addr_str,\n- sizeof v6_addr_str));\n- modify_packet(pkt, ftp_data_start + addr_offset_from_ftp_data_start,\n- addr_size, v6_addr_str, strlen(v6_addr_str),\n- orig_used_size);\n- return (int) strlen(v6_addr_str) - (int) addr_size;\n-}\n-\n-/* Increment/decrement a TCP sequence number. */\n-static void\n-adj_seqnum(ovs_16aligned_be32 *val, int32_t inc)\n-{\n- put_16aligned_be32(val, htonl(ntohl(get_16aligned_be32(val)) + inc));\n-}\n-\n void\n conn_update_state_hook_register(int priority, conn_update_state_hook_fn fn)\n {\n@@ -3801,122 +3214,3 @@ conn_update_state_hook_unregister(conn_update_state_hook_fn fn)\n }\n }\n }\n-\n-static void\n-handle_ftp_ctl(struct conntrack *ct, const struct conn_lookup_ctx *ctx,\n- struct dp_packet *pkt, struct conn *ec, long long now,\n- enum ftp_ctl_pkt ftp_ctl, bool nat)\n-{\n- struct ip_header *l3_hdr = dp_packet_l3(pkt);\n- ovs_be32 v4_addr_rep = 0;\n- union ct_addr v6_addr_rep;\n- size_t addr_offset_from_ftp_data_start = 0;\n- size_t addr_size = 0;\n- char *ftp_data_start;\n- enum ct_alg_mode mode = CT_FTP_MODE_ACTIVE;\n-\n- if (detect_ftp_ctl_type(ctx, pkt) != ftp_ctl) {\n- return;\n- }\n-\n- struct ovs_16aligned_ip6_hdr *nh6 = dp_packet_l3(pkt);\n- int64_t seq_skew = 0;\n-\n- if (ftp_ctl == CT_FTP_CTL_INTEREST) {\n- enum ftp_ctl_pkt rc;\n- if (ctx->key.dl_type == htons(ETH_TYPE_IPV6)) {\n- rc = process_ftp_ctl_v6(ct, pkt, ec,\n- &v6_addr_rep, &ftp_data_start,\n- &addr_offset_from_ftp_data_start,\n- &addr_size, &mode);\n- } else {\n- rc = process_ftp_ctl_v4(ct, pkt, ec,\n- &v4_addr_rep, &ftp_data_start,\n- &addr_offset_from_ftp_data_start,\n- &addr_size);\n- }\n- if (rc == CT_FTP_CTL_INVALID) {\n- static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5);\n- VLOG_WARN_RL(&rl, \"Invalid FTP control packet format\");\n- pkt->md.ct_state |= CS_TRACKED | CS_INVALID;\n- return;\n- } else if (rc == CT_FTP_CTL_INTEREST) {\n- uint16_t ip_len;\n-\n- if (ctx->key.dl_type == htons(ETH_TYPE_IPV6)) {\n- if (nat) {\n- seq_skew = repl_ftp_v6_addr(pkt, v6_addr_rep,\n- ftp_data_start,\n- addr_offset_from_ftp_data_start,\n- addr_size, mode);\n- }\n-\n- if (seq_skew) {\n- ip_len = ntohs(nh6->ip6_ctlun.ip6_un1.ip6_un1_plen) +\n- seq_skew;\n- nh6->ip6_ctlun.ip6_un1.ip6_un1_plen = htons(ip_len);\n- }\n- } else {\n- if (nat) {\n- seq_skew = repl_ftp_v4_addr(pkt, v4_addr_rep,\n- ftp_data_start,\n- addr_offset_from_ftp_data_start,\n- addr_size);\n- }\n- if (seq_skew) {\n- ip_len = ntohs(l3_hdr->ip_tot_len) + seq_skew;\n- if (dp_packet_ip_checksum_valid(pkt)) {\n- dp_packet_ip_checksum_set_partial(pkt);\n- } else {\n- l3_hdr->ip_csum = recalc_csum16(l3_hdr->ip_csum,\n- l3_hdr->ip_tot_len,\n- htons(ip_len));\n- }\n- l3_hdr->ip_tot_len = htons(ip_len);\n- }\n- }\n- } else {\n- OVS_NOT_REACHED();\n- }\n- }\n-\n- struct tcp_header *th = dp_packet_l4(pkt);\n-\n- if (nat && ec->seq_skew != 0) {\n- ctx->reply != ec->seq_skew_dir ?\n- adj_seqnum(&th->tcp_ack, -ec->seq_skew) :\n- adj_seqnum(&th->tcp_seq, ec->seq_skew);\n- }\n-\n- if (dp_packet_l4_checksum_valid(pkt)) {\n- dp_packet_l4_checksum_set_partial(pkt);\n- } else {\n- th->tcp_csum = 0;\n- if (ctx->key.dl_type == htons(ETH_TYPE_IPV6)) {\n- th->tcp_csum = packet_csum_upperlayer6(nh6, th, ctx->key.nw_proto,\n- dp_packet_l4_size(pkt));\n- } else {\n- uint32_t tcp_csum = packet_csum_pseudoheader(l3_hdr);\n- th->tcp_csum = csum_finish(\n- csum_continue(tcp_csum, th, dp_packet_l4_size(pkt)));\n- }\n- }\n-\n- if (seq_skew) {\n- conn_seq_skew_set(ct, ec, now, seq_skew + ec->seq_skew,\n- ctx->reply);\n- }\n-}\n-\n-static void\n-handle_tftp_ctl(struct conntrack *ct,\n- const struct conn_lookup_ctx *ctx OVS_UNUSED,\n- struct dp_packet *pkt, struct conn *conn_for_expectation,\n- long long now OVS_UNUSED, enum ftp_ctl_pkt ftp_ctl OVS_UNUSED,\n- bool nat OVS_UNUSED)\n-{\n- expectation_create(ct,\n- conn_for_expectation->key_node[CT_DIR_FWD].key.src.port,\n- conn_for_expectation,\n- !!(pkt->md.ct_state & CS_REPLY_DIR), false, false);\n-}\n", "prefixes": [ "ovs-dev", "RFC", "03/12" ] }