From patchwork Tue Dec 12 18:04:02 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Naveen Yerramneni X-Patchwork-Id: 1875350 X-Patchwork-Delegate: nusiddiq@redhat.com 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=r0ea4gvm; 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=s65nb0Ww; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=140.211.166.137; helo=smtp4.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org) Received: from smtp4.osuosl.org (smtp4.osuosl.org [140.211.166.137]) (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 4SqRN607Wqz1ySd for ; Wed, 13 Dec 2023 05:04:53 +1100 (AEDT) Received: from localhost (localhost [127.0.0.1]) by smtp4.osuosl.org (Postfix) with ESMTP id 1106441622; Tue, 12 Dec 2023 18:04:51 +0000 (UTC) DKIM-Filter: OpenDKIM Filter v2.11.0 smtp4.osuosl.org 1106441622 Authentication-Results: smtp4.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=r0ea4gvm; 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=s65nb0Ww X-Virus-Scanned: amavisd-new at osuosl.org Received: from smtp4.osuosl.org ([127.0.0.1]) by localhost (smtp4.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id Yvv4JkYctd3o; Tue, 12 Dec 2023 18:04:48 +0000 (UTC) Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [IPv6:2605:bc80:3010:104::8cd3:938]) by smtp4.osuosl.org (Postfix) with ESMTPS id 17D97416C2; Tue, 12 Dec 2023 18:04:47 +0000 (UTC) DKIM-Filter: OpenDKIM Filter v2.11.0 smtp4.osuosl.org 17D97416C2 Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id C82E5C0072; Tue, 12 Dec 2023 18:04:46 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp4.osuosl.org (smtp4.osuosl.org [IPv6:2605:bc80:3010::137]) by lists.linuxfoundation.org (Postfix) with ESMTP id 51797C0037 for ; Tue, 12 Dec 2023 18:04:45 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp4.osuosl.org (Postfix) with ESMTP id 0BE5E416C2 for ; Tue, 12 Dec 2023 18:04:45 +0000 (UTC) DKIM-Filter: OpenDKIM Filter v2.11.0 smtp4.osuosl.org 0BE5E416C2 X-Virus-Scanned: amavisd-new at osuosl.org Received: from smtp4.osuosl.org ([127.0.0.1]) by localhost (smtp4.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id VialhTxbFwrV for ; Tue, 12 Dec 2023 18:04:41 +0000 (UTC) Received: from mx0b-002c1b01.pphosted.com (mx0b-002c1b01.pphosted.com [148.163.155.12]) by smtp4.osuosl.org (Postfix) with ESMTPS id 85AFA408E6 for ; Tue, 12 Dec 2023 18:04:41 +0000 (UTC) DKIM-Filter: OpenDKIM Filter v2.11.0 smtp4.osuosl.org 85AFA408E6 Received: from pps.filterd (m0127842.ppops.net [127.0.0.1]) by mx0b-002c1b01.pphosted.com (8.17.1.24/8.17.1.24) with ESMTP id 3BCFvEVj014368; Tue, 12 Dec 2023 10:04:39 -0800 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=nutanix.com; h= from:to:cc:subject:date:message-id:content-transfer-encoding :content-type:mime-version; s=proofpoint20171006; bh=EQdph+jK8xb OGAUzC1sqbZND+aSITSa2zOVs4iyjHtY=; b=r0ea4gvmE0xoz0dX9+81I5h0hII +8F5Y3XMsXu9epPzM260PEMxM2dI2Bc5Q53Nhl+4wypiUDA7d0zYiNDiVb6hmu4b ntOktbgnSpbjP49Nhlr7OukG3DMmKwVBnBzLfJAs0ZHC+VEgtsS9EIu40oHn3faY svRUELyCqLDDPrToE2SWiI3s6fiYeIQK+OX4cSqxraw5Oy2nYqpVQkjfvox/t0ms Hune8d/ThNdy4GzoQAfxY80K6OY1akRjmhTsAFQMknRjhdNabQRuisVqhIdfEMA0 ulEvmw6J2f6mr4AR15wtwlKaV46YKgFGFoJ/nYriC3rKpJOtgfmBIQYg59w== Received: from nam12-mw2-obe.outbound.protection.outlook.com (mail-mw2nam12lp2040.outbound.protection.outlook.com [104.47.66.40]) by mx0b-002c1b01.pphosted.com (PPS) with ESMTPS id 3uvrege58d-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Tue, 12 Dec 2023 10:04:35 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=BienuqVAfB7cqkERFy/dcC+53Z+xqd2vAeejGiDvVmAbED0CGWlR+CdodA8eJaerU0cGwtZMuNzEXaF8FFX+Kv30obdE45oynBf0zSZxMX46/P38CnPV/pz4hI0H8TkWHaQr6Hh2eJXZpSjc6MMFZ41Mw3vqIGb6WfqwITyvlMRTxg2yp0fk9vYI66R4zeDwEfVeIxFTT7UggCVDvcxxfOjkl9HEVq3YEHzTY66aKjQQTtrsbmWqJH74ea8nlY0/9AATwZ26ijzUR0esU90FpZmX2MN43hY2ymOzcJBEk04rl8l+88172gr7818euQcUeV8nRpy2CQT9wasr6haFSA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector9901; 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=EQdph+jK8xbOGAUzC1sqbZND+aSITSa2zOVs4iyjHtY=; b=Ir7ul1HjHaMC3/r+UP9dObiy9R8AeEaQ51X+4st18HnE0+DdhHdhHaBIv1iJVLcGthu7sy87JQEpe+EkUGUpt4PUDU+61kqNxvdLieVaE0d4py3IL1PiVqnqmcTzBUvZfZ5fwYgYDadtw29dfepvsXbPLS+62rlAy9S0sm7s/wgEmxOpnpv+QWad8ps3xEYZQmNf+8zGoVzBlIf9C6H1Jvoq8q34PIiOVXipRd3hvkFNTPGKU1kn7aJOTY9NtU3RsUj1oXKA/xxDGk0nSqiP02s6OIkU/OF0TnOp5S6pi8JDz8KtGs7duiaVnRyWaUxTiUHS4/WSaG+SdlyzvtPN+Q== 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=EQdph+jK8xbOGAUzC1sqbZND+aSITSa2zOVs4iyjHtY=; b=s65nb0WwN42FFoRdRmfI+4rO816iRwS7W0p0XlmJGITnsR66ZgMcPxk8XpZUZRqn4QekRdVh/DDsbT1ivPy6Ua7Cf2K+l4b9y2nIuhLn33RWBL8gkciri8nHSPwdIK7n7AJ0XAUUnxaUm6IiDIjGzxR+vmRepZxMT4icg1aTrzsqRElBVUBA6dA7vfn5zuvLshoC53kpO1J/Amc/TAum8V+ZBxReIJWnUh6mPiEqnK2WofEd4AOpRC0WoCxyT8aFG2gEikOoVO2cNoxKUz65JxW7ZQKt1keyBrOSWJJ9H2JSEEjdt2WACpSPFnepOxuCjOeZGl0KWjc38gJ9o7fKDA== Received: from SJ0PR02MB7808.namprd02.prod.outlook.com (2603:10b6:a03:326::16) by PH0PR02MB8810.namprd02.prod.outlook.com (2603:10b6:510:f4::5) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.7068.32; Tue, 12 Dec 2023 18:04:30 +0000 Received: from SJ0PR02MB7808.namprd02.prod.outlook.com ([fe80::34e0:1ccb:5b26:8ce9]) by SJ0PR02MB7808.namprd02.prod.outlook.com ([fe80::34e0:1ccb:5b26:8ce9%7]) with mapi id 15.20.7068.033; Tue, 12 Dec 2023 18:04:26 +0000 From: Naveen Yerramneni To: dev@openvswitch.org Date: Tue, 12 Dec 2023 18:04:02 +0000 Message-Id: <20231212180402.155433-1-naveen.yerramneni@nutanix.com> X-Mailer: git-send-email 2.36.6 X-ClientProxiedBy: BY3PR05CA0038.namprd05.prod.outlook.com (2603:10b6:a03:39b::13) To SJ0PR02MB7808.namprd02.prod.outlook.com (2603:10b6:a03:326::16) MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: SJ0PR02MB7808:EE_|PH0PR02MB8810:EE_ X-MS-Office365-Filtering-Correlation-Id: 75b901d1-8e88-487d-540c-08dbfb3cc729 x-proofpoint-crosstenant: true X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0; X-Microsoft-Antispam-Message-Info: AbVowsj5+jQJst6HHDfl0xsnoSZ4vSltdAT8IHTdP9d9AMbUMzoJpHUcsFrlqFMIyMtLpOu014X96drZRI2IR8l+nncbI0sgbcOXfKGgsNqCBm+tgL98ZRYVc1NeO4LPbsE3VbjEMLbvGF4OKuOrtgHvfCKkpYu10N7fQ0GAiyc1UWlG9LLACBXbyPDsv2viREXPfIzQDz/7TxMtjs9mNM+3jOWELnEOJnslaC+pducgrMjm44wUhNICjQYGKEmXC5POOHqDTtPIky+T6C55HpBu4zXWrwYmRi4D8Q6xwmo6xy4vvmpQXZrF/sSyoUWD6QRsYelcV1LFEAyyBHCek1RvenQw75n5+5DTJvmohnAUp+bRXj+hAoip/2MI0e/nnggtsSIXyfBVN2blssHtwt3HUrKToCsYAbxToGqUZlN83XmXn8CIAC0cDZNtliyGOzsykgOq6ZYg2KjtYZjblBHjHgpTcxg3s+/omUvVZmUDiEguYAdzv5xMcJfnfsMQSIzv+dcK6jbsozFL+rKcI2W3zes/D8ri8LeshDEjhQnnafhIaOXmwZAmTF6TBtnOk+iNSWUAED+7rEZ2K3yJoLdKCs/VT8+z4wLZ3PT1QoHSHMQWZtQ/tV+IJkYPrwhyAt0fEN0lRfIpCmImw1Ea/2pNbvmiTHgsDkmfBDRcj5xNjt1UmmzaC4IuM7cWeeiM X-Forefront-Antispam-Report: CIP:255.255.255.255; CTRY:; LANG:en; SCL:1; SRV:; IPV:NLI; SFV:NSPM; H:SJ0PR02MB7808.namprd02.prod.outlook.com; PTR:; CAT:NONE; SFS:(13230031)(136003)(346002)(376002)(39860400002)(396003)(366004)(230273577357003)(230922051799003)(230173577357003)(186009)(451199024)(1800799012)(64100799003)(66899024)(41300700001)(66574015)(107886003)(2616005)(1076003)(26005)(83380400001)(38350700005)(36756003)(38100700002)(5660300002)(8676002)(316002)(8936002)(4326008)(2906002)(30864003)(6666004)(86362001)(6512007)(6506007)(44832011)(66476007)(54906003)(6916009)(66556008)(52116002)(66946007)(478600001)(6486002)(559001)(579004)(414714003); DIR:OUT; SFP:1102; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: lhz06nRDh7PCL+xEYxk5Agq9GzX6z9E/P4BH9veOFgDK/O/FO4agSfYSdVgA2zTsEUe4oTe0LPtMGffFC4bEQAkCfMKSieMQXoZS/gWEfdcSE8GsxVpTJHduVkA8P1hSW3iq6AJtNKlHaDtTJYLler5tzwXOrvRQPxLP+3WzsynivNouoQMo8/AMiUGg8NEhTnqVF9wX1j8znXzBSktWFlhaJI9zhS3c7xe3mQIUwqN0210sVaabFEV+KyELOWz/bCSCj2CCyqTwNHhiy1nCJmOkwbcqMk3m2aLSLNjPcCOXZVdF+Ref7jI/N2B06yyEyAMc+1kmI0YXFx8xTqWbv2272kcmqzi4PPgnMr/EuWvEPeHL7fFCHLlWSYT4FVu/9h44qoHrsXBcBzPLoyQIgVbBF5WkZzyRJYPflbT8qo46mEAfbIQYZkoDXK24t4LQskSWT9g3mBtakiA+BlE6DgHIZ5kVB+D2Jih9foQWnGK20ilvPg5Yjimjri5FqZT5+hikuiSt8/Nx3iTthHUeJaRVEcB0SboM/ZxWOCtGscpoVKLkknXyuaNLQBqTLFRPb+ZsHjc7efQhQ+thQBNz4wEngzQTb4yji5Kf4Q/IX/L5OpS/TImgYFjlkyhmuBtApRak2EfkFGKVOakgOkipw3PlFpXUXz9n1CnuSZK20NOn4acXX9hzuHjnUDmNgpqlYZdEO1Jp9haZkC4H1btHq04kqo2fahZXiYHRedutav+uHAHeGp8wMok0WUwWFyT501TLMReGJ1VJ8V5Rst18H4pcBl5o0vhwnc+uO8FdLnT6YQ2dL19L4vDQDOIPt+k9J/wALtwmx2cND7vWryaYRTOGPbGNjfT/7fygVOIblzc3l60SC/nKDgcfEdNU2mUFQMfiieBEfs7Db+384aJT7OSVnfx2t5nSW+XT2SK6h78KmmDTTiY75/ZKhehdX5x69ltA5yKof3UYu63chNBH2KMfaK7SmsCTAtDmVxS8p8X07NIHY5725uxplYqVrUskVVF1Gw3z4KUvbx77Wqb224qUX0wYoao/D8f14RX4NBhue9pfqQZsxGoqXmWML0UdEK7okTs0LUBJXUXTuqel9v444bjiI9cuR9sb3gNXoJ2dDURAIIKzbQhUlzvKVylknjsiL//KxQS2jHVO3aqKO6ZG06UjvNVBLcvLGdWgTy4WZ5CHcqnPoV4D3RGIk9eZ0s8mGcSOfQIzMn2hyKZpcfKMtcOMR8EQB5lWOMN/72DcvypcYthWzwfWHb7hAAZHp64yNOhA79hSDm5eYN4Mp0MZCKDAnHc/9ImOVhGWuzhB3KHb2PW1U6KP9FidSL3cx92NApKglUgrGn/CzFh3ir5JytDswDkrGOBeLLBsz+MTU0GCHkAa+3dOtTDFIasBpCWrnsqjiVdzqspJ/bVJ6cT1td4tKnoKspCWXLuN0rUcQdtn4BkaVmBLCFbRz2oYnEO7Afo/S83c1UQdd1yZUKvFUGtcTKEOMQBGwnaJhS2wk2DqcXffdnbT9Og+Lk5u/X+5jhE3SzKaJ2bnIzxDrrET7QRf5t9/nmOirXNPVc6AXWJmwyC7AkBRcEH9y7XVMc0WXwsshUV+2lMWi1oBtk4LNGoh2gwIjkpED2Vo47Y= X-OriginatorOrg: nutanix.com X-MS-Exchange-CrossTenant-Network-Message-Id: 75b901d1-8e88-487d-540c-08dbfb3cc729 X-MS-Exchange-CrossTenant-AuthSource: SJ0PR02MB7808.namprd02.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 12 Dec 2023 18:04:26.3078 (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: FI8IRdcKElXrrW9Bn84CtEYLvMm2yUrt7PyRYQx9MCb5bSnckbo8ykOQ8A6+8DjUu5EQEddgB00biAjkmaojkVDqcaqcWBZ0cxeBdYhuSpE= X-MS-Exchange-Transport-CrossTenantHeadersStamped: PH0PR02MB8810 X-Proofpoint-GUID: DPevhAUnoM1OMa1ooRzTnu6ox0gVGJTj X-Proofpoint-ORIG-GUID: DPevhAUnoM1OMa1ooRzTnu6ox0gVGJTj X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.272,Aquarius:18.0.997,Hydra:6.0.619,FMLib:17.11.176.26 definitions=2023-11-28_27,2023-11-27_01,2023-05-22_02 X-Proofpoint-Spam-Reason: safe Cc: Huzaifa Calcuttawala Subject: [ovs-dev] [PATCH OVN] DHCP Relay Agent support for overlay subnets X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" This patch contains changes to enable DHCP Relay Agent support for overlay subnets. USE CASE: ---------- - Enable IP address assignment for overlay subnets from the centralized DHCP server present in the underlay network. PREREQUISITES -------------- - Logical Router Port IP should be assigned (statically) from the same overlay subnet which is managed by DHCP server. - LRP IP is used for GIADRR field when relaying the DHCP packets and also same IP needs to be configured as default gateway for the overlay subnet. - Overlay subnets managed by external DHCP server are expected to be directly reachable from the underlay network. EXPECTED PACKET FLOW: ---------------------- Following is the expected packet flow inorder to support DHCP rleay functionality in OVN. 1. DHCP client originates DHCP discovery (broadcast). 2. DHCP relay (running on the OVN) receives the broadcast and forwards the packet to the DHCP server by converting it to unicast. While forwarding the packet, it updates the GIADDR in DHCP header to its interface IP on which DHCP packet is received. 3. DHCP server uses GIADDR field to decide the IP address pool from which IP has to be assigned and DHCP offer is sent to the same IP (GIADDR). 4. DHCP relay agent forwards the offer to the client, it resets the GIADDR field when forwarding the offer to the client. 5. DHCP client sends DHCP request (broadcast) packet. 6. DHCP relay (running on the OVN) receives the broadcast and forwards the packet to the DHCP server by converting it to unicast. While forwarding the packet, it updates the GIADDR in DHCP header to its interface IP on which DHCP packet is received. 7. DHCP Server sends the ACK packet. 8. DHCP relay agent forwards the ACK packet to the client, it resets the GIADDR field when forwarding the ACK to the client. 9. All the future renew/release packets are directly exchanged between DHCP client and DHCP server. OVN DHCP RELAY PACKET FLOW: ---------------------------- To add DHCP Relay support on OVN, we need to replicate all the behavior described above using distributed logical switch and logical router. At, highlevel packet flow is distributed among Logical Switch and Logical Router on source node (where VM is deployed) and redirect chassis(RC) node. 1. Request packet gets processed on the source node where VM is deployed and relays the packet to DHCP server. 2. Response packet is first processed on RC node (which first recieves the packet from underlay network). RC node forwards the packet to the right node by filling in the dest MAC and IP. OVN Packet flow with DHCP relay is explained below. 1. DHCP client (VM) sends the DHCP discover packet (broadcast). 2. Logical switch converts the packet to L2 unicast by setting the destination MAC to LRP's MAC 3. Logical Router receives the packet and redirects it to the OVN controller. 4. OVN controller updates the required information(GIADDR) in the DHCP payload after doing the required checks. If any check fails, packet is dropped. 5. Logical Router converts the packet to L3 unicast and forwards it to the server. This packets gets routed like any other packet (via RC node). 6. Server replies with DHCP offer. 7. RC node processes the DHCP offer and forwards it to the OVN controller. 8. OVN controller does sanity checks and updates the destination MAC (available in DHCP header), destination IP (available in DHCP header), resets GIADDR and reinjects the packet to datapath. If any check fails, packet is dropped. 9. Logical router updates the source IP and port and forwards the packet to logical switch. 10. Logical switch delivers the packet to the DHCP client. 11. Similar steps are performed for Request and Ack packets. 12. All the future renew/release packets are directly exchanged between DHCP client and DHCP server NEW OVN ACTIONS --------------- 1. dhcp_relay_req(, ) - This action executes on the source node on which the DHCP request originated. - This action relays the DHCP request coming from client to the server. Relay-ip is used to update GIADDR in the DHCP header. 2. dhcp_relay_resp_fwd(, ) - This action executes on the first node (RC node) which processes the DHCP response from the server. - This action updates the destination MAC and destination IP so that the response can be forwarded to the appropriate node from which request was originated. - Relay-ip, server-ip are used to validate GIADDR and SERVER ID in the DHCP payload. FLOWS ----- Following are the flows required for one overlay subnet. 1. table=27(ls_in_l2_lkup ), priority=100 , match=(inport == && eth.src == && ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && udp.src == 68 && udp.dst == 67), action=(eth.dst=;outport=;next;/* DHCP_RELAY_REQ */) 2. table=3 (lr_in_ip_input ), priority=110 , match=(inport == && ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && udp.src == 68 && udp.dst == 67), action=(dhcp_relay_req(,);ip4.src=;ip4.dst=;udp.src=67;next; /* DHCP_RELAY_REQ */) 3. table=3 (lr_in_ip_input ), priority=110 , match=(ip4.src == && ip4.dst == && udp.src == 67 && udp.dst == 67), action=(next;/* DHCP_RELAY_RESP */) 4. table=17(lr_in_dhcp_relay_resp_fwd), priority=110 , match=(ip4.src == && ip4.dst == && udp.src == 67 && udp.dst == 67), action=(dhcp_relay_resp_fwd(,);ip4.src=;udp.dst=68;outport=;output; /* DHCP_RELAY_RESP */) NEW PIPELINE STAGES ------------------- Following stage is added for DHCP relay feature. Some of the flows are fitted into the existing pipeline tages. 1. lr_in_dhcp_relay_resp_fwd - Forward teh DHCP response to the appropriate node NB SCHEMA CHANGES ---------------- 1. New DHCP_Relay table "DHCP_Relay": { "columns": { "name": {"type": "string"}, "servers": {"type": {"key": "string", "min": 0, "max": 1}}, "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, "isRoot": true}, 2. New column to Logical_Router_Port table "dhcp_relay": {"type": {"key": {"type": "uuid", "refTable": "DHCP_Relay", "refType": "weak"}, "min": 0, "max": 1}}, 3. New column to Logical_Switch_table "dhcp_relay_port": {"type": {"key": {"type": "uuid", "refTable": "Logical_Router_Port", "refType": "weak"}, "min": 0, "max": 1}}}, Commands to enable the feature: ------------------------------ - ovn-nbctl create DHCP_Relay servers= - ovn-nbctl set Logical_Router_port dhcp_relay= - ovn-nbctl set Logical_Switch dhcp_relay_port= Example: ------- ovn-nbctl ls-add sw1 ovn-nbctl lsp-add sw1 sw1-port1 ovn-nbctl lsp-set-addresses sw1-port1 #Only MAC address has to be specified when logical ports are created. ovn-nbctl lr-add lr1 ovn-nbctl lrp-add lr1 lr1-port1 #GATEWAY IP is set in GIADDR field when relaying the DHCP requests to server. ovn-nbctl lsp-add sw1 lr1-attachment ovn-nbctl lsp-set-type lr1-attachment router ovn-nbctl lsp-set-addresses lr1-attachment ovn-nbctl lsp-set-options lr1-attachment router-port=lr1-port1 ovn-nbctl create DHCP_Relay servers= ovn-nbctl set Logical_Router_port dhcp_relay= ovn-nbctl set Logical_Switch dhcp_relay_port= Limitations: ------------ - All OVN features that needs IP address to be configured on logical port (like proxy arp, etc) will not be supported for overlay subnets on which DHCP relay is enabled. References: ---------- - rfc1541, rfc1542, rfc2131 Signed-off-by: Naveen Yerramneni Co-authored-by: Huzaifa Calcuttawala Signed-off-by: Huzaifa Calcuttawala CC: Mary Manohar --- controller/pinctrl.c | 441 ++++++++++++++++++++++++++++++++++++++++++ include/ovn/actions.h | 26 +++ lib/actions.c | 117 +++++++++++ lib/ovn-l7.h | 1 + northd/northd.c | 177 ++++++++++++++++- ovn-nb.ovsschema | 25 ++- ovn-nb.xml | 28 +++ tests/atlocal.in | 3 + tests/ovn-northd.at | 41 +++- tests/ovn.at | 12 +- tests/system-ovn.at | 150 ++++++++++++++ utilities/ovn-trace.c | 28 +++ 12 files changed, 1032 insertions(+), 17 deletions(-) diff --git a/controller/pinctrl.c b/controller/pinctrl.c index 5a35d56f6..45240f01d 100644 --- a/controller/pinctrl.c +++ b/controller/pinctrl.c @@ -1897,6 +1897,437 @@ is_dhcp_flags_broadcast(ovs_be16 flags) return flags & htons(DHCP_BROADCAST_FLAG); } +static const char *dhcp_msg_str[] = { +[0] = "INVALID", +[DHCP_MSG_DISCOVER] = "DISCOVER", +[DHCP_MSG_OFFER] = "OFFER", +[DHCP_MSG_REQUEST] = "REQUEST", +[OVN_DHCP_MSG_DECLINE] = "DECLINE", +[DHCP_MSG_ACK] = "ACK", +[DHCP_MSG_NAK] = "NAK", +[OVN_DHCP_MSG_RELEASE] = "RELEASE", +[OVN_DHCP_MSG_INFORM] = "INFORM" +}; + +static bool +dhcp_relay_is_msg_type_supported(uint8_t msg_type) +{ + return (msg_type >= DHCP_MSG_DISCOVER && msg_type <= OVN_DHCP_MSG_RELEASE); +} + +static const char *dhcp_msg_str_get(uint8_t msg_type) +{ + if (!dhcp_relay_is_msg_type_supported(msg_type)) { + return "INVALID"; + } + return dhcp_msg_str[msg_type]; +} + +/* Called with in the pinctrl_handler thread context. */ +static void +pinctrl_handle_dhcp_relay_req( + struct rconn *swconn, + struct dp_packet *pkt_in, struct ofputil_packet_in *pin, + struct ofpbuf *userdata, + struct ofpbuf *continuation) +{ + enum ofp_version version = rconn_get_version(swconn); + enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version); + struct dp_packet *pkt_out_ptr = NULL; + + /* Parse relay IP and server IP. */ + ovs_be32 *relay_ip = ofpbuf_try_pull(userdata, sizeof *relay_ip); + ovs_be32 *server_ip = ofpbuf_try_pull(userdata, sizeof *server_ip); + if (!relay_ip || !server_ip) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "DHCP_RELAY_REQ: relay ip or server ip " + "not present in the userdata"); + return; + } + + /* Validate the DHCP request packet. + * Format of the DHCP packet is + * ------------------------------------------------------------------------ + *| UDP HEADER | DHCP HEADER | 4 Byte DHCP Cookie | DHCP OPTIONS(var len)| + * ------------------------------------------------------------------------ + */ + + size_t in_l4_size = dp_packet_l4_size(pkt_in); + const char *end = (char *) dp_packet_l4(pkt_in) + in_l4_size; + const char *in_dhcp_ptr = dp_packet_get_udp_payload(pkt_in); + if (!in_dhcp_ptr) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "DHCP_RELAY_REQ: invalid or incomplete " + "DHCP packet received"); + return; + } + + const struct dhcp_header *in_dhcp_data + = (const struct dhcp_header *) in_dhcp_ptr; + in_dhcp_ptr += sizeof *in_dhcp_data; + if (in_dhcp_ptr > end) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "DHCP_RELAY_REQ: invalid or incomplete " + "DHCP packet received, bad data length"); + return; + } + if (in_dhcp_data->op != DHCP_OP_REQUEST) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "DHCP_RELAY_REQ: invalid opcode in the " + "DHCP packet: %d", in_dhcp_data->op); + return; + } + + /* DHCP options follow the DHCP header. The first 4 bytes of the DHCP + * options is the DHCP magic cookie followed by the actual DHCP options. + */ + ovs_be32 magic_cookie = htonl(DHCP_MAGIC_COOKIE); + if (in_dhcp_ptr + sizeof magic_cookie > end || + get_unaligned_be32((const void *) in_dhcp_ptr) != magic_cookie) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "DHCP_RELAY_REQ: magic cookie not present " + "in the packet"); + return; + } + + if (in_dhcp_data->giaddr) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "DHCP_RELAY_REQ: giaddr is already set"); + return; + } + + if (in_dhcp_data->htype != 0x1) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "DHCP_RELAY_REQ: packet is recieved with " + "unsupported hardware type"); + return; + } + + ovs_be32 *server_id_ptr = NULL; + const uint8_t *in_dhcp_msg_type = NULL; + + in_dhcp_ptr += sizeof magic_cookie; + ovs_be32 request_ip = in_dhcp_data->ciaddr; + while (in_dhcp_ptr < end) { + const struct dhcp_opt_header *in_dhcp_opt = + (const struct dhcp_opt_header *) in_dhcp_ptr; + if (in_dhcp_opt->code == DHCP_OPT_END) { + break; + } + if (in_dhcp_opt->code == DHCP_OPT_PAD) { + in_dhcp_ptr += 1; + continue; + } + in_dhcp_ptr += sizeof *in_dhcp_opt; + if (in_dhcp_ptr > end) { + break; + } + in_dhcp_ptr += in_dhcp_opt->len; + if (in_dhcp_ptr > end) { + break; + } + + switch (in_dhcp_opt->code) { + case DHCP_OPT_MSG_TYPE: + if (in_dhcp_opt->len == 1) { + in_dhcp_msg_type = DHCP_OPT_PAYLOAD(in_dhcp_opt); + } + break; + case DHCP_OPT_REQ_IP: + if (in_dhcp_opt->len == 4) { + request_ip = get_unaligned_be32(DHCP_OPT_PAYLOAD(in_dhcp_opt)); + } + break; + /* Server Identifier */ + case OVN_DHCP_OPT_CODE_SERVER_ID: + if (in_dhcp_opt->len == 4) { + server_id_ptr = DHCP_OPT_PAYLOAD(in_dhcp_opt); + } + break; + default: + break; + } + } + + /* Check whether the DHCP Message Type (opt 53) is present or not */ + if (!in_dhcp_msg_type) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "DHCP_RELAY_REQ: missing message type"); + return; + } + + /* Relay the DHCP request packet */ + uint16_t new_l4_size = in_l4_size; + size_t new_packet_size = pkt_in->l4_ofs + new_l4_size; + + struct dp_packet pkt_out; + dp_packet_init(&pkt_out, new_packet_size); + dp_packet_clear(&pkt_out); + dp_packet_prealloc_tailroom(&pkt_out, new_packet_size); + pkt_out_ptr = &pkt_out; + + /* Copy the L2 and L3 headers from the pkt_in as they would remain same*/ + dp_packet_put( + &pkt_out, dp_packet_pull(pkt_in, pkt_in->l4_ofs), pkt_in->l4_ofs); + + pkt_out.l2_5_ofs = pkt_in->l2_5_ofs; + pkt_out.l2_pad_size = pkt_in->l2_pad_size; + pkt_out.l3_ofs = pkt_in->l3_ofs; + pkt_out.l4_ofs = pkt_in->l4_ofs; + + struct ip_header *out_ip = dp_packet_l3(&pkt_out); + + struct udp_header *udp = dp_packet_put( + &pkt_out, dp_packet_pull(pkt_in, UDP_HEADER_LEN), UDP_HEADER_LEN); + + struct dhcp_header *dhcp_data = dp_packet_put(&pkt_out, + dp_packet_pull(pkt_in, new_l4_size - UDP_HEADER_LEN), + new_l4_size - UDP_HEADER_LEN); + dhcp_data->giaddr = *relay_ip; + if (udp->udp_csum) { + udp->udp_csum = recalc_csum32(udp->udp_csum, + 0, dhcp_data->giaddr); + } + pin->packet = dp_packet_data(&pkt_out); + pin->packet_len = dp_packet_size(&pkt_out); + + /* Log the DHCP message. */ + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(20, 40); + const struct eth_header *l2 = dp_packet_eth(&pkt_out); + VLOG_INFO_RL(&rl, "DHCP_RELAY_REQ:: MSG_TYPE:%s MAC:"ETH_ADDR_FMT + " XID:%u" + " REQ_IP:"IP_FMT + " GIADDR:"IP_FMT + " SERVER_ADDR:"IP_FMT, + dhcp_msg_str_get(*in_dhcp_msg_type), + ETH_ADDR_BYTES_ARGS(dhcp_data->chaddr), ntohl(dhcp_data->xid), + IP_ARGS(request_ip), IP_ARGS(dhcp_data->giaddr), + IP_ARGS(*server_ip)); + queue_msg(swconn, ofputil_encode_resume(pin, continuation, proto)); + if (pkt_out_ptr) { + dp_packet_uninit(pkt_out_ptr); + } +} + +/* Called with in the pinctrl_handler thread context. */ +static void +pinctrl_handle_dhcp_relay_resp_fwd( + struct rconn *swconn, + struct dp_packet *pkt_in, struct ofputil_packet_in *pin, + struct ofpbuf *userdata, + struct ofpbuf *continuation) +{ + enum ofp_version version = rconn_get_version(swconn); + enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version); + struct dp_packet *pkt_out_ptr = NULL; + + /* Parse relay IP and server IP. */ + ovs_be32 *relay_ip = ofpbuf_try_pull(userdata, sizeof *relay_ip); + ovs_be32 *server_ip = ofpbuf_try_pull(userdata, sizeof *server_ip); + if (!relay_ip || !server_ip) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP: relay ip or server ip " + "not present in the userdata"); + return; + } + + /* Validate the DHCP request packet. + * Format of the DHCP packet is + * ------------------------------------------------------------------------ + *| UDP HEADER | DHCP HEADER | 4 Byte DHCP Cookie | DHCP OPTIONS(var len)| + * ------------------------------------------------------------------------ + */ + + size_t in_l4_size = dp_packet_l4_size(pkt_in); + const char *end = (char *) dp_packet_l4(pkt_in) + in_l4_size; + const char *in_dhcp_ptr = dp_packet_get_udp_payload(pkt_in); + if (!in_dhcp_ptr) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP_FWD: invalid or incomplete " + "packet received"); + return; + } + + const struct dhcp_header *in_dhcp_data + = (const struct dhcp_header *) in_dhcp_ptr; + in_dhcp_ptr += sizeof *in_dhcp_data; + if (in_dhcp_ptr > end) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP_FWD: invalid or incomplete " + "packet received, bad data length"); + return; + } + if (in_dhcp_data->op != DHCP_OP_REPLY) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP_FWD: invalid opcode " + "in the packet: %d", in_dhcp_data->op); + return; + } + + /* DHCP options follow the DHCP header. The first 4 bytes of the DHCP + * options is the DHCP magic cookie followed by the actual DHCP options. + */ + ovs_be32 magic_cookie = htonl(DHCP_MAGIC_COOKIE); + if (in_dhcp_ptr + sizeof magic_cookie > end || + get_unaligned_be32((const void *) in_dhcp_ptr) != magic_cookie) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP_FWD: magic cookie not present " + "in the packet"); + return; + } + + if (!in_dhcp_data->giaddr) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP_FWD: giaddr is " + "not set in request"); + return; + } + ovs_be32 giaddr = in_dhcp_data->giaddr; + + ovs_be32 *server_id_ptr = NULL; + ovs_be32 lease_time = 0; + const uint8_t *in_dhcp_msg_type = NULL; + + in_dhcp_ptr += sizeof magic_cookie; + while (in_dhcp_ptr < end) { + const struct dhcp_opt_header *in_dhcp_opt = + (const struct dhcp_opt_header *) in_dhcp_ptr; + if (in_dhcp_opt->code == DHCP_OPT_END) { + break; + } + if (in_dhcp_opt->code == DHCP_OPT_PAD) { + in_dhcp_ptr += 1; + continue; + } + in_dhcp_ptr += sizeof *in_dhcp_opt; + if (in_dhcp_ptr > end) { + break; + } + in_dhcp_ptr += in_dhcp_opt->len; + if (in_dhcp_ptr > end) { + break; + } + + switch (in_dhcp_opt->code) { + case DHCP_OPT_MSG_TYPE: + if (in_dhcp_opt->len == 1) { + in_dhcp_msg_type = DHCP_OPT_PAYLOAD(in_dhcp_opt); + } + break; + /* Server Identifier */ + case OVN_DHCP_OPT_CODE_SERVER_ID: + if (in_dhcp_opt->len == 4) { + server_id_ptr = DHCP_OPT_PAYLOAD(in_dhcp_opt); + } + break; + case OVN_DHCP_OPT_CODE_LEASE_TIME: + if (in_dhcp_opt->len == 4) { + lease_time = get_unaligned_be32(DHCP_OPT_PAYLOAD(in_dhcp_opt)); + } + break; + default: + break; + } + } + + /* Check whether the DHCP Message Type (opt 53) is present or not */ + if (!in_dhcp_msg_type) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP: missing message type"); + return; + } + + if (!server_id_ptr) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP: missing server identifier"); + return; + } + + if (*server_id_ptr != *server_ip) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP: server identifier mismatch"); + return; + } + + if (giaddr != *relay_ip) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP: giaddr mismatch"); + return; + } + + + /* Update destination MAC & IP so that the packet is forward to the + * right destination node. + */ + uint16_t new_l4_size = in_l4_size; + size_t new_packet_size = pkt_in->l4_ofs + new_l4_size; + + struct dp_packet pkt_out; + dp_packet_init(&pkt_out, new_packet_size); + dp_packet_clear(&pkt_out); + dp_packet_prealloc_tailroom(&pkt_out, new_packet_size); + pkt_out_ptr = &pkt_out; + + /* Copy the L2 and L3 headers from the pkt_in as they would remain same*/ + struct eth_header *eth = dp_packet_put( + &pkt_out, dp_packet_pull(pkt_in, pkt_in->l4_ofs), pkt_in->l4_ofs); + + pkt_out.l2_5_ofs = pkt_in->l2_5_ofs; + pkt_out.l2_pad_size = pkt_in->l2_pad_size; + pkt_out.l3_ofs = pkt_in->l3_ofs; + pkt_out.l4_ofs = pkt_in->l4_ofs; + + struct udp_header *udp = dp_packet_put( + &pkt_out, dp_packet_pull(pkt_in, UDP_HEADER_LEN), UDP_HEADER_LEN); + + struct dhcp_header *dhcp_data = dp_packet_put( + &pkt_out, dp_packet_pull(pkt_in, new_l4_size - UDP_HEADER_LEN), + new_l4_size - UDP_HEADER_LEN); + memcpy(ð->eth_dst, dhcp_data->chaddr, sizeof(eth->eth_dst)); + + /* Send a broadcast IP frame when BROADCAST flag is set. */ + struct ip_header *out_ip = dp_packet_l3(&pkt_out); + ovs_be32 ip_dst; + ovs_be32 ip_dst_orig = get_16aligned_be32(&out_ip->ip_dst); + if (!is_dhcp_flags_broadcast(dhcp_data->flags)) { + ip_dst = dhcp_data->yiaddr; + } else { + ip_dst = htonl(0xffffffff); + } + put_16aligned_be32(&out_ip->ip_dst, ip_dst); + out_ip->ip_csum = recalc_csum32(out_ip->ip_csum, + ip_dst_orig, ip_dst); + if (udp->udp_csum) { + udp->udp_csum = recalc_csum32(udp->udp_csum, + ip_dst_orig, ip_dst); + } + /* Reset giaddr */ + dhcp_data->giaddr = htonl(0x0); + if (udp->udp_csum) { + udp->udp_csum = recalc_csum32(udp->udp_csum, + giaddr, 0); + } + pin->packet = dp_packet_data(&pkt_out); + pin->packet_len = dp_packet_size(&pkt_out); + + /* Log the DHCP message. */ + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(20, 40); + const struct eth_header *l2 = dp_packet_eth(&pkt_out); + VLOG_INFO_RL(&rl, "DHCP_RELAY_RESP_FWD:: MSG_TYPE:%s MAC:"ETH_ADDR_FMT + " XID:%u" + " YIADDR:"IP_FMT + " GIADDR:"IP_FMT + " SERVER_ADDR:"IP_FMT, + dhcp_msg_str_get(*in_dhcp_msg_type), + ETH_ADDR_BYTES_ARGS(dhcp_data->chaddr), ntohl(dhcp_data->xid), + IP_ARGS(dhcp_data->yiaddr), + IP_ARGS(giaddr), IP_ARGS(*server_id_ptr)); + queue_msg(swconn, ofputil_encode_resume(pin, continuation, proto)); + if (pkt_out_ptr) { + dp_packet_uninit(pkt_out_ptr); + } +} + /* Called with in the pinctrl_handler thread context. */ static void pinctrl_handle_put_dhcp_opts( @@ -3203,6 +3634,16 @@ process_packet_in(struct rconn *swconn, const struct ofp_header *msg) ovs_mutex_unlock(&pinctrl_mutex); break; + case ACTION_OPCODE_DHCP_RELAY_REQ: + pinctrl_handle_dhcp_relay_req(swconn, &packet, &pin, + &userdata, &continuation); + break; + + case ACTION_OPCODE_DHCP_RELAY_RESP_FWD: + pinctrl_handle_dhcp_relay_resp_fwd(swconn, &packet, &pin, + &userdata, &continuation); + break; + case ACTION_OPCODE_PUT_DHCP_OPTS: pinctrl_handle_put_dhcp_opts(swconn, &packet, &pin, &headers, &userdata, &continuation); diff --git a/include/ovn/actions.h b/include/ovn/actions.h index 49cfe0624..47d41b90f 100644 --- a/include/ovn/actions.h +++ b/include/ovn/actions.h @@ -95,6 +95,8 @@ struct collector_set_ids; OVNACT(LOOKUP_ND_IP, ovnact_lookup_mac_bind_ip) \ OVNACT(PUT_DHCPV4_OPTS, ovnact_put_opts) \ OVNACT(PUT_DHCPV6_OPTS, ovnact_put_opts) \ + OVNACT(DHCPV4_RELAY_REQ, ovnact_dhcp_relay) \ + OVNACT(DHCPV4_RELAY_RESP_FWD, ovnact_dhcp_relay) \ OVNACT(SET_QUEUE, ovnact_set_queue) \ OVNACT(DNS_LOOKUP, ovnact_result) \ OVNACT(LOG, ovnact_log) \ @@ -387,6 +389,14 @@ struct ovnact_put_opts { size_t n_options; }; +/* OVNACT_DHCP_RELAY. */ +struct ovnact_dhcp_relay { + struct ovnact ovnact; + int family; + ovs_be32 relay_ipv4; + ovs_be32 server_ipv4; +}; + /* Valid arguments to SET_QUEUE action. * * QDISC_MIN_QUEUE_ID is the default queue, so user-defined queues should @@ -750,6 +760,22 @@ enum action_opcode { /* multicast group split buffer action. */ ACTION_OPCODE_MG_SPLIT_BUF, + + /* "dhcp_relay_req(relay_ip, server_ip)". + * + * Arguments follow the action_header, in this format: + * - The 32-bit DHCP relay IP. + * - The 32-bit DHCP server IP. + */ + ACTION_OPCODE_DHCP_RELAY_REQ, + + /* "dhcp_relay_resp_fwd(relay_ip, server_ip)". + * + * Arguments follow the action_header, in this format: + * - The 32-bit DHCP relay IP. + * - The 32-bit DHCP server IP. + */ + ACTION_OPCODE_DHCP_RELAY_RESP_FWD, }; /* Header. */ diff --git a/lib/actions.c b/lib/actions.c index a73fe1a1e..69df428c6 100644 --- a/lib/actions.c +++ b/lib/actions.c @@ -2629,6 +2629,118 @@ ovnact_controller_event_free(struct ovnact_controller_event *event) free_gen_options(event->options, event->n_options); } +static void +format_DHCPV4_RELAY_REQ(const struct ovnact_dhcp_relay *dhcp_relay, + struct ds *s) +{ + ds_put_format(s, "dhcp_relay_req("IP_FMT","IP_FMT");", + IP_ARGS(dhcp_relay->relay_ipv4), + IP_ARGS(dhcp_relay->server_ipv4)); +} + +static void +parse_dhcp_relay_req(struct action_context *ctx, + struct ovnact_dhcp_relay *dhcp_relay) +{ + /* Skip dhcp_relay_req( */ + lexer_force_match(ctx->lexer, LEX_T_LPAREN); + + /* Parse relay ip and server ip. */ + if (ctx->lexer->token.format == LEX_F_IPV4) { + dhcp_relay->family = AF_INET; + dhcp_relay->relay_ipv4 = ctx->lexer->token.value.ipv4; + lexer_get(ctx->lexer); + lexer_match(ctx->lexer, LEX_T_COMMA); + if (ctx->lexer->token.format == LEX_F_IPV4) { + dhcp_relay->family = AF_INET; + dhcp_relay->server_ipv4 = ctx->lexer->token.value.ipv4; + lexer_get(ctx->lexer); + } else { + lexer_syntax_error(ctx->lexer, "expecting IPv4 dhcp server ip"); + return; + } + } else { + lexer_syntax_error(ctx->lexer, "expecting IPv4 dhcp relay " + "and server ips"); + return; + } + lexer_force_match(ctx->lexer, LEX_T_RPAREN); +} + +static void +encode_DHCPV4_RELAY_REQ(const struct ovnact_dhcp_relay *dhcp_relay, + const struct ovnact_encode_params *ep, + struct ofpbuf *ofpacts) +{ + size_t oc_offset = encode_start_controller_op(ACTION_OPCODE_DHCP_RELAY_REQ, + true, ep->ctrl_meter_id, + ofpacts); + ofpbuf_put(ofpacts, &dhcp_relay->relay_ipv4, + sizeof(dhcp_relay->relay_ipv4)); + ofpbuf_put(ofpacts, &dhcp_relay->server_ipv4, + sizeof(dhcp_relay->server_ipv4)); + encode_finish_controller_op(oc_offset, ofpacts); +} + +static void +format_DHCPV4_RELAY_RESP_FWD(const struct ovnact_dhcp_relay *dhcp_relay, + struct ds *s) +{ + ds_put_format(s, "dhcp_relay_resp("IP_FMT","IP_FMT");", + IP_ARGS(dhcp_relay->relay_ipv4), + IP_ARGS(dhcp_relay->server_ipv4)); +} + +static void +parse_dhcp_relay_resp_fwd(struct action_context *ctx, + struct ovnact_dhcp_relay *dhcp_relay) +{ + /* Skip dhcp_relay_resp( */ + lexer_force_match(ctx->lexer, LEX_T_LPAREN); + + /* Parse relay ip and server ip. */ + if (ctx->lexer->token.format == LEX_F_IPV4) { + dhcp_relay->family = AF_INET; + dhcp_relay->relay_ipv4 = ctx->lexer->token.value.ipv4; + lexer_get(ctx->lexer); + lexer_match(ctx->lexer, LEX_T_COMMA); + if (ctx->lexer->token.format == LEX_F_IPV4) { + dhcp_relay->family = AF_INET; + dhcp_relay->server_ipv4 = ctx->lexer->token.value.ipv4; + lexer_get(ctx->lexer); + } else { + lexer_syntax_error(ctx->lexer, "expecting IPv4 dhcp server ip"); + return; + } + } else { + lexer_syntax_error(ctx->lexer, "expecting IPv4 dhcp relay and " + "server ips"); + return; + } + lexer_force_match(ctx->lexer, LEX_T_RPAREN); +} + +static void +encode_DHCPV4_RELAY_RESP_FWD(const struct ovnact_dhcp_relay *dhcp_relay, + const struct ovnact_encode_params *ep, + struct ofpbuf *ofpacts) +{ + size_t oc_offset = encode_start_controller_op( + ACTION_OPCODE_DHCP_RELAY_RESP_FWD, + true, ep->ctrl_meter_id, + ofpacts); + ofpbuf_put(ofpacts, &dhcp_relay->relay_ipv4, + sizeof(dhcp_relay->relay_ipv4)); + ofpbuf_put(ofpacts, &dhcp_relay->server_ipv4, + sizeof(dhcp_relay->server_ipv4)); + encode_finish_controller_op(oc_offset, ofpacts); +} + +static void ovnact_dhcp_relay_free( + struct ovnact_dhcp_relay *dhcp_relay OVS_UNUSED) +{ +} + static void parse_put_opts(struct action_context *ctx, const struct expr_field *dst, struct ovnact_put_opts *po, const struct hmap *gen_opts, @@ -5451,6 +5563,11 @@ parse_action(struct action_context *ctx) parse_sample(ctx); } else if (lexer_match_id(ctx->lexer, "mac_cache_use")) { ovnact_put_MAC_CACHE_USE(ctx->ovnacts); + } else if (lexer_match_id(ctx->lexer, "dhcp_relay_req")) { + parse_dhcp_relay_req(ctx, ovnact_put_DHCPV4_RELAY_REQ(ctx->ovnacts)); + } else if (lexer_match_id(ctx->lexer, "dhcp_relay_resp_fwd")) { + parse_dhcp_relay_resp_fwd(ctx, + ovnact_put_DHCPV4_RELAY_RESP_FWD(ctx->ovnacts)); } else { lexer_syntax_error(ctx->lexer, "expecting action"); } diff --git a/lib/ovn-l7.h b/lib/ovn-l7.h index ad514a922..e08581123 100644 --- a/lib/ovn-l7.h +++ b/lib/ovn-l7.h @@ -69,6 +69,7 @@ struct gen_opts_map { */ #define OVN_DHCP_OPT_CODE_NETMASK 1 #define OVN_DHCP_OPT_CODE_LEASE_TIME 51 +#define OVN_DHCP_OPT_CODE_SERVER_ID 54 #define OVN_DHCP_OPT_CODE_T1 58 #define OVN_DHCP_OPT_CODE_T2 59 diff --git a/northd/northd.c b/northd/northd.c index 07dffb15a..7ac831fae 100644 --- a/northd/northd.c +++ b/northd/northd.c @@ -181,11 +181,13 @@ enum ovn_stage { PIPELINE_STAGE(ROUTER, IN, IP_ROUTING_ECMP, 14, "lr_in_ip_routing_ecmp") \ PIPELINE_STAGE(ROUTER, IN, POLICY, 15, "lr_in_policy") \ PIPELINE_STAGE(ROUTER, IN, POLICY_ECMP, 16, "lr_in_policy_ecmp") \ - PIPELINE_STAGE(ROUTER, IN, ARP_RESOLVE, 17, "lr_in_arp_resolve") \ - PIPELINE_STAGE(ROUTER, IN, CHK_PKT_LEN, 18, "lr_in_chk_pkt_len") \ - PIPELINE_STAGE(ROUTER, IN, LARGER_PKTS, 19, "lr_in_larger_pkts") \ - PIPELINE_STAGE(ROUTER, IN, GW_REDIRECT, 20, "lr_in_gw_redirect") \ - PIPELINE_STAGE(ROUTER, IN, ARP_REQUEST, 21, "lr_in_arp_request") \ + PIPELINE_STAGE(ROUTER, IN, DHCP_RELAY_RESP_FWD, 17, \ + "lr_in_dhcp_relay_resp_fwd") \ + PIPELINE_STAGE(ROUTER, IN, ARP_RESOLVE, 18, "lr_in_arp_resolve") \ + PIPELINE_STAGE(ROUTER, IN, CHK_PKT_LEN, 19, "lr_in_chk_pkt_len") \ + PIPELINE_STAGE(ROUTER, IN, LARGER_PKTS, 20, "lr_in_larger_pkts") \ + PIPELINE_STAGE(ROUTER, IN, GW_REDIRECT, 21, "lr_in_gw_redirect") \ + PIPELINE_STAGE(ROUTER, IN, ARP_REQUEST, 22, "lr_in_arp_request") \ \ /* Logical router egress stages. */ \ PIPELINE_STAGE(ROUTER, OUT, CHECK_DNAT_LOCAL, 0, \ @@ -9610,6 +9612,80 @@ build_dhcpv6_options_flows(struct ovn_port *op, ds_destroy(&match); } +static void +build_lswitch_dhcp_relay_flows(struct ovn_port *op, + const struct hmap *lr_ports, + const struct hmap *lflows, + const struct shash *meter_groups OVS_UNUSED) +{ + if (op->nbrp || !op->nbsp) { + return; + } + /* consider only ports attached to VMs */ + if (strcmp(op->nbsp->type, "")) { + return; + } + + if (!op->od || !op->od->n_router_ports || + !op->od->nbs || !op->od->nbs->dhcp_relay_port) { + return; + } + + struct ds match = DS_EMPTY_INITIALIZER; + struct ds action = DS_EMPTY_INITIALIZER; + struct nbrec_logical_router_port *lrp = op->od->nbs->dhcp_relay_port; + struct ovn_port *rp = ovn_port_find(lr_ports, lrp->name); + + if (!rp || !rp->nbrp || !rp->nbrp->dhcp_relay) { + return; + } + + struct ovn_port *sp = NULL; + struct nbrec_dhcp_relay *dhcp_relay = rp->nbrp->dhcp_relay; + + for (int i = 0; i < op->od->n_router_ports; i++) { + struct ovn_port *sp_tmp = op->od->router_ports[i]; + if (sp_tmp->peer == rp) { + sp = sp_tmp; + break; + } + } + if (!sp) { + return; + } + + char *server_ip_str = NULL; + uint16_t port; + int addr_family; + struct in6_addr server_ip; + + if (!ip_address_and_port_from_lb_key(dhcp_relay->servers, &server_ip_str, + &server_ip, &port, &addr_family)) { + return; + } + + if (server_ip_str == NULL) { + return; + } + + ds_put_format( + &match, "inport == %s && eth.src == %s && " + "ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && " + "udp.src == 68 && udp.dst == 67", + op->json_key, op->lsp_addrs[0].ea_s); + ds_put_format(&action, + "eth.dst=%s;outport=%s;next;/* DHCP_RELAY_REQ */", + rp->lrp_networks.ea_s,sp->json_key); + ovn_lflow_add_with_hint__(lflows, op->od, + S_SWITCH_IN_L2_LKUP, 100, + ds_cstr(&match), + ds_cstr(&action), + op->key, + NULL, + &lrp->header_); + free(server_ip_str); +} + static void build_drop_arp_nd_flows_for_unbound_router_ports(struct ovn_port *op, const struct ovn_port *port, @@ -10181,6 +10257,13 @@ build_lswitch_dhcp_options_and_response(struct ovn_port *op, return; } + if (op->od && op->od->nbs + && op->od->nbs->dhcp_relay_port) { + /* Don't add the DHCP server flows if DHCP Relay is enabled on the + * logical switch. */ + return; + } + bool is_external = lsp_is_external(op->nbsp); if (is_external && (!op->od->n_localnet_ports || !op->nbsp->ha_chassis_group)) { @@ -14458,6 +14541,86 @@ build_dhcpv6_reply_flows_for_lrouter_port( } } +static void +build_dhcp_relay_flows_for_lrouter_port( + struct ovn_port *op, struct hmap *lflows, + struct ds *match) +{ + if (!op->nbrp || !op->nbrp->dhcp_relay) { + return; + } + struct nbrec_dhcp_relay *dhcp_relay = op->nbrp->dhcp_relay; + if (!dhcp_relay->servers) { + return; + } + + int addr_family; + /* currently not supporting custom port */ + uint16_t port; + char *server_ip_str = NULL; + struct in6_addr server_ip; + + if (!ip_address_and_port_from_lb_key(dhcp_relay->servers, &server_ip_str, + &server_ip, &port, &addr_family)) { + return; + } + + if (server_ip_str == NULL) { + return; + } + + struct ds dhcp_action = DS_EMPTY_INITIALIZER; + ds_clear(match); + ds_put_format( + match, "inport == %s && " + "ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && " + "udp.src == 68 && udp.dst == 67", + op->json_key); + ds_put_format(&dhcp_action, + "dhcp_relay_req(%s,%s);" + "ip4.src=%s;ip4.dst=%s;udp.src=67;next; /* DHCP_RELAY_REQ */", + op->lrp_networks.ipv4_addrs[0].addr_s, server_ip_str, + op->lrp_networks.ipv4_addrs[0].addr_s, server_ip_str); + + ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 110, + ds_cstr(match), ds_cstr(&dhcp_action), + &op->nbrp->header_); + + ds_clear(match); + ds_clear(&dhcp_action); + + ds_put_format( + match, "ip4.src == %s && ip4.dst == %s && " + "udp.src == 67 && udp.dst == 67", + server_ip_str, op->lrp_networks.ipv4_addrs[0].addr_s); + ds_put_format(&dhcp_action, "next;/* DHCP_RELAY_RESP */"); + ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 110, + ds_cstr(match), ds_cstr(&dhcp_action), + &op->nbrp->header_); + + ds_clear(match); + ds_clear(&dhcp_action); + + ds_put_format( + match, "ip4.src == %s && ip4.dst == %s && " + "udp.src == 67 && udp.dst == 67", + server_ip_str, op->lrp_networks.ipv4_addrs[0].addr_s); + ds_put_format(&dhcp_action, + "dhcp_relay_resp_fwd(%s,%s);ip4.src=%s;udp.dst=68;" + "outport=%s;output; /* DHCP_RELAY_RESP */", + op->lrp_networks.ipv4_addrs[0].addr_s, server_ip_str, + op->lrp_networks.ipv4_addrs[0].addr_s, op->json_key); + ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_DHCP_RELAY_RESP_FWD, + 110, + ds_cstr(match), ds_cstr(&dhcp_action), + &op->nbrp->header_); + + ds_clear(match); + ds_clear(&dhcp_action); + + free(server_ip_str); +} + static void build_ipv6_input_flows_for_lrouter_port( struct ovn_port *op, struct hmap *lflows, @@ -15673,6 +15836,8 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows, ovn_lflow_add(lflows, od, S_ROUTER_OUT_POST_SNAT, 0, "1", "next;"); ovn_lflow_add(lflows, od, S_ROUTER_OUT_EGR_LOOP, 0, "1", "next;"); ovn_lflow_add(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 0, "1", "next;"); + ovn_lflow_add(lflows, od, S_ROUTER_IN_DHCP_RELAY_RESP_FWD, 0, "1", + "next;"); const char *ct_flag_reg = features->ct_no_masked_label ? "ct_mark" @@ -16154,6 +16319,7 @@ build_lswitch_and_lrouter_iterate_by_lsp(struct ovn_port *op, build_lswitch_dhcp_options_and_response(op, lflows, meter_groups); build_lswitch_external_port(op, lflows); build_lswitch_ip_unicast_lookup(op, lflows, actions, match); + build_lswitch_dhcp_relay_flows(op, lr_ports, lflows, meter_groups); /* Build Logical Router Flows. */ build_ip_routing_flows_for_router_type_lsp(op, lr_ports, lflows); @@ -16183,6 +16349,7 @@ build_lswitch_and_lrouter_iterate_by_lrp(struct ovn_port *op, build_egress_delivery_flows_for_lrouter_port(op, lsi->lflows, &lsi->match, &lsi->actions); build_dhcpv6_reply_flows_for_lrouter_port(op, lsi->lflows, &lsi->match); + build_dhcp_relay_flows_for_lrouter_port(op, lsi->lflows, &lsi->match); build_ipv6_input_flows_for_lrouter_port(op, lsi->lflows, &lsi->match, &lsi->actions, lsi->meter_groups); diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema index b2e0993e0..6863d52cd 100644 --- a/ovn-nb.ovsschema +++ b/ovn-nb.ovsschema @@ -1,7 +1,7 @@ { "name": "OVN_Northbound", - "version": "7.2.0", - "cksum": "1069338687 34162", + "version": "7.3.0", + "cksum": "2325497400 35185", "tables": { "NB_Global": { "columns": { @@ -89,7 +89,12 @@ "type": {"key": {"type": "uuid", "refTable": "Forwarding_Group", "refType": "strong"}, - "min": 0, "max": "unlimited"}}}, + "min": 0, "max": "unlimited"}}, + "dhcp_relay_port": {"type": {"key": {"type": "uuid", + "refTable": "Logical_Router_Port", + "refType": "weak"}, + "min": 0, + "max": 1}}}, "isRoot": true}, "Logical_Switch_Port": { "columns": { @@ -436,6 +441,11 @@ "ipv6_prefix": {"type": {"key": "string", "min": 0, "max": "unlimited"}}, + "dhcp_relay": {"type": {"key": {"type": "uuid", + "refTable": "DHCP_Relay", + "refType": "weak"}, + "min": 0, + "max": 1}}, "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}, @@ -529,6 +539,15 @@ "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, "isRoot": true}, + "DHCP_Relay": { + "columns": { + "servers": {"type": {"key": "string", + "min": 0, + "max": 1}}, + "external_ids": { + "type": {"key": "string", "value": "string", + "min": 0, "max": "unlimited"}}}, + "isRoot": true}, "Connection": { "columns": { "target": {"type": "string"}, diff --git a/ovn-nb.xml b/ovn-nb.xml index fcb1c6ecc..dc20892e1 100644 --- a/ovn-nb.xml +++ b/ovn-nb.xml @@ -608,6 +608,11 @@ Please see the table. + + This column defines the on which + DHCP relay is enabled. + + Groups a set of logical port endpoints for traffic going out of the logical switch. @@ -2980,6 +2985,11 @@ or port has all ingress and egress traffic dropped. + + This column is used to enabled DHCP Relay. Please refer + to table. + +

Gateways, as documented under Gateways in the OVN @@ -4286,6 +4296,24 @@ or + +

+ OVN implements native DHCPv4 relay support which caters to the common + use case of relaying the DHCP requests to external DHCP server. +

+ + +

+ The DHCPv4 server IP address. +

+
+ + + See External IDs at the beginning of this document. + + +
+

Configuration for a database connection to an Open vSwitch database diff --git a/tests/atlocal.in b/tests/atlocal.in index 63d891b89..32d1c374e 100644 --- a/tests/atlocal.in +++ b/tests/atlocal.in @@ -187,6 +187,9 @@ fi # Set HAVE_DHCPD find_command dhcpd +# Set HAVE_DHCLIENT +find_command dhclient + # Set HAVE_BFDD_BEACON find_command bfdd-beacon diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at index 19e4f1263..4d8c9ff26 100644 --- a/tests/ovn-northd.at +++ b/tests/ovn-northd.at @@ -8786,9 +8786,9 @@ ovn-nbctl --wait=sb set logical_router_port R1-PUB options:redirect-type=bridged ovn-sbctl dump-flows R1 > R1flows AT_CAPTURE_FILE([R1flows]) -AT_CHECK([grep "lr_in_arp_resolve" R1flows | grep priority=90 | sort], [0], [dnl - table=17(lr_in_arp_resolve ), priority=90 , match=(outport == "R1-PUB" && ip4.src == 10.0.0.3 && is_chassis_resident("S0-P0")), action=(get_arp(outport, reg0); next;) - table=17(lr_in_arp_resolve ), priority=90 , match=(outport == "R1-PUB" && ip6.src == 1000::3 && is_chassis_resident("S0-P0")), action=(get_nd(outport, xxreg0); next;) +AT_CHECK([grep "lr_in_arp_resolve" R1flows | grep priority=90 | sed 's/table=../table=??/' | sort], [0], [dnl + table=??(lr_in_arp_resolve ), priority=90 , match=(outport == "R1-PUB" && ip4.src == 10.0.0.3 && is_chassis_resident("S0-P0")), action=(get_arp(outport, reg0); next;) + table=??(lr_in_arp_resolve ), priority=90 , match=(outport == "R1-PUB" && ip6.src == 1000::3 && is_chassis_resident("S0-P0")), action=(get_nd(outport, xxreg0); next;) ]) AT_CLEANUP @@ -10966,3 +10966,38 @@ Status: active AT_CLEANUP ]) + +OVN_FOR_EACH_NORTHD_NO_HV([ +AT_SETUP([check DHCP RELAY AGENT]) +ovn_start NORTHD_TYPE + +check ovn-nbctl ls-add ls0 +check ovn-nbctl lsp-add ls0 ls0-port1 +check ovn-nbctl lsp-set-addresses ls0-port1 02:00:00:00:00:10 +check ovn-nbctl lr-add lr0 +check ovn-nbctl lrp-add lr0 lrp1 02:00:00:00:00:01 192.168.1.1/24 +check ovn-nbctl lsp-add ls0 lrp1-attachment +check ovn-nbctl lsp-set-type lrp1-attachment router +check ovn-nbctl lsp-set-addresses lrp1-attachment 00:00:00:00:ff:02 +check ovn-nbctl lsp-set-options lrp1-attachment router-port=lrp1 +check ovn-nbctl lrp-add lr0 lrp-ext 02:00:00:00:00:02 192.168.2.1/24 + +dhcp_relay=$(ovn-nbctl create DHCP_Relay servers=172.16.1.1) +check ovn-nbctl set Logical_Router_port lrp1 dhcp_relay=$dhcp_relay +rp_uuid=$(ovn-nbctl --bare --colum=_uuid list logical_router_port lrp1) +check ovn-nbctl set Logical_Switch ls0 dhcp_relay_port=$rp_uuid + +check ovn-nbctl --wait=sb sync + +ovn-sbctl lflow-list > lflows +AT_CAPTURE_FILE([lflows]) + +AT_CHECK([grep -e "DHCP_RELAY_" lflows | sed 's/table=../table=??/'], [0], [dnl + table=??(lr_in_ip_input ), priority=110 , match=(inport == "lrp1" && ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && udp.src == 68 && udp.dst == 67), action=(dhcp_relay_req(192.168.1.1,172.16.1.1);ip4.src=192.168.1.1;ip4.dst=172.16.1.1;udp.src=67;next; /* DHCP_RELAY_REQ */) + table=??(lr_in_ip_input ), priority=110 , match=(ip4.src == 172.16.1.1 && ip4.dst == 192.168.1.1 && udp.src == 67 && udp.dst == 67), action=(next;/* DHCP_RELAY_RESP */) + table=??(lr_in_dhcp_relay_resp_fwd), priority=110 , match=(ip4.src == 172.16.1.1 && ip4.dst == 192.168.1.1 && udp.src == 67 && udp.dst == 67), action=(dhcp_relay_resp_fwd(192.168.1.1,172.16.1.1);ip4.src=192.168.1.1;udp.dst=68;outport="lrp1";output; /* DHCP_RELAY_RESP */) + table=??(ls_in_l2_lkup ), priority=100 , match=(inport == "ls0-port1" && eth.src == 02:00:00:00:00:10 && ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && udp.src == 68 && udp.dst == 67), action=(eth.dst=02:00:00:00:00:01;outport="lrp1-attachment";next;/* DHCP_RELAY_REQ */) +]) + +AT_CLEANUP +]) diff --git a/tests/ovn.at b/tests/ovn.at index e8c79512b..839c07ce2 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -21905,7 +21905,7 @@ eth_dst=00000000ff01 ip_src=$(ip_to_hex 10 0 0 10) ip_dst=$(ip_to_hex 172 168 0 101) send_icmp_packet 1 1 $eth_src $eth_dst $ip_src $ip_dst c4c9 0000000000000000000000 -AT_CHECK_UNQUOTED([as hv1 ovs-ofctl dump-flows br-int metadata=0x$lr0_dp_key | awk '/table=28, n_packets=1, n_bytes=45/{print $7" "$8}'],[0],[dnl +AT_CHECK_UNQUOTED([as hv1 ovs-ofctl dump-flows br-int metadata=0x$lr0_dp_key | awk '/table=29, n_packets=1, n_bytes=45/{print $7" "$8}'],[0],[dnl priority=80,ip,reg15=0x$lr0_public_dp_key,metadata=0x$lr0_dp_key,nw_src=10.0.0.10 actions=drop ]) @@ -28964,7 +28964,7 @@ AT_CHECK([ grep "priority=100" | \ grep -c "ct(commit,zone=NXM_NX_REG11\\[[0..15\\]],.*exec(move:NXM_OF_ETH_SRC\\[[\\]]->NXM_NX_CT_LABEL\\[[32..79\\]],load:0x[[0-9]]->NXM_NX_CT_MARK\\[[16..31\\]]))" - grep table=25 hv${hv}flows | \ + grep table=26 hv${hv}flows | \ grep "priority=200" | \ grep -c "move:NXM_NX_CT_LABEL\\[[\\]]->NXM_NX_XXREG1\\[[\\]],move:NXM_NX_XXREG1\\[[32..79\\]]->NXM_OF_ETH_DST" done; :], [0], [dnl @@ -29089,7 +29089,7 @@ AT_CHECK([ grep "priority=100" | \ grep -c "ct(commit,zone=NXM_NX_REG11\\[[0..15\\]],.*exec(move:NXM_OF_ETH_SRC\\[[\\]]->NXM_NX_CT_LABEL\\[[32..79\\]],load:0x[[0-9]]->NXM_NX_CT_MARK\\[[16..31\\]]))" - grep table=25 hv${hv}flows | \ + grep table=26 hv${hv}flows | \ grep "priority=200" | \ grep -c "move:NXM_NX_CT_LABEL\\[[\\]]->NXM_NX_XXREG1\\[[\\]],move:NXM_NX_XXREG1\\[[32..79\\]]->NXM_OF_ETH_DST" done; :], [0], [dnl @@ -29586,7 +29586,7 @@ if test X"$1" = X"DGP"; then else prio=2 fi -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=25, n_packets=1,.* priority=$prio,ip,$inport.*$outport.*metadata=0x${sw_key},nw_dst=10.0.1.1 actions=drop" -c], [0], [dnl +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=26, n_packets=1,.* priority=$prio,ip,$inport.*$outport.*metadata=0x${sw_key},nw_dst=10.0.1.1 actions=drop" -c], [0], [dnl 1 ]) @@ -29605,13 +29605,13 @@ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep "actions=controller" | grep if test X"$1" = X"DGP"; then # The packet dst should be resolved once for E/W centralized NAT purpose. - AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=25, n_packets=1,.* priority=100,reg0=0xa000101,reg15=.*metadata=0x${sw_key} actions=mod_dl_dst:00:00:00:00:01:01,resubmit" -c], [0], [dnl + AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=26, n_packets=1,.* priority=100,reg0=0xa000101,reg15=.*metadata=0x${sw_key} actions=mod_dl_dst:00:00:00:00:01:01,resubmit" -c], [0], [dnl 1 ]) fi # The packet should've been finally dropped in the lr_in_arp_resolve stage. -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=25, n_packets=2,.* priority=$prio,ip,$inport.*$outport.*metadata=0x${sw_key},nw_dst=10.0.1.1 actions=drop" -c], [0], [dnl +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=26, n_packets=2,.* priority=$prio,ip,$inport.*$outport.*metadata=0x${sw_key},nw_dst=10.0.1.1 actions=drop" -c], [0], [dnl 1 ]) OVN_CLEANUP([hv1]) diff --git a/tests/system-ovn.at b/tests/system-ovn.at index 7b9daba0d..591933a95 100644 --- a/tests/system-ovn.at +++ b/tests/system-ovn.at @@ -12032,3 +12032,153 @@ as OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d /connection dropped.*/d"]) AT_CLEANUP + +OVN_FOR_EACH_NORTHD([ +AT_SETUP([DHCP RELAY AGENT]) +AT_SKIP_IF([test $HAVE_DHCPD = no]) +AT_SKIP_IF([test $HAVE_DHCLIENT = no]) +AT_SKIP_IF([test $HAVE_TCPDUMP = no]) +ovn_start +OVS_TRAFFIC_VSWITCHD_START() + +ADD_BR([br-int]) +ADD_BR([br-ext]) + +ovs-ofctl add-flow br-ext action=normal +# Set external-ids in br-int needed for ovn-controller +ovs-vsctl \ + -- set Open_vSwitch . external-ids:system-id=hv1 \ + -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ + -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \ + -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ + -- set bridge br-int fail-mode=secure other-config:disable-in-band=true + +# Start ovn-controller +start_daemon ovn-controller + +ADD_NAMESPACES(sw01) +ADD_VETH(sw01, sw01, br-int, "0", "f0:00:00:01:02:03") +ADD_NAMESPACES(sw11) +ADD_VETH(sw11, sw11, br-int, "0", "f0:00:00:02:02:03") +ADD_NAMESPACES(server) +ADD_VETH(s1, server, br-ext, "172.16.1.1/24", "f0:00:00:01:02:05", \ + "172.16.1.254") + +check ovn-nbctl lr-add R1 + +check ovn-nbctl ls-add sw0 +check ovn-nbctl ls-add sw1 +check ovn-nbctl ls-add sw-ext + +check ovn-nbctl lrp-add R1 rp-sw0 00:00:01:01:02:03 192.168.1.1/24 +check ovn-nbctl lrp-add R1 rp-sw1 00:00:03:01:02:03 192.168.2.1/24 +check ovn-nbctl lrp-add R1 rp-ext 00:00:02:01:02:03 172.16.1.254/24 + +dhcp_relay=$(ovn-nbctl create DHCP_Relay servers=172.16.1.1) +check ovn-nbctl set Logical_Router_port rp-sw0 dhcp_relay=$dhcp_relay +check ovn-nbctl set Logical_Router_port rp-sw1 dhcp_relay=$dhcp_relay +check ovn-nbctl lrp-set-gateway-chassis rp-ext hv1 + +check ovn-nbctl lsp-add sw0 sw0-rp -- set Logical_Switch_Port sw0-rp \ + type=router options:router-port=rp-sw0 \ + -- lsp-set-addresses sw0-rp router +check ovn-nbctl lsp-add sw1 sw1-rp -- set Logical_Switch_Port sw1-rp \ + type=router options:router-port=rp-sw1 \ + -- lsp-set-addresses sw1-rp router + +rp_uuid=$(ovn-nbctl --bare --colum=_uuid list logical_router_port rp-sw0) +check ovn-nbctl set Logical_Switch sw0 dhcp_relay_port=$rp_uuid +rp_uuid=$(ovn-nbctl --bare --colum=_uuid list logical_router_port rp-sw1) +check ovn-nbctl set Logical_Switch sw1 dhcp_relay_port=$rp_uuid + +check ovn-nbctl lsp-add sw-ext ext-rp -- set Logical_Switch_Port ext-rp \ + type=router options:router-port=rp-ext \ + -- lsp-set-addresses ext-rp router +check ovn-nbctl lsp-add sw-ext lnet \ + -- lsp-set-addresses lnet unknown \ + -- lsp-set-type lnet localnet \ + -- lsp-set-options lnet network_name=phynet + +check ovn-nbctl lsp-add sw0 sw01 \ + -- lsp-set-addresses sw01 "f0:00:00:01:02:03" + +check ovn-nbctl lsp-add sw1 sw11 \ + -- lsp-set-addresses sw11 "f0:00:00:02:02:03" + +AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=phynet:br-ext]) + +OVN_POPULATE_ARP + +check ovn-nbctl --wait=hv sync + +DHCP_TEST_DIR="/tmp/dhcp-test" +rm -rf $DHCP_TEST_DIR +mkdir $DHCP_TEST_DIR +cat > $DHCP_TEST_DIR/dhcpd.conf < $DHCP_TEST_DIR/dhclien.conf < dhcpd.log 2>&1], [dhcpd.pid]) + +NS_CHECK_EXEC([server], [tcpdump -l -nvv -i s1 udp > pkt.pcap 2>tcpdump_err &]) +OVS_WAIT_UNTIL([grep "listening" tcpdump_err]) +on_exit 'kill $(pidof tcpdump)' + +NS_CHECK_EXEC([sw01], [dhclient -1 -q -lf $DHCP_TEST_DIR/dhclient-sw01.lease -pf $DHCP_TEST_DIR/dhclient-sw01.pid -cf $DHCP_TEST_DIR/dhclien.conf sw01]) +NS_CHECK_EXEC([sw11], [dhclient -1 -q -lf $DHCP_TEST_DIR/dhclient-sw11.lease -pf $DHCP_TEST_DIR/dhclient-sw11.pid -cf $DHCP_TEST_DIR/dhclien.conf sw11]) + +OVS_WAIT_UNTIL([ + total_pkts=$(cat pkt.pcap | wc -l) + test ${total_pkts} -ge 8 +]) + +on_exit 'kill `cat $DHCP_TEST_DIR/dhclient-sw01.pid` && +kill `cat $DHCP_TEST_DIR/dhclient-sw11.pid` && rm -rf $DHCP_TEST_DIR' + +NS_CHECK_EXEC([sw01], [ip addr show sw01 | grep -oP '(?<=inet\s)\d+(\.\d+){3}'], [0], [dnl +192.168.1.10 +]) +NS_CHECK_EXEC([sw11], [ip addr show sw11 | grep -oP '(?<=inet\s)\d+(\.\d+){3}'], [0], [dnl +192.168.2.10 +]) +OVS_APP_EXIT_AND_WAIT([ovn-controller]) + +as ovn-sb +OVS_APP_EXIT_AND_WAIT([ovsdb-server]) + +as ovn-nb +OVS_APP_EXIT_AND_WAIT([ovsdb-server]) + +as northd +OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE]) + +as +OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d +/failed to query port patch-.*/d +/.*terminating with signal 15.*/d"]) +AT_CLEANUP +]) diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c index 0b86eae7b..ae9dd77de 100644 --- a/utilities/ovn-trace.c +++ b/utilities/ovn-trace.c @@ -2328,6 +2328,25 @@ execute_put_dhcp_opts(const struct ovnact_put_opts *pdo, execute_put_opts(pdo, name, uflow, super); } +static void +execute_dhcpv4_relay_resp_fwd(const struct ovnact_dhcp_relay *dr, + const char *name, struct flow *uflow, + struct ovs_list *super) +{ + ovntrace_node_append( + super, OVNTRACE_NODE_ERROR, + "/* We assume that this packet is DHCPOFFER or DHCPACK and " + "DHCP broadcast flag is set. Dest IP is set to broadcast. " + "Dest MAC is set to broadcast but in real network this is unicast " + "which is extracted from DHCP header. */"); + + /* Assume DHCP broadcast flag is set */ + uflow->nw_dst = 0xFFFFFFFF; + /* Dest MAC is set to broadcast but in real network this is unicast */ + struct eth_addr bcast_mac = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + uflow->dl_dst = bcast_mac; +} + static void execute_put_nd_ra_opts(const struct ovnact_put_opts *pdo, const char *name, struct flow *uflow, @@ -3215,6 +3234,15 @@ trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len, "put_dhcpv6_opts", uflow, super); break; + case OVNACT_DHCPV4_RELAY_REQ: + /* Nothing to do for tracing. */ + break; + + case OVNACT_DHCPV4_RELAY_RESP_FWD: + execute_dhcpv4_relay_resp_fwd(ovnact_get_DHCPV4_RELAY_RESP_FWD(a), + "dhcp_relay_resp_fwd", uflow, super); + break; + case OVNACT_PUT_ND_RA_OPTS: execute_put_nd_ra_opts(ovnact_get_PUT_DHCPV6_OPTS(a), "put_nd_ra_opts", uflow, super);