diff --git a/include/ovn/actions.h b/include/ovn/actions.h
index c1dda2f..fb30821 100644
--- a/include/ovn/actions.h
+++ b/include/ovn/actions.h
@@ -68,7 +68,11 @@ struct simap;
     OVNACT(GET_ND,        ovnact_get_mac_bind)      \
     OVNACT(PUT_ND,        ovnact_put_mac_bind)      \
     OVNACT(PUT_DHCPV4_OPTS, ovnact_put_dhcp_opts)   \
-    OVNACT(PUT_DHCPV6_OPTS, ovnact_put_dhcp_opts)
+    OVNACT(PUT_DHCPV6_OPTS, ovnact_put_dhcp_opts)   \
+    OVNACT(PUT_ND_RA,       ovnact_put_nd_ra)       \
+    OVNACT(PUT_ND_OPT_SLL,  ovnact_put_nd_opt_sll)  \
+    OVNACT(PUT_ND_OPT_MTU,  ovnact_put_nd_opt_mtu)  \
+    OVNACT(PUT_ND_OPT_PREFIX, ovnact_put_nd_opt_prefix)
 
 /* enum ovnact_type, with a member OVNACT_<ENUM> for each action. */
 enum OVS_PACKED_ENUM ovnact_type {
@@ -228,6 +232,38 @@ struct ovnact_put_dhcp_opts {
     size_t n_options;
 };
 
+/* OVNACT_PUt_ND_RA. */
+struct ovnact_put_nd_ra {
+    struct ovnact ovnact;
+    uint8_t  cur_hop_limit;
+    uint8_t  mo_flags;
+    uint16_t router_lifetime;
+    uint32_t reachable_time;
+    uint32_t retrans_timer;
+};
+
+/* OVNACT_PUT_ND_OPT_SLL. */
+struct ovnact_put_nd_opt_sll {
+    struct ovnact ovnact;
+    struct eth_addr mac;
+};
+
+/* OVNACT_PUT_ND_OPT_MTU. */
+struct ovnact_put_nd_opt_mtu {
+    struct ovnact ovnact;
+    uint32_t mtu;
+};
+
+/* OVNACT_PUT_ND_OPT_PREFIX. */
+struct ovnact_put_nd_opt_prefix {
+    struct ovnact ovnact;
+    uint8_t prefix_len;
+    uint8_t la_flags;
+    uint32_t valid_lifetime;
+    uint32_t preferred_lifetime;
+    struct in6_addr prefix;
+};
+
 /* Internal use by the helpers below. */
 void ovnact_init(struct ovnact *, enum ovnact_type, size_t len);
 void *ovnact_put(struct ofpbuf *, enum ovnact_type, size_t len);
@@ -360,6 +396,44 @@ enum action_opcode {
      * The actions, in OpenFlow 1.3 format, follow the action_header.
      */
     ACTION_OPCODE_ND_RA,
+
+    /* "put_nd_ra(c, mo, router_lt, reach_t, retrans_t)".
+     *
+     * Specific inner action for ACTION_OPCODE_ND_RA. Arguments in this format:
+     *   - c: 8-bit unsigned integer for current hop limit.
+     *   - mo: 8-bit unsigned integer for Managed address configuration flag
+     *         and Other configuration flag. (including 6-bit reserved 0)
+     *   - router_lt: 16-bit unsigned integer for router lifetime.
+     *   - reach_t: 32-bit unisgned integer for reachable time.
+     *   - retrans_t: 32-bit unsigned integer for retrans timer.
+     */
+    ACTION_OPCODE_PUT_ND_RA,
+
+    /* "put_nd_opt_sll(mac)".
+     *
+     * Specific inner action for ACTION_OPCODE_ND_RA. Arguments in this format:
+     *   - mac: 48-bit ethernet address.
+     */
+    ACTION_OPCODE_PUT_ND_OPT_SLL,
+
+    /* "put_nd_opt_mtu(mtu)".
+     *
+     * Specific inner action for ACTION_OPCODE_ND_RA. Arguments in this format:
+     *   - mtu: 32-bit unsigned integer MTU.
+     */
+    ACTION_OPCODE_PUT_ND_OPT_MTU,
+
+    /* "put_nd_opt_prefix(plen, la, valid_lt, preferred_lt, prefix)".
+     *
+     * Specific inner action for ACTION_OPCODE_ND_RA. Arguments in this format:
+     *   - plen: 8-bit unsigned integer for prefix length.
+     *   - la: 8-bit unsigned integer for on-link flag and autonomous
+     *         address-configuration flag. (including 6-bit reserved 0)
+     *   - valid_lt: 32-bit unsigned integer for valid_lifetime.
+     *   - preferred_lt: 32-bit unsigned integer for preferred_lifetime.
+     *   - prefix: 128-bit IPv6 address.
+     */
+    ACTION_OPCODE_PUT_ND_OPT_PREFIX,
 };
 
 /* Header. */
@@ -445,6 +519,9 @@ struct ovnact_encode_params {
                                    resubmit. */
 };
 
+void ovnact_encode(const struct ovnact *a, const struct ovnact_encode_params *ep,
+                   struct ofpbuf *ofpacts);
+
 void ovnacts_encode(const struct ovnact[], size_t ovnacts_len,
                     const struct ovnact_encode_params *,
                     struct ofpbuf *ofpacts);
diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c
index 464c90d..47c9bf7 100644
--- a/ovn/controller/pinctrl.c
+++ b/ovn/controller/pinctrl.c
@@ -1352,6 +1352,113 @@ reload_metadata(struct ofpbuf *ofpacts, const struct match *md)
     }
 }
 
