@@ -88,6 +88,21 @@ enum action_opcode {
* MFF_ETH_SRC = mac
*/
ACTION_OPCODE_PUT_ND,
+
+ /* "nd_ra { ...actions... }".
+ *
+ * The actions, in OpenFlow 1.3 format, follow the action_header.
+ */
+ ACTION_OPCODE_ND_RA,
+
+ /* slaac (prefix0, [prefix1, ...,] mtu, mac)".
+ *
+ * Arguments follow the action_header, in this format:
+ * - One or more IPv6 prefixes.
+ * - A 16-bit MTU.
+ * - A 48-bit Ether address.
+ */
+ ACTION_OPCODE_SLAAC,
};
/* Header. */
@@ -450,6 +450,17 @@ struct expr_constant_set {
bool in_curlies; /* Whether the constants were in {}. */
};
+/* Context maintained during expr_parse(). */
+struct expr_context {
+ struct lexer *lexer; /* Lexer for pulling more tokens. */
+ const struct shash *symtab; /* Symbol table. */
+ const struct shash *macros; /* Table of macros. */
+ char *error; /* Error, if any, otherwise NULL. */
+ bool not; /* True inside odd number of NOT operators. */
+};
+
+bool parse_constant(struct expr_context *ctx, struct expr_constant_set *cs,
+ size_t *allocated_values);
char *expr_parse_constant_set(struct lexer *, const struct shash *symtab,
struct expr_constant_set *cs)
OVS_WARN_UNUSED_RESULT;
@@ -164,7 +164,11 @@ lflow_init(void)
expr_symtab_add_field(&symtab, "arp.tha", MFF_ARP_THA, "arp", false);
expr_symtab_add_predicate(&symtab, "nd",
- "icmp6.type == {135, 136} && icmp6.code == 0 && ip.ttl == 255");
+ "icmp6.type == {133, 134, 135, 136} && icmp6.code == 0 && ip.ttl == 255");
+ expr_symtab_add_predicate(&symtab, "nd_rs",
+ "icmp6.type == 133 && icmp6.code == 0 && ip.ttl == 255");
+ expr_symtab_add_predicate(&symtab, "nd_ra",
+ "icmp6.type == 134 && icmp6.code == 0 && ip.ttl == 255");
expr_symtab_add_predicate(&symtab, "nd_ns",
"icmp6.type == 135 && icmp6.code == 0 && ip.ttl == 255");
expr_symtab_add_predicate(&symtab, "nd_na",
@@ -73,6 +73,9 @@ static void send_garp_run(const struct ovsrec_bridge *,
static void pinctrl_handle_nd_na(const struct flow *ip_flow,
const struct match *md,
struct ofpbuf *userdata);
+static void pinctrl_handle_nd_ra(const struct flow *ip_flow,
+ struct ofputil_packet_in *pin,
+ struct ofpbuf *userdata);
static void reload_metadata(struct ofpbuf *ofpacts,
const struct match *md);
@@ -421,6 +424,10 @@ process_packet_in(const struct ofp_header *msg)
false);
break;
+ case ACTION_OPCODE_ND_RA:
+ pinctrl_handle_nd_ra(&headers, &pin, &userdata);
+ break;
+
default:
VLOG_WARN_RL(&rl, "unrecognized packet-in opcode %"PRIu32,
ntohl(ah->opcode));
@@ -1019,3 +1026,164 @@ exit:
dp_packet_uninit(&packet);
ofpbuf_uninit(&ofpacts);
}
+
+static bool pinctrl_handle_slaac(const struct flow *ip_flow,
+ struct ofpbuf *userdata,
+ struct dp_packet *packet,
+ int *left_bytes)
+{
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+ bool ret_val = false;
+ struct ipv6_netaddr *prefixes = NULL;
+ /* Get number of prefixes. */
+ uint8_t *n_prefixes = ofpbuf_try_pull(userdata, 1);
+ if (!n_prefixes || *n_prefixes == 0) {
+ VLOG_WARN_RL(&rl, "Failed to parse prefixes number.");
+ goto exit;
+ }
+ (*left_bytes)--;
+
+ /* Get prefixes. */
+ size_t prefixes_size = sizeof(struct ipv6_netaddr) * (*n_prefixes);
+ prefixes = xmalloc(prefixes_size);
+ struct in6_addr *prefix;
+ uint8_t *prefix_len = NULL;
+ for (size_t i = 0; i < *n_prefixes; i++) {
+ prefix = ofpbuf_try_pull(userdata, sizeof(struct in6_addr));
+ if (!prefix) {
+ VLOG_WARN_RL(&rl, "Failed to parse ipv6 prefix.");
+ goto exit;
+ }
+ prefix_len = ofpbuf_try_pull(userdata, 1);
+ if (!prefix_len || *prefix_len == 0) {
+ VLOG_WARN_RL(&rl, "Failed to parse prefix len.");
+ goto exit;
+ }
+ memcpy(&prefixes[i].addr, prefix, sizeof(struct in6_addr));
+ prefixes[i].plen = *prefix_len;
+ }
+ (*left_bytes) -= *n_prefixes * (sizeof(struct in6_addr) + 1);
+
+ /* Get MTU. */
+ ovs_be32 *mtu = ofpbuf_try_pull(userdata, sizeof(ovs_be32));
+ if (!mtu || *mtu == 0) {
+ VLOG_WARN_RL(&rl, "Failed to parse mtu.");
+ goto exit;
+ }
+ (*left_bytes) -= sizeof(ovs_be32);
+
+ /* Get SLL. */
+ struct eth_addr *sll = ofpbuf_try_pull(userdata,
+ sizeof(struct eth_addr));
+ if (!sll) {
+ VLOG_WARN_RL(&rl, "Failed to parse link-layer address.");
+ goto exit;
+ }
+ (*left_bytes) -= sizeof(struct eth_addr);
+
+ ovs_be32 ipv6_src[4], ipv6_dst[4], router_prefix[4];
+ struct in6_addr lla;
+ in6_generate_lla(*sll, &lla);
+ memcpy(ipv6_src, &lla, sizeof ipv6_src);
+ memcpy(ipv6_dst, &ip_flow->ipv6_src, sizeof ipv6_dst);
+
+ uint8_t cur_hop_limit = 64;
+ uint8_t mo_flags = 0;
+ ovs_be16 router_lifetime = htons(0x2a30); // 3 hours.
+ uint8_t la_flags = ND_PREFIX_ON_LINK | ND_PREFIX_AUTONOMOUS_ADDRESS;
+ ovs_be32 valid_lifetime = htonl(0x2a30);
+ ovs_be32 preferred_lifetime = htonl(0x2a30);
+ ovs_be32 reachable_time = 0;
+ ovs_be32 retrans_timer = 0;
+
+ compose_nd_ra(packet, *sll, ip_flow->dl_src, ipv6_src, ipv6_dst,
+ cur_hop_limit, mo_flags,
+ router_lifetime, reachable_time, retrans_timer);
+ for (size_t i = 0; i < *n_prefixes; i++) {
+ memcpy(router_prefix, &prefixes[i].addr, sizeof router_prefix);
+ packet_put_ra_prefix_opt(packet, prefixes[i].plen, la_flags,
+ valid_lifetime, preferred_lifetime,
+ router_prefix);
+ }
+ packet_put_ra_mtu_opt(packet, *mtu);
+ packet_put_ra_sll_opt(packet, *sll);
+ ret_val = true;
+
+exit:
+ if (prefixes) {
+ free(prefixes);
+ }
+ return ret_val;
+}
+
+static void
+pinctrl_handle_nd_ra(const struct flow *ip_flow, struct ofputil_packet_in *pin,
+ struct ofpbuf *userdata)
+{
+ 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);
+ uint64_t ofpacts_stub[4096 / 8];
+ struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub);
+ reload_metadata(&ofpacts, &pin->flow_metadata);
+
+ /* Pull inner action. */
+ struct ofpact *act = ofpbuf_try_pull(userdata, sizeof(struct ofpact));
+ if (!act) {
+ VLOG_WARN_RL(&rl, "Failed to pull ofpact.");
+ return;
+ }
+ int left_bytes = ntohs(act->len) - sizeof(struct ofpact);
+ if (!ofpbuf_try_pull(userdata, sizeof(struct ofpact_controller))) {
+ VLOG_WARN_RL(&rl, "Failed to pull ofpact_controller.");
+ return;
+ }
+ left_bytes -= sizeof(struct ofpact_controller);
+ struct action_header *ah = ofpbuf_try_pull(userdata, sizeof *ah);
+ if (!ah) {
+ VLOG_WARN_RL(&rl, "Failed to pull action_header.");
+ return;
+ }
+ if (ntohl(ah->opcode) != ACTION_OPCODE_SLAAC) {
+ VLOG_WARN_RL(&rl, "Not supported action opcode %d.", ntohl(ah->opcode));
+ return;
+ }
+ left_bytes -= sizeof *ah;
+
+ uint64_t packet_stub[256 / 8];
+ struct dp_packet packet;
+ dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
+ if (!pinctrl_handle_slaac(ip_flow, userdata, &packet, &left_bytes)) {
+ VLOG_WARN_RL(&rl, "Failed to compose a RA packet for SLAAC.");
+ goto exit;
+ }
+ if (left_bytes < 0){
+ VLOG_WARN_RL(&rl, "Left bytes error in compose RA packet.");
+ goto exit;
+ } else if (left_bytes > 0) {
+ ofpbuf_try_pull(userdata, left_bytes);
+ }
+ /* Get left actions. */
+ enum ofperr error = ofpacts_pull_openflow_actions(userdata, userdata->size,
+ version, &ofpacts);
+ if (error) {
+ VLOG_WARN_RL(&rl, "Failed to parse actions for 'ra' (%s)",
+ ofperr_to_string(error));
+ goto exit;
+ }
+
+ struct ofputil_packet_out po = {
+ .packet = dp_packet_data(&packet),
+ .packet_len = dp_packet_size(&packet),
+ .buffer_id = UINT32_MAX,
+ .in_port = OFPP_CONTROLLER,
+ .ofpacts = ofpacts.data,
+ .ofpacts_len = ofpacts.size,
+ };
+
+ queue_msg(ofputil_encode_packet_out(&po, proto));
+
+exit:
+ dp_packet_uninit(&packet);
+ ofpbuf_uninit(&ofpacts);
+}
@@ -1080,6 +1080,71 @@ parse_ct_nat(struct action_context *ctx, bool snat)
ofpbuf_push_uninit(ctx->ofpacts, ct_offset);
}
+static void
+parse_slaac_action(struct action_context *ctx)
+{
+ if (!lexer_match(ctx->lexer, LEX_T_LPAREN)) {
+ action_syntax_error(ctx, "expecting '('");
+ return;
+ }
+
+ struct expr_constant_set cs = {
+ .type = EXPR_C_INTEGER,
+ .values = NULL,
+ .n_values = 0,
+ .in_curlies = false,
+ };
+ struct expr_context expr_ctx = {
+ .lexer = ctx->lexer,
+ .symtab = NULL,
+ };
+ size_t allocated_values = 0;
+
+ do {
+ if (!parse_constant(&expr_ctx, &cs, &allocated_values)) {
+ break;
+ }
+ lexer_match(expr_ctx.lexer, LEX_T_COMMA);
+ } while(!lexer_match(expr_ctx.lexer, LEX_T_RPAREN));
+ /* Multiple prefixes, as least 1 MTU, at least 1 SLL. */
+ if (cs.n_values < 3) {
+ action_syntax_error(ctx, "not enough constants found");
+ goto exit;
+ }
+ /* Check prefixes. */
+ for (size_t i = 0; i < cs.n_values - 2; i++) {
+ if (cs.values[i].format != LEX_F_IPV6) {
+ action_syntax_error(ctx, "expecting IPv6 address format");
+ goto exit;
+ }
+ }
+
+ /* Set controller data. */
+ size_t oc_offset = start_controller_op(ctx->ofpacts, ACTION_OPCODE_SLAAC,
+ false);
+ cs.n_values = cs.n_values - 2;
+ /* Put prefixes number. */
+ ofpbuf_put(ctx->ofpacts, &cs.n_values, 1);
+ /* Put prefixes. */
+ int plen = 0;
+ for (size_t i = 0; i < cs.n_values; i++) {
+ ofpbuf_put(ctx->ofpacts, &cs.values[i].value.ipv6,
+ sizeof(struct in6_addr));
+ plen = ipv6_count_cidr_bits(&cs.values[i].mask.ipv6);
+ ofpbuf_put(ctx->ofpacts, &plen, 1);
+ }
+ /* Put MTU. */
+ ofpbuf_put(ctx->ofpacts, &cs.values[cs.n_values].value.be32[31],
+ sizeof(ovs_be32));
+ /* Put SLL. */
+ ofpbuf_put(ctx->ofpacts, &cs.values[cs.n_values + 1].value.mac,
+ sizeof(struct eth_addr));
+ finish_controller_op(ctx->ofpacts, oc_offset);
+
+exit:
+ expr_constant_set_destroy(&cs);
+}
+
static bool
parse_action(struct action_context *ctx)
{
@@ -1125,6 +1190,10 @@ parse_action(struct action_context *ctx)
parse_get_nd_action(ctx);
} else if (lexer_match_id(ctx->lexer, "put_nd")) {
parse_put_nd_action(ctx);
+ } else if (lexer_match_id(ctx->lexer, "nd_ra")) {
+ parse_nested_action(ctx, ACTION_OPCODE_ND_RA, "nd_rs");
+ } else if (lexer_match_id(ctx->lexer, "slaac")) {
+ parse_slaac_action(ctx);
} else {
action_syntax_error(ctx, "expecting action");
}
@@ -406,15 +406,6 @@ expr_print(const struct expr *e)
/* Parsing. */
-/* Context maintained during expr_parse(). */
-struct expr_context {
- struct lexer *lexer; /* Lexer for pulling more tokens. */
- const struct shash *symtab; /* Symbol table. */
- const struct shash *macros; /* Table of macros. */
- char *error; /* Error, if any, otherwise NULL. */
- bool not; /* True inside odd number of NOT operators. */
-};
-
struct expr *expr_parse__(struct expr_context *);
static void expr_not(struct expr *);
static bool parse_field(struct expr_context *, struct expr_field *);
@@ -757,7 +748,7 @@ parse_macros(struct expr_context *ctx, struct expr_constant_set *cs,
return true;
}
-static bool
+bool
parse_constant(struct expr_context *ctx, struct expr_constant_set *cs,
size_t *allocated_values)
{
@@ -1151,8 +1151,38 @@
</p>
</dd>
- <dt><code>get_nd(<var>P</var>, <var>A</var>);</code></dt>
+ <dt>
+ <code>nd_ra { <var>slaac(...)</var>; <var>action</var>; </code>...<code> };</code>
+ </dt>
+
+ <dd>
+ <p>
+ Generate an IPv6 Router Advertisement (RA) packet per inner action
+ 'slaac' ordered parameters, and eth.src and ip6.src from Router
+ Solicitation (RS) packet being processed.
+ <b>Parameters</b>: One or more IPv6 prefixes, 16-bit MUT, 48-bit
+ mac address.
+ Then executes each nested <var>action</var> on the RA packet.
+ Actions following the <var>ra</var> action, if any, apply to the
+ original, unmodified packet.
+ The composed RA packet will use RS packet eth.src as eth.dst, use
+ RS packet ip6.src as ip6.dst, and use 'slaac' action parameter mac
+ address and its lla as eth.src and ip6.src.
+ </p>
+
+ <p>
+ The RA packet has the same VLAN header, if any, as the IPv6 packet
+ it replaces.
+ </p>
+ <p>
+ <b>Prerequisite:</b> <code>nd_rs</code>
+ </p>
+ </dd>
+
+ <dt><code>get_arp(<var>P</var>, <var>A</var>);</code></dt>
+
+ <dt><code>get_nd(<var>P</var>, <var>A</var>);</code></dt>
<dd>
<p>
<b>Parameters</b>: logical port string field <var>P</var>, 128-bit
@@ -701,7 +701,11 @@ get_nd(inport, outport); => Cannot use string field outport where numeric field
get_nd(xxreg0, ip6.dst); => Cannot use numeric field xxreg0 where string field is required.
# put_nd
-put_nd(inport, nd.target, nd.sll); => actions=push:NXM_NX_XXREG0[],push:NXM_OF_ETH_SRC[],push:NXM_NX_ND_SLL[],push:NXM_NX_ND_TARGET[],pop:NXM_NX_XXREG0[],pop:NXM_OF_ETH_SRC[],controller(userdata=00.00.00.04.00.00.00.00),pop:NXM_OF_ETH_SRC[],pop:NXM_NX_XXREG0[], prereqs=((icmp6.type == 0x87 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd)) || (icmp6.type == 0x88 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd))) && icmp6.code == 0 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd) && ip.ttl == 0xff && (eth.type == 0x800 || eth.type == 0x86dd) && icmp6.type == 0x87 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd) && icmp6.code == 0 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd) && ip.ttl == 0xff && (eth.type == 0x800 || eth.type == 0x86dd)
+put_nd(inport, nd.target, nd.sll); => actions=push:NXM_NX_XXREG0[],push:NXM_OF_ETH_SRC[],push:NXM_NX_ND_SLL[],push:NXM_NX_ND_TARGET[],pop:NXM_NX_XXREG0[],pop:NXM_OF_ETH_SRC[],controller(userdata=00.00.00.04.00.00.00.00),pop:NXM_OF_ETH_SRC[],pop:NXM_NX_XXREG0[], prereqs=((icmp6.type == 0x85 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd)) || (icmp6.type == 0x86 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd)) || (icmp6.type == 0x87 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd)) || (icmp6.type == 0x88 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd))) && icmp6.code == 0 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd) && ip.ttl == 0xff && (eth.type == 0x800 || eth.type == 0x86dd) && icmp6.type == 0x87 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd
) && icmp6.code == 0 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd) && ip.ttl == 0xff && (eth.type == 0x800 || eth.type == 0x86dd)
+
+# ra for slaac
+nd_ra{slaac(fdad:a0f9:a012::/64,fdad:a0f9:c593::/64,1450,fa:16:3e:32:3c:e0); outport = inport; flags.loopback = 1; output;}; => actions=controller(userdata=00.00.00.05.00.00.00.00.ff.ff.00.50.00.00.23.20.00.25.00.00.00.00.00.00.00.03.00.39.00.00.00.06.00.00.00.00.02.fd.ad.a0.f9.a0.12.00.00.00.00.00.00.00.00.00.00.40.fd.ad.a0.f9.c5.93.00.00.00.00.00.00.00.00.00.00.40.00.00.05.aa.fa.16.3e.32.3c.e0.00.00.00.00.00.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.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), prereqs=nd_rs
+nd_ra{slaac(fd80:a123:b345::/64,1450,fa:16:3e:62:f1:e6); outport = inport; flags.loopback = 1; output;}; => actions=controller(userdata=00.00.00.05.00.00.00.00.ff.ff.00.38.00.00.23.20.00.25.00.00.00.00.00.00.00.03.00.28.00.00.00.06.00.00.00.00.01.fd.80.a1.23.b3.45.00.00.00.00.00.00.00.00.00.00.40.00.00.05.aa.fa.16.3e.62.f1.e6.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), prereqs=nd_rs
# Contradictionary prerequisites (allowed but not useful):
ip4.src = ip6.src[0..31]; => actions=move:NXM_NX_IPV6_SRC[0..31]->NXM_OF_IP_SRC[], prereqs=eth.type == 0x800 && eth.type == 0x86dd
@@ -213,7 +213,11 @@ create_symtab(struct shash *symtab)
expr_symtab_add_field(symtab, "arp.tha", MFF_ARP_THA, "arp", false);
expr_symtab_add_predicate(symtab, "nd",
- "icmp6.type == {135, 136} && icmp6.code == 0 && ip.ttl == 255");
+ "icmp6.type == {133, 134, 135, 136} && icmp6.code == 0 && ip.ttl == 255");
+ expr_symtab_add_predicate(symtab, "nd_rs",
+ "icmp6.type == 133 && icmp6.code == 0 && ip.ttl == 255");
+ expr_symtab_add_predicate(symtab, "nd_ra",
+ "icmp6.type == 134 && icmp6.code == 0 && ip.ttl == 255");
expr_symtab_add_predicate(symtab, "nd_ns",
"icmp6.type == 135 && icmp6.code == 0 && ip.ttl == 255");
expr_symtab_add_predicate(symtab, "nd_na",