Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/1.1/patches/2221004/?format=api
{ "id": 2221004, "url": "http://patchwork.ozlabs.org/api/1.1/patches/2221004/?format=api", "web_url": "http://patchwork.ozlabs.org/project/openvswitch/patch/20260408170613.587902-2-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-2-aconole@redhat.com>", "date": "2026-04-08T17:05:57", "name": "[ovs-dev,RFC,01/12] conntrack: Add per-conn storage for conntrack modules.", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "ca9b7f4e569916af39d128f8afa28e9a01401de6", "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-2-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/2221004/comments/", "check": "success", "checks": "http://patchwork.ozlabs.org/api/patches/2221004/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=HTKe6ViG;\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=HTKe6ViG", "smtp2.osuosl.org; dmarc=pass (p=quarantine dis=none)\n header.from=redhat.com" ], "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 4frTxM2gkrz1xv0\n\tfor <incoming@patchwork.ozlabs.org>; Thu, 09 Apr 2026 03:06:31 +1000 (AEST)", "from localhost (localhost [127.0.0.1])\n\tby smtp2.osuosl.org (Postfix) with ESMTP id 4D55E40482;\n\tWed, 8 Apr 2026 17:06:29 +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 Vc1qQKBNcJFe; Wed, 8 Apr 2026 17:06:28 +0000 (UTC)", "from lists.linuxfoundation.org (lf-lists.osuosl.org\n [IPv6:2605:bc80:3010:104::8cd3:938])\n\tby smtp2.osuosl.org (Postfix) with ESMTPS id DED93400C1;\n\tWed, 8 Apr 2026 17:06:27 +0000 (UTC)", "from lf-lists.osuosl.org (localhost [127.0.0.1])\n\tby lists.linuxfoundation.org (Postfix) with ESMTP id BAFDBC0902;\n\tWed, 8 Apr 2026 17:06:27 +0000 (UTC)", "from smtp2.osuosl.org (smtp2.osuosl.org [140.211.166.133])\n by lists.linuxfoundation.org (Postfix) with ESMTP id 56A50C054A\n for <dev@openvswitch.org>; Wed, 8 Apr 2026 17:06:26 +0000 (UTC)", "from localhost (localhost [127.0.0.1])\n by smtp2.osuosl.org (Postfix) with ESMTP id 2E1B840054\n for <dev@openvswitch.org>; Wed, 8 Apr 2026 17:06:26 +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 j6N0yR8w84iP for <dev@openvswitch.org>;\n Wed, 8 Apr 2026 17:06:25 +0000 (UTC)", "from us-smtp-delivery-124.mimecast.com\n (us-smtp-delivery-124.mimecast.com [170.10.133.124])\n by smtp2.osuosl.org (Postfix) with ESMTPS id D4EE84003D\n for <dev@openvswitch.org>; Wed, 8 Apr 2026 17:06:24 +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-518--bM6X05gMKSHOwO1M0jqSA-1; Wed,\n 08 Apr 2026 13:06:20 -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 02A721800281; Wed, 8 Apr 2026 17:06:19 +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 5CC6C300019F; Wed, 8 Apr 2026 17:06:17 +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 smtp2.osuosl.org DED93400C1", "OpenDKIM Filter v2.11.0 smtp2.osuosl.org D4EE84003D" ], "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 smtp2.osuosl.org D4EE84003D", "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n s=mimecast20190719; t=1775667983;\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=niqN7jKf3mpf1VQ0kUJgQjCpBQom1xpKhRshIg2HTVs=;\n b=HTKe6ViGpWWkbz1X01uoSnagB3qKHGdzOZKjQCqygIF9HZgKhwjM8hR9WP4GTeMXs7k425\n 7aHoCDqB8MQak9mHy3FRoxoKj5bk6BnjxxI7f/M1vF6XF6EHRh/NjtXaF8AbQtT7rwNLYx\n 7SbAp4C7FEG0073HR9Gv4g1Im/DPM9Q=", "X-MC-Unique": "-bM6X05gMKSHOwO1M0jqSA-1", "X-Mimecast-MFC-AGG-ID": "-bM6X05gMKSHOwO1M0jqSA_1775667979", "To": "dev@openvswitch.org", "Date": "Wed, 8 Apr 2026 13:05:57 -0400", "Message-ID": "<20260408170613.587902-2-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": "hLZRPB3mq-LE6w2edNAjyhhSamzd1cHjeiYKsJospYw_1775667979", "X-Mimecast-Originator": "redhat.com", "Subject": "[ovs-dev] [RFC 01/12] conntrack: Add per-conn storage for conntrack\n modules.", "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": "Currently, if a conntrack submodule wants to add per-connection\nprivate details, the pattern looks like:\n\n struct private_conn {\n struct conn conn_;\n ... private data ...\n }\n\n ...\n\n new_conn = xalloc(sizeof struct private_conn);\n ...\n return &new_conn->conn_;\n\n ...\n\n struct private_conn *module_conn = (struct private_conn *)conn_;\n\nThis is a common pattern where the underlying allocations are\ndelegated to the submodule areas, and the main processing module\nalways assumes that each module allocates a conn_ storage area at the\nhead of the connection struct anyway.\n\nHowever, this means that some storage details can't be shared in a\nconenient way between modules without leaking details about the\nunderlying implementations of the module. For example, TCP based\nconnections may want to share some TCP block details, but not want to\nexpose the full private TCP connection module internals.\n\nTo facilitate this, introduce a private storage section into\nconnection objects. This will allow storing pre-defined details that\neach module can fill and guarantee some kind of compatibility without\nneeding to completely expose the internals. It will be used in\nupcoming commits.\n\nSigned-off-by: Aaron Conole <aconole@redhat.com>\n---\n lib/conntrack-private.h | 26 ++++++++\n lib/conntrack.c | 44 ++++++++++++++\n lib/conntrack.h | 39 ++++++++++++\n tests/library.at | 18 ++++++\n tests/test-conntrack.c | 130 +++++++++++++++++++++++++++++++++++++++-\n 5 files changed, 256 insertions(+), 1 deletion(-)", "diff": "diff --git a/lib/conntrack-private.h b/lib/conntrack-private.h\nindex f1132e8aa8..bd095277cd 100644\n--- a/lib/conntrack-private.h\n+++ b/lib/conntrack-private.h\n@@ -156,6 +156,10 @@ struct conn {\n bool alg_related; /* True if alg data connection. */\n \n uint32_t tp_id; /* Timeout policy ID. */\n+\n+ /* Private per-module storage. Indexed by ct_private_id_t values obtained\n+ * via conn_private_id_alloc(). Access is protected by conn->lock. */\n+ void *private[CT_CONN_PRIVATE_MAX];\n };\n \n enum ct_update_res {\n@@ -264,4 +268,26 @@ struct ct_l4_proto {\n struct ct_dpif_protoinfo *);\n };\n \n+/* conn_private_get() / conn_private_set()\n+ *\n+ * Fast-path accessors for per-connection private storage slots. Both\n+ * functions are static inlines so they compile to a single load/store with\n+ * bounds-checking asserts that disappear in release builds.\n+ *\n+ * The caller must hold conn->lock when accessing the pointer.\n+ */\n+static inline void *\n+conn_private_get(const struct conn *conn, ct_private_id_t id)\n+{\n+ ovs_assert(id < CT_CONN_PRIVATE_MAX);\n+ return conn->private[id];\n+}\n+\n+static inline void\n+conn_private_set(struct conn *conn, ct_private_id_t id, void *data)\n+{\n+ ovs_assert(id < CT_CONN_PRIVATE_MAX);\n+ conn->private[id] = data;\n+}\n+\n #endif /* conntrack-private.h */\ndiff --git a/lib/conntrack.c b/lib/conntrack.c\nindex e25cc25ca8..373c781eb9 100644\n--- a/lib/conntrack.c\n+++ b/lib/conntrack.c\n@@ -157,6 +157,19 @@ expectation_clean(struct conntrack *ct, const struct conn_key *parent_key);\n \n static struct ct_l4_proto *l4_protos[UINT8_MAX + 1];\n \n+/* Private per-connection storage slot registry.\n+ *\n+ * ct_private_slots[] is written once per slot at module initialization (via\n+ * conn_private_id_alloc()) and then read-only for the lifetime of the process,\n+ * so no additional locking is required to read the destructor pointer.\n+ */\n+struct ct_private_slot {\n+ void (*destructor)(void *); /* NULL means no cleanup required. */\n+};\n+\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 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@@ -607,6 +620,27 @@ conn_force_expire(struct conn *conn)\n atomic_store_relaxed(&conn->expiration, 0);\n }\n \n+ct_private_id_t\n+conn_private_id_alloc(void (*destructor)(void *))\n+{\n+ uint32_t id;\n+\n+ atomic_add(&ct_private_next_id, 1u, &id);\n+ if (id >= CT_CONN_PRIVATE_MAX) {\n+ /* Undo the increment so the counter doesn't overflow.\n+ * Because we are not suppoed to call this after ct initialization,\n+ * there shouldn't be an access race here. */\n+ atomic_sub(&ct_private_next_id, 1u, &id);\n+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);\n+ VLOG_ERR_RL(&rl, \"conntrack: all %d private storage slots are in use; \"\n+ \"cannot allocate a new one\", CT_CONN_PRIVATE_MAX);\n+ return CT_PRIVATE_ID_INVALID;\n+ }\n+\n+ ct_private_slots[id].destructor = destructor;\n+ return id;\n+}\n+\n /* Destroys the connection tracker 'ct' and frees all the allocated memory.\n * The caller of this function must already have shut down packet input\n * and PMD threads (which would have been quiesced). */\n@@ -2719,6 +2753,16 @@ new_conn(struct conntrack *ct, struct dp_packet *pkt, struct conn_key *key,\n static void\n delete_conn__(struct conn *conn)\n {\n+ uint32_t n;\n+\n+ /* Invoke registered destructors for any non-NULL private slots. */\n+ atomic_read_relaxed(&ct_private_next_id, &n);\n+ for (uint32_t i = 0; i < n; i++) {\n+ if (ct_private_slots[i].destructor && conn->private[i]) {\n+ ct_private_slots[i].destructor(conn->private[i]);\n+ }\n+ }\n+\n free(conn->alg);\n free(conn);\n }\ndiff --git a/lib/conntrack.h b/lib/conntrack.h\nindex c3136e9554..e5ca1528bf 100644\n--- a/lib/conntrack.h\n+++ b/lib/conntrack.h\n@@ -91,6 +91,45 @@ struct nat_action_info_t {\n uint16_t nat_flags;\n };\n \n+/* Private per-connection storage slots.\n+ *\n+ * Modules (protocol handlers, offload interfaces, etc.) can reserve a slot\n+ * at initialization time and use it to attach private data to every tracked\n+ * connection. Slot IDs are small integers that index directly into a fixed-\n+ * size array inside struct conn, so get/set operations are O(1) and branch-\n+ * free, safe to call on the datapath fast path.\n+ *\n+ * Usage\n+ * -----\n+ * // At module initialization, allocate and store the returned id.\n+ * static ct_private_id_t my_id;\n+ * my_id = conn_private_id_alloc(my_conn_data_free);\n+ *\n+ * // On the fast path (no lock needed beyond conn->lock for the pointer).\n+ * conn_private_set(conn, my_id, my_data);\n+ * my_data = conn_private_get(conn, my_id);\n+ *\n+ * Thread-safety\n+ * -------------\n+ * The pointer slot itself is protected by conn->lock. The pointed-to data\n+ * is the responsibility of the registering module.\n+ */\n+\n+/* Maximum number of private storage slots available per connection. */\n+#define CT_CONN_PRIVATE_MAX 8\n+\n+typedef unsigned int ct_private_id_t;\n+\n+/* Returned by conn_private_id_alloc() when no slots remain. */\n+#define CT_PRIVATE_ID_INVALID UINT_MAX\n+\n+/* Allocate a private storage slot. 'destructor' (may be NULL) is called with\n+ * the stored pointer when a connection is freed; it must be safe to call with\n+ * a NULL argument. Returns CT_PRIVATE_ID_INVALID on failure (all slots\n+ * taken). Must be called before any connection is created that should carry\n+ * this slot (i.e. at module initialization time). */\n+ct_private_id_t conn_private_id_alloc(void (*destructor)(void *));\n+\n struct conntrack *conntrack_init(void);\n void conntrack_destroy(struct conntrack *);\n \ndiff --git a/tests/library.at b/tests/library.at\nindex 449f15fd5a..6c5b55f045 100644\n--- a/tests/library.at\n+++ b/tests/library.at\n@@ -307,3 +307,21 @@ AT_CLEANUP\n AT_SETUP([Conntrack Library - FTP ALG parsing])\n AT_CHECK([ovstest test-conntrack ftp-alg-large-payload])\n AT_CLEANUP\n+\n+AT_SETUP([conntrack private storage - id alloc])\n+AT_KEYWORDS([conntrack])\n+AT_CHECK([ovstest test-conntrack private-id-alloc], [0], [.\n+])\n+AT_CLEANUP\n+\n+AT_SETUP([conntrack private storage - slot exhaustion])\n+AT_KEYWORDS([conntrack])\n+AT_CHECK([ovstest test-conntrack private-id-exhaustion], [0], [.........\n+], [ignore])\n+AT_CLEANUP\n+\n+AT_SETUP([conntrack private storage - destructor])\n+AT_KEYWORDS([conntrack])\n+AT_CHECK([ovstest test-conntrack private-destructor], [0], [.\n+])\n+AT_CLEANUP\ndiff --git a/tests/test-conntrack.c b/tests/test-conntrack.c\nindex 22db95f914..7f42adbb55 100644\n--- a/tests/test-conntrack.c\n+++ b/tests/test-conntrack.c\n@@ -16,6 +16,7 @@\n \n #include <config.h>\n #include \"conntrack.h\"\n+#include \"conntrack-private.h\"\n \n #include \"dp-packet.h\"\n #include \"fatal-signal.h\"\n@@ -492,7 +493,7 @@ test_pcap(struct ovs_cmdl_context *ctx)\n ovs_pcap_close(pcap);\n }\n \f\n-/* ALG related testing. */\n+/* Conntrack functional testing. */\n \n /* FTP IPv4 PORT payload for testing. */\n #define FTP_PORT_CMD_STR \"PORT 192,168,123,2,113,42\\r\\n\"\n@@ -572,6 +573,124 @@ test_ftp_alg_large_payload(struct ovs_cmdl_context *ctx OVS_UNUSED)\n conntrack_destroy(ct);\n }\n \n+/* Verify that conn_private_id_alloc() returns a valid slot ID and that the\n+ * idiomatic \"store the ID in a static variable at module init\" pattern works.\n+ */\n+static void\n+test_private_id_alloc(struct ovs_cmdl_context *ctx OVS_UNUSED)\n+{\n+ /* Mirrors the real-world pattern: a module stores its slot ID in a static\n+ * so it is initialised once and available everywhere in the translation\n+ * unit. */\n+ static ct_private_id_t my_id = CT_PRIVATE_ID_INVALID;\n+\n+ my_id = conn_private_id_alloc(NULL);\n+\n+ ovs_assert(my_id != CT_PRIVATE_ID_INVALID);\n+\n+ ovs_assert(my_id < CT_CONN_PRIVATE_MAX);\n+\n+ /* The first allocation must yield slot 0. */\n+ ovs_assert(my_id == 0);\n+ printf(\".\\n\");\n+}\n+\n+/* Allocate every available slot and confirm that the next request returns\n+ * CT_PRIVATE_ID_INVALID. Each successful allocation prints one dot so the\n+ * .at test can verify both the count and the error behaviour.\n+ */\n+static void\n+test_private_id_exhaustion(struct ovs_cmdl_context *ctx OVS_UNUSED)\n+{\n+ ct_private_id_t ids[CT_CONN_PRIVATE_MAX];\n+\n+ /* Fill all CT_CONN_PRIVATE_MAX slots. */\n+ for (unsigned int i = 0; i < CT_CONN_PRIVATE_MAX; i++) {\n+ ids[i] = conn_private_id_alloc(NULL);\n+ ovs_assert(ids[i] != CT_PRIVATE_ID_INVALID);\n+\n+ ovs_assert(ids[i] == i);\n+ printf(\".\");\n+ }\n+\n+ /* The very next allocation must fail. */\n+ ct_private_id_t extra = conn_private_id_alloc(NULL);\n+ ovs_assert(extra == CT_PRIVATE_ID_INVALID);\n+ printf(\".\\n\");\n+}\n+\n+/* Globals written by the destructor callback used in test 3. */\n+static int dtor_call_count = 0;\n+static void *dtor_last_ptr = NULL;\n+\n+static void\n+record_destructor(void *data)\n+{\n+ dtor_call_count++;\n+ dtor_last_ptr = data;\n+}\n+\n+/* Register a destructor, commit a real connection, attach a sentinel pointer\n+ * as private data, then destroy the conntrack instance. After draining the\n+ * RCU queue (ovsrcu_exit) the destructor must have been called exactly\n+ * once with the sentinel value.\n+ */\n+static uintptr_t ERRPTR;\n+\n+static void\n+test_private_destructor(struct ovs_cmdl_context *ctx OVS_UNUSED)\n+{\n+ /* Sentinel: a non-NULL pointer value we can identify unambiguously.\n+ * ERRPTR is defined above in case we want to use it in the future as\n+ * a platform-agnostic and portable sentinel value rather than some\n+ * hardcoded hex. */\n+ void *sentinel = (void *)(uintptr_t)&ERRPTR;\n+\n+ static ct_private_id_t dtor_id = CT_PRIVATE_ID_INVALID;\n+ dtor_id = conn_private_id_alloc(record_destructor);\n+ ovs_assert(dtor_id != CT_PRIVATE_ID_INVALID);\n+\n+ /* Create a conntrack instance and commit one UDP connection. */\n+ struct conntrack *lct = conntrack_init();\n+ ovs_be16 dl_type;\n+ struct dp_packet *pkt = build_packet(1, 2, &dl_type);\n+ struct dp_packet_batch batch;\n+ dp_packet_batch_init(&batch);\n+ dp_packet_batch_add(&batch, pkt);\n+\n+ long long now = time_msec();\n+ conntrack_execute(lct, &batch, dl_type, false, true, 0,\n+ NULL, NULL, NULL, NULL, now, 0);\n+\n+ /* After a committed execute the packet carries a cached conn pointer. */\n+ struct conn *conn = pkt->md.conn;\n+ ovs_assert(conn != NULL);\n+\n+ /* Attach the sentinel as private data for our slot. */\n+ ovs_mutex_lock(&conn->lock);\n+ conn_private_set(conn, dtor_id, sentinel);\n+ ovs_mutex_unlock(&conn->lock);\n+\n+ /* Destroying the tracker flushes all connections, queuing delete_conn()\n+ * callbacks via ovsrcu_postpone(). The destructor fires once those\n+ * callbacks are processed. */\n+ conntrack_destroy(lct);\n+\n+ /* ovsrcu_exit() stops the urcu background thread and synchronously drains\n+ * all pending postponed callbacks (including delete_conn__ / destructor\n+ * chain) before returning. ovsrcu_synchronize() is insufficient here: it\n+ * only waits for threads to quiesce, not for the urcu thread to have\n+ * actually executed the queued callbacks. */\n+ ovsrcu_exit();\n+\n+ ovs_assert(dtor_call_count == 1);\n+\n+ ovs_assert(dtor_last_ptr == sentinel);\n+\n+ dp_packet_delete_batch(&batch, true);\n+ printf(\".\\n\");\n+}\n+\n \f\n static const struct ovs_cmdl_command commands[] = {\n /* Connection tracker tests. */\n@@ -597,6 +716,15 @@ static const struct ovs_cmdl_command commands[] = {\n * is rewritten to the SNAT target rather than causing a crash. */\n {\"ftp-alg-large-payload\", \"\", 0, 0,\n test_ftp_alg_large_payload, OVS_RO},\n+ /* Private per-connection storage registry tests.\n+ * Each MUST be run as a separate ovstest invocation so the process-global\n+ * slot counter is fresh (starts at 0). */\n+ {\"private-id-alloc\", \"\", 0, 0,\n+ test_private_id_alloc, OVS_RO},\n+ {\"private-id-exhaustion\", \"\", 0, 0,\n+ test_private_id_exhaustion, OVS_RO},\n+ {\"private-destructor\", \"\", 0, 0,\n+ test_private_destructor, OVS_RO},\n \n {NULL, NULL, 0, 0, NULL, OVS_RO},\n };\n", "prefixes": [ "ovs-dev", "RFC", "01/12" ] }