"dev" <dev-boun...@openvswitch.org> wrote on 06/16/2016 03:53:39 AM:
> From: Zong Kai LI <zealo...@gmail.com> > To: dev@openvswitch.org > Cc: Zong Kai LI <zealo...@gmail.com> > Date: 06/16/2016 03:54 AM > Subject: [ovs-dev] [PATCH] [PATCH, v5] ovn: Add 'na' action and lflow for ND > Sent by: "dev" <dev-boun...@openvswitch.org> > > This patch tries to support ND versus ARP for OVN. > > It adds a new OVN action 'na' in ovn-controller side, and modify lflows > for 'na' action and relevant packets in ovn-northd. > > First, for ovn-northd, it will generate lflows per each lport with its > IPv6 addresses and mac addresss, with 'na' action, such as: > match=(icmp6 && icmp6.type == 135 && > (nd.target == fd81:ce49:a948:0:f816:3eff:fe46:8a42 || > nd.target == fd81:ce49:b123:0:f816:3eff:fe46:8a42)), > action=(na { eth.src = fa:16:3e:46:8a:42; nd.tll = fa:16:3e:46:8a:42; > outport = inport; > inport = ""; /* Allow sending out inport. */ output; };) > > and new lflows will be set in tabel ls_in_arp_nd_rsp, which is renamed > from previous ls_in_arp_rsp. > > Later, for ovn-controller, when it received a ND packet, it frames a > template NA packet for reply. The NA packet will be initialized based on > ND packet, such as NA packet will use: > - ND packet eth.src as eth.dst, > - ND packet eth.dst as eth.src, > - ND packet ip6.src as ip6.dst, > - ND packet nd.target as ip6.src, > - ND packet eth.dst as nd.tll. > > Finally, nested actions in 'na' action will update necessary fileds > for NA packet, such as: > - eth.src, nd.tll > - inport, outport > > Since patch port for IPv6 router interface is not ready yet, this > patch will only try to deal with ND from VM. This patch will set > RSO flags to 011 for NA packets. > > This patch also modified current ACL lflows for ND, not to do conntrack > on ND and NA packets in following tables: > - S_SWITCH_IN_PRE_ACL > - S_SWITCH_OUT_PRE_ACL > - S_SWITCH_IN_ACL > - S_SWITCH_OUT_ACL > > Signed-off-by: Zong Kai LI <zealo...@gmail.com> > --- > lib/packets.c | 29 ++++++++++ > lib/packets.h | 4 ++ > ovn/controller/pinctrl.c | 136 ++++++++++++++++++++++++++++++++++++ > ++--------- > ovn/lib/actions.c | 43 +++++++++++++++ > ovn/lib/actions.h | 6 +++ > ovn/northd/ovn-northd.c | 57 +++++++++++++++++--- > ovn/ovn-sb.xml | 39 ++++++++++++++ > tests/ovn.at | 101 +++++++++++++++++++++++++++++++++++ > tutorial/OVN-Tutorial.md | 6 +-- > 9 files changed, 385 insertions(+), 36 deletions(-) > > diff --git a/lib/packets.c b/lib/packets.c > index 43b5a70..617ff66 100644 > --- a/lib/packets.c > +++ b/lib/packets.c > @@ -1355,6 +1355,35 @@ compose_nd(struct dp_packet *b, const struct > eth_addr eth_src, > ND_MSG_LEN + > ND_OPT_LEN)); > } > > +void > +compose_na(struct dp_packet *b, > + const struct eth_addr eth_src, const struct eth_addr eth_dst, > + const ovs_be32 ipv6_src[4], const ovs_be32 ipv6_dst[4], > + ovs_be16 rco_flags) > +{ > + struct ovs_nd_msg *na; > + struct ovs_nd_opt *nd_opt; > + uint32_t icmp_csum; > + > + eth_compose(b, eth_dst, eth_src, ETH_TYPE_IPV6, IPV6_HEADER_LEN); > + na = compose_ipv6(b, IPPROTO_ICMPV6, ipv6_src, ipv6_dst, 0, 0, 255, > + ND_MSG_LEN + ND_OPT_LEN); > + > + na->icmph.icmp6_type = ND_NEIGHBOR_ADVERT; > + na->icmph.icmp6_code = 0; > + na->rco_flags.hi = rco_flags; > + > + nd_opt = &na->options[0]; > + nd_opt->nd_opt_type = ND_OPT_TARGET_LINKADDR; > + nd_opt->nd_opt_len = 1; > + > + packet_set_nd(b, ipv6_src, eth_addr_zero, eth_src); > + na->icmph.icmp6_cksum = 0; > + icmp_csum = packet_csum_pseudoheader6(dp_packet_l3(b)); > + na->icmph.icmp6_cksum = csum_finish(csum_continue(icmp_csum, na, > + ND_MSG_LEN + > ND_OPT_LEN)); > +} > + > uint32_t > packet_csum_pseudoheader(const struct ip_header *ip) > { > diff --git a/lib/packets.h b/lib/packets.h > index 5945940..55e2500 100644 > --- a/lib/packets.h > +++ b/lib/packets.h > @@ -1069,6 +1069,10 @@ void compose_arp(struct dp_packet *, uint16_t arp_op, > ovs_be32 arp_spa, ovs_be32 arp_tpa); > void compose_nd(struct dp_packet *, const struct eth_addr eth_src, > struct in6_addr *, struct in6_addr *); > +void compose_na(struct dp_packet *, > + const struct eth_addr eth_src, const struct eth_addr eth_dst, > + const ovs_be32 ipv6_src[4], const ovs_be32 ipv6_dst[4], > + ovs_be16 rco_flags); > uint32_t packet_csum_pseudoheader(const struct ip_header *); > void IP_ECN_set_ce(struct dp_packet *pkt, bool is_ipv6); > > diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c > index 116397e..26aefae 100644 > --- a/ovn/controller/pinctrl.c > +++ b/ovn/controller/pinctrl.c > @@ -23,6 +23,8 @@ > #include "flow.h" > #include "lport.h" > #include "ovn-controller.h" > +#include "lib/byte-order.h" > +#include "lib/packets.h" > #include "lib/sset.h" > #include "openvswitch/ofp-actions.h" > #include "openvswitch/ofp-msgs.h" > @@ -64,6 +66,11 @@ static void send_garp_run(const struct ovsrec_bridge *, > const char *chassis_id, > const struct lport_index *lports, > struct hmap *local_datapaths); > +static void pinctrl_handle_na(const struct flow *ip_flow, > + const struct match *md, > + struct ofpbuf *userdata); > +static void reload_metadata(struct ofpbuf *ofpacts, > + const struct match *md); > > COVERAGE_DEFINE(pinctrl_drop_put_arp); > > @@ -153,31 +160,7 @@ pinctrl_handle_arp(const struct flow *ip_flow, > const struct match *md, > struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub); > enum ofp_version version = rconn_get_version(swconn); > > - enum mf_field_id md_fields[] = { > -#if FLOW_N_REGS == 8 > - MFF_REG0, > - MFF_REG1, > - MFF_REG2, > - MFF_REG3, > - MFF_REG4, > - MFF_REG5, > - MFF_REG6, > - MFF_REG7, > -#else > -#error > -#endif > - MFF_METADATA, > - }; > - for (size_t i = 0; i < ARRAY_SIZE(md_fields); i++) { > - const struct mf_field *field = mf_from_id(md_fields[i]); > - if (!mf_is_all_wild(field, &md->wc)) { > - struct ofpact_set_field *sf = ofpact_put_SET_FIELD (&ofpacts); > - sf->field = field; > - sf->flow_has_vlan = false; > - mf_get_value(field, &md->flow, &sf->value); > - memset(&sf->mask, 0xff, field->n_bytes); > - } > - } > + reload_metadata(&ofpacts, md); > enum ofperr error = ofpacts_pull_openflow_actions(userdata, > userdata->size, > version, &ofpacts); > if (error) { > @@ -242,6 +225,10 @@ process_packet_in(const struct ofp_header *msg) > pinctrl_handle_put_arp(&pin.flow_metadata.flow, &headers); > break; > > + case ACTION_OPCODE_NA: > + pinctrl_handle_na(&headers, &pin.flow_metadata, &userdata); > + break; > + > default: > VLOG_WARN_RL(&rl, "unrecognized packet-in opcode %"PRIu32, > ntohl(ah->opcode)); > @@ -734,3 +721,102 @@ send_garp_run(const struct ovsrec_bridge > *br_int, const char *chassis_id, > sset_destroy(&localnet_vifs); > simap_destroy(&localnet_ofports); > } > + > +static void > +reload_metadata(struct ofpbuf *ofpacts, const struct match *md) > +{ > + enum mf_field_id md_fields[] = { > +#if FLOW_N_REGS == 8 > + MFF_REG0, > + MFF_REG1, > + MFF_REG2, > + MFF_REG3, > + MFF_REG4, > + MFF_REG5, > + MFF_REG6, > + MFF_REG7, > +#else > +#error > +#endif > + MFF_METADATA, > + }; > + for (size_t i = 0; i < ARRAY_SIZE(md_fields); i++) { > + const struct mf_field *field = mf_from_id(md_fields[i]); > + if (!mf_is_all_wild(field, &md->wc)) { > + struct ofpact_set_field *sf = ofpact_put_SET_FIELD(ofpacts); > + sf->field = field; > + sf->flow_has_vlan = false; > + mf_get_value(field, &md->flow, &sf->value); > + memset(&sf->mask, 0xff, field->n_bytes); > + } > + } > +} > + > +static void > +pinctrl_handle_na(const struct flow *ip_flow, > + const struct match *md, > + struct ofpbuf *userdata) > +{ > + /* This action only works for IPv6 packets, and the switch > should only send > + * us IPv6 packets this way, but check here just to be sure. */ > + if (ip_flow->dl_type != htons(ETH_TYPE_IPV6)) { > + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); > + VLOG_WARN_RL(&rl, "NA action on non-IPv6 packet (Ethertype > %"PRIx16")", > + ntohs(ip_flow->dl_type)); > + return; > + } > + > + enum ofp_version version = rconn_get_version(swconn); > + enum ofputil_protocol proto = ofputil_protocol_from_ofp_version (version); > + > + uint64_t packet_stub[128 / 8]; > + struct dp_packet packet; > + dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub); > + ovs_be32 ipv6_src[4], ipv6_dst[4]; > + for (int i = 0; i < 4; i++) { > + ipv6_dst[i] = BYTES_TO_BE32(ip_flow->ipv6_src.s6_addr[4*i], > + ip_flow->ipv6_src.s6_addr[4*i+1], > + ip_flow->ipv6_src.s6_addr[4*i+2], > + ip_flow->ipv6_src.s6_addr[4*i+3]); > + ipv6_src[i] = BYTES_TO_BE32(ip_flow->nd_target.s6_addr[4*i], > + ip_flow->nd_target.s6_addr[4*i+1], > + ip_flow->nd_target.s6_addr[4*i+2], > + ip_flow->nd_target.s6_addr[4*i+3]); > + } > + /* Frame the NA packet with RSO=011. > + * Only to compose a template NA packet here, it will be nested actions > + * responsibility to fill correct eth.src and nd.tll into NA packet. */ > + compose_na(&packet, > + ip_flow->dl_dst, ip_flow->dl_src, > + ipv6_src, ipv6_dst, > + htons(0x6000)); > + > + /* Reload previous packet metadata. */ > + uint64_t ofpacts_stub[4096 / 8]; > + struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub); > + reload_metadata(&ofpacts, md); > + > + enum ofperr error = ofpacts_pull_openflow_actions(userdata, > userdata->size, > + version, &ofpacts); > + if (error) { > + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); > + VLOG_WARN_RL(&rl, "failed to parse nested actions for 'na' > action(%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/lib/actions.c b/ovn/lib/actions.c > index 5f0bf19..31a8678 100644 > --- a/ovn/lib/actions.c > +++ b/ovn/lib/actions.c > @@ -442,6 +442,47 @@ emit_ct(struct action_context *ctx, bool > recirc_next, bool commit) > add_prerequisite(ctx, "ip"); > } > > +static void > +parse_na_action(struct action_context *ctx) > +{ > + if (!lexer_match(ctx->lexer, LEX_T_LCURLY)) { > + action_syntax_error(ctx, "expecting `{'"); > + return; > + } > + > + struct ofpbuf *outer_ofpacts = ctx->ofpacts; > + uint64_t inner_ofpacts_stub[1024 / 8]; > + struct ofpbuf inner_ofpacts = > OFPBUF_STUB_INITIALIZER(inner_ofpacts_stub); > + ctx->ofpacts = &inner_ofpacts; > + > + /* Save prerequisites. (XXX What is the right treatment for prereqs?) */ > + struct expr *outer_prereqs = ctx->prereqs; > + ctx->prereqs = NULL; > + > + /* Parse inner actions. */ > + while (!lexer_match(ctx->lexer, LEX_T_RCURLY)) { > + if (!parse_action(ctx)) { > + break; > + } > + } > + > + ctx->ofpacts = outer_ofpacts; > + > + /* controller. */ Nit: not sure what this comment is meant to convey, and it should start with a capital letter. > + size_t oc_offset = start_controller_op(ctx->ofpacts, ACTION_OPCODE_NA); > + ofpacts_put_openflow_actions(inner_ofpacts.data, inner_ofpacts.size, > + ctx->ofpacts, OFP13_VERSION); > + finish_controller_op(ctx->ofpacts, oc_offset); > + > + /* Restore prerequisites. */ > + expr_destroy(ctx->prereqs); > + ctx->prereqs = outer_prereqs; > + add_prerequisite(ctx, "nd"); > + > + /* Free memory. */ > + ofpbuf_uninit(&inner_ofpacts); > +} > + > static bool > parse_action(struct action_context *ctx) > { > @@ -475,6 +516,8 @@ parse_action(struct action_context *ctx) > parse_get_arp_action(ctx); > } else if (lexer_match_id(ctx->lexer, "put_arp")) { > parse_put_arp_action(ctx); > + } else if (lexer_match_id(ctx->lexer, "na")) { > + parse_na_action(ctx); > } else { > action_syntax_error(ctx, "expecting action"); > } > diff --git a/ovn/lib/actions.h b/ovn/lib/actions.h > index 29af06f..ff964d6 100644 > --- a/ovn/lib/actions.h > +++ b/ovn/lib/actions.h > @@ -44,6 +44,12 @@ enum action_opcode { > * MFF_ETH_SRC = mac > */ > ACTION_OPCODE_PUT_ARP, > + > + /* "na { ...actions... }". > + * > + * The actions, in OpenFlow 1.3 format, follow the action_header. > + */ > + ACTION_OPCODE_NA, > }; > > /* Header. */ > diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c > index d53fca9..20d8c98 100644 > --- a/ovn/northd/ovn-northd.c > +++ b/ovn/northd/ovn-northd.c > @@ -93,7 +93,7 @@ enum ovn_stage { > PIPELINE_STAGE(SWITCH, IN, PORT_SEC_ND, 2, "ls_in_port_sec_nd") \ > PIPELINE_STAGE(SWITCH, IN, PRE_ACL, 3, "ls_in_pre_acl") \ > PIPELINE_STAGE(SWITCH, IN, ACL, 4, "ls_in_acl") \ > - PIPELINE_STAGE(SWITCH, IN, ARP_RSP, 5, "ls_in_arp_rsp") \ > + PIPELINE_STAGE(SWITCH, IN, ARP_ND_RSP, 5, "ls_in_arp_nd_rsp") \ > PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 6, "ls_in_l2_lkup") \ > \ > /* Logical switch egress stages. */ \ > @@ -1383,6 +1383,12 @@ build_acls(struct ovn_datapath *od, struct > hmap *lflows, struct hmap *ports) > ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 100, "ip", > "ct_next;"); > ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 100, "ip", > "ct_next;"); > > + /* Ingress and Egress Pre-ACL Table (Priority 110). > + * > + * Not to do conntrack on ND packets. */ > + ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 110, "nd", "next;"); > + ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 110, "nd", "next;"); > + > /* Ingress and Egress ACL Table (Priority 1). > * > * By default, traffic is allowed. This is partially handled by > @@ -1433,6 +1439,12 @@ build_acls(struct ovn_datapath *od, struct > hmap *lflows, struct hmap *ports) > ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX, > "!ct.est && ct.rel && !ct.new && !ct.inv", > "next;"); > + > + /* Ingress and Egress ACL Table (Priority 65535). > + * > + * Not to do conntrack on ND packets. */ > + ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, 65535, "nd", "next;"); > + ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, 65535, "nd", "next;"); > } > > /* Ingress or Egress ACL Table (Various priorities). */ > @@ -1566,13 +1578,13 @@ build_lswitch_flows(struct hmap *datapaths, > struct hmap *ports, > > if (!strcmp(op->nbs->type, "localnet")) { > char *match = xasprintf("inport == %s", op->json_key); > - ovn_lflow_add(lflows, op->od, S_SWITCH_IN_ARP_RSP, 100, > + ovn_lflow_add(lflows, op->od, S_SWITCH_IN_ARP_ND_RSP, 100, > match, "next;"); > free(match); > } > } > > - /* Ingress table 5: ARP responder, reply for known IPs. > + /* Ingress table 5: ARP/ND responder, reply for known IPs. > * (priority 50). */ > HMAP_FOR_EACH (op, key_node, ports) { > if (!op->nbs) { > @@ -1580,7 +1592,7 @@ build_lswitch_flows(struct hmap *datapaths, > struct hmap *ports, > } > > /* > - * Add ARP reply flows if either the > + * Add ARP/ND reply flows if either the > * - port is up or > * - port type is router > */ > @@ -1591,7 +1603,7 @@ build_lswitch_flows(struct hmap *datapaths, > struct hmap *ports, > for (size_t i = 0; i < op->nbs->n_addresses; i++) { > struct lport_addresses laddrs; > if (!extract_lsp_addresses(op->nbs->addresses[i], &laddrs, > - false)) { > + true)) { > continue; > } > for (size_t j = 0; j < laddrs.n_ipv4_addrs; j++) { > @@ -1612,24 +1624,53 @@ build_lswitch_flows(struct hmap *datapaths, > struct hmap *ports, > ETH_ADDR_ARGS(laddrs.ea), > ETH_ADDR_ARGS(laddrs.ea), > IP_ARGS(laddrs.ipv4_addrs[j].addr)); > - ovn_lflow_add(lflows, op->od, S_SWITCH_IN_ARP_RSP, 50, > + ovn_lflow_add(lflows, op->od, S_SWITCH_IN_ARP_ND_RSP, 50, > match, actions); > free(match); > free(actions); > } > > + if (laddrs.n_ipv6_addrs > 0) { > + char ip6_str[INET6_ADDRSTRLEN + 1]; > + struct ds match = DS_EMPTY_INITIALIZER; > + ds_put_cstr(&match, "icmp6 && icmp6.type == 135 && ("); > + for (size_t j = 0; j < laddrs.n_ipv6_addrs; j++) { > + ipv6_string_mapped(ip6_str, > &(laddrs.ipv6_addrs[j].addr)); > + ds_put_format(&match, "nd.target == %s || ", ip6_str); > + } > + ds_chomp(&match, ' '); > + ds_chomp(&match, '|'); > + ds_chomp(&match, '|'); > + ds_chomp(&match, ' '); > + ds_put_cstr(&match, ")"); > + char *actions = xasprintf( > + "na { eth.src = "ETH_ADDR_FMT"; " > + "nd.tll = "ETH_ADDR_FMT"; " > + "outport = inport; " > + "inport = \"\"; /* Allow sending out inport. */ " > + "output; };", > + ETH_ADDR_ARGS(laddrs.ea), > + ETH_ADDR_ARGS(laddrs.ea)); > + > + ovn_lflow_add(lflows, op->od, S_SWITCH_IN_ARP_ND_RSP, 50, > + ds_cstr(&match), actions); > + > + ds_destroy(&match); > + } > + > free(laddrs.ipv4_addrs); > + free(laddrs.ipv6_addrs); > } > } > > - /* Ingress table 5: ARP responder, by default goto next. > + /* Ingress table 5: ARP/ND responder, by default goto next. > * (priority 0)*/ > HMAP_FOR_EACH (od, key_node, datapaths) { > if (!od->nbs) { > continue; > } > > - ovn_lflow_add(lflows, od, S_SWITCH_IN_ARP_RSP, 0, "1", "next;"); > + ovn_lflow_add(lflows, od, S_SWITCH_IN_ARP_ND_RSP, 0, "1", "next;"); > } > > /* Ingress table 6: Destination lookup, broadcast and multicast handling > diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml > index d877f76..e698365 100644 > --- a/ovn/ovn-sb.xml > +++ b/ovn/ovn-sb.xml > @@ -985,6 +985,45 @@ > <p><b>Prerequisite:</b> <code>ip4</code></p> > </dd> > > + <dt> > + <code>na { <var>action</var>; </code>...<code> };</code> > + </dt> > + > + <dd> > + <p> > + Temporarily replaces the IPv6 packet being processed by an NA > + packet and executes each nested <var>action</var> on the NA > + packet. Actions following the <var>na</var> action, ifany, apply > + to the original, unmodified packet. > + </p> > + > + <p> > + The NA packet that this action operates on is > initialized based on > + the IPv6 packet being processed, as follows. These are default > + values that the nested actions will probably want to change: > + </p> > + > + <ul> > + <li><code>eth.dst</code> exchanged with <code>eth.src</code></li> > + <li><code>eth.type = 0x86dd</code></li> > + <li><code>ip6.dst</code> copied from <code>ip6.src</code></li> > + <li><code>ip6.src</code> copied from <code>nd.target</code></li> > + <li><code>icmp6.type = 136</code> (Neighbor Advertisement)</li> > + <li><code>nd.target</code> unchanged</li> > + <li><code>nd.sll = 00:00:00:00:00:00</code></li> > + <li><code>nd.tll</code> copied from <code>eth.dst</code></li> > + </ul> > + > + <p> > + The ND packet has the same VLAN header, if any, as the > IPv6 packet > + it replaces. > + </p> > + > + <p> > + <b>Prerequisite:</b> <code>nd</code> > + </p> > + </dd> > + > <dt><code>get_arp(<var>P</var>, <var>A</var>);</code></dt> > > <dd> > diff --git a/tests/ovn.at b/tests/ovn.at > index a24e774..ba8d789 100644 > --- a/tests/ovn.at > +++ b/tests/ovn.at > @@ -525,6 +525,9 @@ get_arp(reg0, ip4.dst); => Cannot use numeric > field reg0 where string field is r > # put_arp > put_arp(inport, arp.spa, arp.sha); => > actions=push:NXM_NX_REG0[],push:NXM_OF_ETH_SRC[],push:NXM_NX_ARP_SHA [],push:NXM_OF_ARP_SPA[],pop:NXM_NX_REG0[],pop:NXM_OF_ETH_SRC[],controller (userdata=00. > 00.00.01.00.00.00.00),pop:NXM_OF_ETH_SRC[],pop:NXM_NX_REG0[], > prereqs=eth.type == 0x806 && eth.type == 0x806 > > +# na > +na { eth.src = 12:34:56:78:9a:bc; nd.tll = 12:34:56:78:9a:bc; > outport = inport; inport = ""; /* Allow sending out inport. */ > output; }; => actions=controller(userdata=00.00.00.02.00.00.00.00. > 00.19.00.10.80.00.08.06.12.34.56.78.9a.bc.00.00.00.19.00.10.80.00. > 42.06.12.34.56.78.9a.bc.00.00.ff.ff.00.18.00.00.23.20.00.06.00.20. > 00.00.00.00.00.01.0c.04.00.01.0e.04.00.19.00.10.00.01.0c.04.00.00. > 00.00.00.00.00.00.00.19.00.10.00.00.00.02.00.00.00.00.00.00.00. > 00.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00), prereqs=nd > + > # 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 > ip4.src <-> ip6.src[0..31]; => actions=push:NXM_NX_IPV6_SRC[0.. > 31],push:NXM_OF_IP_SRC[],pop:NXM_NX_IPV6_SRC[0.. > 31],pop:NXM_OF_IP_SRC[], prereqs=eth.type == 0x800 && eth.type == 0x86dd > @@ -3137,3 +3140,101 @@ OVS_APP_EXIT_AND_WAIT([ovs-vswitchd]) > OVS_APP_EXIT_AND_WAIT([ovsdb-server]) > > AT_CLEANUP > + > +AT_SETUP([ovn -- nd ]) > +AT_KEYWORDS([ovn-nd]) > +AT_SKIP_IF([test $HAVE_PYTHON = no]) > +ovn_start > + > +#TODO: since patch port for IPv6 logical router port is not ready not, > +# so we are not going to test vifs on different lswitches cases. Try > +# to update for that once relevant stuff implemented. > + > +# In this test cases we create 1 lswitch, it has 2 VIF ports attached > +# with. NS packet we test, from one VIF for another VIF, will be replied > +# by local ovn-controller, but not by target VIF. > + > +# Create hypervisors and logical switch lsw0. > +ovn-nbctl ls-add lsw0 > +net_add n1 > +sim_add hv1 > +as hv1 > +ovs-vsctl add-br br-phys > +ovn_attach n1 br-phys 192.168.0.2 > + > +# Add vif1 to hv1 and lsw0, turn on l2 port security on vif1. > +ovs-vsctl add-port br-int vif1 -- set Interface vif1 external- > ids:iface-id=lp1 options:tx_pcap=hv1/vif1-tx.pcap > options:rxq_pcap=hv1/vif1-rx.pcap ofport-request=1 > +ovn-nbctl lsp-add lsw0 lp1 > +ovn-nbctl lsp-set-addresses lp1 "fa:16:3e:94:05:98 192.168.0.3 > fd81:ce49:a948:0:f816:3eff:fe94:598" > +ovn-nbctl lsp-set-port-security lp1 "fa:16:3e:94:05:98 192.168.0.3 > fd81:ce49:a948:0:f816:3eff:fe94:598" > + > +# Add vif2 to hv1 and lsw0, turn on l2 port security on vif2. > +ovs-vsctl add-port br-int vif2 -- set Interface vif2 external- > ids:iface-id=lp2 options:tx_pcap=hv1/vif2-tx.pcap > options:rxq_pcap=hv1/vif2-rx.pcap ofport-request=2 > +ovn-nbctl lsp-add lsw0 lp2 > +ovn-nbctl lsp-set-addresses lp2 "fa:16:3e:a1:f9:ae 192.168.0.4 > fd81:ce49:a948:0:f816:3eff:fea1:f9ae" > +ovn-nbctl lsp-set-port-security lp2 "fa:16:3e:a1:f9:ae 192.168.0.4 > fd81:ce49:a948:0:f816:3eff:fea1:f9ae" > + > +# Add ACL rule for ICMPv6 on lsw0 > +ovn-nbctl acl-add lsw0 from-lport 1002 'ip6 && icmp6' allow-related > +ovn-nbctl acl-add lsw0 to-lport 1002 'outport == "lp1" && ip6 && > icmp6' allow-related > +ovn-nbctl acl-add lsw0 to-lport 1002 'outport == "lp2" && ip6 && > icmp6' allow-related > + > +# Allow some time for ovn-northd and ovn-controller to catch up. > +# XXX This should be more systematic. > +sleep 1 > + > +# Given the name of a logical port, prints the name of the hypervisor > +# on which it is located. > +vif_to_hv() { > + echo hv1${1%?} > +} > +trim_zeros() { > + sed 's/\(00\)\{1,\}$//' > +} > +for i in 1 2; do > + : > $i.expected > +done > + > +# Complete Neighbor Solicitation packet and Neighbor Advertisement packet > +# vif1 -> NS -> vif2. vif1 <- NA <- ovn-controller. > +# vif2 will not receive NS packet, since ovn-controller will reply for it. > +ns_packet=3333ffa1f9aefa163e94059886dd6000000000203afffd81ce49a9480000f8163efffe940598fd81ce49a9480000f8163efffea1f9ae8700e01160000000fd81ce49a9480000f8163efffea1f9ae0101fa163e940598 > +na_packet=fa163e940598fa163ea1f9ae86dd6000000000203afffd81ce49a9480000f8163efffea1f9aefd81ce49a9480000f8163efffe9405988800e9ed60000000fd81ce49a9480000f8163efffea1f9ae0201fa163ea1f9ae > + > +as hv1 ovs-appctl netdev-dummy/receive vif1 $ns_packet > +echo $na_packet | trim_zeros >> 1.expected > + > +sleep 1 > + > +echo "------ hv1 dump ------" > +as hv1 ovs-vsctl show > +as hv1 ovs-ofctl -O OpenFlow13 show br-int > +as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-int > + > +for i in 1 2; do > + file=hv1/vif$i-tx.pcap > + echo $file > + $PYTHON "$top_srcdir/utilities/ovs-pcap.in" $file | trim_zeros > > $i.packets > + cat $i.expected > expout > + AT_CHECK([cat $i.packets], [0], [expout]) > +done > + > +as hv1 > +OVS_APP_EXIT_AND_WAIT([ovn-controller]) > +OVS_APP_EXIT_AND_WAIT([ovs-vswitchd]) > +OVS_APP_EXIT_AND_WAIT([ovsdb-server]) > + > +as ovn-sb > +OVS_APP_EXIT_AND_WAIT([ovsdb-server]) > + > +as ovn-nb > +OVS_APP_EXIT_AND_WAIT([ovsdb-server]) > + > +as northd > +OVS_APP_EXIT_AND_WAIT([ovn-northd]) > + > +as main > +OVS_APP_EXIT_AND_WAIT([ovs-vswitchd]) > +OVS_APP_EXIT_AND_WAIT([ovsdb-server]) > + > +AT_CLEANUP > diff --git a/tutorial/OVN-Tutorial.md b/tutorial/OVN-Tutorial.md > index c4bcbae..811224d 100644 > --- a/tutorial/OVN-Tutorial.md > +++ b/tutorial/OVN-Tutorial.md > @@ -104,7 +104,7 @@ show the logical flows. > table=2(ls_in_port_sec_nd), priority= 0, match=(1), action= (next;) > table=3( ls_in_pre_acl), priority= 0, match=(1), action= (next;) > table=4( ls_in_acl), priority= 0, match=(1), action= (next;) > - table=5( ls_in_arp_rsp), priority= 0, match=(1), action= (next;) > + table=5(ls_in_arp_nd_rsp), priority= 0, match=(1), action= (next;) > table=6( ls_in_l2_lkup), priority= 100, match=(eth.mcast), > action=(outport = "_MC_flood"; output;) > table=6( ls_in_l2_lkup), priority= 50, match=(eth.dst == > 00:00:00:00:00:01), action=(outport = "sw0-port1"; output;) > table=6( ls_in_l2_lkup), priority= 50, match=(eth.dst == > 00:00:00:00:00:02), action=(outport = "sw0-port2"; output;) > @@ -277,7 +277,7 @@ OVN creates separate logical flows for each > logical switch. > table=2(ls_in_port_sec_nd), priority= 0, match=(1), action= (next;) > table=3( ls_in_pre_acl), priority= 0, match=(1), action= (next;) > table=4( ls_in_acl), priority= 0, match=(1), action= (next;) > - table=5( ls_in_arp_rsp), priority= 0, match=(1), action= (next;) > + table=5(ls_in_arp_nd_rsp), priority= 0, match=(1), action= (next;) > table=6( ls_in_l2_lkup), priority= 100, match=(eth.mcast), > action=(outport = "_MC_flood"; output;) > table=6( ls_in_l2_lkup), priority= 50, match=(eth.dst == > 00:00:00:00:00:03), action=(outport = "sw1-port1"; output;) > table=6( ls_in_l2_lkup), priority= 50, match=(eth.dst == > 00:00:00:00:00:04), action=(outport = "sw1-port2"; output;) > @@ -303,7 +303,7 @@ OVN creates separate logical flows for each > logical switch. > table=2(ls_in_port_sec_nd), priority= 0, match=(1), action= (next;) > table=3( ls_in_pre_acl), priority= 0, match=(1), action= (next;) > table=4( ls_in_acl), priority= 0, match=(1), action= (next;) > - table=5( ls_in_arp_rsp), priority= 0, match=(1), action= (next;) > + table=5(ls_in_arp_nd_rsp), priority= 0, match=(1), action= (next;) > table=6( ls_in_l2_lkup), priority= 100, match=(eth.mcast), > action=(outport = "_MC_flood"; output;) > table=6( ls_in_l2_lkup), priority= 50, match=(eth.dst == > 00:00:00:00:00:01), action=(outport = "sw0-port1"; output;) > table=6( ls_in_l2_lkup), priority= 50, match=(eth.dst == > 00:00:00:00:00:02), action=(outport = "sw0-port2"; output;) > -- Since the nit comment above is for a comment only... Acked-by: Ryan Moats <rmo...@us.ibm.com> _______________________________________________ dev mailing list dev@openvswitch.org http://openvswitch.org/mailman/listinfo/dev