diff --git a/SOURCES/openvswitch-3.1.0.patch b/SOURCES/openvswitch-3.1.0.patch
index 081bd36..11ea003 100644
--- a/SOURCES/openvswitch-3.1.0.patch
+++ b/SOURCES/openvswitch-3.1.0.patch
@@ -748,6 +748,300 @@ index c576ae620..474344194 100644
 +        wc->masks.nw_proto = 0xff;
 +    }
 +}
+diff --git a/lib/meta-flow.xml b/lib/meta-flow.xml
+index a1a20366d..bdd12f6a7 100644
+--- a/lib/meta-flow.xml
++++ b/lib/meta-flow.xml
+@@ -4312,9 +4312,9 @@ r r c c c.
+         <bits name="pln" above="8" below="4" width=".2"/>
+         <bits name="op" above="16" width=".2" fill="yes"/>
+         <bits name="sha" above="48" width="0.5" fill="yes"/>
+-        <bits name="spa" above="16" width="0.3" fill="yes"/>
++        <bits name="spa" above="32" width="0.5" fill="yes"/>
+         <bits name="tha" above="48" width="0.5" fill="yes"/>
+-        <bits name="tpa" above="16" width="0.3" fill="yes"/>
++        <bits name="tpa" above="32" width="0.5" fill="yes"/>
+       </header>
+     </diagram>
+ 
+diff --git a/lib/netdev-native-tnl.c b/lib/netdev-native-tnl.c
+index b89dfdd52..6c9094638 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)
+ {
+@@ -452,7 +452,6 @@ netdev_gre_push_header(const struct netdev *netdev,
+                        const struct ovs_action_push_tnl *data)
+ {
+     struct netdev_vport *dev = netdev_vport_cast(netdev);
+-    struct netdev_tunnel_config *tnl_cfg;
+     struct gre_base_hdr *greh;
+     int ip_tot_size;
+ 
+@@ -468,8 +467,7 @@ netdev_gre_push_header(const struct netdev *netdev,
+         int seq_ofs = gre_header_len(greh->flags) - 4;
+         ovs_16aligned_be32 *seq_opt =
+             ALIGNED_CAST(ovs_16aligned_be32 *, (char *)greh + seq_ofs);
+-        tnl_cfg = &dev->tnl_cfg;
+-        put_16aligned_be32(seq_opt, htonl(tnl_cfg->seqno++));
++        put_16aligned_be32(seq_opt, htonl(atomic_count_inc(&dev->gre_seqno)));
+     }
+ }
+ 
+@@ -478,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)) {
+@@ -495,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;
+ 
+@@ -507,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));
+@@ -519,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;
+@@ -605,7 +597,6 @@ netdev_erspan_push_header(const struct netdev *netdev,
+                           const struct ovs_action_push_tnl *data)
+ {
+     struct netdev_vport *dev = netdev_vport_cast(netdev);
+-    struct netdev_tunnel_config *tnl_cfg;
+     struct erspan_base_hdr *ersh;
+     struct gre_base_hdr *greh;
+     struct erspan_md2 *md2;
+@@ -615,9 +606,8 @@ netdev_erspan_push_header(const struct netdev *netdev,
+                                      data->header_len, &ip_tot_size);
+ 
+     /* update GRE seqno */
+-    tnl_cfg = &dev->tnl_cfg;
+     ovs_16aligned_be32 *seqno = (ovs_16aligned_be32 *) (greh + 1);
+-    put_16aligned_be32(seqno, htonl(tnl_cfg->seqno++));
++    put_16aligned_be32(seqno, htonl(atomic_count_inc(&dev->gre_seqno)));
+ 
+     /* update v2 timestamp */
+     if (greh->protocol == htons(ETH_TYPE_ERSPAN2)) {
+@@ -632,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;
+@@ -641,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 {
+@@ -702,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) {
+@@ -786,7 +770,6 @@ netdev_gtpu_push_header(const struct netdev *netdev,
+                         const struct ovs_action_push_tnl *data)
+ {
+     struct netdev_vport *dev = netdev_vport_cast(netdev);
+-    struct netdev_tunnel_config *tnl_cfg;
+     struct udp_header *udp;
+     struct gtpuhdr *gtpuh;
+     int ip_tot_size;
+@@ -801,10 +784,9 @@ netdev_gtpu_push_header(const struct netdev *netdev,
+ 
+     gtpuh = ALIGNED_CAST(struct gtpuhdr *, udp + 1);
+ 
+-    tnl_cfg = &dev->tnl_cfg;
+-    if (tnl_cfg->set_seq) {
++    if (gtpuh->md.flags & GTPU_S_MASK) {
+         ovs_be16 *seqno = ALIGNED_CAST(ovs_be16 *, gtpuh + 1);
+-        *seqno = htons(tnl_cfg->seqno++);
++        *seqno = htons(atomic_count_inc(&dev->gre_seqno));
+         payload_len += sizeof(struct gtpuhdr_opt);
+     }
+     gtpuh->len = htons(payload_len);
+@@ -815,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. */
+@@ -837,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;
+@@ -920,13 +900,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);
+ 
+@@ -951,10 +928,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));
+@@ -962,14 +939,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 *
+@@ -1033,22 +1005,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-offload-dpdk.c b/lib/netdev-offload-dpdk.c
+index b3421c099..2d7858f51 100644
+--- a/lib/netdev-offload-dpdk.c
++++ b/lib/netdev-offload-dpdk.c
+@@ -2345,13 +2345,13 @@ netdev_offload_dpdk_flow_destroy(struct ufid_to_rte_flow_data *rte_flow_data)
+             ovsrcu_get(void *, &netdev->hw_info.offload_data);
+         data->rte_flow_counters[tid]--;
+ 
+-        ufid_to_rte_flow_disassociate(rte_flow_data);
+         VLOG_DBG_RL(&rl, "%s/%s: rte_flow 0x%"PRIxPTR
+                     " flow destroy %d ufid " UUID_FMT,
+                     netdev_get_name(netdev), netdev_get_name(physdev),
+                     (intptr_t) rte_flow,
+                     netdev_dpdk_get_port_id(physdev),
+                     UUID_ARGS((struct uuid *) ufid));
++        ufid_to_rte_flow_disassociate(rte_flow_data);
+     } else {
+         VLOG_ERR("Failed flow: %s/%s: flow destroy %d ufid " UUID_FMT,
+                  netdev_get_name(netdev), netdev_get_name(physdev),
 diff --git a/lib/netdev-offload-tc.c b/lib/netdev-offload-tc.c
 index 4c78c4816..09f6393f8 100644
 --- a/lib/netdev-offload-tc.c
@@ -1165,6 +1459,460 @@ index 4592262bd..a5fa62487 100644
  }
  
  void
+diff --git a/lib/netdev-vport-private.h b/lib/netdev-vport-private.h
+index d89a28c66..586231057 100644
+--- a/lib/netdev-vport-private.h
++++ b/lib/netdev-vport-private.h
+@@ -22,11 +22,17 @@
+ #include "compiler.h"
+ #include "netdev.h"
+ #include "netdev-provider.h"
++#include "ovs-atomic.h"
+ #include "ovs-thread.h"
+ 
+ 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;
++
+     /* Protects all members below. */
+     struct ovs_mutex mutex;
+ 
+@@ -34,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 3b3927865..4cfe15d5a 100644
+--- a/lib/netdev-vport.c
++++ b/lib/netdev-vport.c
+@@ -37,6 +37,7 @@
+ #include "netdev-provider.h"
+ #include "netdev-vport-private.h"
+ #include "openvswitch/dynamic-string.h"
++#include "ovs-atomic.h"
+ #include "ovs-router.h"
+ #include "packets.h"
+ #include "openvswitch/poll-loop.h"
+@@ -68,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;
+@@ -90,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
+@@ -134,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
+@@ -144,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;
+@@ -162,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_);
+             }
+@@ -198,6 +205,7 @@ netdev_vport_construct(struct netdev *netdev_)
+     uint16_t port = 0;
+ 
+     ovs_mutex_init(&dev->mutex);
++    atomic_count_init(&dev->gre_seqno, 0);
+     eth_addr_random(&dev->etheraddr);
+ 
+     if (name && dpif_port && (strlen(name) > strlen(dpif_port) + 1) &&
+@@ -206,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;
+ }
+ 
+@@ -233,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);
+ }
+@@ -281,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;
+ 
+@@ -465,8 +482,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];
+@@ -510,19 +527,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),
+@@ -538,9 +556,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));
+ 
+@@ -568,6 +586,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;
+@@ -858,11 +877,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_);
+     }
+@@ -887,61 +911,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) ||
+@@ -953,33 +976,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
+              */
+@@ -988,27 +1011,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);
+                 }
+             }
+         }
+@@ -1138,9 +1161,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-windows.c b/lib/netdev-windows.c
 index 4ad45ffa1..3fad501e3 100644
 --- a/lib/netdev-windows.c
