Merge branch 'mainstream'
authorGiuseppe Lettieri <g.lettieri@iet.unipi.it>
Fri, 3 Jan 2014 11:01:47 +0000 (12:01 +0100)
committerGiuseppe Lettieri <g.lettieri@iet.unipi.it>
Fri, 3 Jan 2014 11:01:47 +0000 (12:01 +0100)
Conflicts:
Makefile.am

1  2 
.gitignore
Makefile.am
lib/automake.mk
lib/dpif-netdev.c
lib/dpif-provider.h
lib/dpif.c
lib/netdev-provider.h
lib/netdev.c

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
@@@ -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' \
@@@ -159,10 -164,10 +165,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 -182,8 +183,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 -290,7 +291,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 -304,5 +305,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
@@@ -5,9 -5,12 +5,12 @@@
  # 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 \
        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
+ 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 -401,3 +405,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
@@@ -40,6 -40,7 +40,7 @@@
  #include "flow.h"
  #include "hmap.h"
  #include "list.h"
+ #include "meta-flow.h"
  #include "netdev.h"
  #include "netdev-vport.h"
  #include "netlink.h"
@@@ -165,17 -166,15 +166,15 @@@ static int do_add_port(struct dp_netde
  static int do_del_port(struct dp_netdev *, odp_port_t port_no);
  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 *, 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 *,
+                                       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);
+ static void dp_netdev_port_input(struct dp_netdev *dp, struct ofpbuf *packet,
+                                  struct pkt_metadata *md);
  
  static struct dpif_netdev *
  dpif_netdev_cast(const struct dpif *dpif)
@@@ -210,17 -209,10 +209,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";
  }
@@@ -248,8 -240,7 +247,8 @@@ choose_port(struct dp_netdev *dp, cons
  {
      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;
  
@@@ -351,6 -342,7 +350,7 @@@ dp_netdev_purge_queues(struct dp_netde
  
          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);
          }
      }
@@@ -413,7 -405,7 +413,7 @@@ dpif_netdev_get_stats(const struct dpi
      stats->n_hit = dp->n_hit;
      stats->n_missed = dp->n_missed;
      stats->n_lost = dp->n_lost;
-     stats->n_masks = UINT64_MAX;
+     stats->n_masks = UINT32_MAX;
      stats->n_mask_hit = UINT64_MAX;
      ovs_mutex_unlock(&dp_netdev_mutex);
  
@@@ -796,29 -788,72 +796,72 @@@ get_dpif_flow_stats(struct dp_netdev_fl
  }
  
  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,
@@@ -942,8 -964,13 +972,13 @@@ 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;
      }
@@@ -1112,36 -1139,25 +1147,25 @@@ dpif_netdev_flow_dump_done(const struc
  }
  
  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;
-         /* Make a deep copy of 'packet', because we might modify its data. */
-         copy = ofpbuf_clone_with_headroom(execute->packet, DP_NETDEV_HEADROOM);
-         /* 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;
+     /* Extract flow key. */
+     flow_extract(execute->packet, md->skb_priority, md->pkt_mark, &md->tunnel,
+                  (union flow_in_port *)&md->in_port, &key);
+     ovs_mutex_lock(&dp_netdev_mutex);
+     dp_netdev_execute_actions(dp, &key, execute->packet, md, execute->actions,
+                               execute->actions_len);
+     ovs_mutex_unlock(&dp_netdev_mutex);
+     return 0;
  }
  
  static int
@@@ -1186,7 -1202,6 +1210,6 @@@ dpif_netdev_recv(struct dpif *dpif, str
          struct dp_netdev_upcall *u = &q->upcalls[q->tail++ & QUEUE_MASK];
  
          *upcall = u->upcall;
-         upcall->packet = buf;
  
          ofpbuf_uninit(buf);
          *buf = u->buf;
@@@ -1236,23 -1251,21 +1259,21 @@@ dp_netdev_flow_used(struct dp_netdev_fl
  }
  
  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)
+ dp_netdev_port_input(struct dp_netdev *dp, struct ofpbuf *packet,
+                      struct pkt_metadata *md)
  {
      struct dp_netdev_flow *netdev_flow;
      struct flow key;
-     union flow_in_port in_port_;
  
      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);
