From patchwork Sat Jul 30 10:12:20 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Numan Siddique X-Patchwork-Id: 654264 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 3s1hH15JFSz9sDf for ; Sat, 30 Jul 2016 20:12:29 +1000 (AEST) Received: from archives.nicira.com (localhost [127.0.0.1]) by archives.nicira.com (Postfix) with ESMTP id C6E5E11600; Sat, 30 Jul 2016 03:12:28 -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 58074115BC for ; Sat, 30 Jul 2016 03:12:27 -0700 (PDT) Received: from bar6.cudamail.com (localhost [127.0.0.1]) by mx3v3.cudamail.com (Postfix) with ESMTPS id CF7D516197E for ; Sat, 30 Jul 2016 04:12:26 -0600 (MDT) X-ASG-Debug-ID: 1469873544-0b32374d32d3c10001-byXFYA Received: from mx3-pf1.cudamail.com ([192.168.14.2]) by bar6.cudamail.com with ESMTP id FMEJlF1bCnIL2hvu (version=TLSv1 cipher=DHE-RSA-AES256-SHA bits=256 verify=NO) for ; Sat, 30 Jul 2016 04:12:24 -0600 (MDT) X-Barracuda-Envelope-From: nusiddiq@redhat.com X-Barracuda-RBL-Trusted-Forwarder: 192.168.14.2 Received: from unknown (HELO mx1.redhat.com) (209.132.183.28) by mx3-pf1.cudamail.com with ESMTPS (DHE-RSA-AES256-SHA encrypted); 30 Jul 2016 10:12:24 -0000 Received-SPF: pass (mx3-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-mx13.intmail.prod.int.phx2.redhat.com (int-mx13.intmail.prod.int.phx2.redhat.com [10.5.11.26]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id B60D43F725 for ; Sat, 30 Jul 2016 10:12:23 +0000 (UTC) Received: from nusiddiq.blr.redhat.com (ovpn-116-22.phx2.redhat.com [10.3.116.22]) by int-mx13.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id u6UACL4O011130 for ; Sat, 30 Jul 2016 06:12:22 -0400 X-CudaMail-Envelope-Sender: nusiddiq@redhat.com From: Numan Siddique X-CudaMail-Whitelist-To: dev@openvswitch.org X-CudaMail-MID: CM-V1-729001867 X-CudaMail-DTE: 073016 X-CudaMail-Originating-IP: 209.132.183.28 To: ovs dev X-ASG-Orig-Subj: [##CM-V1-729001867##][PATCH v3 1/2] ovn-controller: Add 'put_dhcpv6_opts' action in ovn-controller In-Reply-To: Organization: Red Hat Message-ID: <7b0def2e-e9d0-616c-b178-9bcfd68b6687@redhat.com> Date: Sat, 30 Jul 2016 15:42:20 +0530 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Thunderbird/45.2.0 MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.68 on 10.5.11.26 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.30]); Sat, 30 Jul 2016 10:12:23 +0000 (UTC) X-Barracuda-Connect: UNKNOWN[192.168.14.2] X-Barracuda-Start-Time: 1469873544 X-Barracuda-Encrypted: DHE-RSA-AES256-SHA X-Barracuda-URL: https://web.cudamail.com:443/cgi-mod/mark.cgi X-ASG-Whitelist: Header =?UTF-8?B?eFwtY3VkYW1haWxcLXdoaXRlbGlzdFwtdG8=?= X-Barracuda-BRTS-Status: 1 X-Virus-Scanned: by bsmtpd at cudamail.com Subject: [ovs-dev] [PATCH v3 1/2] ovn-controller: Add 'put_dhcpv6_opts' action in ovn-controller 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" This patch adds a new OVN action 'put_dhcpv6_opts' to support native DHCPv6 in OVN. ovn-controller parses this action and adds a NXT_PACKET_IN2 OF flow with 'pause' flag set and the DHCPv6 options stored in 'userdata' field. When the valid DHCPv6 packet is received by ovn-controller, it frames a new DHCPv6 reply packet with the DHCPv6 options present in the 'userdata' field and resumes the packet and stores 1 in the 1-bit subfield. If the packet is invalid, it resumes the packet without any modifying and stores 0 in the 1-bit subfield. Eg. reg0[3] = put_dhcpv6_opts(ia_addr = aef0::4, server_id = 00:00:00:00:10:02, dns_server = {ae70::1,ae70::2}....) A new 'DHCPv6_Options' table is added in SB DB which stores the supported DHCPv6 options with DHCPv6 code and type. ovn-northd is expected to popule this table. Upcoming patch will add logical flows using this action. Signed-off-by: Numan Siddique --- include/ovn/actions.h | 12 ++ ovn/controller/lflow.c | 16 ++- ovn/controller/pinctrl.c | 295 +++++++++++++++++++++++++++++++++++++++++++++++ ovn/lib/actions.c | 114 ++++++++++++++++++ ovn/lib/ovn-dhcp.h | 71 ++++++++++++ ovn/ovn-sb.ovsschema | 15 ++- ovn/ovn-sb.xml | 129 +++++++++++++++++++++ tests/ovn.at | 11 ++ tests/test-ovn.c | 15 ++- 9 files changed, 672 insertions(+), 6 deletions(-) diff --git a/include/ovn/actions.h b/include/ovn/actions.h index 114c71e..b772518 100644 --- a/include/ovn/actions.h +++ b/include/ovn/actions.h @@ -78,6 +78,15 @@ enum action_opcode { * The actions, in OpenFlow 1.3 format, follow the action_header. */ ACTION_OPCODE_NA, + + /* "result = put_dhcpv6_opts(option, ...)". + * + * Arguments follow the action_header, in this format: + * - A 32-bit or 64-bit OXM header designating the result field. + * - A 32-bit integer specifying a bit offset within the result field. + * - Any number of DHCPv6 options. + */ + ACTION_OPCODE_PUT_DHCPV6_OPTS, }; /* Header. */ @@ -95,6 +104,9 @@ struct action_params { /* hmap of 'struct dhcp_opts_map' to support 'put_dhcp_opts' action */ const struct hmap *dhcp_opts; + /* hmap of 'struct dhcp_opts_map' to support 'put_dhcpv6_opts' action */ + const struct hmap *dhcpv6_opts; + /* Looks up logical port 'port_name'. If found, stores its port number in * '*portp' and returns true; otherwise, returns false. */ bool (*lookup_port)(const void *aux, const char *port_name, diff --git a/ovn/controller/lflow.c b/ovn/controller/lflow.c index a4f3322..26c0e3b 100644 --- a/ovn/controller/lflow.c +++ b/ovn/controller/lflow.c @@ -338,6 +338,7 @@ static void consider_logical_flow(const struct lport_index *lports, struct group_table *group_table, const struct simap *ct_zones, struct hmap *dhcp_opts_p, + struct hmap *dhcpv6_opts_p, uint32_t *conj_id_ofs_p); static bool @@ -390,17 +391,25 @@ add_logical_flows(struct controller_ctx *ctx, const struct lport_index *lports, } struct hmap dhcp_opts = HMAP_INITIALIZER(&dhcp_opts); + struct hmap dhcpv6_opts = HMAP_INITIALIZER(&dhcpv6_opts); const struct sbrec_dhcp_options *dhcp_opt_row; SBREC_DHCP_OPTIONS_FOR_EACH(dhcp_opt_row, ctx->ovnsb_idl) { dhcp_opt_add(&dhcp_opts, dhcp_opt_row->name, dhcp_opt_row->code, dhcp_opt_row->type); } + + const struct sbrec_dhcpv6_options *dhcpv6_opt_row; + SBREC_DHCPV6_OPTIONS_FOR_EACH(dhcpv6_opt_row, ctx->ovnsb_idl) { + dhcp_opt_add(&dhcpv6_opts, dhcpv6_opt_row->name, dhcpv6_opt_row->code, + dhcpv6_opt_row->type); + } + if (full_logical_flow_processing) { SBREC_LOGICAL_FLOW_FOR_EACH (lflow, ctx->ovnsb_idl) { consider_logical_flow(lports, mcgroups, lflow, local_datapaths, patched_datapaths, group_table, ct_zones, - &dhcp_opts, &conj_id_ofs); + &dhcp_opts, &dhcpv6_opts, &conj_id_ofs); } full_logical_flow_processing = false; } else { @@ -422,12 +431,13 @@ add_logical_flows(struct controller_ctx *ctx, const struct lport_index *lports, consider_logical_flow(lports, mcgroups, lflow, local_datapaths, patched_datapaths, group_table, ct_zones, - &dhcp_opts, &conj_id_ofs); + &dhcp_opts, &dhcpv6_opts, &conj_id_ofs); } } } dhcp_opts_destroy(&dhcp_opts); + dhcp_opts_destroy(&dhcpv6_opts); } static void @@ -439,6 +449,7 @@ consider_logical_flow(const struct lport_index *lports, struct group_table *group_table, const struct simap *ct_zones, struct hmap *dhcp_opts_p, + struct hmap *dhcpv6_opts_p, uint32_t *conj_id_ofs_p) { /* Determine translation of logical table IDs to physical table IDs. */ @@ -511,6 +522,7 @@ consider_logical_flow(const struct lport_index *lports, struct action_params ap = { .symtab = &symtab, .dhcp_opts = dhcp_opts_p, + .dhcpv6_opts = dhcpv6_opts_p, .lookup_port = lookup_port_cb, .aux = &aux, .ct_zones = ct_zones, diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c index 416dad6..2a25ca1 100644 --- a/ovn/controller/pinctrl.c +++ b/ovn/controller/pinctrl.c @@ -37,6 +37,7 @@ #include "ovn-controller.h" #include "ovn/actions.h" #include "ovn/lib/logical-fields.h" +#include "ovn/lib/ovn-dhcp.h" #include "ovn/lib/ovn-util.h" #include "poll-loop.h" #include "rconn.h" @@ -365,6 +366,295 @@ exit: } } +static bool +compose_out_dhcpv6_opts(struct ofpbuf *userdata, + struct ofpbuf *out_dhcpv6_opts, ovs_be32 iaid) +{ + while (userdata->size) { + struct dhcp_opt6_header *userdata_opt = ofpbuf_try_pull( + userdata, sizeof *userdata_opt); + if (!userdata_opt) { + return false; + } + + uint8_t *userdata_opt_data = ofpbuf_try_pull(userdata, + userdata_opt->len); + if (!userdata_opt_data) { + return false; + } + + switch(userdata_opt->code) { + case DHCPV6_OPT_SERVER_ID_CODE: + { + /* The Server Identifier option is used to carry a DUID + * identifying a server between a client and a server. + * See RFC 3315 Sec 9 and Sec 22.3 + * + * We will use DUID Based on Link-layer Address [DUID-LL] + */ + + struct dhcpv6_opt_server_id *opt_server_id = ofpbuf_put_zeros( + out_dhcpv6_opts, sizeof *opt_server_id); + + opt_server_id->opt.code = htons(DHCPV6_OPT_SERVER_ID_CODE); + opt_server_id->opt.len = htons(userdata_opt->len + 4); + opt_server_id->duid_type = htons(DHCPV6_DUID_LL); + opt_server_id->hw_type = htons(DHCPV6_HW_TYPE_ETH); + memcpy(&opt_server_id->mac, userdata_opt_data, + sizeof(struct eth_addr)); + break; + } + + case DHCPV6_OPT_IA_ADDR_CODE: + { + if (userdata_opt->len != sizeof(struct in6_addr)) { + return false; + } + + /* IA Address option is used to specify IPv6 addresses associated + * with an IA_NA or IA_TA. The IA Address option must be + * encapsulated in the Options field of an IA_NA or IA_TA option. + * + * We will encapsulate the IA Address within the IA_NA option. + * Please see RFC 3315 section 22.5 and 22.6 + */ + struct dhcpv6_opt_ia_na *opt_ia_na = ofpbuf_put_zeros( + out_dhcpv6_opts, sizeof *opt_ia_na); + opt_ia_na->opt.code = htons(DHCPV6_OPT_IA_NA_CODE); + /* IA_NA length (in bytes)- + * IAID - 4 + * T1 - 4 + * T2 - 4 + * IA Address - sizeof(struct dhcpv6_opt_ia_addr) + */ + opt_ia_na->opt.len = htons(12 + sizeof(struct dhcpv6_opt_ia_addr)); + opt_ia_na->iaid = iaid; + /* Set the lifetime of the address(es) to infinity */ + opt_ia_na->t1 = htonl(UINT32_MAX); + opt_ia_na->t2 = htonl(UINT32_MAX); + + struct dhcpv6_opt_ia_addr *opt_ia_addr = ofpbuf_put_zeros( + out_dhcpv6_opts, sizeof *opt_ia_addr); + opt_ia_addr->opt.code = htons(DHCPV6_OPT_IA_ADDR_CODE); + opt_ia_addr->opt.len = htons(userdata_opt->len + 8); + memcpy(opt_ia_addr->ipv6.s6_addr, userdata_opt_data, + userdata_opt->len); + opt_ia_addr->t1 = htonl(UINT32_MAX); + opt_ia_addr->t2 = htonl(UINT32_MAX); + break; + } + + case DHCPV6_OPT_DNS_SERVER_CODE: + { + struct dhcpv6_opt_header *opt_dns = ofpbuf_put_zeros( + out_dhcpv6_opts, sizeof *opt_dns); + opt_dns->code = htons(DHCPV6_OPT_DNS_SERVER_CODE); + opt_dns->len = htons(userdata_opt->len); + ofpbuf_put(out_dhcpv6_opts, userdata_opt_data, userdata_opt->len); + break; + } + + case DHCPV6_OPT_DOMAIN_SEARCH_CODE: + { + struct dhcpv6_opt_header *opt_dsl = ofpbuf_put_zeros( + out_dhcpv6_opts, sizeof *opt_dsl); + opt_dsl->code = htons(DHCPV6_OPT_DOMAIN_SEARCH_CODE); + opt_dsl->len = htons(userdata_opt->len + 2); + uint8_t *data = ofpbuf_put_zeros(out_dhcpv6_opts, + userdata_opt->len + 2); + *data = userdata_opt->len; + memcpy(data + 1, userdata_opt_data, userdata_opt->len); + break; + } + + default: + return false; + } + } + return true; +} + +static void +pinctrl_handle_put_dhcpv6_opts( + struct dp_packet *pkt_in, struct ofputil_packet_in *pin, + struct ofpbuf *userdata, struct ofpbuf *continuation OVS_UNUSED) +{ + enum ofp_version version = rconn_get_version(swconn); + enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version); + struct dp_packet *pkt_out_ptr = NULL; + uint32_t success = 0; + + /* Parse result field. */ + const struct mf_field *f; + enum ofperr ofperr = nx_pull_header(userdata, &f, NULL); + if (ofperr) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "bad result OXM (%s)", ofperr_to_string(ofperr)); + goto exit; + } + + /* Parse result offset. */ + ovs_be32 *ofsp = ofpbuf_try_pull(userdata, sizeof *ofsp); + if (!ofsp) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "offset not present in the userdata"); + goto exit; + } + + /* Check that the result is valid and writable. */ + struct mf_subfield dst = { .field = f, .ofs = ntohl(*ofsp), .n_bits = 1 }; + ofperr = mf_check_dst(&dst, NULL); + if (ofperr) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "bad result bit (%s)", ofperr_to_string(ofperr)); + goto exit; + } + + if (!userdata->size) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "DHCPv6 options not present in the userdata"); + goto exit; + } + + struct udp_header *in_udp = dp_packet_l4(pkt_in); + const uint8_t *in_dhcpv6_data = dp_packet_get_udp_payload(pkt_in); + uint8_t out_dhcpv6_msg_type; + switch(*in_dhcpv6_data) { + case DHCPV6_MSG_TYPE_SOLICIT: + out_dhcpv6_msg_type = DHCPV6_MSG_TYPE_ADVT; + break; + + case DHCPV6_MSG_TYPE_REQUEST: + case DHCPV6_MSG_TYPE_CONFIRM: + case DHCPV6_MSG_TYPE_DECLINE: + out_dhcpv6_msg_type = DHCPV6_MSG_TYPE_REPLY; + break; + + default: + /* Invalid or unsupported DHCPv6 message type */ + goto exit; + } + + /* Skip 4 bytes (message type (1 byte) + transaction ID (3 bytes). */ + in_dhcpv6_data += 4; + /* We need to extract IAID from the IA-NA option of the client's DHCPv6 + * solicit/request/confirm packet and copy the same IAID in the Server's + * response. */ + ovs_be32 iaid = 0; + struct dhcpv6_opt_header const *in_opt_client_id = NULL; + uint8_t *end = (uint8_t *)in_udp + ntohs(in_udp->udp_len); + while (in_dhcpv6_data < end) { + struct dhcpv6_opt_header const *in_opt = + (struct dhcpv6_opt_header *)in_dhcpv6_data; + switch(ntohs(in_opt->code)) { + case DHCPV6_OPT_IA_NA_CODE: + { + struct dhcpv6_opt_ia_na *opt_ia_na = ( + struct dhcpv6_opt_ia_na *)in_opt; + iaid = opt_ia_na->iaid; + break; + } + + case DHCPV6_OPT_CLIENT_ID_CODE: + in_opt_client_id = in_opt; + break; + + default: + break; + } + in_dhcpv6_data += ((sizeof *in_opt) + ntohs(in_opt->len)); + } + + if (!in_opt_client_id) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "DHCPv6 option - Client id not present in the " + " DHCPv6 packet"); + goto exit; + } + + if (!iaid) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "DHCPv6 option - IA NA not present in the " + " DHCPv6 packet"); + goto exit; + } + + uint64_t out_ofpacts_dhcpv6_opts_stub[256 / 8]; + struct ofpbuf out_dhcpv6_opts = + OFPBUF_STUB_INITIALIZER(out_ofpacts_dhcpv6_opts_stub); + + if (!compose_out_dhcpv6_opts(userdata, &out_dhcpv6_opts, iaid)) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "Invalid userdata"); + goto exit; + } + + uint16_t new_l4_size = UDP_HEADER_LEN + 4 + sizeof(*in_opt_client_id) + \ + ntohs(in_opt_client_id->len) + out_dhcpv6_opts.size; + size_t new_packet_size = pkt_in->l4_ofs + new_l4_size; + + struct dp_packet pkt_out; + dp_packet_init(&pkt_out, new_packet_size); + dp_packet_clear(&pkt_out); + dp_packet_prealloc_tailroom(&pkt_out, new_packet_size); + pkt_out_ptr = &pkt_out; + + /* Copy the L2 and L3 headers from the pkt_in as they would remain same*/ + dp_packet_put( + &pkt_out, dp_packet_pull(pkt_in, pkt_in->l4_ofs), pkt_in->l4_ofs); + + pkt_out.l2_5_ofs = pkt_in->l2_5_ofs; + pkt_out.l2_pad_size = pkt_in->l2_pad_size; + pkt_out.l3_ofs = pkt_in->l3_ofs; + pkt_out.l4_ofs = pkt_in->l4_ofs; + + /* Pull the dhcpv6 message type and transaction id from the pkt_in. + * Need to preserve the transaction id in the DHCPv6 reply packet*/ + struct udp_header *out_udp = dp_packet_put( + &pkt_out, dp_packet_pull(pkt_in, UDP_HEADER_LEN), UDP_HEADER_LEN); + uint8_t *out_dhcpv6 = dp_packet_put(&pkt_out, dp_packet_pull(pkt_in, 4), 4); + + /* Set the proper dhcpv6 message type */ + *out_dhcpv6 = out_dhcpv6_msg_type; + + /* Copy the Client Identifier */ + dp_packet_put(&pkt_out, in_opt_client_id, + sizeof(*in_opt_client_id) + ntohs(in_opt_client_id->len)); + + /* Copy the DHCPv6 Options */ + dp_packet_put(&pkt_out, out_dhcpv6_opts.data, out_dhcpv6_opts.size); + out_udp->udp_len = htons(new_l4_size); + out_udp->udp_csum = 0; + + struct ovs_16aligned_ip6_hdr *out_ip6 = dp_packet_l3(&pkt_out); + out_ip6->ip6_ctlun.ip6_un1.ip6_un1_plen = out_udp->udp_len; + + uint32_t csum; + csum = packet_csum_pseudoheader6(dp_packet_l3(&pkt_out)); + csum = csum_continue(csum, out_udp, dp_packet_size(&pkt_out) - + ((const unsigned char *)out_udp - + (const unsigned char *)dp_packet_l2(&pkt_out))); + out_udp->udp_csum = csum_finish(csum); + if (!out_udp->udp_csum) { + out_udp->udp_csum = htons(0xffff); + } + + pin->packet = dp_packet_data(&pkt_out); + pin->packet_len = dp_packet_size(&pkt_out); + ofpbuf_uninit(&out_dhcpv6_opts); + success = 1; +exit: + if (!ofperr) { + union mf_subvalue sv; + sv.u8_val = success; + mf_write_subfield(&dst, &sv, &pin->flow_metadata); + } + queue_msg(ofputil_encode_resume(pin, continuation, proto)); + if (pkt_out_ptr) { + dp_packet_uninit(pkt_out_ptr); + } +} + static void process_packet_in(const struct ofp_header *msg) { @@ -410,6 +700,11 @@ process_packet_in(const struct ofp_header *msg) pinctrl_handle_put_dhcp_opts(&packet, &pin, &userdata, &continuation); break; + case ACTION_OPCODE_PUT_DHCPV6_OPTS: + pinctrl_handle_put_dhcpv6_opts(&packet, &pin, &userdata, + &continuation); + break; + case ACTION_OPCODE_NA: pinctrl_handle_na(&headers, &pin.flow_metadata, &userdata); break; diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c index fd5a867..a6d3c37 100644 --- a/ovn/lib/actions.c +++ b/ovn/lib/actions.c @@ -47,6 +47,8 @@ struct action_context { static bool parse_action(struct action_context *); static void parse_put_dhcp_opts_action(struct action_context *, const struct expr_field *dst); +static void parse_put_dhcpv6_opts_action(struct action_context *ctx, + const struct expr_field *dst); static bool action_error_handle_common(struct action_context *ctx) @@ -132,6 +134,12 @@ parse_set_action(struct action_context *ctx) lexer_get(ctx->lexer); /* Skip put_dhcp_opts. */ lexer_get(ctx->lexer); /* Skip '('. */ parse_put_dhcp_opts_action(ctx, &dst); + } else if (ctx->lexer->token.type == LEX_T_ID + && !strcmp(ctx->lexer->token.s, "put_dhcpv6_opts") + && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) { + lexer_get(ctx->lexer); /* Skip put_dhcpv6_opts. */ + lexer_get(ctx->lexer); /* Skip '('. */ + parse_put_dhcpv6_opts_action(ctx, &dst); } else { error = expr_parse_assignment( ctx->lexer, &dst, ctx->ap->symtab, ctx->ap->lookup_port, @@ -627,6 +635,112 @@ parse_put_dhcp_opts_action(struct action_context *ctx, finish_controller_op(ctx->ofpacts, oc_offset); } +static void +parse_dhcpv6_opt(struct action_context *ctx, struct ofpbuf *ofpacts) +{ + if (ctx->lexer->token.type != LEX_T_ID) { + action_syntax_error(ctx, NULL); + return; + } + + const struct dhcp_opts_map *dhcp_opt = dhcp_opts_find( + ctx->ap->dhcpv6_opts, ctx->lexer->token.s); + + if (!dhcp_opt) { + action_syntax_error(ctx, "expecting DHCPv6 option name"); + return; + } + + lexer_get(ctx->lexer); + if (!action_force_match(ctx, LEX_T_EQUALS)) { + return; + } + + struct expr_constant_set cs; + memset(&cs, 0, sizeof(struct expr_constant_set)); + char *error = expr_parse_constant_set(ctx->lexer, NULL, &cs); + if (error) { + action_error(ctx, "%s", error); + free(error); + return; + } + + if (!strcmp(dhcp_opt->type, "str")) { + if (cs.type != EXPR_C_STRING) { + action_error(ctx, "DHCPv6 option %s requires string value.", + dhcp_opt->name); + return; + } + } else { + if (cs.type != EXPR_C_INTEGER) { + action_error(ctx, "DHCPv6 option %s requires numeric value.", + dhcp_opt->name); + return; + } + } + + if (!lexer_match(ctx->lexer, LEX_T_COMMA) && ( + ctx->lexer->token.type != LEX_T_RPAREN)) { + action_syntax_error(ctx, NULL); + return; + } + + struct dhcp_opt6_header *opt = ofpbuf_put_uninit(ofpacts, sizeof *opt); + opt->code = dhcp_opt->code; + + if (!strcmp(dhcp_opt->type, "ipv6")) { + opt->len = cs.n_values * sizeof(struct in6_addr); + for (size_t i = 0; i < cs.n_values; i++) { + ofpbuf_put(ofpacts, &cs.values[i].value.ipv6, + sizeof(struct in6_addr)); + } + } else if (!strcmp(dhcp_opt->type, "mac")) { + opt->len = sizeof(struct eth_addr); + ofpbuf_put(ofpacts, &cs.values[0].value.mac, opt->len); + } else if (!strcmp(dhcp_opt->type, "str")) { + opt->len = strlen(cs.values[0].string); + ofpbuf_put(ofpacts, cs.values[0].string, opt->len); + } + + expr_constant_set_destroy(&cs); + return; +} + +/* Parses the "put_dhcpv6_opts" action. The result should be stored into 'dst'. + * + * The caller has already consumed "put_dhcpv6_opts(", so this just parses the + * rest. */ +static void +parse_put_dhcpv6_opts_action(struct action_context *ctx, + const struct expr_field *dst) +{ + /* Validate that the destination is a 1-bit, modifiable field. */ + struct mf_subfield sf; + struct expr *prereqs; + char *error = expr_expand_field(ctx->lexer, ctx->ap->symtab, + dst, 1, true, &sf, &prereqs); + if (error) { + action_error(ctx, "%s", error); + free(error); + return; + } + ctx->prereqs = expr_combine(EXPR_T_AND, ctx->prereqs, prereqs); + + /* controller. */ + size_t oc_offset = start_controller_op( + ctx->ofpacts, ACTION_OPCODE_PUT_DHCPV6_OPTS, true); + nx_put_header(ctx->ofpacts, sf.field->id, OFP13_VERSION, false); + ovs_be32 ofs = htonl(sf.ofs); + ofpbuf_put(ctx->ofpacts, &ofs, sizeof ofs); + while (!lexer_match(ctx->lexer, LEX_T_RPAREN)) { + parse_dhcpv6_opt(ctx, ctx->ofpacts); + if (ctx->error) { + return; + } + } + finish_controller_op(ctx->ofpacts, oc_offset); +} + static bool action_parse_port(struct action_context *ctx, uint16_t *port) { diff --git a/ovn/lib/ovn-dhcp.h b/ovn/lib/ovn-dhcp.h index 6750f95..65a066b 100644 --- a/ovn/lib/ovn-dhcp.h +++ b/ovn/lib/ovn-dhcp.h @@ -108,4 +108,75 @@ dhcp_opts_destroy(struct hmap *dhcp_opts) hmap_destroy(dhcp_opts); } +struct dhcp_opt6_header { + uint16_t code; + uint16_t len; +}; + +/* Supported DHCPv6 Message Types */ +#define DHCPV6_MSG_TYPE_SOLICIT 1 +#define DHCPV6_MSG_TYPE_ADVT 2 +#define DHCPV6_MSG_TYPE_REQUEST 3 +#define DHCPV6_MSG_TYPE_CONFIRM 4 +#define DHCPV6_MSG_TYPE_REPLY 7 +#define DHCPV6_MSG_TYPE_DECLINE 9 + + +/* DHCPv6 Option codes */ +#define DHCPV6_OPT_CLIENT_ID_CODE 1 +#define DHCPV6_OPT_SERVER_ID_CODE 2 +#define DHCPV6_OPT_IA_NA_CODE 3 +#define DHCPV6_OPT_IA_ADDR_CODE 5 +#define DHCPV6_OPT_DNS_SERVER_CODE 23 +#define DHCPV6_OPT_DOMAIN_SEARCH_CODE 24 + +#define DHCPV6_OPT_SERVER_ID \ + DHCP_OPTION("server_id", DHCPV6_OPT_SERVER_ID_CODE, "mac") + +#define DHCPV6_OPT_IA_ADDR \ + DHCP_OPTION("ia_addr", DHCPV6_OPT_IA_ADDR_CODE, "ipv6") + +#define DHCPV6_OPT_DNS_SERVER \ + DHCP_OPTION("dns_server", DHCPV6_OPT_DNS_SERVER_CODE, "ipv6") + +#define DHCPV6_OPT_DOMAIN_SEARCH \ + DHCP_OPTION("domain_search", DHCPV6_OPT_DOMAIN_SEARCH_CODE, "str") + +OVS_PACKED( +struct dhcpv6_opt_header { + ovs_be16 code; + ovs_be16 len; +}); + +OVS_PACKED( +struct dhcpv6_opt_server_id { + struct dhcpv6_opt_header opt; + ovs_be16 duid_type; + ovs_be16 hw_type; + struct eth_addr mac; +}); + + +OVS_PACKED( +struct dhcpv6_opt_ia_addr { + struct dhcpv6_opt_header opt; + struct in6_addr ipv6; + ovs_be32 t1; + ovs_be32 t2; +}); + +OVS_PACKED( +struct dhcpv6_opt_ia_na { + struct dhcpv6_opt_header opt; + ovs_be32 iaid; + ovs_be32 t1; + ovs_be32 t2; +}); + +#define DHCPV6_DUID_LL 3 +#define DHCPV6_HW_TYPE_ETH 1 + +#define DHCPV6_OPT_PAYLOAD(opt) \ + (void *)((char *)opt + sizeof(struct dhcpv6_opt_header)) + #endif /* OVN_DHCP_H */ diff --git a/ovn/ovn-sb.ovsschema b/ovn/ovn-sb.ovsschema index b1737f5..8604b4e 100644 --- a/ovn/ovn-sb.ovsschema +++ b/ovn/ovn-sb.ovsschema @@ -1,7 +1,7 @@ { "name": "OVN_Southbound", - "version": "1.7.0", - "cksum": "3677179333 6917", + "version": "1.8.0", + "cksum": "59582657 7376", "tables": { "SB_Global": { "columns": { @@ -141,4 +141,15 @@ "type": "string", "enum": ["set", ["bool", "uint8", "uint16", "uint32", "ipv4", "static_routes", "str"]]}}}}, + "isRoot": true}, + "DHCPv6_Options": { + "columns": { + "name": {"type": "string"}, + "code": { + "type": {"key": {"type": "integer", + "minInteger": 0, "maxInteger": 254}}}, + "type": { + "type": {"key": { + "type": "string", + "enum": ["set", ["ipv6", "str", "mac"]]}}}}, "isRoot": true}}} diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml index c5f236e..e0b0311 100644 --- a/ovn/ovn-sb.xml +++ b/ovn/ovn-sb.xml @@ -1229,6 +1229,49 @@

+
+ R = put_dhcpv6_opts(D1 = V1, D2 = V2, ..., Dn = Vn); +
+ +
+

+ Parameters: one or more DHCPv6 option/value pairs. +

+ +

+ Result: stored to a 1-bit subfield R. +

+ +

+ Valid only in the ingress pipeline. +

+ +

+ When this action is applied to a DHCPv6 request packet, it changes + the packet into a DHCPv6 reply, replaces the options by those + specified as parameters, and stores 1 in R. +

+ +

+ When this action is applied to a non-DHCPv6 packet or an invalid + DHCPv6 request packet , it leaves the packet unchanged and stores + 0 in R. +

+ +

+ The contents of the table control the + DHCPv6 option names and values that this action supports. +

+ +

+ Example: + + reg0[3] = put_dhcpv6_opts(ia_addr = aef0::4, server_id = 00:00:00:00:10:02, + dns_server={ae70::1,ae70::2}....); + +

+
+
ct_lb;
ct_lb(ip[:port]...);
@@ -2013,4 +2056,90 @@ tcp.flags = RST; + + +

+ Each row in this table stores the DHCPv6 Options supported by native OVN + DHCPv6. ovn-northd populates this table with the supported + DHCPv6 options. ovn-controller looks up this table to get the + DHCPv6 codes of the DHCPv6 options defined in the "put_dhcpv6_opts" + action. Please refer to the RFC 3315 and RFC 3646 for the possible list + of DHCPv6 options that can be defined here. +

+ + +

+ Name of the DHCPv6 option. +

+ +

+ Example. name="ia_addr" +

+
+ + +

+ DHCPv6 option code for the DHCPv6 option as defined in the appropriate + RFC. +

+ +

+ Example. code=3 +

+
+ + +

+ Data type of the DHCPv6 option code. +

+ +
+
value: ipv6
+
+

+ This indicates that the value of the DHCPv6 option is an IPv6 + address(es). +

+ +

+ Example. "name=ia_addr", "code=5", "type=ipv6". +

+ +

+ put_dhcpv6_opts(..., ia_addr = ae70::4,...) +

+
+ +
value: str
+
+

+ This indicates that the value of the DHCPv6 option is a string. +

+ +

+ Example. "name=domain_search", "code=24", "type=str". +

+ +

+ put_dhcpv6_opts(..., domain_search = ovn.domain,...) +

+
+ +
value: mac
+
+

+ This indicates that the value of the DHCPv6 option is a MAC address. +

+ +

+ Example. "name=server_id", "code=2", "type=mac". +

+ +

+ put_dhcpv6_opts(..., server_id = 01:02:03:04L05:06,...) +

+
+
+
+
diff --git a/tests/ovn.at b/tests/ovn.at index 051b222..ee7eb02 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -685,6 +685,17 @@ reg1[0] = put_dhcp_opts(offerip=1.2.3.4, xyzzy); => Syntax error at `xyzzy' expe reg1[0] = put_dhcp_opts(offerip="xyzzy"); => DHCP option offerip requires numeric value. reg1[0] = put_dhcp_opts(offerip=1.2.3.4, domain=1.2.3.4); => DHCP option domain requires string value. +# put_dhcpv6_opts +reg1[0] = put_dhcpv6_opts(ia_addr = ae70::4, server_id = 00:00:00:00:10:02); => actions=controller(userdata=00.00.00.04.00.00.00.00.80.01.00.08.00.00.00.00.05.00.10.00.ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.04.02.00.06.00.00.00.00.00.10.02,pause), prereqs=1 +reg1[0] = put_dhcpv6_opts(); => actions=controller(userdata=00.00.00.04.00.00.00.00.80.01.00.08.00.00.00.00,pause), prereqs=1 +reg1[0] = put_dhcpv6_opts(dns_server={ae70::1,ae70::2}); => actions=controller(userdata=00.00.00.04.00.00.00.00.80.01.00.08.00.00.00.00.17.00.20.00.ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.01.ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.02,pause), prereqs=1 +reg1[0] = put_dhcpv6_opts(domain_search="ovn.org"); => actions=controller(userdata=00.00.00.04.00.00.00.00.80.01.00.08.00.00.00.00.18.00.07.00.6f.76.6e.2e.6f.72.67,pause), prereqs=1 +reg1[0] = put_dhcpv6_opts(x = 1.2.3.4); => Syntax error at `x' expecting DHCPv6 option name. +reg1[0] = put_dhcpv6_opts(ia_addr=ae70::4, "hi"); => Syntax error at `"hi"'. +reg1[0] = put_dhcpv6_opts(ia_addr=ae70::4, xyzzy); => Syntax error at `xyzzy' expecting DHCPv6 option name. +reg1[0] = put_dhcpv6_opts(ia_addr="ae70::4"); => DHCPv6 option ia_addr requires numeric value. +reg1[0] = put_dhcpv6_opts(ia_addr=ae70::4, domain_search=ae70::1); => DHCPv6 option domain_search requires string value. + # na na { eth.src = 12:34:56:78:9a:bc; nd.tll = 12:34:56:78:9a:bc; outport = inport; inport = ""; /* Allow sending out inport. */ output; }; => actions=controller(userdata=00.00.00.03.00.00.00.00.00.19.00.10.80.00.08.06.12.34.56.78.9a.bc.00.00.00.19.00.10.80.00.42.06.12.34.56.78.9a.bc.00.00.ff.ff.00.18.00.00.23.20.00.06.00.20.00.00.00.00.00.01.1c.04.00.01.1e.04.00.19.00.10.00.01.1c.04.00.00.00.00.00.00.00.00.00.19.00.10.00.00.00.02.00.00.00.00.00.00.00.00.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00), prereqs=nd diff --git a/tests/test-ovn.c b/tests/test-ovn.c index 707b326..e2a67e9 100644 --- a/tests/test-ovn.c +++ b/tests/test-ovn.c @@ -244,7 +244,7 @@ create_symtab(struct shash *symtab) } static void -create_dhcp_opts(struct hmap *dhcp_opts) +create_dhcp_opts(struct hmap *dhcp_opts, struct hmap *dhcpv6_opts) { hmap_init(dhcp_opts); dhcp_opt_add(dhcp_opts, "offerip", 0, "ipv4"); @@ -269,6 +269,13 @@ create_dhcp_opts(struct hmap *dhcp_opts) dhcp_opt_add(dhcp_opts, "tcp_ttl", 37, "uint8"); dhcp_opt_add(dhcp_opts, "mtu", 26, "uint16"); dhcp_opt_add(dhcp_opts, "lease_time", 51, "uint32"); + + /* DHCPv6 options. */ + hmap_init(dhcpv6_opts); + dhcp_opt_add(dhcpv6_opts, "server_id", 2, "mac"); + dhcp_opt_add(dhcpv6_opts, "ia_addr", 5, "ipv6"); + dhcp_opt_add(dhcpv6_opts, "dns_server", 23, "ipv6"); + dhcp_opt_add(dhcpv6_opts, "domain_search", 24, "str"); } static void @@ -1278,11 +1285,12 @@ test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED) { struct shash symtab; struct hmap dhcp_opts; + struct hmap dhcpv6_opts; struct simap ports, ct_zones; struct ds input; create_symtab(&symtab); - create_dhcp_opts(&dhcp_opts); + create_dhcp_opts(&dhcp_opts, &dhcpv6_opts); /* Initialize group ids. */ struct group_table group_table; @@ -1308,6 +1316,7 @@ test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED) struct action_params ap = { .symtab = &symtab, .dhcp_opts = &dhcp_opts, + .dhcpv6_opts = &dhcpv6_opts, .lookup_port = lookup_port_cb, .aux = &ports, .ct_zones = &ct_zones, @@ -1348,6 +1357,8 @@ test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED) simap_destroy(&ct_zones); expr_symtab_destroy(&symtab); shash_destroy(&symtab); + dhcp_opts_destroy(&dhcp_opts); + dhcp_opts_destroy(&dhcpv6_opts); } static unsigned int