diff --git a/lib/netdev-linux.c b/lib/netdev-linux.c
index 584e804b6c8a..93e8ba03906e 100644
--- a/lib/netdev-linux.c
+++ b/lib/netdev-linux.c
@@ -149,6 +149,9 @@ struct tpacket_auxdata {
 #ifndef IFLA_STATS64
 #define IFLA_STATS64 23
 #endif
+#ifndef IFLA_VXLAN_COLLECT_METADATA
+#define IFLA_VXLAN_COLLECT_METADATA 25
+#endif
 #define rtnl_link_stats64 rpl_rtnl_link_stats64
 struct rtnl_link_stats64 {
     uint64_t rx_packets;
@@ -470,6 +473,8 @@ struct netdev_linux {
 
     /* For devices of class netdev_tap_class only. */
     int tap_fd;
+
+    struct netdev_tunnel_config *tnl_cfg;
 };
 
 struct netdev_rxq_linux {
@@ -514,6 +519,8 @@ static bool netdev_linux_miimon_enabled(void);
 static void netdev_linux_miimon_run(void);
 static void netdev_linux_miimon_wait(void);
 static int netdev_linux_get_mtu__(struct netdev_linux *netdev, int *mtup);
+static struct netdev_tunnel_config *
+cache_tunnel_config(const struct netdev_linux *netdev);
 
 static bool
 is_netdev_linux_class(const struct netdev_class *netdev_class)
@@ -756,6 +763,8 @@ netdev_linux_construct(struct netdev *netdev_)
         }
     }
 
+    netdev->tnl_cfg = cache_tunnel_config(netdev);
+
     return 0;
 }
 
@@ -812,6 +821,8 @@ netdev_linux_destruct(struct netdev *netdev_)
 {
     struct netdev_linux *netdev = netdev_linux_cast(netdev_);
 
+    free(netdev->tnl_cfg);
+
     if (netdev->tc && netdev->tc->ops->tc_destroy) {
         netdev->tc->ops->tc_destroy(netdev->tc);
     }
@@ -836,6 +847,12 @@ netdev_linux_dealloc(struct netdev *netdev_)
     free(netdev);
 }
 
+static const struct netdev_tunnel_config *
+netdev_linux_get_tunnel_config(const struct netdev *netdev_)
+{
+    return netdev_linux_cast(netdev_)->tnl_cfg;
+}
+
 static struct netdev_rxq *
 netdev_linux_rxq_alloc(void)
 {
@@ -2771,8 +2788,8 @@ netdev_linux_update_flags(struct netdev *netdev_, enum netdev_flags off,
     return error;
 }
 
-#define NETDEV_LINUX_CLASS(NAME, CONSTRUCT, GET_STATS,          \
-                           GET_FEATURES, GET_STATUS)            \
+#define NETDEV_LINUX_CLASS(NAME, CONSTRUCT, TUNNEL_CONFIG,      \
+                           GET_STATS, GET_FEATURES, GET_STATUS) \
 {                                                               \
     NAME,                                                       \
                                                                 \
@@ -2786,7 +2803,7 @@ netdev_linux_update_flags(struct netdev *netdev_, enum netdev_flags off,
     netdev_linux_dealloc,                                       \
     NULL,                       /* get_config */                \
     NULL,                       /* set_config */                \
-    NULL,                       /* get_tunnel_config */         \
+    TUNNEL_CONFIG,                                              \
     NULL,                       /* build header */              \
     NULL,                       /* push header */               \
     NULL,                       /* pop header */                \
@@ -2846,6 +2863,7 @@ const struct netdev_class netdev_linux_class =
     NETDEV_LINUX_CLASS(
         "system",
         netdev_linux_construct,
+        netdev_linux_get_tunnel_config,
         netdev_linux_get_stats,
         netdev_linux_get_features,
         netdev_linux_get_status);
@@ -2854,6 +2872,7 @@ const struct netdev_class netdev_tap_class =
     NETDEV_LINUX_CLASS(
         "tap",
         netdev_linux_construct_tap,
+        NULL,
         netdev_tap_get_stats,
         netdev_linux_get_features,
         netdev_linux_get_status);
@@ -2862,6 +2881,7 @@ const struct netdev_class netdev_internal_class =
     NETDEV_LINUX_CLASS(
         "internal",
         netdev_linux_construct,
+        NULL,
         netdev_internal_get_stats,
         NULL,                  /* get_features */
         netdev_internal_get_status);
@@ -5431,6 +5451,58 @@ get_stats_via_netlink(const struct netdev *netdev_, struct netdev_stats *stats)
     return error;
 }
 
+static struct netdev_tunnel_config *
+cache_tunnel_config(const struct netdev_linux *netdev)
+{
+    struct ofpbuf request;
+    struct ofpbuf *reply;
+    struct netdev_tunnel_config *result = NULL;
+    int error;
+
+    ofpbuf_init(&request, 0);
+    nl_msg_put_nlmsghdr(&request,
+                        sizeof(struct ifinfomsg) + NL_ATTR_SIZE(IFNAMSIZ),
+                        RTM_GETLINK, NLM_F_REQUEST);
+    ofpbuf_put_zeros(&request, sizeof(struct ifinfomsg));
+    nl_msg_put_string(&request, IFLA_IFNAME, netdev_get_name(&netdev->up));
+    error = nl_transact(NETLINK_ROUTE, &request, &reply);
+    ofpbuf_uninit(&request);
+    if (error) {
+        return NULL;
+    }
+
+    if (ofpbuf_try_pull(reply, NLMSG_HDRLEN + sizeof(struct ifinfomsg))) {
+        const struct nlattr *linkinfo = nl_attr_find(reply, 0, IFLA_LINKINFO);
+        const struct nlattr *a, *infodata;
+        if (linkinfo) {
+            a = nl_attr_find_nested(linkinfo, IFLA_INFO_KIND);
+            if (a && !strcmp(nl_attr_get(a), "vxlan")) {
+                infodata = nl_attr_find_nested(linkinfo, IFLA_INFO_DATA);
+                if (infodata)
+                    a = nl_attr_find_nested(infodata, IFLA_VXLAN_COLLECT_METADATA);
+                if (infodata && a && nl_attr_get_u8(a) != 0) {
+                    a = nl_attr_find_nested(infodata, IFLA_VXLAN_PORT);
+                    result = xzalloc(sizeof *result);
+                    result->dst_port = nl_attr_get_be16(a);
+                    result->in_key_flow = true;
+                    result->out_key_flow = true;
+                    result->ip_src_flow = true;
+                    result->ip_dst_flow = true;
+                    result->ttl_inherit = true;
+                    result->tos_inherit = true;
+                } else {
+                    VLOG_WARN_RL(&rl, "vxlan interface not in metadata mode");
+                }
+            }
+        }
+    } else {
+        VLOG_WARN_RL(&rl, "short RTM_GETLINK reply");
+    }
+
+    ofpbuf_delete(reply);
+    return result;
+}
+
 static int
 get_flags(const struct netdev *dev, unsigned int *flags)
 {
