[ovs-dev] [PATCH v1 4/4] openvswitch: Userspace tunneling.

Pravin B Shelar pshelar at nicira.com
Thu Oct 16 18:38:20 UTC 2014


Following patch adds support for userspace tunneling. Tunneling
needs three more component first is routing table which is configured by
caching kernel routes and second is ARP cache which build automatically
by snooping arp. And third is tunnel protocol table which list all
listening protocols which is populated by vswitchd as tunnel ports
are added.

Tunneling works as follows:
On packet receive vswitchd check if this packet is targeted to tunnel
port. If it is then vswitchd inserts tunnel pop action which pops
header and sends packet to tunnel port.
On packet xmit rather than generating Set tunnel action it generate
tunnel push action which has tunnel header data. datapath can use
tunnel-push action data to generate header for each packet and
forward this packet to output port. Since tunnel-push action
contains most of packet header it need to lookup routing table and
arp table to build this action.

Signed-off-by: Pravin B Shelar <pshelar at nicira.com>
---
 Makefile.am                                       |    1 +
 README-native-tunneling                           |   83 ++++
 datapath/linux/compat/include/linux/openvswitch.h |   13 +
 debian/openvswitch-common.docs                    |    1 +
 lib/automake.mk                                   |    8 +-
 lib/dpif-netdev.c                                 |  106 +++++-
 lib/dpif-netlink.c                                |    3 +-
 lib/dpif-provider.h                               |    5 +-
 lib/dpif.c                                        |   17 +-
 lib/dpif.h                                        |    4 +-
 lib/gre.h                                         |   53 +++
 lib/netdev-bsd.c                                  |    3 +
 lib/netdev-dpdk.c                                 |    3 +
 lib/netdev-dummy.c                                |    3 +
 lib/netdev-linux.c                                |    3 +
 lib/netdev-provider.h                             |    9 +
 lib/netdev-vport.c                                |  457 ++++++++++++++++++++-
 lib/netdev.c                                      |   34 ++
 lib/netdev.h                                      |    9 +
 lib/odp-execute.c                                 |    2 +
 lib/odp-util.c                                    |  245 +++++++++++
 lib/odp-util.h                                    |    2 +
 lib/ofpbuf.h                                      |    8 +
 lib/ovs-router.c                                  |    4 +
 lib/packets.c                                     |   35 ++
 lib/packets.h                                     |    7 +-
 lib/tnl-arp-cache.c                               |  212 ++++++++++
 lib/tnl-arp-cache.h                               |   47 +++
 lib/tnl-ports.c                                   |  197 +++++++++
 lib/tnl-ports.h                                   |   49 +++
 lib/vxlan.h                                       |   50 +++
 ofproto/ofproto-dpif-xlate.c                      |  148 +++++++-
 ofproto/ofproto-dpif.c                            |  101 ++++-
 ofproto/ofproto-dpif.h                            |    1 +
 ofproto/tunnel.c                                  |   82 ++++-
 ofproto/tunnel.h                                  |   11 +-
 rhel/openvswitch.spec.in                          |    2 +-
 tests/ofproto-macros.at                           |    7 +
 tests/tunnel.at                                   |    8 +
 39 files changed, 1977 insertions(+), 56 deletions(-)
 create mode 100644 README-native-tunneling
 create mode 100644 lib/gre.h
 create mode 100644 lib/tnl-arp-cache.c
 create mode 100644 lib/tnl-arp-cache.h
 create mode 100644 lib/tnl-ports.c
 create mode 100644 lib/tnl-ports.h
 create mode 100644 lib/vxlan.h

diff --git a/Makefile.am b/Makefile.am
index 77ceec6..e3e3334 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -84,6 +84,7 @@ EXTRA_DIST = \
 	PORTING \
 	README.md \
 	README-lisp \
+	README-native-tunneling \
 	REPORTING-BUGS \
 	TODO \
 	.travis.yml \
diff --git a/README-native-tunneling b/README-native-tunneling
new file mode 100644
index 0000000..5cc17ec
--- /dev/null
+++ b/README-native-tunneling
@@ -0,0 +1,83 @@
+
+Open vSwitch support tunneling in userspace. Tunneling is implemented in
+platform independent way.
+
+Setup:
+======
+Setup physical bridges for all physical interfaces. create integration bridge.
+Add vxlan port to int-bridge. Assign IP address to physical bridge where
+vxlan traffic is expected.
+
+Example:
+========
+If you want to connect to vxlan tunnel endpoint  (logical ip: 192.168.1.2
+and remote physical ip: 172.168.1.2) that you want to connect to.
+
+In this case you need to configure OVS bridges as follows.
+
+1. Let assume 172.168.1.2/24 network is reachable via eth1 create physical bridge br-eth1
+   assign ip address (172.168.1.1/24) to br-eth1, Add eth1 to br-eth1
+2. Check ovs cached routes using appctl command
+   ovs-appctl ovs/route/show
+   Add tunnel route if not present in OVS route table.
+   ovs-appctl ovs/route/add 172.168.1.1 255.255.255.0 br-eth1
+3. Add integration brdge int-br and add tunnel port using standard syntax.
+   ovs-vsctl add-port int-br vxlan0 -- set interface vxlan0 type=vxlan  options:remote_ip=172.168.1.2
+4. Assign IP address to int-br, So final topology looks like:
+
+
+       192.168.1.1/24
+       +--------------+
+       |    int-br    |                                   192.168.1.2/24
+       +--------------+                                  +--------------+
+       |    vxlan0    |                                  |    vxlan0    |
+       +--------------+                                  +--------------+
+             |                                                 |
+             |                                                 |
+             |                                                 |
+        172.168.1.1/24                                         |
+       +--------------+                                        |
+       |    br-eth1   |                                  172.168.1.2/24
+       +--------------+                                  +---------------+
+       |    eth1      |----------------------------------|    eth1       |
+       +--------------+                                  +----------------
+
+       Host A with OVS.                                      Remote host.
+
+With this setup, ping to vxlan target device (192.168.1.2) should work
+There are following commands that shows internal tables:
+
+Tunneling related commands:
+===========================
+Tunnel routing table:
+    To Add route:
+       ovs-appctl ovs/route/add <IP address>/<prefix length> <output-bridge-name> <gw>
+    To see all routes configured:
+       ovs-appctl ovs/route/show
+    To del route:
+       ovs-appctl ovs/route/del <IP address>/<prefix length>
+
+ARP:
+    To see arp cache content:
+       ovs-appctl tnl/arp/show
+    To flush arp cache:
+       ovs-appctl tnl/arp/flush
+
+To check tunnel ports listening in vswitchd:
+     ovs-appctl tnl/ports/show
+
+To range for VxLan udp source port:
+     To set:
+         ovs-appctl tnl/egress_port_range <num1> <num2>
+     Shows Current range:
+         ovs-appctl tnl/egress_port_range
+
+To check datapath ports:
+     ovs-appctl dpif/show
+
+To check datapath flows:
+     ovs-appctl dpif/dump-flows
+
+Contact
+=======
+bugs at openvswitch.org
diff --git a/datapath/linux/compat/include/linux/openvswitch.h b/datapath/linux/compat/include/linux/openvswitch.h
index b2257e6..ff7c9d9 100644
--- a/datapath/linux/compat/include/linux/openvswitch.h
+++ b/datapath/linux/compat/include/linux/openvswitch.h
@@ -580,6 +580,15 @@ struct ovs_action_hash {
 	uint32_t  hash_basis;
 };
 
+#define TNL_PUSH_HEADER_SIZE 128
+struct ovs_action_push_tnl {
+	uint32_t tnl_port;
+	uint32_t out_port;
+	uint32_t header_len;
+	uint32_t tnl_type;     /* For logging. */
+	uint8_t  header[TNL_PUSH_HEADER_SIZE];
+};
+
 /**
  * enum ovs_action_attr - Action types.
  *
@@ -633,6 +642,10 @@ enum ovs_action_attr {
 				       * data immediately followed by a mask.
 				       * The data must be zero for the unmasked
 				       * bits. */
+#ifndef __KERNEL__
+	OVS_ACTION_ATTR_TUNNEL_PUSH,
+	OVS_ACTION_ATTR_TUNNEL_POP,
+#endif
 	__OVS_ACTION_ATTR_MAX
 };
 
diff --git a/debian/openvswitch-common.docs b/debian/openvswitch-common.docs
index 3bd2ca3..9775954 100644
--- a/debian/openvswitch-common.docs
+++ b/debian/openvswitch-common.docs
@@ -1,2 +1,3 @@
 FAQ
 INSTALL.DPDK
+README-native-tunneling
diff --git a/lib/automake.mk b/lib/automake.mk
index 0ab85c3..26fa4a7 100644
--- a/lib/automake.mk
+++ b/lib/automake.mk
@@ -75,6 +75,7 @@ lib_libopenvswitch_la_SOURCES = \
 	lib/flow.h \
 	lib/guarded-list.c \
 	lib/guarded-list.h \
+	lib/gre.h \
 	lib/hash.c \
 	lib/hash.h \
 	lib/hindex.c \
@@ -230,6 +231,10 @@ lib_libopenvswitch_la_SOURCES = \
 	lib/timer.h \
 	lib/timeval.c \
 	lib/timeval.h \
+	lib/tnl-arp-cache.c \
+	lib/tnl-arp-cache.h \
+	lib/tnl-ports.c \
+	lib/tnl-ports.h \
 	lib/token-bucket.c \
 	lib/token-bucket.h \
 	lib/type-props.h \
@@ -256,7 +261,8 @@ lib_libopenvswitch_la_SOURCES = \
 	lib/vswitch-idl.c \
 	lib/vswitch-idl.h \
 	lib/vtep-idl.c \
-	lib/vtep-idl.h
+	lib/vtep-idl.h \
+	lib/vxlan.h
 
 if WIN32
 lib_libopenvswitch_la_SOURCES += \
diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
index cea7c88..a60eab4 100644
--- a/lib/dpif-netdev.c
+++ b/lib/dpif-netdev.c
@@ -62,6 +62,7 @@
 #include "shash.h"
 #include "sset.h"
 #include "timeval.h"
+#include "tnl-arp-cache.h"
 #include "unixctl.h"
 #include "util.h"
 #include "vlog.h"
@@ -204,6 +205,7 @@ struct dp_netdev {
      * for pin of pmd threads. */
     size_t n_dpdk_rxqs;
     char *pmd_cmask;
+    uint64_t last_tnl_conf_seq;
 };
 
 static struct dp_netdev_port *dp_netdev_lookup_port(const struct dp_netdev *dp,
@@ -582,6 +584,7 @@ create_dp_netdev(const char *name, const struct dpif_class *class,
         return error;
     }
 
+    dp->last_tnl_conf_seq = seq_read(tnl_conf_seq);
     *dpp = dp;
     return 0;
 }
@@ -2054,12 +2057,13 @@ dp_netdev_process_rxq_port(struct dp_netdev_pmd_thread *pmd,
     }
 }
 
-static void
+static bool
 dpif_netdev_run(struct dpif *dpif)
 {
     struct dp_netdev_port *port;
     struct dp_netdev *dp = get_dp_netdev(dpif);
     struct dp_netdev_pmd_thread *non_pmd = dp_netdev_get_nonpmd(dp);
+    uint64_t new_tnl_seq;
 
     ovs_mutex_lock(&dp->non_pmd_mutex);
     CMAP_FOR_EACH (port, node, &dp->ports) {
@@ -2072,6 +2076,14 @@ dpif_netdev_run(struct dpif *dpif)
         }
     }
     ovs_mutex_unlock(&dp->non_pmd_mutex);
+    tnl_arp_cache_run();
+    new_tnl_seq = seq_read(tnl_conf_seq);
+
+    if (dp->last_tnl_conf_seq != new_tnl_seq) {
+        dp->last_tnl_conf_seq = new_tnl_seq;
+        return true;
+    }
+    return false;
 }
 
 static void
@@ -2091,6 +2103,7 @@ dpif_netdev_wait(struct dpif *dpif)
         }
     }
     ovs_mutex_unlock(&dp_netdev_mutex);
