Merge branch 'mainstream'
authorGiuseppe Lettieri <g.lettieri@iet.unipi.it>
Tue, 21 Jan 2014 15:37:36 +0000 (16:37 +0100)
committerGiuseppe Lettieri <g.lettieri@iet.unipi.it>
Tue, 21 Jan 2014 15:37:36 +0000 (16:37 +0100)
Conflicts:
Makefile.am
lib/dpif-netdev.c

1  2 
.gitignore
Makefile.am
lib/automake.mk
lib/dpif-netdev.c
lib/dpif-provider.h
lib/dpif.c
lib/netdev-pltap.c
lib/netdev-provider.h
lib/netdev-tunnel.c
lib/netdev.c
sliver-openvswitch.spec

diff --combined .gitignore
  /depcomp
  /distfiles
  /install-sh
+ /libtool
  /manpage-check
  /missing
  /missing-distfiles
  /package.m4
  /stamp-h1
+ /_build-gcc
+ /_build-clang
  Module.symvers
  TAGS
  cscope.*
  tags
 +myexp/
  _debian
diff --combined Makefile.am
@@@ -1,4 -1,4 +1,4 @@@
- # Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013 Nicira, Inc.
+ # Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 Nicira, Inc.
  #
  # Copying and distribution of this file, with or without modification,
  # are permitted in any medium without royalty provided the copyright
@@@ -10,6 -10,11 +10,11 @@@ ACLOCAL_AMFLAGS = -I m
  SUBDIRS = datapath
  
  AM_CPPFLAGS = $(SSL_CFLAGS)
+ if WIN32
+ AM_CPPFLAGS += -I $(top_srcdir)/include/windows
+ endif
  AM_CPPFLAGS += -I $(top_srcdir)/include
  AM_CPPFLAGS += -I $(top_srcdir)/lib
  AM_CPPFLAGS += -I $(top_builddir)/lib
@@@ -56,6 -61,7 +61,7 @@@ EXTRA_DIST = 
        INSTALL.Fedora \
        INSTALL.KVM \
        INSTALL.Libvirt \
+       INSTALL.NetBSD \
        INSTALL.RHEL \
        INSTALL.SSL \
        INSTALL.XenServer \
@@@ -93,7 -99,7 +99,7 @@@ MAN_FRAGMENTS 
  MAN_ROOTS =
  noinst_DATA =
  noinst_HEADERS =
noinst_LIBRARIES =
lib_LTLIBRARIES =
  noinst_man_MANS =
  noinst_PROGRAMS =
  noinst_SCRIPTS =
@@@ -118,7 -124,6 +124,7 @@@ ro_shell = printf '\043 Generated autom
  
  SUFFIXES += .in
  .in:
 +      @mkdir -p $$(dirname $@)
        $(PERL) $(srcdir)/build-aux/soexpand.pl -I$(srcdir) < $< | \
            sed \
                -e 's,[@]PKIDIR[@],$(PKIDIR),g' \
                  -e 's,[@]bindir[@],$(bindir),g' \
                  -e 's,[@]sbindir[@],$(sbindir),g' \
                  -e 's,[@]abs_top_srcdir[@],$(abs_top_srcdir),g' \
-                 -e 's,[@]ovsdbmonitordir[@],$(ovsdbmonitordir),g' \
              > $@.tmp
        @if head -n 1 $@.tmp | grep '#!' > /dev/null; then \
            echo chmod +x $@.tmp; \
@@@ -159,10 -163,10 +164,10 @@@ dist-hook-git: distfile
        @if test -e $(srcdir)/.git && (git --version) >/dev/null 2>&1; then \
          (cd datapath && $(MAKE) distfiles);                               \
          (cat distfiles; sed 's|^|datapath/|' datapath/distfiles) |        \
-           sort -u > all-distfiles;                                        \
 -          LC_ALL=C sort -u > all-distfiles;                               \
 -        (cd $(srcdir) && git ls-files) | grep -v '\.gitignore$$' |        \
 -          LC_ALL=C sort -u > all-gitfiles;                                \
 -        LC_ALL=C comm -1 -3 all-distfiles all-gitfiles > missing-distfiles; \
++          LC_ALL=C sort -u > all-distfiles;                                       \
 +        (cd $(srcdir) && git ls-files) | grep -vFf $(srcdir)/.non-distfiles |     \
-           sort -u > all-gitfiles;                                         \
-         comm -1 -3 all-distfiles all-gitfiles > missing-distfiles;        \
++          LC_ALL=C sort -u > all-gitfiles;                                        \
++        LC_ALL=C comm -1 -3 all-distfiles all-gitfiles > missing-distfiles;       \
          if test -s missing-distfiles; then                                \
            echo "The distribution is missing the following files:";        \
            cat missing-distfiles;                                          \
@@@ -177,7 -181,8 +182,8 @@@ distfiles: Makefil
        list='$(DISTFILES)'; \
        for file in $$list; do echo $$file; done | \
          sed -e "s|^$$srcdirstrip/||;t" \
-             -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t" | sort -u > $@
+             -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t" | \
+           LC_ALL=C sort -u > $@
  CLEANFILES += distfiles
  endif
  .PHONY: dist-hook-git
@@@ -284,6 -289,7 +290,7 @@@ if LINUX_ENABLE
        cd datapath/linux && $(MAKE) modules_install
  endif
  
+ include m4/automake.mk
  include lib/automake.mk
  include ofproto/automake.mk
  include utilities/automake.mk
@@@ -297,6 -303,5 +304,6 @@@ include rhel/automake.m
  include xenserver/automake.mk
  include python/automake.mk
  include python/compat/automake.mk
 +include planetlab/automake.mk
  include tutorial/automake.mk
  include vtep/automake.mk
diff --combined lib/automake.mk
@@@ -1,13 -1,16 +1,16 @@@
- # Copyright (C) 2009, 2010, 2011, 2012, 2013 Nicira, Inc.
+ # Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Nicira, Inc.
  #
  # Copying and distribution of this file, with or without modification,
  # are permitted in any medium without royalty provided the copyright
  # notice and this notice are preserved.  This file is offered as-is,
  # without warranty of any kind.
  
noinst_LIBRARIES += lib/libopenvswitch.a
lib_LTLIBRARIES += lib/libopenvswitch.la
  
- lib_libopenvswitch_a_SOURCES = \
+ lib_libopenvswitch_la_LIBADD = $(SSL_LIBS)
+ lib_libopenvswitch_la_LDFLAGS = -release $(VERSION)
+ lib_libopenvswitch_la_SOURCES = \
        lib/aes128.c \
        lib/aes128.h \
        lib/async-append.h \
@@@ -29,6 -32,8 +32,8 @@@
        lib/command-line.c \
        lib/command-line.h \
        lib/compiler.h \
+       lib/connectivity.c \
+       lib/connectivity.h \
        lib/coverage.c \
        lib/coverage.h \
        lib/crc32c.c \
@@@ -52,6 -57,8 +57,8 @@@
        lib/dynamic-string.h \
        lib/entropy.c \
        lib/entropy.h \
+       lib/fat-rwlock.c \
+       lib/fat-rwlock.h \
        lib/fatal-signal.c \
        lib/fatal-signal.h \
        lib/flow.c \
@@@ -74,7 -81,6 +81,6 @@@
        lib/jsonrpc.h \
        lib/lacp.c \
        lib/lacp.h \
-       lib/latch.c \
        lib/latch.h \
        lib/learn.c \
        lib/learn.h \
        lib/multipath.c \
        lib/multipath.h \
        lib/netdev-dummy.c \
 +      lib/netdev-tunnel.c \
 +      lib/netdev-pltap.c \
        lib/netdev-provider.h \
        lib/netdev-vport.c \
        lib/netdev-vport.h \
        lib/stream-unix.c \
        lib/stream.c \
        lib/stream.h \
+       lib/stdio.c \
        lib/string.c \
-       lib/string.h \
        lib/svec.c \
        lib/svec.h \
        lib/table.c \
        lib/timeval.h \
        lib/token-bucket.c \
        lib/token-bucket.h \
 +      lib/tunalloc.c \
 +      lib/tunalloc.h \
        lib/type-props.h \
        lib/unaligned.h \
        lib/unicode.c \
        lib/vswitch-idl.h \
        lib/vtep-idl.c \
        lib/vtep-idl.h
+ if WIN32
+ lib_libopenvswitch_la_SOURCES += lib/latch-windows.c
+ else
+ lib_libopenvswitch_la_SOURCES += lib/latch.c
+ endif
+ EXTRA_DIST += \
+       lib/stdio.h.in \
+       lib/string.h.in
  
- nodist_lib_libopenvswitch_a_SOURCES = \
+ nodist_lib_libopenvswitch_la_SOURCES = \
        lib/dirs.c
- CLEANFILES += $(nodist_lib_libopenvswitch_a_SOURCES)
+ CLEANFILES += $(nodist_lib_libopenvswitch_la_SOURCES)
  
- noinst_LIBRARIES += lib/libsflow.a
- lib_libsflow_a_SOURCES = \
+ lib_LTLIBRARIES += lib/libsflow.la
+ lib_libsflow_la_LDFLAGS = -release $(VERSION)
+ lib_libsflow_la_SOURCES = \
        lib/sflow_api.h \
        lib/sflow.h \
        lib/sflow_agent.c \
        lib/sflow_sampler.c \
        lib/sflow_poller.c \
        lib/sflow_receiver.c
- lib_libsflow_a_CFLAGS = $(AM_CFLAGS)
+ lib_libsflow_la_CPPFLAGS = $(AM_CPPFLAGS)
+ lib_libsflow_la_CFLAGS = $(AM_CFLAGS)
  if HAVE_WNO_UNUSED
- lib_libsflow_a_CFLAGS += -Wno-unused
+ lib_libsflow_la_CFLAGS += -Wno-unused
  endif
  if HAVE_WNO_UNUSED_PARAMETER
- lib_libsflow_a_CFLAGS += -Wno-unused-parameter
+ lib_libsflow_la_CFLAGS += -Wno-unused-parameter
  endif
  
  if LINUX_DATAPATH
- lib_libopenvswitch_a_SOURCES += \
+ lib_libopenvswitch_la_SOURCES += \
        lib/dpif-linux.c \
        lib/dpif-linux.h \
        lib/netdev-linux.c \
  endif
  
  if HAVE_POSIX_AIO
- lib_libopenvswitch_a_SOURCES += lib/async-append-aio.c
+ lib_libopenvswitch_la_SOURCES += lib/async-append-aio.c
  else
- lib_libopenvswitch_a_SOURCES += lib/async-append-null.c
+ lib_libopenvswitch_la_SOURCES += lib/async-append-null.c
  endif
  
  if ESX
- lib_libopenvswitch_a_SOURCES += \
+ lib_libopenvswitch_la_SOURCES += \
          lib/route-table-stub.c
  endif
  
  if HAVE_IF_DL
- lib_libopenvswitch_a_SOURCES += \
+ lib_libopenvswitch_la_SOURCES += \
        lib/netdev-bsd.c \
        lib/rtbsd.c \
        lib/rtbsd.h \
  endif
  
  if HAVE_OPENSSL
- lib_libopenvswitch_a_SOURCES += lib/stream-ssl.c
- nodist_lib_libopenvswitch_a_SOURCES += lib/dhparams.c
+ lib_libopenvswitch_la_SOURCES += lib/stream-ssl.c
+ nodist_lib_libopenvswitch_la_SOURCES += lib/dhparams.c
  lib/dhparams.c: lib/dh1024.pem lib/dh2048.pem lib/dh4096.pem
        (echo '#include "lib/dhparams.h"' &&                            \
         openssl dhparam -C -in $(srcdir)/lib/dh1024.pem -noout &&      \
        | sed 's/\(get_dh[0-9]*\)()/\1(void)/' > lib/dhparams.c.tmp
        mv lib/dhparams.c.tmp lib/dhparams.c
  else
- lib_libopenvswitch_a_SOURCES += lib/stream-nossl.c
+ lib_libopenvswitch_la_SOURCES += lib/stream-nossl.c
  endif
  
  EXTRA_DIST += \
@@@ -395,25 -408,3 +412,3 @@@ lib-install-data-local
        $(MKDIR_P) $(DESTDIR)$(LOGDIR)
        $(MKDIR_P) $(DESTDIR)$(DBDIR)
  
- if !USE_LINKER_SECTIONS
- # All distributed sources, with names adjust properly for referencing
- # from $(builddir).
- all_sources = \
-       `for file in $(DIST_SOURCES); do \
-               if test -f $$file; then \
-                       echo $$file; \
-               else \
-                       echo $(VPATH)/$$file; \
-               fi; \
-        done`
- lib/coverage.$(OBJEXT): lib/coverage.def
- lib/coverage.def: $(DIST_SOURCES)
-       sed -n 's|^COVERAGE_DEFINE(\([_a-zA-Z0-9]\{1,\}\)).*$$|COVERAGE_COUNTER(\1)|p' $(all_sources) | LC_ALL=C sort -u > $@
- CLEANFILES += lib/coverage.def
- lib/vlog.$(OBJEXT): lib/vlog-modules.def
- lib/vlog-modules.def: $(DIST_SOURCES)
-       sed -n 's|^VLOG_DEFINE_\(THIS_\)\{0,1\}MODULE(\([_a-zA-Z0-9]\{1,\}\)).*$$|VLOG_MODULE(\2)|p' $(all_sources) | LC_ALL=C sort -u > $@
- CLEANFILES += lib/vlog-modules.def
- endif
diff --combined lib/dpif-netdev.c
@@@ -1,5 -1,5 +1,5 @@@
  /*
-  * Copyright (c) 2009, 2010, 2011, 2012, 2013 Nicira, Inc.
+  * Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014 Nicira, Inc.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
@@@ -39,7 -39,9 +39,9 @@@
  #include "dynamic-string.h"
  #include "flow.h"
  #include "hmap.h"
+ #include "latch.h"
  #include "list.h"
+ #include "meta-flow.h"
  #include "netdev.h"
  #include "netdev-vport.h"
  #include "netlink.h"
@@@ -64,7 -66,6 +66,6 @@@ VLOG_DEFINE_THIS_MODULE(dpif_netdev)
  #define NETDEV_RULE_PRIORITY 0x8000
  
  /* Configuration parameters. */
- enum { MAX_PORTS = 256 };       /* Maximum number of ports. */
  enum { MAX_FLOWS = 65536 };     /* Maximum number of flows in flow table. */
  
  /* Enough headroom to add a vlan tag, plus an extra 2 bytes to allow IP
@@@ -77,68 -78,222 +78,222 @@@ enum { MAX_QUEUE_LEN = 128 };   /* Maxi
  enum { QUEUE_MASK = MAX_QUEUE_LEN - 1 };
  BUILD_ASSERT_DECL(IS_POW2(MAX_QUEUE_LEN));
  
+ /* Protects against changes to 'dp_netdevs'. */
+ static struct ovs_mutex dp_netdev_mutex = OVS_MUTEX_INITIALIZER;
+ /* Contains all 'struct dp_netdev's. */
+ static struct shash dp_netdevs OVS_GUARDED_BY(dp_netdev_mutex)
+     = SHASH_INITIALIZER(&dp_netdevs);
  struct dp_netdev_upcall {
      struct dpif_upcall upcall;  /* Queued upcall information. */
      struct ofpbuf buf;          /* ofpbuf instance for upcall.packet. */
  };
  
+ /* A queue passing packets from a struct dp_netdev to its clients.
+  *
+  *
+  * Thread-safety
+  * =============
+  *
+  * Any access at all requires the owning 'dp_netdev''s queue_mutex. */
  struct dp_netdev_queue {
-     struct dp_netdev_upcall upcalls[MAX_QUEUE_LEN];
-     unsigned int head, tail;
+     struct dp_netdev_upcall upcalls[MAX_QUEUE_LEN] OVS_GUARDED;
+     unsigned int head OVS_GUARDED;
+     unsigned int tail OVS_GUARDED;
  };
  
- /* Datapath based on the network device interface from netdev.h. */
+ /* Datapath based on the network device interface from netdev.h.
+  *
+  *
+  * Thread-safety
+  * =============
+  *
+  * Some members, marked 'const', are immutable.  Accessing other members
+  * requires synchronization, as noted in more detail below.
+  *
+  * Acquisition order is, from outermost to innermost:
+  *
+  *    dp_netdev_mutex (global)
+  *    port_rwlock
+  *    flow_mutex
+  *    cls.rwlock
+  *    queue_mutex
+  */
  struct dp_netdev {
-     const struct dpif_class *class;
-     char *name;
-     int open_cnt;
-     bool destroyed;
-     int max_mtu;                /* Maximum MTU of any port added so far. */
+     const struct dpif_class *const class;
+     const char *const name;
+     struct ovs_refcount ref_cnt;
+     atomic_flag destroyed;
  
+     /* Flows.
+      *
+      * Readers of 'cls' and 'flow_table' must take a 'cls->rwlock' read lock.
+      *
+      * Writers of 'cls' and 'flow_table' must take the 'flow_mutex' and then
+      * the 'cls->rwlock' write lock.  (The outer 'flow_mutex' allows writers to
+      * atomically perform multiple operations on 'cls' and 'flow_table'.)
+      */
+     struct ovs_mutex flow_mutex;
+     struct classifier cls;      /* Classifier.  Protected by cls.rwlock. */
+     struct hmap flow_table OVS_GUARDED; /* Flow table. */
+     /* Queues.
+      *
+      * Everything in 'queues' is protected by 'queue_mutex'. */
+     struct ovs_mutex queue_mutex;
      struct dp_netdev_queue queues[N_QUEUES];
-     struct classifier cls;      /* Classifier. */
-     struct hmap flow_table;     /* Flow table. */
      struct seq *queue_seq;      /* Incremented whenever a packet is queued. */
  
-     /* Statistics. */
-     long long int n_hit;        /* Number of flow table matches. */
-     long long int n_missed;     /* Number of flow table misses. */
-     long long int n_lost;       /* Number of misses not passed to client. */
+     /* Statistics.
+      *
+      * ovsthread_counter is internally synchronized. */
+     struct ovsthread_counter *n_hit;    /* Number of flow table matches. */
+     struct ovsthread_counter *n_missed; /* Number of flow table misses. */
+     struct ovsthread_counter *n_lost;   /* Number of misses not passed up. */
  
-     /* Ports. */
-     struct dp_netdev_port *ports[MAX_PORTS];
-     struct list port_list;
+     /* Ports.
+      *
+      * Any lookup into 'ports' or any access to the dp_netdev_ports found
+      * through 'ports' requires taking 'port_rwlock'. */
+     struct ovs_rwlock port_rwlock;
+     struct hmap ports OVS_GUARDED;
      struct seq *port_seq;       /* Incremented whenever a port changes. */
+     /* Forwarding threads. */
+     struct latch exit_latch;
+     struct dp_forwarder *forwarders;
+     size_t n_forwarders;
  };
  
+ static struct dp_netdev_port *dp_netdev_lookup_port(const struct dp_netdev *dp,
+                                                     odp_port_t)
+     OVS_REQ_RDLOCK(dp->port_rwlock);
  /* A port in a netdev-based datapath. */
  struct dp_netdev_port {
-     odp_port_t port_no;         /* Index into dp_netdev's 'ports'. */
-     struct list node;           /* Element in dp_netdev's 'port_list'. */
+     struct hmap_node node;      /* Node in dp_netdev's 'ports'. */
+     odp_port_t port_no;
      struct netdev *netdev;
      struct netdev_saved_flags *sf;
      struct netdev_rx *rx;
      char *type;                 /* Port type as requested by user. */
  };
  
- /* A flow in dp_netdev's 'flow_table'. */
+ /* A flow in dp_netdev's 'flow_table'.
+  *
+  *
+  * Thread-safety
+  * =============
+  *
+  * Except near the beginning or ending of its lifespan, rule 'rule' belongs to
+  * its dp_netdev's classifier.  The text below calls this classifier 'cls'.
+  *
+  * Motivation
+  * ----------
+  *
+  * The thread safety rules described here for "struct dp_netdev_flow" are
+  * motivated by two goals:
+  *
+  *    - Prevent threads that read members of "struct dp_netdev_flow" from
+  *      reading bad data due to changes by some thread concurrently modifying
+  *      those members.
+  *
+  *    - Prevent two threads making changes to members of a given "struct
+  *      dp_netdev_flow" from interfering with each other.
+  *
+  *
+  * Rules
+  * -----
+  *
+  * A flow 'flow' may be accessed without a risk of being freed by code that
+  * holds a read-lock or write-lock on 'cls->rwlock' or that owns a reference to
+  * 'flow->ref_cnt' (or both).  Code that needs to hold onto a flow for a while
+  * should take 'cls->rwlock', find the flow it needs, increment 'flow->ref_cnt'
+  * with dpif_netdev_flow_ref(), and drop 'cls->rwlock'.
+  *
+  * 'flow->ref_cnt' protects 'flow' from being freed.  It doesn't protect the
+  * flow from being deleted from 'cls' (that's 'cls->rwlock') and it doesn't
+  * protect members of 'flow' from modification (that's 'flow->mutex').
+  *
+  * 'flow->mutex' protects the members of 'flow' from modification.  It doesn't
+  * protect the flow from being deleted from 'cls' (that's 'cls->rwlock') and it
+  * doesn't prevent the flow from being freed (that's 'flow->ref_cnt').
+  *
+  * Some members, marked 'const', are immutable.  Accessing other members
+  * requires synchronization, as noted in more detail below.
+  */
  struct dp_netdev_flow {
      /* Packet classification. */
-     struct cls_rule cr;         /* In owning dp_netdev's 'cls'. */
+     const struct cls_rule cr;   /* In owning dp_netdev's 'cls'. */
+     /* Hash table index by unmasked flow. */
+     const struct hmap_node node; /* In owning dp_netdev's 'flow_table'. */
+     const struct flow flow;      /* The flow that created this entry. */
+     /* Number of references.
+      * The classifier owns one reference.
+      * Any thread trying to keep a rule from being freed should hold its own
+      * reference. */
+     struct ovs_refcount ref_cnt;
+     /* Protects members marked OVS_GUARDED.
+      *
+      * Acquire after datapath's flow_mutex. */
+     struct ovs_mutex mutex OVS_ACQ_AFTER(dp_netdev_mutex);
+     /* Statistics.
+      *
+      * Reading or writing these members requires 'mutex'. */
+     long long int used OVS_GUARDED; /* Last used time, in monotonic msecs. */
+     long long int packet_count OVS_GUARDED; /* Number of packets matched. */
+     long long int byte_count OVS_GUARDED;   /* Number of bytes matched. */
+     uint16_t tcp_flags OVS_GUARDED; /* Bitwise-OR of seen tcp_flags values. */
+     /* Actions.
+      *
+      * Reading 'actions' requires 'mutex'.
+      * Writing 'actions' requires 'mutex' and (to allow for transactions) the
+      * datapath's flow_mutex. */
+     struct dp_netdev_actions *actions OVS_GUARDED;
+ };
+ static struct dp_netdev_flow *dp_netdev_flow_ref(
+     const struct dp_netdev_flow *);
+ static void dp_netdev_flow_unref(struct dp_netdev_flow *);
  
-     /* Hash table index by unmasked flow.*/
-     struct hmap_node node;      /* In owning dp_netdev's 'flow_table'. */
-     struct flow flow;           /* The flow that created this entry. */
+ /* A set of datapath actions within a "struct dp_netdev_flow".
+  *
+  *
+  * Thread-safety
+  * =============
+  *
+  * A struct dp_netdev_actions 'actions' may be accessed without a risk of being
+  * freed by code that holds a read-lock or write-lock on 'flow->mutex' (where
+  * 'flow' is the dp_netdev_flow for which 'flow->actions == actions') or that
+  * owns a reference to 'actions->ref_cnt' (or both). */
+ struct dp_netdev_actions {
+     struct ovs_refcount ref_cnt;
+     /* These members are immutable: they do not change during the struct's
+      * lifetime.  */
+     struct nlattr *actions;     /* Sequence of OVS_ACTION_ATTR_* attributes. */
+     unsigned int size;          /* Size of 'actions', in bytes. */
+ };
  
-     /* Statistics. */
-     long long int used;         /* Last used time, in monotonic msecs. */
-     long long int packet_count; /* Number of packets matched. */
-     long long int byte_count;   /* Number of bytes matched. */
-     uint16_t tcp_flags;         /* Bitwise-OR of seen tcp_flags values. */
+ struct dp_netdev_actions *dp_netdev_actions_create(const struct nlattr *,
+                                                    size_t);
+ struct dp_netdev_actions *dp_netdev_actions_ref(
+     const struct dp_netdev_actions *);
+ void dp_netdev_actions_unref(struct dp_netdev_actions *);
  
-     /* Actions. */
-     struct nlattr *actions;
-     size_t actions_len;
+ /* A thread that receives packets from some ports, looks them up in the flow
+  * table, and executes the actions it finds. */
+ struct dp_forwarder {
+     struct dp_netdev *dp;
+     pthread_t thread;
+     char *name;
+     uint32_t min_hash, max_hash;
  };
  
  /* Interface to netdev-based datapath. */
@@@ -148,34 -303,36 +303,36 @@@ struct dpif_netdev 
      uint64_t last_port_seq;
  };
  
