[{"id":1777552,"web_url":"http://patchwork.ozlabs.org/comment/1777552/","msgid":"<CAA1+qON1KAUkYoX_uKBd5Sw+Vqq8+4L-KgDiuuqJw5_vgs+Gww@mail.gmail.com>","list_archive_url":null,"date":"2017-09-29T13:47:22","subject":"Re: [ovs-dev] [PATCH v8 2/4] ovn-controller: Add a new action -\n\t'put_nd_ra_opts'","submitter":{"id":71978,"url":"http://patchwork.ozlabs.org/api/people/71978/","name":"Mark Michelson","email":"mmichels@redhat.com"},"content":"On Thu, Sep 21, 2017 at 11:10 AM <nusiddiq@redhat.com> wrote:\n\n> From: Numan Siddique <nusiddiq@redhat.com>\n>\n> This patch adds a new OVN action 'put_nd_ra_opts' to support native\n> IPv6 Router Advertisement in OVN. This action can be used to respond\n> to the IPv6 Router Solicitation requests.\n>\n> ovn-controller parses this action and adds a NXT_PACKET_IN2 OF flow\n> with 'pause' flag set and the RA options stored in 'userdata' field.\n> This action is similar to 'put_dhcp_opts' and 'put_dhcpv6_opts'.\n>\n> When a valid IPv6 RS packet is received by the pinctrl module of\n> ovn-controller, it frames a new RA packet and sets the RA options\n> from the 'userdata' field and resumes the packet storing 1 in the\n> 1-bit result sub-field. If the packet is invalid, it resumes the\n> packet without any modifications storing 0 in the 1-bit result\n> sub-field.\n>\n> Eg. reg0[5] = put_nd_ra_opts(address_mode = \"slaac\", mtu = 1450,\n>                              slla = 01:02:03:04:05:06, prefix = aef0::/64)\n>\n> Note that unlike DHCPv4/v6, a new table to store the supported IPv6 ND RA\n> options is not added in SB DB since there are only 3 ND RA options.\n>\n> Co-authored-by: Zongkai LI <zealokii@gmail.com>\n> Signed-off-by: Zongkai LI <zealokii@gmail.com>\n> Signed-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> +++++++++++++++++++++++++++++++++++++++++++++-\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(-)\n>\n> diff --git a/include/ovn/actions.h b/include/ovn/actions.h\n> index 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\n> 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> */\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\n> being\n>       * parsed:\n> diff --git a/ovn/controller/lflow.c b/ovn/controller/lflow.c\n> index 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\n> *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,\n> diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c\n> index 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\n> *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\nI'm curious why the continuation parameter has the OVS_UNUSED attribute\nwhen it is actually used in the function.\n\n\n>  COVERAGE_DEFINE(pinctrl_drop_put_mac_binding);\n>\n> @@ -985,6 +989,11 @@ process_packet_in(const struct ofp_header *msg,\n> 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 =\n> 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 =\n> 1 };\n> +    ofperr = mf_check_dst(&dst, NULL);\n> +    if (ofperr) {\n> +        VLOG_WARN_RL(&rl, \"bad result bit (%s)\",\n> 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\n> 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\n> 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> +}\n> diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c\n> index 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\n> 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\n> rest. */\n> +static void\n> +parse_put_nd_ra_opts(struct action_context *ctx, const struct expr_field\n> *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\n> 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\n> 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\n> 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\n> 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\n> 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\n> 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\n> needs \"\n> +                    \"to be set when address mode is\n> 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> +\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\nI have the same question regarding OVS_UNUSED here as well.\n\n\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>\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,\n> 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>          }\n> diff --git a/ovn/lib/ovn-l7.h b/ovn/lib/ovn-l7.h\n> index 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,\n> \"ipv6\");\n> +    nd_ra_opt_add(nd_ra_opts, \"mtu\", ND_OPT_MTU, \"uint32\");\n> +}\n> +\n>  #endif /* OVN_DHCP_H */\n> diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml\n> index 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> =\n> <var>V1</var>, <var>D2</var> = <var>V2</var>, ..., <var>Dn</var> =\n> <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\n> flag to\n> +                  be set in the RA flag options field. The value of this\n> 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\n> address of\n> +                  the interface from which the Router Advertisement is\n> 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\n> addr_mode\n> +                  is \"slaac\" or \"dhcpv6_stateless\". The value should be\n> an IPv6\n> +                  prefix which will be used for stateless IPv6 address\n> +                  configuration. This option can be defined multiple\n> 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\n> request\n> +            packet, it changes the packet into an IPv6 Router\n> Advertisement\n> +            reply and adds the options specified in the parameters, and\n> 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\n> 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>\n> diff --git a/ovn/utilities/ovn-trace.c b/ovn/utilities/ovn-trace.c\n> index 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\n> 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> -\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\n> *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> */\");\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\n> 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\n> perspective.  It\n>               * has no visible effect as long as the packet remains on the\n> same\n> diff --git a/tests/ovn.at b/tests/ovn.at\n> index 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 =\n> aef0::/64, slla = ae:01:02:03:04:05);\n> +    encodes as\n> 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 =\n> ae:01:02:03:04:10, mtu = 1450);\n> +    encodes as\n> 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 =\n> ae:01:02:03:04:06, prefix = aef0::/64);\n> +    encodes as\n> 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 =\n> 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,\n> 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\n> is dhcpv6_stateful.\n> +reg1[0] = put_nd_ra_opts(addr_mode = \"dhcpv6_stateful\", mtu = 1450,\n> 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\n> 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\n> mode is slaac/dhcpv6_stateless.\n> +reg1[0] = put_nd_ra_opts(addr_mode = \"dhcpv6_stateless\", slla =\n> ae:01:02:03:04:10);\n> +    parse_put_nd_ra_opts - prefix option needs to be set when address\n> mode is slaac/dhcpv6_stateless.\n> +reg1[0] = put_nd_ra_opts(addr_mode = dhcpv6_stateless, prefix =\n> 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 =\n> 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 =\n> 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 =\n> 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 =\n> 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[]\n> diff --git a/tests/test-ovn.c b/tests/test-ovn.c\n> index 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\n> *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\n> 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\n> 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\n> 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>\n> --\n> 2.13.3\n>\n> _______________________________________________\n> dev mailing list\n> dev@openvswitch.org\n> https://mail.openvswitch.org/mailman/listinfo/ovs-dev\n>","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>)","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 3y3Xtm6hznz9t3f\n\tfor <incoming@patchwork.ozlabs.org>;\n\tFri, 29 Sep 2017 23:47:44 +1000 (AEST)","from mail.linux-foundation.org (localhost [127.0.0.1])\n\tby mail.linuxfoundation.org (Postfix) with ESMTP id D501BAB9;\n\tFri, 29 Sep 2017 13:47:39 +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 02549480\n\tfor <dev@openvswitch.org>; Fri, 29 Sep 2017 13:47:38 +0000 (UTC)","from mail-it0-f42.google.com (mail-it0-f42.google.com\n\t[209.85.214.42])\n\tby smtp1.linuxfoundation.org (Postfix) with ESMTPS id 4EF6217E\n\tfor <dev@openvswitch.org>; Fri, 29 Sep 2017 13:47:34 +0000 (UTC)","by mail-it0-f42.google.com with SMTP id 4so59523itv.4\n\tfor <dev@openvswitch.org>; Fri, 29 Sep 2017 06:47:34 -0700 (PDT)"],"X-Greylist":"whitelisted by SQLgrey-1.7.6","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20161025;\n\th=x-gm-message-state:mime-version:references:in-reply-to:from:date\n\t:message-id:subject:to;\n\tbh=MDlYUIHtRZvnlbQWnR2mNmglOizC259Pojj00/LGfIw=;\n\tb=pIaNySCjj7KD2F6Jz/Do4JbwT8L+KnBJXxnkquxMvx5s5Hux5Am8tTa5nLXHGnK15I\n\tczpRfK0wjlDOXckzlP4n2z1OXzCNjNKPNgad2Y9+TdHBcoBTCTSNMTOJEWoz8+DGjPy8\n\t+5DXIs7pmcMOR7QVPC88u/Xx1XGKPfCDud1XTfHnnYe4GKE8whExWTAwzBg5mxZzSkDo\n\tEDn4WFqEyxxtGD/OwaOaqhUnKNQPDedkZCANyelB7eaGMKe6IF5CQ5ox1KHldXNLb5dZ\n\tFDRf+XCA7XC3lr/3e8dCHiOrnKU22LC7wbXyA57WkhbbnyHNtxNrwGE1VIImveCw5WN2\n\t66Jw==","X-Gm-Message-State":"AHPjjUgZRVROd0r6sBVsWTQhO3EUQu3uUeOMAoR3rNZXULqd3y7EU9q+\n\tOCH2jc8j6+nTKz0SGHYpOJDTmYKmROJqUX5rttOtvQ==","X-Google-Smtp-Source":"AOwi7QA1Yy+RE4ttMwieUywijsgBP9JKkyhr1N0bRtPy6aN7cYOyAk/rnoyPZf0uBvQ1EYgPDR8w1x0DW6sqoh/ryZc=","X-Received":"by 10.36.3.141 with SMTP id e135mr7270020ite.146.1506692853438; \n\tFri, 29 Sep 2017 06:47:33 -0700 (PDT)","MIME-Version":"1.0","References":"<20170921160747.24602-1-nusiddiq@redhat.com>\n\t<20170921160923.24850-1-nusiddiq@redhat.com>","In-Reply-To":"<20170921160923.24850-1-nusiddiq@redhat.com>","From":"Mark Michelson <mmichels@redhat.com>","Date":"Fri, 29 Sep 2017 13:47:22 +0000","Message-ID":"<CAA1+qON1KAUkYoX_uKBd5Sw+Vqq8+4L-KgDiuuqJw5_vgs+Gww@mail.gmail.com>","To":"nusiddiq@redhat.com, dev@openvswitch.org","X-Spam-Status":"No, score=0.5 required=5.0 tests=HTML_MESSAGE,\n\tRCVD_IN_DNSWL_NONE,\n\tRCVD_IN_SORBS_SPAM autolearn=disabled version=3.3.1","X-Spam-Checker-Version":"SpamAssassin 3.3.1 (2010-03-16) on\n\tsmtp1.linux-foundation.org","X-Content-Filtered-By":"Mailman/MimeDel 2.1.12","Subject":"Re: [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>","Content-Type":"text/plain; charset=\"us-ascii\"","Content-Transfer-Encoding":"7bit","Sender":"ovs-dev-bounces@openvswitch.org","Errors-To":"ovs-dev-bounces@openvswitch.org"}},{"id":1778436,"web_url":"http://patchwork.ozlabs.org/comment/1778436/","msgid":"<CAH=CPzqTWy7hm2WkxaAAoJwTa-Rv8x_D1dfeeHK7Rqehs3abvQ@mail.gmail.com>","list_archive_url":null,"date":"2017-10-02T16:10:54","subject":"Re: [ovs-dev] [PATCH v8 2/4] ovn-controller: Add a new action -\n\t'put_nd_ra_opts'","submitter":{"id":67480,"url":"http://patchwork.ozlabs.org/api/people/67480/","name":"Numan Siddique","email":"nusiddiq@redhat.com"},"content":"Thanks Mark for the review. I will respin another patch set with your\ncomments addressed.\n\nNuman\n\n\nOn Fri, Sep 29, 2017 at 7:17 PM, Mark Michelson <mmichels@redhat.com> wrote:\n\n>\n>\n> On Thu, Sep 21, 2017 at 11:10 AM <nusiddiq@redhat.com> wrote:\n>\n>> From: Numan Siddique <nusiddiq@redhat.com>\n>>\n>> This patch adds a new OVN action 'put_nd_ra_opts' to support native\n>> IPv6 Router Advertisement in OVN. This action can be used to respond\n>> to the IPv6 Router Solicitation requests.\n>>\n>> ovn-controller parses this action and adds a NXT_PACKET_IN2 OF flow\n>> with 'pause' flag set and the RA options stored in 'userdata' field.\n>> This action is similar to 'put_dhcp_opts' and 'put_dhcpv6_opts'.\n>>\n>> When a valid IPv6 RS packet is received by the pinctrl module of\n>> ovn-controller, it frames a new RA packet and sets the RA options\n>> from the 'userdata' field and resumes the packet storing 1 in the\n>> 1-bit result sub-field. If the packet is invalid, it resumes the\n>> packet without any modifications storing 0 in the 1-bit result\n>> sub-field.\n>>\n>> Eg. reg0[5] = put_nd_ra_opts(address_mode = \"slaac\", mtu = 1450,\n>>                              slla = 01:02:03:04:05:06, prefix = aef0::/64)\n>>\n>> Note that unlike DHCPv4/v6, a new table to store the supported IPv6 ND RA\n>> options is not added in SB DB since there are only 3 ND RA options.\n>>\n>> Co-authored-by: Zongkai LI <zealokii@gmail.com>\n>> Signed-off-by: Zongkai LI <zealokii@gmail.com>\n>> Signed-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>> +++++++++++++++-\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(-)\n>>\n>> diff --git a/include/ovn/actions.h b/include/ovn/actions.h\n>> index 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\n>> 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'\n>> action */\n>>      const struct hmap *dhcpv6_opts;\n>>\n>> +    /* hmap of 'struct gen_opts_map' to support 'put_nd_ra_opts' action\n>> */\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\n>> being\n>>       * parsed:\n>> diff --git a/ovn/controller/lflow.c b/ovn/controller/lflow.c\n>> index 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\n>> *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,\n>> diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c\n>> index 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\n>> *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>\n> I'm curious why the continuation parameter has the OVS_UNUSED attribute\n> when it is actually used in the function.\n>\n>\n>>  COVERAGE_DEFINE(pinctrl_drop_put_mac_binding);\n>>\n>> @@ -985,6 +989,11 @@ process_packet_in(const struct ofp_header *msg,\n>> 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,\n>> &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_\n>> 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)\",\n>> 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\n>> = 1 };\n>> +    ofperr = mf_check_dst(&dst, NULL);\n>> +    if (ofperr) {\n>> +        VLOG_WARN_RL(&rl, \"bad result bit (%s)\",\n>> 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\n>> 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\n>> 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>> */\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>> +}\n>> diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c\n>> index 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\n>> 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\n>> rest. */\n>> +static void\n>> +parse_put_nd_ra_opts(struct action_context *ctx, const struct expr_field\n>> *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\n>> 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\n>> 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\n>> 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\n>> 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\n>> 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\n>> 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\n>> needs \"\n>> +                    \"to be set when address mode is\n>> 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_\n>> 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>\n> I have the same question regarding OVS_UNUSED here as well.\n>\n>\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>>\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->\n>> 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(\n>> ctx->ovnacts));\n>>          } else {\n>>              parse_assignment_action(ctx, false, &lhs);\n>>          }\n>> diff --git a/ovn/lib/ovn-l7.h b/ovn/lib/ovn-l7.h\n>> index 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,\n>> \"ipv6\");\n>> +    nd_ra_opt_add(nd_ra_opts, \"mtu\", ND_OPT_MTU, \"uint32\");\n>> +}\n>> +\n>>  #endif /* OVN_DHCP_H */\n>> diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml\n>> index 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> =\n>> <var>V1</var>, <var>D2</var> = <var>V2</var>, ..., <var>Dn</var> =\n>> <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\n>> flag to\n>> +                  be set in the RA flag options field. The value of this\n>> 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\n>> address of\n>> +                  the interface from which the Router Advertisement is\n>> 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\n>> addr_mode\n>> +                  is \"slaac\" or \"dhcpv6_stateless\". The value should be\n>> an IPv6\n>> +                  prefix which will be used for stateless IPv6 address\n>> +                  configuration. This option can be defined multiple\n>> 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\n>> request\n>> +            packet, it changes the packet into an IPv6 Router\n>> Advertisement\n>> +            reply and adds the options specified in the parameters, and\n>> 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\n>> 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>\n>> diff --git a/ovn/utilities/ovn-trace.c b/ovn/utilities/ovn-trace.c\n>> index 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\n>> 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>> -\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\n>> 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>> */\");\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\n>> 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\n>> perspective.  It\n>>               * has no visible effect as long as the packet remains on\n>> the same\n>> diff --git a/tests/ovn.at b/tests/ovn.at\n>> index 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 =\n>> aef0::/64, slla = ae:01:02:03:04:05);\n>> +    encodes as controller(userdata=00.00.00.\n>> 08.00.00.00.00.00.01.de.10.00.00.00.40.86.00.00.00.ff.00.ff.\n>> ff.00.00.00.00.00.00.00.00.05.01.00.00.00.00.05.dc.03.04.40.\n>> c0.ff.ff.ff.ff.ff.ff.ff.ff.00.00.00.00.ae.f0.00.00.00.00.00.\n>> 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 =\n>> ae:01:02:03:04:10, mtu = 1450);\n>> +    encodes as controller(userdata=00.00.00.\n>> 08.00.00.00.00.00.01.de.10.00.00.00.40.86.00.00.00.ff.80.ff.\n>> ff.00.00.00.00.00.00.00.00.01.01.ae.01.02.03.04.10.05.01.00.\n>> 00.00.00.05.aa,pause)\n>> +    has prereqs ip6\n>> +reg1[0] = put_nd_ra_opts(addr_mode = \"dhcpv6_stateless\", slla =\n>> ae:01:02:03:04:06, prefix = aef0::/64);\n>> +    encodes as controller(userdata=00.00.00.\n>> 08.00.00.00.00.00.01.de.10.00.00.00.40.86.00.00.00.ff.40.ff.\n>> ff.00.00.00.00.00.00.00.00.01.01.ae.01.02.03.04.06.03.04.40.\n>> c0.ff.ff.ff.ff.ff.ff.ff.ff.00.00.00.00.ae.f0.00.00.00.00.00.\n>> 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 =\n>> 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,\n>> 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\n>> is dhcpv6_stateful.\n>> +reg1[0] = put_nd_ra_opts(addr_mode = \"dhcpv6_stateful\", mtu = 1450,\n>> 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\n>> 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\n>> mode is slaac/dhcpv6_stateless.\n>> +reg1[0] = put_nd_ra_opts(addr_mode = \"dhcpv6_stateless\", slla =\n>> ae:01:02:03:04:10);\n>> +    parse_put_nd_ra_opts - prefix option needs to be set when address\n>> mode is slaac/dhcpv6_stateless.\n>> +reg1[0] = put_nd_ra_opts(addr_mode = dhcpv6_stateless, prefix =\n>> 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 =\n>> 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 =\n>> 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 =\n>> 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 =\n>> 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[]\n>> diff --git a/tests/test-ovn.c b/tests/test-ovn.c\n>> index 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\n>> *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\n>> 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\n>> 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\n>> 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>>\n>> --\n>> 2.13.3\n>>\n>> _______________________________________________\n>> dev mailing list\n>> dev@openvswitch.org\n>> https://mail.openvswitch.org/mailman/listinfo/ovs-dev\n>>\n>","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>)","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 3y5Rwq217Wz9t5s\n\tfor <incoming@patchwork.ozlabs.org>;\n\tTue,  3 Oct 2017 03:11:06 +1100 (AEDT)","from mail.linux-foundation.org (localhost [127.0.0.1])\n\tby mail.linuxfoundation.org (Postfix) with ESMTP id 496FDAB2;\n\tMon,  2 Oct 2017 16:11:02 +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 5A1C4989\n\tfor <dev@openvswitch.org>; Mon,  2 Oct 2017 16:11:00 +0000 (UTC)","from mail-oi0-f53.google.com (mail-oi0-f53.google.com\n\t[209.85.218.53])\n\tby smtp1.linuxfoundation.org (Postfix) with ESMTPS id 869021A9\n\tfor <dev@openvswitch.org>; Mon,  2 Oct 2017 16:10:56 +0000 (UTC)","by mail-oi0-f53.google.com with SMTP id s145so4053977oie.1\n\tfor <dev@openvswitch.org>; Mon, 02 Oct 2017 09:10:56 -0700 (PDT)","by 10.157.42.200 with HTTP; Mon, 2 Oct 2017 09:10:54 -0700 (PDT)"],"X-Greylist":"whitelisted by SQLgrey-1.7.6","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20161025;\n\th=x-gm-message-state:mime-version:in-reply-to:references:from:date\n\t:message-id:subject:to:cc;\n\tbh=r/vS0G3TjF0r7FTCPc5tgKcqnBA2MCjr3KMg5Y4fng4=;\n\tb=F8wLsT6lpQifQFhNZ/5Cu0din3/9OVRUe0V1zByt6ngMKxT7wYrlGHKmN9aReN1uXf\n\t8O3GaUunAA5OqWb7TT3XRIcwIGkATFPdeRa2bMKGSO964UI4zUq8Vj4b3rKD+dJljEYo\n\tABsPjL0OYTKNSGKBxHAeMlpdbUqgU11f7NmnQ3JAs9pgZSTwRgGbaadszjcTRwwxovgb\n\tYcpbkxfVY61t4H6Bd5gyHU3N33CBd4964R9KK9vluTrXZm/xterscQgCXOzGWn7WTL9M\n\tGHK6JGoSu7XcpyCife3j4GCUz3oAaIWoaU/5MqWe7Ad78g+HCN4yiGYCyeSAUer8yO3C\n\t4FXA==","X-Gm-Message-State":"AMCzsaUYH2tEDJl2QT2e5y268PkC+9771kDqcDmY0x1pLmsLLwvPUvQF\n\tP5CszeJFHYrPvdhLACBwkkhbG1q0JG2qhqRhGlOYYg==","X-Google-Smtp-Source":"AOwi7QB3CGFQDhuaBDK2AV+5FJg5b7ca8UBHGCEYDqH7cx5D+SDojTSD8qDtLEm6vk7+diSIaEzA+c1Pr9aQMHouAbA=","X-Received":"by 10.202.206.207 with SMTP id\n\te198mr1343296oig.150.1506960655385; \n\tMon, 02 Oct 2017 09:10:55 -0700 (PDT)","MIME-Version":"1.0","In-Reply-To":"<CAA1+qON1KAUkYoX_uKBd5Sw+Vqq8+4L-KgDiuuqJw5_vgs+Gww@mail.gmail.com>","References":"<20170921160747.24602-1-nusiddiq@redhat.com>\n\t<20170921160923.24850-1-nusiddiq@redhat.com>\n\t<CAA1+qON1KAUkYoX_uKBd5Sw+Vqq8+4L-KgDiuuqJw5_vgs+Gww@mail.gmail.com>","From":"Numan Siddique <nusiddiq@redhat.com>","Date":"Mon, 2 Oct 2017 21:40:54 +0530","Message-ID":"<CAH=CPzqTWy7hm2WkxaAAoJwTa-Rv8x_D1dfeeHK7Rqehs3abvQ@mail.gmail.com>","To":"Mark Michelson <mmichels@redhat.com>","X-Spam-Status":"No, score=0.5 required=5.0 tests=HTML_MESSAGE,\n\tRCVD_IN_DNSWL_NONE,\n\tRCVD_IN_SORBS_SPAM autolearn=disabled version=3.3.1","X-Spam-Checker-Version":"SpamAssassin 3.3.1 (2010-03-16) on\n\tsmtp1.linux-foundation.org","X-Content-Filtered-By":"Mailman/MimeDel 2.1.12","Cc":"ovs dev <dev@openvswitch.org>","Subject":"Re: [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>","Content-Type":"text/plain; charset=\"us-ascii\"","Content-Transfer-Encoding":"7bit","Sender":"ovs-dev-bounces@openvswitch.org","Errors-To":"ovs-dev-bounces@openvswitch.org"}}]