+    seq_wait(tnl_conf_seq, dp->last_tnl_conf_seq);
 }
 
 struct rxq_poll {
@@ -2785,15 +2798,48 @@ dpif_netdev_register_upcall_cb(struct dpif *dpif, upcall_callback *cb,
 static void
 dp_netdev_drop_packets(struct dpif_packet ** packets, int cnt, bool may_steal)
 {
-    int i;
-
     if (may_steal) {
+        int i;
+
         for (i = 0; i < cnt; i++) {
             dpif_packet_delete(packets[i]);
         }
     }
 }
 
+static int
+push_tunnel_action(const struct dp_netdev *dp,
+                   const struct nlattr *attr,
+                   struct dpif_packet **packets, int cnt)
+{
+    struct dp_netdev_port *tun_port;
+    struct ovs_action_push_tnl *data;
+
+    data = (struct ovs_action_push_tnl *) nl_attr_get(attr);
+
+    tun_port = dp_netdev_lookup_port(dp, data->tnl_port);
+    if (!tun_port) {
+        return -EINVAL;
+    }
+    netdev_push_header(tun_port->netdev, packets, cnt, data);
+
+    return 0;
+}
+
+static void
+dp_netdev_clone_pkt_batch(struct dpif_packet **tnl_pkt,
+                          struct dpif_packet **packets, int cnt)
+{
+    int i;
+
+    for (i = 0; i < cnt; i++) {
+        struct dpif_packet *inner_pkt;
+
+         inner_pkt = dpif_packet_clone(packets[i]);
+         tnl_pkt[i] = inner_pkt;
+    }
+}
+
 static void
 dp_execute_cb(void *aux_, struct dpif_packet **packets, int cnt,
               const struct nlattr *a, bool may_steal)
@@ -2816,6 +2862,60 @@ dp_execute_cb(void *aux_, struct dpif_packet **packets, int cnt,
         }
         break;
 
+    case OVS_ACTION_ATTR_TUNNEL_PUSH:
+        if (*depth < MAX_RECIRC_DEPTH) {
+            struct dpif_packet *tnl_pkt[NETDEV_MAX_RX_BATCH];
+            int err;
+
+            if (!may_steal) {
+                dp_netdev_clone_pkt_batch(tnl_pkt, packets, cnt);
+                packets = tnl_pkt;
+            }
+
+            err = push_tunnel_action(dp, a, packets, cnt);
+            if (err) {
+                dp_netdev_drop_packets(tnl_pkt, cnt, !may_steal);
+                break;
+            }
+
+            (*depth)++;
+            dp_netdev_input(pmd, packets, cnt);
+            (*depth)--;
+            return;
+        }
+        break;
+
+    case OVS_ACTION_ATTR_TUNNEL_POP:
+        if (*depth < MAX_RECIRC_DEPTH) {
+            odp_port_t portno = u32_to_odp(nl_attr_get_u32(a));
+
+            p = dp_netdev_lookup_port(dp, portno);
+            if (p) {
+                struct dpif_packet *tnl_pkt[NETDEV_MAX_RX_BATCH];
+                int err;
+
+                if (!may_steal) {
+                   dp_netdev_clone_pkt_batch(tnl_pkt, packets, cnt);
+                   packets = tnl_pkt;
+                }
+
+                err = netdev_pop_header(p->netdev, packets, cnt);
+                if (!err) {
+
+                    for (i = 0; i < cnt; i++) {
+                        packets[i]->md.in_port.odp_port = portno;
+                    }
+
+                    (*depth)++;
+                    dp_netdev_input(pmd, packets, cnt);
+                    (*depth)--;
+                    return;
+               }
+               dp_netdev_drop_packets(tnl_pkt, cnt, !may_steal);
+            }
+        }
+        break;
+
     case OVS_ACTION_ATTR_USERSPACE:
         if (!fat_rwlock_tryrdlock(&dp->upcall_rwlock)) {
             const struct nlattr *userdata;
diff --git a/lib/dpif-netlink.c b/lib/dpif-netlink.c
index 67c2814..bf2fb14 100644
--- a/lib/dpif-netlink.c
+++ b/lib/dpif-netlink.c
@@ -561,7 +561,7 @@ dpif_netlink_destroy(struct dpif *dpif_)
     return dpif_netlink_dp_transact(&dp, NULL, NULL);
 }
 
-static void
+static bool
 dpif_netlink_run(struct dpif *dpif_)
 {
     struct dpif_netlink *dpif = dpif_netlink_cast(dpif_);
@@ -572,6 +572,7 @@ dpif_netlink_run(struct dpif *dpif_)
         dpif_netlink_refresh_channels(dpif, dpif->n_handlers);
         fat_rwlock_unlock(&dpif->upcall_lock);
     }
+    return false;
 }
 
 static int
diff --git a/lib/dpif-provider.h b/lib/dpif-provider.h
index 65cf505..4ae1658 100644
--- a/lib/dpif-provider.h
+++ b/lib/dpif-provider.h
@@ -132,8 +132,9 @@ struct dpif_class {
      * the 'close' member function. */
     int (*destroy)(struct dpif *dpif);
 
-    /* Performs periodic work needed by 'dpif', if any is necessary. */
-    void (*run)(struct dpif *dpif);
+    /* Performs periodic work needed by 'dpif', if any is necessary.
+     * Return true if need to revalidate. */
+    bool (*run)(struct dpif *dpif);
 
     /* Arranges for poll_block() to wake up if the "run" member function needs
      * to be called for 'dpif'. */
diff --git a/lib/dpif.c b/lib/dpif.c
index d088f68..b2f05c9 100644
--- a/lib/dpif.c
+++ b/lib/dpif.c
@@ -41,6 +41,8 @@
 #include "shash.h"
 #include "sset.h"
 #include "timeval.h"
+#include "tnl-arp-cache.h"
+#include "tnl-ports.h"
 #include "util.h"
 #include "valgrind.h"
 #include "vlog.h"
@@ -114,6 +116,8 @@ dp_initialize(void)
         }
         dpctl_unixctl_register();
         ovsthread_once_done(&once);
+        tnl_port_map_init();
+        tnl_arp_cache_init();
     }
 }
 
@@ -402,12 +406,13 @@ dpif_close(struct dpif *dpif)
 }
 
 /* Performs periodic work needed by 'dpif'. */
