An upcoming commit will use this as a building block in adding ARP support
to the OVN L3 logical router implementation.

Signed-off-by: Ben Pfaff <b...@ovn.org>
---
 lib/ofp-actions.h               |  30 +++----
 lib/packets.c                   |  38 +++++----
 lib/packets.h                   |   3 +-
 ovn/controller/ovn-controller.c |   4 +-
 ovn/controller/pinctrl.c        | 174 ++++++++++++++++++++++++++++++++--------
 ovn/controller/pinctrl.h        |   5 +-
 ovn/lib/actions.c               |  57 ++++++++++++-
 ovn/lib/actions.h               |  18 ++++-
 ovn/ovn-sb.xml                  |  21 +++--
 tests/ovn.at                    |   3 +
 10 files changed, 277 insertions(+), 76 deletions(-)

diff --git a/lib/ofp-actions.h b/lib/ofp-actions.h
index 24143d3..55058ef 100644
--- a/lib/ofp-actions.h
+++ b/lib/ofp-actions.h
@@ -254,20 +254,22 @@ struct ofpact_output {
  *
  * Used for NXAST_CONTROLLER. */
 struct ofpact_controller {
-    struct ofpact ofpact;
-    uint16_t max_len;           /* Maximum length to send to controller. */
-    uint16_t controller_id;     /* Controller ID to send packet-in. */
-    enum ofp_packet_in_reason reason; /* Reason to put in packet-in. */
-
-    /* If true, this action freezes packet traversal of the OpenFlow tables and
-     * adds a continuation to the packet-in message, that a controller can use
-     * to resume that traversal. */
-    bool pause;
-
-    /* Arbitrary data to include in the packet-in message (currently, only in
-     * NXT_PACKET_IN2). */
-    uint16_t userdata_len;
-    uint8_t userdata[];
+    OFPACT_PADDED_MEMBERS(
+        struct ofpact ofpact;
+        uint16_t max_len;   /* Max length to send to controller. */
+        uint16_t controller_id; /* Controller ID to send packet-in. */
+        enum ofp_packet_in_reason reason; /* Reason to put in packet-in. */
+
+        /* If true, this action freezes packet traversal of the OpenFlow
+         * tables and adds a continuation to the packet-in message, that
+         * a controller can use to resume that traversal. */
+        bool pause;
+
+        /* Arbitrary data to include in the packet-in message (currently,
+         * only in NXT_PACKET_IN2). */
+        uint16_t userdata_len;
+    );
+    uint8_t userdata[0];
 };
 
 /* OFPACT_ENQUEUE.
diff --git a/lib/packets.c b/lib/packets.c
index d82341d..b693f78 100644
--- a/lib/packets.c
+++ b/lib/packets.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014, 2015 Nicira, Inc.
+ * Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -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 f1445de..119c4b7 100644
--- a/lib/packets.h
+++ b/lib/packets.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 Nicira, Inc.
+ * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Nicira, 
Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -1062,6 +1062,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/ovn/controller/ovn-controller.c b/ovn/controller/ovn-controller.c
index 3638342..9799a19 100644
--- a/ovn/controller/ovn-controller.c
+++ b/ovn/controller/ovn-controller.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2015 Nicira, Inc.
+/* Copyright (c) 2015, 2016 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -297,7 +297,7 @@ main(int argc, char *argv[])
 
             enum mf_field_id mff_ovn_geneve = ofctrl_run(br_int);
 
-            pinctrl_run(&ctx, br_int);
+            pinctrl_run(br_int);
 
             struct hmap flow_table = HMAP_INITIALIZER(&flow_table);
             lflow_run(&ctx, &flow_table, &ct_zones, &local_datapaths);
diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c
index 9e94425..70088f6 100644
--- a/ovn/controller/pinctrl.c
+++ b/ovn/controller/pinctrl.c
@@ -15,11 +15,16 @@
  */
 
 #include <config.h>
-#include "dirs.h"
+
 #include "pinctrl.h"
+
+#include "dirs.h"
+#include "dp-packet.h"
+#include "ofp-actions.h"
 #include "ofp-msgs.h"
 #include "ofp-print.h"
 #include "ofp-util.h"
+#include "ovn/lib/actions.h"
 #include "rconn.h"
 #include "openvswitch/vlog.h"
 #include "socket-util.h"
@@ -51,14 +56,19 @@ queue_msg(struct ofpbuf *msg)
     return xid;
 }
 
+/* Sets up 'swconn', a newly (re)connected connection to a switch. */
 static void
-get_switch_config(struct rconn *swconn)
+pinctrl_setup(struct rconn *swconn)
 {
-    struct ofpbuf *request;
+    /* Fetch the switch configuration.  The response later will allow us to
+     * change the miss_send_len to UINT16_MAX, so that we can enable
+     * asynchronous messages. */
+    queue_msg(ofpraw_alloc(OFPRAW_OFPT_GET_CONFIG_REQUEST,
+                           rconn_get_version(swconn), 0));
 
-    request = ofpraw_alloc(OFPRAW_OFPT_GET_CONFIG_REQUEST,
-                           rconn_get_version(swconn), 0);
-    queue_msg(request);
+    /* Set a packet-in format that supports userdata.  */
+    queue_msg(ofputil_make_set_packet_in_format(rconn_get_version(swconn),
+                                                NXPIF_NXT_PACKET_IN2));
 }
 
 static void
@@ -71,35 +81,135 @@ set_switch_config(struct rconn *swconn,
 }
 
 static void
-process_packet_in(struct controller_ctx *ctx OVS_UNUSED,
-                  const struct ofp_header *msg)
+pinctrl_handle_arp(const struct flow *ip_flow, struct ofpbuf *userdata)
 {
-    struct ofputil_packet_in pin;
+    /* Compose an ARP packet. */
+    uint64_t packet_stub[128 / 8];
+    struct dp_packet packet;
+    dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
+    compose_arp__(&packet);
+
+    struct eth_header *eth = dp_packet_l2(&packet);
+    eth->eth_dst = ip_flow->dl_dst;
+    eth->eth_src = ip_flow->dl_src;
+
+    struct arp_eth_header *arp = dp_packet_l3(&packet);
+    arp->ar_op = htons(ARP_OP_REQUEST);
+    arp->ar_sha = ip_flow->dl_src;
+    put_16aligned_be32(&arp->ar_spa, ip_flow->nw_src);
+    arp->ar_tha = eth_addr_zero;
+    put_16aligned_be32(&arp->ar_tpa, ip_flow->nw_dst);
+
+    if (ip_flow->vlan_tci) {
+        eth_push_vlan(&packet, htons(ETH_TYPE_VLAN_8021Q), ip_flow->vlan_tci);
+    }
+
+    /* Compose actions.
+     *
+     * First, add actions to restore the metadata, then add actions from
+     * 'userdata'.
+     */
+    uint64_t ofpacts_stub[1024 / 8];
+    struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub);
+    enum ofp_version version = rconn_get_version(swconn);
+
+    for (int id = 0; id < MFF_N_IDS; id++) {
+        const struct mf_field *field = mf_from_id(id);
+
+        if (field->prereqs == MFP_NONE
+            && field->writable
+            && id != MFF_IN_PORT && id != MFF_IN_PORT_OXM
+            && mf_is_set(field, ip_flow))
+        {
+            struct ofpact_set_field *sf = ofpact_put_SET_FIELD(&ofpacts);
+            sf->field = field;
+            sf->flow_has_vlan = false;
+            mf_get_value(field, ip_flow, &sf->value);
+            bitwise_one(&sf->mask, sizeof sf->mask, 0, field->n_bits);
+        }
+    }
+    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 arp actions (%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,
+    };
+    enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version);
+    queue_msg(ofputil_encode_packet_out(&po, proto));
+
+exit:
+    dp_packet_uninit(&packet);
+    ofpbuf_uninit(&ofpacts);
+}
 
-    if (ofputil_decode_packet_in(msg, true, &pin, NULL, NULL, NULL) != 0) {
+static void
+process_packet_in(const struct ofp_header *msg)
+{
+    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+
+    struct ofputil_packet_in pin;
+    enum ofperr error = ofputil_decode_packet_in(msg, true, &pin,
+                                                 NULL, NULL, NULL);
+    if (error) {
+        VLOG_WARN_RL(&rl, "error decoding packet-in: %s",
+                     ofperr_to_string(error));
         return;
     }
     if (pin.reason != OFPR_ACTION) {
         return;
     }
 
-    /* XXX : process the received packet */
+    struct ofpbuf userdata = ofpbuf_const_initializer(pin.userdata,
+                                                      pin.userdata_len);
+    const struct action_header *ah = ofpbuf_pull(&userdata, sizeof *ah);
+    if (!ah) {
+        VLOG_WARN_RL(&rl, "packet-in userdata lacks action header");
+        return;
+    }
+
+    struct dp_packet packet;
+    dp_packet_use_const(&packet, pin.packet, pin.packet_len);
+    struct flow headers;
+    flow_extract(&packet, &headers);
+
+    const struct flow *md = &pin.flow_metadata.flow;
+    switch (ntohl(ah->opcode)) {
+    case ACTION_OPCODE_ARP:
+        pinctrl_handle_arp(&headers, &userdata);
+        break;
+
+    default:
+        VLOG_WARN_RL(&rl, "unrecognized packet-in command %#"PRIx32,
+                     md->regs[0]);
+        break;
+    }
 }
 
 static void
-pinctrl_recv(struct controller_ctx *ctx, const struct ofp_header *oh,
-             enum ofptype type)
+pinctrl_recv(const struct ofp_header *oh, enum ofptype type)
 {
     if (type == OFPTYPE_ECHO_REQUEST) {
         queue_msg(make_echo_reply(oh));
     } else if (type == OFPTYPE_GET_CONFIG_REPLY) {
+        /* Enable asynchronous messages. */
         struct ofputil_switch_config config;
 
         ofputil_decode_get_config_reply(oh, &config);
         config.miss_send_len = UINT16_MAX;
         set_switch_config(swconn, &config);
     } else if (type == OFPTYPE_PACKET_IN) {
-        process_packet_in(ctx, oh);
+        process_packet_in(oh);
     } else if (type != OFPTYPE_ECHO_REPLY && type != OFPTYPE_BARRIER_REPLY) {
         if (VLOG_IS_DBG_ENABLED()) {
             static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(30, 300);
@@ -113,7 +223,7 @@ pinctrl_recv(struct controller_ctx *ctx, const struct 
ofp_header *oh,
 }
 
 void
-pinctrl_run(struct controller_ctx *ctx, const struct ovsrec_bridge *br_int)
+pinctrl_run(const struct ovsrec_bridge *br_int)
 {
     if (br_int) {
         char *target;
@@ -130,27 +240,27 @@ pinctrl_run(struct controller_ctx *ctx, const struct 
ovsrec_bridge *br_int)
 
     rconn_run(swconn);
 
-    if (!rconn_is_connected(swconn)) {
-        return;
-    }
+    if (rconn_is_connected(swconn)) {
+        if (conn_seq_no != rconn_get_connection_seqno(swconn)) {
+            pinctrl_setup(swconn);
+            conn_seq_no = rconn_get_connection_seqno(swconn);
+        }
 
-    if (conn_seq_no != rconn_get_connection_seqno(swconn)) {
-        get_switch_config(swconn);
-        conn_seq_no = rconn_get_connection_seqno(swconn);
-    }
+        /* Process a limited number of messages per call. */
+        for (int i = 0; i < 50; i++) {
+            struct ofpbuf *msg = rconn_recv(swconn);
+            if (!msg) {
+                break;
+            }
 
-    struct ofpbuf *msg = rconn_recv(swconn);
+            const struct ofp_header *oh = msg->data;
+            enum ofptype type;
 
-    if (!msg) {
-        return;
+            ofptype_decode(&type, oh);
+            pinctrl_recv(oh, type);
+            ofpbuf_delete(msg);
+        }
     }
-
-    const struct ofp_header *oh = msg->data;
-    enum ofptype type;
-
-    ofptype_decode(&type, oh);
-    pinctrl_recv(ctx, oh, type);
-    ofpbuf_delete(msg);
 }
 
 void
diff --git a/ovn/controller/pinctrl.h b/ovn/controller/pinctrl.h
index fd279ff..fca6b52 100644
--- a/ovn/controller/pinctrl.h
+++ b/ovn/controller/pinctrl.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2015 Nicira, Inc.
+/* Copyright (c) 2015, 2016 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -25,8 +25,7 @@ struct controller_ctx;
 
 /* Interface for OVN main loop. */
 void pinctrl_init(void);
-void pinctrl_run(struct controller_ctx *ctx,
-                 const struct ovsrec_bridge *br_int);
+void pinctrl_run(const struct ovsrec_bridge *br_int);
 void pinctrl_wait(void);
 void pinctrl_destroy(void);
 
diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c
index 42e7f3b..f9f7ef7 100644
--- a/ovn/lib/actions.c
+++ b/ovn/lib/actions.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015 Nicira, Inc.
+ * Copyright (c) 2015, 2016 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -184,6 +184,59 @@ add_prerequisite(struct action_context *ctx, const char 
*prerequisite)
 }
 
 static void
+parse_arp_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. */
+    size_t oc_offset = ctx->ofpacts->size;
+    ofpact_put_CONTROLLER(ctx->ofpacts);
+
+    struct action_header ah = { .opcode = htonl(ACTION_OPCODE_ARP) };
+    ofpbuf_put(ctx->ofpacts, &ah, sizeof ah);
+
+    ofpacts_put_openflow_actions(inner_ofpacts.data, inner_ofpacts.size,
+                                 ctx->ofpacts, OFP13_VERSION);
+
+    struct ofpact_controller *oc = ofpbuf_at_assert(ctx->ofpacts, oc_offset,
+                                                    sizeof *oc);
+    ctx->ofpacts->header = oc;
+    oc->max_len = UINT16_MAX;
+    oc->reason = OFPR_ACTION;
+    oc->userdata_len = ctx->ofpacts->size - (oc_offset + sizeof *oc);
+    ofpact_finish(ctx->ofpacts, &oc->ofpact);
+
+    /* Restore prerequisites. */
+    expr_destroy(ctx->prereqs);
+    ctx->prereqs = outer_prereqs;
+    add_prerequisite(ctx, "ip4");
+
+    /* Free memory. */
+    ofpbuf_uninit(&inner_ofpacts);
+}
+
+static void
 emit_ct(struct action_context *ctx, bool recirc_next, bool commit)
 {
     struct ofpact_conntrack *ct = ofpact_put_CT(ctx->ofpacts);
@@ -239,6 +292,8 @@ parse_action(struct action_context *ctx)
         emit_ct(ctx, true, false);
     } else if (lexer_match_id(ctx->lexer, "ct_commit")) {
         emit_ct(ctx, false, true);
+    } else if (lexer_match_id(ctx->lexer, "arp")) {
+        parse_arp_action(ctx);
     } else {
         action_syntax_error(ctx, "expecting action");
     }
diff --git a/ovn/lib/actions.h b/ovn/lib/actions.h
index 2c3644a..5b2367c 100644
--- a/ovn/lib/actions.h
+++ b/ovn/lib/actions.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015 Nicira, Inc.
+ * Copyright (c) 2015, 2016 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
 
 #include <stdint.h>
 #include "compiler.h"
+#include "util.h"
 
 struct expr;
 struct lexer;
@@ -26,6 +27,21 @@ struct ofpbuf;
 struct shash;
 struct simap;
 
+enum action_opcode {
+    /* "arp { ...actions... }".
+     *
+     * The actions, in OpenFlow 1.3 format, follow the action_header.
+     */
+    ACTION_OPCODE_ARP,
+};
+
+/* Header. */
+struct action_header {
+    ovs_be32 opcode;            /* One of ACTION_OPCODE_* */
+    uint8_t pad[4];
+};
+BUILD_ASSERT_DECL(sizeof(struct action_header) == 8);
+
 struct action_params {
     /* A table of "struct expr_symbol"s to support (as one would provide to
      * expr_parse()). */
diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml
index 1ea35d5..dd248b4 100644
--- a/ovn/ovn-sb.xml
+++ b/ovn/ovn-sb.xml
@@ -907,14 +907,6 @@
           Commit the flow to the connection tracking entry associated
           with it by a previous call to <code>ct_next</code>.
         </dd>
-      </dl>
-
-      <p>
-        The following actions will likely be useful later, but they have not
-        been thought out carefully.
-      </p>
-
-      <dl>
 
         <dt><code>arp { <var>action</var>; </code>...<code> };</code></dt>
         <dd>
@@ -942,9 +934,22 @@
             <li><code>arp.tpa</code> copied from <code>ip4.dst</code></li>
           </ul>
 
+          <p>
+            The ARP packet has the same VLAN header, if any, as the IP packet
+            it replaces.
+          </p>
+
           <p><b>Prerequisite:</b> <code>ip4</code></p>
         </dd>
 
+      </dl>
+
+      <p>
+        The following actions will likely be useful later, but they have not
+        been thought out carefully.
+      </p>
+
+      <dl>
         <dt><code>icmp4 { <var>action</var>; </code>...<code> };</code></dt>
         <dd>
           <p>
diff --git a/tests/ovn.at b/tests/ovn.at
index 0917e8d..5e49767 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -507,6 +507,9 @@ ip.ttl => Syntax error at end of input expecting `--'.
 ct_next; => actions=ct(table=27,zone=NXM_NX_REG5[0..15]), prereqs=ip
 ct_commit; => actions=ct(commit,zone=NXM_NX_REG5[0..15]), prereqs=ip
 
+# arp
+arp { eth.dst = ff:ff:ff:ff:ff:ff; output; }; => 
actions=controller(userdata=00.00.00.00.00.00.00.00.00.19.00.10.80.00.06.06.ff.ff.ff.ff.ff.ff.00.00.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00),
 prereqs=ip4
+
 # 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
-- 
2.1.3

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

Reply via email to