[ovs-dev] [RFC PATCH] ovn: Support native DHCPv6

Numan Siddique nusiddiq at redhat.com
Mon Jun 6 07:58:52 UTC 2016


In order for the VMs to use this feature, ovn-controller
should send RA with M/O flags set.

DHCPv6 options - Server id (2), DNS recursive server (23)
and Domain search list (24) are supported in this patch.

This patch is on top of native DHCPv4 patches and it also
requires the upstream fix "b4f70527f" into the ovs datapath.

Is it good to send the DHCPv6 reply packet as packet out
instead of resuming it (because the fix "b4f70527f" might take
some to be available in the distro kernels)

TODO
 - Test cases
 - Documentation
 - More testing
 - Support more DHCPv6 options (if needed)

Signed-Off-by: Numan Siddique <nusiddiq at redhat.com>
---
 lib/packets.c            |  19 ++++
 lib/packets.h            |   2 +
 ovn/controller/lflow.c   |   5 +
 ovn/controller/pinctrl.c | 265 +++++++++++++++++++++++++++++++++++++++++++++++
 ovn/lib/actions.c        | 106 +++++++++++++++++++
 ovn/lib/actions.h        |   5 +
 ovn/lib/ovn-dhcp.h       |  72 +++++++++++++
 ovn/northd/ovn-northd.c  | 257 +++++++++++++++++++++++++++++++++++++--------
 ovn/ovn-sb.ovsschema     |  15 ++-
 ovn/ovn-sb.xml           |  29 ++++++
 10 files changed, 728 insertions(+), 47 deletions(-)

diff --git a/lib/packets.c b/lib/packets.c
index 6a55d6f..20c066f 100644
--- a/lib/packets.c
+++ b/lib/packets.c
@@ -693,6 +693,25 @@ struct in6_addr ipv6_addr_bitand(const struct in6_addr *a,
     return dst;
 }
 
+struct in6_addr ipv6_addr_bitxor(const struct in6_addr *a,
+                                 const struct in6_addr *b)
+{
+    int i;
+    struct in6_addr dst;
+
+#ifdef s6_addr32
+    for (i=0; i<4; i++) {
+        dst.s6_addr32[i] = a->s6_addr32[i] ^ b->s6_addr32[i];
+    }
+#else
+    for (i=0; i<16; i++) {
+        dst.s6_addr[i] = a->s6_addr[i] ^ b->s6_addr[i];
+    }
+#endif
+
+    return dst;
+}
+
 /* Returns an in6_addr consisting of 'mask' high-order 1-bits and 128-N
  * low-order 0-bits. */
 struct in6_addr
diff --git a/lib/packets.h b/lib/packets.h
index 5945940..22c9f6c 100644
--- a/lib/packets.h
+++ b/lib/packets.h
@@ -1027,6 +1027,8 @@ void ipv6_format_masked(const struct in6_addr *addr,
 const char * ipv6_string_mapped(char *addr_str, const struct in6_addr *addr);
 struct in6_addr ipv6_addr_bitand(const struct in6_addr *src,
                                  const struct in6_addr *mask);
+struct in6_addr ipv6_addr_bitxor(const struct in6_addr *src,
+                                 const struct in6_addr *mask);
 struct in6_addr ipv6_create_mask(int mask);
 int ipv6_count_cidr_bits(const struct in6_addr *netmask);
 bool ipv6_is_cidr(const struct in6_addr *netmask);
diff --git a/ovn/controller/lflow.c b/ovn/controller/lflow.c
index 52e6131..64c0b33 100644
--- a/ovn/controller/lflow.c
+++ b/ovn/controller/lflow.c
@@ -211,6 +211,11 @@ 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);
+    }
     const struct sbrec_logical_flow *lflow;
     SBREC_LOGICAL_FLOW_FOR_EACH (lflow, ctx->ovnsb_idl) {
         /* Determine translation of logical table IDs to physical table IDs. */
diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c
index 244dd3d..8bd4171 100644
--- a/ovn/controller/pinctrl.c
+++ b/ovn/controller/pinctrl.c
@@ -34,6 +34,7 @@
 #include "lib/dhcp.h"
 #include "ovn-controller.h"
 #include "ovn/lib/actions.h"
+#include "ovn/lib/ovn-dhcp.h"
 #include "ovn/lib/logical-fields.h"
 #include "ovn/lib/ovn-util.h"
 #include "poll-loop.h"
@@ -383,6 +384,264 @@ 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;
+
+    uint32_t *reg_idx = ofpbuf_try_pull(userdata, sizeof *reg_idx);
+    if (!reg_idx) {
+        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;
+    }
+
+    in_dhcpv6_data += 4;
+    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:
+    /* store the result in the regx */
+    if (reg_idx) {
+        match_set_reg(&pin->flow_metadata, *reg_idx, success);
+    }
+    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)
 {
@@ -427,6 +686,12 @@ process_packet_in(const struct ofp_header *msg)
     case ACTION_OPCODE_PUT_DHCP_OPTS:
         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;
+
     default:
         VLOG_WARN_RL(&rl, "unrecognized packet-in opcode %"PRIu32,
                      ntohl(ah->opcode));
diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c
index 9b4c1bd..4129258 100644
--- a/ovn/lib/actions.c
+++ b/ovn/lib/actions.c
@@ -608,6 +608,110 @@ parse_put_dhcp_opts_action(struct action_context *ctx)
     finish_controller_op(ctx->ofpacts, oc_offset);
 }
 