-void
+bool
 dpif_run(struct dpif *dpif)
 {
     if (dpif->dpif_class->run) {
-        dpif->dpif_class->run(dpif);
+        return dpif->dpif_class->run(dpif);
     }
+    return false;
 }
 
 /* Arranges for poll_block() to wake up when dp_run() needs to be called for
@@ -1002,6 +1007,8 @@ dpif_execute_helper_cb(void *aux_, struct dpif_packet **packets, int cnt,
 
     switch ((enum ovs_action_attr)type) {
     case OVS_ACTION_ATTR_OUTPUT:
+    case OVS_ACTION_ATTR_TUNNEL_PUSH:
+    case OVS_ACTION_ATTR_TUNNEL_POP:
     case OVS_ACTION_ATTR_USERSPACE:
     case OVS_ACTION_ATTR_RECIRC: {
         struct dpif_execute execute;
@@ -1592,3 +1599,9 @@ log_flow_get_message(const struct dpif *dpif, const struct dpif_flow_get *get,
                          get->flow->actions, get->flow->actions_len);
     }
 }
+
+bool dpif_support_tnl_push_pop(const struct dpif *dpif)
+{
+   return !strcmp(dpif->dpif_class->type, "netdev") ||
+          !strcmp(dpif->dpif_class->type, "dummy");
+}
diff --git a/lib/dpif.h b/lib/dpif.h
index f88fa78..6f0710f 100644
--- a/lib/dpif.h
+++ b/lib/dpif.h
@@ -421,7 +421,7 @@ int dpif_create(const char *name, const char *type, struct dpif **);
 int dpif_create_and_open(const char *name, const char *type, struct dpif **);
 void dpif_close(struct dpif *);
 
-void dpif_run(struct dpif *);
+bool dpif_run(struct dpif *);
 void dpif_wait(struct dpif *);
 
 const char *dpif_name(const struct dpif *);
@@ -790,6 +790,8 @@ void dpif_get_netflow_ids(const struct dpif *,
 int dpif_queue_to_priority(const struct dpif *, uint32_t queue_id,
                            uint32_t *priority);
 
+bool dpif_support_tnl_push_pop(const struct dpif *);
+
 #ifdef  __cplusplus
 }
 #endif
diff --git a/lib/gre.h b/lib/gre.h
new file mode 100644
index 0000000..568469c
--- /dev/null
+++ b/lib/gre.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 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.
+ * 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.
+ */
+
+#ifndef NETDEV_GRE_H
+#define NETDEV_GRE_H 1
+
+#include <stddef.h>
+#include <stdint.h>
+#include "list.h"
+#include "packets.h"
+#include "util.h"
+#include "netdev-dpdk.h"
+
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
+/* GRE protocol header */
+struct gre_base_hdr {
+    ovs_be16 flags;
+    ovs_be16 protocol;
+}__attribute__((aligned(4)));
+
+#define GRE_HEADER_SECTION 4
+
+#define GRE_CSUM        htons(0x8000)
+#define GRE_ROUTING     htons(0x4000)
+#define GRE_KEY         htons(0x2000)
+#define GRE_SEQ	        htons(0x1000)
+#define GRE_STRICT      htons(0x0800)
+#define GRE_REC         htons(0x0700)
+#define GRE_FLAGS       htons(0x00F8)
+#define GRE_VERSION     htons(0x0007)
+
+
+#ifdef  __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/netdev-bsd.c b/lib/netdev-bsd.c
index 1bd9108..473c0f6 100644
--- a/lib/netdev-bsd.c
+++ b/lib/netdev-bsd.c
@@ -1562,6 +1562,9 @@ netdev_bsd_update_flags(struct netdev *netdev_, enum netdev_flags off,
     NULL, /* get_config */                           \
     NULL, /* set_config */                           \
     NULL, /* get_tunnel_config */                    \
+    NULL, /* build header */                         \
+    NULL, /* push header */                          \
+    NULL, /* pop header */                           \
     NULL, /* get_numa_id */                          \
     NULL, /* set_multiq */                           \
                                                      \
diff --git a/lib/netdev-dpdk.c b/lib/netdev-dpdk.c
index 9c93768..c8610d7 100644
--- a/lib/netdev-dpdk.c
+++ b/lib/netdev-dpdk.c
@@ -1392,6 +1392,9 @@ unlock_dpdk:
     netdev_dpdk_get_config,                                   \
     NULL,                       /* netdev_dpdk_set_config */  \
     NULL,                       /* get_tunnel_config */       \
+    NULL, /* build header */                                  \
+    NULL, /* push header */                                   \
+    NULL, /* pop header */                                    \
     netdev_dpdk_get_numa_id,    /* get_numa_id */             \
     MULTIQ,                     /* set_multiq */              \
                                                               \
diff --git a/lib/netdev-dummy.c b/lib/netdev-dummy.c
index a2b1f2c..22d980a 100644
--- a/lib/netdev-dummy.c
+++ b/lib/netdev-dummy.c
@@ -1032,6 +1032,9 @@ static const struct netdev_class dummy_class = {
     netdev_dummy_get_config,
     netdev_dummy_set_config,
     NULL,                       /* get_tunnel_config */
+    NULL,                       /* build header */
+    NULL,                       /* push header */
+    NULL,                       /* pop header */
     NULL,                       /* get_numa_id */
     NULL,                       /* set_multiq */
 
diff --git a/lib/netdev-linux.c b/lib/netdev-linux.c
index ab5ef7d..e89063a 100644
--- a/lib/netdev-linux.c
+++ b/lib/netdev-linux.c
@@ -2715,6 +2715,9 @@ netdev_linux_update_flags(struct netdev *netdev_, enum netdev_flags off,
     NULL,                       /* get_config */                \
     NULL,                       /* set_config */                \
     NULL,                       /* get_tunnel_config */         \
+    NULL,                       /* build header */              \
+    NULL,                       /* push header */               \
+    NULL,                       /* pop header */                \
     NULL,                       /* get_numa_id */               \
     NULL,                       /* set_multiq */                \
                                                                 \
diff --git a/lib/netdev-provider.h b/lib/netdev-provider.h
index af9ea3c..62ddab5 100644
--- a/lib/netdev-provider.h
+++ b/lib/netdev-provider.h
@@ -254,6 +254,15 @@ struct netdev_class {
     const struct netdev_tunnel_config *
         (*get_tunnel_config)(const struct netdev *netdev);
 
+    int (*build_header)(const struct netdev *, struct ovs_action_push_tnl *data);
+
+    int (*push_header)(const struct netdev *netdev,
+                       struct dpif_packet **buffers, int cnt,
+                       const struct ovs_action_push_tnl *data);
+
+    int  (*pop_header)(struct netdev *netdev,
+                       struct dpif_packet **buffers, int cnt);
+
     /* Returns the id of the numa node the 'netdev' is on.  If there is no
      * such info, returns NETDEV_NUMA_UNSPEC. */
     int (*get_numa_id)(const struct netdev *netdev);
diff --git a/lib/netdev-vport.c b/lib/netdev-vport.c
index b3b8a11..8885cbb 100644
--- a/lib/netdev-vport.c
+++ b/lib/netdev-vport.c
@@ -25,21 +25,30 @@
 #include <sys/ioctl.h>
 
 #include "byte-order.h"
+#include "csum.h"
 #include "daemon.h"
 #include "dirs.h"
 #include "dpif.h"
+#include "dynamic-string.h"
+#include "flow.h"
+#include "gre.h"
 #include "hash.h"
 #include "hmap.h"
 #include "list.h"
 #include "netdev-provider.h"
+#include "odp-netlink.h"
 #include "ofpbuf.h"
 #include "ovs-router.h"
 #include "packets.h"
+#include "packet-dpif.h"
 #include "poll-loop.h"
 #include "route-table.h"
 #include "shash.h"
 #include "socket-util.h"
 #include "vlog.h"
+#include "vxlan.h"
+#include "unaligned.h"
+#include "util.h"
 
 VLOG_DEFINE_THIS_MODULE(netdev_vport);
 
@@ -80,6 +89,9 @@ static int get_patch_config(const struct netdev *netdev, struct smap *args);
 static int get_tunnel_config(const struct netdev *, struct smap *args);
 static bool tunnel_check_status_change__(struct netdev_vport *);
 
+uint16_t tnl_udp_port_min;
+uint16_t tnl_udp_port_max;
+
 static bool
 is_vport_class(const struct netdev_class *class)
 {
@@ -222,10 +234,24 @@ netdev_vport_alloc(void)
 static int
 netdev_vport_construct(struct netdev *netdev_)
 {
-    struct netdev_vport *netdev = netdev_vport_cast(netdev_);
+    struct netdev_vport *dev = netdev_vport_cast(netdev_);
+    const char *type = netdev_get_type(netdev_);
+
+    ovs_mutex_init(&dev->mutex);
+    eth_addr_random(dev->etheraddr);
 
-    ovs_mutex_init(&netdev->mutex);
-    eth_addr_random(netdev->etheraddr);
+    /* Add a default destination port for tunnel ports if none specified. */
+    if (!strcmp(type, "geneve")) {
+        dev->tnl_cfg.dst_port = htons(GENEVE_DST_PORT);
+    }
+
+    if (!strcmp(type, "vxlan")) {
+        dev->tnl_cfg.dst_port = htons(VXLAN_DST_PORT);
+    }
+
+    if (!strcmp(type, "lisp")) {
+        dev->tnl_cfg.dst_port = htons(LISP_DST_PORT);
+    }
 
     route_table_register();
 
@@ -763,9 +789,388 @@ get_stats(const struct netdev *netdev, struct netdev_stats *stats)
 
     return 0;
 }
+
+
+/* Tunnel push pop ops. */
+
+static struct ip_header *
+ip_hdr(void *eth)
+{
+    return (struct ip_header *) (void *)((char *)eth +
+                                         sizeof (struct eth_header));
+}
+
+static struct gre_base_hdr *
+gre_hdr(struct ip_header *ip)
+{
+     return (struct gre_base_hdr *) (void *)((char *)ip +
+                                         sizeof (struct ip_header));
+}
+
+static void *
+ip_extract_md(struct ofpbuf *packet, struct flow_tnl *tnl)
+{
+    struct ip_header *nh;
+    void *l4;
+
+    nh = ofpbuf_l3(packet);
+    l4 = ofpbuf_l4(packet);
+
+    if (!nh || !l4) {
+        return NULL;
+    }
+
+    tnl->ip_src = get_16aligned_be32(&nh->ip_src);
+    tnl->ip_dst = get_16aligned_be32(&nh->ip_dst);
+    tnl->ip_tos = nh->ip_tos;
+
+    return l4;
+}
+
+/* Return l4 pointer. */
+static void *
+push_ip_header(struct ofpbuf *packet,
+               const void *header, int size, int *ip_tot_size)
+{
+    struct eth_header *eth;
+    struct ip_header *ip;
+
+    eth = ofpbuf_push_uninit(packet, size);
+    *ip_tot_size = ofpbuf_size(packet) - sizeof (struct eth_header);
+
+    memcpy(eth, header, size);
+    ip = ip_hdr(eth);
+    ip->ip_tot_len = htons(*ip_tot_size);
+
+
+    ip->ip_csum = recalc_csum16(ip->ip_csum, 0, ip->ip_tot_len);
+
+    return ip + 1;
+}
+
+static int
+parse_gre_header(struct ofpbuf *packet,
+                 struct flow_tnl *tnl)
+{
+    const struct gre_base_hdr *greh;
+    ovs_be32 *options;
+
+    greh = ip_extract_md(packet, tnl);
+    if (!greh) {
+        return -EINVAL;
+    }
+
+    if (greh->flags & (GRE_VERSION | GRE_ROUTING)) {
+        return -EINVAL;
+    }
+
+    options = (ovs_be32 *)(greh + 1);
+    if (greh->flags & GRE_CSUM) {
+        ovs_be32 pkt_csum;
+
+        pkt_csum = csum(greh, ofpbuf_size(packet) -
+                              sizeof (struct eth_header) -
+                              sizeof (struct ip_header));
+        if (pkt_csum) {
+            return -EINVAL;
+        }
+        tnl->flags = FLOW_TNL_F_CSUM;
+        options++;
+    }
+
+    if (greh->flags & GRE_KEY) {
+        tnl->tun_id = (ovs_be64) ((uint64_t)(*(uint32_t*)options) << 32);
+        tnl->flags |= FLOW_TNL_F_KEY;
+        options++;
+    }
+
+    if (greh->flags & GRE_SEQ) {
+        options++;
+    }
+
+    return ((uint8_t *)options - (uint8_t *) greh) ;
+}
+
+static void
+gre_extract_md(struct dpif_packet *dpif_pkt)
+{
+    struct ofpbuf *packet = &dpif_pkt->ofpbuf;
+    struct pkt_metadata *md = &dpif_pkt->md;
+    struct flow_tnl *tnl = &md->tunnel;
+    int greh_len, tnl_hlen;
+
+    memset(md, 0, sizeof(*md));
+
+    greh_len = parse_gre_header(packet, tnl);
+    if (greh_len <= 0) {
+        memset(md, 0, sizeof(*md));
+        return;
+    }
+
+    tnl_hlen = sizeof (struct eth_header) + sizeof (struct ip_header) + greh_len;
+
+    if (tnl_hlen > ofpbuf_size(packet)) {
+        memset(md, 0, sizeof(*md));
+        return;
+    }
+
+    ofpbuf_reset_packet(packet, tnl_hlen);
+}
+
+static int
+netdev_gre_pop_header(struct netdev *netdev_ OVS_UNUSED,
+                      struct dpif_packet **pkt, int cnt)
+{
+    int i;
+
+    for (i = 0; i < cnt; i++) {
+        gre_extract_md(pkt[i]);
+    }
+    return 0;
+}
+
+static void
+netdev_gre_push_header__(struct ofpbuf *packet,
+                         const void *header, int size)
+{
+    struct gre_base_hdr *greh;
+    int ip_tot_size;
+
+    greh = push_ip_header(packet, header, size,  &ip_tot_size);
+
+    if (greh->flags & GRE_CSUM) {
+        ovs_be32 *options = (ovs_be32 *) (greh + 1);
+
+        *options = csum(greh, ip_tot_size - sizeof (struct ip_header));
+    }
+}
+
+static int
+netdev_gre_push_header(const struct netdev *netdev OVS_UNUSED,
+                       struct dpif_packet **packets, int cnt,
+                       const struct ovs_action_push_tnl *data)
+{
+    int i;
+
+    for (i = 0; i < cnt; i++) {
+        netdev_gre_push_header__(&packets[i]->ofpbuf,
+                                   data->header, data->header_len);
+        packets[i]->md = PKT_METADATA_INITIALIZER(data->out_port);
+    }
+    return 0;
+}
+
+
+static int
+netdev_gre_build_header(const struct netdev *netdev,
+                        struct ovs_action_push_tnl *data)
+{
+    struct netdev_vport *dev = netdev_vport_cast(netdev);
+    struct netdev_tunnel_config *tnl_cfg;
+    struct ip_header *ip;
+    struct gre_base_hdr *greh;
+    ovs_be32 *options;
+    int hlen;
+
+    /* XXX: RCUfy tnl_cfg. */
+    ovs_mutex_lock(&dev->mutex);
+    tnl_cfg = &dev->tnl_cfg;
+
+    ip = ip_hdr(data->header);
+    ip->ip_proto = IPPROTO_GRE;
+
+    greh = gre_hdr(ip);
+    greh->protocol = htons(ETH_TYPE_TEB);
+    greh->flags = 0;
+
+    options = (ovs_be32 *) (greh + 1);
+    if (tnl_cfg->csum) {
+        greh->flags |= GRE_CSUM;
+        *options = 0;
+        options++;
+    }
+
+    if (tnl_cfg->out_key_present) {
+        greh->flags = GRE_KEY;
+        *options = (ovs_be32) (tnl_cfg->out_key >> 32);
+        options++;
+    }
+
+    ovs_mutex_unlock(&dev->mutex);
+
+    hlen = (uint8_t *) options - (uint8_t *) greh;
+
+    data->header_len = sizeof (struct eth_header) + sizeof *ip + hlen;
+    data->tnl_type = OVS_VPORT_TYPE_GRE;
+    return 0;
+}
+
+static void
+vxlan_extract_md(struct dpif_packet *dpif_pkt)
+{
+    struct ofpbuf *packet = &dpif_pkt->ofpbuf;
+    struct pkt_metadata *md = &dpif_pkt->md;
+    struct flow_tnl *tnl = &md->tunnel;
+    struct udp_header *udp;
+    struct vxlanhdr *vxh;
+
+    memset(md, 0, sizeof(*md));
+    if (VXLAN_HLEN > ofpbuf_size(packet)) {
+        return;
+    }
+
+    udp = ip_extract_md(packet, tnl);
+    if (!udp) {
+        return;
+    }
+    vxh = (struct vxlanhdr *) ((const char *)udp + sizeof(*udp));
+
+    if (vxh->vx_flags != htonl(VXLAN_FLAGS) ||
+       (vxh->vx_vni & htonl(0xff))) {
+        VLOG_ERR("invalid vxlan flags=%#x vni=%#x\n",
+                 ntohl(vxh->vx_flags), ntohl(vxh->vx_vni));
+        memset(md, 0, sizeof(*md));
+        return;
+    }
+    tnl->tp_src = udp->udp_src;
+    tnl->tp_dst = udp->udp_dst;
+    tnl->tun_id = htonll(ntohl(vxh->vx_vni) >> 8);
+
+    ofpbuf_reset_packet(packet, VXLAN_HLEN);
+}
+
+static int
+netdev_vxlan_pop_header(struct netdev *netdev_ OVS_UNUSED,
+                        struct dpif_packet **pkt, int cnt)
+{
+    int i;
+
+    for (i = 0; i < cnt; i++) {
+        vxlan_extract_md(pkt[i]);
+    }
+    return 0;
+}
+
+static int
+netdev_vxlan_build_header(const struct netdev *netdev,
+                          struct ovs_action_push_tnl *data)
+{
+    struct netdev_vport *dev = netdev_vport_cast(netdev);
+    struct netdev_tunnel_config *tnl_cfg;
+    struct ip_header *ip;
+    struct udp_header *udp;
+    struct vxlanhdr *vxh;
+
+    /* XXX: RCUfy tnl_cfg. */
+    ovs_mutex_lock(&dev->mutex);
+    tnl_cfg = &dev->tnl_cfg;
+
+    ip = ip_hdr(data->header);
+    ip->ip_proto = IPPROTO_UDP;
+
+    udp = (struct udp_header *) (ip + 1);
+    udp->udp_dst = tnl_cfg->dst_port;
+
+    vxh = (struct vxlanhdr *) (udp + 1);
+    vxh->vx_flags = htonl(VXLAN_FLAGS);
+    vxh->vx_vni = htonl(ntohll(tnl_cfg->out_key) << 8);
+
+    ovs_mutex_unlock(&dev->mutex);
+    data->header_len = VXLAN_HLEN;
+    data->tnl_type = OVS_VPORT_TYPE_VXLAN;
+    return 0;
+}
+
+static ovs_be16
+get_src_port(struct dpif_packet *packet)
+{
+    uint32_t hash;
+
+    hash = dpif_packet_get_dp_hash(packet);
+
+    return htons((((uint64_t) hash * (tnl_udp_port_max - tnl_udp_port_min)) >> 32) +
+                 tnl_udp_port_min);
+}
+
+static void
+netdev_vxlan_push_header__(struct dpif_packet *packet,
+                           const void *header, int size)
+{
+    struct udp_header *udp;
+    int ip_tot_size;
+
+    udp = push_ip_header(&packet->ofpbuf, header, size, &ip_tot_size);
+
+    /* set udp src port */
+    udp->udp_src = get_src_port(packet);
+    udp->udp_len = htons(ip_tot_size - sizeof (struct ip_header));
+    /* udp_csum is zero */
+}
+
+static int
+netdev_vxlan_push_header(const struct netdev *netdev OVS_UNUSED,
+                         struct dpif_packet **packets, int cnt,
+                         const struct ovs_action_push_tnl *data)
+{
+    int i;
+
+    for (i = 0; i < cnt; i++) {
+        netdev_vxlan_push_header__(packets[i],
+                                   data->header, data->header_len);
+        packets[i]->md = PKT_METADATA_INITIALIZER(data->out_port);
+    }
+    return 0;
+}
+
+static void
+netdev_vport_range(struct unixctl_conn *conn, int argc,
+                   const char *argv[], void *aux OVS_UNUSED)
+{
+    int val1, val2;
+
+    if (argc < 3) {
+        struct ds ds = DS_EMPTY_INITIALIZER;
+
+        ds_put_format(&ds, "Tunnel UDP source port range: %"PRIu16"-%"PRIu16"\n",
+                            tnl_udp_port_min, tnl_udp_port_max);
+
+        unixctl_command_reply(conn, ds_cstr(&ds));
+        ds_destroy(&ds);
+        return;
+    }
+
+    if (argc != 3) {
+        return;
+    }
+
+    val1 = atoi(argv[1]);
+    if (val1 <= 0 || val1 > UINT16_MAX) {
+        unixctl_command_reply(conn, "Invalid min.");
+        return;
+    }
+    val2 = atoi(argv[2]);
+    if (val2 <= 0 || val2 > UINT16_MAX) {
+        unixctl_command_reply(conn, "Invalid max.");
+        return;
+    }
+
+    if (val1 > val2) {
+        tnl_udp_port_min = val2;
+        tnl_udp_port_max = val1;
+    } else {
+        tnl_udp_port_min = val1;
+        tnl_udp_port_max = val2;
+    }
+    seq_change(tnl_conf_seq);
+
+    unixctl_command_reply(conn, "OK");
+}
+
 
 #define VPORT_FUNCTIONS(GET_CONFIG, SET_CONFIG,             \
-                        GET_TUNNEL_CONFIG, GET_STATUS)      \
+                        GET_TUNNEL_CONFIG, GET_STATUS,      \
+                        BUILD_HEADER,                       \
+                        PUSH_HEADER, POP_HEADER)            \
     NULL,                                                   \
     netdev_vport_run,                                       \
     netdev_vport_wait,                                      \
@@ -777,6 +1182,9 @@ get_stats(const struct netdev *netdev, struct netdev_stats *stats)
     GET_CONFIG,                                             \
     SET_CONFIG,                                             \
     GET_TUNNEL_CONFIG,                                      \
+    BUILD_HEADER,                                           \
+    PUSH_HEADER,                                            \
+    POP_HEADER,                                             \
     NULL,                       /* get_numa_id */           \
     NULL,                       /* set_multiq */            \
                                                             \
@@ -828,12 +1236,14 @@ get_stats(const struct netdev *netdev, struct netdev_stats *stats)
     NULL,                   /* rx_wait */                   \
     NULL,                   /* rx_drain */
 
-#define TUNNEL_CLASS(NAME, DPIF_PORT)                       \
-    { DPIF_PORT,                                            \
-        { NAME, VPORT_FUNCTIONS(get_tunnel_config,          \
-                                set_tunnel_config,          \
-                                get_netdev_tunnel_config,   \
-                                tunnel_get_status) }}
+
+#define TUNNEL_CLASS(NAME, DPIF_PORT, BUILD_HEADER, PUSH_HEADER, POP_HEADER)   \
+    { DPIF_PORT,                                                               \
+        { NAME, VPORT_FUNCTIONS(get_tunnel_config,                             \
+                                set_tunnel_config,                             \
+                                get_netdev_tunnel_config,                      \
+                                tunnel_get_status,                             \
+                                BUILD_HEADER, PUSH_HEADER, POP_HEADER) }}
 
 void
 netdev_vport_tunnel_register(void)
@@ -841,13 +1251,17 @@ netdev_vport_tunnel_register(void)
     /* The name of the dpif_port should be short enough to accomodate adding
      * a port number to the end if one is necessary. */
     static const struct vport_class vport_classes[] = {
-        TUNNEL_CLASS("geneve", "genev_sys"),
-        TUNNEL_CLASS("gre", "gre_sys"),
-        TUNNEL_CLASS("ipsec_gre", "gre_sys"),
-        TUNNEL_CLASS("gre64", "gre64_sys"),
-        TUNNEL_CLASS("ipsec_gre64", "gre64_sys"),
-        TUNNEL_CLASS("vxlan", "vxlan_sys"),
-        TUNNEL_CLASS("lisp", "lisp_sys")
+        TUNNEL_CLASS("geneve", "genev_sys", NULL, NULL, NULL),
+        TUNNEL_CLASS("gre", "gre_sys", netdev_gre_build_header,
+                                       netdev_gre_push_header,
+                                       netdev_gre_pop_header),
+        TUNNEL_CLASS("ipsec_gre", "gre_sys", NULL, NULL, NULL),
+        TUNNEL_CLASS("gre64", "gre64_sys", NULL,  NULL, NULL),
+        TUNNEL_CLASS("ipsec_gre64", "gre64_sys", NULL, NULL, NULL),
+        TUNNEL_CLASS("vxlan", "vxlan_sys", netdev_vxlan_build_header,
+                                           netdev_vxlan_push_header,
+                                           netdev_vxlan_pop_header),
+        TUNNEL_CLASS("lisp", "lisp_sys", NULL, NULL, NULL)
     };
     static struct ovsthread_once once = OVSTHREAD_ONCE_INITIALIZER;
 
@@ -857,6 +1271,13 @@ netdev_vport_tunnel_register(void)
         for (i = 0; i < ARRAY_SIZE(vport_classes); i++) {
             netdev_register_provider(&vport_classes[i].netdev_class);
         }
+
+        tnl_udp_port_min = 32768;
+        tnl_udp_port_max = 61000;
+
+        unixctl_command_register("tnl/egress_port_range", "min max", 0, 2,
+                                 netdev_vport_range, NULL);
+
         ovsthread_once_done(&once);
     }
 }
@@ -869,6 +1290,6 @@ netdev_vport_patch_register(void)
             { "patch", VPORT_FUNCTIONS(get_patch_config,
                                        set_patch_config,
                                        NULL,
-                                       NULL) }};
+                                       NULL, NULL, NULL, NULL) }};
     netdev_register_provider(&patch_class.netdev_class);
 }