+static bool parse_nd_ra_args(struct ofpbuf *userdata, uint8_t *chl,
+                             uint8_t *mo, ovs_be16 *rlt,
+                             ovs_be32 *rcht, ovs_be32 *rtrt,
+                             uint16_t *args_left_size)
+{
+    int put_nd_ra_args_len =
+        sizeof(uint8_t) * 2 + sizeof(uint16_t) + sizeof(uint32_t) * 2;
+    uint8_t *cur_hop_limit = ofpbuf_try_pull(userdata, sizeof(uint8_t));
+    if (!cur_hop_limit) {
+        return false;
+    }
+    uint8_t *mo_flags = ofpbuf_try_pull(userdata, sizeof(uint8_t));
+    if (!mo_flags) {
+        return false;
+    }
+    ovs_be16 *router_lifetime = ofpbuf_try_pull(userdata, sizeof(ovs_be16));
+    if (!router_lifetime) {
+        return false;
+    }
+    ovs_be32 *reachable_time = ofpbuf_try_pull(userdata, sizeof(ovs_be32));
+    if (!reachable_time) {
+        return false;
+    }
+    ovs_be32 *retrans_timer = ofpbuf_try_pull(userdata, sizeof(ovs_be32));
+    if (!retrans_timer) {
+        return false;
+    }
+    *chl = *cur_hop_limit;
+    *mo = *mo_flags;
+    *rlt = *router_lifetime;
+    *rcht = *reachable_time;
+    *rtrt = *retrans_timer;
+    *args_left_size -= put_nd_ra_args_len;
+    return true;
+}
+
+static bool parse_nd_prefix_opt_args(struct ofpbuf *userdata,
+                                     uint8_t *plen, uint8_t *la, ovs_be32 *vl,
+                                     ovs_be32 *pl, ovs_be32 *p,
+                                     uint16_t *args_left_size)
+{
+    int put_nd_prefix_opt_args_len =
+        sizeof(uint8_t) * 2 + sizeof(uint32_t) *2 + sizeof(struct in6_addr);
+    if (*args_left_size < put_nd_prefix_opt_args_len) {
+        return false;
+    }
+    uint8_t *prefix_len = ofpbuf_try_pull(userdata, sizeof(uint8_t));
+    if (!prefix_len || *prefix_len == 0 || *prefix_len == 128) {
+        return false;
+    }
+    uint8_t *la_flags = ofpbuf_try_pull(userdata, sizeof(uint8_t));
+    if (!la_flags) {
+        return false;
+    }
+    ovs_be32 *valid_lifetime = ofpbuf_try_pull(userdata, sizeof(ovs_be32));
+    if (!valid_lifetime || *valid_lifetime == 0) {
+        return false;
+    }
+    ovs_be32 *preferred_lifetime = ofpbuf_try_pull(userdata, sizeof(ovs_be32));
+    if (!preferred_lifetime || *preferred_lifetime == 0) {
+        return false;
+    }
+    struct in6_addr *prefix = ofpbuf_try_pull(userdata,
+                                              sizeof(struct in6_addr));
+    if (!prefix) {
+        return false;
+    }
+    *plen = *prefix_len;
+    *la = *la_flags;
+    *vl = *valid_lifetime;
+    *pl = *preferred_lifetime;
+    memcpy(p, prefix, sizeof(struct in6_addr));
+    *args_left_size -= put_nd_prefix_opt_args_len;
+    return true;
+}
+
+static bool parse_nd_sll_opt_arg(struct ofpbuf *userdata,
+                                 struct eth_addr *e, uint16_t *args_left_size)
+{
+    if (*args_left_size < sizeof(struct eth_addr)) {
+        return false;
+    }
+    struct eth_addr *sll = ofpbuf_try_pull(userdata, sizeof(struct eth_addr));
+    if (!sll) {
+        return false;
+    }
+    memcpy(e, sll, sizeof(struct eth_addr));
+    *args_left_size -= sizeof(struct eth_addr);
+    return true;
+}
+
+static bool parse_nd_mtu_opt_arg(struct ofpbuf *userdata,
+                                 ovs_be32 *m, uint16_t *args_left_size)
+{
+    if (*args_left_size < sizeof(ovs_be32)) {
+        return false;
+    }
+    ovs_be32 *mtu = ofpbuf_try_pull(userdata, sizeof(ovs_be32));
+    /* Per RFC 2460, consider 1280 as minimum IPv6 MTU. */
+    if (!mtu || *mtu < 1280) {
+        return false;
+    }
+    *m = *mtu;
+    *args_left_size -= sizeof(ovs_be32);
+    return true;
+}
+
 static void
 pinctrl_handle_nd(const struct flow *ip_flow, const struct match *md,
                   struct ofpbuf *userdata)
