From patchwork Tue Jul 26 18:30:50 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Numan Siddique X-Patchwork-Id: 652874 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 3rzRX95nj3z9s2G for ; Wed, 27 Jul 2016 04:31:05 +1000 (AEST) Received: from archives.nicira.com (localhost [127.0.0.1]) by archives.nicira.com (Postfix) with ESMTP id 0620510CE1; Tue, 26 Jul 2016 11:31:05 -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 A741B10CD2 for ; Tue, 26 Jul 2016 11:31:03 -0700 (PDT) Received: from bar6.cudamail.com (localhost [127.0.0.1]) by mx3v3.cudamail.com (Postfix) with ESMTPS id 3A2F216254C for ; Tue, 26 Jul 2016 12:31:03 -0600 (MDT) X-ASG-Debug-ID: 1469557857-0b32371ca1392a0001-byXFYA Received: from mx1-pf1.cudamail.com ([192.168.24.1]) by bar6.cudamail.com with ESMTP id cTwGruHbtuQ0dEvc (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NO) for ; Tue, 26 Jul 2016 12:30:57 -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); 26 Jul 2016 18:30:56 -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 9D95E7F748 for ; Tue, 26 Jul 2016 18:30:53 +0000 (UTC) Received: from nusiddiq.blr.redhat.com (ovpn-116-19.phx2.redhat.com [10.3.116.19]) by int-mx09.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id u6QIUp81024053 for ; Tue, 26 Jul 2016 14:30:52 -0400 X-CudaMail-Envelope-Sender: nusiddiq@redhat.com From: Numan Siddique X-CudaMail-Whitelist-To: dev@openvswitch.org X-CudaMail-MID: CM-E1-725056181 X-CudaMail-DTE: 072616 X-CudaMail-Originating-IP: 209.132.183.28 To: ovs dev X-ASG-Orig-Subj: [##CM-E1-725056181##][PATCH v2 1/2] ovn-controller: Add 'put_dhcpv6_opts' action in ovn-controller In-Reply-To: Organization: Red Hat Message-ID: Date: Wed, 27 Jul 2016 00:00:50 +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.22 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.27]); Tue, 26 Jul 2016 18:30:53 +0000 (UTC) X-Barracuda-Connect: UNKNOWN[192.168.24.1] X-Barracuda-Start-Time: 1469557857 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 v2 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_RECURSIVE_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 with this action. Signed-off-by: Numan Siddique --- ovn/controller/lflow.c | 7 ++ ovn/controller/pinctrl.c | 295 +++++++++++++++++++++++++++++++++++++++++++++++ ovn/lib/actions.c | 114 ++++++++++++++++++ ovn/lib/actions.h | 9 ++ ovn/lib/ovn-dhcp.h | 71 ++++++++++++ ovn/ovn-sb.ovsschema | 15 ++- ovn/ovn-sb.xml | 86 ++++++++++++++ tests/ovn.at | 11 ++ tests/test-ovn.c | 6 + 9 files changed, 612 insertions(+), 2 deletions(-) diff --git a/ovn/controller/lflow.c b/ovn/controller/lflow.c index 42c9055..22e105e 100644 --- a/ovn/controller/lflow.c +++ b/ovn/controller/lflow.c @@ -396,6 +396,13 @@ add_logical_flows(struct controller_ctx *ctx, const struct lport_index *lports, 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(&dhcp_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, diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c index 0ae6501..99a66f9 100644 --- a/ovn/controller/pinctrl.c +++ b/ovn/controller/pinctrl.c @@ -37,6 +37,7 @@ #include "ovn-controller.h" #include "ovn/lib/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_DSL_CODE: + { + struct dhcpv6_opt_header *opt_dsl = ofpbuf_put_zeros( + out_dhcpv6_opts, sizeof *opt_dsl); + opt_dsl->code = htons(DHCPV6_OPT_DSL_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 6e2bf93..4baf87a 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->dhcp_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/actions.h b/ovn/lib/actions.h index 114c71e..990fb7f 100644 --- a/ovn/lib/actions.h +++ b/ovn/lib/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. */ diff --git a/ovn/lib/ovn-dhcp.h b/ovn/lib/ovn-dhcp.h index 6750f95..0a77808 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_DSL_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_RECURSIVE_SERVER", DHCPV6_OPT_DNS_SERVER_CODE, "ipv6") + +#define DHCPV6_OPT_DSL \ + DHCP_OPTION("DOMAIN_SEARCH_LIST", DHCPV6_OPT_DSL_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 605b605..2f80405 100644 --- a/ovn/ovn-sb.ovsschema +++ b/ovn/ovn-sb.ovsschema @@ -1,7 +1,7 @@ { "name": "OVN_Southbound", - "version": "1.6.0", - "cksum": "1715817174 6541", + "version": "1.7.0", + "cksum": "1423097416 7000", "tables": { "Chassis": { "columns": { @@ -132,4 +132,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 3d26e65..abdad7c 100644 --- a/ovn/ovn-sb.xml +++ b/ovn/ovn-sb.xml @@ -1979,4 +1979,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_LIST", "code=24", "type=str". +

+ +

+ put_dhcpv6_opts(..., DOMAIN_SEARCH_LIST = 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 86efcf5..b08bf7a 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -652,6 +652,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_RECURSIVE_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_LIST="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_LIST=ae70::1); => DHCPv6 option DOMAIN_SEARCH_LIST 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 26055bb..fa89db9 100644 --- a/tests/test-ovn.c +++ b/tests/test-ovn.c @@ -269,6 +269,12 @@ 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. */ + dhcp_opt_add(dhcp_opts, "SERVER_ID", 2, "mac"); + dhcp_opt_add(dhcp_opts, "IA_ADDR", 5, "ipv6"); + dhcp_opt_add(dhcp_opts, "DNS_RECURSIVE_SERVER", 23, "ipv6"); + dhcp_opt_add(dhcp_opts, "DOMAIN_SEARCH_LIST", 24, "str"); } static void