+static bool
+parse_dhcpv6_opt(struct action_context *ctx, struct ofpbuf *ofpacts)
+{
+    if (ctx->lexer->token.type != LEX_T_ID) {
+        action_syntax_error(ctx, NULL);
+        return false;
+    }
+
+    enum lex_type lookahead = lexer_lookahead(ctx->lexer);
+    if (lookahead != LEX_T_EQUALS) {
+        action_syntax_error(ctx, NULL);
+        return false;
+    }
+
+    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 valid dhcp option.");
+        return false;
+    }
+
+    lexer_get(ctx->lexer);
+    lexer_get(ctx->lexer);
+
+    struct expr_constant_set cs;
+    memset(&cs, 0, sizeof(struct expr_constant_set));
+    if (!expr_parse_constant_set(ctx->lexer, NULL, &cs)) {
+        action_syntax_error(ctx, "invalid dhcp option values");
+        return false;
+    }
+
+    if (!strcmp(dhcp_opt->type, "str")) {
+        if (cs.type != EXPR_C_STRING) {
+            return false;
+        }
+    } else {
+        if (cs.type != EXPR_C_INTEGER) {
+            return false;
+        }
+    }
+
+    if (!lexer_match(ctx->lexer, LEX_T_COMMA) && (
+        ctx->lexer->token.type != LEX_T_RPAREN)) {
+        action_syntax_error(ctx, NULL);
+        return false;
+    }
+
+    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 true;
+}
+
+static void
+parse_put_dhcpv6_opts_action(struct action_context *ctx)
+{
+    if (!action_force_match(ctx, LEX_T_LPAREN)) {
+        return;
+    }
+
+    const struct expr_symbol *symbol =
+        shash_find_data(ctx->ap->symtab, ctx->lexer->token.s);
+
+    /* Allowed registers to store the result are reg0 to reg4 */
+    if (!symbol || (
+            symbol->field->id < MFF_REG0 && symbol->field->id > MFF_REG4)) {
+        return;
+    }
+
+    lexer_get(ctx->lexer);
+    if (!action_force_match(ctx, LEX_T_COMMA)) {
+        return;
+    }
+
+    /* controller. */
+    size_t oc_offset = start_controller_op(
+        ctx->ofpacts, ACTION_OPCODE_PUT_DHCPV6_OPTS, true);
+    uint32_t *reg_idx = ofpbuf_put_uninit(ctx->ofpacts, sizeof(uint32_t));
+    *reg_idx = symbol->field->id - MFF_REG0;
+
+    while (!lexer_match(ctx->lexer, LEX_T_RPAREN)) {
+        if (!parse_dhcpv6_opt(ctx, ctx->ofpacts)) {
+            return;
+        }
+    }
+    finish_controller_op(ctx->ofpacts, oc_offset);
+}
+
 static void
 emit_ct(struct action_context *ctx, bool recirc_next, bool commit)
 {
@@ -672,6 +776,8 @@ parse_action(struct action_context *ctx)
         parse_put_arp_action(ctx);
     } else if (lexer_match_id(ctx->lexer, "put_dhcp_opts")) {
         parse_put_dhcp_opts_action(ctx);
+    } else if (lexer_match_id(ctx->lexer, "put_dhcpv6_opts")) {
+        parse_put_dhcpv6_opts_action(ctx);
     } else {
         action_syntax_error(ctx, "expecting action");
     }