@@ -1385,18 +1492,67 @@ pinctrl_handle_nd(const struct flow *ip_flow, const struct match *md,
                       &ip_flow->nd_target, &ip_flow->ipv6_src,
                       htonl(ND_RSO_SOLICITED | ND_RSO_OVERRIDE));
     } else {
-        /* xxx Using hardcode data to compose RA packet.
-         * xxx The following patch should fix this. */
         uint8_t cur_hop_limit = 64;
         uint8_t mo_flags = 0;
         ovs_be16 router_lifetime = htons(0x2a30);  /* 3 hours. */
         ovs_be32 reachable_time = 0;
         ovs_be32 retrans_timer = 0;
 
+        uint16_t *args_len_p = ofpbuf_try_pull(userdata, sizeof(uint16_t));
+        uint16_t args_len = args_len_p ? ntohs(*args_len_p) : 0;
+        uint8_t *arg = NULL;
+        bool continue_parse = false;
+        if (args_len >= sizeof(uint8_t)) {
+            arg = ofpbuf_try_pull(userdata, sizeof(uint8_t));
+            args_len -= sizeof(uint8_t);
+            if (arg && *arg == ACTION_OPCODE_PUT_ND_RA) {
+                continue_parse = parse_nd_ra_args(
+                    userdata, &cur_hop_limit, &mo_flags, &router_lifetime,
+                    &reachable_time, &retrans_timer, &args_len);
+            }
+        }
         compose_nd_ra(&packet, ip_flow->dl_dst, ip_flow->dl_src,
                       &ip_flow->ipv6_dst, &ip_flow->ipv6_src,
                       cur_hop_limit, mo_flags,
                       router_lifetime, reachable_time, retrans_timer);
+        if (continue_parse && args_len > sizeof(uint8_t)) {
+            bool sll_opt_parsed = false;
+            bool mtu_opt_parsed = false;
+            struct eth_addr sll;
+            ovs_be32 mtu;
+            uint8_t prefix_len = 0;
+            uint8_t la_flags = 0;
+            ovs_be32 valid_lifetime = 0;
+            ovs_be32 preferred_lifetime = 0;
+            ovs_be32 prefix[4];
+            while (args_len > 0) {
+                arg = ofpbuf_try_pull(userdata, sizeof(uint8_t));
+                args_len -= sizeof(uint8_t);
+                if (arg && *arg == ACTION_OPCODE_PUT_ND_OPT_PREFIX
+                    && parse_nd_prefix_opt_args(userdata, &prefix_len,
+                                                &la_flags, &valid_lifetime,
+                                                &preferred_lifetime, prefix,
+                                                &args_len)) {
+                    packet_put_ra_prefix_opt(&packet, prefix_len, la_flags,
+                                             valid_lifetime, preferred_lifetime,
+                                             prefix);
+                } else if (arg && *arg == ACTION_OPCODE_PUT_ND_OPT_SLL
+                           && !sll_opt_parsed
+                           && parse_nd_sll_opt_arg(userdata, &sll, &args_len)) {
+                        packet_put_ra_sll_opt(&packet, sll);
+                        sll_opt_parsed = true;
+                } else if (arg && *arg == ACTION_OPCODE_PUT_ND_OPT_MTU
+                           && !mtu_opt_parsed
+                           && parse_nd_mtu_opt_arg(userdata, &mtu, &args_len)) {
+                        packet_put_ra_mtu_opt(&packet, mtu);
+                        mtu_opt_parsed = true;
+                } else {
+                    VLOG_WARN_RL(&rl, "Unknown to parse inner arguments for"
+                                      " 'nd_ra'");
+                    break;
+                }
+            }
+        }
     }
 
     /* Reload previous packet metadata. */
diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c
index 96b2f6b..c2f79ad 100644
--- a/ovn/lib/actions.c
+++ b/ovn/lib/actions.c
@@ -1159,7 +1159,45 @@ encode_ND_RA(const struct ovnact_nest *on,
              const struct ovnact_encode_params *ep,
              struct ofpbuf *ofpacts)
 {
-    encode_nested_actions(on, ep, ACTION_OPCODE_ND_RA, ofpacts);
+    uint64_t inner_ofpacts_stub[1024 / 8];
+    uint64_t inner_args_stub[1024 / 8];
+    struct ofpbuf inner_args = OFPBUF_STUB_INITIALIZER(inner_args_stub);
+    struct ofpbuf inner_ofpacts = OFPBUF_STUB_INITIALIZER(inner_ofpacts_stub);
+    const struct ovnact *a;
+    OVNACT_FOR_EACH (a, on->nested, on->nested_len) {
+        if (a->type == OVNACT_PUT_ND_RA) {
+            encode_PUT_ND_RA(
+                (const struct ovnact_put_nd_ra *)a, ep, &inner_args);
+        } else if (a->type == OVNACT_PUT_ND_OPT_SLL) {
+            encode_PUT_ND_OPT_SLL(
+                (const struct ovnact_put_nd_opt_sll *)a, ep, &inner_args);
+        } else if (a->type == OVNACT_PUT_ND_OPT_MTU) {
+            encode_PUT_ND_OPT_MTU(
+                (const struct ovnact_put_nd_opt_mtu *)a, ep, &inner_args);
+        } else if (a->type == OVNACT_PUT_ND_OPT_PREFIX) {
+            encode_PUT_ND_OPT_PREFIX(
+                (const struct ovnact_put_nd_opt_prefix *)a, ep, &inner_args);
+        } else {
+            ovnact_encode(a, ep, &inner_ofpacts);
+        }
+    }
+    uint16_t inner_args_size = inner_args.size ? htons(inner_args.size) : 0;
+
+    /* Add a "controller" action with the data and actions parsed from nested
+     * actions inside "{...}", converted to OpenFlow, as its userdata.
+     * ovn-controller will convert the packet to RA and then send the packet
+     * and actions back to the switch inside an OFPT_PACKET_OUT message. */
+    size_t oc_offset = encode_start_controller_op(ACTION_OPCODE_ND_RA,
+                                                  false, ofpacts);
+    ofpbuf_put(ofpacts, &inner_args_size, sizeof(uint16_t));
+    ofpbuf_put(ofpacts, inner_args.data, inner_args.size);
+    ofpacts_put_openflow_actions(inner_ofpacts.data, inner_ofpacts.size,
+                                 ofpacts, OFP13_VERSION);
+    encode_finish_controller_op(oc_offset, ofpacts);
+
+    /* Free memory. */
+    ofpbuf_uninit(&inner_args);
+    ofpbuf_uninit(&inner_ofpacts);
 }
 
 static void
@@ -1678,7 +1716,199 @@ parse_set_action(struct action_context *ctx)
         lexer_syntax_error(ctx->lexer, "expecting `=' or `<->'");
     }
 }
