From patchwork Thu May 19 08:11:10 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Numan Siddique X-Patchwork-Id: 623891 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 3r9P0N3zc1z9t6Z for ; Thu, 19 May 2016 18:11:16 +1000 (AEST) Received: from archives.nicira.com (localhost [127.0.0.1]) by archives.nicira.com (Postfix) with ESMTP id D8B3910A17; Thu, 19 May 2016 01:11:15 -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 32DBC1099E for ; Thu, 19 May 2016 01:11:15 -0700 (PDT) Received: from bar6.cudamail.com (localhost [127.0.0.1]) by mx3v3.cudamail.com (Postfix) with ESMTPS id C09551625BD for ; Thu, 19 May 2016 02:11:14 -0600 (MDT) X-ASG-Debug-ID: 1463645473-0b3237209d8e560001-byXFYA Received: from mx1-pf1.cudamail.com ([192.168.24.1]) by bar6.cudamail.com with ESMTP id mD1Q4FYAor0KBnkM (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NO) for ; Thu, 19 May 2016 02:11:13 -0600 (MDT) X-Barracuda-Envelope-From: nusiddiq@redhat.com X-Barracuda-RBL-Trusted-Forwarder: 192.168.24.1 Received: from unknown (HELO mx1.redhat.com) (209.132.183.28) by mx1-pf1.cudamail.com with ESMTPS (DHE-RSA-AES256-SHA encrypted); 19 May 2016 08:11:13 -0000 Received-SPF: pass (mx1-pf1.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-mx09.intmail.prod.int.phx2.redhat.com (int-mx09.intmail.prod.int.phx2.redhat.com [10.5.11.22]) (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 58EE8627DF for ; Thu, 19 May 2016 08:11:12 +0000 (UTC) Received: from nusiddiq.blr.redhat.com (dhcp-0-111.blr.redhat.com [10.70.1.111]) by int-mx09.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id u4J8BAUb028625 for ; Thu, 19 May 2016 04:11:11 -0400 X-CudaMail-Envelope-Sender: nusiddiq@redhat.com From: Numan Siddique X-CudaMail-Whitelist-To: dev@openvswitch.org X-CudaMail-MID: CM-E1-518002811 X-CudaMail-DTE: 051916 X-CudaMail-Originating-IP: 209.132.183.28 To: ovs dev X-ASG-Orig-Subj: [##CM-E1-518002811##][PATCH v6 2/2] ovn: Add logical flows to support native DHCP In-Reply-To: <6a901929-de0a-ed41-91f7-0cf9c0e6b14e@redhat.com> Organization: Red Hat Message-ID: <6ea4e0cc-63ad-3cc4-c3d4-30a8057cb956@redhat.com> Date: Thu, 19 May 2016 13:41:10 +0530 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Thunderbird/45.0 MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.68 on 10.5.11.22 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.38]); Thu, 19 May 2016 08:11:12 +0000 (UTC) X-Barracuda-Connect: UNKNOWN[192.168.24.1] X-Barracuda-Start-Time: 1463645473 X-Barracuda-Encrypted: ECDHE-RSA-AES256-GCM-SHA384 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 v6 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. For each logical port following flows are added if the CMS has defined DHCP options in the 'Subnet' column - A logical flow which copies the DHCP options to the DHCP request packets using the 'put_dhcp_opts' action and advances the packet to the next stage. - A logical flow which implements the DHCP reponder by sending the DHCP reply back to the inport once the 'put_dhcp_opts' action is applied. Signed-Off-by: Numan Siddique --- ovn/northd/ovn-northd.8.xml | 89 +++++++++++- ovn/northd/ovn-northd.c | 264 ++++++++++++++++++++++++++++++++++- ovn/ovn-nb.ovsschema | 19 ++- ovn/ovn-nb.xml | 314 +++++++++++++++++++++++++++++++++++++++++- ovn/utilities/ovn-nbctl.8.xml | 29 ++++ ovn/utilities/ovn-nbctl.c | 196 ++++++++++++++++++++++++++ tests/ovn.at | 250 +++++++++++++++++++++++++++++++++ tests/test-ovn-dhcp.c | 135 ++++++++++++++++++ 8 files changed, 1286 insertions(+), 10 deletions(-) diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml index 970c352..9a4a46a 100644 --- a/ovn/northd/ovn-northd.8.xml +++ b/ovn/northd/ovn-northd.8.xml @@ -326,7 +326,88 @@ output; -

Ingress Table 6: Destination Lookup

+

Ingress Table 6: DHCP pause

+ +

+ This table adds the DHCP options to a DHCP packet from the + logical ports configured with IPv4 address(es) and DHCP options. +

+ +
    +
  • +

    + A priority-100 logical flow is added for these logical ports + which matches the packet with udp.src = 68 and + udp.dst = 67 and applies the action + put_dhcp_opts and advances the packet to the table 7. +

    + +
    +put_dhcp_opts(R, offer_ip = O, DHCP_OPTIONS);
    +next;
    +        
    + +

    + The action put_dhcp_opts adds the DHCP options to + the packet and sets the yiaddr to the offer ip + defined in O only if the packet is a + DHCPDISCOVER/DHCPREQUEST and resumes the pipeline by setting + 0x1 to the OVS register R. + If the packet is non-DHCP packet or invalid DHCP packet + it resumes the pipeline without any modifications. +

    + +
  • + +
  • + A priority-0 flow that matches all packets to advances to table 7. +
  • +
+ +

Ingress Table 7: DHCP resume

+ +

+ This table implements DHCP responder for the resumed DHCP packets + from the previous table. +

+ +
    +
  • +

    + A priority 100 logical flow is added for the logical ports configured + with DHCP options which matches the packet with + udp.src = 68, udp.dst = 67 and + reg0 = 0x1 and responds back to the + inport after applying these actions. + If reg0 is set to 1, it means that the action + put_dhcp_opts was successful. +

    + +
    +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 8. +
  • +
+ +

Ingress Table 8: Destination Lookup

This table implements switching behavior. It contains these logical @@ -370,6 +451,12 @@ output; This is similar to ingress table 4 except for to-lport ACLs.

+

+ Also a priority 34000 logical flow is added for each subnet of the logical + switch which has DHCP options defined to allow the DHCP reply packet + from the Ingress Table 7: DHCP resume. +

+

Egress Table 2: Egress Port Security - IP

diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c index 44e9430..8a8e146 100644 --- a/ovn/northd/ovn-northd.c +++ b/ovn/northd/ovn-northd.c @@ -26,6 +26,7 @@ #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" @@ -33,6 +34,7 @@ #include "packets.h" #include "poll-loop.h" #include "smap.h" +#include "sset.h" #include "stream.h" #include "stream-ssl.h" #include "unixctl.h" @@ -94,7 +96,9 @@ enum ovn_stage { 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_PAUSE, 6, "ls_in_dhcp_pause") \ + PIPELINE_STAGE(SWITCH, IN, DHCP_RESUME, 7, "ls_in_dhcp_resume") \ + PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 8, "ls_in_l2_lkup") \ \ /* Logical switch egress stages. */ \ PIPELINE_STAGE(SWITCH, OUT, PRE_ACL, 0, "ls_out_pre_acl") \ @@ -1265,6 +1269,82 @@ has_stateful_acl(struct ovn_datapath *od) return false; } +static bool +build_dhcp_action(struct ovn_port *op, ovs_be32 offer_ip, + struct ds *pause_action, struct ds *resume_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 && subnet->enable_dhcp + && subnet->ip_version == 4)) { + return false; + } + + + const char *server_ip = smap_get(&subnet->dhcp_options, "server_id"); + const char *server_mac = smap_get(&subnet->dhcp_options, "server_mac"); + const char *lease_time = smap_get(&subnet->dhcp_options, "lease_time"); + + if (!(server_ip && server_mac && lease_time)) { + /* "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); + + struct smap_node *node; + /* 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(pause_action, "put_dhcp_opts(reg0, offerip = "IP_FMT", ", + IP_ARGS(offer_ip)); + SMAP_FOR_EACH(node, &dhcp_options) { + ds_put_format(pause_action, "%s = %s, ", node->key, node->value); + } + + ds_chomp(pause_action, ' '); + ds_chomp(pause_action, ','); + ds_put_cstr(pause_action, "); next;"); + + ds_put_format(resume_action, "eth.dst = eth.src; eth.src = %s; " + "ip4.dst = "IP_FMT"; ip4.src = %s; udp.src = 67; " + "udp.dst = 68; outport = inport; inport = \"\";" + " /* Allow sending out inport. */ output;", + server_mac, IP_ARGS(offer_ip), server_ip); + + smap_destroy(&dhcp_options); + return true; +} + static void build_acls(struct ovn_datapath *od, struct hmap *lflows, struct hmap *ports) { @@ -1415,6 +1495,36 @@ build_acls(struct ovn_datapath *od, struct hmap *lflows, struct hmap *ports) acl->match, "drop;"); } } + + /* Add 34000 priority flow to allow DHCP reply from ovn-controller to all + * logical ports of the datapath if the CMS has configured DHCP options*/ + if (od->nbs && od->nbs->n_ports && od->nbs->n_subnets) { + for (size_t i = 0; i < od->nbs->n_subnets; i++) { + if (!(od->nbs->subnets[i]->gateway_ip && + 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"); + const char *lease_time = smap_get( + &od->nbs->subnets[i]->dhcp_options, "lease_time"); + if (server_id && server_mac && lease_time) { + struct ds match = DS_EMPTY_INITIALIZER; + const char *actions = has_stateful ? "ct_commit; next;" : + "next;"; + 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_ACL, 34000, ds_cstr(&match), + actions); + } + } + } } static void @@ -1497,7 +1607,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_IP, 0, "1", "next;"); } - /* Ingress table 3: ARP responder, skip requests coming from localnet ports. + /* Ingress table 5: ARP responder, skip requests coming from localnet ports. * (priority 100). */ HMAP_FOR_EACH (op, key_node, ports) { if (!op->nbs) { @@ -1572,7 +1682,72 @@ 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 + /* Logical switch ingress table 6 and 7: DHCP pause and resume + * priority 100 flows. */ + HMAP_FOR_EACH (op, key_node, ports) { + if (!op->nbs) { + continue; + } + + if (!lport_is_enabled(op->nbs) || !strcmp(op->nbs->type, "router")) { + /* Don't add the DHCP flows if the port is not enabled or if the + * port is a router port */ + 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 pause_action = DS_EMPTY_INITIALIZER; + struct ds resume_action = DS_EMPTY_INITIALIZER; + if (build_dhcp_action(op, laddrs.ipv4_addrs[j].addr, + &pause_action, &resume_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_PAUSE, 100, + ds_cstr(&match), ds_cstr(&pause_action)); + /* If reg0 is set to 1, it means the dhcp_offer action is + * successful */ + ds_put_cstr(&match, " && reg0 == 1"); + ovn_lflow_add(lflows, op->od, S_SWITCH_IN_DHCP_RESUME, 100, + ds_cstr(&match), ds_cstr(&resume_action)); + ds_destroy(&match); + ds_destroy(&pause_action); + ds_destroy(&resume_action); + break; + } + } + } + } + + /* Ingress table 6 and 7: DHCP pause and resume, by default goto next. + * (priority 0)*/ + + HMAP_FOR_EACH (od, key_node, datapaths) { + if (!od->nbs) { + continue; + } + + ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP_PAUSE, 0, "1", "next;"); + ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP_RESUME, 0, "1", "next;"); + } + + /* Ingress table 8: Destination lookup, broadcast and multicast handling * (priority 100). */ HMAP_FOR_EACH (op, key_node, ports) { if (!op->nbs) { @@ -1592,7 +1767,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 8: Destination lookup, unicast handling (priority 50), */ HMAP_FOR_EACH (op, key_node, ports) { if (!op->nbs) { continue; @@ -1629,7 +1804,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, } } - /* Ingress table 6: Destination lookup for unknown MACs (priority 0). */ + /* Ingress table 8: Destination lookup for unknown MACs (priority 0). */ HMAP_FOR_EACH (od, key_node, datapaths) { if (!od->nbs) { continue; @@ -2283,6 +2458,77 @@ 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_MS_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, + DHCP_OPT_T1, + DHCP_OPT_T2 +}; + +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 * @@ -2451,6 +2697,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; @@ -2464,7 +2714,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 8163f6a..5edd032 100644 --- a/ovn/ovn-nb.ovsschema +++ b/ovn/ovn-nb.ovsschema @@ -1,7 +1,7 @@ { "name": "OVN_Northbound", - "version": "2.1.1", - "cksum": "2615511875 5108", + "version": "2.2.0", + "cksum": "2898515457 5921", "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 c01455d..5bbe8c4 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,289 @@
+ +

+ A subnet within an L2 logical switch. This is an optional table. CMS + can add the rows to this table to define the subnets belonging to logical + switch. +

+ + +

+ 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. Please refer to the + RFC 2132 "https://tools.ietf.org/html/rfc2132" for + more details on the DHCP options and their codes. +

+ + +

+ The DHCP option code for this option is 1. +

+ +

+ Example. key="netmask", value="255.255.255.0" +

+
+ + +

+ The DHCP option code for this option is 3. +

+
+ + +

+ The DHCP option code for this option is 6. +

+
+ + +

+ The DHCP option code for this option is 7. +

+
+ + +

+ The DHCP option code for this option is 9. +

+
+ + +

+ The DHCP option code for this option is 16. +

+
+ + +

+ The DHCP option code for this option is 21. +

+
+ + +

+ The DHCP option code for this option is 32. +

+
+ + +

+ The DHCP option code for this option is 41. +

+
+ + +

+ The DHCP option code for this option is 42. +

+
+ + +

+ The DHCP option code for this option is 54. +

+
+ + +

+ The DHCP option code for this option is 66. +

+
+ + +

+ The DHCP option code for this option is 121. +

+ +

+ 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}" +

+
+ + +

+ The DHCP option code for this option is 249. This option is + similar to classless_static_route supported by + Microsoft Windows DHCP clients. +

+
+ + +

+ eth.src will be set to this value in the DHCP + response packet. +

+
+
+ + + +

+ The DHCP option code for this option is 19. +

+ +

+ The value of this DHCP option is of type bool. + + Example. key="ip_forward_enable", value="1" +

+
+ + +

+ The DHCP option code for this option is 31. +

+ +

+ The value of this DHCP option is of type bool. +

+
+ + +

+ The DHCP option code for this option is 36. +

+ +

+ The value of this DHCP option is of type bool. +

+
+ + +

+ The DHCP option code for this option is 23. +

+ +

+ The value of this DHCP option is of type uint8. + + Example. key="default_ttl", value="128". +

+
+ + +

+ The DHCP option code for this option is 37. +

+ +

+ The value of this DHCP option is of type uint8. +

+
+ + +

+ The DHCP option code for this option is 26. +

+ +

+ The value of this DHCP option is of type uint16. +

+
+ + +

+ The DHCP option code for this option is 51. +

+ +

+ The value of this DHCP option is of type uint32. + + Example. key="lease_time", value="42000" +

+
+ + +

+ The DHCP option code for this option is 58. +

+ +

+ The value of this DHCP option is of type uint32. + + Example. key="T1", value="30000" +

+
+ + +

+ The DHCP option code for this option is 59. +

+ +

+ The value of this DHCP option is of type uint32. + + Example. key="T2", value="40000" +

+
+
+ + +

+ 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. @@ -637,7 +949,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 8375ab7..408badb 100644 --- a/ovn/utilities/ovn-nbctl.8.xml +++ b/ovn/utilities/ovn-nbctl.8.xml @@ -64,6 +64,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 5bdf757..fcf577b 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\ @@ -499,6 +511,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, @@ -919,6 +932,175 @@ 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], true); + + /* Validate the cidr */ + int64_t ip_version = 4; + ovs_be32 ip; + unsigned int 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], false); + 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 @@ -1117,6 +1299,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}}}, @@ -1382,6 +1568,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 406055c..72dd8d0 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -2660,3 +2660,253 @@ AT_CHECK([ovstest test-ovn-put-dhcp-opts-action reg0 $dhcp_options $expected_dhc [1], [ignore]) AT_CLEANUP + +AT_SETUP([ovn -- dhcp : 1 HV, 2 LS, 3 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" + +ovn-nbctl lport-add ls1 ls1-lp2 \ +-- lport-set-addresses ls1-lp2 "f0:00:00:00:00:03 10.0.0.5" + +ovn-nbctl lport-set-port-security ls1-lp2 "f0:00:00:00:00:03 10.0.0.5" +#disable dhcp on lport - ls1-lp2 +ovn-nbctl lport-set-options ls1-lp2 "dhcp_disabled=true" + +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 + +ovs-vsctl -- add-port br-int hv1-vif3 -- \ + set interface hv1-vif3 external-ids:iface-id=ls1-lp2 \ + options:tx_pcap=hv1/vif3-tx.pcap \ + options:rxq_pcap=hv1/vif3-rx.pcap \ + ofport-request=3 + +ovn_populate_arp + +sleep 2 + +# This shell function sends a DHCP request packet +# test_dhcp INPORT SRC_MAC DHCP_TYPE OUTPORT... +# The OUTPORTs (zero or more) list the VIFs on which the original DHCP +# packet should be received twice (one from ovn-controller and the other +# from the "ovs-ofctl monitor br-int resume" +test_dhcp() { + 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; + for outport; do + # the packet will be received twice, one from ovn-controller + # and the other from ovs-ofctl monitor br-int resume + echo $request | trim_zeros >> $outport.expected + echo $request | trim_zeros >> $outport.expected + done + as hv1 ovs-appctl netdev-dummy/receive hv1-vif$inport $request +} + +reset_pcap_file() { + local iface=$1 + local pcap_file=$2 + ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \ +options:rxq_pcap=dummy-rx.pcap + rm -f ${pcap_file}*.pcap + ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \ +options:rxq_pcap=${pcap_file}-rx.pcap +} + +trim_zeros() { + sed 's/\(00\)\{1,\}$//' +} + +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 +test_dhcp 1 f00000000001 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.00.00.00.00.0a.00.00.04.01.04.ff......' +# Skip 59 columns - userdata= +dhcp_opts_in_user_data=`cat ofctl_monitor0.log | grep userdata | cut -c 59-` + +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]) + +# ovs-ofctl also resumes the packets and this causes other ports to receive +# the DHCP request packet. So reset the pcap files so that its easier +# for "test-ovn-dhcp" to validate the DHCP reply packet +reset_pcap_file hv1-vif1 hv1/vif1 +reset_pcap_file hv1-vif2 hv1/vif2 +reset_pcap_file hv1-vif3 hv1/vif3 + +# run dhcp test on ls2-lp1. There are no dhcp options defined. So the dhcp +# packet should not be handled by ovn-controller +test_dhcp 2 f00000000002 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 1 + +# 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 2 +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"`]) + +test_dhcp 2 f00000000002 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 59- | 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]) + +reset_pcap_file hv1-vif1 hv1/vif1 +reset_pcap_file hv1-vif2 hv1/vif2 +reset_pcap_file hv1-vif3 hv1/vif3 + +#enable dhcp on lport - ls1-lp2 +ovn-nbctl lport-set-options ls1-lp2 "dhcp_disabled=false" + +sleep 2 + +OVS_WAIT_UNTIL([test 3 = `as hv1 ovs-ofctl dump-flows br-int | \ +grep -c "actions=controller(userdata=00.00.00.02.00.00.00"`]) + +#send invalid dhcp packet on hv1/vif3 (ie ls1-lp3). +# This packet should be received by hv1/vif1 (ls1-lp1) +test_dhcp 3 f00000000003 04 1 + +# NXT_RESUMEs should be 3. +echo "waiting for 3 NXT_RESUMEs..." +OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) + +# vif3-tx.pcap should not have received the DHCP reply packet +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif3-tx.pcap | trim_zeros > 3.packets +AT_CHECK([cat 3.packets], [0], []) + +# vif1-tx.pcap should have received the DHCP (invalid) request packet +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap | trim_zeros > 1.packets +cat 1.expected > expout +AT_CHECK([cat 1.packets], [0], [expout]) + + +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 79cee08..b879a03 100644 --- a/tests/test-ovn-dhcp.c +++ b/tests/test-ovn-dhcp.c @@ -16,6 +16,10 @@ #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" @@ -23,6 +27,7 @@ #include "ovn/lib/expr.h" #include "ovn/lib/logical-fields.h" #include "shash.h" +#include "pcap-file.h" static void add_logical_register(struct shash *symtab, enum mf_field_id id) @@ -146,4 +151,134 @@ test_put_dhcp_opts_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"); + } + + struct dp_packet *packet = NULL; + 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"); + } + + int exit_code = 1; + 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) { + printf("Invalid dhcp op reply code : %d\n", dhcp_data->op); + goto exit; + } + + if (flow.tp_src != htons(DHCP_SERVER_PORT) && + flow.tp_dst != htons(DHCP_CLIENT_PORT)) { + printf("Error. Not a dhcp response packet \n"); + goto exit; + } + + if (flow.nw_dst != expected_offer_ip) { + printf("Error. Offered ip : "IP_FMT " : Expected ip : %s\n", + IP_ARGS(flow.nw_dst), argv[2]); + goto exit; + } + + if(dhcp_data->yiaddr != expected_offer_ip) { + printf("Error. Offered yiaddr : "IP_FMT " : Expected ip : %s\n", + IP_ARGS(dhcp_data->yiaddr), argv[2]); + goto exit; + } + + /* Verify the dhcp option cookie */ + uint8_t const *footer = (uint8_t *)dhcp_data + sizeof(*dhcp_data); + ovs_be32 dhcp_cookie = htonl(DHCP_MAGIC_COOKIE); + if (memcmp(footer, &dhcp_cookie, sizeof(ovs_be32))) { + printf("Error. Invalid dhcp magic cookie\n"); + goto exit; + } + + /* 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"); + goto exit; + } + + /* 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"); + goto exit; + } + + if (memcmp(footer, dhcp_opts.data, dhcp_opts.size)) { + printf("Error. Invalid dhcp options present\n"); + goto exit; + } + + exit_code = 0; + +exit: + fclose(pcap); + if (packet) { + dp_packet_delete(packet); + } + exit(exit_code); +} + OVSTEST_REGISTER("test-ovn-put-dhcp-opts-action", test_put_dhcp_opts_action); +OVSTEST_REGISTER("test-ovn-dhcp", test_ovn_dhcp_main);