@@ -1194,6 +1942,28 @@ index 4ad45ffa1..3fad501e3 100644
      netdev->change_seq = 1;
      netdev->dev_type = info.ovs_type;
      netdev->port_no = info.port_no;
+diff --git a/lib/netdev.h b/lib/netdev.h
+index acf174927..47c15bde7 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
+@@ -130,7 +133,6 @@ struct netdev_tunnel_config {
+     enum netdev_pt_mode pt_mode;
+ 
+     bool set_seq;
+-    uint32_t seqno;
+     uint32_t erspan_idx;
+     uint8_t erspan_ver;
+     uint8_t erspan_dir;
 diff --git a/lib/ofp-parse.c b/lib/ofp-parse.c
 index a90b926ef..102b183a8 100644
 --- a/lib/ofp-parse.c
@@ -1230,6 +2000,51 @@ index 2d382f1e8..ac5d2c3d0 100644
          last_updated = now;
          cpu_cores = count_cpu_cores__();
      }
+diff --git a/lib/smap.c b/lib/smap.c
+index c1633e2a1..47fb34502 100644
+--- a/lib/smap.c
++++ b/lib/smap.c
+@@ -100,7 +100,7 @@ smap_add_format(struct smap *smap, const char *key, const char *format, ...)
+ /* Adds 'key' paired with a string representation of 'addr'. It is the
+  * caller's responsibility to avoid duplicate keys if desirable. */
+ void
+-smap_add_ipv6(struct smap *smap, const char *key, struct in6_addr *addr)
++smap_add_ipv6(struct smap *smap, const char *key, const struct in6_addr *addr)
+ {
+     char buf[INET6_ADDRSTRLEN];
+     ipv6_string_mapped(buf, addr);
+diff --git a/lib/smap.h b/lib/smap.h
+index 2fe6c540a..d1d2ae6f2 100644
+--- a/lib/smap.h
++++ b/lib/smap.h
+@@ -100,7 +100,7 @@ struct smap_node *smap_add_nocopy(struct smap *, char *, char *);
+ bool smap_add_once(struct smap *, const char *, const char *);
+ void smap_add_format(struct smap *, const char *key, const char *, ...)
+     OVS_PRINTF_FORMAT(3, 4);
+-void smap_add_ipv6(struct smap *, const char *, struct in6_addr *);
++void smap_add_ipv6(struct smap *, const char *, const struct in6_addr *);
+ void smap_replace(struct smap *, const char *, const char *);
+ void smap_replace_nocopy(struct smap *, const char *, char *);
+ 
+diff --git a/lib/stream-ssl.c b/lib/stream-ssl.c
+index 62da9febb..86747e58b 100644
+--- a/lib/stream-ssl.c
++++ b/lib/stream-ssl.c
+@@ -1075,7 +1075,13 @@ do_ssl_init(void)
+         VLOG_ERR("SSL_CTX_new: %s", ERR_error_string(ERR_get_error(), NULL));
+         return ENOPROTOOPT;
+     }
+-    SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
++
++    long options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
++#ifdef SSL_OP_IGNORE_UNEXPECTED_EOF
++    options |= SSL_OP_IGNORE_UNEXPECTED_EOF;
++#endif
++    SSL_CTX_set_options(ctx, options);
++
+ #if OPENSSL_VERSION_NUMBER < 0x3000000fL
+     SSL_CTX_set_tmp_dh_callback(ctx, tmp_dh_callback);
+ #else
 diff --git a/lib/tc.c b/lib/tc.c
 index 4c07e2216..5c32c6f97 100644
 --- a/lib/tc.c
