Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/patches/817001/?format=api
{ "id": 817001, "url": "http://patchwork.ozlabs.org/api/patches/817001/?format=api", "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=api", "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=api", "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=api", "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" ] }