summaryrefslogtreecommitdiff
path: root/ovn
diff options
context:
space:
mode:
authorNuman Siddique <nusiddiq@redhat.com>2017-11-02 06:19:24 +0530
committerBen Pfaff <blp@ovn.org>2017-11-02 13:39:14 -0700
commit52ed5fcc5dddc6ad7a7d500df73032fc1c5133b8 (patch)
tree028142e241184a1019e55f6f8fd99f995260b8cb /ovn
parent16936e4d6e3f8dd68dd6a4959e4937c9daab3e4a (diff)
downloadopenvswitch-52ed5fcc5dddc6ad7a7d500df73032fc1c5133b8.tar.gz
ovn-controller: Add a new action - 'put_nd_ra_opts'
This patch adds a new OVN action 'put_nd_ra_opts' to support native IPv6 Router Advertisement in OVN. This action can be used to respond to the IPv6 Router Solicitation requests. ovn-controller parses this action and adds a NXT_PACKET_IN2 OF flow with 'pause' flag set and the RA options stored in 'userdata' field. This action is similar to 'put_dhcp_opts' and 'put_dhcpv6_opts'. When a valid IPv6 RS packet is received by the pinctrl module of ovn-controller, it frames a new RA packet and sets the RA options from the 'userdata' field and resumes the packet storing 1 in the 1-bit result sub-field. If the packet is invalid, it resumes the packet without any modifications storing 0 in the 1-bit result sub-field. Eg. reg0[5] = put_nd_ra_opts(address_mode = "slaac", mtu = 1450, slla = 01:02:03:04:05:06, prefix = aef0::/64) Note that unlike DHCPv4/v6, a new table to store the supported IPv6 ND RA options is not added in SB DB since there are only 3 ND RA options. Co-authored-by: Zongkai LI <zealokii@gmail.com> Signed-off-by: Zongkai LI <zealokii@gmail.com> Signed-off-by: Numan Siddique <nusiddiq@redhat.com> Signed-off-by: Ben Pfaff <blp@ovn.org>
Diffstat (limited to 'ovn')
-rw-r--r--ovn/controller/lflow.c13
-rw-r--r--ovn/controller/pinctrl.c96
-rw-r--r--ovn/lib/actions.c182
-rw-r--r--ovn/lib/ovn-l7.h48
-rw-r--r--ovn/ovn-sb.xml77
-rw-r--r--ovn/utilities/ovn-trace.c51
6 files changed, 450 insertions, 17 deletions
diff --git a/ovn/controller/lflow.c b/ovn/controller/lflow.c
index 6b6b91abc..a62ec6ebe 100644
--- a/ovn/controller/lflow.c
+++ b/ovn/controller/lflow.c
@@ -65,6 +65,7 @@ static void consider_logical_flow(struct controller_ctx *ctx,
const struct sbrec_chassis *chassis,
struct hmap *dhcp_opts,
struct hmap *dhcpv6_opts,
+ struct hmap *nd_ra_opts,
uint32_t *conj_id_ofs,
const struct shash *addr_sets,
struct hmap *flow_table,
@@ -167,17 +168,21 @@ add_logical_flows(struct controller_ctx *ctx,
dhcpv6_opt_row->type);
}
+ struct hmap nd_ra_opts = HMAP_INITIALIZER(&nd_ra_opts);
+ nd_ra_opts_init(&nd_ra_opts);
+
SBREC_LOGICAL_FLOW_FOR_EACH (lflow, ctx->ovnsb_idl) {
consider_logical_flow(ctx, chassis_index,
lflow, local_datapaths,
group_table, chassis,
- &dhcp_opts, &dhcpv6_opts, &conj_id_ofs,
- addr_sets, flow_table, active_tunnels,
- local_lport_ids);
+ &dhcp_opts, &dhcpv6_opts, &nd_ra_opts,
+ &conj_id_ofs, addr_sets, flow_table,
+ active_tunnels, local_lport_ids);
}
dhcp_opts_destroy(&dhcp_opts);
dhcp_opts_destroy(&dhcpv6_opts);
+ nd_ra_opts_destroy(&nd_ra_opts);
}
static void
@@ -189,6 +194,7 @@ consider_logical_flow(struct controller_ctx *ctx,
const struct sbrec_chassis *chassis,
struct hmap *dhcp_opts,
struct hmap *dhcpv6_opts,
+ struct hmap *nd_ra_opts,
uint32_t *conj_id_ofs,
const struct shash *addr_sets,
struct hmap *flow_table,
@@ -224,6 +230,7 @@ consider_logical_flow(struct controller_ctx *ctx,
.symtab = &symtab,
.dhcp_opts = dhcp_opts,
.dhcpv6_opts = dhcpv6_opts,
+ .nd_ra_opts = nd_ra_opts,
.pipeline = ingress ? OVNACT_P_INGRESS : OVNACT_P_EGRESS,
.n_tables = LOG_PIPELINE_LEN,
diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c
index 6218d8a92..3e470717c 100644
--- a/ovn/controller/pinctrl.c
+++ b/ovn/controller/pinctrl.c
@@ -81,6 +81,10 @@ static void pinctrl_handle_nd_na(const struct flow *ip_flow,
struct ofpbuf *userdata);
static void reload_metadata(struct ofpbuf *ofpacts,
const struct match *md);
+static void pinctrl_handle_put_nd_ra_opts(
+ const struct flow *ip_flow, struct dp_packet *pkt_in,
+ struct ofputil_packet_in *pin, struct ofpbuf *userdata,
+ struct ofpbuf *continuation);
COVERAGE_DEFINE(pinctrl_drop_put_mac_binding);
@@ -985,6 +989,11 @@ process_packet_in(const struct ofp_header *msg, struct controller_ctx *ctx)
handle_acl_log(&headers, &userdata);
break;
+ case ACTION_OPCODE_PUT_ND_RA_OPTS:
+ pinctrl_handle_put_nd_ra_opts(&headers, &packet, &pin, &userdata,
+ &continuation);
+ break;
+
default:
VLOG_WARN_RL(&rl, "unrecognized packet-in opcode %"PRIu32,
ntohl(ah->opcode));
@@ -1848,3 +1857,90 @@ exit:
dp_packet_uninit(&packet);
ofpbuf_uninit(&ofpacts);
}
+
+static void
+pinctrl_handle_put_nd_ra_opts(
+ const struct flow *in_flow, struct dp_packet *pkt_in,
+ struct ofputil_packet_in *pin, struct ofpbuf *userdata,
+ struct ofpbuf *continuation)
+{
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+ enum ofp_version version = rconn_get_version(swconn);
+ enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version);
+ struct dp_packet *pkt_out_ptr = NULL;
+ uint32_t success = 0;
+
+ /* Parse result field. */
+ const struct mf_field *f;
+ enum ofperr ofperr = nx_pull_header(userdata, NULL, &f, NULL);
+ if (ofperr) {
+ VLOG_WARN_RL(&rl, "bad result OXM (%s)", ofperr_to_string(ofperr));
+ goto exit;
+ }
+
+ /* Parse result offset. */
+ ovs_be32 *ofsp = ofpbuf_try_pull(userdata, sizeof *ofsp);
+ if (!ofsp) {
+ VLOG_WARN_RL(&rl, "offset not present in the userdata");
+ goto exit;
+ }
+
+ /* Check that the result is valid and writable. */
+ struct mf_subfield dst = { .field = f, .ofs = ntohl(*ofsp), .n_bits = 1 };
+ ofperr = mf_check_dst(&dst, NULL);
+ if (ofperr) {
+ VLOG_WARN_RL(&rl, "bad result bit (%s)", ofperr_to_string(ofperr));
+ goto exit;
+ }
+
+ if (!userdata->size) {
+ VLOG_WARN_RL(&rl, "IPv6 ND RA options not present in the userdata");
+ goto exit;
+ }
+
+ if (!is_icmpv6(in_flow, NULL) || in_flow->tp_dst != htons(0) ||
+ in_flow->tp_src != htons(ND_ROUTER_SOLICIT)) {
+ VLOG_WARN_RL(&rl, "put_nd_ra action on invalid or unsupported packet");
+ goto exit;
+ }
+
+ size_t new_packet_size = pkt_in->l4_ofs + userdata->size;
+ struct dp_packet pkt_out;
+ dp_packet_init(&pkt_out, new_packet_size);
+ dp_packet_clear(&pkt_out);
+ dp_packet_prealloc_tailroom(&pkt_out, new_packet_size);
+ pkt_out_ptr = &pkt_out;
+
+ /* Copy L2 and L3 headers from pkt_in. */
+ dp_packet_put(&pkt_out, dp_packet_pull(pkt_in, pkt_in->l4_ofs),
+ pkt_in->l4_ofs);
+
+ pkt_out.l2_5_ofs = pkt_in->l2_5_ofs;
+ pkt_out.l2_pad_size = pkt_in->l2_pad_size;
+ pkt_out.l3_ofs = pkt_in->l3_ofs;
+ pkt_out.l4_ofs = pkt_in->l4_ofs;
+
+ /* Copy the ICMPv6 Router Advertisement data from 'userdata' field. */
+ dp_packet_put(&pkt_out, userdata->data, userdata->size);
+
+ /* Set the IPv6 payload length and calculate the ICMPv6 checksum. */
+ struct ovs_16aligned_ip6_hdr *nh = dp_packet_l3(&pkt_out);
+ nh->ip6_plen = htons(userdata->size);
+ struct ovs_ra_msg *ra = dp_packet_l4(&pkt_out);
+ ra->icmph.icmp6_cksum = 0;
+ uint32_t icmp_csum = packet_csum_pseudoheader6(nh);
+ ra->icmph.icmp6_cksum = csum_finish(csum_continue(
+ icmp_csum, ra, userdata->size));
+ pin->packet = dp_packet_data(&pkt_out);
+ pin->packet_len = dp_packet_size(&pkt_out);
+ success = 1;
+
+exit:
+ if (!ofperr) {
+ union mf_subvalue sv;
+ sv.u8_val = success;
+ mf_write_subfield(&dst, &sv, &pin->flow_metadata);
+ }
+ queue_msg(ofputil_encode_resume(pin, continuation, proto));
+ dp_packet_uninit(pkt_out_ptr);
+}
diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c
index d4cde6acb..0df5edbaa 100644
--- a/ovn/lib/actions.c
+++ b/ovn/lib/actions.c
@@ -22,6 +22,7 @@
#include "compiler.h"
#include "ovn-l7.h"
#include "hash.h"
+#include "lib/packets.h"
#include "logical-fields.h"
#include "nx-match.h"
#include "openvswitch/dynamic-string.h"
@@ -1496,7 +1497,7 @@ parse_put_opts(struct action_context *ctx, const struct expr_field *dst,
struct ovnact_put_opts *po, const struct hmap *gen_opts,
const char *opts_type)
{
- lexer_get(ctx->lexer); /* Skip put_dhcp[v6]_opts. */
+ lexer_get(ctx->lexer); /* Skip put_dhcp[v6]_opts / put_nd_ra_opts. */
lexer_get(ctx->lexer); /* Skip '('. */
/* Validate that the destination is a 1-bit, modifiable field. */
@@ -1829,6 +1830,181 @@ static void
ovnact_dns_lookup_free(struct ovnact_dns_lookup *dl OVS_UNUSED)
{
}
+
+/* Parses the "put_nd_ra_opts" action.
+ * The caller has already consumed "<dst> =", so this just parses the rest. */
+static void
+parse_put_nd_ra_opts(struct action_context *ctx, const struct expr_field *dst,
+ struct ovnact_put_opts *po)
+{
+ parse_put_opts(ctx, dst, po, ctx->pp->nd_ra_opts, "IPv6 ND RA");
+
+ if (ctx->lexer->error) {
+ return;
+ }
+
+ bool addr_mode_stateful = false;
+ bool prefix_set = false;
+ bool slla_present = false;
+ /* Let's validate the options. */
+ for (struct ovnact_gen_option *o = po->options;
+ o < &po->options[po->n_options]; o++) {
+ const union expr_constant *c = o->value.values;
+ if (o->value.n_values > 1) {
+ lexer_error(ctx->lexer, "Invalid value for \"%s\" option",
+ o->option->name);
+ return;
+ }
+
+ bool ok = true;
+ switch (o->option->code) {
+ case ND_RA_FLAG_ADDR_MODE:
+ ok = (c->string && (!strcmp(c->string, "slaac") ||
+ !strcmp(c->string, "dhcpv6_stateful") ||
+ !strcmp(c->string, "dhcpv6_stateless")));
+ if (ok && !strcmp(c->string, "dhcpv6_stateful")) {
+ addr_mode_stateful = true;
+ }
+ break;
+
+ case ND_OPT_SOURCE_LINKADDR:
+ ok = c->format == LEX_F_ETHERNET;
+ slla_present = true;
+ break;
+
+ case ND_OPT_PREFIX_INFORMATION:
+ ok = c->format == LEX_F_IPV6 && c->masked;
+ prefix_set = true;
+ break;
+
+ case ND_OPT_MTU:
+ ok = c->format == LEX_F_DECIMAL;
+ break;
+ }
+
+ if (!ok) {
+ lexer_error(ctx->lexer, "Invalid value for \"%s\" option",
+ o->option->name);
+ return;
+ }
+ }
+
+ if (!slla_present) {
+ lexer_error(ctx->lexer, "slla option not present");
+ return;
+ }
+
+ if (addr_mode_stateful && prefix_set) {
+ lexer_error(ctx->lexer, "prefix option can't be"
+ " set when address mode is dhcpv6_stateful.");
+ return;
+ }
+
+ if (!addr_mode_stateful && !prefix_set) {
+ lexer_error(ctx->lexer, "prefix option needs "
+ "to be set when address mode is slaac/dhcpv6_stateless.");
+ return;
+ }
+
+ add_prerequisite(ctx, "ip6");
+}
+
+static void
+format_PUT_ND_RA_OPTS(const struct ovnact_put_opts *po,
+ struct ds *s)
+{
+ format_put_opts("put_nd_ra_opts", po, s);
+}
+
+static void
+encode_put_nd_ra_option(const struct ovnact_gen_option *o,
+ struct ofpbuf *ofpacts, struct ovs_ra_msg *ra)
+{
+ const union expr_constant *c = o->value.values;
+
+ switch (o->option->code) {
+ case ND_RA_FLAG_ADDR_MODE:
+ if (!strcmp(c->string, "dhcpv6_stateful")) {
+ ra->mo_flags = IPV6_ND_RA_FLAG_MANAGED_ADDR_CONFIG;
+ } else if (!strcmp(c->string, "dhcpv6_stateless")) {
+ ra->mo_flags = IPV6_ND_RA_FLAG_OTHER_ADDR_CONFIG;
+ }
+ break;
+
+ case ND_OPT_SOURCE_LINKADDR:
+ {
+ struct ovs_nd_lla_opt *lla_opt =
+ ofpbuf_put_uninit(ofpacts, sizeof *lla_opt);
+ lla_opt->type = ND_OPT_SOURCE_LINKADDR;
+ lla_opt->len = 1;
+ lla_opt->mac = c->value.mac;
+ break;
+ }
+
+ case ND_OPT_MTU:
+ {
+ struct ovs_nd_mtu_opt *mtu_opt =
+ ofpbuf_put_uninit(ofpacts, sizeof *mtu_opt);
+ mtu_opt->type = ND_OPT_MTU;
+ mtu_opt->len = 1;
+ mtu_opt->reserved = 0;
+ put_16aligned_be32(&mtu_opt->mtu, c->value.be32_int);
+ break;
+ }
+
+ case ND_OPT_PREFIX_INFORMATION:
+ {
+ struct ovs_nd_prefix_opt *prefix_opt =
+ ofpbuf_put_uninit(ofpacts, sizeof *prefix_opt);
+ uint8_t prefix_len = ipv6_count_cidr_bits(&c->mask.ipv6);
+ prefix_opt->type = ND_OPT_PREFIX_INFORMATION;
+ prefix_opt->len = 4;
+ prefix_opt->prefix_len = prefix_len;
+ prefix_opt->la_flags = IPV6_ND_RA_OPT_PREFIX_FLAGS;
+ put_16aligned_be32(&prefix_opt->valid_lifetime,
+ htonl(IPV6_ND_RA_OPT_PREFIX_VALID_LIFETIME));
+ put_16aligned_be32(&prefix_opt->preferred_lifetime,
+ htonl(IPV6_ND_RA_OPT_PREFIX_PREFERRED_LIFETIME));
+ put_16aligned_be32(&prefix_opt->reserved, 0);
+ memcpy(prefix_opt->prefix.be32, &c->value.be128[7].be32,
+ sizeof(ovs_be32[4]));
+ break;
+ }
+ }
+}
+
+static void
+encode_PUT_ND_RA_OPTS(const struct ovnact_put_opts *po,
+ const struct ovnact_encode_params *ep OVS_UNUSED,
+ struct ofpbuf *ofpacts)
+{
+ struct mf_subfield dst = expr_resolve_field(&po->dst);
+
+ size_t oc_offset = encode_start_controller_op(
+ ACTION_OPCODE_PUT_ND_RA_OPTS, true, ofpacts);
+ nx_put_header(ofpacts, dst.field->id, OFP13_VERSION, false);
+ ovs_be32 ofs = htonl(dst.ofs);
+ ofpbuf_put(ofpacts, &ofs, sizeof ofs);
+
+ /* Frame the complete ICMPv6 Router Advertisement data encoding
+ * the ND RA options in it, in the userdata field, so that when
+ * pinctrl module receives the ICMPv6 Router Solicitation packet
+ * it can copy the userdata field AS IS and resume the packet.
+ */
+ struct ovs_ra_msg *ra = ofpbuf_put_zeros(ofpacts, sizeof *ra);
+ ra->icmph.icmp6_type = ND_ROUTER_ADVERT;
+ ra->cur_hop_limit = IPV6_ND_RA_CUR_HOP_LIMIT;
+ ra->mo_flags = 0;
+ ra->router_lifetime = htons(IPV6_ND_RA_LIFETIME);
+
+ for (const struct ovnact_gen_option *o = po->options;
+ o < &po->options[po->n_options]; o++) {
+ encode_put_nd_ra_option(o, ofpacts, ra);
+ }
+
+ encode_finish_controller_op(oc_offset, ofpacts);
+}
+
static void
parse_log_arg(struct action_context *ctx, struct ovnact_log *log)
@@ -1968,6 +2144,10 @@ parse_set_action(struct action_context *ctx)
} else if (!strcmp(ctx->lexer->token.s, "dns_lookup")
&& lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
parse_dns_lookup(ctx, &lhs, ovnact_put_DNS_LOOKUP(ctx->ovnacts));
+ } else if (!strcmp(ctx->lexer->token.s, "put_nd_ra_opts")
+ && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
+ parse_put_nd_ra_opts(ctx, &lhs,
+ ovnact_put_PUT_ND_RA_OPTS(ctx->ovnacts));
} else {
parse_assignment_action(ctx, false, &lhs);
}
diff --git a/ovn/lib/ovn-l7.h b/ovn/lib/ovn-l7.h
index 40bd75461..41cdacdfc 100644
--- a/ovn/lib/ovn-l7.h
+++ b/ovn/lib/ovn-l7.h
@@ -18,6 +18,7 @@
#define OVN_DHCP_H 1
#include <netinet/in.h>
+#include <netinet/icmp6.h>
#include "openvswitch/hmap.h"
#include "hash.h"
@@ -206,4 +207,51 @@ struct dhcpv6_opt_ia_na {
#define DHCPV6_OPT_PAYLOAD(opt) \
(void *)((char *)opt + sizeof(struct dhcpv6_opt_header))
+static inline struct gen_opts_map *
+nd_ra_opts_find(const struct hmap *nd_ra_opts, char *opt_name)
+{
+ return gen_opts_find(nd_ra_opts, opt_name);
+}
+
+static inline void
+nd_ra_opt_add(struct hmap *nd_ra_opts, char *opt_name, size_t code,
+ char *type)
+{
+ gen_opt_add(nd_ra_opts, opt_name, code, type);
+}
+
+static inline void
+nd_ra_opts_destroy(struct hmap *nd_ra_opts)
+{
+ gen_opts_destroy(nd_ra_opts);
+}
+
+
+#define ND_RA_FLAG_ADDR_MODE 0
+
+
+/* Default values of various IPv6 Neighbor Discovery protocol options and
+ * flags. See RFC 4861 for more information.
+ * */
+#define IPV6_ND_RA_FLAG_MANAGED_ADDR_CONFIG 0x80
+#define IPV6_ND_RA_FLAG_OTHER_ADDR_CONFIG 0x40
+
+#define IPV6_ND_RA_CUR_HOP_LIMIT 255
+#define IPV6_ND_RA_LIFETIME 0xffff
+#define IPV6_ND_RA_REACHABLE_TIME 0
+#define IPV6_ND_RA_RETRANSMIT_TIMER 0
+
+#define IPV6_ND_RA_OPT_PREFIX_FLAGS 0xc0
+#define IPV6_ND_RA_OPT_PREFIX_VALID_LIFETIME 0xffffffff
+#define IPV6_ND_RA_OPT_PREFIX_PREFERRED_LIFETIME 0xffffffff
+
+static inline void
+nd_ra_opts_init(struct hmap *nd_ra_opts)
+{
+ nd_ra_opt_add(nd_ra_opts, "addr_mode", ND_RA_FLAG_ADDR_MODE, "str");
+ nd_ra_opt_add(nd_ra_opts, "slla", ND_OPT_SOURCE_LINKADDR, "mac");
+ nd_ra_opt_add(nd_ra_opts, "prefix", ND_OPT_PREFIX_INFORMATION, "ipv6");
+ nd_ra_opt_add(nd_ra_opts, "mtu", ND_OPT_MTU, "uint32");
+}
+
#endif /* OVN_DHCP_H */
diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml
index 0a894f8cb..fab3f9de6 100644
--- a/ovn/ovn-sb.xml
+++ b/ovn/ovn-sb.xml
@@ -1516,6 +1516,83 @@
<b>Prerequisite:</b> <code>udp</code>
</p>
</dd>
+
+ <dt>
+ <code><var>R</var> = put_nd_ra_opts(<var>D1</var> = <var>V1</var>, <var>D2</var> = <var>V2</var>, ..., <var>Dn</var> = <var>Vn</var>);</code>
+ </dt>
+
+ <dd>
+ <p>
+ <b>Parameters</b>: The following IPv6 ND Router Advertisement
+ option/value pairs as defined in RFC 4861.
+
+ <ul>
+ <li>
+ <code>addr_mode</code>
+ <p>
+ Mandatory parameter which specifies the address mode flag to
+ be set in the RA flag options field. The value of this option
+ is a string and the following values can be defined -
+ "slaac", "dhcpv6_stateful" and "dhcpv6_stateless".
+ </p>
+ </li>
+
+ <li>
+ <code>slla</code>
+ <p>
+ Mandatory parameter which specifies the link-layer address of
+ the interface from which the Router Advertisement is sent.
+ </p>
+ </li>
+
+ <li>
+ <code>mtu</code>
+ <p>
+ Optional parameter which specifies the MTU.
+ </p>
+ </li>
+
+ <li>
+ <code>prefix</code>
+ <p>
+ Optional parameter which should be specified if the addr_mode
+ is "slaac" or "dhcpv6_stateless". The value should be an IPv6
+ prefix which will be used for stateless IPv6 address
+ configuration. This option can be defined multiple times.
+ </p>
+ </li>
+ </ul>
+ </p>
+
+ <p>
+ <b>Result</b>: stored to a 1-bit subfield <var>R</var>.
+ </p>
+
+ <p>
+ Valid only in the ingress pipeline.
+ </p>
+
+ <p>
+ When this action is applied to an IPv6 Router solicitation request
+ packet, it changes the packet into an IPv6 Router Advertisement
+ reply and adds the options specified in the parameters, and stores
+ 1 in <var>R</var>.
+ </p>
+
+ <p>
+ When this action is applied to a non-IPv6 Router solicitation
+ packet or an invalid IPv6 request packet , it leaves the packet
+ unchanged and stores 0 in <var>R</var>.
+ </p>
+
+ <p>
+ <b>Example:</b>
+ <code>
+ reg0[3] = put_nd_ra_opts(addr_mode = "slaac",
+ slla = 00:00:00:00:10:02, prefix = aef0::/64, mtu = 1450);
+ </code>
+ </p>
+ </dd>
</dl>
<dl>
diff --git a/ovn/utilities/ovn-trace.c b/ovn/utilities/ovn-trace.c
index d9465c90c..211148b8b 100644
--- a/ovn/utilities/ovn-trace.c
+++ b/ovn/utilities/ovn-trace.c
@@ -420,6 +420,7 @@ static struct shash address_sets;
/* DHCP options. */
static struct hmap dhcp_opts; /* Contains "struct gen_opts_map"s. */
static struct hmap dhcpv6_opts; /* Contains "struct gen_opts_map"s. */
+static struct hmap nd_ra_opts; /* Contains "struct gen_opts_map"s. */
static struct ovntrace_datapath *
ovntrace_datapath_find_by_sb_uuid(const struct uuid *sb_uuid)
@@ -806,6 +807,7 @@ read_flows(void)
.symtab = &symtab,
.dhcp_opts = &dhcp_opts,
.dhcpv6_opts = &dhcpv6_opts,
+ .nd_ra_opts = &nd_ra_opts,
.pipeline = (!strcmp(sblf->pipeline, "ingress")
? OVNACT_P_INGRESS
: OVNACT_P_EGRESS),
@@ -881,6 +883,9 @@ read_gen_opts(void)
SBREC_DHCPV6_OPTIONS_FOR_EACH(sdo6, ovnsb_idl) {
dhcp_opt_add(&dhcpv6_opts, sdo6->name, sdo6->code, sdo6->type);
}
+
+ hmap_init(&nd_ra_opts);
+ nd_ra_opts_init(&nd_ra_opts);
}
static void
@@ -1541,19 +1546,15 @@ execute_get_mac_bind(const struct ovnact_get_mac_bind *bind,
}
static void
-execute_put_dhcp_opts(const struct ovnact_put_opts *pdo,
- const char *name, struct flow *uflow,
- struct ovs_list *super)
+execute_put_opts(const struct ovnact_put_opts *po,
+ const char *name, struct flow *uflow,
+ struct ovs_list *super)
{
- ovntrace_node_append(
- super, OVNTRACE_NODE_ERROR,
- "/* We assume that this packet is DHCPDISCOVER or DHCPREQUEST. */");
-
/* Format the put_dhcp_opts action. */
struct ds s = DS_EMPTY_INITIALIZER;
- for (const struct ovnact_gen_option *o = pdo->options;
- o < &pdo->options[pdo->n_options]; o++) {
- if (o != pdo->options) {
+ for (const struct ovnact_gen_option *o = po->options;
+ o < &po->options[po->n_options]; o++) {
+ if (o != po->options) {
ds_put_cstr(&s, ", ");
}
ds_put_format(&s, "%s = ", o->option->name);
@@ -1562,22 +1563,41 @@ execute_put_dhcp_opts(const struct ovnact_put_opts *pdo,
ovntrace_node_append(super, OVNTRACE_NODE_MODIFY, "%s(%s)",
name, ds_cstr(&s));
- struct mf_subfield dst = expr_resolve_field(&pdo->dst);
+ struct mf_subfield dst = expr_resolve_field(&po->dst);
if (!mf_is_register(dst.field->id)) {
/* Format assignment. */
ds_clear(&s);
- expr_field_format(&pdo->dst, &s);
+ expr_field_format(&po->dst, &s);
ovntrace_node_append(super, OVNTRACE_NODE_MODIFY,
"%s = 1", ds_cstr(&s));
}
ds_destroy(&s);
- struct mf_subfield sf = expr_resolve_field(&pdo->dst);
+ struct mf_subfield sf = expr_resolve_field(&po->dst);
union mf_subvalue sv = { .u8_val = 1 };
mf_write_subfield_flow(&sf, &sv, uflow);
}
static void
+execute_put_dhcp_opts(const struct ovnact_put_opts *pdo,
+ const char *name, struct flow *uflow,
+ struct ovs_list *super)
+{
+ ovntrace_node_append(
+ super, OVNTRACE_NODE_ERROR,
+ "/* We assume that this packet is DHCPDISCOVER or DHCPREQUEST. */");
+ execute_put_opts(pdo, name, uflow, super);
+}
+
+static void
+execute_put_nd_ra_opts(const struct ovnact_put_opts *pdo,
+ const char *name, struct flow *uflow,
+ struct ovs_list *super)
+{
+ execute_put_opts(pdo, name, uflow, super);
+}
+
+static void
execute_next(const struct ovnact_next *next,
const struct ovntrace_datapath *dp, struct flow *uflow,
enum ovnact_pipeline pipeline, struct ovs_list *super)
@@ -1814,6 +1834,11 @@ trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len,
"put_dhcpv6_opts", uflow, super);
break;
+ case OVNACT_PUT_ND_RA_OPTS:
+ execute_put_nd_ra_opts(ovnact_get_PUT_DHCPV6_OPTS(a),
+ "put_nd_ra_opts", uflow, super);
+ break;
+
case OVNACT_SET_QUEUE:
/* The set_queue action is slippery from a logical perspective. It
* has no visible effect as long as the packet remains on the same