diff --git a/include/ovn/actions.h b/include/ovn/actions.h
index a395ce9..9e3d86a 100644
--- a/include/ovn/actions.h
+++ b/include/ovn/actions.h
@@ -88,6 +88,14 @@ enum action_opcode {
      *     MFF_ETH_SRC = mac
      */
     ACTION_OPCODE_PUT_ND,
+    /* "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. */
@@ -105,6 +113,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 fda10eb..0955ae9 100644
--- a/ovn/controller/lflow.c
+++ b/ovn/controller/lflow.c
@@ -346,6 +346,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
@@ -398,17 +399,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 {
@@ -430,12 +439,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
@@ -447,6 +457,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. */
@@ -519,6 +530,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 bd685fe..9b1ccc0 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"
@@ -366,6 +367,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)
 {
@@ -421,6 +711,11 @@ process_packet_in(const struct ofp_header *msg)
                                        false);
         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 b9d1205..a79165f 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,
@@ -628,6 +636,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 13c9526..4932fe6 100644
--- a/ovn/ovn-sb.xml
+++ b/ovn/ovn-sb.xml
@@ -1234,6 +1234,49 @@
           </p>
         </dd>
 
+        <dt>
+          <code><var>R</var> = put_dhcpv6_opts(<var>D1</var> = <var>V1</var>, <var>D2</var> = <var>V2</var>, ..., <var>Dn</var> = <var>Vn</var>);</code>
+        </dt>
+
+        <dd>
+          <p>
+            <b>Parameters</b>: one or more DHCPv6 option/value pairs.
+          </p>
+
+          <p>
+            <b>Result</b>: stored to a 1-bit subfield <var>R</var>.
+          </p>
+
+          <p>
+            Valid only in the ingress pipeline.
+          </p>
+
+          <p>
+            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 <var>R</var>.
+          </p>
+
+          <p>
+            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 <var>R</var>.
+          </p>
+
+          <p>
+            The contents of the <ref table="DHCPv6_Options"/> table control the
+            DHCPv6 option names and values that this action supports.
+          </p>
+
+          <p>
+            <b>Example:</b>
+            <code>
+              reg0[3] = put_dhcpv6_opts(ia_addr = aef0::4, server_id = 00:00:00:00:10:02,
+              dns_server={ae70::1,ae70::2}....);
+            </code>
+          </p>
+        </dd>
+
         <dt><code>ct_lb;</code></dt>
         <dt><code>ct_lb(</code><var>ip</var>[<code>:</code><var>port</var>]...<code>);</code></dt>
         <dd>
@@ -2018,4 +2061,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", "code=24", "type=str".
+          </p>
+
+          <p>
+            put_dhcpv6_opts(..., domain_search = 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 54fa8c5..7f6758a 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -703,6 +703,17 @@ get_nd(xxreg0, ip6.dst); => Cannot use numeric field xxreg0 where string field i
 # put_nd
 put_nd(inport, nd.target, nd.sll); => actions=push:NXM_NX_XXREG0[],push:NXM_OF_ETH_SRC[],push:NXM_NX_ND_SLL[],push:NXM_NX_ND_TARGET[],pop:NXM_NX_XXREG0[],pop:NXM_OF_ETH_SRC[],controller(userdata=00.00.00.04.00.00.00.00),pop:NXM_OF_ETH_SRC[],pop:NXM_NX_XXREG0[], prereqs=((icmp6.type == 0x87 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd)) || (icmp6.type == 0x88 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd))) && icmp6.code == 0 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd) && ip.ttl == 0xff && (eth.type == 0x800 || eth.type == 0x86dd) && icmp6.type == 0x87 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd) && icmp6.code == 0 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd) && ip.ttl == 0xff && (eth.type == 0x800 || eth.type == 0x86dd)
 
+# 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.05.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.05.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.05.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.05.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.
+
 # Contradictionary prerequisites (allowed but not useful):
 ip4.src = ip6.src[0..31]; => actions=move:NXM_NX_IPV6_SRC[0..31]->NXM_OF_IP_SRC[], prereqs=eth.type == 0x800 && eth.type == 0x86dd
 ip4.src <-> ip6.src[0..31]; => actions=push:NXM_NX_IPV6_SRC[0..31],push:NXM_OF_IP_SRC[],pop:NXM_NX_IPV6_SRC[0..31],pop:NXM_OF_IP_SRC[], prereqs=eth.type == 0x800 && eth.type == 0x86dd
diff --git a/tests/test-ovn.c b/tests/test-ovn.c
index acb6a99..e641986 100644
--- a/tests/test-ovn.c
+++ b/tests/test-ovn.c
@@ -247,7 +247,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");
@@ -272,6 +272,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
@@ -1281,11 +1288,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;
@@ -1311,6 +1319,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,
@@ -1351,6 +1360,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
