{"id":817001,"url":"http://patchwork.ozlabs.org/api/patches/817001/?format=json","web_url":"http://patchwork.ozlabs.org/project/openvswitch/patch/20170921160923.24850-1-nusiddiq@redhat.com/","project":{"id":47,"url":"http://patchwork.ozlabs.org/api/projects/47/?format=json","name":"Open vSwitch","link_name":"openvswitch","list_id":"ovs-dev.openvswitch.org","list_email":"ovs-dev@openvswitch.org","web_url":"http://openvswitch.org/","scm_url":"git@github.com:openvswitch/ovs.git","webscm_url":"https://github.com/openvswitch/ovs","list_archive_url":"","list_archive_url_format":"","commit_url_format":""},"msgid":"<20170921160923.24850-1-nusiddiq@redhat.com>","list_archive_url":null,"date":"2017-09-21T16:09:23","name":"[ovs-dev,v8,2/4] ovn-controller: Add a new action - 'put_nd_ra_opts'","commit_ref":null,"pull_url":null,"state":"superseded","archived":false,"hash":"0045ac66fb7ffe3751ca037a63889233c6358515","submitter":{"id":67480,"url":"http://patchwork.ozlabs.org/api/people/67480/?format=json","name":"Numan Siddique","email":"nusiddiq@redhat.com"},"delegate":null,"mbox":"http://patchwork.ozlabs.org/project/openvswitch/patch/20170921160923.24850-1-nusiddiq@redhat.com/mbox/","series":[{"id":4431,"url":"http://patchwork.ozlabs.org/api/series/4431/?format=json","web_url":"http://patchwork.ozlabs.org/project/openvswitch/list/?series=4431","date":"2017-09-21T16:07:47","name":"ovn IPv6: Add Router Solicitation responder support and generate Neighbor Solicitation request for unknown MACs","version":8,"mbox":"http://patchwork.ozlabs.org/series/4431/mbox/"}],"comments":"http://patchwork.ozlabs.org/api/patches/817001/comments/","check":"pending","checks":"http://patchwork.ozlabs.org/api/patches/817001/checks/","tags":{},"related":[],"headers":{"Return-Path":"<ovs-dev-bounces@openvswitch.org>","X-Original-To":["incoming@patchwork.ozlabs.org","dev@openvswitch.org"],"Delivered-To":["patchwork-incoming@bilbo.ozlabs.org","ovs-dev@mail.linuxfoundation.org"],"Authentication-Results":["ozlabs.org;\n\tspf=pass (mailfrom) smtp.mailfrom=openvswitch.org\n\t(client-ip=140.211.169.12; helo=mail.linuxfoundation.org;\n\tenvelope-from=ovs-dev-bounces@openvswitch.org;\n\treceiver=<UNKNOWN>)","ext-mx07.extmail.prod.ext.phx2.redhat.com;\n\tdmarc=none (p=none dis=none) header.from=redhat.com","ext-mx07.extmail.prod.ext.phx2.redhat.com;\n\tspf=fail smtp.mailfrom=nusiddiq@redhat.com"],"Received":["from mail.linuxfoundation.org (mail.linuxfoundation.org\n\t[140.211.169.12])\n\t(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256\n\tbits)) (No client certificate requested)\n\tby ozlabs.org (Postfix) with ESMTPS id 3xyhQY18qGz9t42\n\tfor <incoming@patchwork.ozlabs.org>;\n\tFri, 22 Sep 2017 02:09:57 +1000 (AEST)","from mail.linux-foundation.org (localhost [127.0.0.1])\n\tby mail.linuxfoundation.org (Postfix) with ESMTP id 49928BA3;\n\tThu, 21 Sep 2017 16:09:52 +0000 (UTC)","from smtp1.linuxfoundation.org (smtp1.linux-foundation.org\n\t[172.17.192.35])\n\tby mail.linuxfoundation.org (Postfix) with ESMTPS id E8DA2B6D\n\tfor <dev@openvswitch.org>; Thu, 21 Sep 2017 16:09:50 +0000 (UTC)","from mx1.redhat.com (mx1.redhat.com [209.132.183.28])\n\tby smtp1.linuxfoundation.org (Postfix) with ESMTPS id 658AFCA\n\tfor <dev@openvswitch.org>; Thu, 21 Sep 2017 16:09:49 +0000 (UTC)","from smtp.corp.redhat.com\n\t(int-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.12])\n\t(using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits))\n\t(No client certificate requested)\n\tby mx1.redhat.com (Postfix) with ESMTPS id D0D61C074EFE;\n\tThu, 21 Sep 2017 16:09:48 +0000 (UTC)","from numans.blr.redhat.com (ovpn-116-55.sin2.redhat.com\n\t[10.67.116.55])\n\tby smtp.corp.redhat.com (Postfix) with ESMTP id C8D6360BE2;\n\tThu, 21 Sep 2017 16:09:44 +0000 (UTC)"],"X-Greylist":["domain auto-whitelisted by SQLgrey-1.7.6","Sender IP whitelisted, not delayed by milter-greylist-4.5.16\n\t(mx1.redhat.com [10.5.110.31]);\n\tThu, 21 Sep 2017 16:09:48 +0000 (UTC)"],"DMARC-Filter":"OpenDMARC Filter v1.3.2 mx1.redhat.com D0D61C074EFE","From":"nusiddiq@redhat.com","To":"dev@openvswitch.org","Date":"Thu, 21 Sep 2017 21:39:23 +0530","Message-Id":"<20170921160923.24850-1-nusiddiq@redhat.com>","In-Reply-To":"<20170921160747.24602-1-nusiddiq@redhat.com>","References":"<20170921160747.24602-1-nusiddiq@redhat.com>","X-Scanned-By":"MIMEDefang 2.79 on 10.5.11.12","X-Spam-Status":"No, score=-5.0 required=5.0 tests=RCVD_IN_DNSWL_HI,\n\tRP_MATCHES_RCVD autolearn=disabled version=3.3.1","X-Spam-Checker-Version":"SpamAssassin 3.3.1 (2010-03-16) on\n\tsmtp1.linux-foundation.org","Subject":"[ovs-dev] [PATCH v8 2/4] ovn-controller: Add a new action -\n\t'put_nd_ra_opts'","X-BeenThere":"ovs-dev@openvswitch.org","X-Mailman-Version":"2.1.12","Precedence":"list","List-Id":"<ovs-dev.openvswitch.org>","List-Unsubscribe":"<https://mail.openvswitch.org/mailman/options/ovs-dev>,\n\t<mailto:ovs-dev-request@openvswitch.org?subject=unsubscribe>","List-Archive":"<http://mail.openvswitch.org/pipermail/ovs-dev/>","List-Post":"<mailto:ovs-dev@openvswitch.org>","List-Help":"<mailto:ovs-dev-request@openvswitch.org?subject=help>","List-Subscribe":"<https://mail.openvswitch.org/mailman/listinfo/ovs-dev>,\n\t<mailto:ovs-dev-request@openvswitch.org?subject=subscribe>","MIME-Version":"1.0","Content-Type":"text/plain; charset=\"us-ascii\"","Content-Transfer-Encoding":"7bit","Sender":"ovs-dev-bounces@openvswitch.org","Errors-To":"ovs-dev-bounces@openvswitch.org"},"content":"From: Numan Siddique <nusiddiq@redhat.com>\n\nThis patch adds a new OVN action 'put_nd_ra_opts' to support native\nIPv6 Router Advertisement in OVN. This action can be used to respond\nto the IPv6 Router Solicitation requests.\n\novn-controller parses this action and adds a NXT_PACKET_IN2 OF flow\nwith 'pause' flag set and the RA options stored in 'userdata' field.\nThis action is similar to 'put_dhcp_opts' and 'put_dhcpv6_opts'.\n\nWhen a valid IPv6 RS packet is received by the pinctrl module of\novn-controller, it frames a new RA packet and sets the RA options\nfrom the 'userdata' field and resumes the packet storing 1 in the\n1-bit result sub-field. If the packet is invalid, it resumes the\npacket without any modifications storing 0 in the 1-bit result\nsub-field.\n\nEg. reg0[5] = put_nd_ra_opts(address_mode = \"slaac\", mtu = 1450,\n                             slla = 01:02:03:04:05:06, prefix = aef0::/64)\n\nNote that unlike DHCPv4/v6, a new table to store the supported IPv6 ND RA\noptions is not added in SB DB since there are only 3 ND RA options.\n\nCo-authored-by: Zongkai LI <zealokii@gmail.com>\nSigned-off-by: Zongkai LI <zealokii@gmail.com>\nSigned-off-by: Numan Siddique <nusiddiq@redhat.com>\n---\n include/ovn/actions.h     |  14 +++-\n ovn/controller/lflow.c    |  13 +++-\n ovn/controller/pinctrl.c  |  96 +++++++++++++++++++++++\n ovn/lib/actions.c         | 194 +++++++++++++++++++++++++++++++++++++++++++++-\n ovn/lib/ovn-l7.h          |  48 ++++++++++++\n ovn/ovn-sb.xml            |  77 ++++++++++++++++++\n ovn/utilities/ovn-trace.c |  51 ++++++++----\n tests/ovn.at              |  31 ++++++++\n tests/test-ovn.c          |  13 +++-\n 9 files changed, 516 insertions(+), 21 deletions(-)","diff":"diff --git a/include/ovn/actions.h b/include/ovn/actions.h\nindex d13a3747b..15cee478d 100644\n--- a/include/ovn/actions.h\n+++ b/include/ovn/actions.h\n@@ -72,7 +72,8 @@ struct simap;\n     OVNACT(PUT_DHCPV6_OPTS,   ovnact_put_opts)        \\\n     OVNACT(SET_QUEUE,         ovnact_set_queue)       \\\n     OVNACT(DNS_LOOKUP,        ovnact_dns_lookup)      \\\n-    OVNACT(LOG,               ovnact_log)\n+    OVNACT(LOG,               ovnact_log)             \\\n+    OVNACT(PUT_ND_RA_OPTS,    ovnact_put_opts)\n \n /* enum ovnact_type, with a member OVNACT_<ENUM> for each action. */\n enum OVS_PACKED_ENUM ovnact_type {\n@@ -418,6 +419,14 @@ enum action_opcode {\n      *   - A variable length string containing the name.\n      */\n     ACTION_OPCODE_LOG,\n+\n+    /* \"result = put_nd_ra_opts(option, ...)\".\n+     * Arguments follow the action_header, in this format:\n+     *   - A 32-bit or 64-bit OXM header designating the result field.\n+     *   - A 32-bit integer specifying a bit offset within the result field.\n+     *   - Any number of ICMPv6 options.\n+     */\n+    ACTION_OPCODE_PUT_ND_RA_OPTS,\n };\n \n /* Header. */\n@@ -438,6 +447,9 @@ struct ovnact_parse_params {\n     /* hmap of 'struct gen_opts_map'  to support 'put_dhcpv6_opts' action */\n     const struct hmap *dhcpv6_opts;\n \n+    /* hmap of 'struct gen_opts_map' to support 'put_nd_ra_opts' action */\n+    const struct hmap *nd_ra_opts;\n+\n     /* Each OVN flow exists in a logical table within a logical pipeline.\n      * These parameters express this context for a set of OVN actions being\n      * parsed:\ndiff --git a/ovn/controller/lflow.c b/ovn/controller/lflow.c\nindex 6b6b91abc..a62ec6ebe 100644\n--- a/ovn/controller/lflow.c\n+++ b/ovn/controller/lflow.c\n@@ -65,6 +65,7 @@ static void consider_logical_flow(struct controller_ctx *ctx,\n                                   const struct sbrec_chassis *chassis,\n                                   struct hmap *dhcp_opts,\n                                   struct hmap *dhcpv6_opts,\n+                                  struct hmap *nd_ra_opts,\n                                   uint32_t *conj_id_ofs,\n                                   const struct shash *addr_sets,\n                                   struct hmap *flow_table,\n@@ -167,17 +168,21 @@ add_logical_flows(struct controller_ctx *ctx,\n                     dhcpv6_opt_row->type);\n     }\n \n+    struct hmap nd_ra_opts = HMAP_INITIALIZER(&nd_ra_opts);\n+    nd_ra_opts_init(&nd_ra_opts);\n+\n     SBREC_LOGICAL_FLOW_FOR_EACH (lflow, ctx->ovnsb_idl) {\n         consider_logical_flow(ctx, chassis_index,\n                               lflow, local_datapaths,\n                               group_table, chassis,\n-                              &dhcp_opts, &dhcpv6_opts, &conj_id_ofs,\n-                              addr_sets, flow_table, active_tunnels,\n-                              local_lport_ids);\n+                              &dhcp_opts, &dhcpv6_opts, &nd_ra_opts,\n+                              &conj_id_ofs, addr_sets, flow_table,\n+                              active_tunnels, local_lport_ids);\n     }\n \n     dhcp_opts_destroy(&dhcp_opts);\n     dhcp_opts_destroy(&dhcpv6_opts);\n+    nd_ra_opts_destroy(&nd_ra_opts);\n }\n \n static void\n@@ -189,6 +194,7 @@ consider_logical_flow(struct controller_ctx *ctx,\n                       const struct sbrec_chassis *chassis,\n                       struct hmap *dhcp_opts,\n                       struct hmap *dhcpv6_opts,\n+                      struct hmap *nd_ra_opts,\n                       uint32_t *conj_id_ofs,\n                       const struct shash *addr_sets,\n                       struct hmap *flow_table,\n@@ -224,6 +230,7 @@ consider_logical_flow(struct controller_ctx *ctx,\n         .symtab = &symtab,\n         .dhcp_opts = dhcp_opts,\n         .dhcpv6_opts = dhcpv6_opts,\n+        .nd_ra_opts = nd_ra_opts,\n \n         .pipeline = ingress ? OVNACT_P_INGRESS : OVNACT_P_EGRESS,\n         .n_tables = LOG_PIPELINE_LEN,\ndiff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c\nindex 43e3cba23..6dbea4efa 100644\n--- a/ovn/controller/pinctrl.c\n+++ b/ovn/controller/pinctrl.c\n@@ -81,6 +81,10 @@ static void pinctrl_handle_nd_na(const struct flow *ip_flow,\n                                  struct ofpbuf *userdata);\n static void reload_metadata(struct ofpbuf *ofpacts,\n                             const struct match *md);\n+static void pinctrl_handle_put_nd_ra_opts(\n+    const struct flow *ip_flow, struct dp_packet *pkt_in,\n+    struct ofputil_packet_in *pin, struct ofpbuf *userdata,\n+    struct ofpbuf *continuation OVS_UNUSED);\n \n COVERAGE_DEFINE(pinctrl_drop_put_mac_binding);\n \n@@ -985,6 +989,11 @@ process_packet_in(const struct ofp_header *msg, struct controller_ctx *ctx)\n         handle_acl_log(&headers, &userdata);\n         break;\n \n+    case ACTION_OPCODE_PUT_ND_RA_OPTS:\n+        pinctrl_handle_put_nd_ra_opts(&headers, &packet, &pin, &userdata,\n+                                      &continuation);\n+        break;\n+\n     default:\n         VLOG_WARN_RL(&rl, \"unrecognized packet-in opcode %\"PRIu32,\n                      ntohl(ah->opcode));\n@@ -1848,3 +1857,90 @@ exit:\n     dp_packet_uninit(&packet);\n     ofpbuf_uninit(&ofpacts);\n }\n+\n+static void\n+pinctrl_handle_put_nd_ra_opts(\n+    const struct flow *in_flow, struct dp_packet *pkt_in,\n+    struct ofputil_packet_in *pin, struct ofpbuf *userdata,\n+    struct ofpbuf *continuation OVS_UNUSED)\n+{\n+    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);\n+    enum ofp_version version = rconn_get_version(swconn);\n+    enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version);\n+    struct dp_packet *pkt_out_ptr = NULL;\n+    uint32_t success = 0;\n+\n+    /* Parse result field. */\n+    const struct mf_field *f;\n+    enum ofperr ofperr = nx_pull_header(userdata, NULL, &f, NULL);\n+    if (ofperr) {\n+       VLOG_WARN_RL(&rl, \"bad result OXM (%s)\", ofperr_to_string(ofperr));\n+       goto exit;\n+    }\n+\n+    /* Parse result offset. */\n+    ovs_be32 *ofsp = ofpbuf_try_pull(userdata, sizeof *ofsp);\n+    if (!ofsp) {\n+        VLOG_WARN_RL(&rl, \"offset not present in the userdata\");\n+        goto exit;\n+    }\n+\n+    /* Check that the result is valid and writable. */\n+    struct mf_subfield dst = { .field = f, .ofs = ntohl(*ofsp), .n_bits = 1 };\n+    ofperr = mf_check_dst(&dst, NULL);\n+    if (ofperr) {\n+        VLOG_WARN_RL(&rl, \"bad result bit (%s)\", ofperr_to_string(ofperr));\n+        goto exit;\n+    }\n+\n+    if (!userdata->size) {\n+        VLOG_WARN_RL(&rl, \"IPv6 ND RA options not present in the userdata\");\n+        goto exit;\n+    }\n+\n+    if (!is_icmpv6(in_flow, NULL) || in_flow->tp_dst != htons(0) ||\n+        in_flow->tp_src != htons(ND_ROUTER_SOLICIT)) {\n+        VLOG_WARN_RL(&rl, \"put_nd_ra action on invalid or unsupported packet\");\n+        goto exit;\n+    }\n+\n+    size_t new_packet_size = pkt_in->l4_ofs + userdata->size;\n+    struct dp_packet pkt_out;\n+    dp_packet_init(&pkt_out, new_packet_size);\n+    dp_packet_clear(&pkt_out);\n+    dp_packet_prealloc_tailroom(&pkt_out, new_packet_size);\n+    pkt_out_ptr = &pkt_out;\n+\n+    /* Copy L2 and L3 headers from pkt_in. */\n+    dp_packet_put(&pkt_out, dp_packet_pull(pkt_in, pkt_in->l4_ofs),\n+                  pkt_in->l4_ofs);\n+\n+    pkt_out.l2_5_ofs = pkt_in->l2_5_ofs;\n+    pkt_out.l2_pad_size = pkt_in->l2_pad_size;\n+    pkt_out.l3_ofs = pkt_in->l3_ofs;\n+    pkt_out.l4_ofs = pkt_in->l4_ofs;\n+\n+    /* Copy the ICMPv6 Router Advertisement data from 'userdata' field. */\n+    dp_packet_put(&pkt_out, userdata->data, userdata->size);\n+\n+    /* Set the IPv6 payload length and calculate the ICMPv6 checksum. */\n+    struct ovs_16aligned_ip6_hdr *nh = dp_packet_l3(&pkt_out);\n+    nh->ip6_plen = htons(userdata->size);\n+    struct ovs_ra_msg *ra = dp_packet_l4(&pkt_out);\n+    ra->icmph.icmp6_cksum = 0;\n+    uint32_t icmp_csum = packet_csum_pseudoheader6(nh);\n+    ra->icmph.icmp6_cksum = csum_finish(csum_continue(\n+        icmp_csum, ra, userdata->size));\n+    pin->packet = dp_packet_data(&pkt_out);\n+    pin->packet_len = dp_packet_size(&pkt_out);\n+    success = 1;\n+\n+exit:\n+    if (!ofperr) {\n+        union mf_subvalue sv;\n+        sv.u8_val = success;\n+        mf_write_subfield(&dst, &sv, &pin->flow_metadata);\n+    }\n+    queue_msg(ofputil_encode_resume(pin, continuation, proto));\n+    dp_packet_uninit(pkt_out_ptr);\n+}\ndiff --git a/ovn/lib/actions.c b/ovn/lib/actions.c\nindex b2559cd07..2981d89f6 100644\n--- a/ovn/lib/actions.c\n+++ b/ovn/lib/actions.c\n@@ -22,6 +22,7 @@\n #include \"compiler.h\"\n #include \"ovn-l7.h\"\n #include \"hash.h\"\n+#include \"lib/packets.h\"\n #include \"logical-fields.h\"\n #include \"nx-match.h\"\n #include \"openvswitch/dynamic-string.h\"\n@@ -1438,7 +1439,7 @@ parse_put_opts(struct action_context *ctx, const struct expr_field *dst,\n                struct ovnact_put_opts *po, const struct hmap *gen_opts,\n                const char *opts_type)\n {\n-    lexer_get(ctx->lexer); /* Skip put_dhcp[v6]_opts. */\n+    lexer_get(ctx->lexer); /* Skip put_dhcp[v6]_opts / put_nd_ra_opts. */\n     lexer_get(ctx->lexer); /* Skip '('. */\n \n     /* Validate that the destination is a 1-bit, modifiable field. */\n@@ -1771,6 +1772,193 @@ static void\n ovnact_dns_lookup_free(struct ovnact_dns_lookup *dl OVS_UNUSED)\n {\n }\n+\n+/* Parses the \"put_nd_ra_opts\" action.\n+ * The caller has already consumed \"<dst> =\", so this just parses the rest. */\n+static void\n+parse_put_nd_ra_opts(struct action_context *ctx, const struct expr_field *dst,\n+                     struct ovnact_put_opts *po)\n+{\n+    parse_put_opts(ctx, dst, po, ctx->pp->nd_ra_opts, \"IPv6 ND RA\");\n+\n+    if (ctx->lexer->error) {\n+        return;\n+    }\n+\n+    bool addr_mode_stateful = false;\n+    bool prefix_set = false;\n+    bool slla_present = false;\n+    /* Let's validate the options. */\n+    for (struct ovnact_gen_option *o = po->options;\n+            o < &po->options[po->n_options]; o++) {\n+        const union expr_constant *c = o->value.values;\n+        if (o->value.n_values > 1) {\n+            lexer_error(ctx->lexer, \"parse_put_nd_ra_opts -Invalid value for\"\n+                        \" the option %s.\", o->option->name);\n+            return;\n+        }\n+\n+        switch (o->option->code) {\n+        case ND_RA_FLAG_ADDR_MODE:\n+            if (!c->string || (strcmp(c->string, \"slaac\") &&\n+                               strcmp(c->string, \"dhcpv6_stateful\") &&\n+                               strcmp(c->string, \"dhcpv6_stateless\"))) {\n+                lexer_error(ctx->lexer, \"parse_put_nd_ra_opts -Invalid value \"\n+                            \"for the option %s.\", o->option->name);\n+                return;\n+            }\n+\n+            if (!strcmp(c->string, \"dhcpv6_stateful\")) {\n+                addr_mode_stateful = true;\n+            }\n+            break;\n+\n+        case ND_OPT_SOURCE_LINKADDR:\n+            if (c->format != LEX_F_ETHERNET) {\n+                lexer_error(ctx->lexer, \"parse_put_nd_ra_opts -Invalid value \"\n+                           \"for the option %s.\", o->option->name);\n+            }\n+            slla_present = true;\n+            break;\n+\n+        case ND_OPT_PREFIX_INFORMATION:\n+            if (c->format != LEX_F_IPV6 || !c->masked) {\n+                lexer_error(ctx->lexer, \"parse_put_nd_ra_opts -Invalid value \"\n+                            \"for the option %s.\", o->option->name);\n+            }\n+            prefix_set = true;\n+            break;\n+\n+        case ND_OPT_MTU:\n+            if (c->format != LEX_F_DECIMAL) {\n+                lexer_error(ctx->lexer, \"parse_put_nd_ra_opts -Invalid value \"\n+                            \"for the option %s.\", o->option->name);\n+            }\n+            break;\n+        }\n+    }\n+\n+    if (ctx->lexer->error) {\n+        return;\n+    }\n+\n+    if (!slla_present) {\n+        lexer_error(ctx->lexer, \"parse_put_nd_ra_opts - slla option not\"\n+                    \" present.\");\n+        return;\n+    }\n+\n+    if (addr_mode_stateful && prefix_set) {\n+        lexer_error(ctx->lexer, \"parse_put_nd_ra_opts - prefix option can't be\"\n+                    \" set when address mode is dhcpv6_stateful.\");\n+        return;\n+    }\n+\n+    if (!addr_mode_stateful && !prefix_set) {\n+        lexer_error(ctx->lexer, \"parse_put_nd_ra_opts - prefix option needs \"\n+                    \"to be set when address mode is slaac/dhcpv6_stateless.\");\n+        return;\n+    }\n+\n+    add_prerequisite(ctx, \"ip6\");\n+}\n+\n+static void\n+format_PUT_ND_RA_OPTS(const struct ovnact_put_opts *po,\n+                      struct ds *s)\n+{\n+    format_put_opts(\"put_nd_ra_opts\", po, s);\n+}\n+\n+static void\n+encode_put_nd_ra_option(const struct ovnact_gen_option *o,\n+                        struct ofpbuf *ofpacts, struct ovs_ra_msg *ra)\n+{\n+    const union expr_constant *c = o->value.values;\n+\n+    switch (o->option->code) {\n+    case ND_RA_FLAG_ADDR_MODE:\n+        if (!strcmp(c->string, \"dhcpv6_stateful\")) {\n+            ra->mo_flags = IPV6_ND_RA_FLAG_MANAGED_ADDR_CONFIG;\n+        } else if (!strcmp(c->string, \"dhcpv6_stateless\")) {\n+            ra->mo_flags = IPV6_ND_RA_FLAG_OTHER_ADDR_CONFIG;\n+        }\n+        break;\n+\n+    case ND_OPT_SOURCE_LINKADDR:\n+    {\n+        struct ovs_nd_lla_opt *lla_opt =\n+            ofpbuf_put_uninit(ofpacts, sizeof *lla_opt);\n+        lla_opt->type = ND_OPT_SOURCE_LINKADDR;\n+        lla_opt->len = 1;\n+        lla_opt->mac = c->value.mac;\n+        break;\n+    }\n+\n+    case ND_OPT_MTU:\n+    {\n+        struct ovs_nd_mtu_opt *mtu_opt =\n+            ofpbuf_put_uninit(ofpacts, sizeof *mtu_opt);\n+        mtu_opt->type = ND_OPT_MTU;\n+        mtu_opt->len = 1;\n+        mtu_opt->reserved = 0;\n+        put_16aligned_be32(&mtu_opt->mtu, c->value.be32_int);\n+        break;\n+    }\n+\n+    case ND_OPT_PREFIX_INFORMATION:\n+    {\n+        struct ovs_nd_prefix_opt *prefix_opt =\n+            ofpbuf_put_uninit(ofpacts, sizeof *prefix_opt);\n+        uint8_t prefix_len = ipv6_count_cidr_bits(&c->mask.ipv6);\n+        prefix_opt->type = ND_OPT_PREFIX_INFORMATION;\n+        prefix_opt->len = 4;\n+        prefix_opt->prefix_len = prefix_len;\n+        prefix_opt->la_flags = IPV6_ND_RA_OPT_PREFIX_FLAGS;\n+        put_16aligned_be32(&prefix_opt->valid_lifetime,\n+                           htonl(IPV6_ND_RA_OPT_PREFIX_VALID_LIFETIME));\n+        put_16aligned_be32(&prefix_opt->preferred_lifetime,\n+                           htonl(IPV6_ND_RA_OPT_PREFIX_PREFERRED_LIFETIME));\n+        put_16aligned_be32(&prefix_opt->reserved, 0);\n+        memcpy(prefix_opt->prefix.be32, &c->value.be128[7].be32,\n+               sizeof(ovs_be32[4]));\n+        break;\n+    }\n+    }\n+}\n+\n+static void\n+encode_PUT_ND_RA_OPTS(const struct ovnact_put_opts *po OVS_UNUSED,\n+                      const struct ovnact_encode_params *ep OVS_UNUSED,\n+                      struct ofpbuf *ofpacts OVS_UNUSED)\n+{\n+    struct mf_subfield dst = expr_resolve_field(&po->dst);\n+\n+    size_t oc_offset = encode_start_controller_op(\n+        ACTION_OPCODE_PUT_ND_RA_OPTS, true, ofpacts);\n+    nx_put_header(ofpacts, dst.field->id, OFP13_VERSION, false);\n+    ovs_be32 ofs = htonl(dst.ofs);\n+    ofpbuf_put(ofpacts, &ofs, sizeof ofs);\n+\n+    /* Frame the complete ICMPv6 Router Advertisement data encoding\n+     * the ND RA options in it, in the userdata field, so that when\n+     * pinctrl module receives the ICMPv6 Router Solicitation packet\n+     * it can copy the userdata field AS IS and resume the packet.\n+     */\n+    struct ovs_ra_msg *ra = ofpbuf_put_zeros(ofpacts, sizeof *ra);\n+    ra->icmph.icmp6_type = ND_ROUTER_ADVERT;\n+    ra->cur_hop_limit = IPV6_ND_RA_CUR_HOP_LIMIT;\n+    ra->mo_flags = 0;\n+    ra->router_lifetime = htons(IPV6_ND_RA_LIFETIME);\n+\n+    for (const struct ovnact_gen_option *o = po->options;\n+         o < &po->options[po->n_options]; o++) {\n+        encode_put_nd_ra_option(o, ofpacts, ra);\n+    }\n+\n+    encode_finish_controller_op(oc_offset, ofpacts);\n+}\n+\n \f\n static void\n parse_log_arg(struct action_context *ctx, struct ovnact_log *log)\n@@ -1910,6 +2098,10 @@ parse_set_action(struct action_context *ctx)\n         } else if (!strcmp(ctx->lexer->token.s, \"dns_lookup\")\n                    && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {\n             parse_dns_lookup(ctx, &lhs, ovnact_put_DNS_LOOKUP(ctx->ovnacts));\n+        } else if (!strcmp(ctx->lexer->token.s, \"put_nd_ra_opts\")\n+                && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {\n+            parse_put_nd_ra_opts(ctx, &lhs,\n+                                 ovnact_put_PUT_ND_RA_OPTS(ctx->ovnacts));\n         } else {\n             parse_assignment_action(ctx, false, &lhs);\n         }\ndiff --git a/ovn/lib/ovn-l7.h b/ovn/lib/ovn-l7.h\nindex 40bd75461..41cdacdfc 100644\n--- a/ovn/lib/ovn-l7.h\n+++ b/ovn/lib/ovn-l7.h\n@@ -18,6 +18,7 @@\n #define OVN_DHCP_H 1\n \n #include <netinet/in.h>\n+#include <netinet/icmp6.h>\n #include \"openvswitch/hmap.h\"\n #include \"hash.h\"\n \n@@ -206,4 +207,51 @@ struct dhcpv6_opt_ia_na {\n #define DHCPV6_OPT_PAYLOAD(opt) \\\n     (void *)((char *)opt + sizeof(struct dhcpv6_opt_header))\n \n+static inline struct gen_opts_map *\n+nd_ra_opts_find(const struct hmap *nd_ra_opts, char *opt_name)\n+{\n+    return gen_opts_find(nd_ra_opts, opt_name);\n+}\n+\n+static inline void\n+nd_ra_opt_add(struct hmap *nd_ra_opts, char *opt_name, size_t code,\n+               char *type)\n+{\n+    gen_opt_add(nd_ra_opts, opt_name, code, type);\n+}\n+\n+static inline void\n+nd_ra_opts_destroy(struct hmap *nd_ra_opts)\n+{\n+    gen_opts_destroy(nd_ra_opts);\n+}\n+\n+\n+#define ND_RA_FLAG_ADDR_MODE    0\n+\n+\n+/* Default values of various IPv6 Neighbor Discovery protocol options and\n+ * flags. See RFC 4861 for more information.\n+ * */\n+#define IPV6_ND_RA_FLAG_MANAGED_ADDR_CONFIG         0x80\n+#define IPV6_ND_RA_FLAG_OTHER_ADDR_CONFIG           0x40\n+\n+#define IPV6_ND_RA_CUR_HOP_LIMIT                    255\n+#define IPV6_ND_RA_LIFETIME                         0xffff\n+#define IPV6_ND_RA_REACHABLE_TIME                   0\n+#define IPV6_ND_RA_RETRANSMIT_TIMER                 0\n+\n+#define IPV6_ND_RA_OPT_PREFIX_FLAGS                 0xc0\n+#define IPV6_ND_RA_OPT_PREFIX_VALID_LIFETIME        0xffffffff\n+#define IPV6_ND_RA_OPT_PREFIX_PREFERRED_LIFETIME    0xffffffff\n+\n+static inline void\n+nd_ra_opts_init(struct hmap *nd_ra_opts)\n+{\n+    nd_ra_opt_add(nd_ra_opts, \"addr_mode\", ND_RA_FLAG_ADDR_MODE, \"str\");\n+    nd_ra_opt_add(nd_ra_opts, \"slla\", ND_OPT_SOURCE_LINKADDR, \"mac\");\n+    nd_ra_opt_add(nd_ra_opts, \"prefix\", ND_OPT_PREFIX_INFORMATION, \"ipv6\");\n+    nd_ra_opt_add(nd_ra_opts, \"mtu\", ND_OPT_MTU, \"uint32\");\n+}\n+\n #endif /* OVN_DHCP_H */\ndiff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml\nindex 0a894f8cb..fab3f9de6 100644\n--- a/ovn/ovn-sb.xml\n+++ b/ovn/ovn-sb.xml\n@@ -1516,6 +1516,83 @@\n             <b>Prerequisite:</b> <code>udp</code>\n           </p>\n         </dd>\n+\n+        <dt>\n+          <code><var>R</var> = put_nd_ra_opts(<var>D1</var> = <var>V1</var>, <var>D2</var> = <var>V2</var>, ..., <var>Dn</var> = <var>Vn</var>);</code>\n+        </dt>\n+\n+        <dd>\n+          <p>\n+            <b>Parameters</b>: The following IPv6 ND Router Advertisement\n+               option/value pairs as defined in RFC 4861.\n+\n+            <ul>\n+              <li>\n+                <code>addr_mode</code>\n+                <p>\n+                  Mandatory parameter which specifies the address mode flag to\n+                  be set in the RA flag options field. The value of this option\n+                  is a string and the following values can be defined -\n+                  \"slaac\", \"dhcpv6_stateful\" and \"dhcpv6_stateless\".\n+                </p>\n+              </li>\n+\n+              <li>\n+                <code>slla</code>\n+                <p>\n+                  Mandatory parameter which specifies the link-layer address of\n+                  the interface from which the Router Advertisement is sent.\n+                </p>\n+              </li>\n+\n+              <li>\n+                <code>mtu</code>\n+                <p>\n+                  Optional parameter which specifies the MTU.\n+                </p>\n+              </li>\n+\n+              <li>\n+                <code>prefix</code>\n+                <p>\n+                  Optional parameter which should be specified if the addr_mode\n+                  is \"slaac\" or \"dhcpv6_stateless\". The value should be an IPv6\n+                  prefix which will be used for stateless IPv6 address\n+                  configuration. This option can be defined multiple times.\n+                </p>\n+              </li>\n+            </ul>\n+          </p>\n+\n+          <p>\n+            <b>Result</b>: stored to a 1-bit subfield <var>R</var>.\n+          </p>\n+\n+          <p>\n+            Valid only in the ingress pipeline.\n+          </p>\n+\n+          <p>\n+            When this action is applied to an IPv6 Router solicitation request\n+            packet, it changes the packet into an IPv6 Router Advertisement\n+            reply and adds the options specified in the parameters, and stores\n+            1 in <var>R</var>.\n+          </p>\n+\n+          <p>\n+            When this action is applied to a non-IPv6 Router solicitation\n+            packet or an invalid IPv6 request packet , it leaves the packet\n+            unchanged and stores 0 in <var>R</var>.\n+          </p>\n+\n+          <p>\n+            <b>Example:</b>\n+            <code>\n+              reg0[3] = put_nd_ra_opts(addr_mode = \"slaac\",\n+              slla = 00:00:00:00:10:02, prefix = aef0::/64, mtu = 1450);\n+            </code>\n+          </p>\n+        </dd>\n       </dl>\n \n       <dl>\ndiff --git a/ovn/utilities/ovn-trace.c b/ovn/utilities/ovn-trace.c\nindex d9465c90c..211148b8b 100644\n--- a/ovn/utilities/ovn-trace.c\n+++ b/ovn/utilities/ovn-trace.c\n@@ -420,6 +420,7 @@ static struct shash address_sets;\n /* DHCP options. */\n static struct hmap dhcp_opts;   /* Contains \"struct gen_opts_map\"s. */\n static struct hmap dhcpv6_opts; /* Contains \"struct gen_opts_map\"s. */\n+static struct hmap nd_ra_opts; /* Contains \"struct gen_opts_map\"s. */\n \n static struct ovntrace_datapath *\n ovntrace_datapath_find_by_sb_uuid(const struct uuid *sb_uuid)\n@@ -806,6 +807,7 @@ read_flows(void)\n             .symtab = &symtab,\n             .dhcp_opts = &dhcp_opts,\n             .dhcpv6_opts = &dhcpv6_opts,\n+            .nd_ra_opts = &nd_ra_opts,\n             .pipeline = (!strcmp(sblf->pipeline, \"ingress\")\n                          ? OVNACT_P_INGRESS\n                          : OVNACT_P_EGRESS),\n@@ -881,6 +883,9 @@ read_gen_opts(void)\n     SBREC_DHCPV6_OPTIONS_FOR_EACH(sdo6, ovnsb_idl) {\n        dhcp_opt_add(&dhcpv6_opts, sdo6->name, sdo6->code, sdo6->type);\n     }\n+\n+    hmap_init(&nd_ra_opts);\n+    nd_ra_opts_init(&nd_ra_opts);\n }\n \n static void\n@@ -1541,19 +1546,15 @@ execute_get_mac_bind(const struct ovnact_get_mac_bind *bind,\n }\n \n static void\n-execute_put_dhcp_opts(const struct ovnact_put_opts *pdo,\n-                      const char *name, struct flow *uflow,\n-                      struct ovs_list *super)\n+execute_put_opts(const struct ovnact_put_opts *po,\n+                 const char *name, struct flow *uflow,\n+                 struct ovs_list *super)\n {\n-    ovntrace_node_append(\n-        super, OVNTRACE_NODE_ERROR,\n-        \"/* We assume that this packet is DHCPDISCOVER or DHCPREQUEST. */\");\n-\n     /* Format the put_dhcp_opts action. */\n     struct ds s = DS_EMPTY_INITIALIZER;\n-    for (const struct ovnact_gen_option *o = pdo->options;\n-         o < &pdo->options[pdo->n_options]; o++) {\n-        if (o != pdo->options) {\n+    for (const struct ovnact_gen_option *o = po->options;\n+         o < &po->options[po->n_options]; o++) {\n+        if (o != po->options) {\n             ds_put_cstr(&s, \", \");\n         }\n         ds_put_format(&s, \"%s = \", o->option->name);\n@@ -1562,22 +1563,41 @@ execute_put_dhcp_opts(const struct ovnact_put_opts *pdo,\n     ovntrace_node_append(super, OVNTRACE_NODE_MODIFY, \"%s(%s)\",\n                          name, ds_cstr(&s));\n \n-    struct mf_subfield dst = expr_resolve_field(&pdo->dst);\n+    struct mf_subfield dst = expr_resolve_field(&po->dst);\n     if (!mf_is_register(dst.field->id)) {\n         /* Format assignment. */\n         ds_clear(&s);\n-        expr_field_format(&pdo->dst, &s);\n+        expr_field_format(&po->dst, &s);\n         ovntrace_node_append(super, OVNTRACE_NODE_MODIFY,\n                              \"%s = 1\", ds_cstr(&s));\n     }\n     ds_destroy(&s);\n \n-    struct mf_subfield sf = expr_resolve_field(&pdo->dst);\n+    struct mf_subfield sf = expr_resolve_field(&po->dst);\n     union mf_subvalue sv = { .u8_val = 1 };\n     mf_write_subfield_flow(&sf, &sv, uflow);\n }\n \n static void\n+execute_put_dhcp_opts(const struct ovnact_put_opts *pdo,\n+                      const char *name, struct flow *uflow,\n+                      struct ovs_list *super)\n+{\n+    ovntrace_node_append(\n+        super, OVNTRACE_NODE_ERROR,\n+        \"/* We assume that this packet is DHCPDISCOVER or DHCPREQUEST. */\");\n+    execute_put_opts(pdo, name, uflow, super);\n+}\n+\n+static void\n+execute_put_nd_ra_opts(const struct ovnact_put_opts *pdo,\n+                       const char *name, struct flow *uflow,\n+                       struct ovs_list *super)\n+{\n+    execute_put_opts(pdo, name, uflow, super);\n+}\n+\n+static void\n execute_next(const struct ovnact_next *next,\n              const struct ovntrace_datapath *dp, struct flow *uflow,\n              enum ovnact_pipeline pipeline, struct ovs_list *super)\n@@ -1814,6 +1834,11 @@ trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len,\n                                   \"put_dhcpv6_opts\", uflow, super);\n             break;\n \n+        case OVNACT_PUT_ND_RA_OPTS:\n+            execute_put_nd_ra_opts(ovnact_get_PUT_DHCPV6_OPTS(a),\n+                                   \"put_nd_ra_opts\", uflow, super);\n+            break;\n+\n         case OVNACT_SET_QUEUE:\n             /* The set_queue action is slippery from a logical perspective.  It\n              * has no visible effect as long as the packet remains on the same\ndiff --git a/tests/ovn.at b/tests/ovn.at\nindex 6c38b973f..e56dc6232 100644\n--- a/tests/ovn.at\n+++ b/tests/ovn.at\n@@ -1066,6 +1066,37 @@ reg1[0] = dns_lookup();\n reg1[0] = dns_lookup(\"foo\");\n     dns_lookup doesn't take any parameters\n \n+# put_nd_ra_opts\n+reg1[0] = put_nd_ra_opts(addr_mode = \"slaac\", mtu = 1500, prefix = aef0::/64, slla = ae:01:02:03:04:05);\n+    encodes as controller(userdata=00.00.00.08.00.00.00.00.00.01.de.10.00.00.00.40.86.00.00.00.ff.00.ff.ff.00.00.00.00.00.00.00.00.05.01.00.00.00.00.05.dc.03.04.40.c0.ff.ff.ff.ff.ff.ff.ff.ff.00.00.00.00.ae.f0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.01.01.ae.01.02.03.04.05,pause)\n+    has prereqs ip6\n+reg1[0] = put_nd_ra_opts(addr_mode = \"dhcpv6_stateful\", slla = ae:01:02:03:04:10, mtu = 1450);\n+    encodes as controller(userdata=00.00.00.08.00.00.00.00.00.01.de.10.00.00.00.40.86.00.00.00.ff.80.ff.ff.00.00.00.00.00.00.00.00.01.01.ae.01.02.03.04.10.05.01.00.00.00.00.05.aa,pause)\n+    has prereqs ip6\n+reg1[0] = put_nd_ra_opts(addr_mode = \"dhcpv6_stateless\", slla = ae:01:02:03:04:06, prefix = aef0::/64);\n+    encodes as controller(userdata=00.00.00.08.00.00.00.00.00.01.de.10.00.00.00.40.86.00.00.00.ff.40.ff.ff.00.00.00.00.00.00.00.00.01.01.ae.01.02.03.04.06.03.04.40.c0.ff.ff.ff.ff.ff.ff.ff.ff.00.00.00.00.ae.f0.00.00.00.00.00.00.00.00.00.00.00.00.00.00,pause)\n+    has prereqs ip6\n+reg1[0] = put_nd_ra_opts(addr_mode = \"slaac\", mtu = 1500, prefix = aef0::/64);\n+    parse_put_nd_ra_opts - slla option not present.\n+reg1[0] = put_nd_ra_opts(addr_mode = \"dhcpv6_stateful\", mtu = 1450, prefix = aef0::/64, prefix = bef0::/64, slla = ae:01:02:03:04:10);\n+    parse_put_nd_ra_opts - prefix option can't be set when address mode is dhcpv6_stateful.\n+reg1[0] = put_nd_ra_opts(addr_mode = \"dhcpv6_stateful\", mtu = 1450, prefix = aef0::/64, prefix = bef0::/64, slla = ae:01:02:03:04:10);\n+    parse_put_nd_ra_opts - prefix option can't be set when address mode is dhcpv6_stateful.\n+reg1[0] = put_nd_ra_opts(addr_mode = \"slaac\", slla = ae:01:02:03:04:10);\n+    parse_put_nd_ra_opts - prefix option needs to be set when address mode is slaac/dhcpv6_stateless.\n+reg1[0] = put_nd_ra_opts(addr_mode = \"dhcpv6_stateless\", slla = ae:01:02:03:04:10);\n+    parse_put_nd_ra_opts - prefix option needs to be set when address mode is slaac/dhcpv6_stateless.\n+reg1[0] = put_nd_ra_opts(addr_mode = dhcpv6_stateless, prefix = aef0::/64, slla = ae:01:02:03:04:10);\n+    Syntax error at `dhcpv6_stateless' expecting constant.\n+reg1[0] = put_nd_ra_opts(addr_mode = \"slaac\", mtu = 1500, prefix = aef0::, slla = ae:01:02:03:04:10);\n+    parse_put_nd_ra_opts -Invalid value for the option prefix.\n+reg1[0] = put_nd_ra_opts(addr_mode = \"foo\", mtu = 1500, slla = ae:01:02:03:04:10);\n+    parse_put_nd_ra_opts -Invalid value for the option addr_mode.\n+reg1[0] = put_nd_ra_opts(addr_mode = \"slaac\", mtu = \"1500\", slla = ae:01:02:03:04:10);\n+    IPv6 ND RA option mtu requires numeric value.\n+reg1[0] = put_nd_ra_opts(addr_mode = \"slaac\", mtu = 10.0.0.4, slla = ae:01:02:03:04:10);\n+    parse_put_nd_ra_opts -Invalid value for the option mtu.\n+\n # Contradictionary prerequisites (allowed but not useful):\n ip4.src = ip6.src[0..31];\n     encodes as move:NXM_NX_IPV6_SRC[0..31]->NXM_OF_IP_SRC[]\ndiff --git a/tests/test-ovn.c b/tests/test-ovn.c\nindex 67221ea50..f9a5085f7 100644\n--- a/tests/test-ovn.c\n+++ b/tests/test-ovn.c\n@@ -155,7 +155,8 @@ create_symtab(struct shash *symtab)\n }\n \n static void\n-create_dhcp_opts(struct hmap *dhcp_opts, struct hmap *dhcpv6_opts)\n+create_gen_opts(struct hmap *dhcp_opts, struct hmap *dhcpv6_opts,\n+                struct hmap *nd_ra_opts)\n {\n     hmap_init(dhcp_opts);\n     dhcp_opt_add(dhcp_opts, \"offerip\", 0, \"ipv4\");\n@@ -187,6 +188,10 @@ create_dhcp_opts(struct hmap *dhcp_opts, struct hmap *dhcpv6_opts)\n     dhcp_opt_add(dhcpv6_opts, \"ia_addr\",  5, \"ipv6\");\n     dhcp_opt_add(dhcpv6_opts, \"dns_server\",  23, \"ipv6\");\n     dhcp_opt_add(dhcpv6_opts, \"domain_search\",  24, \"str\");\n+\n+    /* IPv6 ND RA options. */\n+    hmap_init(nd_ra_opts);\n+    nd_ra_opts_init(nd_ra_opts);\n }\n \n static void\n@@ -1193,12 +1198,13 @@ test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED)\n     struct shash symtab;\n     struct hmap dhcp_opts;\n     struct hmap dhcpv6_opts;\n+    struct hmap nd_ra_opts;\n     struct simap ports;\n     struct ds input;\n     bool ok = true;\n \n     create_symtab(&symtab);\n-    create_dhcp_opts(&dhcp_opts, &dhcpv6_opts);\n+    create_gen_opts(&dhcp_opts, &dhcpv6_opts, &nd_ra_opts);\n \n     /* Initialize group ids. */\n     struct group_table group_table;\n@@ -1226,6 +1232,7 @@ test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED)\n             .symtab = &symtab,\n             .dhcp_opts = &dhcp_opts,\n             .dhcpv6_opts = &dhcpv6_opts,\n+            .nd_ra_opts = &nd_ra_opts,\n             .n_tables = 24,\n             .cur_ltable = 10,\n         };\n@@ -1310,7 +1317,7 @@ test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED)\n     shash_destroy(&symtab);\n     dhcp_opts_destroy(&dhcp_opts);\n     dhcp_opts_destroy(&dhcpv6_opts);\n-\n+    nd_ra_opts_destroy(&nd_ra_opts);\n     exit(ok ? EXIT_SUCCESS : EXIT_FAILURE);\n }\n \f\n","prefixes":["ovs-dev","v8","2/4"]}