- /* All netdev-based datapaths. */
- static struct shash dp_netdevs = SHASH_INITIALIZER(&dp_netdevs);
- /* Global lock for all data. */
- static struct ovs_mutex dp_netdev_mutex = OVS_MUTEX_INITIALIZER;
- static int get_port_by_number(struct dp_netdev *, odp_port_t port_no,
-                               struct dp_netdev_port **portp);
- static int get_port_by_name(struct dp_netdev *, const char *devname,
-                             struct dp_netdev_port **portp);
- static void dp_netdev_free(struct dp_netdev *);
+ static int get_port_by_number(struct dp_netdev *dp, odp_port_t port_no,
+                               struct dp_netdev_port **portp)
+     OVS_REQ_RDLOCK(dp->port_rwlock);
+ static int get_port_by_name(struct dp_netdev *dp, const char *devname,
+                             struct dp_netdev_port **portp)
+     OVS_REQ_RDLOCK(dp->port_rwlock);
+ static void dp_netdev_free(struct dp_netdev *)
+     OVS_REQUIRES(dp_netdev_mutex);
  static void dp_netdev_flow_flush(struct dp_netdev *);
- static int do_add_port(struct dp_netdev *, const char *devname,
-                        const char *type, odp_port_t port_no);
- static int do_del_port(struct dp_netdev *, odp_port_t port_no);
+ static int do_add_port(struct dp_netdev *dp, const char *devname,
+                        const char *type, odp_port_t port_no)
+     OVS_REQ_WRLOCK(dp->port_rwlock);
+ static int do_del_port(struct dp_netdev *dp, odp_port_t port_no)
+     OVS_REQ_WRLOCK(dp->port_rwlock);
  static int dpif_netdev_open(const struct dpif_class *, const char *name,
                              bool create, struct dpif **);
- static int dp_netdev_output_userspace(struct dp_netdev *, const struct ofpbuf *,
+ static int dp_netdev_output_userspace(struct dp_netdev *dp, struct ofpbuf *,
                                      int queue_no, const struct flow *,
-                                     const struct nlattr *userdata);
- static void dp_netdev_execute_actions(struct dp_netdev *, const struct flow *,
-                                       struct ofpbuf *,
+                                     const struct nlattr *userdata)
+     OVS_EXCLUDED(dp->queue_mutex);
+ static void dp_netdev_execute_actions(struct dp_netdev *dp,
+                                       const struct flow *, struct ofpbuf *,
+                                       struct pkt_metadata *,
                                        const struct nlattr *actions,
-                                       size_t actions_len);
- static void dp_netdev_port_input(struct dp_netdev *dp,
-                                  struct dp_netdev_port *port,
-                                  struct ofpbuf *packet, uint32_t skb_priority,
-                                  uint32_t pkt_mark, const struct flow_tnl *tnl);
+                                       size_t actions_len)
+     OVS_REQ_RDLOCK(dp->port_rwlock);
+ static void dp_netdev_port_input(struct dp_netdev *dp, struct ofpbuf *packet,
+                                  struct pkt_metadata *)
+     OVS_REQ_RDLOCK(dp->port_rwlock);
+ static void dp_netdev_set_threads(struct dp_netdev *, int n);
  
  static struct dpif_netdev *
  dpif_netdev_cast(const struct dpif *dpif)
@@@ -210,17 -367,10 +367,17 @@@ dpif_netdev_class_is_dummy(const struc
      return class != &dpif_netdev_class;
  }
  
 +static bool
 +dpif_netdev_class_is_planetlab(const struct dpif_class *class)
 +{
 +    return class == &dpif_planetlab_class;
 +}
 +
  static const char *
  dpif_netdev_port_open_type(const struct dpif_class *class, const char *type)
  {
      return strcmp(type, "internal") ? type
 +                  : dpif_netdev_class_is_planetlab(class) ? "pltap"
                    : dpif_netdev_class_is_dummy(class) ? "dummy"
                    : "tap";
  }
