From patchwork Mon Oct 14 16:27:43 2024
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
X-Patchwork-Submitter: Karthik Chandrashekar
X-Patchwork-Id: 1997061
Return-Path:
X-Original-To: incoming@patchwork.ozlabs.org
Delivered-To: patchwork-incoming@legolas.ozlabs.org
Authentication-Results: legolas.ozlabs.org;
dkim=fail reason="signature verification failed" (2048-bit key;
unprotected) header.d=nutanix.com header.i=@nutanix.com header.a=rsa-sha256
header.s=proofpoint20171006 header.b=twwBEYvV;
dkim=fail reason="signature verification failed" (2048-bit key;
unprotected) header.d=nutanix.com header.i=@nutanix.com header.a=rsa-sha256
header.s=selector1 header.b=Afk95w5G;
dkim-atps=neutral
Authentication-Results: legolas.ozlabs.org;
spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org
(client-ip=140.211.166.133; helo=smtp2.osuosl.org;
envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org)
Received: from smtp2.osuosl.org (smtp2.osuosl.org [140.211.166.133])
(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384)
(No client certificate requested)
by legolas.ozlabs.org (Postfix) with ESMTPS id 4XS2hg4yhfz1xtb
for ; Tue, 15 Oct 2024 03:28:03 +1100 (AEDT)
Received: from localhost (localhost [127.0.0.1])
by smtp2.osuosl.org (Postfix) with ESMTP id DD9614033F;
Mon, 14 Oct 2024 16:28:01 +0000 (UTC)
X-Virus-Scanned: amavis at osuosl.org
Received: from smtp2.osuosl.org ([127.0.0.1])
by localhost (smtp2.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP
id qQaThPAY33Jf; Mon, 14 Oct 2024 16:27:58 +0000 (UTC)
X-Comment: SPF check N/A for local connections - client-ip=140.211.9.56;
helo=lists.linuxfoundation.org;
envelope-from=ovs-dev-bounces@openvswitch.org; receiver=
DKIM-Filter: OpenDKIM Filter v2.11.0 smtp2.osuosl.org E7BA2400E0
Authentication-Results: smtp2.osuosl.org;
dkim=fail reason="signature verification failed" (2048-bit key)
header.d=nutanix.com header.i=@nutanix.com header.a=rsa-sha256
header.s=proofpoint20171006 header.b=twwBEYvV;
dkim=fail reason="signature verification failed" (2048-bit key,
unprotected) header.d=nutanix.com header.i=@nutanix.com header.a=rsa-sha256
header.s=selector1 header.b=Afk95w5G
Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56])
by smtp2.osuosl.org (Postfix) with ESMTPS id E7BA2400E0;
Mon, 14 Oct 2024 16:27:57 +0000 (UTC)
Received: from lf-lists.osuosl.org (localhost [127.0.0.1])
by lists.linuxfoundation.org (Postfix) with ESMTP id B8365C08A6;
Mon, 14 Oct 2024 16:27:57 +0000 (UTC)
X-Original-To: ovs-dev@openvswitch.org
Delivered-To: ovs-dev@lists.linuxfoundation.org
Received: from smtp2.osuosl.org (smtp2.osuosl.org [140.211.166.133])
by lists.linuxfoundation.org (Postfix) with ESMTP id 5A406C08A3
for ; Mon, 14 Oct 2024 16:27:56 +0000 (UTC)
Received: from localhost (localhost [127.0.0.1])
by smtp2.osuosl.org (Postfix) with ESMTP id 3B0374015F
for ; Mon, 14 Oct 2024 16:27:56 +0000 (UTC)
X-Virus-Scanned: amavis at osuosl.org
Received: from smtp2.osuosl.org ([127.0.0.1])
by localhost (smtp2.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP
id KdzVcYuZS7oI for ;
Mon, 14 Oct 2024 16:27:53 +0000 (UTC)
Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=148.163.155.12;
helo=mx0b-002c1b01.pphosted.com; envelope-from=karthik.c@nutanix.com;
receiver=
DMARC-Filter: OpenDMARC Filter v1.4.2 smtp2.osuosl.org C738A400E0
Authentication-Results: smtp2.osuosl.org;
dmarc=pass (p=none dis=none) header.from=nutanix.com
DKIM-Filter: OpenDKIM Filter v2.11.0 smtp2.osuosl.org C738A400E0
Received: from mx0b-002c1b01.pphosted.com (mx0b-002c1b01.pphosted.com
[148.163.155.12])
by smtp2.osuosl.org (Postfix) with ESMTPS id C738A400E0
for ; Mon, 14 Oct 2024 16:27:51 +0000 (UTC)
Received: from pps.filterd (m0127844.ppops.net [127.0.0.1])
by mx0b-002c1b01.pphosted.com (8.18.1.2/8.18.1.2) with ESMTP id
49E82Emt008048
for ; Mon, 14 Oct 2024 09:27:51 -0700
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=nutanix.com; h=
cc:content-transfer-encoding:content-type:date:from:message-id
:mime-version:subject:to; s=proofpoint20171006; bh=SD6iK9Nqt+Lye
x1DILLszQ87w9WGG0dr1zq5DEtdFe0=; b=twwBEYvVs/JIHAudelpHldSMMQyu7
oc7XvdCZRaIJLmb4GJw3nIuGOOKiHw+Gdqm/sTDnNeJqnz7Db+PIWk+W2UR3pop3
RzOJDws4iv6CgQIMEuJSe1u5MfIVR0On1MKvTWiiOhvE0cs5cmaZXeIv5Kg+YI6G
Qvu90lH/OVLuqn5q7BTpzjaznQBvMWWygnXa7BlLD1Ix9YTVwnlK8vYdhWMV/mhP
4kjzmc27xSJ+Nf8cjwlo41UrD/1kTIiF41S0heF0MDg4odsfi0Z1NSN5nsYULxtT
1PzdNO4S1NNNz8WidImDP7RHqmt4wAQfdfdOP5/y7NtmEH/1RDjy6Irtw==
Received: from cy3pr05cu001.outbound.protection.outlook.com
(mail-westcentralusazlp17013078.outbound.protection.outlook.com [40.93.6.78])
by mx0b-002c1b01.pphosted.com (PPS) with ESMTPS id 428yeg9381-1
(version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT)
for ; Mon, 14 Oct 2024 09:27:50 -0700 (PDT)
ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none;
b=vg7FM3MBfUJwsOAxVGLj4lhII4SVGFxzfgrNxkD4qku7MvoYbS2eiTvjSkWGGsigJJDQ9HqaUx08BN+k8ql7fhPNnSTfhg5zn+ZrVyPgRM7NFM3LXeKghOoVwOh74mMGqupk3PHJ6e33hU8xLQFWdTk6gS4EdTJRaW5WwiNg70eSSWpO1s5jRQ9Eni3UWCNh10v18G6E28WAu4G0usIRFEhdVkXNkI7XYeTCDRlrI1gLtJPpD7nHDCIRZcDfCe+wjuYVb1+JsJpX08G9eyHZzbGfbuPDSTU5B/Fl3aB8hMbXEKXwpQpZeKO4d3Mi5bbAF9Er/M9gievaBLuug0A4Ew==
ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com;
s=arcselector10001;
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;
bh=SD6iK9Nqt+Lyex1DILLszQ87w9WGG0dr1zq5DEtdFe0=;
b=spVRVjYGhqeKMQtdULPUv4wPCElD8YgTz+++jluIz9UOHPN7tB0OtSjHyWTh7RnrFj1kLJr5xMRNQ8jAFi3POZBJ8deoX3BXfdZ50BoMWzYc/KhgRXvBMV33aPhVHhHXYtmCIfU1PgCW93p17bGkQIR53FVTONTdLmAW4C4BAWs8GefhMoDYazXv18DQ/gskkRzVYx+x4rCUSccBpG/OxbVDhuiIn99GRKwu+q+cgCwZSTEq7RSZ/jqVNSrl4fJH4Yzqm3lQcMptCBxnLN3krCwUre510uMm758SHEaZYRcMv9Nsafu07u7NR67jkqH5ufl6f1K4rWDjyv3kU0wYsQ==
ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass
smtp.mailfrom=nutanix.com; dmarc=pass action=none header.from=nutanix.com;
dkim=pass header.d=nutanix.com; arc=none
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=nutanix.com;
s=selector1;
h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck;
bh=SD6iK9Nqt+Lyex1DILLszQ87w9WGG0dr1zq5DEtdFe0=;
b=Afk95w5GJLlW6sFoZ/POvvEYPeClb6bMUspWSXK/F19fGFqfZxDx5n1KQJSxluOYxHjxjo+u1rvJG1BBSLsyfL3xiBrtSJ/Ws3k7cmFzTOyIeS1VEoffBxh3YkqgmAO6SOiZhkPKb0P9IPXxHQJ7wqcjoIERT1G7Nc/MwrSLEhtEhi4p+nxq8QwFlrfe3Rw818TxMFXv4Yg/8eslun7BDssAfh1M5Y28H014osQlnOU2kjB6MGC5JyIH5laTtFc/qDchbA5ilaFc2r5d2ETx5dpCbCyVrE6PgNjgsQfMBEw/le/HDu+yxWSm4ivhqo+M44SsnK14y0G7fmOqR5dwBQ==
Received: from CH3PR02MB10217.namprd02.prod.outlook.com
(2603:10b6:610:1be::10) by SA1PR02MB9698.namprd02.prod.outlook.com
(2603:10b6:806:385::11) with Microsoft SMTP Server (version=TLS1_2,
cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.8048.20; Mon, 14 Oct
2024 16:27:47 +0000
Received: from CH3PR02MB10217.namprd02.prod.outlook.com
([fe80::1b78:29fa:4471:f1b4]) by CH3PR02MB10217.namprd02.prod.outlook.com
([fe80::1b78:29fa:4471:f1b4%2]) with mapi id 15.20.8048.020; Mon, 14 Oct 2024
16:27:46 +0000
From: karthik.c@nutanix.com
To: ovs-dev@openvswitch.org
Date: Mon, 14 Oct 2024 16:27:43 +0000
Message-Id: <20241014162743.136480-1-karthik.c@nutanix.com>
X-Mailer: git-send-email 2.22.3
X-ClientProxiedBy: BYAPR08CA0052.namprd08.prod.outlook.com
(2603:10b6:a03:117::29) To CH3PR02MB10217.namprd02.prod.outlook.com
(2603:10b6:610:1be::10)
MIME-Version: 1.0
X-MS-PublicTrafficType: Email
X-MS-TrafficTypeDiagnostic: CH3PR02MB10217:EE_|SA1PR02MB9698:EE_
X-MS-Office365-Filtering-Correlation-Id: bfd42e8b-8f3c-4968-3c40-08dcec6d231e
x-proofpoint-crosstenant: true
X-MS-Exchange-SenderADCheck: 1
X-MS-Exchange-AntiSpam-Relay: 0
X-Microsoft-Antispam: BCL:0;
ARA:13230040|52116014|366016|1800799024|376014|38350700014;
X-Microsoft-Antispam-Message-Info:
GNeeeM/r0ToNHBE9Nvnetij8QjlIH9nB2PprgdBwtN5FZ0/dlgAtcS5/5qEIa6Pp1slRcoddX4Kv5PLG7QuKwlhAAB0N3AAMJ7SYWttpOvs8SPLUIAarIIrVKZA9yhO0C+wYLUnUE0d+q97asKttCh+Ejj5CuGqlmsBH0a+ecxK1s08gOdtG6h585UXkDYmTJmPTOsXmpBTHwazPCq+9Mw1LR+KzVdMNSzDL5OX7JauTJXSBXEb2ocXU4k7J0p66hVU18umYnmYT5kHCmeSOhIjKz+lUGykQQ7x0HF/rQE3PIocsPRQ8gb+kiK4V2sQi1AQNWyhIwnPWkvSAu8E+Si7eDgACT+vWfaewE2X353NFDTL7qUHmKg7xJ6f9o5sxwp9cGhbUCxvJXYGnzA+lpTyTAcqu5iT1iWP9frQIdJxzOBwA5ALj6jAzkX/EvicneblYf0nOOJrbdmfOvMTHxrk7EMjgNIu2C12BsRntSeXUiBICLzI6474ISE49Z7g30xHfKHXjvLaNzmToCiKoKMh/aGq98XPPPlyVQG95dKUc0Jwzuj8yQialr/071PxR89LZ5TClfK2hHA0ML8qax1pGwcnv3mBn6ltF0yIGD57iA2fYgp6ck3PH/Ke6DnGcJqZ7E5Y0WFNrv01IySZbwzuIdlyOE8W2sM8LRIfQE7pScVtxHY9TwsJ3okLdGnJ5JKeLWhvXgTD7cSyylUKRbj/BRRDeRPX3446oACoydj1RwW6lL20SK4DvVbKvaPxCOjgitXwYBN0ohdF9ToSpRQ3LOG57Kz1XWs3hSzc90UNT7gqiwQPFEhZqXTfavgbdurTiR65VHbMiX1S5GbZy4yBBOoaXpGMdsXOcA6d1PpFeVg29xeCB9Jn3qcLX6X/sFuz2KTfX7XzhILPjUWkBiKIDjDfYs5UeF6LP0ICBIXH7UJ0K0otZfPn/BmusXPRfo/GXXpndIkiTf9TL9lTCJRUMyQwF0KYPzrhgT/Ql0PW3KAFp3dGp1+/wwDTKpshKKUbg0AYCSggx7crLNJvCM78zjLjzwqYIC+3E+UlHBWdVSKwlSfut8arQmv6gCODgVebhjK070veoywaDblLG/Dh8GU+u1JHswt5aj6waVl+pH9QUm+Rhxydf3BlkhW/ZNIDzqQSpFZGJ4fBj9VSS9CSZV/F3j0gc9Gc4gmDs9l6PIffiAzsM8ycP/v5GqjQHPPiCMOmVSWv55/YHURinj09HGIAX5/Hmjv4Ie4Y3flMS45Wy3ywfabE3fCsCXW3SHy8fr5On4OQUBmu6NEv+07AtRAOYcWX3nNud90KsJcF19arxVUNA4YV5K54bYrcXlygBYosoAlL4fo3ejXoRoA==
X-Forefront-Antispam-Report: CIP:255.255.255.255; CTRY:; LANG:en; SCL:1; SRV:;
IPV:NLI; SFV:NSPM; H:CH3PR02MB10217.namprd02.prod.outlook.com; PTR:;
CAT:NONE;
SFS:(13230040)(52116014)(366016)(1800799024)(376014)(38350700014); DIR:OUT;
SFP:1102;
X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1
X-MS-Exchange-AntiSpam-MessageData-0:
dRw6UajF4/IO/Cf+Wo6BLIeRNv2mFzds84GEgK0qc7c2CEjLNG4bsLN3I6fm6RwZDdcmlyJ6Dm2PEMlfIFpTGIzIU662P06PY1eNTuZgJwrJU30bjpw81+rXynhGIvurQN69QArK4+SKZR7pP3g5aEhKnUXjPOF64ZT8e9M4uaI67pTnaqBsDT5HA9pOfrzaZ4cZrCUER634ymiNd04QQ9+wXt7s8DxLfQY5ITp1a5ZmBpFrDdG3db6L7sDhLzsCxQcz08UhEfjprkOjeUYpZyR2NYuH+OvLpkPyYPn25hzI9su4VdATBktuOhbAfO1EMU3ml7scXgVJ3xKx9tg22T5djmINkKVxWmHuYB7IVTvszgDq7EoVzzQu3p+Be+coBulRvhEpc6h1XNQ0Y6pApMGLA6kX525zQ4oV+N5vTCKtFPsv/+9+XuM0JZnsl7mqpHu0+ybko0gSIlCL5EoW7yLcmzJIzCjogRuBvuiyrQWQEFc2ypbdLMcUZQpFAx444InNCHDDiNgPiqEVfDOTbY27tExjvNgDDcwdCOyPI3qZkHnsEWKnq4itYCzNvkz+yPOCc3b+SiBYxD/WOQU5AiuMVrRvwWBqVcNr2lRgT7ECNiP7NCSoA0CGAimOwnspbi8O5Fq/83AtIVo04sYWWhTw0WEA/Q/FCA1hp7/p2a0zew8XbxgPvcc83rhn2GqRWZQ39heaVF9O5dF/nBlna3D82cd1+rTwP9TcDpQPzA7Hx3elTcOW9jBbH7fvPvOO3epS18tAsNw3xpVgNtOTYZkuBTxsPA/DvE9jMW1zwPO/JF+56heN2spxdQXsmeVdX3k+4NSK3Q/Rt46YTd6SHhjvllxqaw+ly41o6RmIR1QfJyxBJqKvi9VP17M5lEdpS7WNDTojOXQAIGZIeDwBLc/HUoZNg1P5uLgDRD9G7ZLXodXs5GeKl1LbdJFHarpy09wGwbXk5WT26+V15lb0ewvwFdyfDUOLUSJHAIP/LgAbZntJvXuYrzrRZZKOh5ve5ybXg6lpiKQCqIb38NJJhqHefHpHSKM7cGUQ1Wxr8fbFuWTrkeuJYuSQAF/i2/Q8LIuskqcecP5u3eR2VXEqBsCP48EtijhtLKT2lq2S84ENEyMjQFHTaZZu54hoFNQpo62o81isrrpckIhCoCai8SwuX5cORcuBTV5FVTP9wAQVp1elb4goTZPYZCQkXdhRYZa3AvmTqQ84biASnbiMmlALohmvBYpvQNVlOgtzds6f85APUc74jlvePBomJt1Y2blocMB2XNMI6JQM9/6Ti/Gp5s9vike8hM+PbaGlyXcJaoW1Rdr/Obh2bwYdWXgDEFEGOwMSyNxoW0JV4Cqh/mF+WmnUcfnGesJVpFb94gMM6UMx0mDAp1dWQzPpNDULghTeCJBLpzbo/T5hQGUeN1Bs0yx7k3RKNshKwbnS/A+RwkiZGMNgmv92+nj+NjpxgSjFMasZbvxavnDHZQNWC9YH/plhQULZWmqi1ioJClFcYJ4yNdmQ29NiCsTdzS3kLLWesuD8Y11y5mNEO3ymt6kD5aGMG1uaLy+35xYFISWxWzKYh6gYpRM5dGzvjG9Bv0/nDVhqrNAyDU6eX/3zsQ==
X-OriginatorOrg: nutanix.com
X-MS-Exchange-CrossTenant-Network-Message-Id:
bfd42e8b-8f3c-4968-3c40-08dcec6d231e
X-MS-Exchange-CrossTenant-AuthSource: CH3PR02MB10217.namprd02.prod.outlook.com
X-MS-Exchange-CrossTenant-AuthAs: Internal
X-MS-Exchange-CrossTenant-OriginalArrivalTime: 14 Oct 2024 16:27:46.7125 (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:
vbpIic1M8wayUh/i84agU0nYenaLk/KFW/O7FXK6998d1L9gRFMlvJFxF+CTk3heK1xRAe1h2X6H978feh3oYQ==
X-MS-Exchange-Transport-CrossTenantHeadersStamped: SA1PR02MB9698
X-Proofpoint-GUID: 2J3-gNiZSpATVozBmvOv8mfg7eCI-4q4
X-Authority-Analysis: v=2.4 cv=U/XADvru c=1 sm=1 tr=0 ts=670d4686 cx=c_pps
a=BALyy5icRfvvzfOMzojctg==:117 a=Ol13hO9ccFRV9qXi2t6ftBPywas=:19
a=xqWC_Br6kY4A:10 a=DAUX931o1VcA:10 a=0034W8JfsZAA:10 a=0kUYKlekyDsA:10
a=64Cc0HZtAAAA:8 a=ykOlTvJY75_KCpQHf88A:9
a=zMC55ShFWnuvwLVG:21 a=3ZKOabzyN94A:10 a=14NRyaPF5x3gF6G45PvQ:22
X-Proofpoint-ORIG-GUID: 2J3-gNiZSpATVozBmvOv8mfg7eCI-4q4
X-Proofpoint-Virus-Version: vendor=baseguard
engine=ICAP:2.0.293,Aquarius:18.0.1051,Hydra:6.0.680,FMLib:17.12.62.30
definitions=2024-10-14_10,2024-10-11_01,2024-09-30_01
X-Proofpoint-Spam-Reason: safe
Subject: [ovs-dev] [PATCH ovn v4] Support selection fields for ECMP routes.
X-BeenThere: ovs-dev@openvswitch.org
X-Mailman-Version: 2.1.30
Precedence: list
List-Id:
List-Unsubscribe: ,
List-Archive:
List-Post:
List-Help:
List-Subscribe: ,
Errors-To: ovs-dev-bounces@openvswitch.org
Sender: "dev"
From: Karthik Chandrashekar
- This patch adds the ability to specify a custom set of packet headers
during hash computation for ECMP routes. This is similar to the support
that was added for LB in 5af304e
- For ECMP routes OVN by default use dp_hash as a selection_method
in the OVS flows that have the group action. With this patch when
selection_fields is specified, the selection_method will change
to hash with the specified list of fields used for computing the hash.
- List of fields that are used in the select action is an intersection
of all the fields specified in each Logical_Route_Static_Route
that is part of a given ECMP route.
- In order to allow match based on L4 port numbers, the lr_in_ip_routing
rules have been split into separate lflows with protocol specific
fields when src_port or dst_port is specified in the ecmp_selection_fields.
(This is based on the requirement that pre-requisites of fields
must be provided by any flows that output to the group)
Signed-off-by: Karthik Chandrashekar
---
v2:
- Install separate logical flows for TCP and UDP in lr_in_ip_routing.
- Add more test coverage.
---
v3:
- Address code review comments.
- Install separate logical flows for TCP, UDP, SCTP in lr_in_ip_routing
only when match on src_port or dst_port is specified.
- Update NB and SB documentation.
---
v4:
- Address code review comments.
- Use selection_fields column for Logical_Router_Static_Route instead
of options.
---
include/ovn/actions.h | 1 +
lib/actions.c | 44 ++++++-
northd/northd.c | 103 ++++++++++++++---
northd/northd.h | 1 +
ovn-nb.ovsschema | 10 +-
ovn-nb.xml | 26 +++++
ovn-sb.xml | 10 ++
tests/ovn-northd.at | 69 +++++++----
tests/ovn.at | 258 +++++++++++++++++++++++++++++++++++++++++-
9 files changed, 478 insertions(+), 44 deletions(-)
diff --git a/include/ovn/actions.h b/include/ovn/actions.h
index a95a0daf7..63a12a882 100644
--- a/include/ovn/actions.h
+++ b/include/ovn/actions.h
@@ -339,6 +339,7 @@ struct ovnact_select {
struct ovnact_select_dst *dsts;
size_t n_dsts;
uint8_t ltable; /* Logical table ID of next table. */
+ char *hash_fields;
struct expr_field res_field;
};
diff --git a/lib/actions.c b/lib/actions.c
index 2e05d4134..c5bde996b 100644
--- a/lib/actions.c
+++ b/lib/actions.c
@@ -1541,10 +1541,18 @@ parse_select_action(struct action_context *ctx, struct expr_field *res_field)
struct ovnact_select_dst *dsts = NULL;
size_t allocated_dsts = 0;
size_t n_dsts = 0;
+ bool requires_hash_fields = false;
+ char *hash_fields = NULL;
lexer_get(ctx->lexer); /* Skip "select". */
lexer_get(ctx->lexer); /* Skip '('. */
+ if (lexer_match_id(ctx->lexer, "values")) {
+ lexer_force_match(ctx->lexer, LEX_T_EQUALS);
+ lexer_force_match(ctx->lexer, LEX_T_LPAREN);
+ requires_hash_fields = true;
+ }
+
while (!lexer_match(ctx->lexer, LEX_T_RPAREN)) {
struct ovnact_select_dst dst;
if (!action_parse_uint16(ctx, &dst.id, "id")) {
@@ -1581,11 +1589,31 @@ parse_select_action(struct action_context *ctx, struct expr_field *res_field)
return;
}
+ if (requires_hash_fields) {
+ lexer_force_match(ctx->lexer, LEX_T_SEMICOLON);
+ if (!lexer_match_id(ctx->lexer, "hash_fields")) {
+ lexer_syntax_error(ctx->lexer, "expecting hash_fields");
+ free(dsts);
+ return;
+ }
+ if (!lexer_match(ctx->lexer, LEX_T_EQUALS) ||
+ ctx->lexer->token.type != LEX_T_STRING ||
+ lexer_lookahead(ctx->lexer) != LEX_T_RPAREN) {
+ lexer_syntax_error(ctx->lexer, "invalid hash_fields");
+ free(dsts);
+ return;
+ }
+ hash_fields = xstrdup(ctx->lexer->token.s);
+ lexer_get(ctx->lexer);
+ lexer_force_match(ctx->lexer, LEX_T_RPAREN);
+ }
+
struct ovnact_select *select = ovnact_put_SELECT(ctx->ovnacts);
select->ltable = ctx->pp->cur_ltable + 1;
select->dsts = dsts;
select->n_dsts = n_dsts;
select->res_field = *res_field;
+ select->hash_fields = hash_fields;
}
static void
@@ -1595,6 +1623,9 @@ format_SELECT(const struct ovnact_select *select, struct ds *s)
ds_put_cstr(s, " = ");
ds_put_cstr(s, "select");
ds_put_char(s, '(');
+ if (select->hash_fields) {
+ ds_put_format(s, "values=(");
+ }
for (size_t i = 0; i < select->n_dsts; i++) {
if (i) {
ds_put_cstr(s, ", ");
@@ -1605,6 +1636,9 @@ format_SELECT(const struct ovnact_select *select, struct ds *s)
ds_put_format(s, "=%"PRIu16, dst->weight);
}
ds_put_char(s, ')');
+ if (select->hash_fields) {
+ ds_put_format(s, "; hash_fields=\"%s\")", select->hash_fields);
+ }
ds_put_char(s, ';');
}
@@ -1619,9 +1653,14 @@ encode_SELECT(const struct ovnact_select *select,
struct ofpact_group *og;
struct ds ds = DS_EMPTY_INITIALIZER;
- ds_put_format(&ds, "type=select,selection_method=dp_hash");
+ ds_put_format(&ds, "type=select,selection_method=%s",
+ select->hash_fields ? "hash": "dp_hash");
+ if (select->hash_fields) {
+ ds_put_format(&ds, ",fields(%s)", select->hash_fields);
+ }
- if (ovs_feature_is_supported(OVS_DP_HASH_L4_SYM_SUPPORT)) {
+ if (ovs_feature_is_supported(OVS_DP_HASH_L4_SYM_SUPPORT) &&
+ !select->hash_fields) {
/* Select dp-hash l4_symmetric by setting the upper 32bits of
* selection_method_param to value 1 (1 << 32): */
ds_put_cstr(&ds, ",selection_method_param=0x100000000");
@@ -1654,6 +1693,7 @@ static void
ovnact_select_free(struct ovnact_select *select)
{
free(select->dsts);
+ free(select->hash_fields);
}
static void
diff --git a/northd/northd.c b/northd/northd.c
index 0aa0de637..9b02efb89 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -302,9 +302,9 @@ BUILD_ASSERT_DECL(ACL_OBS_STAGE_MAX < (1 << 2));
* same ip_prefix values:
* - connected route overrides static one;
* - static route overrides src-ip route. */
-#define ROUTE_PRIO_OFFSET_MULTIPLIER 3
-#define ROUTE_PRIO_OFFSET_STATIC 1
-#define ROUTE_PRIO_OFFSET_CONNECTED 2
+#define ROUTE_PRIO_OFFSET_MULTIPLIER 5
+#define ROUTE_PRIO_OFFSET_STATIC 2
+#define ROUTE_PRIO_OFFSET_CONNECTED 4
/* Returns the type of the datapath to which a flow with the given 'stage' may
* be added. */
@@ -11155,6 +11155,22 @@ parsed_routes_add(struct ovn_datapath *od, const struct hmap *lr_ports,
"ecmp_symmetric_reply",
false);
new_pr->is_discard_route = is_discard_route;
+ sset_init(&new_pr->ecmp_selection_fields);
+
+ /* If tp_src or tp_dst is included in the selection_fields, implicitly
+ * include match on ip_proto. */
+ if (route->n_selection_fields) {
+ struct sset field_set = SSET_INITIALIZER(&field_set);
+ for (size_t i = 0; i < route->n_selection_fields; i++) {
+ char *field = route->selection_fields[i];
+ if (!strcmp(field, "tp_src") || !strcmp(field, "tp_dst")) {
+ sset_add(&field_set, "ip_proto");
+ }
+ sset_add(&field_set, field);
+ }
+ sset_clone(&new_pr->ecmp_selection_fields, &field_set);
+ sset_destroy(&field_set);
+ }
size_t hash = uuid_hash(&od->key);
struct parsed_route *pr = parsed_route_lookup(routes, hash, new_pr);
@@ -11162,6 +11178,7 @@ parsed_routes_add(struct ovn_datapath *od, const struct hmap *lr_ports,
hmap_insert(routes, &new_pr->key_node, hash);
} else {
pr->stale = false;
+ sset_destroy(&new_pr->ecmp_selection_fields);
free(new_pr);
}
}
@@ -11211,6 +11228,7 @@ struct ecmp_groups_node {
uint32_t route_table_id;
uint16_t route_count;
struct ovs_list route_list; /* Contains ecmp_route_list_node */
+ struct sset selection_fields;
};
static void
@@ -11226,6 +11244,14 @@ ecmp_groups_add_route(struct ecmp_groups_node *group,
struct ecmp_route_list_node *er = xmalloc(sizeof *er);
er->route = route;
er->id = ++group->route_count;
+
+ if (group->route_count == 1) {
+ sset_clone(&group->selection_fields, &route->ecmp_selection_fields);
+ } else {
+ sset_intersect(&group->selection_fields,
+ &route->ecmp_selection_fields);
+ }
+
ovs_list_insert(&group->route_list, &er->list_node);
}
@@ -11248,6 +11274,7 @@ ecmp_groups_add(struct hmap *ecmp_groups,
eg->is_src_route = route->is_src_route;
eg->origin = smap_get_def(&route->route->options, "origin", "");
eg->route_table_id = route->route_table_id;
+ sset_init(&eg->selection_fields);
ovs_list_init(&eg->route_list);
ecmp_groups_add_route(eg, route);
@@ -11280,6 +11307,7 @@ ecmp_groups_destroy(struct hmap *ecmp_groups)
free(er);
}
hmap_remove(ecmp_groups, &eg->hmap_node);
+ sset_destroy(&eg->selection_fields);
free(eg);
}
hmap_destroy(ecmp_groups);
@@ -11350,7 +11378,8 @@ build_route_prefix_s(const struct in6_addr *prefix, unsigned int plen)
static void
build_route_match(const struct ovn_port *op_inport, uint32_t rtb_id,
const char *network_s, int plen, bool is_src_route,
- bool is_ipv4, struct ds *match, uint16_t *priority, int ofs)
+ bool is_ipv4, struct ds *match, uint16_t *priority, int ofs,
+ bool has_protocol_match)
{
const char *dir;
/* The priority here is calculated to implement longest-prefix-match
@@ -11362,14 +11391,18 @@ build_route_match(const struct ovn_port *op_inport, uint32_t rtb_id,
dir = "dst";
}
- *priority = (plen * ROUTE_PRIO_OFFSET_MULTIPLIER) + ofs;
-
if (op_inport) {
ds_put_format(match, "inport == %s && ", op_inport->json_key);
}
if (rtb_id || ofs == ROUTE_PRIO_OFFSET_STATIC) {
ds_put_format(match, "%s == %d && ", REG_ROUTE_TABLE_ID, rtb_id);
}
+
+ if (has_protocol_match) {
+ ofs += 1;
+ }
+ *priority = (plen * ROUTE_PRIO_OFFSET_MULTIPLIER) + ofs;
+
ds_put_format(match, "ip%s.%s == %s/%d", is_ipv4 ? "4" : "6", dir,
network_s, plen);
}
@@ -11548,7 +11581,7 @@ add_ecmp_symmetric_reply_flows(struct lflow_table *lflows,
static void
build_ecmp_route_flow(struct lflow_table *lflows, struct ovn_datapath *od,
const struct hmap *lr_ports, struct ecmp_groups_node *eg,
- struct lflow_ref *lflow_ref)
+ struct lflow_ref *lflow_ref, const char *protocol)
{
bool is_ipv4 = IN6_IS_ADDR_V4MAPPED(&eg->prefix);
@@ -11560,7 +11593,8 @@ build_ecmp_route_flow(struct lflow_table *lflows, struct ovn_datapath *od,
int ofs = !strcmp(eg->origin, ROUTE_ORIGIN_CONNECTED) ?
ROUTE_PRIO_OFFSET_CONNECTED: ROUTE_PRIO_OFFSET_STATIC;
build_route_match(NULL, eg->route_table_id, prefix_s, eg->plen,
- eg->is_src_route, is_ipv4, &route_match, &priority, ofs);
+ eg->is_src_route, is_ipv4, &route_match, &priority, ofs,
+ protocol != NULL);
free(prefix_s);
struct ds actions = DS_EMPTY_INITIALIZER;
@@ -11568,18 +11602,48 @@ build_ecmp_route_flow(struct lflow_table *lflows, struct ovn_datapath *od,
"; %s = ", REG_ECMP_GROUP_ID, eg->id, REG_ECMP_MEMBER_ID);
if (!ovs_list_is_singleton(&eg->route_list)) {
+ if (protocol && !sset_is_empty(&eg->selection_fields)) {
+ ds_put_format(&route_match, " && %s", protocol);
+ }
+
+ struct ds values = DS_EMPTY_INITIALIZER;
bool is_first = true;
- ds_put_cstr(&actions, "select(");
LIST_FOR_EACH (er, list_node, &eg->route_list) {
if (is_first) {
is_first = false;
} else {
- ds_put_cstr(&actions, ", ");
+ ds_put_cstr(&values, ", ");
}
- ds_put_format(&actions, "%"PRIu16, er->id);
+ ds_put_format(&values, "%"PRIu16, er->id);
+ }
+
+ if (!sset_is_empty(&eg->selection_fields)) {
+ struct sset field_set = SSET_INITIALIZER(&field_set);
+ sset_clone(&field_set, &eg->selection_fields);
+
+ /* If tp_src and tp_dst is specified in selection_fields, replace it
+ * with protocol specific src and dst port fields */
+ if (protocol && sset_contains(&eg->selection_fields, "tp_src")) {
+ sset_add_and_free(&field_set, xasprintf("%s_src", protocol));
+ }
+ if (protocol && sset_contains(&eg->selection_fields, "tp_dst")) {
+ sset_add_and_free(&field_set, xasprintf("%s_dst", protocol));
+ }
+ sset_find_and_delete(&field_set, "tp_src");
+ sset_find_and_delete(&field_set, "tp_dst");
+
+ char *hash_fields = sset_join(&field_set, ",", "");
+ ds_put_format(&actions, "select(values=(%s); hash_fields=\"%s\"",
+ ds_cstr(&values), hash_fields);
+
+ free(hash_fields);
+ sset_destroy(&field_set);
+ } else {
+ ds_put_format(&actions, "select(%s", ds_cstr(&values));
}
ds_put_cstr(&actions, ");");
+ ds_destroy(&values);
} else {
er = CONTAINER_OF(ovs_list_front(&eg->route_list),
struct ecmp_route_list_node, list_node);
@@ -11662,7 +11726,7 @@ add_route(struct lflow_table *lflows, struct ovn_datapath *od,
}
}
build_route_match(op_inport, rtb_id, network_s, plen, is_src_route,
- is_ipv4, &match, &priority, ofs);
+ is_ipv4, &match, &priority, ofs, false);
struct ds common_actions = DS_EMPTY_INITIALIZER;
struct ds actions = DS_EMPTY_INITIALIZER;
@@ -13560,7 +13624,19 @@ build_static_route_flows_for_lrouter(
HMAP_FOR_EACH (group, hmap_node, &ecmp_groups) {
/* add a flow in IP_ROUTING, and one flow for each member in
* IP_ROUTING_ECMP. */
- build_ecmp_route_flow(lflows, od, lr_ports, group, lflow_ref);
+ build_ecmp_route_flow(lflows, od, lr_ports, group, lflow_ref, NULL);
+
+ /* If src or dst port is specified for selection_fields, install
+ * separate ECMP flows with protocol match of TCP, UDP and SCTP */
+ if (sset_contains(&group->selection_fields, "tp_src") ||
+ sset_contains(&group->selection_fields, "tp_dst")) {
+ build_ecmp_route_flow(lflows, od, lr_ports, group, lflow_ref,
+ "tcp");
+ build_ecmp_route_flow(lflows, od, lr_ports, group, lflow_ref,
+ "udp");
+ build_ecmp_route_flow(lflows, od, lr_ports, group, lflow_ref,
+ "sctp");
+ }
}
const struct unique_routes_node *ur;
HMAP_FOR_EACH (ur, hmap_node, &unique_routes) {
@@ -18811,6 +18887,7 @@ static_routes_destroy(struct static_routes_data *data)
{
struct parsed_route *r;
HMAP_FOR_EACH_POP (r, key_node, &data->parsed_routes) {
+ sset_destroy(&r->ecmp_selection_fields);
free(r);
}
hmap_destroy(&data->parsed_routes);
diff --git a/northd/northd.h b/northd/northd.h
index 8f76d642d..c1442ff40 100644
--- a/northd/northd.h
+++ b/northd/northd.h
@@ -707,6 +707,7 @@ struct parsed_route {
bool is_discard_route;
const struct nbrec_logical_router *nbr;
bool stale;
+ struct sset ecmp_selection_fields;
};
void ovnnb_db_run(struct northd_input *input_data,
diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
index b4a395c56..c4a48183d 100644
--- a/ovn-nb.ovsschema
+++ b/ovn-nb.ovsschema
@@ -1,7 +1,7 @@
{
"name": "OVN_Northbound",
- "version": "7.6.0",
- "cksum": "2171465655 38284",
+ "version": "7.7.0",
+ "cksum": "116357561 38626",
"tables": {
"NB_Global": {
"columns": {
@@ -506,6 +506,12 @@
"refType": "weak"},
"min": 0,
"max": 1}},
+ "selection_fields": {
+ "type": {"key": {"type": "string",
+ "enum": ["set",
+ ["eth_src", "eth_dst", "ip_proto", "ip_src",
+ "ip_dst", "tp_src", "tp_dst"]]},
+ "min": 0, "max": "unlimited"}},
"options": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}},
diff --git a/ovn-nb.xml b/ovn-nb.xml
index 2836f58f5..3729a5bee 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -3806,6 +3806,32 @@ or
+
+
+ ECMP routes use OpenFlow groups of type select
to
+ pick a nexthop among the list of available nexthops.
+ OVS supports two selection methods: dp_hash
and
+ hash
for hash computation and selecting
+ the buckets of a group. OVN by default uses dp_hash
.
+ In order to use the hash
selection method,
+ specify the allowed match fields in selection fields.
+ Please see the OVS documentation (man ovs-ofctl) for more
+ details on selection methods and fields.
+
+
+ To match on Layer 4 ports use tp_port
and
+ tp_port
. This match is applicable only for TCP,
+ UDP, SCTP and will be ignored for all other IP packets. When
+ matching on Layer 4 ports, match on ip_proto will be implicitly
+ added in the select action.
+
+
+ Example: {ip_proto,ip_src,ip_dst}
for a 3-tuple match.
+ Example: {ip_proto,ip_src,ip_dst,tp_src,tp_dst}
+ for a 5-tuple match.
+
+
+
Any string to place route to separate routing table. If Logical Router
diff --git a/ovn-sb.xml b/ovn-sb.xml
index 5285cae30..e079018d8 100644
--- a/ovn-sb.xml
+++ b/ovn-sb.xml
@@ -2545,6 +2545,7 @@ tcp.flags = RST;
R = select(N1[=W1], N2[=W2], ...);
+ R = select(values=N1[=W1], N2[=W2], ...; hash_felds="field1,field2,...");
Parameters: Integer N1, N2..., with
@@ -2564,6 +2565,14 @@ tcp.flags = RST;
selection method is based on the 5-tuple hash of packet header.
+
+ By default, dp_hash
is used as the OpenFlow group
+ selection method, but if values
and
+ hash_fields
are specified, hash
is used
+ as the selection method, and the fields listed are used as the
+ hash fields.
+
+
Processing automatically moves on to the next table, as if
next;
were specified. The select
action
@@ -2574,6 +2583,7 @@ tcp.flags = RST;
Example: reg8[16..31] = select(1=20, 2=30, 3=50);
+ Example: reg8[16..31] = select(values=1=20, 2=30, 3=50; hash_fields="ip_proto,src_ip,dst_ip");
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index d6a8c4640..adec96d1f 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -6810,9 +6810,9 @@ AT_CHECK([grep -w "lr_in_ip_routing" lr0flows | ovn_strip_lflows], [0], [dnl
table=??(lr_in_ip_routing ), priority=0 , match=(1), action=(drop;)
table=??(lr_in_ip_routing ), priority=10300, match=(ct_mark.ecmp_reply_port == 1 && reg7 == 0 && ip4.dst == 1.0.0.1/32), action=(ip.ttl--; flags.loopback = 1; eth.src = 00:00:20:20:12:13; reg1 = 192.168.0.1; outport = "lr0-public"; next;)
table=??(lr_in_ip_routing ), priority=10550, match=(nd_rs || nd_ra), action=(drop;)
- table=??(lr_in_ip_routing ), priority=194 , match=(inport == "lr0-public" && ip6.dst == fe80::/64), action=(ip.ttl--; reg8[[0..15]] = 0; xxreg0 = ip6.dst; xxreg1 = fe80::200:20ff:fe20:1213; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;)
- table=??(lr_in_ip_routing ), priority=74 , match=(ip4.dst == 192.168.0.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = ip4.dst; reg1 = 192.168.0.1; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;)
- table=??(lr_in_ip_routing ), priority=97 , match=(reg7 == 0 && ip4.dst == 1.0.0.1/32), action=(ip.ttl--; flags.loopback = 1; reg8[[0..15]] = 1; reg8[[16..31]] = 1; next;)
+ table=??(lr_in_ip_routing ), priority=124 , match=(ip4.dst == 192.168.0.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = ip4.dst; reg1 = 192.168.0.1; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;)
+ table=??(lr_in_ip_routing ), priority=162 , match=(reg7 == 0 && ip4.dst == 1.0.0.1/32), action=(ip.ttl--; flags.loopback = 1; reg8[[0..15]] = 1; reg8[[16..31]] = 1; next;)
+ table=??(lr_in_ip_routing ), priority=324 , match=(inport == "lr0-public" && ip6.dst == fe80::/64), action=(ip.ttl--; reg8[[0..15]] = 0; xxreg0 = ip6.dst; xxreg1 = fe80::200:20ff:fe20:1213; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;)
])
AT_CHECK([grep -e "lr_in_ip_routing_ecmp" lr0flows | ovn_strip_lflows], [0], [dnl
@@ -6828,9 +6828,9 @@ AT_CHECK([grep -w "lr_in_ip_routing" lr0flows | ovn_strip_lflows], [0], [dnl
table=??(lr_in_ip_routing ), priority=0 , match=(1), action=(drop;)
table=??(lr_in_ip_routing ), priority=10300, match=(ct_mark.ecmp_reply_port == 1 && reg7 == 0 && ip4.dst == 1.0.0.1/32), action=(ip.ttl--; flags.loopback = 1; eth.src = 00:00:20:20:12:13; reg1 = 192.168.0.1; outport = "lr0-public"; next;)
table=??(lr_in_ip_routing ), priority=10550, match=(nd_rs || nd_ra), action=(drop;)
- table=??(lr_in_ip_routing ), priority=194 , match=(inport == "lr0-public" && ip6.dst == fe80::/64), action=(ip.ttl--; reg8[[0..15]] = 0; xxreg0 = ip6.dst; xxreg1 = fe80::200:20ff:fe20:1213; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;)
- table=??(lr_in_ip_routing ), priority=74 , match=(ip4.dst == 192.168.0.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = ip4.dst; reg1 = 192.168.0.1; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;)
- table=??(lr_in_ip_routing ), priority=97 , match=(reg7 == 0 && ip4.dst == 1.0.0.1/32), action=(ip.ttl--; flags.loopback = 1; reg8[[0..15]] = 1; reg8[[16..31]] = select(1, 2);)
+ table=??(lr_in_ip_routing ), priority=124 , match=(ip4.dst == 192.168.0.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = ip4.dst; reg1 = 192.168.0.1; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;)
+ table=??(lr_in_ip_routing ), priority=162 , match=(reg7 == 0 && ip4.dst == 1.0.0.1/32), action=(ip.ttl--; flags.loopback = 1; reg8[[0..15]] = 1; reg8[[16..31]] = select(1, 2);)
+ table=??(lr_in_ip_routing ), priority=324 , match=(inport == "lr0-public" && ip6.dst == fe80::/64), action=(ip.ttl--; reg8[[0..15]] = 0; xxreg0 = ip6.dst; xxreg1 = fe80::200:20ff:fe20:1213; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;)
])
AT_CHECK([grep -e "lr_in_ip_routing_ecmp" lr0flows | sed 's/192\.168\.0\..0/192.168.0.??/' | ovn_strip_lflows], [0], [dnl
table=??(lr_in_ip_routing_ecmp), priority=0 , match=(1), action=(drop;)
@@ -6857,9 +6857,9 @@ AT_CHECK([grep -w "lr_in_ip_routing" lr0flows | ovn_strip_lflows], [0], [dnl
table=??(lr_in_ip_routing ), priority=0 , match=(1), action=(drop;)
table=??(lr_in_ip_routing ), priority=10300, match=(ct_mark.ecmp_reply_port == 1 && reg7 == 0 && ip4.dst == 1.0.0.1/32), action=(ip.ttl--; flags.loopback = 1; eth.src = 00:00:20:20:12:13; reg1 = 192.168.0.1; outport = "lr0-public"; next;)
table=??(lr_in_ip_routing ), priority=10550, match=(nd_rs || nd_ra), action=(drop;)
- table=??(lr_in_ip_routing ), priority=194 , match=(inport == "lr0-public" && ip6.dst == fe80::/64), action=(ip.ttl--; reg8[[0..15]] = 0; xxreg0 = ip6.dst; xxreg1 = fe80::200:20ff:fe20:1213; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;)
- table=??(lr_in_ip_routing ), priority=74 , match=(ip4.dst == 192.168.0.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = ip4.dst; reg1 = 192.168.0.1; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;)
- table=??(lr_in_ip_routing ), priority=97 , match=(reg7 == 0 && ip4.dst == 1.0.0.1/32), action=(ip.ttl--; flags.loopback = 1; reg8[[0..15]] = 1; reg8[[16..31]] = select(1, 2);)
+ table=??(lr_in_ip_routing ), priority=124 , match=(ip4.dst == 192.168.0.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = ip4.dst; reg1 = 192.168.0.1; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;)
+ table=??(lr_in_ip_routing ), priority=162 , match=(reg7 == 0 && ip4.dst == 1.0.0.1/32), action=(ip.ttl--; flags.loopback = 1; reg8[[0..15]] = 1; reg8[[16..31]] = select(1, 2);)
+ table=??(lr_in_ip_routing ), priority=324 , match=(inport == "lr0-public" && ip6.dst == fe80::/64), action=(ip.ttl--; reg8[[0..15]] = 0; xxreg0 = ip6.dst; xxreg1 = fe80::200:20ff:fe20:1213; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;)
])
AT_CHECK([grep -e "lr_in_ip_routing_ecmp" lr0flows | sed 's/192\.168\.0\..0/192.168.0.??/' | ovn_strip_lflows], [0], [dnl
table=??(lr_in_ip_routing_ecmp), priority=0 , match=(1), action=(drop;)
@@ -6875,14 +6875,41 @@ check ovn-nbctl --wait=sb lr-route-add lr0 1.0.0.0/24 192.168.0.10
ovn-sbctl dump-flows lr0 > lr0flows
AT_CHECK([grep -e "lr_in_ip_routing.*192.168.0.10" lr0flows | ovn_strip_lflows], [0], [dnl
- table=??(lr_in_ip_routing ), priority=73 , match=(reg7 == 0 && ip4.dst == 1.0.0.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 192.168.0.10; reg1 = 192.168.0.1; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;)
+ table=??(lr_in_ip_routing ), priority=122 , match=(reg7 == 0 && ip4.dst == 1.0.0.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 192.168.0.10; reg1 = 192.168.0.1; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;)
])
check ovn-nbctl --wait=sb lr-route-add lr0 2.0.0.0/24 lr0-public
ovn-sbctl dump-flows lr0 > lr0flows
AT_CHECK([grep -e "lr_in_ip_routing.*2.0.0.0" lr0flows | ovn_strip_lflows], [0], [dnl
- table=??(lr_in_ip_routing ), priority=73 , match=(reg7 == 0 && ip4.dst == 2.0.0.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = ip4.dst; reg1 = 192.168.0.1; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;)
+ table=??(lr_in_ip_routing ), priority=122 , match=(reg7 == 0 && ip4.dst == 2.0.0.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = ip4.dst; reg1 = 192.168.0.1; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;)
+])
+
+check ovn-nbctl lr-route-add lr0 3.3.0.0/16 192.168.0.11
+check ovn-nbctl --ecmp lr-route-add lr0 3.3.0.0/16 192.168.0.12
+
+route1_uuid=$(fetch_column nb:logical_router_static_route _uuid nexthop="192.168.0.11")
+route2_uuid=$(fetch_column nb:logical_router_static_route _uuid nexthop="192.168.0.12")
+
+check ovn-nbctl set logical_router_static_route $route1_uuid selection_fields="ip_proto,ip_src,ip_dst"
+check ovn-nbctl set logical_router_static_route $route2_uuid selection_fields="ip_proto,ip_src,ip_dst"
+
+check ovn-nbctl --wait=sb sync
+ovn-sbctl dump-flows lr0 > lr0flows
+AT_CHECK([grep -e "(lr_in_ip_routing ).*3.3.0.0" lr0flows | sed 's/table=../table=??/' | sort], [0], [dnl
+ table=??(lr_in_ip_routing ), priority=82 , match=(reg7 == 0 && ip4.dst == 3.3.0.0/16), action=(ip.ttl--; flags.loopback = 1; reg8[[0..15]] = 1; reg8[[16..31]] = select(values=(1, 2); hash_fields="ip_dst,ip_proto,ip_src");)
+])
+
+check ovn-nbctl set logical_router_static_route $route1_uuid selection_fields="ip_src,ip_dst,tp_src,tp_dst"
+check ovn-nbctl set logical_router_static_route $route2_uuid selection_fields="ip_src,ip_dst,tp_src,tp_dst"
+
+check ovn-nbctl --wait=sb sync
+ovn-sbctl dump-flows lr0 > lr0flows
+AT_CHECK([grep -e "(lr_in_ip_routing ).*3.3.0.0" lr0flows | sed 's/table=../table=??/' | sort], [0], [dnl
+ table=??(lr_in_ip_routing ), priority=82 , match=(reg7 == 0 && ip4.dst == 3.3.0.0/16), action=(ip.ttl--; flags.loopback = 1; reg8[[0..15]] = 1; reg8[[16..31]] = select(values=(1, 2); hash_fields="ip_dst,ip_proto,ip_src");)
+ table=??(lr_in_ip_routing ), priority=83 , match=(reg7 == 0 && ip4.dst == 3.3.0.0/16 && sctp), action=(ip.ttl--; flags.loopback = 1; reg8[[0..15]] = 1; reg8[[16..31]] = select(values=(1, 2); hash_fields="ip_dst,ip_proto,ip_src,sctp_dst,sctp_src");)
+ table=??(lr_in_ip_routing ), priority=83 , match=(reg7 == 0 && ip4.dst == 3.3.0.0/16 && tcp), action=(ip.ttl--; flags.loopback = 1; reg8[[0..15]] = 1; reg8[[16..31]] = select(values=(1, 2); hash_fields="ip_dst,ip_proto,ip_src,tcp_dst,tcp_src");)
+ table=??(lr_in_ip_routing ), priority=83 , match=(reg7 == 0 && ip4.dst == 3.3.0.0/16 && udp), action=(ip.ttl--; flags.loopback = 1; reg8[[0..15]] = 1; reg8[[16..31]] = select(values=(1, 2); hash_fields="ip_dst,ip_proto,ip_src,udp_dst,udp_src");)
])
AT_CLEANUP
@@ -7306,16 +7333,16 @@ AT_CHECK([grep "lr_in_ip_routing_pre" lr0flows | ovn_strip_lflows], [0], [dnl
grep -e "(lr_in_ip_routing ).*outport" lr0flows
AT_CHECK([grep -e "(lr_in_ip_routing ).*outport" lr0flows | ovn_strip_lflows], [0], [dnl
- table=??(lr_in_ip_routing ), priority=1 , match=(reg7 == 0 && ip4.dst == 0.0.0.0/0), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 192.168.0.10; reg1 = 192.168.0.1; eth.src = 00:00:00:00:00:01; outport = "lrp0"; flags.loopback = 1; next;)
- table=??(lr_in_ip_routing ), priority=1 , match=(reg7 == 2 && ip4.dst == 0.0.0.0/0), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 192.168.0.10; reg1 = 192.168.0.1; eth.src = 00:00:00:00:00:01; outport = "lrp0"; flags.loopback = 1; next;)
- table=??(lr_in_ip_routing ), priority=194 , match=(inport == "lrp0" && ip6.dst == fe80::/64), action=(ip.ttl--; reg8[[0..15]] = 0; xxreg0 = ip6.dst; xxreg1 = fe80::200:ff:fe00:1; eth.src = 00:00:00:00:00:01; outport = "lrp0"; flags.loopback = 1; next;)
- table=??(lr_in_ip_routing ), priority=194 , match=(inport == "lrp1" && ip6.dst == fe80::/64), action=(ip.ttl--; reg8[[0..15]] = 0; xxreg0 = ip6.dst; xxreg1 = fe80::200:ff:fe00:101; eth.src = 00:00:00:00:01:01; outport = "lrp1"; flags.loopback = 1; next;)
- table=??(lr_in_ip_routing ), priority=194 , match=(inport == "lrp2" && ip6.dst == fe80::/64), action=(ip.ttl--; reg8[[0..15]] = 0; xxreg0 = ip6.dst; xxreg1 = fe80::200:ff:fe00:201; eth.src = 00:00:00:00:02:01; outport = "lrp2"; flags.loopback = 1; next;)
- table=??(lr_in_ip_routing ), priority=73 , match=(reg7 == 1 && ip4.dst == 192.168.0.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 192.168.1.10; reg1 = 192.168.1.1; eth.src = 00:00:00:00:01:01; outport = "lrp1"; flags.loopback = 1; next;)
- table=??(lr_in_ip_routing ), priority=74 , match=(ip4.dst == 192.168.0.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = ip4.dst; reg1 = 192.168.0.1; eth.src = 00:00:00:00:00:01; outport = "lrp0"; flags.loopback = 1; next;)
- table=??(lr_in_ip_routing ), priority=74 , match=(ip4.dst == 192.168.1.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = ip4.dst; reg1 = 192.168.1.1; eth.src = 00:00:00:00:01:01; outport = "lrp1"; flags.loopback = 1; next;)
- table=??(lr_in_ip_routing ), priority=74 , match=(ip4.dst == 192.168.2.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = ip4.dst; reg1 = 192.168.2.1; eth.src = 00:00:00:00:02:01; outport = "lrp2"; flags.loopback = 1; next;)
- table=??(lr_in_ip_routing ), priority=97 , match=(reg7 == 2 && ip4.dst == 1.1.1.1/32), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 192.168.0.20; reg1 = 192.168.0.1; eth.src = 00:00:00:00:00:01; outport = "lrp0"; flags.loopback = 1; next;)
+ table=??(lr_in_ip_routing ), priority=122 , match=(reg7 == 1 && ip4.dst == 192.168.0.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 192.168.1.10; reg1 = 192.168.1.1; eth.src = 00:00:00:00:01:01; outport = "lrp1"; flags.loopback = 1; next;)
+ table=??(lr_in_ip_routing ), priority=124 , match=(ip4.dst == 192.168.0.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = ip4.dst; reg1 = 192.168.0.1; eth.src = 00:00:00:00:00:01; outport = "lrp0"; flags.loopback = 1; next;)
+ table=??(lr_in_ip_routing ), priority=124 , match=(ip4.dst == 192.168.1.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = ip4.dst; reg1 = 192.168.1.1; eth.src = 00:00:00:00:01:01; outport = "lrp1"; flags.loopback = 1; next;)
+ table=??(lr_in_ip_routing ), priority=124 , match=(ip4.dst == 192.168.2.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = ip4.dst; reg1 = 192.168.2.1; eth.src = 00:00:00:00:02:01; outport = "lrp2"; flags.loopback = 1; next;)
+ table=??(lr_in_ip_routing ), priority=162 , match=(reg7 == 2 && ip4.dst == 1.1.1.1/32), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 192.168.0.20; reg1 = 192.168.0.1; eth.src = 00:00:00:00:00:01; outport = "lrp0"; flags.loopback = 1; next;)
+ table=??(lr_in_ip_routing ), priority=2 , match=(reg7 == 0 && ip4.dst == 0.0.0.0/0), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 192.168.0.10; reg1 = 192.168.0.1; eth.src = 00:00:00:00:00:01; outport = "lrp0"; flags.loopback = 1; next;)
+ table=??(lr_in_ip_routing ), priority=2 , match=(reg7 == 2 && ip4.dst == 0.0.0.0/0), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 192.168.0.10; reg1 = 192.168.0.1; eth.src = 00:00:00:00:00:01; outport = "lrp0"; flags.loopback = 1; next;)
+ table=??(lr_in_ip_routing ), priority=324 , match=(inport == "lrp0" && ip6.dst == fe80::/64), action=(ip.ttl--; reg8[[0..15]] = 0; xxreg0 = ip6.dst; xxreg1 = fe80::200:ff:fe00:1; eth.src = 00:00:00:00:00:01; outport = "lrp0"; flags.loopback = 1; next;)
+ table=??(lr_in_ip_routing ), priority=324 , match=(inport == "lrp1" && ip6.dst == fe80::/64), action=(ip.ttl--; reg8[[0..15]] = 0; xxreg0 = ip6.dst; xxreg1 = fe80::200:ff:fe00:101; eth.src = 00:00:00:00:01:01; outport = "lrp1"; flags.loopback = 1; next;)
+ table=??(lr_in_ip_routing ), priority=324 , match=(inport == "lrp2" && ip6.dst == fe80::/64), action=(ip.ttl--; reg8[[0..15]] = 0; xxreg0 = ip6.dst; xxreg1 = fe80::200:ff:fe00:201; eth.src = 00:00:00:00:02:01; outport = "lrp2"; flags.loopback = 1; next;)
])
AT_CLEANUP
diff --git a/tests/ovn.at b/tests/ovn.at
index d7f01169c..7674dd297 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -2133,6 +2133,16 @@ reg0 = select(1, 2);
encodes as group:20
uses group: id(20), name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:100,actions=load:1->xxreg0[[96..127]],resubmit(,oflow_in_table),bucket=bucket_id=1,weight:100,actions=load:2->xxreg0[[96..127]],resubmit(,oflow_in_table))
+reg9[[16..31]] = select(values=(1=50, 2=100, 3); hash_fields="ip_src,ip_dst");
+ formats as reg9[[16..31]] = select(values=(1=50, 2=100, 3=100); hash_fields="ip_src,ip_dst");
+ encodes as group:21
+ uses group: id(21), name(type=select,selection_method=hash,fields(ip_src,ip_dst),bucket=bucket_id=0,weight:50,actions=load:1->xreg4[[16..31]],resubmit(,oflow_in_table),bucket=bucket_id=1,weight:100,actions=load:2->xreg4[[16..31]],resubmit(,oflow_in_table),bucket=bucket_id=2,weight:100,actions=load:3->xreg4[[16..31]],resubmit(,oflow_in_table))
+
+reg0 = select(values=(1, 2); hash_fields="ip_dst,ip_src");
+ formats as reg0 = select(values=(1=100, 2=100); hash_fields="ip_dst,ip_src");
+ encodes as group:22
+ uses group: id(22), name(type=select,selection_method=hash,fields(ip_dst,ip_src),bucket=bucket_id=0,weight:100,actions=load:1->xxreg0[[96..127]],resubmit(,oflow_in_table),bucket=bucket_id=1,weight:100,actions=load:2->xxreg0[[96..127]],resubmit(,oflow_in_table))
+
reg0 = select(1=, 2);
Syntax error at `,' expecting weight.
reg0 = select(1=0, 2);
@@ -2141,6 +2151,14 @@ reg0 = select(1=123456, 2);
Syntax error at `123456' expecting weight.
reg0 = select(123);
Syntax error at `;' expecting at least 2 group members.
+reg0 = select(values=1, 2);
+ Syntax error at `1' expecting `('.
+reg0 = select(values=(1, 2); test_fields="hello,world");
+ Syntax error at `test_fields' expecting hash_fields.
+reg0 = select(values=(1, 2); hash_fields);
+ Syntax error at `)' invalid hash_fields.
+reg0 = select(values=(1, 2); hash_fields=);
+ Syntax error at `)' invalid hash_fields.
ip.proto = select(1, 2, 3);
Field ip.proto is not modifiable.
reg0[[0..14]] = select(1, 2, 3);
@@ -2148,12 +2166,12 @@ reg0[[0..14]] = select(1, 2, 3);
fwd_group(liveness=true, childports="eth0", "lsp1");
formats as fwd_group(liveness="true", childports="eth0", "lsp1");
- encodes as group:21
- uses group: id(21), name(type=select,selection_method=dp_hash,bucket=watch_port:5,load=0x5->NXM_NX_REG15[[0..15]],resubmit(,OFTABLE_SAVE_INPORT),bucket=watch_port:17,load=0x17->NXM_NX_REG15[[0..15]],resubmit(,OFTABLE_SAVE_INPORT))
+ encodes as group:23
+ uses group: id(23), name(type=select,selection_method=dp_hash,bucket=watch_port:5,load=0x5->NXM_NX_REG15[[0..15]],resubmit(,OFTABLE_SAVE_INPORT),bucket=watch_port:17,load=0x17->NXM_NX_REG15[[0..15]],resubmit(,OFTABLE_SAVE_INPORT))
fwd_group(childports="eth0", "lsp1");
- encodes as group:22
- uses group: id(22), name(type=select,selection_method=dp_hash,bucket=load=0x5->NXM_NX_REG15[[0..15]],resubmit(,OFTABLE_SAVE_INPORT),bucket=load=0x17->NXM_NX_REG15[[0..15]],resubmit(,OFTABLE_SAVE_INPORT))
+ encodes as group:24
+ uses group: id(24), name(type=select,selection_method=dp_hash,bucket=load=0x5->NXM_NX_REG15[[0..15]],resubmit(,OFTABLE_SAVE_INPORT),bucket=load=0x17->NXM_NX_REG15[[0..15]],resubmit(,OFTABLE_SAVE_INPORT))
fwd_group(childports=eth0);
Syntax error at `eth0' expecting logical switch port.
@@ -2162,8 +2180,8 @@ fwd_group();
Syntax error at `)' expecting `;'.
fwd_group(childports="eth0", "lsp1");
- encodes as group:22
- uses group: id(22), name(type=select,selection_method=dp_hash,bucket=load=0x5->NXM_NX_REG15[[0..15]],resubmit(,OFTABLE_SAVE_INPORT),bucket=load=0x17->NXM_NX_REG15[[0..15]],resubmit(,OFTABLE_SAVE_INPORT))
+ encodes as group:24
+ uses group: id(24), name(type=select,selection_method=dp_hash,bucket=load=0x5->NXM_NX_REG15[[0..15]],resubmit(,OFTABLE_SAVE_INPORT),bucket=load=0x17->NXM_NX_REG15[[0..15]],resubmit(,OFTABLE_SAVE_INPORT))
fwd_group(liveness=xyzzy, childports="eth0", "lsp1");
Syntax error at `xyzzy' expecting true or false.
@@ -26620,6 +26638,234 @@ OVN_CLEANUP([hv1])
AT_CLEANUP
])
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([ECMP static routes - custom hash])
+ovn_start
+
+# Logical network:
+# ls1 (192.168.1.0/24) - lr1 - ls2 (192.168.2.0/24)
+# lsl has lsp11 (192.168.1.11) and ls2 has lsp21 (192.168.2.21) and lsp22
+# (192.168.2.22)
+#
+# Static routes on lr1:
+# 10.0.0.0/24 nexthop 192.168.2.21
+# 10.0.0.0/24 nexthop 192.168.2.22
+#
+# ECMP hash on ip_proto,src_ip,dst_ip,tp_dst
+#
+# Test:
+# lsp11 sends packets to 10.0.0.100 with different source ports
+# Migrate all the VIFs to a hv2
+# Generate the same traffic from lsp11 again
+#
+# Expected result:
+# All packets should go out of a either lsp21 or lsp22 on hv1
+# All packets should go out of the same port even on hv2
+
+ovn-nbctl lr-add lr1
+
+ovn-nbctl ls-add ls1
+ovn-nbctl ls-add ls2
+
+for i in 1 2; do
+ ovn-nbctl lrp-add lr1 lrp-lr1-ls${i} 00:00:00:01:0${i}:01 192.168.${i}.1/24
+ ovn-nbctl lsp-add ls${i} lsp-ls${i}-lr1 -- lsp-set-type lsp-ls${i}-lr1 router \
+ -- lsp-set-options lsp-ls${i}-lr1 router-port=lrp-lr1-ls${i} \
+ -- lsp-set-addresses lsp-ls${i}-lr1 router
+done
+
+#install static routes
+ovn-nbctl lr-route-add lr1 10.0.0.0/24 192.168.2.21
+route_uuid=$(fetch_column nb:logical_router_static_route _uuid nexthop="192.168.2.21")
+check ovn-nbctl set logical_router_static_route $route_uuid selection_fields="eth_src,ip_proto,ip_src,ip_dst,tp_dst"
+
+ovn-nbctl --ecmp lr-route-add lr1 10.0.0.0/24 192.168.2.22
+route_uuid=$(fetch_column nb:logical_router_static_route _uuid nexthop="192.168.2.22")
+check ovn-nbctl set logical_router_static_route $route_uuid selection_fields="eth_dst,ip_proto,ip_src,ip_dst,tp_dst"
+
+# Create logical ports
+ovn-nbctl lsp-add ls1 lsp11 -- \
+ lsp-set-addresses lsp11 "f0:00:00:00:01:11 192.168.1.11"
+ovn-nbctl lsp-add ls2 lsp21 -- \
+ lsp-set-addresses lsp21 "f0:00:00:00:02:21 192.168.2.21"
+ovn-nbctl lsp-add ls2 lsp22 -- \
+ lsp-set-addresses lsp22 "f0:00:00:00:02:22 192.168.2.22"
+
+net_add n1
+sim_add hv1
+as hv1
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+
+sim_add hv2
+as hv2
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.2
+
+as hv1
+ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=lsp11 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+ options:rxq_pcap=hv1/vif1-rx.pcap \
+ ofport-request=1
+
+ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=lsp21 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+ options:rxq_pcap=hv1/vif2-rx.pcap \
+ ofport-request=2
+
+ovs-vsctl -- add-port br-int hv1-vif3 -- \
+ set interface hv1-vif3 external-ids:iface-id=lsp22 \
+ options:tx_pcap=hv1/vif3-tx.pcap \
+ options:rxq_pcap=hv1/vif3-rx.pcap \
+ ofport-request=3
+
+# wait for earlier changes to take effect
+check ovn-nbctl --wait=hv sync
+wait_for_ports_up
+
+ovn-sbctl dump-flows > sbflows
+AT_CAPTURE_FILE([sbflows])
+
+AT_CAPTURE_FILE([ofgroups])
+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-groups br-int > ofgroups
+ grep "selection_method=hash,fields" ofgroups | \
+ grep "nw_proto" | grep "ip_src" | grep "ip_dst" | wc -l], [0], [4
+])
+
+as hv1 ovs-ofctl dump-groups br-int > ofgroups
+AT_CHECK([grep "selection_method=hash,fields(ip_src,ip_dst,nw_proto)" ofgroups | wc -l], [0], [1
+])
+AT_CHECK([grep "selection_method=hash,fields(ip_src,ip_dst,nw_proto,tcp_dst)" ofgroups | wc -l], [0], [1
+])
+AT_CHECK([grep "selection_method=hash,fields(ip_src,ip_dst,nw_proto,udp_dst)" ofgroups | wc -l], [0], [1
+])
+AT_CHECK([grep "selection_method=hash,fields(ip_src,ip_dst,nw_proto,sctp_dst)" ofgroups | wc -l], [0], [1
+])
+
+as hv1 ovs-ofctl dump-flows br-int > oflows
+AT_CAPTURE_FILE([oflows])
+
+for i in $(seq 5001 5010); do
+ packet="inport==\"lsp11\" && eth.src==f0:00:00:00:01:11 && eth.dst==00:00:00:01:01:01 &&
+ ip4 && ip.ttl==64 && ip4.src==192.168.1.11 && ip4.dst==10.0.0.100 &&
+ tcp && tcp.src==$i && tcp.dst==80"
+ OVS_WAIT_UNTIL([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"])
+
+ for j in 1 2; do
+ # Assume all packets go to lsp2${j}.
+ exp_packet="eth.src==00:00:00:01:02:01 && eth.dst==f0:00:00:00:02:2${j} &&
+ ip4 && ip.ttl==63 && ip4.src==192.168.1.11 && ip4.dst==10.0.0.100 &&
+ tcp && tcp.src==$i && tcp.dst==80"
+ echo $exp_packet | ovstest test-ovn expr-to-packets >> expected_lsp2${j}
+ done
+done
+
+# All packets should go out of a single port given the hashing is based on ip_proto,ip_src,ip_dst,tp_dst which is fixed
+OVS_WAIT_UNTIL([
+ hv1_rcv_n1=`$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > lsp21.packets && cat lsp21.packets | wc -l`
+ hv1_rcv_n2=`$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif3-tx.pcap > lsp22.packets && cat lsp22.packets | wc -l`
+ echo $hv1_rcv_n1 $hv1_rcv_n2
+ test $(($hv1_rcv_n1 + $hv1_rcv_n2)) -ge 10])
+
+if test $hv1_rcv_n1 = 0; then
+ AT_CHECK([test $hv1_rcv_n2 -ge 10], [0], [])
+else
+ AT_CHECK([test $hv1_rcv_n1 -ge 10], [0], [])
+fi
+
+# Move all VIFs to hv2 and send the same packets again
+as hv1
+ovs-vsctl del-port hv1-vif1
+ovs-vsctl del-port hv1-vif2
+ovs-vsctl del-port hv1-vif3
+
+wait_column "" Port_Binding chassis logical_port=lsp11
+wait_column "" Port_Binding chassis logical_port=lsp21
+wait_column "" Port_Binding chassis logical_port=lsp22
+
+as hv2
+ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=lsp11 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+ options:rxq_pcap=hv2/vif1-rx.pcap \
+ ofport-request=1
+
+ovs-vsctl -- add-port br-int hv2-vif2 -- \
+ set interface hv2-vif2 external-ids:iface-id=lsp21 \
+ options:tx_pcap=hv2/vif2-tx.pcap \
+ options:rxq_pcap=hv2/vif2-rx.pcap \
+ ofport-request=2
+
+ovs-vsctl -- add-port br-int hv2-vif3 -- \
+ set interface hv2-vif3 external-ids:iface-id=lsp22 \
+ options:tx_pcap=hv2/vif3-tx.pcap \
+ options:rxq_pcap=hv2/vif3-rx.pcap \
+ ofport-request=3
+
+# wait for earlier changes to take effect
+check ovn-nbctl --wait=hv sync
+wait_for_ports_up
+
+AT_CAPTURE_FILE([ofgroups])
+OVS_WAIT_FOR_OUTPUT([as hv2 ovs-ofctl dump-groups br-int > ofgroups
+ grep "selection_method=hash,fields" ofgroups | \
+ grep "nw_proto" | grep "ip_src" | grep "ip_dst" | wc -l], [0], [4
+])
+
+as hv2 ovs-ofctl dump-groups br-int > ofgroups
+AT_CHECK([grep "selection_method=hash,fields(ip_src,ip_dst,nw_proto)" ofgroups | wc -l], [0], [1
+])
+AT_CHECK([grep "selection_method=hash,fields(ip_src,ip_dst,nw_proto,tcp_dst)" ofgroups | wc -l], [0], [1
+])
+AT_CHECK([grep "selection_method=hash,fields(ip_src,ip_dst,nw_proto,udp_dst)" ofgroups | wc -l], [0], [1
+])
+AT_CHECK([grep "selection_method=hash,fields(ip_src,ip_dst,nw_proto,sctp_dst)" ofgroups | wc -l], [0], [1
+])
+
+as hv2 ovs-ofctl dump-flows br-int > oflows
+AT_CAPTURE_FILE([oflows])
+
+for i in $(seq 5001 5010); do
+ packet="inport==\"lsp11\" && eth.src==f0:00:00:00:01:11 && eth.dst==00:00:00:01:01:01 &&
+ ip4 && ip.ttl==64 && ip4.src==192.168.1.11 && ip4.dst==10.0.0.100 &&
+ tcp && tcp.src==$i && tcp.dst==80"
+ OVS_WAIT_UNTIL([as hv2 ovs-appctl -t ovn-controller inject-pkt "$packet"])
+
+ for j in 1 2; do
+ # Assume all packets go to lsp2${j}.
+ exp_packet="eth.src==00:00:00:01:02:01 && eth.dst==f0:00:00:00:02:2${j} &&
+ ip4 && ip.ttl==63 && ip4.src==192.168.1.11 && ip4.dst==10.0.0.100 &&
+ tcp && tcp.src==$i && tcp.dst==80"
+ echo $exp_packet | ovstest test-ovn expr-to-packets >> expected_lsp2${j}
+ done
+done
+
+# All packets should go out of a single port given the hashing is based on ip_proto,ip_src,ip_dst,tp_dst which is fixed
+OVS_WAIT_UNTIL([
+ hv2_rcv_n1=`$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv2/vif2-tx.pcap > lsp21.packets && cat lsp21.packets | wc -l`
+ hv2_rcv_n2=`$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv2/vif3-tx.pcap > lsp22.packets && cat lsp22.packets | wc -l`
+ echo $hv2_rcv_n1 $hv2_rcv_n2
+ test $(($hv2_rcv_n1 + $hv2_rcv_n2)) -ge 10])
+
+if test $hv2_rcv_n1 = 0; then
+ AT_CHECK([test $hv2_rcv_n2 -ge 10], [0], [])
+else
+ AT_CHECK([test $hv2_rcv_n1 -ge 10], [0], [])
+fi
+
+# All packets should out of the same port on both hosts
+if test $hv1_rcv_n1 = 0; then
+ AT_CHECK([test $hv2_rcv_n1 -eq 0], [0], [])
+else
+ AT_CHECK([test $hv2_rcv_n2 -eq 0], [0], [])
+fi
+
+OVN_CLEANUP([hv1], [hv2])
+
+AT_CLEANUP
+])
OVN_FOR_EACH_NORTHD([
AT_SETUP([route tables -- route table routes])