This is useful for generating an ARP request from the OpenFlow flow table,
which in turn is useful for implementing a router.  An upcoming commit
will use this feature to implement ARP request generationg in the OVN L3
logical router.

Signed-off-by: Ben Pfaff <b...@ovn.org>
---
 NEWS                                              |   2 +
 datapath/linux/compat/include/linux/openvswitch.h |   5 +-
 lib/dpif-netdev.c                                 |   3 +-
 lib/dpif.c                                        |   1 +
 lib/odp-execute.c                                 |  26 +++++
 lib/odp-util.c                                    |  12 ++
 lib/ofp-actions.c                                 | 129 +++++++++++++++++++++-
 lib/ofp-actions.h                                 |   3 +-
 lib/ofp-errors.h                                  |   4 +
 lib/packets.c                                     |  36 +++---
 lib/packets.h                                     |   1 +
 ofproto/ofproto-dpif-sflow.c                      |   4 +
 ofproto/ofproto-dpif-xlate.c                      |  70 ++++++++++++
 tests/ofproto-dpif.at                             |  65 +++++++++++
 utilities/ovs-ofctl.8.in                          |  26 +++++
 15 files changed, 370 insertions(+), 17 deletions(-)

diff --git a/NEWS b/NEWS
index a6b1599..578171d 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,7 @@
 Post-v2.5.0
 ---------------------
+   - OpenFlow:
+     * New "arp" OpenFlow extension action for generating ARP packets.
 
 
 v2.5.0 - xx xxx xxxx
diff --git a/datapath/linux/compat/include/linux/openvswitch.h 
b/datapath/linux/compat/include/linux/openvswitch.h
index 3b39ebb..5acf539 100644
--- a/datapath/linux/compat/include/linux/openvswitch.h
+++ b/datapath/linux/compat/include/linux/openvswitch.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2007-2014 Nicira, Inc.
+ * Copyright (c) 2007-2015 Nicira, Inc.
  *
  * This file is offered under your choice of two licenses: Apache 2.0 or GNU
  * GPL 2.0 or later.  The permission statements for each of these licenses is
@@ -783,6 +783,8 @@ enum ovs_nat_attr {
  * ovs_action_push_tnl.
  * @OVS_ACTION_ATTR_TUNNEL_POP: Lookup tunnel port by port-no passed and pop
  * tunnel header.
+ * @OVS_ACTION_ATTR_ARP: Transform IPv4 packet into ARP packet, execute nested
+ * actions, restore IPv4 packet.
  */
 
 enum ovs_action_attr {
@@ -806,6 +808,7 @@ enum ovs_action_attr {
 #ifndef __KERNEL__
        OVS_ACTION_ATTR_TUNNEL_PUSH,   /* struct ovs_action_push_tnl*/
        OVS_ACTION_ATTR_TUNNEL_POP,    /* u32 port number. */
+       OVS_ACTION_ATTR_ARP,           /* Nested OVS_ACTION_ATTR_*. */
 #endif
        __OVS_ACTION_ATTR_MAX,        /* Nothing past this will be accepted
                                       * from userspace. */
diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
index a67ef05..d382fde 100644
--- a/lib/dpif-netdev.c
+++ b/lib/dpif-netdev.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014 Nicira, Inc.
+ * Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014, 2015 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -3627,6 +3627,7 @@ dp_execute_cb(void *aux_, struct dp_packet **packets, int 
cnt,
     case OVS_ACTION_ATTR_SET_MASKED:
     case OVS_ACTION_ATTR_SAMPLE:
     case OVS_ACTION_ATTR_HASH:
+    case OVS_ACTION_ATTR_ARP:
     case OVS_ACTION_ATTR_UNSPEC:
     case __OVS_ACTION_ATTR_MAX:
         OVS_NOT_REACHED();
diff --git a/lib/dpif.c b/lib/dpif.c
index 38e40ba..2926668 100644
--- a/lib/dpif.c
+++ b/lib/dpif.c
@@ -1146,6 +1146,7 @@ dpif_execute_helper_cb(void *aux_, struct dp_packet 
**packets, int cnt,
     case OVS_ACTION_ATTR_SET:
     case OVS_ACTION_ATTR_SET_MASKED:
     case OVS_ACTION_ATTR_SAMPLE:
+    case OVS_ACTION_ATTR_ARP:
     case OVS_ACTION_ATTR_UNSPEC:
     case __OVS_ACTION_ATTR_MAX:
         OVS_NOT_REACHED();
diff --git a/lib/odp-execute.c b/lib/odp-execute.c
index b5204b2..5e5c3ca 100644
--- a/lib/odp-execute.c
+++ b/lib/odp-execute.c
@@ -34,6 +34,8 @@
 #include "unaligned.h"
 #include "util.h"
 
+#include "openvswitch/vlog.h"
+
 /* Masked copy of an ethernet address. 'src' is already properly masked. */
 static void
 ether_addr_copy_masked(struct eth_addr *dst, const struct eth_addr src,
@@ -480,6 +482,23 @@ odp_execute_sample(void *dp, struct dp_packet *packet, 
bool steal,
                         nl_attr_get_size(subactions), dp_execute_action);
 }
 
+static void
+odp_execute_arp(void *dp, struct dp_packet *ip,
+                const struct nlattr *action, odp_execute_cb dp_execute_action)
+{
+    uint64_t stub[DIV_ROUND_UP(2 + ETH_HEADER_LEN + VLAN_HEADER_LEN +
+                               ARP_ETH_HEADER_LEN, 8)];
+    struct dp_packet arp;
+    dp_packet_use_stub(&arp, stub, sizeof stub);
+    compose_arp__(&arp);
+    arp.md = ip->md;
+
+    struct dp_packet *arpp = &arp;
+    odp_execute_actions(dp, &arpp, 1, false, nl_attr_get(action),
+                        nl_attr_get_size(action), dp_execute_action);
+    dp_packet_uninit(&arp);
+}
+
 static bool
 requires_datapath_assistance(const struct nlattr *a)
 {
@@ -503,6 +522,7 @@ requires_datapath_assistance(const struct nlattr *a)
     case OVS_ACTION_ATTR_HASH:
     case OVS_ACTION_ATTR_PUSH_MPLS:
     case OVS_ACTION_ATTR_POP_MPLS:
+    case OVS_ACTION_ATTR_ARP:
         return false;
 
     case OVS_ACTION_ATTR_UNSPEC:
@@ -623,6 +643,12 @@ odp_execute_actions(void *dp, struct dp_packet **packets, 
int cnt, bool steal,
             }
             break;
 
+        case OVS_ACTION_ATTR_ARP:
+            for (i = 0; i < cnt; i++) {
+                odp_execute_arp(dp, packets[i], a, dp_execute_action);
+            }
+            break;
+
         case OVS_ACTION_ATTR_OUTPUT:
         case OVS_ACTION_ATTR_TUNNEL_PUSH:
         case OVS_ACTION_ATTR_TUNNEL_POP:
diff --git a/lib/odp-util.c b/lib/odp-util.c
index 90942c7..8fe14c5 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -120,6 +120,7 @@ odp_action_len(uint16_t type)
     case OVS_ACTION_ATTR_SET_MASKED: return ATTR_LEN_VARIABLE;
     case OVS_ACTION_ATTR_SAMPLE: return ATTR_LEN_VARIABLE;
     case OVS_ACTION_ATTR_CT: return ATTR_LEN_VARIABLE;
+    case OVS_ACTION_ATTR_ARP: return ATTR_LEN_VARIABLE;
 
     case OVS_ACTION_ATTR_UNSPEC:
     case __OVS_ACTION_ATTR_MAX:
@@ -766,6 +767,14 @@ format_odp_conntrack_action(struct ds *ds, const struct 
nlattr *attr)
 }
 
 static void
+format_odp_arp_action(struct ds *ds, const struct nlattr *attr)
+{
+    ds_put_cstr(ds, "arp(");
+    format_odp_actions(ds, nl_attr_get(attr), nl_attr_get_size(attr));
+    ds_put_char(ds, ')');
+}
+
+static void
 format_odp_action(struct ds *ds, const struct nlattr *a)
 {
     int expected_len;
@@ -858,6 +867,9 @@ format_odp_action(struct ds *ds, const struct nlattr *a)
     case OVS_ACTION_ATTR_CT:
         format_odp_conntrack_action(ds, a);
         break;
+    case OVS_ACTION_ATTR_ARP:
+        format_odp_arp_action(ds, a);
+        break;
     case OVS_ACTION_ATTR_UNSPEC:
     case __OVS_ACTION_ATTR_MAX:
     default:
diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c
index 354c768..92b296b 100644
--- a/lib/ofp-actions.c
+++ b/lib/ofp-actions.c
@@ -295,6 +295,9 @@ enum ofp_raw_action_type {
     /* NX1.0+(36): struct nx_action_nat, ... */
     NXAST_RAW_NAT,
 
+    /* NX1.0+(37): struct nx_action_arp, ... */
+    NXAST_RAW_ARP,
+
 /* ## ------------------ ## */
 /* ## Debugging actions. ## */
 /* ## ------------------ ## */
@@ -423,6 +426,9 @@ ofpact_next_flattened(const struct ofpact *ofpact)
     case OFPACT_CT:
         return ofpact_get_CT(ofpact)->actions;
 
+    case OFPACT_ARP:
+        return ofpact_get_ARP(ofpact)->actions;
+
     case OFPACT_WRITE_ACTIONS:
         return ofpact_get_WRITE_ACTIONS(ofpact)->actions;
     }
@@ -5349,7 +5355,108 @@ parse_NAT(char *arg, struct ofpbuf *ofpacts,
     }
     return NULL;
 }
+
+/* Action structure for NXAST_ARP.
+ *
+ * The only content is a series of nested OpenFlow actions to act on the ARP
+ * packet.
+ */
+struct nx_action_arp {
+    ovs_be16 type;              /* OFPAT_VENDOR. */
+    ovs_be16 len;               /* At least 16. */
+    ovs_be32 vendor;            /* NX_VENDOR_ID. */
+    ovs_be16 subtype;           /* NXAST_ARP. */
+    uint8_t zero[6];            /* Must be zero. */
+    /* Followed by a sequence of zero or more OpenFlow actions. The length of
+     * these is included in 'len'. */
+};
+OFP_ASSERT(sizeof(struct nx_action_arp) == 16);
+
+static enum ofperr
+decode_NXAST_RAW_ARP(const struct nx_action_arp *naa,
+                     enum ofp_version ofp_version, struct ofpbuf *out)
+{
+    const size_t arp_offset = ofpacts_pull(out);
+    struct ofpact_nest *arp;
+    struct ofpbuf openflow;
+    int error = 0;
+
+    if (!is_all_zeros(naa->zero, sizeof naa->zero)) {
+        return OFPERR_NXBAC_MUST_BE_ZERO;
+    }
+
+    arp = ofpact_put_ARP(out);
+    ofpbuf_pull(out, sizeof *arp);
+
+    ofpbuf_use_const(&openflow, naa + 1, ntohs(naa->len) - sizeof *naa);
+    error = ofpacts_pull_openflow_actions__(
+        &openflow, openflow.size, ofp_version,
+        1u << OVSINST_OFPIT11_APPLY_ACTIONS, out, OFPACT_ARP);
+    if (error) {
+        goto out;
+    }
+
+    arp = ofpbuf_push_uninit(out, sizeof *arp);
+    out->header = &arp->ofpact;
+    ofpact_update_len(out, &arp->ofpact);
+
+out:
+    ofpbuf_push_uninit(out, arp_offset);
+    return error;
+}
+
+static void
+encode_ARP(const struct ofpact_nest *arp, enum ofp_version ofp_version,
+           struct ofpbuf *out)
+{
+    struct nx_action_arp *naa;
+    const size_t ofs = out->size;
+    size_t len;
+
+    naa = put_NXAST_ARP(out);
+
+    len = ofpacts_put_openflow_actions(arp->actions,
+                                       ofpact_nest_get_action_len(arp),
+                                       out, ofp_version);
+    len += sizeof *naa;
+    naa = ofpbuf_at(out, ofs, sizeof *naa);
+    naa->len = htons(len);
+}
+
+/* Parses 'arg' as the argument to a "arp" action, and appends such an
+ * action to 'ofpacts'.
+ *
+ * Returns NULL if successful, otherwise a malloc()'d string describing the
+ * error.  The caller is responsible for freeing the returned string. */
+static char * OVS_WARN_UNUSED_RESULT
+parse_ARP(char *arg, struct ofpbuf *ofpacts,
+         enum ofputil_protocol *usable_protocols)
+{
+    const size_t arp_offset = ofpacts_pull(ofpacts);
+    struct ofpact_nest *arp = ofpact_put_ARP(ofpacts);
+
+    /* Hide existing actions from ofpacts_parse_copy(), so the
+     * nesting can be handled transparently. */
+    ofpbuf_pull(ofpacts, sizeof *arp);
+    char *error = ofpacts_parse_copy(arg, ofpacts, usable_protocols, false,
+                                     OFPACT_ARP);
+    ofpact_pad(ofpacts);
+    ofpacts->header = ofpbuf_push_uninit(ofpacts, sizeof *arp);
+    arp = ofpacts->header;
+
+    ofpact_update_len(ofpacts, &arp->ofpact);
+    ofpbuf_push_uninit(ofpacts, arp_offset);
+
+    return error;
+}
 
+static void
+format_ARP(const struct ofpact_nest *a, struct ds *s)
+{
+    ds_put_cstr(s, "arp(");
+    ofpacts_format(a->actions, ofpact_nest_get_action_len(a), s);
+    ds_put_char(s, ')');
+}
 
 /* Meter instruction. */
 
@@ -5749,6 +5856,7 @@ ofpact_is_set_or_move_action(const struct ofpact *a)
     case OFPACT_OUTPUT:
     case OFPACT_OUTPUT_REG:
     case OFPACT_POP_MPLS:
+    case OFPACT_ARP:
     case OFPACT_POP_QUEUE:
     case OFPACT_PUSH_MPLS:
     case OFPACT_PUSH_VLAN:
@@ -5822,6 +5930,7 @@ ofpact_is_allowed_in_actions_set(const struct ofpact *a)
     case OFPACT_SAMPLE:
     case OFPACT_STACK_POP:
     case OFPACT_STACK_PUSH:
+    case OFPACT_ARP:
     case OFPACT_DEBUG_RECIRC:
 
     /* The action set may only include actions and thus
@@ -6024,6 +6133,7 @@ ovs_instruction_type_from_ofpact_type(enum ofpact_type 
type)
     case OFPACT_DEC_MPLS_TTL:
     case OFPACT_PUSH_MPLS:
     case OFPACT_POP_MPLS:
+    case OFPACT_ARP:
     case OFPACT_SET_TUNNEL:
     case OFPACT_SET_QUEUE:
     case OFPACT_POP_QUEUE:
@@ -6592,6 +6702,21 @@ ofpact_check__(enum ofputil_protocol *usable_protocols, 
struct ofpact *a,
         flow->dl_type = ofpact_get_POP_MPLS(a)->ethertype;
         return 0;
 
+    case OFPACT_ARP: {
+        if (flow->dl_type != htons(ETH_TYPE_IP)) {
+            return OFPERR_NXBAC_ARP_REQUIRES_IPV4;
+        }
+
+        flow->dl_type = htons(ETH_TYPE_ARP);
+        struct ofpact_nest *oa = ofpact_get_ARP(a);
+        enum ofperr error = ofpacts_check(oa->actions,
+                                          ofpact_nest_get_action_len(oa),
+                                          flow, max_ports, table_id, n_tables,
+                                          usable_protocols);
+        flow->dl_type = htons(ETH_TYPE_IP);
+        return error;
+    }
+
     case OFPACT_SAMPLE:
         return 0;
 
@@ -6784,7 +6909,8 @@ ofpacts_verify_nested(const struct ofpact *a, enum 
ofpact_type outer_action)
 
     if (outer_action) {
         ovs_assert(outer_action == OFPACT_WRITE_ACTIONS
-                   || outer_action == OFPACT_CT);
+                   || outer_action == OFPACT_CT
+                   || outer_action == OFPACT_ARP);
 
         if (outer_action == OFPACT_CT) {
             if (!field) {
@@ -7130,6 +7256,7 @@ ofpact_outputs_to_port(const struct ofpact *ofpact, 
ofp_port_t port)
     case OFPACT_SET_MPLS_TC:
     case OFPACT_SET_MPLS_TTL:
     case OFPACT_DEC_MPLS_TTL:
+    case OFPACT_ARP:
     case OFPACT_SET_TUNNEL:
     case OFPACT_WRITE_METADATA:
     case OFPACT_SET_QUEUE:
diff --git a/lib/ofp-actions.h b/lib/ofp-actions.h
index 18c7395..66b82ed 100644
--- a/lib/ofp-actions.h
+++ b/lib/ofp-actions.h
@@ -86,6 +86,7 @@
     OFPACT(DEC_MPLS_TTL,    ofpact_null,        ofpact, "dec_mpls_ttl") \
     OFPACT(PUSH_MPLS,       ofpact_push_mpls,   ofpact, "push_mpls")    \
     OFPACT(POP_MPLS,        ofpact_pop_mpls,    ofpact, "pop_mpls")     \
+    OFPACT(ARP,             ofpact_nest,        ofpact, "arp")          \
                                                                         \
     /* Metadata. */                                                     \
     OFPACT(SET_TUNNEL,      ofpact_tunnel,      ofpact, "set_tunnel")   \
@@ -475,7 +476,7 @@ struct ofpact_meter {
 
 /* OFPACT_WRITE_ACTIONS.
  *
- * Used for OFPIT11_WRITE_ACTIONS. */
+ * Used for OFPIT11_WRITE_ACTIONS and NXAST_ARP. */
 struct ofpact_nest {
     struct ofpact ofpact;
     uint8_t pad[PAD_SIZE(sizeof(struct ofpact), OFPACT_ALIGNTO)];
diff --git a/lib/ofp-errors.h b/lib/ofp-errors.h
index 9c108d0..f40faf7 100644
--- a/lib/ofp-errors.h
+++ b/lib/ofp-errors.h
@@ -269,6 +269,10 @@ enum ofperr {
      * 64. */
     OFPERR_NXBAC_BAD_CONJUNCTION,
 
+    /* NX1.0-1.1(2,527), NX1.2+(23).  ARP is allowed only in a flow that
+     * matches on Ethertype 0x800. */
+    OFPERR_NXBAC_ARP_REQUIRES_IPV4,
+
 /* ## --------------------- ## */
 /* ## OFPET_BAD_INSTRUCTION ## */
 /* ## --------------------- ## */
diff --git a/lib/packets.c b/lib/packets.c
index d82341d..386a6a4 100644
--- a/lib/packets.c
+++ b/lib/packets.c
@@ -1191,35 +1191,45 @@ packet_format_tcp_flags(struct ds *s, uint16_t 
tcp_flags)
  * 'arp_op', 'arp_sha', 'arp_tha', 'arp_spa', and 'arp_tpa'.  The outer
  * Ethernet frame is initialized with Ethernet source 'arp_sha' and destination
  * 'arp_tha', except that destination ff:ff:ff:ff:ff:ff is used instead if
- * 'broadcast' is true. */
+ * 'broadcast' is true.  Points the L3 header to the ARP header. */
 void
 compose_arp(struct dp_packet *b, uint16_t arp_op,
             const struct eth_addr arp_sha, const struct eth_addr arp_tha,
             bool broadcast, ovs_be32 arp_spa, ovs_be32 arp_tpa)
 {
-    struct eth_header *eth;
-    struct arp_eth_header *arp;
+    compose_arp__(b);
+
+    struct eth_header *eth = dp_packet_l2(b);
+    eth->eth_dst = broadcast ? eth_addr_broadcast : arp_tha;
+    eth->eth_src = arp_sha;
+
+    struct arp_eth_header *arp = dp_packet_l3(b);
+    arp->ar_op = htons(arp_op);
+    arp->ar_sha = arp_sha;
+    arp->ar_tha = arp_tha;
+    put_16aligned_be32(&arp->ar_spa, arp_spa);
+    put_16aligned_be32(&arp->ar_tpa, arp_tpa);
+}
 
+/* Clears 'b' and replaces its contents by an ARP frame.  Sets the fields in
+ * the Ethernet and ARP headers that are fixed for ARP frames to those fixed
+ * values, and zeroes the other fields.  Points the L3 header to the ARP
+ * header. */
+void
+compose_arp__(struct dp_packet *b)
+{
     dp_packet_clear(b);
     dp_packet_prealloc_tailroom(b, ARP_PACKET_SIZE);
     dp_packet_reserve(b, 2 + VLAN_HEADER_LEN);
 
-    eth = dp_packet_put_uninit(b, sizeof *eth);
-    eth->eth_dst = broadcast ? eth_addr_broadcast : arp_tha;
-    eth->eth_src = arp_sha;
+    struct eth_header *eth = dp_packet_put_zeros(b, sizeof *eth);
     eth->eth_type = htons(ETH_TYPE_ARP);
 
-    arp = dp_packet_put_uninit(b, sizeof *arp);
+    struct arp_eth_header *arp = dp_packet_put_zeros(b, sizeof *arp);
     arp->ar_hrd = htons(ARP_HRD_ETHERNET);
     arp->ar_pro = htons(ARP_PRO_IP);
     arp->ar_hln = sizeof arp->ar_sha;
     arp->ar_pln = sizeof arp->ar_spa;
-    arp->ar_op = htons(arp_op);
-    arp->ar_sha = arp_sha;
-    arp->ar_tha = arp_tha;
-
-    put_16aligned_be32(&arp->ar_spa, arp_spa);
-    put_16aligned_be32(&arp->ar_tpa, arp_tpa);
 
     dp_packet_reset_offsets(b);
     dp_packet_set_l3(b, arp);
diff --git a/lib/packets.h b/lib/packets.h
index eaa329f..cde0743 100644
--- a/lib/packets.h
+++ b/lib/packets.h
@@ -1041,6 +1041,7 @@ void packet_set_nd(struct dp_packet *, const ovs_be32 
target[4],
 
 void packet_format_tcp_flags(struct ds *, uint16_t);
 const char *packet_tcp_flag_to_string(uint32_t flag);
+void compose_arp__(struct dp_packet *);
 void compose_arp(struct dp_packet *, uint16_t arp_op,
                  const struct eth_addr arp_sha,
                  const struct eth_addr arp_tha, bool broadcast,
diff --git a/ofproto/ofproto-dpif-sflow.c b/ofproto/ofproto-dpif-sflow.c
index f11699c..4db3357 100644
--- a/ofproto/ofproto-dpif-sflow.c
+++ b/ofproto/ofproto-dpif-sflow.c
@@ -1138,6 +1138,10 @@ dpif_sflow_read_actions(const struct flow *flow,
            }
            break;
 
+    case OVS_ACTION_ATTR_ARP:
+        /* XXX Do not handle this for now. */
+        break;
+
        case OVS_ACTION_ATTR_USERSPACE:
        case OVS_ACTION_ATTR_RECIRC:
        case OVS_ACTION_ATTR_HASH:
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index cf184e4..c1ae417 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -4163,6 +4163,7 @@ recirc_unroll_actions(const struct ofpact *ofpacts, 
size_t ofpacts_len,
         case OFPACT_CONTROLLER:
         case OFPACT_DEC_MPLS_TTL:
         case OFPACT_DEC_TTL:
+        case OFPACT_ARP:
             recirc_put_unroll_xlate(ctx);
             break;
 
@@ -4387,6 +4388,71 @@ compose_conntrack_action(struct xlate_ctx *ctx, struct 
ofpact_conntrack *ofc)
 }
 
 static void
+execute_arp_action(struct xlate_ctx *ctx, const struct ofpact_nest *arp)
+{
+    ctx->xout->slow |= SLOW_ACTION;
+
+    /* Commit the current flow and save it.
+     *
+     * (We don't save ctx->base_flow separately because the commit should
+     * ensure that it is identical.) */
+    xlate_commit_actions(ctx);
+    struct flow old_flow = ctx->xin->flow;
+
+    /* Transform ctx->in->flow to ARP. */
+    ctx->xin->flow.dl_type = htons(ETH_TYPE_ARP);
+    ctx->xin->flow.nw_proto = ARP_OP_REQUEST;
+    ctx->xin->flow.arp_sha = ctx->xin->flow.dl_src;
+    ctx->xin->flow.arp_tha = ctx->xin->flow.dl_dst;
+
+    /* This introduces a read dependency on dl_src and dl_dst, so mark them in
+     * the wildcard mask.  We also have to mark the ARP fields; otherwise at
+     * commit time we won't even consider them. */
+    memset(&ctx->wc->masks.dl_src, 0xff, sizeof ctx->wc->masks.dl_src);
+    memset(&ctx->wc->masks.dl_dst, 0xff, sizeof ctx->wc->masks.dl_dst);
+    memset(&ctx->wc->masks.nw_src, 0xff, sizeof ctx->wc->masks.nw_src);
+    memset(&ctx->wc->masks.nw_dst, 0xff, sizeof ctx->wc->masks.nw_dst);
+    memset(&ctx->wc->masks.nw_proto, 0xff, sizeof ctx->wc->masks.nw_proto);
+    memset(&ctx->wc->masks.arp_sha, 0xff, sizeof ctx->wc->masks.arp_sha);
+    memset(&ctx->wc->masks.arp_tha, 0xff, sizeof ctx->wc->masks.arp_tha);
+
+    /* Transform ctx->base_flow to ARP.  This differs from ctx->xin->flow
+     * because the datapath ARP action zeroes all field values. */
+    ctx->base_flow = ctx->xin->flow;
+    ctx->base_flow.vlan_tci = 0;
+    ctx->base_flow.dl_src = eth_addr_zero;
+    ctx->base_flow.dl_dst = eth_addr_zero;
+    ctx->base_flow.nw_src = 0;  /* arp_spa */
+    ctx->base_flow.nw_dst = 0;  /* arp_tpa */
+    ctx->base_flow.arp_sha = eth_addr_zero;
+    ctx->base_flow.arp_tha = eth_addr_zero;
+
+    /* Save ctx->xin->packet.  Then, if it was nonnull, replace it by a
+     * synthesized ARP packet. */
+    const struct dp_packet *old_packet = ctx->xin->packet;
+    uint64_t stub[DIV_ROUND_UP(2 + ETH_HEADER_LEN + VLAN_HEADER_LEN +
+                               ARP_ETH_HEADER_LEN, 8)];
+    struct dp_packet packet;
+    if (old_packet) {
+        dp_packet_use_stub(&packet, stub, sizeof stub);
+        compose_arp__(&packet);
+        packet.md = old_packet->md;
+        ctx->xin->packet = &packet;
+    }
+
+    size_t start = nl_msg_start_nested(ctx->odp_actions, OVS_ACTION_ATTR_ARP);
+    do_xlate_actions(arp->actions, ofpact_nest_get_action_len(arp), ctx);
+    nl_msg_end_nested(ctx->odp_actions, start);
+
+    /* Restore old flow and base_flow.  Restore old packet. */
+    ctx->base_flow = ctx->xin->flow = old_flow;
+    if (old_packet) {
+        dp_packet_uninit(&packet);
+        ctx->xin->packet = old_packet;
+    }
+}
+
+static void
 do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
                  struct xlate_ctx *ctx)
 {
@@ -4634,6 +4700,10 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t 
ofpacts_len,
             compose_mpls_pop_action(ctx, ofpact_get_POP_MPLS(a)->ethertype);
             break;
 
+        case OFPACT_ARP:
+            execute_arp_action(ctx, ofpact_get_ARP(a));
+            break;
+
         case OFPACT_SET_MPLS_LABEL:
             CHECK_MPLS_RECIRCULATION();
             compose_set_mpls_label_action(
diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
index 60734db..73f7641 100644
--- a/tests/ofproto-dpif.at
+++ b/tests/ofproto-dpif.at
@@ -3044,6 +3044,71 @@ 
arp,in_port=ANY,vlan_tci=0x0000,dl_src=80:88:88:88:88:88,dl_dst=ff:ff:ff:ff:ff:f
 OVS_VSWITCHD_STOP
 AT_CLEANUP
 
+AT_SETUP([ofproto-dpif - IP to ARP transformation slow-path])
+OVS_VSWITCHD_START
+
+# Add pairs of ports for input and output.
+ADD_OF_PORTS([br0], [1], [2], [3], [4], [11], [12], [13], [14])
+for i in 11 12 13 14; do
+    ovs-vsctl -- set Interface p$i type=dummy options:pcap=p$i.pcap
+done
+
+# Add flows that do various kind of "arp" action transformations
+# from port 1 to 11, 2 to 12, 3 to 13, 4 to 14.
+AT_DATA([flows.txt], [dnl
+in_port=1,ip actions=arp(11),arp(set_field:2->arp_op,11),11
+in_port=2,ip actions=mod_vlan_vid:123,arp(12),arp(set_field:2->arp_op,12),12
+in_port=3,ip actions=arp(mod_vlan_vid:123,13),arp(set_field:2->arp_op,13),13
+in_port=4,ip actions=arp(14),strip_vlan,arp(set_field:2->arp_op,14),14
+])
+AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
+
+# Input two packets (one without a VLAN, one with VLAN 99) on each input port.
+for i in 1 2 3 4; do
+    ovs-appctl netdev-dummy/receive p$i 
"in_port($i),eth(src=80:88:88:88:88:88,dst=10:20:30:40:50:60),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.1.2,proto=17,tos=0,ttl=64,frag=no),udp(src=8,dst=10)"
+    ovs-appctl netdev-dummy/receive p$i 
"in_port($i),eth(src=80:88:88:88:88:88,dst=10:20:30:40:50:60),eth_type(0x8100),vlan(vid=99,pcp=7),encap(eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.1.2,proto=17,tos=0,ttl=64,frag=no),udp(src=8,dst=10))"
+done
+AT_CHECK([ovs-appctl time/warp 5000], [0], [ignore])
+
+# Check the packets that were output.
+AT_CHECK([ovs-ofctl parse-pcap p11.pcap], [0], [dnl
+arp,in_port=ANY,vlan_tci=0x0000,dl_src=80:88:88:88:88:88,dl_dst=10:20:30:40:50:60,arp_spa=192.168.0.1,arp_tpa=192.168.1.2,arp_op=1,arp_sha=80:88:88:88:88:88,arp_tha=10:20:30:40:50:60
+arp,in_port=ANY,vlan_tci=0x0000,dl_src=80:88:88:88:88:88,dl_dst=10:20:30:40:50:60,arp_spa=192.168.0.1,arp_tpa=192.168.1.2,arp_op=2,arp_sha=80:88:88:88:88:88,arp_tha=10:20:30:40:50:60
+udp,in_port=ANY,vlan_tci=0x0000,dl_src=80:88:88:88:88:88,dl_dst=10:20:30:40:50:60,nw_src=192.168.0.1,nw_dst=192.168.1.2,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=8,tp_dst=10
+arp,in_port=ANY,dl_vlan=99,dl_vlan_pcp=7,dl_src=80:88:88:88:88:88,dl_dst=10:20:30:40:50:60,arp_spa=192.168.0.1,arp_tpa=192.168.1.2,arp_op=1,arp_sha=80:88:88:88:88:88,arp_tha=10:20:30:40:50:60
+arp,in_port=ANY,dl_vlan=99,dl_vlan_pcp=7,dl_src=80:88:88:88:88:88,dl_dst=10:20:30:40:50:60,arp_spa=192.168.0.1,arp_tpa=192.168.1.2,arp_op=2,arp_sha=80:88:88:88:88:88,arp_tha=10:20:30:40:50:60
+udp,in_port=ANY,dl_vlan=99,dl_vlan_pcp=7,dl_src=80:88:88:88:88:88,dl_dst=10:20:30:40:50:60,nw_src=192.168.0.1,nw_dst=192.168.1.2,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=8,tp_dst=10
+])
+
+AT_CHECK([ovs-ofctl parse-pcap p12.pcap], [0], [dnl
+arp,in_port=ANY,dl_vlan=123,dl_vlan_pcp=0,dl_src=80:88:88:88:88:88,dl_dst=10:20:30:40:50:60,arp_spa=192.168.0.1,arp_tpa=192.168.1.2,arp_op=1,arp_sha=80:88:88:88:88:88,arp_tha=10:20:30:40:50:60
+arp,in_port=ANY,dl_vlan=123,dl_vlan_pcp=0,dl_src=80:88:88:88:88:88,dl_dst=10:20:30:40:50:60,arp_spa=192.168.0.1,arp_tpa=192.168.1.2,arp_op=2,arp_sha=80:88:88:88:88:88,arp_tha=10:20:30:40:50:60
+udp,in_port=ANY,dl_vlan=123,dl_vlan_pcp=0,dl_src=80:88:88:88:88:88,dl_dst=10:20:30:40:50:60,nw_src=192.168.0.1,nw_dst=192.168.1.2,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=8,tp_dst=10
+arp,in_port=ANY,dl_vlan=123,dl_vlan_pcp=7,dl_src=80:88:88:88:88:88,dl_dst=10:20:30:40:50:60,arp_spa=192.168.0.1,arp_tpa=192.168.1.2,arp_op=1,arp_sha=80:88:88:88:88:88,arp_tha=10:20:30:40:50:60
+arp,in_port=ANY,dl_vlan=123,dl_vlan_pcp=7,dl_src=80:88:88:88:88:88,dl_dst=10:20:30:40:50:60,arp_spa=192.168.0.1,arp_tpa=192.168.1.2,arp_op=2,arp_sha=80:88:88:88:88:88,arp_tha=10:20:30:40:50:60
+udp,in_port=ANY,dl_vlan=123,dl_vlan_pcp=7,dl_src=80:88:88:88:88:88,dl_dst=10:20:30:40:50:60,nw_src=192.168.0.1,nw_dst=192.168.1.2,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=8,tp_dst=10
+])
+
+AT_CHECK([ovs-ofctl parse-pcap p13.pcap], [0], [dnl
+arp,in_port=ANY,dl_vlan=123,dl_vlan_pcp=0,dl_src=80:88:88:88:88:88,dl_dst=10:20:30:40:50:60,arp_spa=192.168.0.1,arp_tpa=192.168.1.2,arp_op=1,arp_sha=80:88:88:88:88:88,arp_tha=10:20:30:40:50:60
+arp,in_port=ANY,vlan_tci=0x0000,dl_src=80:88:88:88:88:88,dl_dst=10:20:30:40:50:60,arp_spa=192.168.0.1,arp_tpa=192.168.1.2,arp_op=2,arp_sha=80:88:88:88:88:88,arp_tha=10:20:30:40:50:60
+udp,in_port=ANY,vlan_tci=0x0000,dl_src=80:88:88:88:88:88,dl_dst=10:20:30:40:50:60,nw_src=192.168.0.1,nw_dst=192.168.1.2,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=8,tp_dst=10
+arp,in_port=ANY,dl_vlan=123,dl_vlan_pcp=7,dl_src=80:88:88:88:88:88,dl_dst=10:20:30:40:50:60,arp_spa=192.168.0.1,arp_tpa=192.168.1.2,arp_op=1,arp_sha=80:88:88:88:88:88,arp_tha=10:20:30:40:50:60
+arp,in_port=ANY,dl_vlan=99,dl_vlan_pcp=7,dl_src=80:88:88:88:88:88,dl_dst=10:20:30:40:50:60,arp_spa=192.168.0.1,arp_tpa=192.168.1.2,arp_op=2,arp_sha=80:88:88:88:88:88,arp_tha=10:20:30:40:50:60
+udp,in_port=ANY,dl_vlan=99,dl_vlan_pcp=7,dl_src=80:88:88:88:88:88,dl_dst=10:20:30:40:50:60,nw_src=192.168.0.1,nw_dst=192.168.1.2,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=8,tp_dst=10
+])
+
+AT_CHECK([ovs-ofctl parse-pcap p14.pcap], [0], [dnl
+arp,in_port=ANY,vlan_tci=0x0000,dl_src=80:88:88:88:88:88,dl_dst=10:20:30:40:50:60,arp_spa=192.168.0.1,arp_tpa=192.168.1.2,arp_op=1,arp_sha=80:88:88:88:88:88,arp_tha=10:20:30:40:50:60
+arp,in_port=ANY,vlan_tci=0x0000,dl_src=80:88:88:88:88:88,dl_dst=10:20:30:40:50:60,arp_spa=192.168.0.1,arp_tpa=192.168.1.2,arp_op=2,arp_sha=80:88:88:88:88:88,arp_tha=10:20:30:40:50:60
+udp,in_port=ANY,vlan_tci=0x0000,dl_src=80:88:88:88:88:88,dl_dst=10:20:30:40:50:60,nw_src=192.168.0.1,nw_dst=192.168.1.2,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=8,tp_dst=10
+arp,in_port=ANY,dl_vlan=99,dl_vlan_pcp=7,dl_src=80:88:88:88:88:88,dl_dst=10:20:30:40:50:60,arp_spa=192.168.0.1,arp_tpa=192.168.1.2,arp_op=1,arp_sha=80:88:88:88:88:88,arp_tha=10:20:30:40:50:60
+arp,in_port=ANY,vlan_tci=0x0000,dl_src=80:88:88:88:88:88,dl_dst=10:20:30:40:50:60,arp_spa=192.168.0.1,arp_tpa=192.168.1.2,arp_op=2,arp_sha=80:88:88:88:88:88,arp_tha=10:20:30:40:50:60
+udp,in_port=ANY,vlan_tci=0x0000,dl_src=80:88:88:88:88:88,dl_dst=10:20:30:40:50:60,nw_src=192.168.0.1,nw_dst=192.168.1.2,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=8,tp_dst=10
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
 AT_SETUP([ofproto-dpif - VLAN handling])
 OVS_VSWITCHD_START(
   [set Bridge br0 fail-mode=standalone -- \
diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in
index c37ced3..dc704b6 100644
--- a/utilities/ovs-ofctl.8.in
+++ b/utilities/ovs-ofctl.8.in
@@ -1740,6 +1740,32 @@ nf_conntrack module loaded.
 .
 .RE
 .
+.IP \fBarp(\fIaction\fR...\fB)\fR
+Transforms an IPv4 packet into an ARP packet, then executes each
+nested \fIaction\fR on the ARP packet.  Actions following the
+\fBarp\fR action apply to the original packet, not the ARP packet,
+discarding any changes that the nested actions made to packet data or
+metadata.
+.
+.IP
+The initial values of fields in the ARP packet are derived as follows:
+.RS
+.IP "\fBdl_src\fR and \fBdl_dst\fR"
+Unchanged from the IPv4 packet.
+.IP "\fBdl_type\fR"
+0x0806 (ARP)
+.IP "\fBnw_proto\fR (ARP opcode)"
+1 (ARP request)
+.IP "\fBarp_sha\fR"
+Copied from the Ethernet source address.
+.IP "\fBarp_spa\fR"
+Copied from the IP source address.
+.IP "\fBarp_tha\fR"
+Copied from the Ethernet destination address.
+.IP "\fBarp_tpa\fR"
+Copied from the IP destination address.
+.RE
+.
 .IP \fBdec_ttl\fR
 .IQ \fBdec_ttl(\fIid1\fR[\fB,\fIid2\fR]...\fB)\fR
 Decrement TTL of IPv4 packet or hop limit of IPv6 packet.  If the
-- 
2.1.3

_______________________________________________
dev mailing list
dev@openvswitch.org
http://openvswitch.org/mailman/listinfo/dev

Reply via email to