get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

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

{
    "id": 2221005,
    "url": "http://patchwork.ozlabs.org/api/1.1/patches/2221005/?format=api",
    "web_url": "http://patchwork.ozlabs.org/project/openvswitch/patch/20260408170613.587902-3-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-3-aconole@redhat.com>",
    "date": "2026-04-08T17:05:58",
    "name": "[ovs-dev,RFC,02/12] conntrack: Introduce an observer pattern infrastructure as a hook.",
    "commit_ref": null,
    "pull_url": null,
    "state": "new",
    "archived": false,
    "hash": "455ce4f237bc319426c9e54f62529589c1928913",
    "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-3-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/2221005/comments/",
    "check": "success",
    "checks": "http://patchwork.ozlabs.org/api/patches/2221005/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=K/jwgtDl;\n\tdkim-atps=neutral",
            "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org\n (client-ip=2605:bc80:3010::137; helo=smtp4.osuosl.org;\n envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org)",
            "smtp4.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=K/jwgtDl",
            "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=K/jwgtDl"
        ],
        "Received": [
            "from smtp4.osuosl.org (smtp4.osuosl.org [IPv6:2605:bc80:3010::137])\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 4frTxP4qGxz1xv0\n\tfor <incoming@patchwork.ozlabs.org>; Thu, 09 Apr 2026 03:06:33 +1000 (AEST)",
            "from localhost (localhost [127.0.0.1])\n\tby smtp4.osuosl.org (Postfix) with ESMTP id DFF9340FD2;\n\tWed,  8 Apr 2026 17:06:31 +0000 (UTC)",
            "from smtp4.osuosl.org ([127.0.0.1])\n by localhost (smtp4.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP\n id HHz3xrriH3ON; Wed,  8 Apr 2026 17:06:30 +0000 (UTC)",
            "from lists.linuxfoundation.org (lf-lists.osuosl.org\n [IPv6:2605:bc80:3010:104::8cd3:938])\n\tby smtp4.osuosl.org (Postfix) with ESMTPS id EC4F240FD1;\n\tWed,  8 Apr 2026 17:06:28 +0000 (UTC)",
            "from lf-lists.osuosl.org (localhost [127.0.0.1])\n\tby lists.linuxfoundation.org (Postfix) with ESMTP id 97307C0908;\n\tWed,  8 Apr 2026 17:06:28 +0000 (UTC)",
            "from smtp1.osuosl.org (smtp1.osuosl.org [IPv6:2605:bc80:3010::138])\n by lists.linuxfoundation.org (Postfix) with ESMTP id ADB26C0549\n for <dev@openvswitch.org>; Wed,  8 Apr 2026 17:06:27 +0000 (UTC)",
            "from localhost (localhost [127.0.0.1])\n by smtp1.osuosl.org (Postfix) with ESMTP id 946F78236B\n for <dev@openvswitch.org>; Wed,  8 Apr 2026 17:06:27 +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 YsfMnbJLu_C9 for <dev@openvswitch.org>;\n Wed,  8 Apr 2026 17:06:26 +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 5FBEC82287\n for <dev@openvswitch.org>; Wed,  8 Apr 2026 17:06:26 +0000 (UTC)",
            "from mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com\n (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by\n relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3,\n cipher=TLS_AES_256_GCM_SHA384) id us-mta-70-rGfthQJwPLCunhuJZ_hCFg-1; Wed,\n 08 Apr 2026 13:06:21 -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-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS\n id B7E8B1956096; Wed,  8 Apr 2026 17:06:20 +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 595BD30001BB; Wed,  8 Apr 2026 17:06:19 +0000 (UTC)"
        ],
        "X-Virus-Scanned": [
            "amavis at osuosl.org",
            "amavis at osuosl.org"
        ],
        "X-Comment": "SPF check N/A for local connections -\n client-ip=2605:bc80:3010:104::8cd3:938; helo=lists.linuxfoundation.org;\n envelope-from=ovs-dev-bounces@openvswitch.org; receiver=<UNKNOWN> ",
        "DKIM-Filter": [
            "OpenDKIM Filter v2.11.0 smtp4.osuosl.org EC4F240FD1",
            "OpenDKIM Filter v2.11.0 smtp1.osuosl.org 5FBEC82287"
        ],
        "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 5FBEC82287",
        "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=JtAFxhm8w4npgD70Ayu/PD8hRcujWZq9JkuXr5KbH08=;\n b=K/jwgtDlx6M+lN8v/xJL+OCLPBMIO6XGTmiggU5jiXwjeTVN/eJjk/Yc/EjcaZVspmH3Pn\n BiYlepTiJkQAxyR851GpXmuBOu981bjhYL1BftH09k5+m4bCiyi3Dsj/zXjXvoVdSIF+AC\n DDE1IJPtYh4FucKZ4pGRjnM6UkeeNk4=",
        "X-MC-Unique": "rGfthQJwPLCunhuJZ_hCFg-1",
        "X-Mimecast-MFC-AGG-ID": "rGfthQJwPLCunhuJZ_hCFg_1775667980",
        "To": "dev@openvswitch.org",
        "Date": "Wed,  8 Apr 2026 13:05:58 -0400",
        "Message-ID": "<20260408170613.587902-3-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": "yXaJu7CHH-r1gTKYNtx2fWOhNgix-8Ib_SLobCCCWT0_1775667980",
        "X-Mimecast-Originator": "redhat.com",
        "Subject": "[ovs-dev] [RFC 02/12] conntrack: Introduce an observer pattern\n infrastructure as a hook.",
        "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": "Conntrack has a number of places where it would be useful to monitor\nchanges.  Currently, these are hard coded spots for things like alg\nhandlers that need to fire when connections are added and transition\nso that we can monitor packets.  This leads to very difficult to read\ncode, with messy branches all over the place, and a bunch of unrelated\nfunctions mixed together.\n\nRename the conn_update_state_alg() function to conn_update_state_dist()\nand abstract away the FTP specific hook logic.  The original function\nrequired manual modification to add additional handlers, which we want\nto avoid and make more generic for future additions (which can include\nobservers for hardware offloads).  The hooks are priority based so\nthat some high priority hooks can run early, while later hooks that\nconsume the event can run later.\n\nThis infrastructure relies on the fact that there is only one global\nconntrack instance.  If the conntrack ever returns to allowing for\nmultiple instances, this will need to be re-abstracted.\n\nSigned-off-by: Aaron Conole <aconole@redhat.com>\n---\n lib/conntrack-private.h |  55 +++++++++++++++\n lib/conntrack.c         | 148 +++++++++++++++++++++++++++-------------\n 2 files changed, 157 insertions(+), 46 deletions(-)",
    "diff": "diff --git a/lib/conntrack-private.h b/lib/conntrack-private.h\nindex bd095277cd..a5bf1bb519 100644\n--- a/lib/conntrack-private.h\n+++ b/lib/conntrack-private.h\n@@ -252,6 +252,17 @@ struct conntrack {\n  *    3. 'resources_lock'\n  */\n \n+/* ALG control type identifiers.  These determine which application-layer\n+ * gateway helper applies to a given connection. */\n+enum ct_alg_ctl_type {\n+    CT_ALG_CTL_NONE,\n+    CT_ALG_CTL_FTP,\n+    CT_ALG_CTL_TFTP,\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+\n extern struct ct_l4_proto ct_proto_tcp;\n extern struct ct_l4_proto ct_proto_other;\n extern struct ct_l4_proto ct_proto_icmp4;\n@@ -268,6 +279,50 @@ struct ct_l4_proto {\n                                struct ct_dpif_protoinfo *);\n };\n \n+/* Transient lookup context built for each packet; private to conntrack.c and\n+ * the ALG helper modules. */\n+struct conn_lookup_ctx {\n+    struct conn_key key;\n+    struct conn *conn;\n+    uint32_t hash;\n+    bool reply;\n+    bool icmp_related;\n+};\n+\n+/* conn_update_state_dist() hook\n+ *\n+ * Modules may register a hook to intercept connection state transitions.\n+ * conn_update_state_dist() calls registered hooks in ascending priority order\n+ * until one returns true (meaning the hook handled the update, including any\n+ * call to conn_update_state() it needed to make).  If no hook claims the\n+ * packet the caller falls through to the default conn_update_state() path.\n+ *\n+ * Priority: lower numerical value -> higher precedence -> called first.\n+ * Named constants below cover the common cases; any int value is accepted.\n+ *\n+ * conn->lock is NOT held on entry; hooks must acquire it as needed following\n+ * the lock-ordering rules.\n+ *\n+ * Registration must happen before any connection is processed (i.e. at module\n+ * initialisation time, under ovsthread_once).\n+ */\n+typedef bool (*conn_update_state_hook_fn)(\n+    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+enum conn_update_state_hook_priority {\n+    CT_HOOK_PRI_HIGH   =  50,  /* Before built-in ALG handlers. */\n+    CT_HOOK_PRI_NORMAL = 100,  /* Default; used by built-in ALG handlers. */\n+    CT_HOOK_PRI_LOW    = 150,  /* After built-in ALG handlers. */\n+};\n+\n+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 /* conn_private_get() / conn_private_set()\n  *\n  * Fast-path accessors for per-connection private storage slots.  Both\ndiff --git a/lib/conntrack.c b/lib/conntrack.c\nindex 373c781eb9..d81abe456a 100644\n--- a/lib/conntrack.c\n+++ b/lib/conntrack.c\n@@ -55,14 +55,6 @@ COVERAGE_DEFINE(conntrack_l4csum_err);\n COVERAGE_DEFINE(conntrack_lookup_natted_miss);\n COVERAGE_DEFINE(conntrack_zone_full);\n \n-struct conn_lookup_ctx {\n-    struct conn_key key;\n-    struct conn *conn;\n-    uint32_t hash;\n-    bool reply;\n-    bool icmp_related;\n-};\n-\n enum ftp_ctl_pkt {\n     /* Control packets with address and/or port specifiers. */\n     CT_FTP_CTL_INTEREST,\n@@ -77,15 +69,6 @@ enum ct_alg_mode {\n     CT_TFTP_MODE,\n };\n \n-enum ct_alg_ctl_type {\n-    CT_ALG_CTL_NONE,\n-    CT_ALG_CTL_FTP,\n-    CT_ALG_CTL_TFTP,\n-    /* SIP is not enabled through Openflow and presently only used as\n-     * an example of an alg that allows a wildcard src ip. */\n-    CT_ALG_CTL_SIP,\n-};\n-\n struct zone_limit {\n     struct cmap_node node;\n     struct conntrack_zone_limit czl;\n@@ -170,6 +153,29 @@ struct ct_private_slot {\n static struct ct_private_slot ct_private_slots[CT_CONN_PRIVATE_MAX];\n static atomic_uint32_t ct_private_next_id = 0;\n \n+/* conn_update_state_dist() hook registry.\n+ *\n+ * Written once at module initialisation (under ovsthread_once), then\n+ * read-only during packet processing, so no additional locking is needed.\n+ * Entries are kept sorted by ascending priority so conn_update_state_dist()\n+ * can iterate in order without extra bookkeeping.\n+ */\n+#define CT_UPDATE_STATE_HOOKS_MAX 8\n+\n+struct ct_update_hook {\n+    int priority;\n+    conn_update_state_hook_fn fn;\n+};\n+\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@@ -305,6 +311,9 @@ 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+\n         ovsthread_once_done(&setup_l4_once);\n     }\n     return ct;\n@@ -1314,38 +1323,56 @@ check_orig_tuple(struct conntrack *ct, struct dp_packet *pkt,\n }\n \n static bool\n-conn_update_state_alg(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-        /* Keep sequence tracking in sync with the source of the\n-         * sequence skew. */\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 (ctx->reply != conn->seq_skew_dir) {\n+        if (!*create_new_conn) {\n             handle_ftp_ctl(ct, ctx, pkt, conn, now, CT_FTP_CTL_OTHER,\n                            !!nat_action_info);\n-            /* conn_update_state locks for unrelated fields, so unlock. */\n-            ovs_mutex_unlock(&conn->lock);\n-            *create_new_conn = conn_update_state(ct, pkt, ctx, conn, now);\n-        } else {\n-            /* conn_update_state locks for unrelated fields, so unlock. */\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 == false) {\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+        ovs_mutex_unlock(&conn->lock);\n     }\n-    return false;\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+static bool\n+conn_update_state_dist(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+    for (size_t i = 0; i < n_ct_update_hooks; i++) {\n+        if (ct_update_hooks[i].fn(ct, pkt, ctx, conn, nat_action_info,\n+                                  ct_alg_ctl, now, create_new_conn)) {\n+            return true;\n+        }\n+     }\n+     return false;\n+ }\n+\n static void\n set_cached_conn(const struct nat_action_info_t *nat_action_info,\n                 const struct conn_lookup_ctx *ctx, struct conn *conn,\n@@ -1450,10 +1477,10 @@ process_one(struct conntrack *ct, struct dp_packet *pkt,\n     enum ct_alg_ctl_type ct_alg_ctl = get_alg_ctl_type(pkt, helper);\n \n     if (OVS_LIKELY(conn)) {\n-        if (OVS_LIKELY(!conn_update_state_alg(ct, pkt, ctx, conn,\n-                                              nat_action_info,\n-                                              ct_alg_ctl, now,\n-                                              &create_new_conn))) {\n+        if (OVS_LIKELY(!conn_update_state_dist(ct, pkt, ctx, conn,\n+                                               nat_action_info,\n+                                               ct_alg_ctl, now,\n+                                               &create_new_conn))) {\n             create_new_conn = conn_update_state(ct, pkt, ctx, conn, now);\n         }\n         if (nat_action_info && !create_new_conn) {\n@@ -3746,6 +3773,35 @@ adj_seqnum(ovs_16aligned_be32 *val, int32_t inc)\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+    ovs_assert(n_ct_update_hooks < CT_UPDATE_STATE_HOOKS_MAX);\n+\n+    /* Insert in sorted order (ascending priority = higher precedence). */\n+    size_t i = n_ct_update_hooks;\n+    while (i > 0 && ct_update_hooks[i - 1].priority > priority) {\n+        ct_update_hooks[i] = ct_update_hooks[i - 1];\n+        i--;\n+    }\n+    ct_update_hooks[i].priority = priority;\n+    ct_update_hooks[i].fn = fn;\n+    n_ct_update_hooks++;\n+}\n+\n+void\n+conn_update_state_hook_unregister(conn_update_state_hook_fn fn)\n+{\n+    for (size_t i = 0; i < n_ct_update_hooks; i++) {\n+        if (ct_update_hooks[i].fn == fn) {\n+            memmove(&ct_update_hooks[i], &ct_update_hooks[i + 1],\n+                    (n_ct_update_hooks - i - 1) * sizeof ct_update_hooks[0]);\n+            n_ct_update_hooks--;\n+            return;\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",
    "prefixes": [
        "ovs-dev",
        "RFC",
        "02/12"
    ]
}