diff mbox

[ovs-dev,v3,2/3] ovn-controller: Add a new action - 'put_nd_ra_opts'

Message ID 20170714122613.18901-1-nusiddiq@redhat.com
State Superseded
Headers show

Commit Message

Numan Siddique July 14, 2017, 12:26 p.m. UTC
From: Numan Siddique <nusiddiq@redhat.com>

This patch adds a new OVN action 'put_nd_ra_opts' to support native
IPv6 Router Advertisement in OVN. This action can be used to respond
to the IPv6 Router Solicitation requests.

ovn-controller parses this action and adds a NXT_PACKET_IN2 OF flow
with 'pause' flag set and the RA options stored in 'userdata' field.
This action is similar to 'put_dhcp_opts' and 'put_dhcpv6_opts'.

When a valid IPv6 RS packet is received by the pinctrl module of
ovn-controller, it frames a new RA packet and sets the RA options
from the 'userdata' field and resumes the packet storing 1 in the
1-bit result sub-field. If the packet is invalid, it resumes the
packet without any modifications storing 0 in the 1-bit result
sub-field.

Eg. reg0[5] = put_nd_ra_opts(address_mode = "slaac", mtu = 1450,
                             slla = 01:02:03:04:05:06, prefix = aef0::/64)

Note that unlike DHCPv4/v6, a new table to store the supported IPv6 ND RA
options is not added in SB DB since there are only 3 ND RA options.

Co-authored-by: Zongkai LI <zealokii@gmail.com>
Signed-off-by: Zongkai LI <zealokii@gmail.com>
Signed-off-by: Numan Siddique <nusiddiq@redhat.com>
---
 include/ovn/actions.h     |  14 +++-
 ovn/controller/lflow.c    |  11 +++-
 ovn/controller/pinctrl.c  | 145 +++++++++++++++++++++++++++++++++++++++++
 ovn/lib/actions.c         | 161 +++++++++++++++++++++++++++++++++++++++++++++-
 ovn/lib/ovn-l7.h          |  55 ++++++++++++++++
 ovn/ovn-sb.xml            |  77 ++++++++++++++++++++++
 ovn/utilities/ovn-trace.c |  46 +++++++++----
 tests/ovn.at              |  31 +++++++++
 tests/test-ovn.c          |  13 +++-
 9 files changed, 533 insertions(+), 20 deletions(-)

Comments

Miguel Angel Ajo July 25, 2017, 12:44 p.m. UTC | #1
Looks good to my unexperienced eye.

Acked-by: Miguel Angel Ajo <majopela@redhat.com>

On Fri, Jul 14, 2017 at 2:26 PM, <nusiddiq@redhat.com> wrote:

> From: Numan Siddique <nusiddiq@redhat.com>
>
> This patch adds a new OVN action 'put_nd_ra_opts' to support native
> IPv6 Router Advertisement in OVN. This action can be used to respond
> to the IPv6 Router Solicitation requests.
>
> ovn-controller parses this action and adds a NXT_PACKET_IN2 OF flow
> with 'pause' flag set and the RA options stored in 'userdata' field.
> This action is similar to 'put_dhcp_opts' and 'put_dhcpv6_opts'.
>
> When a valid IPv6 RS packet is received by the pinctrl module of
> ovn-controller, it frames a new RA packet and sets the RA options
> from the 'userdata' field and resumes the packet storing 1 in the
> 1-bit result sub-field. If the packet is invalid, it resumes the
> packet without any modifications storing 0 in the 1-bit result
> sub-field.
>
> Eg. reg0[5] = put_nd_ra_opts(address_mode = "slaac", mtu = 1450,
>                              slla = 01:02:03:04:05:06, prefix = aef0::/64)
>
> Note that unlike DHCPv4/v6, a new table to store the supported IPv6 ND RA
> options is not added in SB DB since there are only 3 ND RA options.
>
> Co-authored-by: Zongkai LI <zealokii@gmail.com>
> Signed-off-by: Zongkai LI <zealokii@gmail.com>
> Signed-off-by: Numan Siddique <nusiddiq@redhat.com>
> ---
>  include/ovn/actions.h     |  14 +++-
>  ovn/controller/lflow.c    |  11 +++-
>  ovn/controller/pinctrl.c  | 145 +++++++++++++++++++++++++++++++++++++++++
>  ovn/lib/actions.c         | 161 ++++++++++++++++++++++++++++++
> +++++++++++++++-
>  ovn/lib/ovn-l7.h          |  55 ++++++++++++++++
>  ovn/ovn-sb.xml            |  77 ++++++++++++++++++++++
>  ovn/utilities/ovn-trace.c |  46 +++++++++----
>  tests/ovn.at              |  31 +++++++++
>  tests/test-ovn.c          |  13 +++-
>  9 files changed, 533 insertions(+), 20 deletions(-)
>
> diff --git a/include/ovn/actions.h b/include/ovn/actions.h
> index de55d88c7..d15c13baa 100644
> --- a/include/ovn/actions.h
> +++ b/include/ovn/actions.h
> @@ -71,7 +71,8 @@ struct simap;
>      OVNACT(PUT_DHCPV4_OPTS, ovnact_put_opts)   \
>      OVNACT(PUT_DHCPV6_OPTS, ovnact_put_opts)   \
>      OVNACT(SET_QUEUE,       ovnact_set_queue)       \
> -    OVNACT(DNS_LOOKUP,      ovnact_dns_lookup)
> +    OVNACT(DNS_LOOKUP,      ovnact_dns_lookup)      \
> +    OVNACT(PUT_ND_RA_OPTS,  ovnact_put_opts)
>
>  /* enum ovnact_type, with a member OVNACT_<ENUM> for each action. */
>  enum OVS_PACKED_ENUM ovnact_type {
> @@ -400,6 +401,14 @@ enum action_opcode {
>       *
>       */
>      ACTION_OPCODE_DNS_LOOKUP,
> +
> +    /* "result = put_nd_ra_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 ICMPv6 options.
> +     */
> +    ACTION_OPCODE_PUT_ND_RA_OPTS,
>  };
>
>  /* Header. */
> @@ -420,6 +429,9 @@ struct ovnact_parse_params {
>      /* hmap of 'struct gen_opts_map'  to support 'put_dhcpv6_opts' action
> */
>      const struct hmap *dhcpv6_opts;
>
> +    /* hmap of 'struct gen_opts_map' to support 'put_nd_ra_opts' action */
> +    const struct hmap *nd_ra_opts;
> +
>      /* Each OVN flow exists in a logical table within a logical pipeline.
>       * These parameters express this context for a set of OVN actions
> being
>       * parsed:
> diff --git a/ovn/controller/lflow.c b/ovn/controller/lflow.c
> index c4fe5f9eb..fce132117 100644
> --- a/ovn/controller/lflow.c
> +++ b/ovn/controller/lflow.c
> @@ -63,6 +63,7 @@ static void consider_logical_flow(const struct
> lport_index *lports,
>                                    const struct sbrec_chassis *chassis,
>                                    struct hmap *dhcp_opts,
>                                    struct hmap *dhcpv6_opts,
> +                                  struct hmap *nd_ra_opts,
>                                    uint32_t *conj_id_ofs,
>                                    const struct shash *addr_sets,
>                                    struct hmap *flow_table);
> @@ -143,15 +144,19 @@ add_logical_flows(struct controller_ctx *ctx, const
> struct lport_index *lports,
>                      dhcpv6_opt_row->type);
>      }
>
> +    struct hmap nd_ra_opts = HMAP_INITIALIZER(&nd_ra_opts);
> +    nd_ra_opts_init(&nd_ra_opts);
> +
>      SBREC_LOGICAL_FLOW_FOR_EACH (lflow, ctx->ovnsb_idl) {
>          consider_logical_flow(lports, mcgroups, lflow, local_datapaths,
>                                group_table, chassis,
> -                              &dhcp_opts, &dhcpv6_opts, &conj_id_ofs,
> -                              addr_sets, flow_table);
> +                              &dhcp_opts, &dhcpv6_opts, &nd_ra_opts,
> +                              &conj_id_ofs, addr_sets, flow_table);
>      }
>
>      dhcp_opts_destroy(&dhcp_opts);
>      dhcp_opts_destroy(&dhcpv6_opts);
> +    nd_ra_opts_destroy(&nd_ra_opts);
>  }
>
>  static void
> @@ -163,6 +168,7 @@ consider_logical_flow(const struct lport_index *lports,
>                        const struct sbrec_chassis *chassis,
>                        struct hmap *dhcp_opts,
>                        struct hmap *dhcpv6_opts,
> +                      struct hmap *nd_ra_opts,
>                        uint32_t *conj_id_ofs,
>                        const struct shash *addr_sets,
>                        struct hmap *flow_table)
> @@ -196,6 +202,7 @@ consider_logical_flow(const struct lport_index *lports,
>          .symtab = &symtab,
>          .dhcp_opts = dhcp_opts,
>          .dhcpv6_opts = dhcpv6_opts,
> +        .nd_ra_opts = nd_ra_opts,
>
>          .pipeline = ingress ? OVNACT_P_INGRESS : OVNACT_P_EGRESS,
>          .n_tables = LOG_PIPELINE_LEN,
> diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c
> index 723966924..1513a0f9f 100644
> --- a/ovn/controller/pinctrl.c
> +++ b/ovn/controller/pinctrl.c
> @@ -78,6 +78,9 @@ static void pinctrl_handle_nd_na(const struct flow
> *ip_flow,
>                                   struct ofpbuf *userdata);
>  static void reload_metadata(struct ofpbuf *ofpacts,
>                              const struct match *md);
> +static void pinctrl_handle_put_nd_ra_opts(
> +    const struct flow *ip_flow, struct ofputil_packet_in *pin,
> +    struct ofpbuf *userdata, struct ofpbuf *continuation OVS_UNUSED);
>
>  COVERAGE_DEFINE(pinctrl_drop_put_mac_binding);
>
> @@ -978,6 +981,11 @@ process_packet_in(const struct ofp_header *msg,
> struct controller_ctx *ctx)
>          pinctrl_handle_dns_lookup(&packet, &pin, &userdata,
> &continuation, ctx);
>          break;
>
> +    case ACTION_OPCODE_PUT_ND_RA_OPTS:
> +        pinctrl_handle_put_nd_ra_opts(&headers, &pin, &userdata,
> +                                      &continuation);
> +        break;
> +
>      default:
>          VLOG_WARN_RL(&rl, "unrecognized packet-in opcode %"PRIu32,
>                       ntohl(ah->opcode));
> @@ -1801,3 +1809,140 @@ exit:
>      dp_packet_uninit(&packet);
>      ofpbuf_uninit(&ofpacts);
>  }
> +
> +static void
> +pinctrl_handle_put_nd_ra_opts(
> +    const struct flow *in_flow, struct ofputil_packet_in *pin,
> +    struct ofpbuf *userdata, struct ofpbuf *continuation OVS_UNUSED)
> +{
> +    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> +    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, NULL, &f, NULL);
> +    if (ofperr) {
> +       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) {
> +        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) {
> +        VLOG_WARN_RL(&rl, "bad result bit (%s)",
> ofperr_to_string(ofperr));
> +        goto exit;
> +    }
> +
> +    if (!userdata->size) {
> +        VLOG_WARN_RL(&rl, "IPv6 ND RA options not present in the
> userdata");
> +        goto exit;
> +    }
> +
> +    if (!is_icmpv6(in_flow, NULL) || in_flow->tp_dst != htons(0) ||
> +        in_flow->tp_src != htons(ND_ROUTER_SOLICIT)) {
> +        VLOG_WARN_RL(&rl, "put_nd_ra action on invalid or unsupported
> packet");
> +        goto exit;
> +    }
> +
> +    ovs_be32 mtu = 0;
> +    uint8_t mo_flags = 0;
> +    struct eth_addr sll = eth_addr_zero;
> +    struct prefix_data {
> +        uint8_t prefix_len;
> +        ovs_be128 prefix;
> +    };
> +    struct prefix_data *prefixes = NULL;
> +    int num_prefixes = 0;
> +
> +    while (userdata->size) {
> +        struct ipv6_nd_ra_opt_header *opt =
> +            ofpbuf_try_pull(userdata, sizeof(*opt));
> +        uint16_t opt_len = ntohs(opt->len);
> +        if (!opt || opt_len > userdata->size) {
> +            break;
> +        }
> +
> +        uint8_t *opt_data = ofpbuf_try_pull(userdata, opt_len);
> +        if (!opt_data) {
> +            break;
> +        }
> +
> +        switch (ntohs(opt->code)) {
> +        case ND_RA_FLAG_ADDR_MODE:
> +            mo_flags = *opt_data;
> +            break;
> +
> +        case ND_RA_OPT_SLLA:
> +            if (opt_len == sizeof(struct eth_addr)) {
> +                memcpy(&sll, opt_data, sizeof(struct eth_addr));
> +            }
> +            break;
> +
> +        case ND_RA_OPT_PREFIX:
> +            if (opt_len == (sizeof(uint8_t) + sizeof(ovs_be128))) {
> +                prefixes = xrealloc(
> +                    prefixes, sizeof(*prefixes) * (num_prefixes + 1));
> +                prefixes[num_prefixes].prefix_len = *opt_data++;
> +                memcpy(&prefixes[num_prefixes].prefix, opt_data,
> +                       sizeof(ovs_be128));
> +                num_prefixes++;
> +            }
> +            break;
> +
> +        case ND_RA_OPT_MTU:
> +            if (opt_len == sizeof(ovs_be32)) {
> +                mtu = *(ALIGNED_CAST(const ovs_be32 *, opt_data));
> +            }
> +            break;
> +        }
> +    }
> +
> +    if (eth_addr_is_zero(sll)) {
> +        VLOG_WARN_RL(&rl, "IPv6 ND RA option - source lla not present in
> the"
> +                     " userdata");
> +        free(prefixes);
> +        goto exit;
> +    }
> +
> +    uint64_t pkt_out_stub[256 / 8];
> +    struct dp_packet pkt_out;
> +    dp_packet_use_stub(&pkt_out, pkt_out_stub, sizeof pkt_out_stub);
> +    pkt_out_ptr = &pkt_out;
> +
> +    compose_nd_ra(&pkt_out, sll, in_flow->dl_src, &in_flow->ipv6_dst,
> +                  &in_flow->ipv6_src, IPV6_ND_RA_CUR_HOP_LIMIT, mo_flags,
> +                  htons(IPV6_ND_RA_LIFETIME), htonl(IPV6_ND_RA_REACHABLE_
> TIME),
> +                  htonl(IPV6_ND_RA_RETRANSMIT_TIMER), mtu);
> +
> +    for (int i = 0; i < num_prefixes; i++) {
> +        packet_put_ra_prefix_opt(
> +            &pkt_out, prefixes[i].prefix_len, IPV6_ND_RA_OPT_PREFIX_FLAGS,
> +            htonl(IPV6_ND_RA_OPT_PREFIX_VALID_LIFETIME),
> +            htonl(IPV6_ND_RA_OPT_PREFIX_PREFERRED_LIFETIME),
> +            prefixes[i].prefix);
> +    }
> +    free(prefixes);
> +
> +    pin->packet = dp_packet_data(&pkt_out);
> +    pin->packet_len = dp_packet_size(&pkt_out);
> +    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));
> +    dp_packet_uninit(pkt_out_ptr);
> +}
> diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c
> index be821650d..8332f81ca 100644
> --- a/ovn/lib/actions.c
> +++ b/ovn/lib/actions.c
> @@ -1437,7 +1437,7 @@ parse_put_opts(struct action_context *ctx, const
> struct expr_field *dst,
>                 struct ovnact_put_opts *po, const struct hmap *gen_opts,
>                 const char *opts_type)
>  {
> -    lexer_get(ctx->lexer); /* Skip put_dhcp[v6]_opts. */
> +    lexer_get(ctx->lexer); /* Skip put_dhcp[v6]_opts/put_nd_ra_opts. */
>      lexer_get(ctx->lexer); /* Skip '('. */
>
>      /* Validate that the destination is a 1-bit, modifiable field. */
> @@ -1770,6 +1770,161 @@ static void
>  ovnact_dns_lookup_free(struct ovnact_dns_lookup *dl OVS_UNUSED)
>  {
>  }
> +
> +/* Parses the "put_nd_ra_opts" action.
> + * The caller has already consumed "<dst> =", so this just parses the
> rest. */
> +static void
> +parse_put_nd_ra_opts(struct action_context *ctx, const struct expr_field
> *dst,
> +                     struct ovnact_put_opts *po)
> +{
> +    parse_put_opts(ctx, dst, po, ctx->pp->nd_ra_opts, "IPv6 ND RA");
> +
> +    if (ctx->lexer->error) {
> +        return;
> +    }
> +
> +    bool addr_mode_stateful = false;
> +    bool prefix_set = false;
> +    bool slla_present = false;
> +    /* Let's validate the options. */
> +    for (struct ovnact_gen_option *o = po->options;
> +            o < &po->options[po->n_options]; o++) {
> +        const union expr_constant *c = o->value.values;
> +        if (o->value.n_values > 1) {
> +            lexer_error(ctx->lexer, "parse_put_nd_ra_opts -Invalid value
> for"
> +                        " the option %s.", o->option->name);
> +            return;
> +        }
> +
> +        switch (o->option->code) {
> +        case ND_RA_FLAG_ADDR_MODE:
> +            if (!c->string || (strcmp(c->string, "slaac") &&
> +                               strcmp(c->string, "dhcpv6_stateful") &&
> +                               strcmp(c->string, "dhcpv6_stateless"))) {
> +                lexer_error(ctx->lexer, "parse_put_nd_ra_opts -Invalid
> value "
> +                            "for the option %s.", o->option->name);
> +                return;
> +            }
> +
> +            if (!strcmp(c->string, "dhcpv6_stateful")) {
> +                addr_mode_stateful = true;
> +            }
> +            break;
> +
> +        case ND_RA_OPT_SLLA:
> +            if (c->format != LEX_F_ETHERNET) {
> +                lexer_error(ctx->lexer, "parse_put_nd_ra_opts -Invalid
> value "
> +                           "for the option %s.", o->option->name);
> +            }
> +            slla_present = true;
> +            break;
> +
> +        case ND_RA_OPT_PREFIX:
> +            if (c->format != LEX_F_IPV6 || !c->masked) {
> +                lexer_error(ctx->lexer, "parse_put_nd_ra_opts -Invalid
> value "
> +                            "for the option %s.", o->option->name);
> +            }
> +            prefix_set = true;
> +            break;
> +
> +        case ND_RA_OPT_MTU:
> +            if (c->format != LEX_F_DECIMAL) {
> +                lexer_error(ctx->lexer, "parse_put_nd_ra_opts -Invalid
> value "
> +                            "for the option %s.", o->option->name);
> +            }
> +            break;
> +        }
> +    }
> +
> +    if (ctx->lexer->error) {
> +        return;
> +    }
> +
> +    if (!slla_present) {
> +        lexer_error(ctx->lexer, "parse_put_nd_ra_opts - slla option not"
> +                    " present.");
> +        return;
> +    }
> +
> +    if (addr_mode_stateful && prefix_set) {
> +        lexer_error(ctx->lexer, "parse_put_nd_ra_opts - prefix option
> can't be"
> +                    " set when address mode is dhcpv6_stateful.");
> +        return;
> +    }
> +
> +    if (!addr_mode_stateful && !prefix_set) {
> +        lexer_error(ctx->lexer, "parse_put_nd_ra_opts - prefix option
> needs "
> +                    "to be set when address mode is
> slaac/dhcpv6_stateless.");
> +        return;
> +    }
> +
> +    add_prerequisite(ctx, "ip6");
> +}
> +
> +static void
> +format_PUT_ND_RA_OPTS(const struct ovnact_put_opts *po,
> +                      struct ds *s)
> +{
> +    format_put_opts("put_nd_ra_opts", po, s);
> +}
> +
> +static void
> +encode_put_nd_ra_option(const struct ovnact_gen_option *o,
> +                        struct ofpbuf *ofpacts)
> +{
> +    struct ipv6_nd_ra_opt_header *opt = ofpbuf_put_zeros(ofpacts, sizeof
> *opt);
> +    opt->code = htons(o->option->code);
> +    const union expr_constant *c = o->value.values;
> +
> +    if (!strcmp(o->option->type, "ipv6")) {
> +        opt->len = htons(sizeof(struct in6_addr));
> +        if (c->masked) {
> +            opt->len = htons(sizeof(struct in6_addr) + 1);
> +            uint8_t prefix_len = ipv6_count_cidr_bits(&c->mask.ipv6);
> +            ofpbuf_put(ofpacts, &prefix_len, 1);
> +        }
> +        ofpbuf_put(ofpacts, &c->value.ipv6, sizeof(struct in6_addr));
> +    } else if (!strcmp(o->option->type, "mac")) {
> +        opt->len = htons(sizeof(struct eth_addr));
> +        ofpbuf_put(ofpacts, &c->value.mac, sizeof(struct eth_addr));
> +    } else if (!strcmp(o->option->type, "uint32")) {
> +        opt->len = htons(sizeof(ovs_be32));
> +        ofpbuf_put(ofpacts, &c->value.be32_int, sizeof(ovs_be32));
> +    } else if (!strcmp(o->option->type, "str")) {
> +        if (o->option->code == ND_RA_FLAG_ADDR_MODE) {
> +            opt->len = htons(1);
> +            uint8_t addr_mode = 0; /* Default 0 is slaac. */
> +            if (!strcmp(c->string, "dhcpv6_stateful")) {
> +                addr_mode = IPV6_ND_RA_FLAG_MANAGED_ADDR_CONFIG;
> +            } else if (!strcmp(c->string, "dhcpv6_stateless")) {
> +                addr_mode = IPV6_ND_RA_FLAG_OTHER_ADDR_CONFIG;
> +            }
> +            ofpbuf_put(ofpacts, &addr_mode, 1);
> +        }
> +    }
> +}
> +
> +static void
> +encode_PUT_ND_RA_OPTS(const struct ovnact_put_opts *po OVS_UNUSED,
> +                      const struct ovnact_encode_params *ep OVS_UNUSED,
> +                      struct ofpbuf *ofpacts OVS_UNUSED)
> +{
> +    struct mf_subfield dst = expr_resolve_field(&po->dst);
> +
> +    size_t oc_offset = encode_start_controller_op(
> +        ACTION_OPCODE_PUT_ND_RA_OPTS, true, ofpacts);
> +    nx_put_header(ofpacts, dst.field->id, OFP13_VERSION, false);
> +    ovs_be32 ofs = htonl(dst.ofs);
> +    ofpbuf_put(ofpacts, &ofs, sizeof ofs);
> +
> +    for (const struct ovnact_gen_option *o = po->options;
> +         o < &po->options[po->n_options]; o++) {
> +        encode_put_nd_ra_option(o, ofpacts);
> +    }
> +
> +    encode_finish_controller_op(oc_offset, ofpacts);
> +}
> +
>
>  /* Parses an assignment or exchange or put_dhcp_opts action. */
>  static void
> @@ -1796,6 +1951,10 @@ parse_set_action(struct action_context *ctx)
>          } else if (!strcmp(ctx->lexer->token.s, "dns_lookup")
>                     && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
>              parse_dns_lookup(ctx, &lhs, ovnact_put_DNS_LOOKUP(ctx->
> ovnacts));
> +        } else if (!strcmp(ctx->lexer->token.s, "put_nd_ra_opts")
> +                && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
> +            parse_put_nd_ra_opts(ctx, &lhs,
> +                                 ovnact_put_PUT_ND_RA_OPTS(ctx-
> >ovnacts));
>          } else {
>              parse_assignment_action(ctx, false, &lhs);
>          }
> diff --git a/ovn/lib/ovn-l7.h b/ovn/lib/ovn-l7.h
> index 40bd75461..130c00224 100644
> --- a/ovn/lib/ovn-l7.h
> +++ b/ovn/lib/ovn-l7.h
> @@ -206,4 +206,59 @@ struct dhcpv6_opt_ia_na {
>  #define DHCPV6_OPT_PAYLOAD(opt) \
>      (void *)((char *)opt + sizeof(struct dhcpv6_opt_header))
>
> +static inline struct gen_opts_map *
> +nd_ra_opts_find(const struct hmap *nd_ra_opts, char *opt_name)
> +{
> +    return gen_opts_find(nd_ra_opts, opt_name);
> +}
> +
> +static inline void
> +nd_ra_opt_add(struct hmap *nd_ra_opts, char *opt_name, size_t code,
> +               char *type)
> +{
> +    gen_opt_add(nd_ra_opts, opt_name, code, type);
> +}
> +
> +static inline void
> +nd_ra_opts_destroy(struct hmap *nd_ra_opts)
> +{
> +    gen_opts_destroy(nd_ra_opts);
> +}
> +
> +
> +#define ND_RA_FLAG_ADDR_MODE    0
> +#define ND_RA_OPT_SLLA          1
> +#define ND_RA_OPT_PREFIX        3
> +#define ND_RA_OPT_MTU           5
> +
> +/* Default values of various IPv6 Neighbor Discovery protocol options and
> + * flags. See RFC 4861 for more information.
> + * */
> +#define IPV6_ND_RA_FLAG_MANAGED_ADDR_CONFIG         0x80
> +#define IPV6_ND_RA_FLAG_OTHER_ADDR_CONFIG           0x40
> +
> +#define IPV6_ND_RA_CUR_HOP_LIMIT                    255
> +#define IPV6_ND_RA_LIFETIME                         0xffff
> +#define IPV6_ND_RA_REACHABLE_TIME                   0
> +#define IPV6_ND_RA_RETRANSMIT_TIMER                 0
> +
> +#define IPV6_ND_RA_OPT_PREFIX_FLAGS                 0xc0
> +#define IPV6_ND_RA_OPT_PREFIX_VALID_LIFETIME        0xffffffff
> +#define IPV6_ND_RA_OPT_PREFIX_PREFERRED_LIFETIME    0xffffffff
> +
> +OVS_PACKED(
> +struct ipv6_nd_ra_opt_header {
> +    ovs_be16 code; /* One of ND_RA_* */
> +    ovs_be16 len;
> +});
> +
> +static inline void
> +nd_ra_opts_init(struct hmap *nd_ra_opts)
> +{
> +    nd_ra_opt_add(nd_ra_opts, "addr_mode", ND_RA_FLAG_ADDR_MODE, "str");
> +    nd_ra_opt_add(nd_ra_opts, "slla", ND_RA_OPT_SLLA, "mac");
> +    nd_ra_opt_add(nd_ra_opts, "prefix", ND_RA_OPT_PREFIX, "ipv6");
> +    nd_ra_opt_add(nd_ra_opts, "mtu", ND_RA_OPT_MTU, "uint32");
> +}
> +
>  #endif /* OVN_DHCP_H */
> diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml
> index b22d1acd4..824ff123a 100644
> --- a/ovn/ovn-sb.xml
> +++ b/ovn/ovn-sb.xml
> @@ -1513,6 +1513,83 @@
>              <b>Prerequisite:</b> <code>udp</code>
>            </p>
>          </dd>
> +
> +        <dt>
> +          <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>
> +        </dt>
> +
> +        <dd>
> +          <p>
> +            <b>Parameters</b>: The following IPv6 ND Router Advertisement
> +               option/value pairs as defined in RFC 4861.
> +
> +            <ul>
> +              <li>
> +                <code>addr_mode</code>
> +                <p>
> +                  Mandatory parameter which specifies the address mode
> flag to
> +                  be set in the RA flag options field. The value of this
> option
> +                  is a string and the following values can be defined -
> +                  "slaac", "dhcpv6_stateful" and "dhcpv6_stateless".
> +                </p>
> +              </li>
> +
> +              <li>
> +                <code>slla</code>
> +                <p>
> +                  Mandatory parameter which specifies the link-layer
> address of
> +                  the interface from which the Router Advertisement is
> sent.
> +                </p>
> +              </li>
> +
> +              <li>
> +                <code>mtu</code>
> +                <p>
> +                  Optional parameter which specifies the MTU.
> +                </p>
> +              </li>
> +
> +              <li>
> +                <code>prefix</code>
> +                <p>
> +                  Optional parameter which should be specified if the
> addr_mode
> +                  is "slaac" or "dhcpv6_stateless". The value should be
> an IPv6
> +                  prefix which will be used for stateless IPv6 address
> +                  configuration. This option can be defined multiple
> times.
> +                </p>
> +              </li>
> +            </ul>
> +          </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 an IPv6 Router solicitation
> request
> +            packet, it changes the packet into an IPv6 Router
> Advertisement
> +            reply and adds the options specified in the parameters, and
> stores
> +            1 in <var>R</var>.
> +          </p>
> +
> +          <p>
> +            When this action is applied to a non-IPv6 Router solicitation
> +            packet or an invalid IPv6 request packet , it leaves the
> packet
> +            unchanged and stores 0 in <var>R</var>.
> +          </p>
> +
> +          <p>
> +            <b>Example:</b>
> +            <code>
> +              reg0[3] = put_nd_ra_opts(addr_mode = "slaac",
> +              slla = 00:00:00:00:10:02, prefix = aef0::/64, mtu = 1450);
> +            </code>
> +          </p>
> +        </dd>
>        </dl>
>
>        <p>
> diff --git a/ovn/utilities/ovn-trace.c b/ovn/utilities/ovn-trace.c
> index a2894f9ea..e3f27bce7 100644
> --- a/ovn/utilities/ovn-trace.c
> +++ b/ovn/utilities/ovn-trace.c
> @@ -1541,19 +1541,15 @@ execute_get_mac_bind(const struct
> ovnact_get_mac_bind *bind,
>  }
>
>  static void
> -execute_put_dhcp_opts(const struct ovnact_put_opts *pdo,
> -                      const char *name, struct flow *uflow,
> -                      struct ovs_list *super)
> +execute_put_opts(const struct ovnact_put_opts *po,
> +                 const char *name, struct flow *uflow,
> +                 struct ovs_list *super)
>  {
> -    ovntrace_node_append(
> -        super, OVNTRACE_NODE_ERROR,
> -        "/* We assume that this packet is DHCPDISCOVER or DHCPREQUEST.
> */");
> -
>      /* Format the put_dhcp_opts action. */
>      struct ds s = DS_EMPTY_INITIALIZER;
> -    for (const struct ovnact_gen_option *o = pdo->options;
> -         o < &pdo->options[pdo->n_options]; o++) {
> -        if (o != pdo->options) {
> +    for (const struct ovnact_gen_option *o = po->options;
> +         o < &po->options[po->n_options]; o++) {
> +        if (o != po->options) {
>              ds_put_cstr(&s, ", ");
>          }
>          ds_put_format(&s, "%s = ", o->option->name);
> @@ -1563,22 +1559,41 @@ execute_put_dhcp_opts(const struct ovnact_put_opts
> *pdo,
>                           name, ds_cstr(&s));
>      ds_destroy(&s);
>
> -    struct mf_subfield dst = expr_resolve_field(&pdo->dst);
> +    struct mf_subfield dst = expr_resolve_field(&po->dst);
>      if (!mf_is_register(dst.field->id)) {
>          /* Format assignment. */
>          struct ds s = DS_EMPTY_INITIALIZER;
> -        expr_field_format(&pdo->dst, &s);
> +        expr_field_format(&po->dst, &s);
>          ovntrace_node_append(super, OVNTRACE_NODE_MODIFY,
>                               "%s = 1", ds_cstr(&s));
>          ds_destroy(&s);
>      }
>
> -    struct mf_subfield sf = expr_resolve_field(&pdo->dst);
> +    struct mf_subfield sf = expr_resolve_field(&po->dst);
>      union mf_subvalue sv = { .u8_val = 1 };
>      mf_write_subfield_flow(&sf, &sv, uflow);
>  }
>
>  static void
> +execute_put_dhcp_opts(const struct ovnact_put_opts *pdo,
> +                      const char *name, struct flow *uflow,
> +                      struct ovs_list *super)
> +{
> +    ovntrace_node_append(
> +        super, OVNTRACE_NODE_ERROR,
> +        "/* We assume that this packet is DHCPDISCOVER or DHCPREQUEST.
> */");
> +    execute_put_opts(pdo, name, uflow, super);
> +}
> +
> +static void
> +execute_put_nd_ra_opts(const struct ovnact_put_opts *pdo,
> +                       const char *name, struct flow *uflow,
> +                       struct ovs_list *super)
> +{
> +    execute_put_opts(pdo, name, uflow, super);
> +}
> +
> +static void
>  execute_next(const struct ovnact_next *next,
>               const struct ovntrace_datapath *dp, struct flow *uflow,
>               enum ovnact_pipeline pipeline, struct ovs_list *super)
> @@ -1801,6 +1816,11 @@ trace_actions(const struct ovnact *ovnacts, size_t
> ovnacts_len,
>                                    "put_dhcpv6_opts", uflow, super);
>              break;
>
> +        case OVNACT_PUT_ND_RA_OPTS:
> +            execute_put_nd_ra_opts(ovnact_get_PUT_DHCPV6_OPTS(a),
> +                                   "put_nd_ra_opts", uflow, super);
> +            break;
> +
>          case OVNACT_SET_QUEUE:
>              /* The set_queue action is slippery from a logical
> perspective.  It
>               * has no visible effect as long as the packet remains on the
> same
> diff --git a/tests/ovn.at b/tests/ovn.at
> index efcbd9139..8add532d7 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -1034,6 +1034,37 @@ reg1[0] = dns_lookup();
>  reg1[0] = dns_lookup("foo");
>      dns_lookup doesn't take any parameters
>
> +# put_nd_ra_opts
> +reg1[0] = put_nd_ra_opts(addr_mode = "slaac", mtu = 1500, prefix =
> aef0::/64, slla = ae:01:02:03:04:05);
> +    encodes as controller(userdata=00.00.00.
> 07.00.00.00.00.00.01.de.10.00.00.00.40.00.00.00.01.00.00.05.
> 00.04.00.00.05.dc.00.03.00.11.40.ae.f0.00.00.00.00.00.00.00.
> 00.00.00.00.00.00.00.00.01.00.06.ae.01.02.03.04.05,pause)
> +    has prereqs ip6
> +reg1[0] = put_nd_ra_opts(addr_mode = "dhcpv6_stateful", mtu = 1450, slla
> = ae:01:02:03:04:10);
> +    encodes as controller(userdata=00.00.00.
> 07.00.00.00.00.00.01.de.10.00.00.00.40.00.00.00.01.80.00.05.
> 00.04.00.00.05.aa.00.01.00.06.ae.01.02.03.04.10,pause)
> +    has prereqs ip6
> +reg1[0] = put_nd_ra_opts(addr_mode = "dhcpv6_stateless", slla =
> ae:01:02:03:04:06, prefix = aef0::/64);
> +    encodes as controller(userdata=00.00.00.
> 07.00.00.00.00.00.01.de.10.00.00.00.40.00.00.00.01.40.00.01.
> 00.06.ae.01.02.03.04.06.00.03.00.11.40.ae.f0.00.00.00.00.00.
> 00.00.00.00.00.00.00.00.00,pause)
> +    has prereqs ip6
> +reg1[0] = put_nd_ra_opts(addr_mode = "slaac", mtu = 1500, prefix =
> aef0::/64);
> +    parse_put_nd_ra_opts - slla option not present.
> +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);
> +    parse_put_nd_ra_opts - prefix option can't be set when address mode
> is dhcpv6_stateful.
> +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);
> +    parse_put_nd_ra_opts - prefix option can't be set when address mode
> is dhcpv6_stateful.
> +reg1[0] = put_nd_ra_opts(addr_mode = "slaac", slla = ae:01:02:03:04:10);
> +    parse_put_nd_ra_opts - prefix option needs to be set when address
> mode is slaac/dhcpv6_stateless.
> +reg1[0] = put_nd_ra_opts(addr_mode = "dhcpv6_stateless", slla =
> ae:01:02:03:04:10);
> +    parse_put_nd_ra_opts - prefix option needs to be set when address
> mode is slaac/dhcpv6_stateless.
> +reg1[0] = put_nd_ra_opts(addr_mode = dhcpv6_stateless, prefix =
> aef0::/64, slla = ae:01:02:03:04:10);
> +    Syntax error at `dhcpv6_stateless' expecting constant.
> +reg1[0] = put_nd_ra_opts(addr_mode = "slaac", mtu = 1500, prefix =
> aef0::, slla = ae:01:02:03:04:10);
> +    parse_put_nd_ra_opts -Invalid value for the option prefix.
> +reg1[0] = put_nd_ra_opts(addr_mode = "foo", mtu = 1500, slla =
> ae:01:02:03:04:10);
> +    parse_put_nd_ra_opts -Invalid value for the option addr_mode.
> +reg1[0] = put_nd_ra_opts(addr_mode = "slaac", mtu = "1500", slla =
> ae:01:02:03:04:10);
> +    IPv6 ND RA option mtu requires numeric value.
> +reg1[0] = put_nd_ra_opts(addr_mode = "slaac", mtu = 10.0.0.4, slla =
> ae:01:02:03:04:10);
> +    parse_put_nd_ra_opts -Invalid value for the option mtu.
> +
>  # Contradictionary prerequisites (allowed but not useful):
>  ip4.src = ip6.src[0..31];
>      encodes as move:NXM_NX_IPV6_SRC[0..31]->NXM_OF_IP_SRC[]
> diff --git a/tests/test-ovn.c b/tests/test-ovn.c
> index 6569c58b4..50c09c486 100644
> --- a/tests/test-ovn.c
> +++ b/tests/test-ovn.c
> @@ -155,7 +155,8 @@ create_symtab(struct shash *symtab)
>  }
>
>  static void
> -create_dhcp_opts(struct hmap *dhcp_opts, struct hmap *dhcpv6_opts)
> +create_gen_opts(struct hmap *dhcp_opts, struct hmap *dhcpv6_opts,
> +                struct hmap *nd_ra_opts)
>  {
>      hmap_init(dhcp_opts);
>      dhcp_opt_add(dhcp_opts, "offerip", 0, "ipv4");
> @@ -187,6 +188,10 @@ create_dhcp_opts(struct hmap *dhcp_opts, struct hmap
> *dhcpv6_opts)
>      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");
> +
> +    /* IPv6 ND RA options. */
> +    hmap_init(nd_ra_opts);
> +    nd_ra_opts_init(nd_ra_opts);
>  }
>
>  static void
> @@ -1192,12 +1197,13 @@ test_parse_actions(struct ovs_cmdl_context *ctx
> OVS_UNUSED)
>      struct shash symtab;
>      struct hmap dhcp_opts;
>      struct hmap dhcpv6_opts;
> +    struct hmap nd_ra_opts;
>      struct simap ports;
>      struct ds input;
>      bool ok = true;
>
>      create_symtab(&symtab);
> -    create_dhcp_opts(&dhcp_opts, &dhcpv6_opts);
> +    create_gen_opts(&dhcp_opts, &dhcpv6_opts, &nd_ra_opts);
>
>      /* Initialize group ids. */
>      struct group_table group_table;
> @@ -1225,6 +1231,7 @@ test_parse_actions(struct ovs_cmdl_context *ctx
> OVS_UNUSED)
>              .symtab = &symtab,
>              .dhcp_opts = &dhcp_opts,
>              .dhcpv6_opts = &dhcpv6_opts,
> +            .nd_ra_opts = &nd_ra_opts,
>              .n_tables = 24,
>              .cur_ltable = 10,
>          };
> @@ -1309,7 +1316,7 @@ test_parse_actions(struct ovs_cmdl_context *ctx
> OVS_UNUSED)
>      shash_destroy(&symtab);
>      dhcp_opts_destroy(&dhcp_opts);
>      dhcp_opts_destroy(&dhcpv6_opts);
> -
> +    nd_ra_opts_destroy(&nd_ra_opts);
>      exit(ok ? EXIT_SUCCESS : EXIT_FAILURE);
>  }
>
> --
> 2.13.0
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
diff mbox

Patch

diff --git a/include/ovn/actions.h b/include/ovn/actions.h
index de55d88c7..d15c13baa 100644
--- a/include/ovn/actions.h
+++ b/include/ovn/actions.h
@@ -71,7 +71,8 @@  struct simap;
     OVNACT(PUT_DHCPV4_OPTS, ovnact_put_opts)   \
     OVNACT(PUT_DHCPV6_OPTS, ovnact_put_opts)   \
     OVNACT(SET_QUEUE,       ovnact_set_queue)       \
-    OVNACT(DNS_LOOKUP,      ovnact_dns_lookup)
+    OVNACT(DNS_LOOKUP,      ovnact_dns_lookup)      \
+    OVNACT(PUT_ND_RA_OPTS,  ovnact_put_opts)
 
 /* enum ovnact_type, with a member OVNACT_<ENUM> for each action. */
 enum OVS_PACKED_ENUM ovnact_type {
@@ -400,6 +401,14 @@  enum action_opcode {
      *
      */
     ACTION_OPCODE_DNS_LOOKUP,
+
+    /* "result = put_nd_ra_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 ICMPv6 options.
+     */
+    ACTION_OPCODE_PUT_ND_RA_OPTS,
 };
 
 /* Header. */
@@ -420,6 +429,9 @@  struct ovnact_parse_params {
     /* hmap of 'struct gen_opts_map'  to support 'put_dhcpv6_opts' action */
     const struct hmap *dhcpv6_opts;
 
+    /* hmap of 'struct gen_opts_map' to support 'put_nd_ra_opts' action */
+    const struct hmap *nd_ra_opts;
+
     /* Each OVN flow exists in a logical table within a logical pipeline.
      * These parameters express this context for a set of OVN actions being
      * parsed:
diff --git a/ovn/controller/lflow.c b/ovn/controller/lflow.c
index c4fe5f9eb..fce132117 100644
--- a/ovn/controller/lflow.c
+++ b/ovn/controller/lflow.c
@@ -63,6 +63,7 @@  static void consider_logical_flow(const struct lport_index *lports,
                                   const struct sbrec_chassis *chassis,
                                   struct hmap *dhcp_opts,
                                   struct hmap *dhcpv6_opts,
+                                  struct hmap *nd_ra_opts,
                                   uint32_t *conj_id_ofs,
                                   const struct shash *addr_sets,
                                   struct hmap *flow_table);
@@ -143,15 +144,19 @@  add_logical_flows(struct controller_ctx *ctx, const struct lport_index *lports,
                     dhcpv6_opt_row->type);
     }
 
+    struct hmap nd_ra_opts = HMAP_INITIALIZER(&nd_ra_opts);
+    nd_ra_opts_init(&nd_ra_opts);
+
     SBREC_LOGICAL_FLOW_FOR_EACH (lflow, ctx->ovnsb_idl) {
         consider_logical_flow(lports, mcgroups, lflow, local_datapaths,
                               group_table, chassis,
-                              &dhcp_opts, &dhcpv6_opts, &conj_id_ofs,
-                              addr_sets, flow_table);
+                              &dhcp_opts, &dhcpv6_opts, &nd_ra_opts,
+                              &conj_id_ofs, addr_sets, flow_table);
     }
 
     dhcp_opts_destroy(&dhcp_opts);
     dhcp_opts_destroy(&dhcpv6_opts);
+    nd_ra_opts_destroy(&nd_ra_opts);
 }
 
 static void
@@ -163,6 +168,7 @@  consider_logical_flow(const struct lport_index *lports,
                       const struct sbrec_chassis *chassis,
                       struct hmap *dhcp_opts,
                       struct hmap *dhcpv6_opts,
+                      struct hmap *nd_ra_opts,
                       uint32_t *conj_id_ofs,
                       const struct shash *addr_sets,
                       struct hmap *flow_table)
@@ -196,6 +202,7 @@  consider_logical_flow(const struct lport_index *lports,
         .symtab = &symtab,
         .dhcp_opts = dhcp_opts,
         .dhcpv6_opts = dhcpv6_opts,
+        .nd_ra_opts = nd_ra_opts,
 
         .pipeline = ingress ? OVNACT_P_INGRESS : OVNACT_P_EGRESS,
         .n_tables = LOG_PIPELINE_LEN,
diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c
index 723966924..1513a0f9f 100644
--- a/ovn/controller/pinctrl.c
+++ b/ovn/controller/pinctrl.c
@@ -78,6 +78,9 @@  static void pinctrl_handle_nd_na(const struct flow *ip_flow,
                                  struct ofpbuf *userdata);
 static void reload_metadata(struct ofpbuf *ofpacts,
                             const struct match *md);
+static void pinctrl_handle_put_nd_ra_opts(
+    const struct flow *ip_flow, struct ofputil_packet_in *pin,
+    struct ofpbuf *userdata, struct ofpbuf *continuation OVS_UNUSED);
 
 COVERAGE_DEFINE(pinctrl_drop_put_mac_binding);
 
@@ -978,6 +981,11 @@  process_packet_in(const struct ofp_header *msg, struct controller_ctx *ctx)
         pinctrl_handle_dns_lookup(&packet, &pin, &userdata, &continuation, ctx);
         break;
 
+    case ACTION_OPCODE_PUT_ND_RA_OPTS:
+        pinctrl_handle_put_nd_ra_opts(&headers, &pin, &userdata,
+                                      &continuation);
+        break;
+
     default:
         VLOG_WARN_RL(&rl, "unrecognized packet-in opcode %"PRIu32,
                      ntohl(ah->opcode));
@@ -1801,3 +1809,140 @@  exit:
     dp_packet_uninit(&packet);
     ofpbuf_uninit(&ofpacts);
 }
+
+static void
+pinctrl_handle_put_nd_ra_opts(
+    const struct flow *in_flow, struct ofputil_packet_in *pin,
+    struct ofpbuf *userdata, struct ofpbuf *continuation OVS_UNUSED)
+{
+    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+    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, NULL, &f, NULL);
+    if (ofperr) {
+       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) {
+        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) {
+        VLOG_WARN_RL(&rl, "bad result bit (%s)", ofperr_to_string(ofperr));
+        goto exit;
+    }
+
+    if (!userdata->size) {
+        VLOG_WARN_RL(&rl, "IPv6 ND RA options not present in the userdata");
+        goto exit;
+    }
+
+    if (!is_icmpv6(in_flow, NULL) || in_flow->tp_dst != htons(0) ||
+        in_flow->tp_src != htons(ND_ROUTER_SOLICIT)) {
+        VLOG_WARN_RL(&rl, "put_nd_ra action on invalid or unsupported packet");
+        goto exit;
+    }
+
+    ovs_be32 mtu = 0;
+    uint8_t mo_flags = 0;
+    struct eth_addr sll = eth_addr_zero;
+    struct prefix_data {
+        uint8_t prefix_len;
+        ovs_be128 prefix;
+    };
+    struct prefix_data *prefixes = NULL;
+    int num_prefixes = 0;
+
+    while (userdata->size) {
+        struct ipv6_nd_ra_opt_header *opt =
+            ofpbuf_try_pull(userdata, sizeof(*opt));
+        uint16_t opt_len = ntohs(opt->len);
+        if (!opt || opt_len > userdata->size) {
+            break;
+        }
+
+        uint8_t *opt_data = ofpbuf_try_pull(userdata, opt_len);
+        if (!opt_data) {
+            break;
+        }
+
+        switch (ntohs(opt->code)) {
+        case ND_RA_FLAG_ADDR_MODE:
+            mo_flags = *opt_data;
+            break;
+
+        case ND_RA_OPT_SLLA:
+            if (opt_len == sizeof(struct eth_addr)) {
+                memcpy(&sll, opt_data, sizeof(struct eth_addr));
+            }
+            break;
+
+        case ND_RA_OPT_PREFIX:
+            if (opt_len == (sizeof(uint8_t) + sizeof(ovs_be128))) {
+                prefixes = xrealloc(
+                    prefixes, sizeof(*prefixes) * (num_prefixes + 1));
+                prefixes[num_prefixes].prefix_len = *opt_data++;
+                memcpy(&prefixes[num_prefixes].prefix, opt_data,
+                       sizeof(ovs_be128));
+                num_prefixes++;
+            }
+            break;
+
+        case ND_RA_OPT_MTU:
+            if (opt_len == sizeof(ovs_be32)) {
+                mtu = *(ALIGNED_CAST(const ovs_be32 *, opt_data));
+            }
+            break;
+        }
+    }
+
+    if (eth_addr_is_zero(sll)) {
+        VLOG_WARN_RL(&rl, "IPv6 ND RA option - source lla not present in the"
+                     " userdata");
+        free(prefixes);
+        goto exit;
+    }
+
+    uint64_t pkt_out_stub[256 / 8];
+    struct dp_packet pkt_out;
+    dp_packet_use_stub(&pkt_out, pkt_out_stub, sizeof pkt_out_stub);
+    pkt_out_ptr = &pkt_out;
+
+    compose_nd_ra(&pkt_out, sll, in_flow->dl_src, &in_flow->ipv6_dst,
+                  &in_flow->ipv6_src, IPV6_ND_RA_CUR_HOP_LIMIT, mo_flags,
+                  htons(IPV6_ND_RA_LIFETIME), htonl(IPV6_ND_RA_REACHABLE_TIME),
+                  htonl(IPV6_ND_RA_RETRANSMIT_TIMER), mtu);
+
+    for (int i = 0; i < num_prefixes; i++) {
+        packet_put_ra_prefix_opt(
+            &pkt_out, prefixes[i].prefix_len, IPV6_ND_RA_OPT_PREFIX_FLAGS,
+            htonl(IPV6_ND_RA_OPT_PREFIX_VALID_LIFETIME),
+            htonl(IPV6_ND_RA_OPT_PREFIX_PREFERRED_LIFETIME),
+            prefixes[i].prefix);
+    }
+    free(prefixes);
+
+    pin->packet = dp_packet_data(&pkt_out);
+    pin->packet_len = dp_packet_size(&pkt_out);
+    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));
+    dp_packet_uninit(pkt_out_ptr);
+}
diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c
index be821650d..8332f81ca 100644
--- a/ovn/lib/actions.c
+++ b/ovn/lib/actions.c
@@ -1437,7 +1437,7 @@  parse_put_opts(struct action_context *ctx, const struct expr_field *dst,
                struct ovnact_put_opts *po, const struct hmap *gen_opts,
                const char *opts_type)
 {
-    lexer_get(ctx->lexer); /* Skip put_dhcp[v6]_opts. */
+    lexer_get(ctx->lexer); /* Skip put_dhcp[v6]_opts/put_nd_ra_opts. */
     lexer_get(ctx->lexer); /* Skip '('. */
 
     /* Validate that the destination is a 1-bit, modifiable field. */
@@ -1770,6 +1770,161 @@  static void
 ovnact_dns_lookup_free(struct ovnact_dns_lookup *dl OVS_UNUSED)
 {
 }
+
+/* Parses the "put_nd_ra_opts" action.
+ * The caller has already consumed "<dst> =", so this just parses the rest. */
+static void
+parse_put_nd_ra_opts(struct action_context *ctx, const struct expr_field *dst,
+                     struct ovnact_put_opts *po)
+{
+    parse_put_opts(ctx, dst, po, ctx->pp->nd_ra_opts, "IPv6 ND RA");
+
+    if (ctx->lexer->error) {
+        return;
+    }
+
+    bool addr_mode_stateful = false;
+    bool prefix_set = false;
+    bool slla_present = false;
+    /* Let's validate the options. */
+    for (struct ovnact_gen_option *o = po->options;
+            o < &po->options[po->n_options]; o++) {
+        const union expr_constant *c = o->value.values;
+        if (o->value.n_values > 1) {
+            lexer_error(ctx->lexer, "parse_put_nd_ra_opts -Invalid value for"
+                        " the option %s.", o->option->name);
+            return;
+        }
+
+        switch (o->option->code) {
+        case ND_RA_FLAG_ADDR_MODE:
+            if (!c->string || (strcmp(c->string, "slaac") &&
+                               strcmp(c->string, "dhcpv6_stateful") &&
+                               strcmp(c->string, "dhcpv6_stateless"))) {
+                lexer_error(ctx->lexer, "parse_put_nd_ra_opts -Invalid value "
+                            "for the option %s.", o->option->name);
+                return;
+            }
+
+            if (!strcmp(c->string, "dhcpv6_stateful")) {
+                addr_mode_stateful = true;
+            }
+            break;
+
+        case ND_RA_OPT_SLLA:
+            if (c->format != LEX_F_ETHERNET) {
+                lexer_error(ctx->lexer, "parse_put_nd_ra_opts -Invalid value "
+                           "for the option %s.", o->option->name);
+            }
+            slla_present = true;
+            break;
+
+        case ND_RA_OPT_PREFIX:
+            if (c->format != LEX_F_IPV6 || !c->masked) {
+                lexer_error(ctx->lexer, "parse_put_nd_ra_opts -Invalid value "
+                            "for the option %s.", o->option->name);
+            }
+            prefix_set = true;
+            break;
+
+        case ND_RA_OPT_MTU:
+            if (c->format != LEX_F_DECIMAL) {
+                lexer_error(ctx->lexer, "parse_put_nd_ra_opts -Invalid value "
+                            "for the option %s.", o->option->name);
+            }
+            break;
+        }
+    }
+
+    if (ctx->lexer->error) {
+        return;
+    }
+
+    if (!slla_present) {
+        lexer_error(ctx->lexer, "parse_put_nd_ra_opts - slla option not"
+                    " present.");
+        return;
+    }
+
+    if (addr_mode_stateful && prefix_set) {
+        lexer_error(ctx->lexer, "parse_put_nd_ra_opts - prefix option can't be"
+                    " set when address mode is dhcpv6_stateful.");
+        return;
+    }
+
+    if (!addr_mode_stateful && !prefix_set) {
+        lexer_error(ctx->lexer, "parse_put_nd_ra_opts - prefix option needs "
+                    "to be set when address mode is slaac/dhcpv6_stateless.");
+        return;
+    }
+
+    add_prerequisite(ctx, "ip6");
+}
+
+static void
+format_PUT_ND_RA_OPTS(const struct ovnact_put_opts *po,
+                      struct ds *s)
+{
+    format_put_opts("put_nd_ra_opts", po, s);
+}
+
+static void
+encode_put_nd_ra_option(const struct ovnact_gen_option *o,
+                        struct ofpbuf *ofpacts)
+{
+    struct ipv6_nd_ra_opt_header *opt = ofpbuf_put_zeros(ofpacts, sizeof *opt);
+    opt->code = htons(o->option->code);
+    const union expr_constant *c = o->value.values;
+
+    if (!strcmp(o->option->type, "ipv6")) {
+        opt->len = htons(sizeof(struct in6_addr));
+        if (c->masked) {
+            opt->len = htons(sizeof(struct in6_addr) + 1);
+            uint8_t prefix_len = ipv6_count_cidr_bits(&c->mask.ipv6);
+            ofpbuf_put(ofpacts, &prefix_len, 1);
+        }
+        ofpbuf_put(ofpacts, &c->value.ipv6, sizeof(struct in6_addr));
+    } else if (!strcmp(o->option->type, "mac")) {
+        opt->len = htons(sizeof(struct eth_addr));
+        ofpbuf_put(ofpacts, &c->value.mac, sizeof(struct eth_addr));
+    } else if (!strcmp(o->option->type, "uint32")) {
+        opt->len = htons(sizeof(ovs_be32));
+        ofpbuf_put(ofpacts, &c->value.be32_int, sizeof(ovs_be32));
+    } else if (!strcmp(o->option->type, "str")) {
+        if (o->option->code == ND_RA_FLAG_ADDR_MODE) {
+            opt->len = htons(1);
+            uint8_t addr_mode = 0; /* Default 0 is slaac. */
+            if (!strcmp(c->string, "dhcpv6_stateful")) {
+                addr_mode = IPV6_ND_RA_FLAG_MANAGED_ADDR_CONFIG;
+            } else if (!strcmp(c->string, "dhcpv6_stateless")) {
+                addr_mode = IPV6_ND_RA_FLAG_OTHER_ADDR_CONFIG;
+            }
+            ofpbuf_put(ofpacts, &addr_mode, 1);
+        }
+    }
+}
+
+static void
+encode_PUT_ND_RA_OPTS(const struct ovnact_put_opts *po OVS_UNUSED,
+                      const struct ovnact_encode_params *ep OVS_UNUSED,
+                      struct ofpbuf *ofpacts OVS_UNUSED)
+{
+    struct mf_subfield dst = expr_resolve_field(&po->dst);
+
+    size_t oc_offset = encode_start_controller_op(
+        ACTION_OPCODE_PUT_ND_RA_OPTS, true, ofpacts);
+    nx_put_header(ofpacts, dst.field->id, OFP13_VERSION, false);
+    ovs_be32 ofs = htonl(dst.ofs);
+    ofpbuf_put(ofpacts, &ofs, sizeof ofs);
+
+    for (const struct ovnact_gen_option *o = po->options;
+         o < &po->options[po->n_options]; o++) {
+        encode_put_nd_ra_option(o, ofpacts);
+    }
+
+    encode_finish_controller_op(oc_offset, ofpacts);
+}
+
 
 /* Parses an assignment or exchange or put_dhcp_opts action. */
 static void
@@ -1796,6 +1951,10 @@  parse_set_action(struct action_context *ctx)
         } else if (!strcmp(ctx->lexer->token.s, "dns_lookup")
                    && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
             parse_dns_lookup(ctx, &lhs, ovnact_put_DNS_LOOKUP(ctx->ovnacts));
+        } else if (!strcmp(ctx->lexer->token.s, "put_nd_ra_opts")
+                && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
+            parse_put_nd_ra_opts(ctx, &lhs,
+                                 ovnact_put_PUT_ND_RA_OPTS(ctx->ovnacts));
         } else {
             parse_assignment_action(ctx, false, &lhs);
         }
diff --git a/ovn/lib/ovn-l7.h b/ovn/lib/ovn-l7.h
index 40bd75461..130c00224 100644
--- a/ovn/lib/ovn-l7.h
+++ b/ovn/lib/ovn-l7.h
@@ -206,4 +206,59 @@  struct dhcpv6_opt_ia_na {
 #define DHCPV6_OPT_PAYLOAD(opt) \
     (void *)((char *)opt + sizeof(struct dhcpv6_opt_header))
 
+static inline struct gen_opts_map *
+nd_ra_opts_find(const struct hmap *nd_ra_opts, char *opt_name)
+{
+    return gen_opts_find(nd_ra_opts, opt_name);
+}
+
+static inline void
+nd_ra_opt_add(struct hmap *nd_ra_opts, char *opt_name, size_t code,
+               char *type)
+{
+    gen_opt_add(nd_ra_opts, opt_name, code, type);
+}
+
+static inline void
+nd_ra_opts_destroy(struct hmap *nd_ra_opts)
+{
+    gen_opts_destroy(nd_ra_opts);
+}
+
+
+#define ND_RA_FLAG_ADDR_MODE    0
+#define ND_RA_OPT_SLLA          1
+#define ND_RA_OPT_PREFIX        3
+#define ND_RA_OPT_MTU           5
+
+/* Default values of various IPv6 Neighbor Discovery protocol options and
+ * flags. See RFC 4861 for more information.
+ * */
+#define IPV6_ND_RA_FLAG_MANAGED_ADDR_CONFIG         0x80
+#define IPV6_ND_RA_FLAG_OTHER_ADDR_CONFIG           0x40
+
+#define IPV6_ND_RA_CUR_HOP_LIMIT                    255
+#define IPV6_ND_RA_LIFETIME                         0xffff
+#define IPV6_ND_RA_REACHABLE_TIME                   0
+#define IPV6_ND_RA_RETRANSMIT_TIMER                 0
+
+#define IPV6_ND_RA_OPT_PREFIX_FLAGS                 0xc0
+#define IPV6_ND_RA_OPT_PREFIX_VALID_LIFETIME        0xffffffff
+#define IPV6_ND_RA_OPT_PREFIX_PREFERRED_LIFETIME    0xffffffff
+
+OVS_PACKED(
+struct ipv6_nd_ra_opt_header {
+    ovs_be16 code; /* One of ND_RA_* */
+    ovs_be16 len;
+});
+
+static inline void
+nd_ra_opts_init(struct hmap *nd_ra_opts)
+{
+    nd_ra_opt_add(nd_ra_opts, "addr_mode", ND_RA_FLAG_ADDR_MODE, "str");
+    nd_ra_opt_add(nd_ra_opts, "slla", ND_RA_OPT_SLLA, "mac");
+    nd_ra_opt_add(nd_ra_opts, "prefix", ND_RA_OPT_PREFIX, "ipv6");
+    nd_ra_opt_add(nd_ra_opts, "mtu", ND_RA_OPT_MTU, "uint32");
+}
+
 #endif /* OVN_DHCP_H */
diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml
index b22d1acd4..824ff123a 100644
--- a/ovn/ovn-sb.xml
+++ b/ovn/ovn-sb.xml
@@ -1513,6 +1513,83 @@ 
             <b>Prerequisite:</b> <code>udp</code>
           </p>
         </dd>
+
+        <dt>
+          <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>
+        </dt>
+
+        <dd>
+          <p>
+            <b>Parameters</b>: The following IPv6 ND Router Advertisement
+               option/value pairs as defined in RFC 4861.
+
+            <ul>
+              <li>
+                <code>addr_mode</code>
+                <p>
+                  Mandatory parameter which specifies the address mode flag to
+                  be set in the RA flag options field. The value of this option
+                  is a string and the following values can be defined -
+                  "slaac", "dhcpv6_stateful" and "dhcpv6_stateless".
+                </p>
+              </li>
+
+              <li>
+                <code>slla</code>
+                <p>
+                  Mandatory parameter which specifies the link-layer address of
+                  the interface from which the Router Advertisement is sent.
+                </p>
+              </li>
+
+              <li>
+                <code>mtu</code>
+                <p>
+                  Optional parameter which specifies the MTU.
+                </p>
+              </li>
+
+              <li>
+                <code>prefix</code>
+                <p>
+                  Optional parameter which should be specified if the addr_mode
+                  is "slaac" or "dhcpv6_stateless". The value should be an IPv6
+                  prefix which will be used for stateless IPv6 address
+                  configuration. This option can be defined multiple times.
+                </p>
+              </li>
+            </ul>
+          </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 an IPv6 Router solicitation request
+            packet, it changes the packet into an IPv6 Router Advertisement
+            reply and adds the options specified in the parameters, and stores
+            1 in <var>R</var>.
+          </p>
+
+          <p>
+            When this action is applied to a non-IPv6 Router solicitation
+            packet or an invalid IPv6 request packet , it leaves the packet
+            unchanged and stores 0 in <var>R</var>.
+          </p>
+
+          <p>
+            <b>Example:</b>
+            <code>
+              reg0[3] = put_nd_ra_opts(addr_mode = "slaac",
+              slla = 00:00:00:00:10:02, prefix = aef0::/64, mtu = 1450);
+            </code>
+          </p>
+        </dd>
       </dl>
 
       <p>
diff --git a/ovn/utilities/ovn-trace.c b/ovn/utilities/ovn-trace.c
index a2894f9ea..e3f27bce7 100644
--- a/ovn/utilities/ovn-trace.c
+++ b/ovn/utilities/ovn-trace.c
@@ -1541,19 +1541,15 @@  execute_get_mac_bind(const struct ovnact_get_mac_bind *bind,
 }
 
 static void
-execute_put_dhcp_opts(const struct ovnact_put_opts *pdo,
-                      const char *name, struct flow *uflow,
-                      struct ovs_list *super)
+execute_put_opts(const struct ovnact_put_opts *po,
+                 const char *name, struct flow *uflow,
+                 struct ovs_list *super)
 {
-    ovntrace_node_append(
-        super, OVNTRACE_NODE_ERROR,
-        "/* We assume that this packet is DHCPDISCOVER or DHCPREQUEST. */");
-
     /* Format the put_dhcp_opts action. */
     struct ds s = DS_EMPTY_INITIALIZER;
-    for (const struct ovnact_gen_option *o = pdo->options;
-         o < &pdo->options[pdo->n_options]; o++) {
-        if (o != pdo->options) {
+    for (const struct ovnact_gen_option *o = po->options;
+         o < &po->options[po->n_options]; o++) {
+        if (o != po->options) {
             ds_put_cstr(&s, ", ");
         }
         ds_put_format(&s, "%s = ", o->option->name);
@@ -1563,22 +1559,41 @@  execute_put_dhcp_opts(const struct ovnact_put_opts *pdo,
                          name, ds_cstr(&s));
     ds_destroy(&s);
 
-    struct mf_subfield dst = expr_resolve_field(&pdo->dst);
+    struct mf_subfield dst = expr_resolve_field(&po->dst);
     if (!mf_is_register(dst.field->id)) {
         /* Format assignment. */
         struct ds s = DS_EMPTY_INITIALIZER;
-        expr_field_format(&pdo->dst, &s);
+        expr_field_format(&po->dst, &s);
         ovntrace_node_append(super, OVNTRACE_NODE_MODIFY,
                              "%s = 1", ds_cstr(&s));
         ds_destroy(&s);
     }
 
-    struct mf_subfield sf = expr_resolve_field(&pdo->dst);
+    struct mf_subfield sf = expr_resolve_field(&po->dst);
     union mf_subvalue sv = { .u8_val = 1 };
     mf_write_subfield_flow(&sf, &sv, uflow);
 }
 
 static void
+execute_put_dhcp_opts(const struct ovnact_put_opts *pdo,
+                      const char *name, struct flow *uflow,
+                      struct ovs_list *super)
+{
+    ovntrace_node_append(
+        super, OVNTRACE_NODE_ERROR,
+        "/* We assume that this packet is DHCPDISCOVER or DHCPREQUEST. */");
+    execute_put_opts(pdo, name, uflow, super);
+}
+
+static void
+execute_put_nd_ra_opts(const struct ovnact_put_opts *pdo,
+                       const char *name, struct flow *uflow,
+                       struct ovs_list *super)
+{
+    execute_put_opts(pdo, name, uflow, super);
+}
+
+static void
 execute_next(const struct ovnact_next *next,
              const struct ovntrace_datapath *dp, struct flow *uflow,
              enum ovnact_pipeline pipeline, struct ovs_list *super)
@@ -1801,6 +1816,11 @@  trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len,
                                   "put_dhcpv6_opts", uflow, super);
             break;
 
+        case OVNACT_PUT_ND_RA_OPTS:
+            execute_put_nd_ra_opts(ovnact_get_PUT_DHCPV6_OPTS(a),
+                                   "put_nd_ra_opts", uflow, super);
+            break;
+
         case OVNACT_SET_QUEUE:
             /* The set_queue action is slippery from a logical perspective.  It
              * has no visible effect as long as the packet remains on the same
diff --git a/tests/ovn.at b/tests/ovn.at
index efcbd9139..8add532d7 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -1034,6 +1034,37 @@  reg1[0] = dns_lookup();
 reg1[0] = dns_lookup("foo");
     dns_lookup doesn't take any parameters
 
+# put_nd_ra_opts
+reg1[0] = put_nd_ra_opts(addr_mode = "slaac", mtu = 1500, prefix = aef0::/64, slla = ae:01:02:03:04:05);
+    encodes as controller(userdata=00.00.00.07.00.00.00.00.00.01.de.10.00.00.00.40.00.00.00.01.00.00.05.00.04.00.00.05.dc.00.03.00.11.40.ae.f0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.01.00.06.ae.01.02.03.04.05,pause)
+    has prereqs ip6
+reg1[0] = put_nd_ra_opts(addr_mode = "dhcpv6_stateful", mtu = 1450, slla = ae:01:02:03:04:10);
+    encodes as controller(userdata=00.00.00.07.00.00.00.00.00.01.de.10.00.00.00.40.00.00.00.01.80.00.05.00.04.00.00.05.aa.00.01.00.06.ae.01.02.03.04.10,pause)
+    has prereqs ip6
+reg1[0] = put_nd_ra_opts(addr_mode = "dhcpv6_stateless", slla = ae:01:02:03:04:06, prefix = aef0::/64);
+    encodes as controller(userdata=00.00.00.07.00.00.00.00.00.01.de.10.00.00.00.40.00.00.00.01.40.00.01.00.06.ae.01.02.03.04.06.00.03.00.11.40.ae.f0.00.00.00.00.00.00.00.00.00.00.00.00.00.00,pause)
+    has prereqs ip6
+reg1[0] = put_nd_ra_opts(addr_mode = "slaac", mtu = 1500, prefix = aef0::/64);
+    parse_put_nd_ra_opts - slla option not present.
+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);
+    parse_put_nd_ra_opts - prefix option can't be set when address mode is dhcpv6_stateful.
+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);
+    parse_put_nd_ra_opts - prefix option can't be set when address mode is dhcpv6_stateful.
+reg1[0] = put_nd_ra_opts(addr_mode = "slaac", slla = ae:01:02:03:04:10);
+    parse_put_nd_ra_opts - prefix option needs to be set when address mode is slaac/dhcpv6_stateless.
+reg1[0] = put_nd_ra_opts(addr_mode = "dhcpv6_stateless", slla = ae:01:02:03:04:10);
+    parse_put_nd_ra_opts - prefix option needs to be set when address mode is slaac/dhcpv6_stateless.
+reg1[0] = put_nd_ra_opts(addr_mode = dhcpv6_stateless, prefix = aef0::/64, slla = ae:01:02:03:04:10);
+    Syntax error at `dhcpv6_stateless' expecting constant.
+reg1[0] = put_nd_ra_opts(addr_mode = "slaac", mtu = 1500, prefix = aef0::, slla = ae:01:02:03:04:10);
+    parse_put_nd_ra_opts -Invalid value for the option prefix.
+reg1[0] = put_nd_ra_opts(addr_mode = "foo", mtu = 1500, slla = ae:01:02:03:04:10);
+    parse_put_nd_ra_opts -Invalid value for the option addr_mode.
+reg1[0] = put_nd_ra_opts(addr_mode = "slaac", mtu = "1500", slla = ae:01:02:03:04:10);
+    IPv6 ND RA option mtu requires numeric value.
+reg1[0] = put_nd_ra_opts(addr_mode = "slaac", mtu = 10.0.0.4, slla = ae:01:02:03:04:10);
+    parse_put_nd_ra_opts -Invalid value for the option mtu.
+
 # Contradictionary prerequisites (allowed but not useful):
 ip4.src = ip6.src[0..31];
     encodes as move:NXM_NX_IPV6_SRC[0..31]->NXM_OF_IP_SRC[]
diff --git a/tests/test-ovn.c b/tests/test-ovn.c
index 6569c58b4..50c09c486 100644
--- a/tests/test-ovn.c
+++ b/tests/test-ovn.c
@@ -155,7 +155,8 @@  create_symtab(struct shash *symtab)
 }
 
 static void
-create_dhcp_opts(struct hmap *dhcp_opts, struct hmap *dhcpv6_opts)
+create_gen_opts(struct hmap *dhcp_opts, struct hmap *dhcpv6_opts,
+                struct hmap *nd_ra_opts)
 {
     hmap_init(dhcp_opts);
     dhcp_opt_add(dhcp_opts, "offerip", 0, "ipv4");
@@ -187,6 +188,10 @@  create_dhcp_opts(struct hmap *dhcp_opts, struct hmap *dhcpv6_opts)
     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");
+
+    /* IPv6 ND RA options. */
+    hmap_init(nd_ra_opts);
+    nd_ra_opts_init(nd_ra_opts);
 }
 
 static void
@@ -1192,12 +1197,13 @@  test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED)
     struct shash symtab;
     struct hmap dhcp_opts;
     struct hmap dhcpv6_opts;
+    struct hmap nd_ra_opts;
     struct simap ports;
     struct ds input;
     bool ok = true;
 
     create_symtab(&symtab);
-    create_dhcp_opts(&dhcp_opts, &dhcpv6_opts);
+    create_gen_opts(&dhcp_opts, &dhcpv6_opts, &nd_ra_opts);
 
     /* Initialize group ids. */
     struct group_table group_table;
@@ -1225,6 +1231,7 @@  test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED)
             .symtab = &symtab,
             .dhcp_opts = &dhcp_opts,
             .dhcpv6_opts = &dhcpv6_opts,
+            .nd_ra_opts = &nd_ra_opts,
             .n_tables = 24,
             .cur_ltable = 10,
         };
@@ -1309,7 +1316,7 @@  test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED)
     shash_destroy(&symtab);
     dhcp_opts_destroy(&dhcp_opts);
     dhcp_opts_destroy(&dhcpv6_opts);
-
+    nd_ra_opts_destroy(&nd_ra_opts);
     exit(ok ? EXIT_SUCCESS : EXIT_FAILURE);
 }