Message ID | 20171002162534.1710-1-nusiddiq@redhat.com |
---|---|
State | Superseded |
Headers | show |
Series | ovn IPv6: Add Router Solicitation responder support and generate Neighbor Solicitation request for unknown | expand |
On Mon, Oct 2, 2017 at 11:27 AM <nusiddiq@redhat.com> wrote: > From: Numan Siddique <nusiddiq@redhat.com> > > This patch adds a new OVN action 'put_nd_ra_opts' to support native > IPv6 Router Advertisement in OVN. This action can be used to respond > to the IPv6 Router Solicitation requests. > > ovn-controller parses this action and adds a NXT_PACKET_IN2 OF flow > with 'pause' flag set and the RA options stored in 'userdata' field. > This action is similar to 'put_dhcp_opts' and 'put_dhcpv6_opts'. > > When a valid IPv6 RS packet is received by the pinctrl module of > ovn-controller, it frames a new RA packet and sets the RA options > from the 'userdata' field and resumes the packet storing 1 in the > 1-bit result sub-field. If the packet is invalid, it resumes the > packet without any modifications storing 0 in the 1-bit result > sub-field. > > Eg. reg0[5] = put_nd_ra_opts(address_mode = "slaac", mtu = 1450, > slla = 01:02:03:04:05:06, prefix = aef0::/64) > > Note that unlike DHCPv4/v6, a new table to store the supported IPv6 ND RA > options is not added in SB DB since there are only 3 ND RA options. > > Co-authored-by: Zongkai LI <zealokii@gmail.com> > Signed-off-by: Zongkai LI <zealokii@gmail.com> > Signed-off-by: Numan Siddique <nusiddiq@redhat.com> > Acked-by: Mark Michelson <mmichels@redhat.com> > --- > include/ovn/actions.h | 14 +++- > ovn/controller/lflow.c | 13 +++- > ovn/controller/pinctrl.c | 96 +++++++++++++++++++++++ > ovn/lib/actions.c | 194 > +++++++++++++++++++++++++++++++++++++++++++++- > ovn/lib/ovn-l7.h | 48 ++++++++++++ > ovn/ovn-sb.xml | 77 ++++++++++++++++++ > ovn/utilities/ovn-trace.c | 51 ++++++++---- > tests/ovn.at | 31 ++++++++ > tests/test-ovn.c | 13 +++- > 9 files changed, 516 insertions(+), 21 deletions(-) > > diff --git a/include/ovn/actions.h b/include/ovn/actions.h > index d13a3747b..15cee478d 100644 > --- a/include/ovn/actions.h > +++ b/include/ovn/actions.h > @@ -72,7 +72,8 @@ struct simap; > OVNACT(PUT_DHCPV6_OPTS, ovnact_put_opts) \ > OVNACT(SET_QUEUE, ovnact_set_queue) \ > OVNACT(DNS_LOOKUP, ovnact_dns_lookup) \ > - OVNACT(LOG, ovnact_log) > + OVNACT(LOG, ovnact_log) \ > + OVNACT(PUT_ND_RA_OPTS, ovnact_put_opts) > > /* enum ovnact_type, with a member OVNACT_<ENUM> for each action. */ > enum OVS_PACKED_ENUM ovnact_type { > @@ -418,6 +419,14 @@ enum action_opcode { > * - A variable length string containing the name. > */ > ACTION_OPCODE_LOG, > + > + /* "result = put_nd_ra_opts(option, ...)". > + * Arguments follow the action_header, in this format: > + * - A 32-bit or 64-bit OXM header designating the result field. > + * - A 32-bit integer specifying a bit offset within the result > field. > + * - Any number of ICMPv6 options. > + */ > + ACTION_OPCODE_PUT_ND_RA_OPTS, > }; > > /* Header. */ > @@ -438,6 +447,9 @@ struct ovnact_parse_params { > /* hmap of 'struct gen_opts_map' to support 'put_dhcpv6_opts' action > */ > const struct hmap *dhcpv6_opts; > > + /* hmap of 'struct gen_opts_map' to support 'put_nd_ra_opts' action */ > + const struct hmap *nd_ra_opts; > + > /* Each OVN flow exists in a logical table within a logical pipeline. > * These parameters express this context for a set of OVN actions > being > * parsed: > diff --git a/ovn/controller/lflow.c b/ovn/controller/lflow.c > index 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 43e3cba23..3a1348937 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 6f16d267a..8d5863c0a 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" > @@ -1438,7 +1439,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. */ > @@ -1771,6 +1772,193 @@ static void > ovnact_dns_lookup_free(struct ovnact_dns_lookup *dl OVS_UNUSED) > { > } > + > +/* Parses the "put_nd_ra_opts" action. > + * The caller has already consumed "<dst> =", so this just parses the > rest. */ > +static void > +parse_put_nd_ra_opts(struct action_context *ctx, const struct expr_field > *dst, > + struct ovnact_put_opts *po) > +{ > + parse_put_opts(ctx, dst, po, ctx->pp->nd_ra_opts, "IPv6 ND RA"); > + > + if (ctx->lexer->error) { > + return; > + } > + > + bool addr_mode_stateful = false; > + bool prefix_set = false; > + bool slla_present = false; > + /* Let's validate the options. */ > + for (struct ovnact_gen_option *o = po->options; > + o < &po->options[po->n_options]; o++) { > + const union expr_constant *c = o->value.values; > + if (o->value.n_values > 1) { > + lexer_error(ctx->lexer, "parse_put_nd_ra_opts -Invalid value > for" > + " the option %s.", o->option->name); > + return; > + } > + > + switch (o->option->code) { > + case ND_RA_FLAG_ADDR_MODE: > + if (!c->string || (strcmp(c->string, "slaac") && > + strcmp(c->string, "dhcpv6_stateful") && > + strcmp(c->string, "dhcpv6_stateless"))) { > + lexer_error(ctx->lexer, "parse_put_nd_ra_opts -Invalid > value " > + "for the option %s.", o->option->name); > + return; > + } > + > + if (!strcmp(c->string, "dhcpv6_stateful")) { > + addr_mode_stateful = true; > + } > + break; > + > + case ND_OPT_SOURCE_LINKADDR: > + if (c->format != LEX_F_ETHERNET) { > + lexer_error(ctx->lexer, "parse_put_nd_ra_opts -Invalid > value " > + "for the option %s.", o->option->name); > + } > + slla_present = true; > + break; > + > + case ND_OPT_PREFIX_INFORMATION: > + if (c->format != LEX_F_IPV6 || !c->masked) { > + lexer_error(ctx->lexer, "parse_put_nd_ra_opts -Invalid > value " > + "for the option %s.", o->option->name); > + } > + prefix_set = true; > + break; > + > + case ND_OPT_MTU: > + if (c->format != LEX_F_DECIMAL) { > + lexer_error(ctx->lexer, "parse_put_nd_ra_opts -Invalid > value " > + "for the option %s.", o->option->name); > + } > + break; > + } > + } > + > + if (ctx->lexer->error) { > + return; > + } > + > + if (!slla_present) { > + lexer_error(ctx->lexer, "parse_put_nd_ra_opts - slla option not" > + " present."); > + return; > + } > + > + if (addr_mode_stateful && prefix_set) { > + lexer_error(ctx->lexer, "parse_put_nd_ra_opts - prefix option > can't be" > + " set when address mode is dhcpv6_stateful."); > + return; > + } > + > + if (!addr_mode_stateful && !prefix_set) { > + lexer_error(ctx->lexer, "parse_put_nd_ra_opts - prefix option > needs " > + "to be set when address mode is > slaac/dhcpv6_stateless."); > + return; > + } > + > + add_prerequisite(ctx, "ip6"); > +} > + > +static void > +format_PUT_ND_RA_OPTS(const struct ovnact_put_opts *po, > + struct ds *s) > +{ > + format_put_opts("put_nd_ra_opts", po, s); > +} > + > +static void > +encode_put_nd_ra_option(const struct ovnact_gen_option *o, > + struct ofpbuf *ofpacts, struct 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) > @@ -1910,6 +2098,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 > diff --git a/tests/ovn.at b/tests/ovn.at > index 6c38b973f..e56dc6232 100644 > --- a/tests/ovn.at > +++ b/tests/ovn.at > @@ -1066,6 +1066,37 @@ reg1[0] = dns_lookup(); > reg1[0] = dns_lookup("foo"); > dns_lookup doesn't take any parameters > > +# put_nd_ra_opts > +reg1[0] = put_nd_ra_opts(addr_mode = "slaac", mtu = 1500, prefix = > aef0::/64, slla = ae:01:02:03:04:05); > + encodes as > controller(userdata=00.00.00.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) > + has prereqs ip6 > +reg1[0] = put_nd_ra_opts(addr_mode = "dhcpv6_stateful", slla = > ae:01:02:03:04:10, mtu = 1450); > + encodes as > 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) > + has prereqs ip6 > +reg1[0] = put_nd_ra_opts(addr_mode = "dhcpv6_stateless", slla = > ae:01:02:03:04:06, prefix = aef0::/64); > + encodes as > controller(userdata=00.00.00.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) > + has prereqs ip6 > +reg1[0] = put_nd_ra_opts(addr_mode = "slaac", mtu = 1500, prefix = > aef0::/64); > + parse_put_nd_ra_opts - slla option not present. > +reg1[0] = put_nd_ra_opts(addr_mode = "dhcpv6_stateful", mtu = 1450, > prefix = aef0::/64, prefix = bef0::/64, slla = ae:01:02:03:04:10); > + parse_put_nd_ra_opts - prefix option can't be set when address mode > is dhcpv6_stateful. > +reg1[0] = put_nd_ra_opts(addr_mode = "dhcpv6_stateful", mtu = 1450, > prefix = aef0::/64, prefix = bef0::/64, slla = ae:01:02:03:04:10); > + parse_put_nd_ra_opts - prefix option can't be set when address mode > is dhcpv6_stateful. > +reg1[0] = put_nd_ra_opts(addr_mode = "slaac", slla = ae:01:02:03:04:10); > + parse_put_nd_ra_opts - prefix option needs to be set when address > mode is slaac/dhcpv6_stateless. > +reg1[0] = put_nd_ra_opts(addr_mode = "dhcpv6_stateless", slla = > ae:01:02:03:04:10); > + parse_put_nd_ra_opts - prefix option needs to be set when address > mode is slaac/dhcpv6_stateless. > +reg1[0] = put_nd_ra_opts(addr_mode = dhcpv6_stateless, prefix = > aef0::/64, slla = ae:01:02:03:04:10); > + Syntax error at `dhcpv6_stateless' expecting constant. > +reg1[0] = put_nd_ra_opts(addr_mode = "slaac", mtu = 1500, prefix = > aef0::, slla = ae:01:02:03:04:10); > + parse_put_nd_ra_opts -Invalid value for the option prefix. > +reg1[0] = put_nd_ra_opts(addr_mode = "foo", mtu = 1500, slla = > ae:01:02:03:04:10); > + parse_put_nd_ra_opts -Invalid value for the option addr_mode. > +reg1[0] = put_nd_ra_opts(addr_mode = "slaac", mtu = "1500", slla = > ae:01:02:03:04:10); > + IPv6 ND RA option mtu requires numeric value. > +reg1[0] = put_nd_ra_opts(addr_mode = "slaac", mtu = 10.0.0.4, slla = > ae:01:02:03:04:10); > + parse_put_nd_ra_opts -Invalid value for the option mtu. > + > # Contradictionary prerequisites (allowed but not useful): > ip4.src = ip6.src[0..31]; > encodes as move:NXM_NX_IPV6_SRC[0..31]->NXM_OF_IP_SRC[] > diff --git a/tests/test-ovn.c b/tests/test-ovn.c > index 67221ea50..f9a5085f7 100644 > --- a/tests/test-ovn.c > +++ b/tests/test-ovn.c > @@ -155,7 +155,8 @@ create_symtab(struct shash *symtab) > } > > static void > -create_dhcp_opts(struct hmap *dhcp_opts, struct hmap *dhcpv6_opts) > +create_gen_opts(struct hmap *dhcp_opts, struct hmap *dhcpv6_opts, > + struct hmap *nd_ra_opts) > { > hmap_init(dhcp_opts); > dhcp_opt_add(dhcp_opts, "offerip", 0, "ipv4"); > @@ -187,6 +188,10 @@ create_dhcp_opts(struct hmap *dhcp_opts, struct hmap > *dhcpv6_opts) > dhcp_opt_add(dhcpv6_opts, "ia_addr", 5, "ipv6"); > dhcp_opt_add(dhcpv6_opts, "dns_server", 23, "ipv6"); > dhcp_opt_add(dhcpv6_opts, "domain_search", 24, "str"); > + > + /* IPv6 ND RA options. */ > + hmap_init(nd_ra_opts); > + nd_ra_opts_init(nd_ra_opts); > } > > static void > @@ -1193,12 +1198,13 @@ test_parse_actions(struct ovs_cmdl_context *ctx > OVS_UNUSED) > struct shash symtab; > struct hmap dhcp_opts; > struct hmap dhcpv6_opts; > + struct hmap nd_ra_opts; > struct simap ports; > struct ds input; > bool ok = true; > > create_symtab(&symtab); > - create_dhcp_opts(&dhcp_opts, &dhcpv6_opts); > + create_gen_opts(&dhcp_opts, &dhcpv6_opts, &nd_ra_opts); > > /* Initialize group ids. */ > struct group_table group_table; > @@ -1226,6 +1232,7 @@ test_parse_actions(struct ovs_cmdl_context *ctx > OVS_UNUSED) > .symtab = &symtab, > .dhcp_opts = &dhcp_opts, > .dhcpv6_opts = &dhcpv6_opts, > + .nd_ra_opts = &nd_ra_opts, > .n_tables = 24, > .cur_ltable = 10, > }; > @@ -1310,7 +1317,7 @@ test_parse_actions(struct ovs_cmdl_context *ctx > OVS_UNUSED) > shash_destroy(&symtab); > dhcp_opts_destroy(&dhcp_opts); > dhcp_opts_destroy(&dhcpv6_opts); > - > + nd_ra_opts_destroy(&nd_ra_opts); > exit(ok ? EXIT_SUCCESS : EXIT_FAILURE); > } > > -- > 2.13.5 > > _______________________________________________ > dev mailing list > dev@openvswitch.org > https://mail.openvswitch.org/mailman/listinfo/ovs-dev >
diff --git a/include/ovn/actions.h b/include/ovn/actions.h index d13a3747b..15cee478d 100644 --- a/include/ovn/actions.h +++ b/include/ovn/actions.h @@ -72,7 +72,8 @@ struct simap; OVNACT(PUT_DHCPV6_OPTS, ovnact_put_opts) \ OVNACT(SET_QUEUE, ovnact_set_queue) \ OVNACT(DNS_LOOKUP, ovnact_dns_lookup) \ - OVNACT(LOG, ovnact_log) + OVNACT(LOG, ovnact_log) \ + OVNACT(PUT_ND_RA_OPTS, ovnact_put_opts) /* enum ovnact_type, with a member OVNACT_<ENUM> for each action. */ enum OVS_PACKED_ENUM ovnact_type { @@ -418,6 +419,14 @@ enum action_opcode { * - A variable length string containing the name. */ ACTION_OPCODE_LOG, + + /* "result = put_nd_ra_opts(option, ...)". + * Arguments follow the action_header, in this format: + * - A 32-bit or 64-bit OXM header designating the result field. + * - A 32-bit integer specifying a bit offset within the result field. + * - Any number of ICMPv6 options. + */ + ACTION_OPCODE_PUT_ND_RA_OPTS, }; /* Header. */ @@ -438,6 +447,9 @@ struct ovnact_parse_params { /* hmap of 'struct gen_opts_map' to support 'put_dhcpv6_opts' action */ const struct hmap *dhcpv6_opts; + /* hmap of 'struct gen_opts_map' to support 'put_nd_ra_opts' action */ + const struct hmap *nd_ra_opts; + /* Each OVN flow exists in a logical table within a logical pipeline. * These parameters express this context for a set of OVN actions being * parsed: diff --git a/ovn/controller/lflow.c b/ovn/controller/lflow.c index 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 43e3cba23..3a1348937 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 6f16d267a..8d5863c0a 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" @@ -1438,7 +1439,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. */ @@ -1771,6 +1772,193 @@ static void ovnact_dns_lookup_free(struct ovnact_dns_lookup *dl OVS_UNUSED) { } + +/* Parses the "put_nd_ra_opts" action. + * The caller has already consumed "<dst> =", so this just parses the rest. */ +static void +parse_put_nd_ra_opts(struct action_context *ctx, const struct expr_field *dst, + struct ovnact_put_opts *po) +{ + parse_put_opts(ctx, dst, po, ctx->pp->nd_ra_opts, "IPv6 ND RA"); + + if (ctx->lexer->error) { + return; + } + + bool addr_mode_stateful = false; + bool prefix_set = false; + bool slla_present = false; + /* Let's validate the options. */ + for (struct ovnact_gen_option *o = po->options; + o < &po->options[po->n_options]; o++) { + const union expr_constant *c = o->value.values; + if (o->value.n_values > 1) { + lexer_error(ctx->lexer, "parse_put_nd_ra_opts -Invalid value for" + " the option %s.", o->option->name); + return; + } + + switch (o->option->code) { + case ND_RA_FLAG_ADDR_MODE: + if (!c->string || (strcmp(c->string, "slaac") && + strcmp(c->string, "dhcpv6_stateful") && + strcmp(c->string, "dhcpv6_stateless"))) { + lexer_error(ctx->lexer, "parse_put_nd_ra_opts -Invalid value " + "for the option %s.", o->option->name); + return; + } + + if (!strcmp(c->string, "dhcpv6_stateful")) { + addr_mode_stateful = true; + } + break; + + case ND_OPT_SOURCE_LINKADDR: + if (c->format != LEX_F_ETHERNET) { + lexer_error(ctx->lexer, "parse_put_nd_ra_opts -Invalid value " + "for the option %s.", o->option->name); + } + slla_present = true; + break; + + case ND_OPT_PREFIX_INFORMATION: + if (c->format != LEX_F_IPV6 || !c->masked) { + lexer_error(ctx->lexer, "parse_put_nd_ra_opts -Invalid value " + "for the option %s.", o->option->name); + } + prefix_set = true; + break; + + case ND_OPT_MTU: + if (c->format != LEX_F_DECIMAL) { + lexer_error(ctx->lexer, "parse_put_nd_ra_opts -Invalid value " + "for the option %s.", o->option->name); + } + break; + } + } + + if (ctx->lexer->error) { + return; + } + + if (!slla_present) { + lexer_error(ctx->lexer, "parse_put_nd_ra_opts - slla option not" + " present."); + return; + } + + if (addr_mode_stateful && prefix_set) { + lexer_error(ctx->lexer, "parse_put_nd_ra_opts - prefix option can't be" + " set when address mode is dhcpv6_stateful."); + return; + } + + if (!addr_mode_stateful && !prefix_set) { + lexer_error(ctx->lexer, "parse_put_nd_ra_opts - prefix option needs " + "to be set when address mode is slaac/dhcpv6_stateless."); + return; + } + + add_prerequisite(ctx, "ip6"); +} + +static void +format_PUT_ND_RA_OPTS(const struct ovnact_put_opts *po, + struct ds *s) +{ + format_put_opts("put_nd_ra_opts", po, s); +} + +static void +encode_put_nd_ra_option(const struct ovnact_gen_option *o, + struct ofpbuf *ofpacts, struct 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) @@ -1910,6 +2098,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 diff --git a/tests/ovn.at b/tests/ovn.at index 6c38b973f..e56dc6232 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -1066,6 +1066,37 @@ reg1[0] = dns_lookup(); reg1[0] = dns_lookup("foo"); dns_lookup doesn't take any parameters +# put_nd_ra_opts +reg1[0] = put_nd_ra_opts(addr_mode = "slaac", mtu = 1500, prefix = aef0::/64, slla = ae:01:02:03:04:05); + encodes as controller(userdata=00.00.00.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) + has prereqs ip6 +reg1[0] = put_nd_ra_opts(addr_mode = "dhcpv6_stateful", slla = ae:01:02:03:04:10, mtu = 1450); + encodes as 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) + has prereqs ip6 +reg1[0] = put_nd_ra_opts(addr_mode = "dhcpv6_stateless", slla = ae:01:02:03:04:06, prefix = aef0::/64); + encodes as controller(userdata=00.00.00.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) + has prereqs ip6 +reg1[0] = put_nd_ra_opts(addr_mode = "slaac", mtu = 1500, prefix = aef0::/64); + parse_put_nd_ra_opts - slla option not present. +reg1[0] = put_nd_ra_opts(addr_mode = "dhcpv6_stateful", mtu = 1450, prefix = aef0::/64, prefix = bef0::/64, slla = ae:01:02:03:04:10); + parse_put_nd_ra_opts - prefix option can't be set when address mode is dhcpv6_stateful. +reg1[0] = put_nd_ra_opts(addr_mode = "dhcpv6_stateful", mtu = 1450, prefix = aef0::/64, prefix = bef0::/64, slla = ae:01:02:03:04:10); + parse_put_nd_ra_opts - prefix option can't be set when address mode is dhcpv6_stateful. +reg1[0] = put_nd_ra_opts(addr_mode = "slaac", slla = ae:01:02:03:04:10); + parse_put_nd_ra_opts - prefix option needs to be set when address mode is slaac/dhcpv6_stateless. +reg1[0] = put_nd_ra_opts(addr_mode = "dhcpv6_stateless", slla = ae:01:02:03:04:10); + parse_put_nd_ra_opts - prefix option needs to be set when address mode is slaac/dhcpv6_stateless. +reg1[0] = put_nd_ra_opts(addr_mode = dhcpv6_stateless, prefix = aef0::/64, slla = ae:01:02:03:04:10); + Syntax error at `dhcpv6_stateless' expecting constant. +reg1[0] = put_nd_ra_opts(addr_mode = "slaac", mtu = 1500, prefix = aef0::, slla = ae:01:02:03:04:10); + parse_put_nd_ra_opts -Invalid value for the option prefix. +reg1[0] = put_nd_ra_opts(addr_mode = "foo", mtu = 1500, slla = ae:01:02:03:04:10); + parse_put_nd_ra_opts -Invalid value for the option addr_mode. +reg1[0] = put_nd_ra_opts(addr_mode = "slaac", mtu = "1500", slla = ae:01:02:03:04:10); + IPv6 ND RA option mtu requires numeric value. +reg1[0] = put_nd_ra_opts(addr_mode = "slaac", mtu = 10.0.0.4, slla = ae:01:02:03:04:10); + parse_put_nd_ra_opts -Invalid value for the option mtu. + # Contradictionary prerequisites (allowed but not useful): ip4.src = ip6.src[0..31]; encodes as move:NXM_NX_IPV6_SRC[0..31]->NXM_OF_IP_SRC[] diff --git a/tests/test-ovn.c b/tests/test-ovn.c index 67221ea50..f9a5085f7 100644 --- a/tests/test-ovn.c +++ b/tests/test-ovn.c @@ -155,7 +155,8 @@ create_symtab(struct shash *symtab) } static void -create_dhcp_opts(struct hmap *dhcp_opts, struct hmap *dhcpv6_opts) +create_gen_opts(struct hmap *dhcp_opts, struct hmap *dhcpv6_opts, + struct hmap *nd_ra_opts) { hmap_init(dhcp_opts); dhcp_opt_add(dhcp_opts, "offerip", 0, "ipv4"); @@ -187,6 +188,10 @@ create_dhcp_opts(struct hmap *dhcp_opts, struct hmap *dhcpv6_opts) dhcp_opt_add(dhcpv6_opts, "ia_addr", 5, "ipv6"); dhcp_opt_add(dhcpv6_opts, "dns_server", 23, "ipv6"); dhcp_opt_add(dhcpv6_opts, "domain_search", 24, "str"); + + /* IPv6 ND RA options. */ + hmap_init(nd_ra_opts); + nd_ra_opts_init(nd_ra_opts); } static void @@ -1193,12 +1198,13 @@ test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED) struct shash symtab; struct hmap dhcp_opts; struct hmap dhcpv6_opts; + struct hmap nd_ra_opts; struct simap ports; struct ds input; bool ok = true; create_symtab(&symtab); - create_dhcp_opts(&dhcp_opts, &dhcpv6_opts); + create_gen_opts(&dhcp_opts, &dhcpv6_opts, &nd_ra_opts); /* Initialize group ids. */ struct group_table group_table; @@ -1226,6 +1232,7 @@ test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED) .symtab = &symtab, .dhcp_opts = &dhcp_opts, .dhcpv6_opts = &dhcpv6_opts, + .nd_ra_opts = &nd_ra_opts, .n_tables = 24, .cur_ltable = 10, }; @@ -1310,7 +1317,7 @@ test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED) shash_destroy(&symtab); dhcp_opts_destroy(&dhcp_opts); dhcp_opts_destroy(&dhcpv6_opts); - + nd_ra_opts_destroy(&nd_ra_opts); exit(ok ? EXIT_SUCCESS : EXIT_FAILURE); }