{"id":2220048,"url":"http://patchwork.ozlabs.org/api/patches/2220048/?format=json","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=json","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=json","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=json","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 &amp;&amp;\n-        reg8[22] == 1 &amp;&amp; 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 &amp;&amp; reg8[22] == 1 &amp;&amp;\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 &amp;&amp; reg8[22] == 1 &amp;&amp;\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 &amp;&amp; reg8[22] == 1 &amp;&amp;\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 &amp;&amp; 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+        &amp;&amp; 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 &amp;&amp; reg8[22] == 1 &amp;&amp;\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 &amp;&amp;\n+        reg8[22] == 1 &amp;&amp; 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 &amp;&amp; reg8[22] == 0 &amp;&amp;\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 &amp;&amp; reg8[22] == 0 &amp;&amp;\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 &amp;&amp;\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 &amp;&amp; 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-        &amp;&amp; reg8[22] == 1 &amp;&amp; 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 &amp;&amp; reg8[22] == 1 &amp;&amp;\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 &amp;&amp; reg8[22] == 1 &amp;&amp;\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 &amp;&amp; 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+        &amp;&amp; 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 &amp;&amp; reg8[22] == 1 &amp;&amp;\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 &amp;&amp;\n+        reg8[22] == 1 &amp;&amp; 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 &amp;&amp; reg8[22] == 0 &amp;&amp;\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 &amp;&amp; reg8[22] == 0 &amp;&amp;\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 &amp;&amp; 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"]}