Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/patches/2220048/?format=api
{ "id": 2220048, "url": "http://patchwork.ozlabs.org/api/patches/2220048/?format=api", "web_url": "http://patchwork.ozlabs.org/project/ovn/patch/20260406044444.311931-5-naveen.yerramneni@nutanix.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": "<20260406044444.311931-5-naveen.yerramneni@nutanix.com>", "list_archive_url": null, "date": "2026-04-06T04:44:44", "name": "[ovs-dev,v2,4/4] northd, tests: Network Function vtap mode logical flow changes.", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "87a7748bdd2c9390b48b41ab9350e32012d1fd09", "submitter": { "id": 85983, "url": "http://patchwork.ozlabs.org/api/people/85983/?format=api", "name": "Naveen Yerramneni", "email": "naveen.yerramneni@nutanix.com" }, "delegate": null, "mbox": "http://patchwork.ozlabs.org/project/ovn/patch/20260406044444.311931-5-naveen.yerramneni@nutanix.com/mbox/", "series": [ { "id": 498821, "url": "http://patchwork.ozlabs.org/api/series/498821/?format=api", "web_url": "http://patchwork.ozlabs.org/project/ovn/list/?series=498821", "date": "2026-04-06T04:44:41", "name": "Add support for Network Function VTAP mode.", "version": 2, "mbox": "http://patchwork.ozlabs.org/series/498821/mbox/" } ], "comments": "http://patchwork.ozlabs.org/api/patches/2220048/comments/", "check": "fail", "checks": "http://patchwork.ozlabs.org/api/patches/2220048/checks/", "tags": {}, "related": [], "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\" (2048-bit key;\n unprotected) header.d=nutanix.com header.i=@nutanix.com header.a=rsa-sha256\n header.s=proofpoint20171006 header.b=0hkzr0NE;\n\tdkim=fail reason=\"signature verification failed\" (2048-bit key;\n unprotected) header.d=nutanix.com header.i=@nutanix.com header.a=rsa-sha256\n header.s=selector1 header.b=NEzslFHq;\n\tdkim-atps=neutral", "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org\n (client-ip=2605:bc80:3010::136; helo=smtp3.osuosl.org;\n envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org)", "smtp3.osuosl.org;\n\tdkim=fail reason=\"signature verification failed\" (2048-bit key)\n header.d=nutanix.com header.i=@nutanix.com header.a=rsa-sha256\n header.s=proofpoint20171006 header.b=0hkzr0NE;\n\tdkim=fail reason=\"signature verification failed\" (2048-bit key,\n unprotected) header.d=nutanix.com header.i=@nutanix.com header.a=rsa-sha256\n header.s=selector1 header.b=NEzslFHq", "smtp4.osuosl.org;\n dmarc=pass (p=none dis=none) header.from=nutanix.com", "smtp4.osuosl.org;\n dkim=pass (2048-bit key) header.d=nutanix.com header.i=@nutanix.com\n header.a=rsa-sha256 header.s=proofpoint20171006 header.b=0hkzr0NE;\n dkim=pass (2048-bit key,\n unprotected) header.d=nutanix.com header.i=@nutanix.com header.a=rsa-sha256\n header.s=selector1 header.b=NEzslFHq" ], "Received": [ "from smtp3.osuosl.org (smtp3.osuosl.org [IPv6:2605:bc80:3010::136])\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 4fpy0y2Vwjz1xy1\n\tfor <incoming@patchwork.ozlabs.org>; Mon, 06 Apr 2026 15:04:18 +1000 (AEST)", "from localhost (localhost [127.0.0.1])\n\tby smtp3.osuosl.org (Postfix) with ESMTP id 4CA9160A47;\n\tMon, 6 Apr 2026 05:04:16 +0000 (UTC)", "from smtp3.osuosl.org ([127.0.0.1])\n by localhost (smtp3.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP\n id M40DFqutzivP; Mon, 6 Apr 2026 05:04:09 +0000 (UTC)", "from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56])\n\tby smtp3.osuosl.org (Postfix) with ESMTPS id 618576078F;\n\tMon, 6 Apr 2026 05:04:09 +0000 (UTC)", "from lf-lists.osuosl.org (localhost [127.0.0.1])\n\tby lists.linuxfoundation.org (Postfix) with ESMTP id 1171BC0908;\n\tMon, 6 Apr 2026 05:04:09 +0000 (UTC)", "from smtp4.osuosl.org (smtp4.osuosl.org [140.211.166.137])\n by lists.linuxfoundation.org (Postfix) with ESMTP id A7CD6C04FA\n for <dev@openvswitch.org>; Mon, 6 Apr 2026 05:04:07 +0000 (UTC)", "from localhost (localhost [127.0.0.1])\n by smtp4.osuosl.org (Postfix) with ESMTP id 6D5574091B\n for <dev@openvswitch.org>; Mon, 6 Apr 2026 05:04:07 +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 tP0iIWZTwRic for <dev@openvswitch.org>;\n Mon, 6 Apr 2026 05:04:05 +0000 (UTC)", "from mx0a-002c1b01.pphosted.com (mx0a-002c1b01.pphosted.com\n [148.163.151.68])\n by smtp4.osuosl.org (Postfix) with ESMTPS id 4D9EB408FD\n for <dev@openvswitch.org>; Mon, 6 Apr 2026 05:04:04 +0000 (UTC)", "from pps.filterd (m0127840.ppops.net [127.0.0.1])\n by mx0a-002c1b01.pphosted.com (8.18.1.11/8.18.1.11) with ESMTP id\n 635Lt2d93233678\n for <dev@openvswitch.org>; Sun, 5 Apr 2026 21:45:37 -0700", "from dm5pr21cu001.outbound.protection.outlook.com\n (mail-centralusazon11021135.outbound.protection.outlook.com [52.101.62.135])\n by mx0a-002c1b01.pphosted.com (PPS) with ESMTPS id 4daxyn24fw-1\n (version=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384 bits=256 verify=NOT)\n for <dev@openvswitch.org>; Sun, 05 Apr 2026 21:45:36 -0700 (PDT)", "from SJ0PR02MB8369.namprd02.prod.outlook.com (2603:10b6:a03:3e4::5)\n by CH0PR02MB8091.namprd02.prod.outlook.com (2603:10b6:610:10b::13)\n with Microsoft SMTP Server (version=TLS1_2,\n cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.9769.18; Mon, 6 Apr\n 2026 04:45:34 +0000", "from SJ0PR02MB8369.namprd02.prod.outlook.com\n ([fe80::7c30:f56e:e3cb:cb38]) by SJ0PR02MB8369.namprd02.prod.outlook.com\n ([fe80::7c30:f56e:e3cb:cb38%4]) with mapi id 15.20.9769.018; Mon, 6 Apr 2026\n 04:45:34 +0000" ], "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 smtp3.osuosl.org 618576078F", "OpenDKIM Filter v2.11.0 smtp4.osuosl.org 4D9EB408FD" ], "X-Greylist": "delayed 1146 seconds by postgrey-1.37 at util1.osuosl.org;\n Mon, 06 Apr 2026 05:04:05 UTC", "DMARC-Filter": "OpenDMARC Filter v1.4.2 smtp4.osuosl.org 4D9EB408FD", "Received-SPF": "Pass (mailfrom) identity=mailfrom; client-ip=148.163.151.68;\n helo=mx0a-002c1b01.pphosted.com; envelope-from=naveen.yerramneni@nutanix.com;\n receiver=<UNKNOWN>", "DKIM-Signature": [ "v=1; a=rsa-sha256; c=relaxed/relaxed; d=nutanix.com; h=\n cc:content-transfer-encoding:content-type:date:from:in-reply-to\n :message-id:mime-version:references:subject:to; s=\n proofpoint20171006; bh=fW8olAuDhIdCF+rtZsLwSDFcTF7XX+pQXAwY7aT3P\n ds=; b=0hkzr0NEqA3EHXblDaY6aLgzJsDQ0fkc7rPOE4kbapyYCemwKl1L0Ocdg\n mlhlFweCrb9t2dr4DGdzV1yRzxWYoTBnbM3dqn5o7WmcHtsvpJlxQ+Bpkop6ksiR\n AhbOkyVND7kIb3oNr7Cwt3fgav+laKJyjKP6CyblBVjuMkI57vPC4/U3UTwgMkL8\n JGQaMrOpUUQ2zjHxTect4W7GIB0xjvetpQ2UMr5dCJD+1zQr4jPwBE0iMudxsyEQ\n uJhKeYzVqWjXuObTvFdyY8ti0jh/Yq2k5BE3dOBwJ1yC+VskEB57EMjjeUWAPlpn\n HnpQ0EG9sCrYmGmpKOu0ywK1h7yEQ==", "v=1; a=rsa-sha256; c=relaxed/relaxed; d=nutanix.com;\n s=selector1;\n h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck;\n bh=fW8olAuDhIdCF+rtZsLwSDFcTF7XX+pQXAwY7aT3Pds=;\n b=NEzslFHqk+cva+ZHqxOk8rD6TCK0WK0nnrtJ5DBQGbeVxe1JuDIT3QrM58NMrXNLaWKRZIfd4f77tJUZleAQRP8mZItoWUbsLZEPnDxcKGqTCQxnhfXEAvbaLsGWZm79wXIDVNUWXzT9S9L6/3BBTYDnvALdOalKLGZvV21uvh6b41ouL/rnOtEt2Lt5yzD/5sDuxj5j7psNuw77pJ3sUxL02jdYqdWoQMLSw8Oj6dbEh2qCMzJByVXdZzixQqTT/J3SbY558KJykgxGPmA52lo0TOGALrgOPIhQp8bqrAaAKqHr0+Ds6xRFMzA1zcZEz9lP+ZvNP2q0xaZ6x5T4Iw==" ], "ARC-Seal": "i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none;\n b=D3ToXK7iZ419fW4iEWZ93BjmcCl8xZwftkCgBwgDj+FPydoXDYye6z9HhsZzho4rzx2bWOeOOIjWjEFdlTQ51z0t0HXBWCH2u14W/LLaBiEqZI1mKQl3ih2A0NZoPkIcmTCTcR5mm5X5xYMZBgwoKgG/Iv+7sjCbunctvrMs6lFmClnZhJ6DtBLPO9eyvQBn96vXmGxS/DwB8A5BLlou4Araqq2eO9FMzulgTk0PY/w4+zhEUh34xObMILt49z5nJT2eqKsqTbUE9YKiv0aTnaBaIu9XQIOvRhlJk7+0h/w2n2R6oMIdIMDIzG3MAp15GfPgrXzDsp/5kY/mH5rGyw==", "ARC-Message-Signature": "i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com;\n s=arcselector10001;\n h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1;\n bh=fW8olAuDhIdCF+rtZsLwSDFcTF7XX+pQXAwY7aT3Pds=;\n b=xqUdP2vkTfK4onnXuO1tacVqI+O5KKA3pvy0c7+ntehOBtEhwc0IGE8KxR7el5Kp1G1Tq8y0shGxsDTsu9pkAmzirJbr7QdBTypBdQgd8UG4wGjPwMcI8RvztzTF+nvgCfpRP3KPDzDq6AdcT+CL18T15B8lGMbN7J8VYTTtSGUiVmP8/rNAm8fUSlxrC20hWnQKXxWQaQ/sLsT8EVPWgPRBmdRgdC7IfRRBSvmD43bCgyBwOx0JOztX1APUyYNW9yKfaoo5EJmS7pTMFfjryHm4zm8ZC71gCUvl796/TV9zUkVcS5xdEWN2zc6W/qpFAmCgQuk8KPMx/BMzLzOTjA==", "ARC-Authentication-Results": "i=1; mx.microsoft.com 1; spf=pass\n smtp.mailfrom=nutanix.com; dmarc=pass action=none header.from=nutanix.com;\n dkim=pass header.d=nutanix.com; arc=none", "From": "Naveen Yerramneni <naveen.yerramneni@nutanix.com>", "To": "dev@openvswitch.org", "Date": "Mon, 6 Apr 2026 04:44:44 +0000", "Message-ID": "<20260406044444.311931-5-naveen.yerramneni@nutanix.com>", "X-Mailer": "git-send-email 2.43.5", "In-Reply-To": "<20260406044444.311931-1-naveen.yerramneni@nutanix.com>", "References": "<20260406044444.311931-1-naveen.yerramneni@nutanix.com>", "X-ClientProxiedBy": "CY8PR10CA0035.namprd10.prod.outlook.com\n (2603:10b6:930:4b::20) To SJ0PR02MB8369.namprd02.prod.outlook.com\n (2603:10b6:a03:3e4::5)", "MIME-Version": "1.0", "X-MS-PublicTrafficType": "Email", "X-MS-TrafficTypeDiagnostic": "SJ0PR02MB8369:EE_|CH0PR02MB8091:EE_", "X-MS-Office365-Filtering-Correlation-Id": "7c04e99c-01c8-4a34-ed31-08de9397572a", "x-proofpoint-crosstenant": "true", "X-MS-Exchange-SenderADCheck": "1", "X-MS-Exchange-AntiSpam-Relay": "0", "X-Microsoft-Antispam": "BCL:0;\n ARA:13230040|1800799024|376014|52116014|366016|38350700014|22082099003|18002099003|56012099003;", "X-Microsoft-Antispam-Message-Info": "\n twL9a6hDQOT7ZzjPKbRoAAhnGCxxk3VZ4IPvyC+Me92NxTMH47BZ0mF8JQErwFj5KLrGZVGC3qbyn4V6rnqrqMbTQUfyL2iOHGfxf/o7o+fknHiVbZ2YHxDgkfl8QxTPgG/g06IpCDv9mLRd+CfHo4jZpjYOH2ylfjSbBqBLJf1FsVxOwNHhnxBCE/oG9nX6tuO1yecXkQOzImUJ0vyewEsqqnGAch0O6GW0+zjjT7rDHnCFDongPsciNYcmZfaD4mubJK7Aw1LuHMA+shXLX05ETgnwE5oMFJHhs4At0C9JTy5eONpVJDGfa4T/6N3b4EqDsuHTFcx2XIRJgv/2YfuKixj5/TATjs3Nz5enAo3W/Y+lLozpskThCuvjkho0zNf5M0iQxn8wWlXxSfrBOK+sI6oLyBjDNIb5WC6LpW26EHCXwLNk2m0t6oV2bYdcMAmsza3i2JOygwrOCGLUKZybknjRBFLhpRX4wnRXf9XomT/A4FS83Ji4vYpxp54QA1671XOGRLfZSiODXAF0lux9BTspTWT9KskAUNNG6izsfJ4MPhbjeZK/9gxuFPLrbb3RJCL6Dh85Exwcvm8fAUqpfG7c5MzK5mS+t/UuQZn+TLZyl2fLw1UVH5qrnvpg/7ZpQbLDb0zLFfVZ/g/I216xFh0F4PPSGnTKIfFLtgtytRSHRNSayhQ+WFofan09oRoB+hEngF2SEON0+O4sik1yZh97ud/eRFmxob0/FpnGi8s5Ih/QMdkKR9r0QLe9KUgcHSihSkqeTdft1rvGvd5p8FMYK8DPqZACbrQ6PJU=", "X-Forefront-Antispam-Report": "CIP:255.255.255.255; CTRY:; LANG:en; SCL:1; SRV:;\n IPV:NLI; SFV:NSPM; H:SJ0PR02MB8369.namprd02.prod.outlook.com; PTR:; CAT:NONE;\n SFS:(13230040)(1800799024)(376014)(52116014)(366016)(38350700014)(22082099003)(18002099003)(56012099003);\n DIR:OUT; SFP:1102;", "X-MS-Exchange-AntiSpam-MessageData-ChunkCount": "1", "X-MS-Exchange-AntiSpam-MessageData-0": "\n tujBlRv3lGiDVY034tvQc2vxI9876PhaXVEC4Qe/V33cVYm1mkyeBk6hrvS+a0YrKuIIJ5XlMjnYGt8CMpUqwue0nfU8iWazm85RXzpL/bpLS9FD5GTrd3lLf6UcqsSUMjWqOJYJ1pbWsdaef9j/zM3jGqvOx/6D5c55cxKTAS2fJmsTbV3pJ5VB3SZi9N9aOAIL3EbkgGVX3lXh2hE7zLacaOi2aLPzCZJmnSDytPd42M7KLcmA5H0ZbpZ3N7P2DySLRanAAxUqtSPEAkg67P0V2DHMP2n3nE+Uehz4WSjkgJuH39cNuJO1wiRQZhZhYIJCHq+oGfSRw+ansYb+xmWTHgZziK6U73LM25IeK28/2BUtr70qC89WszEIMXdlJwCR2ArQkWSNUENFh+1y6XNZgnVm9alYRsE+bnSv2MZlu7SbhBNXam+ZbQRzvKrAYLxRbxsoSUGmIihaR8cmISB8LfYjPTfIUhiyr2iUF1DJ4Ux9uB/m2xdybzqhAmU9JpZfYVtXGXNdYuIPY/IOdxIwta3y0HkddLbY3QKIL03wISa2CbatQrVGSbp5u6JXy/ibO5MexotS2VRe2nZP/iEN25IZiIJ/HDf605K8IUK2GnEFrvgoGV+U8gXqOZZyUKFQOFSFEfnXqum2CMxIVXtA9+u3LAbPoSBWm7EK4F9dnFJClddbmfXisK3nUzAlbMfXQk010I+yRiKfuEvNRnOjiiyqChPi5UDFPnqnJZ5tOAFZF4iv0vthxInJqHKakorbHd8vJoXcB03YOYprOnVxtWgqX/M2LZ3BoLR7lA1RiPfsrI1vi6XrOv7ChOl8d+IWyViqaxVbm5lgmfwbwlbkA0aX2BCWghNR0B+9+u2BPrP7EbDLI/O/zE6EGk0O6OaH7kl5qV32YMLJfa1Lo6h7nBf5OB2DOEzOC/FWd7hei61TQ73IW3Loblz3/3EAyhX1jiDIA6MyPGuddSlB1AZRF46pWnNyJl+ZUK015rs1MN31eLArHDOClNh5Xw3SS9u9fXJqgs6bNdZWissRGquuK6zUYJ+EnofGzUzDzxIha0NQNbJE9eOoNuvfWzZ2+WeFSvuBJ6hA1rZQfTMwbYPvhdCFJHEwg/uaOz9hOp53LiPQcFpCM3/AVLnUxfPgQsiw4IDxOAvZ/qlk9HW+AKxuImaf1Y2e+9EM/4k6d2GGHPWJh3WApHgvLBa/ffB3oiXZeQEnJb8Ttj60TCGV1tFb04Y66HcdxmJtagtvdjy/BncN60PC9T4r1UZze8iH4xfYbnxsKkNZX5L9slLfbSoH5lDpbjBbkUwP84aQjeM2zUkB5QwDJ2QNxPuwMZc8wHvcHVXdQythe1kKBu/luqMY35CJ0aB/+ZICAmNThrqla1kj18R9vUcRWXbS0vCSbRG9L1oSi6kwq1ME6NNS9K/id90zDHd9jv7mpg5x6Hsv3PTViirsQ2hpnK4i9zGQzCgw5Q/Tb728D38hLsAYw5GOvpSV8YN2firnUN48n18D8SFBD3XKIWBjm3HDJCTd/rn+fvQAZ4KhBoOGc80d9mC82qUBaEsbxBm1hjjcCjo+SGEVFy/6jbMtiYCzbl4/D59hswgTfcBtN53PpozuRa+LpoNjkXSVqpt6TpdBhGJuWntTLM4reKrgqlmpAsVsnP3MEczKq3PdfTmqHYn6tY1EiqoNdrJdOtYYG+hFHrpaQLV0chlnRmP44oygCNZ/jtvJN+PAqAhrUsPDrPxjLp77+ptfD4Pkj6OgkNHpqrM=", "X-Exchange-RoutingPolicyChecked": "\n MC0EsQ7Hz/2A6BO7EIqqzekU8t4gOlJDP5HB/ak0GwrKGWr+iR1j259hEtLJ54unVKt+Hb4B/xkEIrth+KL1FJ9H8zjzl46Z6tRJiCtBd7KfVyTM7pT/2rvP4pwzXO19E4di2rBqOLC7GGUiVTnhSrfsMres4CwSTNRsRPdlCZ11GiqyYX6wwz8g1Ij4tmQn7BPYmQBbItntGfEFNEP09N/ptBVxJDkDR9XmojNL4qmfiDWDs8SKaDZCGJyhUP8K3TVn/B9b4zxojF3ZoBN3xq/ADPBEUrMGLt/JFAs+Ban+9lsK8yRbnkim8UH0wndwPblQoqjyWiwP7yVGoyF9ow==", "X-OriginatorOrg": "nutanix.com", "X-MS-Exchange-CrossTenant-Network-Message-Id": "\n 7c04e99c-01c8-4a34-ed31-08de9397572a", "X-MS-Exchange-CrossTenant-AuthSource": "SJ0PR02MB8369.namprd02.prod.outlook.com", "X-MS-Exchange-CrossTenant-AuthAs": "Internal", "X-MS-Exchange-CrossTenant-OriginalArrivalTime": "06 Apr 2026 04:45:34.7754 (UTC)", "X-MS-Exchange-CrossTenant-FromEntityHeader": "Hosted", "X-MS-Exchange-CrossTenant-Id": "bb047546-786f-4de1-bd75-24e5b6f79043", "X-MS-Exchange-CrossTenant-MailboxType": "HOSTED", "X-MS-Exchange-CrossTenant-UserPrincipalName": "\n Vdmzi3IrOoKjCs41GW1/VItp1CuEhpRY8cSjNJBKL+pwaqabJtieLw5/zrHDpKeyEyocMGU8sqVBHxTUsX/LPT+y8JAIX9D12G6pk/X0Faw=", "X-MS-Exchange-Transport-CrossTenantHeadersStamped": "CH0PR02MB8091", "X-Authority-Analysis": "v=2.4 cv=IuMTsb/g c=1 sm=1 tr=0 ts=69d33a70 cx=c_pps\n a=rzUdehR910POqGivuQKQfw==:117 a=6eWqkTHjU83fiwn7nKZWdM+Sl24=:19\n a=z/mQ4Ysz8XfWz/Q5cLBRGdckG28=:19 a=lCpzRmAYbLLaTzLvsPZ7Mbvzbb8=:19\n a=xqWC_Br6kY4A:10 a=A5OVakUREuEA:10 a=0kUYKlekyDsA:10\n a=VkNPw1HP01LnGYTKEx00:22 a=VofLwUrZ8Iiv6rRUPXIb:22 a=_-M8LpHI31CeLmyZm6wg:22\n a=64Cc0HZtAAAA:8 a=tWQQK1fogus7-4CvI1wA:9 a=M4Op4SvhICmsZ2v1:21\n a=O8hF6Hzn-FEA:10", "X-Proofpoint-Spam-Details-Enc": "AW1haW4tMjYwNDA2MDA0MiBTYWx0ZWRfXyV32HVD9Fgxo\n IL7KklqrIieKM/YsTQ2oLk8QCYdCqEk0QR29Z+4we4rxzeCsY+p4EwJAaVWzB4cKj1Oa+QXNIvA\n hpAWjt4LshsdYDZpCf1oDnjkG/U1CaIVpcwFpZWTdZ0oxJkT0ZH2eI3B3bhZv4na/Ju+2lC6fRb\n SVRSJYVaJcAfv3ai35ebCFf13gdyaOK3YLBJxU89hJTPDQqfIcAIou4zL7VnoLwmZW6vDfuj1VW\n skma2jmYSMHNxfnx3mKytUMF5X//zH7X4Z+34QwnGXjm9pg6GI6K2YFyxU8Qqf4fblmvuqi3zyx\n wkrSuX0I/Og1cyEInPDJ6L7sOXt4Fk2PbcVF3EGdataDZeUjGVjKvIcIeBsGOgSeQ6lATRp2IvA\n hZw3oSyO98yUW2bxl2wUAE9aSImFUxdgs9LjcExgd50d2VB0cArvhaXVDCMLc5FsRANadd2X8JO\n Vq2oGprihfgOXBfu6rw==", "X-Proofpoint-GUID": "aJGvKtB7xU5lvSw-_l-29O0d4XOeCBsg", "X-Proofpoint-ORIG-GUID": "aJGvKtB7xU5lvSw-_l-29O0d4XOeCBsg", "X-Proofpoint-Virus-Version": "vendor=baseguard\n engine=ICAP:2.0.293,Aquarius:18.0.1143,Hydra:6.1.51,FMLib:17.12.100.49\n definitions=2026-04-06_01,2026-04-03_01,2025-10-01_01", "X-Proofpoint-Spam-Reason": "safe", "Subject": "[ovs-dev] [PATCH OVN v2 4/4] northd,\n tests: Network Function vtap mode logical flow changes.", "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>", "Cc": "Sragdhara Datta Chaudhuri <sragdha.chaudhu@nutanix.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": "Implement vtap mode in northd where traffic is cloned to the NF port\nwhile the original packet continues to its destination.\n\n- Generate mirror flows that clone packets to NF port\n- Determine NF health from port binding status (no health probes)\n- Validate that health_check requires both inport and outport\n- Clear ct_state for packets egressing through localnet ports\n to avoid matching flows in egress stage based on egress CT info\n\nNote:\n----\nFor inline NF health status, updated the code to consider\nport binding state along with service monitor health.\n\nSigned-off-by: Naveen Yerramneni <naveen.yerramneni@nutanix.com>\nAcked-by: Sragdhara Datta Chaudhuri <sragdha.chaudhu@nutanix.com>\nAcked-by: Aditya Mehakare <aditya.mehakare@nutanix.com>\n---\n NEWS | 5 +\n northd/northd.c | 438 +++++++++++++++++++++++++++++++++++-----\n northd/ovn-northd.8.xml | 270 +++++++++++++++----------\n tests/ovn-northd.at | 231 ++++++++++++++++++++-\n tests/ovn.at | 372 +++++++++++++++++++++++++++++++++-\n tests/system-ovn.at | 265 +++++++++++++++++++++++-\n 6 files changed, 1417 insertions(+), 164 deletions(-)", "diff": "diff --git a/NEWS b/NEWS\nindex 888946b54..d9f437910 100644\n--- a/NEWS\n+++ b/NEWS\n@@ -1,5 +1,10 @@\n Post v26.03.0\n -------------\n+ - Add vtap mode support for Network Function. In vtap mode, traffic matching\n+ ACLs is mirrored to the network function while continuing to flow to the\n+ original destination. This enables passive monitoring use cases where\n+ network functions can observe traffic without being inline in\n+ the data path.\n \n OVN v26.03.0 - xxx xx xxxx\n --------------------------\ndiff --git a/northd/northd.c b/northd/northd.c\nindex ac2361417..c560a65cf 100644\n--- a/northd/northd.c\n+++ b/northd/northd.c\n@@ -3238,6 +3238,66 @@ create_or_get_service_mon(struct ovsdb_idl_txn *ovnsb_txn,\n return mon_info;\n }\n \n+enum nf_port_binding_state{\n+ NF_PORT_STATE_UNKNOWN,\n+ NF_PORT_STATE_CHASSIS_INVALID,\n+ NF_PORT_STATE_DOWN,\n+ NF_PORT_STATE_UP\n+};\n+\n+static enum nf_port_binding_state\n+network_function_port_binding_state(const char **ports, uint8_t n_ports,\n+ struct hmap *ls_ports,\n+ const char **chassis_name_pptr)\n+{\n+ const char *chassis_name = NULL;\n+ enum nf_port_binding_state port_state = NF_PORT_STATE_UNKNOWN;\n+ uint8_t n_port_up = 0;\n+\n+ for (int i = 0; i < n_ports; i++) {\n+ const char *port = ports[i];\n+ struct ovn_port *op = ovn_port_find(ls_ports, port);\n+ if (op == NULL) {\n+ static struct vlog_rate_limit rl =\n+ VLOG_RATE_LIMIT_INIT(1, 1);\n+ VLOG_ERR_RL(&rl, \"NetworkFunction: skip health check, port:%s \"\n+ \"not found\", port);\n+ return port_state;\n+ }\n+ if (op->sb && op->sb->chassis) {\n+ if (chassis_name == NULL) {\n+ chassis_name = op->sb->chassis->name;\n+ } else if (strcmp(chassis_name, op->sb->chassis->name)) {\n+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);\n+ VLOG_ERR_RL(&rl, \"NetworkFunction: chassis mismatch \"\n+ \"for port:%s chassis:%s peer_port_chassis:%s\",\n+ port, op->sb->chassis->name, chassis_name);\n+ return NF_PORT_STATE_CHASSIS_INVALID;\n+ }\n+ if (op->sb->n_up && op->sb->up[0]) {\n+ n_port_up++;\n+ }\n+ } else {\n+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);\n+ VLOG_ERR_RL(&rl, \"NetworkFunction: chassis not set for port:%s\",\n+ port);\n+ return NF_PORT_STATE_CHASSIS_INVALID;\n+ }\n+ }\n+\n+ if (chassis_name_pptr) {\n+ *chassis_name_pptr = chassis_name;\n+ }\n+\n+ if (n_port_up == n_ports) {\n+ port_state = NF_PORT_STATE_UP;\n+ } else {\n+ port_state = NF_PORT_STATE_DOWN;\n+ }\n+\n+ return port_state;\n+}\n+\n static void\n ovn_nf_svc_create(struct ovsdb_idl_txn *ovnsb_txn,\n const struct nbrec_network_function *nbrec_nf,\n@@ -3256,29 +3316,22 @@ ovn_nf_svc_create(struct ovsdb_idl_txn *ovnsb_txn,\n }\n \n const char *ports[] = {nbrec_nf->outport->name, nbrec_nf->inport->name};\n+ size_t n_ports = ARRAY_SIZE(ports);\n const char *chassis_name = NULL;\n- bool port_up = true;\n \n- for (size_t i = 0; i < ARRAY_SIZE(ports); i++) {\n+ for (size_t i = 0; i < n_ports; i++) {\n const char *port = ports[i];\n sset_add(svc_monitor_lsps, port);\n- struct ovn_port *op = ovn_port_find(ls_ports, port);\n- if (op == NULL) {\n- VLOG_ERR_RL(&rl, \"NetworkFunction: skip health check, port:%s \"\n- \"not found\", port);\n- return;\n- }\n+ }\n \n- if (op->sb->chassis) {\n- if (chassis_name == NULL) {\n- chassis_name = op->sb->chassis->name;\n- } else if (strcmp(chassis_name, op->sb->chassis->name)) {\n- VLOG_ERR_RL(&rl, \"NetworkFunction: chassis mismatch \"\n- \"chassis:%s port:%s\\n\",\n- op->sb->chassis->name, port);\n- }\n- }\n- port_up = port_up && (op->sb->n_up && op->sb->up[0]);\n+ bool port_up = false;\n+ enum nf_port_binding_state port_state =\n+ network_function_port_binding_state(ports, n_ports, ls_ports,\n+ &chassis_name);\n+ if (port_state == NF_PORT_STATE_UNKNOWN) {\n+ return;\n+ } else if (port_state == NF_PORT_STATE_UP) {\n+ port_up = true;\n }\n \n struct service_monitor_info *mon_info =\n@@ -3801,6 +3854,16 @@ build_svc_monitors_data(\n NBREC_NETWORK_FUNCTION_TABLE_FOR_EACH (nbrec_nf,\n nbrec_network_function_table) {\n if (nbrec_nf->health_check) {\n+ /* For Network Function, health check requires both\n+ * inport and outport to be set.\n+ */\n+ if (!nbrec_nf->inport || !nbrec_nf->outport) {\n+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);\n+ VLOG_WARN_RL(&rl, \"NetworkFunction: health_check requires \"\n+ \"both inport and outport, skipping health_check \"\n+ \"for network_function:%s\", nbrec_nf->name);\n+ continue;\n+ }\n ovn_nf_svc_create(ovnsb_txn,\n nbrec_nf,\n svc_global_addresses,\n@@ -6382,10 +6445,14 @@ skip_port_from_conntrack(const struct ovn_datapath *od, struct ovn_port *op,\n * router on hostA, not hostB. This would only work with distributed\n * conntrack state across all chassis. */\n \n+ /* Clear the ct_state for packets egressing through localnet ports to\n+ * prevent them from matching flows in ls_out_acl_eval stage based on\n+ * ct_state carried over from ingress pipeline */\n const char *ingress_action = \"next;\";\n- const char *egress_action = has_stateful_acl\n- ? \"next;\"\n- : \"flags.pkt_sampled = 0; ct_clear; next;\";\n+ const char *egress_action =\n+ (has_stateful_acl && !lsp_is_localnet(op->nbsp))\n+ ? \"next;\"\n+ : \"flags.pkt_sampled = 0; ct_clear; next;\";\n \n char *ingress_match = xasprintf(\"ip && inport == %s\", op->json_key);\n char *egress_match = xasprintf(\"ip && outport == %s\", op->json_key);\n@@ -18842,10 +18909,35 @@ build_lswitch_stateful_nf(struct ovn_port *op,\n ds_cstr(match), ds_cstr(actions), lflow_ref);\n }\n \n+static const char*\n+network_function_group_get_mode(const struct nbrec_network_function_group *nfg)\n+{\n+ if (nfg->mode) {\n+ return nfg->mode;\n+ }\n+ return \"inline\";\n+}\n+\n+static bool\n+network_function_group_is_vtap_mode(\n+ const struct nbrec_network_function_group *nfg)\n+{\n+ const char *mode = network_function_group_get_mode(nfg);\n+ if (!strcasecmp(mode, \"vtap\")) {\n+ return true;\n+ }\n+ return false;\n+}\n+\n static const char*\n network_function_group_get_fallback(\n const struct nbrec_network_function_group *nfg)\n {\n+ /* For vtap mode, fallback is always defaulted to fail-open */\n+ if (network_function_group_is_vtap_mode(nfg)) {\n+ return \"fail-open\";\n+ }\n+\n if (nfg->fallback) {\n return nfg->fallback;\n }\n@@ -18873,7 +18965,8 @@ static void\n network_function_update_active(const struct nbrec_network_function_group *nfg,\n struct hmap *local_svc_monitors_map,\n struct hmap *ic_learned_svc_monitors_map,\n- const char *svc_monitor_ip_dst)\n+ const char *svc_monitor_ip_dst,\n+ struct hmap *ls_ports)\n {\n if (!nfg->n_network_function) {\n static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);\n@@ -18885,10 +18978,13 @@ network_function_update_active(const struct nbrec_network_function_group *nfg,\n }\n return;\n }\n+\n /* Array to store healthy network functions */\n struct nbrec_network_function **healthy_nfs =\n xmalloc(sizeof *healthy_nfs * nfg->n_network_function);\n struct nbrec_network_function *nf_active_prev = NULL;\n+ bool is_nfg_vtap = network_function_group_is_vtap_mode(nfg);\n+\n if (nfg->network_function_active) {\n nf_active_prev = nfg->network_function_active;\n }\n@@ -18898,25 +18994,62 @@ network_function_update_active(const struct nbrec_network_function_group *nfg,\n for (size_t i = 0; i < nfg->n_network_function; i++) {\n struct nbrec_network_function *nf = nfg->network_function[i];\n bool is_healthy = false;\n+ const char *inport = nf->inport->name;\n+ const char *ports[2] = {inport, NULL};\n+ size_t n_ports = 1;\n \n- if (nf->health_check == NULL) {\n- VLOG_DBG(\"NetworkFunction: Health check is not configured for \"\n- \"network_function %s, considering it healthy\", nf->name);\n- is_healthy = true;\n+ if (is_nfg_vtap) {\n+ if (nf->outport) {\n+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);\n+ VLOG_ERR_RL(&rl, \"NetworkFunction: outport should not be set \"\n+ \"for vtap mode, network_function:%s\", nf->name);\n+ continue;\n+ }\n+\n+ /* For vtap mode, consider network_function healthy based on\n+ * port binding status. */\n+ if (network_function_port_binding_state(ports, n_ports, ls_ports,\n+ NULL) == NF_PORT_STATE_UP) {\n+ is_healthy = true;\n+ }\n } else {\n- struct service_monitor_info *mon_info =\n- get_service_mon(local_svc_monitors_map,\n- ic_learned_svc_monitors_map,\n- svc_monitor_ip_dst,\n- nf->outport->name, 0, \"icmp\");\n- if (mon_info == NULL) {\n- static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);\n- VLOG_ERR_RL(&rl, \"NetworkFunction: Service_monitor is not \"\n- \"found for network_function:%s\", nf->name);\n- is_healthy = false;\n- } else if (mon_info->sbrec_mon->status\n- && !strcmp(mon_info->sbrec_mon->status, \"online\")) {\n+ /* For inline mode, inport and outport must be specified.\n+ * inport is mandatory in schema, check for outport. */\n+ if (nf->outport == NULL) {\n+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);\n+ VLOG_ERR_RL(&rl, \"NetworkFunction: outport must be set \"\n+ \"for inline mode, network_function:%s\", nf->name);\n+ continue;\n+ }\n+\n+ const char *outport = nf->outport->name;\n+ ports[n_ports++] = outport;\n+\n+ /* Always check port binding state first. */\n+ if (network_function_port_binding_state(ports, n_ports,\n+ ls_ports, NULL) != NF_PORT_STATE_UP) {\n+ continue;\n+ }\n+\n+ if (nf->health_check == NULL) {\n+ /* Consider network_function healthy based on port binding\n+ * status if health_check is not configured. */\n is_healthy = true;\n+ } else {\n+ struct service_monitor_info *mon_info =\n+ get_service_mon(local_svc_monitors_map,\n+ ic_learned_svc_monitors_map,\n+ svc_monitor_ip_dst,\n+ nf->outport->name, 0, \"icmp\");\n+ if (mon_info == NULL) {\n+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5,\n+ 1);\n+ VLOG_ERR_RL(&rl, \"NetworkFunction: Service_monitor is not \"\n+ \"found for network_function:%s\", nf->name);\n+ } else if (mon_info->sbrec_mon->status\n+ && !strcmp(mon_info->sbrec_mon->status, \"online\")) {\n+ is_healthy = true;\n+ }\n }\n }\n \n@@ -18965,15 +19098,15 @@ static void build_network_function_active(\n const struct nbrec_network_function_group_table *nbrec_nfg_table,\n struct hmap *local_svc_monitors_map,\n struct hmap *ic_learned_svc_monitors_map,\n- const char *svc_monitor_ip_dst)\n+ const char *svc_monitor_ip_dst,\n+ struct hmap *ls_ports)\n {\n const struct nbrec_network_function_group *nbrec_nfg;\n NBREC_NETWORK_FUNCTION_GROUP_TABLE_FOR_EACH (nbrec_nfg,\n nbrec_nfg_table) {\n- network_function_update_active(nbrec_nfg,\n- local_svc_monitors_map,\n- ic_learned_svc_monitors_map,\n- svc_monitor_ip_dst);\n+ network_function_update_active(nbrec_nfg, local_svc_monitors_map,\n+ ic_learned_svc_monitors_map,\n+ svc_monitor_ip_dst, ls_ports);\n }\n }\n \n@@ -19006,10 +19139,10 @@ network_function_configure_fail_open_flows(struct lflow_table *lflows,\n }\n \n static void\n-consider_network_function(struct lflow_table *lflows,\n- const struct ovn_datapath *od,\n- struct nbrec_network_function_group *nfg,\n- bool ingress, struct lflow_ref *lflow_ref)\n+consider_network_function_inline(struct lflow_table *lflows,\n+ const struct ovn_datapath *od,\n+ struct nbrec_network_function_group *nfg,\n+ bool ingress, struct lflow_ref *lflow_ref)\n {\n struct ds match = DS_EMPTY_INITIALIZER;\n struct ds action = DS_EMPTY_INITIALIZER;\n@@ -19034,6 +19167,15 @@ consider_network_function(struct lflow_table *lflows,\n return;\n }\n \n+ if (nf->outport == NULL) {\n+ VLOG_ERR_RL(&rl, \"No outport configured for inline mode \"\n+ \"network function:%s\", nf->name);\n+ return;\n+ }\n+\n+ VLOG_DBG(\"network_function %s: inport %s outport %s\",\n+ nf->name, nf->inport->name, nf->outport->name);\n+\n /* If NF ports are present on this LS, use those; otherwise look for child\n * ports. */\n struct ovn_port *input_port =\n@@ -19206,6 +19348,203 @@ consider_network_function(struct lflow_table *lflows,\n ds_destroy(&action);\n }\n \n+static void\n+consider_network_function_vtap(struct lflow_table *lflows,\n+ const struct ovn_datapath *od,\n+ struct nbrec_network_function_group *nfg,\n+ bool ingress, struct lflow_ref *lflow_ref)\n+{\n+ struct nbrec_network_function *nf;\n+ struct ds match = DS_EMPTY_INITIALIZER;\n+ struct ds action = DS_EMPTY_INITIALIZER;\n+ const struct ovn_stage *fwd_stage, *rev_stage;\n+ struct ovn_port *input_port = NULL;\n+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);\n+\n+ if (nfg->fallback && !strcasecmp(nfg->fallback, \"fail-close\")) {\n+ VLOG_WARN_RL(&rl, \"NF vtap mode: fallback is set to fail-close but \"\n+ \"will be overridden to fail-open for nfg:%s\", nfg->name);\n+ }\n+ /* Configure flows with higher priority than default drop rule to allow\n+ * the traffic when there is no active NF available.\n+ */\n+ network_function_configure_fail_open_flows(lflows, od, lflow_ref,\n+ nfg->id, ingress);\n+ /* Currently we support only one active port-pair in a group.\n+ * If there are multiple active pairs, take the first one.\n+ * Load balancing would be added in future. */\n+ nf = nf_get_active(nfg);\n+ if (nf == NULL) {\n+ VLOG_ERR_RL(&rl, \"No active network function available, nfg:%s\",\n+ nfg->name);\n+ return;\n+ }\n+\n+ if (nf->outport) {\n+ VLOG_ERR_RL(&rl, \"Outport is not supported for vtap mode \"\n+ \"network function:%s\", nf->name);\n+ return;\n+ }\n+\n+ VLOG_DBG(\"network_function %s: inport %s\",\n+ nf->name, nf->inport->name);\n+\n+ /* If NF ports are present on this LS, use those; otherwise look for child\n+ * ports. */\n+ input_port = ovn_port_find_port_or_child(od, nf->inport->name);\n+ if (input_port == NULL) {\n+ VLOG_ERR_RL(&rl, \"Port not found for network_function %s\", nf->name);\n+ return;\n+ }\n+\n+ if (ingress) {\n+ fwd_stage = S_SWITCH_IN_NF;\n+ rev_stage = S_SWITCH_OUT_NF;\n+ } else {\n+ fwd_stage = S_SWITCH_OUT_NF;\n+ rev_stage = S_SWITCH_IN_NF;\n+ }\n+\n+ /* Pre NF Table (Priority 99):\n+ *\n+ * Currently, this stage simply writes the active network function ID into\n+ * the nf_id register.\n+ *\n+ * In the future, this stage will be extended to support network function\n+ * load balancing.\n+ */\n+ ds_put_format(&match, REGBIT_NF_ENABLED\" == 1 && \"\n+ REGBIT_NF_ORIG_DIR\" == 1 && \"\n+ REG_NF_GROUP_ID \" == %\"PRIu8,\n+ (uint8_t) nfg->id);\n+ ds_put_format(&action, REG_NF_ID\" = %\"PRIu8\"; next;\", (uint8_t) nf->id);\n+ ovn_lflow_add(lflows, od, ingress ? S_SWITCH_IN_PRE_NF\n+ : S_SWITCH_OUT_PRE_NF,\n+ 99, ds_cstr(&match), ds_cstr(&action), lflow_ref);\n+ ds_clear(&match);\n+ ds_clear(&action);\n+\n+ /* Add forward flows for mirroring:\n+ * Flows to handle request packets for new or existing connections.\n+ *\n+ * from-lport ACL in_network_function priority 99:\n+ * in_acl_eval has already categorized it and populated nf_enabled,\n+ * direction and nfg_id registers. in_pre_nf sets the active network\n+ * function id in nf_id register. Here this rule sets the outport to the\n+ * NF port for the mirrored packet and does output action to skip the rest\n+ * of the ingress pipeline. Original packet continues with ingress\n+ * pipeline.\n+ *\n+ * to-lport ACL out_network_function priority 99:\n+ * out_acl_eval, and out_pre_nf set the nf related registers. Then the\n+ * out_network_function stage sets the outport to NF port for the mirrored\n+ * packet and submits the packet back to ingress pipeline l2_lkup table.\n+ * The l2_lkup would skip mac based lookup as the\n+ * NETWORK_FUNCTION_EGRESS_LOOPBACK is set. Original packet continues with\n+ * the egress pipeline processing.\n+ */\n+ if (ingress) {\n+ ds_put_format(&action, \"clone {outport = %s; output;}; next;\",\n+ input_port->json_key);\n+ } else {\n+ ds_put_format(&action, \"clone {outport = %s; \"\n+ REGBIT_NF_EGRESS_LOOPBACK\" = 1; \"\n+ \"next(pipeline=ingress, table=%d);}; next;\",\n+ input_port->json_key,\n+ ovn_stage_get_table(S_SWITCH_IN_L2_LKUP));\n+ }\n+ ds_put_format(&match, REGBIT_NF_ENABLED\" == 1 && \"\n+ REGBIT_NF_ORIG_DIR\" == 1 && \"\n+ REG_NF_ID \" == %\"PRIu8, (uint8_t) nf->id);\n+ ovn_lflow_add(lflows, od, fwd_stage, 99, ds_cstr(&match),\n+ ds_cstr(&action), lflow_ref);\n+ ds_clear(&match);\n+ ds_clear(&action);\n+\n+ /* Add reverse flows for mirroring:\n+ * Flows to handle response packets for existing connections.\n+ *\n+ * from-lport ACL out_network_function priority 99:\n+ * out_acl stage sets the nf_enabled register based on CT label.\n+ * Here this rule sets the outport to the NF port for the mirrored packet\n+ * based on nf_id fetched from the CT label. Then it submits the packet\n+ * back to ingress pipeline l2_lkup table. The l2_lkup would skip mac\n+ * lookup as the NETWORK_FUNCTION_EGRESS_LOOPBACK is set. Original packet\n+ * continues with the egress pipeline.\n+ *\n+ * to-lport ACL in_network_function priority 99:\n+ * in_acl stage sets the nf_enabled register based on CT label.\n+ * Here this rule sets the outport to the NF port for the mirrored packet\n+ * based on nf_id fetched from the CT label and does output action to skip\n+ * the rest of the ingress pipeline. Original packet continues with the\n+ * ingress pipeline.\n+ */\n+ if (ingress) {\n+ ds_put_format(&action, \"clone {outport = %s; \"\n+ REGBIT_NF_EGRESS_LOOPBACK\" = 1; \"\n+ \"next(pipeline=ingress, table=%d);}; next;\",\n+ input_port->json_key,\n+ ovn_stage_get_table(S_SWITCH_IN_L2_LKUP));\n+ } else {\n+ ds_put_format(&action, \"clone {outport = %s; output;}; next;\",\n+ input_port->json_key);\n+ }\n+ ds_put_format(&match, REGBIT_NF_ENABLED\" == 1 && \"\n+ REGBIT_NF_ORIG_DIR\" == 0 && \"\n+ \"ct_label.nf_id == %\"PRIu8, (uint8_t) nf->id);\n+ ovn_lflow_add(lflows, od, rev_stage, 99, ds_cstr(&match), ds_cstr(&action),\n+ lflow_ref);\n+ ds_clear(&match);\n+ ds_clear(&action);\n+\n+ /* Priority 100 flow in in_network_function:\n+ * Drop packets coming from network-function in vtap mode.\n+ */\n+ ds_put_format(&match, \"inport == %s\", input_port->json_key);\n+ ds_put_format(&action, \"drop;\");\n+ ovn_lflow_add(lflows, od, S_SWITCH_IN_NF, 100,\n+ ds_cstr(&match), ds_cstr(&action), lflow_ref);\n+ ds_clear(&match);\n+ ds_clear(&action);\n+\n+ /* Priority 100 flow in out_network_function:\n+ * Allow packets to go through if outport is network-function port as\n+ * we don't want the packets to be mirrored again based on to-lport\n+ * match.\n+ */\n+ ds_put_format(&match, \"outport == %s\", input_port->json_key);\n+ ds_put_format(&action, \"next;\");\n+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_NF, 100,\n+ ds_cstr(&match), ds_cstr(&action), lflow_ref);\n+ ds_clear(&match);\n+ ds_clear(&action);\n+\n+ /* Priority 110 flow in out_pre_acl:\n+ * Avoid ct for packets going to network-function port in vtap mode since\n+ * these packets gets consumed at VNF.\n+ */\n+ ds_put_format(&match, \"ip && outport == %s\", input_port->json_key);\n+ ds_put_format(&action, \"ct_clear; next;\");\n+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 110, ds_cstr(&match),\n+ ds_cstr(&action), lflow_ref);\n+\n+ ds_destroy(&match);\n+ ds_destroy(&action);\n+}\n+\n+static void\n+consider_network_function(struct lflow_table *lflows,\n+ const struct ovn_datapath *od,\n+ struct nbrec_network_function_group *nfg,\n+ bool ingress, struct lflow_ref *lflow_ref)\n+{\n+ if (network_function_group_is_vtap_mode(nfg)) {\n+ consider_network_function_vtap(lflows, od, nfg, ingress, lflow_ref);\n+ return;\n+ }\n+ consider_network_function_inline(lflows, od, nfg, ingress, lflow_ref);\n+}\n+\n static void\n build_network_function(const struct ovn_datapath *od,\n struct lflow_table *lflows,\n@@ -19232,7 +19571,7 @@ build_network_function(const struct ovn_datapath *od,\n /* Ingress and Egress PRE NF Table (Priority 1): ACL stage determined these\n * packets should be redirected, but there is no active NF in NFG.\n * Reset the nf_id register to 0. This will drop the packet by the\n- * default drop rule in the subsequent NF table.\n+ * default drop rule in the subsequent NF tabl if NF is in fail-close mode.\n */\n ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_NF, 1,\n REGBIT_NF_ENABLED\" == 1 && \" REGBIT_NF_ORIG_DIR\" == 1\",\n@@ -21171,7 +21510,8 @@ ovnnb_db_run(struct northd_input *input_data,\n input_data->nbrec_network_function_group_table,\n &data->local_svc_monitors_map,\n input_data->ic_learned_svc_monitors_map,\n- input_data->svc_global_addresses->ip_dst);\n+ input_data->svc_global_addresses->ip_dst,\n+ &data->ls_ports);\n build_ipam(&data->ls_datapaths.datapaths);\n build_lrouter_groups(&data->lr_ports, &data->lr_datapaths);\n build_ip_mcast(ovnsb_txn, input_data->sbrec_ip_multicast_table,\ndiff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml\nindex 9f2d118dd..c5d663aeb 100644\n--- a/northd/ovn-northd.8.xml\n+++ b/northd/ovn-northd.8.xml\n@@ -1523,39 +1523,45 @@\n \n <ul>\n <li>\n- For each network_function_group <var>id</var> with an active network\n- function, a priority-99 flow matches <code>reg8[21] == 1 &&\n- reg8[22] == 1 && reg0[22..29] == <var>id</var></code> and\n- sets <code>reg0[22..29] = <var>nf_id</var>; next;</code> where\n+ In inline, vtap mode: For each network_function_group <var>id</var>\n+ with an active network function, a priority-99 flow matches\n+ <code>reg8[21] == 1 && reg8[22] == 1 &&\n+ reg0[22..29] == <var>id</var></code> and sets\n+ <code>reg0[22..29] = <var>nf_id</var>; next;</code> where\n <var>nf_id</var> is the ID of the active network function. This\n prepares request packets that matched a <code>from-lport</code> ACL\n- with network_function_group for redirection in the subsequent Network\n- Function table.\n+ with network_function_group for redirection (inline) or mirroring\n+ (vtap) in the subsequent Network Function table.\n </li>\n \n <li>\n- For each network function group with <var>id</var> that has\n- <code>fallback</code> set to <code>fail-open</code>, a priority-10 flow\n- matches <code>reg8[21] == 1 && reg8[22] == 1 &&\n+ In inline mode: For each network function group with <var>id</var> that\n+ has <code>fallback</code> set to <code>fail-open</code>, a priority-10\n+ flow matches <code>reg8[21] == 1 && reg8[22] == 1 &&\n reg0[22..29] == <var>id</var></code> and sets <code>reg8[21] = 0;\n- reg0[22..29] = 0; next;</code>. This clears both the NF enabled bit and\n- the NF group ID, allowing packets to continue processing through the\n- pipeline without network function redirection when no active network\n- function is available (fail-open behavior).\n+ reg0[22..29] = 0; next;</code>.\n+ In vtap mode: A priority-10 flow with the same match and action is\n+ always added (vtap does not support fail-close). This clears both the\n+ NF enabled bit and the NF group ID, allowing packets to continue when\n+ no active network function is available (fail-open behavior).\n </li>\n \n <li>\n- A priority-1 flow matches <code>reg8[21] == 1 && reg8[22] == 1\n- </code> and sets <code>reg0[22..29] = 0; next;</code>. This is a\n- catch-all flow for network function groups with <code>fallback</code>\n- set to <code>fail-close</code> (or default) when no active network\n- function is available. It clears only the NF group ID, leaving the NF\n- enabled bit set. These packets will be dropped by the priority-1 drop\n- rule in the subsequent Network Function table (fail-close behavior).\n+ In inline, vtap mode: A priority-1 flow matches <code>reg8[21] == 1\n+ && reg8[22] == 1</code> and sets <code>reg0[22..29] = 0;\n+ next;</code>. This is a catch-all for when no active network function\n+ is available and no higher-priority flow matched. For inline groups\n+ with <code>fallback</code> set to <code>fail-close</code> (or default)\n+ this leaves the NF enabled bit set so the packet is dropped by the\n+ priority-1 drop rule in the subsequent Network Function table\n+ (fail-close behavior). For vtap groups this flow is superseded by\n+ the priority-10 fail-open flow above and is not reached when an NFG\n+ is configured; it acts as a safety net.\n </li>\n \n <li>\n- A priority-0 flow that simply moves traffic to the next table.\n+ In inline, vtap mode: A priority-0 flow that simply moves traffic to\n+ the next table.\n </li>\n </ul>\n \n@@ -1579,15 +1585,15 @@\n </li>\n \n <li>\n- Corresponding to each of the two priority 100 flows above, a priority\n- 110 flow is added, which has the following extra match and\n- action, but otherwise identical to the priority 100 flow.\n- Match: <code>reg8[21] == 1</code> (packet matched an ACL with\n- <code>network_function_group</code> set)\n- Action: <code>ct_label.nf = 1;\n- ct_label.nf_id = reg0[22..29];</code>\n- This is to commit the network_function information in conntrack so that\n- the response and related packets can be redirected to it as well.\n+ In inline, vtap mode: Corresponding to each of the two priority 100\n+ flows above, a priority 110 flow is added, which has the following\n+ extra match and action, but otherwise identical to the priority 100\n+ flow. Match: <code>reg8[21] == 1</code> (packet matched an ACL with\n+ <code>network_function_group</code> set). Action:\n+ <code>ct_label.nf = 1; ct_label.nf_id = reg0[22..29];</code> This\n+ commits the network_function information in conntrack so that\n+ response and related packets can be redirected or mirrored to it as\n+ well.\n </li>\n \n <li>\n@@ -1631,20 +1637,18 @@\n \n <ul>\n <li>\n- For each network_function port <var>P</var>, a priority-100 flow is\n- added that matches <code>inport == <var>P</var></code> and advances\n- packets to the next table. Thus packets coming from network function\n- are not subject to redirection. This flow also sets\n- <code>reg5[16..31] = ct_label.tun_if_id</code>. This is used for\n- tunneling packet to originating host in case of cross host traffic\n- redirection for VLAN subnet. This ct_label field stores the openflow\n- tunnel interface id of the originating host for this connection and\n- gets populated in egress <code>Stateful</code> table.\n+ In inline: For each network_function port <var>P</var>, a\n+ priority-100 flow matches <code>inport == <var>P</var></code> and\n+ advances packets to the next table (packets from the network function\n+ are not subject to redirection). This flow also sets\n+ <code>reg5[16..31] = ct_label.tun_if_id</code> for cross host traffic\n+ redirection for VLAN subnet; the tunnel id is populated in egress\n+ <code>Stateful</code> table.\n </li>\n \n <li>\n- For each active network function with <var>id</var> that is referenced\n- in a network function group, a priority-99 flow matches\n+ In inline: For each active network function with <var>id</var> that is\n+ referenced in a network function group, a priority-99 flow matches\n <code>reg8[21] == 1 && reg8[22] == 1 &&\n reg0[22..29] == <var>id</var></code> and sets\n <code>outport=<var>P</var>; output;</code> where <var>P</var> is the\n@@ -1655,37 +1659,62 @@\n </li>\n \n <li>\n- For each active network function with <var>id</var> that is referenced\n- in a network function group, a priority-99 rule matches\n+ In vtap mode: For each active network function with <var>id</var>, a\n+ priority-99 forward flow matches <code>reg8[21] == 1 &&\n+ reg8[22] == 1 && reg0[22..29] == <var>id</var></code> and\n+ sets <code>clone { outport = <var>P</var>; output; }; next;</code>\n+ where <var>P</var> is the <code>inport</code> of that network function.\n+ A copy is sent to the NF port while the original packet continues\n+ (mirroring; only inport is used, outport is not supported).\n+ </li>\n+\n+ <li>\n+ In inline: For each active network function with <var>id</var> that is\n+ referenced in a network function group, a priority-99 rule matches\n <code>reg8[21] == 1 && reg8[22] == 0 &&\n ct_label.nf_id == <var>id</var></code> and takes identical action as\n above. This redirects response and related packets for\n <code>to-lport</code> ACLs to the same network function that handled\n- the request, using the NF ID stored in the connection tracking label.\n+ the request.\n </li>\n \n <li>\n- In each of the above cases, when the same packet comes out unchanged\n- through the other port of the network_function, it would match the\n- priority 100 flow and be forwarded to the next table.\n+ In vtap mode: A priority-99 reverse flow matches\n+ <code>reg8[21] == 1 && reg8[22] == 0 &&\n+ ct_label.nf_id == <var>id</var></code> and sets\n+ <code>clone { outport = <var>P</var>; output; }; next;</code> to mirror\n+ response/related packets to the same NF.\n </li>\n \n <li>\n- One priority-100 rule to skip redirection of multicast packets that hit\n- a network_function ACL. Match on <code>reg8[21] == 1 &&\n- eth.mcast</code> and action is to advance to the next table.\n+ In inline: In each of the above cases, when the same packet comes out\n+ unchanged through the other port of the network_function, it would\n+ match the priority 100 flow and be forwarded to the next table.\n </li>\n \n <li>\n- One priority-1 rule that checks <code>reg8[21] == 1</code>, and drops\n- such packets. This is to address the case where a packet hit an ACL\n- with network function but the network function does not have ports or\n- child ports on this logical switch.\n+ In vtap mode: A priority-100 flow matches\n+ <code>inport == <var>P</var></code> (packets from the NF port) and\n+ drops them.\n </li>\n \n <li>\n- One priority-0 fallback flow that matches all packets and advances to\n- the next table.\n+ In inline, vtap mode: One priority-100 rule to skip\n+ redirection/mirroring\n+ of multicast packets that hit a network_function ACL. Match on\n+ <code>reg8[21] == 1 && eth.mcast</code> and action is to\n+ advance to the next table.\n+ </li>\n+\n+ <li>\n+ In inline: One priority-1 rule that checks <code>reg8[21] == 1</code>,\n+ and drops such packets when the network function does not have ports\n+ or child ports on this logical switch.\n+ </li>\n+\n+ <li>\n+ In inline, vtap mode: One priority-0 fallback flow that matches all\n+ packets and advances to the next table.\n </li>\n </ul>\n \n@@ -2845,40 +2874,45 @@ output;\n \n <ul>\n <li>\n- For each network function group with <var>id</var> that has an active\n- network function, a priority-99 flow matches <code>reg8[21] == 1\n- && reg8[22] == 1 && reg0[22..29] == <var>id</var></code>\n- and sets <code>reg0[22..29] = <var>nf_id</var>; next;</code> where\n+ In inline, vtap mode: For each network function group with\n+ <var>id</var> that has an active network function, a priority-99 flow\n+ matches <code>reg8[21] == 1 && reg8[22] == 1 &&\n+ reg0[22..29] == <var>id</var></code> and sets\n+ <code>reg0[22..29] = <var>nf_id</var>; next;</code> where\n <var>nf_id</var> is the <code>id</code> of the active\n <code>Network_Function</code> selected from the group. This prepares\n request packets that matched a <code>to-lport</code> ACL with\n- network_function_group for redirection in the subsequent Network\n- Function table.\n+ network_function_group for redirection (inline) or mirroring (vtap) in\n+ the subsequent Network Function table.\n </li>\n \n <li>\n- For each network function group with <var>id</var> that has\n+ In inline: For each network function group with <var>id</var> that has\n <code>fallback</code> set to <code>fail-open</code>, a priority-10 flow\n matches <code>reg8[21] == 1 && reg8[22] == 1 &&\n reg0[22..29] == <var>id</var></code> and sets <code>reg8[21] = 0;\n- reg0[22..29] = 0; next;</code>. This clears both the NF enabled bit and\n- the NF group ID, allowing packets to continue processing through the\n- pipeline without network function redirection when no active network\n- function is available (fail-open behavior).\n+ reg0[22..29] = 0; next;</code>. In vtap mode: A priority-10 flow with\n+ the same match and action is always added. This clears both the NF\n+ enabled bit and the NF group ID when no active network function is\n+ available (fail-open behavior).\n </li>\n \n <li>\n- A priority-1 flow matches <code>reg8[21] == 1 && reg8[22] == 1\n- </code> and sets <code>reg0[22..29] = 0; next;</code>. This is a\n- catch-all flow for network function groups with <code>fallback</code>\n- set to <code>fail-close</code> (or default) when no active network\n- function is available. It clears only the NF group ID, leaving the NF\n- enabled bit set. These packets will be dropped by the priority-1 drop\n- rule in the subsequent Network Function table (fail-close behavior).\n+ In inline, vtap mode: A priority-1 flow matches <code>reg8[21] == 1\n+ && reg8[22] == 1</code> and sets <code>reg0[22..29] = 0;\n+ next;</code>. This is a catch-all for when no active network function\n+ is available and no higher-priority flow matched. For inline groups\n+ with <code>fallback</code> set to <code>fail-close</code> (or default)\n+ this leaves the NF enabled bit set so the packet is dropped by the\n+ priority-1 drop rule in the subsequent Network Function table\n+ (fail-close behavior). For vtap groups this flow is superseded by\n+ the priority-10 fail-open flow above and is not reached when an NFG\n+ is configured; it acts as a safety net.\n </li>\n \n <li>\n- A priority-0 flow that simply moves traffic to the next table.\n+ In inline, vtap mode: A priority-0 flow that simply moves traffic to\n+ the next table.\n </li>\n </ul>\n \n@@ -2893,18 +2927,16 @@ output;\n \n <ul>\n <li>\n- A priority 120 flow is added for each network function port\n+ In inline: A priority 120 flow is added for each network function port\n <var>P</var> that is identical to the priority 100 flow except for\n additional match <code>outport == <var>P</var></code> and additional\n- action <code>ct_label.tun_if_id = reg5[16..31]</code>. In case packets\n- redirected by network function logic gets tunneled from host1 to host2\n+ action <code>ct_label.tun_if_id = reg5[16..31]</code>. In case packets\n+ redirected by network function logic get tunneled from host1 to host2\n where the network function port resides, host2's physical table 0\n- populates reg5[16..31] with the openflow tunnel interface id on which\n- the packet was received. This priority 120 flow commits the tunnel id\n- to the ct_label. That way, when the same packet comes out of the other\n- port of the network function it can retrieve this information from the\n- peer port's CT entry and tunnel the packet back to host1. This is\n- required to make cross host traffic redirection work for VLAN subnet.\n+ populates reg5[16..31] with the openflow tunnel interface id. This\n+ flow commits the tunnel id to ct_label so the packet can be tunneled\n+ back to host1 when it comes out of the other port of the network\n+ function (required for cross host traffic redirection for VLAN subnet).\n </li>\n </ul>\n \n@@ -2924,54 +2956,86 @@ output;\n \n <ul>\n <li>\n- Similar to ingress <code>Network Function</code> a priority-100 flow is\n- added for each network_function port, that matches the inport with the\n- network function port and advances the packet to the next table.\n+ In inline: Similar to ingress <code>Network Function</code>, a\n+ priority-100 flow is added for each network_function port that matches\n+ the inport with the network function port and advances the packet to\n+ the next table.\n </li>\n \n <li>\n- For each active network function with <var>id</var> that is\n+ In inline: For each active network function with <var>id</var> that is\n referenced in a network function group, a priority-99 flow matches\n <code>reg8[21] == 1 && reg8[22] == 1 &&\n reg0[22..29] == <var>id</var></code> and sets\n <code>outport=<var>P</var>; reg8[23] = 1; next(pipeline=ingress,\n table=<var>T</var>)</code> where <var>P</var> is the\n- <code>outport</code> of that network function and <var>T</var> is\n- the ingress table <code>Destination Lookup</code>. This redirects\n- request packets matching <code>to-lport</code> ACLs with\n+ <code>outport</code> of that network function and <var>T</var> is the\n+ ingress table <code>Destination Lookup</code>. This redirects request\n+ packets matching <code>to-lport</code> ACLs with\n network_function_group to the specific network function selected by\n- the Pre Network Function stage. The packets are injected back to the\n- ingress pipeline from where they get sent out, skipping any further\n- lookup because of <code>reg8[23]</code>.\n+ the Pre Network Function stage.\n+ </li>\n+\n+ <li>\n+ In vtap mode: For each active network function with <var>id</var>, a\n+ priority-99 forward flow matches <code>reg8[21] == 1 &&\n+ reg8[22] == 1 && reg0[22..29] == <var>id</var></code> and sets\n+ <code>clone { outport = <var>P</var>; reg8[23] = 1;\n+ next(pipeline=ingress, table=Destination Lookup); }; next;</code>\n+ where <var>P</var> is the <code>inport</code> of that network function\n+ (mirroring; only inport is used).\n </li>\n \n <li>\n- For each active network function with <var>id</var> that is referenced\n- in a network function group, a priority-99 rule matches\n+ In inline: For each active network function with <var>id</var> that is\n+ referenced in a network function group, a priority-99 rule matches\n <code>reg8[21] == 1 && reg8[22] == 0 &&\n ct_label.nf_id == <var>id</var></code> and takes identical action as\n above. This redirects response and related packets for\n <code>from-lport</code> ACLs to the same network function that handled\n- the request, using the NF ID stored in the connection tracking label.\n+ the request.\n+ </li>\n+\n+ <li>\n+ In vtap mode: A priority-99 reverse flow matches\n+ <code>reg8[21] == 1 && reg8[22] == 0 &&\n+ ct_label.nf_id == <var>id</var></code> and sets\n+ <code>clone { outport = <var>P</var>; output; }; next;</code> to mirror\n+ response/related packets to the same NF.\n+ </li>\n+\n+ <li>\n+ In inline: In each of the above cases, when the same packet comes out\n+ unchanged through the other port of the network_function, it would\n+ match the priority 100 flow and be forwarded to the next table.\n+ </li>\n+\n+ <li>\n+ In vtap mode: A priority-100 flow matches\n+ <code>outport == <var>P</var></code> (packets to the NF port) and\n+ advances to the next table so packets to the NF are not mirrored again.\n </li>\n \n <li>\n- In each of the above cases, when the same packet comes out unchanged\n- through the other port of the network_function, it would match the\n- priority 100 flow and be forwarded to the next table.\n+ In vtap mode: In egress Pre ACL table, a priority-110 flow matches\n+ <code>ip && outport == <var>P</var></code> with action\n+ <code>ct_clear; next;</code> for the vtap NF port so packets toward the\n+ NF are not committed to conntrack.\n </li>\n \n <li>\n- One priority-100 multicast match flow same as\n+ In inline, vtap mode: One priority-100 multicast match flow same as\n ingress <code>Network Function</code>.\n </li>\n \n <li>\n- One priority-1 flow same as ingress <code>Network Function</code>.\n+ In inline, vtap mode: One priority-1 flow same as ingress\n+ <code>Network Function</code>.\n </li>\n \n <li>\n- One priority-0 flow same as ingress <code>Network Function</code>.\n+ In inline, vtap mode: One priority-0 flow same as ingress\n+ <code>Network Function</code>.\n </li>\n </ul>\n \ndiff --git a/tests/ovn-northd.at b/tests/ovn-northd.at\nindex 3e7a6f7f8..0c9440982 100644\n--- a/tests/ovn-northd.at\n+++ b/tests/ovn-northd.at\n@@ -11142,6 +11142,24 @@ AT_CHECK([ovn-sbctl lflow-list sw | grep ls_out_pre_lb | grep priority=110 | gre\n table=??(ls_out_pre_lb ), priority=110 , match=(ip && outport == \"sw-ln\"), action=(flags.pkt_sampled = 0; ct_clear; next;)\n ])\n \n+# Now add a regular port and a stateful ACL to verify that ct_state is\n+# cleared for packets egressing through the localnet port even when\n+# stateful ACLs are configured on the switch.\n+check ovn-nbctl lsp-add sw sw-p1 -- lsp-set-addresses sw-p1 \"00:00:00:00:00:01 10.0.0.2\"\n+check ovn-nbctl acl-add sw from-lport 1002 \"ip4 && tcp && tcp.dst == 80\" allow-related\n+check ovn-nbctl --wait=sb sync\n+\n+# Egress pre_acl: localnet port should get ct_clear even with stateful ACLs.\n+AT_CHECK([ovn-sbctl lflow-list sw | grep ls_out_pre_acl | grep priority=110 | grep sw-ln | ovn_strip_lflows], [0], [dnl\n+ table=??(ls_out_pre_acl ), priority=110 , match=(ip && outport == \"sw-ln\"), action=(flags.pkt_sampled = 0; ct_clear; next;)\n+])\n+\n+# Regular port should not have a skip flow in egress pre_acl when\n+# stateful ACLs are configured (traffic goes through conntrack normally).\n+AT_CHECK([ovn-sbctl lflow-list sw | grep ls_out_pre_acl | grep priority=110 | grep sw-p1 | ovn_strip_lflows], [0], [dnl\n+])\n+\n+\n OVN_CLEANUP_NORTHD\n AT_CLEANUP\n ])\n@@ -18718,7 +18736,7 @@ AT_CLEANUP\n ])\n \n OVN_FOR_EACH_NORTHD_NO_HV([\n-AT_SETUP([Check network function])\n+AT_SETUP([Check network-function in inline mode])\n ovn_start\n \n AS_BOX([Create a NF and add it to a from-lport ACL])\n@@ -18743,11 +18761,12 @@ check ovn-nbctl lsp-add sw0 sw0-p3 -- lsp-set-addresses sw0-p3 \"00:00:00:00:00:0\n check ovn-nbctl pg-add pg0 sw0-p1\n check ovn-nbctl acl-add pg0 from-lport 1002 \"inport == @pg0 && ip4.dst == 10.0.0.3\" allow-related nfg0\n \n-# Add hypervisor and bind NF ports\n-check ovn-sbctl chassis-add hv1 geneve 127.0.0.1\n-check ovn-sbctl lsp-bind sw0-nf-p1 hv1\n-check ovn-sbctl lsp-bind sw0-nf-p2 hv1\n-\n+check ovn-sbctl chassis-add gw1 geneve 127.0.0.1 \\\n+ -- set chassis gw1 other_config:ovn-ct-lb-related=true \\\n+ -- set chassis gw1 other_config:ct-no-masked-label=true\n+chassis_uuid=$(fetch_column Chassis _uuid name=gw1)\n+check ovn-sbctl set port_binding sw0-nf-p1 up=true chassis=$chassis_uuid\n+check ovn-sbctl set port_binding sw0-nf-p2 up=true chassis=$chassis_uuid\n check ovn-nbctl --wait=sb sync\n \n ovn-sbctl dump-flows sw0 > sw0flows\n@@ -18858,8 +18877,8 @@ check ovn-nbctl set logical_switch_port sw0-nf-p4 \\\n check ovn-nbctl nf-add nf1 102 sw0-nf-p3 sw0-nf-p4\n check ovn-nbctl nfg-add nfg1 202 inline nf1\n check ovn-nbctl acl-add pg0 to-lport 1003 \"outport == @pg0 && ip4.src == 10.0.0.4\" allow-related nfg1\n-check ovn-sbctl lsp-bind sw0-nf-p3 hv1\n-check ovn-sbctl lsp-bind sw0-nf-p4 hv1\n+check ovn-sbctl set port_binding sw0-nf-p3 up=true chassis=$chassis_uuid\n+check ovn-sbctl set port_binding sw0-nf-p4 up=true chassis=$chassis_uuid\n check ovn-nbctl --wait=sb sync\n \n ovn-sbctl dump-flows sw0 > sw0flows\n@@ -19034,10 +19053,16 @@ done\n \n nfsw=\"nf-sw\"\n check ovn-nbctl ls-add $nfsw\n+\n+check ovn-sbctl chassis-add gw1 geneve 127.0.0.1 \\\n+ -- set chassis gw1 other_config:ovn-ct-lb-related=true \\\n+ -- set chassis gw1 other_config:ct-no-masked-label=true\n+chassis_uuid=$(fetch_column Chassis _uuid name=gw1)\n+\n for i in {1..4}; do\n port=$nfsw-p$i\n check ovn-nbctl lsp-add $nfsw $port\n- check ovn-sbctl set port_binding $port up=true\n+ check ovn-sbctl set port_binding $port up=true chassis=$chassis_uuid\n check ovn-nbctl lsp-add $sw child-$i $port 100\n done\n check ovn-nbctl set logical_switch_port $nfsw-p1 \\\n@@ -20270,3 +20295,191 @@ check_column \"$global_svc_mon_mac\" sb:Service_Monitor src_mac port=2\n OVN_CLEANUP_NORTHD\n AT_CLEANUP\n ])\n+\n+OVN_FOR_EACH_NORTHD_NO_HV([\n+AT_SETUP([Check network-function in vtap mode])\n+ovn_start\n+\n+AS_BOX([Create a NF and add it to a from-lport ACL])\n+\n+# Create a NF and add it to a from-lport ACL.\n+check ovn-nbctl ls-add sw0\n+check ovn-nbctl lsp-add sw0 sw0-nf-p1\n+check ovn-nbctl set logical_switch_port sw0-nf-p1 options:receive_multicast=false options:lsp_learn_fdb=false options:is-nf=true\n+check ovn-nbctl nf-add nf0 1 sw0-nf-p1\n+check ovn-nbctl nfg-add nfg0 1 vtap nf0\n+\n+check ovn-nbctl lsp-add sw0 sw0-p1 -- lsp-set-addresses sw0-p1 \"00:00:00:00:00:01 10.0.0.2\"\n+check ovn-nbctl lsp-add sw0 sw0-p2 -- lsp-set-addresses sw0-p2 \"00:00:00:00:00:02 10.0.0.3\"\n+check ovn-nbctl lsp-add sw0 sw0-p3 -- lsp-set-addresses sw0-p3 \"00:00:00:00:00:03 10.0.0.4\"\n+\n+check ovn-nbctl pg-add pg0 sw0-p1\n+check ovn-nbctl acl-add pg0 from-lport 1002 \"inport == @pg0 && ip4.dst == 10.0.0.3\" allow-related nfg0\n+\n+check ovn-sbctl chassis-add gw1 geneve 127.0.0.1 \\\n+ -- set chassis gw1 other_config:ovn-ct-lb-related=true \\\n+ -- set chassis gw1 other_config:ct-no-masked-label=true\n+chassis_uuid=$(fetch_column Chassis _uuid name=gw1)\n+check ovn-sbctl set port_binding sw0-nf-p1 up=true chassis=$chassis_uuid\n+check ovn-nbctl --wait=sb sync\n+\n+ovn-sbctl dump-flows sw0 > sw0flows\n+AT_CAPTURE_FILE([sw0flows])\n+\n+AT_CHECK(\n+ [grep -E 'ls_(in|out)_acl_eval' sw0flows | ovn_strip_lflows | grep pg0 | sort], [0], [dnl\n+ table=??(ls_in_acl_eval ), priority=2002 , match=(reg0[[7]] == 1 && (inport == @pg0 && ip4.dst == 10.0.0.3)), action=(reg8[[16]] = 1; reg8[[21]] = 1; reg8[[22]] = 1; reg0[[22..29]] = 1; next;)\n+ table=??(ls_in_acl_eval ), priority=2002 , match=(reg0[[8]] == 1 && (inport == @pg0 && ip4.dst == 10.0.0.3)), action=(reg8[[16]] = 1; reg0[[1]] = 1; reg8[[21]] = 1; reg8[[22]] = 1; reg0[[22..29]] = 1; next;)\n+])\n+\n+# Vtap uses Pre NF (priority 99) to set REG_NF_ID for CT commit, aligned with inline mode.\n+# First box has only from-lport ACL (nfg0), so vtap Pre NF flows are only in IN path.\n+AT_CHECK(\n+ [grep -E 'ls_(in|out)_pre_network_function' sw0flows | ovn_strip_lflows | sort], [0], [dnl\n+ table=??(ls_in_pre_network_function), priority=0 , match=(1), action=(next;)\n+ table=??(ls_in_pre_network_function), priority=1 , match=(reg8[[21]] == 1 && reg8[[22]] == 1), action=(reg0[[22..29]] = 0; next;)\n+ table=??(ls_in_pre_network_function), priority=10 , match=(reg8[[21]] == 1 && reg8[[22]] == 1 && reg0[[22..29]] == 1), action=(reg8[[21]] = 0; reg0[[22..29]] = 0; next;)\n+ table=??(ls_in_pre_network_function), priority=99 , match=(reg8[[21]] == 1 && reg8[[22]] == 1 && reg0[[22..29]] == 1), action=(reg0[[22..29]] = 1; next;)\n+ table=??(ls_out_pre_network_function), priority=0 , match=(1), action=(next;)\n+ table=??(ls_out_pre_network_function), priority=1 , match=(reg8[[21]] == 1 && reg8[[22]] == 1), action=(reg0[[22..29]] = 0; next;)\n+])\n+\n+AT_CHECK(\n+ [grep -E 'ls_(in|out)_network_function' sw0flows | ovn_strip_lflows | sort], [0], [dnl\n+ table=??(ls_in_network_function), priority=0 , match=(1), action=(next;)\n+ table=??(ls_in_network_function), priority=1 , match=(reg8[[21]] == 1), action=(drop;)\n+ table=??(ls_in_network_function), priority=100 , match=(inport == \"sw0-nf-p1\"), action=(drop;)\n+ table=??(ls_in_network_function), priority=100 , match=(reg8[[21]] == 1 && eth.mcast), action=(next;)\n+ table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1 && reg8[[22]] == 1 && reg0[[22..29]] == 1), action=(clone {outport = \"sw0-nf-p1\"; output;}; next;)\n+ table=??(ls_out_network_function), priority=0 , match=(1), action=(next;)\n+ table=??(ls_out_network_function), priority=1 , match=(reg8[[21]] == 1), action=(drop;)\n+ table=??(ls_out_network_function), priority=100 , match=(outport == \"sw0-nf-p1\"), action=(next;)\n+ table=??(ls_out_network_function), priority=100 , match=(reg8[[21]] == 1 && eth.mcast), action=(next;)\n+ table=??(ls_out_network_function), priority=99 , match=(reg8[[21]] == 1 && reg8[[22]] == 0 && ct_label.nf_id == 1), action=(clone {outport = \"sw0-nf-p1\"; reg8[[23]] = 1; next(pipeline=ingress, table=??);}; next;)\n+])\n+\n+AT_CHECK([grep \"ls_in_l2_lkup\" sw0flows | ovn_strip_lflows | grep 'priority=100'], [0], [dnl\n+ table=??(ls_in_l2_lkup ), priority=100 , match=(reg8[[23]] == 1), action=(output;)\n+])\n+\n+# Vtap: egress pre-acl ct_clear for packets going to vtap NF port so they are\n+# not committed to conntrack.\n+AT_CHECK(\n+ [grep 'ls_out_pre_acl' sw0flows | ovn_strip_lflows | grep 'sw0-nf-p1'], [0], [dnl\n+ table=??(ls_out_pre_acl ), priority=110 , match=(ip && outport == \"sw0-nf-p1\"), action=(ct_clear; next;)\n+])\n+\n+AT_CHECK(\n+ [grep -E 'ls_(in|out)_acl_eval' sw0flows | ovn_strip_lflows | grep 'ct_label.nf' | sort], [0], [dnl\n+ table=??(ls_in_acl_eval ), priority=65532, match=(!ct.est && ct.rel && !ct.new && ct_mark.blocked == 0), action=(reg0[[17]] = 1; reg8[[21]] = ct_label.nf; reg8[[16]] = 1; ct_commit_nat;)\n+ table=??(ls_in_acl_eval ), priority=65532, match=(ct.est && !ct.rel && ct.rpl && ct_mark.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; reg0[[17]] = 1; reg8[[21]] = ct_label.nf; reg8[[16]] = 1; next;)\n+ table=??(ls_in_acl_eval ), priority=65532, match=(ct.est && ct_mark.allow_established == 1), action=(reg0[[21]] = 1; reg8[[21]] = ct_label.nf; reg8[[16]] = 1; next;)\n+ table=??(ls_out_acl_eval ), priority=65532, match=(!ct.est && ct.rel && !ct.new && ct_mark.blocked == 0), action=(reg8[[21]] = ct_label.nf; reg8[[16]] = 1; ct_commit_nat;)\n+ table=??(ls_out_acl_eval ), priority=65532, match=(ct.est && !ct.rel && ct.rpl && ct_mark.blocked == 0), action=(reg8[[21]] = ct_label.nf; reg8[[16]] = 1; next;)\n+ table=??(ls_out_acl_eval ), priority=65532, match=(ct.est && ct_mark.allow_established == 1), action=(reg8[[21]] = ct_label.nf; reg8[[16]] = 1; next;)\n+])\n+\n+# ICMP packets from sw0-p1 should be mirrored to sw0-nf-p1 but traffic originated\n+# in opposite direction should not get mirrored.\n+flow_eth_from_p1='eth.src == 00:00:00:00:00:01 && eth.dst == 00:00:00:00:00:02'\n+flow_ip_from_p1='ip.ttl==64 && ip4.src == 10.0.0.2 && ip4.dst == 10.0.0.3'\n+flow_icmp='icmp4.type == 8'\n+flow_from_p1=\"inport == \\\"sw0-p1\\\" && ${flow_eth_from_p1} && ${flow_ip_from_p1} && ${flow_icmp}\"\n+AT_CHECK_UNQUOTED([ovn_trace --ct new --ct new --minimal sw0 \"${flow_from_p1}\"], [0], [dnl\n+ct_next(ct_state=new|trk) {\n+ clone {\n+ output(\"sw0-nf-p1\");\n+ };\n+ ct_next(ct_state=new|trk) {\n+ output(\"sw0-p2\");\n+ };\n+};\n+])\n+flow_eth_rev='eth.src == 00:00:00:00:00:02 && eth.dst == 00:00:00:00:00:01'\n+flow_ip_rev='ip.ttl==64 && ip4.src == 10.0.0.3 && ip4.dst == 10.0.0.2'\n+flow_rev=\"inport == \\\"sw0-p2\\\" && ${flow_eth_rev} && ${flow_ip_rev} && ${flow_icmp}\"\n+AT_CHECK_UNQUOTED([ovn_trace --ct new --ct new --minimal sw0 \"${flow_rev}\"], [0], [dnl\n+ct_next(ct_state=new|trk) {\n+ ct_next(ct_state=new|trk) {\n+ output(\"sw0-p1\");\n+ };\n+};\n+])\n+\n+AS_BOX([Create another NF and add it to a to-lport ACL.])\n+\n+# Create another NF and add it to a to-lport ACL.\n+check ovn-nbctl lsp-add sw0 sw0-nf-p3\n+check ovn-nbctl set logical_switch_port sw0-nf-p3 options:receive_multicast=false options:lsp_learn_fdb=false options:is-nf=true\n+check ovn-nbctl nf-add nf1 2 sw0-nf-p3\n+check ovn-nbctl nfg-add nfg1 2 vtap nf1\n+check ovn-sbctl set port_binding sw0-nf-p3 up=true chassis=$chassis_uuid\n+check ovn-nbctl --wait=sb sync\n+check ovn-nbctl acl-add pg0 to-lport 1003 \"outport == @pg0 && ip4.src == 10.0.0.4\" allow-related nfg1\n+\n+ovn-sbctl dump-flows sw0 > sw0flows\n+AT_CAPTURE_FILE([sw0flows])\n+\n+AT_CHECK(\n+ [grep -E 'ls_(in|out)_acl_eval' sw0flows | ovn_strip_lflows | grep pg0 | sort], [0], [dnl\n+ table=??(ls_in_acl_eval ), priority=2002 , match=(reg0[[7]] == 1 && (inport == @pg0 && ip4.dst == 10.0.0.3)), action=(reg8[[16]] = 1; reg8[[21]] = 1; reg8[[22]] = 1; reg0[[22..29]] = 1; next;)\n+ table=??(ls_in_acl_eval ), priority=2002 , match=(reg0[[8]] == 1 && (inport == @pg0 && ip4.dst == 10.0.0.3)), action=(reg8[[16]] = 1; reg0[[1]] = 1; reg8[[21]] = 1; reg8[[22]] = 1; reg0[[22..29]] = 1; next;)\n+ table=??(ls_out_acl_eval ), priority=2003 , match=(reg0[[7]] == 1 && (outport == @pg0 && ip4.src == 10.0.0.4)), action=(reg8[[16]] = 1; reg8[[21]] = 1; reg8[[22]] = 1; reg0[[22..29]] = 2; next;)\n+ table=??(ls_out_acl_eval ), priority=2003 , match=(reg0[[8]] == 1 && (outport == @pg0 && ip4.src == 10.0.0.4)), action=(reg8[[16]] = 1; reg0[[1]] = 1; reg8[[21]] = 1; reg8[[22]] = 1; reg0[[22..29]] = 2; next;)\n+])\n+\n+# Pre NF: nfg0 (id=1) is from-lport so IN_PRE_NF only; nfg1 (id=2) is to-lport so OUT_PRE_NF only.\n+AT_CHECK(\n+ [grep -E 'ls_(in|out)_pre_network_function' sw0flows | ovn_strip_lflows | sort], [0], [dnl\n+ table=??(ls_in_pre_network_function), priority=0 , match=(1), action=(next;)\n+ table=??(ls_in_pre_network_function), priority=1 , match=(reg8[[21]] == 1 && reg8[[22]] == 1), action=(reg0[[22..29]] = 0; next;)\n+ table=??(ls_in_pre_network_function), priority=10 , match=(reg8[[21]] == 1 && reg8[[22]] == 1 && reg0[[22..29]] == 1), action=(reg8[[21]] = 0; reg0[[22..29]] = 0; next;)\n+ table=??(ls_in_pre_network_function), priority=99 , match=(reg8[[21]] == 1 && reg8[[22]] == 1 && reg0[[22..29]] == 1), action=(reg0[[22..29]] = 1; next;)\n+ table=??(ls_out_pre_network_function), priority=0 , match=(1), action=(next;)\n+ table=??(ls_out_pre_network_function), priority=1 , match=(reg8[[21]] == 1 && reg8[[22]] == 1), action=(reg0[[22..29]] = 0; next;)\n+ table=??(ls_out_pre_network_function), priority=10 , match=(reg8[[21]] == 1 && reg8[[22]] == 1 && reg0[[22..29]] == 2), action=(reg8[[21]] = 0; reg0[[22..29]] = 0; next;)\n+ table=??(ls_out_pre_network_function), priority=99 , match=(reg8[[21]] == 1 && reg8[[22]] == 1 && reg0[[22..29]] == 2), action=(reg0[[22..29]] = 2; next;)\n+])\n+\n+AT_CHECK(\n+ [grep -E 'ls_(in|out)_network_function' sw0flows | ovn_strip_lflows | sort], [0], [dnl\n+ table=??(ls_in_network_function), priority=0 , match=(1), action=(next;)\n+ table=??(ls_in_network_function), priority=1 , match=(reg8[[21]] == 1), action=(drop;)\n+ table=??(ls_in_network_function), priority=100 , match=(inport == \"sw0-nf-p1\"), action=(drop;)\n+ table=??(ls_in_network_function), priority=100 , match=(inport == \"sw0-nf-p3\"), action=(drop;)\n+ table=??(ls_in_network_function), priority=100 , match=(reg8[[21]] == 1 && eth.mcast), action=(next;)\n+ table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1 && reg8[[22]] == 0 && ct_label.nf_id == 2), action=(clone {outport = \"sw0-nf-p3\"; output;}; next;)\n+ table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1 && reg8[[22]] == 1 && reg0[[22..29]] == 1), action=(clone {outport = \"sw0-nf-p1\"; output;}; next;)\n+ table=??(ls_out_network_function), priority=0 , match=(1), action=(next;)\n+ table=??(ls_out_network_function), priority=1 , match=(reg8[[21]] == 1), action=(drop;)\n+ table=??(ls_out_network_function), priority=100 , match=(outport == \"sw0-nf-p1\"), action=(next;)\n+ table=??(ls_out_network_function), priority=100 , match=(outport == \"sw0-nf-p3\"), action=(next;)\n+ table=??(ls_out_network_function), priority=100 , match=(reg8[[21]] == 1 && eth.mcast), action=(next;)\n+ table=??(ls_out_network_function), priority=99 , match=(reg8[[21]] == 1 && reg8[[22]] == 0 && ct_label.nf_id == 1), action=(clone {outport = \"sw0-nf-p1\"; reg8[[23]] = 1; next(pipeline=ingress, table=??);}; next;)\n+ table=??(ls_out_network_function), priority=99 , match=(reg8[[21]] == 1 && reg8[[22]] == 1 && reg0[[22..29]] == 2), action=(clone {outport = \"sw0-nf-p3\"; reg8[[23]] = 1; next(pipeline=ingress, table=??);}; next;)\n+])\n+\n+# Vtap: egress pre-acl ct_clear for all vtap NF ports so that they are not committed to conntrack.\n+AT_CHECK(\n+ [grep 'ls_out_pre_acl' sw0flows | ovn_strip_lflows | grep 'nf-p' | sort], [0], [dnl\n+ table=??(ls_out_pre_acl ), priority=110 , match=(ip && outport == \"sw0-nf-p1\"), action=(ct_clear; next;)\n+ table=??(ls_out_pre_acl ), priority=110 , match=(ip && outport == \"sw0-nf-p3\"), action=(ct_clear; next;)\n+])\n+\n+# ICMP packets to sw0-p1 should be mirrored to sw0-nf-p3.\n+flow_eth_to_p1='eth.src == 00:00:00:00:00:03 && eth.dst == 00:00:00:00:00:01'\n+flow_ip_to_p1='ip.ttl==64 && ip4.src == 10.0.0.4 && ip4.dst == 10.0.0.2'\n+flow_to_p1=\"inport == \\\"sw0-p3\\\" && ${flow_eth_to_p1} && ${flow_ip_to_p1} && ${flow_icmp}\"\n+AT_CHECK_UNQUOTED([ovn_trace --ct new --ct new --minimal sw0 \"${flow_to_p1}\"], [0], [dnl\n+ct_next(ct_state=new|trk) {\n+ ct_next(ct_state=new|trk) {\n+ clone {\n+ output(\"sw0-nf-p3\");\n+ };\n+ output(\"sw0-p1\");\n+ };\n+};\n+])\n+\n+AT_CLEANUP\n+])\ndiff --git a/tests/ovn.at b/tests/ovn.at\nindex f0d4b1dd3..b202c0a2e 100644\n--- a/tests/ovn.at\n+++ b/tests/ovn.at\n@@ -44170,7 +44170,7 @@ AT_CLEANUP\n ])\n \n OVN_FOR_EACH_NORTHD([\n-AT_SETUP([Network function packet flow - outbound])\n+AT_SETUP([Network function inline packet flow - outbound])\n AT_KEYWORDS([ovn])\n TAG_UNSTABLE\n ovn_start\n@@ -44360,7 +44360,7 @@ AT_CLEANUP\n ])\n \n OVN_FOR_EACH_NORTHD([\n-AT_SETUP([Network function packet flow - inbound])\n+AT_SETUP([Network function inline packet flow - inbound])\n AT_KEYWORDS([ovn])\n TAG_UNSTABLE\n ovn_start\n@@ -44553,6 +44553,374 @@ OVN_CLEANUP([hv1],[hv2],[hv3])\n AT_CLEANUP\n ])\n \n+OVN_FOR_EACH_NORTHD([\n+AT_SETUP([Network function vtap packet flow - outbound])\n+AT_KEYWORDS([ovn])\n+TAG_UNSTABLE\n+ovn_start\n+\n+# Create logical topology. One LS sw0 with 3 ports.\n+# From-lport ACL rule mirrors request packets from sw0-p1 to sw0-p2 via vtap NF port sw0-nf-vtap.\n+# In vtap mode, traffic is mirrored (copied) to NF, original packets still reach destination.\n+create_logical_topology() {\n+ sw=$1\n+ check ovn-nbctl ls-add $sw\n+ for i in 1 2; do\n+ check ovn-nbctl lsp-add $sw $sw-p$i -- lsp-set-addresses $sw-p$i \"f0:00:00:00:00:0$i 192.168.0.1$i\"\n+ done\n+ check ovn-nbctl lsp-add $sw $sw-nf-vtap -- lsp-set-addresses $sw-nf-vtap \"f0:00:00:00:01:01\"\n+ check ovn-nbctl set logical_switch_port $sw-nf-vtap \\\n+ options:receive_multicast=false options:lsp_learn_mac=false \\\n+ options:is-nf=true\n+ check ovn-nbctl nf-add nf0 1 $sw-nf-vtap\n+ check ovn-nbctl nfg-add nfg0 1 vtap nf0\n+ check ovn-nbctl pg-add pg0 $sw-p1\n+ check ovn-nbctl acl-add pg0 from-lport 1002 \"inport == @pg0 && ip4.dst == 192.168.0.12\" allow-related nfg0\n+}\n+\n+create_logical_topology sw0\n+\n+# Create three hypervisors\n+net_add n\n+for i in 1 2 3; do\n+ sim_add hv$i\n+ as hv$i\n+ ovs-vsctl add-br br-phys\n+ ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys\n+ ovn_attach n br-phys 192.168.1.$i\n+done\n+\n+test_icmp() {\n+ local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5 icmp_type=$6 outport=$7 in_hv=$8 out_hv=$9\n+ local packet=\"inport==\\\"$inport\\\" && eth.src==$src_mac &&\n+ eth.dst==$dst_mac && ip.ttl==64 && ip4.src==$src_ip\n+ && ip4.dst==$dst_ip && icmp4.type==$icmp_type &&\n+ icmp4.code==0\"\n+ OVS_WAIT_UNTIL([as $in_hv ovs-appctl -t ovn-controller inject-pkt \"$packet\"])\n+ echo \"INJECTED PACKET $packet\"\n+ echo $packet | ovstest test-ovn expr-to-packets >> $out_hv-$outport.expected\n+}\n+\n+test_icmp_mirrored() {\n+ # Inject packet and expect it at both NF (mirrored) and destination\n+ local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5 icmp_type=$6\n+ local nf_outport=$7 dst_outport=$8 in_hv=$9 nf_hv=${10} dst_hv=${11}\n+ local packet=\"inport==\\\"$inport\\\" && eth.src==$src_mac &&\n+ eth.dst==$dst_mac && ip.ttl==64 && ip4.src==$src_ip\n+ && ip4.dst==$dst_ip && icmp4.type==$icmp_type &&\n+ icmp4.code==0\"\n+ OVS_WAIT_UNTIL([as $in_hv ovs-appctl -t ovn-controller inject-pkt \"$packet\"])\n+ echo \"INJECTED PACKET $packet\"\n+ # Expect packet at both NF port (mirrored) and destination port\n+ echo $packet | ovstest test-ovn expr-to-packets >> $nf_hv-$nf_outport.expected\n+ echo $packet | ovstest test-ovn expr-to-packets >> $dst_hv-$dst_outport.expected\n+}\n+\n+packet_mirroring_test() {\n+ local hvp1=$1 hvp2=$2 hvnf=$3\n+\n+ # Test 1: Inject ICMP request from sw0-p1 to sw0-p2\n+ # In vtap mode: single packet should be mirrored to NF AND reach sw0-p2\n+ test_icmp_mirrored sw0-p1 \"f0:00:00:00:00:01\" \"f0:00:00:00:00:02\" \"192.168.0.11\" \"192.168.0.12\" 8 \\\n+ vif-nf vif2 $hvp1 $hvnf $hvp2\n+ OVN_CHECK_PACKETS_REMOVE_BROADCAST([$hvnf/vif-nf-tx.pcap], [$hvnf-vif-nf.expected])\n+ OVN_CHECK_PACKETS_REMOVE_BROADCAST([$hvp2/vif2-tx.pcap], [$hvp2-vif2.expected])\n+\n+ # Test 2: Reverse direction - ICMP request from sw0-p2 to sw0-p1\n+ # No mirroring expected (ACL only matches from-lport on pg0 which contains sw0-p1)\n+ test_icmp sw0-p2 \"f0:00:00:00:00:02\" \"f0:00:00:00:00:01\" \"192.168.0.12\" \"192.168.0.11\" 8 vif1 $hvp2 $hvp1\n+ OVN_CHECK_PACKETS_REMOVE_BROADCAST([$hvp1/vif1-tx.pcap], [$hvp1-vif1.expected])\n+}\n+\n+create_port_binding() {\n+ hvp1=$1 hvp2=$2 hvnf=$3\n+ as $hvp1\n+ ovs-vsctl add-port br-int vif1 -- \\\n+ set interface vif1 external-ids:iface-id=sw0-p1 \\\n+ options:tx_pcap=$hvp1/vif1-tx.pcap \\\n+ options:rxq_pcap=$hvp1/vif1-rx.pcap\n+ as $hvp2\n+ ovs-vsctl add-port br-int vif2 -- \\\n+ set interface vif2 external-ids:iface-id=sw0-p2 \\\n+ options:tx_pcap=$hvp2/vif2-tx.pcap \\\n+ options:rxq_pcap=$hvp2/vif2-rx.pcap\n+ as $hvnf\n+ ovs-vsctl add-port br-int vif-nf -- \\\n+ set interface vif-nf external-ids:iface-id=sw0-nf-vtap \\\n+ options:tx_pcap=$hvnf/vif-nf-tx.pcap \\\n+ options:rxq_pcap=$hvnf/vif-nf-rx.pcap\n+\n+ OVN_POPULATE_ARP\n+ wait_for_ports_up\n+ check ovn-nbctl --wait=hv sync\n+ sleep 1\n+}\n+\n+cleanup_port_binding() {\n+ hvp1=$1 hvp2=$2 hvnf=$3\n+ as $hvp1\n+ ovs-vsctl del-port br-int vif1\n+ as $hvp2\n+ ovs-vsctl del-port br-int vif2\n+ as $hvnf\n+ ovs-vsctl del-port br-int vif-nf\n+ sleep 1\n+}\n+\n+test_nf_vtap_with_multinodes_outbound() {\n+ mode=$1\n+ # Test 1: Bind all 3 ports to one node\n+ echo \"$mode: Network function vtap outbound with single node\"\n+ create_port_binding hv1 hv1 hv1\n+\n+ packet_mirroring_test hv1 hv1 hv1 sw0\n+\n+ cleanup_port_binding hv1 hv1 hv1\n+\n+ # Test 2: src & dst ports on one node, NF on another node\n+ echo \"$mode: Network function vtap outbound with two nodes - nf separate\"\n+ create_port_binding hv1 hv1 hv2\n+\n+ packet_mirroring_test hv1 hv1 hv2 sw0\n+\n+ cleanup_port_binding hv1 hv1 hv2\n+\n+ # Test 3: src and nf on one node, dst on a second node\n+ echo \"$mode: Network function vtap outbound with two nodes - nf with src\"\n+ create_port_binding hv1 hv2 hv1\n+\n+ packet_mirroring_test hv1 hv2 hv1 sw0\n+\n+ cleanup_port_binding hv1 hv2 hv1\n+\n+ # Test 4: src on one node, nf & dst on a second node\n+ echo \"$mode: Network function vtap outbound with two nodes - nf with dst\"\n+ create_port_binding hv1 hv2 hv2\n+\n+ packet_mirroring_test hv1 hv2 hv2 sw0\n+\n+ cleanup_port_binding hv1 hv2 hv2\n+\n+ # Test 5: src on one node, dst on another, NF on a 3rd one\n+ echo \"$mode: Network function vtap outbound with three nodes\"\n+ create_port_binding hv1 hv2 hv3\n+\n+ packet_mirroring_test hv1 hv2 hv3 sw0\n+\n+ cleanup_port_binding hv1 hv2 hv3\n+}\n+\n+test_nf_vtap_with_multinodes_outbound overlay\n+\n+# Tests for VLAN network\n+check ovn-nbctl lsp-add-localnet-port sw0 ln0 phys\n+check ovn-nbctl set logical_switch_port ln0 tag_request=100\n+\n+test_nf_vtap_with_multinodes_outbound VLAN\n+\n+# Cleanup logical topology\n+check ovn-nbctl lsp-del ln0\n+check ovn-nbctl acl-del pg0 from-lport 1002 \"inport == @pg0 && ip4.dst == 192.168.0.12\"\n+check ovn-nbctl pg-del pg0\n+check ovn-nbctl nfg-del nfg0\n+check ovn-nbctl nf-del nf0\n+check ovn-nbctl clear logical_switch_port sw0-nf-vtap options\n+for i in 1 2; do\n+ check ovn-nbctl lsp-del sw0-p$i\n+done\n+check ovn-nbctl lsp-del sw0-nf-vtap\n+check ovn-nbctl ls-del sw0\n+check ovn-nbctl --wait=hv sync\n+\n+OVN_CLEANUP([hv1],[hv2],[hv3])\n+AT_CLEANUP\n+])\n+\n+OVN_FOR_EACH_NORTHD([\n+AT_SETUP([Network function vtap packet flow - inbound])\n+AT_KEYWORDS([ovn])\n+TAG_UNSTABLE\n+ovn_start\n+\n+# Create logical topology. One LS sw0 with 3 ports.\n+# To-lport ACL rule mirrors request packets from sw0-p2 to sw0-p1 via vtap NF port sw0-nf-vtap.\n+# In vtap mode, traffic is mirrored (copied) to NF, original packets still reach destination.\n+create_logical_topology() {\n+ sw=$1\n+ check ovn-nbctl ls-add $sw\n+ for i in 1 2; do\n+ check ovn-nbctl lsp-add $sw $sw-p$i -- lsp-set-addresses $sw-p$i \"f0:00:00:00:00:0$i 192.168.0.1$i\"\n+ done\n+ check ovn-nbctl lsp-add $sw $sw-nf-vtap -- lsp-set-addresses $sw-nf-vtap \"f0:00:00:00:01:01\"\n+ check ovn-nbctl set logical_switch_port $sw-nf-vtap \\\n+ options:receive_multicast=false options:lsp_learn_mac=false \\\n+ options:is-nf=true\n+ check ovn-nbctl nf-add nf0 1 $sw-nf-vtap\n+ check ovn-nbctl nfg-add nfg0 1 vtap nf0\n+ check ovn-nbctl pg-add pg0 $sw-p1\n+ check ovn-nbctl acl-add pg0 to-lport 1002 \"outport == @pg0 && ip4.src == 192.168.0.12\" allow-related nfg0\n+}\n+\n+create_logical_topology sw0\n+\n+# Create three hypervisors\n+net_add n\n+for i in 1 2 3; do\n+ sim_add hv$i\n+ as hv$i\n+ ovs-vsctl add-br br-phys\n+ ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys\n+ ovn_attach n br-phys 192.168.1.$i\n+done\n+\n+test_icmp() {\n+ local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5 icmp_type=$6 outport=$7 in_hv=$8 out_hv=$9\n+ local packet=\"inport==\\\"$inport\\\" && eth.src==$src_mac &&\n+ eth.dst==$dst_mac && ip.ttl==64 && ip4.src==$src_ip\n+ && ip4.dst==$dst_ip && icmp4.type==$icmp_type &&\n+ icmp4.code==0\"\n+ OVS_WAIT_UNTIL([as $in_hv ovs-appctl -t ovn-controller inject-pkt \"$packet\"])\n+ echo \"INJECTED PACKET $packet\"\n+ echo $packet | ovstest test-ovn expr-to-packets >> $out_hv-$outport.expected\n+}\n+\n+test_icmp_mirrored() {\n+ # Inject packet and expect it at both NF (mirrored) and destination\n+ local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5 icmp_type=$6\n+ local nf_outport=$7 dst_outport=$8 in_hv=$9 nf_hv=${10} dst_hv=${11}\n+ local packet=\"inport==\\\"$inport\\\" && eth.src==$src_mac &&\n+ eth.dst==$dst_mac && ip.ttl==64 && ip4.src==$src_ip\n+ && ip4.dst==$dst_ip && icmp4.type==$icmp_type &&\n+ icmp4.code==0\"\n+ OVS_WAIT_UNTIL([as $in_hv ovs-appctl -t ovn-controller inject-pkt \"$packet\"])\n+ echo \"INJECTED PACKET $packet\"\n+ # Expect packet at both NF port (mirrored) and destination port\n+ echo $packet | ovstest test-ovn expr-to-packets >> $nf_hv-$nf_outport.expected\n+ echo $packet | ovstest test-ovn expr-to-packets >> $dst_hv-$dst_outport.expected\n+}\n+\n+packet_mirroring_test() {\n+ local hvp1=$1 hvp2=$2 hvnf=$3\n+\n+ # Test 1: Inject ICMP request from sw0-p2 to sw0-p1\n+ # In vtap mode: single packet should be mirrored to NF AND reach sw0-p1\n+ test_icmp_mirrored sw0-p2 \"f0:00:00:00:00:02\" \"f0:00:00:00:00:01\" \"192.168.0.12\" \"192.168.0.11\" 8 \\\n+ vif-nf vif1 $hvp2 $hvnf $hvp1\n+ OVN_CHECK_PACKETS_REMOVE_BROADCAST([$hvnf/vif-nf-tx.pcap], [$hvnf-vif-nf.expected])\n+ OVN_CHECK_PACKETS_REMOVE_BROADCAST([$hvp1/vif1-tx.pcap], [$hvp1-vif1.expected])\n+\n+ # Test 2: Reverse direction - ICMP request from sw0-p1 to sw0-p2\n+ # No mirroring expected (ACL only matches to-lport on pg0 which contains sw0-p1)\n+ test_icmp sw0-p1 \"f0:00:00:00:00:01\" \"f0:00:00:00:00:02\" \"192.168.0.11\" \"192.168.0.12\" 8 vif2 $hvp1 $hvp2\n+ OVN_CHECK_PACKETS_REMOVE_BROADCAST([$hvp2/vif2-tx.pcap], [$hvp2-vif2.expected])\n+}\n+\n+create_port_binding() {\n+ hvp1=$1 hvp2=$2 hvnf=$3\n+ as $hvp1\n+ ovs-vsctl add-port br-int vif1 -- \\\n+ set interface vif1 external-ids:iface-id=sw0-p1 \\\n+ options:tx_pcap=$hvp1/vif1-tx.pcap \\\n+ options:rxq_pcap=$hvp1/vif1-rx.pcap\n+ as $hvp2\n+ ovs-vsctl add-port br-int vif2 -- \\\n+ set interface vif2 external-ids:iface-id=sw0-p2 \\\n+ options:tx_pcap=$hvp2/vif2-tx.pcap \\\n+ options:rxq_pcap=$hvp2/vif2-rx.pcap\n+ as $hvnf\n+ ovs-vsctl add-port br-int vif-nf -- \\\n+ set interface vif-nf external-ids:iface-id=sw0-nf-vtap \\\n+ options:tx_pcap=$hvnf/vif-nf-tx.pcap \\\n+ options:rxq_pcap=$hvnf/vif-nf-rx.pcap\n+\n+ OVN_POPULATE_ARP\n+ wait_for_ports_up\n+ check ovn-nbctl --wait=hv sync\n+ sleep 1\n+}\n+\n+cleanup_port_binding() {\n+ hvp1=$1 hvp2=$2 hvnf=$3\n+ as $hvp1\n+ ovs-vsctl del-port br-int vif1\n+ as $hvp2\n+ ovs-vsctl del-port br-int vif2\n+ as $hvnf\n+ ovs-vsctl del-port br-int vif-nf\n+ check ovn-nbctl --wait=hv sync\n+ sleep 1\n+}\n+\n+test_nf_vtap_with_multinodes_inbound() {\n+ mode=$1\n+\n+ # Test 1: Bind all 3 ports to one node\n+ echo \"$mode: Network function vtap inbound with single node\"\n+ create_port_binding hv1 hv1 hv1\n+\n+ packet_mirroring_test hv1 hv1 hv1 sw0\n+\n+ cleanup_port_binding hv1 hv1 hv1\n+\n+ # Test 2: src & dst ports on one node, NF on another node\n+ echo \"$mode: Network function vtap inbound with two nodes - nf separate\"\n+ create_port_binding hv1 hv1 hv2\n+\n+ packet_mirroring_test hv1 hv1 hv2 sw0\n+\n+ cleanup_port_binding hv1 hv1 hv2\n+\n+ # Test 3: dst and nf on one node, src on a second node\n+ echo \"$mode: Network function vtap inbound with two nodes - nf with dst\"\n+ create_port_binding hv1 hv2 hv1\n+\n+ packet_mirroring_test hv1 hv2 hv1 sw0\n+\n+ cleanup_port_binding hv1 hv2 hv1\n+\n+ # Test 4: dst on one node, nf & src on a second node\n+ echo \"$mode: Network function vtap inbound with two nodes - nf with src\"\n+ create_port_binding hv1 hv2 hv2\n+\n+ packet_mirroring_test hv1 hv2 hv2 sw0\n+\n+ cleanup_port_binding hv1 hv2 hv2\n+\n+ # Test 5: src on one node, dst on another, NF on a 3rd one\n+ echo \"$mode: Network function vtap inbound with three nodes\"\n+ create_port_binding hv1 hv2 hv3\n+\n+ packet_mirroring_test hv1 hv2 hv3 sw0\n+\n+ cleanup_port_binding hv1 hv2 hv3\n+}\n+\n+test_nf_vtap_with_multinodes_inbound overlay\n+\n+# Tests for VLAN network\n+check ovn-nbctl lsp-add-localnet-port sw0 ln0 phys\n+check ovn-nbctl set logical_switch_port ln0 tag_request=100\n+\n+test_nf_vtap_with_multinodes_inbound VLAN\n+\n+# Cleanup logical topology\n+check ovn-nbctl lsp-del ln0\n+check ovn-nbctl acl-del pg0 to-lport 1002 \"outport == @pg0 && ip4.src == 192.168.0.12\"\n+check ovn-nbctl pg-del pg0\n+check ovn-nbctl nfg-del nfg0\n+check ovn-nbctl nf-del nf0\n+check ovn-nbctl clear logical_switch_port sw0-nf-vtap options\n+for i in 1 2; do\n+ check ovn-nbctl lsp-del sw0-p$i\n+done\n+check ovn-nbctl lsp-del sw0-nf-vtap\n+check ovn-nbctl ls-del sw0\n+check ovn-nbctl --wait=hv sync\n+\n+OVN_CLEANUP([hv1],[hv2],[hv3])\n+AT_CLEANUP\n+])\n+\n OVN_FOR_EACH_NORTHD([\n AT_SETUP([Unicast ARP when proxy ARP is configured])\n AT_SKIP_IF([test $HAVE_SCAPY = no])\ndiff --git a/tests/system-ovn.at b/tests/system-ovn.at\nindex 8d1f21609..4c3348a56 100644\n--- a/tests/system-ovn.at\n+++ b/tests/system-ovn.at\n@@ -19873,7 +19873,7 @@ AT_CLEANUP\n ])\n \n OVN_FOR_EACH_NORTHD([\n-AT_SETUP([Network Function])\n+AT_SETUP([Network Function - inline mode])\n AT_SKIP_IF([test $HAVE_TCPDUMP = no])\n ovn_start\n OVS_TRAFFIC_VSWITCHD_START()\n@@ -20192,6 +20192,269 @@ OVS_TRAFFIC_VSWITCHD_STOP([\"/.*error receiving.*/d\n AT_CLEANUP\n ])\n \n+OVN_FOR_EACH_NORTHD([\n+AT_SETUP([Network Function - vtap mode])\n+AT_SKIP_IF([test $HAVE_TCPDUMP = no])\n+ovn_start\n+OVS_TRAFFIC_VSWITCHD_START()\n+\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_daemon ovn-controller\n+\n+# Create namespaces: client, server, and nf (for vtap)\n+ADD_NAMESPACES(client)\n+ADD_VETH(client, client, br-int, \"192.168.1.10/24\", \"f0:00:00:01:02:10\")\n+ADD_NAMESPACES(server)\n+ADD_VETH(server, server, br-int, \"192.168.1.20/24\", \"f0:00:00:01:02:20\")\n+ADD_NAMESPACES(nf)\n+ADD_VETH(nf-vtap, nf, br-int, \"0\", \"f0:00:00:01:02:30\")\n+ADD_VETH(nf-vtap2, nf, br-int, \"0\", \"f0:00:00:01:02:40\")\n+\n+# Create logical switch and ports\n+check ovn-nbctl ls-add sw0\n+check ovn-nbctl lsp-add sw0 client \\\n+ -- lsp-set-addresses client \"f0:00:00:01:02:10 192.168.1.10/24\"\n+check ovn-nbctl lsp-add sw0 server \\\n+ -- lsp-set-addresses server \"f0:00:00:01:02:20 192.168.1.20/24\"\n+check ovn-nbctl lsp-add sw0 nf-vtap\n+check ovn-nbctl set logical_switch_port nf-vtap options:receive_multicast=false \\\n+ options:lsp_learn_fdb=false \\\n+ options:is-nf=true\n+check ovn-nbctl lsp-add sw0 nf-vtap2\n+check ovn-nbctl set logical_switch_port nf-vtap2 options:receive_multicast=false \\\n+ options:lsp_learn_fdb=false \\\n+ options:is-nf=true\n+\n+AS_BOX([Setup: Create 2 NFs in vtap mode with health check])\n+\n+# Create NF0 with only inport (vtap mode)\n+check ovn-nbctl nf-add nf0 1 nf-vtap\n+nf0_uuid=$(fetch_column nb:network_function _uuid name=nf0)\n+\n+# Create NF1 with only inport (vtap mode)\n+check ovn-nbctl nf-add nf1 2 nf-vtap2\n+nf1_uuid=$(fetch_column nb:network_function _uuid name=nf1)\n+\n+# Create NFG with both NFs\n+check ovn-nbctl nfg-add nfg0 1 vtap nf0\n+nfg_uuid=$(fetch_column nb:network_function_group _uuid name=nfg0)\n+check ovn-nbctl nfg-add-nf nfg0 nf1\n+\n+# Set monitor IPs for health check\n+check ovn-nbctl set nb_global . options:svc_monitor_ip=169.254.100.10\n+check ovn-nbctl set nb_global . options:svc_monitor_ip_dst=169.254.100.11\n+\n+# Create health check configuration and assign to both NFs\n+AT_CHECK(\n+ [ovn-nbctl --wait=sb \\\n+ -- --id=@hc create network_function_health_check name=nf_health_cfg \\\n+ options:interval=1 options:timeout=1 options:success_count=2 options:failure_count=2 \\\n+ -- add network_function $nf0_uuid health_check @hc | uuidfilt], [0], [<0>\n+])\n+nf_health_uuid=$(fetch_column nb:network_function_health_check _uuid name=nf_health_cfg)\n+check ovn-nbctl set network_function $nf1_uuid health_check=$nf_health_uuid\n+\n+# Create port group and ACLs for both from-lport and to-lport traffic mirroring\n+check ovn-nbctl pg-add pg0 client\n+check ovn-nbctl acl-add pg0 from-lport 1001 \"inport == @pg0 && ip4.dst == 192.168.1.20\" allow-related nfg0\n+check ovn-nbctl acl-add pg0 to-lport 1002 \"outport == @pg0 && ip4.src == 192.168.1.20\" allow-related nfg0\n+\n+check ovn-nbctl --wait=hv sync\n+\n+# Bring up NF ports\n+NS_CHECK_EXEC([nf], [ip link set dev nf-vtap up])\n+NS_CHECK_EXEC([nf], [ip link set dev nf-vtap2 up])\n+\n+# Helper function to simulate NF down by removing iface-id\n+nf_down() {\n+ local port=$1\n+ ovs-vsctl remove interface ovs-$port external-ids iface-id\n+}\n+\n+# Helper function to simulate NF up by restoring iface-id\n+nf_up() {\n+ local port=$1\n+ ovs-vsctl set interface ovs-$port external-ids:iface-id=\"$port\"\n+}\n+\n+validate_nf_vtap_with_traffic() {\n+ client_ns=$1; server_ns=$2; sip=$3; direction=$4\n+\n+ # Determine ping command based on IP address format\n+ local ping_cmd=\"ping\"\n+ if [[ \"$sip\" == *\":\"* ]]; then\n+ ping_cmd=\"ping -6\"\n+ fi\n+\n+ AS_BOX([$direction: Verify traffic mirroring to nf0 when nf0 is active])\n+\n+ # Ensure nf0 is up, nf1 is down\n+ nf_up nf-vtap\n+ nf_down nf-vtap2\n+ check ovn-nbctl set network_function_group $nfg_uuid fallback=fail-close\n+ check ovn-nbctl --wait=hv sync\n+\n+ # Wait for health check to detect state\n+ sleep 5\n+\n+ # Use broad filter to capture both IPv4 and IPv6 ICMP\n+ NETNS_START_TCPDUMP([nf], [-nvvv -i nf-vtap icmp or icmp6], [tcpdump-nf-vtap])\n+\n+ # Send 5 ICMP packets - in vtap mode, traffic should be mirrored AND reach destination\n+ # NF should see 10 packets: 5 echo requests (from-lport) + 5 echo replies (to-lport)\n+ NS_CHECK_EXEC([$client_ns], [$ping_cmd -c 5 -i 0.3 $sip], [0], [ignore])\n+\n+ # Verify all mirrored packets were captured (5 requests + 5 replies = 10 packets)\n+ OVS_WAIT_UNTIL([\n+ n=$(cat tcpdump-nf-vtap.tcpdump | wc -l)\n+ test \"$n\" -ge 10\n+ ])\n+\n+ kill $(cat tcpdump-nf-vtap.pid) 2>/dev/null || true\n+\n+ AS_BOX([$direction: Verify failover - traffic mirroring to nf1 when nf0 is down])\n+\n+ # Bring nf0 down, nf1 up (failover)\n+ nf_down nf-vtap\n+ nf_up nf-vtap2\n+ check ovn-nbctl --wait=hv sync\n+\n+ # Wait for health check to detect state change\n+ sleep 5\n+\n+ NETNS_START_TCPDUMP([nf], [-nvvv -i nf-vtap2 icmp or icmp6], [tcpdump-nf-vtap])\n+\n+ # Send 5 ICMP packets - should now be mirrored to nf1\n+ # NF should see 10 packets: 5 echo requests + 5 echo replies\n+ NS_CHECK_EXEC([$client_ns], [$ping_cmd -c 5 -i 0.3 $sip], [0], [ignore])\n+\n+ # Verify all mirrored packets were captured (5 requests + 5 replies = 10 packets)\n+ OVS_WAIT_UNTIL([\n+ n=$(cat tcpdump-nf-vtap.tcpdump | wc -l)\n+ test \"$n\" -ge 10\n+ ])\n+\n+ kill $(cat tcpdump-nf-vtap.pid) 2>/dev/null || true\n+\n+ AS_BOX([$direction: Verify fallback - traffic mirroring back to nf0 when nf0 recovers])\n+\n+ # Bring nf0 back up and nf1 down (fallback to nf0)\n+ nf_up nf-vtap\n+ nf_down nf-vtap2\n+ check ovn-nbctl --wait=hv sync\n+\n+ # Wait for health check to detect state change\n+ sleep 5\n+\n+ NETNS_START_TCPDUMP([nf], [-nvvv -i nf-vtap icmp or icmp6], [tcpdump-nf-vtap])\n+\n+ # Send 5 ICMP packets - should be mirrored back to nf0\n+ # NF should see 10 packets: 5 echo requests + 5 echo replies\n+ NS_CHECK_EXEC([$client_ns], [$ping_cmd -c 5 -i 0.3 $sip], [0], [ignore])\n+\n+ # Verify all mirrored packets were captured (5 requests + 5 replies = 10 packets)\n+ OVS_WAIT_UNTIL([\n+ n=$(cat tcpdump-nf-vtap.tcpdump | wc -l)\n+ test \"$n\" -ge 10\n+ ])\n+\n+ kill $(cat tcpdump-nf-vtap.pid) 2>/dev/null || true\n+\n+ AS_BOX([$direction: Verify fail-close - traffic flows but no mirroring when both NFs are down])\n+\n+ # Bring both NFs down with fail-close\n+ nf_down nf-vtap\n+ nf_down nf-vtap2\n+ check ovn-nbctl set network_function_group $nfg_uuid fallback=fail-close\n+ check ovn-nbctl --wait=hv sync\n+\n+ # Wait for health check to detect both down\n+ sleep 5\n+\n+ NETNS_START_TCPDUMP([nf], [-nvvv -i nf-vtap icmp or icmp6], [tcpdump-nf-vtap])\n+\n+ # Send ICMP packets - in vtap mode, traffic still flows (mirroring is separate from forwarding)\n+ # but no packets should be mirrored to NF with fail-close\n+ NS_CHECK_EXEC([$client_ns], [$ping_cmd -c 3 -i 0.3 $sip], [0], [ignore])\n+\n+ # Verify no packets were mirrored (tcpdump should capture nothing)\n+ sleep 1\n+ AT_CHECK([cat tcpdump-nf-vtap.tcpdump | wc -l], [0], [0\n+])\n+\n+ kill $(cat tcpdump-nf-vtap.pid) 2>/dev/null || true\n+\n+ AS_BOX([$direction: Verify fail-open - traffic flows with no mirroring when both NFs are down])\n+\n+ # Set fail-open mode - in vtap mode, this behaves same as fail-close for traffic flow\n+ # (traffic always flows), difference is in ACL behavior\n+ check ovn-nbctl set network_function_group $nfg_uuid fallback=fail-open\n+ check ovn-nbctl --wait=hv sync\n+\n+ # Send ICMP packets - traffic should flow\n+ NS_CHECK_EXEC([$client_ns], [$ping_cmd -c 3 -i 0.3 $sip], [0], [ignore])\n+}\n+\n+AS_BOX([IPv4 Testing - Inbound traffic])\n+validate_nf_vtap_with_traffic \"client\" \"server\" \"192.168.1.20\" \"Inbound\"\n+\n+AS_BOX([IPv4 Testing - Outbound traffic])\n+validate_nf_vtap_with_traffic \"server\" \"client\" \"192.168.1.10\" \"Outbound\"\n+\n+AS_BOX([IPv6 Testing - Setup])\n+\n+# Remove IPv4 addresses from namespaces\n+ip netns exec client ip addr del 192.168.1.10/24 dev client\n+ip netns exec server ip addr del 192.168.1.20/24 dev server\n+\n+# Add IPv6 addresses to client and server\n+ip netns exec client ip -6 addr add fd00:192:168:1::10/64 dev client\n+ip netns exec server ip -6 addr add fd00:192:168:1::20/64 dev server\n+\n+# Update service monitor IPs to IPv6 for health check\n+check ovn-nbctl set nb_global . options:svc_monitor_ip=fd00:169:254:100::10\n+check ovn-nbctl set nb_global . options:svc_monitor_ip_dst=fd00:169:254:100::11\n+\n+# Configure IPv6-only addresses on logical ports\n+check ovn-nbctl lsp-set-addresses client \"f0:00:00:01:02:10 fd00:192:168:1::10\"\n+check ovn-nbctl lsp-set-addresses server \"f0:00:00:01:02:20 fd00:192:168:1::20\"\n+\n+# Add IPv6 ACLs\n+check ovn-nbctl acl-add pg0 from-lport 1003 \"inport == @pg0 && ip6.dst == fd00:192:168:1::20\" allow-related nfg0\n+check ovn-nbctl acl-add pg0 to-lport 1004 \"outport == @pg0 && ip6.src == fd00:192:168:1::20\" allow-related nfg0\n+\n+check ovn-nbctl --wait=hv sync\n+\n+AS_BOX([IPv6 Testing - Inbound traffic])\n+validate_nf_vtap_with_traffic \"client\" \"server\" \"fd00:192:168:1::20\" \"IPv6 Inbound\"\n+\n+AS_BOX([IPv6 Testing - Outbound traffic])\n+validate_nf_vtap_with_traffic \"server\" \"client\" \"fd00:192:168:1::10\" \"IPv6 Outbound\"\n+\n+# Restore NF iface-ids before cleanup\n+nf_up nf-vtap\n+nf_up nf-vtap2\n+check ovn-nbctl --wait=hv sync\n+\n+OVN_CLEANUP_CONTROLLER([hv1])\n+OVN_CLEANUP_NORTHD\n+\n+as\n+OVS_TRAFFIC_VSWITCHD_STOP([\"/.*error receiving.*/d\n+/failed to query port patch-.*/d\n+/.*terminating with signal 15.*/d\"])\n+AT_CLEANUP\n+])\n+\n OVN_FOR_EACH_NORTHD([\n AT_SETUP([dynamic-routing - BGP learned routes])\n \n", "prefixes": [ "ovs-dev", "v2", "4/4" ] }