diff --git a/lib/netdev.c b/lib/netdev.c
index 670bd62..e4e3b18 100644
--- a/lib/netdev.c
+++ b/lib/netdev.c
@@ -37,6 +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"
@@ -76,6 +77,9 @@ static struct ovs_mutex netdev_class_mutex OVS_ACQ_BEFORE(netdev_mutex);
 static struct hmap netdev_classes OVS_GUARDED_BY(netdev_class_mutex)
     = HMAP_INITIALIZER(&netdev_classes);
 
+/* Incremented whenever tnl route, arp, etc changes. */
+struct seq *tnl_conf_seq;
+
 struct netdev_registered_class {
     /* In 'netdev_classes', by class->type. */
     struct hmap_node hmap_node OVS_GUARDED_BY(netdev_class_mutex);
@@ -151,6 +155,7 @@ netdev_initialize(void)
 #endif
         netdev_dpdk_register();
 
+        tnl_conf_seq = seq_create();
         ovsthread_once_done(&once);
     }
 }
@@ -729,6 +734,35 @@ netdev_send(struct netdev *netdev, int qid, struct dpif_packet **buffers,
     return error;
 }
 
+int
+netdev_pop_header(struct netdev *netdev, struct dpif_packet **buffers, int cnt)
+{
+    return (netdev->netdev_class->pop_header
+             ? netdev->netdev_class->pop_header(netdev, buffers, cnt)
+             : EOPNOTSUPP);
+}
+
+int
+netdev_build_header(const struct netdev *netdev, struct ovs_action_push_tnl *data)
+{
+    if (netdev->netdev_class->build_header) {
+        return netdev->netdev_class->build_header(netdev, data);
+    }
+    return EOPNOTSUPP;
+}
+
+int
+netdev_push_header(const struct netdev *netdev,
+                   struct dpif_packet **buffers, int cnt,
+                   const struct ovs_action_push_tnl *data)
+{
+    if (netdev->netdev_class->push_header) {
+        return netdev->netdev_class->push_header(netdev, buffers, cnt, data);
+    } else {
+        return -EINVAL;
+    }
+}
+
 /* Registers with the poll loop to wake up from the next call to poll_block()
  * when the packet transmission queue has sufficient room to transmit a packet
  * with netdev_send().
diff --git a/lib/netdev.h b/lib/netdev.h
index fc4180a..0c35759 100644
--- a/lib/netdev.h
+++ b/lib/netdev.h
@@ -69,6 +69,7 @@ struct in_addr;
 struct in6_addr;
 struct smap;
 struct sset;
+struct ovs_action_push_tnl;
 
 /* Network device statistics.
  *
@@ -180,6 +181,13 @@ int netdev_send(struct netdev *, int qid, struct dpif_packet **, int cnt,
                 bool may_steal);
 void netdev_send_wait(struct netdev *, int qid);
 
+int netdev_build_header(const struct netdev *, struct ovs_action_push_tnl *data);
+int netdev_push_header(const struct netdev *netdev,
+                       struct dpif_packet **buffers, int cnt,
+                       const struct ovs_action_push_tnl *data);
+int netdev_pop_header(struct netdev *netdev, struct dpif_packet **buffers,
+                      int cnt);
+
 /* Hardware address. */
 int netdev_set_etheraddr(struct netdev *, const uint8_t mac[6]);
 int netdev_get_etheraddr(const struct netdev *, uint8_t mac[6]);
@@ -325,6 +333,7 @@ int netdev_dump_queue_stats(const struct netdev *,
                             netdev_dump_queue_stats_cb *, void *aux);
 
 enum { NETDEV_MAX_RX_BATCH = 256 };     /* Maximum number packets in rx_recv() batch. */
+extern struct seq *tnl_conf_seq;
 
 #ifdef  __cplusplus
 }
diff --git a/lib/odp-execute.c b/lib/odp-execute.c
index 230e6e1..9e785c0 100644
--- a/lib/odp-execute.c
+++ b/lib/odp-execute.c
@@ -412,6 +412,8 @@ odp_execute_actions(void *dp, struct dpif_packet **packets, int cnt, bool steal,
         switch ((enum ovs_action_attr) type) {
             /* These only make sense in the context of a datapath. */
         case OVS_ACTION_ATTR_OUTPUT:
+        case OVS_ACTION_ATTR_TUNNEL_PUSH:
+        case OVS_ACTION_ATTR_TUNNEL_POP:
         case OVS_ACTION_ATTR_USERSPACE:
         case OVS_ACTION_ATTR_RECIRC:
             if (dp_execute_action) {
diff --git a/lib/odp-util.c b/lib/odp-util.c
index 8f5ed08..2db8a00 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -24,18 +24,22 @@
 #include <netinet/icmp6.h>
 #include <stdlib.h>
 #include <string.h>
+
 #include "byte-order.h"
 #include "coverage.h"
 #include "dpif.h"
 #include "dynamic-string.h"
 #include "flow.h"
+#include "gre.h"
 #include "netlink.h"
 #include "ofpbuf.h"
 #include "packets.h"
 #include "simap.h"
 #include "timeval.h"
+#include "unaligned.h"
 #include "util.h"
 #include "vlog.h"
+#include "vxlan.h"
 
 VLOG_DEFINE_THIS_MODULE(odp_util);
 
@@ -74,6 +78,8 @@ odp_action_len(uint16_t type)
 
     switch ((enum ovs_action_attr) type) {
     case OVS_ACTION_ATTR_OUTPUT: return sizeof(uint32_t);
+    case OVS_ACTION_ATTR_TUNNEL_PUSH: return -2;
+    case OVS_ACTION_ATTR_TUNNEL_POP: return sizeof(uint32_t);
     case OVS_ACTION_ATTR_USERSPACE: return -2;
     case OVS_ACTION_ATTR_PUSH_VLAN: return sizeof(struct ovs_action_push_vlan);
     case OVS_ACTION_ATTR_POP_VLAN: return 0;
@@ -507,6 +513,88 @@ format_odp_hash_action(struct ds *ds, const struct ovs_action_hash *hash_act)
 }
 
 static void
+format_odp_tunnel_header(struct ds *ds, struct ovs_action_push_tnl *data)
+{
+    const struct eth_header *eth;
+    const struct ip_header *ip;
+    const void *l3;
+
+    eth = (const struct eth_header *)data->header;
+
+    l3 = eth + 1;
+    ip = (const struct ip_header *)l3;
+
+    /* Ethernet */
+    ds_put_format(ds, "header(size=%"PRIu8",type=%"PRIu8",eth(dst=",
+                  data->header_len, data->tnl_type);
+    ds_put_format(ds, ETH_ADDR_FMT, ETH_ADDR_ARGS(eth->eth_dst));
+    ds_put_format(ds, ",src=");
+    ds_put_format(ds, ETH_ADDR_FMT, ETH_ADDR_ARGS(eth->eth_src));
+    ds_put_format(ds, ",dl_type=0x%04"PRIx16"),", ntohs(eth->eth_type));
+
+    /* IPv4 */
+    ds_put_format(ds, "ipv4(src="IP_FMT",dst="IP_FMT",proto=%"PRIu8
+                  ",tos=%#"PRIx8",ttl=%"PRIu8",frag=%"PRIx16"),",
+                  IP_ARGS(get_16aligned_be32(&ip->ip_src)),
+                  IP_ARGS(get_16aligned_be32(&ip->ip_dst)),
+                  ip->ip_proto, ip->ip_tos,
+                  ip->ip_ttl,
+                  ip->ip_frag_off);
+
+    if (data->tnl_type == OVS_VPORT_TYPE_VXLAN) {
+        const struct vxlanhdr *vxh;
+        const struct udp_header *udp;
+
+        /* UDP */
+        udp = (const struct udp_header *) ((const char *)ip  + sizeof(*ip));
+        ds_put_format(ds, "udp(src=%"PRIu16",dst=%"PRIu16"),",
+                      ntohs(udp->udp_src), ntohs(udp->udp_dst));
+
+        /* VxLan */
+        vxh = (const struct vxlanhdr *)   ((const char *)udp + sizeof(*udp));
+        ds_put_format(ds, "vxlan(flags=%"PRIx32",vni=%"PRIx32")",
+                      vxh->vx_flags, vxh->vx_vni);
+    } else if (data->tnl_type == OVS_VPORT_TYPE_GRE) {
+        const struct gre_base_hdr *greh;
+        ovs_be32 *options;
+        void *l4;
+
+        l4 = ((uint8_t *)l3  + sizeof(struct ip_header));
+        greh = (const struct gre_base_hdr *) l4;
+
+        ds_put_format(ds, "gre((flags=%"PRIx16",proto=%"PRIx16")",
+                           greh->flags, greh->protocol);
+        options = (ovs_be32 *) (greh + 1);
+        if (greh->flags & GRE_CSUM) {
+            ds_put_format(ds, ",csum=%"PRIx32, *options);
+            options++;
+        }
+        if (greh->flags & GRE_KEY) {
+            ds_put_format(ds, ",key=%"PRIx32, htonl(*options));
+            options++;
+        }
+        if (greh->flags & GRE_SEQ) {
+            ds_put_format(ds, ",seq=%"PRIx32, *options);
+            options++;
+        }
+        ds_put_format(ds, ")");
+    }
+    ds_put_format(ds, "),");
+}
+
+static void
+format_odp_tunnel_push_action(struct ds *ds, const struct nlattr *attr)
+{
+    struct ovs_action_push_tnl *data;
+
+    data = (struct ovs_action_push_tnl *) nl_attr_get(attr);
+
+    ds_put_format(ds, "tnl_push(tnl_port(%"PRIu32"),", data->tnl_port);
+    format_odp_tunnel_header(ds, data);
+    ds_put_format(ds, "out_port(%"PRIu32")", data->out_port);
+}
+
+static void
 format_odp_action(struct ds *ds, const struct nlattr *a)
 {
     int expected_len;
@@ -526,6 +614,13 @@ format_odp_action(struct ds *ds, const struct nlattr *a)
     case OVS_ACTION_ATTR_OUTPUT:
         ds_put_format(ds, "%"PRIu32, nl_attr_get_u32(a));
         break;
+    case OVS_ACTION_ATTR_TUNNEL_POP:
+        ds_put_format(ds, "tnl_pop(%"PRIu32, nl_attr_get_u32(a));
+        ds_put_cstr(ds, ")");
+        break;
+    case OVS_ACTION_ATTR_TUNNEL_PUSH:
+        format_odp_tunnel_push_action(ds, a);
+        break;
     case OVS_ACTION_ATTR_USERSPACE:
         format_odp_userspace_action(ds, a);
         break;
@@ -741,6 +836,125 @@ parse_odp_userspace_action(const char *s, struct ofpbuf *actions)
 }
 
 static int
+ovs_parse_tnl_push(const char *s, struct ovs_action_push_tnl *data)
+{
+    struct eth_header *eth;
+    struct ip_header *ip;
+    struct udp_header *udp;
+    struct gre_base_hdr *greh;
+    uint16_t dl_type, udp_src, udp_dst;
+    ovs_be32 sip, dip;
+    uint32_t tnl_type = 0, header_len = 0;
+    void *l3, *l4;
+    int n = 0;
+
+    if (!ovs_scan(s, "tnl_push(tnl_port(%"SCNi32"),%n", &data->tnl_port, &n)) {
+        return 0;
+    }
+
+    eth = (struct eth_header *) data->header;
+    l3 = (data->header + sizeof *eth);
+    l4 = ((uint8_t *) l3 + sizeof (struct ip_header));
+    ip = (struct ip_header *) l3;
+    if (!ovs_scan(&s[n], "header(size=%"SCNi32",type=%"SCNi32","
+                         "eth(dst="ETH_ADDR_SCAN_FMT",%n", &data->tnl_type,
+                         &data->header_len,
+                         ETH_ADDR_SCAN_ARGS(eth->eth_dst), &n)) {
+        return -EINVAL;
+    }
+
+    if (!ovs_scan(&s[n], "src="ETH_ADDR_SCAN_FMT",%n",
+                  ETH_ADDR_SCAN_ARGS(eth->eth_src), &n)) {
+        return -EINVAL;
+    }
+    if (!ovs_scan(&s[n], "dl_type=%"SCNi16",%n", &dl_type, &n)) {
+        return -EINVAL;
+    }
+    eth->eth_type = htons(dl_type);
+
+    /* IPv4 */
+    if (!ovs_scan(&s[n], "ipv4(src="IP_SCAN_FMT",dst="IP_SCAN_FMT",proto=%"SCNi8
+                         ",tos=%"SCNi8",ttl=%"SCNi8",frag=%"SCNi16"),%n",
+                         IP_SCAN_ARGS(&sip),
+                         IP_SCAN_ARGS(&dip),
+                         &ip->ip_proto, &ip->ip_tos,
+                         &ip->ip_ttl, &ip->ip_frag_off, &n)) {
+        return -EINVAL;
+    }
+    put_16aligned_be32(&ip->ip_src, sip);
+    put_16aligned_be32(&ip->ip_dst, dip);
+
+    udp = (struct udp_header *) l4;
+    greh = (struct gre_base_hdr *) l4;
+
+    if (ovs_scan(&s[n], "udp(src=%"SCNi16",dst=%"SCNi16"),%n",
+                         &udp_src, &udp_dst, &n)) {
+        struct vxlanhdr *vxh;
+
+        udp->udp_src = htons(udp_src);
+        udp->udp_dst = htons(udp_dst);
+        udp->udp_len = 0;
+        udp->udp_csum = 0;
+
+        vxh = (struct vxlanhdr *) (udp + 1);
+        if (!ovs_scan(&s[n], "vxlan(flags=%"SCNi32",vni=%"SCNi32")),%n",
+                            &vxh->vx_flags, &vxh->vx_vni, &n)) {
+            return -EINVAL;
+        }
+        tnl_type = OVS_VPORT_TYPE_VXLAN;
+        header_len = sizeof *eth + sizeof *ip +
+                     sizeof *udp + sizeof *vxh;
+    } else if (ovs_scan(&s[n], "gre((flags=%"SCNi16",proto=%"SCNi16"),%n",
+                         &greh->flags, &greh->protocol, &n)){
+
+        ovs_be32 *options = (ovs_be32 *) (greh + 1);
+
+        if (greh->flags & GRE_CSUM) {
+            if (!ovs_scan(&s[n], ",csum=%"SCNi32"%n", options, &n)) {
+                return -EINVAL;
+            }
+
+            options++;
+        }
+        if (greh->flags & GRE_KEY) {
+            uint32_t key;
+
+            if (!ovs_scan(&s[n], ",seq=%"SCNi32"%n", &key, &n)) {
+                return -EINVAL;
+            }
+
+            *options = htonl(key);
+            options++;
+        }
+        if (greh->flags & GRE_SEQ) {
+            if (!ovs_scan(&s[n], ",seq=%"SCNi32"%n", options, &n)) {
+                return -EINVAL;
+            }
+            options++;
+        }
+        if (!ovs_scan(&s[n], ")%n", &n)) {
+            return -EINVAL;
+        }
+
+        tnl_type = OVS_VPORT_TYPE_GRE;
+        header_len = sizeof *eth + sizeof *ip +
+                     ((uint8_t *) options - (uint8_t *) greh);
+    } else {
+        return -EINVAL;
+    }
+
+    /* check tune meta data. */
+    if (data->tnl_type != tnl_type) {
+        return -EINVAL;
+    }
+    if (data->header_len != header_len) {
+        return -EINVAL;
+    }
+
+    return n;
+}
+
+static int
 parse_odp_action(const char *s, const struct simap *port_names,
                  struct ofpbuf *actions)
 {
@@ -887,6 +1101,28 @@ parse_odp_action(const char *s, const struct simap *port_names,
         }
     }
 
+    {
+        uint32_t port;
+        int n;
+
+        if (ovs_scan(s, "tnl_pop(%"SCNi32")%n", &port, &n)) {
+            nl_msg_put_u32(actions, OVS_ACTION_ATTR_TUNNEL_POP, port);
+            return n;
+        }
+    }
+
+    {
+        struct ovs_action_push_tnl data;
+        int n;
+
+        n = ovs_parse_tnl_push(s, &data);
+        if (n > 0) {
+            odp_put_tunnel_push_action(actions, &data);
+            return n;
+        } else if (n < 0) {
+            return n;
+        }
+    }
     return -EINVAL;
 }
 
@@ -3543,6 +3779,15 @@ odp_put_tunnel_action(const struct flow_tnl *tunnel,
     tun_key_to_attr(odp_actions, tunnel);
     nl_msg_end_nested(odp_actions, offset);
 }
+
+void
+odp_put_tunnel_push_action(struct ofpbuf *odp_actions,
+                           struct ovs_action_push_tnl *data)
+{
+    nl_msg_put_unspec(odp_actions, OVS_ACTION_ATTR_TUNNEL_PUSH,
+                      data, sizeof (*data));
+}
+
 
 /* The commit_odp_actions() function and its helpers. */
 
diff --git a/lib/odp-util.h b/lib/odp-util.h
index 11b54dd..5e91fe0 100644
--- a/lib/odp-util.h
+++ b/lib/odp-util.h
@@ -254,4 +254,6 @@ size_t odp_put_userspace_action(uint32_t pid,
 void odp_put_tunnel_action(const struct flow_tnl *tunnel,
                            struct ofpbuf *odp_actions);
 
+void odp_put_tunnel_push_action(struct ofpbuf *odp_actions,
+                                struct ovs_action_push_tnl *data);
 #endif /* odp-util.h */
diff --git a/lib/ofpbuf.h b/lib/ofpbuf.h
index 53c43fb..329af89 100644
--- a/lib/ofpbuf.h
+++ b/lib/ofpbuf.h
@@ -422,6 +422,14 @@ static inline void ofpbuf_set_size(struct ofpbuf *b, uint32_t v)
 }
 #endif
 
+static inline void ofpbuf_reset_packet(struct ofpbuf *b, int off)
+{
+    ofpbuf_set_size(b, ofpbuf_size(b) - off);
+    ofpbuf_set_data(b, (void *) ((unsigned char *) b->frame + off));
+    b->frame = NULL;
+    b->l2_5_ofs = b->l3_ofs = b->l4_ofs = UINT16_MAX;
+}
+
 #ifdef  __cplusplus
 }
 #endif
diff --git a/lib/ovs-router.c b/lib/ovs-router.c
index 6454420..1af930c 100644
--- a/lib/ovs-router.c
+++ b/lib/ovs-router.c
@@ -33,6 +33,7 @@
 #include "dynamic-string.h"
 #include "netdev.h"
 #include "packets.h"
+#include "seq.h"
 #include "ovs-router.h"
 #include "unixctl.h"
 #include "util.h"
@@ -114,6 +115,7 @@ ovs_router_insert__(uint8_t priority, ovs_be32 ip_dst, uint8_t plen,
         /* An old rule with the same match was displaced. */
         ovsrcu_postpone(rt_entry_free, ovs_router_entry_cast(cr));
     }
+    seq_change(tnl_conf_seq);
 }
 
 void
