Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/patches/2197249/?format=api
{ "id": 2197249, "url": "http://patchwork.ozlabs.org/api/patches/2197249/?format=api", "web_url": "http://patchwork.ozlabs.org/project/ovn/patch/20260217151255.1097146-1-erlon@canonical.com/", "project": { "id": 68, "url": "http://patchwork.ozlabs.org/api/projects/68/?format=api", "name": "Open Virtual Network development", "link_name": "ovn", "list_id": "ovs-dev.openvswitch.org", "list_email": "ovs-dev@openvswitch.org", "web_url": "http://openvswitch.org/", "scm_url": "", "webscm_url": "", "list_archive_url": "", "list_archive_url_format": "", "commit_url_format": "" }, "msgid": "<20260217151255.1097146-1-erlon@canonical.com>", "list_archive_url": null, "date": "2026-02-17T15:12:55", "name": "[ovs-dev,v5] controller: ACL correctly handles fragmented traffic.", "commit_ref": null, "pull_url": null, "state": "accepted", "archived": false, "hash": "bdc74dd8e6fa24abb46085763f1ed02353f41285", "submitter": { "id": 91397, "url": "http://patchwork.ozlabs.org/api/people/91397/?format=api", "name": "Erlon Rodrigues Cruz", "email": "erlon@canonical.com" }, "delegate": { "id": 132642, "url": "http://patchwork.ozlabs.org/api/users/132642/?format=api", "username": "amusil", "first_name": "Ales", "last_name": "Musil", "email": "amusil@redhat.com" }, "mbox": "http://patchwork.ozlabs.org/project/ovn/patch/20260217151255.1097146-1-erlon@canonical.com/mbox/", "series": [ { "id": 492439, "url": "http://patchwork.ozlabs.org/api/series/492439/?format=api", "web_url": "http://patchwork.ozlabs.org/project/ovn/list/?series=492439", "date": "2026-02-17T15:12:55", "name": "[ovs-dev,v5] controller: ACL correctly handles fragmented traffic.", "version": 5, "mbox": "http://patchwork.ozlabs.org/series/492439/mbox/" } ], "comments": "http://patchwork.ozlabs.org/api/patches/2197249/comments/", "check": "fail", "checks": "http://patchwork.ozlabs.org/api/patches/2197249/checks/", "tags": {}, "related": [], "headers": { "Return-Path": "<ovs-dev-bounces@openvswitch.org>", "X-Original-To": [ "incoming@patchwork.ozlabs.org", "ovs-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\" (4096-bit key;\n unprotected) header.d=canonical.com header.i=@canonical.com\n header.a=rsa-sha256 header.s=20251003 header.b=cT2nxn9q;\n\tdkim-atps=neutral", "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org\n (client-ip=2605:bc80:3010::138; helo=smtp1.osuosl.org;\n envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org)", "smtp1.osuosl.org;\n\tdkim=fail reason=\"signature verification failed\" (4096-bit key,\n unprotected) header.d=canonical.com header.i=@canonical.com\n header.a=rsa-sha256 header.s=20251003 header.b=cT2nxn9q", "smtp4.osuosl.org; dmarc=pass (p=reject dis=none)\n header.from=canonical.com", "smtp4.osuosl.org; dkim=pass (4096-bit key,\n unprotected) header.d=canonical.com header.i=@canonical.com\n header.a=rsa-sha256 header.s=20251003 header.b=cT2nxn9q" ], "Received": [ "from smtp1.osuosl.org (smtp1.osuosl.org [IPv6:2605:bc80:3010::138])\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 4fFjng3hF2z1xpl\n\tfor <incoming@patchwork.ozlabs.org>; Wed, 18 Feb 2026 02:13:11 +1100 (AEDT)", "from localhost (localhost [127.0.0.1])\n\tby smtp1.osuosl.org (Postfix) with ESMTP id 12F7783E5A;\n\tTue, 17 Feb 2026 15:13:10 +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 oe9jZ2bTlYw7; Tue, 17 Feb 2026 15:13:07 +0000 (UTC)", "from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56])\n\tby smtp1.osuosl.org (Postfix) with ESMTPS id A360381344;\n\tTue, 17 Feb 2026 15:13:07 +0000 (UTC)", "from lf-lists.osuosl.org (localhost [127.0.0.1])\n\tby lists.linuxfoundation.org (Postfix) with ESMTP id 709C5C0035;\n\tTue, 17 Feb 2026 15:13:07 +0000 (UTC)", "from smtp4.osuosl.org (smtp4.osuosl.org [140.211.166.137])\n by lists.linuxfoundation.org (Postfix) with ESMTP id 5D13EC0033\n for <ovs-dev@openvswitch.org>; Tue, 17 Feb 2026 15:13:06 +0000 (UTC)", "from localhost (localhost [127.0.0.1])\n by smtp4.osuosl.org (Postfix) with ESMTP id 4795940565\n for <ovs-dev@openvswitch.org>; Tue, 17 Feb 2026 15:13:06 +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 YSy-ZAD_c_0X for <ovs-dev@openvswitch.org>;\n Tue, 17 Feb 2026 15:13:04 +0000 (UTC)", "from smtp-relay-canonical-1.canonical.com\n (smtp-relay-canonical-1.canonical.com [185.125.188.121])\n by smtp4.osuosl.org (Postfix) with ESMTPS id 3DF1F40547\n for <ovs-dev@openvswitch.org>; Tue, 17 Feb 2026 15:13:02 +0000 (UTC)", "from erlon-tp.pihole (unknown [170.80.40.100])\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 smtp-relay-canonical-1.canonical.com (Postfix) with ESMTPSA id 4A5C04153A;\n Tue, 17 Feb 2026 15:12:59 +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 smtp1.osuosl.org A360381344", "OpenDKIM Filter v2.11.0 smtp4.osuosl.org 3DF1F40547" ], "Received-SPF": "Pass (mailfrom) identity=mailfrom; client-ip=185.125.188.121;\n helo=smtp-relay-canonical-1.canonical.com; envelope-from=erlon@canonical.com;\n receiver=<UNKNOWN>", "DMARC-Filter": "OpenDMARC Filter v1.4.2 smtp4.osuosl.org 3DF1F40547", "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed; d=canonical.com;\n s=20251003; t=1771341180;\n bh=Va43x0wBl2Sy5MYTbd9cHLJVo1HSWjboMyUQqMwTndE=;\n h=From:To:Cc:Subject:Date:Message-ID:MIME-Version;\n b=cT2nxn9qfL5Uf1gq9rLuQZZ80rT+YXaWC02CtD3imFJ3I3vcWU4ibw6AMxyghR0SD\n spde3f9NqwkzK3exiJpDE9jI0poKNBlhsLO/dB844+NSbZjamz2I6r6sEcvbLFGsEO\n oI00PKELJSc3KACzJ57353SkXpbEBtrdG78nSfzkuewqyGw9ggH3o+fK08NS+KfMx5\n uvNCwikWSrUBI43xDUkdFn7jAXOMmo9+GRQCLviBQ51h0p183pQ3+H4w7tTJ4BvQ9x\n uo/VB6IQZzdGrW3Ffw+lkth5ruHqLnsxc2iQUJ+CQ+I+34mTTcvxreCLlfodT2rv7G\n FgyaElcaf/JR65hNb+XpJ8BN3pLEZsutYmWqJKSVc4t3yRzgL8TtQtYXRGjjB2dant\n C0vhp0MZgpjNWWVT7WXsgdkWoBvGHA2A3Gl1kJ4XOMiAeSow3gbwitKRYW3erUZzd5\n scvw/HSsIbkIYA424IM58jl46zy+D3ZB7XaG2d8t+IyYPleg55fr5RgALIFWxSGPW7\n GZzNdoUJ6H93c/0JxC0D2xf+jV6nogWemERzPJQxExiyOI1wkVLRHWrGvVi9uQvVRY\n 7LKB989i1qShYVSgl/SzOqBz4yfjbPd8iIwaOV9g+BlttfDv4nzPgBOHJEpXP8emeP\n l2tHz7kgKo4jXWJaR4RdWHJw=", "To": "ovs-dev@openvswitch.org", "Date": "Tue, 17 Feb 2026 12:12:55 -0300", "Message-ID": "<20260217151255.1097146-1-erlon@canonical.com>", "X-Mailer": "git-send-email 2.43.0", "MIME-Version": "1.0", "Subject": "[ovs-dev] [PATCH ovn v5] controller: ACL correctly handles\n fragmented traffic.", "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": "\"Erlon R. Cruz via dev\" <ovs-dev@openvswitch.org>", "Reply-To": "\"Erlon R. Cruz\" <erlon@canonical.com>", "Cc": "erlon.rodrigues.cruz@canonical.com, dceara@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": "At the current state, OVN can not handle fragmented traffic for ACLs\nin the userspace datapath (DPDK). Just like in the case of LB\n(commit 20a96b9), the kernel DP will try to reassemble the fragments\nduring CT lookup, however userspace won't reassemble them.\n\nThis patch allows OVN to handle fragmented traffic by defining a\ntranslation table on southbound that leverages OpenFlow connection\ntracking capabilities. When a stateful flow is created on NB, we add\na hint in the flow. This hint will be read in SB and if the\nconnection tracking is set to be used, SB will use the alternative\ntranslation table that will use the connection tracking information.\n\nThis approach should not change the current behavior and it's only\nenabled if acl_ct_translation is set:\n\novn-nbctl set NB_Global . options:acl_ct_translation=true\n\nSigned-off-by: Erlon R. Cruz <erlon@canonical.com>\n---\nv2: Rebased on current upstream main, removed external python\ncode traffic generators, added scenario tests for: dhcp, negative\nudp and ovn rule generation, documentation and many code clean ups.\nDHCP traffic is being dropped for some reason. Still need to figure\nthat out for v3.\nv3: Rebased on current upstream main, clean ups. Fix DHCP/broadcast\nbug\nv4: Rebase, code cleanup\nv5: Rebase, code cleanup, make ovn_lflow_find acl_ct_translation aware\n---\n NEWS | 5 +\n controller/lflow.c | 23 +-\n include/ovn/logical-fields.h | 2 +\n lib/logical-fields.c | 67 ++++++\n lib/ovn-util.c | 10 +-\n lib/ovn-util.h | 3 +-\n northd/en-global-config.c | 5 +\n northd/lflow-mgr.c | 76 ++++--\n northd/lflow-mgr.h | 4 +-\n northd/northd.c | 48 +++-\n ovn-nb.xml | 23 ++\n tests/ovn.at | 75 ++++++\n tests/system-ovn.at | 440 +++++++++++++++++++++++++++++++++--\n 13 files changed, 718 insertions(+), 63 deletions(-)", "diff": "diff --git a/NEWS b/NEWS\nindex c566ebfc8..6eb77b0e0 100644\n--- a/NEWS\n+++ b/NEWS\n@@ -104,6 +104,11 @@ Post v25.09.0\n - Add support for special port_security prefix \"VRRPv3\". This prefix allows\n CMS to allow all required traffic for a VRRPv3 virtual router behind LSP.\n See ovn-nb(5) man page for more details.\n+ - Fixed support for fragmented traffic in the userspace datapath. Added the\n+ \"acl_ct_translation\" NB_Global option to enable connection tracking\n+ based L4 field translation for stateful ACLs. When enabled allows proper\n+ handling of IP fragmentation in userspace datapaths. This option breaks\n+ hardware offloading and is disabled by default.\n \n OVN v25.09.0 - xxx xx xxxx\n --------------------------\ndiff --git a/controller/lflow.c b/controller/lflow.c\nindex a12627b0a..35ed6d30b 100644\n--- a/controller/lflow.c\n+++ b/controller/lflow.c\n@@ -51,10 +51,19 @@ COVERAGE_DEFINE(consider_logical_flow);\n /* Contains \"struct expr_symbol\"s for fields supported by OVN lflows. */\n static struct shash symtab;\n \n+/* Alternative symbol table for ACL CT translation.\n+ * This symbol table maps L4 port fields (tcp/udp/sctp) to their connection\n+ * tracking equivalents (ct_tp_src/ct_tp_dst with ct_proto predicates).\n+ * This allows matching on all IP fragments (not just the first fragment)\n+ * so that all fragments can be matched based on the connection tracking state.\n+ */\n+static struct shash acl_ct_symtab;\n+\n void\n lflow_init(void)\n {\n ovn_init_symtab(&symtab);\n+ ovn_init_acl_ct_symtab(&acl_ct_symtab);\n }\n \f\n struct lookup_port_aux {\n@@ -1001,7 +1010,15 @@ convert_match_to_expr(const struct sbrec_logical_flow *lflow,\n lflow->match);\n return NULL;\n }\n- struct expr *e = expr_parse_string(lex_str_get(&match_s), &symtab,\n+\n+ /* Check if this logical flow requires ACL CT translation.\n+ * If the tags contains \"acl_ct_translation\"=\"true\", we use the\n+ * alternative symbol table that maps L4 fields (tcp/udp/sctp ports)\n+ * to their CT equivalents. */\n+ bool ct_trans = smap_get_bool(&lflow->tags, \"acl_ct_translation\", false);\n+ struct shash *symtab_to_use = ct_trans ? &acl_ct_symtab : &symtab;\n+\n+ struct expr *e = expr_parse_string(lex_str_get(&match_s), symtab_to_use,\n addr_sets, port_groups, &addr_sets_ref,\n &port_groups_ref,\n ldp->datapath->tunnel_key,\n@@ -1033,7 +1050,7 @@ convert_match_to_expr(const struct sbrec_logical_flow *lflow,\n e = expr_combine(EXPR_T_AND, e, *prereqs);\n *prereqs = NULL;\n }\n- e = expr_annotate(e, &symtab, &error);\n+ e = expr_annotate(e, symtab_to_use, &error);\n }\n if (error) {\n static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);\n@@ -2062,6 +2079,8 @@ lflow_destroy(void)\n {\n expr_symtab_destroy(&symtab);\n shash_destroy(&symtab);\n+ expr_symtab_destroy(&acl_ct_symtab);\n+ shash_destroy(&acl_ct_symtab);\n }\n \n bool\ndiff --git a/include/ovn/logical-fields.h b/include/ovn/logical-fields.h\nindex a22e4cfb7..3c0fb22e7 100644\n--- a/include/ovn/logical-fields.h\n+++ b/include/ovn/logical-fields.h\n@@ -278,6 +278,8 @@ const char *event_to_string(enum ovn_controller_event event);\n int string_to_event(const char *s);\n const struct ovn_field *ovn_field_from_name(const char *name);\n \n+void ovn_init_acl_ct_symtab(struct shash *symtab);\n+\n /* OVN CT label values\n * ===================\n * These are specific ct.label bit values OVN uses to track different types\ndiff --git a/lib/logical-fields.c b/lib/logical-fields.c\nindex 601208cb9..9b04762a1 100644\n--- a/lib/logical-fields.c\n+++ b/lib/logical-fields.c\n@@ -451,3 +451,70 @@ ovn_field_from_name(const char *name)\n \n return shash_find_data(&ovnfield_by_name, name);\n }\n+\n+/* Removes the symbol with 'name' from 'symtab', freeing its memory. */\n+static void\n+expr_symtab_remove(struct shash *symtab, const char *name)\n+{\n+ struct expr_symbol *symbol = shash_find_and_delete(symtab, name);\n+ if (symbol) {\n+ free(symbol->name);\n+ free(symbol->prereqs);\n+ free(symbol->predicate);\n+ free(symbol);\n+ }\n+}\n+\n+/* Initialize a symbol table for ACL CT translation.\n+ * This creates an alternative symbol table that maps L4 port fields\n+ * (tcp/udp/sctp) to their connection tracking equivalents. This allows\n+ * matching on all IP fragments (not just the first) by using CT state\n+ * which is available for all fragments. */\n+void\n+ovn_init_acl_ct_symtab(struct shash *acl_symtab)\n+{\n+ /* Initialize with the standard symbol table. */\n+ ovn_init_symtab(acl_symtab);\n+\n+ /* Remove the original tcp/udp/sctp symbols that we will override. */\n+ expr_symtab_remove(acl_symtab, \"tcp.src\");\n+ expr_symtab_remove(acl_symtab, \"tcp.dst\");\n+ expr_symtab_remove(acl_symtab, \"tcp\");\n+ expr_symtab_remove(acl_symtab, \"udp.src\");\n+ expr_symtab_remove(acl_symtab, \"udp.dst\");\n+ expr_symtab_remove(acl_symtab, \"udp\");\n+ expr_symtab_remove(acl_symtab, \"sctp.src\");\n+ expr_symtab_remove(acl_symtab, \"sctp.dst\");\n+ expr_symtab_remove(acl_symtab, \"sctp\");\n+\n+ /* Add ct_proto field - CT original direction protocol. Used in the\n+ * tcp/udp/sctp predicate expansions below. */\n+ expr_symtab_add_field(acl_symtab, \"ct_proto\", MFF_CT_NW_PROTO,\n+ \"ct.trk\", false);\n+\n+ /* Override TCP protocol and port fields to use CT equivalents.\n+ * When \"tcp\" is used as a predicate, it expands to \"ct_proto == 6\"\n+ * instead of \"ip.proto == 6\". */\n+ expr_symtab_add_predicate(acl_symtab, \"tcp\",\n+ \"ct.trk && !ct.inv && ct_proto == 6\");\n+ expr_symtab_add_field(acl_symtab, \"tcp.src\", MFF_CT_TP_SRC,\n+ \"tcp\", false);\n+ expr_symtab_add_field(acl_symtab, \"tcp.dst\", MFF_CT_TP_DST,\n+ \"tcp\", false);\n+\n+ /* Override UDP protocol and port fields */\n+ expr_symtab_add_predicate(acl_symtab, \"udp\",\n+ \"ct.trk && !ct.inv && ct_proto == 17\");\n+ expr_symtab_add_field(acl_symtab, \"udp.src\", MFF_CT_TP_SRC,\n+ \"udp\", false);\n+ expr_symtab_add_field(acl_symtab, \"udp.dst\", MFF_CT_TP_DST,\n+ \"udp\", false);\n+\n+ /* Override SCTP protocol and port fields */\n+ expr_symtab_add_predicate(acl_symtab, \"sctp\",\n+ \"ct.trk && !ct.inv && ct_proto == 132\");\n+ expr_symtab_add_field(acl_symtab, \"sctp.src\", MFF_CT_TP_SRC,\n+ \"sctp\", false);\n+ expr_symtab_add_field(acl_symtab, \"sctp.dst\", MFF_CT_TP_DST,\n+ \"sctp\", false);\n+}\ndiff --git a/lib/ovn-util.c b/lib/ovn-util.c\nindex f34c37a79..63a68df68 100644\n--- a/lib/ovn-util.c\n+++ b/lib/ovn-util.c\n@@ -698,20 +698,24 @@ ovn_pipeline_from_name(const char *pipeline)\n uint32_t\n sbrec_logical_flow_hash(const struct sbrec_logical_flow *lf)\n {\n+ bool acl_ct_translation = smap_get_bool(&lf->tags,\n+ \"acl_ct_translation\", false);\n return ovn_logical_flow_hash(lf->table_id,\n ovn_pipeline_from_name(lf->pipeline),\n lf->priority, lf->match,\n- lf->actions);\n+ lf->actions, acl_ct_translation);\n }\n \n uint32_t\n ovn_logical_flow_hash(uint8_t table_id, enum ovn_pipeline pipeline,\n uint16_t priority,\n- const char *match, const char *actions)\n+ const char *match, const char *actions,\n+ bool acl_ct_translation)\n {\n size_t hash = hash_2words((table_id << 16) | priority, pipeline);\n hash = hash_string(match, hash);\n- return hash_string(actions, hash);\n+ hash = hash_string(actions, hash);\n+ return hash_boolean(acl_ct_translation, hash);\n }\n \n \f\ndiff --git a/lib/ovn-util.h b/lib/ovn-util.h\nindex 0efeb217c..b882b8533 100644\n--- a/lib/ovn-util.h\n+++ b/lib/ovn-util.h\n@@ -155,7 +155,8 @@ enum ovn_pipeline {\n uint32_t sbrec_logical_flow_hash(const struct sbrec_logical_flow *);\n uint32_t ovn_logical_flow_hash(uint8_t table_id, enum ovn_pipeline pipeline,\n uint16_t priority,\n- const char *match, const char *actions);\n+ const char *match, const char *actions,\n+ bool acl_ct_translation);\n void ovn_conn_show(struct unixctl_conn *conn, int argc OVS_UNUSED,\n const char *argv[] OVS_UNUSED, void *idl_);\n \ndiff --git a/northd/en-global-config.c b/northd/en-global-config.c\nindex 2556b2888..a6f37b0c3 100644\n--- a/northd/en-global-config.c\n+++ b/northd/en-global-config.c\n@@ -717,6 +717,11 @@ check_nb_options_out_of_sync(\n return true;\n }\n \n+ if (config_out_of_sync(&nb->options, &config_data->nb_options,\n+ \"acl_ct_translation\", false)) {\n+ return true;\n+ }\n+\n return false;\n }\n \ndiff --git a/northd/lflow-mgr.c b/northd/lflow-mgr.c\nindex f21024903..a9811601f 100644\n--- a/northd/lflow-mgr.c\n+++ b/northd/lflow-mgr.c\n@@ -40,12 +40,14 @@ static void ovn_lflow_init(struct ovn_lflow *,\n char *actions, char *io_port,\n char *ctrl_meter, char *stage_hint,\n const char *where, const char *flow_desc,\n- struct uuid sbuuid);\n+ struct uuid sbuuid, bool acl_ct_translation);\n static struct ovn_lflow *ovn_lflow_find(const struct hmap *lflows,\n const struct ovn_stage *stage,\n uint16_t priority, const char *match,\n const char *actions,\n- const char *ctrl_meter, uint32_t hash);\n+ const char *ctrl_meter,\n+ bool acl_ct_translation,\n+ uint32_t hash);\n static void ovn_lflow_destroy(struct lflow_table *lflow_table,\n struct ovn_lflow *lflow);\n static char *ovn_lflow_hint(const struct ovsdb_idl_row *row);\n@@ -56,7 +58,8 @@ static struct ovn_lflow *do_ovn_lflow_add(\n const char *actions, const char *io_port,\n const char *ctrl_meter,\n const struct ovsdb_idl_row *stage_hint,\n- const char *where, const char *flow_desc);\n+ const char *where, const char *flow_desc,\n+ bool acl_ct_translation);\n \n \n static struct ovs_mutex *lflow_hash_lock(const struct hmap *lflow_table,\n@@ -184,6 +187,7 @@ struct ovn_lflow {\n struct ovn_dp_group *dpg; /* Link to unique Sb datapath group. */\n const char *where;\n const char *flow_desc;\n+ bool acl_ct_translation; /* Use CT-based L4 field translation. */\n \n struct uuid sb_uuid; /* SB DB row uuid, specified by northd. */\n struct ovs_list referenced_by; /* List of struct lflow_ref_node. */\n@@ -362,10 +366,12 @@ lflow_table_sync_to_sb(struct lflow_table *lflow_table,\n struct ovn_stage stage = ovn_stage_build(dp_type, pipeline,\n sbflow->table_id);\n \n+ bool acl_ct_translation = smap_get_bool(&sbflow->tags,\n+ \"acl_ct_translation\", false);\n lflow = ovn_lflow_find(\n lflows, &stage,\n sbflow->priority, sbflow->match, sbflow->actions,\n- sbflow->controller_meter, sbflow->hash);\n+ sbflow->controller_meter, acl_ct_translation, sbflow->hash);\n if (lflow) {\n const struct ovn_synced_datapaths *datapaths;\n struct hmap *dp_groups;\n@@ -727,14 +733,15 @@ lflow_ref_sync_lflows(struct lflow_ref *lflow_ref,\n */\n static void\n lflow_table_add_lflow__(struct lflow_table *lflow_table,\n- const struct ovn_synced_datapath *sdp,\n- const unsigned long *dp_bitmap, size_t dp_bitmap_len,\n- const struct ovn_stage *stage, uint16_t priority,\n- const char *match, const char *actions,\n- const char *io_port, const char *ctrl_meter,\n- const struct ovsdb_idl_row *stage_hint,\n- const char *where, const char *flow_desc,\n- struct lflow_ref *lflow_ref)\n+ const struct ovn_synced_datapath *sdp,\n+ const unsigned long *dp_bitmap, size_t dp_bitmap_len,\n+ const struct ovn_stage *stage, uint16_t priority,\n+ const char *match, const char *actions,\n+ const char *io_port, const char *ctrl_meter,\n+ const struct ovsdb_idl_row *stage_hint,\n+ const char *where, const char *flow_desc,\n+ bool acl_ct_translation,\n+ struct lflow_ref *lflow_ref)\n OVS_EXCLUDED(fake_hash_mutex)\n {\n struct ovs_mutex *hash_lock;\n@@ -747,7 +754,7 @@ lflow_table_add_lflow__(struct lflow_table *lflow_table,\n hash = ovn_logical_flow_hash(ovn_stage_get_table(stage),\n ovn_stage_get_pipeline(stage),\n priority, match,\n- actions);\n+ actions, acl_ct_translation);\n \n hash_lock = lflow_hash_lock(&lflow_table->entries, hash);\n struct ovn_lflow *lflow =\n@@ -755,7 +762,8 @@ lflow_table_add_lflow__(struct lflow_table *lflow_table,\n sdp ? sparse_array_len(&sdp->dps->dps_array)\n : dp_bitmap_len,\n hash, stage, priority, match, actions,\n- io_port, ctrl_meter, stage_hint, where, flow_desc);\n+ io_port, ctrl_meter, stage_hint, where, flow_desc,\n+ acl_ct_translation);\n \n if (lflow_ref) {\n struct lflow_ref_node *lrn =\n@@ -815,7 +823,8 @@ lflow_table_add_lflow(struct lflow_table_add_args *args)\n args->dp_bitmap_len, args->stage, args->priority,\n args->match, args->actions, args->io_port,\n args->ctrl_meter, args->stage_hint, args->where,\n- args->flow_desc, args->lflow_ref);\n+ args->flow_desc, args->acl_ct_translation,\n+ args->lflow_ref);\n }\n \n struct ovn_dp_group *\n@@ -933,7 +942,8 @@ ovn_lflow_init(struct ovn_lflow *lflow,\n size_t dp_bitmap_len, const struct ovn_stage *stage,\n uint16_t priority, char *match, char *actions, char *io_port,\n char *ctrl_meter, char *stage_hint, const char *where,\n- const char *flow_desc, struct uuid sbuuid)\n+ const char *flow_desc, struct uuid sbuuid,\n+ bool acl_ct_translation)\n {\n dynamic_bitmap_alloc(&lflow->dpg_bitmap, dp_bitmap_len);\n lflow->dp = dp;\n@@ -949,6 +959,7 @@ ovn_lflow_init(struct ovn_lflow *lflow,\n lflow->where = where;\n lflow->sb_uuid = sbuuid;\n lflow->sync_state = LFLOW_TO_SYNC;\n+ lflow->acl_ct_translation = acl_ct_translation;\n hmap_init(&lflow->dp_refcnts_map);\n ovs_list_init(&lflow->referenced_by);\n }\n@@ -981,25 +992,28 @@ lflow_hash_unlock(struct ovs_mutex *hash_lock)\n static bool\n ovn_lflow_equal(const struct ovn_lflow *a, const struct ovn_stage *stage,\n uint16_t priority, const char *match,\n- const char *actions, const char *ctrl_meter)\n+ const char *actions, const char *ctrl_meter,\n+ bool acl_ct_translation)\n {\n return (ovn_stage_equal(a->stage, stage)\n && a->priority == priority\n && !strcmp(a->match, match)\n && !strcmp(a->actions, actions)\n- && nullable_string_is_equal(a->ctrl_meter, ctrl_meter));\n+ && nullable_string_is_equal(a->ctrl_meter, ctrl_meter)\n+ && a->acl_ct_translation == acl_ct_translation);\n }\n \n static struct ovn_lflow *\n ovn_lflow_find(const struct hmap *lflows,\n const struct ovn_stage *stage, uint16_t priority,\n const char *match, const char *actions,\n- const char *ctrl_meter, uint32_t hash)\n+ const char *ctrl_meter, bool acl_ct_translation,\n+ uint32_t hash)\n {\n struct ovn_lflow *lflow;\n HMAP_FOR_EACH_WITH_HASH (lflow, hmap_node, hash, lflows) {\n if (ovn_lflow_equal(lflow, stage, priority, match, actions,\n- ctrl_meter)) {\n+ ctrl_meter, acl_ct_translation)) {\n return lflow;\n }\n }\n@@ -1039,7 +1053,8 @@ do_ovn_lflow_add(struct lflow_table *lflow_table, size_t dp_bitmap_len,\n uint16_t priority, const char *match, const char *actions,\n const char *io_port, const char *ctrl_meter,\n const struct ovsdb_idl_row *stage_hint,\n- const char *where, const char *flow_desc)\n+ const char *where, const char *flow_desc,\n+ bool acl_ct_translation)\n OVS_REQUIRES(fake_hash_mutex)\n {\n struct ovn_lflow *old_lflow;\n@@ -1049,7 +1064,8 @@ do_ovn_lflow_add(struct lflow_table *lflow_table, size_t dp_bitmap_len,\n ovs_assert(dp_bitmap_len);\n \n old_lflow = ovn_lflow_find(&lflow_table->entries, stage,\n- priority, match, actions, ctrl_meter, hash);\n+ priority, match, actions, ctrl_meter,\n+ acl_ct_translation, hash);\n if (old_lflow) {\n dynamic_bitmap_realloc(&old_lflow->dpg_bitmap, dp_bitmap_len);\n if (old_lflow->sync_state != LFLOW_STALE) {\n@@ -1068,7 +1084,7 @@ do_ovn_lflow_add(struct lflow_table *lflow_table, size_t dp_bitmap_len,\n io_port ? xstrdup(io_port) : NULL,\n nullable_xstrdup(ctrl_meter),\n ovn_lflow_hint(stage_hint), where,\n- flow_desc, sbuuid);\n+ flow_desc, sbuuid, acl_ct_translation);\n \n if (parallelization_state != STATE_USE_PARALLELIZATION) {\n hmap_insert(&lflow_table->entries, &lflow->hmap_node, hash);\n@@ -1122,12 +1138,22 @@ sync_lflow_to_sb(struct ovn_lflow *lflow,\n sbrec_logical_flow_set_match(sbflow, lflow->match);\n sbrec_logical_flow_set_actions(sbflow, lflow->actions);\n sbrec_logical_flow_set_flow_desc(sbflow, lflow->flow_desc);\n+\n+ struct smap tags = SMAP_INITIALIZER(&tags);\n if (lflow->io_port) {\n- struct smap tags = SMAP_INITIALIZER(&tags);\n smap_add(&tags, \"in_out_port\", lflow->io_port);\n+ }\n+\n+ if (lflow->acl_ct_translation) {\n+ smap_add(&tags, \"acl_ct_translation\", \"true\");\n+ }\n+\n+ if (!smap_is_empty(&tags)) {\n sbrec_logical_flow_set_tags(sbflow, &tags);\n- smap_destroy(&tags);\n }\n+\n+ smap_destroy(&tags);\n+\n sbrec_logical_flow_set_controller_meter(sbflow, lflow->ctrl_meter);\n \n /* Trim the source locator lflow->where, which looks something like\ndiff --git a/northd/lflow-mgr.h b/northd/lflow-mgr.h\nindex 4a1655c35..84d0b3e67 100644\n--- a/northd/lflow-mgr.h\n+++ b/northd/lflow-mgr.h\n@@ -24,6 +24,7 @@\n struct ovsdb_idl_txn;\n struct ovn_datapath;\n struct ovsdb_idl_row;\n+struct ovn_lflow;\n \n /* lflow map which stores the logical flows. */\n struct lflow_table {\n@@ -87,12 +88,13 @@ struct lflow_table_add_args {\n const char *flow_desc;\n struct lflow_ref *lflow_ref;\n const char *where;\n+ bool acl_ct_translation;\n };\n \n void lflow_table_add_lflow(struct lflow_table_add_args *args);\n \n-\n #define WITH_HINT(HINT) .stage_hint = HINT\n+#define WITH_CT_TRANSLATION(CT_TRANS) .acl_ct_translation = (CT_TRANS)\n /* The IN_OUT_PORT argument tells the lport name that appears in the MATCH,\n * which helps ovn-controller to bypass lflows parsing when the lport is\n * not local to the chassis. The critiera of the lport to be added using this\ndiff --git a/northd/northd.c b/northd/northd.c\nindex 7fe36b850..937383e4b 100644\n--- a/northd/northd.c\n+++ b/northd/northd.c\n@@ -89,6 +89,12 @@ static bool use_common_zone = false;\n * Otherwise, it will avoid using it. The default is true. */\n static bool use_ct_inv_match = true;\n \n+/* If this option is 'true' northd will flag the related ACL flows to use\n+ * connection tracking fields to properly handle IP fragments. By default this\n+ * option is set to 'false'.\n+ */\n+static bool acl_ct_translation = false;\n+\n /* If this option is 'true' northd will implicitly add a lowest-priority\n * drop rule in the ACL stage of logical switches that have at least one\n * ACL.\n@@ -6306,11 +6312,19 @@ build_ls_stateful_rec_pre_acls(\n \"(udp && udp.src == 546 && udp.dst == 547)\", \"next;\",\n lflow_ref);\n \n- /* Do not send multicast packets to conntrack. */\n- ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 110, \"eth.mcast\",\n- \"next;\", lflow_ref);\n- ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 110, \"eth.mcast\",\n- \"next;\", lflow_ref);\n+ /* Do not send multicast packets to conntrack unless ACL CT\n+ * translation is enabled. When translation is active, L4 port\n+ * fields are rewritten to their CT equivalents (e.g. udp.dst ->\n+ * ct_udp.dst), which requires ct.trk to be set. Skipping CT\n+ * for multicast would leave ct.trk unset and cause all\n+ * CT-translated ACL matches to fail for multicast traffic\n+ * (including DHCP). */\n+ if (!acl_ct_translation) {\n+ ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 110, \"eth.mcast\",\n+ \"next;\", lflow_ref);\n+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 110, \"eth.mcast\",\n+ \"next;\", lflow_ref);\n+ }\n \n /* Ingress and Egress Pre-ACL Table (Priority 100).\n *\n@@ -7286,6 +7300,11 @@ consider_acl(struct lflow_table *lflows, const struct ovn_datapath *od,\n match_tier_len = match->length;\n }\n \n+ /* Check if this ACL needs CT translation for fragment handling.\n+ * All stateful ACLs are marked when the option is enabled; the actual\n+ * translation only affects L4 port fields in ovn-controller. */\n+ bool needs_ct_trans = has_stateful && acl_ct_translation;\n+\n if (!has_stateful\n || !strcmp(acl->action, \"pass\")\n || !strcmp(acl->action, \"allow-stateless\")) {\n@@ -7303,6 +7322,7 @@ consider_acl(struct lflow_table *lflows, const struct ovn_datapath *od,\n \n ds_put_cstr(actions, \"next;\");\n ds_put_format(match, \"(%s)\", acl->match);\n+ /* Stateless ACLs don't need CT translation. */\n ovn_lflow_add(lflows, od, stage, priority, ds_cstr(match),\n ds_cstr(actions), lflow_ref, WITH_HINT(&acl->header_));\n return;\n@@ -7372,7 +7392,9 @@ consider_acl(struct lflow_table *lflows, const struct ovn_datapath *od,\n }\n ds_put_cstr(actions, \"next;\");\n ovn_lflow_add(lflows, od, stage, priority, ds_cstr(match),\n- ds_cstr(actions), lflow_ref, WITH_HINT(&acl->header_));\n+ ds_cstr(actions), lflow_ref,\n+ WITH_HINT(&acl->header_),\n+ WITH_CT_TRANSLATION(needs_ct_trans));\n \n /* Match on traffic in the request direction for an established\n * connection tracking entry that has not been marked for\n@@ -7402,7 +7424,9 @@ consider_acl(struct lflow_table *lflows, const struct ovn_datapath *od,\n }\n ds_put_cstr(actions, \"next;\");\n ovn_lflow_add(lflows, od, stage, priority, ds_cstr(match),\n- ds_cstr(actions), lflow_ref, WITH_HINT(&acl->header_));\n+ ds_cstr(actions), lflow_ref,\n+ WITH_HINT(&acl->header_),\n+ WITH_CT_TRANSLATION(needs_ct_trans));\n } else if (!strcmp(acl->action, \"drop\")\n || !strcmp(acl->action, \"reject\")) {\n if (acl->network_function_group) {\n@@ -7428,7 +7452,9 @@ consider_acl(struct lflow_table *lflows, const struct ovn_datapath *od,\n obs_stage);\n ds_put_cstr(actions, \"next;\");\n ovn_lflow_add(lflows, od, stage, priority, ds_cstr(match),\n- ds_cstr(actions), lflow_ref, WITH_HINT(&acl->header_));\n+ ds_cstr(actions), lflow_ref,\n+ WITH_HINT(&acl->header_),\n+ WITH_CT_TRANSLATION(needs_ct_trans));\n /* For an existing connection without ct_mark.blocked set, we've\n * encountered a policy change. ACLs previously allowed\n * this connection and we committed the connection tracking\n@@ -7454,7 +7480,9 @@ consider_acl(struct lflow_table *lflows, const struct ovn_datapath *od,\n \"ct_commit { ct_mark.blocked = 1; \"\n \"ct_label.obs_point_id = %\"PRIu32\"; }; next;\", obs_pid);\n ovn_lflow_add(lflows, od, stage, priority, ds_cstr(match),\n- ds_cstr(actions), lflow_ref, WITH_HINT(&acl->header_));\n+ ds_cstr(actions), lflow_ref,\n+ WITH_HINT(&acl->header_),\n+ WITH_CT_TRANSLATION(needs_ct_trans));\n }\n }\n \n@@ -20758,6 +20786,8 @@ ovnnb_db_run(struct northd_input *input_data,\n \n use_ct_inv_match = smap_get_bool(input_data->nb_options,\n \"use_ct_inv_match\", true);\n+ acl_ct_translation = smap_get_bool(input_data->nb_options,\n+ \"acl_ct_translation\", false);\n \n /* deprecated, use --event instead */\n controller_event_en = smap_get_bool(input_data->nb_options,\ndiff --git a/ovn-nb.xml b/ovn-nb.xml\nindex ae37d03d1..9bb49c2ea 100644\n--- a/ovn-nb.xml\n+++ b/ovn-nb.xml\n@@ -327,6 +327,29 @@\n </p>\n </column>\n \n+ <column name=\"options\" key=\"acl_ct_translation\">\n+ <p>\n+ If set to <code>true</code>, <code>ovn-northd</code> will enable\n+ connection tracking based L4 field translation for stateful ACLs.\n+ When enabled, ACL matches on L4 port fields (tcp/udp/sctp) use\n+ connection tracking state instead of packet headers. This allows\n+ proper handling of IP fragmentation in userspace datapaths (e.g.,\n+ DPDK) where fragments are not automatically reassembled during\n+ connection tracking lookup.\n+ </p>\n+ <p>\n+ <em>Important:</em> Enabling this option breaks hardware offloading\n+ of flows. It also disables the multicast conntrack bypass, which\n+ means multicast traffic (including DHCP) will go through connection\n+ tracking. This may have a performance impact on multicast-heavy\n+ workloads. Only enable this option if you need to handle fragmented\n+ traffic with stateful ACLs in userspace datapaths.\n+ </p>\n+ <p>\n+ The default value is <code>false</code>.\n+ </p>\n+ </column>\n+\n <column name=\"options\" key=\"default_acl_drop\">\n <p>\n If set to <code>true</code>., <code>ovn-northd</code> will\ndiff --git a/tests/ovn.at b/tests/ovn.at\nindex 3a3b05ab7..ff3dbd6bc 100644\n--- a/tests/ovn.at\n+++ b/tests/ovn.at\n@@ -45700,3 +45700,78 @@ AT_CHECK([grep -q 'name=\"eth-unknown\"' hv1/ovn-controller.log], [1])\n OVN_CLEANUP([hv1])\n AT_CLEANUP\n ])\n+\n+OVN_FOR_EACH_NORTHD([\n+AT_SETUP([ACL CT translation - Rules])\n+AT_KEYWORDS([acl_ct_translation_rules])\n+ovn_start\n+\n+check ovn-nbctl ls-add ls1\n+\n+check ovn-nbctl lsp-add ls1 lp1 \\\n+ -- lsp-set-addresses lp1 \"f0:00:00:00:00:01 10.0.0.1\"\n+check ovn-nbctl lsp-add ls1 lp2 \\\n+ -- lsp-set-addresses lp2 \"f0:00:00:00:00:02 10.0.0.2\"\n+\n+net_add n1\n+sim_add hv1\n+\n+as hv1\n+check ovs-vsctl add-br br-phys\n+ovn_attach n1 br-phys 192.168.0.1\n+check ovs-vsctl -- add-port br-int hv1-vif1 -- \\\n+ set interface hv1-vif1 external-ids:iface-id=lp1\n+check ovs-vsctl -- add-port br-int hv1-vif2 -- \\\n+ set interface hv1-vif2 external-ids:iface-id=lp2\n+\n+# Get the OF table numbers for ACL evaluation stages.\n+acl_in_eval=$(ovn-debug lflow-stage-to-oftable ls_in_acl_eval)\n+acl_out_eval=$(ovn-debug lflow-stage-to-oftable ls_out_acl_eval)\n+\n+# Create port group and add ACLs with TCP/UDP matches.\n+lp1_uuid=$(fetch_column nb:logical_switch_port _uuid name=lp1)\n+lp2_uuid=$(fetch_column nb:logical_switch_port _uuid name=lp2)\n+check ovn-nbctl pg-add pg1 $lp1_uuid $lp2_uuid\n+\n+check ovn-nbctl acl-add pg1 from-lport 1002 \\\n+ \"inport == @pg1 && ip4 && tcp && tcp.dst == 80\" allow-related\n+check ovn-nbctl acl-add pg1 from-lport 1002 \\\n+ \"inport == @pg1 && ip4 && udp && udp.dst == 53\" allow-related\n+check ovn-nbctl acl-add pg1 to-lport 1002 \\\n+ \"outport == @pg1 && ip4 && tcp && tcp.src == 80\" allow-related\n+check ovn-nbctl acl-add pg1 to-lport 1002 \\\n+ \"outport == @pg1 && ip4 && udp && udp.src == 53\" allow-related\n+check ovn-nbctl --wait=hv --log acl-add pg1 to-lport 100 \"outport == @pg1\" drop\n+\n+# Verify lflows do NOT have acl_ct_translation tag when option is disabled (default).\n+check_row_count Logical_Flow 0 'tags:acl_ct_translation=\"true\"'\n+\n+# Verify OpenFlows use packet header fields (tcp.dst, udp.dst) not CT fields.\n+# When CT translation is OFF, we should see tp_dst matches in ACL tables.\n+as hv1\n+AT_CHECK([ovs-ofctl dump-flows br-int table=$acl_in_eval | grep -q \"tp_dst=80\"], [0])\n+\n+# Now enable ACL CT translation.\n+check ovn-nbctl --wait=hv set NB_Global . options:acl_ct_translation=true\n+\n+# Verify lflows now have acl_ct_translation tag in the tags column.\n+wait_row_count Logical_Flow 10 'tags:acl_ct_translation=\"true\"'\n+\n+# Verify OpenFlows now use CT fields (ct_tp_dst) instead of packet headers.\n+# When CT translation is ON, we should see ct_tp_dst matches in ACL tables.\n+as hv1\n+AT_CHECK([ovs-ofctl dump-flows br-int table=$acl_in_eval | grep -q \"ct_tp_dst=80\"], [0])\n+\n+# Now disable ACL CT translation again.\n+check ovn-nbctl --wait=hv set NB_Global . options:acl_ct_translation=false\n+\n+# Verify lflows no longer have acl_ct_translation tag.\n+wait_row_count Logical_Flow 0 'tags:acl_ct_translation=\"true\"'\n+\n+# Verify OpenFlows reverted to packet header fields in ACL tables.\n+as hv1\n+AT_CHECK([ovs-ofctl dump-flows br-int table=$acl_in_eval | grep -q \"tp_dst=80\"], [0])\n+\n+OVN_CLEANUP([hv1])\n+AT_CLEANUP\n+])\ndiff --git a/tests/system-ovn.at b/tests/system-ovn.at\nindex a478dd709..f2c8cdb65 100644\n--- a/tests/system-ovn.at\n+++ b/tests/system-ovn.at\n@@ -18,7 +18,7 @@ ovs-vsctl \\\n -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \\\n -- set bridge br-int fail-mode=secure other-config:disable-in-band=true\n \n-# Start ovn-controller\n+# Start ovn-controller.\n start_daemon ovn-controller\n \n # Logical network:\n@@ -190,7 +190,7 @@ ovs-vsctl \\\n -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \\\n -- set bridge br-int fail-mode=secure other-config:disable-in-band=true\n \n-# Start ovn-controller\n+# Start ovn-controller.\n start_daemon ovn-controller\n \n # Logical network:\n@@ -363,7 +363,7 @@ ovs-vsctl \\\n -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \\\n -- set bridge br-int fail-mode=secure other-config:disable-in-band=true\n \n-# Start ovn-controller\n+# Start ovn-controller.\n start_daemon ovn-controller\n \n # Logical network:\n@@ -468,7 +468,7 @@ ovs-vsctl \\\n -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \\\n -- set bridge br-int fail-mode=secure other-config:disable-in-band=true\n \n-# Start ovn-controller\n+# Start ovn-controller.\n start_daemon ovn-controller\n \n # Logical network:\n@@ -573,7 +573,7 @@ ovs-vsctl \\\n -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \\\n -- set bridge br-int fail-mode=secure other-config:disable-in-band=true\n \n-# Start ovn-controller\n+# Start ovn-controller.\n start_daemon ovn-controller\n \n # Logical network:\n@@ -788,7 +788,7 @@ ovs-vsctl \\\n -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \\\n -- set bridge br-int fail-mode=secure other-config:disable-in-band=true\n \n-# Start ovn-controller\n+# Start ovn-controller.\n start_daemon ovn-controller\n \n # Logical network:\n@@ -1007,7 +1007,7 @@ ovs-vsctl \\\n -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \\\n -- set bridge br-int fail-mode=secure other-config:disable-in-band=true\n \n-# Start ovn-controller\n+# Start ovn-controller.\n start_daemon ovn-controller\n \n # Logical network:\n@@ -1314,7 +1314,7 @@ ovs-vsctl \\\n -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \\\n -- set bridge br-int fail-mode=secure other-config:disable-in-band=true\n \n-# Start ovn-controller\n+# Start ovn-controller.\n start_daemon ovn-controller\n \n # Logical network:\n@@ -1588,7 +1588,7 @@ ovs-vsctl \\\n -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \\\n -- set bridge br-int fail-mode=secure other-config:disable-in-band=true\n \n-# Start ovn-controller\n+# Start ovn-controller.\n start_daemon ovn-controller\n \n # Logical network:\n@@ -2044,7 +2044,7 @@ ovs-vsctl \\\n -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \\\n -- set bridge br-int fail-mode=secure other-config:disable-in-band=true\n \n-# Start ovn-controller\n+# Start ovn-controller.\n start_daemon ovn-controller\n \n # Logical network:\n@@ -2145,7 +2145,7 @@ ovs-vsctl \\\n -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \\\n -- set bridge br-int fail-mode=secure other-config:disable-in-band=true\n \n-# Start ovn-controller\n+# Start ovn-controller.\n start_daemon ovn-controller\n \n # Logical network:\n@@ -7296,7 +7296,7 @@ ADD_BR([br-int])\n ADD_BR([br-ext])\n \n check ovs-ofctl add-flow br-ext action=normal\n-# Set external-ids in br-int needed for ovn-controller\n+# Set external-ids in br-int needed for ovn-controller.\n check ovs-vsctl \\\n -- set Open_vSwitch . external-ids:system-id=hv1 \\\n -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \\\n@@ -7838,7 +7838,7 @@ ADD_BR([br-int])\n ADD_BR([br-ext])\n \n check ovs-ofctl add-flow br-ext action=normal\n-# Set external-ids in br-int needed for ovn-controller\n+# Set external-ids in br-int needed for ovn-controller.\n check ovs-vsctl \\\n -- set Open_vSwitch . external-ids:system-id=hv1 \\\n -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \\\n@@ -10463,7 +10463,7 @@ ovn_start\n OVS_TRAFFIC_VSWITCHD_START()\n ADD_BR([br-int])\n \n-# Set external-ids in br-int needed for ovn-controller\n+# Set external-ids in br-int needed for ovn-controller.\n check ovs-vsctl \\\n -- set Open_vSwitch . external-ids:system-id=hv1 \\\n -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \\\n@@ -11718,7 +11718,7 @@ ovn_start\n OVS_TRAFFIC_VSWITCHD_START()\n ADD_BR([br-int])\n \n-# Set external-ids in br-int needed for ovn-controller\n+# Set external-ids in br-int needed for ovn-controller.\n check ovs-vsctl \\\n -- set Open_vSwitch . external-ids:system-id=hv1 \\\n -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \\\n@@ -12570,7 +12570,7 @@ ADD_BR([br-int])\n ADD_BR([br-ext])\n \n check ovs-ofctl add-flow br-ext action=normal\n-# Set external-ids in br-int needed for ovn-controller\n+# Set external-ids in br-int needed for ovn-controller.\n check ovs-vsctl \\\n -- set Open_vSwitch . external-ids:system-id=hv1 \\\n -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \\\n@@ -12793,7 +12793,7 @@ ADD_BR([br-int])\n ADD_BR([br-ext])\n \n check ovs-ofctl add-flow br-ext action=normal\n-# Set external-ids in br-int needed for ovn-controller\n+# Set external-ids in br-int needed for ovn-controller.\n check ovs-vsctl \\\n -- set Open_vSwitch . external-ids:system-id=hv1 \\\n -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \\\n@@ -12919,7 +12919,7 @@ ADD_BR([br-int])\n ADD_BR([br-ext])\n \n check ovs-ofctl add-flow br-ext action=normal\n-# Set external-ids in br-int needed for ovn-controller\n+# Set external-ids in br-int needed for ovn-controller.\n check ovs-vsctl \\\n -- set Open_vSwitch . external-ids:system-id=hv1 \\\n -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \\\n@@ -13946,7 +13946,7 @@ ADD_BR([br-int])\n ADD_BR([br-ext])\n \n check ovs-ofctl add-flow br-ext action=normal\n-# Set external-ids in br-int needed for ovn-controller\n+# Set external-ids in br-int needed for ovn-controller.\n check ovs-vsctl \\\n -- set Open_vSwitch . external-ids:system-id=hv1 \\\n -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \\\n@@ -14085,7 +14085,7 @@ ADD_BR([br-int])\n ADD_BR([br-ext])\n \n check ovs-ofctl add-flow br-ext action=normal\n-# Set external-ids in br-int needed for ovn-controller\n+# Set external-ids in br-int needed for ovn-controller.\n check ovs-vsctl \\\n -- set Open_vSwitch . external-ids:system-id=hv1 \\\n -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \\\n@@ -14986,7 +14986,7 @@ ovn_start\n OVS_TRAFFIC_VSWITCHD_START()\n ADD_BR([br-int])\n \n-# Set external-ids in br-int needed for ovn-controller\n+# Set external-ids in br-int needed for ovn-controller.\n check ovs-vsctl \\\n -- set Open_vSwitch . external-ids:system-id=hv1 \\\n -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \\\n@@ -17750,7 +17750,7 @@ ovn_start\n OVS_TRAFFIC_VSWITCHD_START()\n ADD_BR([br-int])\n \n-# Set external-ids in br-int needed for ovn-controller\n+# Set external-ids in br-int needed for ovn-controller.\n check ovs-vsctl \\\n -- set Open_vSwitch . external-ids:system-id=hv1 \\\n -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \\\n@@ -20997,3 +20997,399 @@ OVS_TRAFFIC_VSWITCHD_STOP([\"/failed to query port patch-.*/d\n /connection dropped.*/d\"])\n AT_CLEANUP\n ])\n+\n+OVN_FOR_EACH_NORTHD([\n+AT_SETUP([ACL CT translation - UDP fragmentation])\n+AT_KEYWORDS([acl_ct_translation_udp_fragmentation])\n+AT_SKIP_IF([test $HAVE_TCPDUMP = no])\n+\n+CHECK_CONNTRACK()\n+\n+ovn_start\n+OVS_TRAFFIC_VSWITCHD_START()\n+ADD_BR([br-int])\n+\n+# Set external-ids in br-int needed for ovn-controller\n+check ovs-vsctl \\\n+ -- set Open_vSwitch . external-ids:system-id=hv1 \\\n+ -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \\\n+ -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \\\n+ -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \\\n+ -- set bridge br-int fail-mode=secure other-config:disable-in-band=true\n+\n+# Start ovn-controller.\n+start_daemon ovn-controller\n+\n+# Set the minimal fragment size for userspace DP.\n+ovs-appctl dpctl/ipf-set-min-frag v4 500\n+\n+check ovn-nbctl ls-add internal\n+check ovn-nbctl lsp-add internal client \\\n+ -- lsp-set-addresses client \"f0:00:00:01:02:03 172.16.1.3\"\n+check ovn-nbctl lsp-add internal server \\\n+ -- lsp-set-addresses server \"f0:00:0f:01:02:03 172.16.1.2\"\n+\n+ADD_NAMESPACES(client)\n+ADD_VETH(client, client, br-int, \"172.16.1.3/24\", \"f0:00:00:01:02:03\", \\\n+ \"172.16.1.1\")\n+NS_EXEC([client], [ip l set dev client mtu 900])\n+\n+ADD_NAMESPACES(server)\n+ADD_VETH(server, server, br-int, \"172.16.1.2/24\", \"f0:00:0f:01:02:03\", \\\n+ \"172.16.1.1\")\n+NS_EXEC([server], [ip l set dev server mtu 900])\n+\n+# Create data file for traffic testing (8000 bytes will be fragmented with MTU 900).\n+printf %8000s > datafile\n+\n+# Create port group with both client and server.\n+client_uuid=$(fetch_column nb:logical_switch_port _uuid name=client)\n+server_uuid=$(fetch_column nb:logical_switch_port _uuid name=server)\n+check ovn-nbctl pg-add internal_vms $client_uuid $server_uuid\n+\n+# ACL rules - allow outgoing traffic.\n+check ovn-nbctl acl-add internal_vms from-lport 1002 \"inport == @internal_vms && ip4 && udp\" allow-related\n+check ovn-nbctl acl-add internal_vms from-lport 1002 \"inport == @internal_vms && ip4\" allow-related\n+\n+# ACL rules - allow incoming UDP on specific ports.\n+check ovn-nbctl acl-add internal_vms to-lport 1002 \"outport == @internal_vms && ip4 && udp && udp.dst == 5060\" allow-related\n+check ovn-nbctl acl-add internal_vms to-lport 1002 \"outport == @internal_vms && ip4 && udp && udp.dst == 5061\" allow-related\n+\n+# Allow ARP.\n+check ovn-nbctl acl-add internal_vms to-lport 1002 \"outport == @internal_vms && arp\" allow-related\n+\n+# Drop rule.\n+check ovn-nbctl --log --severity=info acl-add internal_vms to-lport 100 \"outport == @internal_vms\" drop\n+\n+# Enable ACL CT translation for fragmentation handling.\n+check ovn-nbctl --wait=hv set NB_Global . options:acl_ct_translation=true\n+\n+check ovn-nbctl --wait=hv sync\n+check ovs-appctl dpctl/flush-conntrack\n+\n+# Start tcpdump to capture IP fragments on both sides.\n+NETNS_START_TCPDUMP([server], [-U -i server -Q in -nn ip and '(ip[[6:2]] & 0x3fff != 0)'], [tcpdump-udp-server])\n+NETNS_START_TCPDUMP([client], [-U -i client -Q in -nn ip and '(ip[[6:2]] & 0x3fff != 0)'], [tcpdump-udp-client])\n+\n+# Start UDP listeners on both sides.\n+NETNS_DAEMONIZE([server], [nc -l -u 172.16.1.2 5060 > udp_server.rcvd], [server.pid])\n+NETNS_DAEMONIZE([client], [nc -l -u 172.16.1.3 5061 > udp_client.rcvd], [client.pid])\n+\n+# Client sends to server (will be fragmented due to MTU 900).\n+NS_CHECK_EXEC([client], [cat datafile | nc -w 1 -u 172.16.1.2 5060], [0], [ignore], [ignore])\n+\n+# Server sends to client (will be fragmented due to MTU 900).\n+NS_CHECK_EXEC([server], [cat datafile | nc -w 1 -u 172.16.1.3 5061], [0], [ignore], [ignore])\n+\n+OVS_WAIT_UNTIL([test -s udp_server.rcvd])\n+OVS_WAIT_UNTIL([test -s udp_client.rcvd])\n+\n+# Verify both sides received data.\n+check cmp datafile udp_server.rcvd\n+check cmp datafile udp_client.rcvd\n+\n+# Verify IP fragments were received on both sides (at least 5 fragments confirms fragmentation).\n+OVS_WAIT_UNTIL([test $(cat tcpdump-udp-server.tcpdump | wc -l) -ge 5])\n+OVS_WAIT_UNTIL([test $(cat tcpdump-udp-client.tcpdump | wc -l) -ge 5])\n+\n+OVN_CLEANUP_CONTROLLER([hv1])\n+OVN_CLEANUP_NORTHD\n+\n+as\n+OVS_TRAFFIC_VSWITCHD_STOP([\"/failed to query port patch-.*/d\n+/connection dropped.*/d\n+/WARN|netdev@ovs-netdev: execute.*/d\n+/dpif|WARN|system@ovs-system: execute.*/d\n+\"])\n+AT_CLEANUP\n+])\n+\n+OVN_FOR_EACH_NORTHD([\n+AT_SETUP([ACL CT translation - TCP traffic])\n+AT_KEYWORDS([acl_ct_translation_tcp_fragmentation])\n+\n+CHECK_CONNTRACK()\n+\n+ovn_start\n+OVS_TRAFFIC_VSWITCHD_START()\n+ADD_BR([br-int])\n+\n+# Set external-ids in br-int needed for ovn-controller\n+check ovs-vsctl \\\n+ -- set Open_vSwitch . external-ids:system-id=hv1 \\\n+ -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \\\n+ -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \\\n+ -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \\\n+ -- set bridge br-int fail-mode=secure other-config:disable-in-band=true\n+\n+# Start ovn-controller\n+start_daemon ovn-controller\n+\n+check ovn-nbctl ls-add internal\n+check ovn-nbctl lsp-add internal client \\\n+ -- lsp-set-addresses client \"f0:00:00:01:02:03 172.16.1.3\"\n+check ovn-nbctl lsp-add internal server \\\n+ -- lsp-set-addresses server \"f0:00:0f:01:02:03 172.16.1.2\"\n+\n+ADD_NAMESPACES(client)\n+ADD_VETH(client, client, br-int, \"172.16.1.3/24\", \"f0:00:00:01:02:03\", \\\n+ \"172.16.1.1\")\n+\n+ADD_NAMESPACES(server)\n+ADD_VETH(server, server, br-int, \"172.16.1.2/24\", \"f0:00:0f:01:02:03\", \\\n+ \"172.16.1.1\")\n+\n+# Create data file for traffic testing.\n+printf %8000s > datafile\n+\n+# Create port group with both client and server.\n+client_uuid=$(fetch_column nb:logical_switch_port _uuid name=client)\n+server_uuid=$(fetch_column nb:logical_switch_port _uuid name=server)\n+check ovn-nbctl pg-add internal_vms $client_uuid $server_uuid\n+\n+# ACL rules - allow outgoing traffic.\n+check ovn-nbctl acl-add internal_vms from-lport 1002 \"inport == @internal_vms && ip4 && tcp\" allow-related\n+check ovn-nbctl acl-add internal_vms from-lport 1002 \"inport == @internal_vms && ip4\" allow-related\n+\n+# ACL rules - allow incoming TCP on specific ports.\n+check ovn-nbctl acl-add internal_vms to-lport 1002 \"outport == @internal_vms && ip4 && tcp && tcp.dst == 8080\" allow-related\n+check ovn-nbctl acl-add internal_vms to-lport 1002 \"outport == @internal_vms && ip4 && tcp && tcp.dst == 8081\" allow-related\n+\n+# Allow ARP.\n+check ovn-nbctl acl-add internal_vms to-lport 1002 \"outport == @internal_vms && arp\" allow-related\n+\n+# Drop rule.\n+check ovn-nbctl --log --severity=info acl-add internal_vms to-lport 100 \"outport == @internal_vms\" drop\n+\n+# Enable ACL CT translation.\n+check ovn-nbctl --wait=hv set NB_Global . options:acl_ct_translation=true\n+\n+check ovn-nbctl --wait=hv sync\n+check ovs-appctl dpctl/flush-conntrack\n+\n+# Test client -> server direction.\n+NETNS_DAEMONIZE([server], [nc -l 172.16.1.2 8080 > tcp_server.rcvd], [server.pid])\n+NS_CHECK_EXEC([client], [nc -w 1 172.16.1.2 8080 < datafile], [0], [ignore], [ignore])\n+\n+OVS_WAIT_UNTIL([test -s tcp_server.rcvd])\n+check cmp datafile tcp_server.rcvd\n+\n+# Clean up first direction.\n+kill $(cat server.pid) 2>/dev/null || true\n+\n+# Test server -> client direction.\n+NETNS_DAEMONIZE([client], [nc -l 172.16.1.3 8081 > tcp_client.rcvd], [client.pid])\n+NS_CHECK_EXEC([server], [nc -w 1 172.16.1.3 8081 < datafile], [0], [ignore], [ignore])\n+\n+OVS_WAIT_UNTIL([test -s tcp_client.rcvd])\n+check cmp datafile tcp_client.rcvd\n+\n+OVN_CLEANUP_CONTROLLER([hv1])\n+OVN_CLEANUP_NORTHD\n+\n+as\n+OVS_TRAFFIC_VSWITCHD_STOP([\"/failed to query port patch-.*/d\n+/connection dropped.*/d\n+\"])\n+AT_CLEANUP\n+])\n+\n+OVN_FOR_EACH_NORTHD([\n+AT_SETUP([ACL CT translation - negative test])\n+AT_KEYWORDS([acl_ct_translation_udp_fragmentation_negative])\n+AT_SKIP_IF([test $HAVE_TCPDUMP = no])\n+\n+CHECK_CONNTRACK()\n+\n+ovn_start\n+OVS_TRAFFIC_VSWITCHD_START()\n+ADD_BR([br-int])\n+\n+# Set external-ids in br-int needed for ovn-controller\n+check ovs-vsctl \\\n+ -- set Open_vSwitch . external-ids:system-id=hv1 \\\n+ -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \\\n+ -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \\\n+ -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \\\n+ -- set bridge br-int fail-mode=secure other-config:disable-in-band=true\n+\n+# Start ovn-controller\n+start_daemon ovn-controller\n+\n+check ovn-nbctl ls-add internal\n+check ovn-nbctl lsp-add internal client \\\n+ -- lsp-set-addresses client \"f0:00:00:01:02:03 172.16.1.3\"\n+check ovn-nbctl lsp-add internal server \\\n+ -- lsp-set-addresses server \"f0:00:0f:01:02:03 172.16.1.2\"\n+\n+ADD_NAMESPACES(client)\n+ADD_VETH(client, client, br-int, \"172.16.1.3/24\", \"f0:00:00:01:02:03\", \\\n+ \"172.16.1.1\")\n+\n+ADD_NAMESPACES(server)\n+ADD_VETH(server, server, br-int, \"172.16.1.2/24\", \"f0:00:0f:01:02:03\", \\\n+ \"172.16.1.1\")\n+\n+# Create port group with both client and server.\n+client_uuid=$(fetch_column nb:logical_switch_port _uuid name=client)\n+server_uuid=$(fetch_column nb:logical_switch_port _uuid name=server)\n+check ovn-nbctl pg-add internal_vms $client_uuid $server_uuid\n+\n+# ACL rules - allow outgoing traffic.\n+check ovn-nbctl acl-add internal_vms from-lport 1002 \"inport == @internal_vms && ip4 && udp\" allow-related\n+check ovn-nbctl acl-add internal_vms from-lport 1002 \"inport == @internal_vms && ip4\" allow-related\n+\n+# ACL rules - allow incoming UDP ONLY on port 5060 (NOT 4000).\n+check ovn-nbctl acl-add internal_vms to-lport 1002 \"outport == @internal_vms && ip4 && udp && udp.dst == 5060\" allow-related\n+\n+# Allow ARP.\n+check ovn-nbctl acl-add internal_vms to-lport 1002 \"outport == @internal_vms && arp\" allow-related\n+\n+# Drop rule - this should drop traffic on port 4000.\n+check ovn-nbctl --log --severity=info acl-add internal_vms to-lport 100 \"outport == @internal_vms\" drop\n+\n+# Enable ACL CT translation.\n+check ovn-nbctl --wait=hv set NB_Global . options:acl_ct_translation=true\n+\n+check ovn-nbctl --wait=hv sync\n+check ovs-appctl dpctl/flush-conntrack\n+\n+# Start tcpdump to capture any incoming packets on server.\n+NETNS_START_TCPDUMP([server], [-U -i server -Q in -nn udp port 4000], [tcpdump-drop-server])\n+\n+# Start UDP listener on server (on port 4000 which is NOT allowed by ACLs).\n+NETNS_DAEMONIZE([server], [nc -l -u 172.16.1.2 4000 > udp_drop.rcvd], [drop_server.pid])\n+\n+# Client sends to server on disallowed port.\n+NS_CHECK_EXEC([client], [echo \"test\" | nc -w 1 -u 172.16.1.2 4000], [0], [ignore], [ignore])\n+\n+# Wait a bit for any packets to arrive.\n+sleep 2\n+\n+# Verify server did NOT receive any data (file should be empty or not exist).\n+AT_CHECK([test ! -s udp_drop.rcvd], [0])\n+\n+# Verify tcpdump captured no packets (traffic was dropped by ACL).\n+AT_CHECK([test $(cat tcpdump-drop-server.tcpdump | wc -l) -eq 0], [0])\n+\n+OVN_CLEANUP_CONTROLLER([hv1])\n+OVN_CLEANUP_NORTHD\n+\n+as\n+OVS_TRAFFIC_VSWITCHD_STOP([\"/failed to query port patch-.*/d\n+/connection dropped.*/d\n+\"])\n+AT_CLEANUP\n+])\n+\n+OVN_FOR_EACH_NORTHD([\n+AT_SETUP([ACL CT translation with DHCP traffic])\n+AT_KEYWORDS([acl_ct_translation_dhcp])\n+AT_SKIP_IF([test $HAVE_DHCPD = no])\n+AT_SKIP_IF([test $HAVE_DHCLIENT = no])\n+\n+CHECK_CONNTRACK()\n+\n+ovn_start\n+OVS_TRAFFIC_VSWITCHD_START()\n+ADD_BR([br-int])\n+\n+# Set external-ids in br-int needed for ovn-controller\n+check ovs-vsctl \\\n+ -- set Open_vSwitch . external-ids:system-id=hv1 \\\n+ -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \\\n+ -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \\\n+ -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \\\n+ -- set bridge br-int fail-mode=secure other-config:disable-in-band=true\n+\n+# Start ovn-controller\n+start_daemon ovn-controller\n+\n+# Create logical switch.\n+check ovn-nbctl ls-add ls1\n+\n+# Create DHCP server port.\n+check ovn-nbctl lsp-add ls1 dhcp-server \\\n+ -- lsp-set-addresses dhcp-server \"00:00:00:00:02:00 192.168.1.2\"\n+\n+# Create DHCP client port.\n+check ovn-nbctl lsp-add ls1 dhcp-client \\\n+ -- lsp-set-addresses dhcp-client \"00:00:00:00:02:01\"\n+\n+# Add OVS ports.\n+ADD_NAMESPACES(server, client)\n+ADD_VETH(server, server, br-int, \"192.168.1.2/24\", \"00:00:00:00:02:00\")\n+ADD_VETH(client, client, br-int, \"0.0.0.0/0\", \"00:00:00:00:02:01\")\n+\n+# Bind logical ports to OVS ports.\n+check ovs-vsctl set Interface ovs-server external_ids:iface-id=dhcp-server\n+check ovs-vsctl set Interface ovs-client external_ids:iface-id=dhcp-client\n+\n+# Create port group with both ports.\n+server_uuid=$(fetch_column nb:logical_switch_port _uuid name=dhcp-server)\n+client_uuid=$(fetch_column nb:logical_switch_port _uuid name=dhcp-client)\n+check ovn-nbctl pg-add dhcp_vms $server_uuid $client_uuid\n+\n+# Add ACL rules - allow outgoing traffic from both ports.\n+check ovn-nbctl acl-add dhcp_vms from-lport 1002 \"inport == @dhcp_vms && ip4\" allow-related\n+check ovn-nbctl acl-add dhcp_vms from-lport 1002 \"inport == @dhcp_vms && udp\" allow-related\n+\n+# Add ACL rules for DHCP traffic (ports 67 and 68).\n+check ovn-nbctl acl-add dhcp_vms to-lport 1002 \"outport == @dhcp_vms && ip4 && udp && udp.dst == 67\" allow-related\n+check ovn-nbctl acl-add dhcp_vms to-lport 1002 \"outport == @dhcp_vms && ip4 && udp && udp.dst == 68\" allow-related\n+\n+# Allow ARP and broadcast.\n+check ovn-nbctl acl-add dhcp_vms to-lport 1002 \"outport == @dhcp_vms && arp\" allow-related\n+\n+# Add drop rule.\n+check ovn-nbctl --log --severity=info acl-add dhcp_vms to-lport 100 \"outport == @dhcp_vms\" drop\n+\n+# Enable ACL CT translation.\n+check ovn-nbctl --wait=hv set NB_Global . options:acl_ct_translation=true\n+\n+# Wait for flows to be installed.\n+check ovn-nbctl --wait=hv sync\n+\n+# Setup DHCP server configuration.\n+DHCP_TEST_DIR=\"$ovs_base/dhcp-test\"\n+mkdir -p $DHCP_TEST_DIR\n+\n+cat > $DHCP_TEST_DIR/dhcpd.conf <<EOF\n+subnet 192.168.1.0 netmask 255.255.255.0 {\n+ range 192.168.1.100 192.168.1.100;\n+ option routers 192.168.1.2;\n+ option broadcast-address 192.168.1.255;\n+ default-lease-time 60;\n+ max-lease-time 120;\n+}\n+EOF\n+\n+touch $DHCP_TEST_DIR/dhcpd.leases\n+chown root:dhcpd $DHCP_TEST_DIR $DHCP_TEST_DIR/dhcpd.leases\n+chmod 775 $DHCP_TEST_DIR\n+chmod 664 $DHCP_TEST_DIR/dhcpd.leases\n+\n+# Start dhcpd as DHCP server in the server namespace.\n+NETNS_DAEMONIZE([server], [dhcpd -4 -f -cf $DHCP_TEST_DIR/dhcpd.conf server > $DHCP_TEST_DIR/dhcpd.log 2>&1], [dhcpd.pid])\n+\n+# Give dhcpd time to start.\n+sleep 1\n+\n+# Request IP via DHCP using dhclient.\n+NS_CHECK_EXEC([client], [dhclient -1 -v -lf $DHCP_TEST_DIR/dhclient.lease -pf $DHCP_TEST_DIR/dhclient.pid client], [0], [ignore], [ignore])\n+# Register cleanup handler to kill dhclient when test exits.\n+on_exit 'kill $(cat $DHCP_TEST_DIR/dhclient.pid) 2>/dev/null || true'\n+\n+# Verify client got an IP address from DHCP.\n+NS_CHECK_EXEC([client], [ip addr show client | grep -q \"192.168.1.100\"], [0])\n+\n+OVN_CLEANUP_CONTROLLER([hv1])\n+\n+OVN_CLEANUP_NORTHD\n+\n+as\n+OVS_TRAFFIC_VSWITCHD_STOP([\"/failed to query port patch-.*/d\n+/connection dropped.*/d\n+\"])\n+AT_CLEANUP\n+])\n", "prefixes": [ "ovs-dev", "v5" ] }