[ovs-dev] [PATCH v2 1/2] ovn-controller: Add 'put_dhcpv6_opts' action in ovn-controller

Numan Siddique nusiddiq at redhat.com
Tue Jul 26 18:30:50 UTC 2016


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 <nusiddiq at redhat.com>
---
 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;
       </dl>
     </column>
   </table>
+
+  <table name="DHCPv6_Options" title="DHCPv6 Options supported by native OVN DHCPv6">
+    <p>
+      Each row in this table stores the DHCPv6 Options supported by native OVN
+      DHCPv6. <code>ovn-northd</code> populates this table with the supported
+      DHCPv6 options. <code>ovn-controller</code> 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.
+    </p>
+
+    <column name="name">
+      <p>
+        Name of the DHCPv6 option.
+      </p>
+
+      <p>
+        Example. name="IA_ADDR"
+      </p>
+    </column>
+
+    <column name="code">
+      <p>
+        DHCPv6 option code for the DHCPv6 option as defined in the appropriate
+        RFC.
+      </p>
+
+      <p>
+        Example. code=3
+      </p>
+    </column>
+
+    <column name="type">
+      <p>
+        Data type of the DHCPv6 option code.
+      </p>
+
+      <dl>
+        <dt><code>value: ipv6</code></dt>
+        <dd>
+          <p>
+            This indicates that the value of the DHCPv6 option is an IPv6
+            address(es).
+          </p>
+
+          <p>
+            Example. "name=IA_ADDR", "code=5", "type=ipv6".
+          </p>
+
+          <p>
+            put_dhcpv6_opts(..., IA_ADDR = ae70::4,...)
+          </p>
+        </dd>
+
+        <dt><code>value: str</code></dt>
+        <dd>
+          <p>
+            This indicates that the value of the DHCPv6 option is a string.
+          </p>
+
+          <p>
+            Example. "name=DOMAIN_SEARCH_LIST", "code=24", "type=str".
+          </p>
+
+          <p>
+            put_dhcpv6_opts(..., DOMAIN_SEARCH_LIST = ovn.domain,...)
+          </p>
+        </dd>
+
+        <dt><code>value: mac</code></dt>
+        <dd>
+          <p>
+            This indicates that the value of the DHCPv6 option is a MAC address.
+          </p>
+
+          <p>
+            Example. "name=SERVER_ID", "code=2", "type=mac".
+          </p>
+
+          <p>
+            put_dhcpv6_opts(..., SERVER_ID = 01:02:03:04L05:06,...)
+          </p>
+        </dd>
+      </dl>
+    </column>
+  </table>
 </database>
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
-- 
2.7.4





More information about the dev mailing list