get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

GET /api/1.1/patches/2221006/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "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"
    ]
}