[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