@@ -215,6 +217,7 @@ ovs_router_del(struct unixctl_conn *conn, int argc OVS_UNUSED,
 
         if (rt_entry_delete(plen + 32, ip, plen)) {
             unixctl_command_reply(conn, "OK");
+            seq_change(tnl_conf_seq);
         } else {
             unixctl_command_reply(conn, "Not found");
         }
@@ -259,6 +262,7 @@ ovs_router_flush(void)
             classifier_remove(&cls, &rt->cr);
         }
     }
+    seq_change(tnl_conf_seq);
 }
 
 /* May not be called more than once. */
diff --git a/lib/packets.c b/lib/packets.c
index 65d8109..af99e3b 100644
--- a/lib/packets.c
+++ b/lib/packets.c
@@ -956,3 +956,38 @@ packet_format_tcp_flags(struct ds *s, uint16_t tcp_flags)
         ds_put_cstr(s, "[800]");
     }
 }
+
+#define ARP_PACKET_SIZE  (2 + ETH_HEADER_LEN + VLAN_HEADER_LEN + \
+                          ARP_ETH_HEADER_LEN)
+
+void
+compose_arp(struct ofpbuf *b, const uint8_t eth_src[ETH_ADDR_LEN],
+            ovs_be32 ip_src, ovs_be32 ip_dst)
+{
+    struct eth_header *eth;
+    struct arp_eth_header *arp;
+
+    ofpbuf_clear(b);
+    ofpbuf_prealloc_tailroom(b, ARP_PACKET_SIZE);
+    ofpbuf_reserve(b, 2 + VLAN_HEADER_LEN);
+
+    eth = ofpbuf_put_uninit(b, sizeof *eth);
+    memcpy(eth->eth_dst, eth_addr_broadcast, ETH_ADDR_LEN);
+    memcpy(eth->eth_src, eth_src, ETH_ADDR_LEN);
+    eth->eth_type = htons(ETH_TYPE_ARP);
+
+    arp = ofpbuf_put_uninit(b, sizeof *arp);
+    arp->ar_hrd = htons(ARP_HRD_ETHERNET);
+    arp->ar_pro = htons(ARP_PRO_IP);
+    arp->ar_hln = sizeof arp->ar_sha;
+    arp->ar_pln = sizeof arp->ar_spa;
+    arp->ar_op = htons(ARP_OP_REQUEST);
+    memcpy(arp->ar_sha, eth_src, ETH_ADDR_LEN);
+    memset(arp->ar_tha, 0, ETH_ADDR_LEN);
+
+    put_16aligned_be32(&arp->ar_spa, ip_src);
+    put_16aligned_be32(&arp->ar_tpa, ip_dst);
+
+    ofpbuf_set_frame(b, eth);
+    ofpbuf_set_l3(b, arp);
+}
diff --git a/lib/packets.h b/lib/packets.h
index 26c6ff1..1379366 100644
--- a/lib/packets.h
+++ b/lib/packets.h
@@ -236,6 +236,7 @@ ovs_be32 set_mpls_lse_values(uint8_t ttl, uint8_t tc, uint8_t bos,
 
 #define ETH_TYPE_IP            0x0800
 #define ETH_TYPE_ARP           0x0806
+#define ETH_TYPE_TEB           0x6558
 #define ETH_TYPE_VLAN_8021Q    0x8100
 #define ETH_TYPE_VLAN          ETH_TYPE_VLAN_8021Q
 #define ETH_TYPE_VLAN_8021AD   0x88a8
@@ -498,6 +499,7 @@ struct ip_header {
     ovs_16aligned_be32 ip_src;
     ovs_16aligned_be32 ip_dst;
 };
+
 BUILD_ASSERT_DECL(IP_HEADER_LEN == sizeof(struct ip_header));
 
 #define ICMP_HEADER_LEN 8
@@ -545,12 +547,13 @@ struct sctp_header {
 BUILD_ASSERT_DECL(SCTP_HEADER_LEN == sizeof(struct sctp_header));
 
 #define UDP_HEADER_LEN 8
+OVS_PACKED(
 struct udp_header {
     ovs_be16 udp_src;
     ovs_be16 udp_dst;
     ovs_be16 udp_len;
     ovs_be16 udp_csum;
-};
+});
 BUILD_ASSERT_DECL(UDP_HEADER_LEN == sizeof(struct udp_header));
 
 #define TCP_FIN 0x001
@@ -735,5 +738,7 @@ void packet_set_sctp_port(struct ofpbuf *, ovs_be16 src, ovs_be16 dst);
 
 void packet_format_tcp_flags(struct ds *, uint16_t);
 const char *packet_tcp_flag_to_string(uint32_t flag);
+void compose_arp(struct ofpbuf *b, const uint8_t eth_src[ETH_ADDR_LEN],
+                 ovs_be32 ip_src, ovs_be32 ip_dst);
 
 #endif /* packets.h */
diff --git a/lib/tnl-arp-cache.c b/lib/tnl-arp-cache.c
new file mode 100644
index 0000000..6053eb6
--- /dev/null
+++ b/lib/tnl-arp-cache.c
@@ -0,0 +1,212 @@
+/*
+ * Copyright (c) 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.
+ * 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 <inttypes.h>
+#include <stdlib.h>
+
+#include "bitmap.h"
+#include "cmap.h"
+#include "coverage.h"
+#include "dpif-netdev.h"
+#include "dynamic-string.h"
+#include "errno.h"
+#include "flow.h"
+#include "netdev.h"
+#include "ovs-thread.h"
+#include "packets.h"
+#include "packet-dpif.h"
+#include "poll-loop.h"
+#include "seq.h"
+#include "timeval.h"
+#include "tnl-arp-cache.h"
+#include "unaligned.h"
+#include "unixctl.h"
+#include "util.h"
+#include "vlog.h"
+
+#define ARP_ENTRY_DEFAULT_IDLE_TIME  3000
+
+struct tnl_arp_entry {
+    struct cmap_node cmap_node;
+    ovs_be32 ip;
+    uint8_t mac[ETH_ADDR_LEN];
+    time_t expires;             /* Expiration time. */
+    char br_name[IFNAMSIZ];
+};
+
+static struct cmap table;
+static struct ovs_mutex mutex = OVS_MUTEX_INITIALIZER;
+
+static struct tnl_arp_entry *
+__tnl_arp_lookup(const char br_name[IFNAMSIZ], ovs_be32 dst)
+{
+    struct tnl_arp_entry *arp;
+
+    CMAP_FOR_EACH_WITH_HASH (arp, cmap_node, dst, &table) {
+        if (arp->ip == dst && !strncmp(arp->br_name, br_name, IFNAMSIZ)) {
+            return arp;
+        }
+    }
+    return NULL;
+}
+
+int
+tnl_arp_lookup(const char br_name[IFNAMSIZ], ovs_be32 dst,
+               uint8_t mac[ETH_ADDR_LEN])
+{
+    struct tnl_arp_entry *arp;
+    int res = ENOENT;
+
+    arp = __tnl_arp_lookup(br_name, dst);
+    if (arp) {
+            memcpy(mac, arp->mac, ETH_ADDR_LEN);
+            res = 0;
+    }
+
+    return res;
+}
+
+static void
+arp_entry_free(struct tnl_arp_entry *arp)
+{
+    free(arp);
+}
+
+static void
+tnl_arp_delete(struct tnl_arp_entry *arp)
+{
+    cmap_remove(&table, &arp->cmap_node, arp->ip);
+    ovsrcu_postpone(arp_entry_free, arp);
+}
+
+int
+tnl_arp_snoop(const struct flow *flow, struct flow_wildcards *wc,
+              const char name[IFNAMSIZ])
+{
+    struct tnl_arp_entry *arp;
+
+    if (flow->dl_type != htons(ETH_TYPE_ARP)) {
+        return EINVAL;
+    }
+
+    memset(&wc->masks.dl_type, 0xff, sizeof wc->masks.dl_type);
+    memset(&wc->masks.nw_proto, 0xff, sizeof wc->masks.nw_proto);
+    memset(&wc->masks.nw_src, 0xff, sizeof wc->masks.nw_src);
+    memset(&wc->masks.nw_dst, 0xff, sizeof wc->masks.nw_dst);
+    memset(&wc->masks.arp_sha, 0xff, sizeof wc->masks.arp_sha);
+    memset(&wc->masks.arp_tha, 0xff, sizeof wc->masks.arp_tha);
+
+    ovs_mutex_lock(&mutex);
+    arp = __tnl_arp_lookup(name, flow->nw_src);
+    if (arp) {
+        if (!memcmp(arp->mac, flow->arp_sha, ETH_ADDR_LEN)) {
+            arp->expires = time_now() + ARP_ENTRY_DEFAULT_IDLE_TIME;
+            ovs_mutex_unlock(&mutex);
+            return 0;
+        }
+        tnl_arp_delete(arp);
+    }
+
+    arp = xmalloc(sizeof *arp);
+
+    arp->ip = flow->nw_src;
+    memcpy(arp->mac, flow->arp_sha, ETH_ADDR_LEN);
+    arp->expires = time_now() + ARP_ENTRY_DEFAULT_IDLE_TIME;
+    strncpy(arp->br_name, name, IFNAMSIZ);
+    cmap_insert(&table, &arp->cmap_node, arp->ip);
+    ovs_mutex_unlock(&mutex);
+    return 0;
+}
+
+void
+tnl_arp_cache_run(void)
+{
+    struct tnl_arp_entry *arp;
+    bool changed = false;
+
+    ovs_mutex_lock(&mutex);
+    CMAP_FOR_EACH(arp, cmap_node, &table) {
+        if (arp->expires <= time_now()) {
+             tnl_arp_delete(arp);
+             changed = true;
+        }
+    }
+    ovs_mutex_unlock(&mutex);
+
+    if (changed) {
+        seq_change(tnl_conf_seq);
+    }
+}
+
+static void
+tnl_arp_cache_flush(struct unixctl_conn *conn OVS_UNUSED, int argc OVS_UNUSED,
+                    const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED)
+{
+    struct tnl_arp_entry *arp;
+
+    ovs_mutex_lock(&mutex);
+    CMAP_FOR_EACH(arp, cmap_node, &table) {
+          tnl_arp_delete(arp);
+    }
+    ovs_mutex_unlock(&mutex);
+    seq_change(tnl_conf_seq);
+    unixctl_command_reply(conn, "OK");
+}
+
+#define MAX_IP_ADDR_LEN 16
+
+static void
+tnl_arp_cache_show(struct unixctl_conn *conn, int argc OVS_UNUSED,
+                   const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED)
+{
+    struct ds ds = DS_EMPTY_INITIALIZER;
+    struct tnl_arp_entry *arp;
+
+    ds_put_cstr(&ds, "IP               MAC                 Bridge\n");
+    ds_put_cstr(&ds, "=============================================\n");
+    ovs_mutex_lock(&mutex);
+    CMAP_FOR_EACH(arp, cmap_node, &table) {
+        int start_len, need_ws;
+
+        start_len = ds.length;
+        ds_put_format(&ds, IP_FMT, IP_ARGS(arp->ip));
+
+        need_ws = MAX_IP_ADDR_LEN - (ds.length - start_len);
+
+        while (need_ws >= 0) {
+            ds_put_format(&ds, " ");
+            need_ws--;
+        }
+
+        ds_put_format(&ds, ETH_ADDR_FMT"   %s\n",
+                      ETH_ADDR_ARGS(arp->mac), arp->br_name);
+
+    }
+    ovs_mutex_unlock(&mutex);
+    unixctl_command_reply(conn, ds_cstr(&ds));
+    ds_destroy(&ds);
+}
+
+void
+tnl_arp_cache_init(void)
+{
+    cmap_init(&table);
+
+    /* XXX: Add documentation for these commands. */
+    unixctl_command_register("tnl/arp/show", "", 0, 0, tnl_arp_cache_show, NULL);
+    unixctl_command_register("tnl/arp/flush", "", 0, 0, tnl_arp_cache_flush, NULL);
+}
diff --git a/lib/tnl-arp-cache.h b/lib/tnl-arp-cache.h
new file mode 100644
index 0000000..f68c3b4
--- /dev/null
+++ b/lib/tnl-arp-cache.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 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.
+ * 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.
+ */
+
+#ifndef OVS_TNL_ARP_CACHE_H
+#define OVS_TNL_ARP_CACHE_H 1
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <net/if.h>
+#include <sys/socket.h>
+
+#include "flow.h"
+#include "netdev.h"
+#include "packets.h"
+#include "util.h"
+
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
+int tnl_arp_snoop(const struct flow *flow, struct flow_wildcards *wc,
+                  const char dev_name[]);
+int tnl_arp_lookup(const char dev_name[], ovs_be32 dst, uint8_t mac[ETH_ADDR_LEN]);
+void tnl_arp_cache_init(void);
+void tnl_arp_cache_run(void);
+
+#ifdef  __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/tnl-ports.c b/lib/tnl-ports.c
new file mode 100644
index 0000000..981b4c1
--- /dev/null
+++ b/lib/tnl-ports.c
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 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.
+ * 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 <stddef.h>
+#include <stdint.h>
+
+#include "classifier.h"
+#include "fat-rwlock.h"
+#include "dynamic-string.h"
+#include "errno.h"
+#include "hash.h"
+#include "ofpbuf.h"
+#include "ovs-thread.h"
+#include "odp-util.h"
+#include "tnl-arp-cache.h"
+#include "tnl-ports.h"
+#include "unixctl.h"
+#include "util.h"
+
+static struct classifier cls;   /* Tunnel ports. */
+
+struct tunnel_port {
+    struct cls_rule cr;
+    odp_port_t portno;
+    struct ovs_refcount ref_cnt;
+    char dev_name[IFNAMSIZ];
+};
+
+static struct tunnel_port *
+tnl_port_cast(const struct cls_rule *cr)
+{
+    if (offsetof(struct tunnel_port, cr) == 0) {
+        return CONTAINER_OF(cr, struct tunnel_port, cr);
+    } else {
+        return cr ? CONTAINER_OF(cr, struct tunnel_port, cr) : NULL;
+    }
+}
+
+static void
+tnl_port_free(struct tunnel_port *p)
+{
+    cls_rule_destroy(&p->cr);
+    free(p);
+}
+
+void
+tnl_port_map_insert(odp_port_t port, ovs_be32 ip_dst, ovs_be16 udp_port,
+                    const char dev_name[])
+{
+    const struct cls_rule *cr;
+    struct tunnel_port *p;
+    struct match match;
+
+    memset(&match, 0, sizeof match);
+
+    match.flow.dl_type = htons(ETH_TYPE_IP);
+    if (udp_port) {
+        match.flow.nw_proto = IPPROTO_UDP;
+    } else {
+        match.flow.nw_proto = IPPROTO_GRE;
+    }
+
+    match.flow.tp_dst = udp_port;
+
+    /* When matching on incoming flow from remove tnl end point,
+     * our dst ip address is source ip for them. */
+    match.flow.nw_src = ip_dst;
+
+    do {
+        cr = classifier_lookup(&cls, &match.flow, NULL);
+        p = tnl_port_cast(cr);
+        /* Try again if the rule was released before we get the reference. */
+    } while (p && !ovs_refcount_try_ref_rcu(&p->ref_cnt));
+
+    if (p) {
+        return; /* Added refcount of an existing port. */
+    }
+
+    p = xzalloc(sizeof *p);
+    p->portno = port;
+
+    match.wc.masks.dl_type = OVS_BE16_MAX;
+    match.wc.masks.nw_proto = 0xff;
+    match.wc.masks.nw_frag = 0xff;      /* XXX: No fragments support. */
+    match.wc.masks.tp_dst = OVS_BE16_MAX;
+    match.wc.masks.nw_src = OVS_BE32_MAX;
+
+    cls_rule_init(&p->cr, &match, 0);   /* Priority == 0. */
+    ovs_refcount_init(&p->ref_cnt);
+    strncpy(p->dev_name, dev_name, IFNAMSIZ);
+
+    classifier_insert(&cls, &p->cr);
+}
+
+static void
+tnl_port_unref(struct cls_rule *cr)
+{
+    struct tunnel_port *p = tnl_port_cast(cr);
+
+    if (cr && ovs_refcount_unref_relaxed(&p->ref_cnt) == 1) {
+        if (classifier_remove(&cls, cr)) {
+            ovsrcu_postpone(tnl_port_free, p);
+        }
+    }
+}
+
+void
+tnl_port_map_delete(ovs_be32 ip_dst, ovs_be16 udp_port)
+{
+    struct cls_rule *cr;
+    struct flow flow;
+
+    memset(&flow, 0, sizeof flow);
+    flow.dl_type = htons(ETH_TYPE_IP);
+    if (udp_port) {
+        flow.nw_proto = IPPROTO_UDP;
+    } else {
+        flow.nw_proto = IPPROTO_GRE;
+    }
+    flow.tp_dst = udp_port;
+    flow.nw_src = ip_dst;
+
+    cr = classifier_lookup(&cls, &flow, NULL);
+    tnl_port_unref(cr);
+}
+
+odp_port_t
+tnl_port_map_lookup(const struct flow *flow, struct flow_wildcards *wc)
+{
+    const struct cls_rule *cr = classifier_lookup(&cls, flow, wc);
+
+    return (cr) ? tnl_port_cast(cr)->portno : ODPP_NONE;
+}
+
+static void
+tnl_port_show(struct unixctl_conn *conn, int argc OVS_UNUSED,
+              const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED)
+{
+    struct ds ds = DS_EMPTY_INITIALIZER;
+    const struct tunnel_port *p;
+
+    ds_put_format(&ds, "Listening ports:\n");
+    CLS_FOR_EACH(p, cr, &cls) {
+        struct odputil_keybuf keybuf;
+        struct odputil_keybuf maskbuf;
+        struct flow flow;
+        const struct nlattr *key, *mask;
+        size_t key_len, mask_len;
+        struct flow_wildcards wc;
+        struct ofpbuf buf;
+
+        ds_put_format(&ds, "%s (%"PRIu32") : ", p->dev_name, p->portno);
+        minimask_expand(&p->cr.match.mask, &wc);
+        miniflow_expand(&p->cr.match.flow, &flow);
+
+        /* Key. */
+        ofpbuf_use_stack(&buf, &keybuf, sizeof keybuf);
+        odp_flow_key_from_flow(&buf, &flow, &wc.masks,
+                               flow.in_port.odp_port, true);
+        key = ofpbuf_data(&buf);
+        key_len = ofpbuf_size(&buf);
+        /* mask*/
+        ofpbuf_use_stack(&buf, &maskbuf, sizeof maskbuf);
+        odp_flow_key_from_mask(&buf, &wc.masks, &flow,
+                               odp_to_u32(wc.masks.in_port.odp_port),
+                               SIZE_MAX, false);
+        mask = ofpbuf_data(&buf);
+        mask_len = ofpbuf_size(&buf);
+
+        /* build string. */
+        odp_flow_format(key, key_len, mask, mask_len, NULL, &ds, false);
+        ds_put_format(&ds, "\n");
+    }
+    unixctl_command_reply(conn, ds_cstr(&ds));
+    ds_destroy(&ds);
+}
+
+void
+tnl_port_map_init(void)
+{
+    classifier_init(&cls, flow_segment_u32s);
+    unixctl_command_register("tnl/ports/show", "", 0, 0, tnl_port_show, NULL);
+}
diff --git a/lib/tnl-ports.h b/lib/tnl-ports.h
new file mode 100644
index 0000000..bc4c954
--- /dev/null
+++ b/lib/tnl-ports.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 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.
+ * 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.
+ */
+
+#ifndef OVS_TNL_PORT_H
+#define OVS_TNL_PORT_H 1
+
+#include <errno.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <net/if.h>
+#include <sys/socket.h>
+
+#include "flow.h"
+#include "packets.h"
+#include "util.h"
+
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
+odp_port_t tnl_port_map_lookup(const struct flow *flow,
+                           struct flow_wildcards *wc);
+
+void tnl_port_map_insert(odp_port_t port, ovs_be32 ip_dst, ovs_be16 udp_port,
+                         const char dev_name[]);
+
+void tnl_port_map_delete(ovs_be32 ip_dst OVS_UNUSED, ovs_be16 udp_port OVS_UNUSED);
+
+void tnl_port_map_init(void);
+
+#ifdef  __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/vxlan.h b/lib/vxlan.h
new file mode 100644
index 0000000..c847bdb
--- /dev/null
+++ b/lib/vxlan.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 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.
+ * 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.
+ */
+
+#ifndef NETDEV_VXLAN_H
+#define NETDEV_VXLAN_H 1
+
+#include <stddef.h>
+#include <stdint.h>
+#include "list.h"
+#include "packets.h"
+#include "util.h"
+#include "netdev-dpdk.h"
+
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
+
+#define VXLAN_FLAGS 0x08000000  /* struct vxlanhdr.vx_flags required value. */
+
+/* VXLAN protocol header */
+OVS_PACKED(
+struct vxlanhdr {
+        ovs_be32 vx_flags;
+        ovs_be32 vx_vni;
+});
+
+#define VXLAN_HLEN (sizeof(struct eth_header) + \
+                    sizeof(struct ip_header)  + \
+                    sizeof(struct udp_header) + \
+                    sizeof(struct vxlanhdr))
+
+#ifdef  __cplusplus
+}
+#endif
+
+#endif
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index 48576ad..ce0e482 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -17,7 +17,12 @@
 #include "ofproto/ofproto-dpif-xlate.h"
 
 #include <errno.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
 
