From patchwork Wed Jun 15 09:18:27 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Numan Siddique X-Patchwork-Id: 635772 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 3rV1Cc3jFlz9t1f for ; Wed, 15 Jun 2016 19:18:36 +1000 (AEST) Received: from archives.nicira.com (localhost [127.0.0.1]) by archives.nicira.com (Postfix) with ESMTP id B85CF1067F; Wed, 15 Jun 2016 02:18:35 -0700 (PDT) X-Original-To: dev@openvswitch.org Delivered-To: dev@openvswitch.org Received: from mx1e3.cudamail.com (mx1.cudamail.com [69.90.118.67]) by archives.nicira.com (Postfix) with ESMTPS id C4C021026B for ; Wed, 15 Jun 2016 02:18:34 -0700 (PDT) Received: from bar5.cudamail.com (localhost [127.0.0.1]) by mx1e3.cudamail.com (Postfix) with ESMTPS id 4FF894202E2 for ; Wed, 15 Jun 2016 03:18:34 -0600 (MDT) X-ASG-Debug-ID: 1465982312-09eadd40c52788a0001-byXFYA Received: from mx1-pf1.cudamail.com ([192.168.24.1]) by bar5.cudamail.com with ESMTP id JsYNtHT7QGBIfGyO (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NO) for ; Wed, 15 Jun 2016 03:18:32 -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); 15 Jun 2016 09:18:31 -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-mx11.intmail.prod.int.phx2.redhat.com (int-mx11.intmail.prod.int.phx2.redhat.com [10.5.11.24]) (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 5B7E16266F for ; Wed, 15 Jun 2016 09:18:30 +0000 (UTC) Received: from nusiddiq.blr.redhat.com (ovpn-116-82.phx2.redhat.com [10.3.116.82]) by int-mx11.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id u5F9IRo6029932 for ; Wed, 15 Jun 2016 05:18:28 -0400 X-CudaMail-Envelope-Sender: nusiddiq@redhat.com From: Numan Siddique X-CudaMail-Whitelist-To: dev@openvswitch.org X-CudaMail-MID: CM-E1-614003570 X-CudaMail-DTE: 061516 X-CudaMail-Originating-IP: 209.132.183.28 To: ovs dev X-ASG-Orig-Subj: [##CM-E1-614003570##][PATCH v2 4/4] ovn-northd: Add logical flows to support native DHCP In-Reply-To: Organization: Red Hat Message-ID: <54231e3c-446c-2c39-3000-caf37e886985@redhat.com> Date: Wed, 15 Jun 2016 14:48:27 +0530 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Thunderbird/45.1.0 MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.68 on 10.5.11.24 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.39]); Wed, 15 Jun 2016 09:18:30 +0000 (UTC) X-Barracuda-Connect: UNKNOWN[192.168.24.1] X-Barracuda-Start-Time: 1465982312 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 v2 4/4] ovn-northd: 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 Co-authored-by: Ben Pfaff Signed-off-by: Ben Pfaff --- ovn/northd/ovn-northd.8.xml | 91 +++++++++++- ovn/northd/ovn-northd.c | 267 ++++++++++++++++++++++++++++++++++- ovn/ovn-nb.ovsschema | 19 ++- ovn/ovn-nb.xml | 314 +++++++++++++++++++++++++++++++++++++++++- ovn/utilities/ovn-nbctl.8.xml | 29 ++++ ovn/utilities/ovn-nbctl.c | 200 ++++++++++++++++++++++++++- tests/automake.mk | 1 + tests/ovn.at | 250 +++++++++++++++++++++++++++++++++ tests/test-ovn-dhcp.c | 156 +++++++++++++++++++++ 9 files changed, 1315 insertions(+), 12 deletions(-) create mode 100644 tests/test-ovn-dhcp.c diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml index 0e59125..e27efeb 100644 --- a/ovn/northd/ovn-northd.8.xml +++ b/ovn/northd/ovn-northd.8.xml @@ -343,7 +343,90 @@ output; -

Ingress Table 6: Destination Lookup

+

Ingress Table 6: DHCP option processing

+ +

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

    + +
    +reg0[0] = put_dhcp_opts(offer_ip = O, options...);
    +next;
    +        
    + +

    + For DHCPDISCOVER and DHCPREQUEST, this transforms the packet into a + DHCP reply, adds the DHCP offer IP O and options to the + packet, and stores 1 into reg0[0]. For other kinds of packets, it + just stores 0 into reg0[0]. Either way, it continues to the next + table. +

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

Ingress Table 7: DHCP responses

+ +

+ This table implements DHCP responder for the DHCP replies generated by + by the previous table. +

+ +
    +
  • +

    + A priority 100 logical flow is added for the logical ports configured + with DHCP options which matches IPv4 packets with udp.src == 68 + && udp.dst == 67 && reg0[0] == 1 and + responds back to the inport after applying these + actions. If reg0[0] 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. +

    + +

    + (This terminates packet processing; the packet does not go on the + next ingress table.) +

    +
  • + +
  • + 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 @@ -387,6 +470,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 d53fca9..d6177fe 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_OPTIONS, 6, "ls_in_dhcp_options") \ + PIPELINE_STAGE(SWITCH, IN, DHCP_RESPONSE, 7, "ls_in_dhcp_response") \ + 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") \ @@ -1325,6 +1329,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 *options_action, struct ds *response_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(options_action, "reg0[0] = put_dhcp_opts(offerip = " + IP_FMT", ", IP_ARGS(offer_ip)); + SMAP_FOR_EACH(node, &dhcp_options) { + ds_put_format(options_action, "%s = %s, ", node->key, node->value); + } + + ds_chomp(options_action, ' '); + ds_chomp(options_action, ','); + ds_put_cstr(options_action, "); next;"); + + ds_put_format(response_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) { @@ -1475,6 +1555,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 @@ -1557,7 +1667,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) { @@ -1632,7 +1742,75 @@ 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 options and response + * priority 100 flows. */ + HMAP_FOR_EACH (op, key_node, ports) { + if (!op->nbs) { + continue; + } + + if (!lsp_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_lsp_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 options_action = DS_EMPTY_INITIALIZER; + struct ds response_action = DS_EMPTY_INITIALIZER; + if (build_dhcp_action(op, laddrs.ipv4_addrs[j].addr, + &options_action, &response_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_OPTIONS, + 100, ds_cstr(&match), + ds_cstr(&options_action)); + /* If reg0[0] is set, it means the put_dhcp_opts action + * is successful */ + ds_put_cstr(&match, " && reg0[0]"); + ovn_lflow_add(lflows, op->od, S_SWITCH_IN_DHCP_RESPONSE, + 100, ds_cstr(&match), + ds_cstr(&response_action)); + ds_destroy(&match); + ds_destroy(&options_action); + ds_destroy(&response_action); + break; + } + } + free(laddrs.ipv4_addrs); + } + } + + /* Ingress table 6 and 7: DHCP options and response, 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_OPTIONS, 0, "1", "next;"); + ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP_RESPONSE, 0, "1", "next;"); + } + + /* Ingress table 8: Destination lookup, broadcast and multicast handling * (priority 100). */ HMAP_FOR_EACH (op, key_node, ports) { if (!op->nbs) { @@ -1652,7 +1830,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; @@ -1689,7 +1867,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; @@ -2401,6 +2579,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 * @@ -2569,6 +2818,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; @@ -2582,7 +2835,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 95eb4f7..f48ab66 100644 --- a/ovn/ovn-nb.ovsschema +++ b/ovn/ovn-nb.ovsschema @@ -1,7 +1,7 @@ { "name": "OVN_Northbound", - "version": "3.0.0", - "cksum": "1808140260 5339", + "version": "3.1.0", + "cksum": "3452892568 6152", "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 d0dc597..30c6e06 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. + @@ -205,6 +212,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. + +
@@ -598,6 +627,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. @@ -645,7 +957,7 @@ Open_vSwitch table of Open_vSwitch database. - + 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 c2ca420..eceaa08 100644 --- a/ovn/utilities/ovn-nbctl.8.xml +++ b/ovn/utilities/ovn-nbctl.8.xml @@ -66,6 +66,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 +
+
+

Logical Switch ACL Commands

[--log] acl-add switch direction priority match action
diff --git a/ovn/utilities/ovn-nbctl.c b/ovn/utilities/ovn-nbctl.c index a3bd619..57f36a3 100644 --- a/ovn/utilities/ovn-nbctl.c +++ b/ovn/utilities/ovn-nbctl.c @@ -357,6 +357,18 @@ Route commands:\n\ remove routes from ROUTER\n\ lr-route-list ROUTER print routes for ROUTER\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\ @@ -593,6 +605,7 @@ nbctl_ls_list(struct ctl_context *ctx) smap_destroy(&lswitches); free(nodes); } + static const struct nbrec_logical_switch_port * lsp_by_name_or_uuid(struct ctl_context *ctx, const char *id, @@ -676,8 +689,8 @@ nbctl_lsp_add(struct ctl_context *ctx) } const char *lsp_name = ctx->argv[2]; - const struct nbrec_logical_switch_port *lsp; - lsp = lsp_by_name_or_uuid(ctx, lsp_name, false); + const struct nbrec_logical_switch_port *lsp = + lsp_by_name_or_uuid(ctx, lsp_name, false); if (lsp) { if (!may_exist) { ctl_fatal("%s: a port with this name already exists", @@ -1018,6 +1031,175 @@ nbctl_lsp_get_options(struct ctl_context *ctx) } } +static void +nbctl_subnet_add(struct ctl_context *ctx) +{ + const struct nbrec_logical_switch *lswitch = ls_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 = ls_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 @@ -1847,6 +2029,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}}}, @@ -2132,6 +2318,16 @@ static const struct ctl_command_syntax nbctl_commands[] = { { "lr-route-list", 1, 1, "ROUTER", NULL, nbctl_lr_route_list, 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/automake.mk b/tests/automake.mk index 777f6db..e9a6c39 100644 --- a/tests/automake.mk +++ b/tests/automake.mk @@ -338,6 +338,7 @@ tests_ovstest_SOURCES = \ tests/test-odp.c \ tests/test-ofpbuf.c \ tests/test-ovn.c \ + tests/test-ovn-dhcp.c \ tests/test-packets.c \ tests/test-random.c \ tests/test-rcu.c \ diff --git a/tests/ovn.at b/tests/ovn.at index 5bd0202..bba02f8 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -2823,6 +2823,256 @@ OVS_APP_EXIT_AND_WAIT([ovsdb-server]) AT_CLEANUP +AT_SETUP([ovn -- dhcp : 1 HV, 2 LS, 3 lsps]) +AT_KEYWORDS([dhcp]) +AT_SKIP_IF([test $HAVE_PYTHON = no]) +ovn_start + +ovn-nbctl ls-add ls1 +ovn-nbctl ls-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 lsp-add ls1 ls1-lp1 \ +-- lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01 10.0.0.4" + +ovn-nbctl lsp-set-port-security ls1-lp1 "f0:00:00:00:00:01 10.0.0.4" + +ovn-nbctl lsp-add ls2 ls2-lp1 \ +-- lsp-set-addresses ls2-lp1 "f0:00:00:00:00:02 20.0.0.3" + +ovn-nbctl lsp-set-port-security ls2-lp1 "f0:00:00:00:00:02 20.0.0.3" + +ovn-nbctl lsp-add ls1 ls1-lp2 \ +-- lsp-set-addresses ls1-lp2 "f0:00:00:00:00:03 10.0.0.5" + +ovn-nbctl lsp-set-port-security ls1-lp2 "f0:00:00:00:00:03 10.0.0.5" +#disable dhcp on lport - ls1-lp2 +ovn-nbctl lsp-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.01.00.04.00.00.00.00.0a.00.00.04.01.04.ff......' +# Skip 71 columns - userdata= +dhcp_opts_in_user_data=`cat ofctl_monitor0.log | grep userdata | cut -c 71-` + +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 71- | 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 lsp-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 + AT_SETUP([ovn -- 2 HVs, 2 LRs connected via LS, gateway router]) AT_KEYWORDS([ovngatewayrouter]) AT_SKIP_IF([test $HAVE_PYTHON = no]) diff --git a/tests/test-ovn-dhcp.c b/tests/test-ovn-dhcp.c new file mode 100644 index 0000000..3b53545 --- /dev/null +++ b/tests/test-ovn-dhcp.c @@ -0,0 +1,156 @@ +/* Copyright (c) 2016 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "command-line.h" +#include "dp-packet.h" +#include +#include "flow.h" +#include "lib/dhcp.h" +#include "ovstest.h" +#include "pcap-file.h" + + +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-dhcp", test_ovn_dhcp_main);