diff --git a/acinclude.m4 b/acinclude.m4
index 23015fe..fc9f11e 100644
--- a/acinclude.m4
+++ b/acinclude.m4
@@ -398,6 +398,7 @@ AC_DEFUN([OVS_CHECK_LINUX_COMPAT], [
 
   OVS_GREP_IFELSE([$KSRC/include/linux/net.h], [sock_create_kern.*net],
                   [OVS_DEFINE([HAVE_SOCK_CREATE_KERN_NET])])
+  OVS_GREP_IFELSE([$KSRC/include/linux/netdevice.h], [ndo_fill_metadata_dst])
   OVS_GREP_IFELSE([$KSRC/include/linux/netdevice.h], [dev_disable_lro])
   OVS_GREP_IFELSE([$KSRC/include/linux/netdevice.h], [dev_get_stats])
   OVS_GREP_IFELSE([$KSRC/include/linux/netdevice.h], [dev_get_by_index_rcu])
diff --git a/datapath/actions.c b/datapath/actions.c
index dcf8591..f7b11f2 100644
--- a/datapath/actions.c
+++ b/datapath/actions.c
@@ -770,7 +770,6 @@ static int output_userspace(struct datapath *dp, struct sk_buff *skb,
 			    struct sw_flow_key *key, const struct nlattr *attr,
 			    const struct nlattr *actions, int actions_len)
 {
-	struct ip_tunnel_info info;
 	struct dp_upcall_info upcall;
 	const struct nlattr *a;
 	int rem;
@@ -798,11 +797,9 @@ static int output_userspace(struct datapath *dp, struct sk_buff *skb,
 			if (vport) {
 				int err;
 
-				upcall.egress_tun_info = &info;
-				err = ovs_vport_get_egress_tun_info(vport, skb,
-								    &upcall);
-				if (err)
-					upcall.egress_tun_info = NULL;
+				err = dev_fill_metadata_dst(vport->dev, skb);
+				if (!err)
+					upcall.egress_tun_info = skb_tunnel_info(skb);
 			}
 
 			break;
diff --git a/datapath/datapath.c b/datapath/datapath.c
index 5bec072..31203a3 100644
--- a/datapath/datapath.c
+++ b/datapath/datapath.c
@@ -497,9 +497,8 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb,
 
 	if (upcall_info->egress_tun_info) {
 		nla = nla_nest_start(user_skb, OVS_PACKET_ATTR_EGRESS_TUN_KEY);
-		err = ovs_nla_put_egress_tunnel_key(user_skb,
-						    upcall_info->egress_tun_info,
-						    upcall_info->egress_tun_opts);
+		err = ovs_nla_put_tunnel_info(user_skb,
+					      upcall_info->egress_tun_info);
 		BUG_ON(err);
 		nla_nest_end(user_skb, nla);
 	}
diff --git a/datapath/datapath.h b/datapath/datapath.h
index ceb3372..9da6093 100644
--- a/datapath/datapath.h
+++ b/datapath/datapath.h
@@ -118,7 +118,6 @@ struct ovs_skb_cb {
  */
 struct dp_upcall_info {
 	struct ip_tunnel_info *egress_tun_info;
-	const void *egress_tun_opts;
 	const struct nlattr *userdata;
 	const struct nlattr *actions;
 	int actions_len;
diff --git a/datapath/flow_netlink.c b/datapath/flow_netlink.c
index 6ffcc53..0003f89 100644
--- a/datapath/flow_netlink.c
+++ b/datapath/flow_netlink.c
@@ -719,7 +719,7 @@ static int __ipv4_tun_to_nlattr(struct sk_buff *skb,
 	if ((output->tun_flags & TUNNEL_OAM) &&
 	    nla_put_flag(skb, OVS_TUNNEL_KEY_ATTR_OAM))
 		return -EMSGSIZE;
-	if (tun_opts) {
+	if (swkey_tun_opts_len) {
 		if (output->tun_flags & TUNNEL_GENEVE_OPT &&
 		    nla_put(skb, OVS_TUNNEL_KEY_ATTR_GENEVE_OPTS,
 			    swkey_tun_opts_len, tun_opts))
@@ -751,13 +751,12 @@ static int ipv4_tun_to_nlattr(struct sk_buff *skb,
 	return 0;
 }
 
-int ovs_nla_put_egress_tunnel_key(struct sk_buff *skb,
-				  const struct ip_tunnel_info *egress_tun_info,
-				  const void *egress_tun_opts)
+int ovs_nla_put_tunnel_info(struct sk_buff *skb,
+			    struct ip_tunnel_info *tun_info)
 {
-	return __ipv4_tun_to_nlattr(skb, &egress_tun_info->key,
-				    egress_tun_opts,
-				    egress_tun_info->options_len);
+	return __ipv4_tun_to_nlattr(skb, &tun_info->key,
+				    ip_tunnel_info_opts(tun_info),
+				    tun_info->options_len);
 }
 
 static int metadata_from_nlattrs(struct net *net, struct sw_flow_match *match,
@@ -2390,10 +2389,7 @@ static int set_action_to_attr(const struct nlattr *a, struct sk_buff *skb)
 		if (!start)
 			return -EMSGSIZE;
 
-		err = ipv4_tun_to_nlattr(skb, &tun_info->key,
-					 tun_info->options_len ?
-					     ip_tunnel_info_opts(tun_info) : NULL,
-					 tun_info->options_len);
+		err = ovs_nla_put_tunnel_info(skb, tun_info);
 		if (err)
 			return err;
 		nla_nest_end(skb, start);
diff --git a/datapath/flow_netlink.h b/datapath/flow_netlink.h
index 1c01755..1c4208b 100644
--- a/datapath/flow_netlink.h
+++ b/datapath/flow_netlink.h
@@ -55,9 +55,8 @@ int ovs_nla_put_mask(const struct sw_flow *flow, struct sk_buff *skb);
 int ovs_nla_get_match(struct net *, struct sw_flow_match *,
 		      const struct nlattr *key, const struct nlattr *mask,
 		      bool log);
-int ovs_nla_put_egress_tunnel_key(struct sk_buff *skb,
-				  const struct ip_tunnel_info *egress_tun_info,
-				  const void *egress_tun_opts);
+int ovs_nla_put_tunnel_info(struct sk_buff *skb,
+			    struct ip_tunnel_info *tun_info);
 
 bool ovs_nla_get_ufid(struct sw_flow_id *, const struct nlattr *, bool log);
 int ovs_nla_get_identifier(struct sw_flow_id *sfid, const struct nlattr *ufid,
diff --git a/datapath/linux/compat/dev-openvswitch.c b/datapath/linux/compat/dev-openvswitch.c
index 0d2088b..544c5e1 100644
--- a/datapath/linux/compat/dev-openvswitch.c
+++ b/datapath/linux/compat/dev-openvswitch.c
@@ -3,6 +3,11 @@
 #include <linux/version.h>
 #include <net/rtnetlink.h>
 
+#include "gso.h"
+#include "vport.h"
+#include "vport-internal_dev.h"
+#include "vport-netdev.h"
+
 #ifndef HAVE_DEV_DISABLE_LRO
 
 #ifdef NETIF_F_LRO
@@ -54,3 +59,27 @@ int rpl_rtnl_delete_link(struct net_device *dev)
 #endif
 	return 0;
 }
+
+#ifndef HAVE_NDO_FILL_METADATA_DST
+int ovs_dev_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb)
+{
+	struct ip_tunnel_info *info;
+	struct vport *vport;
+
+	vport = ovs_netdev_get_vport(dev);
+	if (!vport)
+		return -EINVAL;
+
+	if (!vport->ops->fill_metadata_dst)
+		return -EINVAL;
+
+	info = skb_tunnel_info(skb);
+	if (!info)
+		return -ENOMEM;
+	if (unlikely(!(info->mode & IP_TUNNEL_INFO_TX)))
+		return -EINVAL;
+
+	return vport->ops->fill_metadata_dst(dev, skb);
+}
+EXPORT_SYMBOL_GPL(ovs_dev_fill_metadata_dst);
+#endif
diff --git a/datapath/linux/compat/geneve.c b/datapath/linux/compat/geneve.c
index 0399de7..f38d4a3 100644
--- a/datapath/linux/compat/geneve.c
+++ b/datapath/linux/compat/geneve.c
@@ -765,6 +765,29 @@ static int geneve_change_mtu(struct net_device *dev, int new_mtu)
 	return __geneve_change_mtu(dev, new_mtu, true);
 }
 
+int ovs_geneve_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb)
+{
+	struct ip_tunnel_info *info = skb_tunnel_info(skb);
+	struct geneve_dev *geneve = netdev_priv(dev);
+	struct rtable *rt;
+	struct flowi4 fl4;
+
+	if (ip_tunnel_info_af(info) != AF_INET)
+		return -EINVAL;
+
+	rt = geneve_get_rt(skb, dev, &fl4, info);
+	if (IS_ERR(rt))
+		return PTR_ERR(rt);
+
+	ip_rt_put(rt);
+	info->key.u.ipv4.src = fl4.saddr;
+	info->key.tp_src = udp_flow_src_port(geneve->net, skb,
+					     1, USHRT_MAX, true);
+	info->key.tp_dst = geneve->dst_port;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(ovs_geneve_fill_metadata_dst);
+
 static const struct net_device_ops geneve_netdev_ops = {
 	.ndo_init		= geneve_init,
 	.ndo_uninit		= geneve_uninit,
@@ -775,6 +798,9 @@ static const struct net_device_ops geneve_netdev_ops = {
 	.ndo_change_mtu		= geneve_change_mtu,
 	.ndo_validate_addr	= eth_validate_addr,
 	.ndo_set_mac_address	= eth_mac_addr,
+#ifdef HAVE_NDO_FILL_METADATA_DST
+	.ndo_fill_metadata_dst  = geneve_fill_metadata_dst,
+#endif
 };
 
 static void geneve_get_drvinfo(struct net_device *dev,
diff --git a/datapath/linux/compat/include/linux/netdevice.h b/datapath/linux/compat/include/linux/netdevice.h
index e9fa995..ac612ef 100644
--- a/datapath/linux/compat/include/linux/netdevice.h
+++ b/datapath/linux/compat/include/linux/netdevice.h
@@ -249,4 +249,8 @@ do {								\
 
 #endif
 
+#ifndef HAVE_NDO_FILL_METADATA_DST
+#define dev_fill_metadata_dst ovs_dev_fill_metadata_dst
+int ovs_dev_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb);
+#endif
 #endif /* __LINUX_NETDEVICE_WRAPPER_H */
diff --git a/datapath/linux/compat/include/net/geneve.h b/datapath/linux/compat/include/net/geneve.h
index 550f4a7..a7f2252 100644
--- a/datapath/linux/compat/include/net/geneve.h
+++ b/datapath/linux/compat/include/net/geneve.h
@@ -91,4 +91,7 @@ netdev_tx_t rpl_geneve_xmit(struct sk_buff *skb);
 #define geneve_init_module rpl_geneve_init_module
 #define geneve_cleanup_module rpl_geneve_cleanup_module
 
+#define geneve_fill_metadata_dst ovs_geneve_fill_metadata_dst
+int ovs_geneve_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb);
+
 #endif /*ifdef__NET_GENEVE_H */
diff --git a/datapath/linux/compat/include/net/gre.h b/datapath/linux/compat/include/net/gre.h
index 60618df..8082a98 100644
--- a/datapath/linux/compat/include/net/gre.h
+++ b/datapath/linux/compat/include/net/gre.h
@@ -66,4 +66,7 @@ netdev_tx_t rpl_gre_fb_xmit(struct sk_buff *skb);
 #define ipgre_init rpl_ipgre_init
 #define ipgre_fini rpl_ipgre_fini
 
+#define gre_fill_metadata_dst ovs_gre_fill_metadata_dst
+int ovs_gre_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb);
+
 #endif
diff --git a/datapath/linux/compat/include/net/lisp.h b/datapath/linux/compat/include/net/lisp.h
index b8af17d..6b43c77 100644
--- a/datapath/linux/compat/include/net/lisp.h
+++ b/datapath/linux/compat/include/net/lisp.h
@@ -21,4 +21,7 @@ void rpl_lisp_cleanup_module(void);
 #define lisp_xmit rpl_lisp_xmit
 netdev_tx_t rpl_lisp_xmit(struct sk_buff *skb);
 
+#define lisp_fill_metadata_dst ovs_lisp_fill_metadata_dst
+int ovs_lisp_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb);
+
 #endif /*ifdef__NET_LISP_H */
diff --git a/datapath/linux/compat/include/net/stt.h b/datapath/linux/compat/include/net/stt.h
index 28d4dc5..d2e63d1 100644
--- a/datapath/linux/compat/include/net/stt.h
+++ b/datapath/linux/compat/include/net/stt.h
@@ -64,4 +64,7 @@ static inline netdev_tx_t ovs_stt_xmit(struct sk_buff *skb)
 #define stt_init_module ovs_stt_init_module
 #define stt_cleanup_module ovs_stt_cleanup_module
 
+#define stt_fill_metadata_dst ovs_stt_fill_metadata_dst
+int ovs_stt_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb);
+
 #endif /*ifdef__NET_STT_H */
diff --git a/datapath/linux/compat/include/net/vxlan.h b/datapath/linux/compat/include/net/vxlan.h
index 75a5a7a..589e6f2 100644
--- a/datapath/linux/compat/include/net/vxlan.h
+++ b/datapath/linux/compat/include/net/vxlan.h
@@ -274,4 +274,6 @@ netdev_tx_t rpl_vxlan_xmit(struct sk_buff *skb);
 #define vxlan_init_module rpl_vxlan_init_module
 #define vxlan_cleanup_module rpl_vxlan_cleanup_module
 
+#define vxlan_fill_metadata_dst ovs_vxlan_fill_metadata_dst
+int ovs_vxlan_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb);
 #endif
diff --git a/datapath/linux/compat/ip_gre.c b/datapath/linux/compat/ip_gre.c
index 2e13843..67db9af 100644
--- a/datapath/linux/compat/ip_gre.c
+++ b/datapath/linux/compat/ip_gre.c
@@ -256,11 +256,26 @@ static void build_header(struct sk_buff *skb, int hdr_len, __be16 flags,
 	ovs_skb_set_inner_protocol(skb, proto);
 }
 
+static struct rtable *gre_get_rt(struct sk_buff *skb,
+				 struct net_device *dev,
+				 struct flowi4 *fl,
+				 const struct ip_tunnel_key *key)
+{
+	struct net *net = dev_net(dev);
+
+	memset(fl, 0, sizeof(*fl));
+	fl->daddr = key->u.ipv4.dst;
+	fl->saddr = key->u.ipv4.src;
+	fl->flowi4_tos = RT_TOS(key->tos);
+	fl->flowi4_mark = skb->mark;
+	fl->flowi4_proto = IPPROTO_GRE;
+
+	return ip_route_output_key(net, fl);
+}
 
 netdev_tx_t rpl_gre_fb_xmit(struct sk_buff *skb)
 {
 	struct net_device *dev = skb->dev;
-	struct net *net = dev_net(dev);
 	struct ip_tunnel_info *tun_info;
 	const struct ip_tunnel_key *key;
 	struct flowi4 fl;
@@ -276,14 +291,8 @@ netdev_tx_t rpl_gre_fb_xmit(struct sk_buff *skb)
 		goto err_free_skb;
 
 	key = &tun_info->key;
-	memset(&fl, 0, sizeof(fl));
-	fl.daddr = key->u.ipv4.dst;
-	fl.saddr = key->u.ipv4.src;
-	fl.flowi4_tos = RT_TOS(key->tos);
-	fl.flowi4_mark = skb->mark;
-	fl.flowi4_proto = IPPROTO_GRE;
-
-	rt = ip_route_output_key(net, &fl);
+
+	rt = gre_get_rt(skb, dev, &fl, key);
 	if (IS_ERR(rt))
 		goto err_free_skb;
 
@@ -459,6 +468,25 @@ static netdev_tx_t gre_dev_xmit(struct sk_buff *skb, struct net_device *dev)
 	return NETDEV_TX_OK;
 }
 
+int ovs_gre_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb)
+{
+	struct ip_tunnel_info *info = skb_tunnel_info(skb);
+	struct rtable *rt;
+	struct flowi4 fl4;
+
+	if (ip_tunnel_info_af(info) != AF_INET)
+		return -EINVAL;
+
+	rt = gre_get_rt(skb, dev, &fl4, &info->key);
+	if (IS_ERR(rt))
+		return PTR_ERR(rt);
+
+	ip_rt_put(rt);
+	info->key.u.ipv4.src = fl4.saddr;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(ovs_gre_fill_metadata_dst);
+
 static const struct net_device_ops gre_tap_netdev_ops = {
 	.ndo_init		= gre_tap_init,
 	.ndo_uninit		= ip_tunnel_uninit,
@@ -472,6 +500,9 @@ static const struct net_device_ops gre_tap_netdev_ops = {
 #ifdef HAVE_NDO_GET_IFLINK
 	.ndo_get_iflink		= ip_tunnel_get_iflink,
 #endif
+#ifdef HAVE_NDO_FILL_METADATA_DST
+	.ndo_fill_metadata_dst  = gre_fill_metadata_dst,
+#endif
 };
 
 static void ipgre_tap_setup(struct net_device *dev)
diff --git a/datapath/linux/compat/lisp.c b/datapath/linux/compat/lisp.c
index f1f50ae..0a92dca 100644
--- a/datapath/linux/compat/lisp.c
+++ b/datapath/linux/compat/lisp.c
@@ -456,6 +456,46 @@ static int lisp_change_mtu(struct net_device *dev, int new_mtu)
 	return 0;
 }
 
+static int egress_ipv4_tun_info(struct net_device *dev, struct sk_buff *skb,
+		struct ip_tunnel_info *info,
+		__be16 sport, __be16 dport)
+{
+	struct lisp_dev *lisp = netdev_priv(dev);
+	struct rtable *rt;
+	struct flowi4 fl4;
+
+	memset(&fl4, 0, sizeof(fl4));
+	fl4.flowi4_tos = RT_TOS(info->key.tos);
+	fl4.flowi4_mark = skb->mark;
+	fl4.flowi4_proto = IPPROTO_UDP;
+	fl4.daddr = info->key.u.ipv4.dst;
+
+	rt = ip_route_output_key(lisp->net, &fl4);
+	if (IS_ERR(rt))
+		return PTR_ERR(rt);
+	ip_rt_put(rt);
+
+	info->key.u.ipv4.src = fl4.saddr;
+	info->key.tp_src = sport;
+	info->key.tp_dst = dport;
+	return 0;
+}
+
+int ovs_lisp_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb)
+{
+	struct lisp_dev *lisp = netdev_priv(dev);
+	struct ip_tunnel_info *info = skb_tunnel_info(skb);
+	__be16 sport, dport;
+
+	sport = udp_flow_src_port(dev_net(dev), skb, 1, USHRT_MAX, true);
+	dport = info->key.tp_dst ? : lisp->dst_port;
+
+	if (ip_tunnel_info_af(info) == AF_INET)
+		return egress_ipv4_tun_info(dev, skb, info, sport, dport);
+	return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(ovs_lisp_fill_metadata_dst);
+
 static const struct net_device_ops lisp_netdev_ops = {
 	.ndo_init               = lisp_init,
 	.ndo_uninit             = lisp_uninit,
@@ -466,6 +506,9 @@ static const struct net_device_ops lisp_netdev_ops = {
 	.ndo_change_mtu         = lisp_change_mtu,
 	.ndo_validate_addr      = eth_validate_addr,
 	.ndo_set_mac_address    = eth_mac_addr,
+#ifdef HAVE_NDO_FILL_METADATA_DST
+	.ndo_fill_metadata_dst  = lisp_fill_metadata_dst,
+#endif
 };
 
 static void lisp_get_drvinfo(struct net_device *dev,
diff --git a/datapath/linux/compat/stt.c b/datapath/linux/compat/stt.c
index 86d225e..1528cbe 100644
--- a/datapath/linux/compat/stt.c
+++ b/datapath/linux/compat/stt.c
@@ -980,6 +980,24 @@ err_free_rt:
 	return ret;
 }
 
+static struct rtable *stt_get_rt(struct sk_buff *skb,
+				 struct net_device *dev,
+				 struct flowi4 *fl,
+				 const struct ip_tunnel_key *key)
+{
+	struct net *net = dev_net(dev);
+
+	/* Route lookup */
+	memset(fl, 0, sizeof(*fl));
+	fl->daddr = key->u.ipv4.dst;
+	fl->saddr = key->u.ipv4.src;
+	fl->flowi4_tos = RT_TOS(key->tos);
+	fl->flowi4_mark = skb->mark;
+	fl->flowi4_proto = IPPROTO_TCP;
+
+	return ip_route_output_key(net, fl);
+}
+
 netdev_tx_t ovs_stt_xmit(struct sk_buff *skb)
 {
 	struct net_device *dev = skb->dev;
@@ -1002,14 +1020,7 @@ netdev_tx_t ovs_stt_xmit(struct sk_buff *skb)
 
 	tun_key = &tun_info->key;
 
-	/* Route lookup */
-	memset(&fl, 0, sizeof(fl));
-	fl.daddr = tun_key->u.ipv4.dst;
-	fl.saddr = tun_key->u.ipv4.src;
-	fl.flowi4_tos = RT_TOS(tun_key->tos);
-	fl.flowi4_mark = skb->mark;
-	fl.flowi4_proto = IPPROTO_TCP;
-	rt = ip_route_output_key(net, &fl);
+	rt = stt_get_rt(skb, dev, &fl, tun_key);
 	if (IS_ERR(rt)) {
 		err = PTR_ERR(rt);
 		goto error;
@@ -1802,6 +1813,25 @@ static int stt_change_mtu(struct net_device *dev, int new_mtu)
 	return __stt_change_mtu(dev, new_mtu, true);
 }
 
+int ovs_stt_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb)
+{
+	struct ip_tunnel_info *info = skb_tunnel_info(skb);
+	struct rtable *rt;
+	struct flowi4 fl4;
+
+	if (ip_tunnel_info_af(info) != AF_INET)
+		return -EINVAL;
+
+	rt = stt_get_rt(skb, dev, &fl4, &info->key);
+	if (IS_ERR(rt))
+		return PTR_ERR(rt);
+
+	ip_rt_put(rt);
+	info->key.u.ipv4.src = fl4.saddr;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(ovs_stt_fill_metadata_dst);
+
 static const struct net_device_ops stt_netdev_ops = {
 	.ndo_init               = stt_init,
 	.ndo_uninit             = stt_uninit,
@@ -1812,6 +1842,9 @@ static const struct net_device_ops stt_netdev_ops = {
 	.ndo_change_mtu         = stt_change_mtu,
 	.ndo_validate_addr      = eth_validate_addr,
 	.ndo_set_mac_address    = eth_mac_addr,
+#ifdef HAVE_NDO_FILL_METADATA_DST
+	.ndo_fill_metadata_dst  = stt_fill_metadata_dst,
+#endif
 };
 
 static void stt_get_drvinfo(struct net_device *dev,
diff --git a/datapath/linux/compat/vxlan.c b/datapath/linux/compat/vxlan.c
index 4faa18f..b92a902 100644
--- a/datapath/linux/compat/vxlan.c
+++ b/datapath/linux/compat/vxlan.c
@@ -1648,6 +1648,47 @@ static netdev_tx_t vxlan_dev_xmit(struct sk_buff *skb, struct net_device *dev)
 	return NETDEV_TX_OK;
 }
 
+static int egress_ipv4_tun_info(struct net_device *dev, struct sk_buff *skb,
+		struct ip_tunnel_info *info,
+		__be16 sport, __be16 dport)
+{
+	struct vxlan_dev *vxlan = netdev_priv(dev);
+	struct rtable *rt;
+	struct flowi4 fl4;
+
+	memset(&fl4, 0, sizeof(fl4));
+	fl4.flowi4_tos = RT_TOS(info->key.tos);
+	fl4.flowi4_mark = skb->mark;
+	fl4.flowi4_proto = IPPROTO_UDP;
+	fl4.daddr = info->key.u.ipv4.dst;
+
+	rt = ip_route_output_key(vxlan->net, &fl4);
+	if (IS_ERR(rt))
+		return PTR_ERR(rt);
+	ip_rt_put(rt);
+
+	info->key.u.ipv4.src = fl4.saddr;
+	info->key.tp_src = sport;
+	info->key.tp_dst = dport;
+	return 0;
+}
+
+int ovs_vxlan_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb)
+{
+	struct vxlan_dev *vxlan = netdev_priv(dev);
+	struct ip_tunnel_info *info = skb_tunnel_info(skb);
+	__be16 sport, dport;
+
+	sport = udp_flow_src_port(dev_net(dev), skb, vxlan->cfg.port_min,
+			vxlan->cfg.port_max, true);
+	dport = info->key.tp_dst ? : vxlan->cfg.dst_port;
+
+	if (ip_tunnel_info_af(info) == AF_INET)
+		return egress_ipv4_tun_info(dev, skb, info, sport, dport);
+	return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(ovs_vxlan_fill_metadata_dst);
+
 static const struct net_device_ops vxlan_netdev_ops = {
 	.ndo_init		= vxlan_init,
 	.ndo_uninit		= vxlan_uninit,
@@ -1659,6 +1700,9 @@ static const struct net_device_ops vxlan_netdev_ops = {
 	.ndo_change_mtu		= vxlan_change_mtu,
 	.ndo_validate_addr	= eth_validate_addr,
 	.ndo_set_mac_address	= eth_mac_addr,
+#ifdef HAVE_NDO_FILL_METADATA_DST
+	.ndo_fill_metadata_dst  = vxlan_fill_metadata_dst,
+#endif
 };
 
 /* Info for udev, that this is a virtual tunnel endpoint */
diff --git a/datapath/vport-geneve.c b/datapath/vport-geneve.c
index e65e43d..718b077 100644
--- a/datapath/vport-geneve.c
+++ b/datapath/vport-geneve.c
@@ -52,18 +52,6 @@ static int geneve_get_options(const struct vport *vport,
 	return 0;
 }
 
-static int geneve_get_egress_tun_info(struct vport *vport, struct sk_buff *skb,
-				      struct dp_upcall_info *upcall)
-{
-	struct geneve_port *geneve_port = geneve_vport(vport);
-	struct net *net = ovs_dp_get_net(vport->dp);
-	__be16 dport = htons(geneve_port->dst_port);
-	__be16 sport = udp_flow_src_port(net, skb, 1, USHRT_MAX, true);
-
-	return ovs_tunnel_get_egress_info(upcall, ovs_dp_get_net(vport->dp),
-					  skb, IPPROTO_UDP, sport, dport);
-}
-
 static struct vport *geneve_tnl_create(const struct vport_parms *parms)
 {
 	struct net *net = ovs_dp_get_net(parms->dp);
@@ -128,8 +116,8 @@ static struct vport_ops ovs_geneve_vport_ops = {
 	.create		= geneve_create,
 	.destroy	= ovs_netdev_tunnel_destroy,
 	.get_options	= geneve_get_options,
+	.fill_metadata_dst = geneve_fill_metadata_dst,
 	.send		= geneve_xmit,
-	.get_egress_tun_info	= geneve_get_egress_tun_info,
 };
 
 static int __init ovs_geneve_tnl_init(void)
diff --git a/datapath/vport-gre.c b/datapath/vport-gre.c
index 50aaba5..ffe0079 100644
--- a/datapath/vport-gre.c
+++ b/datapath/vport-gre.c
@@ -84,18 +84,11 @@ static struct vport *gre_create(const struct vport_parms *parms)
 	return ovs_netdev_link(vport, parms->name);
 }
 
-static int gre_get_egress_tun_info(struct vport *vport, struct sk_buff *skb,
-				   struct dp_upcall_info *upcall)
-{
-	return ovs_tunnel_get_egress_info(upcall, ovs_dp_get_net(vport->dp),
-					  skb, IPPROTO_GRE, 0, 0);
-}
-
 static struct vport_ops ovs_gre_vport_ops = {
 	.type		= OVS_VPORT_TYPE_GRE,
 	.create		= gre_create,
 	.send		= gre_fb_xmit,
-	.get_egress_tun_info	= gre_get_egress_tun_info,
+	.fill_metadata_dst = gre_fill_metadata_dst,
 	.destroy	= ovs_netdev_tunnel_destroy,
 };
 
diff --git a/datapath/vport-lisp.c b/datapath/vport-lisp.c
index 3ce8613..25f6932 100644
--- a/datapath/vport-lisp.c
+++ b/datapath/vport-lisp.c
@@ -52,18 +52,6 @@ static int lisp_get_options(const struct vport *vport,
 	return 0;
 }
 
-static int lisp_get_egress_tun_info(struct vport *vport, struct sk_buff *skb,
-				      struct dp_upcall_info *upcall)
-{
-	struct lisp_port *lisp_port = lisp_vport(vport);
-	struct net *net = ovs_dp_get_net(vport->dp);
-	__be16 dport = htons(lisp_port->port_no);
-	__be16 sport = udp_flow_src_port(net, skb, 1, USHRT_MAX, true);
-
-	return ovs_tunnel_get_egress_info(upcall, ovs_dp_get_net(vport->dp),
-					  skb, IPPROTO_UDP, sport, dport);
-}
-
 static struct vport *lisp_tnl_create(const struct vport_parms *parms)
 {
 	struct net *net = ovs_dp_get_net(parms->dp);
@@ -128,8 +116,8 @@ static struct vport_ops ovs_lisp_vport_ops = {
 	.create		= lisp_create,
 	.destroy	= ovs_netdev_tunnel_destroy,
 	.get_options	= lisp_get_options,
+	.fill_metadata_dst = lisp_fill_metadata_dst,
 	.send		= lisp_xmit,
-	.get_egress_tun_info	= lisp_get_egress_tun_info,
 };
 
 static int __init ovs_lisp_tnl_init(void)
diff --git a/datapath/vport-stt.c b/datapath/vport-stt.c
index b8e0c88..e46bec1 100644
--- a/datapath/vport-stt.c
+++ b/datapath/vport-stt.c
@@ -54,18 +54,6 @@ static int stt_get_options(const struct vport *vport,
 	return 0;
 }
 
-static int stt_get_egress_tun_info(struct vport *vport, struct sk_buff *skb,
-				      struct dp_upcall_info *upcall)
-{
-	struct stt_port *stt_port = stt_vport(vport);
-	struct net *net = ovs_dp_get_net(vport->dp);
-	__be16 dport = htons(stt_port->port_no);
-	__be16 sport = udp_flow_src_port(net, skb, 1, USHRT_MAX, true);
-
-	return ovs_tunnel_get_egress_info(upcall, ovs_dp_get_net(vport->dp),
-					  skb, IPPROTO_UDP, sport, dport);
-}
-
 static struct vport *stt_tnl_create(const struct vport_parms *parms)
 {
 	struct net *net = ovs_dp_get_net(parms->dp);
@@ -130,8 +118,8 @@ static struct vport_ops ovs_stt_vport_ops = {
 	.create		= stt_create,
 	.destroy	= ovs_netdev_tunnel_destroy,
 	.get_options	= stt_get_options,
+	.fill_metadata_dst = stt_fill_metadata_dst,
 	.send		= ovs_stt_xmit,
-	.get_egress_tun_info	= stt_get_egress_tun_info,
 };
 
 static int __init ovs_stt_tnl_init(void)
diff --git a/datapath/vport-vxlan.c b/datapath/vport-vxlan.c
index c05f5d4..37e6da1 100644
--- a/datapath/vport-vxlan.c
+++ b/datapath/vport-vxlan.c
@@ -148,31 +148,13 @@ static struct vport *vxlan_create(const struct vport_parms *parms)
 	return ovs_netdev_link(vport, parms->name);
 }
 
-static int vxlan_get_egress_tun_info(struct vport *vport, struct sk_buff *skb,
-				     struct dp_upcall_info *upcall)
-{
-	struct vxlan_dev *vxlan = netdev_priv(vport->dev);
-	struct net *net = ovs_dp_get_net(vport->dp);
-	__be16 dst_port = vxlan_dev_dst_port(vxlan);
-	__be16 src_port;
-	int port_min;
-	int port_max;
-
-	inet_get_local_port_range(net, &port_min, &port_max);
-	src_port = udp_flow_src_port(net, skb, 0, 0, true);
-
-	return ovs_tunnel_get_egress_info(upcall, net,
-					  skb, IPPROTO_UDP,
-					  src_port, dst_port);
-}
-
 static struct vport_ops ovs_vxlan_netdev_vport_ops = {
 	.type			= OVS_VPORT_TYPE_VXLAN,
 	.create			= vxlan_create,
 	.destroy		= ovs_netdev_tunnel_destroy,
 	.get_options		= vxlan_get_options,
+	.fill_metadata_dst	= vxlan_fill_metadata_dst,
 	.send			= vxlan_xmit,
-	.get_egress_tun_info	= vxlan_get_egress_tun_info,
 };
 
 static int __init ovs_vxlan_tnl_init(void)
diff --git a/datapath/vport.c b/datapath/vport.c
index 44b9dfb..c789a40 100644
--- a/datapath/vport.c
+++ b/datapath/vport.c
@@ -41,6 +41,7 @@
 #include "gso.h"
 #include "vport.h"
 #include "vport-internal_dev.h"
+#include "vport-netdev.h"
 
 static LIST_HEAD(vport_ops_list);
 
@@ -525,83 +526,6 @@ void ovs_vport_deferred_free(struct vport *vport)
 }
 EXPORT_SYMBOL_GPL(ovs_vport_deferred_free);
 
-static struct rtable *ovs_tunnel_route_lookup(struct net *net,
-					      const struct ip_tunnel_key *key,
-					      u32 mark,
-					      struct flowi4 *fl,
-					      u8 protocol)
-{
-	struct rtable *rt;
-
-	memset(fl, 0, sizeof(*fl));
-	fl->daddr = key->u.ipv4.dst;
-	fl->saddr = key->u.ipv4.src;
-	fl->flowi4_tos = RT_TOS(key->tos);
-	fl->flowi4_mark = mark;
-	fl->flowi4_proto = protocol;
-
-	rt = ip_route_output_key(net, fl);
-	return rt;
-}
-
-int ovs_tunnel_get_egress_info(struct dp_upcall_info *upcall,
-			       struct net *net,
-			       struct sk_buff *skb,
-			       u8 ipproto,
-			       __be16 tp_src,
-			       __be16 tp_dst)
-{
-	struct ip_tunnel_info *egress_tun_info = upcall->egress_tun_info;
-	struct ip_tunnel_info *tun_info = skb_tunnel_info(skb);
-	const struct ip_tunnel_key *tun_key;
-	u32 skb_mark = skb->mark;
-	struct rtable *rt;
-	struct flowi4 fl;
-
-	if (unlikely(!tun_info))
-		return -EINVAL;
-	if (ip_tunnel_info_af(tun_info) != AF_INET)
-		return -EINVAL;
-
-	tun_key = &tun_info->key;
-
-	/* Route lookup to get srouce IP address.
-	 * The process may need to be changed if the corresponding process
-	 * in vports ops changed.
-	 */
-	rt = ovs_tunnel_route_lookup(net, tun_key, skb_mark, &fl, ipproto);
-	if (IS_ERR(rt))
-		return PTR_ERR(rt);
-
-	ip_rt_put(rt);
-
-	/* Generate egress_tun_info based on tun_info,
-	 * saddr, tp_src and tp_dst
-	 */
-	ip_tunnel_key_init(&egress_tun_info->key,
-			   fl.saddr, tun_key->u.ipv4.dst,
-			   tun_key->tos,
-			   tun_key->ttl,
-			   tp_src, tp_dst,
-			   tun_key->tun_id,
-			   tun_key->tun_flags);
-	egress_tun_info->options_len = tun_info->options_len;
-	egress_tun_info->mode = tun_info->mode;
-	upcall->egress_tun_opts = ip_tunnel_info_opts(tun_info);
-	return 0;
-}
-EXPORT_SYMBOL_GPL(ovs_tunnel_get_egress_info);
-
-int ovs_vport_get_egress_tun_info(struct vport *vport, struct sk_buff *skb,
-				  struct dp_upcall_info *upcall)
-{
-	/* get_egress_tun_info() is only implemented on tunnel ports. */
-	if (unlikely(!vport->ops->get_egress_tun_info))
-		return -EINVAL;
-
-	return vport->ops->get_egress_tun_info(vport, skb, upcall);
-}
-
 static unsigned int packet_length(const struct sk_buff *skb)
 {
 	unsigned int length = skb->len - ETH_HLEN;
diff --git a/datapath/vport.h b/datapath/vport.h
index 6e80493..96a5b2b 100644
--- a/datapath/vport.h
+++ b/datapath/vport.h
@@ -52,16 +52,6 @@ int ovs_vport_set_upcall_portids(struct vport *, const struct nlattr *pids);
 int ovs_vport_get_upcall_portids(const struct vport *, struct sk_buff *);
 u32 ovs_vport_find_upcall_portid(const struct vport *, struct sk_buff *);
 
-int ovs_tunnel_get_egress_info(struct dp_upcall_info *upcall,
-			       struct net *net,
-			       struct sk_buff *,
-			       u8 ipproto,
-			       __be16 tp_src,
-			       __be16 tp_dst);
-
-int ovs_vport_get_egress_tun_info(struct vport *vport, struct sk_buff *skb,
-				  struct dp_upcall_info *upcall);
-
 /**
  * struct vport_portids - array of netlink portids of a vport.
  *                        must be protected by rcu.
@@ -140,8 +130,6 @@ struct vport_parms {
  * have any configuration.
  * @send: Send a packet on the device.
  * zero for dropped packets or negative for error.
- * @get_egress_tun_info: Get the egress tunnel 5-tuple and other info for
- * a packet.
  */
 struct vport_ops {
 	enum ovs_vport_type type;
@@ -154,8 +142,7 @@ struct vport_ops {
 	int (*get_options)(const struct vport *, struct sk_buff *);
 
 	netdev_tx_t (*send)(struct sk_buff *skb);
-	int (*get_egress_tun_info)(struct vport *, struct sk_buff *,
-				   struct dp_upcall_info *upcall);
+	int  (*fill_metadata_dst)(struct net_device *dev, struct sk_buff *skb);
 
 	struct module *owner;
 	struct list_head list;
