[ovs-dev] [PATCH] [PATCH, v5] ovn: Add 'na' action and lflow for ND
Ryan Moats
rmoats at us.ibm.com
Thu Jun 16 15:26:30 UTC 2016
"dev" <dev-bounces at openvswitch.org> wrote on 06/16/2016 03:53:39 AM:
> From: Zong Kai LI <zealokii at gmail.com>
> To: dev at openvswitch.org
> Cc: Zong Kai LI <zealokii at 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-bounces at 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 <zealokii at 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 <rmoats at us.ibm.com>
More information about the dev
mailing list