+#include "tnl-arp-cache.h"
 #include "bfd.h"
 #include "bitmap.h"
 #include "bond.h"
@@ -48,6 +53,8 @@
 #include "ofproto/ofproto-dpif.h"
 #include "ofproto/ofproto-provider.h"
 #include "packet-dpif.h"
+#include "ovs-router.h"
+#include "tnl-ports.h"
 #include "tunnel.h"
 #include "vlog.h"
 
@@ -2139,6 +2146,10 @@ xlate_normal(struct xlate_ctx *ctx)
         return;
     }
 
+    if (ovs_native_tunneling_is_on(ctx->xbridge->ofproto)) {
+        tnl_arp_snoop(flow, wc, ctx->xbridge->name);
+    }
+
     /* Learn source MAC. */
     if (ctx->xin->may_learn) {
         update_learning_table(ctx->xbridge, flow, wc, vlan, in_xbundle);
@@ -2475,6 +2486,116 @@ process_special(struct xlate_ctx *ctx, const struct flow *flow,
     }
 }
 
+static int
+tnl_route_lookup_flow(const struct flow *oflow,
+                      ovs_be32 *ip, struct xport **out_port)
+{
+    char out_dev[IFNAMSIZ];
+    struct xbridge *xbridge;
+    struct xlate_cfg *xcfg;
+    ovs_be32 gw;
+
+    if (!ovs_router_lookup(oflow->tunnel.ip_dst, out_dev, &gw)) {
+        return -ENOENT;
+    }
+
+    if (gw) {
+        *ip = gw;
+    } else {
+        *ip = oflow->tunnel.ip_dst;
+    }
+
+    xcfg = ovsrcu_get(struct xlate_cfg *, &xcfgp);
+    ovs_assert(xcfg);
+
+    HMAP_FOR_EACH (xbridge, hmap_node, &xcfg->xbridges) {
+        if (!strncmp(xbridge->name, out_dev, IFNAMSIZ)) {
+            struct xport *port;
+
+            HMAP_FOR_EACH (port, ofp_node, &xbridge->xports) {
+                if (!strncmp(netdev_get_name(port->netdev), out_dev, IFNAMSIZ)) {
+                    *out_port = port;
+                    return 0;
+                }
+            }
+        }
+    }
+    return -ENOENT;
+}
+
+static int
+xlate_flood_packet(struct xbridge *xbridge, struct ofpbuf *packet)
+{
+    struct ofpact_output output;
+    struct flow flow;
+
+    ofpact_init(&output.ofpact, OFPACT_OUTPUT, sizeof output);
+    /* Use OFPP_NONE as the in_port to avoid special packet processing. */
+    flow_extract(packet, NULL, &flow);
+    flow.in_port.ofp_port = OFPP_NONE;
+    output.port = OFPP_FLOOD;
+    output.max_len = 0;
+
+    return ofproto_dpif_execute_actions(xbridge->ofproto, &flow, NULL,
+                                        &output.ofpact, sizeof output,
+                                        packet);
+}
+
+static void
+tnl_send_arp_request(const struct xport *out_dev, const uint8_t eth_src[ETH_ADDR_LEN],
+                     ovs_be32 ip_src, ovs_be32 ip_dst)
+{
+    struct xbridge *xbridge = out_dev->xbridge;
+    struct ofpbuf packet;
+
+    ofpbuf_init(&packet, 0);
+    compose_arp(&packet, eth_src, ip_src, ip_dst);
+
+    xlate_flood_packet(xbridge, &packet);
+    ofpbuf_uninit(&packet);
+}
+
+static int
+build_tunnel_send(const struct xlate_ctx *ctx, const struct xport *xport,
+                  const struct flow *flow, odp_port_t tunnel_odp_port)
+{
+    struct ovs_action_push_tnl tnl_push_data;
+    struct xport *out_dev = NULL;
+    ovs_be32 s_ip, d_ip = 0;
+    uint8_t smac[ETH_ADDR_LEN];
+    bool send_arp;
+    int res;
+
+    res = tnl_route_lookup_flow(flow, &d_ip, &out_dev);
+    if (res) {
+        return res;
+    }
+
+    /* Use mac addr of bridge port of the peer. */
+    res = netdev_get_etheraddr(out_dev->netdev, smac);
+    if (res) {
+        return res;
+    }
+    res = netdev_get_in4(out_dev->netdev, (struct in_addr *) &s_ip, NULL);
+    if (res) {
+        return res;
+    }
+
+    res = tnl_port_build_header(xport->ofport, d_ip, flow,
+                                out_dev->xbridge->name, smac, s_ip,
+                                &tnl_push_data, &send_arp);
+    if (res) {
+        if (send_arp) {
+            tnl_send_arp_request(out_dev, smac, s_ip, d_ip);
+        }
+        return res;
+    }
+    tnl_push_data.tnl_port = tunnel_odp_port;
+    tnl_push_data.out_port = out_dev->odp_port;
+    odp_put_tunnel_push_action(ctx->xout->odp_actions, &tnl_push_data);
+    return 0;
+}
+
 static void
 compose_output_action__(struct xlate_ctx *ctx, ofp_port_t ofp_port,
                         bool check_stp)