+
+/* Parse put_nd_ra, put_nd_opt_sll, put_nd_prefix, put_nd_mtu action. */
+static void
+parse_put_nd_ra(struct action_context *ctx,
+                struct ovnact_put_nd_ra *put_ra)
+{
+    lexer_force_match(ctx->lexer, LEX_T_LPAREN);
 
+    int field = 0;
+    int n_fields = 5;
+    while (!lexer_match(ctx->lexer, LEX_T_RPAREN)) {
+        if (!lexer_match(ctx->lexer, LEX_T_INTEGER)
+            || ctx->lexer->token.format != LEX_F_DECIMAL) {
+            break;
+        }
+        if (field == 0) {
+            put_ra->cur_hop_limit = ctx->lexer->token.value.u8_val;
+        } else if (field == 1) {
+            put_ra->mo_flags = ctx->lexer->token.value.u8_val;
+        } else if (field == 2) {
+            put_ra->router_lifetime = ntohs(ctx->lexer->token.value.be16_int);
+        } else if (field == 3) {
+            put_ra->reachable_time = ntohl(ctx->lexer->token.value.be32_int);
+        } else if (field == 4) {
+            put_ra->retrans_timer = ntohl(ctx->lexer->token.value.be32_int);
+        }
+        lexer_match(ctx->lexer, LEX_T_COMMA);
+        field++;
+    }
+    if (field != n_fields) {
+        lexer_syntax_error(ctx->lexer, "not enough fields parsed");
+    }
+}
+static void
+parse_put_nd_opt_prefix(struct action_context *ctx,
+                        struct ovnact_put_nd_opt_prefix *prefix)
+{
+    lexer_force_match(ctx->lexer, LEX_T_LPAREN);
+
+    int field = 0;
+    int n_fields = 5;
+    while (!lexer_match(ctx->lexer, LEX_T_RPAREN)) {
+        if (!lexer_match(ctx->lexer, LEX_T_INTEGER)) {
+            break;
+        }
+        if (field < 4 && ctx->lexer->token.format != LEX_F_DECIMAL) {
+            lexer_syntax_error(ctx->lexer, "expecting demical integer");
+        }
+        if (field == 0) {
+            prefix->prefix_len = ctx->lexer->token.value.u8_val;
+        } else if (field == 1) {
+            prefix->la_flags = ctx->lexer->token.value.u8_val;
+        } else if (field == 2) {
+            prefix->valid_lifetime = ntohl(ctx->lexer->token.value.be32_int);
+        } else if (field == 3) {
+            prefix->preferred_lifetime =
+                ntohl(ctx->lexer->token.value.be32_int);
+        } else if (field == 4) {
+            if (ctx->lexer->token.format != LEX_F_IPV6) {
+                lexer_syntax_error(ctx->lexer, "expecting IPv6 address");
+            }
+            memcpy(&prefix->prefix, &ctx->lexer->token.value.ipv6,
+                   sizeof(struct in6_addr));
+        }
+        lexer_match(ctx->lexer, LEX_T_COMMA);
+        field++;
+    }
+    if (field != n_fields) {
+        lexer_syntax_error(ctx->lexer, "not enough fields parsed");
+    }
+}
+static void
+parse_put_nd_opt_sll(struct action_context *ctx,
+                     struct ovnact_put_nd_opt_sll *sll)
+{
+    lexer_force_match(ctx->lexer, LEX_T_LPAREN);
+    if (!lexer_match(ctx->lexer, LEX_T_INTEGER)
+        || ctx->lexer->token.format != LEX_F_ETHERNET) {
+        lexer_syntax_error(ctx->lexer, "expecting ether address");
+    }
+    sll->mac = ctx->lexer->token.value.mac;
+    lexer_force_match(ctx->lexer, LEX_T_RPAREN);
+}
+static void
+parse_put_nd_opt_mtu(struct action_context *ctx,
+                     struct ovnact_put_nd_opt_mtu *mtu)
+{
+    lexer_force_match(ctx->lexer, LEX_T_LPAREN);
+    if (!lexer_match(ctx->lexer, LEX_T_INTEGER)
+        || ctx->lexer->token.format != LEX_F_DECIMAL) {
+        lexer_syntax_error(ctx->lexer, "expecting decimal integer");
+		}
+		mtu->mtu = ntohl(ctx->lexer->token.value.be32_int);
+    lexer_force_match(ctx->lexer, LEX_T_RPAREN);
+}
+
+static void
+format_PUT_ND_RA(const struct ovnact_put_nd_ra *ra, struct ds *s)
+{
+    ds_put_format(s, "put_nd_ra(%u, %u, %u, %u, %u);",
+                  ra->cur_hop_limit, ra->mo_flags, ra->router_lifetime,
+                  ra->reachable_time, ra->retrans_timer);
+}
+static void
+format_PUT_ND_OPT_PREFIX(const struct ovnact_put_nd_opt_prefix *p,
+                         struct ds *s)
+{
+    ds_put_format(s, "put_nd_opt_prefix(%u, %u, %u, %u, ",
+                  p->prefix_len, p->la_flags, p->valid_lifetime,
+                  p->preferred_lifetime);
+    ipv6_format_addr(&p->prefix, s);
+    ds_put_cstr(s, ");");
+}
+static void
+format_PUT_ND_OPT_SLL(const struct ovnact_put_nd_opt_sll *sll, struct ds *s)
+{
+    ds_put_cstr(s, "put_nd_opt_sll(");
+    ds_put_format(s, ETH_ADDR_FMT, ETH_ADDR_ARGS(sll->mac));
+    ds_put_cstr(s, ");");
+}
+static void
+format_PUT_ND_OPT_MTU(const struct ovnact_put_nd_opt_mtu *mtu, struct ds *s)
+{
+    ds_put_format(s, "put_nd_opt_mtu(%u);", mtu->mtu);
+}
+
+static void
+encode_PUT_ND_RA(const struct ovnact_put_nd_ra *ra,
+                 const struct ovnact_encode_params *ep OVS_UNUSED,
+                 struct ofpbuf *ofpacts)
+{
+    uint8_t inner_code = ACTION_OPCODE_PUT_ND_RA;
+    ofpbuf_put(ofpacts, &inner_code, sizeof(uint8_t));
+    ofpbuf_put(ofpacts, &ra->cur_hop_limit, sizeof(uint8_t));
+    ofpbuf_put(ofpacts, &ra->mo_flags, sizeof(uint8_t));
+    uint16_t rlt = htons(ra->router_lifetime);
+    uint32_t rt = htonl(ra->reachable_time);
+    uint32_t rtt = htonl(ra->retrans_timer);
+    ofpbuf_put(ofpacts, &rlt, sizeof(uint16_t));
+    ofpbuf_put(ofpacts, &rt, sizeof(uint32_t));
+    ofpbuf_put(ofpacts, &rtt, sizeof(uint32_t));
+}
+static void
+encode_PUT_ND_OPT_PREFIX(const struct ovnact_put_nd_opt_prefix *prefix,
+                         const struct ovnact_encode_params *ep OVS_UNUSED,
+                         struct ofpbuf *ofpacts)
+{
+    uint8_t inner_code = ACTION_OPCODE_PUT_ND_OPT_PREFIX;
+    ofpbuf_put(ofpacts, &inner_code, sizeof(uint8_t));
+    ofpbuf_put(ofpacts, &prefix->prefix_len, sizeof(uint8_t));
+    ofpbuf_put(ofpacts, &prefix->la_flags, sizeof(uint8_t));
+    uint32_t vlt = htonl(prefix->valid_lifetime);
+    uint32_t plt = htonl(prefix->preferred_lifetime);
+    ofpbuf_put(ofpacts, &vlt, sizeof(uint32_t));
+    ofpbuf_put(ofpacts, &plt, sizeof(uint32_t));
+    ofpbuf_put(ofpacts, &prefix->prefix, sizeof(struct in6_addr));
+}
+static void
+encode_PUT_ND_OPT_SLL(const struct ovnact_put_nd_opt_sll *sll,
+                      const struct ovnact_encode_params *ep OVS_UNUSED,
+                      struct ofpbuf *ofpacts)
+{
+    uint8_t inner_code = ACTION_OPCODE_PUT_ND_OPT_SLL;
+    ofpbuf_put(ofpacts, &inner_code, sizeof(uint8_t));
+    ofpbuf_put(ofpacts, &sll->mac, sizeof(struct eth_addr));
+}
+static void
+encode_PUT_ND_OPT_MTU(const struct ovnact_put_nd_opt_mtu *mtu,
+                      const struct ovnact_encode_params *ep OVS_UNUSED,
+                      struct ofpbuf *ofpacts)
+{
+    uint8_t inner_code = ACTION_OPCODE_PUT_ND_OPT_MTU;
+    ofpbuf_put(ofpacts, &inner_code, sizeof(uint8_t));
+    uint32_t m = htonl(mtu->mtu);
+    ofpbuf_put(ofpacts, &m, sizeof(uint32_t));
+}
+static void
+free_PUT_ND_RA(struct ovnact_put_nd_ra *ra OVS_UNUSED)
+{
+}
+static void
+free_PUT_ND_OPT_PREFIX(struct ovnact_put_nd_opt_prefix *prefix OVS_UNUSED)
+{
+}
+static void
+free_PUT_ND_OPT_SLL(struct ovnact_put_nd_opt_sll *sll OVS_UNUSED)
+{
+}
+static void
+free_PUT_ND_OPT_MTU(struct ovnact_put_nd_opt_mtu *mtu OVS_UNUSED)
+{
+}
+
 static bool
 parse_action(struct action_context *ctx)
 {
@@ -1713,6 +1943,15 @@ parse_action(struct action_context *ctx)
         parse_ND_NA(ctx);
     } else if (lexer_match_id(ctx->lexer, "nd_ra")) {
         parse_ND_RA(ctx);
+    } else if (lexer_match_id(ctx->lexer, "put_nd_ra")) {
+        parse_put_nd_ra(ctx, ovnact_put_PUT_ND_RA(ctx->ovnacts));
+    } else if (lexer_match_id(ctx->lexer, "put_nd_opt_prefix")) {
+        parse_put_nd_opt_prefix(ctx,
+                                ovnact_put_PUT_ND_OPT_PREFIX(ctx->ovnacts));
+    } else if (lexer_match_id(ctx->lexer, "put_nd_opt_sll")) {
+        parse_put_nd_opt_sll(ctx, ovnact_put_PUT_ND_OPT_SLL(ctx->ovnacts));
+    } else if (lexer_match_id(ctx->lexer, "put_nd_opt_mtu")) {
+        parse_put_nd_opt_mtu(ctx, ovnact_put_PUT_ND_OPT_MTU(ctx->ovnacts));
     } else if (lexer_match_id(ctx->lexer, "get_arp")) {
         parse_get_mac_bind(ctx, 32, ovnact_put_GET_ARP(ctx->ovnacts));
     } else if (lexer_match_id(ctx->lexer, "put_arp")) {
@@ -1850,7 +2089,7 @@ ovnacts_format(const struct ovnact *ovnacts, size_t ovnacts_len,
 
 /* Encoding ovnacts to OpenFlow. */
 
-static void
+void
 ovnact_encode(const struct ovnact *a, const struct ovnact_encode_params *ep,
               struct ofpbuf *ofpacts)
 {
diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml
index 7b17d9d..1f60863 100644
--- a/ovn/ovn-sb.xml
+++ b/ovn/ovn-sb.xml
@@ -1242,6 +1242,45 @@
           </ul>
 
           <p>
+            The 'nd_ra' action has specific inner action
+            <code>put_nd_ra(h,mo,lt,ct,tr)</code> to hold values for fields
+            and flags in RA message.
+            <b>Parameters</b>: 8-bit unsigned integer cur_hop_limit field
+            <var>h</var>, 8-bit unsigned integer for "Managed address
+            configuration" and "Other configuration" flags with 6-bit reserved
+            0 in field <var>mo</var>, 16-bit unsigned integer router_lifetime
+            field <var>lt</var>, 32-bit unsigned integer reachable_time
+            field <var>ct</var>, 32-bit unsigned integer retrans_timer
+            field <var>tr</var>.
+          </p>
+
+          <p>
+            The 'nd_ra' action has specific inner action
+            <code>put_nd_opt_prefix(pl,la,vt,pt,p)</code> to hold values for
+            fields and flags in Prefix Information Option for RA message.
+            <b>Parameters</b>: 8-bit unsigned integer prefix lenght field
+            <var>pl</var>, 8-bit unsigned integer for on-link flag and
+            autonomous address-configuration flag with 6-bit reserved 0 in
+            field <var>mo</var>, 32-bit unsigned integer valid_lifetime field
+            <var>vt</var>, 32-bit unsigned integer preferred_lifetime field
+            <var>pt</var>, 128-bit IPv6 prefix field <var>p</var>.
+          </p>
+
+          <p>
+            The 'nd_ra' action has specific inner action
+            <code>put_nd_opt_mtu(m)</code> to hold value for mtu field in MTU
+            Option for RA message.
+            <b>Parameters</b>: 32-bit unsigned integer mtu field <var>m</var>.
+          </p>
+
+          <p>
+            The 'nd_ra' action has specific inner action
+            <code>put_nd_opt_sll(s)</code> to hold value for link-layer address
+            field in Source Link-layer Address Option for RA message.
+            <b>Parameters</b>: 48-bit Ethernet address field <var>s</var>.
+          </p>
+
+          <p>
             The ND packet has the same VLAN header, if any, as the IPv6 packet
             it replaces.
           </p>
diff --git a/ovn/utilities/ovn-trace.c b/ovn/utilities/ovn-trace.c
index 8583d6d..3ee7ade 100644
--- a/ovn/utilities/ovn-trace.c
+++ b/ovn/utilities/ovn-trace.c
@@ -1268,6 +1268,10 @@ trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len,
 
         case OVNACT_PUT_ARP:
         case OVNACT_PUT_ND:
+        case OVNACT_PUT_ND_RA:
+        case OVNACT_PUT_ND_OPT_PREFIX:
+        case OVNACT_PUT_ND_OPT_SLL:
+        case OVNACT_PUT_ND_OPT_MTU:
             /* Nothing to do for tracing. */
             break;
 
diff --git a/tests/ovn.at b/tests/ovn.at
index 788cc0e..3091131 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -886,9 +886,21 @@ nd_na { eth.src = 12:34:56:78:9a:bc; nd.tll = 12:34:56:78:9a:bc; outport = inpor
     has prereqs nd_ns
 
 # nd_ra
-nd_ra{eth.src = 12:34:56:78:9a:bc; ip6.src = fdad:1234:5678::1; outport = inport; flags.loopback = 1; output;};
+nd_ra { eth.src = 12:34:56:78:9a:bc; ip6.src = fdad:1234:5678::1; outport = inport; flags.loopback = 1; output;};
     formats as nd_ra { eth.src = 12:34:56:78:9a:bc; ip6.src = fdad:1234:5678::1; outport = inport; flags.loopback = 1; output; };
-    encodes as controller(userdata=00.00.00.06.00.00.00.00.00.19.00.10.80.00.08.06.12.34.56.78.9a.bc.00.00.00.19.00.18.80.00.34.10.fd.ad.12.34.56.78.00.00.00.00.00.00.00.00.00.01.ff.ff.00.18.00.00.23.20.00.06.00.20.00.00.00.00.00.01.1c.04.00.01.1e.04.ff.ff.00.18.00.00.23.20.00.07.00.00.00.01.14.04.00.00.00.00.00.00.00.01.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00)
+    encodes as controller(userdata=00.00.00.06.00.00.00.00.00.00.00.19.00.10.80.00.08.06.12.34.56.78.9a.bc.00.00.00.19.00.18.80.00.34.10.fd.ad.12.34.56.78.00.00.00.00.00.00.00.00.00.01.ff.ff.00.18.00.00.23.20.00.06.00.20.00.00.00.00.00.01.1c.04.00.01.1e.04.ff.ff.00.18.00.00.23.20.00.07.00.00.00.01.14.04.00.00.00.00.00.00.00.01.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00)
+    has prereqs nd_rs
+nd_ra { put_nd_ra(64,0,10800,0,0);};
+    formats as nd_ra { put_nd_ra(64, 0, 10800, 0, 0); };
+    encodes as controller(userdata=00.00.00.06.00.00.00.00.00.0d.07.40.00.2a.30.00.00.00.00.00.00.00.00)
+    has prereqs nd_rs
+nd_ra { put_nd_opt_sll(12:34:56:78:9a:bc); put_nd_opt_mtu(1280); put_nd_opt_prefix(64,0,10800,10800,fdad:1234:5678::);};
+    formats as nd_ra { put_nd_opt_sll(12:34:56:78:9a:bc); put_nd_opt_mtu(1280); put_nd_opt_prefix(64, 0, 10800, 10800, fdad:1234:5678::); };
+    encodes as controller(userdata=00.00.00.06.00.00.00.00.00.27.08.12.34.56.78.9a.bc.09.00.00.05.00.0a.40.00.00.00.2a.30.00.00.2a.30.fd.ad.12.34.56.78.00.00.00.00.00.00.00.00.00.00)
+    has prereqs nd_rs
+nd_ra { put_nd_ra(64,0,10800,0,0); put_nd_opt_sll(12:34:56:78:9a:bc); put_nd_opt_mtu(1280); put_nd_opt_prefix(64,0,10800,10800,fdad:1234:5678::); eth.src = 12:34:56:78:9a:bc; nd.tll = 12:34:56:78:9a:bc; outport = inport; inport = ""; /* Allow sending out inport. */ output; };
+    formats as nd_ra { put_nd_ra(64, 0, 10800, 0, 0); put_nd_opt_sll(12:34:56:78:9a:bc); put_nd_opt_mtu(1280); put_nd_opt_prefix(64, 0, 10800, 10800, fdad:1234:5678::); eth.src = 12:34:56:78:9a:bc; nd.tll = 12:34:56:78:9a:bc; outport = inport; inport = ""; output; };
+    encodes as controller(userdata=00.00.00.06.00.00.00.00.00.34.07.40.00.2a.30.00.00.00.00.00.00.00.00.08.12.34.56.78.9a.bc.09.00.00.05.00.0a.40.00.00.00.2a.30.00.00.2a.30.fd.ad.12.34.56.78.00.00.00.00.00.00.00.00.00.00.00.19.00.10.80.00.08.06.12.34.56.78.9a.bc.00.00.00.19.00.10.80.00.42.06.12.34.56.78.9a.bc.00.00.ff.ff.00.18.00.00.23.20.00.06.00.20.00.00.00.00.00.01.1c.04.00.01.1e.04.00.19.00.10.00.01.1c.04.00.00.00.00.00.00.00.00.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00)
     has prereqs nd_rs
 
 # get_nd
