From patchwork Thu Apr 28 13:51:41 2016
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
X-Patchwork-Submitter: Numan Siddique
X-Patchwork-Id: 616271
Return-Path:
X-Original-To: incoming@patchwork.ozlabs.org
Delivered-To: patchwork-incoming@bilbo.ozlabs.org
Received: from archives.nicira.com (archives.nicira.com [96.126.127.54])
by ozlabs.org (Postfix) with ESMTP id 3qwdY11xdJz9t7M
for ;
Thu, 28 Apr 2016 23:51:49 +1000 (AEST)
Received: from archives.nicira.com (localhost [127.0.0.1])
by archives.nicira.com (Postfix) with ESMTP id 9092710566;
Thu, 28 Apr 2016 06:51:48 -0700 (PDT)
X-Original-To: dev@openvswitch.org
Delivered-To: dev@openvswitch.org
Received: from mx3v3.cudamail.com (mx3.cudamail.com [64.34.241.5])
by archives.nicira.com (Postfix) with ESMTPS id 9A6E110535
for ; Thu, 28 Apr 2016 06:51:46 -0700 (PDT)
Received: from bar6.cudamail.com (localhost [127.0.0.1])
by mx3v3.cudamail.com (Postfix) with ESMTPS id 32EBE162AA5
for ; Thu, 28 Apr 2016 07:51:46 -0600 (MDT)
X-ASG-Debug-ID: 1461851505-0b32374eda3dd020001-byXFYA
Received: from mx3-pf2.cudamail.com ([192.168.14.1]) by bar6.cudamail.com
with
ESMTP id sPStS0zcoaPhhULR (version=TLSv1 cipher=DHE-RSA-AES256-SHA
bits=256 verify=NO) for ;
Thu, 28 Apr 2016 07:51:45 -0600 (MDT)
X-Barracuda-Envelope-From: nusiddiq@redhat.com
X-Barracuda-RBL-Trusted-Forwarder: 192.168.14.1
Received: from unknown (HELO mx1.redhat.com) (209.132.183.28)
by mx3-pf2.cudamail.com with ESMTPS (DHE-RSA-AES256-SHA encrypted);
28 Apr 2016 13:51:45 -0000
Received-SPF: pass (mx3-pf2.cudamail.com: SPF record at _spf1.redhat.com
designates 209.132.183.28 as permitted sender)
X-Barracuda-Apparent-Source-IP: 209.132.183.28
X-Barracuda-RBL-IP: 209.132.183.28
Received: from int-mx13.intmail.prod.int.phx2.redhat.com
(int-mx13.intmail.prod.int.phx2.redhat.com [10.5.11.26])
(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256
bits)) (No client certificate requested)
by mx1.redhat.com (Postfix) with ESMTPS id 2B64A7AE8E
for ; Thu, 28 Apr 2016 13:51:44 +0000 (UTC)
Received: from nusiddiq.blr.redhat.com (dhcp-0-108.blr.redhat.com
[10.70.1.108])
by int-mx13.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with
ESMTP id u3SDpg6A022438
for ; Thu, 28 Apr 2016 09:51:42 -0400
X-CudaMail-Envelope-Sender: nusiddiq@redhat.com
From: Numan Siddique
X-CudaMail-Whitelist-To: dev@openvswitch.org
X-CudaMail-MID: CM-V2-427014691
X-CudaMail-DTE: 042816
X-CudaMail-Originating-IP: 209.132.183.28
To: ovs dev
X-ASG-Orig-Subj: [##CM-V2-427014691##][PATCH v3 2/2] ovn: Add logical flows
to support native DHCP
In-Reply-To: <57221528.3060508@redhat.com>
Organization: Red Hat
Message-ID: <5722156D.7010602@redhat.com>
Date: Thu, 28 Apr 2016 19:21:41 +0530
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101
Thunderbird/38.6.0
MIME-Version: 1.0
X-Scanned-By: MIMEDefang 2.68 on 10.5.11.26
X-Barracuda-Connect: UNKNOWN[192.168.14.1]
X-Barracuda-Start-Time: 1461851505
X-Barracuda-Encrypted: DHE-RSA-AES256-SHA
X-Barracuda-URL: https://web.cudamail.com:443/cgi-mod/mark.cgi
X-ASG-Whitelist: Header =?UTF-8?B?eFwtY3VkYW1haWxcLXdoaXRlbGlzdFwtdG8=?=
X-Barracuda-BRTS-Status: 1
X-Virus-Scanned: by bsmtpd at cudamail.com
Subject: [ovs-dev] [PATCH v3 2/2] ovn: Add logical flows to support native
DHCP
X-BeenThere: dev@openvswitch.org
X-Mailman-Version: 2.1.16
Precedence: list
List-Id:
List-Unsubscribe: ,
List-Archive:
List-Post:
List-Help:
List-Subscribe: ,
Errors-To: dev-bounces@openvswitch.org
Sender: "dev"
OVN implements a native DHCP support which caters to the common
use case of providing an IP address to a booting instance by
providing stateless replies to DHCP requests based on statically
configured address mappings. To do this it allows a short list of
DHCP options to be configured and applied at each compute host
running ovn-controller.
A new table 'Subnet' is added in OVN NB DB to store the DHCP options.
A logical flow is added for each logical port to handle DHCP packets
using the 'dhcp_offer' action if the CMS has defined DHCP options
in the 'Subnet' column.
Signed-Off-by: Numan Siddique
Acked-by: Ryan Moats
Tested-by: Ryan Moats
---
ovn/northd/ovn-northd.8.xml | 79 ++++++++++--
ovn/northd/ovn-northd.c | 282 +++++++++++++++++++++++++++++++++++++++---
ovn/ovn-nb.ovsschema | 19 ++-
ovn/ovn-nb.xml | 185 ++++++++++++++++++++++++++-
ovn/utilities/ovn-nbctl.8.xml | 29 +++++
ovn/utilities/ovn-nbctl.c | 198 +++++++++++++++++++++++++++++
tests/ovn.at | 187 ++++++++++++++++++++++++++++
tests/test-ovn-dhcp.c | 154 +++++++++++++++++++++++
8 files changed, 1101 insertions(+), 32 deletions(-)
diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml
index 1cd8072..d98daf8 100644
--- a/ovn/northd/ovn-northd.8.xml
+++ b/ovn/northd/ovn-northd.8.xml
@@ -223,17 +223,57 @@
- Ingress Table 3: from-lport
Pre-ACLs
+ Ingress Table 3: DHCP
- Ingress table 3 prepares flows for possible stateful ACL processing
+ Ingress table 3 implements DHCP responder for the logical ports
+ configured with IPv4 address(es) and DHCP options.
+
+ -
+
+ A priority-100 logical flow is added for these logical ports
+ which match dhcp packets (udp.src = 68 and udp.dst = 67)
+ and replies with the DHCP REQUEST/ACK packet with the configured
+ DHCP options.
+
+
+
+dhcp_offer(DHCP OPTIONS)
+eth.dst = eth.src;
+eth.src = E;
+ip4.dst = O;
+ip4.src = S;
+udp.src = 67;
+udp.dst = 68;
+outport = P;
+inport = ""; /* Allow sending out inport. */
+output;
+
+
+
+ Where E is the server mac address and S is the
+ server IPv4 address defined in the DHCP options and O is
+ the IPv4 address defined in the logical port's addresses column.
+
+
+
+ -
+ A priority-0 flow that matches all packets to advances to table 4.
+
+
+
+
+ Ingress Table 4: from-lport
Pre-ACLs
+
+
+ Ingress table 4 prepares flows for possible stateful ACL processing
in table 4. It contains a priority-0 flow that simply moves
traffic to table 4. If stateful ACLs are used in the logical
datapath, a priority-100 flow is added that sends IP packets to
the connection tracker before advancing to table 4.
- Ingress table 4: from-lport
ACLs
+ Ingress table 5: from-lport
ACLs
Logical flows in this table closely reproduce those in the
@@ -249,7 +289,7 @@
- Ingress table 4 also contains a priority 0 flow with action
+ Ingress table 5 also contains a priority 0 flow with action
next;
, so that ACLs allow packets by default. If the
logical datapath has a statetful ACL, the following flows will
also be added:
@@ -281,7 +321,7 @@
-
Ingress Table 5: ARP responder
+ Ingress Table 6: ARP responder
This table implements ARP responder for known IPs. It contains these
@@ -326,7 +366,7 @@ output;
-
Ingress Table 6: Destination Lookup
+ Ingress Table 7: Destination Lookup
This table implements switching behavior. It contains these logical
@@ -357,20 +397,35 @@ output;
-
Egress Table 0: to-lport
Pre-ACLs
+ Egress Table 0: DHCP
+
+
+ -
+ This table adds a priority-100 flow to skip the ACL stages
+ for the DHCP REQUEST/ACK packets from the Ingress Table 3 DHCP
+ responder.
+
+
+ -
+ A priority-0 flow that matches all packets to advances to table 4.
+
+
+
+
+ Egress Table 1: to-lport
Pre-ACLs
- This is similar to ingress table 3 except for to-lport
+ This is similar to ingress table 4 except for to-lport
traffic.
- Egress Table 1: to-lport
ACLs
+ Egress Table 2: to-lport
ACLs
- This is similar to ingress table 4 except for to-lport
ACLs.
+ This is similar to ingress table 5 except for to-lport
ACLs.
- Egress Table 2: Egress Port Security - IP
+ Egress Table 3: Egress Port Security - IP
This is similar to the ingress port security logic in table 1 except
@@ -379,7 +434,7 @@ output;
eth.src
, ip4.src
and ip6.src
- Egress Table 3: Egress Port Security - L2
+ Egress Table 4: Egress Port Security - L2
This is similar to the ingress port security logic in ingress table 0,
diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
index d0319d5..39e2077 100644
--- a/ovn/northd/ovn-northd.c
+++ b/ovn/northd/ovn-northd.c
@@ -26,12 +26,14 @@
#include "hash.h"
#include "hmap.h"
#include "json.h"
+#include "ovn/lib/ovn-dhcp.h"
#include "ovn/lib/lex.h"
#include "ovn/lib/ovn-nb-idl.h"
#include "ovn/lib/ovn-sb-idl.h"
#include "packets.h"
#include "poll-loop.h"
#include "smap.h"
+#include "sset.h"
#include "stream.h"
#include "stream-ssl.h"
#include "unixctl.h"
@@ -90,16 +92,18 @@ enum ovn_stage {
PIPELINE_STAGE(SWITCH, IN, PORT_SEC_L2, 0, "ls_in_port_sec_l2") \
PIPELINE_STAGE(SWITCH, IN, PORT_SEC_IP, 1, "ls_in_port_sec_ip") \
PIPELINE_STAGE(SWITCH, IN, PORT_SEC_ND, 2, "ls_in_port_sec_nd") \
- PIPELINE_STAGE(SWITCH, IN, PRE_ACL, 3, "ls_in_pre_acl") \
- PIPELINE_STAGE(SWITCH, IN, ACL, 4, "ls_in_acl") \
- PIPELINE_STAGE(SWITCH, IN, ARP_RSP, 5, "ls_in_arp_rsp") \
- PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 6, "ls_in_l2_lkup") \
+ PIPELINE_STAGE(SWITCH, IN, DHCP, 3, "ls_in_dhcp") \
+ PIPELINE_STAGE(SWITCH, IN, PRE_ACL, 4, "ls_in_pre_acl") \
+ PIPELINE_STAGE(SWITCH, IN, ACL, 5, "ls_in_acl") \
+ PIPELINE_STAGE(SWITCH, IN, ARP_RSP, 6, "ls_in_arp_rsp") \
+ PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 7, "ls_in_l2_lkup") \
\
/* Logical switch egress stages. */ \
- PIPELINE_STAGE(SWITCH, OUT, PRE_ACL, 0, "ls_out_pre_acl") \
- PIPELINE_STAGE(SWITCH, OUT, ACL, 1, "ls_out_acl") \
- PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_IP, 2, "ls_out_port_sec_ip") \
- PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_L2, 3, "ls_out_port_sec_l2") \
+ PIPELINE_STAGE(SWITCH, OUT, DHCP, 0, "ls_out_dhcp") \
+ PIPELINE_STAGE(SWITCH, OUT, PRE_ACL, 1, "ls_out_pre_acl") \
+ PIPELINE_STAGE(SWITCH, OUT, ACL, 2, "ls_out_acl") \
+ PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_IP, 3, "ls_out_port_sec_ip") \
+ PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_L2, 4, "ls_out_port_sec_l2") \
\
/* Logical router ingress stages. */ \
PIPELINE_STAGE(ROUTER, IN, ADMISSION, 0, "lr_in_admission") \
@@ -1367,6 +1371,90 @@ has_stateful_acl(struct ovn_datapath *od)
return false;
}
+static bool
+build_dhcp_action(struct ovn_port *op, ovs_be32 offer_ip, struct ds *action)
+{
+ if(smap_get_bool(&op->nbs->options, "dhcp_disabled", false)) {
+ /* CMS has disabled native dhcp for this lport */
+ return false;
+ }
+
+ struct nbrec_subnet *subnet = NULL;
+ ovs_be32 host_ip, mask;
+ for (size_t i = 0; i < op->od->nbs->n_subnets; i++) {
+ char *error = ip_parse_masked(op->od->nbs->subnets[i]->cidr, &host_ip,
+ &mask);
+ if (!error && !((offer_ip ^ host_ip) & mask)) {
+ /* offerip belongs to this subnet */
+ subnet = op->od->nbs->subnets[i];
+ break;
+ }
+ free(error);
+ }
+
+ if (!subnet || !subnet->gateway_ip) {
+ return false;
+ }
+
+ struct smap_node *node;
+ char *server_ip= NULL;
+ char *server_mac = NULL;
+ uint8_t options_bmap = 0;
+
+ SMAP_FOR_EACH(node, &subnet->dhcp_options) {
+ if(!strcmp(node->key, "server_id")) {
+ options_bmap |= 0x1;
+ server_ip = node->value;
+ } else if (!strcmp(node->key, "server_mac")) {
+ options_bmap |= 0x2;
+ server_mac = node->value;
+ } else if (!strcmp(node->key, "lease_time")) {
+ options_bmap |= 0x4;
+ }
+ }
+
+ if ((options_bmap & 0x07) != 0x07) {
+ /* "server_id", "server_mac" and "lease_time" should be present
+ * in the dhcp_options. */
+ return false;
+ }
+
+ struct smap dhcp_options = SMAP_INITIALIZER(&dhcp_options);
+ smap_clone(&dhcp_options, &subnet->dhcp_options);
+
+ /* server_mac is not dhcp option, delete it from the smap */
+ smap_remove(&dhcp_options, "server_mac");
+ smap_add(&dhcp_options, "router", subnet->gateway_ip);
+ char *netmask = xasprintf(IP_FMT, IP_ARGS(mask));
+ smap_add(&dhcp_options, "netmask", netmask);
+ free(netmask);
+
+ /* override the dhcp options define in the lport options if any */
+ SMAP_FOR_EACH(node, &op->nbs->options) {
+ if(!strncmp(node->key, "dhcp_opt_", 9)) {
+ smap_replace(&dhcp_options, &node->key[9], node->value);
+ }
+ }
+
+ ds_put_format(action, "dhcp_offer(offerip = "IP_FMT", ",
+ IP_ARGS(offer_ip));
+ SMAP_FOR_EACH(node, &dhcp_options) {
+ ds_put_format(action, "%s = %s, ", node->key, node->value);
+ }
+
+ ds_chomp(action, ' ');
+ ds_chomp(action, ',');
+
+ ds_put_format(action, "); eth.dst = eth.src; eth.src = %s; "
+ "ip4.dst = "IP_FMT"; ip4.src = %s; udp.src = 67;"
+ "udp.dst = 68; ", server_mac, IP_ARGS(offer_ip), server_ip);
+ ds_put_cstr(action, "outport = inport; inport = \"\"; "
+ "/* Allow sending out inport. */ output;");
+
+ smap_destroy(&dhcp_options);
+ return true;
+}
+
static void
build_acls(struct ovn_datapath *od, struct hmap *lflows, struct hmap *ports)
{
@@ -1588,8 +1676,8 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
}
}
- /* Ingress table 1 and 2: Port security - IP and ND, by default goto next.
- * (priority 0)*/
+ /* Ingress table 1 and 2: Port security - IP and ND, tabl3 - DCHP
+ * by default goto next. (priority 0)*/
HMAP_FOR_EACH (od, key_node, datapaths) {
if (!od->nbs) {
continue;
@@ -1597,9 +1685,54 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_ND, 0, "1", "next;");
ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_IP, 0, "1", "next;");
+ ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP, 0, "1", "next;");
}
- /* Ingress table 3: ARP responder, skip requests coming from localnet ports.
+ /* Logical switch ingress table 3: DHCP priority 50. */
+ HMAP_FOR_EACH (op, key_node, ports) {
+ if (!op->nbs) {
+ continue;
+ }
+
+ if (!lport_is_enabled(op->nbs)) {
+ /* Drop packets from disabled logical ports (since logical flow
+ * tables are default-drop). */
+ continue;
+ }
+
+ for (size_t i = 0; i < op->nbs->n_addresses; i++) {
+ struct lport_addresses laddrs;
+ if (!extract_lport_addresses(op->nbs->addresses[i], &laddrs,
+ false)) {
+ continue;
+ }
+
+ if (!laddrs.n_ipv4_addrs) {
+ continue;
+ }
+
+ for (size_t j = 0; j < laddrs.n_ipv4_addrs; j++) {
+ struct ds action = DS_EMPTY_INITIALIZER;
+ if (build_dhcp_action(op, laddrs.ipv4_addrs[j].addr, &action)) {
+ struct ds match = DS_EMPTY_INITIALIZER;
+ ds_put_format(
+ &match, "inport == %s && eth.src == "ETH_ADDR_FMT
+ " && ip4.src == 0.0.0.0 && "
+ "ip4.dst == 255.255.255.255 && udp.src == 68 && "
+ "udp.dst == 67", op->json_key,
+ ETH_ADDR_ARGS(laddrs.ea));
+
+ ovn_lflow_add(lflows, op->od, S_SWITCH_IN_DHCP, 50,
+ ds_cstr(&match), ds_cstr(&action));
+ ds_destroy(&match);
+ ds_destroy(&action);
+ break;
+ }
+ }
+ }
+ }
+
+ /* Ingress table 6: ARP responder, skip requests coming from localnet ports.
* (priority 100). */
HMAP_FOR_EACH (op, key_node, ports) {
if (!op->nbs) {
@@ -1614,7 +1747,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
}
}
- /* Ingress table 5: ARP responder, reply for known IPs.
+ /* Ingress table 6: ARP responder, reply for known IPs.
* (priority 50). */
HMAP_FOR_EACH (op, key_node, ports) {
if (!op->nbs) {
@@ -1664,7 +1797,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
}
}
- /* Ingress table 5: ARP responder, by default goto next.
+ /* Ingress table 6: ARP responder, by default goto next.
* (priority 0)*/
HMAP_FOR_EACH (od, key_node, datapaths) {
if (!od->nbs) {
@@ -1674,7 +1807,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
ovn_lflow_add(lflows, od, S_SWITCH_IN_ARP_RSP, 0, "1", "next;");
}
- /* Ingress table 6: Destination lookup, broadcast and multicast handling
+ /* Ingress table 7: Destination lookup, broadcast and multicast handling
* (priority 100). */
HMAP_FOR_EACH (op, key_node, ports) {
if (!op->nbs) {
@@ -1694,7 +1827,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
"outport = \""MC_FLOOD"\"; output;");
}
- /* Ingress table 6: Destination lookup, unicast handling (priority 50), */
+ /* Ingress table 7: Destination lookup, unicast handling (priority 50), */
HMAP_FOR_EACH (op, key_node, ports) {
if (!op->nbs) {
continue;
@@ -1731,7 +1864,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
}
}
- /* Ingress table 6: Destination lookup for unknown MACs (priority 0). */
+ /* Ingress table 7: Destination lookup for unknown MACs (priority 0). */
HMAP_FOR_EACH (od, key_node, datapaths) {
if (!od->nbs) {
continue;
@@ -1743,6 +1876,47 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
}
}
+ /* Egress table 0: dhcp response - Priority 100 flow to skip ACL
+ * stages for dhcp response from ovn-controller.
+ * Priority 0 flow to advance to the next stage.
+ *
+ * Note: Once the bug reported here [1] is fixed, there is no need
+ * to skip ACL stages for dhcp response packets.
+ * [1] - http://openvswitch.org/pipermail/dev/2016-April/069542.html
+ */
+ HMAP_FOR_EACH (od, key_node, datapaths) {
+ if (!od->nbs) {
+ continue;
+ }
+
+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_DHCP, 0, "1", "next;");
+ if (!(od->nbs->n_ports && od->nbs->n_subnets)) {
+ continue;
+ }
+
+ for (size_t i = 0; i < od->nbs->n_subnets; i++) {
+ if (!(od->nbs->subnets[i]->enable_dhcp &&
+ od->nbs->subnets[i]->ip_version == 4)) {
+ continue;
+ }
+
+ const char *server_id = smap_get(
+ &od->nbs->subnets[i]->dhcp_options, "server_id");
+ const char *server_mac = smap_get(
+ &od->nbs->subnets[i]->dhcp_options, "server_mac");
+
+ if (server_id && server_mac) {
+ struct ds match = DS_EMPTY_INITIALIZER;
+ ds_put_format(&match, "eth.src == %s && ip4.src == %s &&"
+ " udp && udp.src == 67 && udp.dst == 68",
+ server_mac, server_id);
+ ovn_lflow_add(
+ lflows, od, S_SWITCH_OUT_DHCP, 100, ds_cstr(&match),
+ "/* Skip ACL stages for dhcp response */ next(3);");
+ }
+ }
+ }
+
/* Egress table 2: Egress port security - IP (priority 0)
* port security L2 - multicast/broadcast (priority
* 100). */
@@ -2309,6 +2483,74 @@ ovnsb_db_run(struct northd_context *ctx)
}
+static struct dhcp_opts_map supported_dhcp_opts[] = {
+ OFFERIP,
+ DHCP_OPT_NETMASK,
+ DHCP_OPT_ROUTER,
+ DHCP_OPT_DNS_SERVER,
+ DHCP_OPT_LOG_SERVER,
+ DHCP_OPT_LPR_SERVER,
+ DHCP_OPT_SWAP_SERVER,
+ DHCP_OPT_POLICY_FILTER,
+ DHCP_OPT_ROUTER_SOLICITATION,
+ DHCP_OPT_NIS_SERVER,
+ DHCP_OPT_NTP_SERVER,
+ DHCP_OPT_SERVER_ID,
+ DHCP_OPT_TFTP_SERVER,
+ DHCP_OPT_CLASSLESS_STATIC_ROUTE,
+ DHCP_OPT_IP_FORWARD_ENABLE,
+ DHCP_OPT_ROUTER_DISCOVERY,
+ DHCP_OPT_ETHERNET_ENCAP,
+ DHCP_OPT_DEFAULT_TTL,
+ DHCP_OPT_TCP_TTL,
+ DHCP_OPT_MTU,
+ DHCP_OPT_LEASE_TIME
+};
+
+static void
+check_and_add_supported_dhcp_opts_to_sb_db(struct northd_context *ctx)
+{
+ static bool nothing_to_add = false;
+
+ if (nothing_to_add) {
+ return;
+ }
+
+ struct hmap dhcp_opts_to_add = HMAP_INITIALIZER(&dhcp_opts_to_add);
+ for (size_t i = 0; (i < sizeof(supported_dhcp_opts) /
+ sizeof(supported_dhcp_opts[0])); i++) {
+ hmap_insert(&dhcp_opts_to_add, &supported_dhcp_opts[i].hmap_node,
+ dhcp_opt_hash(supported_dhcp_opts[i].name));
+ }
+
+ const struct sbrec_dhcp_options *opt_row, *opt_row_next;
+ SBREC_DHCP_OPTIONS_FOR_EACH_SAFE(opt_row, opt_row_next, ctx->ovnsb_idl) {
+ struct dhcp_opts_map *dhcp_opt =
+ dhcp_opts_find(&dhcp_opts_to_add, opt_row->name);
+ if (dhcp_opt) {
+ hmap_remove(&dhcp_opts_to_add, &dhcp_opt->hmap_node);
+ }
+ else {
+ sbrec_dhcp_options_delete(opt_row);
+ }
+ }
+
+ if (!dhcp_opts_to_add.n) {
+ nothing_to_add = true;
+ }
+
+ struct dhcp_opts_map *opt;
+ HMAP_FOR_EACH_POP(opt, hmap_node, &dhcp_opts_to_add) {
+ struct sbrec_dhcp_options *sbrec_dhcp_option =
+ sbrec_dhcp_options_insert(ctx->ovnsb_txn);
+ sbrec_dhcp_options_set_name(sbrec_dhcp_option, opt->name);
+ sbrec_dhcp_options_set_code(sbrec_dhcp_option, opt->code);
+ sbrec_dhcp_options_set_type(sbrec_dhcp_option, opt->type);
+ }
+
+ hmap_destroy(&dhcp_opts_to_add);
+}
+
static char *default_nb_db_;
static const char *
@@ -2477,6 +2719,10 @@ main(int argc, char *argv[])
add_column_noalert(ovnsb_idl_loop.idl, &sbrec_port_binding_col_options);
add_column_noalert(ovnsb_idl_loop.idl, &sbrec_port_binding_col_mac);
ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_port_binding_col_chassis);
+ ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_dhcp_options);
+ add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dhcp_options_col_code);
+ add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dhcp_options_col_type);
+ add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dhcp_options_col_name);
/* Main loop. */
exiting = false;
@@ -2490,7 +2736,9 @@ main(int argc, char *argv[])
ovnnb_db_run(&ctx);
ovnsb_db_run(&ctx);
-
+ if (ctx.ovnsb_txn) {
+ check_and_add_supported_dhcp_opts_to_sb_db(&ctx);
+ }
unixctl_server_run(unixctl);
unixctl_server_wait(unixctl);
if (exiting) {
diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema
index e3e41e3..693a182 100644
--- a/ovn/ovn-nb.ovsschema
+++ b/ovn/ovn-nb.ovsschema
@@ -1,7 +1,7 @@
{
"name": "OVN_Northbound",
- "version": "2.1.0",
- "cksum": "2201582413 4513",
+ "version": "2.2.0",
+ "cksum": "904993916 5326",
"tables": {
"Logical_Switch": {
"columns": {
@@ -16,6 +16,11 @@
"refType": "strong"},
"min": 0,
"max": "unlimited"}},
+ "subnets": {"type": {"key": {"type": "uuid",
+ "refTable": "Subnet",
+ "refType": "strong"},
+ "min": 0,
+ "max": "unlimited"}},
"external_ids": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}}},
@@ -63,6 +68,16 @@
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}}},
"isRoot": false},
+ "Subnet": {
+ "columns": {
+ "cidr": {"type": "string"},
+ "ip_version": {"type": {"key": {"type": "integer",
+ "enum": ["set", [4, 6]]}}},
+ "gateway_ip": {"type": "string"},
+ "enable_dhcp": {"type": "boolean"},
+ "dhcp_options": {"type": {"key": "string", "value": "string",
+ "min": 0, "max": "unlimited"}}},
+ "isRoot": false},
"Logical_Router": {
"columns": {
"name": {"type": "string"},
diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml
index 843ae4c..3c22e69 100644
--- a/ovn/ovn-nb.xml
+++ b/ovn/ovn-nb.xml
@@ -73,11 +73,18 @@
Access control rules that apply to packets within the logical switch.
+
+
+ Subnets configured to the logical switch.
+
+
+
See External IDs at the beginning of this document.
+
@@ -212,6 +219,28 @@
interface, in kb.
+
+
+
+ These options apply to logical ports with having
+ (empty string)
+
+
+
+ Each logical port can override the DHCP options defined in the
+ of
+ by defining them in this column with the prefix "dhcp_opt_".
+ Please see the of
+ for supported DHCP options.
+
+ Example: key="dhcp_opt_mtu", value="1300"
+
+
+
+ If this is defined, ovn-northd
will disable the native
+ DHCP responder for the logical port.
+
+
@@ -605,6 +634,160 @@
+
+
+
+ cidr of the subnet.
+
+
+
+
+
+ IP version of the subnet -4 or 6.
+
+
+
+
+
+ Gateway ip of the subnet.
+
+
+
+
+
+ If set to true, native dhcp support will be enabled for all the
+ logical ports having the IPv4 address from the subnet cidr.
+
+
+
+
+
+
+ OVN implements a native DHCP support which caters to the common
+ use case of providing an IP address to a booting instance by
+ providing stateless replies to DHCP requests based on statically
+ configured address mappings. To do this it allows a short list of
+ DHCP options to be configured and applied at each compute host
+ running ovn-controller.
+
+
+
+ CMS should define the set of DHCP options as key/value pairs.
+ The defined DHCP options will be include in the DHCP response to the
+ DHCP DISCOVER/REQUEST packet from the logical ports having the IPv4
+ addresses in the .
+
+
+
+
+
+ Below are the supported DHCP options whose values are IPv4 address
+ or addresses. If the value has more than one IPv4 address, then it
+ should be enclosed within '{}' braces.
+
+
+
+
+ Example. key="netmask", value="255.255.255.0"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This option can contain one or more static routes, each of which
+ consists of a destination descriptor and the IP address of the
+ router that should be used to reach that destination. Please see
+ RFC 3442 for more details.
+
+
+
+ Example.
+ key="classless_static_route"
+ value="{30.0.0.0/24,10.0.0.10, 0.0.0.0/0,10.0.0.1}"
+
+
+
+
+
+ eth.src
will be set to this value in the DHCP
+ response packet.
+
+
+
+
+
+
+
+ The value of this DHCP option is of type bool
.
+
+ Example. key="ip_forward_enable", value="1"
+
+
+
+
+
+ The value of this DHCP option is of type bool
.
+
+
+
+
+
+ The value of this DHCP option is of type bool
.
+
+
+
+
+
+ The value of this DHCP option is of type uint8
.
+
+ Example. key="default_ttl", value="128".
+
+
+
+
+
+ The value of this DHCP option is of type uint8
.
+
+
+
+
+
+ The value of this DHCP option is of type uint16
.
+
+
+
+
+
+ The value of this DHCP option is of type uint32
.
+
+ Example. key="lease_time", value="42000"
+
+
+
+
+
+
+ DHCP options "server_id"
, "server_mac"
+ and "lease_time"
are mandatory options which CMS should
+ define for OVN
to support native DHCP.
+
+
+
+
+
Each row represents one L3 logical router.
@@ -633,7 +816,7 @@
column is set to false
, the router is disabled. A disabled
router has all ingress and egress traffic dropped.
-
+
See External IDs at the beginning of this document.
diff --git a/ovn/utilities/ovn-nbctl.8.xml b/ovn/utilities/ovn-nbctl.8.xml
index 3b337dd..adf8813 100644
--- a/ovn/utilities/ovn-nbctl.8.xml
+++ b/ovn/utilities/ovn-nbctl.8.xml
@@ -40,6 +40,35 @@
+ Subnet Commands
+
+
+ subnet-add
lswitch cidr gateway_ip [enable_dhcp]
+ -
+ Adds a Subnet to the lswitch
+
+
+ subnet-del
subnet
+ -
+ Deletes the subnet referred by subnet UUID.
+
+
+ subnet-list
lswitch
+ -
+ Lists all the subnets belonging to the lswitch
+
+
+ subnet-set-dhcp-options
subnet [key=value]...
+ -
+ Sets the DHCP options for the subnet
+
+
+ subnet-get-dhcp-options
subnet
+ -
+ Lists the DHCP options of the subnet
+
+
+
ACL Commands
- [
--log
] acl-add
lswitch direction priority match action
diff --git a/ovn/utilities/ovn-nbctl.c b/ovn/utilities/ovn-nbctl.c
index bdad368..9726c7d 100644
--- a/ovn/utilities/ovn-nbctl.c
+++ b/ovn/utilities/ovn-nbctl.c
@@ -330,6 +330,18 @@ Logical port commands:\n\
Set options related to the type of LPORT\n\
lport-get-options LPORT Get the type specific options for LPORT\n\
\n\
+Subnet commands:\n\
+ subnet-add LSWITCH CIDR GATEWAYIP [ENABLE_DHCP]\n\
+ add a subnet to LSWITCH\n\
+ subnet-del SUBNET_UUID\n\
+ remove subnet from its attached LSWITCH\n\
+ subnet-list LSWITCH\n\
+ list subnets attached to LSWITCH\n\
+ subnet-set-dhcp-options SUBNET_UUID KEY=VALUE [KEY=VALUE]...\n\
+ Set DHCP options for the SUBNET\n\
+ subnet-get-dhcp-options SUBNET_UUID\n\
+ Get the DHCP options for the SUBNET\n\
+\n\
%s\
\n\
Options:\n\
@@ -478,6 +490,7 @@ nbctl_lswitch_list(struct ctl_context *ctx)
smap_destroy(&lswitches);
free(nodes);
}
+
static const struct nbrec_logical_port *
lport_by_name_or_uuid(struct ctl_context *ctx, const char *id)
@@ -878,6 +891,177 @@ nbctl_lport_get_options(struct ctl_context *ctx)
}
}
+static void
+nbctl_subnet_add(struct ctl_context *ctx)
+{
+ const struct nbrec_logical_switch *lswitch;
+ lswitch = lswitch_by_name_or_uuid(ctx, ctx->argv[1]);
+ if (!lswitch) {
+ return;
+ }
+
+ /* Validate the cidr */
+ int64_t ip_version = 4;
+ ovs_be32 ip, plen;
+ char *error = ip_parse_cidr(ctx->argv[2], &ip, &plen);
+ if (error){
+ /* check if its IPv6 cidr */
+ free(error);
+ struct in6_addr ipv6;
+ error = ipv6_parse_cidr(ctx->argv[2], &ipv6, &plen);
+ if (error) {
+ free(error);
+ VLOG_WARN("Invalid cidr format '%s'", ctx->argv[2]);
+ return;
+ }
+ ip_version = 6;
+ }
+
+ /* Validate the gateway ip format */
+ bool valid_gatewap_ip = false;
+ if (ip_version == 4) {
+ valid_gatewap_ip = ip_parse(ctx->argv[3], &ip);
+ }
+ else {
+ struct in6_addr ipv6;
+ valid_gatewap_ip = ipv6_parse(ctx->argv[3], &ipv6);
+ }
+
+ if (!valid_gatewap_ip) {
+ VLOG_WARN("Invalid Gateway ip format '%s'", ctx->argv[3]);
+ return;
+ }
+
+ bool enable_dhcp = true;
+ if (ctx->argc == 5) {
+ if (!strcmp(ctx->argv[4], "false")) {
+ enable_dhcp = false;
+ }
+ }
+
+ struct nbrec_subnet *subnet = nbrec_subnet_insert(ctx->txn);
+ nbrec_subnet_set_cidr(subnet, ctx->argv[2]);
+ nbrec_subnet_set_ip_version(subnet, ip_version);
+ nbrec_subnet_set_gateway_ip(subnet, ctx->argv[3]);
+ nbrec_subnet_set_enable_dhcp(subnet, enable_dhcp);
+
+ /* Insert the subnet into the logical switch */
+ nbrec_logical_switch_verify_subnets(lswitch);
+ struct nbrec_subnet **new_subnets = xmalloc(sizeof *new_subnets *
+ (lswitch->n_subnets + 1));
+ memcpy(new_subnets, lswitch->subnets,
+ sizeof *new_subnets * lswitch->n_subnets);
+ new_subnets[lswitch->n_subnets] = subnet;
+ nbrec_logical_switch_set_subnets(lswitch, new_subnets,
+ lswitch->n_subnets + 1);
+ free(new_subnets);
+}
+
+static void
+remove_subnet(const struct nbrec_logical_switch *lswitch, size_t idx)
+{
+ const struct nbrec_subnet *subnet = lswitch->subnets[idx];
+
+ struct nbrec_subnet **new_subnets
+ = xmemdup(lswitch->subnets, sizeof *new_subnets * lswitch->n_subnets);
+ new_subnets[idx] = new_subnets[lswitch->n_subnets - 1];
+ nbrec_logical_switch_verify_subnets(lswitch);
+ nbrec_logical_switch_set_subnets(lswitch, new_subnets, lswitch->n_subnets - 1);
+ free(new_subnets);
+
+ nbrec_subnet_delete(subnet);
+}
+
+static const struct nbrec_subnet *
+subnet_get(struct ctl_context *ctx, char *id)
+{
+ struct uuid subnet_uuid;
+ if (uuid_from_string(&subnet_uuid, id)) {
+ return nbrec_subnet_get_for_uuid(ctx->idl, &subnet_uuid);
+ }
+
+ return NULL;
+}
+
+static void
+nbctl_subnet_del(struct ctl_context *ctx)
+{
+ const struct nbrec_subnet *subnet = subnet_get(ctx, ctx->argv[1]);
+ if (!subnet) {
+ VLOG_WARN("subnet not found for '%s'", ctx->argv[1]);
+ return;
+ }
+
+ /* Find the switch that contains 'subnet', then delete it. */
+ const struct nbrec_logical_switch *lswitch;
+ NBREC_LOGICAL_SWITCH_FOR_EACH (lswitch, ctx->idl) {
+ for (size_t i = 0; i < lswitch->n_subnets; i++) {
+ if (lswitch->subnets[i] == subnet) {
+ remove_subnet(lswitch, i);
+ return;
+ }
+ }
+ }
+
+ VLOG_WARN("subnet %s is not part of any logical switch",
+ ctx->argv[1]);
+}
+
+static void
+nbctl_subnet_list(struct ctl_context *ctx)
+{
+ const struct nbrec_logical_switch *lswitch;
+ lswitch = lswitch_by_name_or_uuid(ctx, ctx->argv[1]);
+ if (!lswitch) {
+ return;
+ }
+
+ for (size_t i = 0; i < lswitch->n_subnets; i++) {
+ const struct nbrec_subnet *subnet = lswitch->subnets[i];
+ ds_put_format(&ctx->output, UUID_FMT " (%s)\n",
+ UUID_ARGS(&subnet->header_.uuid), subnet->cidr);
+ }
+}
+
+static void
+nbctl_subnet_set_dhcp_options(struct ctl_context *ctx)
+{
+ const struct nbrec_subnet *subnet = subnet_get(ctx, ctx->argv[1]);
+ if (!subnet) {
+ VLOG_WARN("subnet not found for '%s'", ctx->argv[1]);
+ return;
+ }
+
+ struct smap dhcp_options = SMAP_INITIALIZER(&dhcp_options);
+ for (size_t i = 2; i < ctx->argc; i++) {
+ char *key, *value;
+ value = xstrdup(ctx->argv[i]);
+ key = strsep(&value, "=");
+ if (value) {
+ smap_add(&dhcp_options, key, value);
+ }
+ free(key);
+ }
+
+ nbrec_subnet_set_dhcp_options(subnet, &dhcp_options);
+ smap_destroy(&dhcp_options);
+}
+
+static void
+nbctl_subnet_get_dhcp_options(struct ctl_context *ctx)
+{
+ const struct nbrec_subnet *subnet = subnet_get(ctx, ctx->argv[1]);
+ if (!subnet) {
+ VLOG_WARN("subnet not found for '%s'", ctx->argv[1]);
+ return;
+ }
+
+ struct smap_node *node;
+ SMAP_FOR_EACH(node, &subnet->dhcp_options) {
+ ds_put_format(&ctx->output, "%s=%s\n", node->key, node->value);
+ }
+}
+
enum {
DIR_FROM_LPORT,
DIR_TO_LPORT
@@ -1092,6 +1276,10 @@ static const struct ctl_table_class tables[] = {
{{NULL, NULL, NULL},
{NULL, NULL, NULL}}},
+ {&nbrec_table_subnet,
+ {{&nbrec_table_subnet, NULL, NULL},
+ {NULL, NULL, NULL}}},
+
{&nbrec_table_logical_router,
{{&nbrec_table_logical_router, &nbrec_logical_router_col_name, NULL},
{NULL, NULL, NULL}}},
@@ -1351,6 +1539,16 @@ static const struct ctl_command_syntax nbctl_commands[] = {
{ "lport-get-options", 1, 1, "LPORT", NULL, nbctl_lport_get_options, NULL,
"", RO },
+ /* subnet commands */
+ {"subnet-add", 3, 4, "LSWITCH CIDR GATEWAY_IP ENABLE_DHCP", NULL,
+ nbctl_subnet_add, NULL, "", RW },
+ {"subnet-del", 1, 1, "SUBNET", NULL, nbctl_subnet_del, NULL, "", RW},
+ {"subnet-list", 1, 1, "LSWITCH", NULL, nbctl_subnet_list, NULL, "", RO},
+ {"subnet-set-dhcp-options", 1, INT_MAX, "SUBNET KEY=VALUE [KEY=VALUE]...",
+ NULL, nbctl_subnet_set_dhcp_options, NULL, "", RW },
+ {"subnet-get-dhcp-options", 1, 1, "SUBNET", NULL,
+ nbctl_subnet_get_dhcp_options, NULL, "", RO },
+
{NULL, 0, 0, NULL, NULL, NULL, NULL, "", RO},
};
diff --git a/tests/ovn.at b/tests/ovn.at
index 971c8cd..07b22d2 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -2380,3 +2380,190 @@ AT_CHECK([ovstest test-ovn-dhcp-offer-action $dhcp_options $expected_dhcp_opts],
[1], [ignore])
AT_CLEANUP
+
+AT_SETUP([ovn -- dhcp : 1 HV, 2 LS, 2 lports])
+AT_KEYWORDS([dhcp])
+AT_SKIP_IF([test $HAVE_PYTHON = no])
+ovn_start
+
+ovn-nbctl lswitch-add ls1
+ovn-nbctl lswitch-add ls2
+
+ovn-nbctl -- --id=@s1 create Subnet cidr=10.0.0.0/24 gateway_ip=10.0.0.1 \
+ip_version=4 enable_dhcp=true dhcp_options="\"server_id\"=\"10.0.0.1\" \
+\"server_mac\"=\"00:00:00:10:00:01\" \"lease_time\"=\"36\"" \
+-- add Logical_Switch ls1 subnets @s1
+
+ovn-nbctl lport-add ls1 ls1-lp1 \
+-- lport-set-addresses ls1-lp1 "f0:00:00:00:00:01 10.0.0.4"
+
+ovn-nbctl lport-set-port-security ls1-lp1 "f0:00:00:00:00:01 10.0.0.4"
+
+ovn-nbctl lport-add ls2 ls2-lp1 \
+-- lport-set-addresses ls2-lp1 "f0:00:00:00:00:02 20.0.0.3"
+
+ovn-nbctl lport-set-port-security ls2-lp1 "f0:00:00:00:00:02 20.0.0.3"
+
+net_add n1
+sim_add hv1
+
+as hv1
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \
+ 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=ls2-lp1 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+ options:rxq_pcap=hv1/vif2-rx.pcap \
+ ofport-request=2
+
+ovn_populate_arp
+
+sleep 2
+
+send_dhcp_packet() {
+ local inport=$1 src_mac=$2 dhcp_type=$3
+ local request=ffffffffffff${src_mac}080045100110000000008011000000000000ffffffff
+ # udp header and dhcp header
+ request+=0044004300FC0000
+ request+=010106006359aa760000000000000000000000000000000000000000${src_mac}
+ # client hardware padding
+ request+=00000000000000000000
+ # server hostname
+ request+=0000000000000000000000000000000000000000000000000000000000000000
+ request+=0000000000000000000000000000000000000000000000000000000000000000
+ # boot file name
+ request+=0000000000000000000000000000000000000000000000000000000000000000
+ request+=0000000000000000000000000000000000000000000000000000000000000000
+ request+=0000000000000000000000000000000000000000000000000000000000000000
+ request+=0000000000000000000000000000000000000000000000000000000000000000
+ # dhcp magic cookie
+ request+=63825363
+ # dhcp message type
+ request+=3501${dhcp_type}ff
+ shift; shift; shift; shift; shift
+ echo "inport = $inport"
+ echo "src mac = $src_mac"
+ as hv1 ovs-appctl netdev-dummy/receive hv1-vif$inport $request
+}
+
+run_dhcp_test() {
+ local inport=$1 dhcp_type=$2
+ send_dhcp_packet $inport f0000000000$inport $dhcp_type
+}
+
+AT_CAPTURE_FILE([ofctl_monitor0.log])
+as hv1 ovs-ofctl monitor br-int resume --detach --no-chdir \
+--pidfile=ovs-ofctl0.pid 2> ofctl_monitor0.log
+
+echo "---------NB dump-----"
+ovn-nbctl show
+echo "---------------------"
+echo "---------SB dump-----"
+ovn-sbctl list datapath_binding
+echo "---------------------"
+ovn-sbctl list logical_flow
+echo "---------------------"
+
+echo "---------------------"
+ovn-sbctl dump-flows
+echo "---------------------"
+
+echo "------ hv1 dump ----------"
+as hv1 ovs-ofctl dump-flows br-int
+
+#send DHCP DISCOVER
+run_dhcp_test 1 01
+
+# Wait for the expected number of NXT_RESUMEs to be logged.
+echo "waiting for 1 NXT_RESUMEs..."
+OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+
+# Extract the dhcp options in userdata. It will be in the format
+# ' userdata=00.00.00.02.00.00.00.00.0a.00.00.04.01.04.ff......'
+# Skip 47 columns - userdata=
+dhcp_opts_in_user_data=`cat ofctl_monitor0.log | grep userdata | cut -c 47-`
+
+AT_CHECK([ovstest test-ovn-dhcp hv1/vif1-tx.pcap 10.0.0.4 10.0.0.1 \
+ 00:00:00:10:00:01 1 $dhcp_opts_in_user_data], [0], [ignore])
+
+# run dhcp test on ls2-lp1. There are no dhcp options defined. So the dhcp
+# packet should not be handled by ovn-controller
+cat ofctl_monitor*.log
+
+echo "###########################################"
+run_dhcp_test 2 01
+
+# NXT_RESUMEs should be 1.
+echo "waiting for 1 NXT_RESUMEs..."
+cat ofctl_monitor*.log
+echo "###########################################"
+
+OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+
+ovn-nbctl subnet-add ls2 20.0.0.0/24 20.0.0.1 true
+subnet_id=`ovn-nbctl subnet-list ls2 | cut -c -37`
+
+ovn-nbctl subnet-set-dhcp-options $subnet_id "server_id=20.0.0.1" \
+"lease_time=4500"
+
+sleep 2
+
+# there is no "server_mac" field in the dhcp options. So there should be no
+# dhcp flow for this port
+OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | \
+grep -c "actions=controller(userdata=00.00.00.02.00.00.00"`])
+
+ovn-nbctl subnet-set-dhcp-options $subnet_id "server_id=20.0.0.1" \
+"server_mac=00:00:00:10:00:02" "lease_time=4500"
+
+sleep 3
+OVS_WAIT_UNTIL([test 2 = `as hv1 ovs-ofctl dump-flows br-int | \
+grep -c "actions=controller(userdata=00.00.00.02.00.00.00"`])
+
+echo "###########################################"
+run_dhcp_test 2 03
+
+# NXT_RESUMEs should be 2.
+echo "waiting for 2 NXT_RESUMEs..."
+OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+
+dhcp_opts_in_user_data=`cat ofctl_monitor0.log | grep userdata | \
+cut -c 47- | sed '1d'`
+
+AT_CHECK([ovstest test-ovn-dhcp hv1/vif2-tx.pcap 20.0.0.3 20.0.0.1 \
+ 00:00:00:10:00:02 3 $dhcp_opts_in_user_data], [0], [ignore])
+
+
+echo "---------NB dump-----"
+ovn-nbctl show
+echo "---------SB dump-----"
+ovn-sbctl list datapath_binding
+echo "---------------------"
+ovn-sbctl list logical_flow
+ovn-sbctl list port_binding
+echo "---------------------"
+ovn-sbctl dump-flows
+echo "---------------------"
+
+echo "------ hv1 dump ----------"
+as hv1 ovs-ofctl dump-flows br-int
+
+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([ovn-northd])
+
+as main
+OVS_APP_EXIT_AND_WAIT([ovs-vswitchd])
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+AT_CLEANUP
diff --git a/tests/test-ovn-dhcp.c b/tests/test-ovn-dhcp.c
index 60726d9..107cc06 100644
--- a/tests/test-ovn-dhcp.c
+++ b/tests/test-ovn-dhcp.c
@@ -16,10 +16,15 @@
#include
#include
#include "command-line.h"
+#include "dp-packet.h"
+#include
+#include "flow.h"
+#include "lib/dhcp.h"
#include "openvswitch/ofp-actions.h"
#include "ovstest.h"
#include "ovn/lib/actions.h"
#include "ovn/lib/ovn-dhcp.h"
+#include "pcap-file.h"
static void
@@ -114,4 +119,153 @@ test_dhcp_offer_action(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
exit(0);
}
+static void
+test_ovn_dhcp_main(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+ if (argc != 7) {
+ printf("Usage: %s pcap-file offer-ip server-ip"
+ " server-mac dhcp-type userdata\n", argv[0]);
+ exit(1);
+ }
+
+ int retval = 1;
+ FILE *pcap;
+
+ set_program_name(argv[0]);
+
+ pcap = fopen(argv[1], "rb");
+ if (!pcap) {
+ ovs_fatal(errno, "failed to open %s", argv[1]);
+ }
+
+ retval = ovs_pcap_read_header(pcap);
+ if (retval) {
+ ovs_fatal(retval > 0 ? retval : 0, "reading pcap header failed");
+ }
+
+ /* verify if the offer-ip is in proper format */
+ ovs_be32 expected_offer_ip;
+ if (!ovs_scan(argv[2], IP_SCAN_FMT, IP_SCAN_ARGS(&expected_offer_ip))) {
+ ovs_fatal(1, "invalid expected offer ip");
+ }
+
+ /* verify if the server-ip is in proper format */
+ ovs_be32 server_ip;
+ if (!ovs_scan(argv[3], IP_SCAN_FMT, IP_SCAN_ARGS(&server_ip))) {
+ ovs_fatal(1, "invalid expected server ip");
+ }
+
+ struct eth_addr server_mac;
+ if (!eth_addr_from_string(argv[4], &server_mac)) {
+ ovs_fatal(1, "invalid expected server mac");
+ }
+
+ /*
+ * In the testing its been observed that, the pcap file is
+ * receiving 2 packets.
+ *
+ * 1. The resumed dhcp response packet from the ovn-controller
+ * 2. The dhcp response packet without the 'pause' with all the other
+ * actions applied.
+ */
+
+ int num_pcap_reads = 0;
+ int exit_code = 1;
+ struct dp_packet *packet = NULL;
+ do {
+ num_pcap_reads++;
+ retval = ovs_pcap_read(pcap, &packet, NULL);
+ if (retval == EOF) {
+ ovs_fatal(0, "unexpected end of file reading pcap file : [%s]\n",
+ argv[1]);
+ } else if (retval) {
+ ovs_fatal(retval, "error reading pcap file");
+ }
+
+ struct flow flow;
+ flow_extract(packet, &flow);
+
+ struct dhcp_header const *dhcp_data = dp_packet_get_udp_payload(packet);
+ if (dhcp_data->op != (uint8_t)2) {
+ if (num_pcap_reads == 1) {
+ /* This is the incomplete dhcp reply packet without the
+ * 'pause'. Read the next packet */
+ dp_packet_delete(packet);
+ continue;
+ }
+ printf("Invalid dhcp op reply code : %d\n", dhcp_data->op);
+ break;
+ }
+
+ if (flow.tp_src != htons(DHCP_SERVER_PORT) &&
+ flow.tp_dst != htons(DHCP_CLIENT_PORT)) {
+ printf("Error. Not a dhcp response packet \n");
+ break;
+ }
+
+ if (flow.nw_dst != expected_offer_ip) {
+ printf("Error. Offered ip : "IP_FMT " : Expected ip : %s\n",
+ IP_ARGS(flow.nw_dst), argv[2]);
+ break;
+ }
+
+ if(dhcp_data->yiaddr != expected_offer_ip) {
+ printf("Error. Offered yiaddr : "IP_FMT " : Expected ip : %s\n",
+ IP_ARGS(dhcp_data->yiaddr), argv[2]);
+ break;
+ }
+
+ /* Verify the dhcp option cookie */
+ char const *footer = (char *)dhcp_data + sizeof(*dhcp_data);
+ uint32_t cookie = *(uint32_t *)footer;
+ if (cookie != htonl(DHCP_MAGIC_COOKIE)) {
+ printf("Error. Invalid dhcp magic cookie\n");
+ break;
+ }
+
+ /* Validate userdata. It should be ASCII hex */
+ uint64_t dhcp_opts_stub[1024 / 8];
+ struct ofpbuf dhcp_opts = OFPBUF_STUB_INITIALIZER(dhcp_opts_stub);
+ if (atoi(argv[5]) == 1) {
+ /* DHCP reply type should be OFFER (02) */
+ ofpbuf_put_hex(&dhcp_opts, "350102", NULL);
+ } else {
+ /* DHCP reply type should be ACK (05) */
+ ofpbuf_put_hex(&dhcp_opts, "350105", NULL);
+ }
+
+ if (ofpbuf_put_hex(&dhcp_opts, argv[6], NULL)[0] != '\0') {
+ printf("Error. Invalid userdata\n");
+ break;
+ }
+
+ /* 4 bytes padding, 1 byte FF and 4 bytes padding */
+ ofpbuf_put_hex(&dhcp_opts, "00000000FF00000000", NULL);
+
+ footer += sizeof(uint32_t);
+
+ size_t dhcp_opts_size = (const char *)dp_packet_tail(packet) - (
+ const char *)footer;
+ if (dhcp_opts_size != dhcp_opts.size) {
+ printf("Error. dhcp options size mismatch\n");
+ break;
+ }
+
+ if (memcmp(footer, dhcp_opts.data, dhcp_opts.size)) {
+ printf("Error. Invalid dhcp options present\n");
+ break;
+ }
+
+ exit_code = 0;
+ break;
+ }while(num_pcap_reads < 2);
+
+ fclose(pcap);
+ if (packet) {
+ dp_packet_delete(packet);
+ }
+ exit(exit_code);
+}
+
OVSTEST_REGISTER("test-ovn-dhcp-offer-action", test_dhcp_offer_action);
+OVSTEST_REGISTER("test-ovn-dhcp", test_ovn_dhcp_main);