@@ -2619,9 +2740,15 @@ compose_output_action__(struct xlate_ctx *ctx, ofp_port_t ofp_port,
             entry->u.dev.tx = netdev_ref(xport->netdev);
         }
         out_port = odp_port;
-        commit_odp_tunnel_action(flow, &ctx->base_flow,
-                                 ctx->xout->odp_actions);
-        flow->tunnel = flow_tnl; /* Restore tunnel metadata */
+        if (ovs_native_tunneling_is_on(ctx->xbridge->ofproto)) {
+            build_tunnel_send(ctx, xport, flow, odp_port);
+            flow->tunnel = flow_tnl; /* Restore tunnel metadata */
+            goto out;
+        } else {
+            commit_odp_tunnel_action(flow, &ctx->base_flow,
+                                     ctx->xout->odp_actions);
+            flow->tunnel = flow_tnl; /* Restore tunnel metadata */
+        }
     } else {
         odp_port = xport->odp_port;
         out_port = odp_port;
@@ -2641,7 +2768,7 @@ compose_output_action__(struct xlate_ctx *ctx, ofp_port_t ofp_port,
     if (out_port != ODPP_NONE) {
         ctx->xout->slow |= commit_odp_actions(flow, &ctx->base_flow,
                                               ctx->xout->odp_actions,
-                                              &ctx->xout->wc,
+                                              wc,
                                               ctx->xbridge->masked_set_action);
 
         if (ctx->use_recirc) {
@@ -2659,6 +2786,19 @@ compose_output_action__(struct xlate_ctx *ctx, ofp_port_t ofp_port,
             nl_msg_put_u32(ctx->xout->odp_actions, OVS_ACTION_ATTR_RECIRC,
                            xr->recirc_id);
         } else {
+            /* XXX: Write better Filter for tunnel port. We can use inport
+             * int tunnel-port flow to avoid these checks completely. */
+            if (ofp_port == OFPP_LOCAL &&
+                ovs_native_tunneling_is_on(ctx->xbridge->ofproto)) {
+                odp_port_t odp_tnl_port;
+
+                odp_tnl_port = tnl_port_map_lookup(flow, wc);
+                if (odp_tnl_port != ODPP_NONE) {
+                    nl_msg_put_odp_port(ctx->xout->odp_actions, OVS_ACTION_ATTR_TUNNEL_POP,
+                                        odp_tnl_port);
+                    goto out;
+                }
+            }
             add_ipfix_output_action(ctx, out_port);
             nl_msg_put_odp_port(ctx->xout->odp_actions, OVS_ACTION_ATTR_OUTPUT,
                                 out_port);
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index d965d38..8bb3fd3 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -59,6 +59,7 @@
 #include "ofproto-dpif-upcall.h"
 #include "ofproto-dpif-xlate.h"
 #include "poll-loop.h"
+#include "ovs-router.h"
 #include "seq.h"
 #include "simap.h"
 #include "smap.h"
@@ -280,6 +281,10 @@ struct dpif_backer {
     /* Maximum number of MPLS label stack entries that the datapath supports
      * in a match */
     size_t max_mpls_depth;
+
+    /* True if the datapath supports tnl_push and pop actions. */
+    bool enable_tnl_push_pop;
+    struct atomic_count tnl_count;
 };
 
 /* All existing ofproto_backer instances, indexed by ofproto->up.type. */
@@ -341,7 +346,8 @@ struct ofproto_dpif {
 /* All existing ofproto_dpif instances, indexed by ->up.name. */
 static struct hmap all_ofproto_dpifs = HMAP_INITIALIZER(&all_ofproto_dpifs);
 
-static void ofproto_dpif_unixctl_init(void);
+static bool ofproto_use_tnl_push_pop = true;
+static void ofproto_unixctl_init(void);
 
 static inline struct ofproto_dpif *
 ofproto_dpif_cast(const struct ofproto *ofproto)
@@ -508,7 +514,11 @@ type_run(const char *type)
         return 0;
     }
 
-    dpif_run(backer->dpif);
+
+    if (dpif_run(backer->dpif)) {
+        backer->need_revalidate = REV_RECONFIGURE;
+    }
+
     udpif_run(backer->udpif);
 
     /* If vswitchd started with other_config:flow_restore_wait set as "true",
@@ -581,7 +591,8 @@ type_run(const char *type)
 
                 iter->odp_port = node ? u32_to_odp(node->data) : ODPP_NONE;
                 if (tnl_port_reconfigure(iter, iter->up.netdev,
-                                         iter->odp_port)) {
+                                         iter->odp_port,
+                                         ovs_native_tunneling_is_on(ofproto), dp_port)) {
                     backer->need_revalidate = REV_RECONFIGURE;
                 }
             }
@@ -949,6 +960,9 @@ open_dpif_backer(const char *type, struct dpif_backer **backerp)
     backer->masked_set_action = check_masked_set_action(backer);
     backer->rid_pool = recirc_id_pool_create();
 
+    backer->enable_tnl_push_pop = dpif_support_tnl_push_pop(backer->dpif);
+    atomic_count_init(&backer->tnl_count, 0);
+
     error = dpif_recv_set(backer->dpif, backer->recv_set_enable);
     if (error) {
         VLOG_ERR("failed to listen on datapath of type %s: %s",
@@ -969,6 +983,14 @@ open_dpif_backer(const char *type, struct dpif_backer **backerp)
     return error;
 }
 
+bool
+ovs_native_tunneling_is_on(struct ofproto_dpif *ofproto)
+{
+
+    return ofproto_use_tnl_push_pop && ofproto->backer->enable_tnl_push_pop &&
+           atomic_count_get(&ofproto->backer->tnl_count);
+}
+
 /* Tests whether 'backer''s datapath supports recirculation.  Only newer
  * datapaths support OVS_KEY_ATTR_RECIRC_ID in keys.  We need to disable some
  * features on older datapaths that don't support this feature.
@@ -1224,7 +1246,8 @@ construct(struct ofproto *ofproto_)
 
     guarded_list_init(&ofproto->pins);
 
-    ofproto_dpif_unixctl_init();
+    ofproto_unixctl_init();
+    ovs_router_unixctl_register();
 
     hmap_init(&ofproto->vlandev_map);
     hmap_init(&ofproto->realdev_vid_map);
@@ -1516,7 +1539,6 @@ run(struct ofproto *ofproto_)
             }
         }
     }
-
     return 0;
 }
 
@@ -1668,7 +1690,9 @@ port_construct(struct ofport *port_)
     port->odp_port = dpif_port.port_no;
 
     if (netdev_get_tunnel_config(netdev)) {
-        tnl_port_add(port, port->up.netdev, port->odp_port);
+        atomic_count_inc(&ofproto->backer->tnl_count);
+        tnl_port_add(port, port->up.netdev, port->odp_port,
+                     ovs_native_tunneling_is_on(ofproto), namebuf);
         port->is_tunnel = true;
         if (ofproto->ipfix) {
            dpif_ipfix_add_tunnel_port(ofproto->ipfix, port_, port->odp_port);
@@ -1734,6 +1758,10 @@ port_destruct(struct ofport *port_)
         ovs_rwlock_unlock(&ofproto->backer->odp_to_ofport_lock);
     }
 
+    if (port->is_tunnel) {
+        atomic_count_dec(&ofproto->backer->tnl_count);
+    }
+
     if (port->is_tunnel && ofproto->ipfix) {
        dpif_ipfix_del_tunnel_port(ofproto->ipfix, port->odp_port);
     }
@@ -1759,26 +1787,33 @@ static void
 port_modified(struct ofport *port_)
 {
     struct ofport_dpif *port = ofport_dpif_cast(port_);
+    char namebuf[NETDEV_VPORT_NAME_BUFSIZE];
+    struct netdev *netdev = port->up.netdev;
 
     if (port->bundle && port->bundle->bond) {
-        bond_slave_set_netdev(port->bundle->bond, port, port->up.netdev);
+        bond_slave_set_netdev(port->bundle->bond, port, netdev);
     }
 
     if (port->cfm) {
-        cfm_set_netdev(port->cfm, port->up.netdev);
+        cfm_set_netdev(port->cfm, netdev);
     }
 
     if (port->bfd) {
-        bfd_set_netdev(port->bfd, port->up.netdev);
+        bfd_set_netdev(port->bfd, netdev);
     }
 
     ofproto_dpif_monitor_port_update(port, port->bfd, port->cfm,
                                      port->up.pp.hw_addr);
 
-    if (port->is_tunnel && tnl_port_reconfigure(port, port->up.netdev,
-                                                port->odp_port)) {
-        ofproto_dpif_cast(port->up.ofproto)->backer->need_revalidate =
-            REV_RECONFIGURE;
+    netdev_vport_get_dpif_port(netdev, namebuf, sizeof namebuf);
+
+    if (port->is_tunnel) {
+        struct ofproto_dpif *ofproto = ofproto_dpif_cast(port->up.ofproto);
+
+        if (tnl_port_reconfigure(port, netdev, port->odp_port,
+                                 ovs_native_tunneling_is_on(ofproto), namebuf)) {
+            ofproto->backer->need_revalidate = REV_RECONFIGURE;
+        }
     }
 
     ofport_update_peer(port);
@@ -3462,8 +3497,10 @@ ofproto_dpif_execute_actions(struct ofproto_dpif *ofproto,
     struct xlate_in xin;
     ofp_port_t in_port;
     struct dpif_execute execute;
+    struct ds ds;
     int error;
 
+    ds_init(&ds);
     ovs_assert((rule != NULL) != (ofpacts != NULL));
 
     dpif_flow_stats_extract(flow, packet, time_msec(), &stats);
@@ -3481,6 +3518,10 @@ ofproto_dpif_execute_actions(struct ofproto_dpif *ofproto,
 
     execute.actions = ofpbuf_data(xout.odp_actions);
     execute.actions_len = ofpbuf_size(xout.odp_actions);
+
+    format_odp_actions(&ds, ofpbuf_data(xout.odp_actions),
+                           ofpbuf_size(xout.odp_actions));
+
     execute.packet = packet;
     execute.md = pkt_metadata_from_flow(flow);
     execute.needs_help = (xout.slow & SLOW_ACTION) != 0;
@@ -4964,7 +5005,36 @@ ofproto_unixctl_dpif_dump_flows(struct unixctl_conn *conn,
 }
 
 static void
-ofproto_dpif_unixctl_init(void)
+ofproto_revalidate_all_backer(void)
+{
+    const struct shash_node **backers;
+    int i;
+
+    backers = shash_sort(&all_dpif_backers);
+    for (i = 0; i < shash_count(&all_dpif_backers); i++) {
+        struct dpif_backer *backer = backers[i]->data;
+        backer->need_revalidate = REV_RECONFIGURE;
+    }
+    free(backers);
+}
+
+static void
+disable_tnl_push_pop(struct unixctl_conn *conn OVS_UNUSED, int argc OVS_UNUSED,
+                     const char *argv[], void *aux OVS_UNUSED)
+{
+    if (!strcasecmp(argv[1], "off")) {
+        ofproto_use_tnl_push_pop = false;
+        unixctl_command_reply(conn, "Tunnel push-pop off");
+        ofproto_revalidate_all_backer();
+    } else if (!strcasecmp(argv[1], "on")) {
+        ofproto_use_tnl_push_pop = true;
+        unixctl_command_reply(conn, "Tunnel push-pop on");
+        ofproto_revalidate_all_backer();
+    }
+}
+
+static void
+ofproto_unixctl_init(void)
 {
     static bool registered;
     if (registered) {
@@ -4994,6 +5064,9 @@ ofproto_dpif_unixctl_init(void)
                              NULL);
     unixctl_command_register("dpif/dump-flows", "[-m] bridge", 1, 2,
                              ofproto_unixctl_dpif_dump_flows, NULL);
+
+    unixctl_command_register("ofproto/tnl-push-pop", "[on]|[off]", 1, 1,
+                             disable_tnl_push_pop, NULL);
 }
 
 /* Returns true if 'table' is the table used for internal rules,
diff --git a/ofproto/ofproto-dpif.h b/ofproto/ofproto-dpif.h
index a8c5d48..96bef92 100644
--- a/ofproto/ofproto-dpif.h
+++ b/ofproto/ofproto-dpif.h
@@ -296,4 +296,5 @@ static inline bool rule_dpif_is_internal(const struct rule_dpif *rule)
 
 #undef RULE_CAST
 
+bool ovs_native_tunneling_is_on(struct ofproto_dpif *ofproto);
 #endif /* ofproto-dpif.h */
diff --git a/ofproto/tunnel.c b/ofproto/tunnel.c
index 46b0719..7c10987 100644
--- a/ofproto/tunnel.c
+++ b/ofproto/tunnel.c
@@ -19,17 +19,25 @@
 
 #include "byte-order.h"
 #include "connectivity.h"
+#include "csum.h"
+#include "dpif.h"
 #include "dynamic-string.h"
 #include "hash.h"
 #include "hmap.h"
 #include "netdev.h"
 #include "odp-util.h"
+#include "ofpbuf.h"
 #include "packets.h"
+#include "route-table.h"
 #include "seq.h"
 #include "smap.h"
 #include "socket-util.h"
+#include "tnl-arp-cache.h"
+#include "tnl-ports.h"
 #include "tunnel.h"
 #include "vlog.h"
+#include "unaligned.h"
+#include "ofproto-dpif.h"
 
 VLOG_DEFINE_THIS_MODULE(tunnel);
 
@@ -124,7 +132,7 @@ static void tnl_port_del__(const struct ofport_dpif *) OVS_REQ_WRLOCK(rwlock);
 
 static bool
 tnl_port_add__(const struct ofport_dpif *ofport, const struct netdev *netdev,
-               odp_port_t odp_port, bool warn)
+               odp_port_t odp_port, bool warn, bool native_tnl, const char name[])
     OVS_REQ_WRLOCK(rwlock)
 {
     const struct netdev_tunnel_config *cfg;
@@ -173,6 +181,11 @@ tnl_port_add__(const struct ofport_dpif *ofport, const struct netdev *netdev,
     }
     hmap_insert(*map, &tnl_port->match_node, tnl_hash(&tnl_port->match));
     tnl_port_mod_log(tnl_port, "adding");
+
+    if (native_tnl) {
+        tnl_port_map_insert(odp_port, tnl_port->match.ip_dst,
+                            cfg->dst_port, name);
+    }
     return true;
 }
 
@@ -181,10 +194,10 @@ tnl_port_add__(const struct ofport_dpif *ofport, const struct netdev *netdev,
  * tunnel. */
 void
 tnl_port_add(const struct ofport_dpif *ofport, const struct netdev *netdev,
-             odp_port_t odp_port) OVS_EXCLUDED(rwlock)
+             odp_port_t odp_port, bool native_tnl, const char name[]) OVS_EXCLUDED(rwlock)
 {
     ovs_rwlock_wrlock(&rwlock);
-    tnl_port_add__(ofport, netdev, odp_port, true);
+    tnl_port_add__(ofport, netdev, odp_port, true, native_tnl, name);
     ovs_rwlock_unlock(&rwlock);
 }
 
@@ -194,7 +207,8 @@ tnl_port_add(const struct ofport_dpif *ofport, const struct netdev *netdev,
  * tnl_port_add(). */
 bool
 tnl_port_reconfigure(const struct ofport_dpif *ofport,
-                     const struct netdev *netdev, odp_port_t odp_port)
+                     const struct netdev *netdev, odp_port_t odp_port,
+                     bool native_tnl, const char name[])
     OVS_EXCLUDED(rwlock)
 {
     struct tnl_port *tnl_port;
@@ -203,13 +217,13 @@ tnl_port_reconfigure(const struct ofport_dpif *ofport,
     ovs_rwlock_wrlock(&rwlock);
     tnl_port = tnl_find_ofport(ofport);
     if (!tnl_port) {
-        changed = tnl_port_add__(ofport, netdev, odp_port, false);
+        changed = tnl_port_add__(ofport, netdev, odp_port, false, native_tnl, name);
     } else if (tnl_port->netdev != netdev
                || tnl_port->match.odp_port != odp_port
                || tnl_port->change_seq != seq_read(connectivity_seq_get())) {
         VLOG_DBG("reconfiguring %s", tnl_port_get_name(tnl_port));
         tnl_port_del__(ofport);
-        tnl_port_add__(ofport, netdev, odp_port, true);
+        tnl_port_add__(ofport, netdev, odp_port, true, native_tnl, name);
         changed = true;
     }
     ovs_rwlock_unlock(&rwlock);
@@ -227,8 +241,11 @@ tnl_port_del__(const struct ofport_dpif *ofport) OVS_REQ_WRLOCK(rwlock)
 
     tnl_port = tnl_find_ofport(ofport);
     if (tnl_port) {
+        const struct netdev_tunnel_config *cfg =
+            netdev_get_tunnel_config(tnl_port->netdev);
         struct hmap **map;
 
+        tnl_port_map_delete(tnl_port->match.ip_dst, cfg->dst_port);
         tnl_port_mod_log(tnl_port, "removing");
         map = tnl_match_map(&tnl_port->match);
         hmap_remove(*map, &tnl_port->match_node);
@@ -639,3 +656,56 @@ tnl_port_get_name(const struct tnl_port *tnl_port) OVS_REQ_RDLOCK(rwlock)
 {
     return netdev_get_name(tnl_port->netdev);
 }
+
+int
+tnl_port_build_header(const struct ofport_dpif *ofport, ovs_be32 router_ip,
+                      const struct flow *tnl_flow,
+                      const char br_name[], uint8_t smac[ETH_ADDR_LEN],
+                      ovs_be32 ip_src, struct ovs_action_push_tnl *data,
+                      bool *send_arp)
+{
+    struct tnl_port *tnl_port;
+    uint8_t dmac[ETH_ADDR_LEN];
+    struct eth_header *eth;
+    struct ip_header *ip;
+    void *l3;
+    int res;
+
+    *send_arp = false;
+    ovs_rwlock_rdlock(&rwlock);
+    tnl_port = tnl_find_ofport(ofport);
+    ovs_assert(tnl_port);
+
+    res = tnl_arp_lookup(br_name, router_ip, dmac);
+    if (res) {
+        *send_arp = true;
+        goto out;
+    }
+
+    /* Build Ethernet and IP headers. */
+    memset(data->header, 0, sizeof data->header);
+
+    eth = (struct eth_header *)data->header;
+    memcpy(eth->eth_dst, dmac, ETH_ADDR_LEN);
+    memcpy(eth->eth_src, smac, ETH_ADDR_LEN);
+    eth->eth_type = htons(ETH_TYPE_IP);
+
+    l3 = (eth + 1);
+    ip = (struct ip_header *) l3;
+
+    ip->ip_ihl_ver = IP_IHL_VER(5, 4);
+    ip->ip_tos = tnl_flow->tunnel.ip_tos;
+    ip->ip_ttl = tnl_flow->tunnel.ip_ttl;
+    ip->ip_frag_off = (tnl_flow->tunnel.flags & FLOW_TNL_F_DONT_FRAGMENT) ?
+                      htons(IP_DF) : 0;
+
+    put_16aligned_be32(&ip->ip_src, ip_src);
+    put_16aligned_be32(&ip->ip_dst, tnl_flow->tunnel.ip_dst);
+
+    res = netdev_build_header(tnl_port->netdev, data);
+    ip->ip_csum = csum(ip, sizeof *ip);
+out:
+    ovs_rwlock_unlock(&rwlock);
+
+    return res;
+}
diff --git a/ofproto/tunnel.h b/ofproto/tunnel.h
index 27a2f7d..cfacbe4 100644
--- a/ofproto/tunnel.h
+++ b/ofproto/tunnel.h
@@ -25,14 +25,15 @@
  * These functions emulate tunnel virtual ports based on the outer
  * header information from the kernel. */
 
+struct ovs_action_push_tnl;
 struct ofport_dpif;
 struct netdev;
 
 bool tnl_port_reconfigure(const struct ofport_dpif *, const struct netdev *,
-                          odp_port_t);
+                          odp_port_t, bool native_tnl, const char name[]);
 
 void tnl_port_add(const struct ofport_dpif *, const struct netdev *,
-                  odp_port_t odp_port);
+                  odp_port_t odp_port, bool native_tnl, const char name[]);
 void tnl_port_del(const struct ofport_dpif *);
 
 const struct ofport_dpif *tnl_port_receive(const struct flow *);
@@ -48,4 +49,10 @@ tnl_port_should_receive(const struct flow *flow)
     return flow->tunnel.ip_dst != 0;
 }
 
+int tnl_port_build_header(const struct ofport_dpif *ofport, ovs_be32 router_ip,
+                          const struct flow *tnl_flow,
+                          const char br_name[], uint8_t smac[ETH_ADDR_LEN],
+                          ovs_be32 ip_src, struct ovs_action_push_tnl *,
+                          bool *send_arp);
+
 #endif /* tunnel.h */
diff --git a/rhel/openvswitch.spec.in b/rhel/openvswitch.spec.in
index 5e99ccb..3fb096b 100644
--- a/rhel/openvswitch.spec.in
+++ b/rhel/openvswitch.spec.in
@@ -175,6 +175,6 @@ exit 0
 /usr/share/openvswitch/vswitch.ovsschema
 /usr/share/openvswitch/vtep.ovsschema
 %doc COPYING DESIGN INSTALL.SSL NOTICE README.md WHY-OVS FAQ NEWS
-%doc INSTALL.DPDK rhel/README.RHEL
+%doc INSTALL.DPDK rhel/README.RHEL README-native-tunneling
 /var/lib/openvswitch
 /var/log/openvswitch
diff --git a/tests/ofproto-macros.at b/tests/ofproto-macros.at
index 77b9b39..f10304c 100644
--- a/tests/ofproto-macros.at
+++ b/tests/ofproto-macros.at
@@ -114,6 +114,13 @@ m4_define([OVS_VSWITCHD_STOP],
    AT_CHECK([ovs-appctl -t ovs-vswitchd exit])
    AT_CHECK([ovs-appctl -t ovsdb-server exit])])
 
+m4_define([OVS_VSWITCHD_DISABLE_TUNNEL_PUSH_POP],
+  [AT_CHECK([ovs-appctl ofproto/tnl-push-pop off], [0], [dnl
+Tunnel push-pop off
+])])
+
+  [AT_CHECK([ovs-appctl ofproto/tnl-push-pop off])])
+
 # ADD_OF_PORTS(BRIDGE, OF_PORT[, OF_PORT...])
 #
 # Creates a dummy interface with an OpenFlow port number of OF_PORT and
diff --git a/tests/tunnel.at b/tests/tunnel.at
index b2ab6cb..88a51a4 100644
--- a/tests/tunnel.at
+++ b/tests/tunnel.at
@@ -11,6 +11,7 @@ OVS_VSWITCHD_START([add-port br0 p1 -- set Interface p1 type=gre \
 AT_DATA([flows.txt], [dnl
 actions=IN_PORT
 ])
+OVS_VSWITCHD_DISABLE_TUNNEL_PUSH_POP
 
 AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
 
@@ -118,6 +119,7 @@ AT_DATA([flows.txt], [dnl
 actions=output:1
 ])
 
+OVS_VSWITCHD_DISABLE_TUNNEL_PUSH_POP
 AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
 
 AT_CHECK([ovs-appctl dpif/show | tail -n +3], [0], [dnl
@@ -150,6 +152,7 @@ AT_DATA([flows.txt], [dnl
 actions=output:1
 ])
 
+OVS_VSWITCHD_DISABLE_TUNNEL_PUSH_POP
 AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
 
 AT_CHECK([ovs-appctl dpif/show | tail -n +3], [0], [dnl
@@ -192,6 +195,7 @@ AT_DATA([flows.txt], [dnl
 actions=set_tunnel:1,output:1,set_tunnel:2,output:2,set_tunnel:3,output:3,set_tunnel:5,output:4
 ])
 
+OVS_VSWITCHD_DISABLE_TUNNEL_PUSH_POP
 AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
 
 AT_CHECK([ovs-appctl dpif/show | tail -n +3], [0], [dnl
@@ -224,6 +228,7 @@ AT_DATA([flows.txt], [dnl
 actions=IN_PORT,output:1,output:2,output:3
 ])
 
+OVS_VSWITCHD_DISABLE_TUNNEL_PUSH_POP
 AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
 
 AT_CHECK([ovs-appctl dpif/show | tail -n +3], [0], [dnl
@@ -276,6 +281,7 @@ tun_id=3,actions=output:4,set_tunnel:2,resubmit:99,set_tunnel:4,output:2,resubmi
 tun_id=4,actions=output:5
 ])
 
+OVS_VSWITCHD_DISABLE_TUNNEL_PUSH_POP
 AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
 
 AT_CHECK([ovs-appctl dpif/show | tail -n +3], [0], [dnl
@@ -387,6 +393,8 @@ OVS_VSWITCHD_START([dnl
         options:remote_ip=flow ofport_request=4 \
     -- add-port br0 p5 -- set Interface p5 type=gre options:key=flow \
         options:remote_ip=5.5.5.5 ofport_request=5])
+
+OVS_VSWITCHD_DISABLE_TUNNEL_PUSH_POP
 ADD_OF_PORTS([br0], [90])
 AT_DATA([flows.txt], [dnl
 in_port=90 actions=resubmit:1,resubmit:2,resubmit:3,resubmit:4,resubmit:5
-- 
1.7.1




More information about the dev mailing list