On Mon, Aug 1, 2016 at 7:19 PM, Zong Kai LI <zealo...@gmail.com> wrote:
> This patch tries to implement Router Advertisement (RA) responder for SLAAC > on ovn-controller side. > > It parses lflows which have: > - match: inport == LRP_NAME && ip6.dst == ff02::2 && nd_rs > (nd_rs: icmp6.type == 133 && icmp6.code == 0 && ttl == 255) > - action: nd_ra{slaac(prefix,...,MTU, mac_address); > outport = inport; flags.loopback = 1; output;}; > (nd_ra is a new action which stands for RA responder; > slaac is a new action which has the following parameters to tell > ovn-controller to compose RA packet with SLAAC flags, with these > parameters: > - IPv6 prefix(es): such as fd80:a0f9:a012::/64. > - MTU: logical switch MTU, such as 1450. > - MAC address: router port mac address, such as fa:16:3e:12:34:56. > Beside the parameters list above, nd_ra action will use eth.src and > ip6.src > from packet being processed as RA packet eth.src and ip6.src. > After a RA packet is composed, the left nested actions will make RA packet > transmitted back to the inport, where Router Solicitation (RS) packet > comes. > > For inner action 'slaac', as a prototype, it doesn't try to expose all RA > relevant flags and fields. As most flags are constant for in SLAAC > scenario, > so it only exposed few fields user may care, such as prefixes, MTU and LLA. > It uses an ordered parameters list to hold all those values for now, but in > future, when we try to implement more feature on RA, the unexposed ones > should > be exposed, and key-pair style parameter list should be used. > > For outer action 'nd_ra', as a prototype, it's just a RA responder now, > not a > real implement for RA, like it wont send periodic RA broadcast. This will > be > harmful when routing relevant stuff changes, like user changes lrp mac, > disattach a switch from a router. However, a periodic version seems not so > necessary, but a broadcast should be sent when things change. > > For lflow, this patch add prerequisites for Router Solicitation (RS) and RA > message. But it doesn't fix details of nd.target, nd.sll and nd.tll for RS > and RA, since these fields are not supported to be modified via ovs native > actions for now. > --- > include/ovn/actions.h | 15 +++++ > include/ovn/expr.h | 11 ++++ > ovn/controller/lflow.c | 6 +- > ovn/controller/pinctrl.c | 168 ++++++++++++++++++++++++++++++ > +++++++++++++++++ > ovn/lib/actions.c | 69 +++++++++++++++++++ > ovn/lib/expr.c | 11 +--- > ovn/ovn-sb.xml | 32 ++++++++- > tests/ovn.at | 6 +- > tests/test-ovn.c | 6 +- > 9 files changed, 310 insertions(+), 14 deletions(-) > > diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c > index bd685fe..fa1e7d7 100644 > --- a/ovn/controller/pinctrl.c > +++ b/ovn/controller/pinctrl.c > @@ -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); Other router advertisement sender implementations include the source link-layer address option before prefix options, any reason for this ordering? I'm not aware of any client incompatibilities with sending this in this order, but could confuse some less tolerant clients. > + 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); > +} > diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml > index 13c9526..66a9009 100644 > --- a/ovn/ovn-sb.xml > +++ b/ovn/ovn-sb.xml > @@ -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 > MTU? > + 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 > > Is there a reason for specifying the prefixes as the first arguments to slaac()? Usually variable number arguments are passed last after any fixed arguments, I would recommend `slacc(sll, mtu, prefix1, ...)` instead. Other packet in handlers like pinctrl_handle_arp() only accept the flow_metadata rather than the entire packet, any reason for this change since only the metadata is used? Finally tests/test-ovn.c and tests/ovn.at didn't apply cleanly. Thanks, Dustin Lundquist _______________________________________________ dev mailing list dev@openvswitch.org http://openvswitch.org/mailman/listinfo/dev