diff mbox series

[ovs-dev,3/3] netdev-vport: RCU-fy tunnel config.

Message ID 20230520003120.1070717-4-i.maximets@ovn.org
State Accepted
Commit ce8828a37250feed1f8d6e23b33b936dc6a09b4e
Headers show
Series netdev-vport: Fix tunnel config thread safety. | expand

Checks

Context Check Description
ovsrobot/apply-robot success apply and check: success
ovsrobot/github-robot-_Build_and_Test success github build: passed

Commit Message

Ilya Maximets May 20, 2023, 12:31 a.m. UTC
Tunnel config can be accessed by multiple threads at the same time and
it is supposed to be protected by the netdev_vport mutex.  However,
many functions are getting direct access to it via netdev API without
taking the mutex, creating a potential for various race conditions.

Fix that by protecting the tunnel config with RCU.  The whole structure
is replaced on configuration changes.  Individual fields are never
updated and the structure itself is constant.  This way it can be safely
used by different threads within RCU grace period.

Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
---
 lib/netdev-native-tnl.c    |  84 +++++------------
 lib/netdev-vport-private.h |   3 +-
 lib/netdev-vport.c         | 189 +++++++++++++++++++++----------------
 lib/netdev.h               |   3 +
 4 files changed, 134 insertions(+), 145 deletions(-)

Comments

Simon Horman May 25, 2023, 11:51 a.m. UTC | #1
On Sat, May 20, 2023 at 02:31:20AM +0200, Ilya Maximets wrote:
> Tunnel config can be accessed by multiple threads at the same time and
> it is supposed to be protected by the netdev_vport mutex.  However,
> many functions are getting direct access to it via netdev API without
> taking the mutex, creating a potential for various race conditions.
> 
> Fix that by protecting the tunnel config with RCU.  The whole structure
> is replaced on configuration changes.  Individual fields are never
> updated and the structure itself is constant.  This way it can be safely
> used by different threads within RCU grace period.
> 
> Signed-off-by: Ilya Maximets <i.maximets@ovn.org>

Reviewed-by: Simon Horman <simon.horman@corigine.com>
Ilya Maximets May 25, 2023, 4:33 p.m. UTC | #2
On 5/25/23 13:51, Simon Horman wrote:
> On Sat, May 20, 2023 at 02:31:20AM +0200, Ilya Maximets wrote:
>> Tunnel config can be accessed by multiple threads at the same time and
>> it is supposed to be protected by the netdev_vport mutex.  However,
>> many functions are getting direct access to it via netdev API without
>> taking the mutex, creating a potential for various race conditions.
>>
>> Fix that by protecting the tunnel config with RCU.  The whole structure
>> is replaced on configuration changes.  Individual fields are never
>> updated and the structure itself is constant.  This way it can be safely
>> used by different threads within RCU grace period.
>>
>> Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
> 
> Reviewed-by: Simon Horman <simon.horman@corigine.com>
> 

Thanks!  I applied the set.  Also backported down to 2.17
as these are mainly bug fixes.

Best regards, Ilya Maximets.
diff mbox series

Patch

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