From patchwork Mon Jul 4 03:54:55 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Numan Siddique X-Patchwork-Id: 643877 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 3rjY7f1qK5z9sdn for ; Mon, 4 Jul 2016 13:55:07 +1000 (AEST) Received: from archives.nicira.com (localhost [127.0.0.1]) by archives.nicira.com (Postfix) with ESMTP id 4D99210BDE; Sun, 3 Jul 2016 20:55:06 -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 03EA410AEF for ; Sun, 3 Jul 2016 20:55:05 -0700 (PDT) Received: from bar5.cudamail.com (localhost [127.0.0.1]) by mx1e3.cudamail.com (Postfix) with ESMTPS id 5D7EB420A3B for ; Sun, 3 Jul 2016 21:55:04 -0600 (MDT) X-ASG-Debug-ID: 1467604501-09eadd3a0829f430001-byXFYA Received: from mx1-pf1.cudamail.com ([192.168.24.1]) by bar5.cudamail.com with ESMTP id rKbjr62aCPY81wPB (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NO) for ; Sun, 03 Jul 2016 21:55:01 -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); 4 Jul 2016 03:55:00 -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 195E93B3C0 for ; Mon, 4 Jul 2016 03:54:59 +0000 (UTC) Received: from nusiddiq.blr.redhat.com (dhcp-0-167.blr.redhat.com [10.70.1.167]) by int-mx09.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id u643su41001006 for ; Sun, 3 Jul 2016 23:54:57 -0400 X-CudaMail-Envelope-Sender: nusiddiq@redhat.com From: Numan Siddique X-CudaMail-Whitelist-To: dev@openvswitch.org X-CudaMail-MID: CM-E1-702023601 X-CudaMail-DTE: 070316 X-CudaMail-Originating-IP: 209.132.183.28 To: ovs dev X-ASG-Orig-Subj: [##CM-E1-702023601##][PATCH v4] ovn-northd: Add logical flows to support native DHCPv4 Organization: Red Hat Message-ID: <9e5f1174-ac57-9ccc-18de-0f64e208c537@redhat.com> Date: Mon, 4 Jul 2016 09:24:55 +0530 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Thunderbird/45.1.1 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.30]); Mon, 04 Jul 2016 03:54:59 +0000 (UTC) X-Barracuda-Connect: UNKNOWN[192.168.24.1] X-Barracuda-Start-Time: 1467604501 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-Virus-Scanned: by bsmtpd at cudamail.com X-Barracuda-BRTS-Status: 1 Subject: [ovs-dev] [PATCH v4] ovn-northd: Add logical flows to support native DHCPv4 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 DHCPv4 support which caters to the common use case of providing an IP address to a booting instance by providing stateless replies to DHCPv4 requests based on statically configured address mappings. To do this it allows a short list of DHCPv4 options to be configured and applied at each compute host running ovn-controller. A new table 'DHCP_Options' is added in OVN NB DB to store the DHCP options. Logical ports refer to this table to configure the DHCPv4 options. For each logical port configured with DHCPv4 Options following flows are added - A logical flow which copies the DHCPv4 options to the DHCPv4 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 DHCPv4 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 Tested-By: Ramu Ramamurthy against the --- ovn/northd/ovn-northd.8.xml | 91 +++++++++++++- ovn/northd/ovn-northd.c | 267 +++++++++++++++++++++++++++++++++++++-- ovn/ovn-nb.ovsschema | 20 ++- ovn/ovn-nb.xml | 270 ++++++++++++++++++++++++++++++++++++++++ ovn/utilities/ovn-nbctl.8.xml | 30 +++++ ovn/utilities/ovn-nbctl.c | 197 +++++++++++++++++++++++++++++ tests/ovn.at | 281 ++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 1144 insertions(+), 12 deletions(-) diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml index 6bc83ea..7072a7a 100644 --- a/ovn/northd/ovn-northd.8.xml +++ b/ovn/northd/ovn-northd.8.xml @@ -423,7 +423,90 @@ output; -

Ingress Table 10: Destination Lookup

+

Ingress Table 10: DHCP option processing

+ +

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

+ +
    +
  • +

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

    + +
    +reg0[3] = 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[3]. For other kinds of packets, it + just stores 0 into reg0[3]. Either way, it continues to the next + table. +

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

Ingress Table 11: DHCP responses

+ +

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

+ +
    +
  • +

    + A priority 100 logical flow is added for the logical ports configured + with DHCPv4 options which matches IPv4 packets with udp.src == 68 + && udp.dst == 67 && reg0[3] == 1 and + responds back to the inport after applying these + actions. If reg0[3] 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 DHCPv4 options and O is + the IPv4 address defined in the logical port's addresses column. +

    + +

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

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

Ingress Table 12: Destination Lookup

This table implements switching behavior. It contains these logical @@ -497,6 +580,12 @@ output; there are no rules added for load balancing new connections.

+

+ Also a priority 34000 logical flow is added for each logical port which + has DHCPv4 options defined to allow the DHCPv4 reply packet from the + Ingress Table 11: DHCP responses. +

+

Egress Table 6: Egress Port Security - IP

diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c index f4b4435..e605cb0 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" @@ -99,7 +100,9 @@ enum ovn_stage { PIPELINE_STAGE(SWITCH, IN, LB, 7, "ls_in_lb") \ PIPELINE_STAGE(SWITCH, IN, STATEFUL, 8, "ls_in_stateful") \ PIPELINE_STAGE(SWITCH, IN, ARP_ND_RSP, 9, "ls_in_arp_rsp") \ - PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 10, "ls_in_l2_lkup") \ + PIPELINE_STAGE(SWITCH, IN, DHCP_OPTIONS, 10, "ls_in_dhcp_options") \ + PIPELINE_STAGE(SWITCH, IN, DHCP_RESPONSE, 11, "ls_in_dhcp_response") \ + PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 12, "ls_in_l2_lkup") \ \ /* Logical switch egress stages. */ \ PIPELINE_STAGE(SWITCH, OUT, PRE_LB, 0, "ls_out_pre_lb") \ @@ -140,6 +143,7 @@ enum ovn_stage { #define REGBIT_CONNTRACK_DEFRAG "reg0[0]" #define REGBIT_CONNTRACK_COMMIT "reg0[1]" #define REGBIT_CONNTRACK_NAT "reg0[2]" +#define REGBIT_DHCP_OPTS_RESULT "reg0[3]" /* Returns an "enum ovn_stage" built from the arguments. */ static enum ovn_stage @@ -1329,6 +1333,77 @@ lsp_is_up(const struct nbrec_logical_switch_port *lsp) } static bool +build_dhcpv4_action(struct ovn_port *op, ovs_be32 offer_ip, + struct ds *options_action, struct ds *response_action) +{ + if (!op->nbs->dhcpv4_options) { + /* CMS has disabled native DHCPv4 for this lport. */ + return false; + } + + ovs_be32 host_ip, mask; + char *error = ip_parse_masked(op->nbs->dhcpv4_options->cidr, &host_ip, + &mask); + if (error || ((offer_ip ^ host_ip) & mask)) { + /* Either + * - cidr defined is invalid or + * - the offer ip of the logical port doesn't belong to the cidr + * defined in the DHCPv4 options. + * */ + free(error); + return false; + } + + const char *server_ip = smap_get( + &op->nbs->dhcpv4_options->options, "server_id"); + const char *server_mac = smap_get( + &op->nbs->dhcpv4_options->options, "server_mac"); + const char *lease_time = smap_get( + &op->nbs->dhcpv4_options->options, "lease_time"); + const char *router = smap_get( + &op->nbs->dhcpv4_options->options, "router"); + + if (!(server_ip && server_mac && lease_time && router)) { + /* "server_id", "server_mac", "lease_time" and "router" should be + * present in the dhcp_options. */ + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "Required DHCPv4 options not defined for lport - %s", + op->json_key); + return false; + } + + struct smap dhcpv4_options = SMAP_INITIALIZER(&dhcpv4_options); + smap_clone(&dhcpv4_options, &op->nbs->dhcpv4_options->options); + + /* server_mac is not DHCPv4 option, delete it from the smap. */ + smap_remove(&dhcpv4_options, "server_mac"); + char *netmask = xasprintf(IP_FMT, IP_ARGS(mask)); + smap_add(&dhcpv4_options, "netmask", netmask); + free(netmask); + + ds_put_format(options_action, + REGBIT_DHCP_OPTS_RESULT" = put_dhcp_opts(offerip = " + IP_FMT", ", IP_ARGS(offer_ip)); + struct smap_node *node; + SMAP_FOR_EACH(node, &dhcpv4_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(&dhcpv4_options); + return true; +} + +static bool has_stateful_acl(struct ovn_datapath *od) { for (size_t i = 0; i < od->nbs->n_acls; i++) { @@ -1636,6 +1711,35 @@ build_acls(struct ovn_datapath *od, struct hmap *lflows) 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 DHCPv4 options*/ + if (od->nbs && od->nbs->n_ports) { + for (size_t i = 0; i < od->nbs->n_ports; i++) { + if (od->nbs->ports[i]->dhcpv4_options) { + const char *server_id = smap_get( + &od->nbs->ports[i]->dhcpv4_options->options, "server_id"); + const char *server_mac = smap_get( + &od->nbs->ports[i]->dhcpv4_options->options, "server_mac"); + const char *lease_time = smap_get( + &od->nbs->ports[i]->dhcpv4_options->options, "lease_time"); + const char *router = smap_get( + &od->nbs->ports[i]->dhcpv4_options->options, "router"); + if (server_id && server_mac && lease_time && router) { + struct ds match = DS_EMPTY_INITIALIZER; + const char *actions = + has_stateful ? "ct_commit; next;" : "next;"; + ds_put_format(&match, "outport == \"%s\" && eth.src == %s " + "&& ip4.src == %s && udp && udp.src == 67 " + "&& udp.dst == 68", od->nbs->ports[i]->name, + server_mac, server_id); + ovn_lflow_add( + lflows, od, S_SWITCH_OUT_ACL, 34000, ds_cstr(&match), + actions); + } + } + } + } } static void @@ -1814,8 +1918,8 @@ 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. - * (priority 100). */ + /* Ingress table 9: ARP/ND responder, skip requests coming from localnet + * ports. (priority 100). */ HMAP_FOR_EACH (op, key_node, ports) { if (!op->nbs) { continue; @@ -1829,7 +1933,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, } } - /* Ingress table 5: ARP/ND responder, reply for known IPs. + /* Ingress table 9: ARP/ND responder, reply for known IPs. * (priority 50). */ HMAP_FOR_EACH (op, key_node, ports) { if (!op->nbs) { @@ -1916,7 +2020,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, } } - /* Ingress table 5: ARP/ND responder, by default goto next. + /* Ingress table 9: ARP/ND responder, by default goto next. * (priority 0)*/ HMAP_FOR_EACH (od, key_node, datapaths) { if (!od->nbs) { @@ -1926,7 +2030,76 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, ovn_lflow_add(lflows, od, S_SWITCH_IN_ARP_ND_RSP, 0, "1", "next;"); } - /* Ingress table 6: Destination lookup, broadcast and multicast handling + /* Logical switch ingress table 10 and 11: 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; + } + + if (!op->nbs->dhcpv4_options) { + /* CMS has disabled native DHCPv4 for this lport. */ + 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; + } + + 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_dhcpv4_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 REGBIT_DHCP_OPTS_RESULT is set, it means the + * put_dhcp_opts action is successful */ + ds_put_cstr(&match, " && "REGBIT_DHCP_OPTS_RESULT); + 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 10 and 11: 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 12: Destination lookup, broadcast and multicast handling * (priority 100). */ HMAP_FOR_EACH (op, key_node, ports) { if (!op->nbs) { @@ -1946,7 +2119,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 12: Destination lookup, unicast handling (priority 50), */ HMAP_FOR_EACH (op, key_node, ports) { if (!op->nbs) { continue; @@ -1983,7 +2156,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, } } - /* Ingress table 6: Destination lookup for unknown MACs (priority 0). */ + /* Ingress table 12: Destination lookup for unknown MACs (priority 0). */ HMAP_FOR_EACH (od, key_node, datapaths) { if (!od->nbs) { continue; @@ -2933,6 +3106,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 * @@ -3101,6 +3345,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); ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_address_set); add_column_noalert(ovnsb_idl_loop.idl, &sbrec_address_set_col_name); @@ -3118,6 +3366,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); diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema index ee7c2c6..8bb1cd4 100644 --- a/ovn/ovn-nb.ovsschema +++ b/ovn/ovn-nb.ovsschema @@ -1,7 +1,7 @@ { "name": "OVN_Northbound", - "version": "3.2.0", - "cksum": "1784604034 7539", + "version": "3.3.0", + "cksum": "1364201762 8258", "tables": { "Logical_Switch": { "columns": { @@ -48,6 +48,11 @@ "max": "unlimited"}}, "up": {"type": {"key": "boolean", "min": 0, "max": 1}}, "enabled": {"type": {"key": "boolean", "min": 0, "max": 1}}, + "dhcpv4_options": {"type": {"key": {"type": "uuid", + "refTable": "DHCP_Options", + "refType": "weak"}, + "min": 0, + "max": 1}}, "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, @@ -148,6 +153,15 @@ "snat", "dnat_and_snat" ]]}}}}, - "isRoot": false} + "isRoot": false}, + "DHCP_Options": { + "columns": { + "cidr": {"type": "string"}, + "options": {"type": {"key": "string", "value": "string", + "min": 0, "max": "unlimited"}}, + "external_ids": { + "type": {"key": "string", "value": "string", + "min": 0, "max": "unlimited"}}}, + "isRoot": true} } } diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml index ff2e695..646b726 100644 --- a/ovn/ovn-nb.xml +++ b/ovn/ovn-nb.xml @@ -502,6 +502,12 @@ + + This column defines the DHCPv4 Options to be included by the + ovn-controller when it replies to the DHCPv4 requests. + Please see the table. + + See External IDs at the beginning of this document. @@ -921,4 +927,268 @@ + +

+ OVN implements a native DHCPv4 support which caters to the common + use case of providing an IPv4 address to a booting instance by + providing stateless replies to DHCPv4 requests based on statically + configured address mappings. To do this it allows a short list of + DHCPv4 options to be configured and applied at each compute host + running ovn-controller. +

+ + +

+ The DHCPv4 options will be included if the logical port has the IPv4 + address in this . +

+
+ + +

+ CMS should define the set of DHCPv4 options as key/value pairs in the + column of this table. In order for the + ovn-controller to include these DHCPv4 options, the + of + should refer to an entry in this table. +

+ + +

+ Below are the supported DHCPv4 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 DHCPv4 options and their codes. +

+ + +

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

+ +

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

+
+ + +

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

+
+ + +

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

+
+ + +

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

+
+ + +

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

+
+ + +

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

+
+ + +

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

+
+ + +

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

+
+ + +

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

+
+ + +

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

+
+ + +

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

+
+ + +

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

+
+ + +

+ The DHCPv4 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 DHCPv4 option code for this option is 249. This option is + similar to classless_static_route supported by + Microsoft Windows DHCPv4 clients. +

+
+ + +

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

+
+
+ + + +

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

+ +

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

+
+ + +

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

+ +

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

+
+ + +

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

+ +

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

+
+ + +

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

+ +

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

+
+ + +

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

+ +

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

+
+ + +

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

+ +

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

+
+ + +

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

+ +

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

+
+ + +

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

+ +

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

+
+ + +

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

+ +

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

+
+
+ + +

+ DHCPv4 options "server_id", "server_mac", + "router" and "lease_time" are mandatory + options which CMS should define for OVN to support + native DHCPv4. +

+
+
+ + + + 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 b4b8501..ed358d5 100644 --- a/ovn/utilities/ovn-nbctl.8.xml +++ b/ovn/utilities/ovn-nbctl.8.xml @@ -382,6 +382,36 @@ +

DHCP Options commands

+ +
+
dhcp-options-create cidr [key=value]
+
+ Creates a new DHCP Options entry in the DHCP_Options table + with the specified cidr and optional external-ids. +
+ +
dhcp-options-list
+
+ Lists the DHCP Options entries. +
+ +
dhcp-options-del dhcp-option
+
+ Deletes the DHCP Options entry referred by dhcp-option UUID. +
+ +
dhcp-options-set-options dhcp-option [key=value]...
+
+ Set the DHCP Options for the dhcp-option UUID. +
+ +
dhcp-options-get-options dhcp-option
+
+ Lists the DHCP Options for the dhcp-option UUID. +
+
+

Database Commands

These commands query and modify the contents of ovsdb tables. They are a slight abstraction of the ovsdb interface and diff --git a/ovn/utilities/ovn-nbctl.c b/ovn/utilities/ovn-nbctl.c index ad70a05..b21e76c 100644 --- a/ovn/utilities/ovn-nbctl.c +++ b/ovn/utilities/ovn-nbctl.c @@ -69,6 +69,8 @@ static void run_prerequisites(struct ctl_command[], size_t n_commands, struct ovsdb_idl *); static bool do_nbctl(const char *args, struct ctl_command *, size_t n, struct ovsdb_idl *); +static const struct nbrec_dhcp_options *dhcp_options_get( + struct ctl_context *ctx, const char *id, bool must_exist); int main(int argc, char *argv[]) @@ -333,6 +335,9 @@ Logical switch port commands:\n\ lsp-set-options PORT KEY=VALUE [KEY=VALUE]...\n\ set options related to the type of PORT\n\ lsp-get-options PORT get the type specific options for PORT\n\ + lsp-set-dhcpv4-options PORT [DHCP_OPTIONS_UUID]\n\ + set dhcpv4 options for PORT\n\ + lsp-get-dhcpv4-options PORT get the dhcpv4 options for PORT\n\ \n\ Logical router commands:\n\ lr-add [ROUTER] create a logical router named ROUTER\n\ @@ -357,6 +362,19 @@ Route commands:\n\ remove routes from ROUTER\n\ lr-route-list ROUTER print routes for ROUTER\n\ \n\ +\n\ +DHCP Options commands:\n\ + dhcp-options-create CIDR [EXTERNAL_IDS]\n\ + create a DHCP options row with CIDR\n\ + dhcp-options-del DHCP_OPTIONS_UUID\n\ + delete DHCP_OPTIONS_UUID\n\ + dhcp-options-list \n\ + lists the DHCP_Options rows\n\ + dhcp-options-set-options DHCP_OPTIONS_UUID KEY=VALUE [KEY=VALUE]...\n\ + set DHCP options to the DHCP_OPTIONS_UUID\n\ + dhcp-options-get-options DHCO_OPTIONS_UUID \n\ + displays the DHCP options of th DHCP_OPTIONS_UUID\n\ +\n\ %s\ \n\ Options:\n\ @@ -1018,6 +1036,45 @@ nbctl_lsp_get_options(struct ctl_context *ctx) } } +static void +nbctl_lsp_set_dhcpv4_options(struct ctl_context *ctx) +{ + const char *id = ctx->argv[1]; + const struct nbrec_logical_switch_port *lsp; + + lsp = lsp_by_name_or_uuid(ctx, id, true); + const struct nbrec_dhcp_options *dhcp_opt = NULL; + if (ctx->argc == 3 ) { + dhcp_opt = dhcp_options_get(ctx, ctx->argv[2], true); + } + + if (dhcp_opt) { + ovs_be32 ip; + unsigned int plen; + char *error = ip_parse_cidr(dhcp_opt->cidr, &ip, &plen); + if (error){ + free(error); + ctl_fatal("DHCP options cidr '%s' is not IPv4", dhcp_opt->cidr); + return; + } + } + nbrec_logical_switch_port_set_dhcpv4_options(lsp, dhcp_opt); +} + +static void +nbctl_lsp_get_dhcpv4_options(struct ctl_context *ctx) +{ + const char *id = ctx->argv[1]; + const struct nbrec_logical_switch_port *lsp; + + lsp = lsp_by_name_or_uuid(ctx, id, true); + if (lsp->dhcpv4_options) { + ds_put_format(&ctx->output, UUID_FMT " (%s)\n", + UUID_ARGS(&lsp->dhcpv4_options->header_.uuid), + lsp->dhcpv4_options->cidr); + } +} + enum { DIR_FROM_LPORT, DIR_TO_LPORT @@ -1274,6 +1331,126 @@ nbctl_lr_list(struct ctl_context *ctx) free(nodes); } +static const struct nbrec_dhcp_options * +dhcp_options_get(struct ctl_context *ctx, const char *id, bool must_exist) +{ + struct uuid dhcp_opts_uuid; + const struct nbrec_dhcp_options *dhcp_opts = NULL; + if (uuid_from_string(&dhcp_opts_uuid, id)) { + dhcp_opts = nbrec_dhcp_options_get_for_uuid(ctx->idl, &dhcp_opts_uuid); + } + + if (!dhcp_opts && must_exist) { + ctl_fatal("%s: dhcp options UUID not found", id); + } + return dhcp_opts; +} + +static void +nbctl_dhcp_options_create(struct ctl_context *ctx) +{ + /* Validate the cidr */ + ovs_be32 ip; + unsigned int plen; + char *error = ip_parse_cidr(ctx->argv[1], &ip, &plen); + if (error){ + /* check if its IPv6 cidr */ + free(error); + struct in6_addr ipv6; + error = ipv6_parse_cidr(ctx->argv[1], &ipv6, &plen); + if (error) { + free(error); + ctl_fatal("Invalid cidr format '%s'", ctx->argv[1]); + return; + } + } + + struct nbrec_dhcp_options *dhcp_opts = nbrec_dhcp_options_insert(ctx->txn); + nbrec_dhcp_options_set_cidr(dhcp_opts, ctx->argv[1]); + + struct smap ext_ids = SMAP_INITIALIZER(&ext_ids); + for (size_t i = 2; i < ctx->argc; i++) { + char *key, *value; + value = xstrdup(ctx->argv[i]); + key = strsep(&value, "="); + if (value) { + smap_add(&ext_ids, key, value); + } + free(key); + } + + nbrec_dhcp_options_set_external_ids(dhcp_opts, &ext_ids); + smap_destroy(&ext_ids); +} + +static void +nbctl_dhcp_options_set_options(struct ctl_context *ctx) +{ + const struct nbrec_dhcp_options *dhcp_opts = dhcp_options_get( + ctx, ctx->argv[1], true); + + 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_dhcp_options_set_options(dhcp_opts, &dhcp_options); + smap_destroy(&dhcp_options); +} + +static void +nbctl_dhcp_options_get_options(struct ctl_context *ctx) +{ + const struct nbrec_dhcp_options *dhcp_opts = dhcp_options_get( + ctx, ctx->argv[1], true); + + struct smap_node *node; + SMAP_FOR_EACH(node, &dhcp_opts->options) { + ds_put_format(&ctx->output, "%s=%s\n", node->key, node->value); + } +} + +static void +nbctl_dhcp_options_del(struct ctl_context *ctx) +{ + bool must_exist = !shash_find(&ctx->options, "--if-exists"); + const char *id = ctx->argv[1]; + const struct nbrec_dhcp_options *dhcp_opts; + + dhcp_opts = dhcp_options_get(ctx, id, must_exist); + if (!dhcp_opts) { + return; + } + + nbrec_dhcp_options_delete(dhcp_opts); +} + +static void +nbctl_dhcp_options_list(struct ctl_context *ctx) +{ + const struct nbrec_dhcp_options *dhcp_opts; + struct smap dhcp_options; + + smap_init(&dhcp_options); + NBREC_DHCP_OPTIONS_FOR_EACH(dhcp_opts, ctx->idl) { + smap_add_format(&dhcp_options, dhcp_opts->cidr, UUID_FMT, + UUID_ARGS(&dhcp_opts->header_.uuid)); + } + const struct smap_node **nodes = smap_sort(&dhcp_options); + for (size_t i = 0; i < smap_count(&dhcp_options); i++) { + const struct smap_node *node = nodes[i]; + ds_put_format(&ctx->output, "%s\n", node->value); + } + smap_destroy(&dhcp_options); + free(nodes); +} + /* The caller must free the returned string. */ static char * normalize_ipv4_prefix(ovs_be32 ipv4, unsigned int plen) @@ -1879,6 +2056,11 @@ static const struct ctl_table_class tables[] = { {{&nbrec_table_address_set, &nbrec_address_set_col_name, NULL}, {NULL, NULL, NULL}}}, + {&nbrec_table_dhcp_options, + {{&nbrec_table_dhcp_options, NULL, + NULL}, + {NULL, NULL, NULL}}}, + {NULL, {{NULL, NULL, NULL}, {NULL, NULL, NULL}}} }; @@ -2125,6 +2307,10 @@ static const struct ctl_command_syntax nbctl_commands[] = { nbctl_lsp_set_options, NULL, "", RW }, { "lsp-get-options", 1, 1, "PORT", NULL, nbctl_lsp_get_options, NULL, "", RO }, + { "lsp-set-dhcpv4-options", 1, 2, "PORT [DHCP_OPT_UUID]", NULL, + nbctl_lsp_set_dhcpv4_options, NULL, "", RW }, + { "lsp-get-dhcpv4-options", 1, 1, "PORT", NULL, + nbctl_lsp_get_dhcpv4_options, NULL, "", RO }, /* logical router commands. */ { "lr-add", 0, 1, "[ROUTER]", NULL, nbctl_lr_add, NULL, @@ -2150,6 +2336,17 @@ static const struct ctl_command_syntax nbctl_commands[] = { { "lr-route-list", 1, 1, "ROUTER", NULL, nbctl_lr_route_list, NULL, "", RO }, + /* DHCP_Options commands */ + {"dhcp-options-create", 1, INT_MAX, "CIDR [EXTERNAL:IDS]", NULL, + nbctl_dhcp_options_create, NULL, "", RW }, + {"dhcp-options-del", 1, 1, "DHCP_OPT_UUID", NULL, + nbctl_dhcp_options_del, NULL, "", RW}, + {"dhcp-options-list", 0, 0, "", NULL, nbctl_dhcp_options_list, NULL, "", RO}, + {"dhcp-options-set-options", 1, INT_MAX, "DHCP_OPT_UUID KEY=VALUE [KEY=VALUE]...", + NULL, nbctl_dhcp_options_set_options, NULL, "", RW }, + {"dhcp-options-get-options", 1, 1, "DHCP_OPT_UUID", NULL, + nbctl_dhcp_options_get_options, NULL, "", RO }, + {NULL, 0, 0, NULL, NULL, NULL, NULL, "", RO}, }; diff --git a/tests/ovn.at b/tests/ovn.at index feb68d3..b3c7363 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -2936,6 +2936,287 @@ OVN_CLEANUP([hv1],[hv2]) AT_CLEANUP +AT_SETUP([ovn -- dhcpv4 : 1 HV, 2 LS, 2 LSPs/LS]) +AT_KEYWORDS([dhcpv4]) +AT_SKIP_IF([test $HAVE_PYTHON = no]) +ovn_start + +ovn-nbctl ls-add ls1 + +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 ls1 ls1-lp2 \ +-- lsp-set-addresses ls1-lp2 "f0:00:00:00:00:02 10.0.0.6 20.0.0.4" + +ovn-nbctl lsp-set-port-security ls1-lp2 "f0:00:00:00:00:02 10.0.0.6 20.0.0.4" + +ovn-nbctl ls-add ls2 +ovn-nbctl lsp-add ls2 ls2-lp1 \ +-- lsp-set-addresses ls2-lp1 "f0:00:00:00:00:03 30.0.0.6 40.0.0.4" +ovn-nbctl lsp-set-port-security ls2-lp1 "f0:00:00:00:00:03 30.0.0.6 40.0.0.4" +ovn-nbctl lsp-add ls2 ls2-lp2 \ +-- lsp-set-addresses ls2-lp2 "f0:00:00:00:00:04 30.0.0.7" +ovn-nbctl lsp-set-port-security ls2-lp2 "f0:00:00:00:00:04 30.0.0.7" + +ovn-nbctl -- --id=@d1 create DHCP_Options cidr=10.0.0.0/24 \ +options="\"server_id\"=\"10.0.0.1\" \"server_mac\"=\"ff:10:00:00:00:01\" \ +\"lease_time\"=\"3600\" \"router\"=\"10.0.0.1\"" \ +-- add Logical_Switch_Port ls1-lp1 dhcpv4_options @d1 \ +-- add Logical_Switch_Port ls1-lp2 dhcpv4_options @d1 + +ovn-nbctl -- --id=@d2 create DHCP_Options cidr=30.0.0.0/24 \ +options="\"server_id\"=\"30.0.0.1\" \"server_mac\"=\"ff:10:00:00:00:02\" \ +\"lease_time\"=\"3600\"" -- add Logical_Switch_Port ls2-lp2 dhcpv4_options @d2 + +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=ls1-lp2 \ + 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=ls2-lp1 \ + options:tx_pcap=hv1/vif3-tx.pcap \ + options:rxq_pcap=hv1/vif3-rx.pcap \ + ofport-request=3 + +ovs-vsctl -- add-port br-int hv1-vif4 -- \ + set interface hv1-vif4 external-ids:iface-id=ls2-lp2 \ + options:tx_pcap=hv1/vif4-tx.pcap \ + options:rxq_pcap=hv1/vif4-rx.pcap \ + ofport-request=4 + +ovn_populate_arp + +sleep 2 + +as hv1 ovs-vsctl show + +trim_zeros() { + sed 's/\(00\)\{1,\}$//' +} + +# This shell function sends a DHCP request packet +# test_dhcp INPORT SRC_MAC DHCP_TYPE OFFER_IP ... +test_dhcp() { + local inport=$1 src_mac=$2 dhcp_type=$3 offer_ip=$4 + 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 + + if test $offer_ip != 0; then + local srv_mac=$5 srv_ip=$6 expected_dhcp_opts=$7 + # total IP length will be the IP length of the request packet + # (which is 272 in our case) + 8 (padding bytes) + (expected_dhcp_opts / 2) + ip_len=`expr 280 + ${#expected_dhcp_opts} / 2` + udp_len=`expr $ip_len - 20` + printf -v ip_len "%x" $ip_len + printf -v udp_len "%x" $udp_len + # $ip_len var will be in 3 digits i.e 134. So adding a '0' before $ip_len + local reply=${src_mac}${srv_mac}080045100${ip_len}000000008011XXXX${srv_ip}${offer_ip} + # udp header and dhcp header. + # $udp_len var will be in 3 digits. So adding a '0' before $udp_len + reply+=004300440${udp_len}0000020106006359aa760000000000000000 + # your ip address + reply+=${offer_ip} + # next server ip address, relay agent ip address, client mac address + reply+=0000000000000000${src_mac} + # client hardware padding + reply+=00000000000000000000 + # server hostname + reply+=0000000000000000000000000000000000000000000000000000000000000000 + reply+=0000000000000000000000000000000000000000000000000000000000000000 + # boot file name + reply+=0000000000000000000000000000000000000000000000000000000000000000 + reply+=0000000000000000000000000000000000000000000000000000000000000000 + reply+=0000000000000000000000000000000000000000000000000000000000000000 + reply+=0000000000000000000000000000000000000000000000000000000000000000 + # dhcp magic cookie + reply+=63825363 + # dhcp message type + local dhcp_reply_type=02 + if test $dhcp_type = 03; then + dhcp_reply_type=05 + fi + reply+=3501${dhcp_reply_type}${expected_dhcp_opts}00000000ff00000000 + echo $reply >> $inport.expected + else + shift; shift; shift; shift; + for outport; do + echo $request | trim_zeros >> $outport.expected + done + fi + 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 +} + +ip_to_hex() { + printf "%02x%02x%02x%02x" "$@" +} + +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 DHCPDISCOVER. +offer_ip=`ip_to_hex 10 0 0 4` +server_ip=`ip_to_hex 10 0 0 1` +expected_dhcp_opts=0104ffffff0003040a00000136040a000001330400000e10 +test_dhcp 1 f00000000001 01 $offer_ip ff1000000001 $server_ip $expected_dhcp_opts + +# NXT_RESUMEs should be 1. +OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) + +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets +cat 1.expected | cut -c -48 > expout +AT_CHECK([cat 1.packets | cut -c -48], [0], [expout]) +# Skipping the IPv4 checksum. +cat 1.expected | cut -c 53- > expout +AT_CHECK([cat 1.packets | cut -c 53-], [0], [expout]) + +# 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 to test. +reset_pcap_file hv1-vif1 hv1/vif1 +reset_pcap_file hv1-vif2 hv1/vif2 +rm -f 1.expected +rm -f 2.expected + +# Send DHCPREQUEST. +offer_ip=`ip_to_hex 10 0 0 6` +server_ip=`ip_to_hex 10 0 0 1` +expected_dhcp_opts=0104ffffff0003040a00000136040a000001330400000e10 +test_dhcp 2 f00000000002 03 $offer_ip ff1000000001 $server_ip $expected_dhcp_opts + +# NXT_RESUMEs should be 2. +OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) + +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets +cat 2.expected | cut -c -48 > expout +AT_CHECK([cat 2.packets | cut -c -48], [0], [expout]) +# Skipping the IPv4 checksum. +cat 2.expected | cut -c 53- > expout +AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout]) + +reset_pcap_file hv1-vif1 hv1/vif1 +reset_pcap_file hv1-vif2 hv1/vif2 +rm -f 1.expected +rm -f 2.expected + +# Send Invalid DHCPv4 packet on ls1-lp2. It should be received by ovn-controller +# but should be resumed without the reply. +# ls1-lp1 (vif1-tx.pcap) should receive the DHCPv4 request packet twice, +# one from ovn-controller and the other from "ovs-ofctl resume." +offer_ip=0 +test_dhcp 2 f00000000002 08 $offer_ip 1 1 + +# NXT_RESUMEs should be 3. +OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) + +# vif1-tx.pcap should have received the DHCPv4 (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]) + +reset_pcap_file hv1-vif1 hv1/vif1 +reset_pcap_file hv1-vif2 hv1/vif2 +rm -f 1.expected +rm -f 2.expected + +# Send DHCPv4 packet on ls2-lp1. It doesn't have any DHCPv4 options defined. +# ls2-lp2 (vif4-tx.pcap) should receive the DHCPv4 request packet once. + +test_dhcp 3 f00000000003 01 0 4 + +# Send DHCPv4 packet on ls2-lp2. "router" DHCPv4 option is not defined for +# this lport. +test_dhcp 4 f00000000004 01 0 3 + +# NXT_RESUMEs should be 3. +OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) + +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif3-tx.pcap | trim_zeros > 3.packets +cat 3.expected > expout +AT_CHECK([cat 3.packets], [0], [expout]) + +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif4-tx.pcap | trim_zeros > 4.packets +cat 4.expected > expout +AT_CHECK([cat 4.packets], [0], [expout]) + +as hv1 +OVS_APP_EXIT_AND_WAIT([ovn-controller]) +OVS_APP_EXIT_AND_WAIT([ovs-vswitchd]) +OVS_APP_EXIT_AND_WAIT([ovsdb-server]) + +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])