@@ -2969,6 +3784,194 @@ index 3b5c66fe5..d63528e69 100644
  ])
  
  ovs-appctl time/warp 1000
+diff --git a/tests/system-dpdk-macros.at b/tests/system-dpdk-macros.at
+index 53fbc1320..3920f08a5 100644
+--- a/tests/system-dpdk-macros.at
++++ b/tests/system-dpdk-macros.at
+@@ -42,7 +42,7 @@ m4_define([OVS_DPDK_START],
+    OVS_DPDK_START_OVSDB()
+    dnl Enable DPDK functionality
+    AT_CHECK([ovs-vsctl --no-wait set Open_vSwitch . other_config:dpdk-init=true])
+-   OVS_DPDK_START_VSWITCHD()
++   OVS_DPDK_START_VSWITCHD($1)
+ ])
+ 
+ # OVS_DPDK_START_OVSDB()
+@@ -72,7 +72,7 @@ m4_define([OVS_DPDK_START_OVSDB],
+ #
+ m4_define([OVS_DPDK_START_VSWITCHD],
+   [dnl Change DPDK drivers log levels so that tests only catch errors
+-   AT_CHECK([ovs-vsctl --no-wait set Open_vSwitch . other_config:dpdk-extra=--log-level=pmd.*:error])
++   AT_CHECK([ovs-vsctl --no-wait set Open_vSwitch . other_config:dpdk-extra="--log-level=pmd.*:error $1"])
+ 
+    dnl Start ovs-vswitchd.
+    AT_CHECK([ovs-vswitchd --detach --no-chdir --pidfile --log-file -vvconn -vofproto_dpif -vunixctl], [0], [stdout], [stderr])
+diff --git a/tests/system-dpdk.at b/tests/system-dpdk.at
+index cb6c6d590..0f58e8574 100644
+--- a/tests/system-dpdk.at
++++ b/tests/system-dpdk.at
+@@ -32,7 +32,7 @@ dnl Check if EAL init is successful
+ AT_SETUP([OVS-DPDK - EAL init])
+ AT_KEYWORDS([dpdk])
+ OVS_DPDK_PRE_CHECK()
+-OVS_DPDK_START()
++OVS_DPDK_START([--no-pci])
+ AT_CHECK([grep "DPDK Enabled - initializing..." ovs-vswitchd.log], [], [stdout])
+ AT_CHECK([grep "EAL" ovs-vswitchd.log], [], [stdout])
+ AT_CHECK([grep "DPDK Enabled - initialized" ovs-vswitchd.log], [], [stdout])
+@@ -69,7 +69,7 @@ dnl Add vhost-user-client port
+ AT_SETUP([OVS-DPDK - add vhost-user-client port])
+ AT_KEYWORDS([dpdk])
+ OVS_DPDK_PRE_CHECK()
+-OVS_DPDK_START()
++OVS_DPDK_START([--no-pci])
+ 
+ dnl Add userspace bridge and attach it to OVS
+ AT_CHECK([ovs-vsctl add-br br10 -- set bridge br10 datapath_type=netdev])
+@@ -98,7 +98,7 @@ AT_SETUP([OVS-DPDK - ping vhost-user ports])
+ AT_KEYWORDS([dpdk])
+ OVS_DPDK_PRE_CHECK()
+ AT_SKIP_IF([! which dpdk-testpmd >/dev/null 2>/dev/null])
+-OVS_DPDK_START()
++OVS_DPDK_START([--no-pci])
+ 
+ dnl Find number of sockets
+ AT_CHECK([lscpu], [], [stdout])
+@@ -174,7 +174,7 @@ AT_SETUP([OVS-DPDK - ping vhost-user-client ports])
+ AT_KEYWORDS([dpdk])
+ OVS_DPDK_PRE_CHECK()
+ AT_SKIP_IF([! which dpdk-testpmd >/dev/null 2>/dev/null])
+-OVS_DPDK_START()
++OVS_DPDK_START([--no-pci])
+ 
+ dnl Find number of sockets
+ AT_CHECK([lscpu], [], [stdout])
+@@ -309,7 +309,7 @@ AT_SETUP([OVS-DPDK - Ingress policing create delete vport port])
+ AT_KEYWORDS([dpdk])
+ 
+ OVS_DPDK_PRE_CHECK()
+-OVS_DPDK_START()
++OVS_DPDK_START([--no-pci])
+ 
+ dnl Add userspace bridge and attach it to OVS and add ingress policer
+ AT_CHECK([ovs-vsctl add-br br10 -- set bridge br10 datapath_type=netdev])
+@@ -352,7 +352,7 @@ AT_SETUP([OVS-DPDK - Ingress policing no policing rate])
+ AT_KEYWORDS([dpdk])
+ 
+ OVS_DPDK_PRE_CHECK()
+-OVS_DPDK_START()
++OVS_DPDK_START([--no-pci])
+ 
+ dnl Add userspace bridge and attach it to OVS and add ingress policer
+ AT_CHECK([ovs-vsctl add-br br10 -- set bridge br10 datapath_type=netdev])
+@@ -393,7 +393,7 @@ AT_SETUP([OVS-DPDK - Ingress policing no policing burst])
+ AT_KEYWORDS([dpdk])
+ 
+ OVS_DPDK_PRE_CHECK()
+-OVS_DPDK_START()
++OVS_DPDK_START([--no-pci])
+ 
+ dnl Add userspace bridge and attach it to OVS and add ingress policer
+ AT_CHECK([ovs-vsctl add-br br10 -- set bridge br10 datapath_type=netdev])
+@@ -465,7 +465,7 @@ AT_SETUP([OVS-DPDK - QoS create delete vport port])
+ AT_KEYWORDS([dpdk])
+ 
+ OVS_DPDK_PRE_CHECK()
+-OVS_DPDK_START()
++OVS_DPDK_START([--no-pci])
+ 
+ dnl Add userspace bridge and attach it to OVS and add egress policer
+ AT_CHECK([ovs-vsctl add-br br10 -- set bridge br10 datapath_type=netdev])
+@@ -506,7 +506,7 @@ AT_SETUP([OVS-DPDK - QoS no cir])
+ AT_KEYWORDS([dpdk])
+ 
+ OVS_DPDK_PRE_CHECK()
+-OVS_DPDK_START()
++OVS_DPDK_START([--no-pci])
+ 
+ dnl Add userspace bridge and attach it to OVS and add egress policer
+ AT_CHECK([ovs-vsctl add-br br10 -- set bridge br10 datapath_type=netdev])
+@@ -541,7 +541,7 @@ AT_SETUP([OVS-DPDK - QoS no cbs])
+ AT_KEYWORDS([dpdk])
+ 
+ OVS_DPDK_PRE_CHECK()
+-OVS_DPDK_START()
++OVS_DPDK_START([--no-pci])
+ 
+ dnl Add userspace bridge and attach it to OVS and add egress policer
+ AT_CHECK([ovs-vsctl add-br br10 -- set bridge br10 datapath_type=netdev])
+@@ -661,7 +661,7 @@ AT_KEYWORDS([dpdk])
+ 
+ AT_SKIP_IF([! which dpdk-testpmd >/dev/null 2>/dev/null])
+ OVS_DPDK_PRE_CHECK()
+-OVS_DPDK_START()
++OVS_DPDK_START([--no-pci])
+ 
+ dnl Find number of sockets
+ AT_CHECK([lscpu], [], [stdout])
+@@ -717,7 +717,7 @@ AT_KEYWORDS([dpdk])
+ 
+ AT_SKIP_IF([! which dpdk-testpmd >/dev/null 2>/dev/null])
+ OVS_DPDK_PRE_CHECK()
+-OVS_DPDK_START()
++OVS_DPDK_START([--no-pci])
+ 
+ dnl Find number of sockets
+ AT_CHECK([lscpu], [], [stdout])
+@@ -856,7 +856,7 @@ AT_KEYWORDS([dpdk])
+ 
+ AT_SKIP_IF([! which dpdk-testpmd >/dev/null 2>/dev/null])
+ OVS_DPDK_PRE_CHECK()
+-OVS_DPDK_START()
++OVS_DPDK_START([--no-pci])
+ 
+ dnl Find number of sockets
+ AT_CHECK([lscpu], [], [stdout])
+@@ -908,7 +908,7 @@ AT_KEYWORDS([dpdk])
+ 
+ AT_SKIP_IF([! which dpdk-testpmd >/dev/null 2>/dev/null])
+ OVS_DPDK_PRE_CHECK()
+-OVS_DPDK_START()
++OVS_DPDK_START([--no-pci])
+ 
+ dnl Find number of sockets
+ AT_CHECK([lscpu], [], [stdout])
+@@ -963,7 +963,7 @@ dnl MFEX Autovalidator
+ AT_SETUP([OVS-DPDK - MFEX Autovalidator])
+ AT_KEYWORDS([dpdk])
+ OVS_DPDK_PRE_CHECK()
+-OVS_DPDK_START()
++OVS_DPDK_START([--no-pci])
+ AT_CHECK([ovs-vsctl add-br br0 -- set bridge br0 datapath_type=netdev])
+ AT_SKIP_IF([! ovs-appctl dpif-netdev/miniflow-parser-get | sed 1,4d | grep "True"], [], [dnl
+ ])
+@@ -996,7 +996,7 @@ dnl MFEX Autovalidator Fuzzy
+ AT_SETUP([OVS-DPDK - MFEX Autovalidator Fuzzy])
+ AT_KEYWORDS([dpdk])
+ OVS_DPDK_PRE_CHECK()
+-OVS_DPDK_START()
++OVS_DPDK_START([--no-pci])
+ AT_CHECK([ovs-vsctl add-br br0 -- set bridge br0 datapath_type=netdev])
+ AT_SKIP_IF([! ovs-appctl dpif-netdev/miniflow-parser-get | sed 1,4d | grep "True"], [], [dnl
+ ])
+@@ -1032,7 +1032,7 @@ AT_KEYWORDS([dpdk])
+ OVS_DPDK_PRE_CHECK()
+ AT_SKIP_IF([! $PYTHON3 -c "import scapy"], [], [])
+ AT_CHECK([$PYTHON3 $srcdir/mfex_fuzzy.py test_traffic.pcap 1], [], [stdout])
+-OVS_DPDK_START()
++OVS_DPDK_START([--no-pci])
+ AT_CHECK([ovs-vsctl --no-wait set Open_vSwitch . other_config:pmd-cpu-mask=0x1])
+ dnl Add userspace bridge and attach it to OVS
+ AT_CHECK([ovs-vsctl add-br br0 -- set bridge br0 datapath_type=netdev])
+@@ -1153,7 +1153,7 @@ AT_SETUP([OVS-DPDK - user configured mempool])
+ AT_KEYWORDS([dpdk])
+ OVS_DPDK_PRE_CHECK()
+ OVS_DPDK_START_OVSDB()
+-OVS_DPDK_START_VSWITCHD()
++OVS_DPDK_START_VSWITCHD([--no-pci])
+ 
+ AT_CHECK([ovs-vsctl --no-wait set Open_vSwitch . other_config:shared-mempool-config=8000,6000,1500])
+ AT_CHECK([ovs-vsctl --no-wait set Open_vSwitch . other_config:dpdk-init=true])
 diff --git a/tests/system-interface.at b/tests/system-interface.at
 index 784bada12..3bf339582 100644
 --- a/tests/system-interface.at
@@ -3036,6 +4039,46 @@ index 784bada12..3bf339582 100644
 +
 +OVS_TRAFFIC_VSWITCHD_STOP
 +AT_CLEANUP
+diff --git a/tests/system-kmod-macros.at b/tests/system-kmod-macros.at
+index 11920e60b..1f9950f83 100644
+--- a/tests/system-kmod-macros.at
++++ b/tests/system-kmod-macros.at
+@@ -224,3 +224,13 @@ m4_define([VSCTL_ADD_DATAPATH_TABLE],
+ # or necessary for the userspace datapath as it is checking for a kernel
+ # specific regression.
+ m4_define([CHECK_L3L4_CONNTRACK_REASM])
++
++# OVS_CHECK_BAREUDP()
++#
++# The feature needs to be enabled in the kernel configuration (CONFIG_BAREUDP)
++# to work.
++m4_define([OVS_CHECK_BAREUDP],
++[
++    AT_SKIP_IF([! ip link add dev ovs_bareudp0 type bareudp dstport 6635 ethertype mpls_uc 2>&1 >/dev/null])
++    AT_CHECK([ip link del dev ovs_bareudp0])
++])
+diff --git a/tests/system-layer3-tunnels.at b/tests/system-layer3-tunnels.at
+index c37852b21..81123f730 100644
+--- a/tests/system-layer3-tunnels.at
++++ b/tests/system-layer3-tunnels.at
+@@ -154,7 +154,7 @@ OVS_VSWITCHD_STOP
+ AT_CLEANUP
+ 
+ AT_SETUP([layer3 - ping over MPLS Bareudp])
+-OVS_CHECK_MIN_KERNEL(5, 7)
++OVS_CHECK_BAREUDP()
+ OVS_TRAFFIC_VSWITCHD_START([_ADD_BR([br1])])
+ ADD_NAMESPACES(at_ns0, at_ns1)
+ 
+@@ -202,7 +202,7 @@ OVS_TRAFFIC_VSWITCHD_STOP
+ AT_CLEANUP
+ 
+ AT_SETUP([layer3 - ping over Bareudp])
+-OVS_CHECK_MIN_KERNEL(5, 7)
++OVS_CHECK_BAREUDP()
+ OVS_TRAFFIC_VSWITCHD_START([_ADD_BR([br1])])
+ ADD_NAMESPACES(at_ns0, at_ns1)
+ 
 diff --git a/tests/system-offloads-traffic.at b/tests/system-offloads-traffic.at
 index d36da0580..8dd3bdf88 100644
 --- a/tests/system-offloads-traffic.at
@@ -3241,6 +4284,22 @@ index 221d96aef..6678911b4 100644
  OVS_WAIT_UNTIL([grep "listening" tcpdump1_err])
  
  dnl Send UDP client->server
+diff --git a/tests/system-userspace-macros.at b/tests/system-userspace-macros.at
+index b34a84775..40210f7fa 100644
+--- a/tests/system-userspace-macros.at
++++ b/tests/system-userspace-macros.at
+@@ -325,3 +325,11 @@ m4_define([CHECK_L3L4_CONNTRACK_REASM],
+ [
+     AT_SKIP_IF([:])
+ ])
++
++# OVS_CHECK_BAREUDP()
++#
++# The userspace datapath does not support bareudp tunnels.
++m4_define([OVS_CHECK_BAREUDP],
++[
++    AT_SKIP_IF([:])
++])
 diff --git a/tests/testsuite.at b/tests/testsuite.at
 index cf4e3eadf..9d77a9f51 100644
 --- a/tests/testsuite.at
@@ -3250,6 +4309,24 @@ index cf4e3eadf..9d77a9f51 100644
  m4_include([tests/drop-stats.at])
  m4_include([tests/pytest.at])
 +m4_include([tests/learning-switch.at])
+diff --git a/utilities/ovs-appctl-bashcomp.bash b/utilities/ovs-appctl-bashcomp.bash
+index 4384be8ae..0a9af1a18 100644
+--- a/utilities/ovs-appctl-bashcomp.bash
++++ b/utilities/ovs-appctl-bashcomp.bash
+@@ -223,6 +223,13 @@ printf_stderr() {
+ # The code below is taken from Peter Amidon.  His change makes it more
+ # robust.
+ extract_bash_prompt() {
++    # On Bash 4.4+ just use the @P expansion
++    if ((BASH_VERSINFO[0] > 4 ||
++        (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] >= 4))); then
++        _BASH_PROMPT="${PS1@P}"
++        return
++    fi
++
+     local myPS1 v
+ 
+     myPS1="$(sed 's/Begin prompt/\\Begin prompt/; s/End prompt/\\End prompt/' <<< "$PS1")"
 diff --git a/utilities/ovs-ofctl.c b/utilities/ovs-ofctl.c
 index eabec18a3..3ce4e82ec 100644
 --- a/utilities/ovs-ofctl.c
@@ -3287,6 +4364,24 @@ index a49ec9f94..420c11eb8 100755
          if pipes.poll() is None:
              pipes.terminate()
  
+diff --git a/utilities/ovs-vsctl-bashcomp.bash b/utilities/ovs-vsctl-bashcomp.bash
+index fc8245bfb..c5ad24fb7 100644
+--- a/utilities/ovs-vsctl-bashcomp.bash
++++ b/utilities/ovs-vsctl-bashcomp.bash
+@@ -413,6 +413,13 @@ _ovs_vsctl_get_PS1 () {
+         return;
+     fi
+ 
++    # On Bash 4.4+ just use the @P expansion
++    if ((BASH_VERSINFO[0] > 4 ||
++        (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] >= 4))); then
++        printf '%s\n' "${PS1@P}"
++        return
++    fi
++
+     # Original inspiration from
+     # http://stackoverflow.com/questions/10060500/bash-how-to-evaluate-ps1-ps2,
+     # but changed quite a lot to make it more robust.
 diff --git a/vswitchd/bridge.c b/vswitchd/bridge.c
 index bfb2adef1..0deca14b9 100644
 --- a/vswitchd/bridge.c
diff --git a/SOURCES/openvswitch-hugetlbfs.sysusers b/SOURCES/openvswitch-hugetlbfs.sysusers
new file mode 100644
index 0000000..08b2fb1
--- /dev/null
+++ b/SOURCES/openvswitch-hugetlbfs.sysusers
@@ -0,0 +1,2 @@
+#Type Name         ID         GECOS                   Home directory  Shell
+m     openvswitch  hugetlbfs
diff --git a/SOURCES/openvswitch.sysusers b/SOURCES/openvswitch.sysusers
new file mode 100644
index 0000000..a8d06aa
--- /dev/null
+++ b/SOURCES/openvswitch.sysusers
@@ -0,0 +1,2 @@
+#Type Name         ID         GECOS                   Home directory  Shell
+u     openvswitch  -          "Open vSwitch Daemons"  /               /sbin/nologin
diff --git a/SPECS/openvswitch3.1.spec b/SPECS/openvswitch3.1.spec
index 8e28423..09b15f2 100644
--- a/SPECS/openvswitch3.1.spec
+++ b/SPECS/openvswitch3.1.spec
@@ -57,7 +57,7 @@ Summary: Open vSwitch
 Group: System Environment/Daemons daemon/database/utilities
 URL: http://www.openvswitch.org/
 Version: 3.1.0
-Release: 24%{?dist}
+Release: 27%{?dist}
 
 # Nearly all of openvswitch is ASL 2.0.  The bugtool is LGPLv2+, and the
 # lib/sflow*.[ch] files are SISSL
@@ -80,6 +80,8 @@ Source: https://github.com/openvswitch/ovs/archive/%{commit}.tar.gz#/openvswitch
 %else
 Source: https://github.com/openvswitch/ovs/archive/v%{version}.tar.gz#/openvswitch-%{version}.tar.gz
 %endif
+Source2: openvswitch.sysusers
+Source3: openvswitch-hugetlbfs.sysusers
 Source10: https://fast.dpdk.org/rel/dpdk-%{dpdkver}.tar.xz
 
 %define docutilsver 0.12
@@ -133,7 +135,7 @@ BuildRequires: python-nose
 
 BuildRequires: gcc gcc-c++ make
 BuildRequires: autoconf automake libtool
-BuildRequires: systemd-units openssl openssl-devel
+BuildRequires: systemd-units systemd-rpm-macros openssl openssl-devel
 BuildRequires: python3-devel python3-setuptools
 BuildRequires: desktop-file-utils
 BuildRequires: groff-base graphviz
@@ -171,10 +173,8 @@ Requires: openssl iproute module-init-tools
 #Requires: kernel >= 3.15.0-0
 Requires: openvswitch-selinux-extra-policy
 
-Requires(pre): shadow-utils
+%{?sysusers_requires_compat}
 Requires(post): /bin/sed
-Requires(post): /usr/sbin/usermod
-Requires(post): /usr/sbin/groupadd
 Requires(post): systemd-units
 Requires(preun): systemd-units
 Requires(postun): systemd-units
@@ -446,6 +446,11 @@ install -d -m 0755 $RPM_BUILD_ROOT%{_rundir}/openvswitch
 install -d -m 0750 $RPM_BUILD_ROOT%{_localstatedir}/log/openvswitch
 install -d -m 0755 $RPM_BUILD_ROOT%{_sysconfdir}/openvswitch
 
+install -p -D -m 0644 %{SOURCE2} $RPM_BUILD_ROOT%{_sysusersdir}/openvswitch.conf
+%ifarch %{dpdkarches}
+install -p -D -m 0644 %{SOURCE3} $RPM_BUILD_ROOT%{_sysusersdir}/openvswitch-hugetlbfs.conf
+%endif
+
 install -p -D -m 0644 rhel/usr_lib_udev_rules.d_91-vfio.rules \
         $RPM_BUILD_ROOT%{_udevrulesdir}/91-vfio.rules
 
@@ -578,16 +583,10 @@ rm -rf $RPM_BUILD_ROOT
 %endif
 
 %pre
-getent group openvswitch >/dev/null || groupadd -r openvswitch
-getent passwd openvswitch >/dev/null || \
-    useradd -r -g openvswitch -d / -s /sbin/nologin \
-    -c "Open vSwitch Daemons" openvswitch
-
+%sysusers_create_compat %{SOURCE2}
 %ifarch %{dpdkarches}
-    getent group hugetlbfs >/dev/null || groupadd hugetlbfs
-    usermod -a -G hugetlbfs openvswitch
+%sysusers_create_compat %{SOURCE3}
 %endif
-exit 0
 
 %post
 if [ $1 -eq 1 ]; then
@@ -743,6 +742,10 @@ exit 0
 %{_sysconfdir}/sysconfig/network-scripts/ifup-ovs
 %{_sysconfdir}/sysconfig/network-scripts/ifdown-ovs
 %endif
+%{_sysusersdir}/openvswitch.conf
+%ifarch %{dpdkarches}
+%{_sysusersdir}/openvswitch-hugetlbfs.conf
+%endif
 
 %if %{with ipsec}
 %files ipsec
@@ -751,6 +754,31 @@ exit 0
 %endif
 
 %changelog
+* Wed May 31 2023 Timothy Redaelli <tredaelli@redhat.com> - 3.1.0-27
+- Be sure SYSUSERSFILES are copied to dist-git [RH git: 59999cf89f]
+
+
+* Tue May 30 2023 Open vSwitch CI <ovs-ci@redhat.com> - 3.1.0-26
+- Merging upstream branch-3.1 [RH git: 65b407d97b]
+    Commit list:
+    faddfa21df utilities/bashcomp: Fix PS1 generation on new bash. (#2170344)
+    33db42a34b netdev-offload-dpdk: Fix crash in debug log.
+    55535451bb stream-ssl: Disable alerts on unexpected EOF.
+    e3b84fd4ab tests: layer3-tunnels: Skip bareudp tests if not supported by kernel.
+    e913394054 ovs-fields: Modify the width of tpa and spa.
+    23d77ba105 netdev-vport: RCU-fy tunnel config.
+    0f303e4a7f smap: Make argument of smap_add_ipv6 constant.
+    467b891f73 netdev-vport: Fix unsafe handling of GRE sequence number.
+    ea20146882 tests: dpdk: Pass `--no-pci` to tests that do not use physical ports.
+
+
+* Fri May 26 2023 Timothy Redaelli <tredaelli@redhat.com> - 3.1.0-25
+- redhat: Use sysusers instead of useradd/groupadd directly [RH git: 4772a9b0e3] (#2193168)
+    Reported-at: https://bugzilla.redhat.com/2193168
+    Reported-by: Dan Williams <dcbw@redhat.com>
+    Signed-off-by: Timothy Redaelli <tredaelli@redhat.com>
+
+
 * Tue May 16 2023 Open vSwitch CI <ovs-ci@redhat.com> - 3.1.0-24
 - Merging upstream branch-3.1 [RH git: 63d9d4a1d7]
     Commit list: