Merge branch 'mainstream'
authorGiuseppe Lettieri <g.lettieri@iet.unipi.it>
Fri, 25 Oct 2013 20:15:00 +0000 (22:15 +0200)
committerGiuseppe Lettieri <g.lettieri@iet.unipi.it>
Fri, 25 Oct 2013 20:15:00 +0000 (22:15 +0200)
139 files changed:
AUTHORS
FAQ
INSTALL
INSTALL.RHEL
Makefile.am
NEWS
OPENFLOW-1.1+
README-OFTest
acinclude.m4
configure.ac
datapath/datapath.c
datapath/datapath.h
datapath/dp_notify.c
datapath/flow.c
datapath/flow.h
datapath/flow_table.c
datapath/flow_table.h
datapath/linux/Modules.mk
datapath/linux/compat/include/linux/compiler-gcc.h
datapath/linux/compat/include/linux/netdevice.h
datapath/linux/compat/include/net/gre.h
datapath/linux/compat/include/net/sock.h [new file with mode: 0644]
datapath/linux/compat/vxlan.c
datapath/vport-netdev.c
datapath/vport-netdev.h
debian/automake.mk
debian/changelog
debian/control
debian/openvswitch-switch.manpages
debian/openvswitch-vtep.default [new file with mode: 0644]
debian/openvswitch-vtep.dirs [new file with mode: 0644]
debian/openvswitch-vtep.init [new file with mode: 0644]
debian/openvswitch-vtep.install [new file with mode: 0644]
debian/openvswitch-vtep.manpages [new file with mode: 0644]
include/linux/openvswitch.h
include/openflow/nicira-ext.h
include/openflow/openflow-1.0.h
lib/.gitignore
lib/automake.mk
lib/bfd.c
lib/bfd.h
lib/cfm.c
lib/cfm.h
lib/classifier.h
lib/dpif-linux.c
lib/dpif-netdev.c
lib/dpif.h
lib/flow.c
lib/flow.h
lib/heap.c
lib/heap.h
lib/match.c
lib/match.h
lib/meta-flow.c
lib/meta-flow.h
lib/netdev-dummy.c
lib/nx-match.c
lib/nx-match.h
lib/odp-execute.c
lib/odp-util.c
lib/ofp-actions.c
lib/ofp-actions.h
lib/ofp-msgs.c
lib/ofp-msgs.h
lib/ofp-parse.c
lib/ofp-parse.h
lib/ofp-print.c
lib/ofp-util.c
lib/ofp-util.def
lib/ofp-util.h
lib/ovsdb-idl.c
lib/socket-util.c
lib/vtep-idl.ann [new file with mode: 0644]
m4/openvswitch.m4
manpages.mk
ofproto/connmgr.c
ofproto/connmgr.h
ofproto/fail-open.c
ofproto/fail-open.h
ofproto/ofproto-dpif-monitor.c
ofproto/ofproto-dpif-monitor.h
ofproto/ofproto-dpif-upcall.c
ofproto/ofproto-dpif-xlate.c
ofproto/ofproto-dpif.c
ofproto/ofproto-dpif.h
ofproto/ofproto-provider.h
ofproto/ofproto.c
ofproto/pinsched.c
ofproto/pinsched.h
ovsdb/ovsdb-dot.in
python/ovs/jsonrpc.py
python/ovs/socket_util.py
python/ovs/vlog.py
rhel/README.RHEL
rhel/etc_sysconfig_network-scripts_ifdown-ovs
rhel/etc_sysconfig_network-scripts_ifup-ovs
rhel/openvswitch-fedora.spec.in
rhel/openvswitch.spec.in
tests/atlocal.in
tests/automake.mk
tests/bfd.at
tests/flowgen.pl
tests/interface-reconfigure.at
tests/learn.at
tests/library.at
tests/odp.at
tests/ofp-actions.at
tests/ofproto-dpif.at
tests/ovs-monitor-ipsec.at
tests/ovs-ofctl.at
tests/ovs-vsctl.at
tests/ovsdb-server.at
tests/run-oftest
tests/test-bundle.c
tests/test-flows.c
tests/test-multipath.c
tests/test-odp.c
tests/test-unix-socket.py
tests/testsuite.at
tests/vtep-ctl.at [new file with mode: 0644]
utilities/ovs-controller.c
utilities/ovs-dpctl-top.in
utilities/ovs-dpctl.8.in
utilities/ovs-dpctl.c
utilities/ovs-ofctl.8.in
utilities/ovs-ofctl.c
vswitchd/.gitignore
vswitchd/automake.mk
vswitchd/vswitch.gv [deleted file]
vswitchd/vswitch.pic [deleted file]
vtep/.gitignore [new file with mode: 0644]
vtep/README.ovs-vtep [new file with mode: 0644]
vtep/automake.mk [new file with mode: 0644]
vtep/ovs-vtep [new file with mode: 0755]
vtep/vtep-ctl.8.in [new file with mode: 0644]
vtep/vtep-ctl.c [new file with mode: 0644]
vtep/vtep.ovsschema [new file with mode: 0644]
vtep/vtep.xml [new file with mode: 0644]
xenserver/openvswitch-xen.spec.in

diff --git a/AUTHORS b/AUTHORS
index 7892328..ac7886b 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -3,6 +3,7 @@ signed off on commits in the Open vSwitch version control repository.
 
 Aaron Rosen             arosen@clemson.edu
 Alexandru Copot         alex.mihai.c@gmail.com
+Alexei Starovoitov      ast@plumgrid.com
 Alexey I. Froloff       raorn@altlinux.org
 Alex Wang               alexw@nicira.com
 Andrew Evans            aevans@nicira.com
@@ -27,10 +28,12 @@ Daniel Roman            droman@nicira.com
 Danny Kukawka           danny.kukawka@bisect.de
 David Erickson          derickso@stanford.edu
 David S. Miller         davem@davemloft.net
+David Yang              davidy@vmware.com
 Devendra Naga           devendra.aaru@gmail.com
 Dominic Curran          dominic.curran@citrix.com
 Duffie Cooley           dcooley@nicira.com
 Ed Maste                emaste at freebsd.org
+Edouard Bourguignon     madko@linuxed.net
 Edward Tomasz NapieraÅ‚a trasz@freebsd.org
 Ethan Jackson           ethan@nicira.com
 Flavio Leitner          fbl@redhat.com
@@ -52,7 +55,7 @@ Jeremy Stribling        strib@nicira.com
 Jesse Gross             jesse@nicira.com
 Jing Ai                 jinga@google.com
 Joe Perches             joe@perches.com
-Joe Stringer            joe@wand.net.nz
+Joe Stringer            joestringer@nicira.com
 Jun Nakajima            jun.nakajima@intel.com
 Justin Pettit           jpettit@nicira.com
 Keith Amidon            keith@nicira.com
@@ -170,6 +173,7 @@ Jeongkeun Lee           jklee@hp.com
 Jian Qiu                swordqiu@gmail.com
 Joan Cirer              joan@ev0.net
 John Galgay             john@galgay.net
+John Hurley             john.hurley@netronome.com
 Kevin Mancuso           kevin.mancuso@rackspace.com
 Kiran Shanbhog          kiran@vmware.com
 Kirill Kabardin
diff --git a/FAQ b/FAQ
index d36495c..0a081ff 100644 (file)
--- a/FAQ
+++ b/FAQ
@@ -149,6 +149,7 @@ A: The following table lists the Linux kernel versions against which the
        1.10.x     2.6.18 to 3.8
        1.11.x     2.6.18 to 3.8
        2.0.x      2.6.32 to 3.10
+       2.1.x      2.6.32 to 3.11
 
    Open vSwitch userspace should also work with the Linux kernel module
    built into Linux 3.3 and later.
@@ -168,7 +169,7 @@ Q: Should userspace or kernel be upgraded first to minimize downtime?
    kernel version included in the same release or with the version
    from upstream Linux.  However, when upgrading between two releases
    of Open vSwitch it is best to migrate userspace first to reduce
-   the possbility of incompatibilities.
+   the possibility of incompatibilities.
 
 Q: What features are not available in the Open vSwitch kernel datapath
    that ships as part of the upstream Linux kernel?
@@ -197,6 +198,25 @@ A: Tunnel virtual ports are not supported, as described in the
    actions.  On Linux kernels before 2.6.39, maximum-sized VLAN packets
    may not be transmitted.
 
+Q: What happened to the bridge compatibility feature?
+
+A: Bridge compatibility was a feature of Open vSwitch 1.9 and earlier.
+   When it was enabled, Open vSwitch imitated the interface of the
+   Linux kernel "bridge" module.  This allowed users to drop Open
+   vSwitch into environments designed to use the Linux kernel bridge
+   module without adapting the environment to use Open vSwitch.
+
+   Open vSwitch 1.10 and later do not support bridge compatibility.
+   The feature was dropped because version 1.10 adopted a new internal
+   architecture that made bridge compatibility difficult to maintain.
+   Now that many environments use OVS directly, it would be rarely
+   useful in any case.
+
+   To use bridge compatibility, install OVS 1.9 or earlier, including
+   the accompanying kernel modules (both the main and bridge
+   compatibility modules), following the instructions that come with
+   the release.  Be sure to start the ovs-brcompatd daemon.
+
 
 Terminology
 -----------
diff --git a/INSTALL b/INSTALL
index 4c54b59..c8b1aa7 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -93,9 +93,8 @@ To run the unit tests, you also need:
     - Perl.  Version 5.10.1 is known to work.  Earlier versions should
       also work.
 
-If you modify the vswitchd database schema, then the E-R diagram in
-the ovs-vswitchd.conf.db(5) manpage will be updated properly only if
-you have the following:
+The ovs-vswitchd.conf.db(5) manpage will include an E-R diagram, in
+formats other than plain text, only if you have the following:
 
     - "dot" from graphviz (http://www.graphviz.org/).
 
index 26b47d9..9ab5384 100644 (file)
@@ -34,7 +34,7 @@ RHEL.  On RHEL 5, the default RPM source directory is
 
 2. Install build prerequisites:
 
-   yum install gcc make python-devel openssl-devel kernel-devel \
+   yum install gcc make python-devel openssl-devel kernel-devel, graphviz \
        kernel-debug-devel autoconf automake rpm-build redhat-rpm-config
 
 3. Some versions of the RHEL 6 kernel-devel package contain a broken
index 32e85d1..d8cc733 100644 (file)
@@ -27,10 +27,13 @@ endif
 # vSwitch, but it causes trouble if you switch from a version with
 # foo/__init__.py into an (older) version with plain foo.py, since
 # foo/__init__.pyc will cause Python to ignore foo.py.
-run_python = \
-       PYTHONDONTWRITEBYTECODE=yes \
-       PYTHONPATH=$(top_srcdir)/python:$$PYTHONPATH \
-       $(PYTHON)
+if INCLUDE_PYTHON_COMPAT
+run_python = PYTHONPATH=$(top_srcdir)/python:$(top_srcdir)/python/compat:$$PYTHONPATH
+else
+run_python = PYTHONPATH=$(top_srcdir)/python:$$PYTHONPATH
+endif
+run_python += PYTHONDONTWRITEBYTECODE=yes $(PYTHON)
+
 
 ALL_LOCAL =
 BUILT_SOURCES =
@@ -276,3 +279,4 @@ include python/automake.mk
 include python/compat/automake.mk
 include planetlab/automake.mk
 include tutorial/automake.mk
+include vtep/automake.mk
diff --git a/NEWS b/NEWS
index 94e0da9..50f20ad 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -4,10 +4,20 @@ Post-v2.0.0
      IANA-assigned numbers in a future release.  Consider updating
      your installations to specify port numbers instead of using the
      defaults.
+   - OpenFlow:
+     * The OpenFlow 1.1+ "Write-Actions" instruction is now supported.
+   - ovs-vswitchd.conf.db.5 man page will contain graphviz/dot
+     diagram only if graphviz package was installed at the build time.
+   - Support for Linux kernels up to 3.11
+   - ovs-dpctl:
+     The "show" command also displays mega flow mask stats.
 
 
-v2.0.0 - xx xxx xxxx
+v2.0.0 - 15 Oct 2013
 ---------------------
+    - The ovs-vswitchd process is no longer single-threaded.  Multiple
+      threads are now used to handle flow set up and asynchronous
+      logging.
     - OpenFlow:
       * Experimental support for OpenFlow 1.1 (in addition to 1.2 and
         1.3, which had experimental support in 1.10).
index 90f811f..f419803 100644 (file)
@@ -54,9 +54,6 @@ OpenFlow 1.1
 The list of remaining work items for OpenFlow 1.1 is below.  It is
 probably incomplete.
 
-    * Implement Write-Actions instruction.
-      [required for 1.1+]
-
     * The new in_phy_port field in OFPT_PACKET_IN needs some kind of
       implementation.  It has a sensible interpretation for tunnels
       but in general the physical port is not in the datapath for OVS
@@ -106,6 +103,11 @@ additional work specific to Openflow 1.2 are complete.  (This is based
 on the change log at the end of the OF1.2 spec.  I didn't compare the
 specs carefully yet.)
 
+    * Action translation needs some work to transform OpenFlow 1.1
+      field modification actions into OpenFlow 1.2+ "set-field"
+      actions, because OpenFlow 1.2 dropped support for the OF1.1
+      actions.
+
 OpenFlow 1.3
 ------------
 
@@ -114,16 +116,18 @@ following additional work.  (This is based on the change log at the
 end of the OF1.3 spec, reusing most of the section titles directly.  I
 didn't compare the specs carefully yet.)
 
-    * Send errors for unsupported multipart requests.
-      [required for OF1.3+]
-
     * Add support for multipart requests.
+      Currently we always report OFPBRC_MULTIPART_BUFFER_OVERFLOW.
       [optional for OF1.3+]
 
     * Add OFPMP_TABLE_FEATURES statistics.
       [optional for OF1.3+]
 
     * More flexible table miss support.
+      This requires the following.
+      - Change the default table-miss action (in the absense of table-miss
+        entry) from packet_in to drop for OF1.3+.  Decide what to do if
+        a switch is configured to support multiple OF versions.
       [required for OF1.3+]
 
     * IPv6 extension header handling support.  Fully implementing this
index 1b8516b..cdca06d 100644 (file)
@@ -50,6 +50,13 @@ default) and enable verbose logging:
 
     make check-oftest OFT=<oft-binary> OFTFLAGS='--verbose -T basic.Echo'
 
+If you use OFTest that does not include commit 4d1f3eb2c792 (oft:
+change default port to 6653), merged into the OFTest repository in
+October 2013, then you need to add an option to use the IETF-assigned
+controller port:
+
+    make check-oftest OFT=<oft-binary> OFTFLAGS='--port=6653'
+
 Interpreting OFTest Results
 ---------------------------
 
index c293d33..f987fa0 100644 (file)
@@ -134,10 +134,10 @@ AC_DEFUN([OVS_CHECK_LINUX], [
     AC_MSG_RESULT([$kversion])
 
     if test "$version" -ge 3; then
-       if test "$version" = 3 && test "$patchlevel" -le 10; then
+       if test "$version" = 3 && test "$patchlevel" -le 11; then
           : # Linux 3.x
        else
-         AC_ERROR([Linux kernel in $KBUILD is version $kversion, but version newer than 3.10.x is not supported])
+         AC_ERROR([Linux kernel in $KBUILD is version $kversion, but version newer than 3.11.x is not supported])
        fi
     else
        if test "$version" -le 1 || test "$patchlevel" -le 5 || test "$sublevel" -le 31; then
index d6596f9..b906681 100644 (file)
@@ -56,6 +56,7 @@ OVS_CHECK_LOGDIR
 OVS_CHECK_PYTHON
 OVS_CHECK_PYUIC4
 OVS_CHECK_OVSDBMONITOR
+OVS_CHECK_PYTHON_COMPAT
 OVS_CHECK_DOT
 OVS_CHECK_IF_PACKET
 OVS_CHECK_IF_DL
index 9e6df12..50ee6cd 100644 (file)
@@ -223,6 +223,7 @@ void ovs_dp_process_received_packet(struct vport *p, struct sk_buff *skb)
        struct dp_stats_percpu *stats;
        struct sw_flow_key key;
        u64 *stats_counter;
+       u32 n_mask_hit;
        int error;
 
        stats = this_cpu_ptr(dp->stats_percpu);
@@ -235,7 +236,7 @@ void ovs_dp_process_received_packet(struct vport *p, struct sk_buff *skb)
        }
 
        /* Look up flow. */
-       flow = ovs_flow_tbl_lookup(&dp->table, &key);
+       flow = ovs_flow_tbl_lookup(&dp->table, &key, &n_mask_hit);
        if (unlikely(!flow)) {
                struct dp_upcall_info upcall;
 
@@ -252,14 +253,15 @@ void ovs_dp_process_received_packet(struct vport *p, struct sk_buff *skb)
        OVS_CB(skb)->flow = flow;
        OVS_CB(skb)->pkt_key = &key;
 
-       stats_counter = &stats->n_hit;
-       ovs_flow_used(OVS_CB(skb)->flow, skb);
+       ovs_flow_stats_update(OVS_CB(skb)->flow, skb);
        ovs_execute_actions(dp, skb);
+       stats_counter = &stats->n_hit;
 
 out:
        /* Update datapath statistics. */
        u64_stats_update_begin(&stats->sync);
        (*stats_counter)++;
+       stats->n_mask_hit += n_mask_hit;
        u64_stats_update_end(&stats->sync);
 }
 
@@ -454,14 +456,6 @@ out:
        return err;
 }
 
-static void clear_stats(struct sw_flow *flow)
-{
-       flow->used = 0;
-       flow->tcp_flags = 0;
-       flow->packet_count = 0;
-       flow->byte_count = 0;
-}
-
 static int ovs_packet_cmd_execute(struct sk_buff *skb, struct genl_info *info)
 {
        struct ovs_header *ovs_header = info->userhdr;
@@ -566,13 +560,18 @@ static struct genl_ops dp_packet_genl_ops[] = {
        }
 };
 
-static void get_dp_stats(struct datapath *dp, struct ovs_dp_stats *stats)
+static void get_dp_stats(struct datapath *dp, struct ovs_dp_stats *stats,
+                        struct ovs_dp_megaflow_stats *mega_stats)
 {
        int i;
 
+       memset(mega_stats, 0, sizeof(*mega_stats));
+
        stats->n_flows = ovs_flow_tbl_count(&dp->table);
+       mega_stats->n_masks = ovs_flow_tbl_num_masks(&dp->table);
 
        stats->n_hit = stats->n_missed = stats->n_lost = 0;
+
        for_each_possible_cpu(i) {
                const struct dp_stats_percpu *percpu_stats;
                struct dp_stats_percpu local_stats;
@@ -588,6 +587,7 @@ static void get_dp_stats(struct datapath *dp, struct ovs_dp_stats *stats)
                stats->n_hit += local_stats.n_hit;
                stats->n_missed += local_stats.n_missed;
                stats->n_lost += local_stats.n_lost;
+               mega_stats->n_mask_hit += local_stats.n_mask_hit;
        }
 }
 
@@ -629,11 +629,9 @@ static int ovs_flow_cmd_fill_info(struct sw_flow *flow, struct datapath *dp,
 {
        const int skb_orig_len = skb->len;
        struct nlattr *start;
-       struct ovs_flow_stats stats;
+       struct sw_flow_stats flow_stats;
        struct ovs_header *ovs_header;
        struct nlattr *nla;
-       unsigned long used;
-       u8 tcp_flags;
        int err;
 
        ovs_header = genlmsg_put(skb, portid, seq, &dp_flow_genl_family, flags, cmd);
@@ -662,24 +660,24 @@ static int ovs_flow_cmd_fill_info(struct sw_flow *flow, struct datapath *dp,
 
        nla_nest_end(skb, nla);
 
-       spin_lock_bh(&flow->lock);
-       used = flow->used;
-       stats.n_packets = flow->packet_count;
-       stats.n_bytes = flow->byte_count;
-       tcp_flags = flow->tcp_flags;
-       spin_unlock_bh(&flow->lock);
-
-       if (used &&
-           nla_put_u64(skb, OVS_FLOW_ATTR_USED, ovs_flow_used_time(used)))
+       ovs_flow_stats_get(flow, &flow_stats);
+       if (flow_stats.used &&
+           nla_put_u64(skb, OVS_FLOW_ATTR_USED, ovs_flow_used_time(flow_stats.used)))
                goto nla_put_failure;
 
-       if (stats.n_packets &&
-           nla_put(skb, OVS_FLOW_ATTR_STATS,
-                   sizeof(struct ovs_flow_stats), &stats))
-               goto nla_put_failure;
+       if (flow_stats.packet_count) {
+               struct ovs_flow_stats stats = {
+                       .n_packets = flow_stats.packet_count,
+                       .n_bytes = flow_stats.byte_count,
+               };
 
-       if (tcp_flags &&
-           nla_put_u8(skb, OVS_FLOW_ATTR_TCP_FLAGS, tcp_flags))
+               if (nla_put(skb, OVS_FLOW_ATTR_STATS,
+                           sizeof(struct ovs_flow_stats), &stats))
+                       goto nla_put_failure;
+       }
+
+       if (flow_stats.tcp_flags &&
+           nla_put_u8(skb, OVS_FLOW_ATTR_TCP_FLAGS, flow_stats.tcp_flags))
                goto nla_put_failure;
 
        /* If OVS_FLOW_ATTR_ACTIONS doesn't fit, skip dumping the actions if
@@ -746,6 +744,14 @@ static struct sk_buff *ovs_flow_cmd_build_info(struct sw_flow *flow,
        return skb;
 }
 
+static struct sw_flow *__ovs_flow_tbl_lookup(struct flow_table *tbl,
+                                             const struct sw_flow_key *key)
+{
+       u32 __always_unused n_mask_hit;
+
+       return ovs_flow_tbl_lookup(tbl, key, &n_mask_hit);
+}
+
 static int ovs_flow_cmd_new_or_set(struct sk_buff *skb, struct genl_info *info)
 {
        struct nlattr **a = info->attrs;
@@ -796,7 +802,7 @@ static int ovs_flow_cmd_new_or_set(struct sk_buff *skb, struct genl_info *info)
                goto err_unlock_ovs;
 
        /* Check if this is a duplicate flow */
-       flow = ovs_flow_tbl_lookup(&dp->table, &key);
+       flow = __ovs_flow_tbl_lookup(&dp->table, &key);
        if (!flow) {
                /* Bail out if we're not allowed to create a new flow. */
                error = -ENOENT;
@@ -809,7 +815,6 @@ static int ovs_flow_cmd_new_or_set(struct sk_buff *skb, struct genl_info *info)
                        error = PTR_ERR(flow);
                        goto err_unlock_ovs;
                }
-               clear_stats(flow);
 
                flow->key = masked_key;
                flow->unmasked_key = key;
@@ -855,11 +860,8 @@ static int ovs_flow_cmd_new_or_set(struct sk_buff *skb, struct genl_info *info)
                                               info->snd_seq, OVS_FLOW_CMD_NEW);
 
                /* Clear stats. */
-               if (a[OVS_FLOW_ATTR_CLEAR]) {
-                       spin_lock_bh(&flow->lock);
-                       clear_stats(flow);
-                       spin_unlock_bh(&flow->lock);
-               }
+               if (a[OVS_FLOW_ATTR_CLEAR])
+                       ovs_flow_stats_clear(flow);
        }
        ovs_unlock();
 
@@ -908,7 +910,7 @@ static int ovs_flow_cmd_get(struct sk_buff *skb, struct genl_info *info)
                goto unlock;
        }
 
-       flow = ovs_flow_tbl_lookup(&dp->table, &key);
+       flow = __ovs_flow_tbl_lookup(&dp->table, &key);
        if (!flow || !ovs_flow_cmp_unmasked_key(flow, &match)) {
                err = -ENOENT;
                goto unlock;
@@ -956,7 +958,7 @@ static int ovs_flow_cmd_del(struct sk_buff *skb, struct genl_info *info)
        if (err)
                goto unlock;
 
-       flow = ovs_flow_tbl_lookup(&dp->table, &key);
+       flow = __ovs_flow_tbl_lookup(&dp->table, &key);
        if (!flow || !ovs_flow_cmp_unmasked_key(flow, &match)) {
                err = -ENOENT;
                goto unlock;
@@ -1070,6 +1072,7 @@ static size_t ovs_dp_cmd_msg_size(void)
 
        msgsize += nla_total_size(IFNAMSIZ);
        msgsize += nla_total_size(sizeof(struct ovs_dp_stats));
+       msgsize += nla_total_size(sizeof(struct ovs_dp_megaflow_stats));
 
        return msgsize;
 }
@@ -1079,6 +1082,7 @@ static int ovs_dp_cmd_fill_info(struct datapath *dp, struct sk_buff *skb,
 {
        struct ovs_header *ovs_header;
        struct ovs_dp_stats dp_stats;
+       struct ovs_dp_megaflow_stats dp_megaflow_stats;
        int err;
 
        ovs_header = genlmsg_put(skb, portid, seq, &dp_datapath_genl_family,
@@ -1094,8 +1098,14 @@ static int ovs_dp_cmd_fill_info(struct datapath *dp, struct sk_buff *skb,
        if (err)
                goto nla_put_failure;
 
-       get_dp_stats(dp, &dp_stats);
-       if (nla_put(skb, OVS_DP_ATTR_STATS, sizeof(struct ovs_dp_stats), &dp_stats))
+       get_dp_stats(dp, &dp_stats, &dp_megaflow_stats);
+       if (nla_put(skb, OVS_DP_ATTR_STATS, sizeof(struct ovs_dp_stats),
+                       &dp_stats))
+               goto nla_put_failure;
+
+       if (nla_put(skb, OVS_DP_ATTR_MEGAFLOW_STATS,
+                       sizeof(struct ovs_dp_megaflow_stats),
+                       &dp_megaflow_stats))
                goto nla_put_failure;
 
        return genlmsg_end(skb, ovs_header);
index 64920de..879a830 100644 (file)
  * @n_lost: Number of received packets that had no matching flow in the flow
  * table that could not be sent to userspace (normally due to an overflow in
  * one of the datapath's queues).
+ * @n_mask_hit: Number of masks looked up for flow match.
+ *   @n_mask_hit / (@n_hit + @n_missed)  will be the average masks looked
+ *   up per packet.
  */
 struct dp_stats_percpu {
        u64 n_hit;
        u64 n_missed;
        u64 n_lost;
+       u64 n_mask_hit;
        struct u64_stats_sync sync;
 };
 
index b5178fc..0b22d0c 100644 (file)
@@ -66,8 +66,7 @@ void ovs_dp_notify_wq(struct work_struct *work)
                                        continue;
 
                                netdev_vport = netdev_vport_priv(vport);
-                               if (netdev_vport->dev->reg_state == NETREG_UNREGISTERED ||
-                                   netdev_vport->dev->reg_state == NETREG_UNREGISTERING)
+                               if (!(ovs_netdev_get_vport(netdev_vport->dev)))
                                        dp_detach_port_notify(vport);
                        }
                }
@@ -79,7 +78,7 @@ static int dp_device_event(struct notifier_block *unused, unsigned long event,
                           void *ptr)
 {
        struct ovs_net *ovs_net;
-       struct net_device *dev = ptr;
+       struct net_device *dev = netdev_notifier_info_to_dev(ptr);
        struct vport *vport = NULL;
 
        if (!ovs_is_internal_dev(dev))
@@ -89,6 +88,10 @@ static int dp_device_event(struct notifier_block *unused, unsigned long event,
                return NOTIFY_DONE;
 
        if (event == NETDEV_UNREGISTER) {
+               /* upper_dev_unlink and decrement promisc immediately */
+               ovs_netdev_detach_dev(vport);
+
+               /* schedule vport destroy, dev_put and genl notification */
                ovs_net = net_generic(dev_net(dev), ovs_net_id);
                queue_work(system_wq, &ovs_net->dp_notify_work);
        }
index faa4e15..2193a33 100644 (file)
@@ -35,6 +35,7 @@
 #include <linux/ip.h>
 #include <linux/ipv6.h>
 #include <linux/sctp.h>
+#include <linux/smp.h>
 #include <linux/tcp.h>
 #include <linux/udp.h>
 #include <linux/icmp.h>
@@ -62,8 +63,9 @@ u64 ovs_flow_used_time(unsigned long flow_jiffies)
 #define TCP_FLAGS_OFFSET 13
 #define TCP_FLAG_MASK 0x3f
 
-void ovs_flow_used(struct sw_flow *flow, struct sk_buff *skb)
+void ovs_flow_stats_update(struct sw_flow *flow, struct sk_buff *skb)
 {
+       struct sw_flow_stats *stats = &flow->stats[smp_processor_id()];
        u8 tcp_flags = 0;
 
        if ((flow->key.eth.type == htons(ETH_P_IP) ||
@@ -74,12 +76,64 @@ void ovs_flow_used(struct sw_flow *flow, struct sk_buff *skb)
                tcp_flags = *(tcp + TCP_FLAGS_OFFSET) & TCP_FLAG_MASK;
        }
 
-       spin_lock(&flow->lock);
-       flow->used = jiffies;
-       flow->packet_count++;
-       flow->byte_count += skb->len;
-       flow->tcp_flags |= tcp_flags;
-       spin_unlock(&flow->lock);
+       spin_lock(&stats->lock);
+       stats->used = jiffies;
+       stats->packet_count++;
+       stats->byte_count += skb->len;
+       stats->tcp_flags |= tcp_flags;
+       spin_unlock(&stats->lock);
+}
+
+void ovs_flow_stats_get(struct sw_flow *flow, struct sw_flow_stats *res)
+{
+       int cpu, cur_cpu;
+
+       memset(res, 0, sizeof(*res));
+
+       cur_cpu = get_cpu();
+       for_each_possible_cpu(cpu) {
+               struct sw_flow_stats *stats = &flow->stats[cpu];
+
+               if (cpu == cur_cpu)
+                       local_bh_disable();
+
+               spin_lock(&stats->lock);
+               if (time_after(stats->used, res->used))
+                       res->used = stats->used;
+               res->packet_count += stats->packet_count;
+               res->byte_count += stats->byte_count;
+               res->tcp_flags |= stats->tcp_flags;
+               spin_unlock(&stats->lock);
+
+               if (cpu == cur_cpu)
+                       local_bh_enable();
+
+       }
+       put_cpu();
+}
+
+void ovs_flow_stats_clear(struct sw_flow *flow)
+{
+       int cpu, cur_cpu;
+
+       cur_cpu = get_cpu();
+       for_each_possible_cpu(cpu) {
+               struct sw_flow_stats *stats = &flow->stats[cpu];
+
+               if (cpu == cur_cpu)
+                       local_bh_disable();
+
+               spin_lock(&stats->lock);
+               stats->used = 0;
+               stats->packet_count = 0;
+               stats->byte_count = 0;
+               stats->tcp_flags = 0;
+               spin_unlock(&stats->lock);
+
+               if (cpu == cur_cpu)
+                       local_bh_enable();
+       }
+       put_cpu();
 }
 
 static int check_header(struct sk_buff *skb, int len)
index 91a3022..d1ac85a 100644 (file)
@@ -19,6 +19,7 @@
 #ifndef FLOW_H
 #define FLOW_H 1
 
+#include <linux/cache.h>
 #include <linux/kernel.h>
 #include <linux/netlink.h>
 #include <linux/openvswitch.h>
@@ -146,6 +147,14 @@ struct sw_flow_actions {
        struct nlattr actions[];
 };
 
+struct sw_flow_stats {
+       u64 packet_count;               /* Number of packets matched. */
+       u64 byte_count;                 /* Number of bytes matched. */
+       unsigned long used;             /* Last used time (in jiffies). */
+       spinlock_t lock;                /* Lock for atomic stats update. */
+       u8 tcp_flags;                   /* Union of seen TCP flags. */
+} ____cacheline_aligned_in_smp;
+
 struct sw_flow {
        struct rcu_head rcu;
        struct hlist_node hash_node[2];
@@ -155,12 +164,7 @@ struct sw_flow {
        struct sw_flow_key unmasked_key;
        struct sw_flow_mask *mask;
        struct sw_flow_actions __rcu *sf_acts;
-
-       spinlock_t lock;        /* Lock for values below. */
-       unsigned long used;     /* Last used time (in jiffies). */
-       u64 packet_count;       /* Number of packets matched. */
-       u64 byte_count;         /* Number of bytes matched. */
-       u8 tcp_flags;           /* Union of seen TCP flags. */
+       struct sw_flow_stats stats[];
 };
 
 struct arp_eth_header {
@@ -177,7 +181,9 @@ struct arp_eth_header {
        unsigned char       ar_tip[4];          /* target IP address        */
 } __packed;
 
-void ovs_flow_used(struct sw_flow *, struct sk_buff *);
+void ovs_flow_stats_update(struct sw_flow *flow, struct sk_buff *skb);
+void ovs_flow_stats_get(struct sw_flow *flow, struct sw_flow_stats *res);
+void ovs_flow_stats_clear(struct sw_flow *flow);
 u64 ovs_flow_used_time(unsigned long flow_jiffies);
 
 int ovs_flow_extract(struct sk_buff *, u16 in_port, struct sw_flow_key *);
index 98eb809..c2a7aa5 100644 (file)
@@ -76,15 +76,19 @@ void ovs_flow_mask_key(struct sw_flow_key *dst, const struct sw_flow_key *src,
 struct sw_flow *ovs_flow_alloc(void)
 {
        struct sw_flow *flow;
+       int cpu;
 
        flow = kmem_cache_alloc(flow_cache, GFP_KERNEL);
        if (!flow)
                return ERR_PTR(-ENOMEM);
 
-       spin_lock_init(&flow->lock);
        flow->sf_acts = NULL;
        flow->mask = NULL;
 
+       memset(flow->stats, 0, num_possible_cpus() * sizeof(struct sw_flow_stats));
+       for_each_possible_cpu(cpu)
+               spin_lock_init(&flow->stats[cpu].lock);
+
        return flow;
 }
 
@@ -431,13 +435,16 @@ static struct sw_flow *masked_flow_lookup(struct table_instance *ti,
 }
 
 struct sw_flow *ovs_flow_tbl_lookup(struct flow_table *tbl,
-                                   const struct sw_flow_key *key)
+                                   const struct sw_flow_key *key,
+                                   u32 *n_mask_hit)
 {
        struct table_instance *ti = rcu_dereference(tbl->ti);
        struct sw_flow_mask *mask;
        struct sw_flow *flow;
 
+       *n_mask_hit = 0;
        list_for_each_entry_rcu(mask, &tbl->mask_list, list) {
+               (*n_mask_hit)++;
                flow = masked_flow_lookup(ti, key, mask);
                if (flow)  /* Found */
                        return flow;
@@ -445,6 +452,17 @@ struct sw_flow *ovs_flow_tbl_lookup(struct flow_table *tbl,
        return NULL;
 }
 
+int ovs_flow_tbl_num_masks(const struct flow_table *table)
+{
+       struct sw_flow_mask *mask;
+       int num = 0;
+
+       list_for_each_entry(mask, &table->mask_list, list)
+               num++;
+
+       return num;
+}
+
 static struct table_instance *table_instance_expand(struct table_instance *ti)
 {
        return table_instance_rehash(ti, ti->n_buckets * 2);
@@ -561,11 +579,15 @@ int ovs_flow_tbl_insert(struct flow_table *table, struct sw_flow *flow,
  * Returns zero if successful or a negative error code. */
 int ovs_flow_init(void)
 {
+       int flow_size;
+
        BUILD_BUG_ON(__alignof__(struct sw_flow_key) % __alignof__(long));
        BUILD_BUG_ON(sizeof(struct sw_flow_key) % sizeof(long));
 
-       flow_cache = kmem_cache_create("sw_flow", sizeof(struct sw_flow), 0,
-                                       0, NULL);
+       flow_size = sizeof(struct sw_flow) +
+                   (num_possible_cpus() * sizeof(struct sw_flow_stats));
+
+       flow_cache = kmem_cache_create("sw_flow", flow_size, 0, 0, NULL);
        if (flow_cache == NULL)
                return -ENOMEM;
 
index 4db5f78..fbe45d5 100644 (file)
@@ -66,10 +66,12 @@ int ovs_flow_tbl_flush(struct flow_table *flow_table);
 int ovs_flow_tbl_insert(struct flow_table *table, struct sw_flow *flow,
                        struct sw_flow_mask *mask);
 void ovs_flow_tbl_remove(struct flow_table *table, struct sw_flow *flow);
+int  ovs_flow_tbl_num_masks(const struct flow_table *table);
 struct sw_flow *ovs_flow_tbl_dump_next(struct table_instance *table,
                                       u32 *bucket, u32 *idx);
 struct sw_flow *ovs_flow_tbl_lookup(struct flow_table *,
-                                   const struct sw_flow_key *);
+                                   const struct sw_flow_key *,
+                                   u32 *n_mask_hit);
 
 bool ovs_flow_cmp_unmasked_key(const struct sw_flow *flow,
                               struct sw_flow_match *match);
index 057e1d5..fee132e 100644 (file)
@@ -64,5 +64,6 @@ openvswitch_headers += \
        linux/compat/include/net/ipv6.h \
        linux/compat/include/net/net_namespace.h \
        linux/compat/include/net/netlink.h \
+       linux/compat/include/net/sock.h \
        linux/compat/include/net/vxlan.h \
        linux/compat/include/net/sctp/checksum.h
index 1ee8d6f..bf057f7 100644 (file)
@@ -7,3 +7,7 @@
 #ifndef __packed
 #define __packed __attribute__((packed))
 #endif
+
+#ifndef __always_unused
+#define __always_unused __attribute__((unused))
+#endif
index 2b2c855..c5c366b 100644 (file)
@@ -118,6 +118,11 @@ static inline void netdev_upper_dev_unlink(struct net_device *dev,
                                           struct net_device *upper_dev)
 {
 }
+
+static inline struct net_device *netdev_master_upper_dev_get(struct net_device *dev)
+{
+       return NULL;
+}
 #endif
 
 #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,37)
@@ -125,4 +130,11 @@ static inline void netdev_upper_dev_unlink(struct net_device *dev,
 int dev_queue_xmit(struct sk_buff *skb);
 #endif
 
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3,11,0)
+static inline struct net_device *netdev_notifier_info_to_dev(void *info)
+{
+       return info;
+}
+#endif
+
 #endif
index 5f46aed..5b17dcc 100644 (file)
@@ -74,12 +74,17 @@ static inline __be16 tnl_flags_to_gre_flags(__be16 tflags)
 #endif /* LINUX_VERSION_CODE < KERNEL_VERSION(3,10,0) */
 
 #define MAX_GRE_PROTO_PRIORITY 255
+#define gre_cisco_protocol rpl_gre_cisco_protocol
+
 struct gre_cisco_protocol {
        int (*handler)(struct sk_buff *skb, const struct tnl_ptk_info *tpi);
        u8 priority;
 };
 
+#define gre_cisco_register rpl_gre_cisco_register
 int gre_cisco_register(struct gre_cisco_protocol *proto);
+
+#define gre_cisco_unregister rpl_gre_cisco_unregister
 int gre_cisco_unregister(struct gre_cisco_protocol *proto);
 
 #define gre_build_header rpl_gre_build_header
@@ -89,6 +94,7 @@ void gre_build_header(struct sk_buff *skb, const struct tnl_ptk_info *tpi,
 #define gre_handle_offloads rpl_gre_handle_offloads
 struct sk_buff *gre_handle_offloads(struct sk_buff *skb, bool gre_csum);
 
+#define ip_gre_calc_hlen rpl_ip_gre_calc_hlen
 static inline int ip_gre_calc_hlen(__be16 o_flags)
 {
        int addend = 4;
diff --git a/datapath/linux/compat/include/net/sock.h b/datapath/linux/compat/include/net/sock.h
new file mode 100644 (file)
index 0000000..2900704
--- /dev/null
@@ -0,0 +1,13 @@
+#ifndef __NET_SOCK_WRAPPER_H
+#define __NET_SOCK_WRAPPER_H 1
+
+#include_next <net/sock.h>
+
+#ifndef __sk_user_data
+#define __sk_user_data(sk) ((*((void __rcu **)&(sk)->sk_user_data)))
+
+#define rcu_dereference_sk_user_data(sk)       rcu_dereference(__sk_user_data((sk)))
+#define rcu_assign_sk_user_data(sk, ptr)       rcu_assign_pointer(__sk_user_data((sk)), ptr)
+#endif
+
+#endif
index 09d0fd7..64877e0 100644 (file)
 #include <net/vxlan.h>
 
 #include "compat.h"
+#include "datapath.h"
 #include "gso.h"
 #include "vlan.h"
 
-#define PORT_HASH_BITS 8
-#define PORT_HASH_SIZE  (1<<PORT_HASH_BITS)
-
-/* IP header + UDP + VXLAN + Ethernet header */
-#define VXLAN_HEADROOM (20 + 8 + 8 + 14)
 #define VXLAN_HLEN (sizeof(struct udphdr) + sizeof(struct vxlanhdr))
 
 #define VXLAN_FLAGS 0x08000000 /* struct vxlanhdr.vx_flags required value. */
@@ -70,38 +66,6 @@ struct vxlanhdr {
        __be32 vx_vni;
 };
 
-static int vxlan_net_id;
-
-static int vxlan_init_module(void);
-static void vxlan_cleanup_module(void);
-
-/* per-network namespace private data for this module */
-struct vxlan_net {
-       struct hlist_head sock_list[PORT_HASH_SIZE];
-       spinlock_t  sock_lock;
-};
-
-/* Socket hash table head */
-static inline struct hlist_head *vs_head(struct net *net, __be16 port)
-{
-       struct vxlan_net *vn = net_generic(net, vxlan_net_id);
-
-       return &vn->sock_list[hash_32(ntohs(port), PORT_HASH_BITS)];
-}
-
-/* Find VXLAN socket based on network namespace and UDP port */
-
-static struct vxlan_sock *vxlan_find_sock(struct net *net, __be16 port)
-{
-       struct vxlan_sock *vs;
-
-       hlist_for_each_entry_rcu(vs, vs_head(net, port), hlist) {
-               if (inet_sport(vs->sock->sk) == port)
-                       return vs;
-       }
-       return NULL;
-}
-
 /* Callback from net/ipv4/udp.c to receive packets */
 static int vxlan_udp_encap_recv(struct sock *sk, struct sk_buff *skb)
 {
@@ -124,7 +88,7 @@ static int vxlan_udp_encap_recv(struct sock *sk, struct sk_buff *skb)
        if (iptunnel_pull_header(skb, VXLAN_HLEN, htons(ETH_P_TEB)))
                goto drop;
 
-       vs = vxlan_find_sock(sock_net(sk), inet_sport(sk));
+       vs = rcu_dereference_sk_user_data(sk);
        if (!vs)
                goto drop;
 
@@ -275,13 +239,11 @@ static void vxlan_del_work(struct work_struct *work)
 
        sk_release_kernel(vs->sock->sk);
        call_rcu(&vs->rcu, rcu_free_vs);
-       vxlan_cleanup_module();
 }
 
 static struct vxlan_sock *vxlan_socket_create(struct net *net, __be16 port,
                                              vxlan_rcv_t *rcv, void *data)
 {
-       struct vxlan_net *vn = net_generic(net, vxlan_net_id);
        struct vxlan_sock *vs;
        struct sock *sk;
        struct sockaddr_in vxlan_addr = {
@@ -325,9 +287,7 @@ static struct vxlan_sock *vxlan_socket_create(struct net *net, __be16 port,
 
        /* Disable multicast loopback */
        inet_sk(sk)->mc_loop = 0;
-       spin_lock(&vn->sock_lock);
-       hlist_add_head_rcu(&vs->hlist, vs_head(net, port));
-       spin_unlock(&vn->sock_lock);
+       rcu_assign_sk_user_data(vs->sock->sk, vs);
 
        /* Mark socket as an encapsulation socket. */
        udp_sk(sk)->encap_type = 1;
@@ -340,75 +300,13 @@ struct vxlan_sock *vxlan_sock_add(struct net *net, __be16 port,
                                  vxlan_rcv_t *rcv, void *data,
                                  bool no_share)
 {
-       struct vxlan_net *vn;
-       struct vxlan_sock *vs;
-       int err;
-
-       err = vxlan_init_module();
-       if (err)
-               return ERR_PTR(err);
-
-       vn = net_generic(net, vxlan_net_id);
-       vs = vxlan_socket_create(net, port, rcv, data);
-       return vs;
+       return vxlan_socket_create(net, port, rcv, data);
 }
 
 void vxlan_sock_release(struct vxlan_sock *vs)
 {
-       struct vxlan_net *vn = net_generic(sock_net(vs->sock->sk), vxlan_net_id);
-
-       spin_lock(&vn->sock_lock);
-       hlist_del_rcu(&vs->hlist);
-       spin_unlock(&vn->sock_lock);
+       ASSERT_OVSL();
+       rcu_assign_sk_user_data(vs->sock->sk, NULL);
 
        queue_work(system_wq, &vs->del_work);
 }
-
-static int vxlan_init_net(struct net *net)
-{
-       struct vxlan_net *vn = net_generic(net, vxlan_net_id);
-       unsigned int h;
-
-       spin_lock_init(&vn->sock_lock);
-
-       for (h = 0; h < PORT_HASH_SIZE; ++h)
-               INIT_HLIST_HEAD(&vn->sock_list[h]);
-
-       return 0;
-}
-
-static struct pernet_operations vxlan_net_ops = {
-       .init = vxlan_init_net,
-       .id   = &vxlan_net_id,
-       .size = sizeof(struct vxlan_net),
-};
-
-static int refcnt;
-static DEFINE_MUTEX(init_lock);
-DEFINE_COMPAT_PNET_REG_FUNC(device);
-
-static int vxlan_init_module(void)
-{
-       int err = 0;
-
-       mutex_lock(&init_lock);
-       if (refcnt)
-               goto out;
-       err = register_pernet_device(&vxlan_net_ops);
-out:
-       if (!err)
-               refcnt++;
-       mutex_unlock(&init_lock);
-       return err;
-}
-
-static void vxlan_cleanup_module(void)
-{
-       mutex_lock(&init_lock);
-       refcnt--;
-       if (refcnt)
-               goto out;
-       unregister_pernet_device(&vxlan_net_ops);
-out:
-       mutex_unlock(&init_lock);
-}
index 2a83f73..7780386 100644 (file)
@@ -190,16 +190,27 @@ static void free_port_rcu(struct rcu_head *rcu)
        ovs_vport_free(vport_from_priv(netdev_vport));
 }
 
-static void netdev_destroy(struct vport *vport)
+void ovs_netdev_detach_dev(struct vport *vport)
 {
        struct netdev_vport *netdev_vport = netdev_vport_priv(vport);
 
-       netdev_exit();
-       rtnl_lock();
+       ASSERT_RTNL();
        netdev_vport->dev->priv_flags &= ~IFF_OVS_DATAPATH;
        netdev_rx_handler_unregister(netdev_vport->dev);
-       netdev_upper_dev_unlink(netdev_vport->dev, get_dpdev(vport->dp));
+       netdev_upper_dev_unlink(netdev_vport->dev,
+                               netdev_master_upper_dev_get(netdev_vport->dev));
        dev_set_promiscuity(netdev_vport->dev, -1);
+}
+
+static void netdev_destroy(struct vport *vport)
+{
+       struct netdev_vport *netdev_vport = netdev_vport_priv(vport);
+
+       netdev_exit();
+
+       rtnl_lock();
+       if (ovs_netdev_get_vport(netdev_vport->dev))
+               ovs_netdev_detach_dev(vport);
        rtnl_unlock();
 
        call_rcu(&netdev_vport->rcu, free_port_rcu);
index dd298b5..8df01c1 100644 (file)
@@ -39,5 +39,6 @@ netdev_vport_priv(const struct vport *vport)
 }
 
 const char *ovs_netdev_get_name(const struct vport *);
+void ovs_netdev_detach_dev(struct vport *);
 
 #endif /* vport_netdev.h */
index 35c5a9e..33d7282 100644 (file)
@@ -45,6 +45,11 @@ EXTRA_DIST += \
        debian/openvswitch-test.dirs \
        debian/openvswitch-test.install \
        debian/openvswitch-test.manpages \
+       debian/openvswitch-vtep.default \
+       debian/openvswitch-vtep.dirs \
+       debian/openvswitch-vtep.init \
+       debian/openvswitch-vtep.install \
+       debian/openvswitch-vtep.manpages \
        debian/ovsdbmonitor.install \
        debian/ovsdbmonitor.manpages \
        debian/ovs-monitor-ipsec \
index cb55e8e..aea2b7a 100644 (file)
@@ -9,6 +9,9 @@ openvswitch (2.0.90-1) unstable; urgency=low
 openvswitch (2.0.0-1) unstable; urgency=low
    [ Open vSwitch team ]
    * New upstream version
+    - The ovs-vswitchd process is no longer single-threaded.  Multiple
+      threads are now used to handle flow set up and asynchronous
+      logging.
     - OpenFlow:
       * Experimental support for OpenFlow 1.1 (in addition to 1.2 and
         1.3, which had experimental support in 1.10).
@@ -33,8 +36,11 @@ openvswitch (2.0.0-1) unstable; urgency=low
       * New "ofp-parse" for printing OpenFlow messages read from a file.
     - Added configurable flow caching support to IPFIX exporter.
     - Dropped support for Linux pre-2.6.32.
+    - Log file timestamps and ovsdb commit timestamps are now reported
+      with millisecond resolution.  (Previous versions only reported
+      whole seconds.)
 
- -- Open vSwitch team <dev@openvswitch.org>  Wed, 28 Aug 2013 16:11:32 -0700
+ -- Open vSwitch team <dev@openvswitch.org>  Tue, 15 Oct 2013 15:03:42 -0700
 
 openvswitch (1.11.0-1) unstable; urgency=low
    [ Open vSwitch team ]
index 46b5630..215b9ef 100644 (file)
@@ -5,7 +5,7 @@ Maintainer: Open vSwitch developers <dev@openvswitch.org>
 Uploaders: Ben Pfaff <pfaffben@debian.org>, Simon Horman <horms@debian.org>
 Build-Depends:
  debhelper (>= 8), autoconf (>= 2.64), automake (>= 1.10) | automake1.10, 
- libssl-dev, bzip2, openssl,
+ libssl-dev, bzip2, openssl, graphviz,
  python-all (>= 2.6.6-3~), procps, python-qt4,
  python-zopeinterface, python-twisted-conch
 Standards-Version: 3.9.3
@@ -204,3 +204,21 @@ Description: Open vSwitch test package
  .
  This package contains utilities that are useful to diagnose
  performance and connectivity issues in Open vSwitch setup.
+
+Package: openvswitch-vtep
+Architecture: linux-any
+Depends:
+ ${shlibs:Depends}, ${misc:Depends}, openvswitch-common (>= ${binary:Version}),
+ openvswitch-switch (>= ${binary:Version}), python,
+ python-openvswitch (>= ${source:Version})
+Description: Open vSwitch VTEP utilities
+ Open vSwitch is a production quality, multilayer, software-based, Ethernet
+ virtual switch. It is designed to enable massive network automation through
+ programmatic extension, while still supporting standard management interfaces
+ and protocols (e.g. NetFlow, sFlow, SPAN, RSPAN, CLI, LACP, 802.1ag). In
+ addition, it is designed to support distribution across multiple physical
+ servers similar to VMware's vNetwork distributed vswitch or Cisco's Nexus
+ 1000V.
+ .
+ This package provides utilities that are useful to interact with a
+ VTEP-configured database and a VTEP emulator.
index 0afb675..bfcf1d9 100644 (file)
@@ -6,4 +6,4 @@ _debian/utilities/ovs-tcpundump.1
 _debian/utilities/ovs-vlan-test.8
 _debian/utilities/ovs-vsctl.8
 _debian/vswitchd/ovs-vswitchd.8
-vswitchd/ovs-vswitchd.conf.db.5
+_debian/vswitchd/ovs-vswitchd.conf.db.5
diff --git a/debian/openvswitch-vtep.default b/debian/openvswitch-vtep.default
new file mode 100644 (file)
index 0000000..2e888e7
--- /dev/null
@@ -0,0 +1,4 @@
+# This is a POSIX shell fragment                -*- sh -*-
+
+# ENABLE_OVS_VTEP: Whether to start ovs-vtep.
+ENABLE_OVS_VTEP="false"
diff --git a/debian/openvswitch-vtep.dirs b/debian/openvswitch-vtep.dirs
new file mode 100644 (file)
index 0000000..b0a8f64
--- /dev/null
@@ -0,0 +1 @@
+/usr/share/openvswitch
diff --git a/debian/openvswitch-vtep.init b/debian/openvswitch-vtep.init
new file mode 100644 (file)
index 0000000..ebf4e26
--- /dev/null
@@ -0,0 +1,78 @@
+#!/bin/sh
+
+### BEGIN INIT INFO
+# Provides:          openvswitch-vtep
+# Required-Start:    $network $named $remote_fs $syslog
+# Required-Stop:     $remote_fs
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description: Open vSwitch VTEP emulator
+# Description:       Initializes the Open vSwitch VTEP emulator
+### END INIT INFO
+
+
+# Include defaults if available
+default=/etc/default/openvswitch-vtep
+if [ -f $default ] ; then
+    . $default
+fi
+
+start () {
+    if [ "$ENABLE_OVS_VTEP" = "false" ]; then
+        exit 0
+    fi
+
+    update-rc.d -f openvswitch-switch remove >/dev/null 2>&1
+    /etc/init.d/openvswitch-switch stop
+
+    mkdir -p "/var/run/openvswitch"
+
+    if [ ! -e "/etc/openvswitch/conf.db" ]; then
+        ovsdb-tool create /etc/openvswitch/conf.db /usr/share/openvswitch/vswitch.ovsschema
+    fi
+
+    if [ ! -e "/etc/openvswitch/vtep.db" ]; then
+        ovsdb-tool create /etc/openvswitch/vtep.db /usr/share/openvswitch/vtep.ovsschema
+    fi
+
+    if [ ! -e "/etc/openvswitch/ovsclient-cert.pem" ]; then
+        export RANDFILE="/root/.rnd"
+        cd /etc/openvswitch && ovs-pki req ovsclient && ovs-pki self-sign ovsclient
+    fi
+
+    ovsdb-server --pidfile --detach --log-file --remote \
+        punix:/var/run/openvswitch/db.sock \
+        --remote=db:hardware_vtep,Global,managers \
+        --private-key=/etc/openvswitch/ovsclient-privkey.pem \
+        --certificate=/etc/openvswitch/ovsclient-cert.pem \
+        --bootstrap-ca-cert=/etc/openvswitch/vswitchd.cacert \
+        /etc/openvswitch/conf.db /etc/openvswitch/vtep.db
+
+    modprobe openvswitch
+
+    ovs-vswitchd --pidfile --detach --log-file \
+        unix:/var/run/openvswitch/db.sock
+}
+
+stop () {
+    /etc/init.d/openvswitch-switch stop
+}
+
+case $1 in
+    start)
+        start
+        ;;
+    stop)
+        stop
+        ;;
+    restart|force-reload)
+        stop
+        start
+        ;;
+    *)
+        echo "Usage: $0 {start|stop|restart|force-reload}" >&2
+        exit 1
+        ;;
+esac
+
+exit 0
diff --git a/debian/openvswitch-vtep.install b/debian/openvswitch-vtep.install
new file mode 100644 (file)
index 0000000..db91209
--- /dev/null
@@ -0,0 +1,3 @@
+_debian/vtep/vtep-ctl usr/bin
+usr/share/openvswitch/vtep.ovsschema
+usr/share/openvswitch/scripts/ovs-vtep
diff --git a/debian/openvswitch-vtep.manpages b/debian/openvswitch-vtep.manpages
new file mode 100644 (file)
index 0000000..1fcad1e
--- /dev/null
@@ -0,0 +1 @@
+_debian/vtep/vtep-ctl.8
index 09c26b5..f46d9d0 100644 (file)
@@ -83,15 +83,18 @@ enum ovs_datapath_cmd {
  * not be sent.
  * @OVS_DP_ATTR_STATS: Statistics about packets that have passed through the
  * datapath.  Always present in notifications.
+ * @OVS_DP_ATTR_MEGAFLOW_STATS: Statistics about mega flow masks usage for the
+ * datapath. Always present in notifications.
  *
  * These attributes follow the &struct ovs_header within the Generic Netlink
  * payload for %OVS_DP_* commands.
  */
 enum ovs_datapath_attr {
        OVS_DP_ATTR_UNSPEC,
-       OVS_DP_ATTR_NAME,       /* name of dp_ifindex netdev */
-       OVS_DP_ATTR_UPCALL_PID, /* Netlink PID to receive upcalls */
-       OVS_DP_ATTR_STATS,      /* struct ovs_dp_stats */
+       OVS_DP_ATTR_NAME,               /* name of dp_ifindex netdev */
+       OVS_DP_ATTR_UPCALL_PID,         /* Netlink PID to receive upcalls */
+       OVS_DP_ATTR_STATS,              /* struct ovs_dp_stats */
+       OVS_DP_ATTR_MEGAFLOW_STATS,     /* struct ovs_dp_megaflow_stats */
        __OVS_DP_ATTR_MAX
 };
 
@@ -104,6 +107,14 @@ struct ovs_dp_stats {
        __u64 n_flows;           /* Number of flows present */
 };
 
+struct ovs_dp_megaflow_stats {
+       __u64 n_mask_hit;        /* Number of masks used for flow lookups. */
+       __u32 n_masks;           /* Number of masks for the datapath. */
+       __u32 pad0;              /* Pad for future expension. */
+       __u64 pad1;              /* Pad for future expension. */
+       __u64 pad2;              /* Pad for future expension. */
+};
+
 struct ovs_vport_stats {
        __u64   rx_packets;             /* total packets received       */
        __u64   tx_packets;             /* total packets transmitted    */
index ca272fd..3362a49 100644 (file)
@@ -199,8 +199,8 @@ OFP_ASSERT(sizeof(struct nx_set_packet_in_format) == 4);
  * might support fields (new registers, new protocols, etc.) that the
  * controller does not.  The controller must prepared to tolerate these.
  *
- * The 'cookie' and 'table_id' fields have no meaning when 'reason' is
- * OFPR_NO_MATCH.  In this case they should be set to 0. */
+ * The 'cookie' field has no meaning when 'reason' is OFPR_NO_MATCH.  In this
+ * case it should be UINT64_MAX. */
 struct nx_packet_in {
     ovs_be32 buffer_id;       /* ID assigned by datapath. */
     ovs_be16 total_len;       /* Full length of frame. */
index ef0a3ce..34d97c4 100644 (file)
@@ -201,19 +201,6 @@ struct ofp10_action_enqueue {
 };
 OFP_ASSERT(sizeof(struct ofp10_action_enqueue) == 16);
 
-union ofp_action {
-    ovs_be16 type;
-    struct ofp_action_header header;
-    struct ofp_action_vendor_header vendor;
-    struct ofp10_action_output output10;
-    struct ofp_action_vlan_vid vlan_vid;
-    struct ofp_action_vlan_pcp vlan_pcp;
-    struct ofp_action_nw_addr nw_addr;
-    struct ofp_action_nw_tos nw_tos;
-    struct ofp_action_tp_port tp_port;
-};
-OFP_ASSERT(sizeof(union ofp_action) == 8);
-
 /* Send packet (controller -> datapath). */
 struct ofp10_packet_out {
     ovs_be32 buffer_id;           /* ID assigned by datapath or UINT32_MAX. */
index f4c59ad..31346e4 100644 (file)
@@ -8,3 +8,6 @@
 /vswitch-idl.c
 /vswitch-idl.h
 /vswitch-idl.ovsidl
+/vtep-idl.c
+/vtep-idl.h
+/vtep-idl.ovsidl
index ffaf89a..4d7c1c6 100644 (file)
@@ -233,7 +233,9 @@ lib_libopenvswitch_a_SOURCES = \
        lib/vlog.c \
        lib/vlog.h \
        lib/vswitch-idl.c \
-       lib/vswitch-idl.h
+       lib/vswitch-idl.h \
+       lib/vtep-idl.c \
+       lib/vtep-idl.h
 
 nodist_lib_libopenvswitch_a_SOURCES = \
        lib/dirs.c
@@ -338,7 +340,10 @@ MAN_FRAGMENTS += \
 OVSIDL_BUILT += \
        $(srcdir)/lib/vswitch-idl.c \
        $(srcdir)/lib/vswitch-idl.h \
-       $(srcdir)/lib/vswitch-idl.ovsidl
+       $(srcdir)/lib/vswitch-idl.ovsidl \
+       $(srcdir)/lib/vtep-idl.c \
+       $(srcdir)/lib/vtep-idl.h \
+       $(srcdir)/lib/vtep-idl.ovsidl
 
 EXTRA_DIST += $(srcdir)/lib/vswitch-idl.ann
 VSWITCH_IDL_FILES = \
@@ -348,6 +353,14 @@ $(srcdir)/lib/vswitch-idl.ovsidl: $(VSWITCH_IDL_FILES)
        $(OVSDB_IDLC) annotate $(VSWITCH_IDL_FILES) > $@.tmp
        mv $@.tmp $@
 
+EXTRA_DIST += $(srcdir)/lib/vtep-idl.ann
+VTEP_IDL_FILES = \
+       $(srcdir)/vtep/vtep.ovsschema \
+       $(srcdir)/lib/vtep-idl.ann
+$(srcdir)/lib/vtep-idl.ovsidl: $(VTEP_IDL_FILES)
+       $(OVSDB_IDLC) annotate $(VTEP_IDL_FILES) > $@.tmp
+       mv $@.tmp $@
+
 lib/dirs.c: lib/dirs.c.in Makefile
        ($(ro_c) && sed < $(srcdir)/lib/dirs.c.in \
                -e 's,[@]srcdir[@],$(srcdir),g' \
index 6c9e920..115053b 100644 (file)
--- a/lib/bfd.c
+++ b/lib/bfd.c
@@ -446,16 +446,30 @@ bfd_unref(struct bfd *bfd) OVS_EXCLUDED(mutex)
 void
 bfd_wait(const struct bfd *bfd) OVS_EXCLUDED(mutex)
 {
-    ovs_mutex_lock(&mutex);
-    if (bfd->flags & FLAG_FINAL) {
-        poll_immediate_wake();
+    poll_timer_wait_until(bfd_wake_time(bfd));
+}
+
+/* Returns the next wake up time. */
+long long int
+bfd_wake_time(const struct bfd *bfd) OVS_EXCLUDED(mutex)
+{
+    long long int retval;
+
+    if (!bfd) {
+        return LLONG_MAX;
     }
 
-    poll_timer_wait_until(bfd->next_tx);
-    if (bfd->state > STATE_DOWN) {
-        poll_timer_wait_until(bfd->detect_time);
+    ovs_mutex_lock(&mutex);
+    if (bfd->flags & FLAG_FINAL) {
+        retval = 0;
+    } else {
+        retval = bfd->next_tx;
+        if (bfd->state > STATE_DOWN) {
+            retval = MIN(bfd->detect_time, retval);
+        }
     }
     ovs_mutex_unlock(&mutex);
+    return retval;
 }
 
 void
index 0e1e33d..f49a3d6 100644 (file)
--- a/lib/bfd.h
+++ b/lib/bfd.h
@@ -49,5 +49,5 @@ void bfd_unref(struct bfd *);
 bool bfd_forwarding(const struct bfd *);
 void bfd_get_status(const struct bfd *, struct smap *);
 void bfd_set_netdev(struct bfd *, const struct netdev *);
-
+long long int bfd_wake_time(const struct bfd *);
 #endif /* bfd.h */
index e8f86dc..730a00f 100644 (file)
--- a/lib/cfm.c
+++ b/lib/cfm.c
@@ -580,12 +580,27 @@ cfm_compose_ccm(struct cfm *cfm, struct ofpbuf *packet,
 void
 cfm_wait(struct cfm *cfm) OVS_EXCLUDED(mutex)
 {
+    poll_timer_wait_until(cfm_wake_time(cfm));
+}
+
+
+/* Returns the next cfm wakeup time. */
+long long int
+cfm_wake_time(struct cfm *cfm) OVS_EXCLUDED(mutex)
+{
+    long long int retval;
+
+    if (!cfm) {
+        return LLONG_MAX;
+    }
+
     ovs_mutex_lock(&mutex);
-    timer_wait(&cfm->tx_timer);
-    timer_wait(&cfm->fault_timer);
+    retval = MIN(cfm->tx_timer.t, cfm->fault_timer.t);
     ovs_mutex_unlock(&mutex);
+    return retval;
 }
 
+
 /* Configures 'cfm' with settings from 's'. */
 bool
 cfm_configure(struct cfm *cfm, const struct cfm_settings *s)
index cff713f..9d1ea4c 100644 (file)
--- a/lib/cfm.h
+++ b/lib/cfm.h
@@ -82,4 +82,5 @@ int cfm_get_opup(const struct cfm *);
 void cfm_get_remote_mpids(const struct cfm *, uint64_t **rmps, size_t *n_rmps);
 const char *cfm_fault_reason_to_str(int fault);
 
+long long int cfm_wake_time(struct cfm*);
 #endif /* cfm.h */
index 0e39012..8e3bf61 100644 (file)
@@ -148,7 +148,7 @@ cls_table_is_catchall(const struct cls_table *table)
     return minimask_is_catchall(&table->mask);
 }
 
-/* A rule in a "struct classifier". */
+/* A rule in a "struct cls_table". */
 struct cls_rule {
     struct hmap_node hmap_node; /* Within struct cls_table 'rules'. */
     struct list list;           /* List of identical, lower-priority rules. */
index 6f75f57..42958d3 100644 (file)
@@ -74,6 +74,8 @@ struct dpif_linux_dp {
     const char *name;                  /* OVS_DP_ATTR_NAME. */
     const uint32_t *upcall_pid;        /* OVS_DP_UPCALL_PID. */
     struct ovs_dp_stats stats;         /* OVS_DP_ATTR_STATS. */
+    struct ovs_dp_megaflow_stats megaflow_stats;
+                                       /* OVS_DP_ATTR_MEGAFLOW_STATS.*/
 };
 
 static void dpif_linux_dp_init(struct dpif_linux_dp *);
@@ -411,6 +413,8 @@ dpif_linux_get_stats(const struct dpif *dpif_, struct dpif_dp_stats *stats)
         stats->n_missed = dp.stats.n_missed;
         stats->n_lost   = dp.stats.n_lost;
         stats->n_flows  = dp.stats.n_flows;
+        stats->n_masks  = dp.megaflow_stats.n_masks;
+        stats->n_mask_hit  = dp.megaflow_stats.n_mask_hit;
         ofpbuf_delete(buf);
     }
     return error;
@@ -1770,6 +1774,9 @@ dpif_linux_dp_from_ofpbuf(struct dpif_linux_dp *dp, const struct ofpbuf *buf)
         [OVS_DP_ATTR_NAME] = { .type = NL_A_STRING, .max_len = IFNAMSIZ },
         [OVS_DP_ATTR_STATS] = { NL_POLICY_FOR(struct ovs_dp_stats),
                                 .optional = true },
+        [OVS_DP_ATTR_MEGAFLOW_STATS] = {
+                        NL_POLICY_FOR(struct ovs_dp_megaflow_stats),
+                        .optional = true },
     };
 
     struct nlattr *a[ARRAY_SIZE(ovs_datapath_policy)];
@@ -1801,6 +1808,13 @@ dpif_linux_dp_from_ofpbuf(struct dpif_linux_dp *dp, const struct ofpbuf *buf)
                sizeof dp->stats);
     }
 
+    if (a[OVS_DP_ATTR_MEGAFLOW_STATS]) {
+        /* Can't use structure assignment because Netlink doesn't ensure
+         * sufficient alignment for 64-bit members. */
+        memcpy(&dp->megaflow_stats, nl_attr_get(a[OVS_DP_ATTR_MEGAFLOW_STATS]),
+               sizeof dp->megaflow_stats);
+    }
+
     return 0;
 }
 
@@ -1833,6 +1847,8 @@ static void
 dpif_linux_dp_init(struct dpif_linux_dp *dp)
 {
     memset(dp, 0, sizeof *dp);
+    dp->megaflow_stats.n_masks = UINT32_MAX;
+    dp->megaflow_stats.n_mask_hit = UINT64_MAX;
 }
 
 static void
@@ -1871,11 +1887,11 @@ dpif_linux_dp_transact(const struct dpif_linux_dp *request,
     ofpbuf_delete(request_buf);
 
     if (reply) {
+        dpif_linux_dp_init(reply);
         if (!error) {
             error = dpif_linux_dp_from_ofpbuf(reply, *bufp);
         }
         if (error) {
-            dpif_linux_dp_init(reply);
             ofpbuf_delete(*bufp);
             *bufp = NULL;
         }
index 85ccaac..36b6d4a 100644 (file)
@@ -402,6 +402,8 @@ dpif_netdev_get_stats(const struct dpif *dpif, struct dpif_dp_stats *stats)
     stats->n_hit = dp->n_hit;
     stats->n_missed = dp->n_missed;
     stats->n_lost = dp->n_lost;
+    stats->n_masks = UINT64_MAX;
+    stats->n_mask_hit = UINT64_MAX;
     ovs_mutex_unlock(&dp_netdev_mutex);
 
     return 0;
index 8996c0a..ab69c1c 100644 (file)
@@ -387,6 +387,9 @@ struct dpif_dp_stats {
     uint64_t n_missed;          /* Number of flow table misses. */
     uint64_t n_lost;            /* Number of misses not sent to userspace. */
     uint64_t n_flows;           /* Number of flows present. */
+    uint64_t n_masks;           /* Number of mega flow masks. */
+    uint64_t n_mask_hit;        /* Number of mega flow masks visited for
+                                   flow table matches. */
 };
 int dpif_get_dp_stats(const struct dpif *, struct dpif_dp_stats *);
 
index 0678c6f..51851cf 100644 (file)
@@ -35,6 +35,7 @@
 #include "ofpbuf.h"
 #include "openflow/openflow.h"
 #include "packets.h"
+#include "random.h"
 #include "unaligned.h"
 
 COVERAGE_DEFINE(flow_extract);
@@ -602,14 +603,6 @@ flow_wildcards_init_catchall(struct flow_wildcards *wc)
     memset(&wc->masks, 0, sizeof wc->masks);
 }
 
-/* Initializes 'wc' as an exact-match set of wildcards; that is, 'wc' does not
- * wildcard any bits or fields. */
-void
-flow_wildcards_init_exact(struct flow_wildcards *wc)
-{
-    memset(&wc->masks, 0xff, sizeof wc->masks);
-}
-
 /* Returns true if 'wc' matches every packet, false if 'wc' fixes any bits or
  * fields. */
 bool
@@ -799,6 +792,46 @@ flow_hash_symmetric_l4(const struct flow *flow, uint32_t basis)
     return jhash_bytes(&fields, sizeof fields, basis);
 }
 
+/* Initialize a flow with random fields that matter for nx_hash_fields. */
+void
+flow_random_hash_fields(struct flow *flow)
+{
+    uint16_t rnd = random_uint16();
+
+    /* Initialize to all zeros. */
+    memset(flow, 0, sizeof *flow);
+
+    eth_addr_random(flow->dl_src);
+    eth_addr_random(flow->dl_dst);
+
+    flow->vlan_tci = (OVS_FORCE ovs_be16) (random_uint16() & VLAN_VID_MASK);
+
+    /* Make most of the random flows IPv4, some IPv6, and rest random. */
+    flow->dl_type = rnd < 0x8000 ? htons(ETH_TYPE_IP) :
+        rnd < 0xc000 ? htons(ETH_TYPE_IPV6) : (OVS_FORCE ovs_be16)rnd;
+
+    if (dl_type_is_ip_any(flow->dl_type)) {
+        if (flow->dl_type == htons(ETH_TYPE_IP)) {
+            flow->nw_src = (OVS_FORCE ovs_be32)random_uint32();
+            flow->nw_dst = (OVS_FORCE ovs_be32)random_uint32();
+        } else {
+            random_bytes(&flow->ipv6_src, sizeof flow->ipv6_src);
+            random_bytes(&flow->ipv6_dst, sizeof flow->ipv6_dst);
+        }
+        /* Make most of IP flows TCP, some UDP or SCTP, and rest random. */
+        rnd = random_uint16();
+        flow->nw_proto = rnd < 0x8000 ? IPPROTO_TCP :
+            rnd < 0xc000 ? IPPROTO_UDP :
+            rnd < 0xd000 ? IPPROTO_SCTP : (uint8_t)rnd;
+        if (flow->nw_proto == IPPROTO_TCP ||
+            flow->nw_proto == IPPROTO_UDP ||
+            flow->nw_proto == IPPROTO_SCTP) {
+            flow->tp_src = (OVS_FORCE ovs_be16)random_uint16();
+            flow->tp_dst = (OVS_FORCE ovs_be16)random_uint16();
+        }
+    }
+}
+
 /* Masks the fields in 'wc' that are used by the flow hash 'fields'. */
 void
 flow_mask_hash_fields(const struct flow *flow, struct flow_wildcards *wc,
index 4bd1504..ad51496 100644 (file)
@@ -78,13 +78,17 @@ union flow_in_port {
 };
 
 /*
-* A flow in the network.
-*
-* The meaning of 'in_port' is context-dependent.  In most cases, it is a
-* 16-bit OpenFlow 1.0 port number.  In the software datapath interface (dpif)
-* layer and its implementations (e.g. dpif-linux, dpif-netdev), it is instead
-* a 32-bit datapath port number.
-*/
+ * A flow in the network.
+ *
+ * Must be initialized to all zeros to make any compiler-induced padding
+ * zeroed.  Helps also in keeping unused fields (such as mutually exclusive
+ * IPv4 and IPv6 addresses) zeroed out.
+ *
+ * The meaning of 'in_port' is context-dependent.  In most cases, it is a
+ * 16-bit OpenFlow 1.0 port number.  In the software datapath interface (dpif)
+ * layer and its implementations (e.g. dpif-linux, dpif-netdev), it is instead
+ * a 32-bit datapath port number.
+ */
 struct flow {
     struct flow_tnl tunnel;     /* Encapsulating tunnel parameters. */
     ovs_be64 metadata;          /* OpenFlow Metadata. */
@@ -110,15 +114,17 @@ struct flow {
     uint8_t arp_sha[6];         /* ARP/ND source hardware address. */
     uint8_t arp_tha[6];         /* ARP/ND target hardware address. */
     uint8_t nw_ttl;             /* IP TTL/Hop Limit. */
-    uint8_t nw_frag;            /* FLOW_FRAG_* flags. */
+    uint8_t nw_frag;            /* FLOW_FRAG_* flags. Keep last for the
+                                   BUILD_ASSERT_DECL below */
 };
 BUILD_ASSERT_DECL(sizeof(struct flow) % 4 == 0);
 
 #define FLOW_U32S (sizeof(struct flow) / 4)
 
 /* Remember to update FLOW_WC_SEQ when changing 'struct flow'. */
-BUILD_ASSERT_DECL(sizeof(struct flow) == sizeof(struct flow_tnl) + 152 &&
-                  FLOW_WC_SEQ == 21);
+BUILD_ASSERT_DECL(offsetof(struct flow, nw_frag) + 1
+                  == sizeof(struct flow_tnl) + 152
+                  && FLOW_WC_SEQ == 21);
 
 /* Represents the metadata fields of struct flow. */
 struct flow_metadata {
@@ -238,7 +244,6 @@ struct flow_wildcards {
 };
 
 void flow_wildcards_init_catchall(struct flow_wildcards *);
-void flow_wildcards_init_exact(struct flow_wildcards *);
 
 bool flow_wildcards_is_catchall(const struct flow_wildcards *);
 
@@ -262,6 +267,8 @@ bool flow_wildcards_equal(const struct flow_wildcards *,
                           const struct flow_wildcards *);
 uint32_t flow_hash_symmetric_l4(const struct flow *flow, uint32_t basis);
 
+/* Initialize a flow with random fields that matter for nx_hash_fields. */
+void flow_random_hash_fields(struct flow *);
 void flow_mask_hash_fields(const struct flow *, struct flow_wildcards *,
                            enum nx_hash_fields);
 uint32_t flow_hash_fields(const struct flow *, enum nx_hash_fields,
index b2d547a..682915a 100644 (file)
@@ -65,7 +65,7 @@ heap_swap(struct heap *a, struct heap *b)
  *
  * This takes time O(lg n). */
 void
-heap_insert(struct heap *heap, struct heap_node *node, uint32_t priority)
+heap_insert(struct heap *heap, struct heap_node *node, uint64_t priority)
 {
     heap_raw_insert(heap, node, priority);
     float_up(heap, node->idx);
@@ -89,7 +89,7 @@ heap_remove(struct heap *heap, struct heap_node *node)
  *
  * This takes time O(lg n). */
 void
-heap_change(struct heap *heap, struct heap_node *node, uint32_t priority)
+heap_change(struct heap *heap, struct heap_node *node, uint64_t priority)
 {
     heap_raw_change(node, priority);
     float_up_or_down(heap, node->idx);
@@ -104,7 +104,7 @@ heap_change(struct heap *heap, struct heap_node *node, uint32_t priority)
  *
  * This takes time O(1). */
 void
-heap_raw_insert(struct heap *heap, struct heap_node *node, uint32_t priority)
+heap_raw_insert(struct heap *heap, struct heap_node *node, uint64_t priority)
 {
     if (heap->n >= heap->allocated) {
         heap->allocated = heap->n == 0 ? 1 : 2 * heap->n;
index 5c07e04..870f582 100644 (file)
@@ -24,7 +24,7 @@
 /* A heap node, to be embedded inside the data structure in the heap. */
 struct heap_node {
     size_t idx;
-    uint32_t priority;
+    uint64_t priority;
 };
 
 /* A max-heap. */
@@ -43,8 +43,8 @@ static inline size_t heap_count(const struct heap *);
 static inline bool heap_is_empty(const struct heap *);
 
 /* Insertion and deletion. */
-void heap_insert(struct heap *, struct heap_node *, uint32_t priority);
-void heap_change(struct heap *, struct heap_node *, uint32_t priority);
+void heap_insert(struct heap *, struct heap_node *, uint64_t priority);
+void heap_change(struct heap *, struct heap_node *, uint64_t priority);
 void heap_remove(struct heap *, struct heap_node *);
 static inline struct heap_node *heap_pop(struct heap *);
 
@@ -54,8 +54,8 @@ static inline struct heap_node *heap_max(const struct heap *);
 /* The "raw" functions below do not preserve the heap invariants.  After you
  * call them, heap_max() will not necessarily return the right value until you
  * subsequently call heap_rebuild(). */
-void heap_raw_insert(struct heap *, struct heap_node *, uint32_t priority);
-static inline void heap_raw_change(struct heap_node *, uint32_t priority);
+void heap_raw_insert(struct heap *, struct heap_node *, uint64_t priority);
+static inline void heap_raw_change(struct heap_node *, uint64_t priority);
 void heap_raw_remove(struct heap *, struct heap_node *);
 void heap_rebuild(struct heap *);
 
@@ -155,7 +155,7 @@ heap_pop(struct heap *heap)
  *
  * This takes time O(1). */
 static inline void
-heap_raw_change(struct heap_node *node, uint32_t priority)
+heap_raw_change(struct heap_node *node, uint64_t priority)
 {
     node->priority = priority;
 }
index 93f61f9..73b1bf0 100644 (file)
@@ -110,6 +110,10 @@ match_wc_init(struct match *match, const struct flow *flow)
 
         if (flow->nw_frag) {
             memset(&wc->masks.nw_frag, 0xff, sizeof wc->masks.nw_frag);
+            if (flow->nw_frag & FLOW_NW_FRAG_LATER) {
+                /* No transport layer header in later fragments. */
+                return;
+            }
         }
 
         if (flow->nw_proto == IPPROTO_ICMP ||
@@ -128,15 +132,6 @@ match_wc_init(struct match *match, const struct flow *flow)
     return;
 }
 
-/* Converts the flow in 'flow' into an exact-match match in 'match'. */
-void
-match_init_exact(struct match *match, const struct flow *flow)
-{
-    match->flow = *flow;
-    match->flow.skb_priority = 0;
-    flow_wildcards_init_exact(&match->wc);
-}
-
 /* Initializes 'match' as a "catch-all" match that matches every packet. */
 void
 match_init_catchall(struct match *match)
index 48c8aa2..6e87a09 100644 (file)
@@ -38,7 +38,6 @@ void match_init(struct match *,
                 const struct flow *, const struct flow_wildcards *);
 void match_wc_init(struct match *match, const struct flow *flow);
 void match_init_catchall(struct match *);
-void match_init_exact(struct match *, const struct flow *);
 
 void match_zero_wildcarded_fields(struct match *);
 
index 02fee46..12811ef 100644 (file)
@@ -91,7 +91,7 @@ static const struct mf_field mf_fields[MFF_N_IDS] = {
         OFPUTIL_P_NONE,
         OFPUTIL_P_NONE,
     }, {
-        MFF_TUN_TOS, "tun_tos", NULL,
+        MFF_TUN_TTL, "tun_ttl", NULL,
         MF_FIELD_SIZES(u8),
         MFM_NONE,
         MFS_DECIMAL,
@@ -102,7 +102,7 @@ static const struct mf_field mf_fields[MFF_N_IDS] = {
         OFPUTIL_P_NONE,
         OFPUTIL_P_NONE,
     }, {
-        MFF_TUN_TTL, "tun_ttl", NULL,
+        MFF_TUN_TOS, "tun_tos", NULL,
         MF_FIELD_SIZES(u8),
         MFM_NONE,
         MFS_DECIMAL,
@@ -760,11 +760,15 @@ nxm_init_add_field(const struct mf_field *mf, uint32_t header)
 static void
 nxm_do_init(void)
 {
-    const struct mf_field *mf;
+    int i;
 
     hmap_init(&all_fields);
     shash_init(&mf_by_name);
-    for (mf = mf_fields; mf < &mf_fields[MFF_N_IDS]; mf++) {
+    for (i = 0; i < MFF_N_IDS; i++) {
+        const struct mf_field *mf = &mf_fields[i];
+
+        ovs_assert(mf->id == i); /* Fields must be in the enum order. */
+
         nxm_init_add_field(mf, mf->nxm_header);
         if (mf->oxm_header != mf->nxm_header) {
             nxm_init_add_field(mf, mf->oxm_header);
@@ -1017,6 +1021,46 @@ mf_are_prereqs_ok(const struct mf_field *mf, const struct flow *flow)
     NOT_REACHED();
 }
 
+/* Set field and it's prerequisities in the mask.
+ * This is only ever called for writeable 'mf's, but we do not make the
+ * distinction here. */
+void
+mf_mask_field_and_prereqs(const struct mf_field *mf, struct flow *mask)
+{
+    static const union mf_value exact_match_mask = MF_EXACT_MASK_INITIALIZER;
+
+    mf_set_flow_value(mf, &exact_match_mask, mask);
+
+    switch (mf->prereqs) {
+    case MFP_ND:
+    case MFP_ND_SOLICIT:
+    case MFP_ND_ADVERT:
+        mask->tp_src = OVS_BE16_MAX;
+        mask->tp_dst = OVS_BE16_MAX;
+        /* Fall through. */
+    case MFP_TCP:
+    case MFP_UDP:
+    case MFP_SCTP:
+    case MFP_ICMPV4:
+    case MFP_ICMPV6:
+        mask->nw_proto = 0xff;
+        /* Fall through. */
+    case MFP_ARP:
+    case MFP_IPV4:
+    case MFP_IPV6:
+    case MFP_MPLS:
+    case MFP_IP_ANY:
+        mask->dl_type = OVS_BE16_MAX;
+        break;
+    case MFP_VLAN_VID:
+        mask->vlan_tci |= htons(VLAN_CFI);
+        break;
+    case MFP_NONE:
+        break;
+    }
+}
+
+
 /* Returns true if 'value' may be a valid value *as part of a masked match*,
  * false otherwise.
  *
@@ -1494,6 +1538,24 @@ mf_set_value(const struct mf_field *mf,
     }
 }
 
+/* Unwildcard 'mask' member field described by 'mf'.  The caller is
+ * responsible for ensuring that 'mask' meets 'mf''s prerequisites. */
+void
+mf_mask_field(const struct mf_field *mf, struct flow *mask)
+{
+    static const union mf_value exact_match_mask = MF_EXACT_MASK_INITIALIZER;
+
+    /* For MFF_DL_VLAN, we cannot send a all 1's to flow_set_dl_vlan()
+     * as that will be considered as OFP10_VLAN_NONE. So consider it as a
+     * special case. For the rest, calling mf_set_flow_value() is good
+     * enough. */
+    if (mf->id == MFF_DL_VLAN) {
+        flow_set_dl_vlan(mask, htons(VLAN_VID_MASK));
+    } else {
+        mf_set_flow_value(mf, &exact_match_mask, mask);
+    }
+}
+
 /* Sets 'flow' member field described by 'mf' to 'value'.  The caller is
  * responsible for ensuring that 'flow' meets 'mf''s prerequisites.*/
 void
index dd8b95d..d3185e4 100644 (file)
@@ -297,15 +297,17 @@ struct mf_field {
 
 /* The representation of a field's value. */
 union mf_value {
-    uint8_t u8;
-    ovs_be16 be16;
-    ovs_be32 be32;
-    ovs_be64 be64;
-    uint8_t mac[ETH_ADDR_LEN];
     struct in6_addr ipv6;
+    uint8_t mac[ETH_ADDR_LEN];
+    ovs_be64 be64;
+    ovs_be32 be32;
+    ovs_be16 be16;
+    uint8_t u8;
 };
 BUILD_ASSERT_DECL(sizeof(union mf_value) == 16);
 
+#define MF_EXACT_MASK_INITIALIZER { IN6ADDR_EXACT_INIT }
+
 /* Part of a field. */
 struct mf_subfield {
     const struct mf_field *field;
@@ -341,7 +343,7 @@ void mf_get_mask(const struct mf_field *, const struct flow_wildcards *,
 
 /* Prerequisites. */
 bool mf_are_prereqs_ok(const struct mf_field *, const struct flow *);
-void mf_force_prereqs(const struct mf_field *, struct match *);
+void mf_mask_field_and_prereqs(const struct mf_field *, struct flow *mask);
 
 /* Field values. */
 bool mf_is_value_valid(const struct mf_field *, const union mf_value *value);
@@ -353,6 +355,7 @@ void mf_set_value(const struct mf_field *, const union mf_value *value,
 void mf_set_flow_value(const struct mf_field *, const union mf_value *value,
                        struct flow *);
 bool mf_is_zero(const struct mf_field *, const struct flow *);
+void mf_mask_field(const struct mf_field *, struct flow *);
 
 void mf_get(const struct mf_field *, const struct match *,
             union mf_value *value, union mf_value *mask);
index e40c099..fe54576 100644 (file)
@@ -470,6 +470,11 @@ netdev_dummy_rx_recv(struct netdev_rx *rx_, void *buffer, size_t size)
     if (packet->size <= size) {
         memcpy(buffer, packet->data, packet->size);
         retval = packet->size;
+
+        ovs_mutex_lock(&netdev->mutex);
+        netdev->stats.rx_packets++;
+        netdev->stats.rx_bytes += packet->size;
+        ovs_mutex_unlock(&netdev->mutex);
     } else {
         retval = -EMSGSIZE;
     }
@@ -870,8 +875,6 @@ netdev_dummy_receive(struct unixctl_conn *conn,
         }
 
         ovs_mutex_lock(&dummy_dev->mutex);
-        dummy_dev->stats.rx_packets++;
-        dummy_dev->stats.rx_bytes += packet->size;
         netdev_dummy_queue_packet(dummy_dev, packet);
         ovs_mutex_unlock(&dummy_dev->mutex);
     }
index 8444ab7..11cbff3 100644 (file)
@@ -280,8 +280,8 @@ oxm_pull_match__(struct ofpbuf *b, bool strict, struct match *match)
                        strict, match, NULL, NULL);
 }
 
-/* Parses the oxm formatted match description preceded by a struct ofp11_match
- * in 'b' with length 'match_len'.  Stores the result in 'match'.
+/* Parses the oxm formatted match description preceded by a struct
+ * ofp11_match_header in 'b'.  Stores the result in 'match'.
  *
  * Fails with an error when encountering unknown OXM headers.
  *
@@ -293,7 +293,7 @@ oxm_pull_match(struct ofpbuf *b, struct match *match)
 }
 
 /* Behaves the same as oxm_pull_match() with one exception.  Skips over unknown
- * PXM headers instead of failing with an error when they are encountered. */
+ * OXM headers instead of failing with an error when they are encountered. */
 enum ofperr
 oxm_pull_match_loose(struct ofpbuf *b, struct match *match)
 {
@@ -1308,13 +1308,11 @@ void
 nxm_execute_reg_move(const struct ofpact_reg_move *move,
                      struct flow *flow, struct flow_wildcards *wc)
 {
-    union mf_subvalue mask_value;
     union mf_value src_value;
     union mf_value dst_value;
 
-    memset(&mask_value, 0xff, sizeof mask_value);
-    mf_write_subfield_flow(&move->dst, &mask_value, &wc->masks);
-    mf_write_subfield_flow(&move->src, &mask_value, &wc->masks);
+    mf_mask_field_and_prereqs(move->dst.field, &wc->masks);
+    mf_mask_field_and_prereqs(move->src.field, &wc->masks);
 
     mf_get_value(move->dst.field, flow, &dst_value);
     mf_get_value(move->src.field, flow, &src_value);
@@ -1325,8 +1323,33 @@ nxm_execute_reg_move(const struct ofpact_reg_move *move,
 }
 
 void
-nxm_execute_reg_load(const struct ofpact_reg_load *load, struct flow *flow)
-{
+nxm_execute_reg_load(const struct ofpact_reg_load *load, struct flow *flow,
+                     struct flow_wildcards *wc)
+{
+    /* Since at the datapath interface we do not have set actions for
+     * individual fields, but larger sets of fields for a given protocol
+     * layer, the set action will in practice only ever apply to exactly
+     * matched flows for the given protocol layer.  For example, if the
+     * reg_load changes the IP TTL, the corresponding datapath action will
+     * rewrite also the IP addresses and TOS byte.  Since these other field
+     * values may not be explicitly set, they depend on the incoming flow field
+     * values, and are hence all of them are set in the wildcards masks, when
+     * the action is committed to the datapath.  For the rare case, where the
+     * reg_load action does not actually change the value, and no other flow
+     * field values are set (or loaded), the datapath action is skipped, and
+     * no mask bits are set.  Such a datapath flow should, however, be
+     * dependent on the specific field value, so the corresponding wildcard
+     * mask bits must be set, lest the datapath flow be applied to packets
+     * containing some other value in the field and the field value remain
+     * unchanged regardless of the incoming value.
+     *
+     * We set the masks here for the whole fields, and their prerequisities.
+     * Even if only the lower byte of a TCP destination port is set,
+     * we set the mask for the whole field, and also the ip_proto in the IP
+     * header, so that the kernel flow would not be applied on, e.g., a UDP
+     * packet, or any other IP protocol in addition to TCP packets.
+     */
+    mf_mask_field_and_prereqs(load->dst.field, &wc->masks);
     mf_write_subfield_flow(&load->dst, &load->subvalue, flow);
 }
 
index 9dcc19a..7065225 100644 (file)
@@ -85,7 +85,8 @@ void nxm_reg_load_to_nxast(const struct ofpact_reg_load *,
 
 void nxm_execute_reg_move(const struct ofpact_reg_move *, struct flow *,
                           struct flow_wildcards *);
-void nxm_execute_reg_load(const struct ofpact_reg_load *, struct flow *);
+void nxm_execute_reg_load(const struct ofpact_reg_load *, struct flow *,
+                          struct flow_wildcards *);
 void nxm_reg_load(const struct mf_subfield *, uint64_t src_data,
                   struct flow *, struct flow_wildcards *);
 
index 185cf31..19fcc1c 100644 (file)
@@ -200,7 +200,9 @@ odp_execute_actions(void *dp, struct ofpbuf *packet, struct flow *key,
             break;
 
         case OVS_ACTION_ATTR_USERSPACE: {
-            userspace(dp, packet, key, a);
+            if (userspace) {
+                userspace(dp, packet, key, a);
+            }
             break;
         }
 
index d96afa2..6875e01 100644 (file)
@@ -1399,7 +1399,8 @@ generate_all_wildcard_mask(struct ofpbuf *ofp, const struct nlattr *key)
 
 /* Appends to 'ds' a string representation of the 'key_len' bytes of
  * OVS_KEY_ATTR_* attributes in 'key'. If non-null, additionally formats the
- * 'mask_len' bytes of 'mask' which apply to 'key'. */
+ * 'mask_len' bytes of 'mask' which apply to 'key'. If 'portno_names' is
+ * non-null and 'verbose' is true, translates odp port number to its name. */
 void
 odp_flow_format(const struct nlattr *key, size_t key_len,
                 const struct nlattr *mask, size_t mask_len,
@@ -2819,7 +2820,7 @@ parse_l2_5_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
                   uint64_t present_attrs, int out_of_range_attr,
                   uint64_t expected_attrs, struct flow *flow,
                   const struct nlattr *key, size_t key_len,
-                 const struct flow *src_flow)
+                  const struct flow *src_flow)
 {
     static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
     bool is_mask = src_flow != flow;
@@ -2828,13 +2829,13 @@ parse_l2_5_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
     enum ovs_key_attr expected_bit = 0xff;
 
     if (eth_type_mpls(src_flow->dl_type)) {
-       if (!is_mask) {
-           expected_attrs |= (UINT64_C(1) << OVS_KEY_ATTR_MPLS);
+        if (!is_mask) {
+            expected_attrs |= (UINT64_C(1) << OVS_KEY_ATTR_MPLS);
 
-           if (!(present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_MPLS))) {
-               return ODP_FIT_TOO_LITTLE;
-           }
-           flow->mpls_lse = nl_attr_get_be32(attrs[OVS_KEY_ATTR_MPLS]);
+            if (!(present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_MPLS))) {
+                return ODP_FIT_TOO_LITTLE;
+            }
+            flow->mpls_lse = nl_attr_get_be32(attrs[OVS_KEY_ATTR_MPLS]);
         } else if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_MPLS)) {
             flow->mpls_lse = nl_attr_get_be32(attrs[OVS_KEY_ATTR_MPLS]);
 
@@ -2858,7 +2859,7 @@ parse_l2_5_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
             flow->nw_tos = ipv4_key->ipv4_tos;
             flow->nw_ttl = ipv4_key->ipv4_ttl;
             if (is_mask) {
-               flow->nw_frag = ipv4_key->ipv4_frag;
+                flow->nw_frag = ipv4_key->ipv4_frag;
                 check_start = ipv4_key;
                 check_len = sizeof *ipv4_key;
                 expected_bit = OVS_KEY_ATTR_IPV4;
@@ -2881,7 +2882,7 @@ parse_l2_5_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
             flow->nw_tos = ipv6_key->ipv6_tclass;
             flow->nw_ttl = ipv6_key->ipv6_hlimit;
             if (is_mask) {
-               flow->nw_frag = ipv6_key->ipv6_frag;
+                flow->nw_frag = ipv6_key->ipv6_frag;
                 check_start = ipv6_key;
                 check_len = sizeof *ipv6_key;
                 expected_bit = OVS_KEY_ATTR_IPV6;
@@ -3080,17 +3081,17 @@ parse_8021q_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
     } else {
         tci = nl_attr_get_be16(attrs[OVS_KEY_ATTR_VLAN]);
         if (!is_mask) {
-           if (tci == htons(0)) {
+            if (tci == htons(0)) {
                 /* Corner case for a truncated 802.1Q header. */
-               if (fitness == ODP_FIT_PERFECT && nl_attr_get_size(encap)) {
-                   return ODP_FIT_TOO_MUCH;
-               }
-               return fitness;
-           } else if (!(tci & htons(VLAN_CFI))) {
-               VLOG_ERR_RL(&rl, "OVS_KEY_ATTR_VLAN 0x%04"PRIx16" is nonzero "
-                           "but CFI bit is not set", ntohs(tci));
-               return ODP_FIT_ERROR;
-           }
+                if (fitness == ODP_FIT_PERFECT && nl_attr_get_size(encap)) {
+                    return ODP_FIT_TOO_MUCH;
+                }
+                return fitness;
+            } else if (!(tci & htons(VLAN_CFI))) {
+                VLOG_ERR_RL(&rl, "OVS_KEY_ATTR_VLAN 0x%04"PRIx16" is nonzero "
+                            "but CFI bit is not set", ntohs(tci));
+                return ODP_FIT_ERROR;
+            }
         }
         /* Set vlan_tci.
          * Remove the TPID from dl_type since it's not the real Ethertype.  */
index 65430f3..cccd6b1 100644 (file)
@@ -35,6 +35,50 @@ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
 \f
 /* Converting OpenFlow 1.0 to ofpacts. */
 
+union ofp_action {
+    ovs_be16 type;
+    struct ofp_action_header header;
+    struct ofp_action_vendor_header vendor;
+    struct ofp10_action_output output10;
+    struct ofp_action_vlan_vid vlan_vid;
+    struct ofp_action_vlan_pcp vlan_pcp;
+    struct ofp_action_nw_addr nw_addr;
+    struct ofp_action_nw_tos nw_tos;
+    struct ofp11_action_nw_ecn nw_ecn;
+    struct ofp11_action_nw_ttl nw_ttl;
+    struct ofp_action_tp_port tp_port;
+    struct ofp_action_dl_addr dl_addr;
+    struct ofp10_action_enqueue enqueue;
+    struct ofp11_action_output ofp11_output;
+    struct ofp11_action_push push;
+    struct ofp11_action_pop_mpls ofp11_pop_mpls;
+    struct ofp11_action_set_queue ofp11_set_queue;
+    struct ofp11_action_mpls_ttl ofp11_mpls_ttl;
+    struct ofp11_action_group group;
+    struct ofp12_action_set_field set_field;
+    struct nx_action_header nxa_header;
+    struct nx_action_resubmit resubmit;
+    struct nx_action_set_tunnel set_tunnel;
+    struct nx_action_set_tunnel64 set_tunnel64;
+    struct nx_action_write_metadata write_metadata;
+    struct nx_action_set_queue set_queue;
+    struct nx_action_reg_move reg_move;
+    struct nx_action_reg_load reg_load;
+    struct nx_action_stack stack;
+    struct nx_action_note note;
+    struct nx_action_multipath multipath;
+    struct nx_action_bundle bundle;
+    struct nx_action_output_reg output_reg;
+    struct nx_action_cnt_ids cnt_ids;
+    struct nx_action_fin_timeout fin_timeout;
+    struct nx_action_controller controller;
+    struct nx_action_push_mpls push_mpls;
+    struct nx_action_mpls_ttl mpls_ttl;
+    struct nx_action_pop_mpls pop_mpls;
+    struct nx_action_sample sample;
+    struct nx_action_learn learn;
+};
+
 static enum ofperr
 output_from_openflow10(const struct ofp10_action_output *oao,
                        struct ofpbuf *out)
@@ -237,10 +281,26 @@ sample_from_openflow(const struct nx_action_sample *nas,
     return 0;
 }
 
+static enum ofperr
+push_mpls_from_openflow(ovs_be16 ethertype, enum ofpact_mpls_position position,
+                        struct ofpbuf *out)
+{
+    struct ofpact_push_mpls *oam;
+
+    if (!eth_type_mpls(ethertype)) {
+        return OFPERR_OFPBAC_BAD_ARGUMENT;
+    }
+    oam = ofpact_put_PUSH_MPLS(out);
+    oam->ethertype = ethertype;
+    oam->position = position;
+
+    return 0;
+}
+
 static enum ofperr
 decode_nxast_action(const union ofp_action *a, enum ofputil_action_code *code)
 {
-    const struct nx_action_header *nah = (const struct nx_action_header *) a;
+    const struct nx_action_header *nah = &a->nxa_header;
     uint16_t len = ntohs(a->header.len);
 
     if (len < sizeof(struct nx_action_header)) {
@@ -275,8 +335,8 @@ decode_nxast_action(const union ofp_action *a, enum ofputil_action_code *code)
  * '*code' is indeterminate.
  *
  * The caller must have already verified that 'a''s length is potentially
- * correct (that is, a->header.len is nonzero and a multiple of sizeof(union
- * ofp_action) and no longer than the amount of space allocated to 'a').
+ * correct (that is, a->header.len is nonzero and a multiple of
+ * OFP_ACTION_ALIGN and no longer than the amount of space allocated to 'a').
  *
  * This function verifies that 'a''s length is correct for the type of action
  * that it represents. */
@@ -308,12 +368,6 @@ static enum ofperr
 ofpact_from_nxast(const union ofp_action *a, enum ofputil_action_code code,
                   struct ofpbuf *out)
 {
-    const struct nx_action_resubmit *nar;
-    const struct nx_action_set_tunnel *nast;
-    const struct nx_action_set_queue *nasq;
-    const struct nx_action_note *nan;
-    const struct nx_action_set_tunnel64 *nast64;
-    const struct nx_action_write_metadata *nawm;
     struct ofpact_tunnel *tunnel;
     enum ofperr error = 0;
 
@@ -325,24 +379,21 @@ ofpact_from_nxast(const union ofp_action *a, enum ofputil_action_code code,
         NOT_REACHED();
 
     case OFPUTIL_NXAST_RESUBMIT:
-        resubmit_from_openflow((const struct nx_action_resubmit *) a, out);
+        resubmit_from_openflow(&a->resubmit, out);
         break;
 
     case OFPUTIL_NXAST_SET_TUNNEL:
-        nast = (const struct nx_action_set_tunnel *) a;
         tunnel = ofpact_put_SET_TUNNEL(out);
         tunnel->ofpact.compat = code;
-        tunnel->tun_id = ntohl(nast->tun_id);
+        tunnel->tun_id = ntohl(a->set_tunnel.tun_id);
         break;
 
     case OFPUTIL_NXAST_WRITE_METADATA:
-        nawm = ALIGNED_CAST(const struct nx_action_write_metadata *, a);
-        error = metadata_from_nxast(nawm, out);
+        error = metadata_from_nxast(&a->write_metadata, out);
         break;
 
     case OFPUTIL_NXAST_SET_QUEUE:
-        nasq = (const struct nx_action_set_queue *) a;
-        ofpact_put_SET_QUEUE(out)->queue_id = ntohl(nasq->queue_id);
+        ofpact_put_SET_QUEUE(out)->queue_id = ntohl(a->set_queue.queue_id);
         break;
 
     case OFPUTIL_NXAST_POP_QUEUE:
@@ -350,60 +401,51 @@ ofpact_from_nxast(const union ofp_action *a, enum ofputil_action_code code,
         break;
 
     case OFPUTIL_NXAST_REG_MOVE:
-        error = nxm_reg_move_from_openflow(
-            (const struct nx_action_reg_move *) a, out);
+        error = nxm_reg_move_from_openflow(&a->reg_move, out);
         break;
 
     case OFPUTIL_NXAST_REG_LOAD:
-        error = nxm_reg_load_from_openflow(
-            ALIGNED_CAST(const struct nx_action_reg_load *, a), out);
+        error = nxm_reg_load_from_openflow(&a->reg_load, out);
         break;
 
     case OFPUTIL_NXAST_STACK_PUSH:
-        error = nxm_stack_push_from_openflow(
-            (const struct nx_action_stack *) a, out);
+        error = nxm_stack_push_from_openflow(&a->stack, out);
         break;
 
     case OFPUTIL_NXAST_STACK_POP:
-        error = nxm_stack_pop_from_openflow(
-            (const struct nx_action_stack *) a, out);
+        error = nxm_stack_pop_from_openflow(&a->stack, out);
         break;
 
     case OFPUTIL_NXAST_NOTE:
-        nan = (const struct nx_action_note *) a;
-        note_from_openflow(nan, out);
+        note_from_openflow(&a->note, out);
         break;
 
     case OFPUTIL_NXAST_SET_TUNNEL64:
-        nast64 = ALIGNED_CAST(const struct nx_action_set_tunnel64 *, a);
         tunnel = ofpact_put_SET_TUNNEL(out);
         tunnel->ofpact.compat = code;
-        tunnel->tun_id = ntohll(nast64->tun_id);
+        tunnel->tun_id = ntohll(a->set_tunnel64.tun_id);
         break;
 
     case OFPUTIL_NXAST_MULTIPATH:
-        error = multipath_from_openflow((const struct nx_action_multipath *) a,
+        error = multipath_from_openflow(&a->multipath,
                                         ofpact_put_MULTIPATH(out));
         break;
 
     case OFPUTIL_NXAST_BUNDLE:
     case OFPUTIL_NXAST_BUNDLE_LOAD:
-        error = bundle_from_openflow((const struct nx_action_bundle *) a, out);
+        error = bundle_from_openflow(&a->bundle, out);
         break;
 
     case OFPUTIL_NXAST_OUTPUT_REG:
-        error = output_reg_from_openflow(
-            (const struct nx_action_output_reg *) a, out);
+        error = output_reg_from_openflow(&a->output_reg, out);
         break;
 
     case OFPUTIL_NXAST_RESUBMIT_TABLE:
-        nar = (const struct nx_action_resubmit *) a;
-        error = resubmit_table_from_openflow(nar, out);
+        error = resubmit_table_from_openflow(&a->resubmit, out);
         break;
 
     case OFPUTIL_NXAST_LEARN:
-        error = learn_from_openflow(
-            ALIGNED_CAST(const struct nx_action_learn *, a), out);
+        error = learn_from_openflow(&a->learn, out);
         break;
 
     case OFPUTIL_NXAST_EXIT:
@@ -415,50 +457,39 @@ ofpact_from_nxast(const union ofp_action *a, enum ofputil_action_code code,
         break;
 
     case OFPUTIL_NXAST_DEC_TTL_CNT_IDS:
-        error = dec_ttl_cnt_ids_from_openflow(
-                    (const struct nx_action_cnt_ids *) a, out);
+        error = dec_ttl_cnt_ids_from_openflow(&a->cnt_ids, out);
         break;
 
     case OFPUTIL_NXAST_FIN_TIMEOUT:
-        fin_timeout_from_openflow(
-            (const struct nx_action_fin_timeout *) a, out);
+        fin_timeout_from_openflow(&a->fin_timeout, out);
         break;
 
     case OFPUTIL_NXAST_CONTROLLER:
-        controller_from_openflow((const struct nx_action_controller *) a, out);
+        controller_from_openflow(&a->controller, out);
         break;
 
-    case OFPUTIL_NXAST_PUSH_MPLS: {
-        struct nx_action_push_mpls *nxapm = (struct nx_action_push_mpls *)a;
-        if (!eth_type_mpls(nxapm->ethertype)) {
-            return OFPERR_OFPBAC_BAD_ARGUMENT;
-        }
-        ofpact_put_PUSH_MPLS(out)->ethertype = nxapm->ethertype;
+    case OFPUTIL_NXAST_PUSH_MPLS:
+        error = push_mpls_from_openflow(a->push_mpls.ethertype,
+                                        OFPACT_MPLS_AFTER_VLAN, out);
         break;
-    }
 
-    case OFPUTIL_NXAST_SET_MPLS_TTL: {
-        struct nx_action_mpls_ttl *nxamt = (struct nx_action_mpls_ttl *)a;
-        ofpact_put_SET_MPLS_TTL(out)->ttl = nxamt->ttl;
+    case OFPUTIL_NXAST_SET_MPLS_TTL:
+        ofpact_put_SET_MPLS_TTL(out)->ttl = a->mpls_ttl.ttl;
         break;
-    }
 
     case OFPUTIL_NXAST_DEC_MPLS_TTL:
         ofpact_put_DEC_MPLS_TTL(out);
         break;
 
-    case OFPUTIL_NXAST_POP_MPLS: {
-        struct nx_action_pop_mpls *nxapm = (struct nx_action_pop_mpls *)a;
-        if (eth_type_mpls(nxapm->ethertype)) {
+    case OFPUTIL_NXAST_POP_MPLS:
+        if (eth_type_mpls(a->pop_mpls.ethertype)) {
             return OFPERR_OFPBAC_BAD_ARGUMENT;
         }
-        ofpact_put_POP_MPLS(out)->ethertype = nxapm->ethertype;
+        ofpact_put_POP_MPLS(out)->ethertype = a->pop_mpls.ethertype;
         break;
-    }
 
     case OFPUTIL_NXAST_SAMPLE:
-        error = sample_from_openflow(
-            (const struct nx_action_sample *) a, out);
+        error = sample_from_openflow(&a->sample, out);
         break;
     }
 
@@ -504,13 +535,13 @@ ofpact_from_openflow10(const union ofp_action *a, struct ofpbuf *out)
         break;
 
     case OFPUTIL_OFPAT10_SET_DL_SRC:
-        memcpy(ofpact_put_SET_ETH_SRC(out)->mac,
-               ((const struct ofp_action_dl_addr *) a)->dl_addr, ETH_ADDR_LEN);
+        memcpy(ofpact_put_SET_ETH_SRC(out)->mac, a->dl_addr.dl_addr,
+               ETH_ADDR_LEN);
         break;
 
     case OFPUTIL_OFPAT10_SET_DL_DST:
-        memcpy(ofpact_put_SET_ETH_DST(out)->mac,
-               ((const struct ofp_action_dl_addr *) a)->dl_addr, ETH_ADDR_LEN);
+        memcpy(ofpact_put_SET_ETH_DST(out)->mac, a->dl_addr.dl_addr,
+               ETH_ADDR_LEN);
         break;
 
     case OFPUTIL_OFPAT10_SET_NW_SRC:
@@ -525,7 +556,7 @@ ofpact_from_openflow10(const union ofp_action *a, struct ofpbuf *out)
         if (a->nw_tos.nw_tos & ~IP_DSCP_MASK) {
             return OFPERR_OFPBAC_BAD_ARGUMENT;
         }
-        ofpact_put_SET_IPV4_DSCP(out)->dscp = a->nw_tos.nw_tos;
+        ofpact_put_SET_IP_DSCP(out)->dscp = a->nw_tos.nw_tos;
         break;
 
     case OFPUTIL_OFPAT10_SET_TP_SRC:
@@ -538,8 +569,7 @@ ofpact_from_openflow10(const union ofp_action *a, struct ofpbuf *out)
         break;
 
     case OFPUTIL_OFPAT10_ENQUEUE:
-        error = enqueue_from_openflow10((const struct ofp10_action_enqueue *) a,
-                                        out);
+        error = enqueue_from_openflow10(&a->enqueue, out);
         break;
 
 #define NXAST_ACTION(ENUM, STRUCT, EXTENSIBLE, NAME) case OFPUTIL_##ENUM:
@@ -558,32 +588,33 @@ action_next(const union ofp_action *a)
 }
 
 static inline bool
-action_is_valid(const union ofp_action *a, size_t n_actions)
+action_is_valid(const union ofp_action *a, size_t max_actions)
 {
     uint16_t len = ntohs(a->header.len);
     return (!(len % OFP_ACTION_ALIGN)
-            && len >= sizeof *a
-            && len / sizeof *a <= n_actions);
+            && len >= OFP_ACTION_ALIGN
+            && len / OFP_ACTION_ALIGN <= max_actions);
 }
 
 /* This macro is careful to check for actions with bad lengths. */
-#define ACTION_FOR_EACH(ITER, LEFT, ACTIONS, N_ACTIONS)                 \
-    for ((ITER) = (ACTIONS), (LEFT) = (N_ACTIONS);                      \
+#define ACTION_FOR_EACH(ITER, LEFT, ACTIONS, MAX_ACTIONS)                 \
+    for ((ITER) = (ACTIONS), (LEFT) = (MAX_ACTIONS);                      \
          (LEFT) > 0 && action_is_valid(ITER, LEFT);                     \
-         ((LEFT) -= ntohs((ITER)->header.len) / sizeof(union ofp_action), \
+         ((LEFT) -= ntohs((ITER)->header.len) / OFP_ACTION_ALIGN, \
           (ITER) = action_next(ITER)))
 
 static void
-log_bad_action(const union ofp_action *actions, size_t n_actions, size_t ofs,
-               enum ofperr error)
+log_bad_action(const union ofp_action *actions, size_t max_actions,
+               const union ofp_action *bad_action, enum ofperr error)
 {
     if (!VLOG_DROP_WARN(&rl)) {
         struct ds s;
 
         ds_init(&s);
-        ds_put_hex_dump(&s, actions, n_actions * sizeof *actions, 0, false);
-        VLOG_WARN("bad action at offset %#zx (%s):\n%s",
-                  ofs * sizeof *actions, ofperr_get_name(error), ds_cstr(&s));
+        ds_put_hex_dump(&s, actions, max_actions * OFP_ACTION_ALIGN, 0, false);
+        VLOG_WARN("bad action at offset %#tx (%s):\n%s",
+                  (char *)bad_action - (char *)actions,
+                  ofperr_get_name(error), ds_cstr(&s));
         ds_destroy(&s);
     }
 }
@@ -600,13 +631,13 @@ ofpacts_from_openflow(const union ofp_action *in, size_t n_in,
     ACTION_FOR_EACH (a, left, in, n_in) {
         enum ofperr error = ofpact_from_openflow(a, out);
         if (error) {
-            log_bad_action(in, n_in, a - in, error);
+            log_bad_action(in, n_in, a, error);
             return error;
         }
     }
     if (left) {
         enum ofperr error = OFPERR_OFPBAC_BAD_LEN;
-        log_bad_action(in, n_in, n_in - left, error);
+        log_bad_action(in, n_in, a, error);
         return error;
     }
 
@@ -625,7 +656,7 @@ static enum ofperr
 ofpacts_pull_actions(struct ofpbuf *openflow, unsigned int actions_len,
                      struct ofpbuf *ofpacts,
                      enum ofperr (*translate)(const union ofp_action *actions,
-                                              size_t n_actions,
+                                              size_t max_actions,
                                               struct ofpbuf *ofpacts))
 {
     static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
@@ -686,8 +717,8 @@ ofpacts_pull_openflow10(struct ofpbuf *openflow, unsigned int actions_len,
  * '*code' is indeterminate.
  *
  * The caller must have already verified that 'a''s length is potentially
- * correct (that is, a->header.len is nonzero and a multiple of sizeof(union
- * ofp_action) and no longer than the amount of space allocated to 'a').
+ * correct (that is, a->header.len is nonzero and a multiple of
+ * OFP_ACTION_ALIGN and no longer than the amount of space allocated to 'a').
  *
  * This function verifies that 'a''s length is correct for the type of action
  * that it represents. */
@@ -756,8 +787,7 @@ ofpact_from_openflow11(const union ofp_action *a, struct ofpbuf *out)
         NOT_REACHED();
 
     case OFPUTIL_OFPAT11_OUTPUT:
-        return output_from_openflow11((const struct ofp11_action_output *) a,
-                                      out);
+        return output_from_openflow11(&a->ofp11_output, out);
 
     case OFPUTIL_OFPAT11_SET_VLAN_VID:
         if (a->vlan_vid.vlan_vid & ~htons(0xfff)) {
@@ -774,8 +804,7 @@ ofpact_from_openflow11(const union ofp_action *a, struct ofpbuf *out)
         break;
 
     case OFPUTIL_OFPAT11_PUSH_VLAN:
-        if (((const struct ofp11_action_push *)a)->ethertype !=
-            htons(ETH_TYPE_VLAN_8021Q)) {
+        if (a->push.ethertype != htons(ETH_TYPE_VLAN_8021Q)) {
             /* XXX 802.1AD(QinQ) isn't supported at the moment */
             return OFPERR_OFPBAC_BAD_ARGUMENT;
         }
@@ -788,17 +817,17 @@ ofpact_from_openflow11(const union ofp_action *a, struct ofpbuf *out)
 
     case OFPUTIL_OFPAT11_SET_QUEUE:
         ofpact_put_SET_QUEUE(out)->queue_id =
-            ntohl(((const struct ofp11_action_set_queue *)a)->queue_id);
+            ntohl(a->ofp11_set_queue.queue_id);
         break;
 
     case OFPUTIL_OFPAT11_SET_DL_SRC:
-        memcpy(ofpact_put_SET_ETH_SRC(out)->mac,
-               ((const struct ofp_action_dl_addr *) a)->dl_addr, ETH_ADDR_LEN);
+        memcpy(ofpact_put_SET_ETH_SRC(out)->mac, a->dl_addr.dl_addr,
+               ETH_ADDR_LEN);
         break;
 
     case OFPUTIL_OFPAT11_SET_DL_DST:
-        memcpy(ofpact_put_SET_ETH_DST(out)->mac,
-               ((const struct ofp_action_dl_addr *) a)->dl_addr, ETH_ADDR_LEN);
+        memcpy(ofpact_put_SET_ETH_DST(out)->mac, a->dl_addr.dl_addr,
+               ETH_ADDR_LEN);
         break;
 
     case OFPUTIL_OFPAT11_DEC_NW_TTL:
@@ -817,7 +846,18 @@ ofpact_from_openflow11(const union ofp_action *a, struct ofpbuf *out)
         if (a->nw_tos.nw_tos & ~IP_DSCP_MASK) {
             return OFPERR_OFPBAC_BAD_ARGUMENT;
         }
-        ofpact_put_SET_IPV4_DSCP(out)->dscp = a->nw_tos.nw_tos;
+        ofpact_put_SET_IP_DSCP(out)->dscp = a->nw_tos.nw_tos;
+        break;
+
+    case OFPUTIL_OFPAT11_SET_NW_ECN:
+        if (a->nw_ecn.nw_ecn & ~IP_ECN_MASK) {
+            return OFPERR_OFPBAC_BAD_ARGUMENT;
+        }
+        ofpact_put_SET_IP_ECN(out)->ecn = a->nw_ecn.nw_ecn;
+        break;
+
+    case OFPUTIL_OFPAT11_SET_NW_TTL:
+        ofpact_put_SET_IP_TTL(out)->ttl = a->nw_ttl.nw_ttl;
         break;
 
     case OFPUTIL_OFPAT11_SET_TP_SRC:
@@ -829,42 +869,31 @@ ofpact_from_openflow11(const union ofp_action *a, struct ofpbuf *out)
         break;
 
     case OFPUTIL_OFPAT12_SET_FIELD:
-        return nxm_reg_load_from_openflow12_set_field(
-            (const struct ofp12_action_set_field *)a, out);
+        return nxm_reg_load_from_openflow12_set_field(&a->set_field, out);
 
-    case OFPUTIL_OFPAT11_SET_MPLS_TTL: {
-        struct ofp11_action_mpls_ttl *oamt = (struct ofp11_action_mpls_ttl *)a;
-        ofpact_put_SET_MPLS_TTL(out)->ttl = oamt->mpls_ttl;
+    case OFPUTIL_OFPAT11_SET_MPLS_TTL:
+        ofpact_put_SET_MPLS_TTL(out)->ttl = a->ofp11_mpls_ttl.mpls_ttl;
         break;
-    }
 
     case OFPUTIL_OFPAT11_DEC_MPLS_TTL:
         ofpact_put_DEC_MPLS_TTL(out);
         break;
 
-    case OFPUTIL_OFPAT11_PUSH_MPLS: {
-        struct ofp11_action_push *oap = (struct ofp11_action_push *)a;
-        if (!eth_type_mpls(oap->ethertype)) {
-            return OFPERR_OFPBAC_BAD_ARGUMENT;
-        }
-        ofpact_put_PUSH_MPLS(out)->ethertype = oap->ethertype;
+    case OFPUTIL_OFPAT11_PUSH_MPLS:
+        error = push_mpls_from_openflow(a->push.ethertype,
+                                        OFPACT_MPLS_AFTER_VLAN, out);
         break;
-    }
 
-    case OFPUTIL_OFPAT11_POP_MPLS: {
-        struct ofp11_action_pop_mpls *oapm = (struct ofp11_action_pop_mpls *)a;
-        if (eth_type_mpls(oapm->ethertype)) {
+    case OFPUTIL_OFPAT11_POP_MPLS:
+        if (eth_type_mpls(a->ofp11_pop_mpls.ethertype)) {
             return OFPERR_OFPBAC_BAD_ARGUMENT;
         }
-        ofpact_put_POP_MPLS(out)->ethertype = oapm->ethertype;
+        ofpact_put_POP_MPLS(out)->ethertype = a->ofp11_pop_mpls.ethertype;
         break;
-    }
 
-    case OFPUTIL_OFPAT11_GROUP: {
-        struct ofp11_action_group *oag = (struct ofp11_action_group *)a;
-        ofpact_put_GROUP(out)->group_id = ntohl(oag->group_id);
+    case OFPUTIL_OFPAT11_GROUP:
+        ofpact_put_GROUP(out)->group_id = ntohl(a->group.group_id);
         break;
-    }
 
 #define NXAST_ACTION(ENUM, STRUCT, EXTENSIBLE, NAME) case OFPUTIL_##ENUM:
 #include "ofp-util.def"
@@ -880,6 +909,266 @@ ofpacts_from_openflow11(const union ofp_action *in, size_t n_in,
 {
     return ofpacts_from_openflow(in, n_in, out, ofpact_from_openflow11);
 }
+
+/* True if an action sets the value of a field
+ * in a way that is compatibile with the action set.
+ * False otherwise. */
+static bool
+ofpact_is_set_action(const struct ofpact *a)
+{
+    switch (a->type) {
+    case OFPACT_REG_LOAD:
+    case OFPACT_SET_ETH_DST:
+    case OFPACT_SET_ETH_SRC:
+    case OFPACT_SET_IP_DSCP:
+    case OFPACT_SET_IP_ECN:
+    case OFPACT_SET_IP_TTL:
+    case OFPACT_SET_IPV4_DST:
+    case OFPACT_SET_IPV4_SRC:
+    case OFPACT_SET_L4_DST_PORT:
+    case OFPACT_SET_L4_SRC_PORT:
+    case OFPACT_SET_MPLS_TTL:
+    case OFPACT_SET_QUEUE:
+    case OFPACT_SET_TUNNEL:
+    case OFPACT_SET_VLAN_PCP:
+    case OFPACT_SET_VLAN_VID:
+        return true;
+    case OFPACT_BUNDLE:
+    case OFPACT_CLEAR_ACTIONS:
+    case OFPACT_CONTROLLER:
+    case OFPACT_DEC_MPLS_TTL:
+    case OFPACT_DEC_TTL:
+    case OFPACT_ENQUEUE:
+    case OFPACT_EXIT:
+    case OFPACT_FIN_TIMEOUT:
+    case OFPACT_GOTO_TABLE:
+    case OFPACT_GROUP:
+    case OFPACT_LEARN:
+    case OFPACT_METER:
+    case OFPACT_MULTIPATH:
+    case OFPACT_NOTE:
+    case OFPACT_OUTPUT:
+    case OFPACT_OUTPUT_REG:
+    case OFPACT_POP_MPLS:
+    case OFPACT_POP_QUEUE:
+    case OFPACT_PUSH_MPLS:
+    case OFPACT_PUSH_VLAN:
+    case OFPACT_REG_MOVE:
+    case OFPACT_RESUBMIT:
+    case OFPACT_SAMPLE:
+    case OFPACT_STACK_POP:
+    case OFPACT_STACK_PUSH:
+    case OFPACT_STRIP_VLAN:
+    case OFPACT_WRITE_ACTIONS:
+    case OFPACT_WRITE_METADATA:
+        return false;
+    default:
+        NOT_REACHED();
+    }
+}
+
+/* True if an action is allowed in the action set.
+ * False otherwise. */
+static bool
+ofpact_is_allowed_in_actions_set(const struct ofpact *a)
+{
+    switch (a->type) {
+    case OFPACT_DEC_MPLS_TTL:
+    case OFPACT_DEC_TTL:
+    case OFPACT_GROUP:
+    case OFPACT_OUTPUT:
+    case OFPACT_POP_MPLS:
+    case OFPACT_PUSH_MPLS:
+    case OFPACT_PUSH_VLAN:
+    case OFPACT_REG_LOAD:
+    case OFPACT_SET_ETH_DST:
+    case OFPACT_SET_ETH_SRC:
+    case OFPACT_SET_IP_DSCP:
+    case OFPACT_SET_IP_ECN:
+    case OFPACT_SET_IP_TTL:
+    case OFPACT_SET_IPV4_DST:
+    case OFPACT_SET_IPV4_SRC:
+    case OFPACT_SET_L4_DST_PORT:
+    case OFPACT_SET_L4_SRC_PORT:
+    case OFPACT_SET_MPLS_TTL:
+    case OFPACT_SET_QUEUE:
+    case OFPACT_SET_TUNNEL:
+    case OFPACT_SET_VLAN_PCP:
+    case OFPACT_SET_VLAN_VID:
+    case OFPACT_STRIP_VLAN:
+        return true;
+
+    /* In general these actions are excluded because they are not part of
+     * the OpenFlow specification nor map to actions that are defined in
+     * the specification.  Thus the order in which they should be applied
+     * in the action set is undefined. */
+    case OFPACT_BUNDLE:
+    case OFPACT_CONTROLLER:
+    case OFPACT_ENQUEUE:
+    case OFPACT_EXIT:
+    case OFPACT_FIN_TIMEOUT:
+    case OFPACT_LEARN:
+    case OFPACT_MULTIPATH:
+    case OFPACT_NOTE:
+    case OFPACT_OUTPUT_REG:
+    case OFPACT_POP_QUEUE:
+    case OFPACT_REG_MOVE:
+    case OFPACT_RESUBMIT:
+    case OFPACT_SAMPLE:
+    case OFPACT_STACK_POP:
+    case OFPACT_STACK_PUSH:
+
+    /* The action set may only include actions and thus
+     * may not include any instructions */
+    case OFPACT_CLEAR_ACTIONS:
+    case OFPACT_GOTO_TABLE:
+    case OFPACT_METER:
+    case OFPACT_WRITE_ACTIONS:
+    case OFPACT_WRITE_METADATA:
+        return false;
+    default:
+        NOT_REACHED();
+    }
+}
+
+/* Append ofpact 'a' onto the tail of 'out' */
+static void
+ofpact_copy(struct ofpbuf *out, const struct ofpact *a)
+{
+    ofpbuf_put(out, a, OFPACT_ALIGN(a->len));
+}
+
+/* Copies the last ofpact whose type is 'filter' from 'in' to 'out'. */
+static bool
+ofpacts_copy_last(struct ofpbuf *out, const struct ofpbuf *in,
+                  enum ofpact_type filter)
+{
+    const struct ofpact *target;
+    const struct ofpact *a;
+
+    target = NULL;
+    OFPACT_FOR_EACH (a, in->data, in->size) {
+        if (a->type == filter) {
+            target = a;
+        }
+    }
+    if (target) {
+        ofpact_copy(out, target);
+    }
+    return target != NULL;
+}
+
+/* Append all ofpacts, for which 'filter' returns true, from 'in' to 'out'.
+ * The order of appended ofpacts is preserved between 'in' and 'out' */
+static void
+ofpacts_copy_all(struct ofpbuf *out, const struct ofpbuf *in,
+                 bool (*filter)(const struct ofpact *))
+{
+    const struct ofpact *a;
+
+    OFPACT_FOR_EACH (a, in->data, in->size) {
+        if (filter(a)) {
+            ofpact_copy(out, a);
+        }
+    }
+}
+
+/* Reads 'action_set', which contains ofpacts accumulated by
+ * OFPACT_WRITE_ACTIONS instructions, and writes equivalent actions to be
+ * executed directly into 'action_list'.  (These names correspond to the
+ * "Action Set" and "Action List" terms used in OpenFlow 1.1+.)
+ *
+ * In general this involves appending the last instance of each action that is
+ * adimissible in the action set in the order described in the OpenFlow
+ * specification.
+ *
+ * Exceptions:
+ * + output action is only appended if no group action was present in 'in'.
+ * + As a simplification all set actions are copied in the order the are
+ *   provided in 'in' as many set actions applied to a field has the same
+ *   affect as only applying the last action that sets a field and
+ *   duplicates are removed by do_xlate_actions().
+ *   This has an unwanted side-effect of compsoting multiple
+ *   LOAD_REG actions that touch different regions of the same field. */
+void
+ofpacts_execute_action_set(struct ofpbuf *action_list,
+                           const struct ofpbuf *action_set)
+{
+    /* The OpenFlow spec "Action Set" section specifies this order. */
+    ofpacts_copy_last(action_list, action_set, OFPACT_STRIP_VLAN);
+    ofpacts_copy_last(action_list, action_set, OFPACT_POP_MPLS);
+    ofpacts_copy_last(action_list, action_set, OFPACT_PUSH_MPLS);
+    ofpacts_copy_last(action_list, action_set, OFPACT_PUSH_VLAN);
+    ofpacts_copy_last(action_list, action_set, OFPACT_DEC_TTL);
+    ofpacts_copy_last(action_list, action_set, OFPACT_DEC_MPLS_TTL);
+    ofpacts_copy_all(action_list, action_set, ofpact_is_set_action);
+    ofpacts_copy_last(action_list, action_set, OFPACT_SET_QUEUE);
+
+    /* If both OFPACT_GROUP and OFPACT_OUTPUT are present, OpenFlow says that
+     * we should execute only OFPACT_GROUP.
+     *
+     * If neither OFPACT_GROUP nor OFPACT_OUTPUT is present, then we can drop
+     * all the actions because there's no point in modifying a packet that will
+     * not be sent anywhere. */
+    if (!ofpacts_copy_last(action_list, action_set, OFPACT_GROUP) &&
+        !ofpacts_copy_last(action_list, action_set, OFPACT_OUTPUT)) {
+        ofpbuf_clear(action_list);
+    }
+}
+
+
+static enum ofperr
+ofpacts_from_openflow11_for_action_set(const union ofp_action *in,
+                                       size_t n_in, struct ofpbuf *out)
+{
+    enum ofperr error;
+    struct ofpact *a;
+    size_t start = out->size;
+
+    error = ofpacts_from_openflow11(in, n_in, out);
+    if (error) {
+        return error;
+    }
+
+    OFPACT_FOR_EACH (a, ofpact_end(out->data, start), out->size - start) {
+        if (!ofpact_is_allowed_in_actions_set(a)) {
+            VLOG_WARN_RL(&rl, "disallowed action in action set");
+            return OFPERR_OFPBAC_BAD_TYPE;
+        }
+    }
+
+    return 0;
+}
+
+\f
+static enum ofperr
+ofpact_from_openflow13(const union ofp_action *a, struct ofpbuf *out)
+{
+    enum ofputil_action_code code;
+    enum ofperr error;
+
+    error = decode_openflow11_action(a, &code);
+    if (error) {
+        return error;
+    }
+
+    if (code == OFPUTIL_OFPAT11_PUSH_MPLS) {
+        struct ofp11_action_push *oap = (struct ofp11_action_push *)a;
+        error = push_mpls_from_openflow(oap->ethertype,
+                                        OFPACT_MPLS_BEFORE_VLAN, out);
+    } else {
+        error = ofpact_from_openflow11(a, out);
+    }
+
+    return error;
+}
+
+static enum ofperr
+ofpacts_from_openflow13(const union ofp_action *in, size_t n_in,
+                        struct ofpbuf *out)
+{
+    return ofpacts_from_openflow(in, n_in, out, ofpact_from_openflow13);
+}
 \f
 /* OpenFlow 1.1 instructions. */
 
@@ -946,6 +1235,8 @@ ovs_instruction_type_from_ofpact_type(enum ofpact_type type)
         return OVSINST_OFPIT13_METER;
     case OFPACT_CLEAR_ACTIONS:
         return OVSINST_OFPIT11_CLEAR_ACTIONS;
+    case OFPACT_WRITE_ACTIONS:
+        return OVSINST_OFPIT11_WRITE_ACTIONS;
     case OFPACT_WRITE_METADATA:
         return OVSINST_OFPIT11_WRITE_METADATA;
     case OFPACT_GOTO_TABLE:
@@ -964,7 +1255,9 @@ ovs_instruction_type_from_ofpact_type(enum ofpact_type type)
     case OFPACT_SET_ETH_DST:
     case OFPACT_SET_IPV4_SRC:
     case OFPACT_SET_IPV4_DST:
-    case OFPACT_SET_IPV4_DSCP:
+    case OFPACT_SET_IP_DSCP:
+    case OFPACT_SET_IP_ECN:
+    case OFPACT_SET_IP_TTL:
     case OFPACT_SET_L4_SRC_PORT:
     case OFPACT_SET_L4_DST_PORT:
     case OFPACT_REG_MOVE:
@@ -1079,17 +1372,20 @@ decode_openflow11_instructions(const struct ofp11_instruction insts[],
 static void
 get_actions_from_instruction(const struct ofp11_instruction *inst,
                              const union ofp_action **actions,
-                             size_t *n_actions)
+                             size_t *max_actions)
 {
     *actions = ALIGNED_CAST(const union ofp_action *, inst + 1);
-    *n_actions = (ntohs(inst->len) - sizeof *inst) / OFP11_INSTRUCTION_ALIGN;
+    *max_actions = (ntohs(inst->len) - sizeof *inst) / OFP11_INSTRUCTION_ALIGN;
 }
 
-/* Attempts to convert 'actions_len' bytes of OpenFlow 1.1 actions from the
+/* Attempts to convert 'actions_len' bytes of OpenFlow actions from the
  * front of 'openflow' into ofpacts.  On success, replaces any existing content
  * in 'ofpacts' by the converted ofpacts; on failure, clears 'ofpacts'.
  * Returns 0 if successful, otherwise an OpenFlow error.
  *
+ * Actions are processed according to their OpenFlow version which
+ * is provided in the 'version' parameter.
+ *
  * In most places in OpenFlow 1.1 and 1.2, actions appear encapsulated in
  * instructions, so you should call ofpacts_pull_openflow11_instructions()
  * instead of this function.
@@ -1101,15 +1397,27 @@ get_actions_from_instruction(const struct ofp11_instruction *inst,
  * valid in a specific context. */
 enum ofperr
 ofpacts_pull_openflow11_actions(struct ofpbuf *openflow,
+                                enum ofp_version version,
                                 unsigned int actions_len,
                                 struct ofpbuf *ofpacts)
 {
-    return ofpacts_pull_actions(openflow, actions_len, ofpacts,
-                                ofpacts_from_openflow11);
+    switch (version) {
+    case OFP10_VERSION:
+    case OFP11_VERSION:
+    case OFP12_VERSION:
+        return ofpacts_pull_actions(openflow, actions_len, ofpacts,
+                                    ofpacts_from_openflow11);
+    case OFP13_VERSION:
+        return ofpacts_pull_actions(openflow, actions_len, ofpacts,
+                                    ofpacts_from_openflow13);
+    default:
+        NOT_REACHED();
+    }
 }
 
 enum ofperr
 ofpacts_pull_openflow11_instructions(struct ofpbuf *openflow,
+                                     enum ofp_version version,
                                      unsigned int instructions_len,
                                      struct ofpbuf *ofpacts)
 {
@@ -1156,11 +1464,22 @@ ofpacts_pull_openflow11_instructions(struct ofpbuf *openflow,
     }
     if (insts[OVSINST_OFPIT11_APPLY_ACTIONS]) {
         const union ofp_action *actions;
-        size_t n_actions;
+        size_t max_actions;
 
         get_actions_from_instruction(insts[OVSINST_OFPIT11_APPLY_ACTIONS],
-                                     &actions, &n_actions);
-        error = ofpacts_from_openflow11(actions, n_actions, ofpacts);
+                                     &actions, &max_actions);
+        switch (version) {
+        case OFP10_VERSION:
+        case OFP11_VERSION:
+        case OFP12_VERSION:
+            error = ofpacts_from_openflow11(actions, max_actions, ofpacts);
+            break;
+        case OFP13_VERSION:
+            error = ofpacts_from_openflow13(actions, max_actions, ofpacts);
+            break;
+        default:
+            NOT_REACHED();
+        }
         if (error) {
             goto exit;
         }
@@ -1170,7 +1489,26 @@ ofpacts_pull_openflow11_instructions(struct ofpbuf *openflow,
             insts[OVSINST_OFPIT11_CLEAR_ACTIONS]);
         ofpact_put_CLEAR_ACTIONS(ofpacts);
     }
-    /* XXX Write-Actions */
+    if (insts[OVSINST_OFPIT11_WRITE_ACTIONS]) {
+        struct ofpact_nest *on;
+        const union ofp_action *actions;
+        size_t max_actions;
+        size_t start;
+
+        ofpact_pad(ofpacts);
+        start = ofpacts->size;
+        on = ofpact_put(ofpacts, OFPACT_WRITE_ACTIONS,
+                        offsetof(struct ofpact_nest, actions));
+        get_actions_from_instruction(insts[OVSINST_OFPIT11_WRITE_ACTIONS],
+                                     &actions, &max_actions);
+        error = ofpacts_from_openflow11_for_action_set(actions, max_actions,
+                                                       ofpacts);
+        if (error) {
+            goto exit;
+        }
+        on = ofpbuf_at_assert(ofpacts, start, sizeof *on);
+        on->ofpact.len = ofpacts->size - start;
+    }
     if (insts[OVSINST_OFPIT11_WRITE_METADATA]) {
         const struct ofp11_instruction_write_metadata *oiwm;
         struct ofpact_metadata *om;
@@ -1192,11 +1530,6 @@ ofpacts_pull_openflow11_instructions(struct ofpbuf *openflow,
         ogt->table_id = oigt->table_id;
     }
 
-    if (insts[OVSINST_OFPIT11_WRITE_ACTIONS]) {
-        error = OFPERR_OFPBIC_UNSUP_INST;
-        goto exit;
-    }
-
     error = ofpacts_verify(ofpacts->data, ofpacts->size);
 exit:
     if (error) {
@@ -1208,7 +1541,7 @@ exit:
 /* May modify flow->dl_type, caller must restore it. */
 static enum ofperr
 ofpact_check__(const struct ofpact *a, struct flow *flow, ofp_port_t max_ports,
-               uint8_t table_id)
+               uint8_t table_id, bool enforce_consistency)
 {
     const struct ofpact_enqueue *enqueue;
 
@@ -1237,15 +1570,48 @@ ofpact_check__(const struct ofpact *a, struct flow *flow, ofp_port_t max_ports,
 
     case OFPACT_SET_VLAN_VID:
     case OFPACT_SET_VLAN_PCP:
+        return 0;
+
     case OFPACT_STRIP_VLAN:
+        if (!(flow->vlan_tci & htons(VLAN_CFI))) {
+            goto inconsistent;
+        }
+        return 0;
+
     case OFPACT_PUSH_VLAN:
+        if (flow->vlan_tci & htons(VLAN_CFI)) {
+            /* Multiple VLAN headers not supported. */
+            return OFPERR_OFPBAC_BAD_TAG;
+        }
+        return 0;
+
     case OFPACT_SET_ETH_SRC:
     case OFPACT_SET_ETH_DST:
+        return 0;
+
     case OFPACT_SET_IPV4_SRC:
     case OFPACT_SET_IPV4_DST:
-    case OFPACT_SET_IPV4_DSCP:
+        if (flow->dl_type != htons(ETH_TYPE_IP)) {
+            goto inconsistent;
+        }
+        return 0;
+
+    case OFPACT_SET_IP_DSCP:
+    case OFPACT_SET_IP_ECN:
+    case OFPACT_SET_IP_TTL:
+    case OFPACT_DEC_TTL:
+        if (!is_ip_any(flow)) {
+            goto inconsistent;
+        }
+        return 0;
+
     case OFPACT_SET_L4_SRC_PORT:
     case OFPACT_SET_L4_DST_PORT:
+        if (!is_ip_any(flow) ||
+            (flow->nw_proto != IPPROTO_TCP && flow->nw_proto != IPPROTO_UDP
+             && flow->nw_proto != IPPROTO_SCTP)) {
+            goto inconsistent;
+        }
         return 0;
 
     case OFPACT_REG_MOVE:
@@ -1260,16 +1626,25 @@ ofpact_check__(const struct ofpact *a, struct flow *flow, ofp_port_t max_ports,
     case OFPACT_STACK_POP:
         return nxm_stack_pop_check(ofpact_get_STACK_POP(a), flow);
 
-    case OFPACT_DEC_TTL:
     case OFPACT_SET_MPLS_TTL:
     case OFPACT_DEC_MPLS_TTL:
+        if (!eth_type_mpls(flow->dl_type)) {
+            goto inconsistent;
+        }
+        return 0;
+
     case OFPACT_SET_TUNNEL:
     case OFPACT_SET_QUEUE:
     case OFPACT_POP_QUEUE:
-    case OFPACT_FIN_TIMEOUT:
     case OFPACT_RESUBMIT:
         return 0;
 
+    case OFPACT_FIN_TIMEOUT:
+        if (flow->nw_proto != IPPROTO_TCP) {
+            goto inconsistent;
+        }
+        return 0;
+
     case OFPACT_LEARN:
         return learn_check(ofpact_get_LEARN(a), flow);
 
@@ -1286,12 +1661,23 @@ ofpact_check__(const struct ofpact *a, struct flow *flow, ofp_port_t max_ports,
 
     case OFPACT_POP_MPLS:
         flow->dl_type = ofpact_get_POP_MPLS(a)->ethertype;
+        if (!eth_type_mpls(flow->dl_type)) {
+            goto inconsistent;
+        }
         return 0;
 
     case OFPACT_SAMPLE:
         return 0;
 
     case OFPACT_CLEAR_ACTIONS:
+        return 0;
+
+    case OFPACT_WRITE_ACTIONS: {
+        struct ofpact_nest *on = ofpact_get_WRITE_ACTIONS(a);
+        return ofpacts_check(on->actions, ofpact_nest_get_action_len(on),
+                             flow, max_ports, table_id, false);
+    }
+
     case OFPACT_WRITE_METADATA:
         return 0;
 
@@ -1315,6 +1701,12 @@ ofpact_check__(const struct ofpact *a, struct flow *flow, ofp_port_t max_ports,
     default:
         NOT_REACHED();
     }
+
+ inconsistent:
+    if (enforce_consistency) {
+        return OFPERR_OFPBAC_MATCH_INCONSISTENT;
+    }
+    return 0;
 }
 
 /* Checks that the 'ofpacts_len' bytes of actions in 'ofpacts' are
@@ -1324,14 +1716,16 @@ ofpact_check__(const struct ofpact *a, struct flow *flow, ofp_port_t max_ports,
  * May temporarily modify 'flow', but restores the changes before returning. */
 enum ofperr
 ofpacts_check(const struct ofpact ofpacts[], size_t ofpacts_len,
-              struct flow *flow, ofp_port_t max_ports, uint8_t table_id)
+              struct flow *flow, ofp_port_t max_ports, uint8_t table_id,
+              bool enforce_consistency)
 {
     const struct ofpact *a;
     ovs_be16 dl_type = flow->dl_type;
     enum ofperr error = 0;
 
     OFPACT_FOR_EACH (a, ofpacts, ofpacts_len) {
-        error = ofpact_check__(a, flow, max_ports, table_id);
+        error = ofpact_check__(a, flow, max_ports, table_id,
+                               enforce_consistency);
         if (error) {
             break;
         }
@@ -1353,7 +1747,9 @@ ofpacts_verify(const struct ofpact ofpacts[], size_t ofpacts_len)
         enum ovs_instruction_type next;
 
         next = ovs_instruction_type_from_ofpact_type(a->type);
-        if (inst != OVSINST_OFPIT11_APPLY_ACTIONS && next <= inst) {
+        if (inst == OVSINST_OFPIT11_APPLY_ACTIONS
+            ? next < inst
+            : next <= inst) {
             const char *name = ovs_instruction_name_from_type(inst);
             const char *next_name = ovs_instruction_name_from_type(next);
 
@@ -1619,9 +2015,12 @@ ofpact_to_nxast(const struct ofpact *a, struct ofpbuf *out)
     case OFPACT_SET_ETH_DST:
     case OFPACT_SET_IPV4_SRC:
     case OFPACT_SET_IPV4_DST:
-    case OFPACT_SET_IPV4_DSCP:
+    case OFPACT_SET_IP_DSCP:
+    case OFPACT_SET_IP_ECN:
+    case OFPACT_SET_IP_TTL:
     case OFPACT_SET_L4_SRC_PORT:
     case OFPACT_SET_L4_DST_PORT:
+    case OFPACT_WRITE_ACTIONS:
     case OFPACT_CLEAR_ACTIONS:
     case OFPACT_GOTO_TABLE:
     case OFPACT_METER:
@@ -1699,9 +2098,9 @@ ofpact_to_openflow10(const struct ofpact *a, struct ofpbuf *out)
             = ofpact_get_SET_IPV4_DST(a)->ipv4;
         break;
 
-    case OFPACT_SET_IPV4_DSCP:
+    case OFPACT_SET_IP_DSCP:
         ofputil_put_OFPAT10_SET_NW_TOS(out)->nw_tos
-            = ofpact_get_SET_IPV4_DSCP(a)->dscp;
+            = ofpact_get_SET_IP_DSCP(a)->dscp;
         break;
 
     case OFPACT_SET_L4_SRC_PORT:
@@ -1716,6 +2115,7 @@ ofpact_to_openflow10(const struct ofpact *a, struct ofpbuf *out)
 
     case OFPACT_PUSH_VLAN:
     case OFPACT_CLEAR_ACTIONS:
+    case OFPACT_WRITE_ACTIONS:
     case OFPACT_GOTO_TABLE:
     case OFPACT_METER:
         /* XXX */
@@ -1732,6 +2132,8 @@ ofpact_to_openflow10(const struct ofpact *a, struct ofpbuf *out)
     case OFPACT_STACK_PUSH:
     case OFPACT_STACK_POP:
     case OFPACT_DEC_TTL:
+    case OFPACT_SET_IP_ECN:
+    case OFPACT_SET_IP_TTL:
     case OFPACT_SET_MPLS_TTL:
     case OFPACT_DEC_MPLS_TTL:
     case OFPACT_SET_TUNNEL:
@@ -1848,9 +2250,19 @@ ofpact_to_openflow11(const struct ofpact *a, struct ofpbuf *out)
             = ofpact_get_SET_IPV4_DST(a)->ipv4;
         break;
 
-    case OFPACT_SET_IPV4_DSCP:
+    case OFPACT_SET_IP_DSCP:
         ofputil_put_OFPAT11_SET_NW_TOS(out)->nw_tos
-            = ofpact_get_SET_IPV4_DSCP(a)->dscp;
+            = ofpact_get_SET_IP_DSCP(a)->dscp;
+        break;
+
+    case OFPACT_SET_IP_ECN:
+        ofputil_put_OFPAT11_SET_NW_ECN(out)->nw_ecn
+            = ofpact_get_SET_IP_ECN(a)->ecn;
+        break;
+
+    case OFPACT_SET_IP_TTL:
+        ofputil_put_OFPAT11_SET_NW_TTL(out)->nw_ttl
+            = ofpact_get_SET_IP_TTL(a)->ttl;
         break;
 
     case OFPACT_SET_L4_SRC_PORT:
@@ -1892,6 +2304,7 @@ ofpact_to_openflow11(const struct ofpact *a, struct ofpbuf *out)
         break;
 
     case OFPACT_CLEAR_ACTIONS:
+    case OFPACT_WRITE_ACTIONS:
     case OFPACT_GOTO_TABLE:
     case OFPACT_METER:
         NOT_REACHED();
@@ -2016,8 +2429,19 @@ ofpacts_put_openflow11_instructions(const struct ofpact ofpacts[],
             break;
         }
 
-        case OVSINST_OFPIT11_WRITE_ACTIONS:
-            NOT_REACHED();
+        case OVSINST_OFPIT11_WRITE_ACTIONS: {
+            const size_t ofs = openflow->size;
+            const struct ofpact_nest *on;
+
+            on = ofpact_get_WRITE_ACTIONS(a);
+            instruction_put_OFPIT11_WRITE_ACTIONS(openflow);
+            ofpacts_put_openflow11_actions(on->actions,
+                                           ofpact_nest_get_action_len(on),
+                                           openflow);
+            ofpacts_update_instruction_actions(openflow, ofs);
+
+            break;
+        }
         }
     }
 }
@@ -2044,7 +2468,9 @@ ofpact_outputs_to_port(const struct ofpact *ofpact, ofp_port_t port)
     case OFPACT_SET_ETH_DST:
     case OFPACT_SET_IPV4_SRC:
     case OFPACT_SET_IPV4_DST:
-    case OFPACT_SET_IPV4_DSCP:
+    case OFPACT_SET_IP_DSCP:
+    case OFPACT_SET_IP_ECN:
+    case OFPACT_SET_IP_TTL:
     case OFPACT_SET_L4_SRC_PORT:
     case OFPACT_SET_L4_DST_PORT:
     case OFPACT_REG_MOVE:
@@ -2068,6 +2494,7 @@ ofpact_outputs_to_port(const struct ofpact *ofpact, ofp_port_t port)
     case OFPACT_POP_MPLS:
     case OFPACT_SAMPLE:
     case OFPACT_CLEAR_ACTIONS:
+    case OFPACT_WRITE_ACTIONS:
     case OFPACT_GOTO_TABLE:
     case OFPACT_METER:
     case OFPACT_GROUP:
@@ -2301,8 +2728,16 @@ ofpact_format(const struct ofpact *a, struct ds *s)
                       IP_ARGS(ofpact_get_SET_IPV4_DST(a)->ipv4));
         break;
 
-    case OFPACT_SET_IPV4_DSCP:
-        ds_put_format(s, "mod_nw_tos:%d", ofpact_get_SET_IPV4_DSCP(a)->dscp);
+    case OFPACT_SET_IP_DSCP:
+        ds_put_format(s, "mod_nw_tos:%d", ofpact_get_SET_IP_DSCP(a)->dscp);
+        break;
+
+    case OFPACT_SET_IP_ECN:
+        ds_put_format(s, "mod_nw_ecn:%d", ofpact_get_SET_IP_ECN(a)->ecn);
+        break;
+
+    case OFPACT_SET_IP_TTL:
+        ds_put_format(s, "mod_nw_ttl:%d", ofpact_get_SET_IP_TTL(a)->ttl);
         break;
 
     case OFPACT_SET_L4_SRC_PORT:
@@ -2416,6 +2851,16 @@ ofpact_format(const struct ofpact *a, struct ds *s)
             sample->obs_domain_id, sample->obs_point_id);
         break;
 
+    case OFPACT_WRITE_ACTIONS: {
+        struct ofpact_nest *on = ofpact_get_WRITE_ACTIONS(a);
+        ds_put_format(s, "%s(",
+                      ovs_instruction_name_from_type(
+                          OVSINST_OFPIT11_WRITE_ACTIONS));
+        ofpacts_format(on->actions, ofpact_nest_get_action_len(on), s);
+        ds_put_char(s, ')');
+        break;
+    }
+
     case OFPACT_CLEAR_ACTIONS:
         ds_put_format(s, "%s",
                       ovs_instruction_name_from_type(
@@ -2459,7 +2904,6 @@ void
 ofpacts_format(const struct ofpact *ofpacts, size_t ofpacts_len,
                struct ds *string)
 {
-    ds_put_cstr(string, "actions=");
     if (!ofpacts_len) {
         ds_put_cstr(string, "drop");
     } else {
index 0876ed7..e097468 100644 (file)
@@ -17,6 +17,7 @@
 #ifndef OFP_ACTIONS_H
 #define OFP_ACTIONS_H 1
 
+#include <stddef.h>
 #include <stdint.h>
 #include "meta-flow.h"
 #include "ofp-errors.h"
@@ -66,7 +67,9 @@
     DEFINE_OFPACT(SET_ETH_DST,     ofpact_mac,           ofpact)    \
     DEFINE_OFPACT(SET_IPV4_SRC,    ofpact_ipv4,          ofpact)    \
     DEFINE_OFPACT(SET_IPV4_DST,    ofpact_ipv4,          ofpact)    \
-    DEFINE_OFPACT(SET_IPV4_DSCP,   ofpact_dscp,          ofpact)    \
+    DEFINE_OFPACT(SET_IP_DSCP,     ofpact_dscp,          ofpact)    \
+    DEFINE_OFPACT(SET_IP_ECN,      ofpact_ecn,           ofpact)    \
+    DEFINE_OFPACT(SET_IP_TTL,      ofpact_ip_ttl,        ofpact)    \
     DEFINE_OFPACT(SET_L4_SRC_PORT, ofpact_l4_port,       ofpact)    \
     DEFINE_OFPACT(SET_L4_DST_PORT, ofpact_l4_port,       ofpact)    \
     DEFINE_OFPACT(REG_MOVE,        ofpact_reg_move,      ofpact)    \
                                                                     \
     /* Instructions */                                              \
     DEFINE_OFPACT(METER,           ofpact_meter,         ofpact)    \
-    /* XXX Write-Actions */                                         \
     DEFINE_OFPACT(CLEAR_ACTIONS,   ofpact_null,          ofpact)    \
+    DEFINE_OFPACT(WRITE_ACTIONS,   ofpact_nest,          ofpact)    \
     DEFINE_OFPACT(WRITE_METADATA,  ofpact_metadata,      ofpact)    \
     DEFINE_OFPACT(GOTO_TABLE,      ofpact_goto_table,    ofpact)
 
@@ -284,7 +287,7 @@ struct ofpact_ipv4 {
     ovs_be32 ipv4;
 };
 
-/* OFPACT_SET_IPV4_DSCP.
+/* OFPACT_SET_IP_DSCP.
  *
  * Used for OFPAT10_SET_NW_TOS. */
 struct ofpact_dscp {
@@ -292,6 +295,22 @@ struct ofpact_dscp {
     uint8_t dscp;               /* DSCP in high 6 bits, rest ignored. */
 };
 
+/* OFPACT_SET_IP_ECN.
+ *
+ * Used for OFPAT11_SET_NW_ECN. */
+struct ofpact_ecn {
+    struct ofpact ofpact;
+    uint8_t ecn;               /* ECN in low 2 bits, rest ignored. */
+};
+
+/* OFPACT_SET_IP_TTL.
+ *
+ * Used for OFPAT11_SET_NW_TTL. */
+struct ofpact_ip_ttl {
+    struct ofpact ofpact;
+    uint8_t ttl;
+};
+
 /* OFPACT_SET_L4_SRC_PORT, OFPACT_SET_L4_DST_PORT.
  *
  * Used for OFPAT10_SET_TP_SRC, OFPAT10_SET_TP_DST. */
@@ -326,12 +345,26 @@ struct ofpact_reg_load {
     union mf_subvalue subvalue; /* Least-significant bits are used. */
 };
 
+/* The position in the packet at which to insert an MPLS header.
+ *
+ * Used NXAST_PUSH_MPLS, OFPAT11_PUSH_MPLS. */
+enum ofpact_mpls_position {
+    /* Add the MPLS LSE after the Ethernet header but before any VLAN tags.
+     * OpenFlow 1.3+ requires this behavior. */
+   OFPACT_MPLS_BEFORE_VLAN,
+
+   /* Add the MPLS LSE after the Ethernet header and any VLAN tags.
+    * OpenFlow 1.1 and 1.2 require this behavior. */
+   OFPACT_MPLS_AFTER_VLAN
+};
+
 /* OFPACT_PUSH_VLAN/MPLS/PBB
  *
  * Used for NXAST_PUSH_MPLS, OFPAT11_PUSH_MPLS. */
 struct ofpact_push_mpls {
     struct ofpact ofpact;
     ovs_be16 ethertype;
+    enum ofpact_mpls_position position;
 };
 
 /* OFPACT_POP_MPLS
@@ -384,6 +417,25 @@ struct ofpact_meter {
     uint32_t meter_id;
 };
 
+/* OFPACT_WRITE_ACTIONS.
+ *
+ * Used for OFPIT11_WRITE_ACTIONS. */
+struct ofpact_nest {
+    struct ofpact ofpact;
+    uint8_t pad[OFPACT_ALIGN(sizeof(struct ofpact)) - sizeof(struct ofpact)];
+    struct ofpact actions[];
+};
+BUILD_ASSERT_DECL(offsetof(struct ofpact_nest, actions) == OFPACT_ALIGNTO);
+
+static inline size_t
+ofpact_nest_get_action_len(const struct ofpact_nest *on)
+{
+    return on->ofpact.len - offsetof(struct ofpact_nest, actions);
+}
+
+void ofpacts_execute_action_set(struct ofpbuf *action_list,
+                                const struct ofpbuf *action_set);
+
 /* OFPACT_RESUBMIT.
  *
  * Used for NXAST_RESUBMIT, NXAST_RESUBMIT_TABLE. */
@@ -504,14 +556,16 @@ enum ofperr ofpacts_pull_openflow10(struct ofpbuf *openflow,
                                     unsigned int actions_len,
                                     struct ofpbuf *ofpacts);
 enum ofperr ofpacts_pull_openflow11_actions(struct ofpbuf *openflow,
+                                            enum ofp_version version,
                                             unsigned int actions_len,
                                             struct ofpbuf *ofpacts);
 enum ofperr ofpacts_pull_openflow11_instructions(struct ofpbuf *openflow,
+                                                 enum ofp_version version,
                                                  unsigned int instructions_len,
                                                  struct ofpbuf *ofpacts);
 enum ofperr ofpacts_check(const struct ofpact[], size_t ofpacts_len,
                           struct flow *, ofp_port_t max_ports,
-                          uint8_t table_id);
+                          uint8_t table_id, bool enforce_consistency);
 enum ofperr ofpacts_verify(const struct ofpact ofpacts[], size_t ofpacts_len);
 
 /* Converting ofpacts to OpenFlow. */
@@ -665,5 +719,4 @@ enum ovs_instruction_type ovs_instruction_type_from_ofpact_type(
 
 void ofpact_set_field_init(struct ofpact_reg_load *load,
                            const struct mf_field *mf, const void *src);
-
 #endif /* ofp-actions.h */
index d136f73..9f1b453 100644 (file)
@@ -260,22 +260,48 @@ ofphdrs_decode_assert(struct ofphdrs *hdrs,
 }
 
 static bool
-ofphdrs_is_stat(const struct ofphdrs *hdrs)
+ofp_is_stat_request(enum ofp_version version, uint8_t type)
 {
-    switch ((enum ofp_version) hdrs->version) {
+    switch (version) {
+    case OFP10_VERSION:
+        return type == OFPT10_STATS_REQUEST;
+    case OFP11_VERSION:
+    case OFP12_VERSION:
+    case OFP13_VERSION:
+        return type == OFPT11_STATS_REQUEST;
+    }
+
+    return false;
+}
+
+static bool
+ofp_is_stat_reply(enum ofp_version version, uint8_t type)
+{
+    switch (version) {
     case OFP10_VERSION:
-        return (hdrs->type == OFPT10_STATS_REQUEST ||
-                hdrs->type == OFPT10_STATS_REPLY);
+        return type == OFPT10_STATS_REPLY;
     case OFP11_VERSION:
     case OFP12_VERSION:
     case OFP13_VERSION:
-        return (hdrs->type == OFPT11_STATS_REQUEST ||
-                hdrs->type == OFPT11_STATS_REPLY);
+        return type == OFPT11_STATS_REPLY;
     }
 
     return false;
 }
 
+static bool
+ofp_is_stat(enum ofp_version version, uint8_t type)
+{
+    return (ofp_is_stat_request(version, type) ||
+            ofp_is_stat_reply(version, type));
+}
+
+static bool
+ofphdrs_is_stat(const struct ofphdrs *hdrs)
+{
+    return ofp_is_stat(hdrs->version, hdrs->type);
+}
+
 size_t
 ofphdrs_len(const struct ofphdrs *hdrs)
 {
@@ -811,6 +837,13 @@ ofpmsg_body(const struct ofp_header *oh)
     ofphdrs_decode_assert(&hdrs, oh, ntohs(oh->length));
     return (const uint8_t *) oh + ofphdrs_len(&hdrs);
 }
+
+/* Return if it's a stat/multipart (OFPST) request message. */
+bool
+ofpmsg_is_stat_request(const struct ofp_header *oh)
+{
+    return ofp_is_stat_request(oh->version, oh->type);
+}
 \f
 static ovs_be16 *ofpmp_flags__(const struct ofp_header *);
 
index bfc84f3..aa19fe3 100644 (file)
@@ -584,6 +584,7 @@ enum ofptype ofptype_from_ofpraw(enum ofpraw);
 /* OpenFlow message properties. */
 void ofpmsg_update_length(struct ofpbuf *);
 const void *ofpmsg_body(const struct ofp_header *);
+bool ofpmsg_is_stat_request(const struct ofp_header *);
 \f
 /* Multipart messages (aka "statistics").
  *
index 7ca7305..757b971 100644 (file)
@@ -36,6 +36,7 @@
 #include "openflow/openflow.h"
 #include "ovs-thread.h"
 #include "packets.h"
+#include "simap.h"
 #include "socket-util.h"
 #include "vconn.h"
 
@@ -601,7 +602,7 @@ parse_named_action(enum ofputil_action_code code,
     char *error = NULL;
     uint16_t ethertype = 0;
     uint16_t vid = 0;
-    uint8_t tos = 0;
+    uint8_t tos = 0, ecn, ttl;
     uint8_t pcp = 0;
 
     switch (code) {
@@ -696,7 +697,28 @@ parse_named_action(enum ofputil_action_code code,
         if (tos & ~IP_DSCP_MASK) {
             return xasprintf("%s: not a valid TOS", arg);
         }
-        ofpact_put_SET_IPV4_DSCP(ofpacts)->dscp = tos;
+        ofpact_put_SET_IP_DSCP(ofpacts)->dscp = tos;
+        break;
+
+    case OFPUTIL_OFPAT11_SET_NW_ECN:
+        error = str_to_u8(arg, "ECN", &ecn);
+        if (error) {
+            return error;
+        }
+
+        if (ecn & ~IP_ECN_MASK) {
+            return xasprintf("%s: not a valid ECN", arg);
+        }
+        ofpact_put_SET_IP_ECN(ofpacts)->ecn = ecn;
+        break;
+
+    case OFPUTIL_OFPAT11_SET_NW_TTL:
+        error = str_to_u8(arg, "TTL", &ttl);
+        if (error) {
+            return error;
+        }
+
+        ofpact_put_SET_IP_TTL(ofpacts)->ttl = ttl;
         break;
 
     case OFPUTIL_OFPAT11_DEC_NW_TTL:
@@ -879,12 +901,11 @@ str_to_ofpact__(char *pos, char *act, char *arg,
  * Returns NULL if successful, otherwise a malloc()'d string describing the
  * error.  The caller is responsible for freeing the returned string. */
 static char * WARN_UNUSED_RESULT
-str_to_ofpacts(char *str, struct ofpbuf *ofpacts,
-               enum ofputil_protocol *usable_protocols)
+str_to_ofpacts__(char *str, struct ofpbuf *ofpacts,
+                 enum ofputil_protocol *usable_protocols)
 {
     size_t orig_size = ofpacts->size;
     char *pos, *act, *arg;
-    enum ofperr error;
     int n_actions;
 
     pos = str;
@@ -899,13 +920,34 @@ str_to_ofpacts(char *str, struct ofpbuf *ofpacts,
         n_actions++;
     }
 
+    ofpact_pad(ofpacts);
+    return NULL;
+}
+
+
+/* Parses 'str' as a series of actions, and appends them 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 * WARN_UNUSED_RESULT
+str_to_ofpacts(char *str, struct ofpbuf *ofpacts,
+               enum ofputil_protocol *usable_protocols)
+{
+    size_t orig_size = ofpacts->size;
+    char *error_s;
+    enum ofperr error;
+
+    error_s = str_to_ofpacts__(str, ofpacts, usable_protocols);
+    if (error_s) {
+        return error_s;
+    }
+
     error = ofpacts_verify(ofpacts->data, ofpacts->size);
     if (error) {
         ofpacts->size = orig_size;
         return xstrdup("Incorrect action ordering");
     }
 
-    ofpact_pad(ofpacts);
     return NULL;
 }
 
@@ -929,10 +971,24 @@ parse_named_instruction(enum ovs_instruction_type type,
         NOT_REACHED();  /* This case is handled by str_to_inst_ofpacts() */
         break;
 
-    case OVSINST_OFPIT11_WRITE_ACTIONS:
-        /* XXX */
-        error_s = xstrdup("instruction write-actions is not supported yet");
+    case OVSINST_OFPIT11_WRITE_ACTIONS: {
+        struct ofpact_nest *on;
+        size_t ofs;
+
+        ofpact_pad(ofpacts);
+        ofs = ofpacts->size;
+        on = ofpact_put(ofpacts, OFPACT_WRITE_ACTIONS,
+                        offsetof(struct ofpact_nest, actions));
+        error_s = str_to_ofpacts__(arg, ofpacts, usable_protocols);
+
+        on = ofpbuf_at_assert(ofpacts, ofs, sizeof *on);
+        on->ofpact.len = ofpacts->size - ofs;
+
+        if (error_s) {
+            ofpacts->size = ofs;
+        }
         break;
+    }
 
     case OVSINST_OFPIT11_CLEAR_ACTIONS:
         ofpact_put_CLEAR_ACTIONS(ofpacts);
@@ -1096,7 +1152,8 @@ parse_field(const struct mf_field *mf, const char *s, struct match *match,
 
 static char * WARN_UNUSED_RESULT
 parse_ofp_str__(struct ofputil_flow_mod *fm, int command, char *string,
-                enum ofputil_protocol *usable_protocols)
+                enum ofputil_protocol *usable_protocols,
+                bool enforce_consistency)
 {
     enum {
         F_OUT_PORT = 1 << 0,
@@ -1304,10 +1361,21 @@ parse_ofp_str__(struct ofputil_flow_mod *fm, int command, char *string,
             enum ofperr err;
 
             err = ofpacts_check(ofpacts.data, ofpacts.size, &fm->match.flow,
-                                OFPP_MAX, 0);
+                                OFPP_MAX, 0, true);
             if (err) {
-                error = xasprintf("actions are invalid with specified match "
-                                  "(%s)", ofperr_to_string(err));
+                if (!enforce_consistency &&
+                    err == OFPERR_OFPBAC_MATCH_INCONSISTENT) {
+                    /* Allow inconsistent actions to be used with OF 1.0. */
+                    *usable_protocols &= OFPUTIL_P_OF10_ANY;
+                    /* Try again, allowing for inconsistency.
+                     * XXX: As a side effect, logging may be duplicated. */
+                    err = ofpacts_check(ofpacts.data, ofpacts.size,
+                                        &fm->match.flow, OFPP_MAX, 0, false);
+                }
+                if (err) {
+                    error = xasprintf("actions are invalid with specified match "
+                                      "(%s)", ofperr_to_string(err));
+                }
             }
         }
         if (error) {
@@ -1337,12 +1405,14 @@ parse_ofp_str__(struct ofputil_flow_mod *fm, int command, char *string,
  * error.  The caller is responsible for freeing the returned string. */
 char * WARN_UNUSED_RESULT
 parse_ofp_str(struct ofputil_flow_mod *fm, int command, const char *str_,
-              enum ofputil_protocol *usable_protocols)
+              enum ofputil_protocol *usable_protocols,
+              bool enforce_consistency)
 {
     char *string = xstrdup(str_);
     char *error;
 
-    error = parse_ofp_str__(fm, command, string, usable_protocols);
+    error = parse_ofp_str__(fm, command, string, usable_protocols,
+                            enforce_consistency);
     if (error) {
         fm->ofpacts = NULL;
         fm->ofpacts_len = 0;
@@ -1689,9 +1759,11 @@ parse_ofpacts(const char *s_, struct ofpbuf *ofpacts,
 char * WARN_UNUSED_RESULT
 parse_ofp_flow_mod_str(struct ofputil_flow_mod *fm, const char *string,
                        uint16_t command,
-                       enum ofputil_protocol *usable_protocols)
+                       enum ofputil_protocol *usable_protocols,
+                       bool enforce_consistency)
 {
-    char *error = parse_ofp_str(fm, command, string, usable_protocols);
+    char *error = parse_ofp_str(fm, command, string, usable_protocols,
+                                enforce_consistency);
     if (!error) {
         /* Normalize a copy of the match.  This ensures that non-normalized
          * flows get logged but doesn't affect what gets sent to the switch, so
@@ -1753,7 +1825,8 @@ parse_ofp_table_mod(struct ofputil_table_mod *tm, const char *table_id,
 char * WARN_UNUSED_RESULT
 parse_ofp_flow_mod_file(const char *file_name, uint16_t command,
                         struct ofputil_flow_mod **fms, size_t *n_fms,
-                        enum ofputil_protocol *usable_protocols)
+                        enum ofputil_protocol *usable_protocols,
+                        bool enforce_consistency)
 {
     size_t allocated_fms;
     int line_number;
@@ -1782,7 +1855,7 @@ parse_ofp_flow_mod_file(const char *file_name, uint16_t command,
             *fms = x2nrealloc(*fms, &allocated_fms, sizeof **fms);
         }
         error = parse_ofp_flow_mod_str(&(*fms)[*n_fms], ds_cstr(&s), command,
-                                       &usable);
+                                       &usable, enforce_consistency);
         if (error) {
             size_t i;
 
@@ -1814,12 +1887,14 @@ parse_ofp_flow_mod_file(const char *file_name, uint16_t command,
 char * WARN_UNUSED_RESULT
 parse_ofp_flow_stats_request_str(struct ofputil_flow_stats_request *fsr,
                                  bool aggregate, const char *string,
-                                 enum ofputil_protocol *usable_protocols)
+                                 enum ofputil_protocol *usable_protocols,
+                                 bool enforce_consistency)
 {
     struct ofputil_flow_mod fm;
     char *error;
 
-    error = parse_ofp_str(&fm, -1, string, usable_protocols);
+    error = parse_ofp_str(&fm, -1, string, usable_protocols,
+                          enforce_consistency);
     if (error) {
         return error;
     }
@@ -1845,18 +1920,24 @@ parse_ofp_flow_stats_request_str(struct ofputil_flow_stats_request *fsr,
 /* Parses a specification of a flow from 's' into 'flow'.  's' must take the
  * form FIELD=VALUE[,FIELD=VALUE]... where each FIELD is the name of a
  * mf_field.  Fields must be specified in a natural order for satisfying
- * prerequisites.
+ * prerequisites. If 'mask' is specified, fills the mask field for each of the
+ * field specified in flow. If the map, 'names_portno' is specfied, converts
+ * the in_port name into port no while setting the 'flow'.
  *
  * Returns NULL on success, otherwise a malloc()'d string that explains the
  * problem. */
 char *
-parse_ofp_exact_flow(struct flow *flow, const char *s)
+parse_ofp_exact_flow(struct flow *flow, struct flow *mask, const char *s,
+                     const struct simap *portno_names)
 {
     char *pos, *key, *value_s;
     char *error = NULL;
     char *copy;
 
     memset(flow, 0, sizeof *flow);
+    if (mask) {
+        memset(mask, 0, sizeof *mask);
+    }
 
     pos = copy = xstrdup(s);
     while (ofputil_parse_key_value(&pos, &key, &value_s)) {
@@ -1867,6 +1948,9 @@ parse_ofp_exact_flow(struct flow *flow, const char *s)
                 goto exit;
             }
             flow->dl_type = htons(p->dl_type);
+            if (mask) {
+                mask->dl_type = OVS_BE16_MAX;
+            }
 
             if (p->nw_proto) {
                 if (flow->nw_proto) {
@@ -1875,6 +1959,9 @@ parse_ofp_exact_flow(struct flow *flow, const char *s)
                     goto exit;
                 }
                 flow->nw_proto = p->nw_proto;
+                if (mask) {
+                    mask->nw_proto = UINT8_MAX;
+                }
             }
         } else {
             const struct mf_field *mf;
@@ -1898,15 +1985,28 @@ parse_ofp_exact_flow(struct flow *flow, const char *s)
                 goto exit;
             }
 
-            field_error = mf_parse_value(mf, value_s, &value);
-            if (field_error) {
-                error = xasprintf("%s: bad value for %s (%s)",
-                                  s, key, field_error);
-                free(field_error);
-                goto exit;
-            }
+            if (!strcmp(key, "in_port")
+                && portno_names
+                && simap_contains(portno_names, value_s)) {
+                flow->in_port.ofp_port = u16_to_ofp(
+                    simap_get(portno_names, value_s));
+                if (mask) {
+                    mask->in_port.ofp_port = u16_to_ofp(ntohs(OVS_BE16_MAX));
+                }
+            } else {
+                field_error = mf_parse_value(mf, value_s, &value);
+                if (field_error) {
+                    error = xasprintf("%s: bad value for %s (%s)",
+                                      s, key, field_error);
+                    free(field_error);
+                    goto exit;
+                }
 
-            mf_set_flow_value(mf, &value, flow);
+                mf_set_flow_value(mf, &value, flow);
+                if (mask) {
+                    mf_mask_field(mf, mask);
+                }
+            }
         }
     }
 
@@ -1919,6 +2019,9 @@ exit:
 
     if (error) {
         memset(flow, 0, sizeof *flow);
+        if (mask) {
+            memset(mask, 0, sizeof *mask);
+        }
     }
     return error;
 }
index 47ba036..58a3e87 100644 (file)
@@ -32,15 +32,18 @@ struct ofputil_flow_stats_request;
 struct ofputil_group_mod;
 struct ofputil_meter_mod;
 struct ofputil_table_mod;
+struct simap;
 enum ofputil_protocol;
 
 char *parse_ofp_str(struct ofputil_flow_mod *, int command, const char *str_,
-                    enum ofputil_protocol *usable_protocols)
+                    enum ofputil_protocol *usable_protocols,
+                    bool enforce_consistency)
     WARN_UNUSED_RESULT;
 
 char *parse_ofp_flow_mod_str(struct ofputil_flow_mod *, const char *string,
                              uint16_t command,
-                             enum ofputil_protocol *usable_protocols)
+                             enum ofputil_protocol *usable_protocols,
+                             bool enforce_consistency)
     WARN_UNUSED_RESULT;
 
 char *parse_ofp_table_mod(struct ofputil_table_mod *,
@@ -50,19 +53,22 @@ char *parse_ofp_table_mod(struct ofputil_table_mod *,
 
 char *parse_ofp_flow_mod_file(const char *file_name, uint16_t command,
                               struct ofputil_flow_mod **fms, size_t *n_fms,
-                              enum ofputil_protocol *usable_protocols)
+                              enum ofputil_protocol *usable_protocols,
+                              bool enforce_consistency)
     WARN_UNUSED_RESULT;
 
 char *parse_ofp_flow_stats_request_str(struct ofputil_flow_stats_request *,
                                        bool aggregate, const char *string,
-                                       enum ofputil_protocol *usable_protocols)
+                                       enum ofputil_protocol *usable_protocols,
+                                       bool enforce_consistency)
     WARN_UNUSED_RESULT;
 
 char *parse_ofpacts(const char *, struct ofpbuf *ofpacts,
                     enum ofputil_protocol *usable_protocols)
     WARN_UNUSED_RESULT;
 
-char *parse_ofp_exact_flow(struct flow *, const char *);
+char *parse_ofp_exact_flow(struct flow *flow, struct flow *mask, const char *s,
+                           const struct simap *portno_names);
 
 char *parse_ofp_meter_mod_str(struct ofputil_meter_mod *, const char *string,
                               int command,
index 6fe1cee..e4d0303 100644 (file)
@@ -105,11 +105,11 @@ ofp_print_packet_in(struct ds *string, const struct ofp_header *oh,
         ds_put_format(string, " table_id=%"PRIu8, pin.table_id);
     }
 
-    if (pin.cookie) {
+    if (pin.cookie != OVS_BE64_MAX) {
         ds_put_format(string, " cookie=0x%"PRIx64, ntohll(pin.cookie));
     }
 
-    ds_put_format(string, " total_len=%"PRIu16" in_port=", pin.total_len);
+    ds_put_format(string, " total_len=%zu in_port=", pin.total_len);
     ofputil_format_port(pin.fmd.in_port, string);
 
     if (pin.fmd.tun_id != htonll(0)) {
@@ -185,7 +185,7 @@ ofp_print_packet_out(struct ds *string, const struct ofp_header *oh,
     ds_put_cstr(string, " in_port=");
     ofputil_format_port(po.in_port, string);
 
-    ds_put_char(string, ' ');
+    ds_put_cstr(string, " actions=");
     ofpacts_format(po.ofpacts, po.ofpacts_len, string);
 
     if (po.buffer_id == UINT32_MAX) {
@@ -850,6 +850,7 @@ ofp_print_flow_mod(struct ds *s, const struct ofp_header *oh, int verbosity)
     }
     ofp_print_flow_flags(s, fm.flags);
 
+    ds_put_cstr(s, "actions=");
     ofpacts_format(fm.ofpacts, fm.ofpacts_len, s);
     ofpbuf_uninit(&ofpacts);
 }
@@ -1382,6 +1383,7 @@ ofp_print_flow_stats(struct ds *string, struct ofputil_flow_stats *fs)
         ds_put_char(string, ' ');
     }
 
+    ds_put_cstr(string, "actions=");
     ofpacts_format(fs->ofpacts, fs->ofpacts_len, string);
 }
 
@@ -2141,6 +2143,7 @@ ofp_print_nxst_flow_monitor_reply(struct ds *string,
             if (string->string[string->length - 1] != ' ') {
                 ds_put_char(string, ' ');
             }
+            ds_put_cstr(string, "actions=");
             ofpacts_format(update.ofpacts, update.ofpacts_len, string);
         }
     }
@@ -2210,6 +2213,7 @@ ofp_print_group(struct ds *s, uint32_t group_id, uint8_t type,
             ds_put_format(s, "watch_group:%"PRIu32",", bucket->watch_group);
         }
 
+        ds_put_cstr(s, "actions=");
         ofpacts_format(bucket->ofpacts, bucket->ofpacts_len, s);
     }
 }
@@ -2224,7 +2228,7 @@ ofp_print_group_desc(struct ds *s, const struct ofp_header *oh)
         struct ofputil_group_desc gd;
         int retval;
 
-        retval = ofputil_decode_group_desc_reply(&gd, &b);
+        retval = ofputil_decode_group_desc_reply(&gd, &b, oh->version);
         if (retval) {
             if (retval != EOF) {
                 ds_put_cstr(s, " ***parse error***");
index 173b534..8c200ce 100644 (file)
@@ -296,8 +296,8 @@ ofputil_pull_ofp11_match(struct ofpbuf *buf, struct match *match,
     }
 }
 
-/* Converts the ofp11_match in 'match' into a struct match in 'match.  Returns
- * 0 if successful, otherwise an OFPERR_* value. */
+/* Converts the ofp11_match in 'ofmatch' into a struct match in 'match'.
+ * Returns 0 if successful, otherwise an OFPERR_* value. */
 enum ofperr
 ofputil_match_from_ofp11_match(const struct ofp11_match *ofmatch,
                                struct match *match)
@@ -1504,7 +1504,8 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
             return error;
         }
 
-        error = ofpacts_pull_openflow11_instructions(&b, b.size, ofpacts);
+        error = ofpacts_pull_openflow11_instructions(&b, oh->version,
+                                                     b.size, ofpacts);
         if (error) {
             return error;
         }
@@ -2360,7 +2361,8 @@ ofputil_decode_flow_stats_reply(struct ofputil_flow_stats *fs,
             return EINVAL;
         }
 
-        if (ofpacts_pull_openflow11_instructions(msg, length - sizeof *ofs -
+        if (ofpacts_pull_openflow11_instructions(msg, oh->version,
+                                                 length - sizeof *ofs -
                                                  padded_match_len, ofpacts)) {
             VLOG_WARN_RL(&bad_ofmsg_rl, "OFPST_FLOW reply bad instructions");
             return EINVAL;
@@ -2820,6 +2822,7 @@ ofputil_decode_packet_in(struct ofputil_packet_in *pin,
     struct ofpbuf b;
 
     memset(pin, 0, sizeof *pin);
+    pin->cookie = OVS_BE64_MAX;
 
     ofpbuf_use_const(&b, oh, ntohs(oh->length));
     raw = ofpraw_pull_assert(&b);
@@ -2938,7 +2941,6 @@ ofputil_encode_packet_in(const struct ofputil_packet_in *pin,
                          enum ofputil_protocol protocol,
                          enum nx_packet_in_format packet_in_format)
 {
-    size_t send_len = MIN(pin->send_len, pin->packet_len);
     struct ofpbuf *packet;
 
     /* Add OFPT_PACKET_IN. */
@@ -2964,11 +2966,11 @@ ofputil_encode_packet_in(const struct ofputil_packet_in *pin,
         /* The final argument is just an estimate of the space required. */
         packet = ofpraw_alloc_xid(packet_in_raw, packet_in_version,
                                   htonl(0), (sizeof(struct flow_metadata) * 2
-                                             + 2 + send_len));
+                                             + 2 + pin->packet_len));
         ofpbuf_put_zeros(packet, packet_in_size);
         oxm_put_match(packet, &match);
         ofpbuf_put_zeros(packet, 2);
-        ofpbuf_put(packet, pin->packet, send_len);
+        ofpbuf_put(packet, pin->packet, pin->packet_len);
 
         opi = packet->l3;
         opi->pi.buffer_id = htonl(pin->buffer_id);
@@ -2982,14 +2984,14 @@ ofputil_encode_packet_in(const struct ofputil_packet_in *pin,
         struct ofp10_packet_in *opi;
 
         packet = ofpraw_alloc_xid(OFPRAW_OFPT10_PACKET_IN, OFP10_VERSION,
-                                  htonl(0), send_len);
+                                  htonl(0), pin->packet_len);
         opi = ofpbuf_put_zeros(packet, offsetof(struct ofp10_packet_in, data));
         opi->total_len = htons(pin->total_len);
         opi->in_port = htons(ofp_to_u16(pin->fmd.in_port));
         opi->reason = pin->reason;
         opi->buffer_id = htonl(pin->buffer_id);
 
-        ofpbuf_put(packet, pin->packet, send_len);
+        ofpbuf_put(packet, pin->packet, pin->packet_len);
     } else if (packet_in_format == NXPIF_NXM) {
         struct nx_packet_in *npi;
         struct match match;
@@ -3000,11 +3002,11 @@ ofputil_encode_packet_in(const struct ofputil_packet_in *pin,
         /* The final argument is just an estimate of the space required. */
         packet = ofpraw_alloc_xid(OFPRAW_NXT_PACKET_IN, OFP10_VERSION,
                                   htonl(0), (sizeof(struct flow_metadata) * 2
-                                             + 2 + send_len));
+                                             + 2 + pin->packet_len));
         ofpbuf_put_zeros(packet, sizeof *npi);
         match_len = nx_put_match(packet, &match, 0, 0);
         ofpbuf_put_zeros(packet, 2);
-        ofpbuf_put(packet, pin->packet, send_len);
+        ofpbuf_put(packet, pin->packet, pin->packet_len);
 
         npi = packet->l3;
         npi->buffer_id = htonl(pin->buffer_id);
@@ -3092,7 +3094,8 @@ ofputil_decode_packet_out(struct ofputil_packet_out *po,
             return error;
         }
 
-        error = ofpacts_pull_openflow11_actions(&b, ntohs(opo->actions_len),
+        error = ofpacts_pull_openflow11_actions(&b, oh->version,
+                                                ntohs(opo->actions_len),
                                                 ofpacts);
         if (error) {
             return error;
@@ -5674,8 +5677,8 @@ ofputil_append_group_desc_reply(const struct ofputil_group_desc *gds,
 }
 
 static enum ofperr
-ofputil_pull_buckets(struct ofpbuf *msg, size_t buckets_length,
-                     struct list *buckets)
+ofputil_pull_buckets(struct ofpbuf *msg, enum ofp_version version,
+                     size_t buckets_length, struct list *buckets)
 {
     struct ofp11_bucket *ob;
 
@@ -5708,8 +5711,8 @@ ofputil_pull_buckets(struct ofpbuf *msg, size_t buckets_length,
         buckets_length -= ob_len;
 
         ofpbuf_init(&ofpacts, 0);
-        error = ofpacts_pull_openflow11_actions(msg, ob_len - sizeof *ob,
-                                                &ofpacts);
+        error = ofpacts_pull_openflow11_actions(msg, version,
+                                                ob_len - sizeof *ob, &ofpacts);
         if (error) {
             ofpbuf_uninit(&ofpacts);
             ofputil_bucket_list_destroy(buckets);
@@ -5745,7 +5748,7 @@ ofputil_pull_buckets(struct ofpbuf *msg, size_t buckets_length,
  * otherwise a positive errno value. */
 int
 ofputil_decode_group_desc_reply(struct ofputil_group_desc *gd,
-                                struct ofpbuf *msg)
+                                struct ofpbuf *msg, enum ofp_version version)
 {
     struct ofp11_group_desc_stats *ogds;
     size_t length;
@@ -5774,7 +5777,8 @@ ofputil_decode_group_desc_reply(struct ofputil_group_desc *gd,
         return OFPERR_OFPBRC_BAD_LEN;
     }
 
-    return ofputil_pull_buckets(msg, length - sizeof *ogds, &gd->buckets);
+    return ofputil_pull_buckets(msg, version, length - sizeof *ogds,
+                                &gd->buckets);
 }
 
 /* Converts abstract group mod 'gm' into a message for OpenFlow version
@@ -5857,7 +5861,7 @@ ofputil_decode_group_mod(const struct ofp_header *oh,
     gm->type = ogm->type;
     gm->group_id = ntohl(ogm->group_id);
 
-    return ofputil_pull_buckets(&msg, msg.size, &gm->buckets);
+    return ofputil_pull_buckets(&msg, oh->version, msg.size, &gm->buckets);
 }
 
 /* Parse a queue status request message into 'oqsr'.
index f5eec73..752fd06 100644 (file)
@@ -27,7 +27,7 @@ OFPAT11_ACTION(OFPAT11_SET_DL_DST,   ofp_action_dl_addr,  0, "mod_dl_dst")
 OFPAT11_ACTION(OFPAT11_SET_NW_SRC,   ofp_action_nw_addr,  0, "mod_nw_src")
 OFPAT11_ACTION(OFPAT11_SET_NW_DST,   ofp_action_nw_addr,  0, "mod_nw_dst")
 OFPAT11_ACTION(OFPAT11_SET_NW_TOS,   ofp_action_nw_tos,   0, "mod_nw_tos")
-//OFPAT11_ACTION(OFPAT11_SET_NW_ECN,   ofp11_action_nw_ecn, "0, mod_nw_ecn")
+OFPAT11_ACTION(OFPAT11_SET_NW_ECN,   ofp11_action_nw_ecn, 0, "mod_nw_ecn")
 OFPAT11_ACTION(OFPAT11_SET_TP_SRC,   ofp_action_tp_port,  0, "mod_tp_src")
 OFPAT11_ACTION(OFPAT11_SET_TP_DST,   ofp_action_tp_port,  0, "mod_tp_dst")
 OFPAT11_ACTION(OFPAT11_SET_MPLS_TTL, ofp11_action_mpls_ttl, 0, "set_mpls_ttl")
@@ -37,7 +37,7 @@ OFPAT11_ACTION(OFPAT11_POP_VLAN,     ofp_action_header,   0, "pop_vlan")
 OFPAT11_ACTION(OFPAT11_PUSH_MPLS,    ofp11_action_push,   0, "push_mpls")
 OFPAT11_ACTION(OFPAT11_POP_MPLS,     ofp11_action_pop_mpls, 0, "pop_mpls")
 OFPAT11_ACTION(OFPAT11_SET_QUEUE,    ofp11_action_set_queue, 0, "set_queue")
-//OFPAT11_ACTION(OFPAT11_SET_NW_TTL,   ofp11_action_nw_ttl, 0, "set_nw_ttl")
+OFPAT11_ACTION(OFPAT11_SET_NW_TTL,   ofp11_action_nw_ttl, 0, "mod_nw_ttl")
 OFPAT11_ACTION(OFPAT11_DEC_NW_TTL,   ofp_action_header,   0, NULL)
 OFPAT11_ACTION(OFPAT12_SET_FIELD,    ofp12_action_set_field, 1, "set_field")
 OFPAT11_ACTION(OFPAT11_GROUP,        ofp11_action_group,   0, "group")
index d5f34d7..1f77808 100644 (file)
@@ -30,6 +30,7 @@
 #include "type-props.h"
 
 struct ofpbuf;
+union ofp_action;
 
 /* Port numbers. */
 enum ofperr ofputil_port_from_ofp11(ovs_be32 ofp11_port,
@@ -88,6 +89,7 @@ enum ofputil_protocol {
     OFPUTIL_P_OF10_NXM_TID = 1 << 3,
 #define OFPUTIL_P_OF10_STD_ANY (OFPUTIL_P_OF10_STD | OFPUTIL_P_OF10_STD_TID)
 #define OFPUTIL_P_OF10_NXM_ANY (OFPUTIL_P_OF10_NXM | OFPUTIL_P_OF10_NXM_TID)
+#define OFPUTIL_P_OF10_ANY (OFPUTIL_P_OF10_STD_ANY | OFPUTIL_P_OF10_NXM_ANY)
 
     /* OpenFlow 1.1 protocol.
      *
@@ -375,21 +377,33 @@ struct ofpbuf *ofputil_encode_flow_removed(const struct ofputil_flow_removed *,
 
 /* Abstract packet-in message. */
 struct ofputil_packet_in {
-    struct list list_node; /* For queueing packet_ins. */
-
+    /* Packet data and metadata.
+     *
+     * To save bandwidth, in some cases a switch may send only the first
+     * several bytes of a packet, indicated by 'packet_len < total_len'.  When
+     * the full packet is included, 'packet_len == total_len'. */
     const void *packet;
-    size_t packet_len;
-
-    enum ofp_packet_in_reason reason;    /* One of OFPR_*. */
-    uint16_t controller_id;              /* Controller ID to send to. */
-    uint8_t table_id;
-    ovs_be64 cookie;
+    size_t packet_len;          /* Number of bytes in 'packet'. */
+    size_t total_len;           /* Size of packet, pre-truncation. */
+    struct flow_metadata fmd;
 
+    /* Identifies a buffer in the switch that contains the full packet, to
+     * allow the controller to reference it later without having to send the
+     * entire packet back to the switch.
+     *
+     * UINT32_MAX indicates that the packet is not buffered in the switch.  A
+     * switch should only use UINT32_MAX when it sends the entire packet. */
     uint32_t buffer_id;
-    int send_len;
-    uint16_t total_len;         /* Full length of frame. */
 
-    struct flow_metadata fmd;   /* Metadata at creation time. */
+    /* Reason that the packet-in is being sent. */
+    enum ofp_packet_in_reason reason;    /* One of OFPR_*. */
+
+    /* Information about the OpenFlow flow that triggered the packet-in.
+     *
+     * A packet-in triggered by a flow table miss has no associated flow.  In
+     * that case, 'cookie' is UINT64_MAX. */
+    uint8_t table_id;                    /* OpenFlow table ID. */
+    ovs_be64 cookie;                     /* Flow's cookie. */
 };
 
 enum ofperr ofputil_decode_packet_in(struct ofputil_packet_in *,
@@ -973,7 +987,7 @@ int ofputil_decode_group_stats_reply(struct ofpbuf *,
                                      struct ofputil_group_stats *);
 
 int ofputil_decode_group_desc_reply(struct ofputil_group_desc *,
-                                    struct ofpbuf *);
+                                    struct ofpbuf *, enum ofp_version);
 
 void ofputil_append_group_desc_reply(const struct ofputil_group_desc *,
                                      struct list *buckets,
index 4631291..3366523 100644 (file)
@@ -332,9 +332,6 @@ ovsdb_idl_run(struct ovsdb_idl *idl)
                    && !strcmp(msg->method, "stolen")) {
             /* Someone else stole our lock. */
             ovsdb_idl_parse_lock_notify(idl, msg->params, false);
-        } else if (msg->type == JSONRPC_REPLY && msg->id->type == JSON_STRING
-                   && !strcmp(msg->id->u.string, "echo")) {
-            /* Reply to our echo request.  Ignore it. */
         } else if ((msg->type == JSONRPC_ERROR
                     || msg->type == JSONRPC_REPLY)
                    && ovsdb_idl_txn_process_reply(idl, msg)) {
index 7f34ea2..cd0affc 100644 (file)
@@ -60,6 +60,10 @@ VLOG_DEFINE_THIS_MODULE(socket_util);
 #define O_DIRECTORY 0
 #endif
 
+/* Maximum length of the sun_path member in a struct sockaddr_un, excluding
+ * space for a null terminator. */
+#define MAX_UN_LEN (sizeof(((struct sockaddr_un *) 0)->sun_path) - 1)
+
 static int getsockopt_int(int fd, int level, int option, const char *optname,
                           int *valuep);
 
@@ -337,78 +341,161 @@ drain_fd(int fd, size_t n_packets)
     }
 }
 
-/* Stores in '*un' a sockaddr_un that refers to file 'name'.  Stores in
- * '*un_len' the size of the sockaddr_un. */
-static void
-make_sockaddr_un__(const char *name, struct sockaddr_un *un, socklen_t *un_len)
+/* Attempts to shorten 'name' by opening a file descriptor for the directory
+ * part of the name and indirecting through /proc/self/fd/<dirfd>/<basename>.
+ * On systems with Linux-like /proc, this works as long as <basename> isn't too
+ * long.
+ *
+ * On success, returns 0 and stores the short name in 'short_name' and a
+ * directory file descriptor to eventually be closed in '*dirfpd'. */
+static int
+shorten_name_via_proc(const char *name, char short_name[MAX_UN_LEN + 1],
+                      int *dirfdp)
 {
-    un->sun_family = AF_UNIX;
-    ovs_strzcpy(un->sun_path, name, sizeof un->sun_path);
-    *un_len = (offsetof(struct sockaddr_un, sun_path)
-                + strlen (un->sun_path) + 1);
+    char *dir, *base;
+    int dirfd;
+    int len;
+
+    if (!LINUX_DATAPATH) {
+        return ENAMETOOLONG;
+    }
+
+    dir = dir_name(name);
+    dirfd = open(dir, O_DIRECTORY | O_RDONLY);
+    if (dirfd < 0) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+        int error = errno;
+
+        VLOG_WARN_RL(&rl, "%s: open failed (%s)", dir, ovs_strerror(error));
+        free(dir);
+
+        return error;
+    }
+    free(dir);
+
+    base = base_name(name);
+    len = snprintf(short_name, MAX_UN_LEN + 1,
+                   "/proc/self/fd/%d/%s", dirfd, base);
+    free(base);
+
+    if (len >= 0 && len <= MAX_UN_LEN) {
+        *dirfdp = dirfd;
+        return 0;
+    } else {
+        close(dirfd);
+        return ENAMETOOLONG;
+    }
+}
+
+/* Attempts to shorten 'name' by creating a symlink for the directory part of
+ * the name and indirecting through <symlink>/<basename>.  This works on
+ * systems that support symlinks, as long as <basename> isn't too long.
+ *
+ * On success, returns 0 and stores the short name in 'short_name' and the
+ * symbolic link to eventually delete in 'linkname'. */
+static int
+shorten_name_via_symlink(const char *name, char short_name[MAX_UN_LEN + 1],
+                         char linkname[MAX_UN_LEN + 1])
+{
+    char *abs, *dir, *base;
+    const char *tmpdir;
+    int error;
+    int i;
+
+    abs = abs_file_name(NULL, name);
+    dir = dir_name(abs);
+    base = base_name(abs);
+    free(abs);
+
+    tmpdir = getenv("TMPDIR");
+    if (tmpdir == NULL) {
+        tmpdir = "/tmp";
+    }
+
+    for (i = 0; i < 1000; i++) {
+        int len;
+
+        len = snprintf(linkname, MAX_UN_LEN + 1,
+                       "%s/ovs-un-c-%"PRIu32, tmpdir, random_uint32());
+        error = (len < 0 || len > MAX_UN_LEN ? ENAMETOOLONG
+                 : symlink(dir, linkname) ? errno
+                 : 0);
+        if (error != EEXIST) {
+            break;
+        }
+    }
+
+    if (!error) {
+        int len;
+
+        fatal_signal_add_file_to_unlink(linkname);
+
+        len = snprintf(short_name, MAX_UN_LEN + 1, "%s/%s", linkname, base);
+        if (len < 0 || len > MAX_UN_LEN) {
+            fatal_signal_unlink_file_now(linkname);
+            error = ENAMETOOLONG;
+        }
+    }
+
+    if (error) {
+        linkname[0] = '\0';
+    }
+    free(dir);
+    free(base);
+
+    return error;
 }
 
 /* Stores in '*un' a sockaddr_un that refers to file 'name'.  Stores in
  * '*un_len' the size of the sockaddr_un.
  *
- * Returns 0 on success, otherwise a positive errno value.  On success,
- * '*dirfdp' is either -1 or a nonnegative file descriptor that the caller
- * should close after using '*un' to bind or connect.  On failure, '*dirfdp' is
- * -1. */
+ * Returns 0 on success, otherwise a positive errno value.
+ *
+ * Uses '*dirfdp' and 'linkname' to store references to data when the caller no
+ * longer needs to use 'un'.  On success, freeing these references with
+ * free_sockaddr_un() is mandatory to avoid a leak; on failure, freeing them is
+ * unnecessary but harmless. */
 static int
 make_sockaddr_un(const char *name, struct sockaddr_un *un, socklen_t *un_len,
-                 int *dirfdp)
+                 int *dirfdp, char linkname[MAX_UN_LEN + 1])
 {
-    enum { MAX_UN_LEN = sizeof un->sun_path - 1 };
+    char short_name[MAX_UN_LEN + 1];
 
     *dirfdp = -1;
+    linkname[0] = '\0';
     if (strlen(name) > MAX_UN_LEN) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-
-        if (LINUX_DATAPATH) {
-            /* 'name' is too long to fit in a sockaddr_un, but we have a
-             * workaround for that on Linux: shorten it by opening a file
-             * descriptor for the directory part of the name and indirecting
-             * through /proc/self/fd/<dirfd>/<basename>. */
-            char *dir, *base;
-            char *short_name;
-            int dirfd;
-
-            dir = dir_name(name);
-            base = base_name(name);
-
-            dirfd = open(dir, O_DIRECTORY | O_RDONLY);
-            if (dirfd < 0) {
-                free(base);
-                free(dir);
-                return errno;
-            }
-
-            short_name = xasprintf("/proc/self/fd/%d/%s", dirfd, base);
-            free(dir);
-            free(base);
-
-            if (strlen(short_name) <= MAX_UN_LEN) {
-                make_sockaddr_un__(short_name, un, un_len);
-                free(short_name);
-                *dirfdp = dirfd;
-                return 0;
-            }
-            free(short_name);
-            close(dirfd);
+        /* 'name' is too long to fit in a sockaddr_un.  Try a workaround. */
+        int error = shorten_name_via_proc(name, short_name, dirfdp);
+        if (error == ENAMETOOLONG) {
+            error = shorten_name_via_symlink(name, short_name, linkname);
+        }
+        if (error) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
 
             VLOG_WARN_RL(&rl, "Unix socket name %s is longer than maximum "
-                         "%d bytes (even shortened)", name, MAX_UN_LEN);
-        } else {
-            /* 'name' is too long and we have no workaround. */
-            VLOG_WARN_RL(&rl, "Unix socket name %s is longer than maximum "
-                         "%d bytes", name, MAX_UN_LEN);
+                         "%zu bytes", name, MAX_UN_LEN);
+            return error;
         }
 
-        return ENAMETOOLONG;
-    } else {
-        make_sockaddr_un__(name, un, un_len);
-        return 0;
+        name = short_name;
+    }
+
+    un->sun_family = AF_UNIX;
+    ovs_strzcpy(un->sun_path, name, sizeof un->sun_path);
+    *un_len = (offsetof(struct sockaddr_un, sun_path)
+                + strlen (un->sun_path) + 1);
+    return 0;
+}
+
+/* Clean up after make_sockaddr_un(). */
+static void
+free_sockaddr_un(int dirfd, const char *linkname)
+{
+    if (dirfd >= 0) {
+        close(dirfd);
+    }
+    if (linkname[0]) {
+        fatal_signal_unlink_file_now(linkname);
     }
 }
 
@@ -453,6 +540,7 @@ make_unix_socket(int style, bool nonblock,
     }
 
     if (bind_path) {
+        char linkname[MAX_UN_LEN + 1];
         struct sockaddr_un un;
         socklen_t un_len;
         int dirfd;
@@ -463,32 +551,31 @@ make_unix_socket(int style, bool nonblock,
         }
         fatal_signal_add_file_to_unlink(bind_path);
 
-        error = make_sockaddr_un(bind_path, &un, &un_len, &dirfd);
+        error = make_sockaddr_un(bind_path, &un, &un_len, &dirfd, linkname);
         if (!error) {
             error = bind_unix_socket(fd, (struct sockaddr *) &un, un_len);
         }
-        if (dirfd >= 0) {
-            close(dirfd);
-        }
+        free_sockaddr_un(dirfd, linkname);
+
         if (error) {
             goto error;
         }
     }
 
     if (connect_path) {
+        char linkname[MAX_UN_LEN + 1];
         struct sockaddr_un un;
         socklen_t un_len;
         int dirfd;
 
-        error = make_sockaddr_un(connect_path, &un, &un_len, &dirfd);
+        error = make_sockaddr_un(connect_path, &un, &un_len, &dirfd, linkname);
         if (!error
             && connect(fd, (struct sockaddr*) &un, un_len)
             && errno != EINPROGRESS) {
             error = errno;
         }
-        if (dirfd >= 0) {
-            close(dirfd);
-        }
+        free_sockaddr_un(dirfd, linkname);
+
         if (error) {
             goto error;
         }
diff --git a/lib/vtep-idl.ann b/lib/vtep-idl.ann
new file mode 100644 (file)
index 0000000..5ffe585
--- /dev/null
@@ -0,0 +1,9 @@
+# -*- python -*-
+
+# This code, when invoked by "ovsdb-idlc annotate" (by the build
+# process), annotates vswitch.ovsschema with additional data that give
+# the ovsdb-idl engine information about the types involved, so that
+# it can generate more programmer-friendly data structures.
+
+s["idlPrefix"] = "vteprec_"
+s["idlHeader"] = "\"lib/vtep-idl.h\""
index dca6cca..012901f 100644 (file)
@@ -286,6 +286,18 @@ AC_DEFUN([OVS_CHECK_OVSDBMONITOR],
    AC_MSG_RESULT([$BUILD_OVSDBMONITOR])
    AM_CONDITIONAL([BUILD_OVSDBMONITOR], [test $BUILD_OVSDBMONITOR = yes])])
 
+dnl Checks for missing python modules at build time
+AC_DEFUN([OVS_CHECK_PYTHON_COMPAT],
+  [OVS_CHECK_PYTHON_MODULE([uuid])
+   if test $ovs_cv_py_uuid = yes; then
+     INCLUDE_PYTHON_COMPAT=no
+   else
+     INCLUDE_PYTHON_COMPAT=yes
+   fi
+   AC_MSG_CHECKING([whether to add python/compat to PYTHONPATH])
+   AC_MSG_RESULT([$INCLUDE_PYTHON_COMPAT])
+   AM_CONDITIONAL([INCLUDE_PYTHON_COMPAT], [test $INCLUDE_PYTHON_COMPAT = yes])])
+
 # OVS_LINK2_IFELSE(SOURCE1, SOURCE2, [ACTION-IF-TRUE], [ACTION-IF-FALSE])
 # -------------------------------------------------------------
 # Based on AC_LINK_IFELSE, but tries to link both SOURCE1 and SOURCE2
index 2a34f04..5bfcdaa 100644 (file)
@@ -253,3 +253,25 @@ ofproto/ofproto-dpif-unixctl.man:
 ofproto/ofproto-unixctl.man:
 ovsdb/remote-active.man:
 ovsdb/remote-passive.man:
+
+vtep/vtep-ctl.8: \
+       vtep/vtep-ctl.8.in \
+       lib/ssl-bootstrap.man \
+       lib/ssl-peer-ca-cert.man \
+       lib/ssl.man \
+       lib/table.man \
+       lib/vlog.man \
+       ovsdb/remote-active.man \
+       ovsdb/remote-active.man \
+       ovsdb/remote-passive.man \
+       ovsdb/remote-passive.man
+vtep/vtep-ctl.8.in:
+lib/ssl-bootstrap.man:
+lib/ssl-peer-ca-cert.man:
+lib/ssl.man:
+lib/table.man:
+lib/vlog.man:
+ovsdb/remote-active.man:
+ovsdb/remote-active.man:
+ovsdb/remote-passive.man:
+ovsdb/remote-passive.man:
index 02da1f6..c9feae5 100644 (file)
@@ -153,7 +153,7 @@ static void ofconn_set_rate_limit(struct ofconn *, int rate, int burst);
 static void ofconn_send(const struct ofconn *, struct ofpbuf *,
                         struct rconn_packet_counter *);
 
-static void do_send_packet_in(struct ofpbuf *, void *ofconn_);
+static void do_send_packet_ins(struct ofconn *, struct list *txq);
 
 /* A listener for incoming OpenFlow "service" connections. */
 struct ofservice {
@@ -1301,7 +1301,10 @@ ofconn_run(struct ofconn *ofconn,
     size_t i;
 
     for (i = 0; i < N_SCHEDULERS; i++) {
-        pinsched_run(ofconn->schedulers[i], do_send_packet_in, ofconn);
+        struct list txq;
+
+        pinsched_run(ofconn->schedulers[i], &txq);
+        do_send_packet_ins(ofconn, &txq);
     }
 
     rconn_run(ofconn->rconn);
@@ -1435,7 +1438,8 @@ ofconn_send(const struct ofconn *ofconn, struct ofpbuf *msg,
 \f
 /* Sending asynchronous messages. */
 
-static void schedule_packet_in(struct ofconn *, struct ofputil_packet_in);
+static void schedule_packet_in(struct ofconn *, struct ofproto_packet_in,
+                               enum ofp_packet_in_reason wire_reason);
 
 /* Sends an OFPT_PORT_STATUS message with 'opp' and 'reason' to appropriate
  * controllers managed by 'mgr'. */
@@ -1482,74 +1486,108 @@ connmgr_send_flow_removed(struct connmgr *mgr,
     }
 }
 
+/* Normally a send-to-controller action uses reason OFPR_ACTION.  However, in
+ * OpenFlow 1.3 and later, packet_ins generated by a send-to-controller action
+ * in a "table-miss" flow (one with priority 0 and completely wildcarded) are
+ * sent as OFPR_NO_MATCH.  This function returns the reason that should
+ * actually be sent on 'ofconn' for 'pin'. */
+static enum ofp_packet_in_reason
+wire_reason(struct ofconn *ofconn, const struct ofproto_packet_in *pin)
+{
+    if (pin->generated_by_table_miss && pin->up.reason == OFPR_ACTION) {
+        enum ofputil_protocol protocol = ofconn_get_protocol(ofconn);
+
+        if (protocol != OFPUTIL_P_NONE
+            && ofputil_protocol_to_ofp_version(protocol) >= OFP13_VERSION) {
+            return OFPR_NO_MATCH;
+        }
+    }
+    return pin->up.reason;
+}
+
 /* Given 'pin', sends an OFPT_PACKET_IN message to each OpenFlow controller as
  * necessary according to their individual configurations.
  *
  * The caller doesn't need to fill in pin->buffer_id or pin->total_len. */
 void
 connmgr_send_packet_in(struct connmgr *mgr,
-                       const struct ofputil_packet_in *pin)
+                       const struct ofproto_packet_in *pin)
 {
     struct ofconn *ofconn;
 
     LIST_FOR_EACH (ofconn, node, &mgr->all_conns) {
-        if (ofconn_receives_async_msg(ofconn, OAM_PACKET_IN, pin->reason)
+        enum ofp_packet_in_reason reason = wire_reason(ofconn, pin);
+
+        if (ofconn_receives_async_msg(ofconn, OAM_PACKET_IN, reason)
             && ofconn->controller_id == pin->controller_id) {
-            schedule_packet_in(ofconn, *pin);
+            schedule_packet_in(ofconn, *pin, reason);
         }
     }
 }
 
-/* pinsched callback for sending 'ofp_packet_in' on 'ofconn'. */
 static void
-do_send_packet_in(struct ofpbuf *ofp_packet_in, void *ofconn_)
+do_send_packet_ins(struct ofconn *ofconn, struct list *txq)
 {
-    struct ofconn *ofconn = ofconn_;
+    struct ofpbuf *pin, *next_pin;
 
-    rconn_send_with_limit(ofconn->rconn, ofp_packet_in,
-                          ofconn->packet_in_counter, 100);
+    LIST_FOR_EACH_SAFE (pin, next_pin, list_node, txq) {
+        list_remove(&pin->list_node);
+
+        rconn_send_with_limit(ofconn->rconn, pin,
+                              ofconn->packet_in_counter, 100);
+    }
 }
 
 /* Takes 'pin', composes an OpenFlow packet-in message from it, and passes it
  * to 'ofconn''s packet scheduler for sending. */
 static void
-schedule_packet_in(struct ofconn *ofconn, struct ofputil_packet_in pin)
+schedule_packet_in(struct ofconn *ofconn, struct ofproto_packet_in pin,
+                   enum ofp_packet_in_reason wire_reason)
 {
     struct connmgr *mgr = ofconn->connmgr;
+    uint16_t controller_max_len;
+    struct list txq;
 
-    pin.total_len = pin.packet_len;
+    pin.up.total_len = pin.up.packet_len;
 
-    /* Get OpenFlow buffer_id. */
-    if (pin.reason == OFPR_ACTION) {
-        pin.buffer_id = UINT32_MAX;
-    } else if (mgr->fail_open && fail_open_is_active(mgr->fail_open)) {
-        pin.buffer_id = pktbuf_get_null();
-    } else if (!ofconn->pktbuf) {
-        pin.buffer_id = UINT32_MAX;
+    pin.up.reason = wire_reason;
+    if (pin.up.reason == OFPR_ACTION) {
+        controller_max_len = pin.send_len;  /* max_len */
     } else {
-        pin.buffer_id = pktbuf_save(ofconn->pktbuf, pin.packet, pin.packet_len,
-                                    pin.fmd.in_port);
+        controller_max_len = ofconn->miss_send_len;
     }
 
-    /* Figure out how much of the packet to send. */
-    if (pin.reason == OFPR_NO_MATCH) {
-        pin.send_len = pin.packet_len;
+    /* Get OpenFlow buffer_id.
+     * For OpenFlow 1.2+, OFPCML_NO_BUFFER (== UINT16_MAX) specifies
+     * unbuffered.  This behaviour doesn't violate prior versions, too. */
+    if (controller_max_len == UINT16_MAX) {
+        pin.up.buffer_id = UINT32_MAX;
+    } else if (mgr->fail_open && fail_open_is_active(mgr->fail_open)) {
+        pin.up.buffer_id = pktbuf_get_null();
+    } else if (!ofconn->pktbuf) {
+        pin.up.buffer_id = UINT32_MAX;
     } else {
-        /* Caller should have initialized 'send_len' to 'max_len' specified in
-         * output action. */
+        pin.up.buffer_id = pktbuf_save(ofconn->pktbuf,
+                                       pin.up.packet, pin.up.packet_len,
+                                       pin.up.fmd.in_port);
     }
-    if (pin.buffer_id != UINT32_MAX) {
-        pin.send_len = MIN(pin.send_len, ofconn->miss_send_len);
+
+    /* Figure out how much of the packet to send.
+     * If not buffered, send the entire packet.  Otherwise, depending on
+     * the reason of packet-in, send what requested by the controller. */
+    if (pin.up.buffer_id != UINT32_MAX
+        && controller_max_len < pin.up.packet_len) {
+        pin.up.packet_len = controller_max_len;
     }
 
-    /* Make OFPT_PACKET_IN and hand over to packet scheduler.  It might
-     * immediately call into do_send_packet_in() or it might buffer it for a
-     * while (until a later call to pinsched_run()). */
-    pinsched_send(ofconn->schedulers[pin.reason == OFPR_NO_MATCH ? 0 : 1],
-                  pin.fmd.in_port,
-                  ofputil_encode_packet_in(&pin, ofconn_get_protocol(ofconn),
+    /* Make OFPT_PACKET_IN and hand over to packet scheduler. */
+    pinsched_send(ofconn->schedulers[pin.up.reason == OFPR_NO_MATCH ? 0 : 1],
+                  pin.up.fmd.in_port,
+                  ofputil_encode_packet_in(&pin.up,
+                                           ofconn_get_protocol(ofconn),
                                            ofconn->packet_in_format),
-                  do_send_packet_in, ofconn);
+                  &txq);
+    do_send_packet_ins(ofconn, &txq);
 }
 \f
 /* Fail-open settings. */
index 505a757..10caaf4 100644 (file)
@@ -22,6 +22,7 @@
 #include "list.h"
 #include "match.h"
 #include "ofp-errors.h"
+#include "ofp-util.h"
 #include "ofproto.h"
 #include "openflow/nicira-ext.h"
 #include "openvswitch/types.h"
@@ -29,9 +30,6 @@
 struct nlattr;
 struct ofconn;
 struct ofopgroup;
-struct ofputil_flow_removed;
-struct ofputil_packet_in;
-struct ofputil_phy_port;
 struct rule;
 struct simap;
 struct sset;
@@ -64,6 +62,19 @@ enum ofconn_async_msg_type {
     OAM_N_TYPES
 };
 
+/* A packet_in, with extra members to assist in queuing and routing it. */
+struct ofproto_packet_in {
+    struct ofputil_packet_in up;
+    struct list list_node;      /* For queuing. */
+    uint16_t controller_id;     /* Controller ID to send to. */
+    int send_len;               /* Length that the action requested sending. */
+
+    /* True if the packet_in was generated directly by a table-miss flow, that
+     * is, a flow with priority 0 that wildcards all fields.  (Our
+     * interpretation of "directly" is "not via groups".) */
+    bool generated_by_table_miss;
+};
+
 /* Basics. */
 struct connmgr *connmgr_create(struct ofproto *ofproto,
                                const char *dpif_name, const char *local_name);
@@ -141,7 +152,7 @@ void connmgr_send_port_status(struct connmgr *,
 void connmgr_send_flow_removed(struct connmgr *,
                                const struct ofputil_flow_removed *);
 void connmgr_send_packet_in(struct connmgr *,
-                            const struct ofputil_packet_in *);
+                            const struct ofproto_packet_in *);
 
 /* Fail-open settings. */
 enum ofproto_fail_mode connmgr_get_fail_mode(const struct connmgr *);
index 4900c05..bae9dca 100644 (file)
@@ -116,7 +116,7 @@ fail_open_is_active(const struct fail_open *fo)
 static void
 send_bogus_packet_ins(struct fail_open *fo)
 {
-    struct ofputil_packet_in pin;
+    struct ofproto_packet_in pin;
     uint8_t mac[ETH_ADDR_LEN];
     struct ofpbuf b;
 
@@ -125,11 +125,12 @@ send_bogus_packet_ins(struct fail_open *fo)
     compose_rarp(&b, mac);
 
     memset(&pin, 0, sizeof pin);
-    pin.packet = b.data;
-    pin.packet_len = b.size;
-    pin.reason = OFPR_NO_MATCH;
+    pin.up.packet = b.data;
+    pin.up.packet_len = b.size;
+    pin.up.reason = OFPR_NO_MATCH;
+    pin.up.fmd.in_port = OFPP_LOCAL;
     pin.send_len = b.size;
-    pin.fmd.in_port = OFPP_LOCAL;
+    pin.generated_by_table_miss = false;
     connmgr_send_packet_in(fo->connmgr, &pin);
 
     ofpbuf_uninit(&b);
index 4e91ea0..c8e1f32 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009, 2010, 2011 Nicira, Inc.
+ * Copyright (c) 2008, 2009, 2010, 2011, 2013 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,7 +19,7 @@
 
 #include <stdbool.h>
 #include <stdint.h>
-#include "flow.h"
+#include "ofproto-provider.h"
 
 struct connmgr;
 struct fail_open;
@@ -31,6 +31,14 @@ struct ofproto;
  * creates flows with this priority).  And "f0" is mnemonic for "fail open"! */
 #define FAIL_OPEN_PRIORITY 0xf0f0f0
 
+/* Returns true if 'rule' is one created by the "fail open" logic, false
+ * otherwise. */
+static inline bool
+is_fail_open_rule(const struct rule *rule)
+{
+    return rule->cr.priority == FAIL_OPEN_PRIORITY;
+}
+
 struct fail_open *fail_open_create(struct ofproto *, struct connmgr *);
 void fail_open_destroy(struct fail_open *);
 void fail_open_wait(struct fail_open *);
index 75cf206..d06b2e1 100644 (file)
 #include "bfd.h"
 #include "cfm.h"
 #include "hash.h"
+#include "heap.h"
 #include "hmap.h"
+#include "latch.h"
 #include "ofpbuf.h"
 #include "ofproto-dpif.h"
+#include "ovs-thread.h"
+#include "poll-loop.h"
+#include "seq.h"
+#include "timeval.h"
 #include "util.h"
 #include "vlog.h"
 
+VLOG_DEFINE_THIS_MODULE(ofproto_dpif_monitor);
+
+/* Converts the time in millisecond to heap priority. */
+#define MSEC_TO_PRIO(TIME) (LLONG_MAX - (TIME))
+/* Converts the heap priority to time in millisecond. */
+#define PRIO_TO_MSEC(PRIO) (LLONG_MAX - (PRIO))
+
 /* Monitored port.  It owns references to ofport, bfd, cfm structs. */
 struct mport {
     struct hmap_node hmap_node;       /* In monitor_hmap. */
+    struct heap_node heap_node;       /* In monitor_heap. */
     const struct ofport_dpif *ofport; /* The corresponding ofport. */
 
     struct cfm *cfm;                  /* Reference to cfm. */
@@ -39,10 +53,24 @@ struct mport {
 };
 
 /* hmap that contains "struct mport"s. */
-static struct hmap monitor_hmap = HMAP_INITIALIZER(&monitor_hmap);
+static struct hmap monitor_hmap;
+
+/* heap for ordering mport based on bfd/cfm wakeup time. */
+static struct heap monitor_heap;
+
+/* The monitor thread id. */
+static pthread_t monitor_tid;
+/* True if the monitor thread is running. */
+static bool monitor_running;
 
+static struct seq *monitor_seq;
+static struct latch monitor_exit_latch;
 static struct ovs_rwlock monitor_rwlock = OVS_RWLOCK_INITIALIZER;
 
+static void monitor_init(void);
+static void *monitor_main(void *);
+static void monitor_run(void);
+
 static void mport_register(const struct ofport_dpif *, struct bfd *,
                            struct cfm *, uint8_t[ETH_ADDR_LEN])
     OVS_REQ_WRLOCK(monitor_rwlock);
@@ -69,8 +97,8 @@ mport_find(const struct ofport_dpif *ofport) OVS_REQ_WRLOCK(monitor_rwlock)
     return NULL;
 }
 
-/* Creates a new mport and inserts it into monitor_hmap, if it doesn't exist.
- * Otherwise, just updates its fields. */
+/* Creates a new mport and inserts it into monitor_hmap and monitor_heap,
+ * if it doesn't exist.  Otherwise, just updates its fields. */
 static void
 mport_register(const struct ofport_dpif *ofport, struct bfd *bfd,
                struct cfm *cfm, uint8_t *hw_addr)
@@ -82,11 +110,12 @@ mport_register(const struct ofport_dpif *ofport, struct bfd *bfd,
         mport = xzalloc(sizeof *mport);
         mport->ofport = ofport;
         hmap_insert(&monitor_hmap, &mport->hmap_node, hash_pointer(ofport, 0));
+        heap_insert(&monitor_heap, &mport->heap_node, 0);
     }
     mport_update(mport, bfd, cfm, hw_addr);
 }
 
-/* Removes mport from monitor_hmap and frees it. */
+/* Removes mport from monitor_hmap and monitor_heap and frees it. */
 static void
 mport_unregister(const struct ofport_dpif *ofport)
     OVS_REQ_WRLOCK(monitor_rwlock)
@@ -96,6 +125,7 @@ mport_unregister(const struct ofport_dpif *ofport)
     if (mport) {
         mport_update(mport, NULL, NULL, NULL);
         hmap_remove(&monitor_hmap, &mport->hmap_node);
+        heap_remove(&monitor_heap, &mport->heap_node);
         free(mport);
     }
 }
@@ -118,37 +148,66 @@ mport_update(struct mport *mport, struct bfd *bfd, struct cfm *cfm,
     if (hw_addr && memcmp(mport->hw_addr, hw_addr, ETH_ADDR_LEN)) {
         memcpy(mport->hw_addr, hw_addr, ETH_ADDR_LEN);
     }
+    /* If bfd/cfm is added or reconfigured, move the mport on top of the heap
+     * and wakes up the monitor thread. */
+    if (mport->bfd || mport->cfm) {
+        heap_change(&monitor_heap, &mport->heap_node, LLONG_MAX);
+        seq_change(monitor_seq);
+    }
 }
 \f
 
-/* Creates the mport in monitor module if either bfd or cfm
- * is configured.  Otherwise, deletes the mport. */
-void
-ofproto_dpif_monitor_port_update(const struct ofport_dpif *ofport,
-                                 struct bfd *bfd, struct cfm *cfm,
-                                 uint8_t hw_addr[ETH_ADDR_LEN])
+/* Initializes the global variables.  This will only run once. */
+static void
+monitor_init(void)
 {
-    ovs_rwlock_wrlock(&monitor_rwlock);
-    if (!cfm && !bfd) {
-        mport_unregister(ofport);
-    } else {
-        mport_register(ofport, bfd, cfm, hw_addr);
+    static struct ovsthread_once once = OVSTHREAD_ONCE_INITIALIZER;
+
+    if (ovsthread_once_start(&once)) {
+        hmap_init(&monitor_hmap);
+        monitor_seq = seq_create();
+        ovsthread_once_done(&once);
     }
-    ovs_rwlock_unlock(&monitor_rwlock);
 }
 
-/* Checks the sending of control packets on all mports.  Sends the control
- * packets if needed. */
-void
-ofproto_dpif_monitor_run_fast(void)
+/* The 'main' function for the monitor thread. */
+static void *
+monitor_main(void * args OVS_UNUSED)
+{
+    set_subprogram_name("monitor");
+    VLOG_INFO("monitor thread created");
+    while (!latch_is_set(&monitor_exit_latch)) {
+        uint64_t seq = seq_read(monitor_seq);
+
+        monitor_run();
+        latch_wait(&monitor_exit_latch);
+        seq_wait(monitor_seq, seq);
+        poll_block();
+    }
+    VLOG_INFO("monitor thread terminated");
+    return NULL;
+}
+
+/* Checks the sending of control packets on mports that have timed out.
+ * Sends the control packets if needed.  Executes bfd and cfm periodic
+ * functions (run, wait) on those mports. */
+static void
+monitor_run(void)
 {
     uint32_t stub[512 / 4];
+    long long int prio_now;
     struct ofpbuf packet;
-    struct mport *mport;
 
     ofpbuf_use_stub(&packet, stub, sizeof stub);
     ovs_rwlock_rdlock(&monitor_rwlock);
-    HMAP_FOR_EACH (mport, hmap_node, &monitor_hmap) {
+    prio_now = MSEC_TO_PRIO(time_msec());
+    /* Peeks the top of heap and checks if we should run this mport. */
+    while (!heap_is_empty(&monitor_heap)
+           && heap_max(&monitor_heap)->priority >= prio_now) {
+        long long int next_wake_time;
+        struct mport *mport;
+
+        mport = CONTAINER_OF(heap_max(&monitor_heap), struct mport, heap_node);
         if (mport->cfm && cfm_should_send_ccm(mport->cfm)) {
             ofpbuf_clear(&packet);
             cfm_compose_ccm(mport->cfm, &packet, mport->hw_addr);
@@ -159,43 +218,58 @@ ofproto_dpif_monitor_run_fast(void)
             bfd_put_packet(mport->bfd, &packet, mport->hw_addr);
             ofproto_dpif_send_packet(mport->ofport, &packet);
         }
-    }
-    ovs_rwlock_unlock(&monitor_rwlock);
-    ofpbuf_uninit(&packet);
-}
-
-/* Executes bfd_run(), cfm_run() on all mports. */
-void
-ofproto_dpif_monitor_run(void)
-{
-    struct mport *mport;
-
-    ovs_rwlock_rdlock(&monitor_rwlock);
-    HMAP_FOR_EACH (mport, hmap_node, &monitor_hmap) {
         if (mport->cfm) {
             cfm_run(mport->cfm);
+            cfm_wait(mport->cfm);
         }
         if (mport->bfd) {
             bfd_run(mport->bfd);
+            bfd_wait(mport->bfd);
         }
+        /* Computes the next wakeup time for this mport. */
+        next_wake_time = MIN(bfd_wake_time(mport->bfd), cfm_wake_time(mport->cfm));
+        heap_change(&monitor_heap, heap_max(&monitor_heap),
+                    MSEC_TO_PRIO(next_wake_time));
+    }
+
+    /* Waits on the earliest next wakeup time. */
+    if (!heap_is_empty(&monitor_heap)) {
+        poll_timer_wait_until(PRIO_TO_MSEC(heap_max(&monitor_heap)->priority));
     }
     ovs_rwlock_unlock(&monitor_rwlock);
+    ofpbuf_uninit(&packet);
 }
+\f
 
-/* Executes the bfd_wait() and cfm_wait() functions on all mports. */
+/* Creates the mport in monitor module if either bfd or cfm
+ * is configured.  Otherwise, deletes the mport.
+ * Also checks whether the monitor thread should be started
+ * or terminated. */
 void
-ofproto_dpif_monitor_wait(void)
+ofproto_dpif_monitor_port_update(const struct ofport_dpif *ofport,
+                                 struct bfd *bfd, struct cfm *cfm,
+                                 uint8_t hw_addr[ETH_ADDR_LEN])
 {
-    struct mport *mport;
-
-    ovs_rwlock_rdlock(&monitor_rwlock);
-    HMAP_FOR_EACH (mport, hmap_node, &monitor_hmap) {
-        if (mport->cfm) {
-            cfm_wait(mport->cfm);
-        }
-        if (mport->bfd) {
-            bfd_wait(mport->bfd);
-        }
+    monitor_init();
+    ovs_rwlock_wrlock(&monitor_rwlock);
+    if (!cfm && !bfd) {
+        mport_unregister(ofport);
+    } else {
+        mport_register(ofport, bfd, cfm, hw_addr);
     }
     ovs_rwlock_unlock(&monitor_rwlock);
+
+    /* If the monitor thread is not running and the hmap
+     * is not empty, starts it.  If it is and the hmap is empty,
+     * terminates it. */
+    if (!monitor_running && !hmap_is_empty(&monitor_hmap))  {
+        latch_init(&monitor_exit_latch);
+        xpthread_create(&monitor_tid, NULL, monitor_main, NULL);
+        monitor_running = true;
+    } else if (monitor_running && hmap_is_empty(&monitor_hmap))  {
+        latch_set(&monitor_exit_latch);
+        xpthread_join(monitor_tid, NULL);
+        latch_destroy(&monitor_exit_latch);
+        monitor_running = false;
+    }
 }
index 8e26814..f914fbe 100644 (file)
@@ -23,10 +23,6 @@ struct bfd;
 struct cfm;
 struct ofport_dpif;
 
-void ofproto_dpif_monitor_run(void);
-void ofproto_dpif_monitor_run_fast(void);
-void ofproto_dpif_monitor_wait(void);
-
 void ofproto_dpif_monitor_port_update(const struct ofport_dpif *,
                                       struct bfd *, struct cfm *,
                                       uint8_t[OFP_ETH_ALEN]);
index 954e92f..24a5729 100644 (file)
@@ -19,6 +19,7 @@
 #include <stdbool.h>
 #include <inttypes.h>
 
+#include "connmgr.h"
 #include "coverage.h"
 #include "dynamic-string.h"
 #include "dpif.h"
@@ -838,17 +839,17 @@ handle_upcalls(struct udpif *udpif, struct list *upcalls)
         LIST_FOR_EACH (upcall, list_node, upcalls) {
             struct flow_miss *miss = upcall->flow_miss;
             struct ofpbuf *packet = upcall->dpif_upcall.packet;
-            struct ofputil_packet_in *pin;
+            struct ofproto_packet_in *pin;
 
             pin = xmalloc(sizeof *pin);
-            pin->packet = xmemdup(packet->data, packet->size);
-            pin->packet_len = packet->size;
-            pin->reason = OFPR_NO_MATCH;
-            pin->controller_id = 0;
-            pin->table_id = 0;
-            pin->cookie = 0;
+            pin->up.packet = xmemdup(packet->data, packet->size);
+            pin->up.packet_len = packet->size;
+            pin->up.reason = OFPR_NO_MATCH;
+            pin->up.table_id = 0;
+            pin->up.cookie = OVS_BE64_MAX;
+            flow_get_metadata(&miss->flow, &pin->up.fmd);
             pin->send_len = 0; /* Not used for flow table misses. */
-            flow_get_metadata(&miss->flow, &pin->fmd);
+            pin->generated_by_table_miss = false;
             ofproto_dpif_send_packet_in(miss->ofproto, pin);
         }
     }
index efa5cbe..7be691c 100644 (file)
@@ -179,6 +179,15 @@ struct xlate_ctx {
     odp_port_t sflow_odp_port;  /* Output port for composing sFlow action. */
     uint16_t user_cookie_offset;/* Used for user_action_cookie fixup. */
     bool exit;                  /* No further actions should be processed. */
+
+    /* OpenFlow 1.1+ action set.
+     *
+     * 'action_set' accumulates "struct ofpact"s added by OFPACT_WRITE_ACTIONS.
+     * When translation is otherwise complete, ofpacts_execute_action_set()
+     * converts it to a set of "struct ofpact"s that can be translated into
+     * datapath actions.   */
+    struct ofpbuf action_set;   /* Action set. */
+    uint64_t action_set_stub[1024 / 8];
 };
 
 /* A controller may use OFPP_NONE as the ingress port to indicate that
@@ -1811,7 +1820,7 @@ execute_controller_action(struct xlate_ctx *ctx, int len,
                           enum ofp_packet_in_reason reason,
                           uint16_t controller_id)
 {
-    struct ofputil_packet_in *pin;
+    struct ofproto_packet_in *pin;
     struct ofpbuf *packet;
     struct flow key;
 
@@ -1835,16 +1844,20 @@ execute_controller_action(struct xlate_ctx *ctx, int len,
                         ctx->xout->odp_actions.size, NULL, NULL);
 
     pin = xmalloc(sizeof *pin);
-    pin->packet_len = packet->size;
-    pin->packet = ofpbuf_steal_data(packet);
-    pin->reason = reason;
-    pin->controller_id = controller_id;
-    pin->table_id = ctx->table_id;
-    pin->cookie = ctx->rule ? rule_dpif_get_flow_cookie(ctx->rule) : 0;
+    pin->up.packet_len = packet->size;
+    pin->up.packet = ofpbuf_steal_data(packet);
+    pin->up.reason = reason;
+    pin->up.table_id = ctx->table_id;
+    pin->up.cookie = (ctx->rule
+                      ? rule_dpif_get_flow_cookie(ctx->rule)
+                      : OVS_BE64_MAX);
 
-    pin->send_len = len;
-    flow_get_metadata(&ctx->xin->flow, &pin->fmd);
+    flow_get_metadata(&ctx->xin->flow, &pin->up.fmd);
 
+    pin->controller_id = controller_id;
+    pin->send_len = len;
+    pin->generated_by_table_miss = (ctx->rule
+                                    && rule_dpif_is_table_miss(ctx->rule));
     ofproto_dpif_send_packet_in(ctx->xbridge->ofproto, pin);
     ofpbuf_delete(packet);
 }
@@ -2246,6 +2259,26 @@ may_receive(const struct xport *xport, struct xlate_ctx *ctx)
     return true;
 }
 
+static void
+xlate_write_actions(struct xlate_ctx *ctx, const struct ofpact *a)
+{
+    struct ofpact_nest *on = ofpact_get_WRITE_ACTIONS(a);
+    ofpbuf_put(&ctx->action_set, on->actions, ofpact_nest_get_action_len(on));
+    ofpact_pad(&ctx->action_set);
+}
+
+static void
+xlate_action_set(struct xlate_ctx *ctx)
+{
+    uint64_t action_list_stub[1024 / 64];
+    struct ofpbuf action_list;
+
+    ofpbuf_use_stub(&action_list, action_list_stub, sizeof action_list_stub);
+    ofpacts_execute_action_set(&action_list, &ctx->action_set);
+    do_xlate_actions(action_list.data, action_list.size, ctx);
+    ofpbuf_uninit(&action_list);
+}
+
 static void
 do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
                  struct xlate_ctx *ctx)
@@ -2254,6 +2287,8 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
     struct flow *flow = &ctx->xin->flow;
     const struct ofpact *a;
 
+    /* dl_type already in the mask, not set below. */
+
     OFPACT_FOR_EACH (a, ofpacts, ofpacts_len) {
         struct ofpact_controller *controller;
         const struct ofpact_metadata *metadata;
@@ -2320,40 +2355,54 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
             break;
 
         case OFPACT_SET_IPV4_SRC:
-            memset(&wc->masks.nw_src, 0xff, sizeof wc->masks.nw_src);
             if (flow->dl_type == htons(ETH_TYPE_IP)) {
+                memset(&wc->masks.nw_src, 0xff, sizeof wc->masks.nw_src);
                 flow->nw_src = ofpact_get_SET_IPV4_SRC(a)->ipv4;
             }
             break;
 
         case OFPACT_SET_IPV4_DST:
-            memset(&wc->masks.nw_dst, 0xff, sizeof wc->masks.nw_dst);
             if (flow->dl_type == htons(ETH_TYPE_IP)) {
+                memset(&wc->masks.nw_dst, 0xff, sizeof wc->masks.nw_dst);
                 flow->nw_dst = ofpact_get_SET_IPV4_DST(a)->ipv4;
             }
             break;
 
-        case OFPACT_SET_IPV4_DSCP:
-            wc->masks.nw_tos |= IP_DSCP_MASK;
-            /* OpenFlow 1.0 only supports IPv4. */
-            if (flow->dl_type == htons(ETH_TYPE_IP)) {
+        case OFPACT_SET_IP_DSCP:
+            if (is_ip_any(flow)) {
+                wc->masks.nw_tos |= IP_DSCP_MASK;
                 flow->nw_tos &= ~IP_DSCP_MASK;
-                flow->nw_tos |= ofpact_get_SET_IPV4_DSCP(a)->dscp;
+                flow->nw_tos |= ofpact_get_SET_IP_DSCP(a)->dscp;
+            }
+            break;
+
+        case OFPACT_SET_IP_ECN:
+            if (is_ip_any(flow)) {
+                wc->masks.nw_tos |= IP_ECN_MASK;
+                flow->nw_tos &= ~IP_ECN_MASK;
+                flow->nw_tos |= ofpact_get_SET_IP_ECN(a)->ecn;
+            }
+            break;
+
+        case OFPACT_SET_IP_TTL:
+            if (is_ip_any(flow)) {
+                wc->masks.nw_ttl = 0xff;
+                flow->nw_ttl = ofpact_get_SET_IP_TTL(a)->ttl;
             }
             break;
 
         case OFPACT_SET_L4_SRC_PORT:
-            memset(&wc->masks.nw_proto, 0xff, sizeof wc->masks.nw_proto);
-            memset(&wc->masks.tp_src, 0xff, sizeof wc->masks.tp_src);
             if (is_ip_any(flow)) {
+                memset(&wc->masks.nw_proto, 0xff, sizeof wc->masks.nw_proto);
+                memset(&wc->masks.tp_src, 0xff, sizeof wc->masks.tp_src);
                 flow->tp_src = htons(ofpact_get_SET_L4_SRC_PORT(a)->port);
             }
             break;
 
         case OFPACT_SET_L4_DST_PORT:
-            memset(&wc->masks.nw_proto, 0xff, sizeof wc->masks.nw_proto);
-            memset(&wc->masks.tp_dst, 0xff, sizeof wc->masks.tp_dst);
             if (is_ip_any(flow)) {
+                memset(&wc->masks.nw_proto, 0xff, sizeof wc->masks.nw_proto);
+                memset(&wc->masks.tp_dst, 0xff, sizeof wc->masks.tp_dst);
                 flow->tp_dst = htons(ofpact_get_SET_L4_DST_PORT(a)->port);
             }
             break;
@@ -2379,7 +2428,7 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
             break;
 
         case OFPACT_REG_LOAD:
-            nxm_execute_reg_load(ofpact_get_REG_LOAD(a), flow);
+            nxm_execute_reg_load(ofpact_get_REG_LOAD(a), flow, wc);
             break;
 
         case OFPACT_STACK_PUSH:
@@ -2457,11 +2506,11 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
             break;
 
         case OFPACT_CLEAR_ACTIONS:
-            /* XXX
-             * Nothing to do because writa-actions is not supported for now.
-             * When writa-actions is supported, clear-actions also must
-             * be supported at the same time.
-             */
+            ofpbuf_clear(&ctx->action_set);
+            break;
+
+        case OFPACT_WRITE_ACTIONS:
+            xlate_write_actions(ctx, a);
             break;
 
         case OFPACT_WRITE_METADATA:
@@ -2745,7 +2794,7 @@ xlate_actions__(struct xlate_in *xin, struct xlate_out *xout)
         }
         ctx.rule = rule;
     }
-    xout->fail_open = ctx.rule && rule_dpif_fail_open(ctx.rule);
+    xout->fail_open = ctx.rule && rule_dpif_is_fail_open(ctx.rule);
 
     if (xin->ofpacts) {
         ofpacts = xin->ofpacts;
@@ -2759,6 +2808,8 @@ xlate_actions__(struct xlate_in *xin, struct xlate_out *xout)
     }
 
     ofpbuf_use_stub(&ctx.stack, ctx.init_stack, sizeof ctx.init_stack);
+    ofpbuf_use_stub(&ctx.action_set,
+                    ctx.action_set_stub, sizeof ctx.action_set_stub);
 
     if (mbridge_has_mirrors(ctx.xbridge->mbridge)) {
         /* Do this conditionally because the copy is expensive enough that it
@@ -2817,6 +2868,10 @@ xlate_actions__(struct xlate_in *xin, struct xlate_out *xout)
             }
         }
 
+        if (ctx.action_set.size) {
+            xlate_action_set(&ctx);
+        }
+
         if (ctx.xbridge->has_in_band
             && in_band_must_output_to_local_port(flow)
             && !actions_output_to_local_port(&ctx)) {
@@ -2840,6 +2895,7 @@ xlate_actions__(struct xlate_in *xin, struct xlate_out *xout)
     }
 
     ofpbuf_uninit(&ctx.stack);
+    ofpbuf_uninit(&ctx.action_set);
 
     /* Clear the metadata and register wildcard masks, because we won't
      * use non-header fields as part of the cache. */
index 69625cf..be7f807 100644 (file)
@@ -545,11 +545,11 @@ ofproto_dpif_flow_mod(struct ofproto_dpif *ofproto,
  * Takes ownership of 'pin' and pin->packet. */
 void
 ofproto_dpif_send_packet_in(struct ofproto_dpif *ofproto,
-                            struct ofputil_packet_in *pin)
+                            struct ofproto_packet_in *pin)
 {
     if (!guarded_list_push_back(&ofproto->pins, &pin->list_node, 1024)) {
         COVERAGE_INC(packet_in_overflow);
-        free(CONST_CAST(void *, pin->packet));
+        free(CONST_CAST(void *, pin->up.packet));
         free(pin);
     }
 }
@@ -1359,7 +1359,7 @@ destruct(struct ofproto *ofproto_)
 {
     struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
     struct rule_dpif *rule, *next_rule;
-    struct ofputil_packet_in *pin, *next_pin;
+    struct ofproto_packet_in *pin, *next_pin;
     struct facet *facet, *next_facet;
     struct cls_cursor cursor;
     struct oftable *table;
@@ -1397,7 +1397,7 @@ destruct(struct ofproto *ofproto_)
     guarded_list_pop_all(&ofproto->pins, &pins);
     LIST_FOR_EACH_SAFE (pin, next_pin, list_node, &pins) {
         list_remove(&pin->list_node);
-        free(CONST_CAST(void *, pin->packet));
+        free(CONST_CAST(void *, pin->up.packet));
         free(pin);
     }
     guarded_list_destroy(&ofproto->pins);
@@ -1428,7 +1428,7 @@ static int
 run_fast(struct ofproto *ofproto_)
 {
     struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
-    struct ofputil_packet_in *pin, *next_pin;
+    struct ofproto_packet_in *pin, *next_pin;
     struct list pins;
 
     /* Do not perform any periodic activity required by 'ofproto' while
@@ -1441,11 +1441,10 @@ run_fast(struct ofproto *ofproto_)
     LIST_FOR_EACH_SAFE (pin, next_pin, list_node, &pins) {
         connmgr_send_packet_in(ofproto->up.connmgr, pin);
         list_remove(&pin->list_node);
-        free(CONST_CAST(void *, pin->packet));
+        free(CONST_CAST(void *, pin->up.packet));
         free(pin);
     }
 
-    ofproto_dpif_monitor_run_fast();
     return 0;
 }
 
@@ -1487,9 +1486,6 @@ run(struct ofproto *ofproto_)
         dpif_ipfix_run(ofproto->ipfix);
     }
 
-    ofproto_dpif_monitor_run_fast();
-    ofproto_dpif_monitor_run();
-
     HMAP_FOR_EACH (ofport, up.hmap_node, &ofproto->up.ports) {
         port_run(ofport);
     }
@@ -1546,7 +1542,6 @@ wait(struct ofproto *ofproto_)
     if (ofproto->ipfix) {
         dpif_ipfix_wait(ofproto->ipfix);
     }
-    ofproto_dpif_monitor_wait();
     HMAP_FOR_EACH (bundle, hmap_node, &ofproto->bundles) {
         bundle_wait(bundle);
     }
@@ -4309,9 +4304,15 @@ rule_dpif_credit_stats(struct rule_dpif *rule,
 }
 
 bool
-rule_dpif_fail_open(const struct rule_dpif *rule)
+rule_dpif_is_fail_open(const struct rule_dpif *rule)
+{
+    return is_fail_open_rule(&rule->up);
+}
+
+bool
+rule_dpif_is_table_miss(const struct rule_dpif *rule)
 {
-    return rule->up.cr.priority == FAIL_OPEN_PRIORITY;
+    return rule_is_table_miss(&rule->up);
 }
 
 ovs_be64
@@ -4633,9 +4634,7 @@ rule_dpif_lookup_in_table(struct ofproto_dpif *ofproto,
         cls_rule = classifier_lookup(cls, &ofpc_normal_flow, wc);
     } else if (frag && ofproto->up.frag_handling == OFPC_FRAG_DROP) {
         cls_rule = &ofproto->drop_frags_rule->up.cr;
-        if (wc) {
-            flow_wildcards_init_exact(wc);
-        }
+        /* Frag mask in wc already set above. */
     } else {
         cls_rule = classifier_lookup(cls, flow, wc);
     }
@@ -5055,7 +5054,7 @@ trace_format_rule(struct ds *result, int level, const struct rule_dpif *rule)
     actions = rule_dpif_get_actions(rule);
 
     ds_put_char_multiple(result, '\t', level);
-    ds_put_cstr(result, "OpenFlow ");
+    ds_put_cstr(result, "OpenFlow actions=");
     ofpacts_format(actions->ofpacts, actions->ofpacts_len, result);
     ds_put_char(result, '\n');
 
@@ -5206,7 +5205,7 @@ ofproto_unixctl_trace(struct unixctl_conn *conn, int argc, const char *argv[],
             goto exit;
         }
         ds_put_format(&result, "Bridge: %s\n", ofproto->up.name);
-    } else if (!parse_ofp_exact_flow(&flow, argv[argc - 1])) {
+    } else if (!parse_ofp_exact_flow(&flow, NULL, argv[argc - 1], NULL)) {
         if (argc != 3) {
             unixctl_command_reply_error(conn, "Must specify bridge name");
             goto exit;
index 51b1979..c93d37c 100644 (file)
@@ -28,6 +28,7 @@
 union user_action_cookie;
 struct dpif_flow_stats;
 struct ofproto_dpif;
+struct ofproto_packet_in;
 struct ofport_dpif;
 struct dpif_backer;
 struct OVS_LOCKABLE rule_dpif;
@@ -73,7 +74,8 @@ void rule_dpif_unref(struct rule_dpif *);
 void rule_dpif_credit_stats(struct rule_dpif *rule ,
                             const struct dpif_flow_stats *);
 
-bool rule_dpif_fail_open(const struct rule_dpif *rule);
+bool rule_dpif_is_fail_open(const struct rule_dpif *);
+bool rule_dpif_is_table_miss(const struct rule_dpif *);
 
 struct rule_actions *rule_dpif_get_actions(const struct rule_dpif *);
 
@@ -97,7 +99,7 @@ int ofproto_dpif_execute_actions(struct ofproto_dpif *, const struct flow *,
                                  struct rule_dpif *, const struct ofpact *,
                                  size_t ofpacts_len, struct ofpbuf *);
 void ofproto_dpif_send_packet_in(struct ofproto_dpif *,
-                                 struct ofputil_packet_in *pin);
+                                 struct ofproto_packet_in *);
 int ofproto_dpif_send_packet(const struct ofport_dpif *, struct ofpbuf *);
 void ofproto_dpif_flow_mod(struct ofproto_dpif *, struct ofputil_flow_mod *);
 
index de566e3..07bb266 100644 (file)
@@ -398,6 +398,18 @@ struct rule_actions *rule_get_actions(const struct rule *rule)
 struct rule_actions *rule_get_actions__(const struct rule *rule)
     OVS_REQUIRES(rule->mutex);
 
+/* Returns true if 'rule' is an OpenFlow 1.3 "table-miss" rule, false
+ * otherwise.
+ *
+ * ("Table-miss" rules are special because a packet_in generated through one
+ * uses OFPR_NO_MATCH as its reason, whereas packet_ins generated by any other
+ * rule use OFPR_ACTION.) */
+static inline bool
+rule_is_table_miss(const struct rule *rule)
+{
+    return rule->cr.priority == 0 && cls_rule_is_catchall(&rule->cr);
+}
+
 /* A set of actions within a "struct rule".
  *
  *
index b1c93fb..8fa5e84 100644 (file)
@@ -241,6 +241,7 @@ static long long int ofport_get_usage(const struct ofproto *,
                                       ofp_port_t ofp_port);
 static void ofport_set_usage(struct ofproto *, ofp_port_t ofp_port,
                              long long int last_used);
+static void ofport_remove_usage(struct ofproto *, ofp_port_t ofp_port);
 
 /* Ofport usage.
  *
@@ -450,7 +451,7 @@ ofproto_enumerate_names(const char *type, struct sset *names)
 {
     const struct ofproto_class *class = ofproto_class_find__(type);
     return class ? class->enumerate_names(type, names) : EAFNOSUPPORT;
- }
+}
 
 int
 ofproto_create(const char *datapath_name, const char *datapath_type,
@@ -1138,7 +1139,8 @@ ofproto_get_snoops(const struct ofproto *ofproto, struct sset *snoops)
 }
 
 static void
-ofproto_rule_delete__(struct ofproto *ofproto, struct rule *rule)
+ofproto_rule_delete__(struct ofproto *ofproto, struct rule *rule,
+                      uint8_t reason)
     OVS_REQUIRES(ofproto_mutex)
 {
     struct ofopgroup *group;
@@ -1146,7 +1148,7 @@ ofproto_rule_delete__(struct ofproto *ofproto, struct rule *rule)
     ovs_assert(!rule->pending);
 
     group = ofopgroup_create_unattached(ofproto);
-    delete_flow__(rule, group, OFPRR_DELETE);
+    delete_flow__(rule, group, reason);
     ofopgroup_submit(group);
 }
 
@@ -1201,7 +1203,7 @@ ofproto_flush__(struct ofproto *ofproto)
         ovs_rwlock_unlock(&table->cls.rwlock);
         CLS_CURSOR_FOR_EACH_SAFE (rule, next_rule, cr, &cursor) {
             if (!rule->pending) {
-                ofproto_rule_delete__(ofproto, rule);
+                ofproto_rule_delete__(ofproto, rule, OFPRR_DELETE);
             }
         }
     }
@@ -1972,6 +1974,13 @@ alloc_ofp_port(struct ofproto *ofproto, const char *netdev_name)
             if (!last_used_at) {
                 port_idx = ofproto->alloc_port_no;
                 break;
+            } else if ( last_used_at < time_msec() - 60*60*1000) {
+                /* If the port with ofport 'ofproto->alloc_port_no' was deleted
+                 * more than an hour ago, consider it usable. */
+                ofport_remove_usage(ofproto,
+                    u16_to_ofp(ofproto->alloc_port_no));
+                port_idx = ofproto->alloc_port_no;
+                break;
             } else if (last_used_at < lru) {
                 lru = last_used_at;
                 lru_ofport = ofproto->alloc_port_no;
@@ -2254,6 +2263,20 @@ ofport_set_usage(struct ofproto *ofproto, ofp_port_t ofp_port,
                 hash_ofp_port(ofp_port));
 }
 
+static void
+ofport_remove_usage(struct ofproto *ofproto, ofp_port_t ofp_port)
+{
+    struct ofport_usage *usage;
+    HMAP_FOR_EACH_IN_BUCKET (usage, hmap_node, hash_ofp_port(ofp_port),
+                             &ofproto->ofport_usage) {
+        if (usage->ofp_port == ofp_port) {
+            hmap_remove(&ofproto->ofport_usage, &usage->hmap_node);
+            free(usage);
+            break;
+        }
+    }
+}
+
 int
 ofproto_port_get_stats(const struct ofport *port, struct netdev_stats *stats)
 {
@@ -2800,13 +2823,15 @@ reject_slave_controller(struct ofconn *ofconn)
 static enum ofperr
 ofproto_check_ofpacts(struct ofproto *ofproto,
                       const struct ofpact ofpacts[], size_t ofpacts_len,
-                      struct flow *flow, uint8_t table_id)
+                      struct flow *flow, uint8_t table_id,
+                      bool enforce_consistency)
 {
     enum ofperr error;
     uint32_t mid;
 
     error = ofpacts_check(ofpacts, ofpacts_len, flow,
-                          u16_to_ofp(ofproto->max_ports), table_id);
+                          u16_to_ofp(ofproto->max_ports), table_id,
+                          enforce_consistency);
     if (error) {
         return error;
     }
@@ -2864,7 +2889,8 @@ handle_packet_out(struct ofconn *ofconn, const struct ofp_header *oh)
     /* Verify actions against packet, then send packet if successful. */
     in_port_.ofp_port = po.in_port;
     flow_extract(payload, 0, 0, NULL, &in_port_, &flow);
-    error = ofproto_check_ofpacts(p, po.ofpacts, po.ofpacts_len, &flow, 0);
+    error = ofproto_check_ofpacts(p, po.ofpacts, po.ofpacts_len, &flow, 0,
+                                  oh->version > OFP10_VERSION);
     if (!error) {
         error = p->ofproto_class->packet_out(p, payload, &flow,
                                              po.ofpacts, po.ofpacts_len);
@@ -3555,6 +3581,7 @@ flow_stats_ds(struct rule *rule, struct ds *results)
     cls_rule_format(&rule->cr, results);
     ds_put_char(results, ',');
 
+    ds_put_cstr(results, "actions=");
     ofpacts_format(actions->ofpacts, actions->ofpacts_len, results);
 
     ds_put_cstr(results, "\n");
@@ -3913,7 +3940,8 @@ add_flow(struct ofproto *ofproto, struct ofconn *ofconn,
 
     /* Verify actions. */
     error = ofproto_check_ofpacts(ofproto, fm->ofpacts, fm->ofpacts_len,
-                                  &fm->match.flow, table_id);
+                                  &fm->match.flow, table_id,
+                                  request && request->version > OFP10_VERSION);
     if (error) {
         cls_rule_destroy(&cr);
         return error;
@@ -4035,9 +4063,10 @@ modify_flows__(struct ofproto *ofproto, struct ofconn *ofconn,
             continue;
         }
 
-        /* Verify actions. */
+        /* Verify actions, enforce consistency check on OF1.1+. */
         error = ofpacts_check(fm->ofpacts, fm->ofpacts_len, &fm->match.flow,
-                              u16_to_ofp(ofproto->max_ports), rule->table_id);
+                              u16_to_ofp(ofproto->max_ports), rule->table_id,
+                              request && request->version > OFP10_VERSION);
         if (error) {
             return error;
         }
@@ -4299,8 +4328,7 @@ ofproto_rule_expire(struct rule *rule, uint8_t reason)
     ovs_assert(reason == OFPRR_HARD_TIMEOUT || reason == OFPRR_IDLE_TIMEOUT
                || reason == OFPRR_DELETE || reason == OFPRR_GROUP_DELETE);
 
-    ofproto_rule_send_removed(rule, reason);
-    ofproto_rule_delete__(ofproto, rule);
+    ofproto_rule_delete__(ofproto, rule, reason);
 }
 
 /* Reduces '*timeout' to no more than 'max'.  A value of zero in either case
@@ -5649,6 +5677,13 @@ handle_openflow__(struct ofconn *ofconn, const struct ofpbuf *msg)
     if (error) {
         return error;
     }
+    if (oh->version >= OFP13_VERSION && ofpmsg_is_stat_request(oh)
+        && ofpmp_more(oh)) {
+        /* We have no buffer implementation for multipart requests.
+         * Report overflow for requests which consists of multiple
+         * messages. */
+        return OFPERR_OFPBRC_MULTIPART_BUFFER_OVERFLOW;
+    }
 
     switch (type) {
         /* OpenFlow requests. */
@@ -5762,7 +5797,7 @@ handle_openflow__(struct ofconn *ofconn, const struct ofpbuf *msg)
         /* FIXME: Change the following once they are implemented: */
     case OFPTYPE_QUEUE_GET_CONFIG_REQUEST:
     case OFPTYPE_TABLE_FEATURES_STATS_REQUEST:
-        return OFPERR_OFPBRC_BAD_TYPE;
+        /* fallthrough */
 
     case OFPTYPE_HELLO:
     case OFPTYPE_ERROR:
@@ -5793,7 +5828,11 @@ handle_openflow__(struct ofconn *ofconn, const struct ofpbuf *msg)
     case OFPTYPE_METER_FEATURES_STATS_REPLY:
     case OFPTYPE_TABLE_FEATURES_STATS_REPLY:
     default:
-        return OFPERR_OFPBRC_BAD_TYPE;
+        if (ofpmsg_is_stat_request(oh)) {
+            return OFPERR_OFPBRC_BAD_STAT;
+        } else {
+            return OFPERR_OFPBRC_BAD_TYPE;
+        }
     }
 }
 
index 91e9c41..831afef 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009, 2010, 2011, 2012 Nicira, Inc.
+ * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -186,15 +186,16 @@ get_token(struct pinsched *ps)
 
 void
 pinsched_send(struct pinsched *ps, ofp_port_t port_no,
-              struct ofpbuf *packet, pinsched_tx_cb *cb, void *aux)
+              struct ofpbuf *packet, struct list *txq)
 {
+    list_init(txq);
     if (!ps) {
-        cb(packet, aux);
+        list_push_back(txq, &packet->list_node);
     } else if (!ps->n_queued && get_token(ps)) {
         /* In the common case where we are not constrained by the rate limit,
          * let the packet take the normal path. */
         ps->n_normal++;
-        cb(packet, aux);
+        list_push_back(txq, &packet->list_node);
     } else {
         /* Otherwise queue it up for the periodic callback to drain out. */
         struct pinqueue *q;
@@ -217,15 +218,17 @@ pinsched_send(struct pinsched *ps, ofp_port_t port_no,
 }
 
 void
-pinsched_run(struct pinsched *ps, pinsched_tx_cb *cb, void *aux)
+pinsched_run(struct pinsched *ps, struct list *txq)
 {
+    list_init(txq);
     if (ps) {
         int i;
 
         /* Drain some packets out of the bucket if possible, but limit the
          * number of iterations to allow other code to get work done too. */
         for (i = 0; ps->n_queued && get_token(ps) && i < 50; i++) {
-            cb(get_tx_packet(ps), aux);
+            struct ofpbuf *packet = get_tx_packet(ps);
+            list_push_back(txq, &packet->list_node);
         }
     }
 }
index 06b22f4..8cce1f2 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009, 2010, 2011, 2012 Nicira, Inc.
+ * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #include <stdint.h>
 #include "flow.h"
 
+struct list;
 struct ofpbuf;
 
-typedef void pinsched_tx_cb(struct ofpbuf *, void *aux);
 struct pinsched *pinsched_create(int rate_limit, int burst_limit);
 void pinsched_get_limits(const struct pinsched *,
                          int *rate_limit, int *burst_limit);
 void pinsched_set_limits(struct pinsched *, int rate_limit, int burst_limit);
 void pinsched_destroy(struct pinsched *);
 void pinsched_send(struct pinsched *, ofp_port_t port_no, struct ofpbuf *,
-                   pinsched_tx_cb *, void *aux);
-void pinsched_run(struct pinsched *, pinsched_tx_cb *, void *aux);
+                   struct list *txq);
+void pinsched_run(struct pinsched *, struct list *txq);
 void pinsched_wait(struct pinsched *);
 
 unsigned int pinsched_count_txqlen(const struct pinsched *);
index 8ea8473..006d7ed 100755 (executable)
@@ -37,7 +37,7 @@ def printEdge(tableName, type, baseType, label):
             baseType.ref_table_name,
             ', '.join(['%s=%s' % (k,v) for k,v in options.items()]))
 
-def schemaToDot(schemaFile):
+def schemaToDot(schemaFile, arrows):
     schema = ovs.db.schema.DbSchema.from_json(ovs.json.from_file(schemaFile))
 
     print "digraph %s {" % schema.name
@@ -45,7 +45,8 @@ def schemaToDot(schemaFile):
     print '\tsize="6.5,4";'
     print '\tmargin="0";'
     print "\tnode [shape=box];"
-    print "\tedge [dir=none, arrowhead=none, arrowtail=none];"
+    if not arrows:
+        print "\tedge [dir=none, arrowhead=none, arrowtail=none];"
     for tableName, table in schema.tables.iteritems():
         options = {}
         if table.is_root:
@@ -69,6 +70,7 @@ usage: %(argv0)s [OPTIONS] SCHEMA
 where SCHEMA is an OVSDB schema in JSON format
 
 The following options are also available:
+  --no-arrows                 omit arrows from diagram
   -h, --help                  display this help message
   -V, --version               display version information\
 """ % {'argv0': argv0}
@@ -78,13 +80,17 @@ if __name__ == "__main__":
     try:
         try:
             options, args = getopt.gnu_getopt(sys.argv[1:], 'hV',
-                                              ['help', 'version'])
+                                              ['no-arrows',
+                                               'help', 'version',])
         except getopt.GetoptError, geo:
             sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
             sys.exit(1)
 
+        arrows = True
         for key, value in options:
-            if key in ['-h', '--help']:
+            if key == '--no-arrows':
+                arrows = False
+            elif key in ['-h', '--help']:
                 usage()
             elif key in ['-V', '--version']:
                 print "ovsdb-dot (Open vSwitch) @VERSION@"
@@ -96,7 +102,7 @@ if __name__ == "__main__":
                              "(use --help for help)\n" % argv0)
             sys.exit(1)
 
-        schemaToDot(args[0])
+        schemaToDot(args[0], arrows)
 
     except ovs.db.error.Error, e:
         sys.stderr.write("%s: %s\n" % (argv0, e.msg))
index c1540eb..6e7a2cc 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2010, 2011, 2012 Nicira, Inc.
+# Copyright (c) 2010, 2011, 2012, 2013 Nicira, Inc.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -226,7 +226,8 @@ class Connection(object):
         return self.received_bytes
 
     def __log_msg(self, title, msg):
-        vlog.dbg("%s: %s %s" % (self.name, title, msg))
+        if vlog.dbg_is_enabled():
+            vlog.dbg("%s: %s %s" % (self.name, title, msg))
 
     def send(self, msg):
         if self.status:
index 7dac5bb..c657047 100644 (file)
@@ -14,6 +14,8 @@
 
 import errno
 import os
+import os.path
+import random
 import select
 import socket
 import sys
@@ -25,7 +27,33 @@ import ovs.vlog
 vlog = ovs.vlog.Vlog("socket_util")
 
 
-def make_unix_socket(style, nonblock, bind_path, connect_path):
+def make_short_name(long_name):
+    if long_name is None:
+        return None
+    long_name = os.path.abspath(long_name)
+    long_dirname = os.path.dirname(long_name)
+    tmpdir = os.getenv('TMPDIR', '/tmp')
+    for x in xrange(0, 1000):
+        link_name = \
+            '%s/ovs-un-py-%d-%d' % (tmpdir, random.randint(0, 10000), x)
+        try:
+            os.symlink(long_dirname, link_name)
+            ovs.fatal_signal.add_file_to_unlink(link_name)
+            return os.path.join(link_name, os.path.basename(long_name))
+        except OSError, e:
+            if e.errno != errno.EEXIST:
+                break
+    raise Exception("Failed to create temporary symlink")
+
+
+def free_short_name(short_name):
+    if short_name is None:
+        return
+    link_name = os.path.dirname(short_name)
+    ovs.fatal_signal.unlink_file_now(link_name)
+
+
+def make_unix_socket(style, nonblock, bind_path, connect_path, short=False):
     """Creates a Unix domain socket in the given 'style' (either
     socket.SOCK_DGRAM or socket.SOCK_STREAM) that is bound to 'bind_path' (if
     'bind_path' is not None) and connected to 'connect_path' (if 'connect_path'
@@ -106,6 +134,22 @@ def make_unix_socket(style, nonblock, bind_path, connect_path):
                     os.close(connect_dirfd)
                 if bind_dirfd is not None:
                     os.close(bind_dirfd)
+        elif (eno == "AF_UNIX path too long"):
+            if short:
+                return get_exception_errno(e), None
+            short_bind_path = None
+            try:
+                short_bind_path = make_short_name(bind_path)
+                short_connect_path = make_short_name(connect_path)
+            except:
+                free_short_name(short_bind_path)
+                return errno.ENAMETOOLONG, None
+            try:
+                return make_unix_socket(style, nonblock, short_bind_path,
+                                        short_connect_path, short=True)
+            finally:
+                free_short_name(short_bind_path)
+                free_short_name(short_connect_path)
         else:
             return get_exception_errno(e), None
 
index 8447c0c..478f08e 100644 (file)
@@ -92,6 +92,29 @@ class Vlog:
     def dbg(self, message, **kwargs):
         self.__log("DBG", message, **kwargs)
 
+    def __is_enabled(self, level):
+        level = LEVELS.get(level.lower(), logging.DEBUG)
+        for f, f_level in Vlog.__mfl[self.name].iteritems():
+            f_level = LEVELS.get(f_level, logging.CRITICAL)
+            if level >= f_level:
+                return True
+        return False
+
+    def emer_is_enabled(self):
+        return self.__is_enabled("EMER")
+
+    def err_is_enabled(self):
+        return self.__is_enabled("ERR")
+
+    def warn_is_enabled(self):
+        return self.__is_enabled("WARN")
+
+    def info_is_enabled(self):
+        return self.__is_enabled("INFO")
+
+    def dbg_is_enabled(self):
+        return self.__is_enabled("DBG")
+
     def exception(self, message):
         """Logs 'message' at ERR log level.  Includes a backtrace when in
         exception context."""
index ba2774a..435772f 100644 (file)
@@ -23,6 +23,8 @@ assignments.  The following OVS-specific variable names are supported:
 
         * "OVSBond", if <name> is an OVS bond.
 
+        * "OVSTunnel", if <name> is an OVS tunnel.
+
     - OVS_BRIDGE: If TYPE is anything other than "OVSBridge", set to
       the name of the OVS bridge to which the port should be attached.
 
@@ -39,6 +41,12 @@ assignments.  The following OVS-specific variable names are supported:
     - BOND_IFACES: For "OVSBond" interfaces, a list of physical
       interfaces to bond together.
 
+    - OVS_TUNNEL_TYPE: For "OVSTunnel" interfaces, the type of the tunnel.
+      For example, "gre", "vxlan", etc.
+
+    - OVS_TUNNEL_OPTIONS: For "OVSTunnel" interfaces, this field should be
+      used to specify the tunnel options like remote_ip, key, etc.
+
 Note
 ----
 
@@ -125,6 +133,17 @@ DEVICE=gige-*
 ONBOOT=yes
 HOTPLUG=no
 
+An Open vSwitch Tunnel:
+
+==> ifcfg-gre0 <==
+DEVICE=ovs-gre0
+ONBOOT=yes
+DEVICETYPE=ovs
+TYPE=OVSTunnel
+OVS_BRIDGE=ovsbridge0
+OVS_TUNNEL_TYPE=gre
+OVS_TUNNEL_OPTIONS="options:remote_ip=A.B.C.D"
+
 Reporting Bugs
 --------------
 
index d2a2f4b..8e768c8 100755 (executable)
@@ -42,7 +42,7 @@ case "$TYPE" in
                retval=$?
                ovs-vsctl -t ${TIMEOUT} -- --if-exists del-br "$DEVICE"
                ;;
-       OVSPort|OVSIntPort|OVSBond)
+       OVSPort|OVSIntPort|OVSBond|OVSTunnel)
                ${OTHERSCRIPT} ${CONFIG} $2
                retval=$?
                ovs-vsctl -t ${TIMEOUT} -- --if-exists del-port "$OVS_BRIDGE" "$DEVICE"
index 8904c59..017346d 100755 (executable)
@@ -117,6 +117,11 @@ case "$TYPE" in
                ${OTHERSCRIPT} ${CONFIG} ${2}
                OVSINTF=${DEVICE} /sbin/ifup "$OVS_BRIDGE"
                ;;
+       OVSTunnel)
+               ifup_ovs_bridge
+               ovs-vsctl -t ${TIMEOUT} -- --may-exist add-port "$OVS_BRIDGE" "$DEVICE" $OVS_OPTIONS -- set Interface "$DEVICE" type=$OVS_TUNNEL_TYPE $OVS_TUNNEL_OPTIONS ${OVS_EXTRA+-- $OVS_EXTRA}
+               ${OTHERSCRIPT} ${CONFIG} ${2}
+               ;;
        *)
                echo $"Invalid OVS interface type $TYPE"
                exit 1
index 27a3b03..73b09ba 100644 (file)
@@ -144,12 +144,15 @@ systemctl start openvswitch.service
 /usr/share/openvswitch/scripts/ovs-bugtool-*
 /usr/share/openvswitch/scripts/ovs-check-dead-ifs
 /usr/share/openvswitch/scripts/ovs-lib
+/usr/share/openvswitch/scripts/ovs-vtep
 %config /usr/share/openvswitch/vswitch.ovsschema
+%config /usr/share/openvswitch/vtep.ovsschema
 /usr/sbin/ovs-bugtool
 /usr/sbin/ovs-vswitchd
 /usr/sbin/ovsdb-server
 /usr/bin/ovs-appctl
 /usr/bin/ovs-dpctl
+/usr/bin/ovs-dpctl-top
 /usr/bin/ovs-ofctl
 /usr/bin/ovs-vsctl
 /usr/bin/ovsdb-client
@@ -158,21 +161,25 @@ systemctl start openvswitch.service
 /usr/bin/ovs-pki
 /usr/bin/ovs-test
 /usr/bin/ovs-l3ping
+/usr/bin/vtep-ctl
 %doc /usr/share/man/man8/ovs-controller.8.gz
 %doc /usr/share/man/man8/ovs-pki.8.gz
 %doc /usr/share/man/man1/ovsdb-client.1.gz
 %doc /usr/share/man/man1/ovsdb-server.1.gz
 %doc /usr/share/man/man1/ovsdb-tool.1.gz
 %doc /usr/share/man/man5/ovs-vswitchd.conf.db.5.gz
+%doc /usr/share/man/man5/vtep.5.gz
 %doc /usr/share/man/man8/ovs-appctl.8.gz
 %doc /usr/share/man/man8/ovs-bugtool.8.gz
 %doc /usr/share/man/man8/ovs-dpctl.8.gz
+%doc /usr/share/man/man8/ovs-dpctl-top.8.gz
 %doc /usr/share/man/man8/ovs-ofctl.8.gz
 %doc /usr/share/man/man8/ovs-parse-backtrace.8.gz
 %doc /usr/share/man/man8/ovs-vsctl.8.gz
 %doc /usr/share/man/man8/ovs-vswitchd.8.gz
 %doc /usr/share/man/man8/ovs-test.8.gz
 %doc /usr/share/man/man8/ovs-l3ping.8.gz
+%doc /usr/share/man/man8/vtep-ctl.8.gz
 /var/lib/openvswitch
 /var/log/openvswitch
 /usr/share/openvswitch/scripts/ovs-ctl
index f77cd3a..3faf463 100644 (file)
@@ -124,6 +124,7 @@ exit 0
 /usr/bin/ovs-vsctl
 /usr/bin/ovsdb-client
 /usr/bin/ovsdb-tool
+/usr/bin/vtep-ctl
 /usr/sbin/ovs-bugtool
 /usr/sbin/ovs-vswitchd
 /usr/sbin/ovsdb-server
@@ -134,6 +135,7 @@ exit 0
 /usr/share/man/man1/ovsdb-server.1.gz
 /usr/share/man/man1/ovsdb-tool.1.gz
 /usr/share/man/man5/ovs-vswitchd.conf.db.5.gz
+/usr/share/man/man5/vtep.5.gz
 /usr/share/man/man8/ovs-appctl.8.gz
 /usr/share/man/man8/ovs-bugtool.8.gz
 /usr/share/man/man8/ovs-ctl.8.gz
@@ -145,6 +147,7 @@ exit 0
 /usr/share/man/man8/ovs-vlan-test.8.gz
 /usr/share/man/man8/ovs-vsctl.8.gz
 /usr/share/man/man8/ovs-vswitchd.8.gz
+/usr/share/man/man8/vtep-ctl.8.gz
 /usr/share/openvswitch/bugtool-plugins/
 /usr/share/openvswitch/python/
 /usr/share/openvswitch/scripts/ovs-bugtool-*
@@ -152,8 +155,10 @@ exit 0
 /usr/share/openvswitch/scripts/ovs-ctl
 /usr/share/openvswitch/scripts/ovs-lib
 /usr/share/openvswitch/scripts/ovs-save
+/usr/share/openvswitch/scripts/ovs-vtep
 /usr/share/openvswitch/scripts/sysconfig.template
 /usr/share/openvswitch/vswitch.ovsschema
+/usr/share/openvswitch/vtep.ovsschema
 /usr/share/doc/openvswitch-%{version}/FAQ
 /usr/share/doc/openvswitch-%{version}/README.RHEL
 /var/lib/openvswitch
index 3db626c..5c0db2a 100644 (file)
@@ -25,6 +25,21 @@ export PYTHONIOENCODING
 PYTHONDONTWRITEBYTECODE=yes
 export PYTHONDONTWRITEBYTECODE
 
+# Test whether the current working directory name is all ASCII
+# characters.  Some Python code doesn't tolerate non-ASCII characters
+# in filenames very well, so if the current working directory is
+# non-ASCII then we skip the tests that run those programs.
+#
+# This would be just papering over a real problem, except that the
+# tests that we skip are launched from initscripts and thus normally
+# run in system directories with ASCII names.  (This problem only came
+# up at all because the Debian autobuilders do build in a top-level
+# directory named /«BUILDDIR».)
+case `pwd | tr -d ' -~'` in
+    '') non_ascii_cwd=false ;;
+    *) non_ascii_cwd=true
+esac
+
 if test $HAVE_PYTHON = yes; then
     if python -m argparse 2>/dev/null; then
         :
index 8f51a65..f2e0edc 100644 (file)
@@ -64,11 +64,12 @@ TESTSUITE_AT = \
        tests/ovs-xapi-sync.at \
        tests/stp.at \
        tests/interface-reconfigure.at \
-       tests/vlog.at
+       tests/vlog.at \
+       tests/vtep-ctl.at
 TESTSUITE = $(srcdir)/tests/testsuite
 DISTCLEANFILES += tests/atconfig tests/atlocal
 
-AUTOTEST_PATH = utilities:vswitchd:ovsdb:tests
+AUTOTEST_PATH = utilities:vswitchd:ovsdb:vtep:tests
 
 check-local: tests/atconfig tests/atlocal $(TESTSUITE)
        $(SHELL) '$(TESTSUITE)' -C tests AUTOTEST_PATH=$(AUTOTEST_PATH) $(TESTSUITEFLAGS)
index 3154909..73bf07f 100644 (file)
@@ -255,32 +255,24 @@ OVS_VSWITCHD_START([add-br br1 -- set bridge br1 datapath-type=dummy -- \
                     options:peer=p0 ofport_request=2 -- \
                     add-port br0 p0 -- set Interface p0 type=patch \
                     options:peer=p1 ofport_request=1 -- \
-                    set Interface p0 bfd:enable=true bfd:min_tx=300 bfd:min_rx=300 bfd:decay_min_rx=3000 -- \
+                    set Interface p0 bfd:enable=true bfd:min_tx=300 bfd:min_rx=300 -- \
                     set Interface p1 bfd:enable=true bfd:min_tx=500 bfd:min_rx=500])
 
 ovs-appctl time/stop
-for i in `seq 0 1`; do ovs-appctl time/warp 500; done
-
-# figuring out which port initiates the bfd session is important,
-# since this whole unit test is based on exact timing sequence.
-# for example, if p0 starts the bfd session, the p0 should have gone
-# [up] now, and it will decay after 3000ms. if p1 starts the bfd
-# session, we should wait for another 1000ms for p0 to go [up], and
-# then 3000ms for it to decay.
-
-# check which port sends the first bfd control packet.
-if [ ovs-appctl bfd/show p0 | grep "Remote Session State: init" ]
-then
-# if p0 sends first, it should have gone up already.
-    BFD_CHECK([p0], [true], [false], [none], [up], [No Diagnostic], [none], [init], [No Diagnostic])
-else
-# if p1 sends first, wait 1000ms for p0 to go up.
-    BFD_CHECK([p0], [false], [false], [none], [init], [No Diagnostic], [none], [down], [No Diagnostic])
-    for i in `seq 0 1`; do ovs-appctl time/warp 500; done
-fi
 
+# wait for a while to stablize everything.
+for i in `seq 0 9`; do ovs-appctl time/warp 500; done
+BFD_CHECK([p0], [true], [false], [none], [up], [No Diagnostic], [none], [up], [No Diagnostic])
+BFD_CHECK([p1], [true], [false], [none], [up], [No Diagnostic], [none], [up], [No Diagnostic])
+BFD_CHECK_TX([p0], [500ms], [300ms], [500ms])
+BFD_CHECK_RX([p0], [500ms], [300ms], [500ms])
 
 # Test-1 BFD decay: decay to decay_min_rx
+AT_CHECK([ovs-vsctl set interface p0 bfd:decay_min_rx=3000])
+# set the bfd:decay_min_rx of p0 to 3000ms.
+BFD_CHECK_TX([p0], [500ms], [300ms], [500ms])
+BFD_CHECK_RX([p0], [500ms], [300ms], [500ms])
+
 # bfd:decay_min_rx is set to 3000ms after the local state of p0 goes up,
 # so for the first 2500ms, there should be no change.
 for i in `seq 0 4`; do ovs-appctl time/warp 500; done
@@ -288,61 +280,40 @@ BFD_CHECK([p0], [true], [false], [none], [up], [No Diagnostic], [none], [up], [N
 BFD_CHECK_TX([p0], [500ms], [300ms], [500ms])
 BFD_CHECK_RX([p0], [500ms], [300ms], [500ms])
 
-# advance the clock by 500ms.
-ovs-appctl time/warp 500
-# now at 3000ms, min_rx should decay to 3000ms and there should be
-# poll sequence flags.
-BFD_CHECK([p0], [true], [false], [none], [up], [No Diagnostic], [final], [up], [No Diagnostic])
-BFD_CHECK([p1], [true], [false], [none], [up], [No Diagnostic], [poll], [up], [No Diagnostic])
+# advance the clock by 2000ms.
+for i in `seq 0 3`; do ovs-appctl time/warp 500; done
+# now, min_rx should decay to 3000ms.
 BFD_CHECK_TX([p0], [500ms], [300ms], [500ms])
 BFD_CHECK_RX([p0], [3000ms], [3000ms], [500ms])
 
-# since the tx_min of p0 is still 500ms, after 500ms from decay,
-# the control message will be sent from p0 to p1, and p1 'flag'
-# will go back to none.
-ovs-appctl time/warp 500
-BFD_CHECK([p1], [true], [false], [none], [up], [No Diagnostic], [none], [up], [No Diagnostic])
-
 # the rx_min of p0 is 3000ms now, and p1 will send next control message
-# 3000ms after decay. so, advance clock by 2500ms to make that happen.
-for i in `seq 0 4`; do ovs-appctl time/warp 500; done
+# 3000ms after decay. so, advance clock by 5000ms to make that happen.
+for i in `seq 0 9`; do ovs-appctl time/warp 500; done
 BFD_CHECK([p0], [true], [false], [none], [up], [No Diagnostic], [none], [up], [No Diagnostic])
+BFD_CHECK([p1], [true], [false], [none], [up], [No Diagnostic], [none], [up], [No Diagnostic])
 BFD_CHECK_TX([p0], [500ms], [300ms], [500ms])
 BFD_CHECK_RX([p0], [3000ms], [3000ms], [500ms])
 # End of Test-1 ###############################################################
 
 
 # Test-2 BFD decay: go back to cfg_min_rx when there is traffic
-# receive packet at 1/100ms rate for 3000ms.
-for i in `seq 0 30`
+# receive packet at 1/100ms rate for 5000ms.
+for i in `seq 0 49`
 do
     ovs-appctl time/warp 100
     AT_CHECK([ovs-ofctl packet-out br1 3 2  "90e2ba01475000101856b2e80806000108000604000100101856b2e80202020300000000000002020202"],
              [0], [stdout], [])
 done
 # after a decay interval (3000ms), the p0 min_rx will go back to
-# cfg_min_rx. there should be poll sequence flags.
-BFD_CHECK([p0], [true], [false], [none], [up], [No Diagnostic], [final], [up], [No Diagnostic])
-BFD_CHECK([p1], [true], [false], [none], [up], [No Diagnostic], [poll], [up], [No Diagnostic])
-BFD_CHECK_TX([p0], [500ms], [300ms], [500ms])
-BFD_CHECK_RX([p0], [500ms], [300ms], [500ms])
-
-# 500ms later, both direction will send control messages,
-# and their 'flag' will go back to none.
-ovs-appctl time/warp 500
-BFD_CHECK([p0], [true], [false], [none], [up], [No Diagnostic], [none], [up], [No Diagnostic])
-BFD_CHECK([p1], [true], [false], [none], [up], [No Diagnostic], [none], [up], [No Diagnostic])
+# cfg_min_rx.
 BFD_CHECK_TX([p0], [500ms], [300ms], [500ms])
 BFD_CHECK_RX([p0], [500ms], [300ms], [500ms])
 # End of Test-2 ###############################################################
 
 
 # Test-3 BFD decay: go back to cfg_min_rx when decay_min_rx is changed
-# advance the clock by 2500ms to 3000m after restore of
-# min_rx. p0 is decayed, and there should be the poll sequence flags.
-for i in `seq 0 4`; do ovs-appctl time/warp 500; done
-BFD_CHECK([p0], [true], [false], [none], [up], [No Diagnostic], [final], [up], [No Diagnostic])
-BFD_CHECK([p1], [true], [false], [none], [up], [No Diagnostic], [poll], [up], [No Diagnostic])
+# advance the clock by 5000m. p0 shoud decay.
+for i in `seq 0 9`; do ovs-appctl time/warp 500; done
 BFD_CHECK_TX([p0], [500ms], [300ms], [500ms])
 BFD_CHECK_RX([p0], [3000ms], [3000ms], [500ms])
 
@@ -353,10 +324,8 @@ BFD_CHECK([p1], [true], [false], [none], [up], [No Diagnostic], [none], [up], [N
 
 # change decay_min_rx to 1000ms.
 # for decay_min_rx < 2000ms, the decay detection time is set to 2000ms.
-# this should firstly reset the min_rx and start poll sequence.
+# this should reset the min_rx.
 AT_CHECK([ovs-vsctl set Interface p0 bfd:decay_min_rx=1000])
-BFD_CHECK([p0], [true], [false], [none], [up], [No Diagnostic], [final], [up], [No Diagnostic])
-BFD_CHECK([p1], [true], [false], [none], [up], [No Diagnostic], [poll], [up], [No Diagnostic])
 BFD_CHECK_TX([p0], [500ms], [300ms], [500ms])
 BFD_CHECK_RX([p0], [500ms], [300ms], [500ms])
 
@@ -371,22 +340,18 @@ do
     BFD_CHECK_RX([p0], [500ms], [300ms], [500ms])
 done
 
-ovs-appctl time/warp 500
-# at 2000ms, decay should happen and there should be the poll sequence flags.
-BFD_CHECK([p0], [true], [false], [none], [up], [No Diagnostic], [final], [up], [No Diagnostic])
-BFD_CHECK([p1], [true], [false], [none], [up], [No Diagnostic], [poll], [up], [No Diagnostic])
+# advance the clock by 2000ms, decay should have happened.
+for i in `seq 0 3`; do ovs-appctl time/warp 500; done
 BFD_CHECK_TX([p0], [500ms], [300ms], [500ms])
 BFD_CHECK_RX([p0], [1000ms], [1000ms], [500ms])
 # advance the clock, so 'flag' go back to none.
-for i in `seq 0 4`; do ovs-appctl time/warp 500; done
+for i in `seq 0 9`; do ovs-appctl time/warp 500; done
 # End of Test-3 ###############################################################
 
 
 # Test-4 BFD decay: set min_rx to 800ms.
 # this should firstly reset the min_rx and then re-decay to 1000ms.
 AT_CHECK([ovs-vsctl set Interface p0 bfd:min_rx=800])
-BFD_CHECK([p0], [true], [false], [none], [up], [No Diagnostic], [final], [up], [No Diagnostic])
-BFD_CHECK([p1], [true], [false], [none], [up], [No Diagnostic], [poll], [up], [No Diagnostic])
 BFD_CHECK_TX([p0], [500ms], [300ms], [500ms])
 BFD_CHECK_RX([p0], [800ms], [800ms], [500ms])
 
@@ -401,21 +366,17 @@ do
     BFD_CHECK_RX([p0], [800ms], [800ms], [500ms])
 done
 
-ovs-appctl time/warp 400
-# at 2000ms, decay should happen and there should be the poll sequence flags.
-BFD_CHECK([p0], [true], [false], [none], [up], [No Diagnostic], [final], [up], [No Diagnostic])
-BFD_CHECK([p1], [true], [false], [none], [up], [No Diagnostic], [poll], [up], [No Diagnostic])
+# advance the clock by 2000ms, decay should have happened.
+for i in `seq 0 3`; do ovs-appctl time/warp 500; done
 BFD_CHECK_TX([p0], [500ms], [300ms], [500ms])
 BFD_CHECK_RX([p0], [1000ms], [1000ms], [500ms])
 # advance the clock, so 'flag' go back to none.
-for i in `seq 0 4`; do ovs-appctl time/warp 500; done
+for i in `seq 0 9`; do ovs-appctl time/warp 500; done
 # End of Test-4 ###############################################################
 
 
 # Test-5 BFD decay: set min_rx to 300ms and decay_min_rx to 5000ms together.
 AT_CHECK([ovs-vsctl set Interface p0 bfd:min_rx=300 bfd:decay_min_rx=5000])
-BFD_CHECK([p0], [true], [false], [none], [up], [No Diagnostic], [final], [up], [No Diagnostic])
-BFD_CHECK([p1], [true], [false], [none], [up], [No Diagnostic], [poll], [up], [No Diagnostic])
 BFD_CHECK_TX([p0], [500ms], [300ms], [500ms])
 BFD_CHECK_RX([p0], [500ms], [300ms], [500ms])
 
@@ -432,10 +393,8 @@ do
     BFD_CHECK_RX([p0], [500ms], [300ms], [500ms])
 done
 
-ovs-appctl time/warp 500
-# at 5000ms, decay should happen and there should be the poll sequence flags.
-BFD_CHECK([p0], [true], [false], [none], [up], [No Diagnostic], [final], [up], [No Diagnostic])
-BFD_CHECK([p1], [true], [false], [none], [up], [No Diagnostic], [poll], [up], [No Diagnostic])
+# advance the clock by 2000ms, decay should have happened.
+for i in `seq 0 3`; do ovs-appctl time/warp 500; done
 BFD_CHECK_TX([p0], [500ms], [300ms], [500ms])
 BFD_CHECK_RX([p0], [5000ms], [5000ms], [500ms])
 # advance the clock, to make 'flag' go back to none.
@@ -445,9 +404,7 @@ for i in `seq 0 9`; do ovs-appctl time/warp 500; done
 
 # Test-6 BFD decay: set decay_min_rx to 0 to disable bfd decay.
 AT_CHECK([ovs-vsctl set Interface p0 bfd:decay_min_rx=0])
-# min_rx is reset, and there should be the poll sequence flags.
-BFD_CHECK([p0], [true], [false], [none], [up], [No Diagnostic], [final], [up], [No Diagnostic])
-BFD_CHECK([p1], [true], [false], [none], [up], [No Diagnostic], [poll], [up], [No Diagnostic])
+# min_rx is reset.
 BFD_CHECK_TX([p0], [500ms], [300ms], [500ms])
 BFD_CHECK_RX([p0], [500ms], [300ms], [500ms])
 for i in `seq 0 20`
@@ -466,8 +423,6 @@ AT_CHECK([ovs-vsctl set Interface p0 bfd:decay_min_rx=3000 -- set interface p1 b
 # there will be poll sequences from both sides. and it is hard to determine the
 # order. so just skip 10000ms and check the RX/TX. at that time, p0 should be in decay already.
 for i in `seq 0 19`; do ovs-appctl time/warp 500; done
-BFD_CHECK([p0], [true], [false], [none], [up], [No Diagnostic], [none], [up], [No Diagnostic])
-BFD_CHECK([p1], [true], [false], [none], [up], [No Diagnostic], [none], [up], [No Diagnostic])
 BFD_CHECK_TX([p0], [500ms], [300ms], [5000ms])
 BFD_CHECK_RX([p0], [5000ms], [3000ms], [500ms])
 # then, there should be no change of status,
@@ -481,16 +436,7 @@ do
 done
 # reset the p1's min_tx to 500ms.
 AT_CHECK([ovs-vsctl set Interface p1 bfd:min_tx=500])
-# check the poll sequence. since p0 has been in decay, now the RX will show 3000ms.
-BFD_CHECK([p1], [true], [false], [none], [up], [No Diagnostic], [final], [up], [No Diagnostic])
-BFD_CHECK([p0], [true], [false], [none], [up], [No Diagnostic], [poll], [up], [No Diagnostic])
-BFD_CHECK_TX([p0], [500ms], [300ms], [500ms])
-BFD_CHECK_RX([p0], [3000ms], [3000ms], [500ms])
-# advance the clock by 3000ms, at that time, p1 will send the control packets.
-# then there will be no poll flags.
-for i in `seq 0 5`; do ovs-appctl time/warp 500; done
-BFD_CHECK([p0], [true], [false], [none], [up], [No Diagnostic], [none], [up], [No Diagnostic])
-BFD_CHECK([p1], [true], [false], [none], [up], [No Diagnostic], [none], [up], [No Diagnostic])
+# since p0 has been in decay, now the RX will show 3000ms.
 BFD_CHECK_TX([p0], [500ms], [300ms], [500ms])
 BFD_CHECK_RX([p0], [3000ms], [3000ms], [500ms])
 # End of Test-7 ###############################################################
@@ -500,8 +446,8 @@ BFD_CHECK_RX([p0], [3000ms], [3000ms], [500ms])
 # turn bfd off on p1
 AT_CHECK([ovs-vsctl set Interface p1 bfd:enable=false])
 
-# check the state change of bfd on p0. After 9000 ms (3 min_rx intervals)
-for i in `seq 0 8`; do ovs-appctl time/warp 1000; done
+# check the state change of bfd on p0. After 15000 ms (> 3 min_rx intervals)
+for i in `seq 0 14`; do ovs-appctl time/warp 1000; done
 BFD_CHECK([p0], [false], [false], [none], [down], [Control Detection Time Expired], [none], [down], [No Diagnostic])
 BFD_CHECK_TX([p0], [1000ms], [1000ms], [0ms])
 BFD_CHECK_RX([p0], [300ms], [300ms], [1ms])
@@ -512,8 +458,8 @@ for i in `seq 0 3`; do ovs-appctl time/warp 500; done
 BFD_CHECK_TX([p0], [500ms], [300ms], [500ms])
 BFD_CHECK_RX([p0], [500ms], [300ms], [500ms])
 
-# since the decay_min_rx is still 3000ms, so after 3000ms, there should be the decay.
-for i in `seq 0 5`; do ovs-appctl time/warp 500; done
+# since the decay_min_rx is still 3000ms, so after 5000ms, p0 should have decayed.
+for i in `seq 0 9`; do ovs-appctl time/warp 500; done
 BFD_CHECK_TX([p0], [500ms], [300ms], [500ms])
 BFD_CHECK_RX([p0], [3000ms], [3000ms], [500ms])
 # End of Test-8 ################################################################
@@ -552,23 +498,21 @@ done
 # receive one packet.
 AT_CHECK([ovs-ofctl packet-out br1 3 2  "90e2ba01475000101856b2e80806000108000604000100101856b2e80202020300000000000002020202"],
              [0], [stdout], [])
-for i in `seq 0 14`
-do
-    ovs-appctl time/warp 100
-    # the forwarding flag should be true, since there is data received.
-    BFD_CHECK([p0], [true], [false], [none], [down], [No Diagnostic], [none], [down], [No Diagnostic])
-    BFD_CHECK_TX([p0], [1000ms], [1000ms], [0ms])
-    BFD_CHECK_RX([p0], [500ms], [500ms], [1ms])
-done
+# wait for 1000ms
+for i in `seq 0 14`; do ovs-appctl time/warp 100; done
+# the forwarding flag should turn to true sometime in this 1000ms, since there is data received.
+BFD_CHECK([p0], [true], [false], [none], [down], [No Diagnostic], [none], [down], [No Diagnostic])
+BFD_CHECK_TX([p0], [1000ms], [1000ms], [0ms])
+BFD_CHECK_RX([p0], [500ms], [500ms], [1ms])
 
-# Stop sending packets for 1000ms.
-for i in `seq 0 9`; do ovs-appctl time/warp 100; done
+# Stop sending packets for 2000ms.
+for i in `seq 0 19`; do ovs-appctl time/warp 100; done
 BFD_CHECK([p0], [false], [false], [none], [down], [No Diagnostic], [none], [down], [No Diagnostic])
 BFD_CHECK_TX([p0], [1000ms], [1000ms], [0ms])
 BFD_CHECK_RX([p0], [500ms], [500ms], [1ms])
 
-# receive packet at 1/100ms rate for 1000ms.
-for i in `seq 0 9`
+# receive packet at 1/100ms rate for 2000ms.
+for i in `seq 0 19`
 do
     ovs-appctl time/warp 100
     AT_CHECK([ovs-ofctl packet-out br1 3 2  "90e2ba01475000101856b2e80806000108000604000100101856b2e80202020300000000000002020202"],
@@ -623,7 +567,7 @@ for i in `seq 0 1`
 do
     ovs-appctl time/warp 500
     BFD_CHECK([p0], [true], [false], [none], [up], [No Diagnostic], [none], [up], [No Diagnostic])
-    for i in `seq 0 5`
+    for i in `seq 0 4`
     do
         AT_CHECK([ovs-ofctl packet-out br1 3 2  "90e2ba01475000101856b2e80806000108000604000100101856b2e80202020300000000000002020202"],
                  [0], [stdout], [])
@@ -693,42 +637,28 @@ do
     BFD_CHECK([p0], [true], [false], [none], [up], [No Diagnostic], [none], [up], [No Diagnostic])
 done
 
-# reconfigure the decay_min_rx to 1000ms. check the poll sequence.
+# reconfigure the decay_min_rx to 1000ms.
 AT_CHECK([ovs-vsctl set interface p0 bfd:decay_min_rx=1000])
-BFD_CHECK([p0], [true], [false], [none], [up], [No Diagnostic], [final], [up], [No Diagnostic])
-BFD_CHECK([p1], [true], [false], [none], [up], [No Diagnostic], [poll], [up], [No Diagnostic])
 BFD_CHECK_TX([p0], [500ms], [300ms], [500ms])
 BFD_CHECK_RX([p0], [500ms], [300ms], [500ms])
 
-# wait for 2000ms to decay.
-for i in `seq 0 3`; do ovs-appctl time/warp 500; done
-BFD_CHECK([p0], [true], [false], [none], [up], [No Diagnostic], [final], [up], [No Diagnostic])
-BFD_CHECK([p1], [true], [false], [none], [up], [No Diagnostic], [poll], [up], [No Diagnostic])
-BFD_CHECK_TX([p0], [500ms], [300ms], [500ms])
-BFD_CHECK_RX([p0], [1000ms], [1000ms], [500ms])
-
-# wait for 1000ms, so that the flags will go back to none.
-for i in `seq 0 1`; do ovs-appctl time/warp 500; done
-BFD_CHECK([p0], [true], [false], [none], [up], [No Diagnostic], [none], [up], [No Diagnostic])
-BFD_CHECK([p1], [true], [false], [none], [up], [No Diagnostic], [none], [up], [No Diagnostic])
+# wait for 5000ms to decay.
+for i in `seq 0 9`; do ovs-appctl time/warp 500; done
 BFD_CHECK_TX([p0], [500ms], [300ms], [500ms])
 BFD_CHECK_RX([p0], [1000ms], [1000ms], [500ms])
 
 # stop the bfd on one side.
 AT_CHECK([ovs-vsctl set Interface p1 bfd:enable=false], [0])
-# for within 2500ms, the detection timer is not out.
-# there is no change to status.
-for i in `seq 0 4`
+
+# advance clock by 4000ms, while receiving packets.
+# the STATE should go DOWN, due to Control Detection Time Expired.
+# but forwarding flag should be still true.
+for i in `seq 0 7`
 do
     ovs-appctl time/warp 500
-    BFD_CHECK([p0], [true], [false], [none], [up], [No Diagnostic], [none], [up], [No Diagnostic])
     AT_CHECK([ovs-ofctl packet-out br1 3 2  "90e2ba01475000101856b2e80806000108000604000100101856b2e80202020300000000000002020202"],
              [0], [stdout], [])
 done
-
-# at 3000ms, the STATE should go DOWN, due to Control Detection Time Expired.
-# but forwarding flag should be still true.
-ovs-appctl time/warp 500
 BFD_CHECK([p0], [true], [false], [none], [down], [Control Detection Time Expired], [none], [down], [No Diagnostic])
 
 # receive packet at 1/100ms rate for 1000ms.
@@ -741,8 +671,8 @@ do
     BFD_CHECK([p0], [true], [false], [none], [down], [Control Detection Time Expired], [none], [down], [No Diagnostic])
 done
 
-# stop receiving for 2000ms.
-for i in `seq 0 19`; do ovs-appctl time/warp 100; done
+# stop receiving for 3000ms.
+for i in `seq 0 29`; do ovs-appctl time/warp 100; done
 BFD_CHECK([p0], [false], [false], [none], [down], [Control Detection Time Expired], [none], [down], [No Diagnostic])
 
 # reset bfd forwarding_if_rx.
@@ -762,7 +692,7 @@ BFD_CHECK_RX([p0], [300ms], [300ms], [1ms])
 # re-enable bfd on the other end. the states should be up.
 AT_CHECK([ovs-vsctl set Interface p1 bfd:enable=true bfd:min_tx=300 bfd:min_rx=300])
 # advance the clock, to stablize the states.
-for i in `seq 0 9`; do ovs-appctl time/warp 500; done
+for i in `seq 0 19`; do ovs-appctl time/warp 500; done
 BFD_CHECK([p0], [true], [false], [none], [up], [Control Detection Time Expired], [none], [up], [No Diagnostic])
 BFD_CHECK([p1], [true], [false], [none], [up], [No Diagnostic], [none], [up], [Control Detection Time Expired])
 BFD_CHECK_TX([p0], [300ms], [300ms], [300ms])
index 536fb32..cdc275e 100755 (executable)
@@ -109,7 +109,8 @@ sub output {
 
     # Compose packet.
     my $packet = '';
-    my $wildcards = 0;
+    my $wildcards = 1 << 5 | 1 << 6 | 1 << 7 | 32 << 8 | 32 << 14 | 1 << 21;
+
     $packet .= pack_ethaddr($flow{DL_DST});
     $packet .= pack_ethaddr($flow{DL_SRC});
     if ($flow{DL_VLAN} != 0xffff) {
@@ -139,6 +140,7 @@ sub output {
                           0,               # checksum
                           0x0a00020f,      # source
                           0xc0a80114);     # dest
+            $wildcards &= ~( 1 << 5 | 63 << 8 | 63 << 14 | 1 << 21);
             if ($attrs{IP_OPTIONS} eq 'yes') {
                 substr($ip, 0, 1) = pack('C', (4 << 4) | 8);
                 $ip .= pack('CCnnnCCCx',
@@ -151,6 +153,7 @@ sub output {
                             2,
                             3);
             }
+
             if ($attrs{IP_FRAGMENT} ne 'no') {
                 my (%frag_map) = ('first' => 0x2000, # more frags, ofs 0
                                   'middle' => 0x2111, # more frags, ofs 0x888
@@ -158,39 +161,43 @@ sub output {
                 substr($ip, 6, 2)
                   = pack('n', $frag_map{$attrs{IP_FRAGMENT}});
             }
-
-            if ($attrs{TP_PROTO} =~ '^TCP') {
-                my $tcp = pack('nnNNnnnn',
-                               $flow{TP_SRC},     # source port
-                               $flow{TP_DST},     # dest port
-                               87123455,          # seqno
-                               712378912,         # ackno
-                               (5 << 12) | 0x02 | 0x10, # hdrlen, SYN, ACK
-                               5823,                    # window size
-                               18923,                   # checksum
-                               12893); # urgent pointer
-                if ($attrs{TP_PROTO} eq 'TCP+options') {
-                    substr($tcp, 12, 2) = pack('n', (6 << 12) | 0x02 | 0x10);
-                    $tcp .= pack('CCn', 2, 4, 1975); # MSS option
+            if ($attrs{IP_FRAGMENT} eq 'no' || $attrs{IP_FRAGMENT} eq 'first') {
+                if ($attrs{TP_PROTO} =~ '^TCP') {
+                    my $tcp = pack('nnNNnnnn',
+                                   $flow{TP_SRC},     # source port
+                                   $flow{TP_DST},     # dest port
+                                   87123455,          # seqno
+                                   712378912,         # ackno
+                                   (5 << 12) | 0x02 | 0x10, # hdrlen, SYN, ACK
+                                   5823,                    # window size
+                                   18923,                   # checksum
+                                   12893); # urgent pointer
+                    if ($attrs{TP_PROTO} eq 'TCP+options') {
+                        substr($tcp, 12, 2) = pack('n', (6 << 12) | 0x02 | 0x10);
+                        $tcp .= pack('CCn', 2, 4, 1975); # MSS option
+                    }
+                    $tcp .= 'payload';
+                    $ip .= $tcp;
+                    $wildcards &= ~(1 << 6 | 1 << 7);
+                } elsif ($attrs{TP_PROTO} eq 'UDP') {
+                    my $len = 15;
+                    my $udp = pack('nnnn', $flow{TP_SRC}, $flow{TP_DST}, $len, 0);
+                    $udp .= chr($len) while length($udp) < $len;
+                    $ip .= $udp;
+                    $wildcards &= ~(1 << 6 | 1 << 7);
+                } elsif ($attrs{TP_PROTO} eq 'ICMP') {
+                    $ip .= pack('CCnnn',
+                                8,        # echo request
+                                0,        # code
+                                0,        # checksum
+                                736,      # identifier
+                                931);     # sequence number
+                    $wildcards &= ~(1 << 6 | 1 << 7);
+                } elsif ($attrs{TP_PROTO} eq 'other') {
+                    $ip .= 'other header';
+                } else {
+                    die;
                 }
-                $tcp .= 'payload';
-                $ip .= $tcp;
-            } elsif ($attrs{TP_PROTO} eq 'UDP') {
-                my $len = 15;
-                my $udp = pack('nnnn', $flow{TP_SRC}, $flow{TP_DST}, $len, 0);
-                $udp .= chr($len) while length($udp) < $len;
-                $ip .= $udp;
-            } elsif ($attrs{TP_PROTO} eq 'ICMP') {
-                $ip .= pack('CCnnn',
-                            8,        # echo request
-                            0,        # code
-                            0,        # checksum
-                            736,      # identifier
-                            931);     # sequence number
-            } elsif ($attrs{TP_PROTO} eq 'other') {
-                $ip .= 'other header';
-            } else {
-                die;
             }
             substr($ip, 2, 2) = pack('n', length($ip));
             $packet .= $ip;
index fdfe12e..26a77eb 100644 (file)
@@ -696,6 +696,7 @@ AT_BANNER([interface-reconfigure])
 
 AT_SETUP([non-VLAN, non-bond])
 AT_KEYWORDS([interface-reconfigure])
+AT_SKIP_IF([$non_ascii_cwd])
 ifr_setup
 
 AT_CHECK([ifr_run --force xenbr2 up], [0], [], [stderr])
@@ -771,6 +772,7 @@ AT_CLEANUP
 \f
 AT_SETUP([VLAN, non-bond])
 AT_KEYWORDS([interface-reconfigure])
+AT_SKIP_IF([$non_ascii_cwd])
 ifr_setup
 
 AT_CHECK([ifr_run --force xapi3 up], [0], [], [stderr])
@@ -844,6 +846,7 @@ AT_CLEANUP
 \f
 AT_SETUP([Bond, non-VLAN])
 AT_KEYWORDS([interface-reconfigure])
+AT_SKIP_IF([$non_ascii_cwd])
 ifr_setup
 
 # Pretend that bond0 exists, even though it would really be created by
@@ -932,6 +935,7 @@ AT_CLEANUP
 \f
 AT_SETUP([VLAN on bond])
 AT_KEYWORDS([interface-reconfigure])
+AT_SKIP_IF([$non_ascii_cwd])
 ifr_setup
 
 # Pretend that bond0 exists, even though it would really be created by
index f99cc3a..7a4bda5 100644 (file)
@@ -75,12 +75,14 @@ AT_CHECK([[ovs-ofctl parse-flow 'actions=learn(load:5->NXM_OF_IP_DST[])']],
   [1], [], [stderr])
 AT_CHECK([sed -e 's/.*|meta_flow|WARN|//' < stderr], [0],
   [[destination field ip_dst lacks correct prerequisites
+destination field ip_dst lacks correct prerequisites
 ovs-ofctl: actions are invalid with specified match (OFPBAC_MATCH_INCONSISTENT)
 ]], [[]])
 AT_CHECK([[ovs-ofctl parse-flow 'actions=learn(load:NXM_OF_IP_DST[]->NXM_NX_REG1[])']],
   [1], [], [stderr])
 AT_CHECK([sed -e 's/.*|meta_flow|WARN|//' < stderr], [0],
   [[source field ip_dst lacks correct prerequisites
+source field ip_dst lacks correct prerequisites
 ovs-ofctl: actions are invalid with specified match (OFPBAC_MATCH_INCONSISTENT)
 ]])
 AT_CLEANUP
index a978c13..6d1c6da 100644 (file)
@@ -135,7 +135,6 @@ dnl is about 100 bytes.  On Linux, we work around this by indirecting through
 dnl a directory fd using /proc/self/fd/<dirfd>.  We do not have a workaround
 dnl for other platforms, so we skip the test there.
 AT_SETUP([test unix socket, long pathname - C])
-AT_SKIP_IF([test ! -d /proc/self/fd])
 dnl Linux has a 108 byte limit; this is 150 bytes long.
 longname=012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
 mkdir $longname
@@ -155,7 +154,6 @@ dnl a directory fd using /proc/self/fd/<dirfd>.  We do not have a workaround
 dnl for other platforms, so we skip the test there.
 AT_SETUP([test unix socket, long pathname - Python])
 AT_SKIP_IF([test $HAVE_PYTHON = no])
-AT_SKIP_IF([test ! -d /proc/self/fd])
 dnl Linux has a 108 byte limit; this is 150 bytes long.
 longname=012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
 mkdir $longname
index 469e120..b505345 100644 (file)
@@ -151,6 +151,71 @@ AT_CHECK_UNQUOTED([test-odp parse-wc-keys < odp.txt], [0], [`cat odp.txt`
 ])
 AT_CLEANUP
 
+AT_SETUP([OVS datapath wildcarded key filtering.])
+dnl We could add a test for invalid forms, but that's less important.
+AT_DATA([odp-base.txt], [dnl
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x1234/0xfff0)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0800),ipv4(src=35.8.2.41/255.255.255.0,dst=172.16.0.20/255.255.255.0,proto=5/0xf0,tos=0x80/0xf0,ttl=128/0xf0,frag=no/0xf0)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0800),ipv4(src=35.8.2.41,dst=172.16.0.20,proto=6,tos=0,ttl=128,frag=no),tcp(src=80/0xff00,dst=8080/0xff)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0800),ipv4(src=35.8.2.41,dst=172.16.0.20,proto=17,tos=0,ttl=128,frag=no),udp(src=81/0xff00,dst=6632/0xff)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0800),ipv4(src=35.8.2.41,dst=172.16.0.20,proto=17,tos=0,ttl=128,frag=no),udp(src=81/0xff,dst=6632/0xff00)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0800),ipv4(src=35.8.2.41,dst=172.16.0.20,proto=1,tos=0,ttl=128,frag=no),icmp(type=1/0xf0,code=2/0xff)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1/::255,dst=::2/::255,label=0/0xf0,proto=10/0xf0,tclass=0x70/0xf0,hlimit=128/0xf0,frag=no/0xf0)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0,proto=6,tclass=0,hlimit=128,frag=no),tcp(src=80/0xff00,dst=8080/0xff)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0806),arp(sip=1.2.3.4/255.255.255.250,tip=5.6.7.8/255.255.255.250,op=1/0xf0,sha=00:0f:10:11:12:13/ff:ff:ff:ff:ff:00,tha=00:14:15:16:17:18/ff:ff:ff:ff:ff:00)
+])
+AT_DATA([odp-vlan-base.txt], [dnl
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x8100),vlan(vid=99,pcp=7),encap(eth_type(0x0800),ipv4(src=35.8.2.41,dst=172.16.0.20,proto=6,tos=0,ttl=128,frag=no),tcp(src=80/0xff00,dst=8080/0xff))
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x8100),vlan(vid=100,pcp=7),encap(eth_type(0x0800),ipv4(src=35.8.2.41,dst=172.16.0.20,proto=6,tos=0,ttl=128,frag=no),tcp(src=80/0xff00,dst=8080/0xff))
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x8100),vlan(vid=99,pcp=7),encap(eth_type(0x0800),ipv4(src=35.8.2.41,dst=172.16.0.20,proto=17,tos=0,ttl=128,frag=no),udp(src=81/0xff00,dst=6632/0xff))
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x8100),vlan(vid=100,pcp=7),encap(eth_type(0x0800),ipv4(src=35.8.2.41,dst=172.16.0.20,proto=17,tos=0,ttl=128,frag=no),udp(src=81/0xff00,dst=6632/0xff))
+])
+AT_DATA([odp-eth-type.txt], [dnl
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x1234/0xfff0)
+])
+AT_DATA([odp-vlan.txt], [dnl
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x8100),vlan(vid=99,pcp=7),encap(eth_type(0x0800),ipv4(src=35.8.2.41,dst=172.16.0.20,proto=6,tos=0,ttl=128,frag=no),tcp(src=80/0xff00,dst=8080/0xff))
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x8100),vlan(vid=99,pcp=7),encap(eth_type(0x0800),ipv4(src=35.8.2.41,dst=172.16.0.20,proto=17,tos=0,ttl=128,frag=no),udp(src=81/0xff00,dst=6632/0xff))
+])
+AT_DATA([odp-ipv4.txt], [dnl
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0800),ipv4(src=35.8.2.41/255.255.255.0,dst=172.16.0.20/255.255.255.0,proto=5/0xf0,tos=0x80/0xf0,ttl=128/0xf0,frag=no/0xf0)
+])
+AT_DATA([odp-icmp.txt], [dnl
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0800),ipv4(src=35.8.2.41/255.255.255.0,dst=172.16.0.20/255.255.255.0,proto=5/0xf0,tos=0x80/0xf0,ttl=128/0xf0,frag=no/0xf0)
+])
+AT_DATA([odp-arp.txt], [dnl
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0806),arp(sip=1.2.3.4/255.255.255.250,tip=5.6.7.8/255.255.255.250,op=1/0xf0,sha=00:0f:10:11:12:13/ff:ff:ff:ff:ff:00,tha=00:14:15:16:17:18/ff:ff:ff:ff:ff:00)
+])
+AT_DATA([odp-tcp.txt], [dnl
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0800),ipv4(src=35.8.2.41/255.255.255.0,dst=172.16.0.20/255.255.255.0,proto=5/0xf0,tos=0x80/0xf0,ttl=128/0xf0,frag=no/0xf0)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0800),ipv4(src=35.8.2.41,dst=172.16.0.20,proto=6,tos=0,ttl=128,frag=no),tcp(src=80/0xff00,dst=8080/0xff)
+])
+AT_DATA([odp-tcp6.txt], [dnl
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1/::255,dst=::2/::255,label=0/0xf0,proto=10/0xf0,tclass=0x70/0xf0,hlimit=128/0xf0,frag=no/0xf0)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0,proto=6,tclass=0,hlimit=128,frag=no),tcp(src=80/0xff00,dst=8080/0xff)
+])
+AT_CHECK_UNQUOTED([test-odp parse-filter filter='dl_type=0x1235' < odp-base.txt], [0], [`cat odp-eth-type.txt`
+])
+AT_CHECK_UNQUOTED([test-odp parse-filter filter='dl_vlan=99' < odp-vlan-base.txt], [0], [`cat odp-vlan.txt`
+])
+AT_CHECK_UNQUOTED([test-odp parse-filter filter='dl_vlan=99,ip' < odp-vlan-base.txt], [0], [`cat odp-vlan.txt`
+])
+AT_CHECK_UNQUOTED([test-odp parse-filter filter='ip,nw_src=35.8.2.199' < odp-base.txt], [0], [`cat odp-ipv4.txt`
+])
+AT_CHECK_UNQUOTED([test-odp parse-filter filter='ip,nw_dst=172.16.0.199' < odp-base.txt], [0], [`cat odp-ipv4.txt`
+])
+AT_CHECK_UNQUOTED([test-odp parse-filter filter='dl_type=0x0800,nw_src=35.8.2.199,nw_dst=172.16.0.199' < odp-base.txt], [0], [`cat odp-ipv4.txt`
+])
+AT_CHECK_UNQUOTED([test-odp parse-filter filter='icmp,nw_src=35.8.2.199' < odp-base.txt], [0], [`cat odp-icmp.txt`
+])
+AT_CHECK_UNQUOTED([test-odp parse-filter filter='arp,arp_spa=1.2.3.5' < odp-base.txt], [0], [`cat odp-arp.txt`
+])
+AT_CHECK_UNQUOTED([test-odp parse-filter filter='tcp,tp_src=90' < odp-base.txt], [0], [`cat odp-tcp.txt`
+])
+AT_CHECK_UNQUOTED([test-odp parse-filter filter='tcp6,tp_src=90' < odp-base.txt], [0], [`cat odp-tcp6.txt`
+])
+AT_CLEANUP
+
 AT_SETUP([OVS datapath actions parsing and formatting - valid forms])
 AT_DATA([actions.txt], [dnl
 1,2,3
index ebad040..0909ce1 100644 (file)
@@ -416,9 +416,29 @@ dnl and OVS reorders it to the canonical order)
 # 31: ff -> 00
 0001 0008 01 000000 0002 0018 00000000 fedcba9876543210 ffffffffffffffff
 
-dnl Write-Actions not supported yet.
-# bad OF1.1 instructions: OFPBIC_UNSUP_INST
-0003 0008 01 000000
+dnl empty Write-Actions non-zero padding
+# actions=write_actions(drop)
+#  0: 00 -> (none)
+#  1: 03 -> (none)
+#  2: 00 -> (none)
+#  3: 08 -> (none)
+#  4: 00 -> (none)
+#  5: 00 -> (none)
+#  6: 00 -> (none)
+#  7: 01 -> (none)
+0003 0008 00000001
+
+dnl Check that an empty Write-Actions instruction gets dropped.
+# actions=write_actions(drop)
+#  0: 00 -> (none)
+#  1: 03 -> (none)
+#  2: 00 -> (none)
+#  3: 08 -> (none)
+#  4: 00 -> (none)
+#  5: 00 -> (none)
+#  6: 00 -> (none)
+#  7: 00 -> (none)
+0003 0008 00000000
 
 dnl Clear-Actions too-long
 # bad OF1.1 instructions: OFPBIC_BAD_LEN
index 5316ce9..c569463 100644 (file)
@@ -34,6 +34,37 @@ AT_CHECK([tail -1 stdout], [0],
 OVS_VSWITCHD_STOP
 AT_CLEANUP
 
+AT_SETUP([ofproto-dpif - write actions])
+OVS_VSWITCHD_START
+ADD_OF_PORTS([br0], [1], [10], [11], [12], [13])
+AT_DATA([flows.txt], [dnl
+table=0 in_port=1,ip actions=output(10),write_actions(set_field:192.168.3.90->ip_src,output(12)),goto_table(1)
+table=1 ip actions=write_actions(output(13)),goto_table(2)
+table=2 ip actions=set_field:192.168.3.91->ip_src,output(11)
+])
+AT_CHECK([ovs-ofctl -O OpenFlow12 add-flows br0 flows.txt])
+AT_CHECK([ovs-appctl ofproto/trace br0 'in_port=1,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:07,dl_type=0x0800,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_proto=1,nw_tos=0,nw_ttl=128,icmp_type=8,icmp_code=0'], [0], [stdout])
+AT_CHECK([tail -1 stdout], [0],
+  [Datapath actions: 10,set(ipv4(src=192.168.3.91,dst=192.168.0.2,proto=1,tos=0,ttl=128,frag=no)),11,set(ipv4(src=192.168.3.90,dst=192.168.0.2,proto=1,tos=0,ttl=128,frag=no)),13
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif - clear actions])
+OVS_VSWITCHD_START
+ADD_OF_PORTS([br0], [1], [10], [11], [12])
+AT_DATA([flows.txt], [dnl
+table=0 in_port=1,ip actions=output(10),write_actions(set_field:192.168.3.90->ip_src,output(12)),goto_table(1)
+table=1 ip actions=set_field:192.168.3.91->ip_src,output(11),clear_actions
+])
+AT_CHECK([ovs-ofctl -O OpenFlow12 add-flows br0 flows.txt])
+AT_CHECK([ovs-appctl ofproto/trace br0 'in_port=1,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:07,dl_type=0x0800,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_proto=1,nw_tos=0,nw_ttl=128,icmp_type=8,icmp_code=0'], [0], [stdout])
+AT_CHECK([tail -1 stdout], [0],
+  [Datapath actions: 10,set(ipv4(src=192.168.3.91,dst=192.168.0.2,proto=1,tos=0,ttl=128,frag=no)),11
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
 AT_SETUP([ofproto-dpif - registers])
 OVS_VSWITCHD_START
 ADD_OF_PORTS([br0], [20], [21], [22], [33], [90])
@@ -137,7 +168,7 @@ AT_CHECK([ovs-ofctl monitor br0 65534 invalid_ttl --detach --no-chdir --pidfile
 AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(1),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=1,tos=0,ttl=2,frag=no)' -generate], [0], [stdout])
 OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
 AT_CHECK([cat ofctl_monitor.log], [0], [dnl
-NXT_PACKET_IN (xid=0x0): table_id=1 total_len=42 in_port=1 (via invalid_ttl) data_len=42 (unbuffered)
+NXT_PACKET_IN (xid=0x0): table_id=1 cookie=0x0 total_len=42 in_port=1 (via invalid_ttl) data_len=42 (unbuffered)
 icmp,metadata=0,in_port=0,vlan_tci=0x0000,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,nw_ecn=0,nw_ttl=1,icmp_type=0,icmp_code=0
 ])
 OVS_VSWITCHD_STOP
@@ -2904,6 +2935,76 @@ AT_CHECK([ovs-appctl bond/show | sed -n '/^.*may_enable:.*/p'], [0], [dnl
        may_enable: true
 ])
 
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif - ofproto-dpif-monitor 1])
+OVS_VSWITCHD_START([add-port br0 p0 -- set interface p0 type=gre options:remote_ip=1.2.3.4])
+
+# enable bfd on p0.
+AT_CHECK([ovs-vsctl set interface p0 bfd:enable=true])
+# check log.
+AT_CHECK([sed -n "s/^.*|ofproto_dpif_monitor(monitor)|INFO|\(.* created\)$/\1/p" ovs-vswitchd.log], [0], [dnl
+monitor thread created
+])
+# disable bfd on p0.
+AT_CHECK([ovs-vsctl set interface p0 bfd:enable=false])
+# check log.
+AT_CHECK([sed -n "s/^.*|ofproto_dpif_monitor(monitor)|INFO|\(.* terminated\)$/\1/p" ovs-vswitchd.log], [0], [dnl
+monitor thread terminated
+])
+AT_CHECK([cat ovs-vswitchd.log | sed -e '/^.*ofproto_dpif_monitor.*$/d' > ovs-vswitchd.log])
+
+# enable cfm on p0.
+AT_CHECK([ovs-vsctl set interface p0 cfm_mpid=10])
+# check log.
+AT_CHECK([sed -n "s/^.*|ofproto_dpif_monitor(monitor)|INFO|\(.* created\)$/\1/p" ovs-vswitchd.log], [0], [dnl
+monitor thread created
+])
+# disable cfm on p0.
+AT_CHECK([ovs-vsctl remove interface p0 cfm_mpid 10])
+# check log.
+AT_CHECK([sed -n "s/^.*|ofproto_dpif_monitor(monitor)|INFO|\(.* terminated\)$/\1/p" ovs-vswitchd.log], [0], [dnl
+monitor thread terminated
+])
+AT_CHECK([cat ovs-vswitchd.log | sed -e '/^.*ofproto_dpif_monitor.*$/d' > ovs-vswitchd.log])
+
+# enable both bfd and cfm on p0.
+AT_CHECK([ovs-vsctl set interface p0 bfd:enable=true cfm_mpid=10])
+# check log.
+AT_CHECK([sed -n "s/^.*|ofproto_dpif_monitor(monitor)|INFO|\(.* created\)$/\1/p" ovs-vswitchd.log], [0], [dnl
+monitor thread created
+])
+# disable bfd on p0.
+AT_CHECK([ovs-vsctl set interface p0 bfd:enable=false])
+# check log, there should not be the log of thread terminated.
+AT_CHECK([sed -n "s/^.*|ofproto_dpif_monitor(monitor)|INFO|\(.* terminated\)$/\1/p" ovs-vswitchd.log], [0], [dnl
+])
+# reenable bfd on p0.
+AT_CHECK([ovs-vsctl set interface p0 bfd:enable=true])
+# check log, should still be on log of thread created.
+AT_CHECK([sed -n "s/^.*|ofproto_dpif_monitor(monitor)|INFO|\(.* created\)$/\1/p" ovs-vswitchd.log], [0], [dnl
+monitor thread created
+])
+# disable bfd and cfm together.
+AT_CHECK([ovs-vsctl set interface p0 bfd:enable=false -- remove interface p0 cfm_mpid 10])
+# check log.
+AT_CHECK([sed -n "s/^.*|ofproto_dpif_monitor(monitor)|INFO|\(.* terminated\)$/\1/p" ovs-vswitchd.log], [0], [dnl
+monitor thread terminated
+])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+# this test helps avoid the deadlock between the main thread and monitor thread.
+AT_SETUP([ofproto-dpif - ofproto-dpif-monitor 2])
+OVS_VSWITCHD_START
+
+for i in `seq 1 199`
+do
+    AT_CHECK([ovs-vsctl add-port br0 p$i -- set interface p$i type=gre options:remote_ip=1.2.3.4 options:key=$i bfd:enable=true])
+done
+
 OVS_VSWITCHD_STOP
 AT_CLEANUP
 \f
index bd150cf..67705fa 100644 (file)
@@ -2,6 +2,7 @@ AT_BANNER([ovs-monitor-ipsec])
 
 AT_SETUP([ovs-monitor-ipsec])
 AT_SKIP_IF([test $HAVE_PYTHON = no])
+AT_SKIP_IF([$non_ascii_cwd])
 
 OVS_RUNDIR=`pwd`; export OVS_RUNDIR
 OVS_DBDIR=`pwd`; export OVS_DBDIR
index cbd6aec..131beaf 100644 (file)
@@ -139,6 +139,43 @@ OFPT_FLOW_MOD: ADD actions=sample(probability=12345,collector_set_id=23456,obs_d
 ]])
 AT_CLEANUP
 
+AT_SETUP([ovs-ofctl parse-flows (OpenFlow 1.1)])
+AT_DATA([flows.txt], [[
+# comment
+tcp,tp_src=123,out_port=5,actions=flood
+in_port=LOCAL dl_vlan=9 dl_src=00:0A:E4:25:6B:B0 actions=drop
+udp dl_vlan_pcp=7 idle_timeout=5 actions=strip_vlan output:0
+tcp,nw_src=192.168.0.3,tp_dst=80 actions=set_queue:37,output:1
+udp,nw_src=192.168.0.3,tp_dst=53 actions=mod_nw_ecn:2,output:1
+cookie=0x123456789abcdef hard_timeout=10 priority=60000 actions=controller
+actions=note:41.42.43,note:00.01.02.03.04.05.06.07,note
+ip,actions=mod_nw_ttl:1,set_field:10.4.3.77->ip_src
+sctp actions=drop
+sctp actions=drop
+in_port=0 actions=resubmit:0
+actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678)
+]])
+
+AT_CHECK([ovs-ofctl --protocols OpenFlow11 parse-flows flows.txt
+], [0], [stdout])
+AT_CHECK([[sed 's/ (xid=0x[0-9a-fA-F]*)//' stdout]], [0],
+[[usable protocols: any
+chosen protocol: OpenFlow11
+OFPT_FLOW_MOD (OF1.1): ADD table:255 tcp,tp_src=123 out_port:5 actions=FLOOD
+OFPT_FLOW_MOD (OF1.1): ADD table:255 in_port=LOCAL,dl_vlan=9,dl_src=00:0a:e4:25:6b:b0 actions=drop
+OFPT_FLOW_MOD (OF1.1): ADD table:255 udp,dl_vlan_pcp=7 idle:5 actions=strip_vlan,output:0
+OFPT_FLOW_MOD (OF1.1): ADD table:255 tcp,nw_src=192.168.0.3,tp_dst=80 actions=set_queue:37,output:1
+OFPT_FLOW_MOD (OF1.1): ADD table:255 udp,nw_src=192.168.0.3,tp_dst=53 actions=mod_nw_ecn:2,output:1
+OFPT_FLOW_MOD (OF1.1): ADD table:255 priority=60000 cookie:0x123456789abcdef hard:10 actions=CONTROLLER:65535
+OFPT_FLOW_MOD (OF1.1): ADD table:255 actions=note:41.42.43.00.00.00,note:00.01.02.03.04.05.06.07.00.00.00.00.00.00,note:00.00.00.00.00.00
+OFPT_FLOW_MOD (OF1.1): ADD table:255 ip actions=mod_nw_ttl:1,load:0xa04034d->NXM_OF_IP_SRC[]
+OFPT_FLOW_MOD (OF1.1): ADD table:255 sctp actions=drop
+OFPT_FLOW_MOD (OF1.1): ADD table:255 sctp actions=drop
+OFPT_FLOW_MOD (OF1.1): ADD table:255 in_port=0 actions=resubmit:0
+OFPT_FLOW_MOD (OF1.1): ADD table:255 actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678)
+]])
+AT_CLEANUP
+
 AT_SETUP([ovs-ofctl parse-flows (OpenFlow 1.2)])
 AT_DATA([flows.txt], [[
 # comment
@@ -176,6 +213,12 @@ OFPT_FLOW_MOD (OF1.2): ADD table:255 actions=sample(probability=12345,collector_
 ]])
 AT_CLEANUP
 
+AT_SETUP([ovs-ofctl action inconsistency (OpenFlow 1.1)])
+AT_CHECK([ovs-ofctl --protocols OpenFlow11 add-flow br0 'ip actions=mod_tp_dst:1234'
+], [1], [stdout], [ovs-ofctl: actions are invalid with specified match (OFPBAC_MATCH_INCONSISTENT)
+])
+AT_CLEANUP
+
 AT_SETUP([ovs-ofctl parse-flows (With Tunnel-Parameters)])
 AT_DATA([flows.txt], [[
 tun_id=0x1234000056780000/0xffff0000ffff0000,tun_src=1.1.1.1,tun_dst=2.2.2.2,tun_tos=0x3,tun_ttl=20,tun_flags=key|csum actions=drop
@@ -229,7 +272,7 @@ actions=output:1,bundle_load(eth_src,0,hrw,ofport,NXM_NX_REG0[16..31],slaves:1),
 actions=resubmit:1,resubmit(2),resubmit(,3),resubmit(2,3)
 send_flow_rem,actions=output:1,output:NXM_NX_REG0[],output:2,output:NXM_NX_REG1[16..31],output:3
 check_overlap,actions=output:1,exit,output:2
-actions=fin_timeout(idle_timeout=5,hard_timeout=15)
+tcp,actions=fin_timeout(idle_timeout=5,hard_timeout=15)
 actions=controller(max_len=123,reason=invalid_ttl,id=555)
 actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678)
 ]])
@@ -265,7 +308,7 @@ NXT_FLOW_MOD: ADD table:255 actions=output:1,bundle_load(eth_src,0,hrw,ofport,NX
 NXT_FLOW_MOD: ADD table:255 actions=resubmit:1,resubmit:2,resubmit(,3),resubmit(2,3)
 NXT_FLOW_MOD: ADD table:255 send_flow_rem actions=output:1,output:NXM_NX_REG0[],output:2,output:NXM_NX_REG1[16..31],output:3
 NXT_FLOW_MOD: ADD table:255 check_overlap actions=output:1,exit,output:2
-NXT_FLOW_MOD: ADD table:255 actions=fin_timeout(idle_timeout=5,hard_timeout=15)
+NXT_FLOW_MOD: ADD table:255 tcp actions=fin_timeout(idle_timeout=5,hard_timeout=15)
 NXT_FLOW_MOD: ADD table:255 actions=controller(reason=invalid_ttl,max_len=123,id=555)
 NXT_FLOW_MOD: ADD table:255 actions=sample(probability=12345,collector_set_id=23456,obs_domain_id=34567,obs_point_id=45678)
 ]])
index 4449f7a..851e4d8 100644 (file)
@@ -620,11 +620,11 @@ AT_SETUP([database commands -- positive checks])
 AT_KEYWORDS([ovs-vsctl])
 OVS_VSCTL_SETUP
 AT_CHECK(
-  [RUN_OVS_VSCTL_TOGETHER([--id=@br0 create b name=br0],
+  [RUN_OVS_VSCTL_TOGETHER([--id=@br0 create bridge name=br0],
                           [set o . bridges=@br0])],
   [0], [stdout], [], [OVS_VSCTL_CLEANUP])
 cp stdout out1
-AT_CHECK([RUN_OVS_VSCTL([list b], [get b br0 _uuid])],
+AT_CHECK([RUN_OVS_VSCTL([list bridge], [get bridge br0 _uuid])],
   [0], [stdout], [], [OVS_VSCTL_CLEANUP])
 cp stdout out2
 AT_CHECK([${PERL} $srcdir/uuidfilt.pl out1 out2], [0],
@@ -651,26 +651,26 @@ stp_enable          : false
 <0>
 ]], [ignore], [test ! -e pid || kill `cat pid`])
 AT_CHECK(
-  [RUN_OVS_VSCTL([--columns=fail_mode,name,datapath_type list b])],
+  [RUN_OVS_VSCTL([--columns=fail_mode,name,datapath_type list bridge])],
   [0],
   [[fail_mode           : []
 name                : "br0"
 datapath_type       : ""
 ]], [ignore], [test ! -e pid || kill `cat pid`])
 AT_CHECK(
-  [RUN_OVS_VSCTL([--columns=fail_mode,name,datapath_type find b])],
+  [RUN_OVS_VSCTL([--columns=fail_mode,name,datapath_type find bridge])],
   [0],
   [[fail_mode           : []
 name                : "br0"
 datapath_type       : ""
 ]], [ignore], [test ! -e pid || kill `cat pid`])
 AT_CHECK([
-  RUN_OVS_VSCTL_TOGETHER([--id=@br1 create b name=br1 datapath_type="foo"],
-                         [--id=@br2 create b name=br2 external-ids:bar=quux],
+  RUN_OVS_VSCTL_TOGETHER([--id=@br1 create bridge name=br1 datapath_type="foo"],
+                         [--id=@br2 create bridge name=br2 external-ids:bar=quux],
                          [add o . bridges @br1 @br2])],
   [0], [stdout], [], [OVS_VSCTL_CLEANUP])
 AT_CHECK(
-  [RUN_OVS_VSCTL([--columns=name find b datapath_type!=foo])], [0], [stdout],
+  [RUN_OVS_VSCTL([--columns=name find bridge datapath_type!=foo])], [0], [stdout],
   [ignore], [test ! -e pid || kill `cat pid`])
 AT_CHECK([sed -n '/./p' stdout | sort], [0],
   [[name                : "br0"
@@ -691,25 +691,25 @@ AT_CHECK([RUN_OVS_VSCTL([get bridge br0 other_config:hwaddr -- --if-exists get b
   [0], ["00:11:22:33:44:55"
 
 ], [], [OVS_VSCTL_CLEANUP])
-AT_CHECK([RUN_OVS_VSCTL([remove br br0 other_config hwaddr 'datapath_id=""' -- get br br0 other_config])],
+AT_CHECK([RUN_OVS_VSCTL([remove bridge br0 other_config hwaddr 'datapath_id=""' -- get bridge br0 other_config])],
   [0], [{datapath_id="0123456789ab"}
 ], [], [OVS_VSCTL_CLEANUP])
-AT_CHECK([RUN_OVS_VSCTL([remove br br0 other_config 'datapath_id="0123456789ab"' -- get br br0 other_config])],
+AT_CHECK([RUN_OVS_VSCTL([remove bridge br0 other_config 'datapath_id="0123456789ab"' -- get bridge br0 other_config])],
   [0], [{}
 ], [], [OVS_VSCTL_CLEANUP])
-AT_CHECK([RUN_OVS_VSCTL([clear br br0 external-ids -- get br br0 external_ids])],
+AT_CHECK([RUN_OVS_VSCTL([clear bridge br0 external-ids -- get bridge br0 external_ids])],
   [0], [{}
 ], [], [OVS_VSCTL_CLEANUP])
-AT_CHECK([RUN_OVS_VSCTL_TOGETHER([destroy b br0],
-                                 [destroy b br1],
-                                 [destroy b br2],
+AT_CHECK([RUN_OVS_VSCTL_TOGETHER([destroy bridge br0],
+                                 [destroy bridge br1],
+                                 [destroy bridge br2],
                                  [clear o . bridges])],
   [0], [stdout], [], [OVS_VSCTL_CLEANUP])
-AT_CHECK([RUN_OVS_VSCTL([list b])],
+AT_CHECK([RUN_OVS_VSCTL([list bridge])],
   [0], [], [], [OVS_VSCTL_CLEANUP])
-AT_CHECK([RUN_OVS_VSCTL([--if-exists get b x datapath_id])],
+AT_CHECK([RUN_OVS_VSCTL([--if-exists get bridge x datapath_id])],
   [0], [], [], [OVS_VSCTL_CLEANUP])
-AT_CHECK([RUN_OVS_VSCTL([--if-exists list b x])],
+AT_CHECK([RUN_OVS_VSCTL([--if-exists list bridge x])],
   [0], [], [], [OVS_VSCTL_CLEANUP])
 AT_CHECK([RUN_OVS_VSCTL([--if-exists set controller x connection_mode=standalone])],
   [0], [], [], [OVS_VSCTL_CLEANUP])
@@ -763,46 +763,46 @@ targets             : ["1.2.3.4:567"]
 AT_CHECK([RUN_OVS_VSCTL([list interx x])],
   [1], [], [ovs-vsctl: unknown table "interx"
 ], [OVS_VSCTL_CLEANUP])
-AT_CHECK([RUN_OVS_VSCTL([list b x])],
+AT_CHECK([RUN_OVS_VSCTL([list bridge x])],
   [1], [], [ovs-vsctl: no row "x" in table Bridge
 ], [OVS_VSCTL_CLEANUP])
-AT_CHECK([RUN_OVS_VSCTL([get b x datapath_id])],
+AT_CHECK([RUN_OVS_VSCTL([get bridge x datapath_id])],
   [1], [], [ovs-vsctl: no row "x" in table Bridge
 ], [OVS_VSCTL_CLEANUP])
-AT_CHECK([RUN_OVS_VSCTL([get b br0 d])],
+AT_CHECK([RUN_OVS_VSCTL([get bridge br0 d])],
   [1], [], [ovs-vsctl: Bridge contains more than one column whose name matches "d"
 ], [OVS_VSCTL_CLEANUP])
-AT_CHECK([RUN_OVS_VSCTL([get b br0 x])],
+AT_CHECK([RUN_OVS_VSCTL([get bridge br0 x])],
   [1], [], [ovs-vsctl: Bridge does not contain a column whose name matches "x"
 ], [OVS_VSCTL_CLEANUP])
-AT_CHECK([RUN_OVS_VSCTL([get b br0 :y=z])],
+AT_CHECK([RUN_OVS_VSCTL([get bridge br0 :y=z])],
   [1], [], [ovs-vsctl: :y=z: missing column name
 ], [OVS_VSCTL_CLEANUP])
-AT_CHECK([RUN_OVS_VSCTL([get b br0 datapath_id:y=z])],
+AT_CHECK([RUN_OVS_VSCTL([get bridge br0 datapath_id:y=z])],
   [1], [], [ovs-vsctl: datapath_id:y=z: trailing garbage "=z" in argument
 ], [OVS_VSCTL_CLEANUP])
-AT_CHECK([RUN_OVS_VSCTL([set b br0 'datapath_id:y>=z'])],
+AT_CHECK([RUN_OVS_VSCTL([set bridge br0 'datapath_id:y>=z'])],
   [1], [], [ovs-vsctl: datapath_id:y>=z: argument does not end in "=" followed by a value.
 ], [OVS_VSCTL_CLEANUP])
 AT_CHECK([RUN_OVS_VSCTL([set controller x connection_mode=standalone])],
   [1], [], [ovs-vsctl: no row "x" in table Controller
 ], [OVS_VSCTL_CLEANUP])
-AT_CHECK([RUN_OVS_VSCTL([wait-until b br0 datapath_id:y,z])],
+AT_CHECK([RUN_OVS_VSCTL([wait-until bridge br0 datapath_id:y,z])],
   [1], [], [ovs-vsctl: datapath_id:y,z: argument does not end in "=", "!=", "<", ">", "<=", ">=", "{=}", "{!=}", "{<}", "{>}", "{<=}", or "{>=}" followed by a value.
 ], [OVS_VSCTL_CLEANUP])
-AT_CHECK([RUN_OVS_VSCTL([get b br0 datapath_id::])],
+AT_CHECK([RUN_OVS_VSCTL([get bridge br0 datapath_id::])],
   [1], [], [ovs-vsctl: datapath_id::: trailing garbage ":" in argument
 ], [OVS_VSCTL_CLEANUP])
-AT_CHECK([RUN_OVS_VSCTL([get b br0 datapath_id:x])],
+AT_CHECK([RUN_OVS_VSCTL([get bridge br0 datapath_id:x])],
   [1], [], [ovs-vsctl: cannot specify key to get for non-map column datapath_id
 ], [OVS_VSCTL_CLEANUP])
-AT_CHECK([RUN_OVS_VSCTL([get b br0 external_ids:x])],
+AT_CHECK([RUN_OVS_VSCTL([get bridge br0 external_ids:x])],
   [1], [], [ovs-vsctl: no key "x" in Bridge record "br0" column external_ids
 ], [OVS_VSCTL_CLEANUP])
-AT_CHECK([RUN_OVS_VSCTL([set b br0 flood_vlans=-1])],
+AT_CHECK([RUN_OVS_VSCTL([set bridge br0 flood_vlans=-1])],
   [1], [], [ovs-vsctl: constraint violation: -1 is not in the valid range 0 to 4095 (inclusive)
 ], [OVS_VSCTL_CLEANUP])
-AT_CHECK([RUN_OVS_VSCTL([set b br0 flood_vlans=4096])],
+AT_CHECK([RUN_OVS_VSCTL([set bridge br0 flood_vlans=4096])],
   [1], [], [ovs-vsctl: constraint violation: 4096 is not in the valid range 0 to 4095 (inclusive)
 ], [OVS_VSCTL_CLEANUP])
 AT_CHECK([RUN_OVS_VSCTL([set c br1 'connection-mode=xyz'])],
@@ -811,7 +811,7 @@ AT_CHECK([RUN_OVS_VSCTL([set c br1 'connection-mode=xyz'])],
 AT_CHECK([RUN_OVS_VSCTL([set c br1 connection-mode:x=y])],
   [1], [], [ovs-vsctl: cannot specify key to set for non-map column connection_mode
 ], [OVS_VSCTL_CLEANUP])
-AT_CHECK([RUN_OVS_VSCTL([add b br1 datapath_id x y])],
+AT_CHECK([RUN_OVS_VSCTL([add bridge br1 datapath_id x y])],
   [1], [], [ovs-vsctl: "add" operation would put 2 values in column datapath_id of table Bridge but the maximum number is 1
 ], [OVS_VSCTL_CLEANUP])
 AT_CHECK([RUN_OVS_VSCTL([remove netflow `cat netflow-uuid` targets '"1.2.3.4:567"'])],
@@ -826,7 +826,7 @@ AT_CHECK([RUN_OVS_VSCTL([clear netflow x targets])],
 AT_CHECK([RUN_OVS_VSCTL([clear netflow `cat netflow-uuid` targets])],
   [1], [], [ovs-vsctl: "clear" operation cannot be applied to column targets of table NetFlow, which is not allowed to be empty
 ], [OVS_VSCTL_CLEANUP])
-AT_CHECK([RUN_OVS_VSCTL([destroy b br2])],
+AT_CHECK([RUN_OVS_VSCTL([destroy bridge br2])],
   [1], [], [ovs-vsctl: no row "br2" in table Bridge
 ], [OVS_VSCTL_CLEANUP])
 AT_CHECK([RUN_OVS_VSCTL([add in br1 name x])],
@@ -835,10 +835,10 @@ AT_CHECK([RUN_OVS_VSCTL([add in br1 name x])],
 AT_CHECK([RUN_OVS_VSCTL([set port br1 name br2])],
   [1], [], [ovs-vsctl: cannot modify read-only column name in table Port
 ], [OVS_VSCTL_CLEANUP])
-AT_CHECK([RUN_OVS_VSCTL([remove b br1 name br1])],
+AT_CHECK([RUN_OVS_VSCTL([remove bridge br1 name br1])],
   [1], [], [ovs-vsctl: cannot modify read-only column name in table Bridge
 ], [OVS_VSCTL_CLEANUP])
-AT_CHECK([RUN_OVS_VSCTL([clear b br1 name])],
+AT_CHECK([RUN_OVS_VSCTL([clear bridge br1 name])],
   [1], [], [ovs-vsctl: cannot modify read-only column name in table Bridge
 ], [OVS_VSCTL_CLEANUP])
 OVS_VSCTL_CLEANUP
@@ -1006,7 +1006,7 @@ OVS_VSCTL_SETUP
 # Start ovs-vsctls in background.
 (RUN_OVS_VSCTL([[wait-until o . bridges!=[] -- get bridge br10 other-config:abc]])) > stdout1 &
 (RUN_OVS_VSCTL([[wait-until bridge br1 -- get bridge br1 other-config:abc]])) > stdout2 &
-(RUN_OVS_VSCTL([[wait-until b br1 other-config={abc=def} -- get bridge br1 other-config]])) > stdout3 &
+(RUN_OVS_VSCTL([[wait-until bridge br1 other-config={abc=def} -- get bridge br1 other-config]])) > stdout3 &
 (RUN_OVS_VSCTL([[wait-until port bond0 'bond_updelay>50' -- get port bond0 bond-updelay]])) > stdout4 &
 
 # Give the ovs-vsctls a chance to read the database
@@ -1118,7 +1118,7 @@ dnl The bug is documented in ovs-vsctl.8.
 AT_SETUP([created row UUID is wrong in same execution])
 AT_KEYWORDS([ovs-vsctl])
 OVS_VSCTL_SETUP
-AT_CHECK([RUN_OVS_VSCTL([--id=@br0 create Bridge name=br0 -- add Open_vSwitch . bridges @br0 -- list b])],
+AT_CHECK([RUN_OVS_VSCTL([--id=@br0 create Bridge name=br0 -- add Open_vSwitch . bridges @br0 -- list bridge])],
   [0], [stdout], [], [OVS_VSCTL_CLEANUP])
 AT_CHECK([${PERL} $srcdir/uuidfilt.pl stdout], [0],
   [[<0>
index c87d9c4..444ab96 100644 (file)
@@ -412,7 +412,7 @@ AT_CHECK([test ! -e socket1])
 AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/list-remotes])
 AT_CLEANUP
 
-AT_SETUP([ovsdb-server/add-remote and remove-remote with --monitor])
+AT_SETUP([ovsdb-server/add-remote with --monitor])
 AT_KEYWORDS([ovsdb server positive])
 # Start ovsdb-server, initially with no remotes.
 OVS_RUNDIR=`pwd`; export OVS_RUNDIR
@@ -440,6 +440,25 @@ OVS_WAIT_WHILE([kill -0 `cat old.pid`])
 OVS_WAIT_UNTIL(
   [test -s ovsdb-server.pid && test `cat ovsdb-server.pid` != `cat old.pid`])
 OVS_WAIT_UNTIL([test -S socket1])
+AT_CLEANUP
+
+AT_SETUP([ovsdb-server/add-remote and remove-remote with --monitor])
+AT_KEYWORDS([ovsdb server positive])
+# Start ovsdb-server, initially with no remotes.
+OVS_RUNDIR=`pwd`; export OVS_RUNDIR
+OVS_LOGDIR=`pwd`; export OVS_LOGDIR
+ordinal_schema > schema
+AT_CHECK([ovsdb-tool create db schema], [0], [ignore], [ignore])
+ON_EXIT([kill `cat *.pid`])
+AT_CHECK([ovsdb-server -v -vvlog:off --monitor --detach --no-chdir --pidfile --log-file db])
+
+# Add a remote.
+AT_CHECK([test ! -e socket1])
+AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/add-remote punix:socket1])
+OVS_WAIT_UNTIL([test -S socket1])
+AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/list-remotes],
+  [0], [punix:socket1
+])
 
 # Remove the remote.
 AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/remove-remote punix:socket1])
@@ -505,7 +524,7 @@ AT_CHECK(
   [0], [stdout], [ignore], [test ! -e pid || kill `cat pid`])
 cat stdout >> output
 AT_CHECK_UNQUOTED(
-  [${PERL} $srcdir/uuidfilt.pl output], [0], 
+  [cat output], [0],
   [[[{"rows":[{"private_key":"$PKIDIR/testpki-privkey2.pem"}]}]
 ]], [ignore], [test ! -e pid || kill `cat pid`])
 OVSDB_SERVER_SHUTDOWN
index d12a22f..c73c25c 100755 (executable)
@@ -74,7 +74,7 @@ for port in p1 p2 p3 p4; do
                                options:pstream=punix:$OVS_RUNDIR/$port
 done
 run ovs-vsctl \
-    -- set-controller br0 tcp:127.0.0.1 \
+    -- set-controller br0 tcp:127.0.0.1:6653 \
     -- set controller br0 connection-mode=out-of-band max-backoff=1000
 
 # Run OFTest.
index 1bb2b0b..5e5ef52 100644 (file)
@@ -23,7 +23,6 @@
 #include "flow.h"
 #include "ofp-actions.h"
 #include "ofpbuf.h"
-#include "random.h"
 
 #include "util.h"
 
@@ -116,7 +115,6 @@ main(int argc, char *argv[])
     int old_active;
 
     set_program_name(argv[0]);
-    random_init();
 
     if (argc != 2) {
         ovs_fatal(0, "usage: %s bundle_action", program_name);
@@ -140,7 +138,7 @@ main(int argc, char *argv[])
     /* Generate flows. */
     flows = xmalloc(N_FLOWS * sizeof *flows);
     for (i = 0; i < N_FLOWS; i++) {
-        random_bytes(&flows[i], sizeof flows[i]);
+        flow_random_hash_fields(&flows[i]);
         flows[i].regs[0] = ofp_to_u16(OFPP_NONE);
     }
 
index 8308bf8..6528b07 100644 (file)
@@ -70,7 +70,7 @@ main(int argc OVS_UNUSED, char *argv[])
 
         in_port_.ofp_port = u16_to_ofp(1);
         flow_extract(packet, 0, 0, NULL, &in_port_, &flow);
-        match_init_exact(&match, &flow);
+        match_wc_init(&match, &flow);
         ofputil_match_to_ofp10_match(&match, &extracted_match);
 
         if (memcmp(&expected_match, &extracted_match, sizeof expected_match)) {
index 4ba3692..bf879c7 100644 (file)
@@ -26,7 +26,6 @@
 
 #include "flow.h"
 #include "ofp-actions.h"
-#include "random.h"
 #include "util.h"
 
 int
@@ -39,7 +38,6 @@ main(int argc, char *argv[])
     int n;
 
     set_program_name(argv[0]);
-    random_init();
 
     if (argc != 2) {
         ovs_fatal(0, "usage: %s multipath_action", program_name);
@@ -65,7 +63,7 @@ main(int argc, char *argv[])
             struct flow_wildcards wc;
             struct flow flow;
 
-            random_bytes(&flow, sizeof flow);
+            flow_random_hash_fields(&flow);
 
             mp.max_link = n - 1;
             multipath_execute(&mp, &flow, &wc);
index 183a3b3..471851b 100644 (file)
@@ -20,7 +20,9 @@
 
 #include "dynamic-string.h"
 #include "flow.h"
+#include "match.h"
 #include "odp-util.h"
+#include "ofp-parse.h"
 #include "ofpbuf.h"
 #include "util.h"
 #include "vlog.h"
@@ -135,15 +137,96 @@ parse_actions(void)
     return 0;
 }
 
+static int
+parse_filter(char *filter_parse)
+{
+    struct ds in;
+    struct flow flow_filter;
+    struct flow_wildcards wc_filter;
+    char *error, *filter = NULL;
+
+    vlog_set_levels_from_string_assert("odp_util:console:dbg");
+    if (filter_parse && !strncmp(filter_parse, "filter=", 7)) {
+        filter = strdup(filter_parse+7);
+        memset(&flow_filter, 0, sizeof(flow_filter));
+        memset(&wc_filter, 0, sizeof(wc_filter));
+
+        error = parse_ofp_exact_flow(&flow_filter, &wc_filter.masks, filter,
+                                     NULL);
+        if (error) {
+            ovs_fatal(0, "Failed to parse filter (%s)", error);
+        }
+    } else {
+        ovs_fatal(0, "No filter to parse.");
+    }
+
+    ds_init(&in);
+    while (!ds_get_test_line(&in, stdin)) {
+        struct ofpbuf odp_key;
+        struct ofpbuf odp_mask;
+        struct ds out;
+        int error;
+
+        /* Convert string to OVS DP key. */
+        ofpbuf_init(&odp_key, 0);
+        ofpbuf_init(&odp_mask, 0);
+        error = odp_flow_from_string(ds_cstr(&in), NULL,
+                                     &odp_key, &odp_mask);
+        if (error) {
+            printf("odp_flow_from_string: error\n");
+            goto next;
+        }
+
+        if (filter) {
+            struct flow flow;
+            struct flow_wildcards wc;
+            struct match match, match_filter;
+            struct minimatch minimatch;
+
+            odp_flow_key_to_flow(odp_key.data, odp_key.size, &flow);
+            odp_flow_key_to_mask(odp_mask.data, odp_mask.size, &wc.masks,
+                                 &flow);
+            match_init(&match, &flow, &wc);
+
+            match_init(&match_filter, &flow_filter, &wc);
+            match_init(&match_filter, &match_filter.flow, &wc_filter);
+            minimatch_init(&minimatch, &match_filter);
+
+            if (!minimatch_matches_flow(&minimatch, &match.flow)) {
+                minimatch_destroy(&minimatch);
+                goto next;
+            }
+            minimatch_destroy(&minimatch);
+        }
+        /* Convert odp_key to string. */
+        ds_init(&out);
+        odp_flow_format(odp_key.data, odp_key.size,
+                        odp_mask.data, odp_mask.size, NULL, &out, false);
+        puts(ds_cstr(&out));
+        ds_destroy(&out);
+
+    next:
+        ofpbuf_uninit(&odp_key);
+        ofpbuf_uninit(&odp_mask);
+    }
+    ds_destroy(&in);
+
+    free(filter);
+    return 0;
+}
+
 int
 main(int argc, char *argv[])
 {
+    set_program_name(argv[0]);
     if (argc == 2 &&!strcmp(argv[1], "parse-keys")) {
         return parse_keys(false);
     } else if (argc == 2 &&!strcmp(argv[1], "parse-wc-keys")) {
         return parse_keys(true);
     } else if (argc == 2 && !strcmp(argv[1], "parse-actions")) {
         return parse_actions();
+    } else if (argc == 3 && !strcmp(argv[1], "parse-filter")) {
+        return parse_filter(argv[2]);
     } else {
         ovs_fatal(0, "usage: %s parse-keys | parse-wc-keys | parse-actions", argv[0]);
     }
index 5a3f3c0..f55f6c3 100644 (file)
@@ -40,6 +40,7 @@ def main(argv):
     if error:
         sys.stderr.write("%s: bind failed (%s)" % (sockname1,
                                                    os.strerror(error)))
+        sys.exit(1)
     sock1.listen(1)
 
     # Connect to 'sockname2' (which should be the same file, perhaps under a
@@ -49,6 +50,7 @@ def main(argv):
     if error:
         sys.stderr.write("%s: connect failed (%s)" % (sockname2,
                                                       os.strerror(error)))
+        sys.exit(1)
 
 if __name__ == '__main__':
     main(sys.argv)
index 9e16f94..772a7eb 100644 (file)
@@ -107,3 +107,4 @@ m4_include([tests/ovs-xapi-sync.at])
 m4_include([tests/interface-reconfigure.at])
 m4_include([tests/stp.at])
 m4_include([tests/vlog.at])
+m4_include([tests/vtep-ctl.at])
diff --git a/tests/vtep-ctl.at b/tests/vtep-ctl.at
new file mode 100644 (file)
index 0000000..40ee16e
--- /dev/null
@@ -0,0 +1,889 @@
+dnl VTEP_OVSDB_INIT([$1])
+dnl
+dnl Creates an empty database named $1.
+m4_define([VTEP_OVSDB_INIT],
+  [OVS_RUNDIR=`pwd`; export OVS_RUNDIR
+   AT_CHECK(
+     [ovsdb-tool create $1 $abs_top_srcdir/vtep/vtep.ovsschema],
+     [0], [stdout], [ignore])
+   AT_CHECK(
+     [[ovsdb-tool transact $1 \
+        '["hardware_vtep",
+          {"op": "insert",
+           "table": "Global",
+           "row": {}}]']],
+     [0], [ignore], [ignore])])
+
+dnl VTEP_CTL_SETUP
+dnl
+dnl Creates an empty database in the current directory and then starts
+dnl an ovsdb-server on it for vtep-ctl to connect to.
+m4_define([VTEP_CTL_SETUP],
+  [VTEP_OVSDB_INIT([db])
+   AT_CHECK([ovsdb-server --detach --no-chdir --pidfile="`pwd`"/pid --remote=punix:socket --unixctl="`pwd`"/unixctl db >/dev/null 2>&1], [0], [ignore], [ignore])])
+
+dnl VTEP_CTL_CLEANUP
+dnl
+dnl Kills off the database server.
+m4_define([VTEP_CTL_CLEANUP], [OVSDB_SERVER_SHUTDOWN])
+
+dnl RUN_VTEP_CTL(COMMAND, ...)
+dnl
+dnl Executes each vtep-ctl COMMAND.
+m4_define([RUN_VTEP_CTL],
+  [m4_foreach([command], [$@], [vtep-ctl --timeout=5 -vreconnect:emer --db=unix:socket command
+])])
+m4_define([RUN_VTEP_CTL_ONELINE],
+  [m4_foreach([command], [$@], [vtep-ctl --timeout=5 -vreconnect:emer --db=unix:socket --oneline -- command
+])])
+
+dnl RUN_VTEP_CTL_TOGETHER(COMMAND, ...)
+dnl
+dnl Executes each vtep-ctl COMMAND in a single run of vtep-ctl.
+m4_define([RUN_VTEP_CTL_TOGETHER],
+  [vtep-ctl --timeout=5 -vreconnect:emer --db=unix:socket --oneline dnl
+m4_foreach([command], [$@], [ -- command])])
+
+dnl CHECK_PSWITCHES([PSWITCH], ...)
+dnl
+dnl Verifies that "vtep-ctl list-ps" prints the specified list of
+dnl physical switches, which must be in alphabetical order.
+m4_define([CHECK_PSWITCHES],
+  [dnl Check that the pswitches appear on list-ps, without --oneline.
+   AT_CHECK(
+     [RUN_VTEP_CTL([list-ps])],
+     [0],
+     [m4_foreach([psinfo], [$@], [m4_car(psinfo)
+])],
+     [],
+     [VTEP_CTL_CLEANUP])
+
+   dnl Check that the pswitches appear on list-ps, with --oneline.
+   AT_CHECK(
+     [RUN_VTEP_CTL_ONELINE([list-ps])],
+     [0],
+     [m4_join([\n], m4_foreach([psinfo], [$@], [m4_car(psinfo),]))
+],
+     [],
+     [VTEP_CTL_CLEANUP])
+
+   dnl Check that each pswitch exists according to ps-exists and that
+   dnl a pswitch that should not exist does not.
+   m4_foreach([psinfo], [$@],
+              [AT_CHECK([RUN_VTEP_CTL([ps-exists m4_car(psinfo)])], [0], [],
+                        [], [VTEP_CTL_CLEANUP])])
+   AT_CHECK([RUN_VTEP_CTL([ps-exists nonexistent])], [2], [], [],
+            [VTEP_CTL_CLEANUP])])
+
+dnl CHECK_PORTS(PSWITCH, PORT[, PORT...])
+dnl
+dnl Verifies that "vtep-ctl list-ports PSWITCH" prints the specified
+dnl list of ports, which must be in alphabetical order.
+m4_define([CHECK_PORTS],
+  [dnl Check ports without --oneline.
+   AT_CHECK(
+     [RUN_VTEP_CTL([list-ports $1])],
+     [0],
+     [m4_foreach([port], m4_cdr($@), [port
+])],
+     [],
+     [VTEP_CTL_CLEANUP])
+
+   dnl Check ports with --oneline.
+   AT_CHECK(
+     [RUN_VTEP_CTL_ONELINE([list-ports $1])],
+     [0],
+     [m4_join([\n], m4_shift($@))
+],
+     [],
+     [VTEP_CTL_CLEANUP])])
+
+
+dnl CHECK_LSWITCHES([LSWITCH], ...)
+dnl
+dnl Verifies that "vtep-ctl list-ls" prints the specified list of
+dnl logical switches, which must be in alphabetical order.
+m4_define([CHECK_LSWITCHES],
+  [dnl Check that the lswitches appear on list-ls, without --oneline.
+   AT_CHECK(
+     [RUN_VTEP_CTL([list-ls])],
+     [0],
+     [m4_foreach([lsinfo], [$@], [m4_car(lsinfo)
+])],
+     [],
+     [VTEP_CTL_CLEANUP])
+
+   dnl Check that the lswitches appear on list-ls, with --oneline.
+   AT_CHECK(
+     [RUN_VTEP_CTL_ONELINE([list-ls])],
+     [0],
+     [m4_join([\n], m4_foreach([lsinfo], [$@], [m4_car(lsinfo),]))
+],
+     [],
+     [VTEP_CTL_CLEANUP])
+
+   dnl Check that each lswitch exists according to ls-exists and that
+   dnl a pswitch that should not exist does not.
+   m4_foreach([lsinfo], [$@],
+              [AT_CHECK([RUN_VTEP_CTL([ls-exists m4_car(lsinfo)])], [0], [],
+                        [], [VTEP_CTL_CLEANUP])])
+   AT_CHECK([RUN_VTEP_CTL([ls-exists nonexistent])], [2], [], [],
+            [VTEP_CTL_CLEANUP])])
+
+dnl ----------------------------------------------------------------------
+AT_BANNER([vtep-ctl unit tests -- physical switch tests])
+
+AT_SETUP([add-ps a])
+AT_KEYWORDS([vtep-ctl])
+VTEP_CTL_SETUP
+AT_CHECK([RUN_VTEP_CTL([add-ps a])], [0], [], [], [VTEP_CTL_CLEANUP])
+CHECK_PSWITCHES([a])
+VTEP_CTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([add-ps a, add-ps a])
+AT_KEYWORDS([vtep-ctl])
+VTEP_CTL_SETUP
+AT_CHECK([RUN_VTEP_CTL([add-ps a])], [0], [], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL([add-ps a])], [1], [],
+  [vtep-ctl: cannot create physical switch a because it already exists
+], [VTEP_CTL_CLEANUP])
+VTEP_CTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([add-ps a, add-ps b])
+AT_KEYWORDS([vtep-ctl])
+VTEP_CTL_SETUP
+AT_CHECK([RUN_VTEP_CTL([add-ps a], [add-ps b])], [0], [], [],
+         [VTEP_CTL_CLEANUP])
+CHECK_PSWITCHES([a], [b])
+VTEP_CTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([add-ps a, add-ps b, del-ps a])
+AT_KEYWORDS([vtep-ctl])
+VTEP_CTL_SETUP
+AT_CHECK([RUN_VTEP_CTL([add-ps a], [add-ps b], [del-ps a])], [0], [], [],
+         [VTEP_CTL_CLEANUP])
+CHECK_PSWITCHES([b])
+VTEP_CTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([add-ps a, del-ps a, add-ps a])
+AT_KEYWORDS([vtep-ctl])
+VTEP_CTL_SETUP
+AT_CHECK([RUN_VTEP_CTL_TOGETHER(
+  [add-ps a],
+  [del-ps a],
+  [add-ps a])], [0], [
+
+
+], [], [VTEP_CTL_CLEANUP])
+CHECK_PSWITCHES([a])
+VTEP_CTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([add-ps a, add-port a a1, add-port a a2])
+AT_KEYWORDS([vtep-ctl])
+VTEP_CTL_SETUP
+AT_CHECK([RUN_VTEP_CTL(
+   [add-ps a],
+   [--if-exists del-ps b],
+   [add-port a a1],
+   [add-port a a2])], [0], [], [], [VTEP_CTL_CLEANUP])
+CHECK_PSWITCHES([a])
+CHECK_PORTS([a], [a1], [a2])
+VTEP_CTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([add-ps a, add-port a a1, add-port a a1])
+AT_KEYWORDS([vtep-ctl])
+VTEP_CTL_SETUP
+AT_CHECK([RUN_VTEP_CTL(
+   [add-ps a],
+   [add-port a a1])], [0], [], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL([add-port a a1])], [1], [],
+  [vtep-ctl: cannot create a port named a1 on a because a port with that name already exists
+], [VTEP_CTL_CLEANUP])
+VTEP_CTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([add-ps a b, add-port a a1, add-port b b1, del-ps a])
+AT_KEYWORDS([vtep-ctl])
+VTEP_CTL_SETUP
+AT_CHECK([RUN_VTEP_CTL_TOGETHER(
+   [add-ps a],
+   [add-ps b],
+   [add-port a a1],
+   [add-port b b1],
+   [--if-exists del-port b b2],
+   [del-ps a])], [0], [
+
+
+
+
+
+], [], [VTEP_CTL_CLEANUP])
+CHECK_PSWITCHES([b])
+CHECK_PORTS([b], [b1])
+VTEP_CTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([add-ps a b, add-port a a1, add-port b b1, del-port a a1])
+AT_KEYWORDS([vtep-ctl])
+VTEP_CTL_SETUP
+AT_CHECK([RUN_VTEP_CTL(
+  [add-ps a],
+  [add-ps b],
+  [add-port a a1],
+  [--may-exist add-port b b1],
+  [del-port a a1])], [0], [], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL([--may-exist add-port b b1])], [0], [], [],
+  [VTEP_CTL_CLEANUP])
+CHECK_PSWITCHES([a], [b])
+CHECK_PORTS([a])
+CHECK_PORTS([b], [b1])
+VTEP_CTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([add-ps a b, add-port a p1, add-port b p1, del-port a p1])
+AT_KEYWORDS([vtep-ctl])
+VTEP_CTL_SETUP
+AT_CHECK([RUN_VTEP_CTL(
+  [add-ps a],
+  [add-ps b],
+  [add-port a p1],
+  [add-port b p1])], [0], [], [], [VTEP_CTL_CLEANUP])
+CHECK_PSWITCHES([a], [b])
+CHECK_PORTS([a], [p1])
+CHECK_PORTS([b], [p1])
+AT_CHECK([RUN_VTEP_CTL([del-port a p1])], [0], [], [],
+  [VTEP_CTL_CLEANUP])
+CHECK_PSWITCHES([a], [b])
+CHECK_PORTS([a])
+CHECK_PORTS([b], [p1])
+VTEP_CTL_CLEANUP
+AT_CLEANUP
+
+
+dnl ----------------------------------------------------------------------
+AT_BANNER([vtep-ctl unit tests -- logical switch tests])
+
+AT_SETUP([add-ls a])
+AT_KEYWORDS([vtep-ctl])
+VTEP_CTL_SETUP
+AT_CHECK([RUN_VTEP_CTL([add-ls a])], [0], [], [], [VTEP_CTL_CLEANUP])
+CHECK_LSWITCHES([a])
+VTEP_CTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([add-ls a, add-ls a])
+AT_KEYWORDS([vtep-ctl])
+VTEP_CTL_SETUP
+AT_CHECK([RUN_VTEP_CTL([add-ls a])], [0], [], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL([add-ls a])], [1], [],
+  [vtep-ctl: cannot create logical switch a because it already exists
+], [VTEP_CTL_CLEANUP])
+VTEP_CTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([add-ls a, add-ls b])
+AT_KEYWORDS([vtep-ctl])
+VTEP_CTL_SETUP
+AT_CHECK([RUN_VTEP_CTL([add-ls a], [add-ls b])], [0], [], [],
+         [VTEP_CTL_CLEANUP])
+CHECK_LSWITCHES([a], [b])
+VTEP_CTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([add-ls a, add-ls b, del-ls a])
+AT_KEYWORDS([vtep-ctl])
+VTEP_CTL_SETUP
+AT_CHECK([RUN_VTEP_CTL([add-ls a], [add-ls b], [del-ls a])], [0], [], [],
+         [VTEP_CTL_CLEANUP])
+CHECK_LSWITCHES([b])
+VTEP_CTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([add-ls a, del-ls a, add-ls a])
+AT_KEYWORDS([vtep-ctl])
+VTEP_CTL_SETUP
+AT_CHECK([RUN_VTEP_CTL_TOGETHER(
+  [add-ls a],
+  [del-ls a],
+  [add-ls a])], [0], [
+
+
+], [], [VTEP_CTL_CLEANUP])
+CHECK_LSWITCHES([a])
+VTEP_CTL_CLEANUP
+AT_CLEANUP
+
+
+dnl ----------------------------------------------------------------------
+AT_BANNER([vtep-ctl unit tests -- logical binding tests])
+
+AT_SETUP([bind-ls ps1 pp1 300 ls1])
+AT_KEYWORDS([vtep-ctl])
+VTEP_CTL_SETUP
+AT_CHECK([RUN_VTEP_CTL(
+   [add-ps ps1],
+   [add-port ps1 pp1],
+   [add-ls ls1])], [0], [], [], [VTEP_CTL_CLEANUP])
+CHECK_PSWITCHES([ps1])
+CHECK_PORTS([ps1], [pp1])
+CHECK_LSWITCHES([ls1])
+AT_CHECK([RUN_VTEP_CTL(
+   [bind-ls ps1 pp1 300 ls1])], [0], [], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL([list-bindings ps1 pp1])], [0],
+   [0300 ls1
+], [], [VTEP_CTL_CLEANUP])
+VTEP_CTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([bind-ls ps1 pp1 300 ls1, bind-ls ps1 pp1 400 ls2])
+AT_KEYWORDS([vtep-ctl])
+VTEP_CTL_SETUP
+AT_CHECK([RUN_VTEP_CTL(
+   [add-ps ps1],
+   [add-port ps1 pp1],
+   [add-ls ls1],
+   [add-ls ls2])], [0], [], [], [VTEP_CTL_CLEANUP])
+CHECK_PSWITCHES([ps1])
+CHECK_PORTS([ps1], [pp1])
+CHECK_LSWITCHES([ls1], [ls2])
+AT_CHECK([RUN_VTEP_CTL(
+   [bind-ls ps1 pp1 300 ls1])], [0], [], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL(
+   [bind-ls ps1 pp1 400 ls2])], [0], [], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL([list-bindings ps1 pp1])], [0],
+   [0300 ls1
+0400 ls2
+], [], [VTEP_CTL_CLEANUP])
+VTEP_CTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([bind-ls ps1 pp1 300, bind-ls ps2 pp2 300 ls2])
+AT_KEYWORDS([vtep-ctl])
+VTEP_CTL_SETUP
+AT_CHECK([RUN_VTEP_CTL(
+   [add-ps ps1],
+   [add-ps ps2],
+   [add-port ps1 pp1],
+   [add-port ps2 pp2],
+   [add-ls ls1],
+   [add-ls ls2])], [0], [], [], [VTEP_CTL_CLEANUP])
+CHECK_PSWITCHES([ps1], [ps2])
+CHECK_PORTS([ps1], [pp1])
+CHECK_PORTS([ps2], [pp2])
+CHECK_LSWITCHES([ls1], [ls2])
+AT_CHECK([RUN_VTEP_CTL(
+   [bind-ls ps1 pp1 300 ls1])], [0], [], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL(
+   [bind-ls ps2 pp2 300 ls2])], [0], [], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL([list-bindings ps1 pp1])], [0],
+   [0300 ls1
+], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL([list-bindings ps2 pp2])], [0],
+   [0300 ls2
+], [], [VTEP_CTL_CLEANUP])
+VTEP_CTL_CLEANUP
+AT_CLEANUP
+
+
+dnl ----------------------------------------------------------------------
+AT_BANNER([vtep-ctl unit tests -- MAC binding tests])
+
+AT_SETUP([add-ucast-local ls1])
+AT_KEYWORDS([vtep-ctl])
+VTEP_CTL_SETUP
+AT_CHECK([RUN_VTEP_CTL(
+   [add-ls ls1])], [0], [], [], [VTEP_CTL_CLEANUP])
+CHECK_LSWITCHES([ls1])
+AT_CHECK([RUN_VTEP_CTL(
+   [add-ucast-local ls1 00:11:22:33:44:55 10.0.0.10],
+   [add-ucast-local ls1 00:11:22:33:44:66 vxlan_over_ipv4 10.0.0.11])
+], [0], [], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL([list-local-macs ls1])], [0],
+   [ucast-mac-local
+  00:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.10
+  00:11:22:33:44:66 -> vxlan_over_ipv4/10.0.0.11
+
+mcast-mac-local
+
+], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL([list-remote-macs ls1])], [0],
+   [ucast-mac-remote
+
+mcast-mac-remote
+
+], [], [VTEP_CTL_CLEANUP])
+VTEP_CTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([add-ucast-local ls1, overwrite])
+AT_KEYWORDS([vtep-ctl])
+VTEP_CTL_SETUP
+AT_CHECK([RUN_VTEP_CTL(
+   [add-ls ls1])], [0], [], [], [VTEP_CTL_CLEANUP])
+CHECK_LSWITCHES([ls1])
+AT_CHECK([RUN_VTEP_CTL(
+   [add-ucast-local ls1 00:11:22:33:44:55 10.0.0.10],
+   [add-ucast-local ls1 00:11:22:33:44:55 10.0.0.11])
+], [0], [], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL([list-local-macs ls1])], [0],
+   [ucast-mac-local
+  00:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.11
+
+mcast-mac-local
+
+], [], [VTEP_CTL_CLEANUP])
+VTEP_CTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([add-ucast-local ls1, del-ucast-local ls1])
+AT_KEYWORDS([vtep-ctl])
+VTEP_CTL_SETUP
+AT_CHECK([RUN_VTEP_CTL(
+   [add-ls ls1])], [0], [], [], [VTEP_CTL_CLEANUP])
+CHECK_LSWITCHES([ls1])
+AT_CHECK([RUN_VTEP_CTL(
+   [add-ucast-local ls1 00:11:22:33:44:55 10.0.0.10],
+   [add-ucast-local ls1 00:11:22:33:44:66 vxlan_over_ipv4 10.0.0.11])
+], [0], [], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL([list-local-macs ls1])], [0],
+   [ucast-mac-local
+  00:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.10
+  00:11:22:33:44:66 -> vxlan_over_ipv4/10.0.0.11
+
+mcast-mac-local
+
+], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL(
+   [del-ucast-local ls1 00:11:22:33:44:55])
+], [0], [], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL([list-local-macs ls1])], [0],
+   [ucast-mac-local
+  00:11:22:33:44:66 -> vxlan_over_ipv4/10.0.0.11
+
+mcast-mac-local
+
+], [], [VTEP_CTL_CLEANUP])
+VTEP_CTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([add-ucast-remote ls1])
+AT_KEYWORDS([vtep-ctl])
+VTEP_CTL_SETUP
+AT_CHECK([RUN_VTEP_CTL(
+   [add-ls ls1])], [0], [], [], [VTEP_CTL_CLEANUP])
+CHECK_LSWITCHES([ls1])
+AT_CHECK([RUN_VTEP_CTL(
+   [add-ucast-remote ls1 00:11:22:33:44:55 10.0.0.10],
+   [add-ucast-remote ls1 00:11:22:33:44:66 vxlan_over_ipv4 10.0.0.11])
+], [0], [], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL([list-remote-macs ls1])], [0],
+   [ucast-mac-remote
+  00:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.10
+  00:11:22:33:44:66 -> vxlan_over_ipv4/10.0.0.11
+
+mcast-mac-remote
+
+], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL([list-local-macs ls1])], [0],
+   [ucast-mac-local
+
+mcast-mac-local
+
+], [], [VTEP_CTL_CLEANUP])
+VTEP_CTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([add-ucast-remote ls1, overwrite])
+AT_KEYWORDS([vtep-ctl])
+VTEP_CTL_SETUP
+AT_CHECK([RUN_VTEP_CTL(
+   [add-ls ls1])], [0], [], [], [VTEP_CTL_CLEANUP])
+CHECK_LSWITCHES([ls1])
+AT_CHECK([RUN_VTEP_CTL(
+   [add-ucast-remote ls1 00:11:22:33:44:55 10.0.0.10],
+   [add-ucast-remote ls1 00:11:22:33:44:55 10.0.0.11])
+], [0], [], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL([list-remote-macs ls1])], [0],
+   [ucast-mac-remote
+  00:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.11
+
+mcast-mac-remote
+
+], [], [VTEP_CTL_CLEANUP])
+VTEP_CTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([add-ucast-remote ls1, del-ucast-remote ls1])
+AT_KEYWORDS([vtep-ctl])
+VTEP_CTL_SETUP
+AT_CHECK([RUN_VTEP_CTL(
+   [add-ls ls1])], [0], [], [], [VTEP_CTL_CLEANUP])
+CHECK_LSWITCHES([ls1])
+AT_CHECK([RUN_VTEP_CTL(
+   [add-ucast-remote ls1 00:11:22:33:44:55 10.0.0.10],
+   [add-ucast-remote ls1 00:11:22:33:44:66 vxlan_over_ipv4 10.0.0.11])
+], [0], [], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL([list-remote-macs ls1])], [0],
+   [ucast-mac-remote
+  00:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.10
+  00:11:22:33:44:66 -> vxlan_over_ipv4/10.0.0.11
+
+mcast-mac-remote
+
+], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL(
+   [del-ucast-remote ls1 00:11:22:33:44:55])
+], [0], [], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL([list-remote-macs ls1])], [0],
+   [ucast-mac-remote
+  00:11:22:33:44:66 -> vxlan_over_ipv4/10.0.0.11
+
+mcast-mac-remote
+
+], [], [VTEP_CTL_CLEANUP])
+VTEP_CTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([add-ucast-local ls1, add-ucast-remote ls1])
+AT_KEYWORDS([vtep-ctl])
+VTEP_CTL_SETUP
+AT_CHECK([RUN_VTEP_CTL(
+   [add-ls ls1])], [0], [], [], [VTEP_CTL_CLEANUP])
+CHECK_LSWITCHES([ls1])
+AT_CHECK([RUN_VTEP_CTL(
+   [add-ucast-local ls1 00:11:22:33:44:55 10.0.0.10],
+   [add-ucast-local ls1 00:11:22:33:44:66 10.0.0.11],
+   [add-ucast-remote ls1 02:11:22:33:44:55 10.0.0.10],
+   [add-ucast-remote ls1 02:11:22:33:44:66 vxlan_over_ipv4 10.0.0.11])
+], [0], [], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL([list-local-macs ls1])], [0],
+   [ucast-mac-local
+  00:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.10
+  00:11:22:33:44:66 -> vxlan_over_ipv4/10.0.0.11
+
+mcast-mac-local
+
+], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL([list-remote-macs ls1])], [0],
+   [ucast-mac-remote
+  02:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.10
+  02:11:22:33:44:66 -> vxlan_over_ipv4/10.0.0.11
+
+mcast-mac-remote
+
+], [], [VTEP_CTL_CLEANUP])
+VTEP_CTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([add-mcast-local ls1])
+AT_KEYWORDS([vtep-ctl])
+VTEP_CTL_SETUP
+AT_CHECK([RUN_VTEP_CTL(
+   [add-ls ls1])], [0], [], [], [VTEP_CTL_CLEANUP])
+CHECK_LSWITCHES([ls1])
+AT_CHECK([RUN_VTEP_CTL(
+   [add-mcast-local ls1 01:11:22:33:44:55 10.0.0.10],
+   [add-mcast-local ls1 01:11:22:33:44:66 vxlan_over_ipv4 10.0.0.11],
+   [add-mcast-local ls1 01:11:22:33:44:55 10.0.0.12])
+], [0], [], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL([list-local-macs ls1])], [0],
+   [ucast-mac-local
+
+mcast-mac-local
+  01:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.10
+  01:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.12
+  01:11:22:33:44:66 -> vxlan_over_ipv4/10.0.0.11
+
+], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL([list-remote-macs ls1])], [0],
+   [ucast-mac-remote
+
+mcast-mac-remote
+
+], [], [VTEP_CTL_CLEANUP])
+VTEP_CTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([add-mcast-local ls1, del-mcast-local ls1])
+AT_KEYWORDS([vtep-ctl])
+VTEP_CTL_SETUP
+AT_CHECK([RUN_VTEP_CTL(
+   [add-ls ls1])], [0], [], [], [VTEP_CTL_CLEANUP])
+CHECK_LSWITCHES([ls1])
+AT_CHECK([RUN_VTEP_CTL(
+   [add-mcast-local ls1 01:11:22:33:44:55 10.0.0.10],
+   [add-mcast-local ls1 01:11:22:33:44:66 vxlan_over_ipv4 10.0.0.11],
+   [add-mcast-local ls1 01:11:22:33:44:55 10.0.0.12],
+   [add-mcast-local ls1 01:11:22:33:44:55 10.0.0.13])
+], [0], [], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL([list-local-macs ls1])], [0],
+   [ucast-mac-local
+
+mcast-mac-local
+  01:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.10
+  01:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.12
+  01:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.13
+  01:11:22:33:44:66 -> vxlan_over_ipv4/10.0.0.11
+
+], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL(
+   [del-mcast-local ls1 01:11:22:33:44:55 10.0.0.12])
+], [0], [], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL([list-local-macs ls1])], [0],
+   [ucast-mac-local
+
+mcast-mac-local
+  01:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.10
+  01:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.13
+  01:11:22:33:44:66 -> vxlan_over_ipv4/10.0.0.11
+
+], [], [VTEP_CTL_CLEANUP])
+VTEP_CTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([add-mcast-remote ls1])
+AT_KEYWORDS([vtep-ctl])
+VTEP_CTL_SETUP
+AT_CHECK([RUN_VTEP_CTL(
+   [add-ls ls1])], [0], [], [], [VTEP_CTL_CLEANUP])
+CHECK_LSWITCHES([ls1])
+AT_CHECK([RUN_VTEP_CTL(
+   [add-mcast-remote ls1 01:11:22:33:44:55 10.0.0.10],
+   [add-mcast-remote ls1 01:11:22:33:44:66 vxlan_over_ipv4 10.0.0.11],
+   [add-mcast-remote ls1 01:11:22:33:44:55 10.0.0.12])
+], [0], [], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL([list-remote-macs ls1])], [0],
+   [ucast-mac-remote
+
+mcast-mac-remote
+  01:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.10
+  01:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.12
+  01:11:22:33:44:66 -> vxlan_over_ipv4/10.0.0.11
+
+], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL([list-local-macs ls1])], [0],
+   [ucast-mac-local
+
+mcast-mac-local
+
+], [], [VTEP_CTL_CLEANUP])
+VTEP_CTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([add-mcast-remote ls1, del-mcast-remote ls1])
+AT_KEYWORDS([vtep-ctl])
+VTEP_CTL_SETUP
+AT_CHECK([RUN_VTEP_CTL(
+   [add-ls ls1])], [0], [], [], [VTEP_CTL_CLEANUP])
+CHECK_LSWITCHES([ls1])
+AT_CHECK([RUN_VTEP_CTL(
+   [add-mcast-remote ls1 01:11:22:33:44:55 10.0.0.10],
+   [add-mcast-remote ls1 01:11:22:33:44:66 vxlan_over_ipv4 10.0.0.11],
+   [add-mcast-remote ls1 01:11:22:33:44:55 10.0.0.12],
+   [add-mcast-remote ls1 01:11:22:33:44:55 10.0.0.13])
+], [0], [], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL([list-remote-macs ls1])], [0],
+   [ucast-mac-remote
+
+mcast-mac-remote
+  01:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.10
+  01:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.12
+  01:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.13
+  01:11:22:33:44:66 -> vxlan_over_ipv4/10.0.0.11
+
+], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL(
+   [del-mcast-remote ls1 01:11:22:33:44:55 10.0.0.12])
+], [0], [], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL([list-remote-macs ls1])], [0],
+   [ucast-mac-remote
+
+mcast-mac-remote
+  01:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.10
+  01:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.13
+  01:11:22:33:44:66 -> vxlan_over_ipv4/10.0.0.11
+
+], [], [VTEP_CTL_CLEANUP])
+VTEP_CTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([add-mcast-local ls1, add-mcast-remote ls1])
+AT_KEYWORDS([vtep-ctl])
+VTEP_CTL_SETUP
+AT_CHECK([RUN_VTEP_CTL(
+   [add-ls ls1])], [0], [], [], [VTEP_CTL_CLEANUP])
+CHECK_LSWITCHES([ls1])
+AT_CHECK([RUN_VTEP_CTL(
+   [add-mcast-local ls1 01:11:22:33:44:55 10.0.0.10],
+   [add-mcast-local ls1 01:11:22:33:44:66 10.0.0.11],
+   [add-mcast-local ls1 01:11:22:33:44:55 10.0.0.12],
+   [add-mcast-remote ls1 03:11:22:33:44:55 10.0.0.10],
+   [add-mcast-remote ls1 03:11:22:33:44:66 vxlan_over_ipv4 10.0.0.11],
+   [add-mcast-remote ls1 03:11:22:33:44:55 10.0.0.12])
+], [0], [], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL([list-local-macs ls1])], [0],
+   [ucast-mac-local
+
+mcast-mac-local
+  01:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.10
+  01:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.12
+  01:11:22:33:44:66 -> vxlan_over_ipv4/10.0.0.11
+
+], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL([list-remote-macs ls1])], [0],
+   [ucast-mac-remote
+
+mcast-mac-remote
+  03:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.10
+  03:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.12
+  03:11:22:33:44:66 -> vxlan_over_ipv4/10.0.0.11
+
+], [], [VTEP_CTL_CLEANUP])
+VTEP_CTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([add local and remote macs, clear-local-macs])
+AT_KEYWORDS([vtep-ctl])
+VTEP_CTL_SETUP
+AT_CHECK([RUN_VTEP_CTL(
+   [add-ls ls1])], [0], [], [], [VTEP_CTL_CLEANUP])
+CHECK_LSWITCHES([ls1])
+AT_CHECK([RUN_VTEP_CTL(
+   [add-ucast-local ls1 00:11:22:33:44:55 10.0.0.10],
+   [add-mcast-local ls1 01:11:22:33:44:55 10.0.0.10],
+   [add-mcast-local ls1 01:11:22:33:44:66 vxlan_over_ipv4 10.0.0.11],
+   [add-mcast-local ls1 01:11:22:33:44:55 10.0.0.12],
+   [add-ucast-remote ls1 00:11:22:33:44:55 10.0.0.10],
+   [add-mcast-remote ls1 01:11:22:33:44:55 10.0.0.10],
+   [add-mcast-remote ls1 01:11:22:33:44:66 vxlan_over_ipv4 10.0.0.11],
+   [add-mcast-remote ls1 01:11:22:33:44:55 10.0.0.12])
+], [0], [], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL([list-local-macs ls1])], [0],
+   [ucast-mac-local
+  00:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.10
+
+mcast-mac-local
+  01:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.10
+  01:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.12
+  01:11:22:33:44:66 -> vxlan_over_ipv4/10.0.0.11
+
+], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL([list-remote-macs ls1])], [0],
+   [ucast-mac-remote
+  00:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.10
+
+mcast-mac-remote
+  01:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.10
+  01:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.12
+  01:11:22:33:44:66 -> vxlan_over_ipv4/10.0.0.11
+
+], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL(
+   [clear-local-macs ls1])], [0], [], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL([list-local-macs ls1])], [0],
+   [ucast-mac-local
+
+mcast-mac-local
+
+], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL([list-remote-macs ls1])], [0],
+   [ucast-mac-remote
+  00:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.10
+
+mcast-mac-remote
+  01:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.10
+  01:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.12
+  01:11:22:33:44:66 -> vxlan_over_ipv4/10.0.0.11
+
+], [], [VTEP_CTL_CLEANUP])
+VTEP_CTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([add local and remote macs, clear-remote-macs])
+AT_KEYWORDS([vtep-ctl])
+VTEP_CTL_SETUP
+AT_CHECK([RUN_VTEP_CTL(
+   [add-ls ls1])], [0], [], [], [VTEP_CTL_CLEANUP])
+CHECK_LSWITCHES([ls1])
+AT_CHECK([RUN_VTEP_CTL(
+   [add-ucast-local ls1 00:11:22:33:44:55 10.0.0.10],
+   [add-mcast-local ls1 01:11:22:33:44:55 10.0.0.10],
+   [add-mcast-local ls1 01:11:22:33:44:66 vxlan_over_ipv4 10.0.0.11],
+   [add-mcast-local ls1 01:11:22:33:44:55 10.0.0.12],
+   [add-ucast-remote ls1 00:11:22:33:44:55 10.0.0.10],
+   [add-mcast-remote ls1 01:11:22:33:44:55 10.0.0.10],
+   [add-mcast-remote ls1 01:11:22:33:44:66 vxlan_over_ipv4 10.0.0.11],
+   [add-mcast-remote ls1 01:11:22:33:44:55 10.0.0.12])
+], [0], [], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL([list-local-macs ls1])], [0],
+   [ucast-mac-local
+  00:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.10
+
+mcast-mac-local
+  01:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.10
+  01:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.12
+  01:11:22:33:44:66 -> vxlan_over_ipv4/10.0.0.11
+
+], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL([list-remote-macs ls1])], [0],
+   [ucast-mac-remote
+  00:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.10
+
+mcast-mac-remote
+  01:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.10
+  01:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.12
+  01:11:22:33:44:66 -> vxlan_over_ipv4/10.0.0.11
+
+], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL(
+   [clear-remote-macs ls1])], [0], [], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL([list-local-macs ls1])], [0],
+   [ucast-mac-local
+  00:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.10
+
+mcast-mac-local
+  01:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.10
+  01:11:22:33:44:55 -> vxlan_over_ipv4/10.0.0.12
+  01:11:22:33:44:66 -> vxlan_over_ipv4/10.0.0.11
+
+], [], [VTEP_CTL_CLEANUP])
+AT_CHECK([RUN_VTEP_CTL([list-remote-macs ls1])], [0],
+   [ucast-mac-remote
+
+mcast-mac-remote
+
+], [], [VTEP_CTL_CLEANUP])
+VTEP_CTL_CLEANUP
+AT_CLEANUP
+
+dnl ----------------------------------------------------------------------
+AT_BANNER([vtep-ctl unit tests -- manager commands])
+
+AT_SETUP([managers])
+AT_KEYWORDS([manager vtep-ctl])
+VTEP_CTL_SETUP
+AT_CHECK([RUN_VTEP_CTL_TOGETHER(
+  [del-manager],
+  [get-manager],
+  [set-manager tcp:4.5.6.7],
+  [get-manager],
+  [set-manager tcp:8.9.10.11 tcp:5.4.3.2],
+  [get-manager],
+  [del-manager],
+  [get-manager])], [0], [
+
+
+tcp:4.5.6.7
+
+tcp:5.4.3.2\ntcp:8.9.10.11
+
+
+], [], [VTEP_CTL_CLEANUP])
+VTEP_CTL_CLEANUP
+AT_CLEANUP
index f487d8c..9596ad4 100644 (file)
@@ -332,7 +332,7 @@ parse_options(int argc, char *argv[])
         case OPT_WITH_FLOWS:
             error = parse_ofp_flow_mod_file(optarg, OFPFC_ADD, &default_flows,
                                             &n_default_flows,
-                                            &usable_protocols);
+                                            &usable_protocols, false);
             if (error) {
                 ovs_fatal(0, "%s", error);
             }
index f43fdeb..7f0f1f8 100755 (executable)
@@ -72,7 +72,7 @@ commands are supported:
 
   h - halt output. Any character will restart sampling
 
-  f - cycle through flow fields
+  f - cycle through flow fields. The initial field is in_port
 
   q - q for quit.
 
@@ -295,16 +295,32 @@ class OutputFormat:
         self.field_type = field_type
         self.generator = generator
 
+##
+# The order below is important. The initial flow field depends on whether
+# --script or top mode is used. In top mode, the expected behavior, in_port
+# flow fields are shown first. A future feature will allow users to
+# filter output by selecting a row. Filtering by in_port is a natural
+# filtering starting point.
+#
+# In script mode, all fields are shown. The expectation is that users could
+# filter output by piping through grep.
+#
+# In top mode, the default flow field is in_port. In --script mode,
+# the default flow field is all.
+#
+# All is added to the end of the OUTPUT_FORMAT list.
+##
 OUTPUT_FORMAT = [
+    OutputFormat("in_port", element_passthrough_get),
     OutputFormat("eth", element_eth_get),
+    OutputFormat("eth_type", element_passthrough_get),
     OutputFormat("ipv4", element_ipv4_get),
     OutputFormat("ipv6", element_ipv6_get),
-    OutputFormat("tunnel", element_tunnel_get),
     OutputFormat("udp", element_dst_port_get),
     OutputFormat("tcp", element_dst_port_get),
-    OutputFormat("eth_type", element_passthrough_get),
-    OutputFormat("in_port", element_passthrough_get)
+    OutputFormat("tunnel", element_tunnel_get),
     ]
+##
 
 
 ELEMENT_KEY = {
@@ -713,8 +729,18 @@ def column_picker(order, obj):
 
 
 class Render:
-    """ Renders flow data. """
-    def __init__(self, console_width):
+    """ Renders flow data.
+
+    The two FIELD_SELECT variables should be set to the actual field minus
+    1. During construction, an internal method increments and initializes
+    this object.
+    """
+    FLOW_FIELDS = [_field.field_type for _field in OUTPUT_FORMAT] + ["all"]
+
+    FIELD_SELECT_SCRIPT = 7
+    FIELD_SELECT_TOP = -1
+
+    def __init__(self, console_width, field_select):
         """ Calculate column widths taking into account changes in format."""
 
         self._start_time = datetime.datetime.now()
@@ -759,12 +785,12 @@ class Render:
         # _field_types hold which fields are displayed in the field
         # column, with the keyword all implying all fields.
         ##
-        self._field_types = ["all"] + [ii.field_type for ii in OUTPUT_FORMAT]
+        self._field_types = Render.FLOW_FIELDS
 
         ##
         # The default is to show all field types.
         ##
-        self._field_type_select = -1
+        self._field_type_select = field_select
         self.field_type_toggle()
 
     def _field_type_select_get(self):
@@ -1144,7 +1170,7 @@ def flows_top(args):
     """ handles top like behavior when --script is not specified. """
 
     flow_db = FlowDB(args.accumulate)
-    render = Render(0)
+    render = Render(0, Render.FIELD_SELECT_TOP)
 
     decay_timer = decay_timer_start(flow_db, args.accumulateDecay)
     lines = []
@@ -1219,7 +1245,7 @@ def flows_script(args):
                 ihdl.close()
 
     (_, console_width) = get_terminal_size()
-    render = Render(console_width)
+    render = Render(console_width, Render.FIELD_SELECT_SCRIPT)
 
     for line in render.format(flow_db):
         print line
@@ -1685,3 +1711,12 @@ elif __name__ == 'ovs-dpctl-top':
 
             for (ipv6_test, ipv6_check) in ipv6s:
                 self.assertEqual(ipv6_to_network(ipv6_test), ipv6_check)
+
+        def test_ui(self):
+            """ test_ui: test expected ui behavior. """
+            #pylint: disable=W0212
+            top_render = Render(80, Render.FIELD_SELECT_TOP)
+            script_render = Render(80, Render.FIELD_SELECT_SCRIPT)
+            self.assertEqual(top_render._field_type_select_get(), "in_port")
+            self.assertEqual(script_render._field_type_select_get(), "all")
+            #pylint: enable=W0212
index 5c01570..5a0dd70 100644 (file)
@@ -101,6 +101,25 @@ port is identified as port 0.)  If \fB\-s\fR or \fB\-\-statistics\fR
 is specified, then packet and byte counters are also printed for each
 port.
 .IP
+The datapath numbers consists of flow stats and mega flow mask stats.
+.IP
+The "lookups" row displays three stats related to flow lookup triggered
+by processing incoming packets in the datapath. "hit" displays number
+of packets matches existing flows. "missed" displays the number of
+packets not matching any existing flow and require user space processing.
+"lost" displays number of packets destined for user space process but
+subsequently dropped before reaching userspace. The sum of "hit" and "miss"
+equals to the total number of packets datapath processed.
+.IP
+The "flows" row displays the number of flows in datapath.
+.IP
+The "masks" row displays the mega flow mask stats. This row is omitted
+for datapath not implementing mega flow. "hit" displays the total number
+of masks visited for matching incoming packets. "total" displays number of
+masks in the datapath. "hit/pkt" displays the average number of masks
+visited per packet; the ratio between "hit" and total number of
+packets processed by the datapath".
+.IP
 If one or more datapaths are specified, information on only those
 datapaths are displayed.  Otherwise, \fBovs\-dpctl\fR displays information
 about all configured datapaths.
@@ -118,11 +137,19 @@ exactly one datapath exists, in which case that datapath is the
 default.  When multiple datapaths exist, then a datapath name is
 required.
 .
-.IP "[\fB\-m \fR| \fB\-\-more\fR] \fBdump\-flows\fR [\fIdp\fR]"
+.IP "[\fB\-m \fR| \fB\-\-more\fR] \fBdump\-flows\fR [\fIdp\fR] [\fBfilter=\fIfilter\fR]"
 Prints to the console all flow entries in datapath \fIdp\fR's flow
 table.  Without \fB\-m\fR or \fB\-\-more\fR, output omits match fields
 that a flow wildcards entirely; with \fB\-m\fR or \fB\-\-more\fR,
 output includes all wildcarded fields.
+.IP
+If \fBfilter=\fIfilter\fR is specified, only displays the flows
+that match the \fIfilter\fR. \fIfilter\fR is a flow in the form similiar
+to that accepted by \fBovs\-ofctl\fR(8)'s \fBadd\-flow\fR command. (This is
+not an OpenFlow flow: besides other differences, it never contains wildcards.)
+The \fIfilter\fR is also useful to match wildcarded fields in the datapath
+flow. As an example, \fBfilter='tcp,tp_src=100'\fR will match the
+datapath flow containing '\fBtcp(src=80/0xff00,dst=8080/0xff)\fR'.
 .
 .IP "\fBadd\-flow\fR [\fIdp\fR] \fIflow actions\fR"
 .IQ "[\fB\-\-clear\fR] [\fB\-\-may-create\fR] [\fB\-s\fR | \fB\-\-statistics\fR] \fBmod\-flow\fR [\fIdp\fR] \fIflow actions\fR"
index 4fb02dd..78475e7 100644 (file)
 #include "dpif.h"
 #include "dynamic-string.h"
 #include "flow.h"
+#include "match.h"
 #include "netdev.h"
 #include "netlink.h"
 #include "odp-util.h"
+#include "ofp-parse.h"
 #include "ofpbuf.h"
 #include "packets.h"
 #include "shash.h"
@@ -561,7 +563,15 @@ show_dpif(struct dpif *dpif)
         printf("\tlookups: hit:%"PRIu64" missed:%"PRIu64" lost:%"PRIu64"\n"
                "\tflows: %"PRIu64"\n",
                stats.n_hit, stats.n_missed, stats.n_lost, stats.n_flows);
+        if (stats.n_masks != UINT64_MAX) {
+            uint64_t n_pkts = stats.n_hit + stats.n_missed;
+            double avg = n_pkts ? (double) stats.n_mask_hit / n_pkts : 0.0;
+
+            printf("\tmasks: hit:%"PRIu64" total:%"PRIu64" hit/pkt:%.2f\n",
+                   stats.n_mask_hit, stats.n_masks, avg);
+        }
     }
+
     DPIF_PORT_FOR_EACH (&dpif_port, &dump, dpif) {
         printf("\tport %u: %s", dpif_port.port_no, dpif_port.name);
 
@@ -746,20 +756,38 @@ dpctl_dump_flows(int argc, char *argv[])
     struct dpif_port dpif_port;
     struct dpif_port_dump port_dump;
     struct hmap portno_names;
+    struct simap names_portno;
     size_t actions_len;
     struct dpif *dpif;
     size_t key_len;
     size_t mask_len;
     struct ds ds;
-    char *name;
+    char *name, *error, *filter = NULL;
+    struct flow flow_filter;
+    struct flow_wildcards wc_filter;
 
+    if (argc > 1 && !strncmp(argv[argc - 1], "filter=", 7)) {
+        filter = xstrdup(argv[--argc] + 7);
+    }
     name = (argc == 2) ? xstrdup(argv[1]) : get_one_dp();
+
     run(parsed_dpif_open(name, false, &dpif), "opening datapath");
     free(name);
 
     hmap_init(&portno_names);
+    simap_init(&names_portno);
     DPIF_PORT_FOR_EACH (&dpif_port, &port_dump, dpif) {
         odp_portno_names_set(&portno_names, dpif_port.port_no, dpif_port.name);
+        simap_put(&names_portno, dpif_port.name,
+                  odp_to_u32(dpif_port.port_no));
+    }
+
+    if (filter) {
+        error = parse_ofp_exact_flow(&flow_filter, &wc_filter.masks, filter,
+                                     &names_portno);
+        if (error) {
+            ovs_fatal(0, "Failed to parse filter (%s)", error);
+        }
     }
 
     ds_init(&ds);
@@ -767,6 +795,26 @@ dpctl_dump_flows(int argc, char *argv[])
     while (dpif_flow_dump_next(&flow_dump, &key, &key_len,
                                &mask, &mask_len,
                                &actions, &actions_len, &stats)) {
+        if (filter) {
+            struct flow flow;
+            struct flow_wildcards wc;
+            struct match match, match_filter;
+            struct minimatch minimatch;
+
+            odp_flow_key_to_flow(key, key_len, &flow);
+            odp_flow_key_to_mask(mask, mask_len, &wc.masks, &flow);
+            match_init(&match, &flow, &wc);
+
+            match_init(&match_filter, &flow_filter, &wc);
+            match_init(&match_filter, &match_filter.flow, &wc_filter);
+            minimatch_init(&minimatch, &match_filter);
+
+            if (!minimatch_matches_flow(&minimatch, &match.flow)) {
+                minimatch_destroy(&minimatch);
+                continue;
+            }
+            minimatch_destroy(&minimatch);
+        }
         ds_clear(&ds);
         odp_flow_format(key, key_len, mask, mask_len, &portno_names, &ds,
                         verbosity);
@@ -778,8 +826,11 @@ dpctl_dump_flows(int argc, char *argv[])
         printf("%s\n", ds_cstr(&ds));
     }
     dpif_flow_dump_done(&flow_dump);
+
+    free(filter);
     odp_portno_names_destroy(&portno_names);
     hmap_destroy(&portno_names);
+    simap_destroy(&names_portno);
     ds_destroy(&ds);
     dpif_close(dpif);
 }
@@ -1166,7 +1217,7 @@ static const struct command all_commands[] = {
     { "set-if", 2, INT_MAX, dpctl_set_if },
     { "dump-dps", 0, 0, dpctl_dump_dps },
     { "show", 0, INT_MAX, dpctl_show },
-    { "dump-flows", 0, 1, dpctl_dump_flows },
+    { "dump-flows", 0, 2, dpctl_dump_flows },
     { "add-flow", 2, 3, dpctl_add_flow },
     { "mod-flow", 2, 3, dpctl_mod_flow },
     { "del-flow", 1, 2, dpctl_del_flow },
index c43b48c..75ea43b 100644 (file)
@@ -1099,9 +1099,23 @@ Sets the TCP or UDP or SCTP source port to \fIport\fR.
 Sets the TCP or UDP or SCTP destination port to \fIport\fR.
 .
 .IP \fBmod_nw_tos\fB:\fItos\fR
-Sets the IPv4 ToS/DSCP field to \fItos\fR, which must be a multiple of
-4 between 0 and 255.  This action does not modify the two least
-significant bits of the ToS field (the ECN bits).
+Sets the DSCP bits in the IPv4 ToS/DSCP or IPv6 traffic class field to
+\fItos\fR, which must be a multiple of 4 between 0 and 255.  This action
+does not modify the two least significant bits of the ToS field (the ECN bits).
+.
+.IP \fBmod_nw_ecn\fB:\fIecn\fR
+Sets the ECN bits in the IPv4 ToS or IPv6 traffic class field to \fIecn\fR,
+which must be a value between 0 and 3, inclusive.  This action does not modify
+the six most significant bits of the field (the DSCP bits).
+.IP
+Requires OpenFlow 1.1 or later.
+.
+.IP \fBmod_nw_ttl\fB:\fIttl\fR
+Sets the IPv4 TTL or IPv6 hop limit field to \fIttl\fR, which is specified as
+a decimal number between 0 and 255, inclusive.  Switch behavior when setting
+\fIttl\fR to zero is not well specified, though.
+.IP
+Requires OpenFlow 1.1 or later.
 .RE
 .IP
 The following actions are Nicira vendor extensions that, as of this writing, are
@@ -1142,7 +1156,8 @@ actions were applied.
 .IP \fBdec_ttl\fR
 .IQ \fBdec_ttl\fB[\fR(\fIid1,id2\fI)\fR]\fR
 Decrement TTL of IPv4 packet or hop limit of IPv6 packet.  If the
-TTL or hop limit is initially zero, no decrement occurs.  Instead,
+TTL or hop limit is initially zero or decrementing would make it so, no
+decrement occurs, as packets reaching TTL zero must be rejected.  Instead,
 a ``packet-in'' message with reason code \fBOFPR_INVALID_TTL\fR is
 sent to each connected controller that has enabled receiving them,
 if any.  Processing the current set of actions then stops.  However,
@@ -1161,9 +1176,9 @@ Set the TTL of the outer MPLS label stack entry of a packet.
 .
 .IP \fBdec_mpls_ttl\fR
 Decrement TTL of the outer MPLS label stack entry of a packet.  If the TTL
-is initially zero, no decrement occurs.  Instead, a ``packet-in'' message
-with reason code \fBOFPR_INVALID_TTL\fR is sent to each connected
-controller with controller id zer that has enabled receiving them.
+is initially zero or decrementing would make it so, no decrement occurs.
+Instead, a ``packet-in'' message with reason code \fBOFPR_INVALID_TTL\fR
+is sent to the main controller (id zero), if it has enabled receiving them.
 Processing the current set of actions then stops.  However, if the current
 set of actions was reached through ``resubmit'' then remaining actions in
 outer levels resume processing.
@@ -1346,6 +1361,91 @@ to \fBactions=\fR field.
 .IP \fBclear_actions\fR
 Clears all the actions in the action set immediately.
 .
+.IP \fBwrite_actions(\fR[\fIaction\fR][\fB,\fIaction\fR...]\fB)
+Add the specific actions to the action set.  The syntax of
+\fIactions\fR is the same as in the \fBactions=\fR field.  The action
+set is carried between flow tables and then executed at the end of the
+pipeline.
+.
+.IP
+The actions in the action set are applied in the following order, as
+required by the OpenFlow specification, regardless of the order in
+which they were added to the action set.  Except as specified
+otherwise below, the action set only holds at most a single action of
+each type.  When more than one action of a single type is written to
+the action set, the one written later replaces the earlier action:
+.
+.RS
+.IP 1.
+\fBstrip_vlan\fR
+.IQ
+\fBpop_mpls\fR
+.
+.IP 2.
+\fBpush_mpls\fR
+.
+.IP 3.
+\fBpush_vlan\fR
+.
+.IP 4.
+\fBdec_ttl\fR
+.IQ
+\fBdec_mpls_ttl\fR
+.
+.IP 5.
+\fBload\fR
+.IQ
+\fBmod_dl_dst\fR
+.IQ
+\fBmod_dl_src\fR
+.IQ
+\fBmod_nw_dst\fR
+.IQ
+\fBmod_nw_src\fR
+.IQ
+\fBmod_nw_tos\fR
+.IQ
+\fBmod_nw_ecn\fR
+.IQ
+\fBmod_nw_ttl\fR
+.IQ
+\fBmod_tp_dst\fR
+.IQ
+\fBmod_tp_src\fR
+.IQ
+\fBmod_vlan_pcp\fR
+.IQ
+\fBmod_vlan_vid\fR
+.IQ
+\fBset_field\fR
+.IQ
+\fBset_tunnel\fR
+.IQ
+\fBset_tunnel64\fR
+.IQ
+The action set can contain any number of these actions, with
+cumulative effect.  That is, when multiple actions modify the same
+part of a field, the later modification takes effect, and when they
+modify different parts of a field (or different fields), then both
+modifications are applied.
+.
+.IP 6.
+\fBset_queue\fR
+.
+.IP 7.
+\fBgroup\fR
+.IQ
+\fBoutput\fR
+.IQ
+If both actions are present, then \fBgroup\fR is executed and
+\fBoutput\fR is ignored, regardless of the order in which they were
+added to the action set.  (If neither action is present, the action
+set has no real effect, because the modified packet is not sent
+anywhere and thus the modifications are not visible.)
+.RE
+.IP
+Only the actions listed above may be written to the action set.
+.
 .IP \fBwrite_metadata\fB:\fIvalue\fR[/\fImask\fR]
 Updates the metadata field for the flow. If \fImask\fR is omitted, the
 metadata field is set exactly to \fIvalue\fR; if \fImask\fR is specified, then
@@ -1409,10 +1509,12 @@ configuring sample collector sets.
 This action was added in Open vSwitch 1.10.90.
 .
 .IP "\fBexit\fR"
-This action causes Open vSwitch to immediately halt execution of further
-actions.  Those actions which have already been executed are unaffected.  Any
-further actions, including those which may be in other tables, or different
-levels of the \fBresubmit\fR call stack, are ignored.
+This action causes Open vSwitch to immediately halt execution of
+further actions.  Those actions which have already been executed are
+unaffected.  Any further actions, including those which may be in
+other tables, or different levels of the \fBresubmit\fR call stack,
+are ignored.  Actions in the action set is still executed (specify
+\fBclear_actions\fR before \fBexit\fR to discard them).
 .RE
 .
 .PP
index 1cd23e6..1a1d423 100644 (file)
@@ -889,7 +889,9 @@ prepare_dump_flows(int argc, char *argv[], bool aggregate,
 
     error = parse_ofp_flow_stats_request_str(&fsr, aggregate,
                                              argc > 2 ? argv[2] : "",
-                                             &usable_protocols);
+                                             &usable_protocols,
+                                             !(allowed_protocols
+                                               & OFPUTIL_P_OF10_ANY));
     if (error) {
         ovs_fatal(0, "%s", error);
     }
@@ -1111,7 +1113,8 @@ ofctl_flow_mod_file(int argc OVS_UNUSED, char *argv[], uint16_t command)
     char *error;
 
     error = parse_ofp_flow_mod_file(argv[2], command, &fms, &n_fms,
-                                    &usable_protocols);
+                                    &usable_protocols,
+                                    !(allowed_protocols & OFPUTIL_P_OF10_ANY));
     if (error) {
         ovs_fatal(0, "%s", error);
     }
@@ -1130,7 +1133,9 @@ ofctl_flow_mod(int argc, char *argv[], uint16_t command)
         enum ofputil_protocol usable_protocols;
 
         error = parse_ofp_flow_mod_str(&fm, argc > 2 ? argv[2] : "", command,
-                                       &usable_protocols);
+                                       &usable_protocols,
+                                       !(allowed_protocols
+                                         & OFPUTIL_P_OF10_ANY));
         if (error) {
             ovs_fatal(0, "%s", error);
         }
@@ -2113,7 +2118,7 @@ fte_version_format(const struct fte *fte, int index, struct ds *s)
         ds_put_format(s, " hard_timeout=%"PRIu16, version->hard_timeout);
     }
 
-    ds_put_char(s, ' ');
+    ds_put_cstr(s, " actions=");
     ofpacts_format(version->ofpacts, version->ofpacts_len, s);
 
     ds_put_char(s, '\n');
@@ -2205,7 +2210,8 @@ read_flows_from_file(const char *filename, struct classifier *cls, int index)
         char *error;
         enum ofputil_protocol usable;
 
-        error = parse_ofp_str(&fm, OFPFC_ADD, ds_cstr(&s), &usable);
+        error = parse_ofp_str(&fm, OFPFC_ADD, ds_cstr(&s), &usable,
+                              !(allowed_protocols & OFPUTIL_P_OF10_ANY));
         if (error) {
             ovs_fatal(0, "%s:%d: %s", filename, line_number, error);
         }
@@ -2624,7 +2630,8 @@ ofctl_parse_flow(int argc OVS_UNUSED, char *argv[])
     struct ofputil_flow_mod fm;
     char *error;
 
-    error = parse_ofp_flow_mod_str(&fm, argv[1], OFPFC_ADD, &usable_protocols);
+    error = parse_ofp_flow_mod_str(&fm, argv[1], OFPFC_ADD, &usable_protocols,
+                                   !(allowed_protocols & OFPUTIL_P_OF10_ANY));
     if (error) {
         ovs_fatal(0, "%s", error);
     }
@@ -2642,7 +2649,8 @@ ofctl_parse_flows(int argc OVS_UNUSED, char *argv[])
     char *error;
 
     error = parse_ofp_flow_mod_file(argv[1], OFPFC_ADD, &fms, &n_fms,
-                                    &usable_protocols);
+                                    &usable_protocols,
+                                    !(allowed_protocols & OFPUTIL_P_OF10_ANY));
     if (error) {
         ovs_fatal(0, "%s", error);
     }
@@ -2795,6 +2803,7 @@ ofctl_parse_ofp10_actions(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
 
         /* Print cls_rule. */
         ds_init(&s);
+        ds_put_cstr(&s, "actions=");
         ofpacts_format(ofpacts.data, ofpacts.size, &s);
         puts(ds_cstr(&s));
         ds_destroy(&s);
@@ -2969,8 +2978,8 @@ ofctl_parse_ofp11_actions(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
         /* Convert to ofpacts. */
         ofpbuf_init(&ofpacts, 0);
         size = of11_in.size;
-        error = ofpacts_pull_openflow11_actions(&of11_in, of11_in.size,
-                                                &ofpacts);
+        error = ofpacts_pull_openflow11_actions(&of11_in, OFP11_VERSION,
+                                                of11_in.size, &ofpacts);
         if (error) {
             printf("bad OF1.1 actions: %s\n\n", ofperr_get_name(error));
             ofpbuf_uninit(&ofpacts);
@@ -2981,6 +2990,7 @@ ofctl_parse_ofp11_actions(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
 
         /* Print cls_rule. */
         ds_init(&s);
+        ds_put_cstr(&s, "actions=");
         ofpacts_format(ofpacts.data, ofpacts.size, &s);
         puts(ds_cstr(&s));
         ds_destroy(&s);
@@ -3037,14 +3047,14 @@ ofctl_parse_ofp11_instructions(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
         /* Convert to ofpacts. */
         ofpbuf_init(&ofpacts, 0);
         size = of11_in.size;
-        error = ofpacts_pull_openflow11_instructions(&of11_in, of11_in.size,
-                                                     &ofpacts);
+        error = ofpacts_pull_openflow11_instructions(&of11_in, OFP11_VERSION,
+                                                     of11_in.size, &ofpacts);
         if (!error) {
-            /* Verify actions. */
+            /* Verify actions, enforce consistency. */
             struct flow flow;
             memset(&flow, 0, sizeof flow);
             error = ofpacts_check(ofpacts.data, ofpacts.size, &flow, OFPP_MAX,
-                                  table_id ? atoi(table_id) : 0);
+                                  table_id ? atoi(table_id) : 0, true);
         }
         if (error) {
             printf("bad OF1.1 instructions: %s\n\n", ofperr_get_name(error));
@@ -3056,6 +3066,7 @@ ofctl_parse_ofp11_instructions(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
 
         /* Print cls_rule. */
         ds_init(&s);
+        ds_put_cstr(&s, "actions=");
         ofpacts_format(ofpacts.data, ofpacts.size, &s);
         puts(ds_cstr(&s));
         ds_destroy(&s);
@@ -3141,7 +3152,8 @@ ofctl_check_vlan(int argc OVS_UNUSED, char *argv[])
     string_s = match_to_string(&match, OFP_DEFAULT_PRIORITY);
     printf("%s -> ", string_s);
     fflush(stdout);
-    error_s = parse_ofp_str(&fm, -1, string_s, &usable_protocols);
+    error_s = parse_ofp_str(&fm, -1, string_s, &usable_protocols,
+                            !(allowed_protocols & OFPUTIL_P_OF10_ANY));
     if (error_s) {
         ovs_fatal(0, "%s", error_s);
     }
index afdff20..04eaf9d 100644 (file)
@@ -4,3 +4,5 @@
 /ovs-vswitchd.8
 /ovs-vswitchd.conf.db.5
 /vswitch.ovsschema.stamp
+/vswitch.gv
+/vswitch.pic
index 6260a72..02d413e 100644 (file)
@@ -25,46 +25,30 @@ pkgdata_DATA += vswitchd/vswitch.ovsschema
 
 # vswitch E-R diagram
 #
-# There are two complications here.  First, if "python" or "dot" is not
-# available, then we have to just use the existing diagram.  Second, different
-# "dot" versions produce slightly different output for the same input, but we
-# don't want to gratuitously change vswitch.pic if someone tweaks the schema in
-# some minor way that doesn't affect the table structure.  To avoid that we
-# store a checksum of vswitch.gv in vswitch.pic and only regenerate vswitch.pic
-# if vswitch.gv actually changes.
-$(srcdir)/vswitchd/vswitch.gv: ovsdb/ovsdb-dot.in vswitchd/vswitch.ovsschema
+# If "python" or "dot" is not available, then we do not add graphical diagram
+# to the documentation.
 if HAVE_PYTHON
-       $(OVSDB_DOT) $(srcdir)/vswitchd/vswitch.ovsschema > $@
-else
-       touch $@
-endif
-$(srcdir)/vswitchd/vswitch.pic: $(srcdir)/vswitchd/vswitch.gv ovsdb/dot2pic
 if HAVE_DOT
-       sum=`cksum < $(srcdir)/vswitchd/vswitch.gv`;                    \
-       if grep "$$sum" $@ >/dev/null 2>&1; then                        \
-         echo "vswitch.gv unchanged, not regenerating vswitch.pic";    \
-         touch $@;                                                     \
-       else                                                            \
-         echo "regenerating vswitch.pic";                              \
-         (echo ".\\\" Generated from vswitch.gv with cksum \"$$sum\""; \
-          dot -T plain < $(srcdir)/vswitchd/vswitch.gv                 \
-           | $(srcdir)/ovsdb/dot2pic -f 3) > $@;                       \
-       fi
-else
-       touch $@
+vswitchd/vswitch.gv: ovsdb/ovsdb-dot.in vswitchd/vswitch.ovsschema
+       $(OVSDB_DOT) --no-arrows $(srcdir)/vswitchd/vswitch.ovsschema > $@
+vswitchd/vswitch.pic: vswitchd/vswitch.gv ovsdb/dot2pic
+       (dot -T plain < vswitchd/vswitch.gv | $(srcdir)/ovsdb/dot2pic -f 3) > $@;
+VSWITCH_PIC = vswitchd/vswitch.pic
+VSWITCH_DOT_DIAGRAM_ARG = --er-diagram=$(VSWITCH_PIC)
+DISTCLEANFILES += vswitchd/vswitch.gv vswitchd/vswitch.pic
+endif
 endif
-EXTRA_DIST += vswitchd/vswitch.gv vswitchd/vswitch.pic
 
 # vswitch schema documentation
 EXTRA_DIST += vswitchd/vswitch.xml
-DISTCLEANFILES += $(srcdir)/vswitchd/ovs-vswitchd.conf.db.5
-dist_man_MANS += vswitchd/ovs-vswitchd.conf.db.5
-$(srcdir)/vswitchd/ovs-vswitchd.conf.db.5: \
+DISTCLEANFILES += vswitchd/ovs-vswitchd.conf.db.5
+man_MANS += vswitchd/ovs-vswitchd.conf.db.5
+vswitchd/ovs-vswitchd.conf.db.5: \
        ovsdb/ovsdb-doc vswitchd/vswitch.xml vswitchd/vswitch.ovsschema \
-       $(srcdir)/vswitchd/vswitch.pic
+       $(VSWITCH_PIC)
        $(OVSDB_DOC) \
                --title="ovs-vswitchd.conf.db" \
-               --er-diagram=$(srcdir)/vswitchd/vswitch.pic \
+               $(VSWITCH_DOT_DIAGRAM_ARG) \
                --version=$(VERSION) \
                $(srcdir)/vswitchd/vswitch.ovsschema \
                $(srcdir)/vswitchd/vswitch.xml > $@.tmp
diff --git a/vswitchd/vswitch.gv b/vswitchd/vswitch.gv
deleted file mode 100644 (file)
index 51fdae1..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-digraph Open_vSwitch {
-       rankdir=LR;
-       size="6.5,4";
-       margin="0";
-       node [shape=box];
-       edge [dir=none, arrowhead=none, arrowtail=none];
-       Bridge [];
-       Bridge -> sFlow [label="sflow?"];
-       Bridge -> Mirror [label="mirrors*"];
-       Bridge -> IPFIX [label="ipfix?"];
-       Bridge -> Port [label="ports*"];
-       Bridge -> Controller [label="controller*"];
-       Bridge -> Flow_Table [label="flow_tables value*"];
-       Bridge -> NetFlow [label="netflow?"];
-       QoS [style=bold];
-       QoS -> Queue [label="queues value*"];
-       sFlow [];
-       Flow_Sample_Collector_Set [style=bold];
-       Flow_Sample_Collector_Set -> Bridge [label="bridge"];
-       Flow_Sample_Collector_Set -> IPFIX [label="ipfix?"];
-       IPFIX [];
-       Open_vSwitch [style=bold];
-       Open_vSwitch -> Bridge [label="bridges*"];
-       Open_vSwitch -> SSL [label="ssl?"];
-       Open_vSwitch -> Manager [label="manager_options*"];
-       Controller [];
-       Flow_Table [];
-       Queue [style=bold];
-       SSL [];
-       Manager [];
-       Mirror [];
-       Mirror -> Port [style=dotted, constraint=false, label="select_src_port*"];
-       Mirror -> Port [style=dotted, constraint=false, label="output_port?"];
-       Mirror -> Port [style=dotted, constraint=false, label="select_dst_port*"];
-       Interface [];
-       NetFlow [];
-       Port [];
-       Port -> QoS [label="qos?"];
-       Port -> Interface [label="interfaces+"];
-}
diff --git a/vswitchd/vswitch.pic b/vswitchd/vswitch.pic
deleted file mode 100644 (file)
index 3bd4e12..0000000
+++ /dev/null
@@ -1,94 +0,0 @@
-.\" Generated from vswitch.gv with cksum "813191710 1237"
-.ps -3
-.PS
-linethick = 1;
-linethick = 1;
-box at 2.421242138,1.420752459 wid 0.3611105806 height 0.213115 "Bridge"
-linethick = 1;
-box at 3.907122541,0.970866694 wid 0.3315131694 height 0.213115 "sFlow"
-linethick = 1;
-box at 3.907122541,0.651194194 wid 0.3492699112 height 0.213115 "Mirror"
-linethick = 1;
-box at 3.907122541,1.929884194 wid 0.3196725 height 0.213115 "IPFIX"
-linethick = 1;
-box at 3.907122541,0.2249556694 wid 0.3196725 height 0.213115 "Port"
-linethick = 1;
-box at 3.907122541,1.610211694 wid 0.509089112 height 0.213115 "Controller"
-linethick = 1;
-box at 3.907122541,1.290539194 wid 0.544636694 height 0.213115 "Flow_Table"
-linethick = 1;
-box at 3.907122541,2.249556694 wid 0.438079194 height 0.213115 "NetFlow"
-linethick = 0.5;
-box at 5.09685834,0.42623 wid 0.3196725 height 0.213115 "QoS"
-box at 5.09685834,0.42623 wid 0.264116944444444 height 0.157559444444444
-linethick = 0.5;
-box at 6.31630237,0.42623 wid 0.3670309153 height 0.213115 "Queue"
-box at 6.31630237,0.42623 wid 0.311475359744445 height 0.157559444444444
-linethick = 0.5;
-box at 0.609764638,2.113376209 wid 1.219486653 height 0.213115 "Flow_Sample_Collector_Set"
-box at 0.609764638,2.113376209 wid 1.16393109744444 height 0.157559444444444
-linethick = 0.5;
-box at 0.609764638,1.101079959 wid 0.686699153 height 0.213115 "Open_vSwitch"
-box at 0.609764638,1.101079959 wid 0.631143597444444 height 0.157559444444444
-linethick = 1;
-box at 2.421242138,0.781407459 wid 0.3196725 height 0.213115 "SSL"
-linethick = 1;
-box at 2.421242138,1.101079959 wid 0.455810362 height 0.213115 "Manager"
-linethick = 1;
-box at 5.09685834,0.1065575 wid 0.461734959 height 0.213115 "Interface"
-linethick = 1;
-spline -> from 2.585425934,1.312575285 to 2.585425934,1.312575285 to 2.608314485,1.295909692 to 2.63111779,1.278434262 to 2.652088306,1.260916209 to 2.703534267,1.217994848 to 2.699016229,1.184834154 to 2.758645806,1.154358709 to 3.077892076,0.991453603 to 3.506551587,0.966220787 to 3.739401036,0.966092918
-"sflow?" at 3.14344625,1.207637459
-linethick = 1;
-spline -> from 2.602048904,1.315686764 to 2.602048904,1.315686764 to 2.620845647,1.299234286 to 2.638150585,1.280949019 to 2.652088306,1.260916209 to 2.760819579,1.104916029 to 2.618714497,0.975299486 to 2.758645806,0.846535403 to 3.02580677,0.600728562 to 3.482938445,0.603584303 to 3.732325618,0.627240068
-"mirrors*" at 3.14344625,0.899814153
-linethick = 1;
-spline -> from 2.530911117,1.527523074 to 2.530911117,1.527523074 to 2.592927582,1.581867399 to 2.674721119,1.644139602 to 2.758645806,1.681221612 to 3.076357648,1.821664397 to 3.190203681,1.737569218 to 3.528246694,1.817402097 to 3.60036481,1.834408674 to 3.679046868,1.85708411 to 3.745964978,1.877628396
-"ipfix?" at 3.14344625,1.870680847
-linethick = 1;
-spline -> from 2.602091527,1.31918185 to 2.602091527,1.31918185 to 2.621399746,1.301919535 to 2.638832553,1.28252607 to 2.652088306,1.260916209 to 2.832980318,0.966817509 to 2.525753734,0.746286107 to 2.758645806,0.491357944 to 3.012678886,0.2132940166 to 3.494830262,0.1973231785 to 3.745964978,0.2103359804
-"ports*" at 3.14344625,0.544636694
-linethick = 1;
-spline -> from 2.60341284,1.443981994 to 2.60341284,1.443981994 to 2.866141012,1.477483672 to 3.352469442,1.539500137 to 3.651128803,1.577562476
-"controller*" at 3.14344625,1.616136291
-linethick = 1;
-spline -> from 2.601835789,1.365385182 to 2.601835789,1.365385182 to 2.652130929,1.352086806 to 2.707114599,1.339598267 to 2.758645806,1.33196875 to 3.055685493,1.28806706 to 3.400420317,1.281460495 to 3.634079603,1.283463776
-"flow_tables value*" at 3.14344625,1.3852475
-linethick = 1;
-spline -> from 2.465612681,1.527650943 to 2.465612681,1.527650943 to 2.518635693,1.642392059 to 2.618245644,1.821962758 to 2.758645806,1.923959597 to 3.037826456,2.126717208 to 3.4396761,2.203481231 to 3.686505893,2.232337002
-"netflow?" at 3.14344625,2.267287862
-linethick = 0.5;
-spline -> from 3.907122541,0.3339597296 to 3.907122541,0.3339597296 to 3.907122541,0.3983332465 to 3.907122541,0.47950875 to 3.907122541,0.543613742
-"select_src_port*" at 4.235660625,0.438079194
-linethick = 0.5;
-spline -> from 3.74485678,0.2306373153 to 3.74485678,0.2306373153 to 3.380984229,0.2450481516 to 2.525029143,0.2888859071 to 2.438973306,0.3847919194 to 2.40734704,0.4200454027 to 2.40734704,0.456108723 to 2.438973306,0.491357944 to 2.524176683,0.586279365 to 3.364062898,0.630223678 to 3.733817423,0.645056482
-"output_port?" at 2.708350666,0.438079194
-linethick = 0.5;
-spline -> from 3.746007601,0.232167481 to 3.746007601,0.232167481 to 3.499731907,0.2491570088 to 3.072905185,0.3055728116 to 3.220423388,0.491357944 to 3.283377559,0.570679347 to 3.551945082,0.614240053 to 3.733945292,0.635125323
-"select_dst_port*" at 3.551902459,0.438079194
-linethick = 1;
-spline -> from 4.067598136,0.2520979958 to 4.067598136,0.2520979958 to 4.29469348,0.2905141057 to 4.70728412,0.3603433666 to 4.93531717,0.3988702963
-"qos?" at 4.52272653,0.4203096653
-linethick = 1;
-spline -> from 4.068450596,0.186646117 to 4.068450596,0.186646117 to 4.135411329,0.1722054446 to 4.214050764,0.1570870665 to 4.28616888,0.1479955806 to 4.48052976,0.1234234211 to 4.70259559,0.1133175078 to 4.86541545,0.1092129129
-"interfaces+" at 4.52272653,0.2012743306
-linethick = 1;
-spline -> from 5.25882574,0.42623 to 5.25882574,0.42623 to 5.48515387,0.42623 to 5.89433467,0.42623 to 6.13174478,0.42623
-"queues value*" at 5.73023612,0.47950875
-linethick = 1;
-spline -> from 0.888817419,2.00669084 to 0.888817419,2.00669084 to 1.263985065,1.863264445 to 1.920421888,1.612257598 to 2.238346845,1.490696802
-"bridge" at 1.70492,1.882530041
-linethick = 1;
-spline -> from 1.117404568,2.220359939 to 1.117404568,2.220359939 to 1.711441319,2.323507599 to 2.716960512,2.428999524 to 3.528246694,2.166654959 to 3.583827086,2.148710676 to 3.58587299,2.121645071 to 3.634804194,2.089720444 to 3.670607514,2.06636304 to 3.709863297,2.042366291 to 3.747158422,2.0203302
-"ipfix?" at 2.421242138,2.385694556
-linethick = 1;
-spline -> from 0.953945363,1.164289868 to 0.953945363,1.164289868 to 1.071883204,1.18577186 to 1.204696472,1.209811232 to 1.326044153,1.231335847 to 1.647123212,1.288280175 to 2.021566267,1.352598282 to 2.23898619,1.389722915
-"bridges*" at 1.70492,1.420752459
-linethick = 1;
-spline -> from 0.953945363,1.037912673 to 0.953945363,1.037912673 to 1.071883204,1.016388058 to 1.204696472,0.992391309 to 1.326044153,0.970866694 to 1.657565847,0.912046954 to 2.045989246,0.845384582 to 2.259700968,0.808899294
-"ssl?" at 1.70492,1.024145444
-linethick = 1;
-spline -> from 0.9547552,1.101079959 to 0.9547552,1.101079959 to 1.318116275,1.101079959 to 1.880057907,1.101079959 to 2.192271382,1.101079959
-"manager_options*" at 1.70492,1.154358709
-.ps +3
-.PE
diff --git a/vtep/.gitignore b/vtep/.gitignore
new file mode 100644 (file)
index 0000000..c898bc4
--- /dev/null
@@ -0,0 +1,8 @@
+/Makefile
+/Makefile.in
+/vtep-ctl
+/vtep-ctl.8
+/vtep.5
+/vtep.gv
+/vtep.ovsschema.stamp
+/vtep.pic
diff --git a/vtep/README.ovs-vtep b/vtep/README.ovs-vtep
new file mode 100644 (file)
index 0000000..79e1538
--- /dev/null
@@ -0,0 +1,82 @@
+                       How to Use the VTEP Emulator
+                       ============================
+
+This document explains how to use ovs-vtep, a VTEP emulator that uses
+Open vSwitch for forwarding.  The emulator is a Python script that
+invokes calls to vtep-ctl and various OVS commands, so these commands
+will need to be available in the emulator's path.
+
+Startup
+=======
+
+These instructions describe how to run with a single ovsdb-server
+instance that handles both the OVS and VTEP schema.
+
+1. Create the initial OVS and VTEP schemas:
+
+    ovsdb-tool create /etc/openvswitch/ovs.db vswitchd/vswitch.ovsschema
+    ovsdb-tool create /etc/openvswitch/vtep.db vtep/vtep.ovsschema
+
+2. Start ovsdb-server and have it handle both databases:
+
+    ovsdb-server --pidfile --detach --log-file \
+      --remote punix:/var/run/openvswitch/db.sock \
+      /etc/openvswitch/ovs.db /etc/openvswitch/vtep.db
+
+3. Start OVS as normal:
+
+    ovs-vswitchd --log-file --detach --pidfile \
+      unix:/var/run/openvswitch/db.sock
+
+4. Create a "physical" switch in OVS:
+
+    ovs-vsctl add-br br0
+    ovs-vsctl add-port br0 eth0
+
+5. Configure the physical switch in the VTEP database:
+
+    vtep-ctl add-ps br0
+    vtep-ctl add-port br0 eth0
+    vtep-ctl set Physical_Switch br0 tunnel_ips=192.168.0.3
+
+6. Start the VTEP emulator:
+
+    ovs-vtep --log-file=/var/log/openvswitch/ovs-vtep.log \
+      --pidfile=/var/run/openvswitch/ovs-vtep.pid \
+      --detach br0
+
+7. Configure the VTEP database's manager to point at a NVC:
+
+    vtep-ctl set-manager tcp:192.168.0.99:6632
+
+The example provided creates a physical switch with a single physical
+port attached to it.  If more than one physical port is needed, it would
+be added to "br0" to both the OVS and VTEP databases:
+
+    ovs-vsctl add-port br0 eth1
+    vtep-ctl add-port br0 eth1
+
+
+Simulating a NVC
+================
+
+A VTEP implementation expects to be driven by a Network Virtualization
+Controller (NVC), such as NSX.  If one does not exist, it's possible to
+use vtep-ctl to simulate one:
+
+1. Create a logical switch:
+
+    vtep-ctl add-ls ls0
+
+2. Bind the logical switch to a port:
+
+    vtep-ctl bind-ls br0 eth0 0 ls0
+    vtep-ctl set Logical_Switch ls0 tunnel_key=33
+
+3. Direct unknown destinations out a tunnel:
+
+    vtep-ctl add-mcast-remote ls0 unknown-dst 192.168.1.34
+
+4. Direct unicast destinations out a different tunnel:
+
+    vtep-ctl add-ucast-remote ls0 11:22:33:44:55:66 192.168.1.33
diff --git a/vtep/automake.mk b/vtep/automake.mk
new file mode 100644 (file)
index 0000000..6e89a05
--- /dev/null
@@ -0,0 +1,69 @@
+bin_PROGRAMS += \
+   vtep/vtep-ctl
+
+MAN_ROOTS += \
+   vtep/vtep-ctl.8.in
+
+DISTCLEANFILES += \
+   vtep/vtep-ctl.8
+
+man_MANS += \
+   vtep/vtep-ctl.8
+
+vtep_vtep_ctl_SOURCES = vtep/vtep-ctl.c
+vtep_vtep_ctl_LDADD = lib/libopenvswitch.a $(SSL_LIBS)
+
+# ovs-vtep
+scripts_SCRIPTS += \
+    vtep/ovs-vtep
+
+EXTRA_DIST += \
+    vtep/ovs-vtep \
+    vtep/README.ovs-vtep
+
+# VTEP schema and IDL
+EXTRA_DIST += vtep/vtep.ovsschema
+pkgdata_DATA += vtep/vtep.ovsschema
+
+# VTEP E-R diagram
+#
+# If "python" or "dot" is not available, then we do not add graphical diagram
+# to the documentation.
+if HAVE_PYTHON
+if HAVE_DOT
+vtep/vtep.gv: ovsdb/ovsdb-dot.in vtep/vtep.ovsschema
+       $(OVSDB_DOT) --no-arrows $(srcdir)/vtep/vtep.ovsschema > $@
+vtep/vtep.pic: vtep/vtep.gv ovsdb/dot2pic
+       (dot -T plain < vtep/vtep.gv | $(srcdir)/ovsdb/dot2pic -f 3) > $@;
+VTEP_PIC = vtep/vtep.pic
+VTEP_DOT_DIAGRAM_ARG = --er-diagram=$(VTEP_PIC)
+DISTCLEANFILES += vtep/vtep.gv vtep/vtep.pic
+endif
+endif
+
+# VTEP schema documentation
+EXTRA_DIST += vtep/vtep.xml
+DISTCLEANFILES += vtep/vtep.5
+dist_man_MANS += vtep/vtep.5
+$(srcdir)/vtep/vtep.5: \
+       ovsdb/ovsdb-doc vtep/vtep.xml vtep/vtep.ovsschema $(VTEP_PIC)
+       $(OVSDB_DOC) \
+               --title="vtep" \
+               $(VTEP_DOT_DIAGRAM_ARG) \
+               $(srcdir)/vtep/vtep.ovsschema \
+               $(srcdir)/vtep/vtep.xml > $@.tmp
+       mv $@.tmp $@
+
+# Version checking for vtep.ovsschema.
+ALL_LOCAL += vtep/vtep.ovsschema.stamp
+vtep/vtep.ovsschema.stamp: vtep/vtep.ovsschema
+       @sum=`sed '/cksum/d' $? | cksum`; \
+       expected=`sed -n 's/.*"cksum": "\(.*\)".*/\1/p' $?`; \
+       if test "X$$sum" = "X$$expected"; then \
+         touch $@; \
+       else \
+         ln=`sed -n '/"cksum":/=' $?`; \
+         echo >&2 "$?:$$ln: checksum \"$$sum\" does not match (you should probably update the version number and fix the checksum)"; \
+         exit 1; \
+       fi
+CLEANFILES += vtep/vtep.ovsschema.stamp
diff --git a/vtep/ovs-vtep b/vtep/ovs-vtep
new file mode 100755 (executable)
index 0000000..721063b
--- /dev/null
@@ -0,0 +1,522 @@
+#!/usr/bin/python
+# Copyright (C) 2013 Nicira, Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Limitations:
+#     - Doesn't support multicast other than "unknown-dst"
+
+import argparse
+import re
+import subprocess
+import sys
+import time
+import types
+
+import ovs.dirs
+import ovs.util
+import ovs.daemon
+import ovs.unixctl.server
+import ovs.vlog
+
+
+VERSION = "0.99"
+
+root_prefix = ""
+
+__pychecker__ = 'no-reuseattr'  # Remove in pychecker >= 0.8.19.
+vlog = ovs.vlog.Vlog("ovs-vtep")
+exiting = False
+
+Tunnel_Ip = ""
+Lswitches = {}
+Bindings = {}
+ls_count = 0
+tun_id = 0
+
+def call_prog(prog, args_list):
+    cmd = [prog, "-vconsole:off"] + args_list
+    output = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()
+    if len(output) == 0 or output[0] == None:
+        output = ""
+    else:
+        output = output[0].strip()
+    return output
+
+def ovs_vsctl(args):
+    return call_prog("ovs-vsctl", args.split())
+
+def ovs_ofctl(args):
+    return call_prog("ovs-ofctl", args.split())
+
+def vtep_ctl(args):
+    return call_prog("vtep-ctl", args.split())
+
+
+def unixctl_exit(conn, unused_argv, unused_aux):
+    global exiting
+    exiting = True
+    conn.reply(None)
+
+
+class Logical_Switch(object):
+    def __init__(self, ls_name):
+        global ls_count
+        self.name = ls_name
+        ls_count += 1
+        self.short_name = "vtep_ls" + str(ls_count)
+        vlog.info("creating lswitch %s (%s)" % (self.name, self.short_name))
+        self.ports = {}
+        self.tunnels = {}
+        self.local_macs = set()
+        self.remote_macs = {}
+        self.unknown_dsts = set()
+        self.tunnel_key = 0
+        self.setup_ls()
+
+    def __del__(self):
+        vlog.info("destroying lswitch %s" % self.name)
+
+    def setup_ls(self):
+        column = vtep_ctl("--columns=tunnel_key find logical_switch "
+                              "name=%s" % self.name)
+        tunnel_key = column.partition(":")[2].strip()
+        if (tunnel_key and type(eval(tunnel_key)) == types.IntType):
+            self.tunnel_key = tunnel_key
+            vlog.info("using tunnel key %s in %s"
+                      % (self.tunnel_key, self.name))
+        else:
+            self.tunnel_key = 0
+            vlog.warn("invalid tunnel key for %s, using 0" % self.name)
+
+        ovs_vsctl("--may-exist add-br %s" % self.short_name)
+        ovs_vsctl("br-set-external-id %s vtep_logical_switch true"
+                  % self.short_name)
+        ovs_vsctl("br-set-external-id %s logical_switch_name %s"
+                  % (self.short_name, self.name))
+
+        vtep_ctl("clear-local-macs %s" % self.name)
+        vtep_ctl("add-mcast-local %s unknown-dst %s" % (self.name, Tunnel_Ip))
+
+        ovs_ofctl("del-flows %s" % self.short_name)
+        ovs_ofctl("add-flow %s priority=0,action=drop" % self.short_name)
+
+    def update_flood(self):
+        flood_ports = self.ports.values()
+
+        # Traffic flowing from one 'unknown-dst' should not be flooded to
+        # port belonging to another 'unknown-dst'.
+        for tunnel in self.unknown_dsts:
+            port_no = self.tunnels[tunnel][0]
+            ovs_ofctl("add-flow %s table=1,priority=1,in_port=%s,action=%s"
+                        % (self.short_name, port_no, ",".join(flood_ports)))
+
+        # Traffic coming from a VTEP physical port should only be flooded to
+        # one 'unknown-dst' and to all other physical ports that belong to that
+        # VTEP device and this logical switch.
+        for tunnel in self.unknown_dsts:
+            port_no = self.tunnels[tunnel][0]
+            flood_ports.append(port_no)
+            break
+
+        ovs_ofctl("add-flow %s table=1,priority=0,action=%s"
+                  % (self.short_name, ",".join(flood_ports)))
+
+    def add_lbinding(self, lbinding):
+        vlog.info("adding %s binding to %s" % (lbinding, self.name))
+        port_no = ovs_vsctl("get Interface %s ofport" % lbinding)
+        self.ports[lbinding] = port_no
+        ovs_ofctl("add-flow %s in_port=%s,action=learn(table=1,"
+                  "priority=1000,idle_timeout=15,cookie=0x5000,"
+                  "NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],"
+                  "output:NXM_OF_IN_PORT[]),resubmit(,1)"
+                  % (self.short_name, port_no))
+
+        self.update_flood()
+
+    def del_lbinding(self, lbinding):
+        vlog.info("removing %s binding from %s" % (lbinding, self.name))
+        port_no = self.ports[lbinding]
+        ovs_ofctl("del-flows %s in_port=%s" % (self.short_name, port_no));
+        del self.ports[lbinding]
+        self.update_flood()
+
+    def add_tunnel(self, tunnel):
+        global tun_id
+        vlog.info("adding tunnel %s" % tunnel)
+        encap, ip = tunnel.split("/")
+
+        if encap != "vxlan_over_ipv4":
+            vlog.warn("unsupported tunnel format %s" % encap)
+            return
+
+        tun_id += 1
+        tun_name = "vx" + str(tun_id)
+
+        ovs_vsctl("add-port %s %s -- set Interface %s type=vxlan "
+                  "options:key=%s options:remote_ip=%s"
+                  % (self.short_name, tun_name, tun_name, self.tunnel_key, ip))
+
+        for i in range(10):
+            port_no = ovs_vsctl("get Interface %s ofport" % tun_name)
+            if port_no != "-1":
+                break
+            elif i == 9:
+                vlog.warn("couldn't create tunnel %s" % tunnel)
+                ovs_vsctl("del-port %s %s" % (self.short_name, tun_name))
+                return
+
+            # Give the system a moment to allocate the port number
+            time.sleep(0.5)
+
+        self.tunnels[tunnel] = (port_no, tun_name)
+
+        ovs_ofctl("add-flow %s table=0,priority=1000,in_port=%s,"
+                  "actions=resubmit(,1)"
+                  % (self.short_name, port_no))
+
+    def del_tunnel(self, tunnel):
+        vlog.info("removing tunnel %s" % tunnel)
+
+        port_no, tun_name = self.tunnels[tunnel]
+        ovs_ofctl("del-flows %s table=0,in_port=%s"
+                    % (self.short_name, port_no))
+        ovs_vsctl("del-port %s %s" % (self.short_name, tun_name))
+
+        del self.tunnels[tunnel]
+
+    def update_local_macs(self):
+        flows = ovs_ofctl("dump-flows %s cookie=0x5000/-1,table=1"
+                          % self.short_name).splitlines()
+        macs = set()
+        for f in flows:
+            mac = re.split(r'.*dl_dst=(.*) .*', f)
+            if len(mac) == 3:
+                macs.add(mac[1])
+
+        for mac in macs.difference(self.local_macs):
+            vlog.info("adding local ucast %s to %s" % (mac, self.name))
+            vtep_ctl("add-ucast-local %s %s %s" % (self.name, mac, Tunnel_Ip))
+
+        for mac in self.local_macs.difference(macs):
+            vlog.info("removing local ucast %s from %s" % (mac, self.name))
+            vtep_ctl("del-ucast-local %s %s" % (self.name, mac))
+
+        self.local_macs = macs
+
+    def add_remote_mac(self, mac, tunnel):
+        port_no = self.tunnels.get(tunnel, (0,""))[0]
+        if not port_no:
+            return
+
+        ovs_ofctl("add-flow %s table=1,priority=1000,dl_dst=%s,action=%s"
+                  % (self.short_name, mac, port_no))
+
+    def del_remote_mac(self, mac):
+        ovs_ofctl("del-flows %s table=1,dl_dst=%s" % (self.short_name, mac))
+
+    def update_remote_macs(self):
+        remote_macs = {}
+        unknown_dsts = set()
+        tunnels = set()
+        parse_ucast = True
+
+        mac_list = vtep_ctl("list-remote-macs %s" % self.name).splitlines()
+        for line in mac_list:
+            if (line.find("mcast-mac-remote") != -1):
+                parse_ucast = False
+                continue
+
+            entry = re.split(r'  (.*) -> (.*)', line)
+            if len(entry) != 4:
+                continue
+
+            if parse_ucast:
+                remote_macs[entry[1]] = entry[2]
+            else:
+                if entry[1] != "unknown-dst":
+                    continue
+
+                unknown_dsts.add(entry[2])
+
+            tunnels.add(entry[2])
+
+        old_tunnels = set(self.tunnels.keys())
+
+        for tunnel in tunnels.difference(old_tunnels):
+            self.add_tunnel(tunnel)
+
+        for tunnel in old_tunnels.difference(tunnels):
+            self.del_tunnel(tunnel)
+
+        for mac in remote_macs.keys():
+            if (self.remote_macs.get(mac) != remote_macs[mac]):
+                self.add_remote_mac(mac, remote_macs[mac])
+
+        for mac in self.remote_macs.keys():
+            if not remote_macs.has_key(mac):
+                self.del_remote_mac(mac)
+
+        self.remote_macs = remote_macs
+
+        if (self.unknown_dsts != unknown_dsts):
+            self.unknown_dsts = unknown_dsts
+            self.update_flood()
+
+    def update_stats(self):
+        # Map Open_vSwitch's "interface:statistics" to columns of
+        # vtep's logical_binding_stats. Since we are using the 'interface' from
+        # the logical switch to collect stats, packets transmitted from it
+        # is received in the physical switch and vice versa.
+        stats_map = {'tx_packets':'packets_to_local',
+                    'tx_bytes':'bytes_to_local',
+                    'rx_packets':'packets_from_local',
+                     'rx_bytes':'bytes_from_local'}
+
+        # Go through all the logical switch's interfaces that end with "-l"
+        # and copy the statistics to logical_binding_stats.
+        for interface in self.ports.iterkeys():
+            if not interface.endswith("-l"):
+                continue
+            vlan, pp_name, logical = interface.split("-")
+            uuid = vtep_ctl("get physical_port %s vlan_stats:%s"
+                            % (pp_name, vlan))
+            if not uuid:
+                continue
+
+            for (mapfrom, mapto) in stats_map.iteritems():
+                value = ovs_vsctl("get interface %s statistics:%s"
+                                % (interface, mapfrom)).strip('"')
+                vtep_ctl("set logical_binding_stats %s %s=%s"
+                        % (uuid, mapto, value))
+
+    def run(self):
+        self.update_local_macs()
+        self.update_remote_macs()
+        self.update_stats()
+
+def add_binding(ps_name, binding, ls):
+    vlog.info("adding binding %s" % binding)
+
+    vlan, pp_name = binding.split("-")
+    pbinding = binding+"-p"
+    lbinding = binding+"-l"
+
+    # Create a patch port that connects the VLAN+port to the lswitch.
+    # Do them as two separate calls so if one side already exists, the
+    # other side is created.
+    ovs_vsctl("add-port %s %s "
+              " -- set Interface %s type=patch options:peer=%s"
+              % (ps_name, pbinding, pbinding, lbinding))
+    ovs_vsctl("add-port %s %s "
+              " -- set Interface %s type=patch options:peer=%s"
+              % (ls.short_name, lbinding, lbinding, pbinding))
+
+    port_no = ovs_vsctl("get Interface %s ofport" % pp_name)
+    patch_no = ovs_vsctl("get Interface %s ofport" % pbinding)
+    vlan_ = vlan.lstrip('0')
+    if vlan_:
+        ovs_ofctl("add-flow %s in_port=%s,dl_vlan=%s,action=strip_vlan,%s"
+                  % (ps_name, port_no, vlan_, patch_no))
+        ovs_ofctl("add-flow %s in_port=%s,action=mod_vlan_vid:%s,%s"
+                  % (ps_name, patch_no, vlan_, port_no))
+    else:
+        ovs_ofctl("add-flow %s in_port=%s,action=%s"
+                  % (ps_name, port_no, patch_no))
+        ovs_ofctl("add-flow %s in_port=%s,action=%s"
+                  % (ps_name, patch_no, port_no))
+
+    # Create a logical_bindings_stats record.
+    if not vlan_:
+        vlan_ = "0"
+    vtep_ctl("set physical_port %s vlan_stats:%s=@stats --\
+            --id=@stats create logical_binding_stats packets_from_local=0"\
+            % (pp_name, vlan_))
+
+    ls.add_lbinding(lbinding)
+    Bindings[binding] = ls.name
+
+def del_binding(ps_name, binding, ls):
+    vlog.info("removing binding %s" % binding)
+
+    vlan, pp_name = binding.split("-")
+    pbinding = binding+"-p"
+    lbinding = binding+"-l"
+
+    port_no = ovs_vsctl("get Interface %s ofport" % pp_name)
+    patch_no = ovs_vsctl("get Interface %s ofport" % pbinding)
+    vlan_ = vlan.lstrip('0')
+    if vlan_:
+        ovs_ofctl("del-flows %s in_port=%s,dl_vlan=%s"
+                  % (ps_name, port_no, vlan_))
+        ovs_ofctl("del-flows %s in_port=%s" % (ps_name, patch_no))
+    else:
+        ovs_ofctl("del-flows %s in_port=%s" % (ps_name, port_no))
+        ovs_ofctl("del-flows %s in_port=%s" % (ps_name, patch_no))
+
+    ls.del_lbinding(lbinding)
+
+    # Destroy the patch port that connects the VLAN+port to the lswitch
+    ovs_vsctl("del-port %s %s -- del-port %s %s"
+              % (ps_name, pbinding, ls.short_name, lbinding))
+
+    # Remove the record that links vlan with stats in logical_binding_stats.
+    vtep_ctl("remove physical_port %s vlan_stats %s" % (pp_name, vlan))
+
+    del Bindings[binding]
+
+def handle_physical(ps_name):
+    # Gather physical ports except the patch ports we created
+    ovs_ports = ovs_vsctl("list-ports %s" % ps_name).split()
+    ovs_port_set = set([port for port in ovs_ports if port[-2:] != "-p"])
+
+    vtep_pp_set = set(vtep_ctl("list-ports %s" % ps_name).split())
+
+    for pp_name in ovs_port_set.difference(vtep_pp_set):
+        vlog.info("adding %s to %s" % (pp_name, ps_name))
+        vtep_ctl("add-port %s %s" % (ps_name, pp_name))
+
+    for pp_name in vtep_pp_set.difference(ovs_port_set):
+        vlog.info("deleting %s from %s" % (pp_name, ps_name))
+        vtep_ctl("del-port %s %s" % (ps_name, pp_name))
+
+    new_bindings = set()
+    for pp_name in vtep_pp_set:
+        binding_set = set(vtep_ctl("list-bindings %s %s"
+                                   % (ps_name, pp_name)).splitlines())
+
+        for b in binding_set:
+            vlan, ls_name = b.split()
+            if ls_name not in Lswitches:
+                Lswitches[ls_name] = Logical_Switch(ls_name)
+
+            binding = "%s-%s" % (vlan, pp_name)
+            ls = Lswitches[ls_name]
+            new_bindings.add(binding)
+
+            if Bindings.has_key(binding):
+                if Bindings[binding] == ls_name:
+                    continue
+                else:
+                    del_binding(ps_name, binding, Lswitches[Bindings[binding]])
+
+            add_binding(ps_name, binding, ls)
+
+
+    dead_bindings = set(Bindings.keys()).difference(new_bindings)
+    for binding in dead_bindings:
+        ls_name = Bindings[binding]
+        ls = Lswitches[ls_name]
+
+        del_binding(ps_name, binding, ls)
+
+        if not len(ls.ports):
+            ovs_vsctl("del-br %s" % Lswitches[ls_name].short_name)
+            del Lswitches[ls_name]
+
+def setup(ps_name):
+    br_list = ovs_vsctl("list-br").split()
+    if (ps_name not in br_list):
+        ovs.util.ovs_fatal(0, "couldn't find OVS bridge %s" % ps_name, vlog)
+
+    call_prog("vtep-ctl", ["set", "physical_switch", ps_name,
+                           'description="OVS VTEP Emulator"'])
+
+    tunnel_ips = vtep_ctl("get physical_switch %s tunnel_ips"
+                          % ps_name).strip('[]"').split(", ")
+    if len(tunnel_ips) != 1 or not tunnel_ips[0]:
+        ovs.util.ovs_fatal(0, "exactly one 'tunnel_ips' should be set", vlog)
+
+    global Tunnel_Ip
+    Tunnel_Ip = tunnel_ips[0]
+
+    ovs_ofctl("del-flows %s" % ps_name)
+
+    # Remove any logical bridges from the previous run
+    for br in br_list:
+        if ovs_vsctl("br-get-external-id %s vtep_logical_switch"
+                     % br) == "true":
+            # Remove the remote side of any logical switch
+            ovs_ports = ovs_vsctl("list-ports %s" % br).split()
+            for port in ovs_ports:
+                port_type = ovs_vsctl("get Interface %s type"
+                                      % port).strip('"')
+                if port_type != "patch":
+                    continue
+
+                peer = ovs_vsctl("get Interface %s options:peer"
+                                 % port).strip('"')
+                if (peer):
+                    ovs_vsctl("del-port %s" % peer)
+
+            ovs_vsctl("del-br %s" % br)
+
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument("ps_name", metavar="PS-NAME",
+                        help="Name of physical switch.")
+    parser.add_argument("--root-prefix", metavar="DIR",
+                        help="Use DIR as alternate root directory"
+                        " (for testing).")
+    parser.add_argument("--version", action="version",
+                        version="%s %s" % (ovs.util.PROGRAM_NAME, VERSION))
+
+    ovs.vlog.add_args(parser)
+    ovs.daemon.add_args(parser)
+    args = parser.parse_args()
+    ovs.vlog.handle_args(args)
+    ovs.daemon.handle_args(args)
+
+    global root_prefix
+    if args.root_prefix:
+        root_prefix = args.root_prefix
+
+    ps_name = args.ps_name
+
+    ovs.daemon.daemonize()
+
+    ovs.unixctl.command_register("exit", "", 0, 0, unixctl_exit, None)
+    error, unixctl = ovs.unixctl.server.UnixctlServer.create(None,
+                                                             version=VERSION)
+    if error:
+        ovs.util.ovs_fatal(error, "could not create unixctl server", vlog)
+
+    setup(ps_name)
+
+    while True:
+        unixctl.run()
+        if exiting:
+            break
+
+        handle_physical(ps_name)
+
+        for ls_name, ls in Lswitches.items():
+            ls.run()
+
+        poller = ovs.poller.Poller()
+        unixctl.wait(poller)
+        poller.timer_wait(1000)
+        poller.block()
+
+    unixctl.close()
+
+if __name__ == '__main__':
+    try:
+        main()
+    except SystemExit:
+        # Let system.exit() calls complete normally
+        raise
+    except:
+        vlog.exception("traceback")
+        sys.exit(ovs.daemon.RESTART_EXIT_CODE)
diff --git a/vtep/vtep-ctl.8.in b/vtep/vtep-ctl.8.in
new file mode 100644 (file)
index 0000000..57bfa65
--- /dev/null
@@ -0,0 +1,590 @@
+.\" -*- nroff -*-
+.de IQ
+.  br
+.  ns
+.  IP "\\$1"
+..
+.de ST
+.  PP
+.  RS -0.15in
+.  I "\\$1"
+.  RE
+..
+.TH vtep\-ctl 8 "March 2013" "Open vSwitch" "Open vSwitch Manual"
+.\" This program's name:
+.ds PN vtep\-ctl
+.\" SSL peer program's name:
+.ds SN ovsdb\-server
+.
+.SH NAME
+vtep\-ctl \- utility for querying and configuring a VTEP database
+.
+.SH SYNOPSIS
+\fBvtep\-ctl\fR [\fIoptions\fR] \fB\-\-\fR [\fIoptions\fR] \fIcommand
+\fR[\fIargs\fR] [\fB\-\-\fR [\fIoptions\fR] \fIcommand \fR[\fIargs\fR]]...
+.
+.SH DESCRIPTION
+The \fBvtep\-ctl\fR program configures a VTEP database.
+See \fBvtep\fR(5) for comprehensive documentation of
+the database schema.
+.PP
+\fBvtep\-ctl\fR connects to an \fBovsdb\-server\fR process that
+maintains a VTEP configuration database.  Using this connection, it
+queries and possibly applies changes to the database, depending on the
+supplied commands.
+.PP
+\fBvtep\-ctl\fR can perform any number of commands in a single run,
+implemented as a single atomic transaction against the database.
+.PP
+The \fBvtep\-ctl\fR command line begins with global options (see
+\fBOPTIONS\fR below for details).  The global options are followed by
+one or more commands.  Each command should begin with \fB\-\-\fR by
+itself as a command-line argument, to separate it from the following
+commands.  (The \fB\-\-\fR before the first command is optional.)  The
+command itself starts with command-specific options, if any, followed by
+the command name and any arguments.  See \fBEXAMPLES\fR below for syntax
+examples.
+.
+.SH OPTIONS
+.
+The following options affect the behavior \fBvtep\-ctl\fR as a whole.
+Some individual commands also accept their own options, which are
+given just before the command name.  If the first command on the
+command line has options, then those options must be separated from
+the global options by \fB\-\-\fR.
+.
+.IP "\fB\-\-db=\fIserver\fR"
+Sets \fIserver\fR as the database server that \fBvtep\-ctl\fR
+contacts to query or modify configuration.  The default is
+\fBunix:@RUNDIR@/db.sock\fR.  \fIserver\fR must take one of the
+following forms:
+.RS
+.so ovsdb/remote-active.man
+.so ovsdb/remote-passive.man
+.RE
+.
+.IP "\fB\-\-no\-syslog\fR"
+By default, \fBvtep\-ctl\fR logs its arguments and the details of any
+changes that it makes to the system log.  This option disables this
+logging.
+.IP
+This option is equivalent to \fB\-\-verbose=vtep_ctl:syslog:warn\fR.
+.
+.IP "\fB\-\-oneline\fR"
+Modifies the output format so that the output for each command is printed
+on a single line.  New-line characters that would otherwise separate
+lines are printed as \fB\\n\fR, and any instances of \fB\\\fR that
+would otherwise appear in the output are doubled.
+Prints a blank line for each command that has no output.
+This option does not affect the formatting of output from the
+\fBlist\fR or \fBfind\fR commands; see \fBTable Formatting Options\fR
+below.
+.
+.IP "\fB\-\-dry\-run\fR"
+Prevents \fBvtep\-ctl\fR from actually modifying the database.
+.
+.IP "\fB\-t \fIsecs\fR"
+.IQ "\fB\-\-timeout=\fIsecs\fR"
+By default, or with a \fIsecs\fR of \fB0\fR, \fBvtep\-ctl\fR waits
+forever for a response from the database.  This option limits runtime
+to approximately \fIsecs\fR seconds.  If the timeout expires,
+\fBvtep\-ctl\fR will exit with a \fBSIGALRM\fR signal.  (A timeout
+would normally happen only if the database cannot be contacted, or if
+the system is overloaded.)
+.
+.SS "Table Formatting Options"
+These options control the format of output from the \fBlist\fR and
+\fBfind\fR commands.
+.so lib/table.man
+.
+.SS "Public Key Infrastructure Options"
+.so lib/ssl.man
+.so lib/ssl-bootstrap.man
+.so lib/ssl-peer-ca-cert.man
+.so lib/vlog.man
+.
+.SH COMMANDS
+The commands implemented by \fBvtep\-ctl\fR are described in the
+sections below.
+.
+.SS "Physical Switch Commands"
+These commands examine and manipulate physical switches.
+.
+.IP "[\fB\-\-may\-exist\fR] \fBadd\-ps \fIpswitch\fR"
+Creates a new physical switch named \fIpswitch\fR.  Initially the switch
+will have no ports.
+.IP
+Without \fB\-\-may\-exist\fR, attempting to create a switch that
+exists is an error.  With \fB\-\-may\-exist\fR, this command does
+nothing if \fIpswitch\fR already exists.
+.
+.IP "[\fB\-\-if\-exists\fR] \fBdel\-ps \fIpswitch\fR"
+Deletes \fIpswitch\fR and all of its ports.
+.IP
+Without \fB\-\-if\-exists\fR, attempting to delete a switch that does
+not exist is an error.  With \fB\-\-if\-exists\fR, attempting to
+delete a switch that does not exist has no effect.
+.
+.IP "\fBlist\-ps\fR"
+Lists all existing physical switches on standard output, one per line.
+.
+.IP "\fBps\-exists \fIpswitch\fR"
+Tests whether \fIpswitch\fR exists.  If so, \fBvtep\-ctl\fR exits
+successfully with exit code 0.  If not, \fBvtep\-ctl\fR exits
+unsuccessfully with exit code 2.
+.
+.SS "Port Commands"
+.
+These commands examine and manipulate VTEP physical ports.
+.
+.IP "\fBlist\-ports \fIpswitch\fR"
+Lists all of the ports within \fIpswitch\fR on standard output, one per
+line.
+.
+.IP "[\fB\-\-may\-exist\fR] \fBadd\-port \fIpswitch port\fR"
+Creates on \fIpswitch\fR a new port named \fIport\fR from the network
+device of the same name.
+.IP
+Without \fB\-\-may\-exist\fR, attempting to create a port that exists
+is an error.  With \fB\-\-may\-exist\fR, this command does nothing if
+\fIport\fR already exists on \fIpswitch\fR.
+.
+.IP "[\fB\-\-if\-exists\fR] \fBdel\-port \fR[\fIpswitch\fR] \fIport\fR"
+Deletes \fIport\fR.  If \fIpswitch\fR is omitted, \fIport\fR is removed
+from whatever switch contains it; if \fIpswitch\fR is specified, it
+must be the switch that contains \fIport\fR.
+.IP
+Without \fB\-\-if\-exists\fR, attempting to delete a port that does
+not exist is an error.  With \fB\-\-if\-exists\fR, attempting to
+delete a port that does not exist has no effect.
+.
+.SS "Logical Switch Commands"
+These commands examine and manipulate logical switches.
+.
+.IP "[\fB\-\-may\-exist\fR] \fBadd\-ls \fIlswitch\fR"
+Creates a new logical switch named \fIlswitch\fR.  Initially the switch
+will have no locator bindings.
+.IP
+Without \fB\-\-may\-exist\fR, attempting to create a switch that
+exists is an error.  With \fB\-\-may\-exist\fR, this command does
+nothing if \fIlswitch\fR already exists.
+.
+.IP "[\fB\-\-if\-exists\fR] \fBdel\-ls \fIlswitch\fR"
+Deletes \fIlswitch\fR.
+.IP
+Without \fB\-\-if\-exists\fR, attempting to delete a switch that does
+not exist is an error.  With \fB\-\-if\-exists\fR, attempting to
+delete a switch that does not exist has no effect.
+.
+.IP "\fBlist\-ls\fR"
+Lists all existing logical switches on standard output, one per line.
+.
+.IP "\fBls\-exists \fIlswitch\fR"
+Tests whether \fIlswitch\fR exists.  If so, \fBvtep\-ctl\fR exits
+successfully with exit code 0.  If not, \fBvtep\-ctl\fR exits
+unsuccessfully with exit code 2.
+.
+.IP "\fBbind\-ls \fIpswitch port vlan lswitch\fR"
+Bind logical switch \fIlswitch\fR to the \fIport\fR/\fIvlan\fR
+combination on the physical switch \fIpswitch\fR.
+.
+.IP "\fBunbind\-ls \fIpswitch port vlan\fR"
+Remove the logical switch binding from the \fIport\fR/\fIvlan\fR
+combination on the physical switch \fIpswitch\fR.
+.
+.IP "\fBlist\-bindings \fIpswitch port\fR"
+List the logical switch bindings for \fIport\fR on the physical switch
+\fIpswitch\fR.
+.
+.SS "Local MAC Binding Commands"
+These commands examine and manipulate local MAC bindings for the logical
+switch.  The local maps are written by the VTEP to refer to MACs it has
+learned on its physical ports.
+.
+.IP "\fBadd\-ucast\-local \fIlswitch mac\fR [\fIencap\fR] \fIip\fR"
+Map the unicast Ethernet address \fImac\fR to the physical location
+\fIip\fR using encapsulation \fIencap\fR on \fIlswitch\fR.  If
+\fIencap\fR is not specified, the default is "vxlan_over_ipv4".  The
+local mappings are used by the VTEP to refer to MACs learned on its
+physical ports.
+.
+.IP "\fBdel\-ucast\-local \fIlswitch mac\fR"
+Remove the local unicast Ethernet address \fImac\fR map from
+\fIlswitch\fR.  The local mappings are used by the VTEP to refer to MACs
+learned on its physical ports.
+.
+.IP "\fBadd\-mcast\-local \fIlswitch mac\fR [\fIencap\fR] \fIip\fR"
+Add physical location \fIip\fR using encapsulation \fIencap\fR to the
+local mac binding table for multicast Ethernet address \fImac\fR on
+\fIlswitch\fR.  If \fIencap\fR is not specified, the default is
+"vxlan_over_ipv4".  The local mappings are used by the VTEP to refer to
+MACs learned on its physical ports.
+.
+.IP "\fBdel\-mcast\-local \fIlswitch mac\fR [\fIencap\fR] \fIip\fR"
+Remove physical location \fIip\fR using encapsulation \fIencap\fR from
+the local mac binding table for multicast Ethernet address \fImac\fR on
+\fIlswitch\fR.  If \fIencap\fR is not specified, the default is
+"vxlan_over_ipv4".  The local mappings are used by the VTEP to refer to
+MACs learned on its physical ports.
+.
+.IP "\fBclear\-local\-macs \fIlswitch\fR"
+Clear the local MAC bindings for \fIlswitch\fR.
+.
+.IP "\fBlist\-local\-macs \fIlswitch\fR"
+List the local MAC bindings for \fIlswitch\fR, one per line.
+.
+.SS "Remote MAC Binding Commands"
+These commands examine and manipulate local and remote MAC bindings for
+the logical switch.  The remote maps are written by the network
+virtualization controller to refer to MACs that it has learned.
+.
+.IP "\fBadd\-ucast\-remote \fIlswitch mac\fR [\fIencap\fR] \fIip\fR"
+Map the unicast Ethernet address \fImac\fR to the physical location
+\fIip\fR using encapsulation \fIencap\fR on \fIlswitch\fR.  If
+\fIencap\fR is not specified, the default is "vxlan_over_ipv4".  The
+remote mappings are used by the network virtualization platform to refer
+to MACs that it has learned.
+.
+.IP "\fBdel\-ucast\-remote \fIlswitch mac\fR"
+Remove the remote unicast Ethernet address \fImac\fR map from
+\fIlswitch\fR.  The remote mappings are used by the network
+virtualization platform to refer to MACs that it has learned.
+.
+.IP "\fBadd\-mcast\-remote \fIlswitch mac\fR [\fIencap\fR] \fIip\fR"
+Add physical location \fIip\fR using encapsulation \fIencap\fR to the
+remote mac binding table for multicast Ethernet address \fImac\fR on
+\fIlswitch\fR.  If \fIencap\fR is not specified, the default is
+"vxlan_over_ipv4".  The remote mappings are used by the network
+virtualization platform to refer to MACs that it has learned.
+.
+.IP "\fBdel\-mcast\-remote \fIlswitch mac\fR [\fIencap\fR] \fIip\fR"
+Remove physical location \fIip\fR using encapsulation \fIencap\fR from
+the remote mac binding table for multicast Ethernet address \fImac\fR on
+\fIlswitch\fR.  If \fIencap\fR is not specified, the default is
+"vxlan_over_ipv4".  The remote mappings are used by the network
+virtualization platform to refer to MACs that it has learned.
+.
+.IP "\fBclear\-remote\-macs \fIlswitch\fR"
+Clear the remote MAC bindings for \fIlswitch\fR.
+.
+.IP "\fBlist\-remote\-macs \fIlswitch\fR"
+List the remote MAC bindings for \fIlswitch\fR, one per line.
+.
+.SS "Manager Connectivity"
+.
+These commands manipulate the \fBmanagers\fR column in the \fBGlobal\fR
+table and rows in the \fBManagers\fR table.  When \fBovsdb\-server\fR is
+configured to use the \fBmanagers\fR column for OVSDB connections (as
+described in \fBINSTALL.Linux\fR and in the startup scripts provided
+with Open vSwitch), this allows the administrator to use \fBvtep\-ctl\fR
+to configure database connections.
+.
+.IP "\fBget\-manager\fR"
+Prints the configured manager(s).
+.
+.IP "\fBdel\-manager\fR"
+Deletes the configured manager(s).
+.
+.IP "\fBset\-manager\fR \fItarget\fR\&..."
+Sets the configured manager target or targets.  Each \fItarget\fR may
+use any of the following forms:
+.
+.RS
+.so ovsdb/remote-active.man
+.so ovsdb/remote-passive.man
+.RE
+.
+.SS "Database Commands"
+.
+These commands query and modify the contents of \fBovsdb\fR tables.
+They are a slight abstraction of the \fBovsdb\fR interface and as such
+they operate at a lower level than other \fBvtep\-ctl\fR commands.
+.PP
+.ST "Identifying Tables, Records, and Columns"
+.PP
+Each of these commands has a \fItable\fR parameter to identify a table
+within the database.  Many of them also take a \fIrecord\fR parameter
+that identifies a particular record within a table.  The \fIrecord\fR
+parameter may be the UUID for a record, and many tables offer
+additional ways to identify records.  Some commands also take
+\fIcolumn\fR parameters that identify a particular field within the
+records in a table.
+.PP
+The following tables are currently defined:
+.IP "\fBGlobal\fR"
+Top-level configuration for a hardware VTEP.  This table contains
+exactly one record, identified by specifying \fB.\fR as the record name.
+.IP "\fBManager\fR"
+Configuration for an OVSDB connection.  Records may be identified
+by target (e.g. \fBtcp:1.2.3.4\fR).
+.IP "\fBPhysical_Switch\fR"
+A physical switch that implements a VTEP.  Records may be identified by
+physical switch name.
+.IP "\fBPhysical_Port\fR"
+A port within a physical switch.
+.IP "\fBLogical_Binding_Stats\fR"
+Reports statistics for the logical switch with which a VLAN on a
+physical port is associated.
+.IP "\fBLogical_Switch\fR"
+A logical Ethernet switch.  Records may be identified by logical switch
+name.
+.IP "\fBUcast_Macs_Local\fR"
+Mapping of locally discovered unicast MAC addresses to tunnels.
+.IP "\fBUcast_Macs_Remote\fR"
+Mapping of remotely programmed unicast MAC addresses to tunnels.
+.IP "\fBMcast_Macs_Local\fR"
+Mapping of locally discovered multicast MAC addresses to tunnels.
+.IP "\fBMcast_Macs_Remote\fR"
+Mapping of remotely programmed multicast MAC addresses to tunnels.
+.IP "\fBPhysical_Locator_Set\fR"
+A set of one or more physical locators.
+.IP "\fBPhysical_Locator\fR"
+Identifies an endpoint to which logical switch traffic may be
+encapsulated and forwarded.  Records may be identified by physical
+locator name.
+.PP
+Record names must be specified in full and with correct
+capitalization.  Names of tables and columns are not case-sensitive,
+and \fB\-\-\fR and \fB_\fR are treated interchangeably.  Unique
+abbreviations are acceptable, e.g. \fBman\fR or \fBm\fR is sufficient
+to identify the \fBManager\fR table.
+.
+.ST "Database Values"
+.PP
+Each column in the database accepts a fixed type of data.  The
+currently defined basic types, and their representations, are:
+.IP "integer"
+A decimal integer in the range \-2**63 to 2**63\-1, inclusive.
+.IP "real"
+A floating-point number.
+.IP "Boolean"
+True or false, written \fBtrue\fR or \fBfalse\fR, respectively.
+.IP "string"
+An arbitrary Unicode string, except that null bytes are not allowed.
+Quotes are optional for most strings that begin with an English letter
+or underscore and consist only of letters, underscores, hyphens, and
+periods.  However, \fBtrue\fR and \fBfalse\fR and strings that match
+the syntax of UUIDs (see below) must be enclosed in double quotes to
+distinguish them from other basic types.  When double quotes are used,
+the syntax is that of strings in JSON, e.g. backslashes may be used to
+escape special characters.  The empty string must be represented as a
+pair of double quotes (\fB""\fR).
+.IP "UUID"
+Either a universally unique identifier in the style of RFC 4122,
+e.g. \fBf81d4fae\-7dec\-11d0\-a765\-00a0c91e6bf6\fR, or an \fB@\fIname\fR
+defined by a \fBget\fR or \fBcreate\fR command within the same \fBvtep\-ctl\fR
+invocation.
+.PP
+Multiple values in a single column may be separated by spaces or a
+single comma.  When multiple values are present, duplicates are not
+allowed, and order is not important.  Conversely, some database
+columns can have an empty set of values, represented as \fB[]\fR, and
+square brackets may optionally enclose other non-empty sets or single
+values as well.
+.PP
+A few database columns are ``maps'' of key-value pairs, where the key
+and the value are each some fixed database type.  These are specified
+in the form \fIkey\fB=\fIvalue\fR, where \fIkey\fR and \fIvalue\fR
+follow the syntax for the column's key type and value type,
+respectively.  When multiple pairs are present (separated by spaces or
+a comma), duplicate keys are not allowed, and again the order is not
+important.  Duplicate values are allowed.  An empty map is represented
+as \fB{}\fR.  Curly braces may optionally enclose non-empty maps as
+well (but use quotes to prevent the shell from expanding
+\fBother-config={0=x,1=y}\fR into \fBother-config=0=x
+other-config=1=y\fR, which may not have the desired effect).
+.
+.ST "Database Command Syntax"
+.IP "[\fB\-\-columns=\fIcolumn\fR[\fB,\fIcolumn\fR]...] \fBlist \fItable \fR[\fIrecord\fR]..."
+Lists the data in each specified \fIrecord\fR.  If no
+records are specified, lists all the records in \fItable\fR.
+.IP
+If \fB\-\-columns\fR is specified, only the requested columns are
+listed, in the specified order.  Otherwise, all columns are listed, in
+alphabetical order by column name.
+.
+.IP "[\fB\-\-columns=\fIcolumn\fR[\fB,\fIcolumn\fR]...] \fBfind \fItable \fR[\fIcolumn\fR[\fB:\fIkey\fR]\fB=\fIvalue\fR]..."
+Lists the data in each record in \fItable\fR whose \fIcolumn\fR equals
+\fIvalue\fR or, if \fIkey\fR is specified, whose \fIcolumn\fR contains
+a \fIkey\fR with the specified \fIvalue\fR.  The following operators
+may be used where \fB=\fR is written in the syntax summary:
+.RS
+.IP "\fB= != < > <= >=\fR"
+Selects records in which \fIcolumn\fR[\fB:\fIkey\fR] equals, does not
+equal, is less than, is greater than, is less than or equal to, or is
+greater than or equal to \fIvalue\fR, respectively.
+.IP
+Consider \fIcolumn\fR[\fB:\fIkey\fR] and \fIvalue\fR as sets of
+elements.  Identical sets are considered equal.  Otherwise, if the
+sets have different numbers of elements, then the set with more
+elements is considered to be larger.  Otherwise, consider a element
+from each set pairwise, in increasing order within each set.  The
+first pair that differs determines the result.  (For a column that
+contains key-value pairs, first all the keys are compared, and values
+are considered only if the two sets contain identical keys.)
+.IP "\fB{=} {!=}\fR"
+Test for set equality or inequality, respectively.
+.IP "\fB{<=}\fR"
+Selects records in which \fIcolumn\fR[\fB:\fIkey\fR] is a subset of
+\fIvalue\fR.  For example, \fBflood-vlans{<=}1,2\fR selects records in
+which the \fBflood-vlans\fR column is the empty set or contains 1 or 2
+or both.
+.IP "\fB{<}\fR"
+Selects records in which \fIcolumn\fR[\fB:\fIkey\fR] is a proper
+subset of \fIvalue\fR.  For example, \fBflood-vlans{<}1,2\fR selects
+records in which the \fBflood-vlans\fR column is the empty set or
+contains 1 or 2 but not both.
+.IP "\fB{>=} {>}\fR"
+Same as \fB{<=}\fR and \fB{<}\fR, respectively, except that the
+relationship is reversed.  For example, \fBflood-vlans{>=}1,2\fR
+selects records in which the \fBflood-vlans\fR column contains both 1
+and 2.
+.RE
+.IP
+For arithmetic operators (\fB= != < > <= >=\fR), when \fIkey\fR is
+specified but a particular record's \fIcolumn\fR does not contain
+\fIkey\fR, the record is always omitted from the results.  Thus, the
+condition \fBother-config:mtu!=1500\fR matches records that have a
+\fBmtu\fR key whose value is not 1500, but not those that lack an
+\fBmtu\fR key.
+.IP
+For the set operators, when \fIkey\fR is specified but a particular
+record's \fIcolumn\fR does not contain \fIkey\fR, the comparison is
+done against an empty set.  Thus, the condition
+\fBother-config:mtu{!=}1500\fR matches records that have a \fBmtu\fR
+key whose value is not 1500 and those that lack an \fBmtu\fR key.
+.IP
+Don't forget to escape \fB<\fR or \fB>\fR from interpretation by the
+shell.
+.IP
+If \fB\-\-columns\fR is specified, only the requested columns are
+listed, in the specified order.  Otherwise all columns are listed, in
+alphabetical order by column name.
+.IP
+The UUIDs shown for rows created in the same \fBvtep\-ctl\fR
+invocation will be wrong.
+.
+.IP "[\fB\-\-id=@\fIname\fR] [\fB\-\-if\-exists\fR] \fBget \fItable record \fR[\fIcolumn\fR[\fB:\fIkey\fR]]..."
+Prints the value of each specified \fIcolumn\fR in the given
+\fIrecord\fR in \fItable\fR.  For map columns, a \fIkey\fR may
+optionally be specified, in which case the value associated with
+\fIkey\fR in the column is printed, instead of the entire map.
+.IP
+For a map column, without \fB\-\-if\-exists\fR it is an error if
+\fIkey\fR does not exist; with it, a blank line is printed.  If
+\fIcolumn\fR is not a map column or if \fIkey\fR is not specified,
+\fB\-\-if\-exists\fR has no effect.
+.IP
+If \fB@\fIname\fR is specified, then the UUID for \fIrecord\fR may be
+referred to by that name later in the same \fBvtep\-ctl\fR
+invocation in contexts where a UUID is expected.
+.IP
+Both \fB\-\-id\fR and the \fIcolumn\fR arguments are optional, but
+usually at least one or the other should be specified.  If both are
+omitted, then \fBget\fR has no effect except to verify that
+\fIrecord\fR exists in \fItable\fR.
+.
+.IP "\fBset \fItable record column\fR[\fB:\fIkey\fR]\fB=\fIvalue\fR..."
+Sets the value of each specified \fIcolumn\fR in the given
+\fIrecord\fR in \fItable\fR to \fIvalue\fR.  For map columns, a
+\fIkey\fR may optionally be specified, in which case the value
+associated with \fIkey\fR in that column is changed (or added, if none
+exists), instead of the entire map.
+.
+.IP "\fBadd \fItable record column \fR[\fIkey\fB=\fR]\fIvalue\fR..."
+Adds the specified value or key-value pair to \fIcolumn\fR in
+\fIrecord\fR in \fItable\fR.  If \fIcolumn\fR is a map, then \fIkey\fR
+is required, otherwise it is prohibited.  If \fIkey\fR already exists
+in a map column, then the current \fIvalue\fR is not replaced (use the
+\fBset\fR command to replace an existing value).
+.
+.IP "\fBremove \fItable record column \fR\fIvalue\fR..."
+.IQ "\fBremove \fItable record column \fR\fIkey\fR..."
+.IQ "\fBremove \fItable record column \fR\fIkey\fB=\fR\fIvalue\fR..."
+Removes the specified values or key-value pairs from \fIcolumn\fR in
+\fIrecord\fR in \fItable\fR.  The first form applies to columns that
+are not maps: each specified \fIvalue\fR is removed from the column.
+The second and third forms apply to map columns: if only a \fIkey\fR
+is specified, then any key-value pair with the given \fIkey\fR is
+removed, regardless of its value; if a \fIvalue\fR is given then a
+pair is removed only if both key and value match.
+.IP
+It is not an error if the column does not contain the specified key or
+value or pair.
+.
+.IP "\fBclear\fR \fItable record column\fR..."
+Sets each \fIcolumn\fR in \fIrecord\fR in \fItable\fR to the empty set
+or empty map, as appropriate.  This command applies only to columns
+that are allowed to be empty.
+.
+.IP "[\fB\-\-id=@\fIname\fR] \fBcreate\fR \fItable column\fR[\fB:\fIkey\fR]\fB=\fIvalue\fR..."
+Creates a new record in \fItable\fR and sets the initial values of
+each \fIcolumn\fR.  Columns not explicitly set will receive their
+default values.  Outputs the UUID of the new row.
+.IP
+If \fB@\fIname\fR is specified, then the UUID for the new row may be
+referred to by that name elsewhere in the same \fBvtep\-ctl\fR
+invocation in contexts where a UUID is expected.  Such references may
+precede or follow the \fBcreate\fR command.
+.IP
+Records in the Open vSwitch database are significant only when they
+can be reached directly or indirectly from the \fBOpen_vSwitch\fR
+table.  Except for records in the \fBQoS\fR or \fBQueue\fR tables,
+records that are not reachable from the \fBOpen_vSwitch\fR table are
+automatically deleted from the database.  This deletion happens
+immediately, without waiting for additional \fBvtep\-ctl\fR commands
+or other database activity.  Thus, a \fBcreate\fR command must
+generally be accompanied by additional commands \fIwithin the same
+\fBvtep\-ctl\fI invocation\fR to add a chain of references to the
+newly created record from the top-level \fBOpen_vSwitch\fR record.
+The \fBEXAMPLES\fR section gives some examples that show how to do
+this.
+.
+.IP "\fR[\fB\-\-if\-exists\fR] \fBdestroy \fItable record\fR..."
+Deletes each specified \fIrecord\fR from \fItable\fR.  Unless
+\fB\-\-if\-exists\fR is specified, each \fIrecord\fRs must exist.
+.IP "\fB\-\-all destroy \fItable\fR"
+Deletes all records from the \fItable\fR.
+.IP
+The \fBdestroy\fR command is only useful for records in the \fBQoS\fR
+or \fBQueue\fR tables.  Records in other tables are automatically
+deleted from the database when they become unreachable from the
+\fBOpen_vSwitch\fR table.  This means that deleting the last reference
+to a record is sufficient for deleting the record itself.  For records
+in these tables, \fBdestroy\fR is silently ignored.  See the
+\fBEXAMPLES\fR section below for more information.
+.
+.IP "\fBwait\-until \fItable record \fR[\fIcolumn\fR[\fB:\fIkey\fR]\fB=\fIvalue\fR]..."
+Waits until \fItable\fR contains a record named \fIrecord\fR whose
+\fIcolumn\fR equals \fIvalue\fR or, if \fIkey\fR is specified, whose
+\fIcolumn\fR contains a \fIkey\fR with the specified \fIvalue\fR.  Any
+of the operators \fB!=\fR, \fB<\fR, \fB>\fR, \fB<=\fR, or \fB>=\fR may
+be substituted for \fB=\fR to test for inequality, less than, greater
+than, less than or equal to, or greater than or equal to,
+respectively.  (Don't forget to escape \fB<\fR or \fB>\fR from
+interpretation by the shell.)
+.IP
+If no \fIcolumn\fR[\fB:\fIkey\fR]\fB=\fIvalue\fR arguments are given,
+this command waits only until \fIrecord\fR exists.  If more than one
+such argument is given, the command waits until all of them are
+satisfied.
+.IP
+Consider specifying \fB\-\-timeout=0\fR along with
+\fB\-\-wait\-until\fR, to prevent \fBvtep\-ctl\fR from terminating
+after waiting only at most 5 seconds.
+.IP "\fBcomment \fR[\fIarg\fR]..."
+This command has no effect on behavior, but any database log record
+created by the command will include the command and its arguments.
+.PP
+.SH "EXIT STATUS"
+.IP "0"
+Successful program execution.
+.IP "1"
+Usage, syntax, or configuration file error.
+.IP "2"
+The \fIswitch\fR argument to \fBps\-exists\fR specified the name of a
+physical switch that does not exist.
+.SH "SEE ALSO"
+.
+.BR ovsdb\-server (1),
+.BR vtep (5).
diff --git a/vtep/vtep-ctl.c b/vtep/vtep-ctl.c
new file mode 100644 (file)
index 0000000..7b904f6
--- /dev/null
@@ -0,0 +1,3890 @@
+/*
+ * Copyright (c) 2009, 2010, 2011, 2012 Nicira, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <float.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "command-line.h"
+#include "compiler.h"
+#include "dirs.h"
+#include "dynamic-string.h"
+#include "hash.h"
+#include "json.h"
+#include "ovsdb-data.h"
+#include "ovsdb-idl.h"
+#include "poll-loop.h"
+#include "process.h"
+#include "stream.h"
+#include "stream-ssl.h"
+#include "smap.h"
+#include "sset.h"
+#include "svec.h"
+#include "lib/vtep-idl.h"
+#include "table.h"
+#include "timeval.h"
+#include "util.h"
+#include "vconn.h"
+#include "vlog.h"
+
+VLOG_DEFINE_THIS_MODULE(vtep_ctl);
+
+/* vtep_ctl_fatal() also logs the error, so it is preferred in this file. */
+#define ovs_fatal please_use_vtep_ctl_fatal_instead_of_ovs_fatal
+
+struct vtep_ctl_context;
+
+/* A command supported by vtep-ctl. */
+struct vtep_ctl_command_syntax {
+    const char *name;           /* e.g. "add-ps" */
+    int min_args;               /* Min number of arguments following name. */
+    int max_args;               /* Max number of arguments following name. */
+
+    /* If nonnull, calls ovsdb_idl_add_column() or ovsdb_idl_add_table() for
+     * each column or table in ctx->idl that it uses. */
+    void (*prerequisites)(struct vtep_ctl_context *ctx);
+
+    /* Does the actual work of the command and puts the command's output, if
+     * any, in ctx->output or ctx->table.
+     *
+     * Alternatively, if some prerequisite of the command is not met and the
+     * caller should wait for something to change and then retry, it may set
+     * ctx->try_again to true.  (Only the "wait-until" command currently does
+     * this.) */
+    void (*run)(struct vtep_ctl_context *ctx);
+
+    /* If nonnull, called after the transaction has been successfully
+     * committed.  ctx->output is the output from the "run" function, which
+     * this function may modify and otherwise postprocess as needed.  (Only the
+     * "create" command currently does any postprocessing.) */
+    void (*postprocess)(struct vtep_ctl_context *ctx);
+
+    /* A comma-separated list of supported options, e.g. "--a,--b", or the
+     * empty string if the command does not support any options. */
+    const char *options;
+    enum { RO, RW } mode;       /* Does this command modify the database? */
+};
+
+struct vtep_ctl_command {
+    /* Data that remains constant after initialization. */
+    const struct vtep_ctl_command_syntax *syntax;
+    int argc;
+    char **argv;
+    struct shash options;
+
+    /* Data modified by commands. */
+    struct ds output;
+    struct table *table;
+};
+
+/* --db: The database server to contact. */
+static const char *db;
+
+/* --oneline: Write each command's output as a single line? */
+static bool oneline;
+
+/* --dry-run: Do not commit any changes. */
+static bool dry_run;
+
+/* --timeout: Time to wait for a connection to 'db'. */
+static int timeout;
+
+/* Format for table output. */
+static struct table_style table_style = TABLE_STYLE_DEFAULT;
+
+/* All supported commands. */
+static const struct vtep_ctl_command_syntax all_commands[];
+
+/* The IDL we're using and the current transaction, if any.
+ * This is for use by vtep_ctl_exit() only, to allow it to clean up.
+ * Other code should use its context arguments. */
+static struct ovsdb_idl *the_idl;
+static struct ovsdb_idl_txn *the_idl_txn;
+
+static void vtep_ctl_exit(int status) NO_RETURN;
+static void vtep_ctl_fatal(const char *, ...) PRINTF_FORMAT(1, 2) NO_RETURN;
+static char *default_db(void);
+static void usage(void) NO_RETURN;
+static void parse_options(int argc, char *argv[], struct shash *local_options);
+static bool might_write_to_db(char **argv);
+
+static struct vtep_ctl_command *parse_commands(int argc, char *argv[],
+                                            struct shash *local_options,
+                                            size_t *n_commandsp);
+static void parse_command(int argc, char *argv[], struct shash *local_options,
+                          struct vtep_ctl_command *);
+static const struct vtep_ctl_command_syntax *find_command(const char *name);
+static void run_prerequisites(struct vtep_ctl_command[], size_t n_commands,
+                              struct ovsdb_idl *);
+static void do_vtep_ctl(const char *args, struct vtep_ctl_command *, size_t n,
+                     struct ovsdb_idl *);
+
+static const struct vtep_ctl_table_class *get_table(const char *table_name);
+static void set_column(const struct vtep_ctl_table_class *,
+                       const struct ovsdb_idl_row *, const char *arg,
+                       struct ovsdb_symbol_table *);
+
+static bool is_condition_satisfied(const struct vtep_ctl_table_class *,
+                                   const struct ovsdb_idl_row *,
+                                   const char *arg,
+                                   struct ovsdb_symbol_table *);
+
+static struct vtep_ctl_lswitch *find_lswitch(struct vtep_ctl_context *,
+                                             const char *name,
+                                             bool must_exist);
+
+int
+main(int argc, char *argv[])
+{
+    extern struct vlog_module VLM_reconnect;
+    struct ovsdb_idl *idl;
+    struct vtep_ctl_command *commands;
+    struct shash local_options;
+    unsigned int seqno;
+    size_t n_commands;
+    char *args;
+
+    set_program_name(argv[0]);
+    signal(SIGPIPE, SIG_IGN);
+    vlog_set_levels(NULL, VLF_CONSOLE, VLL_WARN);
+    vlog_set_levels(&VLM_reconnect, VLF_ANY_FACILITY, VLL_WARN);
+    vteprec_init();
+
+    /* Log our arguments.  This is often valuable for debugging systems. */
+    args = process_escape_args(argv);
+    VLOG(might_write_to_db(argv) ? VLL_INFO : VLL_DBG, "Called as %s", args);
+
+    /* Parse command line. */
+    shash_init(&local_options);
+    parse_options(argc, argv, &local_options);
+    commands = parse_commands(argc - optind, argv + optind, &local_options,
+                              &n_commands);
+
+    if (timeout) {
+        time_alarm(timeout);
+    }
+
+    /* Initialize IDL. */
+    idl = the_idl = ovsdb_idl_create(db, &vteprec_idl_class, false, false);
+    run_prerequisites(commands, n_commands, idl);
+
+    /* Execute the commands.
+     *
+     * 'seqno' is the database sequence number for which we last tried to
+     * execute our transaction.  There's no point in trying to commit more than
+     * once for any given sequence number, because if the transaction fails
+     * it's because the database changed and we need to obtain an up-to-date
+     * view of the database before we try the transaction again. */
+    seqno = ovsdb_idl_get_seqno(idl);
+    for (;;) {
+        ovsdb_idl_run(idl);
+
+        if (seqno != ovsdb_idl_get_seqno(idl)) {
+            seqno = ovsdb_idl_get_seqno(idl);
+            do_vtep_ctl(args, commands, n_commands, idl);
+        }
+
+        if (seqno == ovsdb_idl_get_seqno(idl)) {
+            ovsdb_idl_wait(idl);
+            poll_block();
+        }
+    }
+}
+
+static struct option *
+find_option(const char *name, struct option *options, size_t n_options)
+{
+    size_t i;
+
+    for (i = 0; i < n_options; i++) {
+        if (!strcmp(options[i].name, name)) {
+            return &options[i];
+        }
+    }
+    return NULL;
+}
+
+static struct option *
+add_option(struct option **optionsp, size_t *n_optionsp,
+           size_t *allocated_optionsp)
+{
+    if (*n_optionsp >= *allocated_optionsp) {
+        *optionsp = x2nrealloc(*optionsp, allocated_optionsp,
+                               sizeof **optionsp);
+    }
+    return &(*optionsp)[(*n_optionsp)++];
+}
+
+static void
+parse_options(int argc, char *argv[], struct shash *local_options)
+{
+    enum {
+        OPT_DB = UCHAR_MAX + 1,
+        OPT_ONELINE,
+        OPT_NO_SYSLOG,
+        OPT_DRY_RUN,
+        OPT_PEER_CA_CERT,
+        OPT_LOCAL,
+        VLOG_OPTION_ENUMS,
+        TABLE_OPTION_ENUMS
+    };
+    static const struct option global_long_options[] = {
+        {"db", required_argument, NULL, OPT_DB},
+        {"no-syslog", no_argument, NULL, OPT_NO_SYSLOG},
+        {"dry-run", no_argument, NULL, OPT_DRY_RUN},
+        {"oneline", no_argument, NULL, OPT_ONELINE},
+        {"timeout", required_argument, NULL, 't'},
+        {"help", no_argument, NULL, 'h'},
+        {"version", no_argument, NULL, 'V'},
+        VLOG_LONG_OPTIONS,
+        TABLE_LONG_OPTIONS,
+        STREAM_SSL_LONG_OPTIONS,
+        {"peer-ca-cert", required_argument, NULL, OPT_PEER_CA_CERT},
+        {NULL, 0, NULL, 0},
+    };
+    const int n_global_long_options = ARRAY_SIZE(global_long_options) - 1;
+    char *tmp, *short_options;
+
+    const struct vtep_ctl_command_syntax *p;
+    struct option *options, *o;
+    size_t allocated_options;
+    size_t n_options;
+    size_t i;
+
+    tmp = long_options_to_short_options(global_long_options);
+    short_options = xasprintf("+%s", tmp);
+    free(tmp);
+
+    /* We want to parse both global and command-specific options here, but
+     * getopt_long() isn't too convenient for the job.  We copy our global
+     * options into a dynamic array, then append all of the command-specific
+     * options. */
+    options = xmemdup(global_long_options, sizeof global_long_options);
+    allocated_options = ARRAY_SIZE(global_long_options);
+    n_options = n_global_long_options;
+    for (p = all_commands; p->name; p++) {
+        if (p->options[0]) {
+            char *save_ptr = NULL;
+            char *name;
+            char *s;
+
+            s = xstrdup(p->options);
+            for (name = strtok_r(s, ",", &save_ptr); name != NULL;
+                 name = strtok_r(NULL, ",", &save_ptr)) {
+                char *equals;
+                int has_arg;
+
+                ovs_assert(name[0] == '-' && name[1] == '-' && name[2]);
+                name += 2;
+
+                equals = strchr(name, '=');
+                if (equals) {
+                    has_arg = required_argument;
+                    *equals = '\0';
+                } else {
+                    has_arg = no_argument;
+                }
+
+                o = find_option(name, options, n_options);
+                if (o) {
+                    ovs_assert(o - options >= n_global_long_options);
+                    ovs_assert(o->has_arg == has_arg);
+                } else {
+                    o = add_option(&options, &n_options, &allocated_options);
+                    o->name = xstrdup(name);
+                    o->has_arg = has_arg;
+                    o->flag = NULL;
+                    o->val = OPT_LOCAL;
+                }
+            }
+
+            free(s);
+        }
+    }
+    o = add_option(&options, &n_options, &allocated_options);
+    memset(o, 0, sizeof *o);
+
+    table_style.format = TF_LIST;
+
+    for (;;) {
+        int idx;
+        int c;
+
+        c = getopt_long(argc, argv, short_options, options, &idx);
+        if (c == -1) {
+            break;
+        }
+
+        switch (c) {
+        case OPT_DB:
+            db = optarg;
+            break;
+
+        case OPT_ONELINE:
+            oneline = true;
+            break;
+
+        case OPT_NO_SYSLOG:
+            vlog_set_levels(&VLM_vtep_ctl, VLF_SYSLOG, VLL_WARN);
+            break;
+
+        case OPT_DRY_RUN:
+            dry_run = true;
+            break;
+
+        case OPT_LOCAL:
+            if (shash_find(local_options, options[idx].name)) {
+                vtep_ctl_fatal("'%s' option specified multiple times",
+                            options[idx].name);
+            }
+            shash_add_nocopy(local_options,
+                             xasprintf("--%s", options[idx].name),
+                             optarg ? xstrdup(optarg) : NULL);
+            break;
+
+        case 'h':
+            usage();
+
+        case 'V':
+            ovs_print_version(0, 0);
+            exit(EXIT_SUCCESS);
+
+        case 't':
+            timeout = strtoul(optarg, NULL, 10);
+            if (timeout < 0) {
+                vtep_ctl_fatal("value %s on -t or --timeout is invalid",
+                            optarg);
+            }
+            break;
+
+        VLOG_OPTION_HANDLERS
+        TABLE_OPTION_HANDLERS(&table_style)
+
+        STREAM_SSL_OPTION_HANDLERS
+
+        case OPT_PEER_CA_CERT:
+            stream_ssl_set_peer_ca_cert_file(optarg);
+            break;
+
+        case '?':
+            exit(EXIT_FAILURE);
+
+        default:
+            abort();
+        }
+    }
+    free(short_options);
+
+    if (!db) {
+        db = default_db();
+    }
+
+    for (i = n_global_long_options; options[i].name; i++) {
+        free(CONST_CAST(char *, options[i].name));
+    }
+    free(options);
+}
+
+static struct vtep_ctl_command *
+parse_commands(int argc, char *argv[], struct shash *local_options,
+               size_t *n_commandsp)
+{
+    struct vtep_ctl_command *commands;
+    size_t n_commands, allocated_commands;
+    int i, start;
+
+    commands = NULL;
+    n_commands = allocated_commands = 0;
+
+    for (start = i = 0; i <= argc; i++) {
+        if (i == argc || !strcmp(argv[i], "--")) {
+            if (i > start) {
+                if (n_commands >= allocated_commands) {
+                    struct vtep_ctl_command *c;
+
+                    commands = x2nrealloc(commands, &allocated_commands,
+                                          sizeof *commands);
+                    for (c = commands; c < &commands[n_commands]; c++) {
+                        shash_moved(&c->options);
+                    }
+                }
+                parse_command(i - start, &argv[start], local_options,
+                              &commands[n_commands++]);
+            } else if (!shash_is_empty(local_options)) {
+                vtep_ctl_fatal("missing command name (use --help for help)");
+            }
+            start = i + 1;
+        }
+    }
+    if (!n_commands) {
+        vtep_ctl_fatal("missing command name (use --help for help)");
+    }
+    *n_commandsp = n_commands;
+    return commands;
+}
+
+static void
+parse_command(int argc, char *argv[], struct shash *local_options,
+              struct vtep_ctl_command *command)
+{
+    const struct vtep_ctl_command_syntax *p;
+    struct shash_node *node;
+    int n_arg;
+    int i;
+
+    shash_init(&command->options);
+    shash_swap(local_options, &command->options);
+    for (i = 0; i < argc; i++) {
+        const char *option = argv[i];
+        const char *equals;
+        char *key, *value;
+
+        if (option[0] != '-') {
+            break;
+        }
+
+        equals = strchr(option, '=');
+        if (equals) {
+            key = xmemdup0(option, equals - option);
+            value = xstrdup(equals + 1);
+        } else {
+            key = xstrdup(option);
+            value = NULL;
+        }
+
+        if (shash_find(&command->options, key)) {
+            vtep_ctl_fatal("'%s' option specified multiple times", argv[i]);
+        }
+        shash_add_nocopy(&command->options, key, value);
+    }
+    if (i == argc) {
+        vtep_ctl_fatal("missing command name (use --help for help)");
+    }
+
+    p = find_command(argv[i]);
+    if (!p) {
+        vtep_ctl_fatal("unknown command '%s'; use --help for help", argv[i]);
+    }
+
+    SHASH_FOR_EACH (node, &command->options) {
+        const char *s = strstr(p->options, node->name);
+        int end = s ? s[strlen(node->name)] : EOF;
+
+        if (end != '=' && end != ',' && end != ' ' && end != '\0') {
+            vtep_ctl_fatal("'%s' command has no '%s' option",
+                        argv[i], node->name);
+        }
+        if ((end == '=') != (node->data != NULL)) {
+            if (end == '=') {
+                vtep_ctl_fatal("missing argument to '%s' option on '%s' "
+                            "command", node->name, argv[i]);
+            } else {
+                vtep_ctl_fatal("'%s' option on '%s' does not accept an "
+                            "argument", node->name, argv[i]);
+            }
+        }
+    }
+
+    n_arg = argc - i - 1;
+    if (n_arg < p->min_args) {
+        vtep_ctl_fatal("'%s' command requires at least %d arguments",
+                    p->name, p->min_args);
+    } else if (n_arg > p->max_args) {
+        int j;
+
+        for (j = i + 1; j < argc; j++) {
+            if (argv[j][0] == '-') {
+                vtep_ctl_fatal("'%s' command takes at most %d arguments "
+                            "(note that options must precede command "
+                            "names and follow a \"--\" argument)",
+                            p->name, p->max_args);
+            }
+        }
+
+        vtep_ctl_fatal("'%s' command takes at most %d arguments",
+                    p->name, p->max_args);
+    }
+
+    command->syntax = p;
+    command->argc = n_arg + 1;
+    command->argv = &argv[i];
+}
+
+/* Returns the "struct vtep_ctl_command_syntax" for a given command 'name', or a
+ * null pointer if there is none. */
+static const struct vtep_ctl_command_syntax *
+find_command(const char *name)
+{
+    static struct shash commands = SHASH_INITIALIZER(&commands);
+
+    if (shash_is_empty(&commands)) {
+        const struct vtep_ctl_command_syntax *p;
+
+        for (p = all_commands; p->name; p++) {
+            shash_add_assert(&commands, p->name, p);
+        }
+    }
+
+    return shash_find_data(&commands, name);
+}
+
+static void
+vtep_ctl_fatal(const char *format, ...)
+{
+    char *message;
+    va_list args;
+
+    va_start(args, format);
+    message = xvasprintf(format, args);
+    va_end(args);
+
+    vlog_set_levels(&VLM_vtep_ctl, VLF_CONSOLE, VLL_OFF);
+    VLOG_ERR("%s", message);
+    ovs_error(0, "%s", message);
+    vtep_ctl_exit(EXIT_FAILURE);
+}
+
+/* Frees the current transaction and the underlying IDL and then calls
+ * exit(status).
+ *
+ * Freeing the transaction and the IDL is not strictly necessary, but it makes
+ * for a clean memory leak report from valgrind in the normal case.  That makes
+ * it easier to notice real memory leaks. */
+static void
+vtep_ctl_exit(int status)
+{
+    if (the_idl_txn) {
+        ovsdb_idl_txn_abort(the_idl_txn);
+        ovsdb_idl_txn_destroy(the_idl_txn);
+    }
+    ovsdb_idl_destroy(the_idl);
+    exit(status);
+}
+
+static void
+usage(void)
+{
+    printf("\
+%s: VTEP configuration utility\n\
+usage: %s [OPTIONS] COMMAND [ARG...]\n\
+\n\
+Manager commands:\n\
+  get-manager                 print the managers\n\
+  del-manager                 delete the managers\n\
+  set-manager TARGET...       set the list of managers to TARGET...\n\
+\n\
+Physical Switch commands:\n\
+  add-ps PS                   create a new physical switch named PS\n\
+  del-ps PS                   delete PS and all of its ports\n\
+  list-ps                     print the names of all the physical switches\n\
+  ps-exists PS                exit 2 if PS does not exist\n\
+\n\
+Port commands:\n\
+  list-ports PS               print the names of all the ports on PS\n\
+  add-port PS PORT            add network device PORT to PS\n\
+  del-port PS PORT            delete PORT from PS\n\
+\n\
+Logical Switch commands:\n\
+  add-ls LS                   create a new logical switch named LS\n\
+  del-ls LS                   delete LS and all of its ports\n\
+  list-ls                     print the names of all the logical switches\n\
+  ls-exists LS                exit 2 if LS does not exist\n\
+  bind-ls PS PORT VLAN LS     bind LS to VLAN on PORT\n\
+  unbind-ls PS PORT VLAN      unbind logical switch on VLAN from PORT\n\
+  list-bindings PS PORT       list bindings for PORT on PS\n\
+\n\
+MAC binding commands:\n\
+  add-ucast-local LS MAC [ENCAP] IP   add ucast local entry in LS\n\
+  del-ucast-local LS MAC              del ucast local entry from LS\n\
+  add-mcast-local LS MAC [ENCAP] IP   add mcast local entry in LS\n\
+  del-mcast-local LS MAC [ENCAP] IP   del mcast local entry from LS\n\
+  clear-local-macs LS                 clear local mac entries\n\
+  list-local-macs LS                  list local mac entries\n\
+  add-ucast-remote LS MAC [ENCAP] IP  add ucast remote entry in LS\n\
+  del-ucast-remote LS MAC             del ucast remote entry from LS\n\
+  add-mcast-remote LS MAC [ENCAP] IP  add mcast remote entry in LS\n\
+  del-mcast-remote LS MAC [ENCAP] IP  del mcast remote entry from LS\n\
+  clear-remote-macs LS                clear remote mac entries\n\
+  list-remote-macs LS                 list remote mac entries\n\
+\n\
+Database commands:\n\
+  list TBL [REC]              list RECord (or all records) in TBL\n\
+  find TBL CONDITION...       list records satisfying CONDITION in TBL\n\
+  get TBL REC COL[:KEY]       print values of COLumns in RECord in TBL\n\
+  set TBL REC COL[:KEY]=VALUE set COLumn values in RECord in TBL\n\
+  add TBL REC COL [KEY=]VALUE add (KEY=)VALUE to COLumn in RECord in TBL\n\
+  remove TBL REC COL [KEY=]VALUE  remove (KEY=)VALUE from COLumn\n\
+  clear TBL REC COL           clear values from COLumn in RECord in TBL\n\
+  create TBL COL[:KEY]=VALUE  create and initialize new record\n\
+  destroy TBL REC             delete RECord from TBL\n\
+  wait-until TBL REC [COL[:KEY]=VALUE]  wait until condition is true\n\
+Potentially unsafe database commands require --force option.\n\
+\n\
+Options:\n\
+  --db=DATABASE               connect to DATABASE\n\
+                              (default: %s)\n\
+  -t, --timeout=SECS          wait at most SECS seconds\n\
+  --dry-run                   do not commit changes to database\n\
+  --oneline                   print exactly one line of output per command\n",
+           program_name, program_name, default_db());
+    vlog_usage();
+    printf("\
+  --no-syslog                 equivalent to --verbose=vtep_ctl:syslog:warn\n");
+    stream_usage("database", true, true, false);
+    printf("\n\
+Other options:\n\
+  -h, --help                  display this help message\n\
+  -V, --version               display version information\n");
+    exit(EXIT_SUCCESS);
+}
+
+static char *
+default_db(void)
+{
+    static char *def;
+    if (!def) {
+        def = xasprintf("unix:%s/db.sock", ovs_rundir());
+    }
+    return def;
+}
+
+/* Returns true if it looks like this set of arguments might modify the
+ * database, otherwise false.  (Not very smart, so it's prone to false
+ * positives.) */
+static bool
+might_write_to_db(char **argv)
+{
+    for (; *argv; argv++) {
+        const struct vtep_ctl_command_syntax *p = find_command(*argv);
+        if (p && p->mode == RW) {
+            return true;
+        }
+    }
+    return false;
+}
+\f
+struct vtep_ctl_context {
+    /* Read-only. */
+    int argc;
+    char **argv;
+    struct shash options;
+
+    /* Modifiable state. */
+    struct ds output;
+    struct table *table;
+    struct ovsdb_idl *idl;
+    struct ovsdb_idl_txn *txn;
+    struct ovsdb_symbol_table *symtab;
+    const struct vteprec_global *vtep_global;
+    bool verified_ports;
+
+    /* A cache of the contents of the database.
+     *
+     * A command that needs to use any of this information must first
+     * call vtep_ctl_context_populate_cache().  A command that changes
+     * anything that could invalidate the cache must either call
+     * vtep_ctl_context_invalidate_cache() or manually update the cache
+     * to maintain its correctness. */
+    bool cache_valid;
+    struct shash pswitches; /* Maps from physical switch name to
+                             * struct vtep_ctl_pswitch. */
+    struct shash ports;     /* Maps from port name to struct vtep_ctl_port. */
+
+    struct shash lswitches; /* Maps from logical switch name to
+                             * struct vtep_ctl_lswitch. */
+    struct shash plocs;     /* Maps from "<encap>+<dst_ip>" to
+                             * struct vteprec_physical_locator. */
+
+    /* A command may set this member to true if some prerequisite is not met
+     * and the caller should wait for something to change and then retry. */
+    bool try_again;
+};
+
+struct vtep_ctl_pswitch {
+    const struct vteprec_physical_switch *ps_cfg;
+    char *name;
+    struct list ports;          /* Contains "struct vteprec_physical_port"s. */
+};
+
+struct vtep_ctl_port {
+    struct list ports_node;     /* In struct vtep_ctl_pswitch's 'ports' list. */
+    const struct vteprec_physical_port *port_cfg;
+    struct vtep_ctl_pswitch *ps;
+    struct shash bindings;      /* Maps from vlan to vtep_ctl_lswitch. */
+};
+
+struct vtep_ctl_lswitch {
+    const struct vteprec_logical_switch *ls_cfg;
+    char *name;
+    struct shash ucast_local;   /* Maps from mac to vteprec_ucast_macs_local. */
+    struct shash ucast_remote;  /* Maps from mac to vteprec_ucast_macs_remote.*/
+    struct shash mcast_local;   /* Maps from mac to vtep_ctl_mcast_mac. */
+    struct shash mcast_remote;  /* Maps from mac to vtep_ctl_mcast_mac. */
+};
+
+struct vtep_ctl_mcast_mac {
+    const struct vteprec_mcast_macs_local *local_cfg;
+    const struct vteprec_mcast_macs_remote *remote_cfg;
+
+    const struct vteprec_physical_locator_set *ploc_set_cfg;
+    struct list locators;       /* Contains 'vtep_ctl_ploc's. */
+};
+
+struct vtep_ctl_ploc {
+    struct list locators_node;  /* In struct vtep_ctl_ploc_set's 'locators'
+                                   list. */
+    const struct vteprec_physical_locator *ploc_cfg;
+};
+
+static void
+verify_ports(struct vtep_ctl_context *ctx)
+{
+    if (!ctx->verified_ports) {
+        const struct vteprec_physical_switch *ps;
+
+        vteprec_global_verify_switches(ctx->vtep_global);
+        VTEPREC_PHYSICAL_SWITCH_FOR_EACH (ps, ctx->idl) {
+            vteprec_physical_switch_verify_ports(ps);
+        }
+
+        ctx->verified_ports = true;
+    }
+}
+
+static struct vtep_ctl_port *
+add_port_to_cache(struct vtep_ctl_context *ctx,
+                  struct vtep_ctl_pswitch *ps,
+                  struct vteprec_physical_port *port_cfg)
+{
+    char *cache_name = xasprintf("%s+%s", ps->name, port_cfg->name);
+    struct vtep_ctl_port *port;
+
+    port = xmalloc(sizeof *port);
+    list_push_back(&ps->ports, &port->ports_node);
+    port->port_cfg = port_cfg;
+    port->ps = ps;
+    shash_add(&ctx->ports, cache_name, port);
+    free(cache_name);
+    shash_init(&port->bindings);
+
+    return port;
+}
+
+static void
+del_cached_port(struct vtep_ctl_context *ctx, struct vtep_ctl_port *port)
+{
+    char *cache_name = xasprintf("%s+%s", port->ps->name, port->port_cfg->name);
+
+    list_remove(&port->ports_node);
+    shash_find_and_delete(&ctx->ports, port->port_cfg->name);
+    vteprec_physical_port_delete(port->port_cfg);
+    free(cache_name);
+    free(port);
+}
+
+static struct vtep_ctl_pswitch *
+add_pswitch_to_cache(struct vtep_ctl_context *ctx,
+                     struct vteprec_physical_switch *ps_cfg)
+{
+    struct vtep_ctl_pswitch *ps = xmalloc(sizeof *ps);
+    ps->ps_cfg = ps_cfg;
+    ps->name = xstrdup(ps_cfg->name);
+    list_init(&ps->ports);
+    shash_add(&ctx->pswitches, ps->name, ps);
+    return ps;
+}
+
+static void
+vtep_delete_pswitch(const struct vteprec_global *vtep_global,
+                    const struct vteprec_physical_switch *ps)
+{
+    struct vteprec_physical_switch **pswitches;
+    size_t i, n;
+
+    pswitches = xmalloc(sizeof *vtep_global->switches
+                        * vtep_global->n_switches);
+    for (i = n = 0; i < vtep_global->n_switches; i++) {
+        if (vtep_global->switches[i] != ps) {
+            pswitches[n++] = vtep_global->switches[i];
+        }
+    }
+    vteprec_global_set_switches(vtep_global, pswitches, n);
+    free(pswitches);
+}
+
+static void
+del_cached_pswitch(struct vtep_ctl_context *ctx, struct vtep_ctl_pswitch *ps)
+{
+    ovs_assert(list_is_empty(&ps->ports));
+    if (ps->ps_cfg) {
+        vteprec_physical_switch_delete(ps->ps_cfg);
+        vtep_delete_pswitch(ctx->vtep_global, ps->ps_cfg);
+    }
+    shash_find_and_delete(&ctx->pswitches, ps->name);
+    free(ps->name);
+    free(ps);
+}
+
+static struct vtep_ctl_lswitch *
+add_lswitch_to_cache(struct vtep_ctl_context *ctx,
+                     const struct vteprec_logical_switch *ls_cfg)
+{
+    struct vtep_ctl_lswitch *ls = xmalloc(sizeof *ls);
+    ls->ls_cfg = ls_cfg;
+    ls->name = xstrdup(ls_cfg->name);
+    shash_add(&ctx->lswitches, ls->name, ls);
+    shash_init(&ls->ucast_local);
+    shash_init(&ls->ucast_remote);
+    shash_init(&ls->mcast_local);
+    shash_init(&ls->mcast_remote);
+    return ls;
+}
+
+static void
+del_cached_lswitch(struct vtep_ctl_context *ctx, struct vtep_ctl_lswitch *ls)
+{
+    if (ls->ls_cfg) {
+        vteprec_logical_switch_delete(ls->ls_cfg);
+    }
+    shash_find_and_delete(&ctx->lswitches, ls->name);
+    free(ls->name);
+    free(ls);
+}
+
+static void
+commit_ls_bindings(struct vtep_ctl_port *port)
+{
+    struct vteprec_logical_switch **binding_values;
+    int64_t *binding_keys;
+    size_t n_bindings;
+    struct shash_node *node;
+    int i;
+
+    n_bindings = shash_count(&port->bindings);
+    binding_keys = xmalloc(n_bindings * sizeof *binding_keys);
+    binding_values = xmalloc(n_bindings * sizeof *binding_values);
+
+    i = 0;
+    SHASH_FOR_EACH(node, &port->bindings) {
+        struct vtep_ctl_lswitch *ls_entry = node->data;
+
+        binding_keys[i] = strtoll(node->name, NULL, 0);
+        binding_values[i] = (struct vteprec_logical_switch *)ls_entry->ls_cfg;
+        i++;
+    }
+
+    vteprec_physical_port_set_vlan_bindings(port->port_cfg,
+                                            binding_keys, binding_values,
+                                            n_bindings);
+    free(binding_values);
+    free(binding_keys);
+}
+
+static void
+add_ls_binding_to_cache(struct vtep_ctl_port *port,
+                        const char *vlan,
+                        struct vtep_ctl_lswitch *ls)
+{
+    if (shash_find(&port->bindings, vlan)) {
+        vtep_ctl_fatal("multiple bindings for vlan %s", vlan);
+    }
+
+    shash_add(&port->bindings, vlan, ls);
+}
+
+static void
+del_cached_ls_binding(struct vtep_ctl_port *port, const char *vlan)
+{
+    if (!shash_find(&port->bindings, vlan)) {
+        vtep_ctl_fatal("no binding for vlan %s", vlan);
+    }
+
+    shash_find_and_delete(&port->bindings, vlan);
+}
+
+static struct vteprec_physical_locator *
+find_ploc(struct vtep_ctl_context *ctx, const char *encap,
+          const char *dst_ip)
+{
+    struct vteprec_physical_locator *ploc;
+    char *name = xasprintf("%s+%s", encap, dst_ip);
+
+    ovs_assert(ctx->cache_valid);
+
+    ploc = shash_find_data(&ctx->plocs, name);
+    free(name);
+
+    return ploc;
+}
+
+static void
+add_ploc_to_cache(struct vtep_ctl_context *ctx,
+                  struct vteprec_physical_locator *ploc)
+{
+    char *name = xasprintf("%s+%s", ploc->encapsulation_type, ploc->dst_ip);
+    struct vteprec_physical_locator *orig_ploc;
+
+    orig_ploc = find_ploc(ctx, ploc->encapsulation_type, ploc->dst_ip);
+    if (!orig_ploc) {
+        shash_add(&ctx->plocs, name, ploc);
+    }
+
+    free(name);
+}
+
+static void
+add_ploc_to_mcast_mac(struct vtep_ctl_mcast_mac *mcast_mac,
+                      struct vteprec_physical_locator *ploc_cfg)
+{
+    struct vtep_ctl_ploc *ploc;
+
+    ploc = xmalloc(sizeof *ploc);
+    ploc->ploc_cfg = ploc_cfg;
+    list_push_back(&mcast_mac->locators, &ploc->locators_node);
+}
+
+static void
+del_ploc_from_mcast_mac(struct vtep_ctl_mcast_mac *mcast_mac,
+                        struct vteprec_physical_locator *ploc_cfg)
+{
+    struct vtep_ctl_ploc *ploc;
+
+    LIST_FOR_EACH (ploc, locators_node, &mcast_mac->locators) {
+        if (ploc->ploc_cfg == ploc_cfg) {
+            list_remove(&ploc->locators_node);
+            free(ploc);
+            return;
+        }
+    }
+}
+
+static struct vtep_ctl_mcast_mac *
+add_mcast_mac_to_cache(struct vtep_ctl_context *ctx,
+                       struct vtep_ctl_lswitch *ls, const char *mac,
+                       struct vteprec_physical_locator_set *ploc_set_cfg,
+                       bool local)
+{
+    struct vtep_ctl_mcast_mac *mcast_mac;
+    struct shash *mcast_shash;
+    size_t i;
+
+    mcast_mac = xmalloc(sizeof *mcast_mac);
+    mcast_shash = local ? &ls->mcast_local : &ls->mcast_remote;
+
+    mcast_mac->ploc_set_cfg = ploc_set_cfg;
+    list_init(&mcast_mac->locators);
+    shash_add(mcast_shash, mac, mcast_mac);
+    for (i = 0; i < ploc_set_cfg->n_locators; i++) {
+        struct vteprec_physical_locator *ploc_cfg;
+
+        ploc_cfg = ploc_set_cfg->locators[i];
+        add_ploc_to_mcast_mac(mcast_mac, ploc_cfg);
+        add_ploc_to_cache(ctx, ploc_cfg);
+    }
+
+    return mcast_mac;
+}
+
+static void
+vtep_ctl_context_invalidate_cache(struct vtep_ctl_context *ctx)
+{
+    struct shash_node *node;
+
+    if (!ctx->cache_valid) {
+        return;
+    }
+    ctx->cache_valid = false;
+
+    SHASH_FOR_EACH (node, &ctx->pswitches) {
+        struct vtep_ctl_pswitch *ps = node->data;
+        free(ps->name);
+        free(ps);
+    }
+    shash_destroy(&ctx->pswitches);
+
+    SHASH_FOR_EACH (node, &ctx->ports) {
+        struct vtep_ctl_port *port = node->data;
+        shash_destroy(&port->bindings);
+    }
+    shash_destroy_free_data(&ctx->ports);
+
+    SHASH_FOR_EACH (node, &ctx->lswitches) {
+        struct vtep_ctl_lswitch *ls = node->data;
+        struct shash_node *node2, *next_node2;
+
+        shash_destroy(&ls->ucast_local);
+        shash_destroy(&ls->ucast_remote);
+
+        SHASH_FOR_EACH_SAFE (node2, next_node2, &ls->mcast_local) {
+            struct vtep_ctl_mcast_mac *mcast_mac = node2->data;
+            struct vtep_ctl_ploc *ploc, *next_ploc;
+
+            LIST_FOR_EACH_SAFE (ploc, next_ploc, locators_node,
+                                &mcast_mac->locators) {
+                free(ploc);
+            }
+            free(mcast_mac);
+        }
+        shash_destroy(&ls->mcast_local);
+
+        SHASH_FOR_EACH_SAFE (node2, next_node2, &ls->mcast_remote) {
+            struct vtep_ctl_mcast_mac *mcast_mac = node2->data;
+            struct vtep_ctl_ploc *ploc, *next_ploc;
+
+            LIST_FOR_EACH_SAFE (ploc, next_ploc, locators_node,
+                                &mcast_mac->locators) {
+                free(ploc);
+            }
+            free(mcast_mac);
+        }
+        shash_destroy(&ls->mcast_remote);
+
+        free(ls->name);
+        free(ls);
+    }
+    shash_destroy(&ctx->lswitches);
+    shash_destroy(&ctx->plocs);
+}
+
+static void
+pre_get_info(struct vtep_ctl_context *ctx)
+{
+    ovsdb_idl_add_column(ctx->idl, &vteprec_global_col_switches);
+
+    ovsdb_idl_add_column(ctx->idl, &vteprec_physical_switch_col_name);
+    ovsdb_idl_add_column(ctx->idl, &vteprec_physical_switch_col_ports);
+
+    ovsdb_idl_add_column(ctx->idl, &vteprec_physical_port_col_name);
+    ovsdb_idl_add_column(ctx->idl, &vteprec_physical_port_col_vlan_bindings);
+
+    ovsdb_idl_add_column(ctx->idl, &vteprec_logical_switch_col_name);
+
+    ovsdb_idl_add_column(ctx->idl, &vteprec_ucast_macs_local_col_MAC);
+    ovsdb_idl_add_column(ctx->idl, &vteprec_ucast_macs_local_col_locator);
+    ovsdb_idl_add_column(ctx->idl,
+                         &vteprec_ucast_macs_local_col_logical_switch);
+
+    ovsdb_idl_add_column(ctx->idl, &vteprec_ucast_macs_remote_col_MAC);
+    ovsdb_idl_add_column(ctx->idl, &vteprec_ucast_macs_remote_col_locator);
+    ovsdb_idl_add_column(ctx->idl,
+                         &vteprec_ucast_macs_remote_col_logical_switch);
+
+    ovsdb_idl_add_column(ctx->idl, &vteprec_mcast_macs_local_col_MAC);
+    ovsdb_idl_add_column(ctx->idl,
+                         &vteprec_mcast_macs_local_col_locator_set);
+    ovsdb_idl_add_column(ctx->idl,
+                         &vteprec_mcast_macs_local_col_logical_switch);
+
+    ovsdb_idl_add_column(ctx->idl, &vteprec_mcast_macs_remote_col_MAC);
+    ovsdb_idl_add_column(ctx->idl,
+                         &vteprec_mcast_macs_remote_col_locator_set);
+    ovsdb_idl_add_column(ctx->idl,
+                         &vteprec_mcast_macs_remote_col_logical_switch);
+
+    ovsdb_idl_add_column(ctx->idl,
+                         &vteprec_physical_locator_set_col_locators);
+
+    ovsdb_idl_add_column(ctx->idl,
+                         &vteprec_physical_locator_col_dst_ip);
+    ovsdb_idl_add_column(ctx->idl,
+                         &vteprec_physical_locator_col_encapsulation_type);
+}
+
+static void
+vtep_ctl_context_populate_cache(struct vtep_ctl_context *ctx)
+{
+    const struct vteprec_global *vtep_global = ctx->vtep_global;
+    const struct vteprec_logical_switch *ls_cfg;
+    const struct vteprec_ucast_macs_local *ucast_local_cfg;
+    const struct vteprec_ucast_macs_remote *ucast_remote_cfg;
+    const struct vteprec_mcast_macs_local *mcast_local_cfg;
+    const struct vteprec_mcast_macs_remote *mcast_remote_cfg;
+    struct sset pswitches, ports, lswitches;
+    size_t i;
+
+    if (ctx->cache_valid) {
+        /* Cache is already populated. */
+        return;
+    }
+    ctx->cache_valid = true;
+    shash_init(&ctx->pswitches);
+    shash_init(&ctx->ports);
+    shash_init(&ctx->lswitches);
+    shash_init(&ctx->plocs);
+
+    sset_init(&pswitches);
+    sset_init(&ports);
+    for (i = 0; i < vtep_global->n_switches; i++) {
+        struct vteprec_physical_switch *ps_cfg = vtep_global->switches[i];
+        struct vtep_ctl_pswitch *ps;
+        size_t j;
+
+        if (!sset_add(&pswitches, ps_cfg->name)) {
+            VLOG_WARN("%s: database contains duplicate physical switch name",
+                      ps_cfg->name);
+            continue;
+        }
+        ps = add_pswitch_to_cache(ctx, ps_cfg);
+        if (!ps) {
+            continue;
+        }
+
+        for (j = 0; j < ps_cfg->n_ports; j++) {
+            struct vteprec_physical_port *port_cfg = ps_cfg->ports[j];
+
+            if (!sset_add(&ports, port_cfg->name)) {
+                /* Duplicate port name.  (We will warn about that later.) */
+                continue;
+            }
+        }
+    }
+    sset_destroy(&pswitches);
+    sset_destroy(&ports);
+
+    sset_init(&lswitches);
+    VTEPREC_LOGICAL_SWITCH_FOR_EACH (ls_cfg, ctx->idl) {
+        if (!sset_add(&lswitches, ls_cfg->name)) {
+            VLOG_WARN("%s: database contains duplicate logical switch name",
+                      ls_cfg->name);
+            continue;
+        }
+        add_lswitch_to_cache(ctx, ls_cfg);
+    }
+    sset_destroy(&lswitches);
+
+    VTEPREC_UCAST_MACS_LOCAL_FOR_EACH (ucast_local_cfg, ctx->idl) {
+        struct vtep_ctl_lswitch *ls;
+
+        if (!ucast_local_cfg->logical_switch) {
+            continue;
+        }
+        ls = find_lswitch(ctx, ucast_local_cfg->logical_switch->name, false);
+        if (!ls) {
+            continue;
+        }
+
+        if (ucast_local_cfg->locator) {
+            add_ploc_to_cache(ctx, ucast_local_cfg->locator);
+        }
+
+        shash_add(&ls->ucast_local, ucast_local_cfg->MAC, ucast_local_cfg);
+    }
+
+    VTEPREC_UCAST_MACS_REMOTE_FOR_EACH (ucast_remote_cfg, ctx->idl) {
+        struct vtep_ctl_lswitch *ls;
+
+        if (!ucast_remote_cfg->logical_switch) {
+            continue;
+        }
+        ls = find_lswitch(ctx, ucast_remote_cfg->logical_switch->name, false);
+        if (!ls) {
+            continue;
+        }
+
+        if (ucast_remote_cfg->locator) {
+            add_ploc_to_cache(ctx, ucast_remote_cfg->locator);
+        }
+
+        shash_add(&ls->ucast_remote, ucast_remote_cfg->MAC, ucast_remote_cfg);
+    }
+
+    VTEPREC_MCAST_MACS_LOCAL_FOR_EACH (mcast_local_cfg, ctx->idl) {
+        struct vtep_ctl_mcast_mac *mcast_mac;
+        struct vtep_ctl_lswitch *ls;
+
+        if (!mcast_local_cfg->logical_switch) {
+            continue;
+        }
+        ls = find_lswitch(ctx, mcast_local_cfg->logical_switch->name, false);
+        if (!ls) {
+            continue;
+        }
+
+        mcast_mac = add_mcast_mac_to_cache(ctx, ls, mcast_local_cfg->MAC,
+                                           mcast_local_cfg->locator_set,
+                                           true);
+        mcast_mac->local_cfg = mcast_local_cfg;
+    }
+
+    VTEPREC_MCAST_MACS_REMOTE_FOR_EACH (mcast_remote_cfg, ctx->idl) {
+        struct vtep_ctl_mcast_mac *mcast_mac;
+        struct vtep_ctl_lswitch *ls;
+
+        if (!mcast_remote_cfg->logical_switch) {
+            continue;
+        }
+        ls = find_lswitch(ctx, mcast_remote_cfg->logical_switch->name, false);
+        if (!ls) {
+            continue;
+        }
+
+        mcast_mac = add_mcast_mac_to_cache(ctx, ls, mcast_remote_cfg->MAC,
+                                           mcast_remote_cfg->locator_set,
+                                           false);
+        mcast_mac->remote_cfg = mcast_remote_cfg;
+    }
+
+    sset_init(&pswitches);
+    for (i = 0; i < vtep_global->n_switches; i++) {
+        struct vteprec_physical_switch *ps_cfg = vtep_global->switches[i];
+        struct vtep_ctl_pswitch *ps;
+        size_t j;
+
+        if (!sset_add(&pswitches, ps_cfg->name)) {
+            continue;
+        }
+        ps = shash_find_data(&ctx->pswitches, ps_cfg->name);
+        for (j = 0; j < ps_cfg->n_ports; j++) {
+            struct vteprec_physical_port *port_cfg = ps_cfg->ports[j];
+            struct vtep_ctl_port *port;
+            size_t k;
+
+            port = shash_find_data(&ctx->ports, port_cfg->name);
+            if (port) {
+                if (port_cfg == port->port_cfg) {
+                    VLOG_WARN("%s: port is in multiple physical switches "
+                              "(%s and %s)",
+                              port_cfg->name, ps->name, port->ps->name);
+                } else {
+                    /* Log as an error because this violates the database's
+                     * uniqueness constraints, so the database server shouldn't
+                     * have allowed it. */
+                    VLOG_ERR("%s: database contains duplicate port name",
+                             port_cfg->name);
+                }
+                continue;
+            }
+
+            port = add_port_to_cache(ctx, ps, port_cfg);
+
+            for (k = 0; k < port_cfg->n_vlan_bindings; k++) {
+                struct vteprec_logical_switch *ls_cfg;
+                struct vtep_ctl_lswitch *ls;
+                char *vlan;
+
+                vlan = xasprintf("%"PRId64, port_cfg->key_vlan_bindings[k]);
+                if (shash_find(&port->bindings, vlan)) {
+                    vtep_ctl_fatal("multiple bindings for vlan %s", vlan);
+                }
+                ls_cfg = port_cfg->value_vlan_bindings[k];
+                ls = find_lswitch(ctx, ls_cfg->name, true);
+
+                shash_add_nocopy(&port->bindings, vlan, ls);
+            }
+        }
+    }
+    sset_destroy(&pswitches);
+}
+
+static struct vtep_ctl_pswitch *
+find_pswitch(struct vtep_ctl_context *ctx, const char *name, bool must_exist)
+{
+    struct vtep_ctl_pswitch *ps;
+
+    ovs_assert(ctx->cache_valid);
+
+    ps = shash_find_data(&ctx->pswitches, name);
+    if (must_exist && !ps) {
+        vtep_ctl_fatal("no physical switch named %s", name);
+    }
+    vteprec_global_verify_switches(ctx->vtep_global);
+    return ps;
+}
+
+static struct vtep_ctl_port *
+find_port(struct vtep_ctl_context *ctx, const char *ps_name,
+          const char *port_name, bool must_exist)
+{
+    char *cache_name = xasprintf("%s+%s", ps_name, port_name);
+    struct vtep_ctl_port *port;
+
+    ovs_assert(ctx->cache_valid);
+
+    port = shash_find_data(&ctx->ports, cache_name);
+    if (port && !strcmp(port_name, port->ps->name)) {
+        port = NULL;
+    }
+    free(cache_name);
+    if (must_exist && !port) {
+        vtep_ctl_fatal("no port named %s", port_name);
+    }
+    verify_ports(ctx);
+    return port;
+}
+
+static void
+pswitch_insert_port(const struct vteprec_physical_switch *ps,
+                    struct vteprec_physical_port *port)
+{
+    struct vteprec_physical_port **ports;
+    size_t i;
+
+    ports = xmalloc(sizeof *ps->ports * (ps->n_ports + 1));
+    for (i = 0; i < ps->n_ports; i++) {
+        ports[i] = ps->ports[i];
+    }
+    ports[ps->n_ports] = port;
+    vteprec_physical_switch_set_ports(ps, ports, ps->n_ports + 1);
+    free(ports);
+}
+
+static void
+pswitch_delete_port(const struct vteprec_physical_switch *ps,
+                    const struct vteprec_physical_port *port)
+{
+    struct vteprec_physical_port **ports;
+    size_t i, n;
+
+    ports = xmalloc(sizeof *ps->ports * ps->n_ports);
+    for (i = n = 0; i < ps->n_ports; i++) {
+        if (ps->ports[i] != port) {
+            ports[n++] = ps->ports[i];
+        }
+    }
+    vteprec_physical_switch_set_ports(ps, ports, n);
+    free(ports);
+}
+
+static void
+vtep_insert_pswitch(const struct vteprec_global *vtep_global,
+                    struct vteprec_physical_switch *ps)
+{
+    struct vteprec_physical_switch **pswitches;
+    size_t i;
+
+    pswitches = xmalloc(sizeof *vtep_global->switches
+                        * (vtep_global->n_switches + 1));
+    for (i = 0; i < vtep_global->n_switches; i++) {
+        pswitches[i] = vtep_global->switches[i];
+    }
+    pswitches[vtep_global->n_switches] = ps;
+    vteprec_global_set_switches(vtep_global, pswitches,
+                                vtep_global->n_switches + 1);
+    free(pswitches);
+}
+
+static void
+cmd_add_ps(struct vtep_ctl_context *ctx)
+{
+    const char *ps_name = ctx->argv[1];
+    bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
+    struct vteprec_physical_switch *ps;
+
+    vtep_ctl_context_populate_cache(ctx);
+    if (find_pswitch(ctx, ps_name, false)) {
+        if (!may_exist) {
+            vtep_ctl_fatal("cannot create physical switch %s because it "
+                           "already exists", ps_name);
+        }
+        return;
+    }
+
+    ps = vteprec_physical_switch_insert(ctx->txn);
+    vteprec_physical_switch_set_name(ps, ps_name);
+
+    vtep_insert_pswitch(ctx->vtep_global, ps);
+
+    vtep_ctl_context_invalidate_cache(ctx);
+}
+
+static void
+del_port(struct vtep_ctl_context *ctx, struct vtep_ctl_port *port)
+{
+    pswitch_delete_port(port->ps->ps_cfg, port->port_cfg);
+    del_cached_port(ctx, port);
+}
+
+static void
+del_pswitch(struct vtep_ctl_context *ctx, struct vtep_ctl_pswitch *ps)
+{
+    struct vtep_ctl_port *port, *next_port;
+
+    LIST_FOR_EACH_SAFE (port, next_port, ports_node, &ps->ports) {
+        del_port(ctx, port);
+    }
+
+    del_cached_pswitch(ctx, ps);
+}
+
+static void
+cmd_del_ps(struct vtep_ctl_context *ctx)
+{
+    bool must_exist = !shash_find(&ctx->options, "--if-exists");
+    struct vtep_ctl_pswitch *ps;
+
+    vtep_ctl_context_populate_cache(ctx);
+    ps = find_pswitch(ctx, ctx->argv[1], must_exist);
+    if (ps) {
+        del_pswitch(ctx, ps);
+    }
+}
+
+static void
+output_sorted(struct svec *svec, struct ds *output)
+{
+    const char *name;
+    size_t i;
+
+    svec_sort(svec);
+    SVEC_FOR_EACH (i, name, svec) {
+        ds_put_format(output, "%s\n", name);
+    }
+}
+
+static void
+cmd_list_ps(struct vtep_ctl_context *ctx)
+{
+    struct shash_node *node;
+    struct svec pswitches;
+
+    vtep_ctl_context_populate_cache(ctx);
+
+    svec_init(&pswitches);
+    SHASH_FOR_EACH (node, &ctx->pswitches) {
+        struct vtep_ctl_pswitch *ps = node->data;
+
+        svec_add(&pswitches, ps->name);
+    }
+    output_sorted(&pswitches, &ctx->output);
+    svec_destroy(&pswitches);
+}
+
+static void
+cmd_ps_exists(struct vtep_ctl_context *ctx)
+{
+    vtep_ctl_context_populate_cache(ctx);
+    if (!find_pswitch(ctx, ctx->argv[1], false)) {
+        vtep_ctl_exit(2);
+    }
+}
+
+static void
+cmd_list_ports(struct vtep_ctl_context *ctx)
+{
+    struct vtep_ctl_pswitch *ps;
+    struct vtep_ctl_port *port;
+    struct svec ports;
+
+    vtep_ctl_context_populate_cache(ctx);
+    ps = find_pswitch(ctx, ctx->argv[1], true);
+    vteprec_physical_switch_verify_ports(ps->ps_cfg);
+
+    svec_init(&ports);
+    LIST_FOR_EACH (port, ports_node, &ps->ports) {
+        if (strcmp(port->port_cfg->name, ps->name)) {
+            svec_add(&ports, port->port_cfg->name);
+        }
+    }
+    output_sorted(&ports, &ctx->output);
+    svec_destroy(&ports);
+}
+
+static void
+add_port(struct vtep_ctl_context *ctx, const char *ps_name,
+         const char *port_name, bool may_exist)
+{
+    struct vtep_ctl_port *vtep_ctl_port;
+    struct vtep_ctl_pswitch *ps;
+    struct vteprec_physical_port *port;
+
+    vtep_ctl_context_populate_cache(ctx);
+
+    vtep_ctl_port = find_port(ctx, ps_name, port_name, false);
+    if (vtep_ctl_port) {
+        if (!may_exist) {
+            vtep_ctl_fatal("cannot create a port named %s on %s because a "
+                           "port with that name already exists",
+                           port_name, ps_name);
+        }
+        return;
+    }
+
+    ps = find_pswitch(ctx, ps_name, true);
+
+    port = vteprec_physical_port_insert(ctx->txn);
+    vteprec_physical_port_set_name(port, port_name);
+
+    pswitch_insert_port(ps->ps_cfg, port);
+
+    add_port_to_cache(ctx, ps, port);
+}
+
+static void
+cmd_add_port(struct vtep_ctl_context *ctx)
+{
+    bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
+    add_port(ctx, ctx->argv[1], ctx->argv[2], may_exist);
+}
+
+static void
+cmd_del_port(struct vtep_ctl_context *ctx)
+{
+    bool must_exist = !shash_find(&ctx->options, "--if-exists");
+    struct vtep_ctl_port *port;
+
+    vtep_ctl_context_populate_cache(ctx);
+
+    port = find_port(ctx, ctx->argv[1], ctx->argv[2], must_exist);
+    if (port) {
+        if (ctx->argc == 3) {
+            struct vtep_ctl_pswitch *ps;
+
+            ps = find_pswitch(ctx, ctx->argv[1], true);
+            if (port->ps != ps) {
+                vtep_ctl_fatal("physical switch %s does not have a port %s",
+                               ctx->argv[1], ctx->argv[2]);
+            }
+        }
+
+        del_port(ctx, port);
+    }
+}
+
+static struct vtep_ctl_lswitch *
+find_lswitch(struct vtep_ctl_context *ctx, const char *name, bool must_exist)
+{
+    struct vtep_ctl_lswitch *ls;
+
+    ovs_assert(ctx->cache_valid);
+
+    ls = shash_find_data(&ctx->lswitches, name);
+    if (must_exist && !ls) {
+        vtep_ctl_fatal("no logical switch named %s", name);
+    }
+    return ls;
+}
+
+static void
+cmd_add_ls(struct vtep_ctl_context *ctx)
+{
+    const char *ls_name = ctx->argv[1];
+    bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
+    struct vteprec_logical_switch *ls;
+
+    vtep_ctl_context_populate_cache(ctx);
+    if (find_lswitch(ctx, ls_name, false)) {
+        if (!may_exist) {
+            vtep_ctl_fatal("cannot create logical switch %s because it "
+                           "already exists", ls_name);
+        }
+        return;
+    }
+
+    ls = vteprec_logical_switch_insert(ctx->txn);
+    vteprec_logical_switch_set_name(ls, ls_name);
+
+    vtep_ctl_context_invalidate_cache(ctx);
+}
+
+static void
+del_lswitch(struct vtep_ctl_context *ctx, struct vtep_ctl_lswitch *ls)
+{
+    del_cached_lswitch(ctx, ls);
+}
+
+static void
+cmd_del_ls(struct vtep_ctl_context *ctx)
+{
+    bool must_exist = !shash_find(&ctx->options, "--if-exists");
+    struct vtep_ctl_lswitch *ls;
+
+    vtep_ctl_context_populate_cache(ctx);
+    ls = find_lswitch(ctx, ctx->argv[1], must_exist);
+    if (ls) {
+        del_lswitch(ctx, ls);
+    }
+}
+
+static void
+cmd_list_ls(struct vtep_ctl_context *ctx)
+{
+    struct shash_node *node;
+    struct svec lswitches;
+
+    vtep_ctl_context_populate_cache(ctx);
+
+    svec_init(&lswitches);
+    SHASH_FOR_EACH (node, &ctx->lswitches) {
+        struct vtep_ctl_lswitch *ls = node->data;
+
+        svec_add(&lswitches, ls->name);
+    }
+    output_sorted(&lswitches, &ctx->output);
+    svec_destroy(&lswitches);
+}
+
+static void
+cmd_ls_exists(struct vtep_ctl_context *ctx)
+{
+    vtep_ctl_context_populate_cache(ctx);
+    if (!find_lswitch(ctx, ctx->argv[1], false)) {
+        vtep_ctl_exit(2);
+    }
+}
+
+static void
+cmd_list_bindings(struct vtep_ctl_context *ctx)
+{
+    const struct shash_node *node;
+    struct vtep_ctl_port *port;
+    struct svec bindings;
+
+    vtep_ctl_context_populate_cache(ctx);
+    port = find_port(ctx, ctx->argv[1], ctx->argv[2], true);
+
+    svec_init(&bindings);
+    SHASH_FOR_EACH (node, &port->bindings) {
+        struct vtep_ctl_lswitch *lswitch = node->data;
+        char *binding;
+        
+        binding = xasprintf("%04lld %s", strtoll(node->name, NULL, 0),
+                            lswitch->name);
+        svec_add_nocopy(&bindings, binding);
+    }
+    output_sorted(&bindings, &ctx->output);
+    svec_destroy(&bindings);
+}
+
+static void
+cmd_bind_ls(struct vtep_ctl_context *ctx)
+{
+    struct vtep_ctl_lswitch *ls;
+    struct vtep_ctl_port *port;
+    const char *vlan;
+
+    vtep_ctl_context_populate_cache(ctx);
+
+    port = find_port(ctx, ctx->argv[1], ctx->argv[2], true);
+    vlan = ctx->argv[3];
+    ls = find_lswitch(ctx, ctx->argv[4], true);
+
+    add_ls_binding_to_cache(port, vlan, ls);
+    commit_ls_bindings(port);
+
+    vtep_ctl_context_invalidate_cache(ctx);
+}
+
+static void
+cmd_unbind_ls(struct vtep_ctl_context *ctx)
+{
+    struct vtep_ctl_port *port;
+    const char *vlan;
+
+    vtep_ctl_context_populate_cache(ctx);
+
+    port = find_port(ctx, ctx->argv[1], ctx->argv[2], true);
+    vlan = ctx->argv[3];
+
+    del_cached_ls_binding(port, vlan);
+    commit_ls_bindings(port);
+
+    vtep_ctl_context_invalidate_cache(ctx);
+}
+
+static void
+add_ucast_entry(struct vtep_ctl_context *ctx, bool local)
+{
+    struct vtep_ctl_lswitch *ls;
+    const char *mac;
+    const char *encap;
+    const char *dst_ip;
+    struct vteprec_physical_locator *ploc_cfg;
+
+    vtep_ctl_context_populate_cache(ctx);
+
+    ls = find_lswitch(ctx, ctx->argv[1], true);
+    mac = ctx->argv[2];
+
+    if (ctx->argc == 4) {
+        encap = "vxlan_over_ipv4";
+        dst_ip = ctx->argv[3];
+    } else {
+        encap = ctx->argv[3];
+        dst_ip = ctx->argv[4];
+    }
+
+    ploc_cfg = find_ploc(ctx, encap, dst_ip);
+    if (!ploc_cfg) {
+        ploc_cfg = vteprec_physical_locator_insert(ctx->txn);
+        vteprec_physical_locator_set_dst_ip(ploc_cfg, dst_ip);
+        vteprec_physical_locator_set_encapsulation_type(ploc_cfg, encap);
+
+        add_ploc_to_cache(ctx, ploc_cfg);
+    }
+
+    if (local) {
+        struct vteprec_ucast_macs_local *ucast_cfg;
+
+        ucast_cfg = shash_find_data(&ls->ucast_local, mac);
+        if (!ucast_cfg) {
+            ucast_cfg = vteprec_ucast_macs_local_insert(ctx->txn);
+            vteprec_ucast_macs_local_set_MAC(ucast_cfg, mac);
+            vteprec_ucast_macs_local_set_logical_switch(ucast_cfg, ls->ls_cfg);
+            shash_add(&ls->ucast_local, mac, ucast_cfg);
+        }
+        vteprec_ucast_macs_local_set_locator(ucast_cfg, ploc_cfg);
+    } else {
+        struct vteprec_ucast_macs_remote *ucast_cfg;
+
+        ucast_cfg = shash_find_data(&ls->ucast_remote, mac);
+        if (!ucast_cfg) {
+            ucast_cfg = vteprec_ucast_macs_remote_insert(ctx->txn);
+            vteprec_ucast_macs_remote_set_MAC(ucast_cfg, mac);
+            vteprec_ucast_macs_remote_set_logical_switch(ucast_cfg, ls->ls_cfg);
+            shash_add(&ls->ucast_remote, mac, ucast_cfg);
+        }
+        vteprec_ucast_macs_remote_set_locator(ucast_cfg, ploc_cfg);
+    }
+
+    vtep_ctl_context_invalidate_cache(ctx);
+}
+
+static void
+cmd_add_ucast_local(struct vtep_ctl_context *ctx)
+{
+    add_ucast_entry(ctx, true);
+}
+
+static void
+cmd_add_ucast_remote(struct vtep_ctl_context *ctx)
+{
+    add_ucast_entry(ctx, false);
+}
+
+static void
+del_ucast_entry(struct vtep_ctl_context *ctx, bool local)
+{
+    struct vtep_ctl_lswitch *ls;
+    struct shash *ucast_shash;
+    struct shash_node *node;
+
+    vtep_ctl_context_populate_cache(ctx);
+
+    ls = find_lswitch(ctx, ctx->argv[1], true);
+    ucast_shash = local ? &ls->ucast_local : &ls->ucast_remote;
+
+    node = shash_find(ucast_shash, ctx->argv[2]);
+    if (!node) {
+        return;
+    }
+
+    if (local) {
+        struct vteprec_ucast_macs_local *ucast_cfg = node->data;
+        vteprec_ucast_macs_local_delete(ucast_cfg);
+    } else {
+        struct vteprec_ucast_macs_remote *ucast_cfg = node->data;
+        vteprec_ucast_macs_remote_delete(ucast_cfg);
+    }
+    shash_delete(ucast_shash, node);
+
+    vtep_ctl_context_invalidate_cache(ctx);
+}
+
+static void
+cmd_del_ucast_local(struct vtep_ctl_context *ctx)
+{
+    del_ucast_entry(ctx, true);
+}
+
+static void
+cmd_del_ucast_remote(struct vtep_ctl_context *ctx)
+{
+    del_ucast_entry(ctx, false);
+}
+
+static void
+commit_mcast_entries(struct vtep_ctl_mcast_mac *mcast_mac)
+{
+    struct vtep_ctl_ploc *ploc;
+    struct vteprec_physical_locator **locators = NULL;
+    size_t n_locators;
+    int i;
+
+    n_locators = list_size(&mcast_mac->locators);
+    ovs_assert(n_locators);
+
+    locators = xmalloc(n_locators * sizeof *locators);
+
+    i = 0;
+    LIST_FOR_EACH (ploc, locators_node, &mcast_mac->locators) {
+        locators[i] = (struct vteprec_physical_locator *)ploc->ploc_cfg;
+        i++;
+    }
+
+    vteprec_physical_locator_set_set_locators(mcast_mac->ploc_set_cfg,
+                                              locators,
+                                              n_locators);
+
+    free(locators);
+}
+
+static void
+add_mcast_entry(struct vtep_ctl_context *ctx,
+                struct vtep_ctl_lswitch *ls, const char *mac,
+                const char *encap, const char *dst_ip, bool local)
+{
+    struct shash *mcast_shash;
+    struct vtep_ctl_mcast_mac *mcast_mac;
+    struct vteprec_physical_locator *ploc_cfg;
+    struct vteprec_physical_locator_set *ploc_set_cfg;
+
+    mcast_shash = local ? &ls->mcast_local : &ls->mcast_remote;
+
+    /* Physical locator sets are immutable, so allocate a new one. */
+    ploc_set_cfg = vteprec_physical_locator_set_insert(ctx->txn);
+
+    mcast_mac = shash_find_data(mcast_shash, mac);
+    if (!mcast_mac) {
+        mcast_mac = add_mcast_mac_to_cache(ctx, ls, mac, ploc_set_cfg, local);
+
+        if (local) {
+            mcast_mac->local_cfg = vteprec_mcast_macs_local_insert(ctx->txn);
+            vteprec_mcast_macs_local_set_MAC(mcast_mac->local_cfg, mac);
+            vteprec_mcast_macs_local_set_locator_set(mcast_mac->local_cfg,
+                                                     ploc_set_cfg);
+            vteprec_mcast_macs_local_set_logical_switch(mcast_mac->local_cfg,
+                                                        ls->ls_cfg);
+            mcast_mac->remote_cfg = NULL;
+        } else {
+            mcast_mac->remote_cfg = vteprec_mcast_macs_remote_insert(ctx->txn);
+            vteprec_mcast_macs_remote_set_MAC(mcast_mac->remote_cfg, mac);
+            vteprec_mcast_macs_remote_set_locator_set(mcast_mac->remote_cfg,
+                                                      ploc_set_cfg);
+            vteprec_mcast_macs_remote_set_logical_switch(mcast_mac->remote_cfg,
+                                                         ls->ls_cfg);
+            mcast_mac->local_cfg = NULL;
+        }
+    } else {
+        mcast_mac->ploc_set_cfg = ploc_set_cfg;
+        if (local) {
+            vteprec_mcast_macs_local_set_locator_set(mcast_mac->local_cfg,
+                                                     ploc_set_cfg);
+        } else {
+            vteprec_mcast_macs_remote_set_locator_set(mcast_mac->remote_cfg,
+                                                      ploc_set_cfg);
+        }
+    }
+
+    ploc_cfg = find_ploc(ctx, encap, dst_ip);
+    if (!ploc_cfg) {
+        ploc_cfg = vteprec_physical_locator_insert(ctx->txn);
+        vteprec_physical_locator_set_dst_ip(ploc_cfg, dst_ip);
+        vteprec_physical_locator_set_encapsulation_type(ploc_cfg, encap);
+
+        add_ploc_to_cache(ctx, ploc_cfg);
+    }
+
+    add_ploc_to_mcast_mac(mcast_mac, ploc_cfg);
+    commit_mcast_entries(mcast_mac);
+}
+
+static void
+del_mcast_entry(struct vtep_ctl_context *ctx,
+                struct vtep_ctl_lswitch *ls, const char *mac,
+                const char *encap, const char *dst_ip, bool local)
+{
+    struct vtep_ctl_mcast_mac *mcast_mac;
+    struct shash *mcast_shash;
+    struct vteprec_physical_locator *ploc_cfg;
+    struct vteprec_physical_locator_set *ploc_set_cfg;
+
+    mcast_shash = local ? &ls->mcast_local : &ls->mcast_remote;
+
+    mcast_mac = shash_find_data(mcast_shash, mac);
+    if (!mcast_mac) {
+        return;
+    }
+
+    ploc_cfg = find_ploc(ctx, encap, dst_ip);
+    if (!ploc_cfg) {
+        /* Couldn't find the physical locator, so just ignore. */
+        return;
+    }
+
+    /* Physical locator sets are immutable, so allocate a new one. */
+    ploc_set_cfg = vteprec_physical_locator_set_insert(ctx->txn);
+    mcast_mac->ploc_set_cfg = ploc_set_cfg;
+
+    del_ploc_from_mcast_mac(mcast_mac, ploc_cfg);
+    if (list_is_empty(&mcast_mac->locators)) {
+        struct shash_node *node = shash_find(mcast_shash, mac);
+
+        vteprec_physical_locator_set_delete(ploc_set_cfg);
+
+        if (local) {
+            vteprec_mcast_macs_local_delete(mcast_mac->local_cfg);
+        } else {
+            vteprec_mcast_macs_remote_delete(mcast_mac->remote_cfg);
+        }
+        
+        free(node->data);
+        shash_delete(mcast_shash, node);
+    } else {
+        if (local) {
+            vteprec_mcast_macs_local_set_locator_set(mcast_mac->local_cfg,
+                                                     ploc_set_cfg);
+        } else {
+            vteprec_mcast_macs_remote_set_locator_set(mcast_mac->remote_cfg,
+                                                      ploc_set_cfg);
+        }
+        commit_mcast_entries(mcast_mac);
+    }
+}
+
+static void
+add_del_mcast_entry(struct vtep_ctl_context *ctx, bool add, bool local)
+{
+    struct vtep_ctl_lswitch *ls;
+    const char *mac;
+    const char *encap;
+    const char *dst_ip;
+
+    vtep_ctl_context_populate_cache(ctx);
+
+    ls = find_lswitch(ctx, ctx->argv[1], true);
+    mac = ctx->argv[2];
+
+    if (ctx->argc == 4) {
+        encap = "vxlan_over_ipv4";
+        dst_ip = ctx->argv[3];
+    } else {
+        encap = ctx->argv[3];
+        dst_ip = ctx->argv[4];
+    }
+
+    if (add) {
+        add_mcast_entry(ctx, ls, mac, encap, dst_ip, local);
+    } else {
+        del_mcast_entry(ctx, ls, mac, encap, dst_ip, local);
+    }
+
+    vtep_ctl_context_invalidate_cache(ctx);
+}
+
+static void
+cmd_add_mcast_local(struct vtep_ctl_context *ctx)
+{
+    add_del_mcast_entry(ctx, true, true);
+}
+
+static void
+cmd_add_mcast_remote(struct vtep_ctl_context *ctx)
+{
+    add_del_mcast_entry(ctx, true, false);
+}
+
+static void
+cmd_del_mcast_local(struct vtep_ctl_context *ctx)
+{
+    add_del_mcast_entry(ctx, false, true);
+}
+
+static void
+cmd_del_mcast_remote(struct vtep_ctl_context *ctx)
+{
+    add_del_mcast_entry(ctx, false, false);
+}
+
+static void
+clear_macs(struct vtep_ctl_context *ctx, bool local)
+{
+    struct vtep_ctl_lswitch *ls;
+    const struct shash_node *node;
+    struct shash *ucast_shash;
+    struct shash *mcast_shash;
+
+    vtep_ctl_context_populate_cache(ctx);
+    ls = find_lswitch(ctx, ctx->argv[1], true);
+
+    ucast_shash = local ? &ls->ucast_local : &ls->ucast_remote;
+    mcast_shash = local ? &ls->mcast_local : &ls->mcast_remote;
+
+    SHASH_FOR_EACH (node, ucast_shash) {
+        if (local) {
+            struct vteprec_ucast_macs_local *ucast_cfg = node->data;
+            vteprec_ucast_macs_local_delete(ucast_cfg);
+        } else {
+            struct vteprec_ucast_macs_remote *ucast_cfg = node->data;
+            vteprec_ucast_macs_remote_delete(ucast_cfg);
+        }
+    }
+
+    SHASH_FOR_EACH (node, mcast_shash) {
+        struct vtep_ctl_mcast_mac *mcast_mac = node->data;
+        if (local) {
+            vteprec_mcast_macs_local_delete(mcast_mac->local_cfg);
+        } else {
+            vteprec_mcast_macs_remote_delete(mcast_mac->remote_cfg);
+        }
+    }
+
+    vtep_ctl_context_invalidate_cache(ctx);
+}
+
+static void
+cmd_clear_local_macs(struct vtep_ctl_context *ctx)
+{
+    clear_macs(ctx, true);
+}
+
+static void
+cmd_clear_remote_macs(struct vtep_ctl_context *ctx)
+{
+    clear_macs(ctx, false);
+}
+
+static void
+list_macs(struct vtep_ctl_context *ctx, bool local)
+{
+    struct vtep_ctl_lswitch *ls;
+    const struct shash_node *node;
+    struct shash *ucast_shash;
+    struct svec ucast_macs;
+    struct shash *mcast_shash;
+    struct svec mcast_macs;
+
+    vtep_ctl_context_populate_cache(ctx);
+    ls = find_lswitch(ctx, ctx->argv[1], true);
+
+    ucast_shash = local ? &ls->ucast_local : &ls->ucast_remote;
+    mcast_shash = local ? &ls->mcast_local : &ls->mcast_remote;
+
+    svec_init(&ucast_macs);
+    SHASH_FOR_EACH (node, ucast_shash) {
+        struct vteprec_ucast_macs_local *ucast_local = node->data;
+        struct vteprec_ucast_macs_remote *ucast_remote = node->data;
+        struct vteprec_physical_locator *ploc_cfg;
+        char *entry;
+
+        ploc_cfg = local ? ucast_local->locator : ucast_remote->locator;
+
+        entry = xasprintf("  %s -> %s/%s", node->name,
+                          ploc_cfg->encapsulation_type, ploc_cfg->dst_ip);
+        svec_add_nocopy(&ucast_macs, entry);
+    }
+    ds_put_format(&ctx->output, "ucast-mac-%s\n", local ? "local" : "remote");
+    output_sorted(&ucast_macs, &ctx->output);
+    ds_put_char(&ctx->output, '\n');
+    svec_destroy(&ucast_macs);
+
+    svec_init(&mcast_macs);
+    SHASH_FOR_EACH (node, mcast_shash) {
+        struct vtep_ctl_mcast_mac *mcast_mac = node->data;
+        struct vtep_ctl_ploc *ploc;
+        char *entry;
+
+        LIST_FOR_EACH (ploc, locators_node, &mcast_mac->locators) {
+            entry = xasprintf("  %s -> %s/%s", node->name,
+                              ploc->ploc_cfg->encapsulation_type,
+                              ploc->ploc_cfg->dst_ip);
+            svec_add_nocopy(&mcast_macs, entry);
+        }
+    }
+    ds_put_format(&ctx->output, "mcast-mac-%s\n", local ? "local" : "remote");
+    output_sorted(&mcast_macs, &ctx->output);
+    ds_put_char(&ctx->output, '\n');
+    svec_destroy(&mcast_macs);
+}
+
+static void
+cmd_list_local_macs(struct vtep_ctl_context *ctx)
+{
+    list_macs(ctx, true);
+}
+
+static void
+cmd_list_remote_macs(struct vtep_ctl_context *ctx)
+{
+    list_macs(ctx, false);
+}
+
+static void
+verify_managers(const struct vteprec_global *vtep_global)
+{
+    size_t i;
+
+    vteprec_global_verify_managers(vtep_global);
+
+    for (i = 0; i < vtep_global->n_managers; ++i) {
+        const struct vteprec_manager *mgr = vtep_global->managers[i];
+
+        vteprec_manager_verify_target(mgr);
+    }
+}
+
+static void
+pre_manager(struct vtep_ctl_context *ctx)
+{
+    ovsdb_idl_add_column(ctx->idl, &vteprec_global_col_managers);
+    ovsdb_idl_add_column(ctx->idl, &vteprec_manager_col_target);
+}
+
+static void
+cmd_get_manager(struct vtep_ctl_context *ctx)
+{
+    const struct vteprec_global *vtep_global = ctx->vtep_global;
+    struct svec targets;
+    size_t i;
+
+    verify_managers(vtep_global);
+
+    /* Print the targets in sorted order for reproducibility. */
+    svec_init(&targets);
+
+    for (i = 0; i < vtep_global->n_managers; i++) {
+        svec_add(&targets, vtep_global->managers[i]->target);
+    }
+
+    svec_sort_unique(&targets);
+    for (i = 0; i < targets.n; i++) {
+        ds_put_format(&ctx->output, "%s\n", targets.names[i]);
+    }
+    svec_destroy(&targets);
+}
+
+static void
+delete_managers(const struct vtep_ctl_context *ctx)
+{
+    const struct vteprec_global *vtep_global = ctx->vtep_global;
+    size_t i;
+
+    /* Delete Manager rows pointed to by 'managers' column. */
+    for (i = 0; i < vtep_global->n_managers; i++) {
+        vteprec_manager_delete(vtep_global->managers[i]);
+    }
+
+    /* Delete 'Manager' row refs in 'managers' column. */
+    vteprec_global_set_managers(vtep_global, NULL, 0);
+}
+
+static void
+cmd_del_manager(struct vtep_ctl_context *ctx)
+{
+    const struct vteprec_global *vtep_global = ctx->vtep_global;
+
+    verify_managers(vtep_global);
+    delete_managers(ctx);
+}
+
+static void
+insert_managers(struct vtep_ctl_context *ctx, char *targets[], size_t n)
+{
+    struct vteprec_manager **managers;
+    size_t i;
+
+    /* Insert each manager in a new row in Manager table. */
+    managers = xmalloc(n * sizeof *managers);
+    for (i = 0; i < n; i++) {
+        if (stream_verify_name(targets[i]) && pstream_verify_name(targets[i])) {
+            VLOG_WARN("target type \"%s\" is possibly erroneous", targets[i]);
+        }
+        managers[i] = vteprec_manager_insert(ctx->txn);
+        vteprec_manager_set_target(managers[i], targets[i]);
+    }
+
+    /* Store uuids of new Manager rows in 'managers' column. */
+    vteprec_global_set_managers(ctx->vtep_global, managers, n);
+    free(managers);
+}
+
+static void
+cmd_set_manager(struct vtep_ctl_context *ctx)
+{
+    const size_t n = ctx->argc - 1;
+
+    verify_managers(ctx->vtep_global);
+    delete_managers(ctx);
+    insert_managers(ctx, &ctx->argv[1], n);
+}
+
+/* Parameter commands. */
+
+struct vtep_ctl_row_id {
+    const struct ovsdb_idl_table_class *table;
+    const struct ovsdb_idl_column *name_column;
+    const struct ovsdb_idl_column *uuid_column;
+};
+
+struct vtep_ctl_table_class {
+    struct ovsdb_idl_table_class *class;
+    struct vtep_ctl_row_id row_ids[2];
+};
+
+static const struct vtep_ctl_table_class tables[] = {
+    {&vteprec_table_global,
+     {{&vteprec_table_global, NULL, NULL},
+      {NULL, NULL, NULL}}},
+
+    {&vteprec_table_logical_binding_stats,
+     {{NULL, NULL, NULL},
+      {NULL, NULL, NULL}}},
+
+    {&vteprec_table_logical_switch,
+     {{&vteprec_table_logical_switch, &vteprec_logical_switch_col_name, NULL},
+      {NULL, NULL, NULL}}},
+
+    {&vteprec_table_ucast_macs_local,
+     {{NULL, NULL, NULL},
+      {NULL, NULL, NULL}}},
+
+    {&vteprec_table_ucast_macs_remote,
+     {{NULL, NULL, NULL},
+      {NULL, NULL, NULL}}},
+
+    {&vteprec_table_mcast_macs_local,
+     {{NULL, NULL, NULL},
+      {NULL, NULL, NULL}}},
+
+    {&vteprec_table_mcast_macs_remote,
+     {{NULL, NULL, NULL},
+      {NULL, NULL, NULL}}},
+
+    {&vteprec_table_manager,
+     {{&vteprec_table_manager, &vteprec_manager_col_target, NULL},
+      {NULL, NULL, NULL}}},
+
+    {&vteprec_table_physical_locator,
+     {{NULL, NULL, NULL},
+      {NULL, NULL, NULL}}},
+
+    {&vteprec_table_physical_locator_set,
+     {{NULL, NULL, NULL},
+      {NULL, NULL, NULL}}},
+
+    {&vteprec_table_physical_port,
+     {{&vteprec_table_physical_port, &vteprec_physical_port_col_name, NULL},
+      {NULL, NULL, NULL}}},
+
+    {&vteprec_table_physical_switch,
+     {{&vteprec_table_physical_switch, &vteprec_physical_switch_col_name, NULL},
+      {NULL, NULL, NULL}}},
+
+    {NULL, {{NULL, NULL, NULL}, {NULL, NULL, NULL}}}
+};
+
+static void
+die_if_error(char *error)
+{
+    if (error) {
+        vtep_ctl_fatal("%s", error);
+    }
+}
+
+static int
+to_lower_and_underscores(unsigned c)
+{
+    return c == '-' ? '_' : tolower(c);
+}
+
+static unsigned int
+score_partial_match(const char *name, const char *s)
+{
+    int score;
+
+    if (!strcmp(name, s)) {
+        return UINT_MAX;
+    }
+    for (score = 0; ; score++, name++, s++) {
+        if (to_lower_and_underscores(*name) != to_lower_and_underscores(*s)) {
+            break;
+        } else if (*name == '\0') {
+            return UINT_MAX - 1;
+        }
+    }
+    return *s == '\0' ? score : 0;
+}
+
+static const struct vtep_ctl_table_class *
+get_table(const char *table_name)
+{
+    const struct vtep_ctl_table_class *table;
+    const struct vtep_ctl_table_class *best_match = NULL;
+    unsigned int best_score = 0;
+
+    for (table = tables; table->class; table++) {
+        unsigned int score = score_partial_match(table->class->name,
+                                                 table_name);
+        if (score > best_score) {
+            best_match = table;
+            best_score = score;
+        } else if (score == best_score) {
+            best_match = NULL;
+        }
+    }
+    if (best_match) {
+        return best_match;
+    } else if (best_score) {
+        vtep_ctl_fatal("multiple table names match \"%s\"", table_name);
+    } else {
+        vtep_ctl_fatal("unknown table \"%s\"", table_name);
+    }
+}
+
+static const struct vtep_ctl_table_class *
+pre_get_table(struct vtep_ctl_context *ctx, const char *table_name)
+{
+    const struct vtep_ctl_table_class *table_class;
+    int i;
+
+    table_class = get_table(table_name);
+    ovsdb_idl_add_table(ctx->idl, table_class->class);
+
+    for (i = 0; i < ARRAY_SIZE(table_class->row_ids); i++) {
+        const struct vtep_ctl_row_id *id = &table_class->row_ids[i];
+        if (id->table) {
+            ovsdb_idl_add_table(ctx->idl, id->table);
+        }
+        if (id->name_column) {
+            ovsdb_idl_add_column(ctx->idl, id->name_column);
+        }
+        if (id->uuid_column) {
+            ovsdb_idl_add_column(ctx->idl, id->uuid_column);
+        }
+    }
+
+    return table_class;
+}
+
+static const struct ovsdb_idl_row *
+get_row_by_id(struct vtep_ctl_context *ctx, const struct vtep_ctl_table_class *table,
+              const struct vtep_ctl_row_id *id, const char *record_id)
+{
+    const struct ovsdb_idl_row *referrer, *final;
+
+    if (!id->table) {
+        return NULL;
+    }
+
+    if (!id->name_column) {
+        if (strcmp(record_id, ".")) {
+            return NULL;
+        }
+        referrer = ovsdb_idl_first_row(ctx->idl, id->table);
+        if (!referrer || ovsdb_idl_next_row(referrer)) {
+            return NULL;
+        }
+    } else {
+        const struct ovsdb_idl_row *row;
+
+        referrer = NULL;
+        for (row = ovsdb_idl_first_row(ctx->idl, id->table);
+             row != NULL;
+             row = ovsdb_idl_next_row(row))
+        {
+            const struct ovsdb_datum *name;
+
+            name = ovsdb_idl_get(row, id->name_column,
+                                 OVSDB_TYPE_STRING, OVSDB_TYPE_VOID);
+            if (name->n == 1 && !strcmp(name->keys[0].string, record_id)) {
+                if (referrer) {
+                    vtep_ctl_fatal("multiple rows in %s match \"%s\"",
+                                table->class->name, record_id);
+                }
+                referrer = row;
+            }
+        }
+    }
+    if (!referrer) {
+        return NULL;
+    }
+
+    final = NULL;
+    if (id->uuid_column) {
+        const struct ovsdb_datum *uuid;
+
+        ovsdb_idl_txn_verify(referrer, id->uuid_column);
+        uuid = ovsdb_idl_get(referrer, id->uuid_column,
+                             OVSDB_TYPE_UUID, OVSDB_TYPE_VOID);
+        if (uuid->n == 1) {
+            final = ovsdb_idl_get_row_for_uuid(ctx->idl, table->class,
+                                               &uuid->keys[0].uuid);
+        }
+    } else {
+        final = referrer;
+    }
+
+    return final;
+}
+
+static const struct ovsdb_idl_row *
+get_row (struct vtep_ctl_context *ctx,
+         const struct vtep_ctl_table_class *table, const char *record_id)
+{
+    const struct ovsdb_idl_row *row;
+    struct uuid uuid;
+
+    if (uuid_from_string(&uuid, record_id)) {
+        row = ovsdb_idl_get_row_for_uuid(ctx->idl, table->class, &uuid);
+    } else {
+        int i;
+
+        for (i = 0; i < ARRAY_SIZE(table->row_ids); i++) {
+            row = get_row_by_id(ctx, table, &table->row_ids[i], record_id);
+            if (row) {
+                break;
+            }
+        }
+    }
+    return row;
+}
+
+static const struct ovsdb_idl_row *
+must_get_row(struct vtep_ctl_context *ctx,
+             const struct vtep_ctl_table_class *table, const char *record_id)
+{
+    const struct ovsdb_idl_row *row = get_row(ctx, table, record_id);
+    if (!row) {
+        vtep_ctl_fatal("no row \"%s\" in table %s",
+                    record_id, table->class->name);
+    }
+    return row;
+}
+
+static char *
+get_column(const struct vtep_ctl_table_class *table, const char *column_name,
+           const struct ovsdb_idl_column **columnp)
+{
+    const struct ovsdb_idl_column *best_match = NULL;
+    unsigned int best_score = 0;
+    size_t i;
+
+    for (i = 0; i < table->class->n_columns; i++) {
+        const struct ovsdb_idl_column *column = &table->class->columns[i];
+        unsigned int score = score_partial_match(column->name, column_name);
+        if (score > best_score) {
+            best_match = column;
+            best_score = score;
+        } else if (score == best_score) {
+            best_match = NULL;
+        }
+    }
+
+    *columnp = best_match;
+    if (best_match) {
+        return NULL;
+    } else if (best_score) {
+        return xasprintf("%s contains more than one column whose name "
+                         "matches \"%s\"", table->class->name, column_name);
+    } else {
+        return xasprintf("%s does not contain a column whose name matches "
+                         "\"%s\"", table->class->name, column_name);
+    }
+}
+
+static struct ovsdb_symbol *
+create_symbol(struct ovsdb_symbol_table *symtab, const char *id, bool *newp)
+{
+    struct ovsdb_symbol *symbol;
+
+    if (id[0] != '@') {
+        vtep_ctl_fatal("row id \"%s\" does not begin with \"@\"", id);
+    }
+
+    if (newp) {
+        *newp = ovsdb_symbol_table_get(symtab, id) == NULL;
+    }
+
+    symbol = ovsdb_symbol_table_insert(symtab, id);
+    if (symbol->created) {
+        vtep_ctl_fatal("row id \"%s\" may only be specified on one --id option",
+                    id);
+    }
+    symbol->created = true;
+    return symbol;
+}
+
+static void
+pre_get_column(struct vtep_ctl_context *ctx,
+               const struct vtep_ctl_table_class *table, const char *column_name,
+               const struct ovsdb_idl_column **columnp)
+{
+    die_if_error(get_column(table, column_name, columnp));
+    ovsdb_idl_add_column(ctx->idl, *columnp);
+}
+
+static char *
+missing_operator_error(const char *arg, const char **allowed_operators,
+                       size_t n_allowed)
+{
+    struct ds s;
+
+    ds_init(&s);
+    ds_put_format(&s, "%s: argument does not end in ", arg);
+    ds_put_format(&s, "\"%s\"", allowed_operators[0]);
+    if (n_allowed == 2) {
+        ds_put_format(&s, " or \"%s\"", allowed_operators[1]);
+    } else if (n_allowed > 2) {
+        size_t i;
+
+        for (i = 1; i < n_allowed - 1; i++) {
+            ds_put_format(&s, ", \"%s\"", allowed_operators[i]);
+        }
+        ds_put_format(&s, ", or \"%s\"", allowed_operators[i]);
+    }
+    ds_put_format(&s, " followed by a value.");
+
+    return ds_steal_cstr(&s);
+}
+
+/* Breaks 'arg' apart into a number of fields in the following order:
+ *
+ *      - The name of a column in 'table', stored into '*columnp'.  The column
+ *        name may be abbreviated.
+ *
+ *      - Optionally ':' followed by a key string.  The key is stored as a
+ *        malloc()'d string into '*keyp', or NULL if no key is present in
+ *        'arg'.
+ *
+ *      - If 'valuep' is nonnull, an operator followed by a value string.  The
+ *        allowed operators are the 'n_allowed' string in 'allowed_operators',
+ *        or just "=" if 'n_allowed' is 0.  If 'operatorp' is nonnull, then the
+ *        index of the operator within 'allowed_operators' is stored into
+ *        '*operatorp'.  The value is stored as a malloc()'d string into
+ *        '*valuep', or NULL if no value is present in 'arg'.
+ *
+ * On success, returns NULL.  On failure, returned a malloc()'d string error
+ * message and stores NULL into all of the nonnull output arguments. */
+static char * WARN_UNUSED_RESULT
+parse_column_key_value(const char *arg,
+                       const struct vtep_ctl_table_class *table,
+                       const struct ovsdb_idl_column **columnp, char **keyp,
+                       int *operatorp,
+                       const char **allowed_operators, size_t n_allowed,
+                       char **valuep)
+{
+    const char *p = arg;
+    char *column_name;
+    char *error;
+
+    ovs_assert(!(operatorp && !valuep));
+    *keyp = NULL;
+    if (valuep) {
+        *valuep = NULL;
+    }
+
+    /* Parse column name. */
+    error = ovsdb_token_parse(&p, &column_name);
+    if (error) {
+        goto error;
+    }
+    if (column_name[0] == '\0') {
+        free(column_name);
+        error = xasprintf("%s: missing column name", arg);
+        goto error;
+    }
+    error = get_column(table, column_name, columnp);
+    free(column_name);
+    if (error) {
+        goto error;
+    }
+
+    /* Parse key string. */
+    if (*p == ':') {
+        p++;
+        error = ovsdb_token_parse(&p, keyp);
+        if (error) {
+            goto error;
+        }
+    }
+
+    /* Parse value string. */
+    if (valuep) {
+        size_t best_len;
+        size_t i;
+        int best;
+
+        if (!allowed_operators) {
+            static const char *equals = "=";
+            allowed_operators = &equals;
+            n_allowed = 1;
+        }
+
+        best = -1;
+        best_len = 0;
+        for (i = 0; i < n_allowed; i++) {
+            const char *op = allowed_operators[i];
+            size_t op_len = strlen(op);
+
+            if (op_len > best_len && !strncmp(op, p, op_len) && p[op_len]) {
+                best_len = op_len;
+                best = i;
+            }
+        }
+        if (best < 0) {
+            error = missing_operator_error(arg, allowed_operators, n_allowed);
+            goto error;
+        }
+
+        if (operatorp) {
+            *operatorp = best;
+        }
+        *valuep = xstrdup(p + best_len);
+    } else {
+        if (*p != '\0') {
+            error = xasprintf("%s: trailing garbage \"%s\" in argument",
+                              arg, p);
+            goto error;
+        }
+    }
+    return NULL;
+
+error:
+    *columnp = NULL;
+    free(*keyp);
+    *keyp = NULL;
+    if (valuep) {
+        free(*valuep);
+        *valuep = NULL;
+        if (operatorp) {
+            *operatorp = -1;
+        }
+    }
+    return error;
+}
+
+static const struct ovsdb_idl_column *
+pre_parse_column_key_value(struct vtep_ctl_context *ctx,
+                           const char *arg,
+                           const struct vtep_ctl_table_class *table)
+{
+    const struct ovsdb_idl_column *column;
+    const char *p;
+    char *column_name;
+
+    p = arg;
+    die_if_error(ovsdb_token_parse(&p, &column_name));
+    if (column_name[0] == '\0') {
+        vtep_ctl_fatal("%s: missing column name", arg);
+    }
+
+    pre_get_column(ctx, table, column_name, &column);
+    free(column_name);
+
+    return column;
+}
+
+static void
+check_mutable(const struct vtep_ctl_table_class *table,
+              const struct ovsdb_idl_column *column)
+{
+    if (!column->mutable) {
+        vtep_ctl_fatal("cannot modify read-only column %s in table %s",
+                    column->name, table->class->name);
+    }
+}
+
+static void
+pre_cmd_get(struct vtep_ctl_context *ctx)
+{
+    const char *id = shash_find_data(&ctx->options, "--id");
+    const char *table_name = ctx->argv[1];
+    const struct vtep_ctl_table_class *table;
+    int i;
+
+    /* Using "get" without --id or a column name could possibly make sense.
+     * Maybe, for example, a vtep-ctl run wants to assert that a row exists.
+     * But it is unlikely that an interactive user would want to do that, so
+     * issue a warning if we're running on a terminal. */
+    if (!id && ctx->argc <= 3 && isatty(STDOUT_FILENO)) {
+        VLOG_WARN("\"get\" command without row arguments or \"--id\" is "
+                  "possibly erroneous");
+    }
+
+    table = pre_get_table(ctx, table_name);
+    for (i = 3; i < ctx->argc; i++) {
+        if (!strcasecmp(ctx->argv[i], "_uuid")
+            || !strcasecmp(ctx->argv[i], "-uuid")) {
+            continue;
+        }
+
+        pre_parse_column_key_value(ctx, ctx->argv[i], table);
+    }
+}
+
+static void
+cmd_get(struct vtep_ctl_context *ctx)
+{
+    const char *id = shash_find_data(&ctx->options, "--id");
+    bool if_exists = shash_find(&ctx->options, "--if-exists");
+    const char *table_name = ctx->argv[1];
+    const char *record_id = ctx->argv[2];
+    const struct vtep_ctl_table_class *table;
+    const struct ovsdb_idl_row *row;
+    struct ds *out = &ctx->output;
+    int i;
+
+    table = get_table(table_name);
+    row = must_get_row(ctx, table, record_id);
+    if (id) {
+        struct ovsdb_symbol *symbol;
+        bool new;
+
+        symbol = create_symbol(ctx->symtab, id, &new);
+        if (!new) {
+            vtep_ctl_fatal("row id \"%s\" specified on \"get\" command was used "
+                        "before it was defined", id);
+        }
+        symbol->uuid = row->uuid;
+
+        /* This symbol refers to a row that already exists, so disable warnings
+         * about it being unreferenced. */
+        symbol->strong_ref = true;
+    }
+    for (i = 3; i < ctx->argc; i++) {
+        const struct ovsdb_idl_column *column;
+        const struct ovsdb_datum *datum;
+        char *key_string;
+
+        /* Special case for obtaining the UUID of a row.  We can't just do this
+         * through parse_column_key_value() below since it returns a "struct
+         * ovsdb_idl_column" and the UUID column doesn't have one. */
+        if (!strcasecmp(ctx->argv[i], "_uuid")
+            || !strcasecmp(ctx->argv[i], "-uuid")) {
+            ds_put_format(out, UUID_FMT"\n", UUID_ARGS(&row->uuid));
+            continue;
+        }
+
+        die_if_error(parse_column_key_value(ctx->argv[i], table,
+                                            &column, &key_string,
+                                            NULL, NULL, 0, NULL));
+
+        ovsdb_idl_txn_verify(row, column);
+        datum = ovsdb_idl_read(row, column);
+        if (key_string) {
+            union ovsdb_atom key;
+            unsigned int idx;
+
+            if (column->type.value.type == OVSDB_TYPE_VOID) {
+                vtep_ctl_fatal("cannot specify key to get for non-map column %s",
+                            column->name);
+            }
+
+            die_if_error(ovsdb_atom_from_string(&key,
+                                                &column->type.key,
+                                                key_string, ctx->symtab));
+
+            idx = ovsdb_datum_find_key(datum, &key,
+                                       column->type.key.type);
+            if (idx == UINT_MAX) {
+                if (!if_exists) {
+                    vtep_ctl_fatal("no key \"%s\" in %s record \"%s\" column %s",
+                                key_string, table->class->name, record_id,
+                                column->name);
+                }
+            } else {
+                ovsdb_atom_to_string(&datum->values[idx],
+                                     column->type.value.type, out);
+            }
+            ovsdb_atom_destroy(&key, column->type.key.type);
+        } else {
+            ovsdb_datum_to_string(datum, &column->type, out);
+        }
+        ds_put_char(out, '\n');
+
+        free(key_string);
+    }
+}
+
+static void
+parse_column_names(const char *column_names,
+                   const struct vtep_ctl_table_class *table,
+                   const struct ovsdb_idl_column ***columnsp,
+                   size_t *n_columnsp)
+{
+    const struct ovsdb_idl_column **columns;
+    size_t n_columns;
+
+    if (!column_names) {
+        size_t i;
+
+        n_columns = table->class->n_columns + 1;
+        columns = xmalloc(n_columns * sizeof *columns);
+        columns[0] = NULL;
+        for (i = 0; i < table->class->n_columns; i++) {
+            columns[i + 1] = &table->class->columns[i];
+        }
+    } else {
+        char *s = xstrdup(column_names);
+        size_t allocated_columns;
+        char *save_ptr = NULL;
+        char *column_name;
+
+        columns = NULL;
+        allocated_columns = n_columns = 0;
+        for (column_name = strtok_r(s, ", ", &save_ptr); column_name;
+             column_name = strtok_r(NULL, ", ", &save_ptr)) {
+            const struct ovsdb_idl_column *column;
+
+            if (!strcasecmp(column_name, "_uuid")) {
+                column = NULL;
+            } else {
+                die_if_error(get_column(table, column_name, &column));
+            }
+            if (n_columns >= allocated_columns) {
+                columns = x2nrealloc(columns, &allocated_columns,
+                                     sizeof *columns);
+            }
+            columns[n_columns++] = column;
+        }
+        free(s);
+
+        if (!n_columns) {
+            vtep_ctl_fatal("must specify at least one column name");
+        }
+    }
+    *columnsp = columns;
+    *n_columnsp = n_columns;
+}
+
+
+static void
+pre_list_columns(struct vtep_ctl_context *ctx,
+                 const struct vtep_ctl_table_class *table,
+                 const char *column_names)
+{
+    const struct ovsdb_idl_column **columns;
+    size_t n_columns;
+    size_t i;
+
+    parse_column_names(column_names, table, &columns, &n_columns);
+    for (i = 0; i < n_columns; i++) {
+        if (columns[i]) {
+            ovsdb_idl_add_column(ctx->idl, columns[i]);
+        }
+    }
+    free(columns);
+}
+
+static void
+pre_cmd_list(struct vtep_ctl_context *ctx)
+{
+    const char *column_names = shash_find_data(&ctx->options, "--columns");
+    const char *table_name = ctx->argv[1];
+    const struct vtep_ctl_table_class *table;
+
+    table = pre_get_table(ctx, table_name);
+    pre_list_columns(ctx, table, column_names);
+}
+
+static struct table *
+list_make_table(const struct ovsdb_idl_column **columns, size_t n_columns)
+{
+    struct table *out;
+    size_t i;
+
+    out = xmalloc(sizeof *out);
+    table_init(out);
+
+    for (i = 0; i < n_columns; i++) {
+        const struct ovsdb_idl_column *column = columns[i];
+        const char *column_name = column ? column->name : "_uuid";
+
+        table_add_column(out, "%s", column_name);
+    }
+
+    return out;
+}
+
+static void
+list_record(const struct ovsdb_idl_row *row,
+            const struct ovsdb_idl_column **columns, size_t n_columns,
+            struct table *out)
+{
+    size_t i;
+
+    table_add_row(out);
+    for (i = 0; i < n_columns; i++) {
+        const struct ovsdb_idl_column *column = columns[i];
+        struct cell *cell = table_add_cell(out);
+
+        if (!column) {
+            struct ovsdb_datum datum;
+            union ovsdb_atom atom;
+
+            atom.uuid = row->uuid;
+
+            datum.keys = &atom;
+            datum.values = NULL;
+            datum.n = 1;
+
+            cell->json = ovsdb_datum_to_json(&datum, &ovsdb_type_uuid);
+            cell->type = &ovsdb_type_uuid;
+        } else {
+            const struct ovsdb_datum *datum = ovsdb_idl_read(row, column);
+
+            cell->json = ovsdb_datum_to_json(datum, &column->type);
+            cell->type = &column->type;
+        }
+    }
+}
+
+static void
+cmd_list(struct vtep_ctl_context *ctx)
+{
+    const char *column_names = shash_find_data(&ctx->options, "--columns");
+    const struct ovsdb_idl_column **columns;
+    const char *table_name = ctx->argv[1];
+    const struct vtep_ctl_table_class *table;
+    struct table *out;
+    size_t n_columns;
+    int i;
+
+    table = get_table(table_name);
+    parse_column_names(column_names, table, &columns, &n_columns);
+    out = ctx->table = list_make_table(columns, n_columns);
+    if (ctx->argc > 2) {
+        for (i = 2; i < ctx->argc; i++) {
+            list_record(must_get_row(ctx, table, ctx->argv[i]),
+                        columns, n_columns, out);
+        }
+    } else {
+        const struct ovsdb_idl_row *row;
+
+        for (row = ovsdb_idl_first_row(ctx->idl, table->class); row != NULL;
+             row = ovsdb_idl_next_row(row)) {
+            list_record(row, columns, n_columns, out);
+        }
+    }
+    free(columns);
+}
+
+static void
+pre_cmd_find(struct vtep_ctl_context *ctx)
+{
+    const char *column_names = shash_find_data(&ctx->options, "--columns");
+    const char *table_name = ctx->argv[1];
+    const struct vtep_ctl_table_class *table;
+    int i;
+
+    table = pre_get_table(ctx, table_name);
+    pre_list_columns(ctx, table, column_names);
+    for (i = 2; i < ctx->argc; i++) {
+        pre_parse_column_key_value(ctx, ctx->argv[i], table);
+    }
+}
+
+static void
+cmd_find(struct vtep_ctl_context *ctx)
+{
+    const char *column_names = shash_find_data(&ctx->options, "--columns");
+    const struct ovsdb_idl_column **columns;
+    const char *table_name = ctx->argv[1];
+    const struct vtep_ctl_table_class *table;
+    const struct ovsdb_idl_row *row;
+    struct table *out;
+    size_t n_columns;
+
+    table = get_table(table_name);
+    parse_column_names(column_names, table, &columns, &n_columns);
+    out = ctx->table = list_make_table(columns, n_columns);
+    for (row = ovsdb_idl_first_row(ctx->idl, table->class); row;
+         row = ovsdb_idl_next_row(row)) {
+        int i;
+
+        for (i = 2; i < ctx->argc; i++) {
+            if (!is_condition_satisfied(table, row, ctx->argv[i],
+                                        ctx->symtab)) {
+                goto next_row;
+            }
+        }
+        list_record(row, columns, n_columns, out);
+
+    next_row: ;
+    }
+    free(columns);
+}
+
+static void
+pre_cmd_set(struct vtep_ctl_context *ctx)
+{
+    const char *table_name = ctx->argv[1];
+    const struct vtep_ctl_table_class *table;
+    int i;
+
+    table = pre_get_table(ctx, table_name);
+    for (i = 3; i < ctx->argc; i++) {
+        const struct ovsdb_idl_column *column;
+
+        column = pre_parse_column_key_value(ctx, ctx->argv[i], table);
+        check_mutable(table, column);
+    }
+}
+
+static void
+set_column(const struct vtep_ctl_table_class *table,
+           const struct ovsdb_idl_row *row, const char *arg,
+           struct ovsdb_symbol_table *symtab)
+{
+    const struct ovsdb_idl_column *column;
+    char *key_string, *value_string;
+    char *error;
+
+    error = parse_column_key_value(arg, table, &column, &key_string,
+                                   NULL, NULL, 0, &value_string);
+    die_if_error(error);
+    if (!value_string) {
+        vtep_ctl_fatal("%s: missing value", arg);
+    }
+
+    if (key_string) {
+        union ovsdb_atom key, value;
+        struct ovsdb_datum datum;
+
+        if (column->type.value.type == OVSDB_TYPE_VOID) {
+            vtep_ctl_fatal("cannot specify key to set for non-map column %s",
+                        column->name);
+        }
+
+        die_if_error(ovsdb_atom_from_string(&key, &column->type.key,
+                                            key_string, symtab));
+        die_if_error(ovsdb_atom_from_string(&value, &column->type.value,
+                                            value_string, symtab));
+
+        ovsdb_datum_init_empty(&datum);
+        ovsdb_datum_add_unsafe(&datum, &key, &value, &column->type);
+
+        ovsdb_atom_destroy(&key, column->type.key.type);
+        ovsdb_atom_destroy(&value, column->type.value.type);
+
+        ovsdb_datum_union(&datum, ovsdb_idl_read(row, column),
+                          &column->type, false);
+        ovsdb_idl_txn_write(row, column, &datum);
+    } else {
+        struct ovsdb_datum datum;
+
+        die_if_error(ovsdb_datum_from_string(&datum, &column->type,
+                                             value_string, symtab));
+        ovsdb_idl_txn_write(row, column, &datum);
+    }
+
+    free(key_string);
+    free(value_string);
+}
+
+static void
+cmd_set(struct vtep_ctl_context *ctx)
+{
+    const char *table_name = ctx->argv[1];
+    const char *record_id = ctx->argv[2];
+    const struct vtep_ctl_table_class *table;
+    const struct ovsdb_idl_row *row;
+    int i;
+
+    table = get_table(table_name);
+    row = must_get_row(ctx, table, record_id);
+    for (i = 3; i < ctx->argc; i++) {
+        set_column(table, row, ctx->argv[i], ctx->symtab);
+    }
+}
+
+static void
+pre_cmd_add(struct vtep_ctl_context *ctx)
+{
+    const char *table_name = ctx->argv[1];
+    const char *column_name = ctx->argv[3];
+    const struct vtep_ctl_table_class *table;
+    const struct ovsdb_idl_column *column;
+
+    table = pre_get_table(ctx, table_name);
+    pre_get_column(ctx, table, column_name, &column);
+    check_mutable(table, column);
+}
+
+static void
+cmd_add(struct vtep_ctl_context *ctx)
+{
+    const char *table_name = ctx->argv[1];
+    const char *record_id = ctx->argv[2];
+    const char *column_name = ctx->argv[3];
+    const struct vtep_ctl_table_class *table;
+    const struct ovsdb_idl_column *column;
+    const struct ovsdb_idl_row *row;
+    const struct ovsdb_type *type;
+    struct ovsdb_datum old;
+    int i;
+
+    table = get_table(table_name);
+    row = must_get_row(ctx, table, record_id);
+    die_if_error(get_column(table, column_name, &column));
+
+    type = &column->type;
+    ovsdb_datum_clone(&old, ovsdb_idl_read(row, column), &column->type);
+    for (i = 4; i < ctx->argc; i++) {
+        struct ovsdb_type add_type;
+        struct ovsdb_datum add;
+
+        add_type = *type;
+        add_type.n_min = 1;
+        add_type.n_max = UINT_MAX;
+        die_if_error(ovsdb_datum_from_string(&add, &add_type, ctx->argv[i],
+                                             ctx->symtab));
+        ovsdb_datum_union(&old, &add, type, false);
+        ovsdb_datum_destroy(&add, type);
+    }
+    if (old.n > type->n_max) {
+        vtep_ctl_fatal("\"add\" operation would put %u %s in column %s of "
+                    "table %s but the maximum number is %u",
+                    old.n,
+                    type->value.type == OVSDB_TYPE_VOID ? "values" : "pairs",
+                    column->name, table->class->name, type->n_max);
+    }
+    ovsdb_idl_txn_verify(row, column);
+    ovsdb_idl_txn_write(row, column, &old);
+}
+
+static void
+pre_cmd_remove(struct vtep_ctl_context *ctx)
+{
+    const char *table_name = ctx->argv[1];
+    const char *column_name = ctx->argv[3];
+    const struct vtep_ctl_table_class *table;
+    const struct ovsdb_idl_column *column;
+
+    table = pre_get_table(ctx, table_name);
+    pre_get_column(ctx, table, column_name, &column);
+    check_mutable(table, column);
+}
+
+static void
+cmd_remove(struct vtep_ctl_context *ctx)
+{
+    const char *table_name = ctx->argv[1];
+    const char *record_id = ctx->argv[2];
+    const char *column_name = ctx->argv[3];
+    const struct vtep_ctl_table_class *table;
+    const struct ovsdb_idl_column *column;
+    const struct ovsdb_idl_row *row;
+    const struct ovsdb_type *type;
+    struct ovsdb_datum old;
+    int i;
+
+    table = get_table(table_name);
+    row = must_get_row(ctx, table, record_id);
+    die_if_error(get_column(table, column_name, &column));
+
+    type = &column->type;
+    ovsdb_datum_clone(&old, ovsdb_idl_read(row, column), &column->type);
+    for (i = 4; i < ctx->argc; i++) {
+        struct ovsdb_type rm_type;
+        struct ovsdb_datum rm;
+        char *error;
+
+        rm_type = *type;
+        rm_type.n_min = 1;
+        rm_type.n_max = UINT_MAX;
+        error = ovsdb_datum_from_string(&rm, &rm_type,
+                                        ctx->argv[i], ctx->symtab);
+        if (error && ovsdb_type_is_map(&rm_type)) {
+            free(error);
+            rm_type.value.type = OVSDB_TYPE_VOID;
+            die_if_error(ovsdb_datum_from_string(&rm, &rm_type,
+                                                 ctx->argv[i], ctx->symtab));
+        }
+        ovsdb_datum_subtract(&old, type, &rm, &rm_type);
+        ovsdb_datum_destroy(&rm, &rm_type);
+    }
+    if (old.n < type->n_min) {
+        vtep_ctl_fatal("\"remove\" operation would put %u %s in column %s of "
+                    "table %s but the minimum number is %u",
+                    old.n,
+                    type->value.type == OVSDB_TYPE_VOID ? "values" : "pairs",
+                    column->name, table->class->name, type->n_min);
+    }
+    ovsdb_idl_txn_verify(row, column);
+    ovsdb_idl_txn_write(row, column, &old);
+}
+
+static void
+pre_cmd_clear(struct vtep_ctl_context *ctx)
+{
+    const char *table_name = ctx->argv[1];
+    const struct vtep_ctl_table_class *table;
+    int i;
+
+    table = pre_get_table(ctx, table_name);
+    for (i = 3; i < ctx->argc; i++) {
+        const struct ovsdb_idl_column *column;
+
+        pre_get_column(ctx, table, ctx->argv[i], &column);
+        check_mutable(table, column);
+    }
+}
+
+static void
+cmd_clear(struct vtep_ctl_context *ctx)
+{
+    const char *table_name = ctx->argv[1];
+    const char *record_id = ctx->argv[2];
+    const struct vtep_ctl_table_class *table;
+    const struct ovsdb_idl_row *row;
+    int i;
+
+    table = get_table(table_name);
+    row = must_get_row(ctx, table, record_id);
+    for (i = 3; i < ctx->argc; i++) {
+        const struct ovsdb_idl_column *column;
+        const struct ovsdb_type *type;
+        struct ovsdb_datum datum;
+
+        die_if_error(get_column(table, ctx->argv[i], &column));
+
+        type = &column->type;
+        if (type->n_min > 0) {
+            vtep_ctl_fatal("\"clear\" operation cannot be applied to column %s "
+                        "of table %s, which is not allowed to be empty",
+                        column->name, table->class->name);
+        }
+
+        ovsdb_datum_init_empty(&datum);
+        ovsdb_idl_txn_write(row, column, &datum);
+    }
+}
+
+static void
+pre_create(struct vtep_ctl_context *ctx)
+{
+    const char *id = shash_find_data(&ctx->options, "--id");
+    const char *table_name = ctx->argv[1];
+    const struct vtep_ctl_table_class *table;
+
+    table = get_table(table_name);
+    if (!id && !table->class->is_root) {
+        VLOG_WARN("applying \"create\" command to table %s without --id "
+                  "option will have no effect", table->class->name);
+    }
+}
+
+static void
+cmd_create(struct vtep_ctl_context *ctx)
+{
+    const char *id = shash_find_data(&ctx->options, "--id");
+    const char *table_name = ctx->argv[1];
+    const struct vtep_ctl_table_class *table = get_table(table_name);
+    const struct ovsdb_idl_row *row;
+    const struct uuid *uuid;
+    int i;
+
+    if (id) {
+        struct ovsdb_symbol *symbol = create_symbol(ctx->symtab, id, NULL);
+        if (table->class->is_root) {
+            /* This table is in the root set, meaning that rows created in it
+             * won't disappear even if they are unreferenced, so disable
+             * warnings about that by pretending that there is a reference. */
+            symbol->strong_ref = true;
+        }
+        uuid = &symbol->uuid;
+    } else {
+        uuid = NULL;
+    }
+
+    row = ovsdb_idl_txn_insert(ctx->txn, table->class, uuid);
+    for (i = 2; i < ctx->argc; i++) {
+        set_column(table, row, ctx->argv[i], ctx->symtab);
+    }
+    ds_put_format(&ctx->output, UUID_FMT, UUID_ARGS(&row->uuid));
+}
+
+/* This function may be used as the 'postprocess' function for commands that
+ * insert new rows into the database.  It expects that the command's 'run'
+ * function prints the UUID reported by ovsdb_idl_txn_insert() as the command's
+ * sole output.  It replaces that output by the row's permanent UUID assigned
+ * by the database server and appends a new-line.
+ *
+ * Currently we use this only for "create", because the higher-level commands
+ * are supposed to be independent of the actual structure of the VTEP
+ * configuration. */
+static void
+post_create(struct vtep_ctl_context *ctx)
+{
+    const struct uuid *real;
+    struct uuid dummy;
+
+    if (!uuid_from_string(&dummy, ds_cstr(&ctx->output))) {
+        NOT_REACHED();
+    }
+    real = ovsdb_idl_txn_get_insert_uuid(ctx->txn, &dummy);
+    if (real) {
+        ds_clear(&ctx->output);
+        ds_put_format(&ctx->output, UUID_FMT, UUID_ARGS(real));
+    }
+    ds_put_char(&ctx->output, '\n');
+}
+
+static void
+pre_cmd_destroy(struct vtep_ctl_context *ctx)
+{
+    const char *table_name = ctx->argv[1];
+
+    pre_get_table(ctx, table_name);
+}
+
+static void
+cmd_destroy(struct vtep_ctl_context *ctx)
+{
+    bool must_exist = !shash_find(&ctx->options, "--if-exists");
+    bool delete_all = shash_find(&ctx->options, "--all");
+    const char *table_name = ctx->argv[1];
+    const struct vtep_ctl_table_class *table;
+    int i;
+
+    table = get_table(table_name);
+
+    if (delete_all && ctx->argc > 2) {
+        vtep_ctl_fatal("--all and records argument should not be specified together");
+    }
+
+    if (delete_all && !must_exist) {
+        vtep_ctl_fatal("--all and --if-exists should not be specified together");
+    }
+
+    if (delete_all) {
+        const struct ovsdb_idl_row *row;
+        const struct ovsdb_idl_row *next_row;
+
+        for (row = ovsdb_idl_first_row(ctx->idl, table->class);
+             row;) {
+             next_row = ovsdb_idl_next_row(row);
+             ovsdb_idl_txn_delete(row);
+             row = next_row;
+        }
+    } else {
+        for (i = 2; i < ctx->argc; i++) {
+            const struct ovsdb_idl_row *row;
+
+            row = (must_exist ? must_get_row : get_row)(ctx, table, ctx->argv[i]);
+            if (row) {
+                ovsdb_idl_txn_delete(row);
+            }
+        }
+    }
+}
+
+#define RELOPS                                  \
+    RELOP(RELOP_EQ,     "=")                    \
+    RELOP(RELOP_NE,     "!=")                   \
+    RELOP(RELOP_LT,     "<")                    \
+    RELOP(RELOP_GT,     ">")                    \
+    RELOP(RELOP_LE,     "<=")                   \
+    RELOP(RELOP_GE,     ">=")                   \
+    RELOP(RELOP_SET_EQ, "{=}")                  \
+    RELOP(RELOP_SET_NE, "{!=}")                 \
+    RELOP(RELOP_SET_LT, "{<}")                  \
+    RELOP(RELOP_SET_GT, "{>}")                  \
+    RELOP(RELOP_SET_LE, "{<=}")                 \
+    RELOP(RELOP_SET_GE, "{>=}")
+
+enum relop {
+#define RELOP(ENUM, STRING) ENUM,
+    RELOPS
+#undef RELOP
+};
+
+static bool
+is_set_operator(enum relop op)
+{
+    return (op == RELOP_SET_EQ || op == RELOP_SET_NE ||
+            op == RELOP_SET_LT || op == RELOP_SET_GT ||
+            op == RELOP_SET_LE || op == RELOP_SET_GE);
+}
+
+static bool
+evaluate_relop(const struct ovsdb_datum *a, const struct ovsdb_datum *b,
+               const struct ovsdb_type *type, enum relop op)
+{
+    switch (op) {
+    case RELOP_EQ:
+    case RELOP_SET_EQ:
+        return ovsdb_datum_compare_3way(a, b, type) == 0;
+    case RELOP_NE:
+    case RELOP_SET_NE:
+        return ovsdb_datum_compare_3way(a, b, type) != 0;
+    case RELOP_LT:
+        return ovsdb_datum_compare_3way(a, b, type) < 0;
+    case RELOP_GT:
+        return ovsdb_datum_compare_3way(a, b, type) > 0;
+    case RELOP_LE:
+        return ovsdb_datum_compare_3way(a, b, type) <= 0;
+    case RELOP_GE:
+        return ovsdb_datum_compare_3way(a, b, type) >= 0;
+
+    case RELOP_SET_LT:
+        return b->n > a->n && ovsdb_datum_includes_all(a, b, type);
+    case RELOP_SET_GT:
+        return a->n > b->n && ovsdb_datum_includes_all(b, a, type);
+    case RELOP_SET_LE:
+        return ovsdb_datum_includes_all(a, b, type);
+    case RELOP_SET_GE:
+        return ovsdb_datum_includes_all(b, a, type);
+
+    default:
+        NOT_REACHED();
+    }
+}
+
+static bool
+is_condition_satisfied(const struct vtep_ctl_table_class *table,
+                       const struct ovsdb_idl_row *row, const char *arg,
+                       struct ovsdb_symbol_table *symtab)
+{
+    static const char *operators[] = {
+#define RELOP(ENUM, STRING) STRING,
+        RELOPS
+#undef RELOP
+    };
+
+    const struct ovsdb_idl_column *column;
+    const struct ovsdb_datum *have_datum;
+    char *key_string, *value_string;
+    struct ovsdb_type type;
+    int operator;
+    bool retval;
+    char *error;
+
+    error = parse_column_key_value(arg, table, &column, &key_string,
+                                   &operator, operators, ARRAY_SIZE(operators),
+                                   &value_string);
+    die_if_error(error);
+    if (!value_string) {
+        vtep_ctl_fatal("%s: missing value", arg);
+    }
+
+    type = column->type;
+    type.n_max = UINT_MAX;
+
+    have_datum = ovsdb_idl_read(row, column);
+    if (key_string) {
+        union ovsdb_atom want_key;
+        struct ovsdb_datum b;
+        unsigned int idx;
+
+        if (column->type.value.type == OVSDB_TYPE_VOID) {
+            vtep_ctl_fatal("cannot specify key to check for non-map column %s",
+                        column->name);
+        }
+
+        die_if_error(ovsdb_atom_from_string(&want_key, &column->type.key,
+                                            key_string, symtab));
+
+        type.key = type.value;
+        type.value.type = OVSDB_TYPE_VOID;
+        die_if_error(ovsdb_datum_from_string(&b, &type, value_string, symtab));
+
+        idx = ovsdb_datum_find_key(have_datum,
+                                   &want_key, column->type.key.type);
+        if (idx == UINT_MAX && !is_set_operator(operator)) {
+            retval = false;
+        } else {
+            struct ovsdb_datum a;
+
+            if (idx != UINT_MAX) {
+                a.n = 1;
+                a.keys = &have_datum->values[idx];
+                a.values = NULL;
+            } else {
+                a.n = 0;
+                a.keys = NULL;
+                a.values = NULL;
+            }
+
+            retval = evaluate_relop(&a, &b, &type, operator);
+        }
+
+        ovsdb_atom_destroy(&want_key, column->type.key.type);
+        ovsdb_datum_destroy(&b, &type);
+    } else {
+        struct ovsdb_datum want_datum;
+
+        die_if_error(ovsdb_datum_from_string(&want_datum, &column->type,
+                                             value_string, symtab));
+        retval = evaluate_relop(have_datum, &want_datum, &type, operator);
+        ovsdb_datum_destroy(&want_datum, &column->type);
+    }
+
+    free(key_string);
+    free(value_string);
+
+    return retval;
+}
+
+static void
+pre_cmd_wait_until(struct vtep_ctl_context *ctx)
+{
+    const char *table_name = ctx->argv[1];
+    const struct vtep_ctl_table_class *table;
+    int i;
+
+    table = pre_get_table(ctx, table_name);
+
+    for (i = 3; i < ctx->argc; i++) {
+        pre_parse_column_key_value(ctx, ctx->argv[i], table);
+    }
+}
+
+static void
+cmd_wait_until(struct vtep_ctl_context *ctx)
+{
+    const char *table_name = ctx->argv[1];
+    const char *record_id = ctx->argv[2];
+    const struct vtep_ctl_table_class *table;
+    const struct ovsdb_idl_row *row;
+    int i;
+
+    table = get_table(table_name);
+
+    row = get_row(ctx, table, record_id);
+    if (!row) {
+        ctx->try_again = true;
+        return;
+    }
+
+    for (i = 3; i < ctx->argc; i++) {
+        if (!is_condition_satisfied(table, row, ctx->argv[i], ctx->symtab)) {
+            ctx->try_again = true;
+            return;
+        }
+    }
+}
+\f
+/* Prepares 'ctx', which has already been initialized with
+ * vtep_ctl_context_init(), for processing 'command'. */
+static void
+vtep_ctl_context_init_command(struct vtep_ctl_context *ctx,
+                           struct vtep_ctl_command *command)
+{
+    ctx->argc = command->argc;
+    ctx->argv = command->argv;
+    ctx->options = command->options;
+
+    ds_swap(&ctx->output, &command->output);
+    ctx->table = command->table;
+
+    ctx->verified_ports = false;
+
+    ctx->try_again = false;
+}
+
+/* Prepares 'ctx' for processing commands, initializing its members with the
+ * values passed in as arguments.
+ *
+ * If 'command' is nonnull, calls vtep_ctl_context_init_command() to prepare for
+ * that particular command. */
+static void
+vtep_ctl_context_init(struct vtep_ctl_context *ctx,
+                      struct vtep_ctl_command *command,
+                      struct ovsdb_idl *idl, struct ovsdb_idl_txn *txn,
+                      const struct vteprec_global *vtep_global,
+                      struct ovsdb_symbol_table *symtab)
+{
+    if (command) {
+        vtep_ctl_context_init_command(ctx, command);
+    }
+    ctx->idl = idl;
+    ctx->txn = txn;
+    ctx->vtep_global = vtep_global;
+    ctx->symtab = symtab;
+    ctx->cache_valid = false;
+}
+
+/* Completes processing of 'command' within 'ctx'. */
+static void
+vtep_ctl_context_done_command(struct vtep_ctl_context *ctx,
+                           struct vtep_ctl_command *command)
+{
+    ds_swap(&ctx->output, &command->output);
+    command->table = ctx->table;
+}
+
+/* Finishes up with 'ctx'.
+ *
+ * If command is nonnull, first calls vtep_ctl_context_done_command() to complete
+ * processing that command within 'ctx'. */
+static void
+vtep_ctl_context_done(struct vtep_ctl_context *ctx, struct vtep_ctl_command *command)
+{
+    if (command) {
+        vtep_ctl_context_done_command(ctx, command);
+    }
+}
+
+static void
+run_prerequisites(struct vtep_ctl_command *commands, size_t n_commands,
+                  struct ovsdb_idl *idl)
+{
+    struct vtep_ctl_command *c;
+
+    ovsdb_idl_add_table(idl, &vteprec_table_global);
+    for (c = commands; c < &commands[n_commands]; c++) {
+        if (c->syntax->prerequisites) {
+            struct vtep_ctl_context ctx;
+
+            ds_init(&c->output);
+            c->table = NULL;
+
+            vtep_ctl_context_init(&ctx, c, idl, NULL, NULL, NULL);
+            (c->syntax->prerequisites)(&ctx);
+            vtep_ctl_context_done(&ctx, c);
+
+            ovs_assert(!c->output.string);
+            ovs_assert(!c->table);
+        }
+    }
+}
+
+static void
+do_vtep_ctl(const char *args, struct vtep_ctl_command *commands,
+            size_t n_commands, struct ovsdb_idl *idl)
+{
+    struct ovsdb_idl_txn *txn;
+    const struct vteprec_global *vtep_global;
+    enum ovsdb_idl_txn_status status;
+    struct ovsdb_symbol_table *symtab;
+    struct vtep_ctl_context ctx;
+    struct vtep_ctl_command *c;
+    struct shash_node *node;
+    char *error = NULL;
+
+    txn = the_idl_txn = ovsdb_idl_txn_create(idl);
+    if (dry_run) {
+        ovsdb_idl_txn_set_dry_run(txn);
+    }
+
+    ovsdb_idl_txn_add_comment(txn, "vtep-ctl: %s", args);
+
+    vtep_global = vteprec_global_first(idl);
+    if (!vtep_global) {
+        /* XXX add verification that table is empty */
+        vtep_global = vteprec_global_insert(txn);
+    }
+
+    symtab = ovsdb_symbol_table_create();
+    for (c = commands; c < &commands[n_commands]; c++) {
+        ds_init(&c->output);
+        c->table = NULL;
+    }
+    vtep_ctl_context_init(&ctx, NULL, idl, txn, vtep_global, symtab);
+    for (c = commands; c < &commands[n_commands]; c++) {
+        vtep_ctl_context_init_command(&ctx, c);
+        if (c->syntax->run) {
+            (c->syntax->run)(&ctx);
+        }
+        vtep_ctl_context_done_command(&ctx, c);
+
+        if (ctx.try_again) {
+            vtep_ctl_context_done(&ctx, NULL);
+            goto try_again;
+        }
+    }
+    vtep_ctl_context_done(&ctx, NULL);
+
+    SHASH_FOR_EACH (node, &symtab->sh) {
+        struct ovsdb_symbol *symbol = node->data;
+        if (!symbol->created) {
+            vtep_ctl_fatal("row id \"%s\" is referenced but never created "
+                           "(e.g. with \"-- --id=%s create ...\")",
+                        node->name, node->name);
+        }
+        if (!symbol->strong_ref) {
+            if (!symbol->weak_ref) {
+                VLOG_WARN("row id \"%s\" was created but no reference to it "
+                          "was inserted, so it will not actually appear in "
+                          "the database", node->name);
+            } else {
+                VLOG_WARN("row id \"%s\" was created but only a weak "
+                          "reference to it was inserted, so it will not "
+                          "actually appear in the database", node->name);
+            }
+        }
+    }
+
+    status = ovsdb_idl_txn_commit_block(txn);
+    if (status == TXN_UNCHANGED || status == TXN_SUCCESS) {
+        for (c = commands; c < &commands[n_commands]; c++) {
+            if (c->syntax->postprocess) {
+                struct vtep_ctl_context ctx;
+
+                vtep_ctl_context_init(&ctx, c, idl, txn, vtep_global, symtab);
+                (c->syntax->postprocess)(&ctx);
+                vtep_ctl_context_done(&ctx, c);
+            }
+        }
+    }
+    error = xstrdup(ovsdb_idl_txn_get_error(txn));
+    ovsdb_idl_txn_destroy(txn);
+    txn = the_idl_txn = NULL;
+
+    switch (status) {
+    case TXN_UNCOMMITTED:
+    case TXN_INCOMPLETE:
+        NOT_REACHED();
+
+    case TXN_ABORTED:
+        /* Should not happen--we never call ovsdb_idl_txn_abort(). */
+        vtep_ctl_fatal("transaction aborted");
+
+    case TXN_UNCHANGED:
+    case TXN_SUCCESS:
+        break;
+
+    case TXN_TRY_AGAIN:
+        goto try_again;
+
+    case TXN_ERROR:
+        vtep_ctl_fatal("transaction error: %s", error);
+
+    case TXN_NOT_LOCKED:
+        /* Should not happen--we never call ovsdb_idl_set_lock(). */
+        vtep_ctl_fatal("database not locked");
+
+    default:
+        NOT_REACHED();
+    }
+    free(error);
+
+    ovsdb_symbol_table_destroy(symtab);
+
+    for (c = commands; c < &commands[n_commands]; c++) {
+        struct ds *ds = &c->output;
+
+        if (c->table) {
+            table_print(c->table, &table_style);
+        } else if (oneline) {
+            size_t j;
+
+            ds_chomp(ds, '\n');
+            for (j = 0; j < ds->length; j++) {
+                int ch = ds->string[j];
+                switch (ch) {
+                case '\n':
+                    fputs("\\n", stdout);
+                    break;
+
+                case '\\':
+                    fputs("\\\\", stdout);
+                    break;
+
+                default:
+                    putchar(ch);
+                }
+            }
+            putchar('\n');
+        } else {
+            fputs(ds_cstr(ds), stdout);
+        }
+        ds_destroy(&c->output);
+        table_destroy(c->table);
+        free(c->table);
+
+        shash_destroy_free_data(&c->options);
+    }
+    free(commands);
+
+    ovsdb_idl_destroy(idl);
+
+    exit(EXIT_SUCCESS);
+
+try_again:
+    /* Our transaction needs to be rerun, or a prerequisite was not met.  Free
+     * resources and return so that the caller can try again. */
+    if (txn) {
+        ovsdb_idl_txn_abort(txn);
+        ovsdb_idl_txn_destroy(txn);
+    }
+    ovsdb_symbol_table_destroy(symtab);
+    for (c = commands; c < &commands[n_commands]; c++) {
+        ds_destroy(&c->output);
+        table_destroy(c->table);
+        free(c->table);
+    }
+    free(error);
+}
+
+static const struct vtep_ctl_command_syntax all_commands[] = {
+    /* Physical Switch commands. */
+    {"add-ps", 1, 1, pre_get_info, cmd_add_ps, NULL, "--may-exist", RW},
+    {"del-ps", 1, 1, pre_get_info, cmd_del_ps, NULL, "--if-exists", RW},
+    {"list-ps", 0, 0, pre_get_info, cmd_list_ps, NULL, "", RO},
+    {"ps-exists", 1, 1, pre_get_info, cmd_ps_exists, NULL, "", RO},
+
+    /* Port commands. */
+    {"list-ports", 1, 1, pre_get_info, cmd_list_ports, NULL, "", RO},
+    {"add-port", 2, 2, pre_get_info, cmd_add_port, NULL, "--may-exist",
+     RW},
+    {"del-port", 2, 2, pre_get_info, cmd_del_port, NULL, "--if-exists", RW},
+
+    /* Logical Switch commands. */
+    {"add-ls", 1, 1, pre_get_info, cmd_add_ls, NULL, "--may-exist", RW},
+    {"del-ls", 1, 1, pre_get_info, cmd_del_ls, NULL, "--if-exists", RW},
+    {"list-ls", 0, 0, pre_get_info, cmd_list_ls, NULL, "", RO},
+    {"ls-exists", 1, 1, pre_get_info, cmd_ls_exists, NULL, "", RO},
+    {"list-bindings", 2, 2, pre_get_info, cmd_list_bindings, NULL, "", RO},
+    {"bind-ls", 4, 4, pre_get_info, cmd_bind_ls, NULL, "", RO},
+    {"unbind-ls", 3, 3, pre_get_info, cmd_unbind_ls, NULL, "", RO},
+
+    /* MAC binding commands. */
+    {"add-ucast-local", 3, 4, pre_get_info, cmd_add_ucast_local, NULL, "", RW},
+    {"del-ucast-local", 2, 2, pre_get_info, cmd_del_ucast_local, NULL, "", RW},
+    {"add-mcast-local", 3, 4, pre_get_info, cmd_add_mcast_local, NULL, "", RW},
+    {"del-mcast-local", 3, 4, pre_get_info, cmd_del_mcast_local, NULL, "", RW},
+    {"clear-local-macs", 1, 1, pre_get_info, cmd_clear_local_macs, NULL, "",
+     RO},
+    {"list-local-macs", 1, 1, pre_get_info, cmd_list_local_macs, NULL, "", RO},
+    {"add-ucast-remote", 3, 4, pre_get_info, cmd_add_ucast_remote, NULL, "",
+     RW},
+    {"del-ucast-remote", 2, 2, pre_get_info, cmd_del_ucast_remote, NULL, "",
+     RW},
+    {"add-mcast-remote", 3, 4, pre_get_info, cmd_add_mcast_remote, NULL, "",
+     RW},
+    {"del-mcast-remote", 3, 4, pre_get_info, cmd_del_mcast_remote, NULL, "",
+     RW},
+    {"clear-remote-macs", 1, 1, pre_get_info, cmd_clear_remote_macs, NULL, "",
+     RO},
+    {"list-remote-macs", 1, 1, pre_get_info, cmd_list_remote_macs, NULL, "",
+     RO},
+
+    /* Manager commands. */
+    {"get-manager", 0, 0, pre_manager, cmd_get_manager, NULL, "", RO},
+    {"del-manager", 0, 0, pre_manager, cmd_del_manager, NULL, "", RW},
+    {"set-manager", 1, INT_MAX, pre_manager, cmd_set_manager, NULL, "", RW},
+
+    /* Database commands. */
+    {"comment", 0, INT_MAX, NULL, NULL, NULL, "", RO},
+    {"get", 2, INT_MAX, pre_cmd_get, cmd_get, NULL, "--if-exists,--id=", RO},
+    {"list", 1, INT_MAX, pre_cmd_list, cmd_list, NULL, "--columns=", RO},
+    {"find", 1, INT_MAX, pre_cmd_find, cmd_find, NULL, "--columns=", RO},
+    {"set", 3, INT_MAX, pre_cmd_set, cmd_set, NULL, "", RW},
+    {"add", 4, INT_MAX, pre_cmd_add, cmd_add, NULL, "", RW},
+    {"remove", 4, INT_MAX, pre_cmd_remove, cmd_remove, NULL, "", RW},
+    {"clear", 3, INT_MAX, pre_cmd_clear, cmd_clear, NULL, "", RW},
+    {"create", 2, INT_MAX, pre_create, cmd_create, post_create, "--id=", RW},
+    {"destroy", 1, INT_MAX, pre_cmd_destroy, cmd_destroy, NULL,
+     "--if-exists,--all", RW},
+    {"wait-until", 2, INT_MAX, pre_cmd_wait_until, cmd_wait_until, NULL, "",
+     RO},
+
+    {NULL, 0, 0, NULL, NULL, NULL, NULL, RO},
+};
+
diff --git a/vtep/vtep.ovsschema b/vtep/vtep.ovsschema
new file mode 100644 (file)
index 0000000..d03d96d
--- /dev/null
@@ -0,0 +1,157 @@
+{
+  "name": "hardware_vtep",
+  "cksum": "825115144 5318",
+  "tables": {
+    "Global": {
+      "columns": {
+        "managers": {
+          "type": {"key": {"type": "uuid",
+                           "refTable": "Manager"},
+                   "min": 0, "max": "unlimited"}},
+       "switches": {
+         "type": {"key": {"type": "uuid", "refTable": "Physical_Switch"},
+                  "min": 0, "max": "unlimited"}}
+      },
+      "maxRows": 1,
+      "isRoot": true},
+    "Physical_Switch": {
+      "columns": {
+       "ports": {
+         "type": {"key": {"type": "uuid", "refTable": "Physical_Port"},
+                  "min": 0, "max": "unlimited"}},
+        "name": {"type": "string"},
+        "description": {"type": "string"},
+        "management_ips": {
+         "type": {"key": {"type": "string"}, "min": 0, "max": "unlimited"}},
+        "tunnel_ips": {
+         "type": {"key": {"type": "string"}, "min": 0, "max": "unlimited"}}},
+      "indexes": [["name"]]},
+    "Physical_Port": {
+      "columns": {
+        "name": {"type": "string"},
+        "description": {"type": "string"},
+       "vlan_bindings": {
+         "type": {"key": {"type": "integer",
+                          "minInteger": 0, "maxInteger": 4095},
+                  "value": {"type": "uuid", "refTable": "Logical_Switch"},
+                  "min": 0, "max": "unlimited"}},
+        "vlan_stats": {
+         "type": {"key": {"type": "integer",
+                          "minInteger": 0, "maxInteger": 4095},
+                  "value": {"type": "uuid",
+                            "refTable": "Logical_Binding_Stats"},
+                  "min": 0, "max": "unlimited"}}}},
+    "Logical_Binding_Stats": {
+      "columns": {
+        "bytes_from_local": {"type": "integer"},
+        "packets_from_local": {"type": "integer"},
+        "bytes_to_local": {"type": "integer"},
+        "packets_to_local": {"type": "integer"}}},
+    "Logical_Switch": {
+      "columns": {
+        "name": {"type": "string"},
+        "description": {"type": "string"},
+       "tunnel_key": {"type": {"key": "integer", "min": 0, "max": 1}}},
+      "isRoot": true,
+      "indexes": [["name"]]},
+    "Ucast_Macs_Local": {
+      "columns": {
+        "MAC": {"type": "string"},
+       "logical_switch": {
+          "type": {"key": {"type": "uuid",
+                          "refTable": "Logical_Switch"}}},
+       "locator": {
+          "type": {"key": {"type": "uuid",
+                          "refTable": "Physical_Locator"}}},
+        "ipaddr": {"type": "string"}},
+      "isRoot": true},
+    "Ucast_Macs_Remote": {
+      "columns": {
+        "MAC": {"type": "string"},
+       "logical_switch": {
+          "type": {"key": {"type": "uuid",
+                          "refTable": "Logical_Switch"}}},
+       "locator": {
+          "type": {"key": {"type": "uuid",
+                          "refTable": "Physical_Locator"}}},
+        "ipaddr": {"type": "string"}},
+      "isRoot": true},
+    "Mcast_Macs_Local": {
+      "columns": {
+        "MAC": {"type": "string"},
+       "logical_switch": {
+          "type": {"key": {"type": "uuid",
+                          "refTable": "Logical_Switch"}}},
+       "locator_set": {
+          "type": {"key": {"type": "uuid",
+                          "refTable": "Physical_Locator_Set"}}},
+        "ipaddr": {"type": "string"}},
+      "isRoot": true},
+    "Mcast_Macs_Remote": {
+      "columns": {
+        "MAC": {"type": "string"},
+       "logical_switch": {
+          "type": {"key": {"type": "uuid",
+                          "refTable": "Logical_Switch"}}},
+       "locator_set": {
+          "type": {"key": {"type": "uuid",
+                          "refTable": "Physical_Locator_Set"}}},
+        "ipaddr": {"type": "string"}},
+      "isRoot": true},
+    "Logical_Router": {
+      "columns": {
+        "name": {"type": "string"},
+        "description": {"type": "string"},
+       "switch_binding": {
+         "type": {"key": {"type": "string"},
+                  "value": {"type": "uuid",
+                             "refTable": "Logical_Switch"},
+                  "min": 0, "max": "unlimited"}},
+       "static_routes": {
+         "type": {"key": {"type": "string"},
+                  "value": {"type" : "string"},
+                  "min": 0, "max": "unlimited"}}},
+      "isRoot": true,
+      "indexes": [["name"]]},
+    "Physical_Locator_Set": {
+      "columns": {
+        "locators": {
+         "type": {"key": {"type": "uuid", "refTable": "Physical_Locator"},
+                  "min": 1, "max": "unlimited"},
+                  "mutable": false}}},
+    "Physical_Locator": {
+      "columns": {
+        "encapsulation_type": {
+          "type": {
+            "key": {
+              "enum": ["set", ["vxlan_over_ipv4"]],
+              "type": "string"}},
+         "mutable": false},
+        "dst_ip": {"type": "string", "mutable": false},
+       "bfd": {
+          "type": {"key": "string", "value": "string",
+              "min": 0, "max": "unlimited"}},
+       "bfd_status": {
+          "type": {"key": "string", "value": "string",
+              "min": 0, "max": "unlimited"}}},
+      "indexes": [["encapsulation_type", "dst_ip"]]},
+    "Manager": {
+      "columns": {
+        "target": {"type": "string"},
+        "max_backoff": {
+          "type": {"key": {"type": "integer",
+                           "minInteger": 1000},
+                   "min": 0, "max": 1}},
+        "inactivity_probe": {
+          "type": {"key": "integer", "min": 0, "max": 1}},
+        "other_config": {
+          "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}},
+        "is_connected": {
+          "type": "boolean",
+          "ephemeral": true},
+        "status": {
+          "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"},
+          "ephemeral": true}},
+      "indexes": [["target"]],
+      "isRoot": false}},
+  "version": "1.0.0"}
diff --git a/vtep/vtep.xml b/vtep/vtep.xml
new file mode 100644 (file)
index 0000000..9fd7495
--- /dev/null
@@ -0,0 +1,717 @@
+<?xml version="1.0" encoding="utf-8"?>
+<database title="Hardware VTEP Database">
+  <p>
+    This schema specifies relations that a VTEP can use to integrate
+    physical ports into logical switches maintained by a network
+    virtualization controller such as NSX.
+  </p>
+  
+  <p>Glossary:</p>
+
+  <dl>
+    <dt>VTEP</dt>
+    <dd>
+      VXLAN Tunnel End Point, an entity which originates and/or terminates
+      VXLAN tunnels.
+    </dd>
+
+    <dt>HSC</dt>
+    <dd>
+      Hardware Switch Controller.
+    </dd>
+
+    <dt>NVC</dt>
+    <dd>
+      Network Virtualization Controller, e.g. NSX.
+    </dd>
+
+    <dt>VRF</dt>
+    <dd>
+      Virtual Routing and Forwarding instance.
+    </dd>
+ </dl>
+
+  <table name="Global" title="Top-level configuration.">
+    Top-level configuration for a hardware VTEP.  There must be
+    exactly one record in the <ref table="Global"/> table.
+
+    <column name="switches">
+      The physical switches managed by the VTEP.
+    </column>
+
+    <group title="Database Configuration">
+      <p>
+        These columns primarily configure the database server
+        (<code>ovsdb-server</code>), not the hardware VTEP itself.
+      </p>
+
+      <column name="managers">
+        Database clients to which the database server should connect or
+        to which it should listen, along with options for how these
+        connection should be configured.  See the <ref table="Manager"/>
+        table for more information.
+      </column>
+    </group>
+  </table>
+
+  <table name="Manager" title="OVSDB management connection.">
+    <p>
+      Configuration for a database connection to an Open vSwitch Database
+      (OVSDB) client.
+    </p>
+
+    <p>
+      The database server can initiate and maintain active connections
+      to remote clients.  It can also listen for database connections.
+    </p>
+
+    <group title="Core Features">
+      <column name="target">
+        <p>Connection method for managers.</p>
+        <p>
+          The following connection methods are currently supported:
+        </p>
+        <dl>
+          <dt><code>ssl:<var>ip</var></code>[<code>:<var>port</var></code>]</dt>
+          <dd>
+            <p>
+              The specified SSL <var>port</var> (default: 6632) on the host at
+              the given <var>ip</var>, which must be expressed as an IP address
+              (not a DNS name).
+            </p>
+            <p>
+             SSL key and certificate configuration happens outside the
+             database.
+            </p>
+          </dd>
+
+          <dt><code>tcp:<var>ip</var></code>[<code>:<var>port</var></code>]</dt>
+          <dd>
+            The specified TCP <var>port</var> (default: 6632) on the host at
+            the given <var>ip</var>, which must be expressed as an IP address
+            (not a DNS name).
+          </dd>
+          <dt><code>pssl:</code>[<var>port</var>][<code>:<var>ip</var></code>]</dt>
+          <dd>
+            <p>
+              Listens for SSL connections on the specified TCP <var>port</var>
+              (default: 6632).  If <var>ip</var>, which must be expressed as an
+              IP address (not a DNS name), is specified, then connections are
+              restricted to the specified local IP address.
+            </p>
+          </dd>
+          <dt><code>ptcp:</code>[<var>port</var>][<code>:<var>ip</var></code>]</dt>
+          <dd>
+            Listens for connections on the specified TCP <var>port</var>
+            (default: 6632).  If <var>ip</var>, which must be expressed as an
+            IP address (not a DNS name), is specified, then connections are
+            restricted to the specified local IP address.
+          </dd>
+        </dl>
+      </column>
+    </group>
+
+    <group title="Client Failure Detection and Handling">
+      <column name="max_backoff">
+        Maximum number of milliseconds to wait between connection attempts.
+        Default is implementation-specific.
+      </column>
+
+      <column name="inactivity_probe">
+        Maximum number of milliseconds of idle time on connection to the
+        client before sending an inactivity probe message.  If the Open
+        vSwitch database does not communicate with the client for the
+        specified number of seconds, it will send a probe.  If a
+        response is not received for the same additional amount of time,
+        the database server assumes the connection has been broken
+        and attempts to reconnect.  Default is implementation-specific.
+        A value of 0 disables inactivity probes.
+      </column>
+    </group>
+
+    <group title="Status">
+      <column name="is_connected">
+        <code>true</code> if currently connected to this manager,
+        <code>false</code> otherwise.
+      </column>
+
+      <column name="status" key="last_error">
+        A human-readable description of the last error on the connection
+        to the manager; i.e. <code>strerror(errno)</code>.  This key
+        will exist only if an error has occurred.
+      </column>
+
+      <column name="status" key="state"
+              type='{"type": "string", "enum": ["set", ["VOID", "BACKOFF", "CONNECTING", "ACTIVE", "IDLE"]]}'>
+        <p>
+          The state of the connection to the manager:
+        </p>
+        <dl>
+          <dt><code>VOID</code></dt>
+          <dd>Connection is disabled.</dd>
+
+          <dt><code>BACKOFF</code></dt>
+          <dd>Attempting to reconnect at an increasing period.</dd>
+
+          <dt><code>CONNECTING</code></dt>
+          <dd>Attempting to connect.</dd>
+
+          <dt><code>ACTIVE</code></dt>
+          <dd>Connected, remote host responsive.</dd>
+
+          <dt><code>IDLE</code></dt>
+          <dd>Connection is idle.  Waiting for response to keep-alive.</dd>
+        </dl>
+        <p>
+          These values may change in the future.  They are provided only for
+          human consumption.
+        </p>
+      </column>
+
+      <column name="status" key="sec_since_connect"
+              type='{"type": "integer", "minInteger": 0}'>
+        The amount of time since this manager last successfully connected
+        to the database (in seconds). Value is empty if manager has never
+        successfully connected.
+      </column>
+
+      <column name="status" key="sec_since_disconnect"
+              type='{"type": "integer", "minInteger": 0}'>
+        The amount of time since this manager last disconnected from the
+        database (in seconds). Value is empty if manager has never
+        disconnected.
+      </column>
+
+      <column name="status" key="locks_held">
+        Space-separated list of the names of OVSDB locks that the connection
+        holds.  Omitted if the connection does not hold any locks.
+      </column>
+
+      <column name="status" key="locks_waiting">
+        Space-separated list of the names of OVSDB locks that the connection is
+        currently waiting to acquire.  Omitted if the connection is not waiting
+        for any locks.
+      </column>
+
+      <column name="status" key="locks_lost">
+        Space-separated list of the names of OVSDB locks that the connection
+        has had stolen by another OVSDB client.  Omitted if no locks have been
+        stolen from this connection.
+      </column>
+
+      <column name="status" key="n_connections"
+              type='{"type": "integer", "minInteger": 2}'>
+        <p>
+          When <ref column="target"/> specifies a connection method that
+          listens for inbound connections (e.g. <code>ptcp:</code> or
+          <code>pssl:</code>) and more than one connection is actually active,
+          the value is the number of active connections.  Otherwise, this
+          key-value pair is omitted.
+        </p>
+        <p>
+          When multiple connections are active, status columns and key-value
+          pairs (other than this one) report the status of one arbitrarily
+          chosen connection.
+        </p>
+      </column>
+    </group>
+
+    <group title="Connection Parameters">
+      <p>
+        Additional configuration for a connection between the manager
+        and the database server.
+      </p>
+
+      <column name="other_config" key="dscp"
+                type='{"type": "integer"}'>
+        The Differentiated Service Code Point (DSCP) is specified using 6 bits
+        in the Type of Service (TOS) field in the IP header. DSCP provides a
+        mechanism to classify the network traffic and provide Quality of
+        Service (QoS) on IP networks.
+
+        The DSCP value specified here is used when establishing the
+        connection between the manager and the database server.  If no
+        value is specified, a default value of 48 is chosen.  Valid DSCP
+        values must be in the range 0 to 63.
+      </column>
+    </group>
+  </table>
+
+  <table name="Physical_Switch" title="A physical switch.">
+    A physical switch that implements a VTEP.
+
+    <column name="ports">
+      The physical ports within the switch.
+    </column>
+
+    <group title="Network Status">
+      <column name="management_ips">
+        IPv4 or IPv6 addresses at which the switch may be contacted
+        for management purposes.
+      </column>
+
+      <column name="tunnel_ips">
+        <p>
+          IPv4 or IPv6 addresses on which the switch may originate or
+          terminate tunnels.
+        </p>
+
+        <p>
+          This column is intended to allow a <ref table="Manager"/> to
+          determine the <ref table="Physical_Switch"/> that terminates
+          the tunnel represented by a <ref table="Physical_Locator"/>.
+        </p>
+      </column>
+    </group>
+
+    <group title="Identification">
+      <column name="name">
+       Symbolic name for the switch, such as its hostname.
+      </column>
+      
+      <column name="description">
+       An extended description for the switch, such as its switch login
+       banner.
+      </column>
+    </group>
+  </table>
+
+  <table name="Physical_Port" title="A port within a physical switch.">
+    A port within a <ref table="Physical_Switch"/>.
+
+    <column name="vlan_bindings">
+      Identifies how VLANs on the physical port are bound to logical switches.
+      If, for example, the map contains a (VLAN, logical switch) pair, a packet
+      that arrives on the port in the VLAN is considered to belong to the
+      paired logical switch.
+    </column>
+
+    <column name="vlan_stats">
+      Statistics for VLANs bound to logical switches on the physical port.  An
+      implementation that fully supports such statistics would populate this
+      column with a mapping for every VLAN that is bound in <ref
+      column="vlan_bindings"/>.  An implementation that does not support such
+      statistics or only partially supports them would not populate this column
+      or partially populate it, respectively.
+    </column>
+
+    <group title="Identification">
+      <column name="name">
+       Symbolic name for the port.  The name ought to be unique within a given
+       <ref table="Physical_Switch"/>, but the database is not capable of
+       enforcing this.
+      </column>
+      
+      <column name="description">
+       An extended description for the port.
+      </column>
+    </group>
+  </table>
+
+  <table name="Logical_Binding_Stats" title="Statistics for a VLAN on a physical port bound to a logical network.">
+    Reports statistics for the <ref table="Logical_Switch"/> with which a VLAN
+    on a <ref table="Physical_Port"/> is associated.
+
+    <group title="Statistics">
+      These statistics count only packets to which the binding applies.
+
+      <column name="packets_from_local">
+        Number of packets sent by the <ref table="Physical_Switch"/>.
+      </column>
+
+      <column name="bytes_from_local">
+        Number of bytes in packets sent by the <ref table="Physical_Switch"/>.
+      </column>
+
+      <column name="packets_to_local">
+        Number of packets received by the <ref table="Physical_Switch"/>.
+      </column>
+
+      <column name="bytes_to_local">
+        Number of bytes in packets received by the <ref
+        table="Physical_Switch"/>.
+      </column>
+    </group>
+  </table>
+
+  <table name="Logical_Switch" title="A layer-2 domain.">
+    A logical Ethernet switch, whose implementation may span physical and
+    virtual media, possibly crossing L3 domains via tunnels; a logical layer-2
+    domain; an Ethernet broadcast domain.
+
+
+
+    <group title="Per Logical-Switch Tunnel Key">
+      <p>
+        Tunnel protocols tend to have a field that allows the tunnel
+        to be partitioned into sub-tunnels: VXLAN has a VNI, GRE and
+        STT have a key, CAPWAP has a WSI, and so on.  We call these
+        generically ``tunnel keys.''  Given that one needs to use a
+        tunnel key at all, there are at least two reasonable ways to
+        assign their values:
+      </p>
+
+      <ul>
+        <li>
+          <p>
+            Per <ref table="Logical_Switch"/>+<ref table="Physical_Locator"/>
+            pair.  That is, each logical switch may be assigned a different
+            tunnel key on every <ref table="Physical_Locator"/>.  This model is
+            especially flexible.
+          </p>
+
+          <p>
+            In this model, <ref table="Physical_Locator"/> carries the tunnel
+            key.  Therefore, one <ref table="Physical_Locator"/> record will
+            exist for each logical switch carried at a given IP destination.
+          </p>
+        </li>
+
+        <li>
+          <p>
+            Per <ref table="Logical_Switch"/>.  That is, every tunnel
+            associated with a particular logical switch carries the same tunnel
+            key, regardless of the <ref table="Physical_Locator"/> to which the
+            tunnel is addressed.  This model may ease switch implementation
+            because it imposes fewer requirements on the hardware datapath.
+          </p>
+
+          <p>
+            In this model, <ref table="Logical_Switch"/> carries the tunnel
+            key.  Therefore, one <ref table="Physical_Locator"/> record will
+            exist for each IP destination.
+          </p>
+        </li>
+      </ul>
+
+      <column name="tunnel_key">
+        <p>
+          This column is used only in the tunnel key per <ref
+          table="Logical_Switch"/> model (see above), because only in that
+          model is there a tunnel key associated with a logical switch.
+        </p>
+
+        <p>
+          For <code>vxlan_over_ipv4</code> encapsulation, this column
+          is the VXLAN VNI that identifies a logical switch.  It must
+          be in the range 0 to 16,777,215.
+        </p>
+      </column>
+    </group>
+
+    <group title="Identification">
+      <column name="name">
+       Symbolic name for the logical switch.
+      </column>
+      
+      <column name="description">
+       An extended description for the logical switch, such as its switch
+       login banner.
+      </column>
+    </group>
+  </table>
+
+  <table name="Ucast_Macs_Local" title="Unicast MACs (local)">
+    <p>
+      Mapping of unicast MAC addresses to tunnels (physical
+      locators). This table is written by the HSC, so it contains the
+      MAC addresses that have been learned on physical ports by a
+      VTEP. 
+    </p>
+
+    <column name="MAC">
+      A MAC address that has been learned by the VTEP.
+    </column>
+
+    <column name="logical_switch">
+      The Logical switch to which this mapping applies.
+    </column>
+
+    <column name="locator">
+      The physical locator to be used to reach this MAC address. In
+      this table, the physical locator will be one of the tunnel IP
+      addresses of the appropriate VTEP.
+    </column>
+
+    <column name="ipaddr">
+      The IP address to which this MAC corresponds. Optional field for
+      the purpose of ARP supression.
+    </column>
+
+  </table>
+
+ <table name="Ucast_Macs_Remote" title="Unicast MACs (remote)">
+    <p>
+      Mapping of unicast MAC addresses to tunnels (physical
+      locators). This table is written by the NVC, so it contains the
+      MAC addresses that the NVC has learned. These include VM MAC
+      addresses, in which case the physical locators will be
+      hypervisor IP addresses. The NVC will also report MACs that it
+      has learned from other HSCs in the network, in which case the
+      physical locators will be tunnel IP addresses of the
+      corresponding VTEPs.
+    </p>
+
+    <column name="MAC">
+      A MAC address that has been learned by the NSC.
+    </column>
+
+    <column name="logical_switch">
+      The Logical switch to which this mapping applies.
+    </column>
+
+    <column name="locator">
+      The physical locator to be used to reach this MAC address. In
+      this table, the physical locator will be either a hypervisor IP
+      address or a tunnel IP addresses of another VTEP.
+    </column>
+
+    <column name="ipaddr">
+      The IP address to which this MAC corresponds. Optional field for
+      the purpose of ARP supression.
+    </column>
+
+  </table>
+
+  <table name="Mcast_Macs_Local" title="Multicast MACs (local)">
+    <p>
+      Mapping of multicast MAC addresses to tunnels (physical
+      locators). This table is written by the HSC, so it contains the
+      MAC addresses that have been learned on physical ports by a
+      VTEP. These may be learned by IGMP snooping, for example. This
+      table also specifies how to handle unknown unicast and broadcast packets.
+    </p>
+
+    <column name="MAC">
+      <p>
+       A MAC address that has been learned by the VTEP. 
+      </p>
+      <p>
+       The keyword <code>unknown-dst</code> is used as a special
+       ``Ethernet address'' that indicates the locations to which
+       packets in a logical switch whose destination addresses do not
+       otherwise appear in <ref table="Ucast_Macs_Local"/> (for
+       unicast addresses) or <ref table="Mcast_Macs_Local"/> (for
+       multicast addresses) should be sent.
+      </p>
+    </column>
+
+    <column name="logical_switch">
+      The Logical switch to which this mapping applies.
+    </column>
+
+    <column name="locator_set">
+      The physical locator set to be used to reach this MAC address. In
+      this table, the physical locator set will be contain one or more tunnel IP
+      addresses of the appropriate VTEP(s).
+    </column>
+
+  </table>
+
+  <table name="Mcast_Macs_Remote" title="Multicast MACs (remote)">
+    <p>
+      Mapping of multicast MAC addresses to tunnels (physical
+      locators). This table is written by the NVC, so it contains the
+      MAC addresses that the NVC has learned. This
+      table also specifies how to handle unknown unicast and broadcast
+      packets.
+    </p>
+    <p>
+      Multicast packet replication may be handled by a service node,
+      in which case the physical locators will be IP addresses of
+      service nodes. If the VTEP supports replication onto multiple
+      tunnels, then this may be used to replicate directly onto
+      VTEP-hyperisor tunnels.
+    </p>
+
+    <column name="MAC">
+      <p>
+       A MAC address that has been learned by the NSC.
+      </p>
+      <p>
+       The keyword <code>unknown-dst</code> is used as a special
+       ``Ethernet address'' that indicates the locations to which
+       packets in a logical switch whose destination addresses do not
+       otherwise appear in <ref table="Ucast_Macs_Remote"/> (for
+       unicast addresses) or <ref table="Mcast_Macs_Remote"/> (for
+       multicast addresses) should be sent.
+      </p>
+    </column>
+
+    <column name="logical_switch">
+      The Logical switch to which this mapping applies.
+    </column>
+
+    <column name="locator_set">
+      The physical locator set to be used to reach this MAC address. In
+      this table, the physical locator set will be either a service node IP
+      address or a set of tunnel IP addresses of hypervisors (and
+      potentially other VTEPs).
+    </column>
+
+    <column name="ipaddr">
+      The IP address to which this MAC corresponds. Optional field for
+      the purpose of ARP supression.
+    </column>
+
+  </table>
+
+  <table name="Logical_Router" title="A logical L3 router.">
+    <p>
+      A logical router, or VRF. A logical router may be connected to one or more
+      logical switches. Subnet addresses and interface addresses may be configured on the 
+      interfaces.
+    </p>
+    
+    <column name="switch_binding">
+      Maps from an IPv4 or IPv6 address prefix in CIDR notation to a
+      logical switch. Multiple prefixes may map to the same switch. By
+      writing a 32-bit (or 128-bit for v6) address with a /N prefix
+      length, both the router's interface address and the subnet
+      prefix can be configured. For example, 192.68.1.1/24 creates a
+      /24 subnet for the logical switch attached to the interface and
+      assigns the address 192.68.1.1 to the router interface.
+    </column>
+
+    <column name="static_routes">
+      One or more static routes, mapping IP prefixes to next hop IP addresses.
+    </column>
+
+    <group title="Identification">
+      <column name="name">
+       Symbolic name for the logical router.
+      </column>
+      
+      <column name="description">
+       An extended description for the logical router.
+      </column>
+    </group>
+  </table>
+
+  <table name="Physical_Locator_Set">
+    <p>
+      A set of one or more <ref table="Physical_Locator"/>s.
+    </p>
+
+    <p>
+      This table exists only because OVSDB does not have a way to
+      express the type ``map from string to one or more <ref
+      table="Physical_Locator"/> records.''
+    </p>
+
+    <column name="locators"/>    
+  </table>
+
+  <table name="Physical_Locator">
+    <p>
+      Identifies an endpoint to which logical switch traffic may be
+      encapsulated and forwarded.
+    </p>
+
+    <p>
+      For the <code>vxlan_over_ipv4</code> encapsulation, the only
+      encapsulation defined so far, all endpoints associated with a given <ref
+      table="Logical_Switch"/> must use a common tunnel key, which is carried
+      in the <ref table="Logical_Switch" column="tunnel_key"/> column of <ref
+      table="Logical_Switch"/>.
+    </p>
+
+    <p>
+      For some encapsulations yet to be defined, we expect <ref
+      table="Physical_Locator"/> to identify both an endpoint and a tunnel key.
+      When the first such encapsulation is defined, we expect to add a
+      ``tunnel_key'' column to <ref table="Physical_Locator"/> to allow the
+      tunnel key to be defined.
+    </p>
+
+    <p>
+      See the ``Per Logical-Switch Tunnel Key'' section in the <ref
+      table="Logical_Switch"/> table for further discussion of the model.
+    </p>
+
+    <column name="encapsulation_type">
+      The type of tunneling encapsulation.
+    </column>
+
+    <column name="dst_ip">
+      <p>
+        For <code>vxlan_over_ipv4</code> encapsulation, the IPv4 address of the
+        VXLAN tunnel endpoint.
+      </p>
+
+      <p>
+        We expect that this column could be used for IPv4 or IPv6 addresses in
+        encapsulations to be introduced later.
+      </p>
+    </column>
+    <group title="Bidirectional Forwarding Detection (BFD)">
+      <p>
+       BFD, defined in RFC 5880, allows point to point detection of
+       connectivity failures by occasional transmission of BFD control
+       messages.
+      </p>
+      
+      <p>
+       BFD operates by regularly transmitting BFD control messages at a
+       rate negotiated independently in each direction.  Each endpoint
+       specifies the rate at which it expects to receive control messages,
+       and the rate at which it's willing to transmit them.  An endpoint
+       which fails to receive BFD control messages for a period of three
+       times the expected reception rate, will signal a connectivity
+       fault.  In the case of a unidirectional connectivity issue, the
+       system not receiving BFD control messages will signal the problem
+       to its peer in the messages is transmists.
+      </p>
+      
+      <column name="bfd" key="min_rx">
+       The minimum rate, in milliseconds, at which this BFD session is
+       willing to receive BFD control messages.  The actual rate may slower
+       if the remote endpoint isn't willing to transmit as quickly as
+       specified.  Defaults to <code>1000</code>.
+      </column>
+      
+      <column name="bfd" key="min_tx">
+       The minimum rate, in milliseconds, at which this BFD session is
+       willing to transmit BFD control messages.  The actual rate may be
+       slower if the remote endpoint isn't willing to receive as quickly as
+       specified.  Defaults to <code>100</code>.
+      </column>
+      
+      <column name="bfd" key="cpath_down">
+       Concatenated path down may be used when the local system should not
+       have traffic forwarded to it for some reason other than a connectivty
+       failure on the interface being monitored.  The local BFD session will
+       notify the remote session of the connectivity problem, at which time
+       the remote session may choose not to forward traffic.  Defaults to
+       <code>false</code>.
+      </column>
+      
+      <column name="bfd_status" key="state">
+       State of the BFD session.  One of <code>ADMIN_DOWN</code>,
+       <code>DOWN</code>, <code>INIT</code>, or <code>UP</code>.
+      </column>
+      
+      <column name="bfd_status" key="forwarding">
+       True if the BFD session believes this <ref table="Physical_Locator"/> may be
+       used to forward traffic.  Typically this means the local session is
+       up, and the remote system isn't signalling a problem such as
+       concatenated path down.
+      </column>
+      
+      <column name="bfd_status" key="diagnostic">
+       A short message indicating what the BFD session thinks is wrong in
+       case of a problem.
+      </column>
+      
+      <column name="bfd_status" key="remote state">
+       State of the remote endpoint's BFD session.
+      </column>
+      
+      <column name="bfd_status" key="remote diagnostic">
+       A short message indicating what the remote endpoint's BFD session
+       thinks is wrong in case of a problem.
+      </column>
+    </group>
+  </table>
+
+</database>
index 87efd88..e9c2d16 100644 (file)
@@ -419,7 +419,9 @@ exit 0
 /usr/share/openvswitch/scripts/ovs-save
 /usr/share/openvswitch/scripts/ovs-ctl
 /usr/share/openvswitch/scripts/ovs-lib
+/usr/share/openvswitch/scripts/ovs-vtep
 /usr/share/openvswitch/vswitch.ovsschema
+/usr/share/openvswitch/vtep.ovsschema
 /usr/sbin/ovs-bugtool
 /usr/sbin/ovs-vlan-bug-workaround
 /usr/sbin/ovs-vswitchd
@@ -435,11 +437,13 @@ exit 0
 /usr/bin/ovs-vsctl
 /usr/bin/ovsdb-client
 /usr/bin/ovsdb-tool
+/usr/bin/vtep-ctl
 /usr/lib/xsconsole/plugins-base/XSFeatureVSwitch.py
 /usr/share/man/man1/ovsdb-client.1.gz
 /usr/share/man/man1/ovsdb-server.1.gz
 /usr/share/man/man1/ovsdb-tool.1.gz
 /usr/share/man/man5/ovs-vswitchd.conf.db.5.gz
+/usr/share/man/man5/vtep.5.gz
 /usr/share/man/man8/ovs-appctl.8.gz
 /usr/share/man/man8/ovs-bugtool.8.gz
 /usr/share/man/man8/ovs-ctl.8.gz
@@ -453,6 +457,7 @@ exit 0
 /usr/share/man/man8/ovs-vlan-test.8.gz
 /usr/share/man/man8/ovs-vsctl.8.gz
 /usr/share/man/man8/ovs-vswitchd.8.gz
+/usr/share/man/man8/vtep-ctl.8.gz
 /var/lib/openvswitch
 /var/log/openvswitch
 %exclude /usr/lib/xsconsole/plugins-base/*.py[co]