diff --git a/lib/netdev-native-tnl.c b/lib/netdev-native-tnl.c
index e31d61dd5..53dde61f1 100644
--- a/lib/netdev-native-tnl.c
+++ b/lib/netdev-native-tnl.c
@@ -320,7 +320,7 @@ netdev_tnl_ip_build_header(struct ovs_action_push_tnl *data,
 }
 
 static void *
-udp_build_header(struct netdev_tunnel_config *tnl_cfg,
+udp_build_header(const struct netdev_tunnel_config *tnl_cfg,
                  struct ovs_action_push_tnl *data,
                  const struct netdev_tnl_build_header_params *params)
 {
@@ -476,16 +476,11 @@ netdev_gre_build_header(const struct netdev *netdev,
                         struct ovs_action_push_tnl *data,
                         const struct netdev_tnl_build_header_params *params)
 {
-    struct netdev_vport *dev = netdev_vport_cast(netdev);
-    struct netdev_tunnel_config *tnl_cfg;
+    const struct netdev_tunnel_config *tnl_cfg;
     struct gre_base_hdr *greh;
     ovs_16aligned_be32 *options;
     unsigned int hlen;
 
-    /* XXX: RCUfy tnl_cfg. */
-    ovs_mutex_lock(&dev->mutex);
-    tnl_cfg = &dev->tnl_cfg;
-
     greh = netdev_tnl_ip_build_header(data, params, IPPROTO_GRE);
 
     if (params->flow->packet_type == htonl(PT_ETH)) {
@@ -493,8 +488,7 @@ netdev_gre_build_header(const struct netdev *netdev,
     } else if (pt_ns(params->flow->packet_type) == OFPHTN_ETHERTYPE) {
         greh->protocol = pt_ns_type_be(params->flow->packet_type);
     } else {
-        ovs_mutex_unlock(&dev->mutex);
-        return 1;
+        return EINVAL;
     }
     greh->flags = 0;
 
@@ -505,6 +499,8 @@ netdev_gre_build_header(const struct netdev *netdev,
         options++;
     }
 
+    tnl_cfg = netdev_get_tunnel_config(netdev);
+
     if (tnl_cfg->out_key_present) {
         greh->flags |= htons(GRE_KEY);
         put_16aligned_be32(options, be64_to_be32(params->flow->tunnel.tun_id));
@@ -517,8 +513,6 @@ netdev_gre_build_header(const struct netdev *netdev,
         options++;
     }
 
-    ovs_mutex_unlock(&dev->mutex);
-
     hlen = (uint8_t *) options - (uint8_t *) greh;
 
     data->header_len += hlen;
@@ -628,8 +622,7 @@ netdev_erspan_build_header(const struct netdev *netdev,
                            struct ovs_action_push_tnl *data,
                            const struct netdev_tnl_build_header_params *params)
 {
-    struct netdev_vport *dev = netdev_vport_cast(netdev);
-    struct netdev_tunnel_config *tnl_cfg;
+    const struct netdev_tunnel_config *tnl_cfg;
     struct gre_base_hdr *greh;
     struct erspan_base_hdr *ersh;
     unsigned int hlen;
@@ -637,21 +630,19 @@ netdev_erspan_build_header(const struct netdev *netdev,
     int erspan_ver;
     uint16_t sid;
 
-    /* XXX: RCUfy tnl_cfg. */
-    ovs_mutex_lock(&dev->mutex);
-    tnl_cfg = &dev->tnl_cfg;
     greh = netdev_tnl_ip_build_header(data, params, IPPROTO_GRE);
     ersh = ERSPAN_HDR(greh);
 
     tun_id = ntohl(be64_to_be32(params->flow->tunnel.tun_id));
     /* ERSPAN only has 10-bit session ID */
     if (tun_id & ~ERSPAN_SID_MASK) {
-        ovs_mutex_unlock(&dev->mutex);
-        return 1;
+        return EINVAL;
     } else {
         sid = (uint16_t) tun_id;
     }
 
+    tnl_cfg = netdev_get_tunnel_config(netdev);
+
     if (tnl_cfg->erspan_ver_flow) {
         erspan_ver = params->flow->tunnel.erspan_ver;
     } else {
@@ -698,12 +689,9 @@ netdev_erspan_build_header(const struct netdev *netdev,
         hlen = ERSPAN_GREHDR_LEN + sizeof *ersh + ERSPAN_V2_MDSIZE;
     } else {
         VLOG_WARN_RL(&err_rl, "ERSPAN version error %d", tnl_cfg->erspan_ver);
-        ovs_mutex_unlock(&dev->mutex);
-        return 1;
+        return EINVAL;
     }
 
-    ovs_mutex_unlock(&dev->mutex);
-
     data->header_len += hlen;
 
     if (params->is_ipv6) {
@@ -809,13 +797,12 @@ netdev_gtpu_build_header(const struct netdev *netdev,
                          struct ovs_action_push_tnl *data,
                          const struct netdev_tnl_build_header_params *params)
 {
-    struct netdev_vport *dev = netdev_vport_cast(netdev);
-    struct netdev_tunnel_config *tnl_cfg;
+    const struct netdev_tunnel_config *tnl_cfg;
     struct gtpuhdr *gtph;
     unsigned int gtpu_hlen;
 
-    ovs_mutex_lock(&dev->mutex);
-    tnl_cfg = &dev->tnl_cfg;
+    tnl_cfg = netdev_get_tunnel_config(netdev);
+
     gtph = udp_build_header(tnl_cfg, data, params);
 
     /* Set to default if not set in flow. */
@@ -831,7 +818,6 @@ netdev_gtpu_build_header(const struct netdev *netdev,
         gtph->md.flags |= GTPU_S_MASK;
         gtpu_hlen += sizeof(struct gtpuhdr_opt);
     }
-    ovs_mutex_unlock(&dev->mutex);
 
     data->header_len += gtpu_hlen;
     data->tnl_type = OVS_VPORT_TYPE_GTPU;
@@ -844,19 +830,15 @@ netdev_srv6_build_header(const struct netdev *netdev,
                          struct ovs_action_push_tnl *data,
                          const struct netdev_tnl_build_header_params *params)
 {
-    struct netdev_vport *dev = netdev_vport_cast(netdev);
-    struct netdev_tunnel_config *tnl_cfg;
+    const struct netdev_tunnel_config *tnl_cfg;
     const struct in6_addr *segs;
     struct srv6_base_hdr *srh;
     struct in6_addr *s;
     ovs_be16 dl_type;
-    int err = 0;
     int nr_segs;
     int i;
 
-    ovs_mutex_lock(&dev->mutex);
-    tnl_cfg = &dev->tnl_cfg;
-
+    tnl_cfg = netdev_get_tunnel_config(netdev);
     if (tnl_cfg->srv6_num_segs) {
         nr_segs = tnl_cfg->srv6_num_segs;
         segs = tnl_cfg->srv6_segs;
@@ -870,8 +852,7 @@ netdev_srv6_build_header(const struct netdev *netdev,
     }
 
     if (!ipv6_addr_equals(&segs[0], &params->flow->tunnel.ipv6_dst)) {
-        err = EINVAL;
-        goto out;
+        return EINVAL;
     }
 
     srh = netdev_tnl_ip_build_header(data, params, IPPROTO_ROUTING);
@@ -888,8 +869,7 @@ netdev_srv6_build_header(const struct netdev *netdev,
     } else if (dl_type == htons(ETH_TYPE_IPV6)) {
         srh->rt_hdr.nexthdr = IPPROTO_IPV6;
     } else {
-        err = EOPNOTSUPP;
-        goto out;
+        return EOPNOTSUPP;
     }
 
     s = ALIGNED_CAST(struct in6_addr *,
@@ -902,10 +882,8 @@ netdev_srv6_build_header(const struct netdev *netdev,
 
     data->header_len += sizeof *srh + 8 * srh->rt_hdr.hdrlen;
     data->tnl_type = OVS_VPORT_TYPE_SRV6;
-out:
-    ovs_mutex_unlock(&dev->mutex);
 
-    return err;
+    return 0;
 }
 
 void
@@ -1044,13 +1022,10 @@ netdev_vxlan_build_header(const struct netdev *netdev,
                           struct ovs_action_push_tnl *data,
                           const struct netdev_tnl_build_header_params *params)
 {
-    struct netdev_vport *dev = netdev_vport_cast(netdev);
-    struct netdev_tunnel_config *tnl_cfg;
+    const struct netdev_tunnel_config *tnl_cfg;
     struct vxlanhdr *vxh;
 
-    /* XXX: RCUfy tnl_cfg. */
-    ovs_mutex_lock(&dev->mutex);
-    tnl_cfg = &dev->tnl_cfg;
+    tnl_cfg = netdev_get_tunnel_config(netdev);
 
     vxh = udp_build_header(tnl_cfg, data, params);
 
@@ -1075,10 +1050,10 @@ netdev_vxlan_build_header(const struct netdev *netdev,
                 vxh->vx_gpe.next_protocol = VXLAN_GPE_NP_ETHERNET;
                 break;
             default:
-                goto drop;
+                return EINVAL;
             }
         } else {
-            goto drop;
+            return EINVAL;
         }
     } else {
         put_16aligned_be32(&vxh->vx_flags, htonl(VXLAN_FLAGS));
@@ -1086,14 +1061,9 @@ netdev_vxlan_build_header(const struct netdev *netdev,
                            htonl(ntohll(params->flow->tunnel.tun_id) << 8));
     }
 
-    ovs_mutex_unlock(&dev->mutex);
     data->header_len += sizeof *vxh;
     data->tnl_type = OVS_VPORT_TYPE_VXLAN;
     return 0;
-
-drop:
-    ovs_mutex_unlock(&dev->mutex);
-    return 1;
 }
 
 struct dp_packet *
@@ -1157,22 +1127,14 @@ netdev_geneve_build_header(const struct netdev *netdev,
                            struct ovs_action_push_tnl *data,
                            const struct netdev_tnl_build_header_params *params)
 {
-    struct netdev_vport *dev = netdev_vport_cast(netdev);
-    struct netdev_tunnel_config *tnl_cfg;
     struct genevehdr *gnh;
     int opt_len;
     bool crit_opt;
 
-    /* XXX: RCUfy tnl_cfg. */
-    ovs_mutex_lock(&dev->mutex);
-    tnl_cfg = &dev->tnl_cfg;
-
-    gnh = udp_build_header(tnl_cfg, data, params);
+    gnh = udp_build_header(netdev_get_tunnel_config(netdev), data, params);
 
     put_16aligned_be32(&gnh->vni, htonl(ntohll(params->flow->tunnel.tun_id) << 8));
 
-    ovs_mutex_unlock(&dev->mutex);
-
     opt_len = tun_metadata_to_geneve_header(&params->flow->tunnel,
                                             gnh->options, &crit_opt);
 
diff --git a/lib/netdev-vport-private.h b/lib/netdev-vport-private.h
index e3c3bdb43..586231057 100644
--- a/lib/netdev-vport-private.h
+++ b/lib/netdev-vport-private.h
@@ -28,6 +28,8 @@
 struct netdev_vport {
     struct netdev up;
 
+    OVSRCU_TYPE(const struct netdev_tunnel_config *) tnl_cfg;
+
     /* Sequence number for outgoing GRE packets. */
     atomic_count gre_seqno;
 
@@ -38,7 +40,6 @@ struct netdev_vport {
     struct netdev_stats stats;
 
     /* Tunnels. */
-    struct netdev_tunnel_config tnl_cfg;
     char egress_iface[IFNAMSIZ];
     bool carrier_status;
 
diff --git a/lib/netdev-vport.c b/lib/netdev-vport.c
index 6bbaa2feb..480117a14 100644
--- a/lib/netdev-vport.c
+++ b/lib/netdev-vport.c
@@ -69,8 +69,8 @@ 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 *);
 static void update_vxlan_global_cfg(struct netdev *,
-                                    struct netdev_tunnel_config *,
-                                    struct netdev_tunnel_config *);
+                                    const struct netdev_tunnel_config *,
+                                    const struct netdev_tunnel_config *);
 
 struct vport_class {
     const char *dpif_port;
@@ -91,10 +91,16 @@ vport_class_cast(const struct netdev_class *class)
     return CONTAINER_OF(class, struct vport_class, netdev_class);
 }
 
+static const struct netdev_tunnel_config *
+vport_tunnel_config(struct netdev_vport *netdev)
+{
+    return ovsrcu_get(const struct netdev_tunnel_config *, &netdev->tnl_cfg);
+}
+
 static const struct netdev_tunnel_config *
 get_netdev_tunnel_config(const struct netdev *netdev)
 {
-    return &netdev_vport_cast(netdev)->tnl_cfg;
+    return vport_tunnel_config(netdev_vport_cast(netdev));
 }
 
 bool
@@ -135,8 +141,6 @@ netdev_vport_get_dpif_port(const struct netdev *netdev,
     }
 
     if (netdev_vport_needs_dst_port(netdev)) {
-        const struct netdev_vport *vport = netdev_vport_cast(netdev);
-
         /*
          * Note: IFNAMSIZ is 16 bytes long. Implementations should choose
          * a dpif port name that is short enough to fit including any
@@ -145,7 +149,7 @@ netdev_vport_get_dpif_port(const struct netdev *netdev,
         BUILD_ASSERT(NETDEV_VPORT_NAME_BUFSIZE >= IFNAMSIZ);
         ovs_assert(strlen(dpif_port) + 6 < IFNAMSIZ);
         snprintf(namebuf, bufsize, "%s_%d", dpif_port,
-                 ntohs(vport->tnl_cfg.dst_port));
+                 ntohs(netdev_get_tunnel_config(netdev)->dst_port));
         return namebuf;
     } else {
         return dpif_port;
@@ -163,12 +167,14 @@ netdev_vport_route_changed(void)
 
     vports = netdev_get_vports(&n_vports);
     for (i = 0; i < n_vports; i++) {
+        const struct netdev_tunnel_config *tnl_cfg;
         struct netdev *netdev_ = vports[i];
         struct netdev_vport *netdev = netdev_vport_cast(netdev_);
 
         ovs_mutex_lock(&netdev->mutex);
         /* Finds all tunnel vports. */
-        if (ipv6_addr_is_set(&netdev->tnl_cfg.ipv6_dst)) {
+        tnl_cfg = netdev_get_tunnel_config(netdev_);
+        if (tnl_cfg && ipv6_addr_is_set(&tnl_cfg->ipv6_dst)) {
             if (tunnel_check_status_change__(netdev)) {
                 netdev_change_seq_changed(netdev_);
             }
@@ -208,26 +214,31 @@ netdev_vport_construct(struct netdev *netdev_)
         port = atoi(p);
     }
 
+    struct netdev_tunnel_config *tnl_cfg = xzalloc(sizeof *tnl_cfg);
+
     /* If a destination port for tunnel ports is specified in the netdev
      * name, use it instead of the default one. Otherwise, use the default
      * destination port */
     if (!strcmp(type, "geneve")) {
-        dev->tnl_cfg.dst_port = port ? htons(port) : htons(GENEVE_DST_PORT);
+        tnl_cfg->dst_port = port ? htons(port) : htons(GENEVE_DST_PORT);
     } else if (!strcmp(type, "vxlan")) {
-        dev->tnl_cfg.dst_port = port ? htons(port) : htons(VXLAN_DST_PORT);
-        update_vxlan_global_cfg(netdev_, NULL, &dev->tnl_cfg);
+        tnl_cfg->dst_port = port ? htons(port) : htons(VXLAN_DST_PORT);
+        update_vxlan_global_cfg(netdev_, NULL, tnl_cfg);
     } else if (!strcmp(type, "lisp")) {
-        dev->tnl_cfg.dst_port = port ? htons(port) : htons(LISP_DST_PORT);
+        tnl_cfg->dst_port = port ? htons(port) : htons(LISP_DST_PORT);
     } else if (!strcmp(type, "stt")) {
-        dev->tnl_cfg.dst_port = port ? htons(port) : htons(STT_DST_PORT);
+        tnl_cfg->dst_port = port ? htons(port) : htons(STT_DST_PORT);
     } else if (!strcmp(type, "gtpu")) {
-        dev->tnl_cfg.dst_port = port ? htons(port) : htons(GTPU_DST_PORT);
+        tnl_cfg->dst_port = port ? htons(port) : htons(GTPU_DST_PORT);
     } else if (!strcmp(type, "bareudp")) {
-        dev->tnl_cfg.dst_port = htons(port);
+        tnl_cfg->dst_port = htons(port);
     }
 
-    dev->tnl_cfg.dont_fragment = true;
-    dev->tnl_cfg.ttl = DEFAULT_TTL;
+    tnl_cfg->dont_fragment = true;
+    tnl_cfg->ttl = DEFAULT_TTL;
+
+    ovsrcu_set(&dev->tnl_cfg, tnl_cfg);
+
     return 0;
 }
 
@@ -235,12 +246,15 @@ static void
 netdev_vport_destruct(struct netdev *netdev_)
 {
     struct netdev_vport *netdev = netdev_vport_cast(netdev_);
+    const struct netdev_tunnel_config *tnl_cfg = vport_tunnel_config(netdev);
     const char *type = netdev_get_type(netdev_);
 
     if (!strcmp(type, "vxlan")) {
-        update_vxlan_global_cfg(netdev_, &netdev->tnl_cfg, NULL);
+        update_vxlan_global_cfg(netdev_, tnl_cfg, NULL);
     }
 
+    ovsrcu_set(&netdev->tnl_cfg, NULL);
+    ovsrcu_postpone(free, CONST_CAST(struct netdev_tunnel_config *, tnl_cfg));
     free(netdev->peer);
     ovs_mutex_destroy(&netdev->mutex);
 }
@@ -283,15 +297,16 @@ static bool
 tunnel_check_status_change__(struct netdev_vport *netdev)
     OVS_REQUIRES(netdev->mutex)
 {
+    const struct netdev_tunnel_config *tnl_cfg = vport_tunnel_config(netdev);
+    const struct in6_addr *route;
     char iface[IFNAMSIZ];
     bool status = false;
-    struct in6_addr *route;
     struct in6_addr gw;
     uint32_t mark;
 
     iface[0] = '\0';
-    route = &netdev->tnl_cfg.ipv6_dst;
-    mark = netdev->tnl_cfg.egress_pkt_mark;
+    route = &tnl_cfg->ipv6_dst;
+    mark = tnl_cfg->egress_pkt_mark;
     if (ovs_router_lookup(mark, route, iface, NULL, &gw)) {
         struct netdev *egress_netdev;
 
@@ -498,8 +513,8 @@ vxlan_get_port_ext_gbp_str(uint16_t port, bool gbp,
 
 static void
 update_vxlan_global_cfg(struct netdev *netdev,
-                        struct netdev_tunnel_config *old_cfg,
-                        struct netdev_tunnel_config *new_cfg)
+                        const struct netdev_tunnel_config *old_cfg,
+                        const struct netdev_tunnel_config *new_cfg)
 {
     unsigned int count;
     char namebuf[20];
@@ -543,19 +558,20 @@ static bool
 is_concomitant_vxlan_tunnel_present(struct netdev_vport *dev,
                                     const struct netdev_tunnel_config *tnl_cfg)
 {
-    char namebuf[20];
-    const char *type = netdev_get_type(&dev->up);
+    const struct netdev_tunnel_config *dev_tnl_cfg = vport_tunnel_config(dev);
     struct vport_class *vclass = vport_class_cast(netdev_get_class(&dev->up));
+    const char *type = netdev_get_type(&dev->up);
+    char namebuf[20];
 
     if (strcmp(type, "vxlan")) {
         return false;
     }
 
-    if (dev->tnl_cfg.dst_port == tnl_cfg->dst_port &&
-        (dev->tnl_cfg.exts & (1 << OVS_VXLAN_EXT_GBP)) ==
+    if (dev_tnl_cfg->dst_port == tnl_cfg->dst_port &&
+        (dev_tnl_cfg->exts & (1 << OVS_VXLAN_EXT_GBP)) ==
         (tnl_cfg->exts & (1 << OVS_VXLAN_EXT_GBP))) {
 
-        if (ntohs(dev->tnl_cfg.dst_port) == VXLAN_DST_PORT) {
+        if (ntohs(dev_tnl_cfg->dst_port) == VXLAN_DST_PORT) {
             /* Special case where we kept the default port/gbp, only ok if
                the opposite of the default does not exits */
             vxlan_get_port_ext_gbp_str(ntohs(tnl_cfg->dst_port),
@@ -571,9 +587,9 @@ is_concomitant_vxlan_tunnel_present(struct netdev_vport *dev,
     }
 
     /* Same port: ok if no one is left with the previous configuration */
-    if (dev->tnl_cfg.dst_port == tnl_cfg->dst_port) {
-        vxlan_get_port_ext_gbp_str(ntohs(dev->tnl_cfg.dst_port),
-                                   dev->tnl_cfg.exts &
+    if (dev_tnl_cfg->dst_port == tnl_cfg->dst_port) {
+        vxlan_get_port_ext_gbp_str(ntohs(dev_tnl_cfg->dst_port),
+                                   dev_tnl_cfg->exts &
                                    (1 << OVS_VXLAN_EXT_GBP),
                                    namebuf, sizeof(namebuf));
 
@@ -601,6 +617,7 @@ static int
 set_tunnel_config(struct netdev *dev_, const struct smap *args, char **errp)
 {
     struct netdev_vport *dev = netdev_vport_cast(dev_);
+    const struct netdev_tunnel_config *curr_tnl_cfg;
     const char *name = netdev_get_name(dev_);
     const char *type = netdev_get_type(dev_);
     struct ds errors = DS_EMPTY_INITIALIZER;
@@ -902,11 +919,16 @@ set_tunnel_config(struct netdev *dev_, const struct smap *args, char **errp)
         err = EEXIST;
         goto out;
     }
-    update_vxlan_global_cfg(dev_, &dev->tnl_cfg, &tnl_cfg);
 
     ovs_mutex_lock(&dev->mutex);
-    if (memcmp(&dev->tnl_cfg, &tnl_cfg, sizeof tnl_cfg)) {
-        dev->tnl_cfg = tnl_cfg;
+
+    curr_tnl_cfg = vport_tunnel_config(dev);
+    update_vxlan_global_cfg(dev_, curr_tnl_cfg, &tnl_cfg);
+
+    if (memcmp(curr_tnl_cfg, &tnl_cfg, sizeof tnl_cfg)) {
+        ovsrcu_set(&dev->tnl_cfg, xmemdup(&tnl_cfg, sizeof tnl_cfg));
+        ovsrcu_postpone(free, CONST_CAST(struct netdev_tunnel_config *,
+                                         curr_tnl_cfg));
         tunnel_check_status_change__(dev);
         netdev_change_seq_changed(dev_);
     }
@@ -931,61 +953,60 @@ out:
 static int
 get_tunnel_config(const struct netdev *dev, struct smap *args)
 {
-    struct netdev_vport *netdev = netdev_vport_cast(dev);
+    const struct netdev_tunnel_config *tnl_cfg = netdev_get_tunnel_config(dev);
     const char *type = netdev_get_type(dev);
-    struct netdev_tunnel_config tnl_cfg;
 
-    ovs_mutex_lock(&netdev->mutex);
-    tnl_cfg = netdev->tnl_cfg;
-    ovs_mutex_unlock(&netdev->mutex);
+    if (!tnl_cfg) {
+        return 0;
+    }
 
-    if (ipv6_addr_is_set(&tnl_cfg.ipv6_dst)) {
-        smap_add_ipv6(args, "remote_ip", &tnl_cfg.ipv6_dst);
-    } else if (tnl_cfg.ip_dst_flow) {
+    if (ipv6_addr_is_set(&tnl_cfg->ipv6_dst)) {
+        smap_add_ipv6(args, "remote_ip", &tnl_cfg->ipv6_dst);
+    } else if (tnl_cfg->ip_dst_flow) {
         smap_add(args, "remote_ip", "flow");
     }
 
-    if (ipv6_addr_is_set(&tnl_cfg.ipv6_src)) {
-        smap_add_ipv6(args, "local_ip", &tnl_cfg.ipv6_src);
-    } else if (tnl_cfg.ip_src_flow) {
+    if (ipv6_addr_is_set(&tnl_cfg->ipv6_src)) {
+        smap_add_ipv6(args, "local_ip", &tnl_cfg->ipv6_src);
+    } else if (tnl_cfg->ip_src_flow) {
         smap_add(args, "local_ip", "flow");
     }
 
-    if (tnl_cfg.in_key_flow && tnl_cfg.out_key_flow) {
+    if (tnl_cfg->in_key_flow && tnl_cfg->out_key_flow) {
         smap_add(args, "key", "flow");
-    } else if (tnl_cfg.in_key_present && tnl_cfg.out_key_present
-               && tnl_cfg.in_key == tnl_cfg.out_key) {
-        smap_add_format(args, "key", "%"PRIu64, ntohll(tnl_cfg.in_key));
+    } else if (tnl_cfg->in_key_present && tnl_cfg->out_key_present
+               && tnl_cfg->in_key == tnl_cfg->out_key) {
+        smap_add_format(args, "key", "%"PRIu64, ntohll(tnl_cfg->in_key));
     } else {
-        if (tnl_cfg.in_key_flow) {
+        if (tnl_cfg->in_key_flow) {
             smap_add(args, "in_key", "flow");
-        } else if (tnl_cfg.in_key_present) {
+        } else if (tnl_cfg->in_key_present) {
             smap_add_format(args, "in_key", "%"PRIu64,
-                            ntohll(tnl_cfg.in_key));
+                            ntohll(tnl_cfg->in_key));
         }
 
-        if (tnl_cfg.out_key_flow) {
+        if (tnl_cfg->out_key_flow) {
             smap_add(args, "out_key", "flow");
-        } else if (tnl_cfg.out_key_present) {
+        } else if (tnl_cfg->out_key_present) {
             smap_add_format(args, "out_key", "%"PRIu64,
-                            ntohll(tnl_cfg.out_key));
+                            ntohll(tnl_cfg->out_key));
         }
     }
 
-    if (tnl_cfg.ttl_inherit) {
+    if (tnl_cfg->ttl_inherit) {
         smap_add(args, "ttl", "inherit");
-    } else if (tnl_cfg.ttl != DEFAULT_TTL) {
-        smap_add_format(args, "ttl", "%"PRIu8, tnl_cfg.ttl);
+    } else if (tnl_cfg->ttl != DEFAULT_TTL) {
+        smap_add_format(args, "ttl", "%"PRIu8, tnl_cfg->ttl);
     }
 
-    if (tnl_cfg.tos_inherit) {
+    if (tnl_cfg->tos_inherit) {
         smap_add(args, "tos", "inherit");
-    } else if (tnl_cfg.tos) {
-        smap_add_format(args, "tos", "0x%x", tnl_cfg.tos);
+    } else if (tnl_cfg->tos) {
+        smap_add_format(args, "tos", "0x%x", tnl_cfg->tos);
     }
 
-    if (tnl_cfg.dst_port) {
-        uint16_t dst_port = ntohs(tnl_cfg.dst_port);
+    if (tnl_cfg->dst_port) {
+        uint16_t dst_port = ntohs(tnl_cfg->dst_port);
 
         if ((!strcmp("geneve", type) && dst_port != GENEVE_DST_PORT) ||
             (!strcmp("vxlan", type) && dst_port != VXLAN_DST_PORT) ||
@@ -997,33 +1018,33 @@ get_tunnel_config(const struct netdev *dev, struct smap *args)
         }
     }
 
-    if (tnl_cfg.csum) {
+    if (tnl_cfg->csum) {
         smap_add(args, "csum", "true");
     }
 
-    if (tnl_cfg.set_seq) {
+    if (tnl_cfg->set_seq) {
         smap_add(args, "seq", "true");
     }
 
-    enum tunnel_layers layers = tunnel_supported_layers(type, &tnl_cfg);
-    if (tnl_cfg.pt_mode != default_pt_mode(layers)) {
+    enum tunnel_layers layers = tunnel_supported_layers(type, tnl_cfg);
+    if (tnl_cfg->pt_mode != default_pt_mode(layers)) {
         smap_add(args, "packet_type",
-                 tnl_cfg.pt_mode == NETDEV_PT_LEGACY_L2 ? "legacy_l2"
-                 : tnl_cfg.pt_mode == NETDEV_PT_LEGACY_L3 ? "legacy_l3"
+                 tnl_cfg->pt_mode == NETDEV_PT_LEGACY_L2 ? "legacy_l2"
+                 : tnl_cfg->pt_mode == NETDEV_PT_LEGACY_L3 ? "legacy_l3"
                  : "ptap");
     }
 
-    if (!tnl_cfg.dont_fragment) {
+    if (!tnl_cfg->dont_fragment) {
         smap_add(args, "df_default", "false");
     }
 
-    if (tnl_cfg.set_egress_pkt_mark) {
+    if (tnl_cfg->set_egress_pkt_mark) {
         smap_add_format(args, "egress_pkt_mark",
-                        "%"PRIu32, tnl_cfg.egress_pkt_mark);
+                        "%"PRIu32, tnl_cfg->egress_pkt_mark);
     }
 
     if (!strcmp("erspan", type) || !strcmp("ip6erspan", type)) {
-        if (tnl_cfg.erspan_ver_flow) {
+        if (tnl_cfg->erspan_ver_flow) {
             /* since version number is not determined,
              * assume print all other as flow
              */
@@ -1032,27 +1053,27 @@ get_tunnel_config(const struct netdev *dev, struct smap *args)
             smap_add(args, "erspan_dir", "flow");
             smap_add(args, "erspan_hwid", "flow");
         } else {
-            smap_add_format(args, "erspan_ver", "%d", tnl_cfg.erspan_ver);
+            smap_add_format(args, "erspan_ver", "%d", tnl_cfg->erspan_ver);
 
-            if (tnl_cfg.erspan_ver == 1) {
-                if (tnl_cfg.erspan_idx_flow) {
+            if (tnl_cfg->erspan_ver == 1) {
+                if (tnl_cfg->erspan_idx_flow) {
                     smap_add(args, "erspan_idx", "flow");
                 } else {
                     smap_add_format(args, "erspan_idx", "0x%x",
-                                    tnl_cfg.erspan_idx);
+                                    tnl_cfg->erspan_idx);
                 }
-            } else if (tnl_cfg.erspan_ver == 2) {
-                if (tnl_cfg.erspan_dir_flow) {
+            } else if (tnl_cfg->erspan_ver == 2) {
+                if (tnl_cfg->erspan_dir_flow) {
                     smap_add(args, "erspan_dir", "flow");
                 } else {
                     smap_add_format(args, "erspan_dir", "%d",
-                                    tnl_cfg.erspan_dir);
+                                    tnl_cfg->erspan_dir);
                 }
-                if (tnl_cfg.erspan_hwid_flow) {
+                if (tnl_cfg->erspan_hwid_flow) {
                     smap_add(args, "erspan_hwid", "flow");
                 } else {
                     smap_add_format(args, "erspan_hwid", "0x%x",
-                                    tnl_cfg.erspan_hwid);
+                                    tnl_cfg->erspan_hwid);
                 }
             }
         }
@@ -1182,9 +1203,11 @@ netdev_vport_get_stats(const struct netdev *netdev, struct netdev_stats *stats)
 static enum netdev_pt_mode
 netdev_vport_get_pt_mode(const struct netdev *netdev)
 {
-    struct netdev_vport *dev = netdev_vport_cast(netdev);
+    const struct netdev_tunnel_config *tnl_cfg;
+
+    tnl_cfg = netdev_get_tunnel_config(netdev);
 
-    return dev->tnl_cfg.pt_mode;
+    return tnl_cfg ? tnl_cfg->pt_mode : NETDEV_PT_UNKNOWN;
 }
 
 
diff --git a/lib/netdev.h b/lib/netdev.h
index 1fab91273..aaec9ded1 100644
--- a/lib/netdev.h
+++ b/lib/netdev.h
@@ -72,6 +72,9 @@ struct sset;
 struct ovs_action_push_tnl;
 
 enum netdev_pt_mode {
+    /* Unknown mode.  The netdev is not configured yet. */
+    NETDEV_PT_UNKNOWN = 0,
+
     /* The netdev is packet type aware.  It can potentially carry any kind of
      * packet.  This "modern" mode is appropriate for both netdevs that handle
      * only a single kind of packet (such as a virtual or physical Ethernet