+     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) {
          dp_netdev_flow_used(netdev_flow, packet);
-         dp_netdev_execute_actions(dp, &key, packet,
+         dp_netdev_execute_actions(dp, &key, packet, md,
                                    netdev_flow->actions,
                                    netdev_flow->actions_len);
          dp->n_hit++;
@@@ -1268,22 -1281,25 +1289,25 @@@ dpif_netdev_run(struct dpif *dpif
      struct dp_netdev_port *port;
      struct dp_netdev *dp;
      struct ofpbuf packet;
+     size_t buf_size;
  
      ovs_mutex_lock(&dp_netdev_mutex);
      dp = get_dp_netdev(dpif);
-     ofpbuf_init(&packet,
-                 DP_NETDEV_HEADROOM + VLAN_ETH_HEADER_LEN + dp->max_mtu);
+     ofpbuf_init(&packet, 0);
+     buf_size = DP_NETDEV_HEADROOM + VLAN_ETH_HEADER_LEN + dp->max_mtu;
  
      LIST_FOR_EACH (port, node, &dp->port_list) {
          int error;
  
-         /* Reset packet contents. */
+         /* Reset packet contents. Packet data may have been stolen. */
          ofpbuf_clear(&packet);
-         ofpbuf_reserve(&packet, DP_NETDEV_HEADROOM);
+         ofpbuf_reserve_with_tailroom(&packet, DP_NETDEV_HEADROOM, buf_size);
  
          error = port->rx ? netdev_rx_recv(port->rx, &packet) : EOPNOTSUPP;
          if (!error) {
-             dp_netdev_port_input(dp, port, &packet, 0, 0, NULL);
+             struct pkt_metadata md = PKT_METADATA_INITIALIZER(port->port_no);
+             dp_netdev_port_input(dp, &packet, &md);
          } else if (error != EAGAIN && error != EOPNOTSUPP) {
              static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
  
@@@ -1322,8 -1338,18 +1346,18 @@@ dpif_netdev_wait(struct dpif *dpif
      ovs_mutex_unlock(&dp_netdev_mutex);
  }
  
+ static void
+ dp_netdev_output_port(struct dp_netdev *dp, struct ofpbuf *packet,
+                       odp_port_t out_port)
+ {
+     struct dp_netdev_port *p = dp->ports[odp_to_u32(out_port)];
+     if (p) {
+         netdev_send(p->netdev, packet);
+     }
+ }
  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)
  {
          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);
  
@@@ -1379,85 -1400,91 +1408,99 @@@ 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)
  {
      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);
  
- 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;
+     switch ((enum ovs_action_attr)type) {
+     case OVS_ACTION_ATTR_OUTPUT:
+         dp_netdev_output_port(aux->dp, packet, u32_to_odp(nl_attr_get_u32(a)));
+         break;
+     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)
  {
      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);
  }
  
 +#define DPIF_NETDEV_CLASS_FUNCTIONS                   \
 +    dpif_netdev_enumerate,                            \
 +    dpif_netdev_port_open_type,                               \
 +    dpif_netdev_open,                                 \
 +    dpif_netdev_close,                                        \
 +    dpif_netdev_destroy,                              \
 +    dpif_netdev_run,                                  \
 +    dpif_netdev_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,                   \
 +    dpif_netdev_get_max_ports,                                \
 +    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,
 -    dpif_netdev_run,
 -    dpif_netdev_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,
 -    dpif_netdev_get_max_ports,
 -    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
@@@ -1529,4 -1556,3 +1572,4 @@@ dpif_dummy_register(bool override
                               "DP PORT NEW-NUMBER",
                               3, 3, dpif_dummy_change_port_number, NULL);
  }
 +
diff --combined lib/dpif-provider.h
@@@ -311,12 -311,10 +311,10 @@@ struct dpif_class 
      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-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 ## */
  /* ## ------------------- ## */
@@@ -664,9 -664,6 +664,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.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);
      }
@@@ -333,6 -333,7 +335,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 -389,19 +391,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;
  }
  
@@@ -1496,18 -1503,6 +1505,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'.
   *