@@@ -231,7 -381,7 +388,7 @@@ create_dpif_netdev(struct dp_netdev *dp
      uint16_t netflow_id = hash_string(dp->name, 0);
      struct dpif_netdev *dpif;
  
-     dp->open_cnt++;
+     ovs_refcount_ref(&dp->ref_cnt);
  
      dpif = xmalloc(sizeof *dpif);
      dpif_init(&dpif->dpif, dp->class, dp->name, netflow_id >> 8, netflow_id);
   * Return ODPP_NONE on failure. */
  static odp_port_t
  choose_port(struct dp_netdev *dp, const char *name)
+     OVS_REQ_RDLOCK(dp->port_rwlock)
  {
      uint32_t port_no;
  
 -    if (dp->class != &dpif_netdev_class) {
 +    if (dp->class != &dpif_netdev_class && 
 +        dp->class != &dpif_planetlab_class) {
          const char *p;
          int start_no = 0;
  
          for (p = name; *p != '\0'; p++) {
              if (isdigit((unsigned char) *p)) {
                  port_no = start_no + strtol(p, NULL, 10);
-                 if (port_no > 0 && port_no < MAX_PORTS
-                     && !dp->ports[port_no]) {
+                 if (port_no > 0 && port_no != odp_to_u32(ODPP_NONE)
+                     && !dp_netdev_lookup_port(dp, u32_to_odp(port_no))) {
                      return u32_to_odp(port_no);
                  }
                  break;
          }
      }
  
-     for (port_no = 1; port_no < MAX_PORTS; port_no++) {
-         if (!dp->ports[port_no]) {
+     for (port_no = 1; port_no <= UINT16_MAX; port_no++) {
+         if (!dp_netdev_lookup_port(dp, u32_to_odp(port_no))) {
              return u32_to_odp(port_no);
          }
      }
  static int
  create_dp_netdev(const char *name, const struct dpif_class *class,
                   struct dp_netdev **dpp)
+     OVS_REQUIRES(dp_netdev_mutex)
  {
      struct dp_netdev *dp;
      int error;
      int i;
  
      dp = xzalloc(sizeof *dp);
-     dp->class = class;
-     dp->name = xstrdup(name);
-     dp->open_cnt = 0;
-     dp->max_mtu = ETH_PAYLOAD_MAX;
+     shash_add(&dp_netdevs, name, dp);
+     *CONST_CAST(const struct dpif_class **, &dp->class) = class;
+     *CONST_CAST(const char **, &dp->name) = xstrdup(name);
+     ovs_refcount_init(&dp->ref_cnt);
+     atomic_flag_init(&dp->destroyed);
+     ovs_mutex_init(&dp->flow_mutex);
+     classifier_init(&dp->cls, NULL);
+     hmap_init(&dp->flow_table);
+     ovs_mutex_init(&dp->queue_mutex);
+     ovs_mutex_lock(&dp->queue_mutex);
      for (i = 0; i < N_QUEUES; i++) {
          dp->queues[i].head = dp->queues[i].tail = 0;
      }
+     ovs_mutex_unlock(&dp->queue_mutex);
      dp->queue_seq = seq_create();
-     classifier_init(&dp->cls, NULL);
-     hmap_init(&dp->flow_table);
-     list_init(&dp->port_list);
+     dp->n_hit = ovsthread_counter_create();
+     dp->n_missed = ovsthread_counter_create();
+     dp->n_lost = ovsthread_counter_create();
+     ovs_rwlock_init(&dp->port_rwlock);
+     hmap_init(&dp->ports);
      dp->port_seq = seq_create();
+     latch_init(&dp->exit_latch);
  
+     ovs_rwlock_wrlock(&dp->port_rwlock);
      error = do_add_port(dp, name, "internal", ODPP_LOCAL);
+     ovs_rwlock_unlock(&dp->port_rwlock);
      if (error) {
          dp_netdev_free(dp);
          return error;
      }
-     shash_add(&dp_netdevs, name, dp);
+     dp_netdev_set_threads(dp, 2);
  
      *dpp = dp;
      return 0;
@@@ -346,49 -513,79 +521,79 @@@ dp_netdev_purge_queues(struct dp_netde
  {
      int i;
  
+     ovs_mutex_lock(&dp->queue_mutex);
      for (i = 0; i < N_QUEUES; i++) {
          struct dp_netdev_queue *q = &dp->queues[i];
  
          while (q->tail != q->head) {
              struct dp_netdev_upcall *u = &q->upcalls[q->tail++ & QUEUE_MASK];
+             ofpbuf_uninit(&u->upcall.packet);
              ofpbuf_uninit(&u->buf);
          }
      }
+     ovs_mutex_unlock(&dp->queue_mutex);
  }
  
+ /* Requires dp_netdev_mutex so that we can't get a new reference to 'dp'
+  * through the 'dp_netdevs' shash while freeing 'dp'. */
  static void
  dp_netdev_free(struct dp_netdev *dp)
+     OVS_REQUIRES(dp_netdev_mutex)
  {
      struct dp_netdev_port *port, *next;
  
+     shash_find_and_delete(&dp_netdevs, dp->name);
+     dp_netdev_set_threads(dp, 0);
+     free(dp->forwarders);
      dp_netdev_flow_flush(dp);
-     LIST_FOR_EACH_SAFE (port, next, node, &dp->port_list) {
+     ovs_rwlock_wrlock(&dp->port_rwlock);
+     HMAP_FOR_EACH_SAFE (port, next, node, &dp->ports) {
          do_del_port(dp, port->port_no);
      }
+     ovs_rwlock_unlock(&dp->port_rwlock);
+     ovsthread_counter_destroy(dp->n_hit);
+     ovsthread_counter_destroy(dp->n_missed);
+     ovsthread_counter_destroy(dp->n_lost);
      dp_netdev_purge_queues(dp);
      seq_destroy(dp->queue_seq);
+     ovs_mutex_destroy(&dp->queue_mutex);
      classifier_destroy(&dp->cls);
      hmap_destroy(&dp->flow_table);
+     ovs_mutex_destroy(&dp->flow_mutex);
      seq_destroy(dp->port_seq);
-     free(dp->name);
+     hmap_destroy(&dp->ports);
+     atomic_flag_destroy(&dp->destroyed);
+     ovs_refcount_destroy(&dp->ref_cnt);
+     latch_destroy(&dp->exit_latch);
+     free(CONST_CAST(char *, dp->name));
      free(dp);
  }
  
+ static void
+ dp_netdev_unref(struct dp_netdev *dp)
+ {
+     if (dp) {
+         /* Take dp_netdev_mutex so that, if dp->ref_cnt falls to zero, we can't
+          * get a new reference to 'dp' through the 'dp_netdevs' shash. */
+         ovs_mutex_lock(&dp_netdev_mutex);
+         if (ovs_refcount_unref(&dp->ref_cnt) == 1) {
+             dp_netdev_free(dp);
+         }
+         ovs_mutex_unlock(&dp_netdev_mutex);
+     }
+ }
  static void
  dpif_netdev_close(struct dpif *dpif)
  {
      struct dp_netdev *dp = get_dp_netdev(dpif);
  
-     ovs_mutex_lock(&dp_netdev_mutex);
-     ovs_assert(dp->open_cnt > 0);
-     if (--dp->open_cnt == 0 && dp->destroyed) {
-         shash_find_and_delete(&dp_netdevs, dp->name);
-         dp_netdev_free(dp);
-     }
+     dp_netdev_unref(dp);
      free(dpif);
-     ovs_mutex_unlock(&dp_netdev_mutex);
  }
  
  static int
@@@ -396,9 -593,12 +601,12 @@@ dpif_netdev_destroy(struct dpif *dpif
  {
      struct dp_netdev *dp = get_dp_netdev(dpif);
  
-     ovs_mutex_lock(&dp_netdev_mutex);
-     dp->destroyed = true;
-     ovs_mutex_unlock(&dp_netdev_mutex);
+     if (!atomic_flag_test_and_set(&dp->destroyed)) {
+         if (ovs_refcount_unref(&dp->ref_cnt) == 1) {
+             /* Can't happen: 'dpif' still owns a reference to 'dp'. */
+             OVS_NOT_REACHED();
+         }
+     }
  
      return 0;
  }
@@@ -408,14 -608,15 +616,15 @@@ dpif_netdev_get_stats(const struct dpi
  {
      struct dp_netdev *dp = get_dp_netdev(dpif);
  
-     ovs_mutex_lock(&dp_netdev_mutex);
+     fat_rwlock_rdlock(&dp->cls.rwlock);
      stats->n_flows = hmap_count(&dp->flow_table);
-     stats->n_hit = dp->n_hit;
-     stats->n_missed = dp->n_missed;
-     stats->n_lost = dp->n_lost;
-     stats->n_masks = UINT64_MAX;
+     fat_rwlock_unlock(&dp->cls.rwlock);
+     stats->n_hit = ovsthread_counter_read(dp->n_hit);
+     stats->n_missed = ovsthread_counter_read(dp->n_missed);
+     stats->n_lost = ovsthread_counter_read(dp->n_lost);
+     stats->n_masks = UINT32_MAX;
      stats->n_mask_hit = UINT64_MAX;
-     ovs_mutex_unlock(&dp_netdev_mutex);
  
      return 0;
  }
  static int
  do_add_port(struct dp_netdev *dp, const char *devname, const char *type,
              odp_port_t port_no)
+     OVS_REQ_WRLOCK(dp->port_rwlock)
  {
      struct netdev_saved_flags *sf;
      struct dp_netdev_port *port;
      struct netdev_rx *rx;
      enum netdev_flags flags;
      const char *open_type;
-     int mtu;
      int error;
  
      /* XXX reject devices already in some dp_netdev. */
      port->rx = rx;
      port->type = xstrdup(type);
  
-     error = netdev_get_mtu(netdev, &mtu);
-     if (!error && mtu > dp->max_mtu) {
-         dp->max_mtu = mtu;
-     }
-     list_push_back(&dp->port_list, &port->node);
-     dp->ports[odp_to_u32(port_no)] = port;
+     hmap_insert(&dp->ports, &port->node, hash_int(odp_to_u32(port_no), 0));
      seq_change(dp->port_seq);
  
      return 0;
@@@ -495,18 -690,11 +698,11 @@@ dpif_netdev_port_add(struct dpif *dpif
      odp_port_t port_no;
      int error;
  
-     ovs_mutex_lock(&dp_netdev_mutex);
+     ovs_rwlock_wrlock(&dp->port_rwlock);
      dpif_port = netdev_vport_get_dpif_port(netdev, namebuf, sizeof namebuf);
      if (*port_nop != ODPP_NONE) {
-         uint32_t port_idx = odp_to_u32(*port_nop);
-         if (port_idx >= MAX_PORTS) {
-             error = EFBIG;
-         } else if (dp->ports[port_idx]) {
-             error = EBUSY;
-         } else {
-             error = 0;
-             port_no = *port_nop;
-         }
+         port_no = *port_nop;
+         error = dp_netdev_lookup_port(dp, *port_nop) ? EBUSY : 0;
      } else {
          port_no = choose_port(dp, dpif_port);
          error = port_no == ODPP_NONE ? EFBIG : 0;
          *port_nop = port_no;
          error = do_add_port(dp, dpif_port, netdev_get_type(netdev), port_no);
      }
-     ovs_mutex_unlock(&dp_netdev_mutex);
+     ovs_rwlock_unlock(&dp->port_rwlock);
  
      return error;
  }
@@@ -526,9 -714,9 +722,9 @@@ dpif_netdev_port_del(struct dpif *dpif
      struct dp_netdev *dp = get_dp_netdev(dpif);
      int error;
  
-     ovs_mutex_lock(&dp_netdev_mutex);
+     ovs_rwlock_wrlock(&dp->port_rwlock);
      error = port_no == ODPP_LOCAL ? EINVAL : do_del_port(dp, port_no);
-     ovs_mutex_unlock(&dp_netdev_mutex);
+     ovs_rwlock_unlock(&dp->port_rwlock);
  
      return error;
  }
  static bool
  is_valid_port_number(odp_port_t port_no)
  {
-     return odp_to_u32(port_no) < MAX_PORTS;
+     return port_no != ODPP_NONE;
+ }
+ static struct dp_netdev_port *
+ dp_netdev_lookup_port(const struct dp_netdev *dp, odp_port_t port_no)
+     OVS_REQ_RDLOCK(dp->port_rwlock)
+ {
+     struct dp_netdev_port *port;
+     HMAP_FOR_EACH_IN_BUCKET (port, node, hash_int(odp_to_u32(port_no), 0),
+                              &dp->ports) {
+         if (port->port_no == port_no) {
+             return port;
+         }
+     }
+     return NULL;
  }
  
  static int
  get_port_by_number(struct dp_netdev *dp,
                     odp_port_t port_no, struct dp_netdev_port **portp)
+     OVS_REQ_RDLOCK(dp->port_rwlock)
  {
      if (!is_valid_port_number(port_no)) {
          *portp = NULL;
          return EINVAL;
      } else {
-         *portp = dp->ports[odp_to_u32(port_no)];
+         *portp = dp_netdev_lookup_port(dp, port_no);
          return *portp ? 0 : ENOENT;
      }
  }
  static int
  get_port_by_name(struct dp_netdev *dp,
                   const char *devname, struct dp_netdev_port **portp)
+     OVS_REQ_RDLOCK(dp->port_rwlock)
  {
      struct dp_netdev_port *port;
  
-     LIST_FOR_EACH (port, node, &dp->port_list) {
+     HMAP_FOR_EACH (port, node, &dp->ports) {
          if (!strcmp(netdev_get_name(port->netdev), devname)) {
              *portp = port;
              return 0;
  
  static int
  do_del_port(struct dp_netdev *dp, odp_port_t port_no)
+     OVS_REQ_WRLOCK(dp->port_rwlock)
  {
      struct dp_netdev_port *port;
      int error;
          return error;
      }
  
-     list_remove(&port->node);
-     dp->ports[odp_to_u32(port_no)] = NULL;
+     hmap_remove(&dp->ports, &port->node);
      seq_change(dp->port_seq);
  
      netdev_close(port->netdev);
@@@ -608,12 -813,12 +821,12 @@@ dpif_netdev_port_query_by_number(const 
      struct dp_netdev_port *port;
      int error;
  
-     ovs_mutex_lock(&dp_netdev_mutex);
+     ovs_rwlock_rdlock(&dp->port_rwlock);
      error = get_port_by_number(dp, port_no, &port);
      if (!error && dpif_port) {
          answer_port_query(port, dpif_port);
      }
-     ovs_mutex_unlock(&dp_netdev_mutex);
+     ovs_rwlock_unlock(&dp->port_rwlock);
  
      return error;
  }
@@@ -626,33 -831,50 +839,50 @@@ dpif_netdev_port_query_by_name(const st
      struct dp_netdev_port *port;
      int error;
  
-     ovs_mutex_lock(&dp_netdev_mutex);
+     ovs_rwlock_rdlock(&dp->port_rwlock);
      error = get_port_by_name(dp, devname, &port);
      if (!error && dpif_port) {
          answer_port_query(port, dpif_port);
      }
-     ovs_mutex_unlock(&dp_netdev_mutex);
+     ovs_rwlock_unlock(&dp->port_rwlock);
  
      return error;
  }
  
- static uint32_t
- dpif_netdev_get_max_ports(const struct dpif *dpif OVS_UNUSED)
+ static void
+ dp_netdev_remove_flow(struct dp_netdev *dp, struct dp_netdev_flow *flow)
+     OVS_REQ_WRLOCK(dp->cls.rwlock)
+     OVS_REQUIRES(dp->flow_mutex)
  {
-     return MAX_PORTS;
+     struct cls_rule *cr = CONST_CAST(struct cls_rule *, &flow->cr);
+     struct hmap_node *node = CONST_CAST(struct hmap_node *, &flow->node);
+     classifier_remove(&dp->cls, cr);
+     hmap_remove(&dp->flow_table, node);
+     dp_netdev_flow_unref(flow);
  }
  
- static void
- dp_netdev_free_flow(struct dp_netdev *dp, struct dp_netdev_flow *netdev_flow)
+ static struct dp_netdev_flow *
+ dp_netdev_flow_ref(const struct dp_netdev_flow *flow_)
  {
-     ovs_rwlock_wrlock(&dp->cls.rwlock);
-     classifier_remove(&dp->cls, &netdev_flow->cr);
-     ovs_rwlock_unlock(&dp->cls.rwlock);
-     cls_rule_destroy(&netdev_flow->cr);
+     struct dp_netdev_flow *flow = CONST_CAST(struct dp_netdev_flow *, flow_);
+     if (flow) {
+         ovs_refcount_ref(&flow->ref_cnt);
+     }
+     return flow;
+ }
  
-     hmap_remove(&dp->flow_table, &netdev_flow->node);
-     free(netdev_flow->actions);
-     free(netdev_flow);
+ static void
+ dp_netdev_flow_unref(struct dp_netdev_flow *flow)
+ {
+     if (flow && ovs_refcount_unref(&flow->ref_cnt) == 1) {
+         cls_rule_destroy(CONST_CAST(struct cls_rule *, &flow->cr));
+         ovs_mutex_lock(&flow->mutex);
+         dp_netdev_actions_unref(flow->actions);
+         ovs_mutex_unlock(&flow->mutex);
+         ovs_mutex_destroy(&flow->mutex);
+         free(flow);
+     }
  }
  
  static void
@@@ -660,9 -882,13 +890,13 @@@ dp_netdev_flow_flush(struct dp_netdev *
  {
      struct dp_netdev_flow *netdev_flow, *next;
  
+     ovs_mutex_lock(&dp->flow_mutex);
+     fat_rwlock_wrlock(&dp->cls.rwlock);
      HMAP_FOR_EACH_SAFE (netdev_flow, next, node, &dp->flow_table) {
-         dp_netdev_free_flow(dp, netdev_flow);
+         dp_netdev_remove_flow(dp, netdev_flow);
      }
+     fat_rwlock_unlock(&dp->cls.rwlock);
+     ovs_mutex_unlock(&dp->flow_mutex);
  }
  
  static int
@@@ -670,15 -896,13 +904,13 @@@ dpif_netdev_flow_flush(struct dpif *dpi
  {
      struct dp_netdev *dp = get_dp_netdev(dpif);
  
-     ovs_mutex_lock(&dp_netdev_mutex);
      dp_netdev_flow_flush(dp);
-     ovs_mutex_unlock(&dp_netdev_mutex);
      return 0;
  }
  
  struct dp_netdev_port_state {
-     odp_port_t port_no;
+     uint32_t bucket;
+     uint32_t offset;
      char *name;
  };
  
@@@ -695,27 -919,29 +927,29 @@@ dpif_netdev_port_dump_next(const struc
  {
      struct dp_netdev_port_state *state = state_;
      struct dp_netdev *dp = get_dp_netdev(dpif);
-     uint32_t port_idx;
+     struct hmap_node *node;
+     int retval;
  
-     ovs_mutex_lock(&dp_netdev_mutex);
-     for (port_idx = odp_to_u32(state->port_no);
-          port_idx < MAX_PORTS; port_idx++) {
-         struct dp_netdev_port *port = dp->ports[port_idx];
-         if (port) {
-             free(state->name);
-             state->name = xstrdup(netdev_get_name(port->netdev));
-             dpif_port->name = state->name;
-             dpif_port->type = port->type;
-             dpif_port->port_no = port->port_no;
-             state->port_no = u32_to_odp(port_idx + 1);
-             ovs_mutex_unlock(&dp_netdev_mutex);
+     ovs_rwlock_rdlock(&dp->port_rwlock);
+     node = hmap_at_position(&dp->ports, &state->bucket, &state->offset);
+     if (node) {
+         struct dp_netdev_port *port;
  
-             return 0;
-         }
+         port = CONTAINER_OF(node, struct dp_netdev_port, node);
+         free(state->name);
+         state->name = xstrdup(netdev_get_name(port->netdev));
+         dpif_port->name = state->name;
+         dpif_port->type = port->type;
+         dpif_port->port_no = port->port_no;
+         retval = 0;
+     } else {
+         retval = EOF;
      }
-     ovs_mutex_unlock(&dp_netdev_mutex);
+     ovs_rwlock_unlock(&dp->port_rwlock);
  
-     return EOF;
+     return retval;
  }
  
  static int
@@@ -734,7 -960,6 +968,6 @@@ dpif_netdev_port_poll(const struct dpi
      uint64_t new_port_seq;
      int error;
  
-     ovs_mutex_lock(&dp_netdev_mutex);
      new_port_seq = seq_read(dpif->dp->port_seq);
      if (dpif->last_port_seq != new_port_seq) {
          dpif->last_port_seq = new_port_seq;
      } else {
          error = EAGAIN;
      }
-     ovs_mutex_unlock(&dp_netdev_mutex);
  
      return error;
  }
@@@ -752,42 -976,49 +984,49 @@@ dpif_netdev_port_poll_wait(const struc
  {
      struct dpif_netdev *dpif = dpif_netdev_cast(dpif_);
  
-     ovs_mutex_lock(&dp_netdev_mutex);
      seq_wait(dpif->dp->port_seq, dpif->last_port_seq);
-     ovs_mutex_unlock(&dp_netdev_mutex);
+ }
+ static struct dp_netdev_flow *
+ dp_netdev_flow_cast(const struct cls_rule *cr)
+ {
+     return cr ? CONTAINER_OF(cr, struct dp_netdev_flow, cr) : NULL;
  }
  
  static struct dp_netdev_flow *
  dp_netdev_lookup_flow(const struct dp_netdev *dp, const struct flow *flow)
+     OVS_EXCLUDED(dp->cls.rwlock)
  {
-     struct cls_rule *cr;
+     struct dp_netdev_flow *netdev_flow;
  
-     ovs_rwlock_wrlock(&dp->cls.rwlock);
-     cr = classifier_lookup(&dp->cls, flow, NULL);
-     ovs_rwlock_unlock(&dp->cls.rwlock);
+     fat_rwlock_rdlock(&dp->cls.rwlock);
+     netdev_flow = dp_netdev_flow_cast(classifier_lookup(&dp->cls, flow, NULL));
+     dp_netdev_flow_ref(netdev_flow);
+     fat_rwlock_unlock(&dp->cls.rwlock);
  
-     return (cr
-             ? CONTAINER_OF(cr, struct dp_netdev_flow, cr)
-             : NULL);
+     return netdev_flow;
  }
  
  static struct dp_netdev_flow *
  dp_netdev_find_flow(const struct dp_netdev *dp, const struct flow *flow)
+     OVS_REQ_RDLOCK(dp->cls.rwlock)
  {
      struct dp_netdev_flow *netdev_flow;
  
      HMAP_FOR_EACH_WITH_HASH (netdev_flow, node, flow_hash(flow, 0),
                               &dp->flow_table) {
          if (flow_equal(&netdev_flow->flow, flow)) {
-             return netdev_flow;
+             return dp_netdev_flow_ref(netdev_flow);
          }
      }
      return NULL;
  }
  
  static void
  get_dpif_flow_stats(struct dp_netdev_flow *netdev_flow,
                      struct dpif_flow_stats *stats)
+     OVS_REQ_RDLOCK(netdev_flow->mutex)
  {
      stats->n_packets = netdev_flow->packet_count;
      stats->n_bytes = netdev_flow->byte_count;
  }
  
  static int
- dpif_netdev_flow_mask_from_nlattrs(const struct nlattr *key, uint32_t key_len,
-                                    const struct nlattr *mask_key,
-                                    uint32_t mask_key_len, struct flow *flow,
-                                    struct flow *mask)
+ dpif_netdev_mask_from_nlattrs(const struct nlattr *key, uint32_t key_len,
+                               const struct nlattr *mask_key,
+                               uint32_t mask_key_len, const struct flow *flow,
+                               struct flow *mask)
+ {
+     if (mask_key_len) {
+         if (odp_flow_key_to_mask(mask_key, mask_key_len, mask, flow)) {
+             /* This should not happen: it indicates that
+              * odp_flow_key_from_mask() and odp_flow_key_to_mask()
+              * disagree on the acceptable form of a mask.  Log the problem
+              * as an error, with enough details to enable debugging. */
+             static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+             if (!VLOG_DROP_ERR(&rl)) {
+                 struct ds s;
+                 ds_init(&s);
+                 odp_flow_format(key, key_len, mask_key, mask_key_len, NULL, &s,
+                                 true);
+                 VLOG_ERR("internal error parsing flow mask %s", ds_cstr(&s));
+                 ds_destroy(&s);
+             }
+             return EINVAL;
+         }
+         /* Force unwildcard the in_port. */
+         mask->in_port.odp_port = u32_to_odp(UINT32_MAX);
+     } else {
+         enum mf_field_id id;
+         /* No mask key, unwildcard everything except fields whose
+          * prerequisities are not met. */
+         memset(mask, 0x0, sizeof *mask);
+         for (id = 0; id < MFF_N_IDS; ++id) {
+             /* Skip registers and metadata. */
+             if (!(id >= MFF_REG0 && id < MFF_REG0 + FLOW_N_REGS)
+                 && id != MFF_METADATA) {
+                 const struct mf_field *mf = mf_from_id(id);
+                 if (mf_are_prereqs_ok(mf, flow)) {
+                     mf_mask_field(mf, mask);
+                 }
+             }
+         }
+     }
+     return 0;
+ }
+ static int
+ dpif_netdev_flow_from_nlattrs(const struct nlattr *key, uint32_t key_len,
+                               struct flow *flow)
  {
      odp_port_t in_port;
  
-     if (odp_flow_key_to_flow(key, key_len, flow)
-         || (mask_key
-             && odp_flow_key_to_mask(mask_key, mask_key_len, mask, flow))) {
+     if (odp_flow_key_to_flow(key, key_len, flow)) {
          /* This should not happen: it indicates that odp_flow_key_from_flow()
-          * and odp_flow_key_to_flow() disagree on the acceptable form of a flow
-          * or odp_flow_key_from_mask() and odp_flow_key_to_mask() disagree on
-          * the acceptable form of a mask.  Log the problem as an error, with
-          * enough details to enable debugging. */
+          * and odp_flow_key_to_flow() disagree on the acceptable form of a
+          * flow.  Log the problem as an error, with enough details to enable
+          * debugging. */
          static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
  
          if (!VLOG_DROP_ERR(&rl)) {
              struct ds s;
  
              ds_init(&s);
-             odp_flow_format(key, key_len, mask_key, mask_key_len, NULL, &s,
-                             true);
+             odp_flow_format(key, key_len, NULL, 0, NULL, &s, true);
              VLOG_ERR("internal error parsing flow key %s", ds_cstr(&s));
              ds_destroy(&s);
          }
          return EINVAL;
      }
  
-     if (mask_key) {
-         /* Force unwildcard the in_port. */
-         mask->in_port.odp_port = u32_to_odp(UINT32_MAX);
-     }
      in_port = flow->in_port.odp_port;
      if (!is_valid_port_number(in_port) && in_port != ODPP_NONE) {
          return EINVAL;
      return 0;
  }
  
- static int
- dpif_netdev_flow_from_nlattrs(const struct nlattr *key, uint32_t key_len,
-                               struct flow *flow)
- {
-     return dpif_netdev_flow_mask_from_nlattrs(key, key_len, NULL, 0, flow,
-                                               NULL);
- }
  static int
  dpif_netdev_flow_get(const struct dpif *dpif,
                       const struct nlattr *nl_key, size_t nl_key_len,
          return error;
      }
  
-     ovs_mutex_lock(&dp_netdev_mutex);
+     fat_rwlock_rdlock(&dp->cls.rwlock);
      netdev_flow = dp_netdev_find_flow(dp, &key);
+     fat_rwlock_unlock(&dp->cls.rwlock);
      if (netdev_flow) {
+         struct dp_netdev_actions *actions = NULL;
+         ovs_mutex_lock(&netdev_flow->mutex);
          if (stats) {
              get_dpif_flow_stats(netdev_flow, stats);
          }
          if (actionsp) {
-             *actionsp = ofpbuf_clone_data(netdev_flow->actions,
-                                           netdev_flow->actions_len);
+             actions = dp_netdev_actions_ref(netdev_flow->actions);
+         }
+         ovs_mutex_unlock(&netdev_flow->mutex);
+         dp_netdev_flow_unref(netdev_flow);
+         if (actionsp) {
+             *actionsp = ofpbuf_clone_data(actions->actions, actions->size);
+             dp_netdev_actions_unref(actions);
          }
      } else {
          error = ENOENT;
      }
-     ovs_mutex_unlock(&dp_netdev_mutex);
  
      return error;
  }
  
- static int
- set_flow_actions(struct dp_netdev_flow *netdev_flow,
-                  const struct nlattr *actions, size_t actions_len)
- {
-     netdev_flow->actions = xrealloc(netdev_flow->actions, actions_len);
-     netdev_flow->actions_len = actions_len;
-     memcpy(netdev_flow->actions, actions, actions_len);
-     return 0;
- }
  static int
  dp_netdev_flow_add(struct dp_netdev *dp, const struct flow *flow,
                     const struct flow_wildcards *wc,
                     const struct nlattr *actions,
                     size_t actions_len)
+     OVS_REQUIRES(dp->flow_mutex)
  {
      struct dp_netdev_flow *netdev_flow;
      struct match match;
-     int error;
  
      netdev_flow = xzalloc(sizeof *netdev_flow);
-     netdev_flow->flow = *flow;
+     *CONST_CAST(struct flow *, &netdev_flow->flow) = *flow;
+     ovs_refcount_init(&netdev_flow->ref_cnt);
  
-     match_init(&match, flow, wc);
-     cls_rule_init(&netdev_flow->cr, &match, NETDEV_RULE_PRIORITY);
-     ovs_rwlock_wrlock(&dp->cls.rwlock);
-     classifier_insert(&dp->cls, &netdev_flow->cr);
-     ovs_rwlock_unlock(&dp->cls.rwlock);
+     ovs_mutex_init(&netdev_flow->mutex);
+     ovs_mutex_lock(&netdev_flow->mutex);
  
-     error = set_flow_actions(netdev_flow, actions, actions_len);
-     if (error) {
-         ovs_rwlock_wrlock(&dp->cls.rwlock);
-         classifier_remove(&dp->cls, &netdev_flow->cr);
-         ovs_rwlock_unlock(&dp->cls.rwlock);
-         cls_rule_destroy(&netdev_flow->cr);
+     netdev_flow->actions = dp_netdev_actions_create(actions, actions_len);
  
-         free(netdev_flow);
-         return error;
-     }
+     match_init(&match, flow, wc);
+     cls_rule_init(CONST_CAST(struct cls_rule *, &netdev_flow->cr),
+                   &match, NETDEV_RULE_PRIORITY);
+     fat_rwlock_wrlock(&dp->cls.rwlock);
+     classifier_insert(&dp->cls,
+                       CONST_CAST(struct cls_rule *, &netdev_flow->cr));
+     hmap_insert(&dp->flow_table,
+                 CONST_CAST(struct hmap_node *, &netdev_flow->node),
+                 flow_hash(flow, 0));
+     fat_rwlock_unlock(&dp->cls.rwlock);
+     ovs_mutex_unlock(&netdev_flow->mutex);
  
-     hmap_insert(&dp->flow_table, &netdev_flow->node, flow_hash(flow, 0));
      return 0;
  }
  
  static void
  clear_stats(struct dp_netdev_flow *netdev_flow)
+     OVS_REQUIRES(netdev_flow->mutex)
  {
      netdev_flow->used = 0;
      netdev_flow->packet_count = 0;
@@@ -942,13 -1206,18 +1214,18 @@@ dpif_netdev_flow_put(struct dpif *dpif
      struct flow_wildcards wc;
      int error;
  
-     error = dpif_netdev_flow_mask_from_nlattrs(put->key, put->key_len,
-                 put->mask, put->mask_len, &flow, &wc.masks);
+     error = dpif_netdev_flow_from_nlattrs(put->key, put->key_len, &flow);
+     if (error) {
+         return error;
+     }
+     error = dpif_netdev_mask_from_nlattrs(put->key, put->key_len,
+                                           put->mask, put->mask_len,
+                                           &flow, &wc.masks);
      if (error) {
          return error;
      }
  
-     ovs_mutex_lock(&dp_netdev_mutex);
+     ovs_mutex_lock(&dp->flow_mutex);
      netdev_flow = dp_netdev_lookup_flow(dp, &flow);
      if (!netdev_flow) {
          if (put->flags & DPIF_FP_CREATE) {
      } else {
          if (put->flags & DPIF_FP_MODIFY
              && flow_equal(&flow, &netdev_flow->flow)) {
-             error = set_flow_actions(netdev_flow, put->actions,
-                                      put->actions_len);
-             if (!error) {
-                 if (put->stats) {
-                     get_dpif_flow_stats(netdev_flow, put->stats);
-                 }
-                 if (put->flags & DPIF_FP_ZERO_STATS) {
-                     clear_stats(netdev_flow);
-                 }
+             struct dp_netdev_actions *new_actions;
+             struct dp_netdev_actions *old_actions;
+             new_actions = dp_netdev_actions_create(put->actions,
+                                                    put->actions_len);
+             ovs_mutex_lock(&netdev_flow->mutex);
+             old_actions = netdev_flow->actions;
+             netdev_flow->actions = new_actions;
+             if (put->stats) {
+                 get_dpif_flow_stats(netdev_flow, put->stats);
+             }
+             if (put->flags & DPIF_FP_ZERO_STATS) {
+                 clear_stats(netdev_flow);
              }
+             ovs_mutex_unlock(&netdev_flow->mutex);
+             dp_netdev_actions_unref(old_actions);
          } else if (put->flags & DPIF_FP_CREATE) {
              error = EEXIST;
          } else {
              /* Overlapping flow. */
              error = EINVAL;
          }
+         dp_netdev_flow_unref(netdev_flow);
      }
-     ovs_mutex_unlock(&dp_netdev_mutex);
+     ovs_mutex_unlock(&dp->flow_mutex);
  
      return error;
  }
@@@ -1002,17 -1280,21 +1288,21 @@@ dpif_netdev_flow_del(struct dpif *dpif
          return error;
      }
  
-     ovs_mutex_lock(&dp_netdev_mutex);
+     ovs_mutex_lock(&dp->flow_mutex);
+     fat_rwlock_wrlock(&dp->cls.rwlock);
      netdev_flow = dp_netdev_find_flow(dp, &key);
      if (netdev_flow) {
          if (del->stats) {
+             ovs_mutex_lock(&netdev_flow->mutex);
              get_dpif_flow_stats(netdev_flow, del->stats);
+             ovs_mutex_unlock(&netdev_flow->mutex);
          }
-         dp_netdev_free_flow(dp, netdev_flow);
+         dp_netdev_remove_flow(dp, netdev_flow);
      } else {
          error = ENOENT;
      }
-     ovs_mutex_unlock(&dp_netdev_mutex);
+     fat_rwlock_unlock(&dp->cls.rwlock);
+     ovs_mutex_unlock(&dp->flow_mutex);
  
      return error;
  }
  struct dp_netdev_flow_state {
      uint32_t bucket;
      uint32_t offset;
-     struct nlattr *actions;
+     struct dp_netdev_actions *actions;
      struct odputil_keybuf keybuf;
      struct odputil_keybuf maskbuf;
      struct dpif_flow_stats stats;
@@@ -1050,15 -1332,17 +1340,17 @@@ dpif_netdev_flow_dump_next(const struc
      struct dp_netdev_flow *netdev_flow;
      struct hmap_node *node;
  
-     ovs_mutex_lock(&dp_netdev_mutex);
+     fat_rwlock_rdlock(&dp->cls.rwlock);
      node = hmap_at_position(&dp->flow_table, &state->bucket, &state->offset);
+     if (node) {
+         netdev_flow = CONTAINER_OF(node, struct dp_netdev_flow, node);
+         dp_netdev_flow_ref(netdev_flow);
+     }
+     fat_rwlock_unlock(&dp->cls.rwlock);
      if (!node) {
-         ovs_mutex_unlock(&dp_netdev_mutex);
          return EOF;
      }
  
-     netdev_flow = CONTAINER_OF(node, struct dp_netdev_flow, node);
      if (key) {
          struct ofpbuf buf;
  
          *mask_len = buf.size;
      }
  
-     if (actions) {
-         free(state->actions);
-         state->actions = xmemdup(netdev_flow->actions,
-                          netdev_flow->actions_len);
+     if (actions || stats) {
+         dp_netdev_actions_unref(state->actions);
+         state->actions = NULL;
  
-         *actions = state->actions;
-         *actions_len = netdev_flow->actions_len;
+         ovs_mutex_lock(&netdev_flow->mutex);
+         if (actions) {
+             state->actions = dp_netdev_actions_ref(netdev_flow->actions);
+             *actions = state->actions->actions;
+             *actions_len = state->actions->size;
+         }
+         if (stats) {
+             get_dpif_flow_stats(netdev_flow, &state->stats);
+             *stats = &state->stats;
+         }
+         ovs_mutex_unlock(&netdev_flow->mutex);
      }
  
-     if (stats) {
-         get_dpif_flow_stats(netdev_flow, &state->stats);
-         *stats = &state->stats;
-     }
+     dp_netdev_flow_unref(netdev_flow);
  
-     ovs_mutex_unlock(&dp_netdev_mutex);
      return 0;
  }
  
@@@ -1106,42 -1394,33 +1402,33 @@@ dpif_netdev_flow_dump_done(const struc
  {
      struct dp_netdev_flow_state *state = state_;
  
-     free(state->actions);
+     dp_netdev_actions_unref(state->actions);
      free(state);
      return 0;
  }
  
  static int
- dpif_netdev_execute(struct dpif *dpif, const struct dpif_execute *execute)
+ dpif_netdev_execute(struct dpif *dpif, struct dpif_execute *execute)
  {
      struct dp_netdev *dp = get_dp_netdev(dpif);
-     struct flow md;
-     int error;
+     struct pkt_metadata *md = &execute->md;
+     struct flow key;
  
      if (execute->packet->size < ETH_HEADER_LEN ||
          execute->packet->size > UINT16_MAX) {
          return EINVAL;
      }
  
-     /* Get packet metadata. */
-     error = dpif_netdev_flow_from_nlattrs(execute->key, execute->key_len, &md);
-     if (!error) {
-         struct ofpbuf *copy;
-         struct flow key;
+     /* Extract flow key. */
+     flow_extract(execute->packet, md->skb_priority, md->pkt_mark, &md->tunnel,
+                  (union flow_in_port *)&md->in_port, &key);
  
-         /* Make a deep copy of 'packet', because we might modify its data. */
-         copy = ofpbuf_clone_with_headroom(execute->packet, DP_NETDEV_HEADROOM);
+     ovs_rwlock_rdlock(&dp->port_rwlock);
+     dp_netdev_execute_actions(dp, &key, execute->packet, md, execute->actions,
+                               execute->actions_len);
+     ovs_rwlock_unlock(&dp->port_rwlock);
  
-         /* Extract flow key. */
-         flow_extract(copy, md.skb_priority, md.pkt_mark, &md.tunnel,
-                      &md.in_port, &key);
-         ovs_mutex_lock(&dp_netdev_mutex);
-         dp_netdev_execute_actions(dp, &key, copy,
-                                   execute->actions, execute->actions_len);
-         ovs_mutex_unlock(&dp_netdev_mutex);
-         ofpbuf_delete(copy);
-     }
-     return error;
+     return 0;
  }
  
  static int
@@@ -1159,9 -1438,9 +1446,9 @@@ dpif_netdev_queue_to_priority(const str
  }
  
  static struct dp_netdev_queue *
- find_nonempty_queue(struct dpif *dpif)
+ find_nonempty_queue(struct dp_netdev *dp)
+     OVS_REQUIRES(dp->queue_mutex)
  {
-     struct dp_netdev *dp = get_dp_netdev(dpif);
      int i;
  
      for (i = 0; i < N_QUEUES; i++) {
@@@ -1177,16 -1456,16 +1464,16 @@@ static in
  dpif_netdev_recv(struct dpif *dpif, struct dpif_upcall *upcall,
                   struct ofpbuf *buf)
  {
+     struct dp_netdev *dp = get_dp_netdev(dpif);
      struct dp_netdev_queue *q;
      int error;
  
-     ovs_mutex_lock(&dp_netdev_mutex);
-     q = find_nonempty_queue(dpif);
+     ovs_mutex_lock(&dp->queue_mutex);
+     q = find_nonempty_queue(dp);
      if (q) {
          struct dp_netdev_upcall *u = &q->upcalls[q->tail++ & QUEUE_MASK];
  
          *upcall = u->upcall;
-         upcall->packet = buf;
  
          ofpbuf_uninit(buf);
          *buf = u->buf;
      } else {
          error = EAGAIN;
      }
-     ovs_mutex_unlock(&dp_netdev_mutex);
+     ovs_mutex_unlock(&dp->queue_mutex);
  
      return error;
  }
@@@ -1206,128 -1485,237 +1493,237 @@@ dpif_netdev_recv_wait(struct dpif *dpif
      struct dp_netdev *dp = get_dp_netdev(dpif);
      uint64_t seq;
  
-     ovs_mutex_lock(&dp_netdev_mutex);
+     ovs_mutex_lock(&dp->queue_mutex);
      seq = seq_read(dp->queue_seq);
-     if (find_nonempty_queue(dpif)) {
+     if (find_nonempty_queue(dp)) {
          poll_immediate_wake();
      } else {
          seq_wait(dp->queue_seq, seq);
      }
-     ovs_mutex_unlock(&dp_netdev_mutex);
+     ovs_mutex_unlock(&dp->queue_mutex);
  }
  
  static void
  dpif_netdev_recv_purge(struct dpif *dpif)
  {
      struct dpif_netdev *dpif_netdev = dpif_netdev_cast(dpif);
-     ovs_mutex_lock(&dp_netdev_mutex);
      dp_netdev_purge_queues(dpif_netdev->dp);
-     ovs_mutex_unlock(&dp_netdev_mutex);
  }
  \f
- static void
- dp_netdev_flow_used(struct dp_netdev_flow *netdev_flow,
-                     const struct ofpbuf *packet)
+ /* Creates and returns a new 'struct dp_netdev_actions', with a reference count
+  * of 1, whose actions are a copy of from the 'ofpacts_len' bytes of
+  * 'ofpacts'. */
+ struct dp_netdev_actions *
+ dp_netdev_actions_create(const struct nlattr *actions, size_t size)
  {
-     netdev_flow->used = time_msec();
-     netdev_flow->packet_count++;
-     netdev_flow->byte_count += packet->size;
-     netdev_flow->tcp_flags |= packet_get_tcp_flags(packet, &netdev_flow->flow);
+     struct dp_netdev_actions *netdev_actions;
+     netdev_actions = xmalloc(sizeof *netdev_actions);
+     ovs_refcount_init(&netdev_actions->ref_cnt);
+     netdev_actions->actions = xmemdup(actions, size);
+     netdev_actions->size = size;
+     return netdev_actions;
  }
  
- static void
- dp_netdev_port_input(struct dp_netdev *dp, struct dp_netdev_port *port,
-                      struct ofpbuf *packet, uint32_t skb_priority,
-                      uint32_t pkt_mark, const struct flow_tnl *tnl)
+ /* Increments 'actions''s refcount. */
+ struct dp_netdev_actions *
+ dp_netdev_actions_ref(const struct dp_netdev_actions *actions_)
  {
-     struct dp_netdev_flow *netdev_flow;
-     struct flow key;
-     union flow_in_port in_port_;
+     struct dp_netdev_actions *actions;
  
-     if (packet->size < ETH_HEADER_LEN) {
-         return;
-     }
-     in_port_.odp_port = port->port_no;
-     flow_extract(packet, skb_priority, pkt_mark, tnl, &in_port_, &key);
-     netdev_flow = dp_netdev_lookup_flow(dp, &key);
-     if (netdev_flow) {
-         dp_netdev_flow_used(netdev_flow, packet);
-         dp_netdev_execute_actions(dp, &key, packet,
-                                   netdev_flow->actions,
-                                   netdev_flow->actions_len);
-         dp->n_hit++;
-     } else {
-         dp->n_missed++;
-         dp_netdev_output_userspace(dp, packet, DPIF_UC_MISS, &key, NULL);
+     actions = CONST_CAST(struct dp_netdev_actions *, actions_);
+     if (actions) {
+         ovs_refcount_ref(&actions->ref_cnt);
      }
+     return actions;
  }
  
- static void
- dpif_netdev_run(struct dpif *dpif)
+ /* Decrements 'actions''s refcount and frees 'actions' if the refcount reaches
+  * 0. */
+ void
+ dp_netdev_actions_unref(struct dp_netdev_actions *actions)
  {
-     struct dp_netdev_port *port;
-     struct dp_netdev *dp;
+     if (actions && ovs_refcount_unref(&actions->ref_cnt) == 1) {
+         free(actions->actions);
+         free(actions);
+     }
+ }
\f
+ static void *
+ dp_forwarder_main(void *f_)
+ {
+     struct dp_forwarder *f = f_;
+     struct dp_netdev *dp = f->dp;
      struct ofpbuf packet;
  
-     ovs_mutex_lock(&dp_netdev_mutex);
-     dp = get_dp_netdev(dpif);
-     ofpbuf_init(&packet,
-                 DP_NETDEV_HEADROOM + VLAN_ETH_HEADER_LEN + dp->max_mtu);
-     LIST_FOR_EACH (port, node, &dp->port_list) {
-         int error;
+     f->name = xasprintf("forwarder_%u", ovsthread_id_self());
+     set_subprogram_name("%s", f->name);
+     ofpbuf_init(&packet, 0);
+     while (!latch_is_set(&dp->exit_latch)) {
+         bool received_anything;
+         int i;
+         ovs_rwlock_rdlock(&dp->port_rwlock);
+         for (i = 0; i < 50; i++) {
+             struct dp_netdev_port *port;
+             received_anything = false;
+             HMAP_FOR_EACH (port, node, &f->dp->ports) {
+                 if (port->rx
+                     && port->node.hash >= f->min_hash
+                     && port->node.hash <= f->max_hash) {
+                     int buf_size;
+                     int error;
+                     int mtu;
+                     if (netdev_get_mtu(port->netdev, &mtu)) {
+                         mtu = ETH_PAYLOAD_MAX;
+                     }
+                     buf_size = DP_NETDEV_HEADROOM + VLAN_ETH_HEADER_LEN + mtu;
+                     ofpbuf_clear(&packet);
+                     ofpbuf_reserve_with_tailroom(&packet, DP_NETDEV_HEADROOM,
+                                                  buf_size);
+                     error = netdev_rx_recv(port->rx, &packet);
+                     if (!error) {
+                         struct pkt_metadata md
+                             = PKT_METADATA_INITIALIZER(port->port_no);
+                         dp_netdev_port_input(dp, &packet, &md);
+                         received_anything = true;
+                     } else if (error != EAGAIN && error != EOPNOTSUPP) {
+                         static struct vlog_rate_limit rl
+                             = VLOG_RATE_LIMIT_INIT(1, 5);
+                         VLOG_ERR_RL(&rl, "error receiving data from %s: %s",
+                                     netdev_get_name(port->netdev),
+                                     ovs_strerror(error));
+                     }
+                 }
+             }
  
-         /* Reset packet contents. */
-         ofpbuf_clear(&packet);
-         ofpbuf_reserve(&packet, DP_NETDEV_HEADROOM);
+             if (!received_anything) {
+                 break;
+             }
+         }
  
-         error = port->rx ? netdev_rx_recv(port->rx, &packet) : EOPNOTSUPP;
-         if (!error) {
-             dp_netdev_port_input(dp, port, &packet, 0, 0, NULL);
-         } else if (error != EAGAIN && error != EOPNOTSUPP) {
-             static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+         if (received_anything) {
+             poll_immediate_wake();
+         } else {
+             struct dp_netdev_port *port;
  
-             VLOG_ERR_RL(&rl, "error receiving data from %s: %s",
-                         netdev_get_name(port->netdev), ovs_strerror(error));
+             HMAP_FOR_EACH (port, node, &f->dp->ports)
+                 if (port->rx
+                     && port->node.hash >= f->min_hash
+                     && port->node.hash <= f->max_hash) {
+                     netdev_rx_wait(port->rx);
+                 }
+             seq_wait(dp->port_seq, seq_read(dp->port_seq));
+             latch_wait(&dp->exit_latch);
          }
+         ovs_rwlock_unlock(&dp->port_rwlock);
+         poll_block();
      }
      ofpbuf_uninit(&packet);
-     ovs_mutex_unlock(&dp_netdev_mutex);
+     free(f->name);
+     return NULL;
  }
  
  static void
- dpif_netdev_wait(struct dpif *dpif)
+ dp_netdev_set_threads(struct dp_netdev *dp, int n)
  {
-     struct dp_netdev_port *port;
+     int i;
  
-     /* There is a race here, if thread A calls dpif_netdev_wait(dpif) and
-      * thread B calls dpif_port_add(dpif) or dpif_port_remove(dpif) before
-      * A makes it to poll_block().
-      *
-      * But I think it doesn't matter:
-      *
-      *     - In the dpif_port_add() case, A will not wake up when a packet
-      *       arrives on the new port, but this would also happen if the
-      *       ordering were reversed.
-      *
-      *     - In the dpif_port_remove() case, A might wake up spuriously, but
-      *       that is harmless. */
+     if (n == dp->n_forwarders) {
+         return;
+     }
  
-     ovs_mutex_lock(&dp_netdev_mutex);
-     LIST_FOR_EACH (port, node, &get_dp_netdev(dpif)->port_list) {
-         if (port->rx) {
-             netdev_rx_wait(port->rx);
+     /* Stop existing threads. */
+     latch_set(&dp->exit_latch);
+     for (i = 0; i < dp->n_forwarders; i++) {
+         struct dp_forwarder *f = &dp->forwarders[i];
+         xpthread_join(f->thread, NULL);
+     }
+     latch_poll(&dp->exit_latch);
+     free(dp->forwarders);
+     /* Start new threads. */
+     dp->forwarders = xmalloc(n * sizeof *dp->forwarders);
+     dp->n_forwarders = n;
+     for (i = 0; i < n; i++) {
+         struct dp_forwarder *f = &dp->forwarders[i];
+         f->dp = dp;
+         f->min_hash = UINT32_MAX / n * i;
+         f->max_hash = UINT32_MAX / n * (i + 1) - 1;
+         if (i == n - 1) {
+             f->max_hash = UINT32_MAX;
          }
+         xpthread_create(&f->thread, NULL, dp_forwarder_main, f);
+     }
+ }
\f
+ static void
+ dp_netdev_flow_used(struct dp_netdev_flow *netdev_flow,
+                     const struct ofpbuf *packet)
+     OVS_REQUIRES(netdev_flow->mutex)
+ {
+     netdev_flow->used = time_msec();
+     netdev_flow->packet_count++;
+     netdev_flow->byte_count += packet->size;
+     netdev_flow->tcp_flags |= packet_get_tcp_flags(packet, &netdev_flow->flow);
+ }
+ static void
+ dp_netdev_port_input(struct dp_netdev *dp, struct ofpbuf *packet,
+                      struct pkt_metadata *md)
+     OVS_REQ_RDLOCK(dp->port_rwlock)
+ {
+     struct dp_netdev_flow *netdev_flow;
+     struct flow key;
+     if (packet->size < ETH_HEADER_LEN) {
+         return;
+     }
+     flow_extract(packet, md->skb_priority, md->pkt_mark, &md->tunnel,
+                  (union flow_in_port *)&md->in_port, &key);
+     netdev_flow = dp_netdev_lookup_flow(dp, &key);
+     if (netdev_flow) {
+         struct dp_netdev_actions *actions;
+         ovs_mutex_lock(&netdev_flow->mutex);
+         dp_netdev_flow_used(netdev_flow, packet);
+         actions = dp_netdev_actions_ref(netdev_flow->actions);
+         ovs_mutex_unlock(&netdev_flow->mutex);
+         dp_netdev_execute_actions(dp, &key, packet, md,
+                                   actions->actions, actions->size);
+         dp_netdev_actions_unref(actions);
+         ovsthread_counter_inc(dp->n_hit, 1);
+     } else {
+         ovsthread_counter_inc(dp->n_missed, 1);
+         dp_netdev_output_userspace(dp, packet, DPIF_UC_MISS, &key, NULL);
      }
-     ovs_mutex_unlock(&dp_netdev_mutex);
  }
  
  static int
- dp_netdev_output_userspace(struct dp_netdev *dp, const struct ofpbuf *packet,
+ dp_netdev_output_userspace(struct dp_netdev *dp, struct ofpbuf *packet,
                             int queue_no, const struct flow *flow,
                             const struct nlattr *userdata)
+     OVS_EXCLUDED(dp->queue_mutex)
  {
      struct dp_netdev_queue *q = &dp->queues[queue_no];
+     int error;
+     ovs_mutex_lock(&dp->queue_mutex);
      if (q->head - q->tail < MAX_QUEUE_LEN) {
          struct dp_netdev_upcall *u = &q->upcalls[q->head++ & QUEUE_MASK];
          struct dpif_upcall *upcall = &u->upcall;
          upcall->type = queue_no;
  
          /* Allocate buffer big enough for everything. */
-         buf_size = ODPUTIL_FLOW_KEY_BYTES + 2 + packet->size;
+         buf_size = ODPUTIL_FLOW_KEY_BYTES;
          if (userdata) {
              buf_size += NLA_ALIGN(userdata->nla_len);
          }
                                            NLA_ALIGN(userdata->nla_len));
          }
  
-         /* Put packet.
-          *
-          * We adjust 'data' and 'size' in 'buf' so that only the packet itself
-          * is visible in 'upcall->packet'.  The ODP flow and (if present)
-          * userdata become part of the headroom. */
-         ofpbuf_put_zeros(buf, 2);
-         buf->data = ofpbuf_put(buf, packet->data, packet->size);
-         buf->size = packet->size;
-         upcall->packet = buf;
+         /* Steal packet data. */
+         ovs_assert(packet->source == OFPBUF_MALLOC);
+         upcall->packet = *packet;
+         ofpbuf_use(packet, NULL, 0);
  
          seq_change(dp->queue_seq);
  
-         return 0;
+         error = 0;
      } else {
-         dp->n_lost++;
-         return ENOBUFS;
+         ovsthread_counter_inc(dp->n_lost, 1);
+         error = ENOBUFS;
      }
+     ovs_mutex_unlock(&dp->queue_mutex);
+     return error;
  }
  
  struct dp_netdev_execute_aux {
  };
  
  static void
- dp_netdev_action_output(void *aux_, struct ofpbuf *packet,
-                         const struct flow *flow OVS_UNUSED,
-                         odp_port_t out_port)
+ dp_execute_cb(void *aux_, struct ofpbuf *packet,
+               const struct pkt_metadata *md OVS_UNUSED,
+               const struct nlattr *a, bool may_steal)
+     OVS_NO_THREAD_SAFETY_ANALYSIS
  {
      struct dp_netdev_execute_aux *aux = aux_;
-     struct dp_netdev_port *p = aux->dp->ports[odp_to_u32(out_port)];
-     if (p) {
-         netdev_send(p->netdev, packet);
-     }
- }
+     int type = nl_attr_type(a);
+     struct dp_netdev_port *p;
+     switch ((enum ovs_action_attr)type) {
+     case OVS_ACTION_ATTR_OUTPUT:
+         p = dp_netdev_lookup_port(aux->dp, u32_to_odp(nl_attr_get_u32(a)));
+         if (p) {
+             netdev_send(p->netdev, packet);
+         }
+         break;
  
- static void
- dp_netdev_action_userspace(void *aux_, struct ofpbuf *packet,
-                            const struct flow *flow OVS_UNUSED,
-                            const struct nlattr *a)
- {
-     struct dp_netdev_execute_aux *aux = aux_;
-     const struct nlattr *userdata;
+     case OVS_ACTION_ATTR_USERSPACE: {
+         const struct nlattr *userdata;
+         userdata = nl_attr_find_nested(a, OVS_USERSPACE_ATTR_USERDATA);
  
-     userdata = nl_attr_find_nested(a, OVS_USERSPACE_ATTR_USERDATA);
-     dp_netdev_output_userspace(aux->dp, packet, DPIF_UC_ACTION, aux->key,
-                                userdata);
+         /* Make a copy if we are not allowed to steal the packet's data. */
+         if (!may_steal) {
+             packet = ofpbuf_clone_with_headroom(packet, DP_NETDEV_HEADROOM);
+         }
+         dp_netdev_output_userspace(aux->dp, packet, DPIF_UC_ACTION, aux->key,
+                                    userdata);
+         if (!may_steal) {
+             ofpbuf_uninit(packet);
+         }
+         break;
+     }
+     case OVS_ACTION_ATTR_PUSH_VLAN:
+     case OVS_ACTION_ATTR_POP_VLAN:
+     case OVS_ACTION_ATTR_PUSH_MPLS:
+     case OVS_ACTION_ATTR_POP_MPLS:
+     case OVS_ACTION_ATTR_SET:
+     case OVS_ACTION_ATTR_SAMPLE:
+     case OVS_ACTION_ATTR_UNSPEC:
+     case __OVS_ACTION_ATTR_MAX:
+         OVS_NOT_REACHED();
+     }
  }
  
  static void
  dp_netdev_execute_actions(struct dp_netdev *dp, const struct flow *key,
-                           struct ofpbuf *packet,
+                           struct ofpbuf *packet, struct pkt_metadata *md,
                            const struct nlattr *actions, size_t actions_len)
+     OVS_REQ_RDLOCK(dp->port_rwlock)
  {
      struct dp_netdev_execute_aux aux = {dp, key};
-     struct flow md = *key;   /* Packet metadata, may be modified by actions. */
  
-     odp_execute_actions(&aux, packet, &md, actions, actions_len,
-                         dp_netdev_action_output, dp_netdev_action_userspace);
+     odp_execute_actions(&aux, packet, md, actions, actions_len, dp_execute_cb);
  }
  
-     dpif_netdev_run,                                  \
-     dpif_netdev_wait,                                 \
 +#define DPIF_NETDEV_CLASS_FUNCTIONS                   \
 +    dpif_netdev_enumerate,                            \
 +    dpif_netdev_port_open_type,                               \
 +    dpif_netdev_open,                                 \
 +    dpif_netdev_close,                                        \
 +    dpif_netdev_destroy,                              \
-     dpif_netdev_get_max_ports,                                \
++    NULL,                                     \
++    NULL,                                     \
 +    dpif_netdev_get_stats,                            \
 +    dpif_netdev_port_add,                             \
 +    dpif_netdev_port_del,                             \
 +    dpif_netdev_port_query_by_number,                 \
 +    dpif_netdev_port_query_by_name,                   \
 +    NULL,                       /* port_get_pid */    \
 +    dpif_netdev_port_dump_start,                      \
 +    dpif_netdev_port_dump_next,                               \
 +    dpif_netdev_port_dump_done,                               \
 +    dpif_netdev_port_poll,                            \
 +    dpif_netdev_port_poll_wait,                               \
 +    dpif_netdev_flow_get,                             \
 +    dpif_netdev_flow_put,                             \
 +    dpif_netdev_flow_del,                             \
 +    dpif_netdev_flow_flush,                           \
 +    dpif_netdev_flow_dump_start,                      \
 +    dpif_netdev_flow_dump_next,                               \
 +    dpif_netdev_flow_dump_done,                               \
 +    dpif_netdev_execute,                              \
 +    NULL,                       /* operate */         \
 +    dpif_netdev_recv_set,                             \
 +    dpif_netdev_queue_to_priority,                    \
 +    dpif_netdev_recv,                                 \
 +    dpif_netdev_recv_wait,                            \
 +    dpif_netdev_recv_purge,                           \
 +
  const struct dpif_class dpif_netdev_class = {
      "netdev",
 -    dpif_netdev_enumerate,
 -    dpif_netdev_port_open_type,
 -    dpif_netdev_open,
 -    dpif_netdev_close,
 -    dpif_netdev_destroy,
 -    NULL,                       /* run */
 -    NULL,                       /* wait */
 -    dpif_netdev_get_stats,
 -    dpif_netdev_port_add,
 -    dpif_netdev_port_del,
 -    dpif_netdev_port_query_by_number,
 -    dpif_netdev_port_query_by_name,
 -    NULL,                       /* port_get_pid */
 -    dpif_netdev_port_dump_start,
 -    dpif_netdev_port_dump_next,
 -    dpif_netdev_port_dump_done,
 -    dpif_netdev_port_poll,
 -    dpif_netdev_port_poll_wait,
 -    dpif_netdev_flow_get,
 -    dpif_netdev_flow_put,
 -    dpif_netdev_flow_del,
 -    dpif_netdev_flow_flush,
 -    dpif_netdev_flow_dump_start,
 -    dpif_netdev_flow_dump_next,
 -    dpif_netdev_flow_dump_done,
 -    dpif_netdev_execute,
 -    NULL,                       /* operate */
 -    dpif_netdev_recv_set,
 -    dpif_netdev_queue_to_priority,
 -    dpif_netdev_recv,
 -    dpif_netdev_recv_wait,
 -    dpif_netdev_recv_purge,
 +    DPIF_NETDEV_CLASS_FUNCTIONS
 +};
 +
 +const struct dpif_class dpif_planetlab_class = {
 +    "planetlab",
 +    DPIF_NETDEV_CLASS_FUNCTIONS
  };
  
  static void
@@@ -1466,33 -1863,42 +1879,42 @@@ dpif_dummy_change_port_number(struct un
  {
      struct dp_netdev_port *port;
      struct dp_netdev *dp;
-     int port_no;
+     odp_port_t port_no;
  
+     ovs_mutex_lock(&dp_netdev_mutex);
      dp = shash_find_data(&dp_netdevs, argv[1]);
      if (!dp || !dpif_netdev_class_is_dummy(dp->class)) {
+         ovs_mutex_unlock(&dp_netdev_mutex);
          unixctl_command_reply_error(conn, "unknown datapath or not a dummy");
          return;
      }
+     ovs_refcount_ref(&dp->ref_cnt);
+     ovs_mutex_unlock(&dp_netdev_mutex);
  
+     ovs_rwlock_wrlock(&dp->port_rwlock);
      if (get_port_by_name(dp, argv[2], &port)) {
          unixctl_command_reply_error(conn, "unknown port");
-         return;
+         goto exit;
      }
  
-     port_no = atoi(argv[3]);
-     if (port_no <= 0 || port_no >= MAX_PORTS) {
+     port_no = u32_to_odp(atoi(argv[3]));
+     if (!port_no || port_no == ODPP_NONE) {
          unixctl_command_reply_error(conn, "bad port number");
-         return;
+         goto exit;
      }
-     if (dp->ports[port_no]) {
+     if (dp_netdev_lookup_port(dp, port_no)) {
          unixctl_command_reply_error(conn, "port number already in use");
-         return;
+         goto exit;
      }
-     dp->ports[odp_to_u32(port->port_no)] = NULL;
-     dp->ports[port_no] = port;
-     port->port_no = u32_to_odp(port_no);
+     hmap_remove(&dp->ports, &port->node);
+     port->port_no = port_no;
+     hmap_insert(&dp->ports, &port->node, hash_int(odp_to_u32(port_no), 0));
      seq_change(dp->port_seq);
      unixctl_command_reply(conn, NULL);
+ exit:
+     ovs_rwlock_unlock(&dp->port_rwlock);
+     dp_netdev_unref(dp);
  }
  
  static void
@@@ -1529,4 -1935,3 +1951,4 @@@ dpif_dummy_register(bool override
                               "DP PORT NEW-NUMBER",
                               3, 3, dpif_dummy_change_port_number, NULL);
  }
 +
diff --combined lib/dpif-provider.h
@@@ -144,10 -144,6 +144,6 @@@ struct dpif_class 
      int (*port_query_by_name)(const struct dpif *dpif, const char *devname,
                                struct dpif_port *port);
  
-     /* Returns one greater than the largest port number accepted in flow
-      * actions. */
-     uint32_t (*get_max_ports)(const struct dpif *dpif);
      /* Returns the Netlink PID value to supply in OVS_ACTION_ATTR_USERSPACE
       * actions as the OVS_USERSPACE_ATTR_PID attribute's value, for use in
       * flows whose packets arrived on port 'port_no'.
      int (*flow_dump_done)(const struct dpif *dpif, void *state);
  
      /* Performs the 'execute->actions_len' bytes of actions in
-      * 'execute->actions' on the Ethernet frame specified in 'execute->packet'
-      * taken from the flow specified in the 'execute->key_len' bytes of
-      * 'execute->key'.  ('execute->key' is mostly redundant with
-      * 'execute->packet', but it contains some metadata that cannot be
-      * recovered from 'execute->packet', such as tunnel and in_port.) */
-     int (*execute)(struct dpif *dpif, const struct dpif_execute *execute);
+      * 'execute->actions' on the Ethernet frame in 'execute->packet'
+      * and on the packet metadata in 'execute->md'.
+      * May modify both packet and metadata. */
+     int (*execute)(struct dpif *dpif, struct dpif_execute *execute);
  
      /* Executes each of the 'n_ops' operations in 'ops' on 'dpif', in the order
       * in which they are specified, placing each operation's results in the
       * '*upcall', using 'buf' for storage.  Should only be called if 'recv_set'
       * has been used to enable receiving packets from 'dpif'.
       *
-      * The implementation should point 'upcall->packet' and 'upcall->key' into
-      * data in the caller-provided 'buf'.  If necessary to make room, the
-      * implementation may expand the data in 'buf'.  (This is hardly a great
-      * way to do things but it works out OK for the dpif providers that exist
-      * so far.)
+      * The implementation should point 'upcall->key' and 'upcall->userdata'
+      * (if any) into data in the caller-provided 'buf'.  The implementation may
+      * also use 'buf' for storing the data of 'upcall->packet'.  If necessary
+      * to make room, the implementation may reallocate the data in 'buf'.
+      *
+      * The caller owns the data of 'upcall->packet' and may modify it.  If
+      * packet's headroom is exhausted as it is manipulated, 'upcall->packet'
+      * will be reallocated.  This requires the data of 'upcall->packet' to be
+      * released with ofpbuf_uninit() before 'upcall' is destroyed.  However,
+      * when an error is returned, the 'upcall->packet' may be uninitialized
+      * and should not be released.
       *
       * This function must not block.  If no upcall is pending when it is
       * called, it should return EAGAIN without blocking. */
  
  extern const struct dpif_class dpif_linux_class;
  extern const struct dpif_class dpif_netdev_class;
 +extern const struct dpif_class dpif_planetlab_class;
  
  #ifdef  __cplusplus
  }
diff --combined lib/dpif.c
@@@ -61,7 -61,6 +61,7 @@@ static const struct dpif_class *base_dp
      &dpif_linux_class,
  #endif
      &dpif_netdev_class,
 +    &dpif_planetlab_class,
  };
  
  struct registered_dpif_class {
@@@ -1065,97 -1064,82 +1065,82 @@@ struct dpif_execute_helper_aux 
      int error;
  };
  
+ /* This is called for actions that need the context of the datapath to be
+  * meaningful. */
  static void
- dpif_execute_helper_execute__(void *aux_, struct ofpbuf *packet,
-                               const struct flow *flow,
-                               const struct nlattr *actions, size_t actions_len)
+ dpif_execute_helper_cb(void *aux_, struct ofpbuf *packet,
+                        const struct pkt_metadata *md,
+                        const struct nlattr *action, bool may_steal OVS_UNUSED)
  {
      struct dpif_execute_helper_aux *aux = aux_;
      struct dpif_execute execute;
-     struct odputil_keybuf key_stub;
-     struct ofpbuf key;
-     int error;
-     ofpbuf_use_stub(&key, &key_stub, sizeof key_stub);
-     odp_flow_key_from_flow(&key, flow, flow->in_port.odp_port);
-     execute.key = key.data;
-     execute.key_len = key.size;
-     execute.actions = actions;
-     execute.actions_len = actions_len;
-     execute.packet = packet;
-     execute.needs_help = false;
-     error = aux->dpif->dpif_class->execute(aux->dpif, &execute);
-     if (error) {
-         aux->error = error;
+     int type = nl_attr_type(action);
+     switch ((enum ovs_action_attr)type) {
+     case OVS_ACTION_ATTR_OUTPUT:
+     case OVS_ACTION_ATTR_USERSPACE:
+         execute.actions = action;
+         execute.actions_len = NLA_ALIGN(action->nla_len);
+         execute.packet = packet;
+         execute.md = *md;
+         execute.needs_help = false;
+         aux->error = aux->dpif->dpif_class->execute(aux->dpif, &execute);
+         break;
+     case OVS_ACTION_ATTR_PUSH_VLAN:
+     case OVS_ACTION_ATTR_POP_VLAN:
+     case OVS_ACTION_ATTR_PUSH_MPLS:
+     case OVS_ACTION_ATTR_POP_MPLS:
+     case OVS_ACTION_ATTR_SET:
+     case OVS_ACTION_ATTR_SAMPLE:
+     case OVS_ACTION_ATTR_UNSPEC:
+     case __OVS_ACTION_ATTR_MAX:
+         OVS_NOT_REACHED();
      }
  }
  
- static void
- dpif_execute_helper_output_cb(void *aux, struct ofpbuf *packet,
-                               const struct flow *flow, odp_port_t out_port)
- {
-     uint64_t actions_stub[DIV_ROUND_UP(NL_A_U32_SIZE, 8)];
-     struct ofpbuf actions;
-     ofpbuf_use_stack(&actions, actions_stub, sizeof actions_stub);
-     nl_msg_put_u32(&actions, OVS_ACTION_ATTR_OUTPUT, odp_to_u32(out_port));
-     dpif_execute_helper_execute__(aux, packet, flow,
-                                   actions.data, actions.size);
- }
- static void
- dpif_execute_helper_userspace_cb(void *aux, struct ofpbuf *packet,
-                                  const struct flow *flow,
-                                  const struct nlattr *action)
- {
-     dpif_execute_helper_execute__(aux, packet, flow,
-                                   action, NLA_ALIGN(action->nla_len));
- }
  /* Executes 'execute' by performing most of the actions in userspace and
   * passing the fully constructed packets to 'dpif' for output and userspace
   * actions.
   *
   * This helps with actions that a given 'dpif' doesn't implement directly. */
  static int
- dpif_execute_with_help(struct dpif *dpif, const struct dpif_execute *execute)
+ dpif_execute_with_help(struct dpif *dpif, struct dpif_execute *execute)
  {
-     struct dpif_execute_helper_aux aux;
-     enum odp_key_fitness fit;
-     struct ofpbuf *packet;
-     struct flow flow;
+     struct dpif_execute_helper_aux aux = {dpif, 0};
  
      COVERAGE_INC(dpif_execute_with_help);
  
-     fit = odp_flow_key_to_flow(execute->key, execute->key_len, &flow);
-     if (fit == ODP_FIT_ERROR) {
-         return EINVAL;
-     }
-     aux.dpif = dpif;
-     aux.error = 0;
-     packet = ofpbuf_clone_with_headroom(execute->packet, VLAN_HEADER_LEN);
-     odp_execute_actions(&aux, packet, &flow,
+     odp_execute_actions(&aux, execute->packet, &execute->md,
                          execute->actions, execute->actions_len,
-                         dpif_execute_helper_output_cb,
-                         dpif_execute_helper_userspace_cb);
-     ofpbuf_delete(packet);
+                         dpif_execute_helper_cb);
      return aux.error;
  }
  
- static int
- dpif_execute__(struct dpif *dpif, const struct dpif_execute *execute)
+ /* Causes 'dpif' to perform the 'execute->actions_len' bytes of actions in
+  * 'execute->actions' on the Ethernet frame in 'execute->packet' and on packet
+  * metadata in 'execute->md'.  The implementation is allowed to modify both the
+  * '*execute->packet' and 'execute->md'.
+  *
+  * Some dpif providers do not implement every action.  The Linux kernel
+  * datapath, in particular, does not implement ARP field modification.  If
+  * 'needs_help' is true, the dpif layer executes in userspace all of the
+  * actions that it can, and for OVS_ACTION_ATTR_OUTPUT and
+  * OVS_ACTION_ATTR_USERSPACE actions it passes the packet through to the dpif
+  * implementation.
+  *
+  * This works even if 'execute->actions_len' is too long for a Netlink
+  * attribute.
+  *
+  * Returns 0 if successful, otherwise a positive errno value. */
+ int
+ dpif_execute(struct dpif *dpif, struct dpif_execute *execute)
  {
      int error;
  
      COVERAGE_INC(dpif_execute);
      if (execute->actions_len > 0) {
-         error = (execute->needs_help
+         error = (execute->needs_help || nl_attr_oversized(execute->actions_len)
                   ? dpif_execute_with_help(dpif, execute)
                   : dpif->dpif_class->execute(dpif, execute));
      } else {
      return error;
  }
  
- /* Causes 'dpif' to perform the 'actions_len' bytes of actions in 'actions' on
-  * the Ethernet frame specified in 'packet' taken from the flow specified in
-  * the 'key_len' bytes of 'key'.  ('key' is mostly redundant with 'packet', but
-  * it contains some metadata that cannot be recovered from 'packet', such as
-  * tunnel and in_port.)
-  *
-  * Some dpif providers do not implement every action.  The Linux kernel
-  * datapath, in particular, does not implement ARP field modification.  If
-  * 'needs_help' is true, the dpif layer executes in userspace all of the
-  * actions that it can, and for OVS_ACTION_ATTR_OUTPUT and
-  * OVS_ACTION_ATTR_USERSPACE actions it passes the packet through to the dpif
-  * implementation.
-  *
-  * This works even if 'actions_len' is too long for a Netlink attribute.
-  *
-  * Returns 0 if successful, otherwise a positive errno value. */
- int
- dpif_execute(struct dpif *dpif,
-              const struct nlattr *key, size_t key_len,
-              const struct nlattr *actions, size_t actions_len,
-              const struct ofpbuf *buf,
-              bool needs_help)
- {
-     struct dpif_execute execute;
-     execute.key = key;
-     execute.key_len = key_len;
-     execute.actions = actions;
-     execute.actions_len = actions_len;
-     execute.packet = buf;
-     execute.needs_help = needs_help || nl_attr_oversized(actions_len);
-     return dpif_execute__(dpif, &execute);
- }
  /* Executes each of the 'n_ops' operations in 'ops' on 'dpif', in the order in
   * which they are specified, placing each operation's results in the "output"
   * members documented in comments.
@@@ -1256,7 -1206,7 +1207,7 @@@ dpif_operate(struct dpif *dpif, struct 
                  /* Help the dpif provider to execute one op. */
                  struct dpif_op *op = ops[0];
  
-                 op->error = dpif_execute__(dpif, &op->u.execute);
+                 op->error = dpif_execute(dpif, &op->u.execute);
                  ops++;
                  n_ops--;
              }
                  break;
  
              case DPIF_OP_EXECUTE:
-                 op->error = dpif_execute__(dpif, &op->u.execute);
+                 op->error = dpif_execute(dpif, &op->u.execute);
                  break;
  
              default:
-                 NOT_REACHED();
+                 OVS_NOT_REACHED();
              }
          }
      }
@@@ -1317,10 -1267,15 +1268,15 @@@ dpif_recv_set(struct dpif *dpif, bool e
   * '*upcall', using 'buf' for storage.  Should only be called if
   * dpif_recv_set() has been used to enable receiving packets on 'dpif'.
   *
-  * 'upcall->packet' and 'upcall->key' point into data in the caller-provided
-  * 'buf', so their memory cannot be freed separately from 'buf'.  (This is
-  * hardly a great way to do things but it works out OK for the dpif providers
-  * and clients that exist so far.)
+  * 'upcall->key' and 'upcall->userdata' point into data in the caller-provided
+  * 'buf', so their memory cannot be freed separately from 'buf'.
+  *
+  * The caller owns the data of 'upcall->packet' and may modify it.  If
+  * packet's headroom is exhausted as it is manipulated, 'upcall->packet'
+  * will be reallocated.  This requires the data of 'upcall->packet' to be
+  * released with ofpbuf_uninit() before 'upcall' is destroyed.  However,
+  * when an error is returned, the 'upcall->packet' may be uninitialized
+  * and should not be released.
   *
   * Returns 0 if successful, otherwise a positive errno value.  Returns EAGAIN
   * if no upcall is immediately available. */
@@@ -1332,8 -1287,8 +1288,8 @@@ dpif_recv(struct dpif *dpif, struct dpi
          struct ds flow;
          char *packet;
  
-         packet = ofp_packet_to_string(upcall->packet->data,
-                                       upcall->packet->size);
+         packet = ofp_packet_to_string(upcall->packet.data,
+                                       upcall->packet.size);
  
          ds_init(&flow);
          odp_flow_key_format(upcall->key, upcall->key_len, &flow);
diff --combined lib/netdev-pltap.c
index 46ab2fd,0000000..04e68ba
mode 100644,000000..100644
--- /dev/null
@@@ -1,874 -1,0 +1,873 @@@
- netdev_pltap_rx_recv(struct netdev_rx *rx_, void *buffer, size_t size)
 +/*
 + * Copyright (c) 2012 Giuseppe Lettieri
 + *
 + * 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 <sys/types.h>
 +#include <unistd.h>
 +#include <fcntl.h>
 +#include <arpa/inet.h>
 +#include <sys/ioctl.h>
 +#include <sys/socket.h>
 +#include <net/if.h>
 +#include <net/if_arp.h>
 +#include <linux/if_tun.h>
 +#include <netinet/in.h>
 +#include <errno.h>
 +
 +#include "flow.h"
 +#include "list.h"
 +#include "netdev-provider.h"
 +#include "odp-util.h"
 +#include "ofp-print.h"
 +#include "ofpbuf.h"
 +#include "packets.h"
 +#include "poll-loop.h"
 +#include "shash.h"
 +#include "sset.h"
 +#include "unixctl.h"
 +#include "socket-util.h"
 +#include "vlog.h"
 +#include "tunalloc.h"
 +
 +VLOG_DEFINE_THIS_MODULE(netdev_pltap);
 +
 +/* Protects 'sync_list'. */
 +static struct ovs_mutex sync_list_mutex = OVS_MUTEX_INITIALIZER;
 +
 +static struct list sync_list OVS_GUARDED_BY(sync_list_mutex)
 +    = LIST_INITIALIZER(&sync_list);
 +
 +struct netdev_pltap {
 +    struct netdev up;
 +
 +    /* In sync_list. */
 +    struct list sync_list OVS_GUARDED_BY(sync_list_mutex);
 +
 +    /* Protects all members below. */
 +    struct ovs_mutex mutex OVS_ACQ_AFTER(sync_list_mutex);
 +
 +    char *real_name;
 +    struct netdev_stats stats;
 +    enum netdev_flags new_flags;
 +    enum netdev_flags flags;
 +    int fd;
 +    struct sockaddr_in local_addr;
 +    int local_netmask;
 +    bool valid_local_ip;
 +    bool valid_local_netmask;
 +    bool sync_flags_needed;
 +    unsigned int change_seq;
 +};
 +
 +
 +struct netdev_rx_pltap {
 +    struct netdev_rx up;    
 +    int fd;
 +};
 +
 +static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
 +
 +/* Protects 'pltap_netdevs' */
 +static struct ovs_mutex pltap_netdevs_mutex = OVS_MUTEX_INITIALIZER;
 +static struct shash pltap_netdevs OVS_GUARDED_BY(pltap_netdevs_mutex)
 +    = SHASH_INITIALIZER(&pltap_netdevs);
 +
 +static int netdev_pltap_construct(struct netdev *netdev_);
 +
 +static void netdev_pltap_update_seq(struct netdev_pltap *) 
 +    OVS_REQUIRES(dev->mutex);
 +static int get_flags(struct netdev_pltap *dev, enum netdev_flags *flags)
 +    OVS_REQUIRES(dev->mutex);
 +
 +static bool
 +netdev_pltap_finalized(struct netdev_pltap *dev)
 +    OVS_REQUIRES(dev->mutex)
 +{
 +    return dev->valid_local_ip && dev->valid_local_netmask;
 +}
 +
 +static bool
 +is_netdev_pltap_class(const struct netdev_class *class)
 +{
 +    return class->construct == netdev_pltap_construct;
 +}
 +
 +static struct netdev_pltap *
 +netdev_pltap_cast(const struct netdev *netdev)
 +{
 +    ovs_assert(is_netdev_pltap_class(netdev_get_class(netdev)));
 +    return CONTAINER_OF(netdev, struct netdev_pltap, up);
 +}
 +
 +static struct netdev_rx_pltap*
 +netdev_rx_pltap_cast(const struct netdev_rx *rx)
 +{
 +    ovs_assert(is_netdev_pltap_class(netdev_get_class(rx->netdev)));
 +    return CONTAINER_OF(rx, struct netdev_rx_pltap, up);
 +}
 +
 +static void sync_needed(struct netdev_pltap *dev)
 +    OVS_REQUIRES(dev->mutex, sync_list_mutex)
 +{
 +    if (dev->sync_flags_needed)
 +        return;
 +
 +    dev->sync_flags_needed = true;
 +    list_insert(&sync_list, &dev->sync_list);
 +}
 +
 +static void sync_done(struct netdev_pltap *dev)
 +    OVS_REQUIRES(dev->mutex, sync_list_mutex)
 +{
 +    if (!dev->sync_flags_needed)
 +        return;
 +
 +    (void) list_remove(&dev->sync_list);
 +    dev->sync_flags_needed = false;
 +}
 +
 +static struct netdev *
 +netdev_pltap_alloc(void)
 +{
 +    struct netdev_pltap *netdev = xzalloc(sizeof *netdev);
 +    return &netdev->up;
 +}
 +
 +static int
 +netdev_pltap_construct(struct netdev *netdev_)
 +{
 +    struct netdev_pltap *netdev = netdev_pltap_cast(netdev_);
 +    int error;
 +
 +    ovs_mutex_init(&netdev->mutex);
 +    netdev->real_name = xzalloc(IFNAMSIZ + 1);
 +    memset(&netdev->local_addr, 0, sizeof(netdev->local_addr));
 +    netdev->valid_local_ip = false;
 +    netdev->valid_local_netmask = false;
 +    netdev->flags = 0;
 +    netdev->sync_flags_needed = false;
 +    netdev->change_seq = 1;
 +
 +
 +    /* Open tap device. */
 +    netdev->fd = tun_alloc(IFF_TAP, netdev->real_name);
 +    if (netdev->fd < 0) {
 +        error = errno;
 +        VLOG_WARN("tun_alloc(IFF_TAP, %s) failed: %s",
 +            netdev_get_name(netdev_), ovs_strerror(error));
 +        return error;
 +    }
 +    VLOG_DBG("real_name = %s", netdev->real_name);
 +
 +    /* Make non-blocking. */
 +    error = set_nonblocking(netdev->fd);
 +    if (error) {
 +        return error;
 +    }
 +
 +    ovs_mutex_lock(&pltap_netdevs_mutex);
 +    shash_add(&pltap_netdevs, netdev_get_name(netdev_), netdev);
 +    ovs_mutex_unlock(&pltap_netdevs_mutex);
 +    return 0;
 +}
 +
 +static void
 +netdev_pltap_destruct(struct netdev *netdev_)
 +{
 +    struct netdev_pltap *netdev = netdev_pltap_cast(netdev_);
 +
 +    ovs_mutex_lock(&pltap_netdevs_mutex);
 +    if (netdev->fd != -1)
 +      close(netdev->fd);
 +
 +    if (netdev->sync_flags_needed) {
 +        ovs_mutex_lock(&sync_list_mutex);
 +        (void) list_remove(&netdev->sync_list);
 +        ovs_mutex_unlock(&sync_list_mutex);
 +    }
 +
 +    shash_find_and_delete(&pltap_netdevs,
 +                          netdev_get_name(netdev_));
 +    ovs_mutex_unlock(&pltap_netdevs_mutex);
 +    ovs_mutex_destroy(&netdev->mutex);
 +}
 +
 +static void
 +netdev_pltap_dealloc(struct netdev *netdev_)
 +{
 +    struct netdev_pltap *netdev = netdev_pltap_cast(netdev_);
 +    free(netdev);
 +}
 +
 +static int netdev_pltap_up(struct netdev_pltap *dev) OVS_REQUIRES(dev->mutex);
 +
 +static struct netdev_rx *
 +netdev_pltap_rx_alloc(void)
 +{
 +    struct netdev_rx_pltap *rx = xzalloc(sizeof *rx);
 +    return &rx->up;
 +}
 +
 +static int
 +netdev_pltap_rx_construct(struct netdev_rx *rx_)
 +{
 +    struct netdev_rx_pltap *rx = netdev_rx_pltap_cast(rx_);
 +    struct netdev *netdev_ = rx->up.netdev;
 +    struct netdev_pltap *netdev =
 +        netdev_pltap_cast(netdev_);
 +    int error = 0;
 +
 +    ovs_mutex_lock(&netdev->mutex);
 +    rx->fd = netdev->fd;
 +    if (!netdev_pltap_finalized(netdev))
 +        goto out;
 +    error = netdev_pltap_up(netdev);
 +    if (error) {
 +        goto out;
 +    }
 +out:
 +    ovs_mutex_unlock(&netdev->mutex);
 +    return error;
 +}
 +
 +static void
 +netdev_pltap_rx_destruct(struct netdev_rx *rx_ OVS_UNUSED)
 +{
 +}
 +
 +static void
 +netdev_pltap_rx_dealloc(struct netdev_rx *rx_)
 +{
 +    struct netdev_rx_pltap *rx = netdev_rx_pltap_cast(rx_);
 +
 +    free(rx);
 +}
 +
 +static int vsys_transaction(const char *script,
 +      const char **preply, char *format, ...)
 +{
 +    char *msg = NULL, *reply = NULL;
 +    const size_t reply_size = 1024;
 +    int ifd = -1, ofd = -1, maxfd;
 +    size_t bytes_to_write, bytes_to_read,
 +           bytes_written = 0, bytes_read = 0;
 +    int error = 0;
 +    char *ofname = NULL, *ifname = NULL;
 +    va_list args;
 +
 +    va_start(args, format);
 +    msg = xvasprintf(format, args);
 +    va_end(args);
 +    reply = (char*)xmalloc(reply_size);
 +    if (!msg || !reply) {
 +      VLOG_ERR("Out of memory");
 +      error = ENOMEM;
 +      goto cleanup;
 +    }
 +
 +    ofname = xasprintf("/vsys/%s.out", script);
 +    ifname = xasprintf("/vsys/%s.in", script);
 +    if (!ofname || !ifname) {
 +      VLOG_ERR("Out of memory");
 +      error = ENOMEM;
 +      goto cleanup;
 +    }
 +
 +    ofd = open(ofname, O_RDONLY | O_NONBLOCK);
 +    if (ofd < 0) {
 +        VLOG_ERR("Cannot open %s: %s", ofname, ovs_strerror(errno));
 +      error = errno;
 +      goto cleanup;
 +    }
 +    ifd = open(ifname, O_WRONLY | O_NONBLOCK);
 +    if (ifd < 0) {
 +        VLOG_ERR("Cannot open %s: %s", ifname, ovs_strerror(errno));
 +      error = errno;
 +      goto cleanup;
 +    }
 +    maxfd = (ifd < ofd) ? ofd : ifd;
 +
 +    bytes_to_write = strlen(msg);
 +    bytes_to_read = reply_size;
 +    while (bytes_to_write || bytes_to_read) {
 +        fd_set readset, writeset, errorset;
 +
 +      FD_ZERO(&readset);
 +      FD_ZERO(&writeset);
 +      FD_ZERO(&errorset);
 +      if (bytes_to_write) {
 +          FD_SET(ifd, &writeset);
 +          FD_SET(ifd, &errorset);
 +      }
 +      FD_SET(ofd, &readset);
 +      FD_SET(ofd, &errorset);
 +      if (select(maxfd + 1, &readset, &writeset, &errorset, NULL) < 0) {
 +          if (errno == EINTR)
 +              continue;
 +          VLOG_ERR("selec error: %s", ovs_strerror(errno));
 +          error = errno;
 +          goto cleanup;
 +      }
 +      if (FD_ISSET(ifd, &errorset) || FD_ISSET(ofd, &errorset)) {
 +          VLOG_ERR("error condition on ifd or ofd");
 +          goto cleanup;
 +      }
 +      if (FD_ISSET(ifd, &writeset)) {
 +          ssize_t n = write(ifd, msg + bytes_written, bytes_to_write);    
 +          if (n < 0) {
 +              if (errno != EAGAIN && errno != EINTR) {
 +                  VLOG_ERR("write on %s: %s", ifname, ovs_strerror(errno));
 +                  error = errno;
 +                  goto cleanup;
 +              }
 +            } else {
 +              bytes_written += n;
 +              bytes_to_write -= n;
 +              if (bytes_to_write == 0)
 +                  close(ifd);
 +          }
 +      }
 +      if (FD_ISSET(ofd, &readset)) {
 +          ssize_t n = read(ofd, reply + bytes_read, bytes_to_read);    
 +          if (n < 0) {
 +              if (errno != EAGAIN && errno != EINTR) {
 +                  VLOG_ERR("read on %s: %s", ofname, ovs_strerror(errno));
 +                  error = errno;
 +                  goto cleanup;
 +              }
 +            } else if (n == 0) {
 +              bytes_to_read = 0;
 +            } else {
 +              bytes_read += n;
 +              bytes_to_read -= n;
 +          }
 +      }
 +    }
 +    if (bytes_read) {
 +      reply[bytes_read] = '\0';
 +      if (preply) {
 +              *preply = reply;
 +              reply = NULL; /* prevent freeing the reply msg */
 +      } else {
 +              VLOG_ERR("%s returned: %s", script, reply);
 +      }
 +      error = EAGAIN;
 +      goto cleanup;
 +    }
 +
 +cleanup:
 +    free(msg);
 +    free(reply);
 +    free(ofname);
 +    free(ifname);
 +    close(ifd);
 +    close(ofd);
 +    return error;
 +}
 +
 +static int
 +netdev_pltap_up(struct netdev_pltap *dev)
 +    OVS_REQUIRES(dev->mutex)
 +{
 +    if (!netdev_pltap_finalized(dev)) {
 +        return 0;
 +    }
 +    
 +    return vsys_transaction("vif_up", NULL, "%s\n"IP_FMT"\n%d\n",
 +             dev->real_name,
 +             IP_ARGS(dev->local_addr.sin_addr.s_addr),
 +             dev->local_netmask);
 +}
 +
 +static int
 +netdev_pltap_down(struct netdev_pltap *dev)
 +    OVS_REQUIRES(dev->mutex)
 +{
 +    if (!netdev_pltap_finalized(dev)) {
 +        return 0;
 +    }
 +    
 +    return vsys_transaction("vif_down", NULL, "%s\n", dev->real_name);
 +}
 +
 +static int
 +netdev_pltap_promisc(struct netdev_pltap *dev, bool promisc)
 +    OVS_REQUIRES(dev-mutex)
 +{
 +    if (!netdev_pltap_finalized(dev)) {
 +        return 0;
 +    }
 +
 +    return vsys_transaction("promisc", NULL, "%s\n%s",
 +             dev->real_name,
 +             (promisc ? "" : "-\n"));
 +}
 +
 +static void
 +netdev_pltap_sync_flags(struct netdev_pltap *dev)
 +    OVS_REQUIRES(sync_list_mutex)
 +{
 +
 +    ovs_mutex_lock(&dev->mutex);
 +
 +    if (dev->fd < 0 || !netdev_pltap_finalized(dev)) {
 +      goto out;
 +    }
 +    
 +    VLOG_DBG("sync_flags(%s): current: %s %s  target: %s %s",
 +        dev->real_name,
 +      (dev->flags & NETDEV_UP ? "UP" : "-"),
 +      (dev->flags & NETDEV_PROMISC ? "PROMISC" : "-"),
 +      (dev->new_flags & NETDEV_UP ? "UP" : "-"),
 +      (dev->new_flags & NETDEV_PROMISC ? "PROMISC" : "-"));
 +
 +    if ((dev->new_flags & NETDEV_UP) && !(dev->flags & NETDEV_UP)) {
 +        (void) netdev_pltap_up(dev);
 +    } else if (!(dev->new_flags & NETDEV_UP) && (dev->flags & NETDEV_UP)) {
 +        (void) netdev_pltap_down(dev);
 +    }
 +
 +    if ((dev->new_flags & NETDEV_PROMISC) ^ (dev->flags & NETDEV_PROMISC)) {
 +        (void) netdev_pltap_promisc(dev, dev->new_flags & NETDEV_PROMISC);
 +    }
 +
 +    netdev_pltap_update_seq(dev);
 +
 +out:
 +    sync_done(dev);
 +    ovs_mutex_unlock(&dev->mutex);
 +}
 +
 +
 +static int
 +netdev_pltap_get_config(const struct netdev *dev_, struct smap *args)
 +{
 +    struct netdev_pltap *netdev = netdev_pltap_cast(dev_);
 +
 +    ovs_mutex_lock(&netdev->mutex);
 +    if (netdev->valid_local_ip)
 +      smap_add_format(args, "local_ip", IP_FMT,
 +            IP_ARGS(netdev->local_addr.sin_addr.s_addr));
 +    if (netdev->valid_local_netmask)
 +        smap_add_format(args, "local_netmask", "%"PRIu32,
 +            ntohs(netdev->local_netmask));
 +    ovs_mutex_unlock(&netdev->mutex);
 +    return 0;
 +}
 +
 +static int
 +netdev_pltap_set_config(struct netdev *dev_, const struct smap *args)
 +{
 +    struct netdev_pltap *netdev = netdev_pltap_cast(dev_);
 +    struct shash_node *node;
 +
 +    ovs_mutex_lock(&sync_list_mutex);
 +    ovs_mutex_lock(&netdev->mutex);
 +    VLOG_DBG("pltap_set_config(%s)", netdev_get_name(dev_));
 +    SMAP_FOR_EACH(node, args) {
 +        VLOG_DBG("arg: %s->%s", node->name, (char*)node->data);
 +      if (!strcmp(node->name, "local_ip")) {
 +          struct in_addr addr;
 +          if (lookup_ip(node->data, &addr)) {
 +              VLOG_WARN("%s: bad 'local_ip'", node->name);
 +          } else {
 +              netdev->local_addr.sin_addr = addr;
 +              netdev->valid_local_ip = true;
 +          }
 +      } else if (!strcmp(node->name, "local_netmask")) {
 +          netdev->local_netmask = atoi(node->data);
 +          // XXX check valididy
 +          netdev->valid_local_netmask = true;
 +      } else {
 +          VLOG_WARN("%s: unknown argument '%s'", 
 +              netdev_get_name(dev_), node->name);
 +      }
 +    }
 +    if (netdev_pltap_finalized(netdev)) {
 +        netdev->new_flags |= NETDEV_UP;
 +        sync_needed(netdev);
 +    }
 +    ovs_mutex_unlock(&netdev->mutex);
 +    ovs_mutex_unlock(&sync_list_mutex);
 +    return 0;
 +}
 +
 +static int
-       { .iov_base = buffer, .iov_len = size }
++netdev_pltap_rx_recv(struct netdev_rx *rx_, struct ofpbuf *buffer)
 +{
++    size_t size = ofpbuf_tailroom(buffer);
 +    struct netdev_rx_pltap *rx = netdev_rx_pltap_cast(rx_);
 +    struct tun_pi pi;
 +    struct iovec iov[2] = {
 +        { .iov_base = &pi, .iov_len = sizeof(pi) },
-     netdev_pltap_change_seq,
++      { .iov_base = buffer->data, .iov_len = size }
 +    };
 +    for (;;) {
 +        ssize_t retval;
 +        retval = readv(rx->fd, iov, 2);
 +        if (retval >= 0) {
 +            if (retval <= size) {
 +              return retval;
 +          } else {
 +              return -EMSGSIZE;
 +          }
 +        } else if (errno != EINTR) {
 +            if (errno != EAGAIN) {
 +                VLOG_WARN_RL(&rl, "error receiveing Ethernet packet on %s: %s",
 +                    netdev_rx_get_name(rx_), ovs_strerror(errno));
 +            }
 +            return -errno;
 +        }
 +    }
 +}
 +
 +static void
 +netdev_pltap_rx_wait(struct netdev_rx *rx_)
 +{
 +    struct netdev_rx_pltap *rx = netdev_rx_pltap_cast(rx_);
 +    struct netdev_pltap *netdev =
 +        netdev_pltap_cast(rx->up.netdev);
 +    if (rx->fd >= 0 && netdev_pltap_finalized(netdev)) {
 +        poll_fd_wait(rx->fd, POLLIN);
 +    }
 +}
 +
 +static int
 +netdev_pltap_send(struct netdev *netdev_, const void *buffer, size_t size)
 +{
 +    struct netdev_pltap *dev = 
 +      netdev_pltap_cast(netdev_);
 +    struct tun_pi pi = { 0, 0x86 };
 +    struct iovec iov[2] = {
 +        { .iov_base = &pi, .iov_len = sizeof(pi) },
 +      { .iov_base = (char*) buffer, .iov_len = size }
 +    };
 +    if (dev->fd < 0)
 +        return EAGAIN;
 +    for (;;) {
 +        ssize_t retval;
 +        retval = writev(dev->fd, iov, 2);
 +        if (retval >= 0) {
 +          if (retval != size + 4) {
 +              VLOG_WARN_RL(&rl, "sent partial Ethernet packet (%"PRIdSIZE" bytes of %"PRIuSIZE") on %s",
 +                           retval, size + 4, netdev_get_name(netdev_));
 +          }
 +            return 0;
 +        } else if (errno != EINTR) {
 +            if (errno != EAGAIN) {
 +                VLOG_WARN_RL(&rl, "error sending Ethernet packet on %s: %s",
 +                    netdev_get_name(netdev_), ovs_strerror(errno));
 +            }
 +            return errno;
 +        }
 +    }
 +}
 +
 +static void
 +netdev_pltap_send_wait(struct netdev *netdev_)
 +{
 +    struct netdev_pltap *dev = 
 +      netdev_pltap_cast(netdev_);
 +    if (dev->fd >= 0 && netdev_pltap_finalized(dev)) {
 +        poll_fd_wait(dev->fd, POLLOUT);
 +    }
 +}
 +
 +static int
 +netdev_pltap_rx_drain(struct netdev_rx *rx_)
 +{
 +    struct netdev_rx_pltap *rx = netdev_rx_pltap_cast(rx_);
 +    char buffer[128];
 +    int error;
 +
 +    if (rx->fd < 0)
 +      return EAGAIN;
 +    for (;;) {
 +      error = recv(rx->fd, buffer, 128, MSG_TRUNC);
 +      if (error) {
 +            if (error == -EAGAIN)
 +              break;
 +            else if (error != -EMSGSIZE)
 +              return error;
 +      }
 +    }
 +    return 0;
 +}
 +
 +static int
 +netdev_pltap_set_etheraddr(struct netdev *netdevi OVS_UNUSED,
 +                           const uint8_t mac[ETH_ADDR_LEN] OVS_UNUSED)
 +{
 +    return ENOTSUP;
 +}
 +
 +
 +// XXX from netdev-linux.c
 +static int
 +get_etheraddr(struct netdev_pltap *dev, uint8_t ea[ETH_ADDR_LEN])
 +    OVS_REQUIRES(dev->mutex)
 +{
 +    struct ifreq ifr;
 +    int hwaddr_family;
 +    int error;
 +
 +    memset(&ifr, 0, sizeof ifr);
 +    ovs_strzcpy(ifr.ifr_name, dev->real_name, sizeof ifr.ifr_name);
 +    error = af_inet_ifreq_ioctl(dev->real_name, &ifr,
 +        SIOCGIFHWADDR, "SIOCGIFHWADDR");
 +    if (error) {
 +        return error;
 +    }
 +    hwaddr_family = ifr.ifr_hwaddr.sa_family;
 +    if (hwaddr_family != AF_UNSPEC && hwaddr_family != ARPHRD_ETHER) {
 +        VLOG_WARN("%s device has unknown hardware address family %d",
 +                  dev->real_name, hwaddr_family);
 +    }
 +    memcpy(ea, ifr.ifr_hwaddr.sa_data, ETH_ADDR_LEN);
 +    return 0;
 +}
 +
 +static int
 +get_flags(struct netdev_pltap *dev, enum netdev_flags *flags)
 +    OVS_REQUIRES(dev->mutex)
 +{
 +    struct ifreq ifr;
 +    int error;
 +
 +    error = af_inet_ifreq_ioctl(dev->real_name, &ifr,
 +        SIOCGIFFLAGS, "SIOCGIFFLAGS");
 +    if (error) {
 +        return error;
 +    }
 +    *flags = 0;
 +    if (ifr.ifr_flags & IFF_UP)
 +      *flags |= NETDEV_UP;
 +    if (ifr.ifr_flags & IFF_PROMISC)
 +        *flags |= NETDEV_PROMISC;
 +    return 0;
 +}
 +
 +static int
 +netdev_pltap_get_etheraddr(const struct netdev *netdev,
 +                           uint8_t mac[ETH_ADDR_LEN])
 +{
 +    struct netdev_pltap *dev = 
 +      netdev_pltap_cast(netdev);
 +    int error = 0;
 +
 +    ovs_mutex_lock(&dev->mutex);
 +    if (dev->fd < 0) {
 +        error = EAGAIN;
 +        goto out;
 +    }
 +    error = get_etheraddr(dev, mac);
 +
 +out:
 +    ovs_mutex_unlock(&dev->mutex);
 +    return error;
 +}
 +
 +
 +// XXX can we read stats in planetlab?
 +static int
 +netdev_pltap_get_stats(const struct netdev *netdev OVS_UNUSED, struct netdev_stats *stats OVS_UNUSED)
 +{
 +    return ENOTSUP;
 +}
 +
 +static int
 +netdev_pltap_set_stats(struct netdev *netdev OVS_UNUSED, const struct netdev_stats *stats OVS_UNUSED)
 +{
 +    return ENOTSUP;
 +}
 +
 +
 +static int
 +netdev_pltap_update_flags(struct netdev *dev_,
 +                          enum netdev_flags off, enum netdev_flags on,
 +                          enum netdev_flags *old_flagsp)
 +{
 +    struct netdev_pltap *netdev =
 +        netdev_pltap_cast(dev_);
 +    int error = 0;
 +
 +    ovs_mutex_lock(&sync_list_mutex);
 +    ovs_mutex_lock(&netdev->mutex);
 +    if ((off | on) & ~(NETDEV_UP | NETDEV_PROMISC)) {
 +        error = EINVAL;
 +        goto out;
 +    }
 +
 +    if (netdev_pltap_finalized(netdev)) {
 +        error = get_flags(netdev, &netdev->flags);
 +    }
 +    *old_flagsp = netdev->flags;
 +    netdev->new_flags |= on;
 +    netdev->new_flags &= ~off;
 +    if (netdev->flags != netdev->new_flags) {
 +      /* we cannot sync here, since we may be in a signal handler */
 +        sync_needed(netdev);
 +    }
 +
 +out:
 +    ovs_mutex_unlock(&netdev->mutex);
 +    ovs_mutex_unlock(&sync_list_mutex);
 +    return error;
 +}
 +
 +static unsigned int
 +netdev_pltap_change_seq(const struct netdev *netdev)
 +{
 +    struct netdev_pltap *dev =
 +        netdev_pltap_cast(netdev);
 +    unsigned int change_seq;
 +
 +    ovs_mutex_lock(&dev->mutex);
 +    change_seq = dev->change_seq;
 +    ovs_mutex_unlock(&dev->mutex);
 +
 +    return change_seq;
 +}
 +\f
 +/* Helper functions. */
 +
 +static void
 +netdev_pltap_update_seq(struct netdev_pltap *dev)
 +    OVS_REQUIRES(dev->mutex)
 +{
 +    dev->change_seq++;
 +    if (!dev->change_seq) {
 +        dev->change_seq++;
 +    }
 +}
 +
 +static void
 +netdev_pltap_get_real_name(struct unixctl_conn *conn,
 +                     int argc OVS_UNUSED, const char *argv[], void *aux OVS_UNUSED)
 +{
 +    struct netdev_pltap *pltap_dev;
 +
 +    ovs_mutex_lock(&pltap_netdevs_mutex);
 +    pltap_dev = shash_find_data(&pltap_netdevs, argv[1]);
 +    if (!pltap_dev) {
 +        unixctl_command_reply_error(conn, "no such pltap netdev");
 +        goto out;
 +    }
 +    if (pltap_dev->fd < 0) {
 +      unixctl_command_reply_error(conn, "no real device attached");
 +        goto out;     
 +    }
 +
 +    unixctl_command_reply(conn, pltap_dev->real_name);
 +
 +out:
 +    ovs_mutex_unlock(&pltap_netdevs_mutex);
 +}
 +
 +static int
 +netdev_pltap_init(void)
 +{
 +    unixctl_command_register("netdev-pltap/get-tapname", "port",
 +                             1, 1, netdev_pltap_get_real_name, NULL);
 +    return 0;
 +}
 +
 +static void
 +netdev_pltap_run(void)
 +{
 +    struct netdev_pltap *iter, *next;
 +    ovs_mutex_lock(&sync_list_mutex);
 +    LIST_FOR_EACH_SAFE(iter, next, sync_list, &sync_list) {
 +        netdev_pltap_sync_flags(iter);
 +    }
 +    ovs_mutex_unlock(&sync_list_mutex);
 +}
 +
 +static void
 +netdev_pltap_wait(void)
 +{
 +    ovs_mutex_lock(&sync_list_mutex);
 +    if (!list_is_empty(&sync_list)) {
 +        VLOG_DBG("netdev_pltap: scheduling sync");
 +        poll_immediate_wake();
 +    }
 +    ovs_mutex_unlock(&sync_list_mutex);
 +}
 +
 +const struct netdev_class netdev_pltap_class = {
 +    "pltap",
 +    netdev_pltap_init,
 +    netdev_pltap_run,  
 +    netdev_pltap_wait,            
 +
 +    netdev_pltap_alloc,
 +    netdev_pltap_construct,
 +    netdev_pltap_destruct,
 +    netdev_pltap_dealloc,
 +    netdev_pltap_get_config,
 +    netdev_pltap_set_config, 
 +    NULL,                                 /* get_tunnel_config */
 +
 +    netdev_pltap_send, 
 +    netdev_pltap_send_wait,  
 +
 +    netdev_pltap_set_etheraddr,
 +    netdev_pltap_get_etheraddr,
 +    NULL,                                 /* get_mtu */
 +    NULL,                                 /* set_mtu */
 +    NULL,                       /* get_ifindex */
 +    NULL,                                 /* get_carrier */
 +    NULL,                       /* get_carrier_resets */
 +    NULL,                       /* get_miimon */
 +    netdev_pltap_get_stats,
 +    netdev_pltap_set_stats,
 +
 +    NULL,                       /* get_features */
 +    NULL,                       /* set_advertisements */
 +
 +    NULL,                       /* set_policing */
 +    NULL,                       /* get_qos_types */
 +    NULL,                       /* get_qos_capabilities */
 +    NULL,                       /* get_qos */
 +    NULL,                       /* set_qos */
 +    NULL,                       /* get_queue */
 +    NULL,                       /* set_queue */
 +    NULL,                       /* delete_queue */
 +    NULL,                       /* get_queue_stats */
 +    NULL,                       /* queue_dump_start */
 +    NULL,                       /* queue_dump_next */
 +    NULL,                       /* queue_dump_done */
 +    NULL,                       /* dump_queue_stats */
 +
 +    NULL,                       /* get_in4 */
 +    NULL,                       /* set_in4 */
 +    NULL,                       /* get_in6 */
 +    NULL,                       /* add_router */
 +    NULL,                       /* get_next_hop */
 +    NULL,                       /* get_drv_info */
 +    NULL,                       /* arp_lookup */
 +
 +    netdev_pltap_update_flags,
 +
 +    netdev_pltap_rx_alloc,
 +    netdev_pltap_rx_construct,
 +    netdev_pltap_rx_destruct,
 +    netdev_pltap_rx_dealloc,
 +    netdev_pltap_rx_recv,
 +    netdev_pltap_rx_wait,
 +    netdev_pltap_rx_drain,
 +};
diff --combined lib/netdev-provider.h
@@@ -148,7 -148,18 +148,18 @@@ struct netdev *netdev_rx_get_netdev(con
   * Each "dealloc" function frees raw memory that was allocated by the the
   * "alloc" function.  The memory's base and derived members might not have ever
   * been initialized (but if "construct" returned successfully, then it has been
-  * "destruct"ed already).  The "dealloc" function is not allowed to fail. */
+  * "destruct"ed already).  The "dealloc" function is not allowed to fail.
+  *
+  *
+  * Device Change Notification
+  * ==========================
+  *
+  * Minimally, implementations are required to report changes to netdev flags,
+  * features, ethernet address or carrier through connectivity_seq. Changes to
+  * other properties are allowed to cause notification through this interface,
+  * although implementations should try to avoid this. connectivity_seq_get()
+  * can be used to acquire a reference to the struct seq. The interface is
+  * described in detail in seq.h. */
  struct netdev_class {
      /* Type of netdevs in this class, e.g. "system", "tap", "gre", etc.
       *
      int (*update_flags)(struct netdev *netdev, enum netdev_flags off,
                          enum netdev_flags on, enum netdev_flags *old_flags);
  
-     /* Returns a sequence number which indicates changes in one of 'netdev''s
-      * properties.  The returned sequence number must be nonzero so that
-      * callers have a value which they may use as a reset when tracking
-      * 'netdev'.
-      *
-      * Minimally, the returned sequence number is required to change whenever
-      * 'netdev''s flags, features, ethernet address, or carrier changes.  The
-      * returned sequence number is allowed to change even when 'netdev' doesn't
-      * change, although implementations should try to avoid this. */
-     unsigned int (*change_seq)(const struct netdev *netdev);
  /* ## ------------------- ## */
  /* ## netdev_rx Functions ## */
  /* ## ------------------- ## */
      void (*rx_destruct)(struct netdev_rx *);
      void (*rx_dealloc)(struct netdev_rx *);
  
-     /* Attempts to receive a packet from 'rx' into the 'size' bytes in
-      * 'buffer'.  If successful, returns the number of bytes in the received
-      * packet, otherwise a negative errno value.  Returns -EAGAIN immediately
-      * if no packet is ready to be received.
+     /* Attempts to receive a packet from 'rx' into the tailroom of 'buffer',
+      * which should initially be empty.  If successful, returns 0 and
+      * increments 'buffer->size' by the number of bytes in the received packet,
+      * otherwise a positive errno value.  Returns EAGAIN immediately if no
+      * packet is ready to be received.
+      *
+      * Must return EMSGSIZE, and discard the packet, if the received packet
+      * is longer than 'ofpbuf_tailroom(buffer)'.
+      *
+      * Implementations may make use of VLAN_HEADER_LEN bytes of tailroom to
+      * add a VLAN header which is obtained out-of-band to the packet. If
+      * this occurs then VLAN_HEADER_LEN bytes of tailroom will no longer be
+      * available for the packet, otherwise it may be used for the packet
+      * itself.
       *
-      * Must return -EMSGSIZE, and discard the packet, if the received packet
-      * is longer than 'size' bytes.
+      * It is advised that the tailroom of 'buffer' should be
+      * VLAN_HEADER_LEN bytes longer than the MTU to allow space for an
+      * out-of-band VLAN header to be added to the packet.
       *
-      * Specify NULL if this */
-     int (*rx_recv)(struct netdev_rx *rx, void *buffer, size_t size);
+      * This function may be set to null if it would always return EOPNOTSUPP
+      * anyhow. */
+     int (*rx_recv)(struct netdev_rx *rx, struct ofpbuf *buffer);
  
      /* Registers with the poll loop to wake up from the next call to
       * poll_block() when a packet is ready to be received with netdev_rx_recv()
@@@ -664,9 -676,6 +676,9 @@@ extern const struct netdev_class netdev
  extern const struct netdev_class netdev_bsd_class;
  #endif
  
 +extern const struct netdev_class netdev_tunnel_class;
 +extern const struct netdev_class netdev_pltap_class;
 +
  #ifdef  __cplusplus
  }
  #endif
diff --combined lib/netdev-tunnel.c
index a985ce9,0000000..b197e53
mode 100644,000000..100644
--- /dev/null
@@@ -1,631 -1,0 +1,630 @@@
- netdev_tunnel_rx_recv(struct netdev_rx *rx_, void *buffer, size_t size)
 +/*
 + * Copyright (c) 2010, 2011, 2012 Nicira Networks.
 + *
 + * 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 <unistd.h>
 +#include <sys/socket.h>
 +#include <netinet/in.h>
 +#include <arpa/inet.h>
 +#include <errno.h>
 +
 +#include "flow.h"
 +#include "list.h"
 +#include "netdev-provider.h"
 +#include "odp-util.h"
 +#include "ofp-print.h"
 +#include "ofpbuf.h"
 +#include "packets.h"
 +#include "poll-loop.h"
 +#include "shash.h"
 +#include "sset.h"
 +#include "unixctl.h"
 +#include "socket-util.h"
 +#include "vlog.h"
 +
 +VLOG_DEFINE_THIS_MODULE(netdev_tunnel);
 +
 +struct netdev_tunnel {
 +    struct netdev up;
 +
 +    /* Protects all members below. */
 +    struct ovs_mutex mutex;
 +
 +    uint8_t hwaddr[ETH_ADDR_LEN];
 +    struct netdev_stats stats;
 +    enum netdev_flags flags;
 +    int sockfd;
 +    struct sockaddr_in local_addr;
 +    struct sockaddr_in remote_addr;
 +    bool valid_remote_ip;
 +    bool valid_remote_port;
 +    bool connected;
 +    unsigned int change_seq;
 +};
 +
 +struct netdev_rx_tunnel {
 +    struct netdev_rx up;
 +    int fd;
 +};
 +
 +static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
 +
 +static struct ovs_mutex tunnel_netdevs_mutex = OVS_MUTEX_INITIALIZER;
 +static struct shash tunnel_netdevs OVS_GUARDED_BY(tunnel_netdevs_mutex)
 +    = SHASH_INITIALIZER(&tunnel_netdevs);
 +
 +static int netdev_tunnel_construct(struct netdev *netdevp_);
 +static void netdev_tunnel_update_seq(struct netdev_tunnel *);
 +
 +static bool
 +is_netdev_tunnel_class(const struct netdev_class *class)
 +{
 +    return class->construct == netdev_tunnel_construct;
 +}
 +
 +static struct netdev_tunnel *
 +netdev_tunnel_cast(const struct netdev *netdev)
 +{
 +    ovs_assert(is_netdev_tunnel_class(netdev_get_class(netdev)));
 +    return CONTAINER_OF(netdev, struct netdev_tunnel, up);
 +}
 +
 +static struct netdev_rx_tunnel *
 +netdev_rx_tunnel_cast(const struct netdev_rx *rx)
 +{
 +    ovs_assert(is_netdev_tunnel_class(netdev_get_class(rx->netdev)));
 +    return CONTAINER_OF(rx, struct netdev_rx_tunnel, up);
 +}
 +
 +static struct netdev *
 +netdev_tunnel_alloc(void)
 +{
 +    struct netdev_tunnel *netdev = xzalloc(sizeof *netdev);
 +    return &netdev->up;
 +}
 +
 +static int
 +netdev_tunnel_construct(struct netdev *netdev_)
 +{
 +    static atomic_uint next_n = ATOMIC_VAR_INIT(0);
 +    struct netdev_tunnel *netdev = netdev_tunnel_cast(netdev_);
 +    unsigned int n;
 +
 +    atomic_add(&next_n, 1, &n);
 +
 +    ovs_mutex_init(&netdev->mutex);
 +    netdev->hwaddr[0] = 0xfe;
 +    netdev->hwaddr[1] = 0xff;
 +    netdev->hwaddr[2] = 0xff;
 +    netdev->hwaddr[3] = n >> 16;
 +    netdev->hwaddr[4] = n >> 8;
 +    netdev->hwaddr[5] = n;
 +    netdev->flags = 0;
 +    netdev->change_seq = 1;
 +    memset(&netdev->remote_addr, 0, sizeof(netdev->remote_addr));
 +    netdev->valid_remote_ip = false;
 +    netdev->valid_remote_port = false;
 +    netdev->connected = false;
 +
 +
 +    netdev->sockfd = inet_open_passive(SOCK_DGRAM, "", 0, &netdev->local_addr, 0);
 +    if (netdev->sockfd < 0) {
 +      return netdev->sockfd;
 +    }
 +
 +
 +    shash_add(&tunnel_netdevs, netdev_get_name(netdev_), netdev);
 +
 +    n++;
 +
 +    VLOG_DBG("tunnel_create: name=%s, fd=%d, port=%d",
 +        netdev_get_name(netdev_), netdev->sockfd, netdev->local_addr.sin_port);
 +
 +    return 0;
 +
 +}
 +
 +static void
 +netdev_tunnel_destruct(struct netdev *netdev_)
 +{
 +    struct netdev_tunnel *netdev = netdev_tunnel_cast(netdev_);
 +
 +    ovs_mutex_lock(&tunnel_netdevs_mutex);
 +
 +    if (netdev->sockfd != -1)
 +      close(netdev->sockfd);
 +
 +    shash_find_and_delete(&tunnel_netdevs,
 +                          netdev_get_name(netdev_));
 +
 +    ovs_mutex_destroy(&netdev->mutex);
 +    ovs_mutex_unlock(&tunnel_netdevs_mutex);
 +}
 +
 +static void
 +netdev_tunnel_dealloc(struct netdev *netdev_)
 +{
 +    struct netdev_tunnel *netdev = netdev_tunnel_cast(netdev_);
 +    free(netdev);
 +}
 +
 +static int
 +netdev_tunnel_get_config(const struct netdev *dev_, struct smap *args)
 +{
 +    struct netdev_tunnel *netdev = netdev_tunnel_cast(dev_);
 +
 +    ovs_mutex_lock(&netdev->mutex);
 +    if (netdev->valid_remote_ip)
 +      smap_add_format(args, "remote_ip", IP_FMT,
 +              IP_ARGS(netdev->remote_addr.sin_addr.s_addr));
 +    if (netdev->valid_remote_port)
 +        smap_add_format(args, "remote_port", "%"PRIu16,
 +              ntohs(netdev->remote_addr.sin_port));
 +    ovs_mutex_unlock(&netdev->mutex);
 +    return 0;
 +}
 +
 +static int
 +netdev_tunnel_connect(struct netdev_tunnel *dev)
 +    OVS_REQUIRES(dev->mutex)
 +{
 +    char buf[1024];
 +    if (dev->sockfd < 0)
 +        return EBADF;
 +    if (!dev->valid_remote_ip || !dev->valid_remote_port)
 +        return 0;
 +    dev->remote_addr.sin_family = AF_INET;
 +    if (connect(dev->sockfd, (struct sockaddr*) &dev->remote_addr, sizeof(dev->remote_addr)) < 0) {
 +        return errno;
 +    }
 +    dev->connected = true;
 +    netdev_tunnel_update_seq(dev);
 +    VLOG_DBG("%s: connected to (%s, %d)", netdev_get_name(&dev->up),
 +        inet_ntop(AF_INET, &dev->remote_addr.sin_addr, buf, 1024), ntohs(dev->remote_addr.sin_port));
 +    return 0;
 +}
 +
 +static int
 +netdev_tunnel_set_config(struct netdev *dev_, const struct smap *args)
 +{
 +    struct netdev_tunnel *netdev = netdev_tunnel_cast(dev_);
 +    struct shash_node *node;
 +    int error;
 +
 +    ovs_mutex_lock(&netdev->mutex);
 +    VLOG_DBG("tunnel_set_config(%s)", netdev_get_name(dev_));
 +    SMAP_FOR_EACH(node, args) {
 +        VLOG_DBG("arg: %s->%s", node->name, (char*)node->data);
 +      if (!strcmp(node->name, "remote_ip")) {
 +          struct in_addr addr;
 +          if (lookup_ip(node->data, &addr)) {
 +              VLOG_WARN("%s: bad 'remote_ip'", node->name);
 +          } else {
 +              netdev->remote_addr.sin_addr = addr;
 +              netdev->valid_remote_ip = true;
 +          }
 +      } else if (!strcmp(node->name, "remote_port")) {
 +          netdev->remote_addr.sin_port = htons(atoi(node->data));
 +          netdev->valid_remote_port = true;
 +      } else {
 +          VLOG_WARN("%s: unknown argument '%s'", 
 +              netdev_get_name(dev_), node->name);
 +      }
 +    }
 +    error = netdev_tunnel_connect(netdev);        
 +    ovs_mutex_unlock(&netdev->mutex);
 +    return error;
 +}
 +
 +static struct netdev_rx *
 +netdev_tunnel_rx_alloc(void)
 +{
 +    struct netdev_rx_tunnel *rx = xzalloc(sizeof *rx);
 +    return &rx->up;
 +}
 +
 +static int
 +netdev_tunnel_rx_construct(struct netdev_rx *rx_)
 +{   
 +    struct netdev_rx_tunnel *rx = netdev_rx_tunnel_cast(rx_);
 +    struct netdev *netdev_ = rx->up.netdev;
 +    struct netdev_tunnel *netdev = netdev_tunnel_cast(netdev_);
 +
 +    ovs_mutex_lock(&netdev->mutex);
 +    rx->fd = netdev->sockfd;
 +    ovs_mutex_unlock(&netdev->mutex);
 +    return 0;
 +}
 +
 +static void
 +netdev_tunnel_rx_destruct(struct netdev_rx *rx_ OVS_UNUSED)
 +{
 +}
 +
 +static void
 +netdev_tunnel_rx_dealloc(struct netdev_rx *rx_)
 +{
 +    struct netdev_rx_tunnel *rx = netdev_rx_tunnel_cast(rx_);
 +
 +    free(rx);
 +}
 +
 +static int
-         retval = recv(rx->fd, buffer, size, MSG_TRUNC);
++netdev_tunnel_rx_recv(struct netdev_rx *rx_, struct ofpbuf *buffer)
 +{
++    size_t size = ofpbuf_tailroom(buffer);
 +    struct netdev_rx_tunnel *rx = netdev_rx_tunnel_cast(rx_);
 +    struct netdev_tunnel *netdev =
 +        netdev_tunnel_cast(rx_->netdev);
 +    if (!netdev->connected)
 +        return -EAGAIN;
 +    for (;;) {
 +        ssize_t retval;
-                   netdev_rx_get_name(rx_), (uintptr_t)buffer, size, retval);
++        retval = recv(rx->fd, buffer->data, size, MSG_TRUNC);
 +          VLOG_DBG("%s: recv(%"PRIxPTR", %"PRIuSIZE", MSG_TRUNC) = %"PRIdSIZE,
-           netdev->stats.rx_packets++;
-           netdev->stats.rx_bytes += retval;
++                  netdev_rx_get_name(rx_), (uintptr_t)buffer->data, size, retval);
 +        if (retval >= 0) {
-     NULL,                       /* get_drv_info */
++              netdev->stats.rx_packets++;
++              netdev->stats.rx_bytes += retval;
 +            if (retval <= size) {
 +                  return retval;
 +            } else {
 +                netdev->stats.rx_errors++;
 +                netdev->stats.rx_length_errors++;
 +                return -EMSGSIZE;
 +            }
 +        } else if (errno != EINTR) {
 +            if (errno != EAGAIN) {
 +                VLOG_WARN_RL(&rl, "error receiveing Ethernet packet on %s: %s",
 +                    netdev_rx_get_name(rx_), ovs_strerror(errno));
 +                  netdev->stats.rx_errors++;
 +            }
 +            return -errno;
 +        }
 +    }
 +}
 +
 +static void
 +netdev_tunnel_rx_wait(struct netdev_rx *rx_)
 +{
 +    struct netdev_rx_tunnel *rx = 
 +      netdev_rx_tunnel_cast(rx_);
 +    if (rx->fd >= 0) {
 +        poll_fd_wait(rx->fd, POLLIN);
 +    }
 +}
 +
 +static int
 +netdev_tunnel_send(struct netdev *netdev_, const void *buffer, size_t size)
 +{
 +    struct netdev_tunnel *dev = 
 +      netdev_tunnel_cast(netdev_);
 +    if (!dev->connected)
 +        return EAGAIN;
 +    for (;;) {
 +        ssize_t retval;
 +        retval = send(dev->sockfd, buffer, size, 0);
 +      VLOG_DBG("%s: send(%"PRIxPTR", %"PRIuSIZE") = %"PRIdSIZE,
 +               netdev_get_name(netdev_), (uintptr_t)buffer, size, retval);
 +        if (retval >= 0) {
 +          dev->stats.tx_packets++;
 +          dev->stats.tx_bytes += retval;
 +          if (retval != size) {
 +              VLOG_WARN_RL(&rl, "sent partial Ethernet packet (%"PRIdSIZE" bytes of "
 +                           "%"PRIuSIZE") on %s", retval, size, netdev_get_name(netdev_));
 +              dev->stats.tx_errors++;
 +          }
 +            return 0;
 +        } else if (errno != EINTR) {
 +            if (errno != EAGAIN) {
 +                VLOG_WARN_RL(&rl, "error sending Ethernet packet on %s: %s",
 +                    netdev_get_name(netdev_), ovs_strerror(errno));
 +              dev->stats.tx_errors++;
 +            }
 +            return errno;
 +        }
 +    }
 +}
 +
 +static void
 +netdev_tunnel_send_wait(struct netdev *netdev_)
 +{
 +    struct netdev_tunnel *dev = netdev_tunnel_cast(netdev_);
 +    if (dev->sockfd >= 0) {
 +        poll_fd_wait(dev->sockfd, POLLOUT);
 +    }
 +}
 +
 +static int
 +netdev_tunnel_rx_drain(struct netdev_rx *rx_)
 +{
 +    struct netdev_tunnel *netdev =
 +        netdev_tunnel_cast(rx_->netdev);
 +    struct netdev_rx_tunnel *rx = 
 +      netdev_rx_tunnel_cast(rx_);
 +    char buffer[128];
 +    int error;
 +
 +    if (!netdev->connected)
 +      return 0;
 +    for (;;) {
 +      error = recv(rx->fd, buffer, 128, MSG_TRUNC);
 +      if (error) {
 +            if (error == -EAGAIN)
 +              break;
 +            else if (error != -EMSGSIZE)
 +              return error;
 +      }
 +    }
 +    return 0;
 +}
 +
 +static int
 +netdev_tunnel_set_etheraddr(struct netdev *netdev,
 +                           const uint8_t mac[ETH_ADDR_LEN])
 +{
 +    struct netdev_tunnel *dev = netdev_tunnel_cast(netdev);
 +
 +    ovs_mutex_lock(&dev->mutex);
 +    if (!eth_addr_equals(dev->hwaddr, mac)) {
 +        memcpy(dev->hwaddr, mac, ETH_ADDR_LEN);
 +        netdev_tunnel_update_seq(dev);
 +    }
 +    ovs_mutex_unlock(&dev->mutex);
 +
 +    return 0;
 +}
 +
 +static int
 +netdev_tunnel_get_etheraddr(const struct netdev *netdev,
 +                           uint8_t mac[ETH_ADDR_LEN])
 +{
 +    const struct netdev_tunnel *dev = netdev_tunnel_cast(netdev);
 +
 +    ovs_mutex_lock(&dev->mutex);
 +    memcpy(mac, dev->hwaddr, ETH_ADDR_LEN);
 +    ovs_mutex_unlock(&dev->mutex);
 +    return 0;
 +}
 +
 +
 +static int
 +netdev_tunnel_get_stats(const struct netdev *netdev, struct netdev_stats *stats)
 +{
 +    const struct netdev_tunnel *dev = netdev_tunnel_cast(netdev);
 +
 +    ovs_mutex_lock(&dev->mutex);
 +    *stats = dev->stats;
 +    ovs_mutex_unlock(&dev->mutex);
 +    return 0;
 +}
 +
 +static int
 +netdev_tunnel_set_stats(struct netdev *netdev, const struct netdev_stats *stats)
 +{
 +    struct netdev_tunnel *dev = netdev_tunnel_cast(netdev);
 +
 +    ovs_mutex_lock(&dev->mutex);
 +    dev->stats = *stats;
 +    ovs_mutex_unlock(&dev->mutex);
 +    return 0;
 +}
 +
 +static int
 +netdev_tunnel_update_flags(struct netdev *dev_,
 +                          enum netdev_flags off, enum netdev_flags on,
 +                          enum netdev_flags *old_flagsp)
 +{
 +    struct netdev_tunnel *netdev =
 +        netdev_tunnel_cast(dev_);
 +    int error = 0;
 +
 +    ovs_mutex_lock(&netdev->mutex);
 +    if ((off | on) & ~(NETDEV_UP | NETDEV_PROMISC)) {
 +        error = EINVAL;
 +        goto out;
 +    }
 +
 +    // XXX should we actually do something with these flags?
 +    *old_flagsp = netdev->flags;
 +    netdev->flags |= on;
 +    netdev->flags &= ~off;
 +    if (*old_flagsp != netdev->flags) {
 +        netdev_tunnel_update_seq(netdev);
 +    }
 +
 +out:
 +    ovs_mutex_unlock(&netdev->mutex);
 +    return error;
 +}
 +
 +static unsigned int
 +netdev_tunnel_change_seq(const struct netdev *netdev_)
 +{
 +    struct netdev_tunnel *netdev = netdev_tunnel_cast(netdev_);
 +    unsigned int change_seq;
 +
 +
 +    ovs_mutex_lock(&netdev->mutex);
 +    change_seq = netdev->change_seq;
 +    ovs_mutex_unlock(&netdev->mutex);
 +    return change_seq;
 +}
 +\f
 +/* Helper functions. */
 +
 +static void
 +netdev_tunnel_update_seq(struct netdev_tunnel *dev)
 +    OVS_REQUIRES(dev->mutex)
 +{
 +    dev->change_seq++;
 +    if (!dev->change_seq) {
 +        dev->change_seq++;
 +    }
 +}
 +
 +static void
 +netdev_tunnel_get_port(struct unixctl_conn *conn,
 +                     int argc OVS_UNUSED, const char *argv[], void *aux OVS_UNUSED)
 +{
 +    struct netdev_tunnel *tunnel_dev;
 +    char buf[6];
 +
 +    ovs_mutex_lock(&tunnel_netdevs_mutex);
 +    tunnel_dev = shash_find_data(&tunnel_netdevs, argv[1]);
 +    if (!tunnel_dev) {
 +        unixctl_command_reply_error(conn, "no such tunnel netdev");
 +        goto out;
 +    }
 +
 +    ovs_mutex_lock(&tunnel_dev->mutex);
 +    sprintf(buf, "%d", ntohs(tunnel_dev->local_addr.sin_port));
 +    ovs_mutex_unlock(&tunnel_dev->mutex);
 +
 +    unixctl_command_reply(conn, buf);
 +out:
 +    ovs_mutex_unlock(&tunnel_netdevs_mutex);
 +}
 +
 +static void
 +netdev_tunnel_get_tx_bytes(struct unixctl_conn *conn,
 +                     int argc OVS_UNUSED, const char *argv[], void *aux OVS_UNUSED)
 +{
 +    struct netdev_tunnel *tunnel_dev;
 +    char buf[128];
 +
 +    ovs_mutex_lock(&tunnel_netdevs_mutex);
 +    tunnel_dev = shash_find_data(&tunnel_netdevs, argv[1]);
 +    if (!tunnel_dev) {
 +        unixctl_command_reply_error(conn, "no such tunnel netdev");
 +        goto out;
 +    }
 +
 +    ovs_mutex_lock(&tunnel_dev->mutex);
 +    sprintf(buf, "%"PRIu64, tunnel_dev->stats.tx_bytes);
 +    ovs_mutex_unlock(&tunnel_dev->mutex);
 +    unixctl_command_reply(conn, buf);
 +out:
 +    ovs_mutex_unlock(&tunnel_netdevs_mutex);
 +}
 +
 +static void
 +netdev_tunnel_get_rx_bytes(struct unixctl_conn *conn,
 +                     int argc OVS_UNUSED, const char *argv[], void *aux OVS_UNUSED)
 +{
 +    struct netdev_tunnel *tunnel_dev;
 +    char buf[128];
 +
 +    ovs_mutex_lock(&tunnel_netdevs_mutex);
 +    tunnel_dev = shash_find_data(&tunnel_netdevs, argv[1]);
 +    if (!tunnel_dev) {
 +        unixctl_command_reply_error(conn, "no such tunnel netdev");
 +        goto out;
 +    }
 +
 +    sprintf(buf, "%"PRIu64, tunnel_dev->stats.rx_bytes);
 +    unixctl_command_reply(conn, buf);
 +out:
 +    ovs_mutex_unlock(&tunnel_netdevs_mutex);
 +}
 +
 +
 +static int
 +netdev_tunnel_init(void)
 +{
 +    unixctl_command_register("netdev-tunnel/get-port", "NAME",
 +                             1, 1, netdev_tunnel_get_port, NULL);
 +    unixctl_command_register("netdev-tunnel/get-tx-bytes", "NAME",
 +                             1, 1, netdev_tunnel_get_tx_bytes, NULL);
 +    unixctl_command_register("netdev-tunnel/get-rx-bytes", "NAME",
 +                             1, 1, netdev_tunnel_get_rx_bytes, NULL);
 +    return 0;
 +}
 +
 +static void
 +netdev_tunnel_run(void)
 +{
 +}
 +
 +static void
 +netdev_tunnel_wait(void)
 +{
 +}
 +
 +const struct netdev_class netdev_tunnel_class = {
 +    "tunnel",
 +    netdev_tunnel_init,    
 +    netdev_tunnel_run,      
 +    netdev_tunnel_wait,   
 +
 +    netdev_tunnel_alloc,
 +    netdev_tunnel_construct,
 +    netdev_tunnel_destruct,
 +    netdev_tunnel_dealloc,
 +    netdev_tunnel_get_config,
 +    netdev_tunnel_set_config, 
 +    NULL,                                 /* get_tunnel_config */
 +
 +    netdev_tunnel_send, 
 +    netdev_tunnel_send_wait,  
 +
 +    netdev_tunnel_set_etheraddr,
 +    netdev_tunnel_get_etheraddr,
 +    NULL,                                 /* get_mtu */
 +    NULL,                                 /* set_mtu */
 +    NULL,                       /* get_ifindex */
 +    NULL,                                 /* get_carrier */
 +    NULL,                       /* get_carrier_resets */
 +    NULL,                       /* get_miimon */
 +    netdev_tunnel_get_stats,
 +    netdev_tunnel_set_stats,
 +
 +    NULL,                       /* get_features */
 +    NULL,                       /* set_advertisements */
 +
 +    NULL,                       /* set_policing */
 +    NULL,                       /* get_qos_types */
 +    NULL,                       /* get_qos_capabilities */
 +    NULL,                       /* get_qos */
 +    NULL,                       /* set_qos */
 +    NULL,                       /* get_queue */
 +    NULL,                       /* set_queue */
 +    NULL,                       /* delete_queue */
 +    NULL,                       /* get_queue_stats */
 +    NULL,                       /* queue_dump_start */
 +    NULL,                       /* queue_dump_next */
 +    NULL,                       /* queue_dump_done */
 +    NULL,                       /* dump_queue_stats */
 +
 +    NULL,                       /* get_in4 */
 +    NULL,                       /* set_in4 */
 +    NULL,                       /* get_in6 */
 +    NULL,                       /* add_router */
 +    NULL,                       /* get_next_hop */
-     netdev_tunnel_change_seq,
++    NULL,                       /* get_status */
 +    NULL,                       /* arp_lookup */
 +
 +    netdev_tunnel_update_flags,
 +
 +    netdev_tunnel_rx_alloc,
 +    netdev_tunnel_rx_construct,
 +    netdev_tunnel_rx_destruct,
 +    netdev_tunnel_rx_dealloc,
 +    netdev_tunnel_rx_recv,
 +    netdev_tunnel_rx_wait,
 +    netdev_tunnel_rx_drain,
 +};
diff --combined lib/netdev.c
@@@ -24,6 -24,7 +24,7 @@@
  #include <string.h>
  #include <unistd.h>
  
+ #include "connectivity.h"
  #include "coverage.h"
  #include "dpif.h"
  #include "dynamic-string.h"
@@@ -36,6 -37,7 +37,7 @@@
  #include "openflow/openflow.h"
  #include "packets.h"
  #include "poll-loop.h"
+ #include "seq.h"
  #include "shash.h"
  #include "smap.h"
  #include "sset.h"
@@@ -109,8 -111,6 +111,8 @@@ netdev_initialize(void
          netdev_register_provider(&netdev_tap_class);
          netdev_register_provider(&netdev_bsd_class);
  #endif
 +        netdev_register_provider(&netdev_tunnel_class);
 +        netdev_register_provider(&netdev_pltap_class);
  
          ovsthread_once_done(&once);
      }
@@@ -224,6 -224,7 +226,7 @@@ netdev_unregister_provider(const char *
          atomic_read(&rc->ref_cnt, &ref_cnt);
          if (!ref_cnt) {
              hmap_remove(&netdev_classes, &rc->hmap_node);
+             atomic_destroy(&rc->ref_cnt);
              free(rc);
              error = 0;
          } else {
@@@ -333,6 -334,7 +336,7 @@@ netdev_open(const char *name, const cha
                      int old_ref_cnt;
  
                      atomic_add(&rc->ref_cnt, 1, &old_ref_cnt);
+                     seq_change(connectivity_seq_get());
                  } else {
                      free(netdev->name);
                      ovs_assert(list_is_empty(&netdev->saved_flags_list));
@@@ -388,13 -390,19 +392,19 @@@ netdev_set_config(struct netdev *netdev
  {
      if (netdev->netdev_class->set_config) {
          const struct smap no_args = SMAP_INITIALIZER(&no_args);
-         return netdev->netdev_class->set_config(netdev,
-                                                 args ? args : &no_args);
+         int error;
+         error = netdev->netdev_class->set_config(netdev,
+                                                  args ? args : &no_args);
+         if (error) {
+             VLOG_WARN("%s: could not set configuration (%s)",
+                       netdev_get_name(netdev), ovs_strerror(error));
+         }
+         return error;
      } else if (args && !smap_is_empty(args)) {
          VLOG_WARN("%s: arguments provided to device that is not configurable",
                    netdev_get_name(netdev));
      }
      return 0;
  }
  
@@@ -492,6 -500,13 +502,13 @@@ netdev_parse_name(const char *netdev_na
      }
  }
  
+ /* Attempts to open a netdev_rx handle for obtaining packets received on
+  * 'netdev'.  On success, returns 0 and stores a nonnull 'netdev_rx *' into
+  * '*rxp'.  On failure, returns a positive errno value and stores NULL into
+  * '*rxp'.
+  *
+  * Some kinds of network devices might not support receiving packets.  This
+  * function returns EOPNOTSUPP in that case.*/
  int
  netdev_rx_open(struct netdev *netdev, struct netdev_rx **rxp)
      OVS_EXCLUDED(netdev_mutex)
      return error;
  }
  
+ /* Closes 'rx'. */
  void
  netdev_rx_close(struct netdev_rx *rx)
      OVS_EXCLUDED(netdev_mutex)
      }
  }
  
+ /* Attempts to receive a packet from 'rx' into the tailroom of 'buffer', which
+  * must initially be empty.  If successful, returns 0 and increments
+  * 'buffer->size' by the number of bytes in the received packet, otherwise a
+  * positive errno value.
+  *
+  * Returns EAGAIN immediately if no packet is ready to be received.
+  *
+  * Returns EMSGSIZE, and discards the packet, if the received packet is longer
+  * than 'ofpbuf_tailroom(buffer)'.
+  *
+  * Implementations may make use of VLAN_HEADER_LEN bytes of tailroom to
+  * add a VLAN header which is obtained out-of-band to the packet. If
+  * this occurs then VLAN_HEADER_LEN bytes of tailroom will no longer be
+  * available for the packet, otherwise it may be used for the packet
+  * itself.
+  *
+  * It is advised that the tailroom of 'buffer' should be
+  * VLAN_HEADER_LEN bytes longer than the MTU to allow space for an
+  * out-of-band VLAN header to be added to the packet.  At the very least,
+  * 'buffer' must have at least ETH_TOTAL_MIN bytes of tailroom.
+  *
+  * This function may be set to null if it would always return EOPNOTSUPP
+  * anyhow. */
  int
  netdev_rx_recv(struct netdev_rx *rx, struct ofpbuf *buffer)
  {
      ovs_assert(buffer->size == 0);
      ovs_assert(ofpbuf_tailroom(buffer) >= ETH_TOTAL_MIN);
  
-     retval = rx->netdev->netdev_class->rx_recv(rx, buffer->data,
-                                                ofpbuf_tailroom(buffer));
-     if (retval >= 0) {
+     retval = rx->netdev->netdev_class->rx_recv(rx, buffer);
+     if (!retval) {
          COVERAGE_INC(netdev_received);
-         buffer->size += retval;
          if (buffer->size < ETH_TOTAL_MIN) {
              ofpbuf_put_zeros(buffer, ETH_TOTAL_MIN - buffer->size);
          }
          return 0;
      } else {
-         return -retval;
+         return retval;
      }
  }
  
+ /* Arranges for poll_block() to wake up when a packet is ready to be received
+  * on 'rx'. */
  void
  netdev_rx_wait(struct netdev_rx *rx)
  {
      rx->netdev->netdev_class->rx_wait(rx);
  }
  
+ /* Discards any packets ready to be received on 'rx'. */
  int
  netdev_rx_drain(struct netdev_rx *rx)
  {
@@@ -1496,18 -1536,6 +1538,6 @@@ netdev_dump_queue_stats(const struct ne
              : EOPNOTSUPP);
  }
  
- /* Returns a sequence number which indicates changes in one of 'netdev''s
-  * properties.  The returned sequence will be nonzero so that callers have a
-  * value which they may use as a reset when tracking 'netdev'.
-  *
-  * The returned sequence number will change whenever 'netdev''s flags,
-  * features, ethernet address, or carrier changes.  It may change for other
-  * reasons as well, or no reason at all. */
- unsigned int
- netdev_change_seq(const struct netdev *netdev)
- {
-     return netdev->netdev_class->change_seq(netdev);
- }
  \f
  /* Returns the class type of 'netdev'.
   *
diff --combined sliver-openvswitch.spec
index e8dda27,0000000..a3a5abd
mode 100644,000000..100644
--- /dev/null
@@@ -1,106 -1,0 +1,106 @@@
- %define version 2.0.90
 +%define name sliver-openvswitch
 +# to check for any change:
 +# grep AC_INIT configure.ac 
++%define version 2.1.90
 +%define taglevel 0
 +
 +%define debug_package %{nil}
 +
 +%define release %{taglevel}%{?pldistro:.%{pldistro}}%{?date:.%{date}}
 +
 +Vendor: OneLab
 +Packager: OneLab <support@planet-lab.eu>
 +Distribution: PlanetLab %{plrelease}
 +URL: %{SCMURL}
 +# Dependencies
 +# mar 2013 - because of the move to f18 I have to turn off auto requires
 +# this is because rpm would otherwise find deps to /bin/python and /bin/perl
 +# In other modules I was able to solve this by referring to /usr/bin/python 
 +# instead of just python in the builds scripts, but here it looks too complex
 +AutoReq: no
 +
 +Summary: Openvswitch modified for running from a PlanetLab sliver
 +Name: %{name}
 +Version: %{version}
 +Release: %{release}
 +License: GPL
 +Group: System Environment/Applications
 +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot
 +Source0: sliver-openvswitch-%{version}.tar.gz
 +
 +%description
 +Openvswitch tuned for running within a PlanetLab sliver
 +
 +%prep 
 +%setup -q
 +
 +%build
 +./boot.sh
 +# let's be as close as the regular linux/fedora layout
 +./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var --with-logdir=/var/log
 +make
 +
 +%install
 +make install DESTDIR=$RPM_BUILD_ROOT
 +
 +%clean
 +rm -rf $RPM_BUILD_ROOT
 +
 +%files
 +/usr/bin/*
 +/usr/sbin/*
 +/usr/share/openvswitch
 +/usr/share/man
 +
 +%post
 +
 +%postun
 +
 +%changelog
 +* Sat Jul 06 2013 Giuseppe Lettieri <g.lettieri@iet.unipi.it> - sliver-openvswitch-1.11.90-1
 +- merge with mainstream
 +
 +* Sat Jul 06 2013 Giuseppe Lettieri <g.lettieri@iet.unipi.it> - sliver-openvswitch-1.10.90-3
 +- merge with mainstream
 +
 +* Wed May 01 2013 Giuseppe Lettieri <g.lettieri@iet.unipi.it> - sliver-openvswitch-1.10.90-2
 +- - fixed several bugs in the external-nodes support in exp-tool/Makefile
 +- - let sliver-ovs return an error if tap device configuration failed
 +
 +* Mon Apr 22 2013 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - sliver-openvswitch-1.10.90-1
 +- merged with upstream (develoment version 1.10.90)
 +- integrated ALLEGRA contributions for the termination of virtual cables in external nodes.
 +
 +* Fri Feb 22 2013 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - sliver-openvswitch-1.9.90-3
 +- pulled mainstream - amazingly this is still known as 1.9.90 despite the size of changes
 +
 +* Fri Dec 21 2012 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - sliver-openvswitch-1.9.90-2
 +- merged with upstream
 +- handling of promisc &up/down flags for tap devices
 +- small improvements to the Makefile
 +
 +* Fri Nov 23 2012 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - sliver-openvswitch-1.8.90-6
 +- fixes in the exp-tool makefile (bash redirections, scp with key..)
 +
 +* Tue Oct 16 2012 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - sliver-openvswitch-1.8.90-5
 +- numerous additional make targets for finer control (use make help)
 +- including gprobe for reporting traffic to an ndnmap instance
 +- related, more functions in sliver-ovs as well, like exposing
 +- detailed info (mac, dpids..) relevant to the OF controller
 +- retrieving rx_bytes/tx_bytes (fixed) accessible through ovs-appctl
 +
 +* Fri Sep 28 2012 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - sliver-openvswitch-1.8.90-4
 +- fix file descriptor leaks
 +
 +* Fri Sep 28 2012 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - sliver-openvswitch-1.8.90-3
 +- can specify OpenFlow controller ip/port for each ovs instance
 +- through $(CONTROLLER_<id>), or $(CONTROLLER) by default
 +
 +* Thu Sep 27 2012 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - sliver-openvswitch-1.8.90-2
 +- add/skip packet information on tap send/recv
 +
 +* Wed Sep 26 2012 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - sliver-openvswitch-1.8.90-1
 +- merged mainstream 1.8.90
 +- planetlab extensions to the openvswitch: single helper command tool 'sliver-ovs' in /usr/sbin
 +- planetlab exp-tool : single config file (conf.mk)
 +- planetlab exp-tool : can retrieve and save current topology