diff --git a/ovn/lib/actions.h b/ovn/lib/actions.h
index 77c66cf..49b84e9 100644
--- a/ovn/lib/actions.h
+++ b/ovn/lib/actions.h
@@ -49,6 +49,11 @@ enum action_opcode {
      *
      */
     ACTION_OPCODE_PUT_DHCP_OPTS,
+
+    /* "put_dhcpv6_opts(reg_idx, ...dhcpv6 actions ...)".
+     *
+     */
+    ACTION_OPCODE_PUT_DHCPV6_OPTS,
 };
 
 /* Header. */
diff --git a/ovn/lib/ovn-dhcp.h b/ovn/lib/ovn-dhcp.h
index 4da614b..1cb4906 100644
--- a/ovn/lib/ovn-dhcp.h
+++ b/ovn/lib/ovn-dhcp.h
@@ -66,6 +66,7 @@ struct dhcp_opts_map {
 #define DHCP_OPT_T1 DHCP_OPTION("T1", 58, "uint32")
 #define DHCP_OPT_T2 DHCP_OPTION("T2", 59, "uint32")
 
+
 static inline uint32_t
 dhcp_opt_hash(char *opt_name)
 {
@@ -108,4 +109,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/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
index 11969f1..eef411a 100644
--- a/ovn/northd/ovn-northd.c
+++ b/ovn/northd/ovn-northd.c
@@ -1405,6 +1405,90 @@ build_dhcp_action(struct ovn_port *op, ovs_be32 offer_ip,
     return true;
 }
 
+static bool
+build_dhcpv6_action(struct ovn_port *op, struct in6_addr *offer_ip,
+                    struct ds *pause_action,
+                    struct ds *resume_action OVS_UNUSED)
+{
+    if(smap_get_bool(&op->nbs->options, "dhcp_disabled", false)) {
+        /* CMS has disabled native dhcp for this lport */
+        return false;
+    }
+
+    struct nbrec_subnet *subnet = NULL;
+    struct in6_addr host_ip, mask;
+
+    for (size_t i = 0; i < op->od->nbs->n_subnets; i++) {
+        char *error = ipv6_parse_masked(op->od->nbs->subnets[i]->cidr, &host_ip,
+                                        &mask);
+        if (!error) {
+            struct in6_addr ip6_mask = ipv6_addr_bitxor(offer_ip, &host_ip);
+            ip6_mask = ipv6_addr_bitand(&ip6_mask, &mask);
+            char ip6_str[IPV6_SCAN_LEN + 1];
+            memset(ip6_str, 0, sizeof(ip6_str));
+            ipv6_string_mapped(ip6_str, &ip6_mask);
+            if (!strcmp(ip6_str, "::")) {
+               /* offer_ip belongs to this subnet */
+                subnet = op->od->nbs->subnets[i];
+                break;
+            }
+        }
+        free(error);
+    }
+
+    if (!(subnet && subnet->enable_dhcp && subnet->ip_version == 6)) {
+        return false;
+    }
+
+    /* SERVER_ID should be the MAC address */
+    const char *server_mac = smap_get(&subnet->dhcp_options, "SERVER_ID");
+    struct eth_addr ea;
+    if (!server_mac || !eth_addr_from_string(server_mac, &ea)) {
+        /* "SERVER_ID" should be present in the dhcp_options. */
+        return false;
+    }
+
+    /* Get the link local ip of the DHCPv6 server from the server mac */
+    struct in6_addr lla;
+    in6_generate_lla(ea, &lla);
+
+    char server_ip[IPV6_SCAN_LEN + 1];
+    memset(server_ip, 0, sizeof(server_ip));
+    ipv6_string_mapped(server_ip, &lla);
+
+    char ia_addr[IPV6_SCAN_LEN + 1];
+    memset(ia_addr, 0, sizeof(ia_addr));
+    ipv6_string_mapped(ia_addr, offer_ip);
+
+    struct smap dhcpv6_options = SMAP_INITIALIZER(&dhcpv6_options);
+    smap_clone(&dhcpv6_options, &subnet->dhcp_options);
+    smap_remove(&dhcpv6_options, "SERVER_ID");
+
+    struct smap_node *node;
+    /* override the dhcpv6 options define in the lport options if any */
+    SMAP_FOR_EACH(node, &op->nbs->options) {
+        if(!strncmp(node->key, "dhcpv6_opt_", 11)) {
+            smap_replace(&dhcpv6_options, &node->key[11], node->value);
+        }
+    }
+
+    ds_put_format(pause_action, "put_dhcpv6_opts(reg0, IA_ADDR = %s, "
+                  "SERVER_ID = %s", ia_addr, server_mac);
+    SMAP_FOR_EACH(node, &dhcpv6_options) {
+            ds_put_format(pause_action, "%s = %s, ", node->key, node->value);
+    }
+    ds_chomp(pause_action, ' ');
+    ds_chomp(pause_action, ',');
+    ds_put_cstr(pause_action, "); next;");
+
+    ds_put_format(resume_action, "eth.dst = eth.src; eth.src = %s; "
+                  "ip6.dst = ip6.src; ip6.src = %s; udp.src = 547; "
+                  "udp.dst = 546; outport = inport; inport = \"\";"
+                  " /* Allow sending out inport. */ output;",
+                  server_mac, server_ip);
+    return true;
+}
+
 static void
 build_acls(struct ovn_datapath *od, struct hmap *lflows, struct hmap *ports)
 {
@@ -1745,55 +1829,82 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
     /* Logical switch ingress table 6 and 7: DHCP pause and resume
      * priority 100 flows. */
     HMAP_FOR_EACH (op, key_node, ports) {
-        if (!op->nbs) {
+       if (!op->nbs) {
            continue;
-        }
+       }
 
-        if (!lport_is_enabled(op->nbs) || !strcmp(op->nbs->type, "router")) {
-            /* Don't add the DHCP flows if the port is not enabled or if the
-             * port is a router port */
-            continue;
-        }
-
-        for (size_t i = 0; i < op->nbs->n_addresses; i++) {
-            struct lport_addresses laddrs;
-            if (!extract_lport_addresses(op->nbs->addresses[i], &laddrs,
-                                        false)) {
-                continue;
-            }
-
-            if (!laddrs.n_ipv4_addrs) {
-                continue;
-            }
-
-            for (size_t j = 0; j < laddrs.n_ipv4_addrs; j++) {
-                struct ds pause_action = DS_EMPTY_INITIALIZER;
+       if (!lport_is_enabled(op->nbs) || !strcmp(op->nbs->type, "router")) {
+           /* Don't add the DHCP flows if the port is not enabled or if the
+            * port is a router port */
+           continue;
+       }
+
+       for (size_t i = 0; i < op->nbs->n_addresses; i++) {
+           struct lport_addresses laddrs;
+           if (!extract_lport_addresses(op->nbs->addresses[i], &laddrs,
+                                        true)) {
+               continue;
+           }
+
+           for (size_t j = 0; j < laddrs.n_ipv4_addrs; j++) {
+               struct ds pause_action = DS_EMPTY_INITIALIZER;
+               struct ds resume_action = DS_EMPTY_INITIALIZER;
+               if (build_dhcp_action(op, laddrs.ipv4_addrs[j].addr,
+                                     &pause_action, &resume_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_PAUSE, 100,
+                                 ds_cstr(&match), ds_cstr(&pause_action));
+                   /* If reg0 is set to 1, it means the dhcp_offer action is
+                    * successful */
+                   ds_put_cstr(&match, " && reg0 == 1");
+                   ovn_lflow_add(lflows, op->od, S_SWITCH_IN_DHCP_RESUME, 100,
+                                 ds_cstr(&match), ds_cstr(&resume_action));
+                   ds_destroy(&match);
+                   ds_destroy(&pause_action);
+                   ds_destroy(&resume_action);
+                   break;
+               }
+           }
+           free(laddrs.ipv4_addrs);
+
+           for (size_t j = 0; j < laddrs.n_ipv6_addrs; j++) {
+               struct ds pause_action = DS_EMPTY_INITIALIZER;
                 struct ds resume_action = DS_EMPTY_INITIALIZER;
-                if (build_dhcp_action(op, laddrs.ipv4_addrs[j].addr,
-                                      &pause_action, &resume_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_PAUSE, 100,
-                                  ds_cstr(&match), ds_cstr(&pause_action));
-                    /* If reg0 is set to 1, it means the put_dhcp_opts action
-                     * is successful */
-                    ds_put_cstr(&match, " && reg0 == 1");
-                    ovn_lflow_add(lflows, op->od, S_SWITCH_IN_DHCP_RESUME, 100,
-                                  ds_cstr(&match), ds_cstr(&resume_action));
-                    ds_destroy(&match);
-                    ds_destroy(&pause_action);
-                    ds_destroy(&resume_action);
-                    break;
+                if (build_dhcpv6_action(op, &laddrs.ipv6_addrs[j].addr,
+                                        &pause_action, &resume_action)) {
+                  struct ds match = DS_EMPTY_INITIALIZER;
+                  ds_put_format(
+                      &match, "inport == %s && eth.src == "ETH_ADDR_FMT
+                      " && ip6.dst == ff02::1:2 && "
+                      "udp.src == 546 && "
+                      "udp.dst == 547", op->json_key,
+                      ETH_ADDR_ARGS(laddrs.ea));
+
+                  ovn_lflow_add(lflows, op->od, S_SWITCH_IN_DHCP_PAUSE, 100,
+                                ds_cstr(&match), ds_cstr(&pause_action));
+
+                  /* If reg0 is set to 1, it means the put_dhcpv6_opts action is
+                   * successful */
+                  ds_put_cstr(&match, " && reg0 == 1");
+                  ovn_lflow_add(lflows, op->od, S_SWITCH_IN_DHCP_RESUME, 100,
+                                ds_cstr(&match), ds_cstr(&resume_action));
+                  ds_destroy(&match);
+                  ds_destroy(&pause_action);
+                  ds_destroy(&resume_action);
+                  break;
                 }
-            }
-            free(laddrs.ipv4_addrs);
-        }
+
+           }
+
+           free(laddrs.ipv6_addrs);
+       }
     }
 
     /* Ingress table 6 and 7: DHCP pause and resume, by default goto next.
@@ -2600,6 +2711,13 @@ static struct dhcp_opts_map supported_dhcp_opts[] = {
     DHCP_OPT_T2
 };
 
+static struct dhcp_opts_map supported_dhcpv6_opts[] = {
+    DHCPV6_OPT_IA_ADDR,
+    DHCPV6_OPT_SERVER_ID,
+    DHCPV6_OPT_DSL,
+    DHCPV6_OPT_DNS_SERVER
+};
+
 static void
 check_and_add_supported_dhcp_opts_to_sb_db(struct northd_context *ctx)
 {
@@ -2644,6 +2762,50 @@ check_and_add_supported_dhcp_opts_to_sb_db(struct northd_context *ctx)
     hmap_destroy(&dhcp_opts_to_add);
 }
 
+static void
+check_and_add_supported_dhcpv6_opts_to_sb_db(struct northd_context *ctx)
+{
+    static bool nothing_to_add = false;
+
+    if (nothing_to_add) {
+        return;
+    }
+
+    struct hmap dhcpv6_opts_to_add = HMAP_INITIALIZER(&dhcpv6_opts_to_add);
+    for (size_t i = 0; (i < sizeof(supported_dhcpv6_opts) /
+                            sizeof(supported_dhcpv6_opts[0])); i++) {
+        hmap_insert(&dhcpv6_opts_to_add, &supported_dhcpv6_opts[i].hmap_node,
+                    dhcp_opt_hash(supported_dhcpv6_opts[i].name));
+    }
+
+    const struct sbrec_dhcpv6_options *opt_row, *opt_row_next;
+    SBREC_DHCPV6_OPTIONS_FOR_EACH_SAFE(opt_row, opt_row_next, ctx->ovnsb_idl) {
+        struct dhcp_opts_map *dhcp_opt =
+            dhcp_opts_find(&dhcpv6_opts_to_add, opt_row->name);
+        if (dhcp_opt) {
+            hmap_remove(&dhcpv6_opts_to_add, &dhcp_opt->hmap_node);
+        }
+        else {
+            sbrec_dhcpv6_options_delete(opt_row);
+        }
+    }
+
+    if (!dhcpv6_opts_to_add.n) {
+        nothing_to_add = true;
+    }
+
+    struct dhcp_opts_map *opt;
+    HMAP_FOR_EACH_POP(opt, hmap_node, &dhcpv6_opts_to_add) {
+        struct sbrec_dhcpv6_options *sbrec_dhcpv6_option =
+            sbrec_dhcpv6_options_insert(ctx->ovnsb_txn);
+        sbrec_dhcpv6_options_set_name(sbrec_dhcpv6_option, opt->name);
+        sbrec_dhcpv6_options_set_code(sbrec_dhcpv6_option, opt->code);
+        sbrec_dhcpv6_options_set_type(sbrec_dhcpv6_option, opt->type);
+    }
+
+    hmap_destroy(&dhcpv6_opts_to_add);
+}
+
 static char *default_nb_db_;
 
 static const char *
@@ -2816,6 +2978,10 @@ main(int argc, char *argv[])
     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_dhcpv6_options);
+    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dhcpv6_options_col_code);
+    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dhcpv6_options_col_type);
+    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dhcpv6_options_col_name);
 
     /* Main loop. */
     exiting = false;
@@ -2831,6 +2997,7 @@ main(int argc, char *argv[])
         ovnsb_db_run(&ctx);
         if (ctx.ovnsb_txn) {
             check_and_add_supported_dhcp_opts_to_sb_db(&ctx);
+            check_and_add_supported_dhcpv6_opts_to_sb_db(&ctx);
         }
         unixctl_server_run(unixctl);
         unixctl_server_wait(unixctl);
diff --git a/ovn/ovn-sb.ovsschema b/ovn/ovn-sb.ovsschema
index a1343c9..53ce016 100644
--- a/ovn/ovn-sb.ovsschema
+++ b/ovn/ovn-sb.ovsschema
@@ -1,7 +1,7 @@
 {
     "name": "OVN_Southbound",
-    "version": "1.4.0",
-    "cksum": "198773462 6073",
+    "version": "1.5.0",
+    "cksum": "83840326 6532",
     "tables": {
         "Chassis": {
             "columns": {
@@ -122,4 +122,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 88ad5fe..b5c8cff 100644
--- a/ovn/ovn-sb.xml
+++ b/ovn/ovn-sb.xml
@@ -1738,4 +1738,33 @@ tcp.flags = RST;
       </dl>
     </column>
   </table>
+
+  <table name="DHCPv6_Options" title="DHCPv6 Options supported by native OVN DHCPv6">
+    <p>
+      DHCPv6 options
+    </p>
+
+    <column name="name">
+      <p>
+        Name of the DHCP option.
+      </p>
+
+      <p>
+        Example. name="router"
+      </p>
+    </column>
+
+    <column name="code">
+      <p>
+        DHCP option code for the DHCP option as defined in the RFC 2132.
+      </p>
+
+      <p>
+        Example. code=3
+      </p>
+    </column>
+
+    <column name="type">
+    </column>
+  </table>
 </database>
-- 
2.5.5




More information about the dev mailing list