diff --git a/SOURCES/openvswitch-2.17.0.patch b/SOURCES/openvswitch-2.17.0.patch
index c624213..193d288 100644
--- a/SOURCES/openvswitch-2.17.0.patch
+++ b/SOURCES/openvswitch-2.17.0.patch
@@ -1,3 +1,16 @@
+diff --git a/.ci/linux-build.sh b/.ci/linux-build.sh
+index 6cd38ff3ef..c4ec93a398 100755
+--- a/.ci/linux-build.sh
++++ b/.ci/linux-build.sh
+@@ -220,7 +220,7 @@ fi
+ 
+ if [ "$DPDK" ] || [ "$DPDK_SHARED" ]; then
+     if [ -z "$DPDK_VER" ]; then
+-        DPDK_VER="21.11"
++        DPDK_VER="21.11.1"
+     fi
+     install_dpdk $DPDK_VER
+ fi
 diff --git a/.cirrus.yml b/.cirrus.yml
 index a7ae793bc4..a4d2a5bbcd 100644
 --- a/.cirrus.yml
@@ -14,20 +27,48 @@ index a7ae793bc4..a4d2a5bbcd 100644
      memory: 4G
  
 diff --git a/Documentation/faq/releases.rst b/Documentation/faq/releases.rst
-index af524251ff..530d36e25a 100644
+index af524251ff..319ee38c7d 100644
 --- a/Documentation/faq/releases.rst
 +++ b/Documentation/faq/releases.rst
-@@ -208,8 +208,8 @@ Q: What DPDK version does each Open vSwitch release work with?
+@@ -208,9 +208,9 @@ Q: What DPDK version does each Open vSwitch release work with?
      2.12.x       18.11.9
      2.13.x       19.11.10
      2.14.x       19.11.10
 -    2.15.x       20.11.1
 -    2.16.x       20.11.1
+-    2.17.x       21.11.0
 +    2.15.x       20.11.4
 +    2.16.x       20.11.4
-     2.17.x       21.11.0
++    2.17.x       21.11.1
      ============ ========
  
+ Q: Are all the DPDK releases that OVS versions work with maintained?
+diff --git a/Documentation/intro/install/dpdk.rst b/Documentation/intro/install/dpdk.rst
+index d9f44055db..f8f01bfadd 100644
+--- a/Documentation/intro/install/dpdk.rst
++++ b/Documentation/intro/install/dpdk.rst
+@@ -42,7 +42,7 @@ Build requirements
+ In addition to the requirements described in :doc:`general`, building Open
+ vSwitch with DPDK will require the following:
+ 
+-- DPDK 21.11
++- DPDK 21.11.1
+ 
+ - A `DPDK supported NIC`_
+ 
+@@ -73,9 +73,9 @@ Install DPDK
+ #. Download the `DPDK sources`_, extract the file and set ``DPDK_DIR``::
+ 
+        $ cd /usr/src/
+-       $ wget https://fast.dpdk.org/rel/dpdk-21.11.tar.xz
+-       $ tar xf dpdk-21.11.tar.xz
+-       $ export DPDK_DIR=/usr/src/dpdk-21.11
++       $ wget https://fast.dpdk.org/rel/dpdk-21.11.1.tar.xz
++       $ tar xf dpdk-21.11.1.tar.xz
++       $ export DPDK_DIR=/usr/src/dpdk-stable-21.11
+        $ cd $DPDK_DIR
+ 
+ #. Configure and install DPDK using Meson
 diff --git a/Documentation/intro/install/general.rst b/Documentation/intro/install/general.rst
 index c4300cd53e..a297aadac8 100644
 --- a/Documentation/intro/install/general.rst
@@ -42,12 +83,24 @@ index c4300cd53e..a297aadac8 100644
  
  - GNU make.
 diff --git a/NEWS b/NEWS
-index c10e9bfacc..8cae5f7de7 100644
+index c10e9bfacc..7c71284f97 100644
 --- a/NEWS
 +++ b/NEWS
-@@ -1,3 +1,21 @@
-+v2.17.2 - xx xxx xxxx
+@@ -1,3 +1,33 @@
++v2.17.3 - xx xxx xxxx
++---------------------
++   - OVSDB:
++     * New Local_Config schema added to support Connections (--remote)
++       configuration in a clustered databse independently for each server.
++       E.g. for listening on unique addresses.  See the ovsdb.local-config.5
++       manpage for schema details.
++
++v2.17.2 - 15 Jun 2022
 +---------------------
++   - Bug fixes
++   - DPDK:
++     * OVS validated with DPDK 21.11.1.  It is recommended to use this version
++       until further releases.
 +
 +v2.17.1 - 08 Apr 2022
 +---------------------
@@ -95,7 +148,7 @@ index 0c360fd1ef..61e88105f5 100644
     AC_ARG_ENABLE(
       [sparse],
 diff --git a/configure.ac b/configure.ac
-index 4e9bcce272..9ba141b223 100644
+index 4e9bcce272..5cc3f4801e 100644
 --- a/configure.ac
 +++ b/configure.ac
 @@ -13,7 +13,7 @@
@@ -103,7 +156,7 @@ index 4e9bcce272..9ba141b223 100644
  
  AC_PREREQ(2.63)
 -AC_INIT(openvswitch, 2.17.0, bugs@openvswitch.org)
-+AC_INIT(openvswitch, 2.17.2, bugs@openvswitch.org)
++AC_INIT(openvswitch, 2.17.3, bugs@openvswitch.org)
  AC_CONFIG_SRCDIR([datapath/datapath.c])
  AC_CONFIG_MACRO_DIR([m4])
  AC_CONFIG_AUX_DIR([build-aux])
@@ -127,16 +180,179 @@ index 70ac0a0e56..218e7db814 100644
  
      return NDIS_STATUS_SUCCESS;
  }
+diff --git a/datapath-windows/ovsext/PacketIO.c b/datapath-windows/ovsext/PacketIO.c
+index cc0840704a..2a206305ec 100644
+--- a/datapath-windows/ovsext/PacketIO.c
++++ b/datapath-windows/ovsext/PacketIO.c
+@@ -45,7 +45,9 @@ extern NDIS_STRING ovsExtFriendlyNameUC;
+ 
+ static VOID OvsFinalizeCompletionList(OvsCompletionList *completionList);
+ static VOID OvsCompleteNBLIngress(POVS_SWITCH_CONTEXT switchContext,
+-                    PNET_BUFFER_LIST netBufferLists, ULONG sendCompleteFlags);
++                                  PNET_BUFFER_LIST netBufferLists,
++                                  ULONG sendCompleteFlags,
++                                  BOOLEAN isSendComplete);
+ 
+ VOID
+ OvsInitCompletionList(OvsCompletionList *completionList,
+@@ -155,7 +157,7 @@ OvsSendNBLIngress(POVS_SWITCH_CONTEXT switchContext,
+         OvsReportNBLIngressError(switchContext, netBufferLists, &filterReason,
+                                  NDIS_STATUS_PAUSED);
+         OvsCompleteNBLIngress(switchContext, netBufferLists,
+-                              sendCompleteFlags);
++                              sendCompleteFlags, FALSE);
+         return;
+     }
+ 
+@@ -175,6 +177,79 @@ OvsSendNBLIngress(POVS_SWITCH_CONTEXT switchContext,
+                             NDIS_DEFAULT_PORT_NUMBER, sendFlags);
+ }
+ 
++static __inline BOOLEAN
++OvsCheckNBLSingleSource(PNET_BUFFER_LIST netBufferLists)
++{
++    UINT32 sourcePortId = 0;
++    BOOLEAN singleSource = TRUE;
++    PNET_BUFFER_LIST curNbl = netBufferLists;
++    PNDIS_SWITCH_FORWARDING_DETAIL_NET_BUFFER_LIST_INFO info;
++
++    while (curNbl != NULL) {
++        info = NET_BUFFER_LIST_SWITCH_FORWARDING_DETAIL(curNbl);
++        if (info == NULL) {
++            /* We are not able to determine the source port ID */
++            singleSource = FALSE;
++            OVS_LOG_INFO("nbl %p has no source port", curNbl);
++            break;
++        }
++        if (curNbl == netBufferLists) {
++            sourcePortId = info->SourcePortId;
++        } else if (info->SourcePortId != sourcePortId) {
++            singleSource = FALSE;
++            OVS_LOG_INFO("Source port in nbl %p is %u, not from %u",
++                         curNbl, info->SourcePortId, sourcePortId);
++            break;
++        }
++        curNbl = NET_BUFFER_LIST_NEXT_NBL(curNbl);
++    }
++
++    return singleSource;
++}
++
++/*
++ * SendNetBufferListsCompleteHandler releases the NetBufferLists with flag
++ * NDIS_SEND_COMPLETE_FLAGS_SWITCH_SINGLE_SOURCE if all the NBLs have same
++ * source port, for cloned NBLs, source port might be changed, although the
++ * cloned NBLs have same source port, there parent NBLs may have different
++ * source ports, so we should have a check before passing the flag to
++ * NdisFSendNetBufferListsComplete.
++ */
++static __inline VOID
++OvsCompleteUpperLayerNBL(NDIS_HANDLE ndisHandle,
++                         PNET_BUFFER_LIST netBufferLists,
++                         ULONG sendCompleteFlags,
++                         BOOLEAN isSendComplete)
++{
++    BOOLEAN singleSource = TRUE;
++    PNET_BUFFER_LIST curNbl, nextNbl;
++
++    /* To check whether the NBLs are from the same source port */
++    if (isSendComplete &&
++        (sendCompleteFlags & NDIS_SEND_COMPLETE_FLAGS_SWITCH_SINGLE_SOURCE)) {
++        singleSource = OvsCheckNBLSingleSource(netBufferLists);
++    }
++
++    if (singleSource) {
++        NdisFSendNetBufferListsComplete(ndisHandle,
++                                        netBufferLists,
++                                        sendCompleteFlags);
++    } else {
++        /*
++         * Not from a single source port, releasing the NBls without flag
++         * NDIS_SEND_COMPLETE_FLAGS_SWITCH_SINGLE_SOURCE doesn't help, so
++         * let's release them one by one.
++         */
++        for (curNbl = netBufferLists; curNbl != NULL; curNbl = nextNbl) {
++            nextNbl = NET_BUFFER_LIST_NEXT_NBL(curNbl);
++            NET_BUFFER_LIST_NEXT_NBL(curNbl) = NULL;
++            NdisFSendNetBufferListsComplete(ndisHandle,
++                                            curNbl,
++                                            sendCompleteFlags);
++        }
++    }
++}
++
+ static __inline VOID
+ OvsStartNBLIngressError(POVS_SWITCH_CONTEXT switchContext,
+                         PNET_BUFFER_LIST nblList,
+@@ -184,8 +259,8 @@ OvsStartNBLIngressError(POVS_SWITCH_CONTEXT switchContext,
+ {
+     ASSERT(error);
+     OvsReportNBLIngressError(switchContext, nblList, filterReason, error);
+-    NdisFSendNetBufferListsComplete(switchContext->NdisFilterHandle, nblList,
+-                                    sendCompleteFlags);
++    OvsCompleteUpperLayerNBL(switchContext->NdisFilterHandle, nblList,
++                             sendCompleteFlags, FALSE);
+ }
+ 
+ static VOID
+@@ -427,7 +502,8 @@ OvsExtSendNBL(NDIS_HANDLE filterModuleContext,
+ static VOID
+ OvsCompleteNBLIngress(POVS_SWITCH_CONTEXT switchContext,
+                       PNET_BUFFER_LIST netBufferLists,
+-                      ULONG sendCompleteFlags)
++                      ULONG sendCompleteFlags,
++                      BOOLEAN isSendComplete)
+ {
+     PNET_BUFFER_LIST curNbl = NULL, nextNbl = NULL;
+     OvsCompletionList newList;
+@@ -449,8 +525,10 @@ OvsCompleteNBLIngress(POVS_SWITCH_CONTEXT switchContext,
+ 
+     /* Complete the NBL's that were sent by the upper layer. */
+     if (newList.dropNbl != NULL) {
+-        NdisFSendNetBufferListsComplete(switchContext->NdisFilterHandle, newList.dropNbl,
+-                                        sendCompleteFlags);
++        OvsCompleteUpperLayerNBL(switchContext->NdisFilterHandle,
++                                 newList.dropNbl,
++                                 sendCompleteFlags,
++                                 isSendComplete);
+     }
+ }
+ 
+@@ -466,7 +544,7 @@ OvsExtSendNBLComplete(NDIS_HANDLE filterModuleContext,
+                       ULONG sendCompleteFlags)
+ {
+     OvsCompleteNBLIngress((POVS_SWITCH_CONTEXT)filterModuleContext,
+-                          netBufferLists, sendCompleteFlags);
++                          netBufferLists, sendCompleteFlags, TRUE);
+ }
+ 
+ 
+@@ -476,7 +554,8 @@ OvsFinalizeCompletionList(OvsCompletionList *completionList)
+     if (completionList->dropNbl != NULL) {
+         OvsCompleteNBLIngress(completionList->switchContext,
+                               completionList->dropNbl,
+-                              completionList->sendCompleteFlags);
++                              completionList->sendCompleteFlags,
++                              FALSE);
+ 
+         completionList->dropNbl = NULL;
+         completionList->dropNblNext = &completionList->dropNbl;
 diff --git a/debian/changelog b/debian/changelog
-index 3e0d3a66e3..c3b2852c28 100644
+index 3e0d3a66e3..5ddd655d6c 100644
 --- a/debian/changelog
 +++ b/debian/changelog
-@@ -1,3 +1,15 @@
+@@ -1,3 +1,21 @@
++openvswitch (2.17.3-1) unstable; urgency=low
++   [ Open vSwitch team ]
++   * New upstream version
++
++ -- Open vSwitch team <dev@openvswitch.org>  Wed, 15 Jun 2022 12:04:07 +0200
++
 +openvswitch (2.17.2-1) unstable; urgency=low
 +   [ Open vSwitch team ]
 +   * New upstream version
 +
-+ -- Open vSwitch team <dev@openvswitch.org>  Fri, 08 Apr 2022 14:57:49 +0200
++ -- Open vSwitch team <dev@openvswitch.org>  Wed, 15 Jun 2022 12:04:07 +0200
 +
 +openvswitch (2.17.1-1) unstable; urgency=low
 +   [ Open vSwitch team ]
@@ -147,6 +363,27 @@ index 3e0d3a66e3..c3b2852c28 100644
  openvswitch (2.17.0-1) unstable; urgency=low
  
     * New upstream version
+diff --git a/debian/openvswitch-switch.install b/debian/openvswitch-switch.install
+index 6a6e9a5435..5ac3df77b1 100644
+--- a/debian/openvswitch-switch.install
++++ b/debian/openvswitch-switch.install
+@@ -14,4 +14,5 @@ usr/share/openvswitch/scripts/ovs-check-dead-ifs
+ usr/share/openvswitch/scripts/ovs-ctl
+ usr/share/openvswitch/scripts/ovs-kmod-ctl
+ usr/share/openvswitch/scripts/ovs-save
++usr/share/openvswitch/local-config.ovsschema
+ usr/share/openvswitch/vswitch.ovsschema
+diff --git a/debian/openvswitch-switch.manpages b/debian/openvswitch-switch.manpages
+index 7fd7bc55da..088734b0dc 100644
+--- a/debian/openvswitch-switch.manpages
++++ b/debian/openvswitch-switch.manpages
+@@ -1,5 +1,6 @@
+ ovsdb/ovsdb-server.1
+ ovsdb/ovsdb-server.5
++ovsdb/ovsdb.local-config.5
+ debian/tmp/usr/share/man/man8/ovs-ctl.8
+ utilities/ovs-dpctl-top.8
+ utilities/ovs-dpctl.8
 diff --git a/dpdk/lib/vhost/vhost_user.c b/dpdk/lib/vhost/vhost_user.c
 index a781346c4d..550b0ee8b5 100644
 --- a/dpdk/lib/vhost/vhost_user.c
@@ -280,6 +517,37 @@ index 0000000000..6fae6f727c
 +
 +#endif /* __KERNEL__ || !HAVE_TCA_STATS_PKT64 */
 +#endif /* __LINUX_GEN_STATS_WRAPPER_H */
+diff --git a/include/openvswitch/flow.h b/include/openvswitch/flow.h
+index 3054015d93..df10cf579e 100644
+--- a/include/openvswitch/flow.h
++++ b/include/openvswitch/flow.h
+@@ -141,15 +141,14 @@ struct flow {
+     uint8_t nw_tos;             /* IP ToS (including DSCP and ECN). */
+     uint8_t nw_ttl;             /* IP TTL/Hop Limit. */
+     uint8_t nw_proto;           /* IP protocol or low 8 bits of ARP opcode. */
++    /* L4 (64-bit aligned) */
+     struct in6_addr nd_target;  /* IPv6 neighbor discovery (ND) target. */
+     struct eth_addr arp_sha;    /* ARP/ND source hardware address. */
+     struct eth_addr arp_tha;    /* ARP/ND target hardware address. */
+-    ovs_be16 tcp_flags;         /* TCP flags/ICMPv6 ND options type.
+-                                 * With L3 to avoid matching L4. */
++    ovs_be16 tcp_flags;         /* TCP flags/ICMPv6 ND options type. */
+     ovs_be16 pad2;              /* Pad to 64 bits. */
+     struct ovs_key_nsh nsh;     /* Network Service Header keys */
+ 
+-    /* L4 (64-bit aligned) */
+     ovs_be16 tp_src;            /* TCP/UDP/SCTP source port/ICMP type. */
+     ovs_be16 tp_dst;            /* TCP/UDP/SCTP destination port/ICMP code. */
+     ovs_be16 ct_tp_src;         /* CT original tuple source port/ICMP type. */
+@@ -179,7 +178,7 @@ BUILD_ASSERT_DECL(offsetof(struct flow, igmp_group_ip4) + sizeof(uint32_t)
+ enum {
+     FLOW_SEGMENT_1_ENDS_AT = offsetof(struct flow, dl_dst),
+     FLOW_SEGMENT_2_ENDS_AT = offsetof(struct flow, nw_src),
+-    FLOW_SEGMENT_3_ENDS_AT = offsetof(struct flow, tp_src),
++    FLOW_SEGMENT_3_ENDS_AT = offsetof(struct flow, nd_target),
+ };
+ BUILD_ASSERT_DECL(FLOW_SEGMENT_1_ENDS_AT % sizeof(uint64_t) == 0);
+ BUILD_ASSERT_DECL(FLOW_SEGMENT_2_ENDS_AT % sizeof(uint64_t) == 0);
 diff --git a/include/openvswitch/hmap.h b/include/openvswitch/hmap.h
 index 4e001cc692..beb48295b9 100644
 --- a/include/openvswitch/hmap.h
@@ -536,6 +804,86 @@ index 8ad5eeb327..6272d340cf 100644
  
  /* Inline implementations. */
  
+diff --git a/include/openvswitch/ofp-actions.h b/include/openvswitch/ofp-actions.h
+index 41bcb55d20..b7231c7bb3 100644
+--- a/include/openvswitch/ofp-actions.h
++++ b/include/openvswitch/ofp-actions.h
+@@ -218,7 +218,9 @@ struct ofpact *ofpact_next_flattened(const struct ofpact *);
+ static inline struct ofpact *
+ ofpact_end(const struct ofpact *ofpacts, size_t ofpacts_len)
+ {
+-    return ALIGNED_CAST(struct ofpact *, (uint8_t *) ofpacts + ofpacts_len);
++    return ofpacts
++           ? ALIGNED_CAST(struct ofpact *, (uint8_t *) ofpacts + ofpacts_len)
++           : NULL;
+ }
+ 
+ static inline bool
+diff --git a/include/openvswitch/ofpbuf.h b/include/openvswitch/ofpbuf.h
+index 1136ba04c8..32f03ea837 100644
+--- a/include/openvswitch/ofpbuf.h
++++ b/include/openvswitch/ofpbuf.h
+@@ -179,7 +179,11 @@ static inline void ofpbuf_delete(struct ofpbuf *b)
+ static inline void *ofpbuf_at(const struct ofpbuf *b, size_t offset,
+                               size_t size)
+ {
+-    return offset + size <= b->size ? (char *) b->data + offset : NULL;
++    if (offset + size <= b->size) {
++        ovs_assert(b->data);
++        return (char *) b->data + offset;
++    }
++    return NULL;
+ }
+ 
+ /* Returns a pointer to byte 'offset' in 'b', which must contain at least
+@@ -188,20 +192,23 @@ static inline void *ofpbuf_at_assert(const struct ofpbuf *b, size_t offset,
+                                      size_t size)
+ {
+     ovs_assert(offset + size <= b->size);
+-    return ((char *) b->data) + offset;
++    ovs_assert(b->data);
++    return (char *) b->data + offset;
+ }
+ 
+ /* Returns a pointer to byte following the last byte of data in use in 'b'. */
+ static inline void *ofpbuf_tail(const struct ofpbuf *b)
+ {
+-    return (char *) b->data + b->size;
++    ovs_assert(b->data || !b->size);
++    return b->data ? (char *) b->data + b->size : NULL;
+ }
+ 
+ /* Returns a pointer to byte following the last byte allocated for use (but
+  * not necessarily in use) in 'b'. */
+ static inline void *ofpbuf_end(const struct ofpbuf *b)
+ {
+-    return (char *) b->base + b->allocated;
++    ovs_assert(b->base || !b->allocated);
++    return b->base ? (char *) b->base + b->allocated : NULL;
+ }
+ 
+ /* Returns the number of bytes of headroom in 'b', that is, the number of bytes
+@@ -249,6 +256,11 @@ static inline void *ofpbuf_pull(struct ofpbuf *b, size_t size)
+ {
+     ovs_assert(b->size >= size);
+     void *data = b->data;
++
++    if (!size) {
++        return data;
++    }
++
+     b->data = (char*)b->data + size;
+     b->size = b->size - size;
+     return data;
+@@ -270,7 +282,7 @@ static inline struct ofpbuf *ofpbuf_from_list(const struct ovs_list *list)
+ static inline bool ofpbuf_equal(const struct ofpbuf *a, const struct ofpbuf *b)
+ {
+     return a->size == b->size &&
+-           memcmp(a->data, b->data, a->size) == 0;
++           (a->size == 0 || memcmp(a->data, b->data, a->size) == 0);
+ }
+ 
+ static inline bool ofpbuf_oversized(const struct ofpbuf *ofpacts)
 diff --git a/include/openvswitch/shash.h b/include/openvswitch/shash.h
 index c249e13e1f..4e7badd4dc 100644
 --- a/include/openvswitch/shash.h
@@ -568,7 +916,7 @@ index c249e13e1f..4e7badd4dc 100644
  void shash_destroy(struct shash *);
  void shash_destroy_free_data(struct shash *);
 diff --git a/include/openvswitch/util.h b/include/openvswitch/util.h
-index 228b185c3a..96f600160b 100644
+index 228b185c3a..8e6c46a85f 100644
 --- a/include/openvswitch/util.h
 +++ b/include/openvswitch/util.h
 @@ -145,6 +145,150 @@ OVS_NO_RETURN void ovs_assert_failure(const char *, const char *, const char *);
@@ -722,6 +1070,16 @@ index 228b185c3a..96f600160b 100644
  /* Returns the number of elements in ARRAY. */
  #define ARRAY_SIZE(ARRAY) __ARRAY_SIZE(ARRAY)
  
+@@ -285,6 +429,9 @@ is_pow2(uintmax_t x)
+  * segfault, so it is important to be aware of correct alignment. */
+ #define ALIGNED_CAST(TYPE, ATTR) ((TYPE) (void *) (ATTR))
+ 
++#define IS_PTR_ALIGNED(OBJ) \
++    (!(OBJ) || (uintptr_t) (OBJ) % __alignof__(OVS_TYPEOF(OBJ)) == 0)
++
+ #ifdef __cplusplus
+ }
+ #endif
 diff --git a/ipsec/ovs-monitor-ipsec.in b/ipsec/ovs-monitor-ipsec.in
 index a8b0705d9f..631a8fca80 100755
 --- a/ipsec/ovs-monitor-ipsec.in
@@ -754,6 +1112,28 @@ index a8b0705d9f..631a8fca80 100755
      def _nss_clear_database(self):
          """Remove all OVS IPsec related state from the NSS database"""
          try:
+diff --git a/lib/automake.mk b/lib/automake.mk
+index a23cdc4ade..e9a5978e88 100644
+--- a/lib/automake.mk
++++ b/lib/automake.mk
+@@ -38,8 +38,6 @@ lib_libopenvswitchavx512_la_CFLAGS = \
+ 	-fPIC \
+ 	$(AM_CFLAGS)
+ lib_libopenvswitchavx512_la_SOURCES = \
+-	lib/cpu.c \
+-	lib/cpu.h \
+ 	lib/dpif-netdev-lookup-avx512-gather.c \
+ 	lib/dpif-netdev-extract-avx512.c \
+ 	lib/dpif-netdev-avx512.c
+@@ -89,6 +87,8 @@ lib_libopenvswitch_la_SOURCES = \
+ 	lib/conntrack.h \
+ 	lib/coverage.c \
+ 	lib/coverage.h \
++	lib/cpu.c \
++	lib/cpu.h \
+ 	lib/crc32c.c \
+ 	lib/crc32c.h \
+ 	lib/csum.c \
 diff --git a/lib/cfm.c b/lib/cfm.c
 index cc43e70e31..c3742f3de2 100644
 --- a/lib/cfm.c
@@ -840,7 +1220,7 @@ index c502d23112..72e2ec5f71 100644
  #define CMAP_CURSOR_FOR_EACH(NODE, MEMBER, CURSOR, CMAP)    \
      for (*(CURSOR) = cmap_cursor_start(CMAP);               \
 diff --git a/lib/conntrack.c b/lib/conntrack.c
-index 33a1a92953..08da4ddf79 100644
+index 33a1a92953..0103fb5396 100644
 --- a/lib/conntrack.c
 +++ b/lib/conntrack.c
 @@ -1526,14 +1526,14 @@ set_label(struct dp_packet *pkt, struct conn *conn,
@@ -860,6 +1240,15 @@ index 33a1a92953..08da4ddf79 100644
              ovs_mutex_lock(&conn->lock);
              if (now < conn->expiration || count >= limit) {
                  min_expiration = MIN(min_expiration, conn->expiration);
+@@ -2242,7 +2242,7 @@ nat_range_hash(const struct conn *conn, uint32_t basis,
+     hash = ct_addr_hash_add(hash, &nat_info->min_addr);
+     hash = ct_addr_hash_add(hash, &nat_info->max_addr);
+     hash = hash_add(hash,
+-                    (nat_info->max_port << 16)
++                    ((uint32_t) nat_info->max_port << 16)
+                     | nat_info->min_port);
+     hash = ct_endpoint_hash_add(hash, &conn->key.src);
+     hash = ct_endpoint_hash_add(hash, &conn->key.dst);
 @@ -2265,8 +2265,16 @@ set_sport_range(const struct nat_action_info_t *ni, const struct conn_key *k,
      if (((ni->nat_action & NAT_ACTION_SNAT_ALL) == NAT_ACTION_SRC) ||
          ((ni->nat_action & NAT_ACTION_DST))) {
@@ -998,10 +1387,38 @@ index d344514343..1afcc65adb 100644
      }
  
 diff --git a/lib/dpif-netdev-avx512.c b/lib/dpif-netdev-avx512.c
-index b7131ba3f1..11d9a00052 100644
+index b7131ba3f1..82a4138184 100644
 --- a/lib/dpif-netdev-avx512.c
 +++ b/lib/dpif-netdev-avx512.c
-@@ -159,7 +159,7 @@ dp_netdev_input_outer_avx512(struct dp_netdev_pmd_thread *pmd,
+@@ -20,7 +20,6 @@
+ 
+ #include <config.h>
+ 
+-#include "cpu.h"
+ #include "dpif-netdev.h"
+ #include "dpif-netdev-perf.h"
+ #include "dpif-netdev-private.h"
+@@ -59,19 +58,6 @@ struct dpif_userdata {
+         struct pkt_flow_meta pkt_meta[NETDEV_MAX_BURST];
+ };
+ 
+-int32_t
+-dp_netdev_input_outer_avx512_probe(void)
+-{
+-    bool avx512f_available = cpu_has_isa(OVS_CPU_ISA_X86_AVX512F);
+-    bool bmi2_available = cpu_has_isa(OVS_CPU_ISA_X86_BMI2);
+-
+-    if (!avx512f_available || !bmi2_available) {
+-        return -ENOTSUP;
+-    }
+-
+-    return 0;
+-}
+-
+ int32_t
+ dp_netdev_input_outer_avx512(struct dp_netdev_pmd_thread *pmd,
+                              struct dp_packet_batch *packets,
+@@ -159,7 +145,7 @@ dp_netdev_input_outer_avx512(struct dp_netdev_pmd_thread *pmd,
          mf_mask = mfex_func(packets, keys, batch_size, in_port, pmd);
      }
  
@@ -1010,7 +1427,7 @@ index b7131ba3f1..11d9a00052 100644
      uint32_t iter = lookup_pkts_bitmask;
      while (iter) {
          uint32_t i = raw_ctz(iter);
-@@ -183,7 +183,7 @@ dp_netdev_input_outer_avx512(struct dp_netdev_pmd_thread *pmd,
+@@ -183,7 +169,7 @@ dp_netdev_input_outer_avx512(struct dp_netdev_pmd_thread *pmd,
           * classifed by vector mfex else do a scalar miniflow extract
           * for that packet.
           */
@@ -1019,7 +1436,7 @@ index b7131ba3f1..11d9a00052 100644
  
          /* Check for a partial hardware offload match. */
          if (hwol_enabled) {
-@@ -204,7 +204,7 @@ dp_netdev_input_outer_avx512(struct dp_netdev_pmd_thread *pmd,
+@@ -204,7 +190,7 @@ dp_netdev_input_outer_avx512(struct dp_netdev_pmd_thread *pmd,
  
                  pkt_meta[i].bytes = dp_packet_size(packet);
                  phwol_hits++;
@@ -1028,7 +1445,7 @@ index b7131ba3f1..11d9a00052 100644
                  continue;
              }
          }
-@@ -227,7 +227,7 @@ dp_netdev_input_outer_avx512(struct dp_netdev_pmd_thread *pmd,
+@@ -227,7 +213,7 @@ dp_netdev_input_outer_avx512(struct dp_netdev_pmd_thread *pmd,
              if (f) {
                  rules[i] = &f->cr;
                  emc_hits++;
@@ -1037,7 +1454,7 @@ index b7131ba3f1..11d9a00052 100644
                  continue;
              }
          }
-@@ -237,7 +237,7 @@ dp_netdev_input_outer_avx512(struct dp_netdev_pmd_thread *pmd,
+@@ -237,7 +223,7 @@ dp_netdev_input_outer_avx512(struct dp_netdev_pmd_thread *pmd,
              if (f) {
                  rules[i] = &f->cr;
                  smc_hits++;
@@ -1047,10 +1464,30 @@ index b7131ba3f1..11d9a00052 100644
              }
          }
 diff --git a/lib/dpif-netdev-extract-avx512.c b/lib/dpif-netdev-extract-avx512.c
-index c1c1fefb6a..a0fedb1376 100644
+index c1c1fefb6a..92980ca1b9 100644
 --- a/lib/dpif-netdev-extract-avx512.c
 +++ b/lib/dpif-netdev-extract-avx512.c
-@@ -619,7 +619,7 @@ mfex_avx512_process(struct dp_packet_batch *packets,
+@@ -42,7 +42,6 @@
+ #include <stdint.h>
+ #include <string.h>
+ 
+-#include "cpu.h"
+ #include "flow.h"
+ 
+ #include "dpif-netdev-private-dpcls.h"
+@@ -544,7 +543,11 @@ mfex_avx512_process(struct dp_packet_batch *packets,
+          */
+         __m512i v512_zeros = _mm512_setzero_si512();
+         __m512i v_blk0;
++#if __GNUC__ >= 4
+         if (__builtin_constant_p(use_vbmi) && use_vbmi) {
++#else
++        if (use_vbmi) {
++#endif
+             v_blk0 = _mm512_maskz_permutexvar_epi8_wrap(k_shuf, v_shuf,
+                                                         v_pkt0);
+         } else {
+@@ -619,7 +622,7 @@ mfex_avx512_process(struct dp_packet_batch *packets,
          };
  
          /* This packet has its miniflow created, add to hitmask. */
@@ -1059,6 +1496,251 @@ index c1c1fefb6a..a0fedb1376 100644
      }
  
      return hitmask;
+@@ -659,47 +662,5 @@ DECLARE_MFEX_FUNC(ip_udp, PROFILE_ETH_IPV4_UDP)
+ DECLARE_MFEX_FUNC(ip_tcp, PROFILE_ETH_IPV4_TCP)
+ DECLARE_MFEX_FUNC(dot1q_ip_udp, PROFILE_ETH_VLAN_IPV4_UDP)
+ DECLARE_MFEX_FUNC(dot1q_ip_tcp, PROFILE_ETH_VLAN_IPV4_TCP)
+-
+-
+-static int32_t
+-avx512_isa_probe(uint32_t needs_vbmi)
+-{
+-    static enum ovs_cpu_isa isa_required[] = {
+-        OVS_CPU_ISA_X86_AVX512F,
+-        OVS_CPU_ISA_X86_AVX512BW,
+-        OVS_CPU_ISA_X86_BMI2,
+-    };
+-
+-    int32_t ret = 0;
+-    for (uint32_t i = 0; i < ARRAY_SIZE(isa_required); i++) {
+-        if (!cpu_has_isa(isa_required[i])) {
+-            ret = -ENOTSUP;
+-        }
+-    }
+-
+-    if (needs_vbmi) {
+-        if (!cpu_has_isa(OVS_CPU_ISA_X86_AVX512VBMI)) {
+-            ret = -ENOTSUP;
+-        }
+-    }
+-
+-    return ret;
+-}
+-
+-/* Probe functions to check ISA requirements. */
+-int32_t
+-mfex_avx512_probe(void)
+-{
+-    const uint32_t needs_vbmi = 0;
+-    return avx512_isa_probe(needs_vbmi);
+-}
+-
+-int32_t
+-mfex_avx512_vbmi_probe(void)
+-{
+-    const uint32_t needs_vbmi = 1;
+-    return avx512_isa_probe(needs_vbmi);
+-}
+-
+ #endif /* __CHECKER__ */
+ #endif /* __x86_64__ */
+diff --git a/lib/dpif-netdev-lookup-avx512-gather.c b/lib/dpif-netdev-lookup-avx512-gather.c
+index 7bc1e9e9a5..fb2084392a 100644
+--- a/lib/dpif-netdev-lookup-avx512-gather.c
++++ b/lib/dpif-netdev-lookup-avx512-gather.c
+@@ -23,7 +23,6 @@
+ #include "dpif-netdev-lookup.h"
+ 
+ #include "cmap.h"
+-#include "cpu.h"
+ #include "flow.h"
+ #include "pvector.h"
+ #include "openvswitch/vlog.h"
+@@ -396,18 +395,11 @@ dpcls_avx512_gather_mf_any(struct dpcls_subtable *subtable, uint32_t keys_map,
+ }
+ 
+ dpcls_subtable_lookup_func
+-dpcls_subtable_avx512_gather_probe(uint32_t u0_bits, uint32_t u1_bits)
++dpcls_subtable_avx512_gather_probe__(uint32_t u0_bits, uint32_t u1_bits,
++                                     bool use_vpop)
+ {
+     dpcls_subtable_lookup_func f = NULL;
+ 
+-    int avx512f_available = cpu_has_isa(OVS_CPU_ISA_X86_AVX512F);
+-    int bmi2_available = cpu_has_isa(OVS_CPU_ISA_X86_BMI2);
+-    if (!avx512f_available || !bmi2_available) {
+-        return NULL;
+-    }
+-
+-    int use_vpop = cpu_has_isa(OVS_CPU_ISA_X86_VPOPCNTDQ);
+-
+     CHECK_LOOKUP_FUNCTION(9, 4, use_vpop);
+     CHECK_LOOKUP_FUNCTION(9, 1, use_vpop);
+     CHECK_LOOKUP_FUNCTION(5, 3, use_vpop);
+diff --git a/lib/dpif-netdev-lookup.c b/lib/dpif-netdev-lookup.c
+index bd0a99abe7..b1d2801575 100644
+--- a/lib/dpif-netdev-lookup.c
++++ b/lib/dpif-netdev-lookup.c
+@@ -18,10 +18,25 @@
+ #include <errno.h>
+ #include "dpif-netdev-lookup.h"
+ 
++#include "cpu.h"
+ #include "openvswitch/vlog.h"
+ 
+ VLOG_DEFINE_THIS_MODULE(dpif_netdev_lookup);
+ 
++#if (__x86_64__ && HAVE_AVX512F && HAVE_LD_AVX512_GOOD && __SSE4_2__)
++static dpcls_subtable_lookup_func
++dpcls_subtable_avx512_gather_probe(uint32_t u0_bits, uint32_t u1_bits)
++{
++    if (!cpu_has_isa(OVS_CPU_ISA_X86_AVX512F)
++        || !cpu_has_isa(OVS_CPU_ISA_X86_BMI2)) {
++        return NULL;
++    }
++
++    return dpcls_subtable_avx512_gather_probe__(u0_bits, u1_bits,
++        cpu_has_isa(OVS_CPU_ISA_X86_VPOPCNTDQ));
++}
++#endif
++
+ /* Actual list of implementations goes here */
+ static struct dpcls_subtable_lookup_info_t subtable_lookups[] = {
+     /* The autovalidator implementation will not be used by default, it must
+diff --git a/lib/dpif-netdev-lookup.h b/lib/dpif-netdev-lookup.h
+index 59f51faa0e..5d2d845945 100644
+--- a/lib/dpif-netdev-lookup.h
++++ b/lib/dpif-netdev-lookup.h
+@@ -44,7 +44,8 @@ dpcls_subtable_generic_probe(uint32_t u0_bit_count, uint32_t u1_bit_count);
+ 
+ /* Probe function for AVX-512 gather implementation */
+ dpcls_subtable_lookup_func
+-dpcls_subtable_avx512_gather_probe(uint32_t u0_bit_cnt, uint32_t u1_bit_cnt);
++dpcls_subtable_avx512_gather_probe__(uint32_t u0_bit_cnt, uint32_t u1_bit_cnt,
++                                     bool use_vpop);
+ 
+ 
+ /* Subtable registration and iteration helpers */
+diff --git a/lib/dpif-netdev-private-dpif.c b/lib/dpif-netdev-private-dpif.c
+index 84d4ec156e..5ae119a308 100644
+--- a/lib/dpif-netdev-private-dpif.c
++++ b/lib/dpif-netdev-private-dpif.c
+@@ -22,6 +22,7 @@
+ #include <errno.h>
+ #include <string.h>
+ 
++#include "cpu.h"
+ #include "openvswitch/dynamic-string.h"
+ #include "openvswitch/vlog.h"
+ #include "util.h"
+@@ -33,6 +34,19 @@ enum dpif_netdev_impl_info_idx {
+     DPIF_NETDEV_IMPL_AVX512
+ };
+ 
++#if (__x86_64__ && HAVE_AVX512F && HAVE_LD_AVX512_GOOD && __SSE4_2__)
++static int32_t
++dp_netdev_input_outer_avx512_probe(void)
++{
++    if (!cpu_has_isa(OVS_CPU_ISA_X86_AVX512F)
++        || !cpu_has_isa(OVS_CPU_ISA_X86_BMI2)) {
++        return -ENOTSUP;
++    }
++
++    return 0;
++}
++#endif
++
+ /* Actual list of implementations goes here. */
+ static struct dpif_netdev_impl_info_t dpif_impls[] = {
+     /* The default scalar C code implementation. */
+diff --git a/lib/dpif-netdev-private-dpif.h b/lib/dpif-netdev-private-dpif.h
+index 0da639c55a..3e38630f53 100644
+--- a/lib/dpif-netdev-private-dpif.h
++++ b/lib/dpif-netdev-private-dpif.h
+@@ -67,10 +67,7 @@ dp_netdev_input(struct dp_netdev_pmd_thread *pmd,
+                 struct dp_packet_batch *packets,
+                 odp_port_t in_port);
+ 
+-/* AVX512 enabled DPIF implementation and probe functions. */
+-int32_t
+-dp_netdev_input_outer_avx512_probe(void);
+-
++/* AVX512 enabled DPIF implementation function. */
+ int32_t
+ dp_netdev_input_outer_avx512(struct dp_netdev_pmd_thread *pmd,
+                              struct dp_packet_batch *packets,
+diff --git a/lib/dpif-netdev-private-extract.c b/lib/dpif-netdev-private-extract.c
+index a29bdcfa78..fe04ea80ff 100644
+--- a/lib/dpif-netdev-private-extract.c
++++ b/lib/dpif-netdev-private-extract.c
+@@ -19,6 +19,7 @@
+ #include <stdint.h>
+ #include <string.h>
+ 
++#include "cpu.h"
+ #include "dp-packet.h"
+ #include "dpif-netdev-private-dpcls.h"
+ #include "dpif-netdev-private-extract.h"
+@@ -33,6 +34,43 @@ VLOG_DEFINE_THIS_MODULE(dpif_netdev_extract);
+ /* Variable to hold the default MFEX implementation. */
+ static ATOMIC(miniflow_extract_func) default_mfex_func;
+ 
++#if (__x86_64__ && HAVE_AVX512F && HAVE_LD_AVX512_GOOD && __SSE4_2__)
++static int32_t
++avx512_isa_probe(bool needs_vbmi)
++{
++    static enum ovs_cpu_isa isa_required[] = {
++        OVS_CPU_ISA_X86_AVX512F,
++        OVS_CPU_ISA_X86_AVX512BW,
++        OVS_CPU_ISA_X86_BMI2,
++    };
++
++    for (uint32_t i = 0; i < ARRAY_SIZE(isa_required); i++) {
++        if (!cpu_has_isa(isa_required[i])) {
++            return -ENOTSUP;
++        }
++    }
++
++    if (needs_vbmi && !cpu_has_isa(OVS_CPU_ISA_X86_AVX512VBMI)) {
++        return -ENOTSUP;
++    }
++
++    return 0;
++}
++
++/* Probe functions to check ISA requirements. */
++static int32_t
++mfex_avx512_probe(void)
++{
++    return avx512_isa_probe(false);
++}
++
++static int32_t
++mfex_avx512_vbmi_probe(void)
++{
++    return avx512_isa_probe(true);
++}
++#endif
++
+ /* Implementations of available extract options and
+  * the implementations are always in order of preference.
+  */
+diff --git a/lib/dpif-netdev-private-extract.h b/lib/dpif-netdev-private-extract.h
+index f9a757ba41..3e06148c5a 100644
+--- a/lib/dpif-netdev-private-extract.h
++++ b/lib/dpif-netdev-private-extract.h
+@@ -176,10 +176,8 @@ mfex_study_traffic(struct dp_packet_batch *packets,
+ int
+ mfex_set_study_pkt_cnt(uint32_t pkt_cmp_count, const char *name);
+ 
+-/* AVX512 MFEX Probe and Implementations functions. */
++/* AVX512 MFEX Implementation functions. */
+ #ifdef __x86_64__
+-int32_t mfex_avx512_probe(void);
+-int32_t mfex_avx512_vbmi_probe(void);
+ 
+ #define DECLARE_AVX512_MFEX_PROTOTYPE(name)                                 \
+     uint32_t                                                                \
 diff --git a/lib/dpif-netdev-private-flow.h b/lib/dpif-netdev-private-flow.h
 index 66016eb099..7425dd44e7 100644
 --- a/lib/dpif-netdev-private-flow.h
@@ -1072,10 +1754,20 @@ index 66016eb099..7425dd44e7 100644
      /* Statistics. */
      struct dp_netdev_flow_stats stats;
 diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
-index 9f35713ef5..51c276b1d8 100644
+index 9f35713ef5..3d9d8929f7 100644
 --- a/lib/dpif-netdev.c
 +++ b/lib/dpif-netdev.c
-@@ -1932,13 +1932,13 @@ static void
+@@ -93,7 +93,8 @@ VLOG_DEFINE_THIS_MODULE(dpif_netdev);
+ /* Auto Load Balancing Defaults */
+ #define ALB_IMPROVEMENT_THRESHOLD    25
+ #define ALB_LOAD_THRESHOLD           95
+-#define ALB_REBALANCE_INTERVAL       1 /* 1 Min */
++#define ALB_REBALANCE_INTERVAL       1     /* 1 Min */
++#define MAX_ALB_REBALANCE_INTERVAL   20000 /* 20000 Min */
+ #define MIN_TO_MSEC                  60000
+ 
+ #define FLOW_DUMP_MAX_BATCH 50
+@@ -1932,13 +1933,13 @@ static void
  dp_netdev_free(struct dp_netdev *dp)
      OVS_REQUIRES(dp_netdev_mutex)
  {
@@ -1091,7 +1783,7 @@ index 9f35713ef5..51c276b1d8 100644
          do_del_port(dp, port);
      }
      ovs_rwlock_unlock(&dp->port_rwlock);
-@@ -3006,7 +3006,7 @@ static void
+@@ -3006,7 +3007,7 @@ static void
  queue_netdev_flow_put(struct dp_netdev_pmd_thread *pmd,
                        struct dp_netdev_flow *flow, struct match *match,
                        const struct nlattr *actions, size_t actions_len,
@@ -1100,7 +1792,7 @@ index 9f35713ef5..51c276b1d8 100644
  {
      struct dp_offload_thread_item *item;
      struct dp_offload_flow_item *flow_offload;
-@@ -3021,7 +3021,7 @@ queue_netdev_flow_put(struct dp_netdev_pmd_thread *pmd,
+@@ -3021,7 +3022,7 @@ queue_netdev_flow_put(struct dp_netdev_pmd_thread *pmd,
      flow_offload->actions = xmalloc(actions_len);
      memcpy(flow_offload->actions, actions, actions_len);
      flow_offload->actions_len = actions_len;
@@ -1109,7 +1801,7 @@ index 9f35713ef5..51c276b1d8 100644
  
      item->timestamp = pmd->ctx.now;
      dp_netdev_offload_flow_enqueue(item);
-@@ -4095,6 +4095,7 @@ dp_netdev_flow_add(struct dp_netdev_pmd_thread *pmd,
+@@ -4095,6 +4096,7 @@ dp_netdev_flow_add(struct dp_netdev_pmd_thread *pmd,
      flow->dead = false;
      flow->batch = NULL;
      flow->mark = INVALID_FLOW_MARK;
@@ -1117,7 +1809,7 @@ index 9f35713ef5..51c276b1d8 100644
      *CONST_CAST(unsigned *, &flow->pmd_id) = pmd->core_id;
      *CONST_CAST(struct flow *, &flow->flow) = match->flow;
      *CONST_CAST(ovs_u128 *, &flow->ufid) = *ufid;
-@@ -4129,7 +4130,7 @@ dp_netdev_flow_add(struct dp_netdev_pmd_thread *pmd,
+@@ -4129,7 +4131,7 @@ dp_netdev_flow_add(struct dp_netdev_pmd_thread *pmd,
      }
  
      queue_netdev_flow_put(pmd, flow, match, actions, actions_len,
@@ -1126,7 +1818,7 @@ index 9f35713ef5..51c276b1d8 100644
      log_netdev_flow_change(flow, match, NULL, actions, actions_len);
  
      return flow;
-@@ -4171,7 +4172,7 @@ flow_put_on_pmd(struct dp_netdev_pmd_thread *pmd,
+@@ -4171,7 +4173,7 @@ flow_put_on_pmd(struct dp_netdev_pmd_thread *pmd,
              ovsrcu_set(&netdev_flow->actions, new_actions);
  
              queue_netdev_flow_put(pmd, netdev_flow, match,
@@ -1135,7 +1827,70 @@ index 9f35713ef5..51c276b1d8 100644
                                    DP_NETDEV_FLOW_OFFLOAD_OP_MOD);
              log_netdev_flow_change(netdev_flow, match, old_actions,
                                     put->actions, put->actions_len);
-@@ -5684,23 +5685,28 @@ sched_numa_list_put_in_place(struct sched_numa_list *numa_list)
+@@ -4778,8 +4780,8 @@ dpif_netdev_set_config(struct dpif *dpif, const struct smap *other_config)
+     uint32_t insert_min, cur_min;
+     uint32_t tx_flush_interval, cur_tx_flush_interval;
+     uint64_t rebalance_intvl;
+-    uint8_t rebalance_load, cur_rebalance_load;
+-    uint8_t rebalance_improve;
++    uint8_t cur_rebalance_load;
++    uint32_t rebalance_load, rebalance_improve;
+     bool log_autolb = false;
+     enum sched_assignment_type pmd_rxq_assign_type;
+ 
+@@ -4880,8 +4882,12 @@ dpif_netdev_set_config(struct dpif *dpif, const struct smap *other_config)
+ 
+     struct pmd_auto_lb *pmd_alb = &dp->pmd_alb;
+ 
+-    rebalance_intvl = smap_get_int(other_config, "pmd-auto-lb-rebal-interval",
+-                                   ALB_REBALANCE_INTERVAL);
++    rebalance_intvl = smap_get_ullong(other_config,
++                                      "pmd-auto-lb-rebal-interval",
++                                      ALB_REBALANCE_INTERVAL);
++    if (rebalance_intvl > MAX_ALB_REBALANCE_INTERVAL) {
++        rebalance_intvl = ALB_REBALANCE_INTERVAL;
++    }
+ 
+     /* Input is in min, convert it to msec. */
+     rebalance_intvl =
+@@ -4894,21 +4900,21 @@ dpif_netdev_set_config(struct dpif *dpif, const struct smap *other_config)
+         log_autolb = true;
+     }
+ 
+-    rebalance_improve = smap_get_int(other_config,
+-                                     "pmd-auto-lb-improvement-threshold",
+-                                     ALB_IMPROVEMENT_THRESHOLD);
++    rebalance_improve = smap_get_uint(other_config,
++                                      "pmd-auto-lb-improvement-threshold",
++                                      ALB_IMPROVEMENT_THRESHOLD);
+     if (rebalance_improve > 100) {
+         rebalance_improve = ALB_IMPROVEMENT_THRESHOLD;
+     }
+     if (rebalance_improve != pmd_alb->rebalance_improve_thresh) {
+         pmd_alb->rebalance_improve_thresh = rebalance_improve;
+         VLOG_INFO("PMD auto load balance improvement threshold set to "
+-                  "%"PRIu8"%%", rebalance_improve);
++                  "%"PRIu32"%%", rebalance_improve);
+         log_autolb = true;
+     }
+ 
+-    rebalance_load = smap_get_int(other_config, "pmd-auto-lb-load-threshold",
+-                                  ALB_LOAD_THRESHOLD);
++    rebalance_load = smap_get_uint(other_config, "pmd-auto-lb-load-threshold",
++                                   ALB_LOAD_THRESHOLD);
+     if (rebalance_load > 100) {
+         rebalance_load = ALB_LOAD_THRESHOLD;
+     }
+@@ -4916,7 +4922,7 @@ dpif_netdev_set_config(struct dpif *dpif, const struct smap *other_config)
+     if (rebalance_load != cur_rebalance_load) {
+         atomic_store_relaxed(&pmd_alb->rebalance_load_thresh,
+                              rebalance_load);
+-        VLOG_INFO("PMD auto load balance load threshold set to %"PRIu8"%%",
++        VLOG_INFO("PMD auto load balance load threshold set to %"PRIu32"%%",
+                   rebalance_load);
+         log_autolb = true;
+     }
+@@ -5684,23 +5690,28 @@ sched_numa_list_put_in_place(struct sched_numa_list *numa_list)
      }
  }
  
@@ -1169,7 +1924,7 @@ index 9f35713ef5..51c276b1d8 100644
                      rxq->pmd->numa_id !=
                          netdev_get_numa_id(rxq->port->netdev)) {
                      return true;
-@@ -6000,10 +6006,10 @@ sched_numa_list_schedule(struct sched_numa_list *numa_list,
+@@ -6000,10 +6011,10 @@ sched_numa_list_schedule(struct sched_numa_list *numa_list,
              /* Find any numa with available PMDs. */
              for (int j = 0; j < n_numa; j++) {
                  numa = sched_numa_list_next(numa_list, last_cross_numa);
@@ -1181,7 +1936,7 @@ index 9f35713ef5..51c276b1d8 100644
                  numa = NULL;
              }
          }
-@@ -6111,7 +6117,7 @@ sched_numa_list_variance(struct sched_numa_list *numa_list)
+@@ -6111,7 +6122,7 @@ sched_numa_list_variance(struct sched_numa_list *numa_list)
   * pmd_rebalance_dry_run() can be avoided when it is not needed.
   */
  static bool
@@ -1190,7 +1945,7 @@ index 9f35713ef5..51c276b1d8 100644
      OVS_REQ_RDLOCK(dp->port_rwlock)
  {
      struct dp_netdev_pmd_thread *pmd;
-@@ -6342,11 +6348,11 @@ pmd_remove_stale_ports(struct dp_netdev *dp,
+@@ -6342,11 +6353,11 @@ pmd_remove_stale_ports(struct dp_netdev *dp,
      OVS_EXCLUDED(pmd->port_mutex)
      OVS_REQ_RDLOCK(dp->port_rwlock)
  {
@@ -1205,7 +1960,7 @@ index 9f35713ef5..51c276b1d8 100644
          struct dp_netdev_port *port = poll->rxq->port;
  
          if (port->need_reconfigure
-@@ -6354,7 +6360,7 @@ pmd_remove_stale_ports(struct dp_netdev *dp,
+@@ -6354,7 +6365,7 @@ pmd_remove_stale_ports(struct dp_netdev *dp,
              dp_netdev_del_rxq_from_pmd(pmd, poll);
          }
      }
@@ -1214,7 +1969,7 @@ index 9f35713ef5..51c276b1d8 100644
          struct dp_netdev_port *port = tx->port;
  
          if (port->need_reconfigure
-@@ -6430,8 +6436,7 @@ reconfigure_datapath(struct dp_netdev *dp)
+@@ -6430,8 +6441,7 @@ reconfigure_datapath(struct dp_netdev *dp)
      /* We only reconfigure the ports that we determined above, because they're
       * not being used by any pmd thread at the moment.  If a port fails to
       * reconfigure we remove it from the datapath. */
@@ -1224,7 +1979,7 @@ index 9f35713ef5..51c276b1d8 100644
          int err;
  
          if (!port->need_reconfigure) {
-@@ -6487,10 +6492,10 @@ reconfigure_datapath(struct dp_netdev *dp)
+@@ -6487,10 +6497,10 @@ reconfigure_datapath(struct dp_netdev *dp)
      }
  
      CMAP_FOR_EACH (pmd, node, &dp->poll_threads) {
@@ -1237,7 +1992,7 @@ index 9f35713ef5..51c276b1d8 100644
              if (poll->rxq->pmd != pmd) {
                  dp_netdev_del_rxq_from_pmd(pmd, poll);
  
-@@ -6682,7 +6687,7 @@ dpif_netdev_run(struct dpif *dpif)
+@@ -6682,7 +6692,7 @@ dpif_netdev_run(struct dpif *dpif)
              if (pmd_rebalance &&
                  !dp_netdev_is_reconf_required(dp) &&
                  !ports_require_restart(dp) &&
@@ -1246,7 +2001,7 @@ index 9f35713ef5..51c276b1d8 100644
                  pmd_rebalance_dry_run(dp)) {
                  VLOG_INFO("PMD auto load balance dry run. "
                            "Requesting datapath reconfigure.");
-@@ -7364,15 +7369,15 @@ static struct dp_netdev_pmd_thread *
+@@ -7364,15 +7374,15 @@ static struct dp_netdev_pmd_thread *
  dp_netdev_get_pmd(struct dp_netdev *dp, unsigned core_id)
  {
      struct dp_netdev_pmd_thread *pmd;
@@ -1268,6 +2023,68 @@ index 9f35713ef5..51c276b1d8 100644
  }
  
  /* Sets the 'struct dp_netdev_pmd_thread' for non-pmd threads. */
+@@ -7505,6 +7515,7 @@ dp_netdev_destroy_pmd(struct dp_netdev_pmd_thread *pmd)
+     seq_destroy(pmd->reload_seq);
+     ovs_mutex_destroy(&pmd->port_mutex);
+     ovs_mutex_destroy(&pmd->bond_mutex);
++    free(pmd->netdev_input_func_userdata);
+     free(pmd);
+ }
+ 
+diff --git a/lib/dpif-netlink.c b/lib/dpif-netlink.c
+index 71e35ccdda..06e1e8ca02 100644
+--- a/lib/dpif-netlink.c
++++ b/lib/dpif-netlink.c
+@@ -85,7 +85,7 @@ enum { MAX_PORTS = USHRT_MAX };
+ #define EPOLLEXCLUSIVE (1u << 28)
+ #endif
+ 
+-#define OVS_DP_F_UNSUPPORTED (1 << 31);
++#define OVS_DP_F_UNSUPPORTED (1u << 31);
+ 
+ /* This PID is not used by the kernel datapath when using dispatch per CPU,
+  * but it is required to be set (not zero). */
+diff --git a/lib/dynamic-string.c b/lib/dynamic-string.c
+index fd0127ed17..3b4520f87c 100644
+--- a/lib/dynamic-string.c
++++ b/lib/dynamic-string.c
+@@ -152,7 +152,10 @@ ds_put_format_valist(struct ds *ds, const char *format, va_list args_)
+ 
+     va_copy(args, args_);
+     available = ds->string ? ds->allocated - ds->length + 1 : 0;
+-    needed = vsnprintf(&ds->string[ds->length], available, format, args);
++    needed = vsnprintf(ds->string
++                       ? &ds->string[ds->length]
++                       : NULL,
++                       available, format, args);
+     va_end(args);
+ 
+     if (needed < available) {
+@@ -162,7 +165,8 @@ ds_put_format_valist(struct ds *ds, const char *format, va_list args_)
+ 
+         va_copy(args, args_);
+         available = ds->allocated - ds->length + 1;
+-        needed = vsnprintf(&ds->string[ds->length], available, format, args);
++        needed = vsnprintf(&ds->string[ds->length],
++                           available, format, args);
+         va_end(args);
+ 
+         ovs_assert(needed < available);
+@@ -198,10 +202,11 @@ ds_put_strftime_msec(struct ds *ds, const char *template, long long int when,
+         localtime_msec(when, &tm);
+     }
+ 
++    ds_reserve(ds, 64);
+     for (;;) {
+-        size_t avail = ds->string ? ds->allocated - ds->length + 1 : 0;
+-        size_t used = strftime_msec(&ds->string[ds->length], avail, template,
+-                                    &tm);
++        size_t avail = ds->allocated - ds->length + 1;
++        char *dest = &ds->string[ds->length];
++        size_t used = strftime_msec(dest, avail, template, &tm);
+         if (used) {
+             ds->length += used;
+             return;
 diff --git a/lib/fat-rwlock.c b/lib/fat-rwlock.c
 index d913b2088f..771ccc9737 100644
 --- a/lib/fat-rwlock.c
@@ -1585,10 +2402,10 @@ index 499b441746..a8c7fad098 100644
  
              ovs_list_remove(&isid_vlan_map->m_entries);
 diff --git a/lib/lldp/lldpd.c b/lib/lldp/lldpd.c
-index a024dc5e58..403f1f525a 100644
+index a024dc5e58..4bff7b017f 100644
 --- a/lib/lldp/lldpd.c
 +++ b/lib/lldp/lldpd.c
-@@ -134,12 +134,12 @@ lldpd_hardware_cleanup(struct lldpd *cfg, struct lldpd_hardware *hardware)
+@@ -134,24 +134,20 @@ lldpd_hardware_cleanup(struct lldpd *cfg, struct lldpd_hardware *hardware)
  void
  lldpd_cleanup(struct lldpd *cfg)
  {
@@ -1600,11 +2417,18 @@ index a024dc5e58..403f1f525a 100644
      VLOG_DBG("cleanup all ports");
  
 -    LIST_FOR_EACH_SAFE (hw, hw_next, h_entries, &cfg->g_hardware) {
+-        if (!hw->h_flags) {
+-            ovs_list_remove(&hw->h_entries);
+-            lldpd_remote_cleanup(hw, NULL, true);
+-            lldpd_hardware_cleanup(cfg, hw);
+-        } else {
+-            lldpd_remote_cleanup(hw, NULL, false);
+-        }
 +    LIST_FOR_EACH_SAFE (hw, h_entries, &cfg->g_hardware) {
-         if (!hw->h_flags) {
-             ovs_list_remove(&hw->h_entries);
-             lldpd_remote_cleanup(hw, NULL, true);
-@@ -151,7 +151,7 @@ lldpd_cleanup(struct lldpd *cfg)
++        ovs_list_remove(&hw->h_entries);
++        lldpd_remote_cleanup(hw, NULL, true);
++        lldpd_hardware_cleanup(cfg, hw);
+     }
  
      VLOG_DBG("cleanup all chassis");
  
@@ -1676,6 +2500,21 @@ index 6730301b67..029ca28558 100644
          if (m->port == port) {
              mcast_snooping_flush_mrouter(m);
              ms->need_revalidate = true;
+diff --git a/lib/meta-flow.c b/lib/meta-flow.c
+index e03cd8d0c5..c576ae6202 100644
+--- a/lib/meta-flow.c
++++ b/lib/meta-flow.c
+@@ -3442,7 +3442,9 @@ mf_get_vl_mff(const struct mf_field *mff,
+               const struct vl_mff_map *vl_mff_map)
+ {
+     if (mff && mff->variable_len && vl_mff_map) {
+-        return &mf_get_vl_mff__(mff->id, vl_mff_map)->mf;
++        struct vl_mf_field *vl_mff = mf_get_vl_mff__(mff->id, vl_mff_map);
++
++        return vl_mff ? &vl_mff->mf : NULL;
+     }
+ 
+     return NULL;
 diff --git a/lib/namemap.c b/lib/namemap.c
 index 785cda4c27..dd317ea52e 100644
 --- a/lib/namemap.c
@@ -1711,7 +2550,7 @@ index 482400d8d1..ca3f2431ea 100644
          count = umem_pool_count(&pool->umem_info->mpool);
          ovs_assert(count + pool->lost_in_rings <= NUM_FRAMES);
 diff --git a/lib/netdev-dpdk.c b/lib/netdev-dpdk.c
-index b6b29c75e3..4d32781a95 100644
+index b6b29c75e3..e28e397d7e 100644
 --- a/lib/netdev-dpdk.c
 +++ b/lib/netdev-dpdk.c
 @@ -622,9 +622,9 @@ dpdk_mp_full(const struct rte_mempool *mp) OVS_REQUIRES(dpdk_mp_mutex)
@@ -1726,7 +2565,402 @@ index b6b29c75e3..4d32781a95 100644
          if (!dmp->refcount && dpdk_mp_full(dmp->mp)) {
              VLOG_DBG("Freeing mempool \"%s\"", dmp->mp->name);
              ovs_list_remove(&dmp->list_node);
-@@ -4686,11 +4686,11 @@ trtcm_policer_qos_construct(const struct smap *details,
+@@ -2561,90 +2561,6 @@ netdev_dpdk_vhost_update_tx_counters(struct netdev_dpdk *dev,
+     }
+ }
+ 
+-static void
+-__netdev_dpdk_vhost_send(struct netdev *netdev, int qid,
+-                         struct dp_packet **pkts, int cnt)
+-{
+-    struct netdev_dpdk *dev = netdev_dpdk_cast(netdev);
+-    struct rte_mbuf **cur_pkts = (struct rte_mbuf **) pkts;
+-    struct netdev_dpdk_sw_stats sw_stats_add;
+-    unsigned int n_packets_to_free = cnt;
+-    unsigned int total_packets = cnt;
+-    int i, retries = 0;
+-    int max_retries = VHOST_ENQ_RETRY_MIN;
+-    int vid = netdev_dpdk_get_vid(dev);
+-
+-    qid = dev->tx_q[qid % netdev->n_txq].map;
+-
+-    if (OVS_UNLIKELY(vid < 0 || !dev->vhost_reconfigured || qid < 0
+-                     || !(dev->flags & NETDEV_UP))) {
+-        rte_spinlock_lock(&dev->stats_lock);
+-        dev->stats.tx_dropped+= cnt;
+-        rte_spinlock_unlock(&dev->stats_lock);
+-        goto out;
+-    }
+-
+-    if (OVS_UNLIKELY(!rte_spinlock_trylock(&dev->tx_q[qid].tx_lock))) {
+-        COVERAGE_INC(vhost_tx_contention);
+-        rte_spinlock_lock(&dev->tx_q[qid].tx_lock);
+-    }
+-
+-    sw_stats_add.tx_invalid_hwol_drops = cnt;
+-    if (userspace_tso_enabled()) {
+-        cnt = netdev_dpdk_prep_hwol_batch(dev, cur_pkts, cnt);
+-    }
+-
+-    sw_stats_add.tx_invalid_hwol_drops -= cnt;
+-    sw_stats_add.tx_mtu_exceeded_drops = cnt;
+-    cnt = netdev_dpdk_filter_packet_len(dev, cur_pkts, cnt);
+-    sw_stats_add.tx_mtu_exceeded_drops -= cnt;
+-
+-    /* Check has QoS has been configured for the netdev */
+-    sw_stats_add.tx_qos_drops = cnt;
+-    cnt = netdev_dpdk_qos_run(dev, cur_pkts, cnt, true);
+-    sw_stats_add.tx_qos_drops -= cnt;
+-
+-    n_packets_to_free = cnt;
+-
+-    do {
+-        int vhost_qid = qid * VIRTIO_QNUM + VIRTIO_RXQ;
+-        unsigned int tx_pkts;
+-
+-        tx_pkts = rte_vhost_enqueue_burst(vid, vhost_qid, cur_pkts, cnt);
+-        if (OVS_LIKELY(tx_pkts)) {
+-            /* Packets have been sent.*/
+-            cnt -= tx_pkts;
+-            /* Prepare for possible retry.*/
+-            cur_pkts = &cur_pkts[tx_pkts];
+-            if (OVS_UNLIKELY(cnt && !retries)) {
+-                /*
+-                 * Read max retries as there are packets not sent
+-                 * and no retries have already occurred.
+-                 */
+-                atomic_read_relaxed(&dev->vhost_tx_retries_max, &max_retries);
+-            }
+-        } else {
+-            /* No packets sent - do not retry.*/
+-            break;
+-        }
+-    } while (cnt && (retries++ < max_retries));
+-
+-    rte_spinlock_unlock(&dev->tx_q[qid].tx_lock);
+-
+-    sw_stats_add.tx_failure_drops = cnt;
+-    sw_stats_add.tx_retries = MIN(retries, max_retries);
+-
+-    rte_spinlock_lock(&dev->stats_lock);
+-    netdev_dpdk_vhost_update_tx_counters(dev, pkts, total_packets,
+-                                         &sw_stats_add);
+-    rte_spinlock_unlock(&dev->stats_lock);
+-
+-out:
+-    for (i = 0; i < n_packets_to_free; i++) {
+-        dp_packet_delete(pkts[i]);
+-    }
+-}
+-
+ static void
+ netdev_dpdk_extbuf_free(void *addr OVS_UNUSED, void *opaque)
+ {
+@@ -2749,76 +2665,69 @@ dpdk_copy_dp_packet_to_mbuf(struct rte_mempool *mp, struct dp_packet *pkt_orig)
+     return pkt_dest;
+ }
+ 
+-/* Tx function. Transmit packets indefinitely */
+-static void
+-dpdk_do_tx_copy(struct netdev *netdev, int qid, struct dp_packet_batch *batch)
+-    OVS_NO_THREAD_SAFETY_ANALYSIS
+-{
+-    const size_t batch_cnt = dp_packet_batch_size(batch);
+-#if !defined(__CHECKER__) && !defined(_WIN32)
+-    const size_t PKT_ARRAY_SIZE = batch_cnt;
+-#else
+-    /* Sparse or MSVC doesn't like variable length array. */
+-    enum { PKT_ARRAY_SIZE = NETDEV_MAX_BURST };
+-#endif
++/* Replace packets in a 'batch' with their corresponding copies using
++ * DPDK memory.
++ *
++ * Returns the number of good packets in the batch. */
++static size_t
++dpdk_copy_batch_to_mbuf(struct netdev *netdev, struct dp_packet_batch *batch)
++{
+     struct netdev_dpdk *dev = netdev_dpdk_cast(netdev);
+-    struct dp_packet *pkts[PKT_ARRAY_SIZE];
+-    struct netdev_dpdk_sw_stats *sw_stats = dev->sw_stats;
+-    uint32_t cnt = batch_cnt;
+-    uint32_t dropped = 0;
+-    uint32_t tx_failure = 0;
+-    uint32_t mtu_drops = 0;
+-    uint32_t qos_drops = 0;
+-
+-    if (dev->type != DPDK_DEV_VHOST) {
+-        /* Check if QoS has been configured for this netdev. */
+-        cnt = netdev_dpdk_qos_run(dev, (struct rte_mbuf **) batch->packets,
+-                                  batch_cnt, false);
+-        qos_drops = batch_cnt - cnt;
+-    }
+-
+-    uint32_t txcnt = 0;
+-
+-    for (uint32_t i = 0; i < cnt; i++) {
+-        struct dp_packet *packet = batch->packets[i];
+-        uint32_t size = dp_packet_size(packet);
+-
+-        if (size > dev->max_packet_len
+-            && !(packet->mbuf.ol_flags & RTE_MBUF_F_TX_TCP_SEG)) {
+-            VLOG_WARN_RL(&rl, "Too big size %u max_packet_len %d", size,
+-                         dev->max_packet_len);
+-            mtu_drops++;
+-            continue;
+-        }
++    size_t i, size = dp_packet_batch_size(batch);
++    struct dp_packet *packet;
+ 
+-        pkts[txcnt] = dpdk_copy_dp_packet_to_mbuf(dev->dpdk_mp->mp, packet);
+-        if (OVS_UNLIKELY(!pkts[txcnt])) {
+-            dropped = cnt - i;
+-            break;
+-        }
++    DP_PACKET_BATCH_REFILL_FOR_EACH (i, size, packet, batch) {
++        if (OVS_UNLIKELY(packet->source == DPBUF_DPDK)) {
++            dp_packet_batch_refill(batch, packet, i);
++        } else {
++            struct dp_packet *pktcopy;
+ 
+-        txcnt++;
+-    }
++            pktcopy = dpdk_copy_dp_packet_to_mbuf(dev->dpdk_mp->mp, packet);
++            if (pktcopy) {
++                dp_packet_batch_refill(batch, pktcopy, i);
++            }
+ 
+-    if (OVS_LIKELY(txcnt)) {
+-        if (dev->type == DPDK_DEV_VHOST) {
+-            __netdev_dpdk_vhost_send(netdev, qid, pkts, txcnt);
+-        } else {
+-            tx_failure += netdev_dpdk_eth_tx_burst(dev, qid,
+-                                                   (struct rte_mbuf **)pkts,
+-                                                   txcnt);
++            dp_packet_delete(packet);
+         }
+     }
+ 
+-    dropped += qos_drops + mtu_drops + tx_failure;
+-    if (OVS_UNLIKELY(dropped)) {
+-        rte_spinlock_lock(&dev->stats_lock);
+-        dev->stats.tx_dropped += dropped;
+-        sw_stats->tx_failure_drops += tx_failure;
+-        sw_stats->tx_mtu_exceeded_drops += mtu_drops;
+-        sw_stats->tx_qos_drops += qos_drops;
+-        rte_spinlock_unlock(&dev->stats_lock);
++    return dp_packet_batch_size(batch);
++}
++
++static size_t
++netdev_dpdk_common_send(struct netdev *netdev, struct dp_packet_batch *batch,
++                        struct netdev_dpdk_sw_stats *stats)
++{
++    struct rte_mbuf **pkts = (struct rte_mbuf **) batch->packets;
++    struct netdev_dpdk *dev = netdev_dpdk_cast(netdev);
++    size_t cnt, pkt_cnt = dp_packet_batch_size(batch);
++
++    memset(stats, 0, sizeof *stats);
++
++    /* Copy dp-packets to mbufs. */
++    if (OVS_UNLIKELY(batch->packets[0]->source != DPBUF_DPDK)) {
++        cnt = dpdk_copy_batch_to_mbuf(netdev, batch);
++        stats->tx_failure_drops += pkt_cnt - cnt;
++        pkt_cnt = cnt;
+     }
++
++    /* Drop oversized packets. */
++    cnt = netdev_dpdk_filter_packet_len(dev, pkts, pkt_cnt);
++    stats->tx_mtu_exceeded_drops += pkt_cnt - cnt;
++    pkt_cnt = cnt;
++
++    /* Prepare each mbuf for hardware offloading. */
++    if (userspace_tso_enabled()) {
++        cnt = netdev_dpdk_prep_hwol_batch(dev, pkts, pkt_cnt);
++        stats->tx_invalid_hwol_drops += pkt_cnt - cnt;
++        pkt_cnt = cnt;
++    }
++
++    /* Apply Quality of Service policy. */
++    cnt = netdev_dpdk_qos_run(dev, pkts, pkt_cnt, true);
++    stats->tx_qos_drops += pkt_cnt - cnt;
++
++    return cnt;
+ }
+ 
+ static int
+@@ -2826,25 +2735,92 @@ netdev_dpdk_vhost_send(struct netdev *netdev, int qid,
+                        struct dp_packet_batch *batch,
+                        bool concurrent_txq OVS_UNUSED)
+ {
++    struct netdev_dpdk *dev = netdev_dpdk_cast(netdev);
++    int max_retries = VHOST_ENQ_RETRY_MIN;
++    int cnt, batch_cnt, vhost_batch_cnt;
++    int vid = netdev_dpdk_get_vid(dev);
++    struct netdev_dpdk_sw_stats stats;
++    struct rte_mbuf **pkts;
++    int retries;
+ 
+-    if (OVS_UNLIKELY(batch->packets[0]->source != DPBUF_DPDK)) {
+-        dpdk_do_tx_copy(netdev, qid, batch);
++    batch_cnt = cnt = dp_packet_batch_size(batch);
++    qid = dev->tx_q[qid % netdev->n_txq].map;
++    if (OVS_UNLIKELY(vid < 0 || !dev->vhost_reconfigured || qid < 0
++                     || !(dev->flags & NETDEV_UP))) {
++        rte_spinlock_lock(&dev->stats_lock);
++        dev->stats.tx_dropped += cnt;
++        rte_spinlock_unlock(&dev->stats_lock);
+         dp_packet_delete_batch(batch, true);
+-    } else {
+-        __netdev_dpdk_vhost_send(netdev, qid, batch->packets,
+-                                 dp_packet_batch_size(batch));
++        return 0;
++    }
++
++    if (OVS_UNLIKELY(!rte_spinlock_trylock(&dev->tx_q[qid].tx_lock))) {
++        COVERAGE_INC(vhost_tx_contention);
++        rte_spinlock_lock(&dev->tx_q[qid].tx_lock);
++    }
++
++    cnt = netdev_dpdk_common_send(netdev, batch, &stats);
++
++    pkts = (struct rte_mbuf **) batch->packets;
++    vhost_batch_cnt = cnt;
++    retries = 0;
++    do {
++        int vhost_qid = qid * VIRTIO_QNUM + VIRTIO_RXQ;
++        int tx_pkts;
++
++        tx_pkts = rte_vhost_enqueue_burst(vid, vhost_qid, pkts, cnt);
++        if (OVS_LIKELY(tx_pkts)) {
++            /* Packets have been sent.*/
++            cnt -= tx_pkts;
++            /* Prepare for possible retry.*/
++            pkts = &pkts[tx_pkts];
++            if (OVS_UNLIKELY(cnt && !retries)) {
++                /*
++                 * Read max retries as there are packets not sent
++                 * and no retries have already occurred.
++                 */
++                atomic_read_relaxed(&dev->vhost_tx_retries_max, &max_retries);
++            }
++        } else {
++            /* No packets sent - do not retry.*/
++            break;
++        }
++    } while (cnt && (retries++ < max_retries));
++
++    rte_spinlock_unlock(&dev->tx_q[qid].tx_lock);
++
++    stats.tx_failure_drops += cnt;
++    stats.tx_retries = MIN(retries, max_retries);
++
++    rte_spinlock_lock(&dev->stats_lock);
++    netdev_dpdk_vhost_update_tx_counters(dev, batch->packets, batch_cnt,
++                                         &stats);
++    rte_spinlock_unlock(&dev->stats_lock);
++
++    pkts = (struct rte_mbuf **) batch->packets;
++    for (int i = 0; i < vhost_batch_cnt; i++) {
++        rte_pktmbuf_free(pkts[i]);
+     }
++
+     return 0;
+ }
+ 
+-static inline void
+-netdev_dpdk_send__(struct netdev_dpdk *dev, int qid,
+-                   struct dp_packet_batch *batch,
+-                   bool concurrent_txq)
++static int
++netdev_dpdk_eth_send(struct netdev *netdev, int qid,
++                     struct dp_packet_batch *batch, bool concurrent_txq)
+ {
++    struct rte_mbuf **pkts = (struct rte_mbuf **) batch->packets;
++    struct netdev_dpdk *dev = netdev_dpdk_cast(netdev);
++    int batch_cnt = dp_packet_batch_size(batch);
++    struct netdev_dpdk_sw_stats stats;
++    int cnt, dropped;
++
+     if (OVS_UNLIKELY(!(dev->flags & NETDEV_UP))) {
++        rte_spinlock_lock(&dev->stats_lock);
++        dev->stats.tx_dropped += dp_packet_batch_size(batch);
++        rte_spinlock_unlock(&dev->stats_lock);
+         dp_packet_delete_batch(batch, true);
+-        return;
++        return 0;
+     }
+ 
+     if (OVS_UNLIKELY(concurrent_txq)) {
+@@ -2852,56 +2828,27 @@ netdev_dpdk_send__(struct netdev_dpdk *dev, int qid,
+         rte_spinlock_lock(&dev->tx_q[qid].tx_lock);
+     }
+ 
+-    if (OVS_UNLIKELY(batch->packets[0]->source != DPBUF_DPDK)) {
+-        struct netdev *netdev = &dev->up;
++    cnt = netdev_dpdk_common_send(netdev, batch, &stats);
+ 
+-        dpdk_do_tx_copy(netdev, qid, batch);
+-        dp_packet_delete_batch(batch, true);
+-    } else {
++    dropped = batch_cnt - cnt;
++
++    dropped += netdev_dpdk_eth_tx_burst(dev, qid, pkts, cnt);
++    if (OVS_UNLIKELY(dropped)) {
+         struct netdev_dpdk_sw_stats *sw_stats = dev->sw_stats;
+-        int dropped;
+-        int tx_failure, mtu_drops, qos_drops, hwol_drops;
+-        int batch_cnt = dp_packet_batch_size(batch);
+-        struct rte_mbuf **pkts = (struct rte_mbuf **) batch->packets;
+ 
+-        hwol_drops = batch_cnt;
+-        if (userspace_tso_enabled()) {
+-            batch_cnt = netdev_dpdk_prep_hwol_batch(dev, pkts, batch_cnt);
+-        }
+-        hwol_drops -= batch_cnt;
+-        mtu_drops = batch_cnt;
+-        batch_cnt = netdev_dpdk_filter_packet_len(dev, pkts, batch_cnt);
+-        mtu_drops -= batch_cnt;
+-        qos_drops = batch_cnt;
+-        batch_cnt = netdev_dpdk_qos_run(dev, pkts, batch_cnt, true);
+-        qos_drops -= batch_cnt;
+-
+-        tx_failure = netdev_dpdk_eth_tx_burst(dev, qid, pkts, batch_cnt);
+-
+-        dropped = tx_failure + mtu_drops + qos_drops + hwol_drops;
+-        if (OVS_UNLIKELY(dropped)) {
+-            rte_spinlock_lock(&dev->stats_lock);
+-            dev->stats.tx_dropped += dropped;
+-            sw_stats->tx_failure_drops += tx_failure;
+-            sw_stats->tx_mtu_exceeded_drops += mtu_drops;
+-            sw_stats->tx_qos_drops += qos_drops;
+-            sw_stats->tx_invalid_hwol_drops += hwol_drops;
+-            rte_spinlock_unlock(&dev->stats_lock);
+-        }
++        rte_spinlock_lock(&dev->stats_lock);
++        dev->stats.tx_dropped += dropped;
++        sw_stats->tx_failure_drops += stats.tx_failure_drops;
++        sw_stats->tx_mtu_exceeded_drops += stats.tx_mtu_exceeded_drops;
++        sw_stats->tx_qos_drops += stats.tx_qos_drops;
++        sw_stats->tx_invalid_hwol_drops += stats.tx_invalid_hwol_drops;
++        rte_spinlock_unlock(&dev->stats_lock);
+     }
+ 
+     if (OVS_UNLIKELY(concurrent_txq)) {
+         rte_spinlock_unlock(&dev->tx_q[qid].tx_lock);
+     }
+-}
+-
+-static int
+-netdev_dpdk_eth_send(struct netdev *netdev, int qid,
+-                     struct dp_packet_batch *batch, bool concurrent_txq)
+-{
+-    struct netdev_dpdk *dev = netdev_dpdk_cast(netdev);
+ 
+-    netdev_dpdk_send__(dev, qid, batch, concurrent_txq);
+     return 0;
+ }
+ 
+@@ -4686,11 +4633,11 @@ trtcm_policer_qos_construct(const struct smap *details,
  static void
  trtcm_policer_qos_destruct(struct qos_conf *conf)
  {
@@ -1741,7 +2975,7 @@ index b6b29c75e3..4d32781a95 100644
          free(queue);
      }
 diff --git a/lib/netdev-linux.c b/lib/netdev-linux.c
-index 620a451dec..9d125029de 100644
+index 620a451dec..2766b3f2bf 100644
 --- a/lib/netdev-linux.c
 +++ b/lib/netdev-linux.c
 @@ -5331,11 +5331,11 @@ static void
@@ -1758,8 +2992,84 @@ index 620a451dec..9d125029de 100644
          hmap_remove(&hfsc->tc.queues, &hc->tc_queue.hmap_node);
          free(hc);
      }
+@@ -6295,7 +6295,14 @@ get_stats_via_netlink(const struct netdev *netdev_, struct netdev_stats *stats)
+     if (ofpbuf_try_pull(reply, NLMSG_HDRLEN + sizeof(struct ifinfomsg))) {
+         const struct nlattr *a = nl_attr_find(reply, 0, IFLA_STATS64);
+         if (a && nl_attr_get_size(a) >= sizeof(struct rtnl_link_stats64)) {
+-            netdev_stats_from_rtnl_link_stats64(stats, nl_attr_get(a));
++            const struct rtnl_link_stats64 *lstats = nl_attr_get(a);
++            struct rtnl_link_stats64 aligned_lstats;
++
++            if (!IS_PTR_ALIGNED(lstats)) {
++                memcpy(&aligned_lstats, lstats, sizeof aligned_lstats);
++                lstats = &aligned_lstats;
++            }
++            netdev_stats_from_rtnl_link_stats64(stats, lstats);
+             error = 0;
+         } else {
+             a = nl_attr_find(reply, 0, IFLA_STATS);
+diff --git a/lib/netdev-offload-dpdk.c b/lib/netdev-offload-dpdk.c
+index 94dc6a9b74..12d299603a 100644
+--- a/lib/netdev-offload-dpdk.c
++++ b/lib/netdev-offload-dpdk.c
+@@ -363,6 +363,8 @@ dump_flow_pattern(struct ds *s,
+ 
+         ds_put_cstr(s, "eth ");
+         if (eth_spec) {
++            uint32_t has_vlan_mask;
++
+             if (!eth_mask) {
+                 eth_mask = &rte_flow_item_eth_mask;
+             }
+@@ -377,6 +379,9 @@ dump_flow_pattern(struct ds *s,
+             DUMP_PATTERN_ITEM(eth_mask->type, false, "type", "0x%04"PRIx16,
+                               ntohs(eth_spec->type),
+                               ntohs(eth_mask->type), 0);
++            has_vlan_mask = eth_mask->has_vlan ? UINT32_MAX : 0;
++            DUMP_PATTERN_ITEM(has_vlan_mask, false, "has_vlan", "%d",
++                              eth_spec->has_vlan, eth_mask->has_vlan, 0);
+         }
+         ds_put_cstr(s, "/ ");
+     } else if (item->type == RTE_FLOW_ITEM_TYPE_VLAN) {
+@@ -1369,6 +1374,7 @@ parse_flow_match(struct netdev *netdev,
+                  struct flow_patterns *patterns,
+                  struct match *match)
+ {
++    struct rte_flow_item_eth *eth_spec = NULL, *eth_mask = NULL;
+     struct flow *consumed_masks;
+     uint8_t proto = 0;
+ 
+@@ -1414,6 +1420,11 @@ parse_flow_match(struct netdev *netdev,
+         memset(&consumed_masks->dl_src, 0, sizeof consumed_masks->dl_src);
+         consumed_masks->dl_type = 0;
+ 
++        spec->has_vlan = 0;
++        mask->has_vlan = 1;
++        eth_spec = spec;
++        eth_mask = mask;
++
+         add_flow_pattern(patterns, RTE_FLOW_ITEM_TYPE_ETH, spec, mask, NULL);
+     }
+ 
+@@ -1427,8 +1438,14 @@ parse_flow_match(struct netdev *netdev,
+         spec->tci = match->flow.vlans[0].tci & ~htons(VLAN_CFI);
+         mask->tci = match->wc.masks.vlans[0].tci & ~htons(VLAN_CFI);
+ 
+-        /* Match any protocols. */
+-        mask->inner_type = 0;
++        if (eth_spec && eth_mask) {
++            eth_spec->has_vlan = 1;
++            eth_mask->has_vlan = 1;
++            spec->inner_type = eth_spec->type;
++            mask->inner_type = eth_mask->type;
++            eth_spec->type = match->flow.vlans[0].tpid;
++            eth_mask->type = match->wc.masks.vlans[0].tpid;
++        }
+ 
+         add_flow_pattern(patterns, RTE_FLOW_ITEM_TYPE_VLAN, spec, mask, NULL);
+     }
 diff --git a/lib/netdev-offload-tc.c b/lib/netdev-offload-tc.c
-index 9845e8d3fe..a41b627585 100644
+index 9845e8d3fe..262faf3c62 100644
 --- a/lib/netdev-offload-tc.c
 +++ b/lib/netdev-offload-tc.c
 @@ -417,11 +417,11 @@ delete_chains_from_netdev(struct netdev *netdev, struct tcf_id *id)
@@ -1860,6 +3170,15 @@ index 9845e8d3fe..a41b627585 100644
      }
  
      if (mask->ct_zone) {
+@@ -1638,7 +1640,7 @@ netdev_tc_flow_put(struct netdev *netdev, struct match *match,
+ 
+     if (mask->vlans[0].tpid && eth_type_vlan(key->vlans[0].tpid)) {
+         flower.key.encap_eth_type[0] = flower.key.eth_type;
+-        flower.mask.encap_eth_type[0] = flower.mask.eth_type;
++        flower.mask.encap_eth_type[0] = CONSTANT_HTONS(0xffff);
+         flower.key.eth_type = key->vlans[0].tpid;
+         flower.mask.eth_type = mask->vlans[0].tpid;
+     }
 @@ -1841,7 +1843,25 @@ netdev_tc_flow_put(struct netdev *netdev, struct match *match,
                  VLOG_DBG_RL(&rl, "Can't find netdev for output port %d", port);
                  return ENODEV;
@@ -1898,11 +3217,33 @@ index 9845e8d3fe..a41b627585 100644
      }
  
 diff --git a/lib/odp-util.c b/lib/odp-util.c
-index 9a705cffa3..3ae850b669 100644
+index 9a705cffa3..2d2a6893c6 100644
 --- a/lib/odp-util.c
 +++ b/lib/odp-util.c
-@@ -4630,6 +4630,11 @@ odp_flow_format(const struct nlattr *key, size_t key_len,
-                     ds_put_char(ds, ',');
+@@ -3429,16 +3429,16 @@ format_eth(struct ds *ds, const char *name, const struct eth_addr key,
+ 
+ static void
+ format_be64(struct ds *ds, const char *name, ovs_be64 key,
+-            const ovs_be64 *mask, bool verbose)
++            const ovs_32aligned_be64 *mask_, bool verbose)
+ {
+-    bool mask_empty = mask && !*mask;
++    ovs_be64 mask = mask_ ? get_32aligned_be64(mask_) : htonll(0);
+ 
+-    if (verbose || !mask_empty) {
+-        bool mask_full = !mask || *mask == OVS_BE64_MAX;
++    if (verbose || mask) {
++        bool mask_full = !mask_ || mask == OVS_BE64_MAX;
+ 
+         ds_put_format(ds, "%s=0x%"PRIx64, name, ntohll(key));
+         if (!mask_full) { /* Partially masked. */
+-            ds_put_format(ds, "/%#"PRIx64, ntohll(*mask));
++            ds_put_format(ds, "/%#"PRIx64, ntohll(mask));
+         }
+         ds_put_char(ds, ',');
+     }
+@@ -4630,6 +4630,11 @@ odp_flow_format(const struct nlattr *key, size_t key_len,
+                     ds_put_char(ds, ',');
                  }
                  ds_put_cstr(ds, "eth()");
 +            } else if (attr_type == OVS_KEY_ATTR_PACKET_TYPE && is_wildcard) {
@@ -1913,8 +3254,185 @@ index 9a705cffa3..3ae850b669 100644
              }
              ofpbuf_clear(&ofp);
          }
+@@ -7161,11 +7166,6 @@ parse_l2_5_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
+                 }
+             }
+         }
+-    } else if (src_flow->nw_proto == IPPROTO_IGMP
+-               && src_flow->dl_type == htons(ETH_TYPE_IP)) {
+-        /* OVS userspace parses the IGMP type, code, and group, but its
+-         * datapaths do not, so there is always missing information. */
+-        return ODP_FIT_TOO_LITTLE;
+     }
+     if (is_mask && expected_bit != OVS_KEY_ATTR_UNSPEC) {
+         if ((flow->tp_src || flow->tp_dst) && flow->nw_proto != 0xff) {
+@@ -7188,7 +7188,8 @@ parse_8021q_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
+                    uint64_t present_attrs, int out_of_range_attr,
+                    uint64_t expected_attrs, struct flow *flow,
+                    const struct nlattr *key, size_t key_len,
+-                   const struct flow *src_flow, char **errorp)
++                   const struct flow *src_flow, char **errorp,
++                   bool ignore_vlan_limit)
+ {
+     static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+     bool is_mask = src_flow != flow;
+@@ -7196,9 +7197,11 @@ parse_8021q_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
+     const struct nlattr *encap;
+     enum odp_key_fitness encap_fitness;
+     enum odp_key_fitness fitness = ODP_FIT_ERROR;
++    int vlan_limit;
+     int encaps = 0;
+ 
+-    while (encaps < flow_vlan_limit &&
++    vlan_limit = ignore_vlan_limit ? FLOW_MAX_VLAN_HEADERS : flow_vlan_limit;
++    while (encaps < vlan_limit &&
+            (is_mask
+             ? (src_flow->vlans[encaps].tci & htons(VLAN_CFI)) != 0
+             : eth_type_vlan(flow->dl_type))) {
+@@ -7259,6 +7262,14 @@ parse_8021q_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
+         }
+         expected_attrs = 0;
+ 
++        /* For OVS to be backward compatible with newer datapath
++         * implementations, we should ignore out of range attributes. */
++        if (out_of_range_attr) {
++            VLOG_DBG("Flow key decode found unknown OVS_KEY_ATTR, %d",
++                     out_of_range_attr);
++            out_of_range_attr = 0;
++        }
++
+         if (!parse_ethertype(attrs, present_attrs, &expected_attrs,
+                              flow, src_flow, errorp)) {
+             return ODP_FIT_ERROR;
+@@ -7281,7 +7292,7 @@ parse_8021q_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
+ static enum odp_key_fitness
+ odp_flow_key_to_flow__(const struct nlattr *key, size_t key_len,
+                        struct flow *flow, const struct flow *src_flow,
+-                       char **errorp)
++                       char **errorp, bool ignore_vlan_limit)
+ {
+     /* New "struct flow" fields that are visible to the datapath (including all
+      * data fields) should be translated from equivalent datapath flow fields
+@@ -7308,6 +7319,14 @@ odp_flow_key_to_flow__(const struct nlattr *key, size_t key_len,
+     }
+     expected_attrs = 0;
+ 
++    /* For OVS to be backward compatible with newer datapath implementations,
++     * we should ignore out of range attributes. */
++    if (out_of_range_attr) {
++        VLOG_DBG("Flow key decode found unknown OVS_KEY_ATTR, %d",
++                 out_of_range_attr);
++        out_of_range_attr = 0;
++    }
++
+     /* Metadata. */
+     if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_RECIRC_ID)) {
+         flow->recirc_id = nl_attr_get_u32(attrs[OVS_KEY_ATTR_RECIRC_ID]);
+@@ -7431,7 +7450,7 @@ odp_flow_key_to_flow__(const struct nlattr *key, size_t key_len,
+         : eth_type_vlan(src_flow->dl_type)) {
+         fitness = parse_8021q_onward(attrs, present_attrs, out_of_range_attr,
+                                      expected_attrs, flow, key, key_len,
+-                                     src_flow, errorp);
++                                     src_flow, errorp, ignore_vlan_limit);
+     } else {
+         if (is_mask) {
+             /* A missing VLAN mask means exact match on vlan_tci 0 (== no
+@@ -7497,7 +7516,7 @@ enum odp_key_fitness
+ odp_flow_key_to_flow(const struct nlattr *key, size_t key_len,
+                      struct flow *flow, char **errorp)
+ {
+-    return odp_flow_key_to_flow__(key, key_len, flow, flow, errorp);
++    return odp_flow_key_to_flow__(key, key_len, flow, flow, errorp, false);
+ }
+ 
+ /* Converts the 'mask_key_len' bytes of OVS_KEY_ATTR_* attributes in 'mask_key'
+@@ -7509,14 +7528,16 @@ odp_flow_key_to_flow(const struct nlattr *key, size_t key_len,
+  * If 'errorp' is nonnull, this function uses it for detailed error reports: if
+  * the return value is ODP_FIT_ERROR, it stores a malloc()'d error string in
+  * '*errorp', otherwise NULL. */
+-enum odp_key_fitness
+-odp_flow_key_to_mask(const struct nlattr *mask_key, size_t mask_key_len,
+-                     struct flow_wildcards *mask, const struct flow *src_flow,
+-                     char **errorp)
++static enum odp_key_fitness
++odp_flow_key_to_mask__(const struct nlattr *mask_key, size_t mask_key_len,
++                       struct flow_wildcards *mask,
++                       const struct flow *src_flow,
++                       char **errorp, bool ignore_vlan_limit)
+ {
+     if (mask_key_len) {
+         return odp_flow_key_to_flow__(mask_key, mask_key_len,
+-                                      &mask->masks, src_flow, errorp);
++                                      &mask->masks, src_flow, errorp,
++                                      ignore_vlan_limit);
+     } else {
+         if (errorp) {
+             *errorp = NULL;
+@@ -7530,6 +7551,15 @@ odp_flow_key_to_mask(const struct nlattr *mask_key, size_t mask_key_len,
+     }
+ }
+ 
++enum odp_key_fitness
++odp_flow_key_to_mask(const struct nlattr *mask_key, size_t mask_key_len,
++                     struct flow_wildcards *mask,
++                     const struct flow *src_flow, char **errorp)
++{
++    return odp_flow_key_to_mask__(mask_key, mask_key_len, mask, src_flow,
++                                  errorp, false);
++}
++
+ /* Converts the netlink formated key/mask to match.
+  * Fails if odp_flow_key_from_key/mask and odp_flow_key_key/mask
+  * disagree on the acceptable form of flow */
+@@ -7540,12 +7570,15 @@ parse_key_and_mask_to_match(const struct nlattr *key, size_t key_len,
+ {
+     enum odp_key_fitness fitness;
+ 
+-    fitness = odp_flow_key_to_flow(key, key_len, &match->flow, NULL);
++    fitness = odp_flow_key_to_flow__(key, key_len, &match->flow, &match->flow,
++                                     NULL, true);
+     if (fitness) {
+-        /* This should not happen: it indicates that
+-         * odp_flow_key_from_flow() and odp_flow_key_to_flow() disagree on
+-         * the acceptable form of a flow.  Log the problem as an error,
+-         * with enough details to enable debugging. */
++        /* This will happen when the odp_flow_key_to_flow() function can't
++         * parse the netlink message to a match structure.  It will return
++         * ODP_FIT_TOO_LITTLE if there is not enough information to parse the
++         * content successfully, ODP_FIT_TOO_MUCH if there is too much netlink
++         * data and we do not know how to safely ignore it, and ODP_FIT_ERROR
++         * in any other case. */
+         static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+ 
+         if (!VLOG_DROP_ERR(&rl)) {
+@@ -7553,20 +7586,18 @@ parse_key_and_mask_to_match(const struct nlattr *key, size_t key_len,
+ 
+             ds_init(&s);
+             odp_flow_format(key, key_len, NULL, 0, NULL, &s, true);
+-            VLOG_ERR("internal error parsing flow key %s", ds_cstr(&s));
++            VLOG_ERR("internal error parsing flow key %s (%s)",
++                     ds_cstr(&s), odp_key_fitness_to_string(fitness));
+             ds_destroy(&s);
+         }
+ 
+         return EINVAL;
+     }
+ 
+-    fitness = odp_flow_key_to_mask(mask, mask_len, &match->wc, &match->flow,
+-                                   NULL);
++    fitness = odp_flow_key_to_mask__(mask, mask_len, &match->wc, &match->flow,
++                                     NULL, true);
+     if (fitness) {
+-        /* This should not happen: it indicates that
+-         * odp_flow_key_from_mask() and odp_flow_key_to_mask()
+-         * disagree on the acceptable form of a mask.  Log the problem
+-         * as an error, with enough details to enable debugging. */
++        /* This should not happen, see comment above. */
+         static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+ 
+         if (!VLOG_DROP_ERR(&rl)) {
 diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c
-index 006837c2e1..a0b70a89d7 100644
+index 006837c2e1..c13f97b5c9 100644
 --- a/lib/ofp-actions.c
 +++ b/lib/ofp-actions.c
 @@ -853,7 +853,9 @@ decode_NXAST_RAW_CONTROLLER2(const struct ext_action_header *eah,
@@ -1928,6 +3446,31 @@ index 006837c2e1..a0b70a89d7 100644
              break;
          }
  
+@@ -3200,16 +3202,21 @@ set_field_split_str(char *arg, char **key, char **value, char **delim)
+ {
+     char *value_end;
+ 
++    *key = NULL;
+     *value = arg;
+-    value_end = strstr(arg, "->");
+-    *key = value_end + strlen("->");
+     if (delim) {
+-        *delim = value_end;
++        *delim = NULL;
+     }
+ 
++    value_end = strstr(arg, "->");
+     if (!value_end) {
+         return xasprintf("%s: missing `->'", arg);
+     }
++
++    *key = value_end + strlen("->");
++    if (delim) {
++        *delim = value_end;
++    }
+     if (strlen(value_end) <= strlen("->")) {
+         return xasprintf("%s: missing field name following `->'", arg);
+     }
 diff --git a/lib/ofp-msgs.c b/lib/ofp-msgs.c
 index fec54f75f8..93aa812978 100644
 --- a/lib/ofp-msgs.c
@@ -1991,8 +3534,34 @@ index 4579548ee1..9485ddfc93 100644
              break;
           }
  
+diff --git a/lib/ofpbuf.c b/lib/ofpbuf.c
+index 271105bdea..879275a7a3 100644
+--- a/lib/ofpbuf.c
++++ b/lib/ofpbuf.c
+@@ -426,6 +426,10 @@ void
+ ofpbuf_reserve(struct ofpbuf *b, size_t size)
+ {
+     ovs_assert(!b->size);
++
++    if (!size) {
++        return;
++    }
+     ofpbuf_prealloc_tailroom(b, size);
+     b->data = (char*)b->data + size;
+ }
+@@ -436,6 +440,10 @@ ofpbuf_reserve(struct ofpbuf *b, size_t size)
+ void *
+ ofpbuf_push_uninit(struct ofpbuf *b, size_t size)
+ {
++    if (!size) {
++        return b->data;
++    }
++
+     ofpbuf_prealloc_headroom(b, size);
+     b->data = (char*)b->data - size;
+     b->size += size;
 diff --git a/lib/ovs-lldp.c b/lib/ovs-lldp.c
-index 162311fa45..a9d205ec83 100644
+index 162311fa45..2d13e971ed 100644
 --- a/lib/ovs-lldp.c
 +++ b/lib/ovs-lldp.c
 @@ -559,9 +559,9 @@ aa_mapping_unregister_mapping(struct lldp *lldp,
@@ -2007,7 +3576,22 @@ index 162311fa45..a9d205ec83 100644
                          &hw->h_lport.p_isid_vlan_maps) {
          uint32_t isid = lm->isid_vlan_data.isid;
  
-@@ -953,8 +953,8 @@ lldp_ref(const struct lldp *lldp_)
+@@ -738,6 +738,14 @@ lldp_put_packet(struct lldp *lldp, struct dp_packet *packet,
+     ovs_mutex_unlock(&mutex);
+ }
+ 
++/* Is LLDP enabled?
++ */
++bool
++lldp_is_enabled(struct lldp *lldp)
++{
++    return lldp ? lldp->enabled : false;
++}
++
+ /* Configures the LLDP stack.
+  */
+ bool
+@@ -953,8 +961,8 @@ lldp_ref(const struct lldp *lldp_)
  void
  lldp_destroy_dummy(struct lldp *lldp)
  {
@@ -2018,7 +3602,7 @@ index 162311fa45..a9d205ec83 100644
      struct lldpd *cfg;
  
      if (!lldp) {
-@@ -963,13 +963,13 @@ lldp_destroy_dummy(struct lldp *lldp)
+@@ -963,13 +971,13 @@ lldp_destroy_dummy(struct lldp *lldp)
  
      cfg = lldp->lldpd;
  
@@ -2034,6 +3618,18 @@ index 162311fa45..a9d205ec83 100644
          ovs_list_remove(&chassis->list);
          free(chassis);
      }
+diff --git a/lib/ovs-lldp.h b/lib/ovs-lldp.h
+index 0e536e8c27..661ac4e18a 100644
+--- a/lib/ovs-lldp.h
++++ b/lib/ovs-lldp.h
+@@ -86,6 +86,7 @@ void lldp_run(struct lldpd *cfg);
+ bool lldp_should_send_packet(struct lldp *cfg);
+ bool lldp_should_process_flow(struct lldp *lldp, const struct flow *flow);
+ bool lldp_configure(struct lldp *lldp, const struct smap *cfg);
++bool lldp_is_enabled(struct lldp *lldp);
+ void lldp_process_packet(struct lldp *cfg, const struct dp_packet *);
+ void lldp_put_packet(struct lldp *lldp, struct dp_packet *packet,
+                      const struct eth_addr eth_src);
 diff --git a/lib/ovs-numa.h b/lib/ovs-numa.h
 index ecc251a7ff..83bd10cca5 100644
 --- a/lib/ovs-numa.h
@@ -2050,6 +3646,129 @@ index ecc251a7ff..83bd10cca5 100644
 +    HMAP_FOR_EACH (ITER, hmap_node, &(DUMP)->numas)
  
  #endif /* ovs-numa.h */
+diff --git a/lib/ovs-rcu.c b/lib/ovs-rcu.c
+index 1866bd3088..946aa04d18 100644
+--- a/lib/ovs-rcu.c
++++ b/lib/ovs-rcu.c
+@@ -444,3 +444,40 @@ ovsrcu_init_module(void)
+         ovsthread_once_done(&once);
+     }
+ }
++
++static void
++ovsrcu_barrier_func(void *seq_)
++{
++    struct seq *seq = (struct seq *) seq_;
++    seq_change(seq);
++}
++
++/* Similar to the kernel rcu_barrier, ovsrcu_barrier waits for all outstanding
++ * RCU callbacks to complete. However, unlike the kernel rcu_barrier, which
++ * might return immediately if there are no outstanding RCU callbacks,
++ * this API will at least wait for a grace period.
++ *
++ * Another issue the caller might need to know is that the barrier is just
++ * for "one-shot", i.e. if inside some RCU callbacks, another RCU callback is
++ * registered, this API only guarantees the first round of RCU callbacks have
++ * been executed after it returns.
++ */
++void
++ovsrcu_barrier(void)
++{
++    struct seq *seq = seq_create();
++    /* First let all threads flush their cbsets. */
++    ovsrcu_synchronize();
++
++    /* Then register a new cbset, ensure this cbset
++     * is at the tail of the global list. */
++    uint64_t seqno = seq_read(seq);
++    ovsrcu_postpone__(ovsrcu_barrier_func, (void *) seq);
++
++    do {
++        seq_wait(seq, seqno);
++        poll_block();
++    } while (seqno == seq_read(seq));
++
++    seq_destroy(seq);
++}
+diff --git a/lib/ovs-rcu.h b/lib/ovs-rcu.h
+index ecc4c92010..8b397b7fb0 100644
+--- a/lib/ovs-rcu.h
++++ b/lib/ovs-rcu.h
+@@ -155,6 +155,19 @@
+  *         port_delete(id);
+  *     }
+  *
++ * Use ovsrcu_barrier() to wait for all the outstanding RCU callbacks to
++ * finish. This is useful when you have to destroy some resources however
++ * these resources are referenced in the outstanding RCU callbacks.
++ *
++ *     void rcu_cb(void *A) {
++ *         do_something(A);
++ *     }
++ *
++ *     void destroy_A() {
++ *         ovsrcu_postpone(rcu_cb, A); // will use A later
++ *         ovsrcu_barrier(); // wait for rcu_cb done
++ *         do_destroy_A(); // free A
++ *     }
+  */
+ 
+ #include "compiler.h"
+@@ -310,4 +323,6 @@ void ovsrcu_synchronize(void);
+ 
+ void ovsrcu_exit(void);
+ 
++void ovsrcu_barrier(void);
++
+ #endif /* ovs-rcu.h */
+diff --git a/lib/ovs-router.c b/lib/ovs-router.c
+index 09b81c6e5a..5d0fbd503e 100644
+--- a/lib/ovs-router.c
++++ b/lib/ovs-router.c
+@@ -164,9 +164,10 @@ static void rt_init_match(struct match *match, uint32_t mark,
+     match->flow.pkt_mark = mark;
+ }
+ 
+-static int
+-get_src_addr(const struct in6_addr *ip6_dst,
+-             const char output_bridge[], struct in6_addr *psrc)
++int
++ovs_router_get_netdev_source_address(const struct in6_addr *ip6_dst,
++                                     const char output_bridge[],
++                                     struct in6_addr *psrc)
+ {
+     struct in6_addr *mask, *addr6;
+     int err, n_in6, i, max_plen = -1;
+@@ -235,9 +236,11 @@ ovs_router_insert__(uint32_t mark, uint8_t priority, bool local,
+     p->plen = plen;
+     p->local = local;
+     p->priority = priority;
+-    err = get_src_addr(ip6_dst, output_bridge, &p->src_addr);
++    err = ovs_router_get_netdev_source_address(ip6_dst, output_bridge,
++                                               &p->src_addr);
+     if (err && ipv6_addr_is_set(gw)) {
+-        err = get_src_addr(gw, output_bridge, &p->src_addr);
++        err = ovs_router_get_netdev_source_address(gw, output_bridge,
++                                                   &p->src_addr);
+     }
+     if (err) {
+         struct ds ds = DS_EMPTY_INITIALIZER;
+diff --git a/lib/ovs-router.h b/lib/ovs-router.h
+index 34ea163eef..d8ce3c00de 100644
+--- a/lib/ovs-router.h
++++ b/lib/ovs-router.h
+@@ -37,6 +37,10 @@ void ovs_router_flush(void);
+ 
+ void ovs_router_disable_system_routing_table(void);
+ 
++int ovs_router_get_netdev_source_address(const struct in6_addr *ip6_dst,
++                                         const char output_bridge[],
++                                         struct in6_addr *psrc);
++
+ #ifdef  __cplusplus
+ }
+ #endif
 diff --git a/lib/ovsdb-cs.c b/lib/ovsdb-cs.c
 index dead31275d..9713c7dc7c 100644
 --- a/lib/ovsdb-cs.c
@@ -2088,6 +3807,107 @@ index dead31275d..9713c7dc7c 100644
              struct sset *sset = node->data;
              sset_destroy(sset);
              free(sset);
+diff --git a/lib/ovsdb-data.c b/lib/ovsdb-data.c
+index 6b1c20ff85..61ad7679a6 100644
+--- a/lib/ovsdb-data.c
++++ b/lib/ovsdb-data.c
+@@ -1957,6 +1957,19 @@ ovsdb_datum_add_unsafe(struct ovsdb_datum *datum,
+     }
+ }
+ 
++void
++ovsdb_datum_add_from_index_unsafe(struct ovsdb_datum *dst,
++                                  const struct ovsdb_datum *src,
++                                  size_t idx,
++                                  const struct ovsdb_type *type)
++{
++    const union ovsdb_atom *key = &src->keys[idx];
++    const union ovsdb_atom *value = type->value.type != OVSDB_TYPE_VOID
++                                    ? &src->values[idx]
++                                    : NULL;
++    ovsdb_datum_add_unsafe(dst, key, value, type, NULL);
++}
++
+ /* Adds 'n' atoms starting from index 'start_idx' from 'src' to the end of
+  * 'dst'.  'dst' should have enough memory allocated to hold the additional
+  * 'n' atoms.  Atoms are not cloned, i.e. 'dst' will reference the same data.
+@@ -2165,12 +2178,10 @@ ovsdb_datum_added_removed(struct ovsdb_datum *added,
+         int c = ovsdb_atom_compare_3way(&old->keys[oi], &new->keys[ni],
+                                         type->key.type);
+         if (c < 0) {
+-            ovsdb_datum_add_unsafe(removed, &old->keys[oi], &old->values[oi],
+-                                   type, NULL);
++            ovsdb_datum_add_from_index_unsafe(removed, old, oi, type);
+             oi++;
+         } else if (c > 0) {
+-            ovsdb_datum_add_unsafe(added, &new->keys[ni], &new->values[ni],
+-                                   type, NULL);
++            ovsdb_datum_add_from_index_unsafe(added, new, ni, type);
+             ni++;
+         } else {
+             if (type->value.type != OVSDB_TYPE_VOID &&
+@@ -2186,13 +2197,11 @@ ovsdb_datum_added_removed(struct ovsdb_datum *added,
+     }
+ 
+     for (; oi < old->n; oi++) {
+-        ovsdb_datum_add_unsafe(removed, &old->keys[oi], &old->values[oi],
+-                               type, NULL);
++        ovsdb_datum_add_from_index_unsafe(removed, old, oi, type);
+     }
+ 
+     for (; ni < new->n; ni++) {
+-        ovsdb_datum_add_unsafe(added, &new->keys[ni], &new->values[ni],
+-                               type, NULL);
++        ovsdb_datum_add_from_index_unsafe(added, new, ni, type);
+     }
+ }
+ 
+@@ -2228,12 +2237,10 @@ ovsdb_datum_diff(struct ovsdb_datum *diff,
+         int c = ovsdb_atom_compare_3way(&old->keys[oi], &new->keys[ni],
+                                         type->key.type);
+         if (c < 0) {
+-            ovsdb_datum_add_unsafe(diff, &old->keys[oi], &old->values[oi],
+-                                   type, NULL);
++            ovsdb_datum_add_from_index_unsafe(diff, old, oi, type);
+             oi++;
+         } else if (c > 0) {
+-            ovsdb_datum_add_unsafe(diff, &new->keys[ni], &new->values[ni],
+-                                   type, NULL);
++            ovsdb_datum_add_from_index_unsafe(diff, new, ni, type);
+             ni++;
+         } else {
+             if (type->value.type != OVSDB_TYPE_VOID &&
+@@ -2247,13 +2254,11 @@ ovsdb_datum_diff(struct ovsdb_datum *diff,
+     }
+ 
+     for (; oi < old->n; oi++) {
+-        ovsdb_datum_add_unsafe(diff, &old->keys[oi], &old->values[oi],
+-                               type, NULL);
++        ovsdb_datum_add_from_index_unsafe(diff, old, oi, type);
+     }
+ 
+     for (; ni < new->n; ni++) {
+-        ovsdb_datum_add_unsafe(diff, &new->keys[ni], &new->values[ni],
+-                               type, NULL);
++        ovsdb_datum_add_from_index_unsafe(diff, new, ni, type);
+     }
+ }
+ 
+diff --git a/lib/ovsdb-data.h b/lib/ovsdb-data.h
+index 47115a7b85..ba5d179a65 100644
+--- a/lib/ovsdb-data.h
++++ b/lib/ovsdb-data.h
+@@ -280,6 +280,10 @@ void ovsdb_datum_add_unsafe(struct ovsdb_datum *,
+                             const union ovsdb_atom *value,
+                             const struct ovsdb_type *,
+                             const union ovsdb_atom *range_end_atom);
++void ovsdb_datum_add_from_index_unsafe(struct ovsdb_datum *dst,
++                                       const struct ovsdb_datum *src,
++                                       size_t idx,
++                                       const struct ovsdb_type *type);
+ 
+ /* Transactions with named-uuid row names. */
+ struct json *ovsdb_datum_to_json_with_row_names(const struct ovsdb_datum *,
 diff --git a/lib/ovsdb-idl.c b/lib/ovsdb-idl.c
 index c19128d55c..882ede7559 100644
 --- a/lib/ovsdb-idl.c
@@ -2280,6 +4100,22 @@ index 62c4621181..321043282e 100644
          set_op_destroy(set_op, type);
      }
      hmap_destroy(&list->hmap);
+diff --git a/lib/packets.c b/lib/packets.c
+index d0fba81766..874066e3c6 100644
+--- a/lib/packets.c
++++ b/lib/packets.c
+@@ -427,9 +427,9 @@ add_mpls(struct dp_packet *packet, ovs_be16 ethtype, ovs_be32 lse,
+     }
+ 
+     if (!l3_encap) {
+-        ovs_be32 *header = dp_packet_push_uninit(packet, MPLS_HLEN);
++        struct mpls_hdr *header = dp_packet_push_uninit(packet, MPLS_HLEN);
+ 
+-        *header = lse;
++        put_16aligned_be32(&header->mpls_lse, lse);
+         packet->l2_5_ofs = 0;
+         packet->packet_type = PACKET_TYPE_BE(OFPHTN_ETHERTYPE,
+                                              ntohs(ethtype));
 diff --git a/lib/pcap-file.c b/lib/pcap-file.c
 index 41835f6f4d..3ed7ea4880 100644
 --- a/lib/pcap-file.c
@@ -2774,7 +4610,7 @@ index 9ccb7d4cc4..bf66393df9 100644
                       struct sockaddr_storage *ssp, int *fdp, uint8_t dscp);
  
 diff --git a/lib/sset.c b/lib/sset.c
-index b2e3f43ec9..c3197e305f 100644
+index b2e3f43ec9..6fbaa9d60d 100644
 --- a/lib/sset.c
 +++ b/lib/sset.c
 @@ -212,9 +212,9 @@ sset_add_array(struct sset *set, char **names, size_t n)
@@ -2789,7 +4625,18 @@ index b2e3f43ec9..c3197e305f 100644
          sset_delete(set, SSET_NODE_FROM_NAME(name));
      }
  }
-@@ -320,9 +320,9 @@ sset_at_position(const struct sset *set, struct sset_position *pos)
+@@ -312,7 +312,9 @@ sset_at_position(const struct sset *set, struct sset_position *pos)
+     struct hmap_node *hmap_node;
+ 
+     hmap_node = hmap_at_position(&set->map, &pos->pos);
+-    return SSET_NODE_FROM_HMAP_NODE(hmap_node);
++    return hmap_node
++           ? SSET_NODE_FROM_HMAP_NODE(hmap_node)
++           : NULL;
+ }
+ 
+ /* Replaces 'a' by the intersection of 'a' and 'b'.  That is, removes from 'a'
+@@ -320,9 +322,9 @@ sset_at_position(const struct sset *set, struct sset_position *pos)
  void
  sset_intersect(struct sset *a, const struct sset *b)
  {
@@ -2869,7 +4716,7 @@ index fcaddf10ad..71039e24f1 100644
  
  /* Attempts to guess the content type of a stream whose first few bytes were
 diff --git a/lib/tc.c b/lib/tc.c
-index adb2d3182a..df73a43d4c 100644
+index adb2d3182a..6301e30365 100644
 --- a/lib/tc.c
 +++ b/lib/tc.c
 @@ -568,16 +568,17 @@ nl_parse_flower_vlan(struct nlattr **attrs, struct tc_flower *flower)
@@ -2969,7 +4816,51 @@ index adb2d3182a..df73a43d4c 100644
      action->type = TC_ACT_PEDIT;
  
      return 0;
-@@ -1487,7 +1485,9 @@ nl_parse_act_ct(struct nlattr *options, struct tc_flower *flower)
+@@ -1314,8 +1312,8 @@ nl_parse_act_gact(struct nlattr *options, struct tc_flower *flower)
+     struct nlattr *gact_attrs[ARRAY_SIZE(gact_policy)];
+     const struct tc_gact *p;
+     struct nlattr *gact_parms;
+-    const struct tcf_t *tm;
+     struct tc_action *action;
++    struct tcf_t tm;
+ 
+     if (!nl_parse_nested(options, gact_policy, gact_attrs,
+                          ARRAY_SIZE(gact_policy))) {
+@@ -1335,8 +1333,9 @@ nl_parse_act_gact(struct nlattr *options, struct tc_flower *flower)
+         return EINVAL;
+     }
+ 
+-    tm = nl_attr_get_unspec(gact_attrs[TCA_GACT_TM], sizeof *tm);
+-    nl_parse_tcf(tm, flower);
++    memcpy(&tm, nl_attr_get_unspec(gact_attrs[TCA_GACT_TM], sizeof tm),
++           sizeof tm);
++    nl_parse_tcf(&tm, flower);
+ 
+     return 0;
+ }
+@@ -1357,9 +1356,9 @@ nl_parse_act_mirred(struct nlattr *options, struct tc_flower *flower)
+     struct nlattr *mirred_attrs[ARRAY_SIZE(mirred_policy)];
+     const struct tc_mirred *m;
+     const struct nlattr *mirred_parms;
+-    const struct tcf_t *tm;
+     struct nlattr *mirred_tm;
+     struct tc_action *action;
++    struct tcf_t tm;
+ 
+     if (!nl_parse_nested(options, mirred_policy, mirred_attrs,
+                          ARRAY_SIZE(mirred_policy))) {
+@@ -1387,8 +1386,8 @@ nl_parse_act_mirred(struct nlattr *options, struct tc_flower *flower)
+     action->type = TC_ACT_OUTPUT;
+ 
+     mirred_tm = mirred_attrs[TCA_MIRRED_TM];
+-    tm = nl_attr_get_unspec(mirred_tm, sizeof *tm);
+-    nl_parse_tcf(tm, flower);
++    memcpy(&tm, nl_attr_get_unspec(mirred_tm, sizeof tm), sizeof tm);
++    nl_parse_tcf(&tm, flower);
+ 
+     return 0;
+ }
+@@ -1487,7 +1486,9 @@ nl_parse_act_ct(struct nlattr *options, struct tc_flower *flower)
                  if (ipv4_max) {
                      ovs_be32 addr = nl_attr_get_be32(ipv4_max);
  
@@ -2980,7 +4871,7 @@ index adb2d3182a..df73a43d4c 100644
                  }
              } else if (ipv6_min) {
                  action->ct.range.ip_family = AF_INET6;
-@@ -1496,7 +1496,9 @@ nl_parse_act_ct(struct nlattr *options, struct tc_flower *flower)
+@@ -1496,7 +1497,9 @@ nl_parse_act_ct(struct nlattr *options, struct tc_flower *flower)
                  if (ipv6_max) {
                      struct in6_addr addr = nl_attr_get_in6_addr(ipv6_max);
  
@@ -2991,7 +4882,7 @@ index adb2d3182a..df73a43d4c 100644
                  }
              }
  
-@@ -1504,6 +1506,10 @@ nl_parse_act_ct(struct nlattr *options, struct tc_flower *flower)
+@@ -1504,6 +1507,10 @@ nl_parse_act_ct(struct nlattr *options, struct tc_flower *flower)
                  action->ct.range.port.min = nl_attr_get_be16(port_min);
                  if (port_max) {
                      action->ct.range.port.max = nl_attr_get_be16(port_max);
@@ -3002,7 +4893,7 @@ index adb2d3182a..df73a43d4c 100644
                  }
              }
          }
-@@ -1702,6 +1708,9 @@ static const struct nl_policy stats_policy[] = {
+@@ -1702,6 +1709,9 @@ static const struct nl_policy stats_policy[] = {
      [TCA_STATS_BASIC] = { .type = NL_A_UNSPEC,
                            .min_len = sizeof(struct gnet_stats_basic),
                            .optional = false, },
@@ -3012,7 +4903,7 @@ index adb2d3182a..df73a43d4c 100644
  };
  
  static int
-@@ -1714,8 +1723,11 @@ nl_parse_single_action(struct nlattr *action, struct tc_flower *flower,
+@@ -1714,8 +1724,9 @@ nl_parse_single_action(struct nlattr *action, struct tc_flower *flower,
      const char *act_kind;
      struct nlattr *action_attrs[ARRAY_SIZE(act_policy)];
      struct nlattr *stats_attrs[ARRAY_SIZE(stats_policy)];
@@ -3020,13 +4911,11 @@ index adb2d3182a..df73a43d4c 100644
 -    const struct gnet_stats_basic *bs;
 +    struct ovs_flow_stats *stats_sw = &flower->stats_sw;
 +    struct ovs_flow_stats *stats_hw = &flower->stats_hw;
-+    const struct gnet_stats_basic *bs_all = NULL;
-+    const struct gnet_stats_basic *bs_hw = NULL;
-+    struct gnet_stats_basic bs_sw = { .packets = 0, .bytes = 0, };
++    struct gnet_stats_basic bs_all, bs_hw, bs_sw;
      int err = 0;
  
      if (!nl_parse_nested(action, act_policy, action_attrs,
-@@ -1771,10 +1783,26 @@ nl_parse_single_action(struct nlattr *action, struct tc_flower *flower,
+@@ -1771,10 +1782,30 @@ nl_parse_single_action(struct nlattr *action, struct tc_flower *flower,
          return EPROTO;
      }
  
@@ -3034,16 +4923,19 @@ index adb2d3182a..df73a43d4c 100644
 -    if (bs->packets) {
 -        put_32aligned_u64(&stats->n_packets, bs->packets);
 -        put_32aligned_u64(&stats->n_bytes, bs->bytes);
-+    bs_all = nl_attr_get_unspec(stats_attrs[TCA_STATS_BASIC], sizeof *bs_all);
++    memcpy(&bs_all,
++           nl_attr_get_unspec(stats_attrs[TCA_STATS_BASIC], sizeof bs_all),
++           sizeof bs_all);
 +    if (stats_attrs[TCA_STATS_BASIC_HW]) {
-+        bs_hw = nl_attr_get_unspec(stats_attrs[TCA_STATS_BASIC_HW],
-+                                   sizeof *bs_hw);
++        memcpy(&bs_hw, nl_attr_get_unspec(stats_attrs[TCA_STATS_BASIC_HW],
++                                          sizeof bs_hw),
++               sizeof bs_hw);
 +
-+        bs_sw.packets = bs_all->packets - bs_hw->packets;
-+        bs_sw.bytes = bs_all->bytes - bs_hw->bytes;
++        bs_sw.packets = bs_all.packets - bs_hw.packets;
++        bs_sw.bytes = bs_all.bytes - bs_hw.bytes;
 +    } else {
-+        bs_sw.packets = bs_all->packets;
-+        bs_sw.bytes = bs_all->bytes;
++        bs_sw.packets = bs_all.packets;
++        bs_sw.bytes = bs_all.bytes;
 +    }
 +
 +    if (bs_sw.packets > get_32aligned_u64(&stats_sw->n_packets)) {
@@ -3051,13 +4943,14 @@ index adb2d3182a..df73a43d4c 100644
 +        put_32aligned_u64(&stats_sw->n_bytes, bs_sw.bytes);
 +    }
 +
-+    if (bs_hw && bs_hw->packets > get_32aligned_u64(&stats_hw->n_packets)) {
-+        put_32aligned_u64(&stats_hw->n_packets, bs_hw->packets);
-+        put_32aligned_u64(&stats_hw->n_bytes, bs_hw->bytes);
++    if (stats_attrs[TCA_STATS_BASIC_HW]
++        && bs_hw.packets > get_32aligned_u64(&stats_hw->n_packets)) {
++        put_32aligned_u64(&stats_hw->n_packets, bs_hw.packets);
++        put_32aligned_u64(&stats_hw->n_bytes, bs_hw.bytes);
      }
  
      return 0;
-@@ -2399,14 +2427,14 @@ nl_msg_put_act_flags(struct ofpbuf *request) {
+@@ -2399,14 +2430,14 @@ nl_msg_put_act_flags(struct ofpbuf *request) {
   * first_word_mask/last_word_mask - the mask to use for the first/last read
   * (as we read entire words). */
  static void
@@ -3075,7 +4968,7 @@ index adb2d3182a..df73a43d4c 100644
  
      max_offset = m->offset + m->size;
      start_offset = ROUND_DOWN(m->offset, 4);
-@@ -2473,7 +2501,8 @@ csum_update_flag(struct tc_flower *flower,
+@@ -2473,7 +2504,8 @@ csum_update_flag(struct tc_flower *flower,
  
  static int
  nl_msg_put_flower_rewrite_pedits(struct ofpbuf *request,
@@ -3085,7 +4978,7 @@ index adb2d3182a..df73a43d4c 100644
  {
      struct {
          struct tc_pedit sel;
-@@ -2497,7 +2526,7 @@ nl_msg_put_flower_rewrite_pedits(struct ofpbuf *request,
+@@ -2497,12 +2529,12 @@ nl_msg_put_flower_rewrite_pedits(struct ofpbuf *request,
              continue;
          }
  
@@ -3094,7 +4987,14 @@ index adb2d3182a..df73a43d4c 100644
                       &first_word_mask, &mask, &data);
  
          for (j = 0; j < cnt; j++,  mask++, data++, cur_offset += 4) {
-@@ -2556,6 +2585,29 @@ nl_msg_put_flower_acts_release(struct ofpbuf *request, uint16_t act_index)
+-            ovs_be32 mask_word = *mask;
+-            ovs_be32 data_word = *data;
++            ovs_be32 mask_word = get_unaligned_be32(mask);
++            ovs_be32 data_word = get_unaligned_be32(data);
+ 
+             if (j == 0) {
+                 mask_word &= first_word_mask;
+@@ -2556,6 +2588,29 @@ nl_msg_put_flower_acts_release(struct ofpbuf *request, uint16_t act_index)
      nl_msg_end_nested(request, act_offset);
  }
  
@@ -3124,7 +5024,7 @@ index adb2d3182a..df73a43d4c 100644
  static int
  nl_msg_put_flower_acts(struct ofpbuf *request, struct tc_flower *flower)
  {
-@@ -2572,20 +2624,22 @@ nl_msg_put_flower_acts(struct ofpbuf *request, struct tc_flower *flower)
+@@ -2572,20 +2627,22 @@ nl_msg_put_flower_acts(struct ofpbuf *request, struct tc_flower *flower)
  
          action = flower->actions;
          for (i = 0; i < flower->action_count; i++, action++) {
@@ -3153,7 +5053,7 @@ index adb2d3182a..df73a43d4c 100644
                  }
              }
              break;
-@@ -2914,13 +2968,13 @@ nl_msg_put_flower_options(struct ofpbuf *request, struct tc_flower *flower)
+@@ -2914,13 +2971,13 @@ nl_msg_put_flower_options(struct ofpbuf *request, struct tc_flower *flower)
              FLOWER_PUT_MASKED_VALUE(icmp_code, TCA_FLOWER_KEY_ICMPV6_CODE);
              FLOWER_PUT_MASKED_VALUE(icmp_type, TCA_FLOWER_KEY_ICMPV6_TYPE);
          }
@@ -3172,7 +5072,7 @@ index adb2d3182a..df73a43d4c 100644
      if (host_eth_type == ETH_P_IP) {
              FLOWER_PUT_MASKED_VALUE(ipv4.ipv4_src, TCA_FLOWER_KEY_IPV4_SRC);
              FLOWER_PUT_MASKED_VALUE(ipv4.ipv4_dst, TCA_FLOWER_KEY_IPV4_DST);
-@@ -2993,12 +3047,79 @@ nl_msg_put_flower_options(struct ofpbuf *request, struct tc_flower *flower)
+@@ -2993,12 +3050,79 @@ nl_msg_put_flower_options(struct ofpbuf *request, struct tc_flower *flower)
      return 0;
  }
  
@@ -3253,7 +5153,7 @@ index adb2d3182a..df73a43d4c 100644
          return false;
      }
  
-@@ -3011,8 +3132,8 @@ cmp_tc_flower_match_action(const struct tc_flower *a,
+@@ -3011,8 +3135,8 @@ cmp_tc_flower_match_action(const struct tc_flower *a,
          uint8_t key_b = ((uint8_t *)&b->key)[i] & mask;
  
          if (key_a != key_b) {
@@ -3264,7 +5164,7 @@ index adb2d3182a..df73a43d4c 100644
              return false;
          }
      }
-@@ -3022,14 +3143,15 @@ cmp_tc_flower_match_action(const struct tc_flower *a,
+@@ -3022,14 +3146,15 @@ cmp_tc_flower_match_action(const struct tc_flower *a,
      const struct tc_action *action_b = b->actions;
  
      if (a->action_count != b->action_count) {
@@ -3344,9 +5244,18 @@ index a147ca461d..d6cdddd169 100644
  int tc_del_filter(struct tcf_id *id);
  int tc_get_flower(struct tcf_id *id, struct tc_flower *flower);
 diff --git a/lib/tnl-ports.c b/lib/tnl-ports.c
-index 58269d3b16..f9fee37939 100644
+index 58269d3b16..050eafa6b8 100644
 --- a/lib/tnl-ports.c
 +++ b/lib/tnl-ports.c
+@@ -71,7 +71,7 @@ tnl_port_cast(const struct cls_rule *cr)
+ {
+     BUILD_ASSERT_DECL(offsetof(struct tnl_port_in, cr) == 0);
+ 
+-    return CONTAINER_OF(cr, struct tnl_port_in, cr);
++    return cr ? CONTAINER_OF(cr, struct tnl_port_in, cr) : NULL;
+ }
+ 
+ static void
 @@ -259,14 +259,14 @@ ipdev_map_delete(struct ip_device *ip_dev, ovs_be16 tp_port, uint8_t nw_proto)
  void
  tnl_port_map_delete(odp_port_t port, const char type[])
@@ -3615,19 +5524,144 @@ index ed58de17de..aad9f9c77a 100644
              free(nf_flow);
          }
 diff --git a/ofproto/ofproto-dpif-ipfix.c b/ofproto/ofproto-dpif-ipfix.c
-index 9280e008ea..fc927fe866 100644
+index 9280e008ea..742eed3998 100644
 --- a/ofproto/ofproto-dpif-ipfix.c
 +++ b/ofproto/ofproto-dpif-ipfix.c
-@@ -1078,7 +1078,7 @@ dpif_ipfix_set_options(
+@@ -926,17 +926,21 @@ dpif_ipfix_bridge_exporter_destroy(struct dpif_ipfix_bridge_exporter *exporter)
+ static void
+ dpif_ipfix_bridge_exporter_set_options(
+     struct dpif_ipfix_bridge_exporter *exporter,
+-    const struct ofproto_ipfix_bridge_exporter_options *options)
++    const struct ofproto_ipfix_bridge_exporter_options *options,
++    bool *options_changed)
+ {
+-    bool options_changed;
+-
+     if (!options || sset_is_empty(&options->targets)) {
+         /* No point in doing any work if there are no targets. */
+-        dpif_ipfix_bridge_exporter_clear(exporter);
++        if (exporter->options) {
++            dpif_ipfix_bridge_exporter_clear(exporter);
++            *options_changed = true;
++        } else {
++            *options_changed = false;
++        }
+         return;
+     }
+ 
+-    options_changed = (
++    *options_changed = (
+         !exporter->options
+         || !ofproto_ipfix_bridge_exporter_options_equal(
+             options, exporter->options));
+@@ -945,7 +949,7 @@ dpif_ipfix_bridge_exporter_set_options(
+      * shortchanged in collectors (which indicates that opening one or
+      * more of the configured collectors failed, so that we should
+      * retry). */
+-    if (options_changed
++    if (*options_changed
+         || collectors_count(exporter->exporter.collectors)
+             < sset_count(&options->targets)) {
+         if (!dpif_ipfix_exporter_set_options(
+@@ -957,7 +961,7 @@ dpif_ipfix_bridge_exporter_set_options(
+     }
+ 
+     /* Avoid reconfiguring if options didn't change. */
+-    if (!options_changed) {
++    if (!*options_changed) {
+         return;
+     }
+ 
+@@ -1015,17 +1019,21 @@ dpif_ipfix_flow_exporter_destroy(struct dpif_ipfix_flow_exporter *exporter)
+ static bool
+ dpif_ipfix_flow_exporter_set_options(
+     struct dpif_ipfix_flow_exporter *exporter,
+-    const struct ofproto_ipfix_flow_exporter_options *options)
++    const struct ofproto_ipfix_flow_exporter_options *options,
++    bool *options_changed)
+ {
+-    bool options_changed;
+-
+     if (sset_is_empty(&options->targets)) {
+         /* No point in doing any work if there are no targets. */
+-        dpif_ipfix_flow_exporter_clear(exporter);
++        if (exporter->options) {
++            dpif_ipfix_flow_exporter_clear(exporter);
++            *options_changed = true;
++        } else {
++            *options_changed = false;
++        }
+         return true;
+     }
+ 
+-    options_changed = (
++    *options_changed = (
+         !exporter->options
+         || !ofproto_ipfix_flow_exporter_options_equal(
+             options, exporter->options));
+@@ -1034,7 +1042,7 @@ dpif_ipfix_flow_exporter_set_options(
+      * shortchanged in collectors (which indicates that opening one or
+      * more of the configured collectors failed, so that we should
+      * retry). */
+-    if (options_changed
++    if (*options_changed
+         || collectors_count(exporter->exporter.collectors)
+             < sset_count(&options->targets)) {
+         if (!dpif_ipfix_exporter_set_options(
+@@ -1046,7 +1054,7 @@ dpif_ipfix_flow_exporter_set_options(
+     }
+ 
+     /* Avoid reconfiguring if options didn't change. */
+-    if (!options_changed) {
++    if (!*options_changed) {
+         return true;
+     }
+ 
+@@ -1069,7 +1077,7 @@ remove_flow_exporter(struct dpif_ipfix *di,
+     free(node);
+ }
+ 
+-void
++bool
+ dpif_ipfix_set_options(
+     struct dpif_ipfix *di,
+     const struct ofproto_ipfix_bridge_exporter_options *bridge_exporter_options,
+@@ -1077,16 +1085,19 @@ dpif_ipfix_set_options(
+     size_t n_flow_exporters_options) OVS_EXCLUDED(mutex)
  {
      int i;
++    bool beo_changed, feo_changed, entry_changed;
      struct ofproto_ipfix_flow_exporter_options *options;
 -    struct dpif_ipfix_flow_exporter_map_node *node, *next;
 +    struct dpif_ipfix_flow_exporter_map_node *node;
  
      ovs_mutex_lock(&mutex);
      dpif_ipfix_bridge_exporter_set_options(&di->bridge_exporter,
-@@ -1103,7 +1103,7 @@ dpif_ipfix_set_options(
+-                                           bridge_exporter_options);
++                                           bridge_exporter_options,
++                                           &beo_changed);
+ 
+     /* Add new flow exporters and update current flow exporters. */
+     options = (struct ofproto_ipfix_flow_exporter_options *)
+         flow_exporters_options;
++    feo_changed = false;
+     for (i = 0; i < n_flow_exporters_options; i++) {
+         node = dpif_ipfix_find_flow_exporter_map_node(
+             di, options->collector_set_id);
+@@ -1095,15 +1106,19 @@ dpif_ipfix_set_options(
+             dpif_ipfix_flow_exporter_init(&node->exporter);
+             hmap_insert(&di->flow_exporter_map, &node->node,
+                         hash_int(options->collector_set_id, 0));
++            feo_changed = true;
+         }
+-        if (!dpif_ipfix_flow_exporter_set_options(&node->exporter, options)) {
++        if (!dpif_ipfix_flow_exporter_set_options(&node->exporter,
++                                                  options,
++                                                  &entry_changed)) {
+             remove_flow_exporter(di, node);
+         }
++        feo_changed = entry_changed ? true : feo_changed;
+         options++;
      }
  
      /* Remove dropped flow exporters, if any needs to be removed. */
@@ -3636,7 +5670,20 @@ index 9280e008ea..fc927fe866 100644
          /* This is slow but doesn't take any extra memory, and
           * this table is not supposed to contain many rows anyway. */
          options = (struct ofproto_ipfix_flow_exporter_options *)
-@@ -1215,7 +1215,7 @@ static void
+@@ -1117,10 +1132,12 @@ dpif_ipfix_set_options(
+         }
+         if (i == n_flow_exporters_options) {  /* Not found. */
+             remove_flow_exporter(di, node);
++            feo_changed = true;
+         }
+     }
+ 
+     ovs_mutex_unlock(&mutex);
++    return beo_changed || feo_changed;
+ }
+ 
+ struct dpif_ipfix *
+@@ -1215,7 +1232,7 @@ static void
  dpif_ipfix_clear(struct dpif_ipfix *di) OVS_REQUIRES(mutex)
  {
      struct dpif_ipfix_flow_exporter_map_node *exp_node;
@@ -3645,7 +5692,7 @@ index 9280e008ea..fc927fe866 100644
  
      dpif_ipfix_bridge_exporter_clear(&di->bridge_exporter);
  
-@@ -1224,7 +1224,7 @@ dpif_ipfix_clear(struct dpif_ipfix *di) OVS_REQUIRES(mutex)
+@@ -1224,7 +1241,7 @@ dpif_ipfix_clear(struct dpif_ipfix *di) OVS_REQUIRES(mutex)
          free(exp_node);
      }
  
@@ -3654,7 +5701,7 @@ index 9280e008ea..fc927fe866 100644
          dpif_ipfix_del_port__(di, dip);
      }
  }
-@@ -2799,7 +2799,7 @@ dpif_ipfix_cache_expire(struct dpif_ipfix_exporter *exporter,
+@@ -2799,7 +2816,7 @@ dpif_ipfix_cache_expire(struct dpif_ipfix_exporter *exporter,
                          bool forced_end, const uint64_t export_time_usec,
                          const uint32_t export_time_sec)
  {
@@ -3663,7 +5710,7 @@ index 9280e008ea..fc927fe866 100644
      uint64_t max_flow_start_timestamp_usec;
      bool template_msg_sent = false;
      enum ipfix_flow_end_reason flow_end_reason;
-@@ -2811,7 +2811,7 @@ dpif_ipfix_cache_expire(struct dpif_ipfix_exporter *exporter,
+@@ -2811,7 +2828,7 @@ dpif_ipfix_cache_expire(struct dpif_ipfix_exporter *exporter,
      max_flow_start_timestamp_usec = export_time_usec -
          1000000LL * exporter->cache_active_timeout;
  
@@ -3672,6 +5719,19 @@ index 9280e008ea..fc927fe866 100644
                          &exporter->cache_flow_start_timestamp_list) {
          if (forced_end) {
              flow_end_reason = FORCED_END;
+diff --git a/ofproto/ofproto-dpif-ipfix.h b/ofproto/ofproto-dpif-ipfix.h
+index 1f42cd5275..75c0ab81ac 100644
+--- a/ofproto/ofproto-dpif-ipfix.h
++++ b/ofproto/ofproto-dpif-ipfix.h
+@@ -48,7 +48,7 @@ bool dpif_ipfix_get_bridge_exporter_output_sampling(const struct dpif_ipfix *);
+ bool dpif_ipfix_get_flow_exporter_tunnel_sampling(const struct dpif_ipfix *,
+                                                   const uint32_t);
+ bool dpif_ipfix_is_tunnel_port(const struct dpif_ipfix *, odp_port_t);
+-void dpif_ipfix_set_options(
++bool dpif_ipfix_set_options(
+     struct dpif_ipfix *,
+     const struct ofproto_ipfix_bridge_exporter_options *,
+     const struct ofproto_ipfix_flow_exporter_options *, size_t);
 diff --git a/ofproto/ofproto-dpif-sflow.c b/ofproto/ofproto-dpif-sflow.c
 index 30e7caf54a..e8e1de920b 100644
 --- a/ofproto/ofproto-dpif-sflow.c
@@ -3751,7 +5811,7 @@ index 114aff8ea3..0fc6d2ea60 100644
      enum xc_type type;
      union {
 diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
-index 578cbfe581..17f7e2883f 100644
+index 578cbfe581..7716c22f49 100644
 --- a/ofproto/ofproto-dpif-xlate.c
 +++ b/ofproto/ofproto-dpif-xlate.c
 @@ -865,7 +865,7 @@ xlate_xbridge_init(struct xlate_cfg *xcfg, struct xbridge *xbridge)
@@ -3835,7 +5895,25 @@ index 578cbfe581..17f7e2883f 100644
  static struct xbundle *
  xbundle_lookup(struct xlate_cfg *xcfg, const struct ofbundle *ofbundle)
  {
-@@ -3015,7 +3032,7 @@ xlate_normal(struct xlate_ctx *ctx)
+@@ -2125,9 +2142,14 @@ mirror_packet(struct xlate_ctx *ctx, struct xbundle *xbundle,
+         int snaplen;
+ 
+         /* Get the details of the mirror represented by the rightmost 1-bit. */
+-        ovs_assert(mirror_get(xbridge->mbridge, raw_ctz(mirrors),
+-                              &vlans, &dup_mirrors,
+-                              &out, &snaplen, &out_vlan));
++        if (OVS_UNLIKELY(!mirror_get(xbridge->mbridge, raw_ctz(mirrors),
++                                     &vlans, &dup_mirrors,
++                                     &out, &snaplen, &out_vlan))) {
++            /* The mirror got reconfigured before we got to read it's
++             * configuration. */
++            mirrors = zero_rightmost_1bit(mirrors);
++            continue;
++        }
+ 
+ 
+         /* If this mirror selects on the basis of VLAN, and it does not select
+@@ -3015,7 +3037,7 @@ xlate_normal(struct xlate_ctx *ctx)
      bool is_grat_arp = is_gratuitous_arp(flow, wc);
      if (ctx->xin->allow_side_effects
          && flow->packet_type == htonl(PT_ETH)
@@ -3844,7 +5922,7 @@ index 578cbfe581..17f7e2883f 100644
      ) {
          update_learning_table(ctx, in_xbundle, flow->dl_src, vlan,
                                is_grat_arp);
-@@ -3024,12 +3041,14 @@ xlate_normal(struct xlate_ctx *ctx)
+@@ -3024,12 +3046,14 @@ xlate_normal(struct xlate_ctx *ctx)
          struct xc_entry *entry;
  
          /* Save just enough info to update mac learning table later. */
@@ -3865,7 +5943,15 @@ index 578cbfe581..17f7e2883f 100644
      }
  
      /* Determine output bundle. */
-@@ -3523,6 +3542,9 @@ propagate_tunnel_data_to_flow__(struct flow *dst_flow,
+@@ -3048,7 +3072,6 @@ xlate_normal(struct xlate_ctx *ctx)
+              */
+             ctx->xout->slow |= SLOW_ACTION;
+ 
+-            memset(&wc->masks.tp_src, 0xff, sizeof wc->masks.tp_src);
+             if (mcast_snooping_is_membership(flow->tp_src) ||
+                 mcast_snooping_is_query(flow->tp_src)) {
+                 if (ctx->xin->allow_side_effects && ctx->xin->packet) {
+@@ -3523,6 +3546,9 @@ propagate_tunnel_data_to_flow__(struct flow *dst_flow,
      dst_flow->dl_dst = dmac;
      dst_flow->dl_src = smac;
  
@@ -3875,7 +5961,37 @@ index 578cbfe581..17f7e2883f 100644
      dst_flow->packet_type = htonl(PT_ETH);
      dst_flow->nw_dst = src_flow->tunnel.ip_dst;
      dst_flow->nw_src = src_flow->tunnel.ip_src;
-@@ -4176,6 +4198,10 @@ compose_output_action__(struct xlate_ctx *ctx, ofp_port_t ofp_port,
+@@ -3654,14 +3680,27 @@ native_tunnel_output(struct xlate_ctx *ctx, const struct xport *xport,
+ 
+     err = tnl_neigh_lookup(out_dev->xbridge->name, &d_ip6, &dmac);
+     if (err) {
++        struct in6_addr nh_s_ip6 = in6addr_any;
++
+         xlate_report(ctx, OFT_DETAIL,
+                      "neighbor cache miss for %s on bridge %s, "
+                      "sending %s request",
+                      buf_dip6, out_dev->xbridge->name, d_ip ? "ARP" : "ND");
++
++        err = ovs_router_get_netdev_source_address(&d_ip6,
++                                                   out_dev->xbridge->name,
++                                                   &nh_s_ip6);
++        if (err) {
++            nh_s_ip6 = s_ip6;
++        }
++
+         if (d_ip) {
+-            tnl_send_arp_request(ctx, out_dev, smac, s_ip, d_ip);
++            ovs_be32 nh_s_ip;
++
++            nh_s_ip = in6_addr_get_mapped_ipv4(&nh_s_ip6);
++            tnl_send_arp_request(ctx, out_dev, smac, nh_s_ip, d_ip);
+         } else {
+-            tnl_send_nd_request(ctx, out_dev, smac, &s_ip6, &d_ip6);
++            tnl_send_nd_request(ctx, out_dev, smac, &nh_s_ip6, &d_ip6);
+         }
+         return err;
+     }
+@@ -4176,6 +4215,10 @@ compose_output_action__(struct xlate_ctx *ctx, ofp_port_t ofp_port,
          if (xport->pt_mode == NETDEV_PT_LEGACY_L3) {
              flow->packet_type = PACKET_TYPE_BE(OFPHTN_ETHERTYPE,
                                                 ntohs(flow->dl_type));
@@ -3886,6 +6002,29 @@ index 578cbfe581..17f7e2883f 100644
          }
      }
  
+@@ -5622,7 +5665,8 @@ xlate_sample_action(struct xlate_ctx *ctx,
+ 
+     /* Scale the probability from 16-bit to 32-bit while representing
+      * the same percentage. */
+-    uint32_t probability = (os->probability << 16) | os->probability;
++    uint32_t probability =
++        ((uint32_t) os->probability << 16) | os->probability;
+ 
+     /* If ofp_port in flow sample action is equel to ofp_port,
+      * this sample action is a input port action. */
+@@ -7784,6 +7828,12 @@ xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
+         goto exit;
+     }
+ 
++    if (!xin->frozen_state
++        && xin->flow.ct_state
++        && xin->flow.ct_state & CS_TRACKED) {
++        ctx.conntracked = true;
++    }
++
+     /* Tunnel metadata in udpif format must be normalized before translation. */
+     if (flow->tunnel.flags & FLOW_TNL_F_UDPIF) {
+         const struct tun_table *tun_tab = ofproto_get_tun_tab(
 diff --git a/ofproto/ofproto-dpif-xlate.h b/ofproto/ofproto-dpif-xlate.h
 index 851088d794..2ba90e999c 100644
 --- a/ofproto/ofproto-dpif-xlate.h
@@ -3899,7 +6038,7 @@ index 851088d794..2ba90e999c 100644
  void xlate_bundle_set(struct ofproto_dpif *, struct ofbundle *,
                        const char *name, enum port_vlan_mode,
 diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
-index 8143dd965f..6601f23464 100644
+index 8143dd965f..f9562dee87 100644
 --- a/ofproto/ofproto-dpif.c
 +++ b/ofproto/ofproto-dpif.c
 @@ -215,10 +215,6 @@ struct shash all_dpif_backers = SHASH_INITIALIZER(&all_dpif_backers);
@@ -3950,7 +6089,16 @@ index 8143dd965f..6601f23464 100644
  
      OFPROTO_FOR_EACH_TABLE (table, &ofproto->up) {
          CLS_FOR_EACH (rule, up.cr, &table->cls) {
-@@ -1945,7 +1936,7 @@ run(struct ofproto *ofproto_)
+@@ -1857,6 +1848,8 @@ destruct(struct ofproto *ofproto_, bool del)
+ 
+     seq_destroy(ofproto->ams_seq);
+ 
++    /* Wait for all the meter destroy work to finish. */
++    ovsrcu_barrier();
+     close_dpif_backer(ofproto->backer, del);
+ }
+ 
+@@ -1945,7 +1938,7 @@ run(struct ofproto *ofproto_)
  
      new_dump_seq = seq_read(udpif_dump_seq(ofproto->backer->udpif));
      if (ofproto->dump_seq != new_dump_seq) {
@@ -3959,7 +6107,7 @@ index 8143dd965f..6601f23464 100644
          long long now = time_msec();
  
          /* We know stats are relatively fresh, so now is a good time to do some
-@@ -1955,7 +1946,7 @@ run(struct ofproto *ofproto_)
+@@ -1955,7 +1948,7 @@ run(struct ofproto *ofproto_)
          /* Expire OpenFlow flows whose idle_timeout or hard_timeout
           * has passed. */
          ovs_mutex_lock(&ofproto_mutex);
@@ -3968,20 +6116,59 @@ index 8143dd965f..6601f23464 100644
                              &ofproto->up.expirable) {
              rule_expire(rule_dpif_cast(rule), now);
          }
-@@ -2371,6 +2362,12 @@ set_ipfix(
+@@ -2346,6 +2339,7 @@ set_ipfix(
+     struct dpif_ipfix *di = ofproto->ipfix;
+     bool has_options = bridge_exporter_options || flow_exporters_options;
+     bool new_di = false;
++    bool options_changed = false;
+ 
+     if (has_options && !di) {
+         di = ofproto->ipfix = dpif_ipfix_create();
+@@ -2355,7 +2349,7 @@ set_ipfix(
+     if (di) {
+         /* Call set_options in any case to cleanly flush the flow
+          * caches in the last exporters that are to be destroyed. */
+-        dpif_ipfix_set_options(
++        options_changed = dpif_ipfix_set_options(
+             di, bridge_exporter_options, flow_exporters_options,
+             n_flow_exporters_options);
+ 
+@@ -2371,6 +2365,10 @@ set_ipfix(
              dpif_ipfix_unref(di);
              ofproto->ipfix = NULL;
          }
 +
-+        /* TODO: need to consider ipfix option changes more than
-+         * enable/disable */
-+        if (new_di || !ofproto->ipfix) {
++        if (new_di || options_changed) {
 +            ofproto->backer->need_revalidate = REV_RECONFIGURE;
 +        }
      }
  
      return 0;
-@@ -3106,11 +3103,11 @@ bundle_flush_macs(struct ofbundle *bundle, bool all_ofprotos)
+@@ -2493,11 +2491,11 @@ set_lldp(struct ofport *ofport_,
+ {
+     struct ofport_dpif *ofport = ofport_dpif_cast(ofport_);
+     struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofport->up.ofproto);
++    bool old_enable = lldp_is_enabled(ofport->lldp);
+     int error = 0;
+ 
+-    if (cfg) {
++    if (cfg && !smap_is_empty(cfg)) {
+         if (!ofport->lldp) {
+-            ofproto->backer->need_revalidate = REV_RECONFIGURE;
+             ofport->lldp = lldp_create(ofport->up.netdev, ofport_->mtu, cfg);
+         }
+ 
+@@ -2509,6 +2507,9 @@ set_lldp(struct ofport *ofport_,
+     } else if (ofport->lldp) {
+         lldp_unref(ofport->lldp);
+         ofport->lldp = NULL;
++    }
++
++    if (lldp_is_enabled(ofport->lldp) != old_enable) {
+         ofproto->backer->need_revalidate = REV_RECONFIGURE;
+     }
+ 
+@@ -3106,11 +3107,11 @@ bundle_flush_macs(struct ofbundle *bundle, bool all_ofprotos)
  {
      struct ofproto_dpif *ofproto = bundle->ofproto;
      struct mac_learning *ml = ofproto->ml;
@@ -3995,7 +6182,7 @@ index 8143dd965f..6601f23464 100644
          if (mac_entry_get_port(ml, mac) == bundle) {
              if (all_ofprotos) {
                  struct ofproto_dpif *o;
-@@ -3141,13 +3138,13 @@ bundle_move(struct ofbundle *old, struct ofbundle *new)
+@@ -3141,13 +3142,13 @@ bundle_move(struct ofbundle *old, struct ofbundle *new)
  {
      struct ofproto_dpif *ofproto = old->ofproto;
      struct mac_learning *ml = ofproto->ml;
@@ -4011,7 +6198,7 @@ index 8143dd965f..6601f23464 100644
          if (mac_entry_get_port(ml, mac) == old) {
              mac_entry_set_port(ml, mac, new);
          }
-@@ -3244,7 +3241,7 @@ static void
+@@ -3244,7 +3245,7 @@ static void
  bundle_destroy(struct ofbundle *bundle)
  {
      struct ofproto_dpif *ofproto;
@@ -4020,7 +6207,7 @@ index 8143dd965f..6601f23464 100644
  
      if (!bundle) {
          return;
-@@ -3257,7 +3254,7 @@ bundle_destroy(struct ofbundle *bundle)
+@@ -3257,7 +3258,7 @@ bundle_destroy(struct ofbundle *bundle)
      xlate_bundle_remove(bundle);
      xlate_txn_commit();
  
@@ -4029,7 +6216,7 @@ index 8143dd965f..6601f23464 100644
          bundle_del_port(port);
      }
  
-@@ -3347,9 +3344,7 @@ bundle_set(struct ofproto *ofproto_, void *aux,
+@@ -3347,9 +3348,7 @@ bundle_set(struct ofproto *ofproto_, void *aux,
          }
      }
      if (!ok || ovs_list_size(&bundle->ports) != s->n_members) {
@@ -4040,7 +6227,7 @@ index 8143dd965f..6601f23464 100644
              for (i = 0; i < s->n_members; i++) {
                  if (s->members[i] == port->up.ofp_port) {
                      goto found;
-@@ -3963,6 +3958,10 @@ port_add(struct ofproto *ofproto_, struct netdev *netdev)
+@@ -3963,6 +3962,10 @@ port_add(struct ofproto *ofproto_, struct netdev *netdev)
              simap_put(&ofproto->backer->tnl_backers,
                        dp_port_name, odp_to_u32(port_no));
          }
@@ -4051,7 +6238,7 @@ index 8143dd965f..6601f23464 100644
      }
  
      if (netdev_get_tunnel_config(netdev)) {
-@@ -4471,12 +4470,14 @@ rule_dpif_lookup_from_table(struct ofproto_dpif *ofproto,
+@@ -4471,12 +4474,14 @@ rule_dpif_lookup_from_table(struct ofproto_dpif *ofproto,
                  atomic_add_relaxed(&tbl->n_matched, stats->n_packets, &orig);
              }
              if (xcache) {
@@ -4071,7 +6258,7 @@ index 8143dd965f..6601f23464 100644
              }
              return rule;
          }
-@@ -4507,12 +4508,14 @@ rule_dpif_lookup_from_table(struct ofproto_dpif *ofproto,
+@@ -4507,12 +4512,14 @@ rule_dpif_lookup_from_table(struct ofproto_dpif *ofproto,
                                 stats->n_packets, &orig);
          }
          if (xcache) {
@@ -4091,7 +6278,7 @@ index 8143dd965f..6601f23464 100644
          }
          if (rule) {
              goto out;   /* Match. */
-@@ -5550,9 +5553,9 @@ ct_zone_timeout_policy_sweep(struct dpif_backer *backer)
+@@ -5550,9 +5557,9 @@ ct_zone_timeout_policy_sweep(struct dpif_backer *backer)
  {
      if (!ovs_list_is_empty(&backer->ct_tp_kill_list)
          && time_msec() >= timeout_policy_cleanup_timer) {
@@ -4103,7 +6290,31 @@ index 8143dd965f..6601f23464 100644
              if (!ct_dpif_del_timeout_policy(backer->dpif, ct_tp->tp_id)) {
                  ovs_list_remove(&ct_tp->list_node);
                  ct_timeout_policy_destroy(ct_tp, backer->tp_ids);
-@@ -5818,15 +5821,7 @@ ofproto_dpif_lookup_by_name(const char *name)
+@@ -5594,6 +5601,7 @@ ct_set_zone_timeout_policy(const char *datapath_type, uint16_t zone_id,
+             ct_timeout_policy_unref(backer, ct_zone->ct_tp);
+             ct_zone->ct_tp = ct_tp;
+             ct_tp->ref_count++;
++            backer->need_revalidate = REV_RECONFIGURE;
+         }
+     } else {
+         struct ct_zone *new_ct_zone = ct_zone_alloc(zone_id);
+@@ -5601,6 +5609,7 @@ ct_set_zone_timeout_policy(const char *datapath_type, uint16_t zone_id,
+         cmap_insert(&backer->ct_zones, &new_ct_zone->node,
+                     hash_int(zone_id, 0));
+         ct_tp->ref_count++;
++        backer->need_revalidate = REV_RECONFIGURE;
+     }
+ }
+ 
+@@ -5617,6 +5626,7 @@ ct_del_zone_timeout_policy(const char *datapath_type, uint16_t zone_id)
+     if (ct_zone) {
+         ct_timeout_policy_unref(backer, ct_zone->ct_tp);
+         ct_zone_remove_and_destroy(backer, ct_zone);
++        backer->need_revalidate = REV_RECONFIGURE;
+     }
+ }
+ 
+@@ -5818,15 +5828,7 @@ ofproto_dpif_lookup_by_name(const char *name)
  struct ofproto_dpif *
  ofproto_dpif_lookup_by_uuid(const struct uuid *uuid)
  {
@@ -4134,7 +6345,7 @@ index 14b909973d..47e96e62e1 100644
  
  void ofproto_init_tables(struct ofproto *, int n_tables);
 diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
-index 56aeac7209..2ed1078007 100644
+index 56aeac7209..933f7de2dc 100644
 --- a/ofproto/ofproto.c
 +++ b/ofproto/ofproto.c
 @@ -549,6 +549,7 @@ ofproto_create(const char *datapath_name, const char *datapath_type,
@@ -4322,6 +6533,15 @@ index 56aeac7209..2ed1078007 100644
          ofproto->ofproto_class->group_dealloc(*ofgroup);
      }
      return error;
+@@ -8902,7 +8963,7 @@ eviction_group_hash_rule(struct rule *rule)
+     hash = table->eviction_group_id_basis;
+     miniflow_expand(rule->cr.match.flow, &flow);
+     for (sf = table->eviction_fields;
+-         sf < &table->eviction_fields[table->n_eviction_fields];
++         sf && sf < &table->eviction_fields[table->n_eviction_fields];
+          sf++)
+     {
+         if (mf_are_prereqs_ok(sf->field, &flow, NULL)) {
 @@ -9138,8 +9199,8 @@ oftable_configure_eviction(struct oftable *table, unsigned int eviction,
  
      /* Destroy existing eviction groups, then destroy and recreate data
@@ -4348,6 +6568,48 @@ index b0262da2df..4e15167ab7 100644
  #ifdef  __cplusplus
  }
  #endif
+diff --git a/ovsdb/.gitignore b/ovsdb/.gitignore
+index fbcefafc6e..a4f9d38f11 100644
+--- a/ovsdb/.gitignore
++++ b/ovsdb/.gitignore
+@@ -1,5 +1,7 @@
+ /_server.ovsschema.inc
+ /_server.ovsschema.stamp
++/local-config.ovsschema.stamp
++/ovsdb.local-config.5
+ /ovsdb-client
+ /ovsdb-client.1
+ /ovsdb-doc
+diff --git a/ovsdb/automake.mk b/ovsdb/automake.mk
+index 62cc02686f..3b3140102b 100644
+--- a/ovsdb/automake.mk
++++ b/ovsdb/automake.mk
+@@ -148,4 +148,25 @@ ovsdb/ovsdb-server.5: \
+ 		$(srcdir)/ovsdb/_server.xml > $@.tmp && \
+ 	mv $@.tmp $@
+ 
++EXTRA_DIST += ovsdb/local-config.ovsschema
++pkgdata_DATA += ovsdb/local-config.ovsschema
++
++# Version checking for local-config.ovsschema.
++ALL_LOCAL += ovsdb/local-config.ovsschema.stamp
++ovsdb/local-config.ovsschema.stamp: ovsdb/local-config.ovsschema
++	$(srcdir)/build-aux/cksum-schema-check $? $@
++CLEANFILES += ovsdb/local-config.ovsschema.stamp
++
++# Local_Config schema documentation
++EXTRA_DIST += ovsdb/local-config.xml
++CLEANFILES += ovsdb/ovsdb.local-config.5
++man_MANS += ovsdb/ovsdb.local-config.5
++ovsdb/ovsdb.local-config.5: \
++	ovsdb/ovsdb-doc ovsdb/ ovsdb/local-config.xml ovsdb/local-config.ovsschema
++	$(AM_V_GEN)$(OVSDB_DOC) \
++		--version=$(VERSION) \
++		$(srcdir)/ovsdb/local-config.ovsschema \
++		$(srcdir)/ovsdb/local-config.xml > $@.tmp && \
++	mv $@.tmp $@
++
+ EXTRA_DIST += ovsdb/TODO.rst
 diff --git a/ovsdb/condition.c b/ovsdb/condition.c
 index 388dd54a16..9aa3788dbb 100644
 --- a/ovsdb/condition.c
@@ -4370,6 +6632,18 @@ index 388dd54a16..9aa3788dbb 100644
               hmap_remove(&o_column->o_clauses, &c->hmap_node);
               free(c);
           }
+diff --git a/ovsdb/file.c b/ovsdb/file.c
+index 9f44007d97..ca80c28235 100644
+--- a/ovsdb/file.c
++++ b/ovsdb/file.c
+@@ -524,6 +524,7 @@ ovsdb_file_read__(const char *filename, bool rw,
+ 
+         error = ovsdb_txn_replay_commit(txn);
+         if (error) {
++            ovsdb_error_destroy(error);
+             ovsdb_storage_unread(storage);
+             break;
+         }
 diff --git a/ovsdb/jsonrpc-server.c b/ovsdb/jsonrpc-server.c
 index 351c39d8aa..916a1f414e 100644
 --- a/ovsdb/jsonrpc-server.c
@@ -4491,6 +6765,357 @@ index 351c39d8aa..916a1f414e 100644
          ovsdb_jsonrpc_monitor_destroy(m, false);
      }
  }
+diff --git a/ovsdb/local-config.ovsschema b/ovsdb/local-config.ovsschema
+new file mode 100644
+index 0000000000..bd86d0f4f6
+--- /dev/null
++++ b/ovsdb/local-config.ovsschema
+@@ -0,0 +1,43 @@
++{
++    "name": "Local_Config",
++    "version": "1.0.0",
++    "cksum": "2048726482 1858",
++    "tables": {
++        "Config": {
++            "columns": {
++                "connections": {
++                    "type": {"key": {"type": "uuid",
++                                     "refTable": "Connection"},
++                                     "min": 0,
++                                     "max": "unlimited"}}},
++            "maxRows": 1,
++            "isRoot": true},
++        "Connection": {
++            "columns": {
++                "target": {"type": "string"},
++                "max_backoff": {"type": {"key": {"type": "integer",
++                                         "minInteger": 1000},
++                                         "min": 0,
++                                         "max": 1}},
++                "inactivity_probe": {"type": {"key": "integer",
++                                              "min": 0,
++                                              "max": 1}},
++                "read_only": {"type": "boolean"},
++                "role": {"type": "string"},
++                "other_config": {"type": {"key": "string",
++                                          "value": "string",
++                                          "min": 0,
++                                          "max": "unlimited"}},
++                "external_ids": {"type": {"key": "string",
++                                 "value": "string",
++                                 "min": 0,
++                                 "max": "unlimited"}},
++                "is_connected": {"type": "boolean", "ephemeral": true},
++                "status": {"type": {"key": "string",
++                                    "value": "string",
++                                    "min": 0,
++                                    "max": "unlimited"},
++                                    "ephemeral": true}},
++            "indexes": [["target"]]}
++    }
++}
+diff --git a/ovsdb/local-config.xml b/ovsdb/local-config.xml
+new file mode 100644
+index 0000000000..b502aea4cf
+--- /dev/null
++++ b/ovsdb/local-config.xml
+@@ -0,0 +1,296 @@
++<?xml version="1.0" encoding="utf-8"?>
++<database name="Local_Config" title="OVS Local Configuration Database">
++  <p>
++    This database is for local configuration of an ovsdb-server. The
++    database is meant to be unique, even among multiple clustered db
++    servers, so that configuration that is local to that server can
++    be configured separately (e.g. Connection information).
++  </p>
++
++  <table name="Config" title="ovsdb-server configuration">
++    <p>
++      The root local configuration table for an ovsdb-server. This table
++      must have exactly one row.
++    </p>
++    <group title="Connection Options">
++      <column name="connections">
++        Database clients to which the Open vSwitch database server should
++        connect or on which it should listen, along with options for how these
++        connections should be configured.  See the <ref table="Connection"/>
++        table for more information.
++      </column>
++    </group>
++  </table>
++
++  <table name="Connection" title="OVSDB client connections.">
++    <p>
++      Configuration for a database connection to an Open vSwitch database
++      (OVSDB) client.
++    </p>
++
++    <p>
++      This table primarily configures the Open vSwitch database server
++      (<code>ovsdb-server</code>).
++    </p>
++
++    <p>
++      The Open vSwitch database server can initiate and maintain active
++      connections to remote clients.  It can also listen for database
++      connections.
++    </p>
++
++    <group title="Core Features">
++      <column name="target">
++        <p>Connection methods for clients.</p>
++        <p>
++          The following connection methods are currently supported:
++        </p>
++        <dl>
++          <dt>
++            <code>ssl:<var>host</var></code>[<code>:<var>port</var></code>]
++          </dt>
++          <dd>
++            <p>
++              The specified SSL <var>port</var> on the host at the given
++              <var>host</var>, which can either be a DNS name (if built with
++              unbound library) or an IP address. A valid SSL configuration must
++              be provided when this form is used, this configuration can be
++              specified via command-line options or the <ref table="SSL"/>
++              table.
++            </p>
++            <p>
++              If <var>port</var> is not specified, it defaults to 6640.
++            </p>
++            <p>
++              SSL support is an optional feature that is not always
++              built as part of Open vSwitch.
++            </p>
++          </dd>
++
++          <dt>
++            <code>tcp:<var>host</var></code>[<code>:<var>port</var></code>]
++          </dt>
++          <dd>
++            <p>
++              The specified TCP <var>port</var> on the host at the given
++              <var>host</var>, which can either be a DNS name (if built with
++              unbound library) or an IP address.  If <var>host</var> is an IPv6
++              address, wrap it in square brackets, e.g.
++              <code>tcp:[::1]:6640</code>.
++            </p>
++            <p>
++              If <var>port</var> is not specified, it defaults to 6640.
++            </p>
++          </dd>
++          <dt>
++            <code>pssl:</code>[<var>port</var>][<code>:<var>host</var></code>]
++          </dt>
++          <dd>
++            <p>
++              Listens for SSL connections on the specified TCP <var>port</var>.
++              Specify 0 for <var>port</var> to have the kernel automatically
++              choose an available port.  If <var>host</var>, which can either
++              be a DNS name (if built with unbound library) or an IP address,
++              is specified, then connections are restricted to the resolved or
++              specified local IPaddress (either IPv4 or IPv6 address).  If
++              <var>host</var> is an IPv6 address, wrap in square brackets,
++              e.g. <code>pssl:6640:[::1]</code>.  If <var>host</var> is not
++              specified then it listens only on IPv4 (but not IPv6) addresses.
++              A valid SSL configuration must be provided when this form is
++              used, this can be specified either via command-line options or
++              the <ref table="SSL"/> table.
++            </p>
++            <p>
++              If <var>port</var> is not specified, it defaults to 6640.
++            </p>
++            <p>
++              SSL support is an optional feature that is not always built as
++              part of Open vSwitch.
++            </p>
++          </dd>
++          <dt>
++            <code>ptcp:</code>[<var>port</var>][<code>:<var>host</var></code>]
++          </dt>
++          <dd>
++            <p>
++              Listens for connections on the specified TCP <var>port</var>.
++              Specify 0 for <var>port</var> to have the kernel automatically
++              choose an available port.  If <var>host</var>, which can either
++              be a DNS name (if built with unbound library) or an IP address,
++              is specified, then connections are restricted to the resolved or
++              specified local IP address (either IPv4 or IPv6 address).  If
++              <var>host</var> is an IPv6 address, wrap it in square brackets,
++              e.g. <code>ptcp:6640:[::1]</code>.  If <var>host</var> is not
++              specified then it listens only on IPv4 addresses.
++            </p>
++            <p>
++              If <var>port</var> is not specified, it defaults to 6640.
++            </p>
++          </dd>
++        </dl>
++        <p>When multiple clients are configured, the <ref column="target"/>
++        values must be unique.  Duplicate <ref column="target"/> values yield
++        unspecified results.</p>
++      </column>
++
++      <column name="read_only">
++        <code>true</code> to restrict these connections to read-only
++        transactions, <code>false</code> to allow them to modify the database.
++      </column>
++
++      <column name="role">
++        String containing role name for this connection entry.
++      </column>
++    </group>
++
++    <group title="Client Failure Detection and Handling">
++      <column name="max_backoff">
++        Maximum number of milliseconds to wait between connection attempts.
++        Default is implementation-specific.
++      </column>
++
++      <column name="inactivity_probe">
++        Maximum number of milliseconds of idle time on connection to the client
++        before sending an inactivity probe message.  If Open vSwitch does not
++        communicate with the client for the specified number of seconds, it
++        will send a probe.  If a response is not received for the same
++        additional amount of time, Open vSwitch assumes the connection has been
++        broken and attempts to reconnect.  Default is implementation-specific.
++        A value of 0 disables inactivity probes.
++      </column>
++    </group>
++
++    <group title="Status">
++      <p>
++        Key-value pair of <ref column="is_connected"/> is always updated.
++        Other key-value pairs in the status columns may be updated depends
++        on the <ref column="target"/> type.
++      </p>
++
++      <p>
++        When <ref column="target"/> specifies a connection method that
++        listens for inbound connections (e.g. <code>ptcp:</code> or
++        <code>punix:</code>), both <ref column="n_connections"/> and
++        <ref column="is_connected"/> may also be updated while the
++        remaining key-value pairs are omitted.
++      </p>
++
++      <p>
++        On the other hand, when <ref column="target"/> specifies an
++        outbound connection, all key-value pairs may be updated, except
++        the above-mentioned two key-value pairs associated with inbound
++        connection targets. They are omitted.
++      </p>
++
++    <column name="is_connected">
++        <code>true</code> if currently connected to this client,
++        <code>false</code> otherwise.
++      </column>
++
++      <column name="status" key="last_error">
++        A human-readable description of the last error on the connection
++        to the manager; i.e. <code>strerror(errno)</code>.  This key
++        will exist only if an error has occurred.
++      </column>
++
++      <column name="status" key="state"
++              type='{"type": "string", "enum": ["set", ["VOID", "BACKOFF",
++                   "CONNECTING", "ACTIVE", "IDLE"]]}'>
++        <p>
++          The state of the connection to the manager:
++        </p>
++        <dl>
++          <dt><code>VOID</code></dt>
++          <dd>Connection is disabled.</dd>
++
++          <dt><code>BACKOFF</code></dt>
++          <dd>Attempting to reconnect at an increasing period.</dd>
++
++          <dt><code>CONNECTING</code></dt>
++          <dd>Attempting to connect.</dd>
++
++          <dt><code>ACTIVE</code></dt>
++          <dd>Connected, remote host responsive.</dd>
++
++          <dt><code>IDLE</code></dt>
++          <dd>Connection is idle.  Waiting for response to keep-alive.</dd>
++        </dl>
++        <p>
++          These values may change in the future.  They are provided only for
++          human consumption.
++        </p>
++      </column>
++
++      <column name="status" key="sec_since_connect"
++              type='{"type": "integer", "minInteger": 0}'>
++        The amount of time since this client last successfully connected
++        to the database (in seconds). Value is empty if client has never
++        successfully been connected.
++      </column>
++
++      <column name="status" key="sec_since_disconnect"
++              type='{"type": "integer", "minInteger": 0}'>
++        The amount of time since this client last disconnected from the
++        database (in seconds). Value is empty if client has never
++        disconnected.
++      </column>
++
++      <column name="status" key="locks_held">
++        Space-separated list of the names of OVSDB locks that the connection
++        holds.  Omitted if the connection does not hold any locks.
++      </column>
++
++      <column name="status" key="locks_waiting">
++        Space-separated list of the names of OVSDB locks that the connection is
++        currently waiting to acquire.  Omitted if the connection is not waiting
++        for any locks.
++      </column>
++
++      <column name="status" key="locks_lost">
++        Space-separated list of the names of OVSDB locks that the connection
++        has had stolen by another OVSDB client.  Omitted if no locks have been
++        stolen from this connection.
++      </column>
++
++      <column name="status" key="n_connections"
++              type='{"type": "integer", "minInteger": 2}'>
++        When <ref column="target"/> specifies a connection method that
++        listens for inbound connections (e.g. <code>ptcp:</code> or
++        <code>pssl:</code>) and more than one connection is actually active,
++        the value is the number of active connections.  Otherwise, this
++        key-value pair is omitted.
++      </column>
++
++      <column name="status" key="bound_port" type='{"type": "integer"}'>
++        When <ref column="target"/> is <code>ptcp:</code> or
++        <code>pssl:</code>, this is the TCP port on which the OVSDB server is
++        listening.  (This is particularly useful when <ref
++        column="target"/> specifies a port of 0, allowing the kernel to
++        choose any available port.)
++      </column>
++    </group>
++
++    <group title="Connection Parameters">
++      <column name="other_config" key="dscp"
++              type='{"type": "integer"}'>
++        The Differentiated Service Code Point (DSCP) is specified using 6 bits
++        in the Type of Service (TOS) field in the IP header. DSCP provides a
++        mechanism to classify the network traffic and provide Quality of
++        Service (QoS) on IP networks.
++
++        The DSCP value specified here is used when establishing the connection
++        between the manager and the Open vSwitch.  If no value is specified, a
++        default value of 48 is chosen.  Valid DSCP values must be in the range
++        0 to 63.
++      </column>
++    </group>
++
++    <group title="Configuration">
++      External configuration options
++
++      <column name="external_ids">
++        External client-defined key-value pairs
++      </column>
++    </group>
++  </table>
++</database>
 diff --git a/ovsdb/monitor.c b/ovsdb/monitor.c
 index 0f222cc992..952fa902e4 100644
 --- a/ovsdb/monitor.c
@@ -4785,10 +7410,10 @@ index 30760233ee..e685c8103b 100644
          raft_server_destroy(s);
      }
 diff --git a/ovsdb/raft.c b/ovsdb/raft.c
-index 1a3447a8dd..530c5e5a3d 100644
+index 1a3447a8dd..856d083f21 100644
 --- a/ovsdb/raft.c
 +++ b/ovsdb/raft.c
-@@ -74,6 +74,7 @@ enum raft_failure_test {
+@@ -74,9 +74,12 @@ enum raft_failure_test {
      FT_CRASH_BEFORE_SEND_EXEC_REQ,
      FT_CRASH_AFTER_SEND_EXEC_REQ,
      FT_CRASH_AFTER_RECV_APPEND_REQ_UPDATE,
@@ -4796,7 +7421,12 @@ index 1a3447a8dd..530c5e5a3d 100644
      FT_DELAY_ELECTION,
      FT_DONT_SEND_VOTE_REQUEST,
      FT_STOP_RAFT_RPC,
-@@ -379,12 +380,19 @@ static bool raft_handle_write_error(struct raft *, struct ovsdb_error *);
++    FT_TRANSFER_LEADERSHIP,
++    FT_TRANSFER_LEADERSHIP_AFTER_SEND_APPEND_REQ,
+ };
+ static enum raft_failure_test failure_test;
+ 
+@@ -379,12 +382,19 @@ static bool raft_handle_write_error(struct raft *, struct ovsdb_error *);
  static void raft_run_reconfigure(struct raft *);
  
  static void raft_set_leader(struct raft *, const struct uuid *sid);
@@ -4816,7 +7446,7 @@ index 1a3447a8dd..530c5e5a3d 100644
  static char *
  raft_make_address_passive(const char *address_)
  {
-@@ -692,8 +700,8 @@ static void
+@@ -692,8 +702,8 @@ static void
  raft_set_servers(struct raft *raft, const struct hmap *new_servers,
                   enum vlog_level level)
  {
@@ -4827,7 +7457,7 @@ index 1a3447a8dd..530c5e5a3d 100644
          if (!raft_server_find(new_servers, &s->sid)) {
              ovs_assert(s != raft->remove_server);
  
-@@ -703,7 +711,7 @@ raft_set_servers(struct raft *raft, const struct hmap *new_servers,
+@@ -703,7 +713,7 @@ raft_set_servers(struct raft *raft, const struct hmap *new_servers,
          }
      }
  
@@ -4836,7 +7466,7 @@ index 1a3447a8dd..530c5e5a3d 100644
          if (!raft_find_server(raft, &s->sid)) {
              VLOG(level, "server %s added to configuration", s->nickname);
  
-@@ -1376,8 +1384,8 @@ raft_close__(struct raft *raft)
+@@ -1376,8 +1386,8 @@ raft_close__(struct raft *raft)
          raft->remove_server = NULL;
      }
  
@@ -4847,7 +7477,7 @@ index 1a3447a8dd..530c5e5a3d 100644
          raft_conn_close(conn);
      }
  }
-@@ -1713,8 +1721,8 @@ raft_waiters_run(struct raft *raft)
+@@ -1713,8 +1723,8 @@ raft_waiters_run(struct raft *raft)
      }
  
      uint64_t cur = ovsdb_log_commit_progress(raft->log);
@@ -4858,7 +7488,7 @@ index 1a3447a8dd..530c5e5a3d 100644
          if (cur < w->commit_ticket) {
              break;
          }
-@@ -1736,8 +1744,8 @@ raft_waiters_wait(struct raft *raft)
+@@ -1736,8 +1746,8 @@ raft_waiters_wait(struct raft *raft)
  static void
  raft_waiters_destroy(struct raft *raft)
  {
@@ -4869,7 +7499,7 @@ index 1a3447a8dd..530c5e5a3d 100644
          raft_waiter_destroy(w);
      }
  }
-@@ -1867,6 +1875,8 @@ raft_open_conn(struct raft *raft, const char *address, const struct uuid *sid)
+@@ -1867,6 +1877,8 @@ raft_open_conn(struct raft *raft, const char *address, const struct uuid *sid)
  static void
  raft_conn_close(struct raft_conn *conn)
  {
@@ -4878,7 +7508,21 @@ index 1a3447a8dd..530c5e5a3d 100644
      jsonrpc_session_close(conn->js);
      ovs_list_remove(&conn->list_node);
      free(conn->nickname);
-@@ -1957,16 +1967,29 @@ raft_run(struct raft *raft)
+@@ -1921,6 +1933,13 @@ raft_run(struct raft *raft)
+         return;
+     }
+ 
++    if (failure_test == FT_TRANSFER_LEADERSHIP) {
++        /* Using this function as it conveniently implements all we need and
++         * snapshotting is the main test scenario for leadership transfer. */
++        raft_notify_snapshot_recommended(raft);
++        failure_test = FT_NO_TEST;
++    }
++
+     raft_waiters_run(raft);
+ 
+     if (!raft->listener && time_msec() >= raft->listen_backoff) {
+@@ -1957,16 +1976,29 @@ raft_run(struct raft *raft)
      }
  
      /* Close unneeded sessions. */
@@ -4911,7 +7555,7 @@ index 1a3447a8dd..530c5e5a3d 100644
      HMAP_FOR_EACH (server, hmap_node, &raft->servers) {
          raft_open_conn(raft, server->address, &server->sid);
      }
-@@ -2039,8 +2062,8 @@ raft_run(struct raft *raft)
+@@ -2039,11 +2071,18 @@ raft_run(struct raft *raft)
           * commands becomes new leader: the pending commands can still complete
           * if the crashed leader has replicated the transactions to majority of
           * followers before it crashed. */
@@ -4921,19 +7565,44 @@ index 1a3447a8dd..530c5e5a3d 100644
 +        HMAP_FOR_EACH_SAFE (cmd, hmap_node, &raft->commands) {
              if (cmd->timestamp
                  && now - cmd->timestamp > raft->election_timer * 2) {
-                 raft_command_complete(raft, cmd, RAFT_CMD_TIMEOUT);
-@@ -2243,8 +2266,8 @@ raft_command_initiate(struct raft *raft,
+-                raft_command_complete(raft, cmd, RAFT_CMD_TIMEOUT);
++                if (cmd->index && raft->role != RAFT_LEADER) {
++                    /* This server lost leadership and command didn't complete
++                     * in time.  Likely, it wasn't replicated to the majority
++                     * of servers before losing the leadership. */
++                    raft_command_complete(raft, cmd, RAFT_CMD_LOST_LEADERSHIP);
++                } else {
++                    raft_command_complete(raft, cmd, RAFT_CMD_TIMEOUT);
++                }
+             }
+         }
+         raft_reset_ping_timer(raft);
+@@ -2235,6 +2274,9 @@ raft_command_initiate(struct raft *raft,
+     if (failure_test == FT_CRASH_AFTER_SEND_APPEND_REQ) {
+         ovs_fatal(0, "Raft test: crash after sending append_request.");
+     }
++    if (failure_test == FT_TRANSFER_LEADERSHIP_AFTER_SEND_APPEND_REQ) {
++        failure_test = FT_TRANSFER_LEADERSHIP;
++    }
+     raft_reset_ping_timer(raft);
+ 
+     return cmd;
+@@ -2243,8 +2285,12 @@ raft_command_initiate(struct raft *raft,
  static void
  log_all_commands(struct raft *raft)
  {
 -    struct raft_command *cmd, *next;
 -    HMAP_FOR_EACH_SAFE (cmd, next, hmap_node, &raft->commands) {
++    if (!VLOG_IS_DBG_ENABLED()) {
++        return;
++    }
++
 +    struct raft_command *cmd;
-+    HMAP_FOR_EACH_SAFE (cmd, hmap_node, &raft->commands) {
++    HMAP_FOR_EACH (cmd, hmap_node, &raft->commands) {
          VLOG_DBG("raft command eid: "UUID_FMT, UUID_ARGS(&cmd->eid));
      }
  }
-@@ -2398,8 +2421,8 @@ raft_command_complete(struct raft *raft,
+@@ -2398,8 +2444,8 @@ raft_command_complete(struct raft *raft,
  static void
  raft_complete_all_commands(struct raft *raft, enum raft_command_status status)
  {
@@ -4944,7 +7613,139 @@ index 1a3447a8dd..530c5e5a3d 100644
          raft_command_complete(raft, cmd, status);
      }
  }
-@@ -3354,12 +3377,6 @@ raft_find_peer(struct raft *raft, const struct uuid *uuid)
+@@ -2601,7 +2647,13 @@ raft_become_follower(struct raft *raft)
+      * configuration is already part of the log.  Possibly the configuration
+      * log entry will not be committed, but until we know that we must use the
+      * new configuration.  Our AppendEntries processing will properly update
+-     * the server configuration later, if necessary. */
++     * the server configuration later, if necessary.
++     *
++     * Also we do not complete commands here, as they can still be completed
++     * if their log entries have already been replicated to other servers.
++     * If the entries were actually committed according to the new leader, our
++     * AppendEntries processing will complete the corresponding commands.
++     */
+     struct raft_server *s;
+     HMAP_FOR_EACH (s, hmap_node, &raft->add_servers) {
+         raft_send_add_server_reply__(raft, &s->sid, s->address, false,
+@@ -2615,8 +2667,6 @@ raft_become_follower(struct raft *raft)
+         raft_server_destroy(raft->remove_server);
+         raft->remove_server = NULL;
+     }
+-
+-    raft_complete_all_commands(raft, RAFT_CMD_LOST_LEADERSHIP);
+ }
+ 
+ static void
+@@ -2865,61 +2915,56 @@ raft_update_commit_index(struct raft *raft, uint64_t new_commit_index)
+         return false;
+     }
+ 
+-    if (raft->role == RAFT_LEADER) {
+-        while (raft->commit_index < new_commit_index) {
+-            uint64_t index = ++raft->commit_index;
+-            const struct raft_entry *e = raft_get_entry(raft, index);
+-            if (raft_entry_has_data(e)) {
+-                struct raft_command *cmd
+-                    = raft_find_command_by_eid(raft, &e->eid);
+-                if (cmd) {
+-                    if (!cmd->index) {
+-                        VLOG_DBG("Command completed after role change from"
+-                                 " follower to leader "UUID_FMT,
+-                                 UUID_ARGS(&e->eid));
+-                        cmd->index = index;
+-                    }
+-                    raft_command_complete(raft, cmd, RAFT_CMD_SUCCESS);
++    while (raft->commit_index < new_commit_index) {
++        uint64_t index = ++raft->commit_index;
++        const struct raft_entry *e = raft_get_entry(raft, index);
++
++        if (raft_entry_has_data(e)) {
++            struct raft_command *cmd = raft_find_command_by_eid(raft, &e->eid);
++            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5);
++
++            if (cmd) {
++                if (!cmd->index && raft->role == RAFT_LEADER) {
++                    VLOG_INFO_RL(&rl,
++                        "command completed after role change from "
++                        "follower to leader (eid: "UUID_FMT", "
++                        "commit index: %"PRIu64")", UUID_ARGS(&e->eid), index);
++                } else if (!cmd->index && raft->role != RAFT_LEADER) {
++                    /* This can happen when leader fail-over before sending
++                     * execute_command_reply. */
++                    VLOG_INFO_RL(&rl,
++                        "command completed without reply (eid: "UUID_FMT", "
++                        "commit index: %"PRIu64")", UUID_ARGS(&e->eid), index);
++                } else if (cmd->index && raft->role != RAFT_LEADER) {
++                    /* This can happen if current server lost leadership after
++                     * sending append requests to the majority of servers, but
++                     * before receiving majority of append replies. */
++                    VLOG_INFO_RL(&rl,
++                        "command completed after role change from "
++                        "leader to follower (eid: "UUID_FMT", "
++                        "commit index: %"PRIu64")", UUID_ARGS(&e->eid), index);
++                    /* Clearing 'sid' to avoid sending cmd execution reply. */
++                    cmd->sid = UUID_ZERO;
++                } else {
++                    /* (cmd->index && raft->role == RAFT_LEADER)
++                     * Normal command completion on a leader. */
+                 }
+-            }
+-            if (e->election_timer) {
+-                VLOG_INFO("Election timer changed from %"PRIu64" to %"PRIu64,
+-                          raft->election_timer, e->election_timer);
+-                raft->election_timer = e->election_timer;
+-                raft->election_timer_new = 0;
+-                raft_update_probe_intervals(raft);
+-            }
+-            if (e->servers) {
+-                /* raft_run_reconfigure() can write a new Raft entry, which can
+-                 * reallocate raft->entries, which would invalidate 'e', so
+-                 * this case must be last, after the one for 'e->data'. */
+-                raft_run_reconfigure(raft);
++                cmd->index = index;
++                raft_command_complete(raft, cmd, RAFT_CMD_SUCCESS);
+             }
+         }
+-    } else {
+-        while (raft->commit_index < new_commit_index) {
+-            uint64_t index = ++raft->commit_index;
+-            const struct raft_entry *e = raft_get_entry(raft, index);
+-            if (e->election_timer) {
+-                VLOG_INFO("Election timer changed from %"PRIu64" to %"PRIu64,
+-                          raft->election_timer, e->election_timer);
+-                raft->election_timer = e->election_timer;
+-                raft_update_probe_intervals(raft);
+-            }
++        if (e->election_timer) {
++            VLOG_INFO("Election timer changed from %"PRIu64" to %"PRIu64,
++                      raft->election_timer, e->election_timer);
++            raft->election_timer = e->election_timer;
++            raft->election_timer_new = 0;
++            raft_update_probe_intervals(raft);
+         }
+-        /* Check if any pending command can be completed, and complete it.
+-         * This can happen when leader fail-over before sending
+-         * execute_command_reply. */
+-        const struct uuid *eid = raft_get_eid(raft, new_commit_index);
+-        struct raft_command *cmd = raft_find_command_by_eid(raft, eid);
+-        if (cmd) {
+-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5);
+-            VLOG_INFO_RL(&rl,
+-                         "Command completed without reply (eid: "UUID_FMT", "
+-                         "commit index: %"PRIu64")",
+-                         UUID_ARGS(eid), new_commit_index);
+-            cmd->index = new_commit_index;
+-            raft_command_complete(raft, cmd, RAFT_CMD_SUCCESS);
++        if (e->servers && raft->role == RAFT_LEADER) {
++            /* raft_run_reconfigure() can write a new Raft entry, which can
++             * reallocate raft->entries, which would invalidate 'e', so
++             * this case must be last, after the one for 'e->data'. */
++            raft_run_reconfigure(raft);
+         }
+     }
+ 
+@@ -3354,12 +3399,6 @@ raft_find_peer(struct raft *raft, const struct uuid *uuid)
      return s && !uuid_equals(&raft->sid, &s->sid) ? s : NULL;
  }
  
@@ -4957,7 +7758,7 @@ index 1a3447a8dd..530c5e5a3d 100644
  /* Figure 3.1: "If there exists an N such that N > commitIndex, a
   * majority of matchIndex[i] >= N, and log[N].term == currentTerm, set
   * commitIndex = N (sections 3.5 and 3.6)." */
-@@ -4142,6 +4159,10 @@ static void
+@@ -4142,6 +4181,10 @@ static void
  raft_handle_install_snapshot_request(
      struct raft *raft, const struct raft_install_snapshot_request *rq)
  {
@@ -4968,7 +7769,7 @@ index 1a3447a8dd..530c5e5a3d 100644
      if (raft_handle_install_snapshot_request__(raft, rq)) {
          union raft_rpc rpy = {
              .install_snapshot_reply = {
-@@ -4940,6 +4961,8 @@ raft_unixctl_failure_test(struct unixctl_conn *conn OVS_UNUSED,
+@@ -4940,6 +4983,8 @@ raft_unixctl_failure_test(struct unixctl_conn *conn OVS_UNUSED,
          failure_test = FT_CRASH_AFTER_SEND_EXEC_REQ;
      } else if (!strcmp(test, "crash-after-receiving-append-request-update")) {
          failure_test = FT_CRASH_AFTER_RECV_APPEND_REQ_UPDATE;
@@ -4977,6 +7778,18 @@ index 1a3447a8dd..530c5e5a3d 100644
      } else if (!strcmp(test, "delay-election")) {
          failure_test = FT_DELAY_ELECTION;
          struct raft *raft;
+@@ -4952,6 +4997,11 @@ raft_unixctl_failure_test(struct unixctl_conn *conn OVS_UNUSED,
+         failure_test = FT_DONT_SEND_VOTE_REQUEST;
+     } else if (!strcmp(test, "stop-raft-rpc")) {
+         failure_test = FT_STOP_RAFT_RPC;
++    } else if (!strcmp(test,
++                       "transfer-leadership-after-sending-append-request")) {
++        failure_test = FT_TRANSFER_LEADERSHIP_AFTER_SEND_APPEND_REQ;
++    } else if (!strcmp(test, "transfer-leadership")) {
++        failure_test = FT_TRANSFER_LEADERSHIP;
+     } else if (!strcmp(test, "clear")) {
+         failure_test = FT_NO_TEST;
+         unixctl_command_reply(conn, "test dismissed");
 diff --git a/ovsdb/relay.c b/ovsdb/relay.c
 index ef0e44d340..122ee8c52f 100644
 --- a/ovsdb/relay.c
@@ -5549,8 +8362,48 @@ index c4c6c87e9f..6b0d023ae3 100644
          if deadline is not None:
              remaining = deadline - now
              return max(0, remaining)
+diff --git a/rhel/openvswitch-fedora.spec.in b/rhel/openvswitch-fedora.spec.in
+index 16ef1ac3ab..d0ae78e4ed 100644
+--- a/rhel/openvswitch-fedora.spec.in
++++ b/rhel/openvswitch-fedora.spec.in
+@@ -455,6 +455,7 @@ fi
+ %{_datadir}/openvswitch/scripts/ovs-ctl
+ %{_datadir}/openvswitch/scripts/ovs-kmod-ctl
+ %{_datadir}/openvswitch/scripts/ovs-systemd-reload
++%config %{_datadir}/openvswitch/local-config.ovsschema
+ %config %{_datadir}/openvswitch/vswitch.ovsschema
+ %config %{_datadir}/openvswitch/vtep.ovsschema
+ %{_bindir}/ovs-appctl
+@@ -476,6 +477,7 @@ fi
+ %{_mandir}/man1/ovsdb-server.1*
+ %{_mandir}/man1/ovsdb-tool.1*
+ %{_mandir}/man5/ovsdb-server.5*
++%{_mandir}/man5/ovsdb.local-config.5*
+ %{_mandir}/man5/ovs-vswitchd.conf.db.5*
+ %{_mandir}/man5/ovsdb.5*
+ %{_mandir}/man5/vtep.5*
+diff --git a/rhel/openvswitch.spec.in b/rhel/openvswitch.spec.in
+index 220e5c7472..2d8ff18bb0 100644
+--- a/rhel/openvswitch.spec.in
++++ b/rhel/openvswitch.spec.in
+@@ -229,6 +229,7 @@ exit 0
+ /usr/share/man/man1/ovsdb-client.1.gz
+ /usr/share/man/man1/ovsdb-server.1.gz
+ /usr/share/man/man1/ovsdb-tool.1.gz
++/usr/share/man/man5/ovsdb.local-config.5.gz
+ /usr/share/man/man5/ovsdb-server.5.gz
+ /usr/share/man/man5/ovs-vswitchd.conf.db.5.gz
+ %{_mandir}/man5/ovsdb.5*
+@@ -262,6 +263,7 @@ exit 0
+ /usr/share/openvswitch/scripts/ovs-vtep
+ /usr/share/openvswitch/scripts/sysconfig.template
+ /usr/share/openvswitch/scripts/ovs-monitor-ipsec
++/usr/share/openvswitch/local-config.ovsschema
+ /usr/share/openvswitch/vswitch.ovsschema
+ /usr/share/openvswitch/vtep.ovsschema
+ %doc NOTICE README.rst NEWS rhel/README.RHEL.rst
 diff --git a/tests/alb.at b/tests/alb.at
-index 2bef06f39c..0036bd1f29 100644
+index 2bef06f39c..922185d61d 100644
 --- a/tests/alb.at
 +++ b/tests/alb.at
 @@ -96,6 +96,52 @@ OVS_WAIT_UNTIL([tail -n +$LINENUM ovs-vswitchd.log | grep "PMD auto load balance
@@ -5606,35 +8459,658 @@ index 2bef06f39c..0036bd1f29 100644
  AT_SETUP([ALB - PMD/RxQ assignment type])
  OVS_VSWITCHD_START([add-port br0 p0 \
                      -- set Interface p0 type=dummy-pmd options:n_rxq=3 \
-diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
-index 7c2edeb9d4..ffb7208c7f 100644
---- a/tests/ofproto-dpif.at
-+++ b/tests/ofproto-dpif.at
-@@ -81,11 +81,12 @@ recirc_id(0),in_port(4),packet_type(ns=0,id=0),eth(src=50:54:00:00:00:0b,dst=ff:
- 
- ovs-appctl netdev-dummy/set-admin-state p1 up
- ovs-appctl time/warp 100
--OVS_WAIT_UNTIL([ovs-appctl bond/show | STRIP_RECIRC_ID | STRIP_ACTIVE_MEMBER_MAC], [0], [dnl
-+OVS_WAIT_UNTIL_EQUAL([ovs-appctl bond/show | STRIP_RECIRC_ID | STRIP_ACTIVE_MEMBER_MAC], [dnl
- ---- bond0 ----
- bond_mode: active-backup
- bond may use recirculation: no, <del>
- bond-hash-basis: 0
-+lb_output action: disabled, bond-id: -1
- updelay: 0 ms
- downdelay: 0 ms
- lacp_status: off
-@@ -99,7 +100,6 @@ member p1: enabled
+@@ -197,7 +243,25 @@ get_log_next_line_num
+ AT_CHECK([ovs-vsctl set open_vswitch . other_config:pmd-auto-lb-rebal-interval="0"])
+ CHECK_ALB_PARAM([interval], [1 mins], [+$LINENUM])
  
- member p2: enabled
-   may_enable: true
--
- ])
+-# No check for above max as it is only a documented max value and not a hard limit
++# Set new value
++get_log_next_line_num
++AT_CHECK([ovs-vsctl set open_vswitch . other_config:pmd-auto-lb-rebal-interval="100"])
++CHECK_ALB_PARAM([interval], [100 mins], [+$LINENUM])
++
++# Set above max value
++get_log_next_line_num
++AT_CHECK([ovs-vsctl set open_vswitch . other_config:pmd-auto-lb-rebal-interval="20001"])
++CHECK_ALB_PARAM([interval], [1 mins], [+$LINENUM])
++
++# Set new value
++get_log_next_line_num
++AT_CHECK([ovs-vsctl set open_vswitch . other_config:pmd-auto-lb-rebal-interval="1000"])
++CHECK_ALB_PARAM([interval], [1000 mins], [+$LINENUM])
++
++# Set Negative value
++get_log_next_line_num
++AT_CHECK([ovs-vsctl set open_vswitch . other_config:pmd-auto-lb-rebal-interval="-1"])
++CHECK_ALB_PARAM([interval], [1 mins], [+$LINENUM])
  
  OVS_VSWITCHD_STOP
-@@ -129,11 +129,12 @@ ovs-appctl time/warp 100
- OVS_WAIT_UNTIL([test -n "`ovs-appctl bond/show | fgrep 'member p1: disabled'`"])
- ovs-appctl netdev-dummy/set-admin-state p1 up
+ AT_CLEANUP
+diff --git a/tests/classifier.at b/tests/classifier.at
+index cdcd72c156..f652b59837 100644
+--- a/tests/classifier.at
++++ b/tests/classifier.at
+@@ -129,6 +129,31 @@ Datapath actions: 3
+ OVS_VSWITCHD_STOP(["/'prefixes' with incompatible field: ipv6_label/d"])
+ AT_CLEANUP
+ 
++AT_SETUP([flow classifier - ipv6 ND dependency])
++OVS_VSWITCHD_START
++add_of_ports br0 1 2
++AT_DATA([flows.txt], [dnl
++ table=0,priority=100,ipv6,ipv6_src=1000::/10 actions=resubmit(,1)
++ table=0,priority=0 actions=NORMAL
++ table=1,priority=110,ipv6,ipv6_dst=1000::3 actions=resubmit(,2)
++ table=1,priority=100,ipv6,ipv6_dst=1000::4 actions=resubmit(,2)
++ table=1,priority=0 actions=NORMAL
++ table=2,priority=120,icmp6,nw_ttl=255,icmp_type=135,icmp_code=0,nd_target=1000::1 actions=NORMAL
++ table=2,priority=100,tcp actions=NORMAL
++ table=2,priority=100,icmp6 actions=NORMAL
++ table=2,priority=0 actions=NORMAL
++])
++AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
++
++# test ICMPv6 echo request (which should have no nd_target field)
++AT_CHECK([ovs-appctl ofproto/trace br0 "in_port=1,eth_src=f6:d2:b0:19:5e:7b,eth_dst=d2:49:19:91:78:fe,dl_type=0x86dd,ipv6_src=1000::3,ipv6_dst=1000::4,nw_proto=58,icmpv6_type=128,icmpv6_code=0"], [0], [stdout])
++AT_CHECK([tail -2 stdout], [0],
++  [Megaflow: recirc_id=0,eth,icmp6,in_port=1,dl_src=f6:d2:b0:19:5e:7b,dl_dst=d2:49:19:91:78:fe,ipv6_src=1000::/10,ipv6_dst=1000::4,nw_ttl=0,nw_frag=no
++Datapath actions: 100,2
++])
++OVS_VSWITCHD_STOP
++AT_CLEANUP
++
+ AT_BANNER([conjunctive match])
+ 
+ AT_SETUP([single conjunctive match])
+diff --git a/tests/completion.at b/tests/completion.at
+index 00e3a46b8b..b6155af253 100644
+--- a/tests/completion.at
++++ b/tests/completion.at
+@@ -351,22 +351,22 @@ OVS_VSWITCHD_START
+ TMP="$(ovs-vsctl --commands | cut -d',' -f1-2 | tr -d ',[[]]' | tr -s ' ' '\n')
+ $(ovs-vsctl --options | grep -- '--' | sed -e 's/=.*$/=/g')"
+ MATCH="$(PREPARE_MATCH_NOSPACE(${TMP}))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "--db=unix:$OVS_RUNDIR/db.sock "],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "--db=unix:$OVS_RUNDIR/db.sock "],
+ [0], [dnl
+ ${MATCH}
+ ])
+ # complete ovs-vsctl [TAB]
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test ""],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test ""],
+ [0], [dnl
+ ${MATCH}
+ ])
+ 
+ # complete on global options.
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "--dry-run "],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "--dry-run "],
+ [0], [dnl
+ ${MATCH}
+ ])
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "--dry-run --pretty "],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "--dry-run --pretty "],
+ [0], [dnl
+ ${MATCH}
+ ])
+@@ -374,7 +374,7 @@ ${MATCH}
+ # complete on local options.
+ TMP="$(ovs-vsctl --commands | grep -- '--may-exist' | cut -d',' -f1-2 | tr -d ',[[]]' | tr -s ' ' '\n' | grep -v -- '--may-exist')"
+ MATCH="$(PREPARE_MATCH_SPACE(${TMP}))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "--may-exist "],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "--may-exist "],
+ [0], [dnl
+ ${MATCH}
+ ])
+@@ -385,37 +385,37 @@ ${MATCH}
+ # test !.  no following arguments are expanded.
+ TMP="$(ovsdb-client --no-heading list-tables)"
+ MATCH="$(PREPARE_MATCH_SPACE(${TMP}))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "set "],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "set "],
+ [0], [dnl
+ ${MATCH}
+ ])
+ # test ?.  will show completions for both current and following arguments.
+ ovs-vsctl br-set-external-id br0 bridge-id br0
+ MATCH="$(PREPARE_MATCH_SPACE(bridge-id --))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "br-get-external-id br0 "],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "br-get-external-id br0 "],
+ [0], [dnl
+ ${MATCH}
+ ])
+ # test *.  argument with this prefix could be completed for zero or more times.
+ TMP="$(ovs-vsctl --no-heading --columns=_uuid,name list Bridge | tr -d '\"')"
+ MATCH="$(PREPARE_MATCH_SPACE(${TMP} --))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "destroy Bridge "],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "destroy Bridge "],
+ [0], [dnl
+ ${MATCH}
+ ])
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "destroy Bridge br0 "],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "destroy Bridge br0 "],
+ [0], [dnl
+ ${MATCH}
+ ])
+ # test +.  the first time, an argument is required, after that, it becomes '*'.
+ TMP="$(ovsdb-client --no-heading list-columns Open_vSwitch Bridge | awk '/key.*value/ { print $1":"; next } { print $1; next }')"
+ MATCH="$(PREPARE_MATCH_NOSPACE(${TMP}))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "set Bridge br0 "],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "set Bridge br0 "],
+ [0], [dnl
+ ${MATCH}
+ ])
+ MATCH="$(PREPARE_MATCH_NOSPACE(${TMP} --))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "set Bridge br0 other_config:random_key=123 "],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "set Bridge br0 other_config:random_key=123 "],
+ [0], [dnl
+ ${MATCH}
+ ])
+@@ -453,12 +453,12 @@ OVS_VSWITCHD_START(
+ #
+ TMP="$(ovsdb-client --no-heading list-tables)"
+ MATCH="$(PREPARE_MATCH_SPACE(${TMP}))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "set "],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "set "],
+ [0], [dnl
+ ${MATCH}
+ ])
+ MATCH="$(PREPARE_MATCH_SPACE(Open_vSwitch))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "set Open"],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "set Open"],
+ [0], [dnl
+ ${MATCH}
+ ])
+@@ -469,13 +469,13 @@ ${MATCH}
+ #
+ TMP="$(ovs-vsctl --no-heading --columns=_uuid list Open_vSwitch | tr -d '\"')"
+ MATCH="$(PREPARE_MATCH_SPACE(${TMP}))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "set Open_vSwitch "],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "set Open_vSwitch "],
+ [0], [dnl
+ ${MATCH}
+ ])
+ TMP="$(ovs-vsctl --no-heading --columns=_uuid,name list Bridge | tr -d '\"')"
+ MATCH="$(PREPARE_MATCH_SPACE(${TMP}))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "set Bridge "],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "set Bridge "],
+ [0], [dnl
+ ${MATCH}
+ ])
+@@ -486,13 +486,13 @@ ${MATCH}
+ #
+ TMP="$(ovs-vsctl list-br)"
+ MATCH="$(PREPARE_MATCH_SPACE(${TMP}))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "br-to-vlan "],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "br-to-vlan "],
+ [0], [dnl
+ ${MATCH}
+ ])
+ # this also helps check the '_ovs_vsctl_check_startswith_string'.
+ MATCH="$(PREPARE_MATCH_SPACE(--weird-br_name))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "br-to-vlan --"],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "br-to-vlan --"],
+ [0], [dnl
+ ${MATCH}
+ ])
+@@ -503,14 +503,14 @@ ${MATCH}
+ #
+ TMP="$(ovs-vsctl --no-heading --columns=name list Port | tr -d '\"')"
+ MATCH="$(PREPARE_MATCH_SPACE(${TMP}))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "port-to-br "],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "port-to-br "],
+ [0], [dnl
+ ${MATCH}
+ ])
+ # complete on ports in particular bridge.
+ TMP="$(ovs-vsctl list-ports br0)"
+ MATCH="$(PREPARE_MATCH_SPACE(${TMP}))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "del-port br0 "],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "del-port br0 "],
+ [0], [dnl
+ ${MATCH}
+ ])
+@@ -523,7 +523,7 @@ for br in `ovs-vsctl list-br`; do
+     TMP="${TMP} $(ovs-vsctl list-ifaces $br)"
+ done
+ MATCH="$(PREPARE_MATCH_SPACE(${TMP}))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "iface-to-br "],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "iface-to-br "],
+ [0], [dnl
+ ${MATCH}
+ ])
+@@ -533,7 +533,7 @@ ${MATCH}
+ # test: _ovs_vsctl_complete_bridge_fail_mode
+ #
+ MATCH="$(PREPARE_MATCH_SPACE(standalone secure))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "set-fail-mode br0 "],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "set-fail-mode br0 "],
+ [0], [dnl
+ ${MATCH}
+ ])
+@@ -542,25 +542,25 @@ ${MATCH}
+ #
+ # test: _ovs_vsctl_complete_key
+ #
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "br-set-external-id br0 "],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "br-set-external-id br0 "],
+ [0], [dnl
+ 
+ ])
+ # since there is no key added yet, we will only get our own input.
+ MATCH="$(PREPARE_MATCH_SPACE(test_key))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "br-set-external-id br0 test_key"],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "br-set-external-id br0 test_key"],
+ [0], [dnl
+ ${MATCH}
+ ])
+ # now add a key, as we should see it.
+ ovs-vsctl br-set-external-id br0 bridge-id br0
+ MATCH="$(PREPARE_MATCH_SPACE(bridge-id))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "br-set-external-id br0 "],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "br-set-external-id br0 "],
+ [0], [dnl
+ ${MATCH}
+ ])
+ MATCH="$(PREPARE_MATCH_SPACE(bridge-id --))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "br-get-external-id br0 "],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "br-get-external-id br0 "],
+ [0], [dnl
+ ${MATCH}
+ ])
+@@ -571,7 +571,7 @@ ${MATCH}
+ #
+ # should just return the user input.
+ MATCH="$(PREPARE_MATCH_SPACE(test_value --))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "br-set-external-id br0 bridge-id test_value"],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "br-set-external-id br0 bridge-id test_value"],
+ [0], [dnl
+ ${MATCH}
+ ])
+@@ -583,13 +583,13 @@ ${MATCH}
+ TMP="$(ovsdb-client --no-heading list-columns Open_vSwitch Open_vSwitch | tr -d ':' | cut -d' ' -f1)"
+ UUID="$(ovs-vsctl --no-heading --columns=_uuid list Open_vSwitch | tr -d ' ')"
+ MATCH="$(PREPARE_MATCH_SPACE(${TMP}))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "clear Open_vSwitch $UUID "],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "clear Open_vSwitch $UUID "],
+ [0], [dnl
+ ${MATCH}
+ ])
+ TMP="$(ovsdb-client --no-heading list-columns Open_vSwitch Bridge | tr -d ':' | cut -d' ' -f1)"
+ MATCH="$(PREPARE_MATCH_SPACE(${TMP}))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "clear Bridge br0 "],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "clear Bridge br0 "],
+ [0], [dnl
+ ${MATCH}
+ ])
+@@ -597,7 +597,7 @@ ${MATCH}
+ # so, with one specified COLUMN 'other_config', it should still complete on
+ # COLUMNs, plus '--'.
+ MATCH="$(PREPARE_MATCH_SPACE(${TMP} --))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "clear Bridge br0 other_config "],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "clear Bridge br0 other_config "],
+ [0], [dnl
+ ${MATCH}
+ ])
+@@ -608,19 +608,19 @@ ${MATCH}
+ #
+ # with no key available, should always get user input.
+ MATCH="$(PREPARE_MATCH_NOSPACE(random_key))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "add Bridge br0 other_config random_key"],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "add Bridge br0 other_config random_key"],
+ [0], [dnl
+ ${MATCH}
+ ])
+ MATCH="$(PREPARE_MATCH_NOSPACE(abc))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "add Bridge br0 other_config random_key=abc"],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "add Bridge br0 other_config random_key=abc"],
+ [0], [dnl
+ ${MATCH}
+ ])
+ # now add two random keys.
+ ovs-vsctl set Bridge br0 other_config:random_key1=abc other_config:random_val1=xyz
+ MATCH="$(PREPARE_MATCH_NOSPACE(random_key1= random_val1=))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "add Bridge br0 other_config ran"],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "add Bridge br0 other_config ran"],
+ [0], [dnl
+ ${MATCH}
+ ])
+@@ -632,25 +632,25 @@ ${MATCH}
+ # at first, we should complete on column.
+ TMP="$(ovsdb-client --no-heading list-columns Open_vSwitch Bridge | awk '/key.*value/ { print $1":"; next } { print $1; next }')"
+ MATCH="$(PREPARE_MATCH_NOSPACE(${TMP}))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "set Bridge br0 "],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "set Bridge br0 "],
+ [0], [dnl
+ ${MATCH}
+ ])
+ MATCH="$(PREPARE_MATCH_NOSPACE(other_config:))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "set Bridge br0 other"],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "set Bridge br0 other"],
+ [0], [dnl
+ ${MATCH}
+ ])
+ # then, with the ':' we should complete on key.
+ TMP="$(ovs-vsctl --no-heading --columns=other_config list Bridge br0 | tr -d '{\"}' | tr -s ', ' '\n' | cut -d'=' -f1)"
+ MATCH="$(PREPARE_MATCH_NOSPACE(${TMP}))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "set Bridge br0 other_config:"],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "set Bridge br0 other_config:"],
+ [0], [dnl
+ ${MATCH}
+ ])
+ # finally, if user fill in some value, we should just complete on user input.
+ MATCH="$(PREPARE_MATCH_NOSPACE(random_val1))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "set Bridge br0 other_config:random_val1=12345"],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "set Bridge br0 other_config:random_val1=12345"],
+ [0], [dnl
+ ${MATCH}
+ ])
+@@ -661,12 +661,12 @@ ${MATCH}
+ #
+ touch private_key certificate
+ MATCH="$(PREPARE_MATCH_SPACE(private_key))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "set-ssl priva"],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "set-ssl priva"],
+ [0], [dnl
+ ${MATCH}
+ ])
+ MATCH="$(PREPARE_MATCH_SPACE(certificate))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "set-ssl private_key cer"],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "set-ssl private_key cer"],
+ [0], [dnl
+ ${MATCH}
+ ])
+@@ -676,20 +676,20 @@ ${MATCH}
+ # test: _ovs_vsctl_complete_target
+ #
+ MATCH="$(PREPARE_MATCH_NOSPACE(pssl: ptcp: punix: ssl: tcp: unix:))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "set-manager "],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "set-manager "],
+ [0], [dnl
+ ${MATCH}
+ ])
+ # filename completion on unix, punix.
+ MATCH="$(PREPARE_MATCH_NOSPACE(testsuite.log))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "set-manager unix:test"],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "set-manager unix:test"],
+ [0], [dnl
+ ${MATCH}
+ ])
+ # no completion on other type, just return available types.
+ # in real environment, bash will not complete on anything.
+ MATCH="$(PREPARE_MATCH_NOSPACE(pssl: ptcp: punix: tcp: unix:))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "set-manager ssl:something"],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "set-manager ssl:something"],
+ [0], [dnl
+ ${MATCH}
+ ])
+@@ -699,14 +699,14 @@ ${MATCH}
+ # test: _ovs_vsctl_complete_new
+ #
+ # test 'add-br'
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "add-br "],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "add-br "],
+ [0], [dnl
+ --- BEGIN MESSAGE
+ Enter a new bridge:
+ > ovs-vsctl add-br --- END MESSAGE
+ ])
+ # user input does not change the output.
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "add-br new-br"],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "add-br new-br"],
+ [0], [dnl
+ --- BEGIN MESSAGE
+ Enter a new bridge:
+@@ -715,7 +715,7 @@ Enter a new bridge:
+ # after specifying the new bridge name, we should complete on parent bridge.
+ TMP="$(ovs-vsctl list-br)"
+ MATCH="$(PREPARE_MATCH_SPACE(${TMP}))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "add-br new-br "],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "add-br new-br "],
+ [0], [dnl
+ ${MATCH}
+ ])
+@@ -724,7 +724,7 @@ ${MATCH}
+ # of '*COLUMN?:KEY=VALUE'.
+ TMP="$(ovsdb-client --no-heading list-columns Open_vSwitch Port | awk '/key.*value/ { print $1":"; next } { print $1; next }')"
+ MATCH="$(PREPARE_MATCH_NOSPACE(${TMP} --))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "add-port br0 new-port "],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "add-port br0 new-port "],
+ [0], [dnl
+ ${MATCH}
+ ])
+@@ -736,13 +736,13 @@ ${MATCH}
+ # after '--', there should be no global options available for completion.
+ TMP="$(ovs-vsctl --commands | cut -d',' -f1-2 | tr -d ',[[]]' | tr -s ' ' '\n')"
+ MATCH="$(PREPARE_MATCH_NOSPACE(${TMP}))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "init -- "],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "init -- "],
+ [0], [dnl
+ ${MATCH}
+ ])
+ TMP="$(ovs-vsctl --no-heading --columns=name,_uuid list Port | tr -d '\"')"
+ MATCH="$(PREPARE_MATCH_SPACE(${TMP} newp1 newp2))"
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "add-port br0 newp1 -- add-port br1 newp2 -- set Port "],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "add-port br0 newp1 -- add-port br1 newp2 -- set Port "],
+ [0], [dnl
+ ${MATCH}
+ ])
+@@ -757,25 +757,25 @@ AT_SKIP_IF([eval 'test ${BASH_VERSINFO[[0]]} -lt 4'])
+ OVS_VSWITCHD_START
+ 
+ # complete non-matching command.
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "invalid"],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "invalid"],
+ [0], [dnl
+ 
+ ])
+ 
+ # complete after invalid command.
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "invalid argu"],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "invalid argu"],
+ [0], [dnl
+ 
+ ])
+ 
+ # complete non-matching end argument.
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "set INVALID_"],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "set INVALID_"],
+ [0], [dnl
+ 
+ ])
+ 
+ # complete after invalid intermediate argument.
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "set INVALID_TBL "],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "set INVALID_TBL "],
+ [1], [dnl
+ --- BEGIN MESSAGE
+ Cannot complete 'INVALID_TBL' at index 3:
+@@ -783,12 +783,12 @@ Cannot complete 'INVALID_TBL' at index 3:
+ 
+ # complete ovs-vsctl --db=wrongdb [TAB]
+ # should return 1 and show nothing.
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test "--db=wrongdb"],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test "--db=wrongdb"],
+ [1], [])
+ 
+ OVS_VSWITCHD_STOP
+ # delete ovsdb-server and try again.
+-AT_CHECK_UNQUOTED([ovs-vsctl-bashcomp.bash test ""],
++AT_CHECK_UNQUOTED([bash ovs-vsctl-bashcomp.bash test ""],
+ [1], [])
+ 
+ AT_CLEANUP
+diff --git a/tests/drop-stats.at b/tests/drop-stats.at
+index f3e19cd83b..1d3af98dab 100644
+--- a/tests/drop-stats.at
++++ b/tests/drop-stats.at
+@@ -83,6 +83,9 @@ AT_CHECK([
+     ovs-ofctl -Oopenflow13 add-flows br0 flows.txt
+     ovs-ofctl -Oopenflow13 dump-flows br0 | ofctl_strip | sort | grep actions ], [0], [ignore])
+ 
++ovs-appctl time/warp 15000
++AT_CHECK([ovs-appctl revalidator/wait])
++
+ AT_CHECK([
+     ovs-appctl netdev-dummy/receive p1 'in_port(1),packet_type(ns=0,id=0),eth(src=3a:6d:d2:09:9c:ab,dst=1e:2c:e9:2a:66:9e),ipv4(src=192.168.10.10,dst=192.168.10.30,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)'
+ ], [0], [ignore])
+diff --git a/tests/library.at b/tests/library.at
+index db4997d8f0..6489be2c15 100644
+--- a/tests/library.at
++++ b/tests/library.at
+@@ -252,7 +252,7 @@ AT_CHECK([ovstest test-barrier], [0], [])
+ AT_CLEANUP
+ 
+ AT_SETUP([rcu])
+-AT_CHECK([ovstest test-rcu-quiesce], [0], [])
++AT_CHECK([ovstest test-rcu], [0], [])
+ AT_CLEANUP
+ 
+ AT_SETUP([stopwatch module])
+diff --git a/tests/mcast-snooping.at b/tests/mcast-snooping.at
+index 757cf7186e..fe475e7b38 100644
+--- a/tests/mcast-snooping.at
++++ b/tests/mcast-snooping.at
+@@ -216,3 +216,70 @@ AT_CHECK([ovs-appctl mdb/show br0], [0], [dnl
+ ])
+ 
+ AT_CLEANUP
++
++
++AT_SETUP([mcast - igmp flood for non-snoop enabled])
++OVS_VSWITCHD_START([])
++
++AT_CHECK([
++    ovs-vsctl set bridge br0 \
++    datapath_type=dummy], [0])
++
++add_of_ports br0 1 2
++
++AT_CHECK([ovs-ofctl add-flow br0 action=normal])
++
++ovs-appctl time/stop
++
++dnl Basic scenario - needs to flood for IGMP followed by unicast ICMP
++dnl in reverse direction
++AT_CHECK([ovs-appctl netdev-dummy/receive p1 \
++    '0101000c29a0aa55aa550001080046c00028000040000102d3494565eb4ae0000016940400002200f9020000000104000000e00000fb000000000000'])
++AT_CHECK([ovs-appctl netdev-dummy/receive p2 \
++    'aa55aa5500010101000c29a008004500001c00010000400164dc0a0101010a0101020800f7ffffffffff'])
++
++
++AT_CHECK([ovs-appctl dpctl/dump-flows | grep -e .*ipv4 | sort | dnl
++          strip_stats | strip_used | strip_recirc | dnl
++          sed -e 's/,packet_type(ns=[[0-9]]*,id=[[0-9]]*),/,/'],
++                     [0], [dnl
++recirc_id(<recirc>),in_port(1),eth(src=aa:55:aa:55:00:01,dst=01:01:00:0c:29:a0),eth_type(0x0800),ipv4(frag=no), packets:0, bytes:0, used:never, actions:100,2
++recirc_id(<recirc>),in_port(2),eth(src=01:01:00:0c:29:a0,dst=aa:55:aa:55:00:01),eth_type(0x0800),ipv4(frag=no), packets:0, bytes:0, used:never, actions:1
++])
++
++ovs-appctl time/warp 100000
++
++dnl Next we should clear the flows and install a complex case
++AT_CHECK([ovs-ofctl del-flows br0])
++
++AT_DATA([flows.txt], [dnl
++table=0, arp actions=NORMAL
++table=0, ip,in_port=1 actions=ct(table=1,zone=64000)
++table=0, in_port=2 actions=output:1
++table=1, ip,ct_state=+trk+inv actions=drop
++table=1  ip,in_port=1,icmp,ct_state=+trk+new actions=output:2
++table=1, in_port=1,ip,ct_state=+trk+new actions=controller(userdata=00.de.ad.be.ef.ca.fe.01)
++table=1, in_port=1,ip,ct_state=+trk+est actions=output:2
++])
++AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
++
++ovs-appctl time/warp 100000
++
++dnl Send the IGMP, followed by a unicast ICMP - ensure we won't black hole
++AT_CHECK([ovs-appctl netdev-dummy/receive p1 \
++    '0101000c29a0aa55aa550001080046c00028000040000102d3494565eb4ae0000016940400002200f9020000000104000000e00000fb000000000000'])
++AT_CHECK([ovs-appctl netdev-dummy/receive p1 \
++    'aa55aa550001aa55aa55000208004500001c00010000400164dc0a0101010a0101020800f7ffffffffff'])
++
++
++AT_CHECK([ovs-appctl dpctl/dump-flows | grep -e .*ipv4 | sort | dnl
++          strip_stats | strip_used | strip_recirc | dnl
++          sed 's/pid=[[0-9]]*,//
++               s/,packet_type(ns=[[0-9]]*,id=[[0-9]]*),/,/'],
++                     [0], [dnl
++ct_state(+new-inv+trk),recirc_id(<recirc>),in_port(1),eth_type(0x0800),ipv4(proto=1,frag=no), packets:0, bytes:0, used:never, actions:2
++ct_state(+new-inv+trk),recirc_id(<recirc>),in_port(1),eth_type(0x0800),ipv4(proto=2,frag=no), packets:0, bytes:0, used:never, actions:userspace(controller(reason=1,dont_send=0,continuation=0,recirc_id=<recirc>,rule_cookie=0,controller_id=0,max_len=65535))
++recirc_id(<recirc>),in_port(1),eth_type(0x0800),ipv4(frag=no), packets:0, bytes:0, used:0.0s, actions:ct(zone=64000),recirc(<recirc>)
++])
++
++AT_CLEANUP
+diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
+index 7c2edeb9d4..c923ed6606 100644
+--- a/tests/ofproto-dpif.at
++++ b/tests/ofproto-dpif.at
+@@ -29,6 +29,39 @@ AT_CHECK([ovs-appctl revalidator/wait])
+ OVS_VSWITCHD_STOP
+ AT_CLEANUP
+ 
++AT_SETUP([ofproto-dpif - lldp revalidator event(REV_RECONFIGURE)])
++OVS_VSWITCHD_START(
++    [add-port br0 p1 -- set interface p1 ofport_request=1 type=dummy]
++)
++dnl first revalidation triggered by add interface
++AT_CHECK([ovs-appctl coverage/read-counter rev_reconfigure], [0], [dnl
++1
++])
++
++dnl enable lldp
++AT_CHECK([ovs-vsctl set interface p1 lldp:enable=true])
++AT_CHECK([ovs-appctl revalidator/wait])
++AT_CHECK([ovs-appctl coverage/read-counter rev_reconfigure], [0], [dnl
++2
++])
++
++dnl disable lldp
++AT_CHECK([ovs-vsctl set interface p1 lldp:enable=false])
++AT_CHECK([ovs-appctl revalidator/wait])
++AT_CHECK([ovs-appctl coverage/read-counter rev_reconfigure], [0], [dnl
++3
++])
++
++dnl remove lldp, no revalidation as lldp was disabled
++AT_CHECK([ovs-vsctl remove interface p1 lldp enable])
++AT_CHECK([ovs-appctl revalidator/wait])
++AT_CHECK([ovs-appctl coverage/read-counter rev_reconfigure], [0], [dnl
++3
++])
++
++OVS_VSWITCHD_STOP
++AT_CLEANUP
++
+ AT_SETUP([ofproto-dpif - active-backup bonding (with primary)])
+ 
+ dnl Create br0 with members p1, p2 and p7, creating bond0 with p1 and
+@@ -81,11 +114,12 @@ recirc_id(0),in_port(4),packet_type(ns=0,id=0),eth(src=50:54:00:00:00:0b,dst=ff:
+ 
+ ovs-appctl netdev-dummy/set-admin-state p1 up
+ ovs-appctl time/warp 100
+-OVS_WAIT_UNTIL([ovs-appctl bond/show | STRIP_RECIRC_ID | STRIP_ACTIVE_MEMBER_MAC], [0], [dnl
++OVS_WAIT_UNTIL_EQUAL([ovs-appctl bond/show | STRIP_RECIRC_ID | STRIP_ACTIVE_MEMBER_MAC], [dnl
+ ---- bond0 ----
+ bond_mode: active-backup
+ bond may use recirculation: no, <del>
+ bond-hash-basis: 0
++lb_output action: disabled, bond-id: -1
+ updelay: 0 ms
+ downdelay: 0 ms
+ lacp_status: off
+@@ -99,7 +133,6 @@ member p1: enabled
+ 
+ member p2: enabled
+   may_enable: true
+-
+ ])
+ 
+ OVS_VSWITCHD_STOP
+@@ -129,11 +162,12 @@ ovs-appctl time/warp 100
+ OVS_WAIT_UNTIL([test -n "`ovs-appctl bond/show | fgrep 'member p1: disabled'`"])
+ ovs-appctl netdev-dummy/set-admin-state p1 up
  ovs-appctl time/warp 100
 -OVS_WAIT_UNTIL([ovs-appctl bond/show | STRIP_RECIRC_ID | STRIP_ACTIVE_MEMBER_MAC], [0], [dnl
 +OVS_WAIT_UNTIL_EQUAL([ovs-appctl bond/show | STRIP_RECIRC_ID | STRIP_ACTIVE_MEMBER_MAC], [dnl
@@ -5646,7 +9122,7 @@ index 7c2edeb9d4..ffb7208c7f 100644
  updelay: 0 ms
  downdelay: 0 ms
  lacp_status: off
-@@ -150,7 +151,6 @@ member p2: enabled
+@@ -150,7 +184,6 @@ member p2: enabled
  
  member p3: enabled
    may_enable: true
@@ -5654,7 +9130,7 @@ index 7c2edeb9d4..ffb7208c7f 100644
  ])
  
  dnl Now delete the primary and verify that the output shows that the
-@@ -171,11 +171,12 @@ ovs-vsctl \
+@@ -171,11 +204,12 @@ ovs-vsctl \
     --id=@p1 create Interface name=p1 type=dummy options:pstream=punix:$OVS_RUNDIR/p1.sock ofport_request=1 -- \
     set Port bond0 interfaces="$uuids, @p1]"
  ovs-appctl time/warp 100
@@ -5668,7 +9144,7 @@ index 7c2edeb9d4..ffb7208c7f 100644
  updelay: 0 ms
  downdelay: 0 ms
  lacp_status: off
-@@ -192,17 +193,17 @@ member p2: enabled
+@@ -192,17 +226,17 @@ member p2: enabled
  
  member p3: enabled
    may_enable: true
@@ -5688,7 +9164,7 @@ index 7c2edeb9d4..ffb7208c7f 100644
  updelay: 0 ms
  downdelay: 0 ms
  lacp_status: off
-@@ -211,25 +212,25 @@ active-backup primary: p2
+@@ -211,25 +245,25 @@ active-backup primary: p2
  <active member mac del>
  
  member p1: enabled
@@ -5717,7 +9193,7 @@ index 7c2edeb9d4..ffb7208c7f 100644
  updelay: 0 ms
  downdelay: 0 ms
  lacp_status: off
-@@ -238,15 +239,14 @@ active-backup primary: <none>
+@@ -238,15 +272,14 @@ active-backup primary: <none>
  <active member mac del>
  
  member p1: enabled
@@ -5734,7 +9210,7 @@ index 7c2edeb9d4..ffb7208c7f 100644
  ])
  
  OVS_VSWITCHD_STOP
-@@ -5573,7 +5573,36 @@ check_flows () {
+@@ -5573,7 +5606,36 @@ check_flows () {
      echo "n_packets=$n"
      test "$n" = 1
  }
@@ -5772,11 +9248,63 @@ index 7c2edeb9d4..ffb7208c7f 100644
  
  OVS_VSWITCHD_STOP
  AT_CLEANUP
+@@ -7600,13 +7662,28 @@ dnl configure bridge IPFIX and ensure that sample action generation works at the
+ dnl datapath level.
+ AT_SETUP([ofproto-dpif - Bridge IPFIX sanity check])
+ OVS_VSWITCHD_START
++dnl first revalidation triggered by add interface
++AT_CHECK([ovs-appctl coverage/read-counter rev_reconfigure], [0], [dnl
++1
++])
++
+ add_of_ports br0 1 2 3
++AT_CHECK([ovs-appctl coverage/read-counter rev_reconfigure], [0], [dnl
++2
++])
+ 
+ dnl Sample every packet using bridge-based sampling.
+ AT_CHECK([ovs-vsctl -- set bridge br0 ipfix=@fix -- \
+                     --id=@fix create ipfix targets=\"127.0.0.1:4739\" \
+-                              sampling=1], [0], [ignore])
++                              sampling=2], [0], [ignore])
++AT_CHECK([ovs-appctl coverage/read-counter rev_reconfigure], [0], [dnl
++3
++])
+ 
++AT_CHECK([ovs-vsctl set ipfix `ovs-vsctl get bridge br0 ipfix` sampling=1], [0])
++AT_CHECK([ovs-appctl coverage/read-counter rev_reconfigure], [0], [dnl
++4
++])
+ dnl Send some packets that should be sampled.
+ for i in `seq 1 3`; do
+     AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800)'])
 diff --git a/tests/ofproto-macros.at b/tests/ofproto-macros.at
-index 736d9809cb..7051d95396 100644
+index 736d9809cb..b18f0fbc1e 100644
 --- a/tests/ofproto-macros.at
 +++ b/tests/ofproto-macros.at
-@@ -175,6 +175,7 @@ m4_define([_OVS_VSWITCHD_START],
+@@ -134,6 +134,21 @@ strip_ufid () {
+     sed 's/mega_ufid:[[-0-9a-f]]* //
+     s/ufid:[[-0-9a-f]]* //'
+ }
++
++# Strips packets: and bytes: from output
++strip_stats () {
++    sed 's/packets:[[0-9]]*/packets:0/
++    s/bytes:[[0-9]]*/bytes:0/'
++}
++
++# Changes all 'recirc(...)' and 'recirc=...' to say 'recirc(<recirc_id>)' and
++# 'recirc=<recirc_id>' respectively.  This should make output easier to
++# compare.
++strip_recirc() {
++   sed 's/recirc_id([[x0-9]]*)/recirc_id(<recirc>)/
++        s/recirc_id=[[x0-9]]*/recirc_id=<recirc>/
++        s/recirc([[x0-9]]*)/recirc(<recirc>)/'
++}
+ m4_divert_pop([PREPARE_TESTS])
+ 
+ m4_define([TESTABLE_LOG], [-vPATTERN:ANY:'%c|%p|%m'])
+@@ -175,6 +190,7 @@ m4_define([_OVS_VSWITCHD_START],
  /dpdk|INFO|DPDK Disabled - Use other_config:dpdk-init to enable/d
  /netlink_socket|INFO|netlink: could not enable listening to all nsid/d
  /probe tc:/d
@@ -5784,7 +9312,7 @@ index 736d9809cb..7051d95396 100644
  /tc: Using policy/d']])
  ])
  
-@@ -239,6 +240,7 @@ check_logs () {
+@@ -239,6 +255,7 @@ check_logs () {
  /timeval.*context switches: [[0-9]]* voluntary, [[0-9]]* involuntary/d
  /ovs_rcu.*blocked [[0-9]]* ms waiting for .* to quiesce/d
  /Dropped [[0-9]]* log messages/d
@@ -5850,11 +9378,119 @@ index bba4fea2bc..977b2eba1f 100644
  /|WARN|/p
  /|ERR|/p
  /|EMER|/p" ovs-vswitchd.log
+diff --git a/tests/ovsdb-client.at b/tests/ovsdb-client.at
+index 06b671df8c..2d14f1ac26 100644
+--- a/tests/ovsdb-client.at
++++ b/tests/ovsdb-client.at
+@@ -3,6 +3,7 @@ AT_BANNER([OVSDB -- ovsdb-client commands])
+ AT_SETUP([ovsdb-client get-schema-version and get-schema-cksum])
+ AT_KEYWORDS([ovsdb client positive])
+ ordinal_schema > schema
++on_exit 'kill `cat *.pid`'
+ AT_CHECK([ovsdb-tool create db schema], [0], [ignore], [ignore])
+ AT_CHECK([ovsdb-server --detach --no-chdir --pidfile --remote=punix:socket db], [0], [ignore], [ignore])
+ AT_CHECK([ovsdb-client get-schema-version unix:socket ordinals], [0], [5.1.3
+@@ -14,6 +15,7 @@ AT_CLEANUP
+ 
+ AT_SETUP([ovsdb-client needs-conversion (no conversion needed)])
+ AT_KEYWORDS([ovsdb client file positive])
++on_exit 'kill `cat *.pid`'
+ ordinal_schema > schema
+ touch .db.~lock~
+ AT_CHECK([ovsdb-tool create db schema], [0], [], [ignore])
+@@ -27,6 +29,7 @@ AT_SETUP([ovsdb-client needs-conversion (conversion needed)])
+ AT_KEYWORDS([ovsdb client file positive])
+ ordinal_schema > schema
+ touch .db.~lock~
++on_exit 'kill `cat *.pid`'
+ AT_CHECK([ovsdb-tool create db schema], [0], [], [ignore])
+ AT_CHECK([ovsdb-server --detach --no-chdir --pidfile --remote=punix:socket db], [0], [ignore], [ignore])
+ sed 's/5\.1\.3/5.1.4/' < schema > schema2
 diff --git a/tests/ovsdb-cluster.at b/tests/ovsdb-cluster.at
-index fc6253cfe9..ee9c7b9379 100644
+index fc6253cfe9..920b833b72 100644
 --- a/tests/ovsdb-cluster.at
 +++ b/tests/ovsdb-cluster.at
-@@ -400,6 +400,61 @@ done
+@@ -1,12 +1,25 @@
+ OVS_START_SHELL_HELPERS
+-# ovsdb_check_cluster N_SERVERS SCHEMA_FUNC OUTPUT TRANSACTION...
++# ovsdb_check_cluster N_SERVERS SCHEMA_FUNC OUTPUT USE_LOCAL_CONFIG TRANSACTION...
+ ovsdb_check_cluster () {
+-    local n=$1 schema_func=$2 output=$3
+-    shift; shift; shift
++    set -x
++    local n=$1 schema_func=$2 output=$3 local_config=$4
++    shift; shift; shift; shift
+ 
+     $schema_func > schema
+     schema=`ovsdb-tool schema-name schema`
+     AT_CHECK([ovsdb-tool '-vPATTERN:console:%c|%p|%m' create-cluster s1.db schema unix:s1.raft], [0], [], [stderr])
++    if test X$local_config == X"yes"; then
++        for i in `seq $n`; do
++            AT_CHECK([ovsdb-tool create c$i.db $top_srcdir/ovsdb/local-config.ovsschema], [0], [], [stderr])
++            local ctxn="[[\"Local_Config\",
++                         {\"op\": \"insert\", \"table\": \"Config\",
++                          \"row\": {\"connections\": [\"named-uuid\",\"conn$n\"]}},
++                         {\"op\": \"insert\", \"table\": \"Connection\", \"uuid-name\": \"conn$n\",
++                          \"row\": {\"target\": \"punix:s$i.ovsdb\"}}]]"
++
++            AT_CHECK([ovsdb-tool transact c$i.db "$ctxn"], [0], [ignore], [stderr])
++        done
++    fi
+     AT_CHECK([grep -v 'from ephemeral to persistent' stderr], [1])
+     cid=`ovsdb-tool db-cid s1.db`
+     for i in `seq 2 $n`; do
+@@ -15,7 +28,13 @@ ovsdb_check_cluster () {
+ 
+     on_exit 'kill `cat *.pid`'
+     for i in `seq $n`; do
+-        AT_CHECK([ovsdb-server -vraft -vconsole:off -vsyslog:off --detach --no-chdir --log-file=s$i.log --pidfile=s$i.pid --unixctl=s$i --remote=punix:s$i.ovsdb s$i.db])
++        local remote=punix:s$i.ovsdb
++        local config_db=
++        if test X$local_config == X"yes"; then
++            remote=db:Local_Config,Config,connections
++            config_db=c$i.db
++        fi
++        AT_CHECK([ovsdb-server -vraft -vconsole:off -vsyslog:off --detach --no-chdir --log-file=s$i.log --pidfile=s$i.pid --unixctl=s$i --remote=$remote s$i.db $config_db])
+     done
+     for i in `seq $n`; do
+         AT_CHECK([ovsdb_client_wait unix:s$i.ovsdb $schema connected])
+@@ -40,7 +59,7 @@ AT_BANNER([OVSDB - clustered transactions (1 server)])
+ m4_define([OVSDB_CHECK_EXECUTION],
+   [AT_SETUP([$1 - cluster of 1])
+    AT_KEYWORDS([ovsdb server positive unix cluster cluster1 $5])
+-   ovsdb_check_cluster 1 "$2" '$4' m4_foreach([txn], [$3], ['txn' ])
++   ovsdb_check_cluster 1 "$2" '$4' no m4_foreach([txn], [$3], ['txn' ])
+    AT_CLEANUP])
+ EXECUTION_EXAMPLES
+ 
+@@ -49,7 +68,7 @@ AT_BANNER([OVSDB - clustered transactions (3 servers)])
+ m4_define([OVSDB_CHECK_EXECUTION],
+   [AT_SETUP([$1 - cluster of 3])
+    AT_KEYWORDS([ovsdb server positive unix cluster cluster3 $5])
+-   ovsdb_check_cluster 3 "$2" '$4' m4_foreach([txn], [$3], ['txn' ])
++   ovsdb_check_cluster 3 "$2" '$4' no m4_foreach([txn], [$3], ['txn' ])
+    AT_CLEANUP])
+ EXECUTION_EXAMPLES
+ 
+@@ -58,7 +77,16 @@ AT_BANNER([OVSDB - clustered transactions (5 servers)])
+ m4_define([OVSDB_CHECK_EXECUTION],
+   [AT_SETUP([$1 - cluster of 5])
+    AT_KEYWORDS([ovsdb server positive unix cluster cluster5 $5])
+-   ovsdb_check_cluster 5 "$2" '$4' m4_foreach([txn], [$3], ['txn' ])
++   ovsdb_check_cluster 5 "$2" '$4' no m4_foreach([txn], [$3], ['txn' ])
++   AT_CLEANUP])
++EXECUTION_EXAMPLES
++
++# Test a 3-server cluster using a Local_Config db.
++AT_BANNER([OVSDB - clustered transactions Local_Config (3 servers)])
++m4_define([OVSDB_CHECK_EXECUTION],
++  [AT_SETUP([$1 - cluster of 3])
++   AT_KEYWORDS([ovsdb server positive unix cluster cluster3 Local_Config $5])
++   ovsdb_check_cluster 3 "$2" '$4' yes m4_foreach([txn], [$3], ['txn' ])
+    AT_CLEANUP])
+ EXECUTION_EXAMPLES
+ 
+@@ -400,6 +428,61 @@ done
  
  AT_CLEANUP
  
@@ -5916,7 +9552,11 @@ index fc6253cfe9..ee9c7b9379 100644
  
  
  OVS_START_SHELL_HELPERS
-@@ -416,9 +471,8 @@ ovsdb_cluster_failure_test () {
+@@ -413,12 +496,12 @@ ovsdb_cluster_failure_test () {
+     if test "$crash_node" == "1"; then
+         new_leader=$5
+     fi
++    log_grep=$6
  
      cp $top_srcdir/vswitchd/vswitch.ovsschema schema
      schema=`ovsdb-tool schema-name schema`
@@ -5928,7 +9568,57 @@ index fc6253cfe9..ee9c7b9379 100644
  
      n=3
      join_cluster() {
-@@ -629,9 +683,8 @@ ovsdb_torture_test () {
+@@ -434,7 +517,7 @@ ovsdb|WARN|schema: changed 30 columns in 'Open_vSwitch' database from ephemeral
+     start_server() {
+         local i=$1
+         printf "\ns$i: starting\n"
+-        AT_CHECK([ovsdb-server -vjsonrpc -vconsole:off -vsyslog:off --detach --no-chdir --log-file=s$i.log --pidfile=s$i.pid --unixctl=s$i --remote=punix:s$i.ovsdb s$i.db])
++        AT_CHECK([ovsdb-server -vjsonrpc -vraft -vconsole:off -vsyslog:off --detach --no-chdir --log-file=s$i.log --pidfile=s$i.pid --unixctl=s$i --remote=punix:s$i.ovsdb s$i.db])
+     }
+     connect_server() {
+         local i=$1
+@@ -460,14 +543,23 @@ ovsdb|WARN|schema: changed 30 columns in 'Open_vSwitch' database from ephemeral
+         fi
+         AT_CHECK([ovs-appctl -t "`pwd`"/s$delay_election_node cluster/failure-test delay-election], [0], [ignore])
+     fi
++
++    # Initializing the database separately to avoid extra 'wait' operation
++    # in later transactions.
++    AT_CHECK([ovs-vsctl -v --db="$db" --no-leader-only --no-shuffle-remotes --no-wait init], [0], [ignore], [ignore])
++
+     AT_CHECK([ovs-appctl -t "`pwd`"/s$crash_node cluster/failure-test $crash_command], [0], [ignore])
+     AT_CHECK([ovs-vsctl -v --db="$db" --no-leader-only --no-shuffle-remotes --no-wait create QoS type=x], [0], [ignore], [ignore])
+ 
+-    # Make sure that the node really crashed.
+-    AT_CHECK([ls s$crash_node.ovsdb], [2], [ignore], [ignore])
+-    # XXX: Client will fail if remotes contains unix socket that doesn't exist (killed).
+-    if test "$remote_1" = "$crash_node"; then
+-        db=unix:s$remote_2.ovsdb
++    # Make sure that the node really crashed or has specific log message.
++    if test -z "$log_grep"; then
++        AT_CHECK([ls s$crash_node.ovsdb], [2], [ignore], [ignore])
++        # XXX: Client will fail if remotes contains unix socket that doesn't exist (killed).
++        if test "$remote_1" = "$crash_node"; then
++            db=unix:s$remote_2.ovsdb
++        fi
++    else
++        OVS_WAIT_UNTIL([grep -q "$log_grep" s${crash_node}.log])
+     fi
+     AT_CHECK([ovs-vsctl --db="$db" --no-leader-only --no-wait --columns=type --bare list QoS], [0], [x
+ ])
+@@ -563,6 +655,11 @@ AT_KEYWORDS([ovsdb server negative unix cluster pending-txn])
+ ovsdb_cluster_failure_test 2 2 3 crash-after-receiving-append-request-update
+ AT_CLEANUP
+ 
++AT_SETUP([OVSDB cluster - txn on leader, leader transfers leadership after sending appendReq])
++AT_KEYWORDS([ovsdb server negative unix cluster pending-txn transfer])
++ovsdb_cluster_failure_test 1 2 1 transfer-leadership-after-sending-append-request -1 "Transferring leadership"
++AT_CLEANUP
++
+ 
+ AT_SETUP([OVSDB cluster - competing candidates])
+ AT_KEYWORDS([ovsdb server negative unix cluster competing-candidates])
+@@ -629,9 +726,8 @@ ovsdb_torture_test () {
      local variant=$3            # 'kill' and restart or 'remove' and add
      cp $top_srcdir/vswitchd/vswitch.ovsschema schema
      schema=`ovsdb-tool schema-name schema`
@@ -5940,8 +9630,367 @@ index fc6253cfe9..ee9c7b9379 100644
  
      join_cluster() {
          local i=$1
+diff --git a/tests/ovsdb-server.at b/tests/ovsdb-server.at
+index 876cb836cd..e672c13b27 100644
+--- a/tests/ovsdb-server.at
++++ b/tests/ovsdb-server.at
+@@ -4,7 +4,7 @@ m4_define([OVSDB_SERVER_SHUTDOWN],
+   [OVS_APP_EXIT_AND_WAIT_BY_TARGET([ovsdb-server], [ovsdb-server.pid])])
+ 
+ m4_define([OVSDB_SERVER_SHUTDOWN_N],
+-  [cp pid$1 savepid$1
++  [cp $1.pid savepid$1
+    AT_CHECK([ovs-appctl -t "`pwd`"/unixctl$1 -e exit], [0], [ignore], [ignore])
+    OVS_WAIT_WHILE([kill -0 `cat savepid$1`], [kill `cat savepid$1`])])
+ 
+@@ -30,14 +30,13 @@ m4_define([OVSDB_CHECK_EXECUTION],
+    AT_KEYWORDS([ovsdb server positive unix $5])
+    $2 > schema
+    AT_CHECK([ovsdb-tool create db schema], [0], [stdout], [ignore])
++   on_exit 'kill `cat *.pid`'
+    AT_CHECK([ovsdb-server --detach --no-chdir --pidfile --remote=punix:socket db], [0], [ignore], [ignore])
+    m4_foreach([txn], [$3], 
+-     [AT_CHECK([ovsdb-client transact unix:socket 'txn'], [0], [stdout], [ignore],
+-     [test ! -e pid || kill `cat pid`])
++     [AT_CHECK([ovsdb-client transact unix:socket 'txn'], [0], [stdout], [ignore])
+ cat stdout >> output
+ ])
+-   AT_CHECK([uuidfilt output], [0], [$4], [ignore],
+-            [test ! -e pid || kill `cat pid`])
++   AT_CHECK([uuidfilt output], [0], [$4], [ignore])
+    OVSDB_SERVER_SHUTDOWN
+    AT_CLEANUP])
+ 
+@@ -88,8 +87,7 @@ AT_CHECK([uuidfilt output], [0],
+   [[[{"uuid":["uuid","<0>"]}]
+ [{"uuid":["uuid","<1>"]}]
+ [{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<2>"],"name":"zero","number":0},{"_uuid":["uuid","<1>"],"_version":["uuid","<3>"],"name":"one","number":1}]}]
+-]], [],
+-         [test ! -e pid || kill `cat pid`])
++]], [])
+ AT_CLEANUP
+ 
+ AT_SETUP([truncating database log with bad transaction])
+@@ -136,8 +134,7 @@ AT_CHECK([uuidfilt output], [0],
+   [[[{"uuid":["uuid","<0>"]}]
+ [{"uuid":["uuid","<1>"]}]
+ [{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<2>"],"name":"zero","number":0},{"_uuid":["uuid","<1>"],"_version":["uuid","<3>"],"name":"one","number":1}]}]
+-]], [],
+-         [test ! -e pid || kill `cat pid`])
++]], [])
+ AT_CLEANUP
+ 
+ dnl CHECK_DBS([databases])
+@@ -159,6 +156,7 @@ ordinal_schema > schema1
+ constraint_schema > schema2
+ AT_CHECK([ovsdb-tool create db1 schema1], [0], [ignore], [ignore])
+ AT_CHECK([ovsdb-tool create db2 schema2], [0], [ignore], [ignore])
++on_exit 'kill `cat *.pid`'
+ AT_CHECK([ovsdb-server --detach --no-chdir --pidfile --remote=punix:db.sock db1 db2], [0], [ignore], [ignore])
+ CHECK_DBS([constraints
+ ordinals
+@@ -166,7 +164,7 @@ ordinals
+ AT_CHECK(
+   [[ovstest test-jsonrpc request unix:db.sock get_schema [\"nonexistent\"]]], [0],
+   [[{"error":{"details":"get_schema request specifies unknown database nonexistent","error":"unknown database","syntax":"[\"nonexistent\"]"},"id":0,"result":null}
+-]], [], [test ! -e pid || kill `cat pid`])
++]], [])
+ OVSDB_SERVER_SHUTDOWN
+ AT_CLEANUP
+ 
+@@ -393,7 +391,7 @@ AT_CHECK(
+         "table": "Manager",
+         "uuid-name": "x",
+         "row": {"target": "punix:socket2"}}]']], [0], [ignore], [ignore])
+-on_exit 'kill `cat ovsdb-server.pid`'
++on_exit 'kill `cat *.pid`'
+ AT_CHECK([ovsdb-server --detach --no-chdir --pidfile --remote=db:mydb,Root,managers --remote=db:mydb,Root,manager_options --log-file db], [0], [ignore], [ignore])
+ ovs-appctl -t ovsdb-server time/warp 6000 1000
+ AT_CHECK(
+@@ -686,6 +684,7 @@ ovsdb_check_online_compaction() {
+                   ovsdb-tool create-cluster db schema unix:s1.raft
+               fi])
+     dnl Start ovsdb-server.
++    on_exit 'kill `cat *.pid`'
+     AT_CHECK([ovsdb-server -vvlog:off -vconsole:off --detach --no-chdir --pidfile --remote=punix:socket --log-file db], [0])
+     AT_CHECK([ovsdb_client_wait unix:socket ordinals connected])
+     AT_CAPTURE_FILE([ovsdb-server.log])
+@@ -832,7 +831,7 @@ _uuid                                name  number
+ <0> five  5
+ <1> four  4
+ <2> three 3
+-], [], [test ! -e pid || kill `cat pid`])
++], [])
+     OVSDB_SERVER_SHUTDOWN
+ }
+ OVS_END_SHELL_HELPERS
+@@ -1319,15 +1318,14 @@ m4_define([OVSDB_CHECK_EXECUTION],
+    $2 > schema
+    PKIDIR=$abs_top_builddir/tests
+    AT_CHECK([ovsdb-tool create db schema], [0], [stdout], [ignore])
++   on_exit 'kill `cat *.pid`'
+    AT_CHECK([ovsdb-server --log-file --detach --no-chdir --pidfile --private-key=$PKIDIR/testpki-privkey2.pem --certificate=$PKIDIR/testpki-cert2.pem --ca-cert=$PKIDIR/testpki-cacert.pem --remote=pssl:0:127.0.0.1 db], [0], [ignore], [ignore])
+    PARSE_LISTENING_PORT([ovsdb-server.log], [SSL_PORT])
+    m4_foreach([txn], [$3], 
+-     [AT_CHECK([ovsdb-client --private-key=$PKIDIR/testpki-privkey.pem --certificate=$PKIDIR/testpki-cert.pem --ca-cert=$PKIDIR/testpki-cacert.pem transact ssl:127.0.0.1:$SSL_PORT 'txn'], [0], [stdout], [ignore],
+-     [test ! -e pid || kill `cat pid`])
++     [AT_CHECK([ovsdb-client --private-key=$PKIDIR/testpki-privkey.pem --certificate=$PKIDIR/testpki-cert.pem --ca-cert=$PKIDIR/testpki-cacert.pem transact ssl:127.0.0.1:$SSL_PORT 'txn'], [0], [stdout], [ignore])
+ cat stdout >> output
+ ])
+-   AT_CHECK([uuidfilt output], [0], [$4], [ignore],
+-            [test ! -e pid || kill `cat pid`])
++   AT_CHECK([uuidfilt output], [0], [$4], [ignore])
+    OVSDB_SERVER_SHUTDOWN
+    AT_CLEANUP])
+ 
+@@ -1356,16 +1354,15 @@ m4_define([OVSDB_CHECK_EXECUTION],
+    AT_SKIP_IF([test $HAVE_IPV6 = no])
+    $2 > schema
+    PKIDIR=$abs_top_builddir/tests
++   on_exit 'kill `cat *.pid`'
+    AT_CHECK([ovsdb-tool create db schema], [0], [stdout], [ignore])
+    AT_CHECK([ovsdb-server --log-file --detach --no-chdir --pidfile --private-key=$PKIDIR/testpki-privkey2.pem --certificate=$PKIDIR/testpki-cert2.pem --ca-cert=$PKIDIR/testpki-cacert.pem --remote=pssl:0:[[::1]] db], [0], [ignore], [ignore])
+    PARSE_LISTENING_PORT([ovsdb-server.log], [SSL_PORT])
+    m4_foreach([txn], [$3],
+-     [AT_CHECK([ovsdb-client --private-key=$PKIDIR/testpki-privkey.pem --certificate=$PKIDIR/testpki-cert.pem --ca-cert=$PKIDIR/testpki-cacert.pem transact ssl:[[::1]]:$SSL_PORT 'txn'], [0], [stdout], [ignore],
+-     [test ! -e pid || kill `cat pid`])
++     [AT_CHECK([ovsdb-client --private-key=$PKIDIR/testpki-privkey.pem --certificate=$PKIDIR/testpki-cert.pem --ca-cert=$PKIDIR/testpki-cacert.pem transact ssl:[[::1]]:$SSL_PORT 'txn'], [0], [stdout], [ignore])
+ cat stdout >> output
+ ])
+-   AT_CHECK([uuidfilt output], [0], [$4], [ignore],
+-            [test ! -e pid || kill `cat pid`])
++   AT_CHECK([uuidfilt output], [0], [$4], [ignore])
+    OVSDB_SERVER_SHUTDOWN
+    AT_CLEANUP])
+ 
+@@ -1392,16 +1389,15 @@ m4_define([OVSDB_CHECK_EXECUTION],
+    AT_KEYWORDS([ovsdb server positive tcp $5])
+    $2 > schema
+    PKIDIR=$abs_top_builddir/tests
++   on_exit 'kill `cat *.pid`'
+    AT_CHECK([ovsdb-tool create db schema], [0], [stdout], [ignore])
+    AT_CHECK([ovsdb-server --log-file --detach --no-chdir --pidfile --remote=ptcp:0:127.0.0.1 db], [0], [ignore], [ignore])
+    PARSE_LISTENING_PORT([ovsdb-server.log], [TCP_PORT])
+    m4_foreach([txn], [$3],
+-     [AT_CHECK([ovsdb-client transact tcp:127.0.0.1:$TCP_PORT 'txn'], [0], [stdout], [ignore],
+-     [test ! -e pid || kill `cat pid`])
++     [AT_CHECK([ovsdb-client transact tcp:127.0.0.1:$TCP_PORT 'txn'], [0], [stdout], [ignore])
+ cat stdout >> output
+ ])
+-   AT_CHECK([uuidfilt output], [0], [$4], [ignore],
+-            [test ! -e pid || kill `cat pid`])
++   AT_CHECK([uuidfilt output], [0], [$4], [ignore])
+    OVSDB_SERVER_SHUTDOWN
+    AT_CLEANUP])
+ 
+@@ -1429,16 +1425,15 @@ m4_define([OVSDB_CHECK_EXECUTION],
+    AT_SKIP_IF([test $HAVE_IPV6 = no])
+    $2 > schema
+    PKIDIR=$abs_top_builddir/tests
++   on_exit 'kill `cat *.pid`'
+    AT_CHECK([ovsdb-tool create db schema], [0], [stdout], [ignore])
+    AT_CHECK([ovsdb-server --log-file --detach --no-chdir --pidfile --remote=ptcp:0:[[::1]] db], [0], [ignore], [ignore])
+    PARSE_LISTENING_PORT([ovsdb-server.log], [TCP_PORT])
+    m4_foreach([txn], [$3],
+-     [AT_CHECK([ovsdb-client transact tcp:[[::1]]:$TCP_PORT 'txn'], [0], [stdout], [ignore],
+-     [test ! -e pid || kill `cat pid`])
++     [AT_CHECK([ovsdb-client transact tcp:[[::1]]:$TCP_PORT 'txn'], [0], [stdout], [ignore])
+ cat stdout >> output
+ ])
+-   AT_CHECK([uuidfilt output], [0], [$4], [ignore],
+-            [test ! -e pid || kill `cat pid`])
++   AT_CHECK([uuidfilt output], [0], [$4], [ignore])
+    OVSDB_SERVER_SHUTDOWN
+    AT_CLEANUP])
+ 
+@@ -1518,9 +1513,9 @@ m4_define([OVSDB_CHECK_EXECUTION],
+    target=4
+    $2 > schema
+    schema_name=`ovsdb-tool schema-name schema`
++   on_exit 'kill `cat *.pid`'
+    AT_CHECK([ovsdb-tool create db1 schema], [0], [stdout], [ignore])
+ 
+-   on_exit 'kill `cat *.pid`'
+    AT_CHECK([ovsdb-server --detach --no-chdir --log-file=ovsdb-server1.log dnl
+                           --pidfile --remote=punix:db1.sock db1
+             ], [0], [ignore], [ignore])
+@@ -1576,12 +1571,11 @@ m4_define([OVSDB_CHECK_EXECUTION],
+    AT_CHECK([ovsdb-tool create db1 schema], [0], [stdout], [ignore])
+    AT_CHECK([ovsdb-tool create db2 schema], [0], [stdout], [ignore])
+ 
++   on_exit 'kill `cat *.pid`'
+    AT_CHECK([ovsdb-server --detach --no-chdir --log-file=ovsdb-server1.log --pidfile --remote=punix:db.sock db1], [0], [ignore], [ignore])
+    i
+-   on_exit 'test ! -e pid || kill `cat pid`'
+ 
+-   AT_CHECK([ovsdb-server --detach --no-chdir --log-file=ovsdb-server2.log --pidfile=pid2 --remote=punix:db2.sock --unixctl=unixctl2 --sync-from=unix:db.sock db2], [0], [ignore], [ignore])
+-   on_exit 'test ! -e pid2 || kill `cat pid2`'
++   AT_CHECK([ovsdb-server --detach --no-chdir --log-file=ovsdb-server2.log --pidfile=2.pid --remote=punix:db2.sock --unixctl=unixctl2 --sync-from=unix:db.sock db2], [0], [ignore], [ignore])
+ 
+    m4_foreach([txn], [$3],
+      [AT_CHECK([ovsdb-client transact 'txn'], [0], [stdout], [ignore])
+@@ -1622,11 +1616,10 @@ m4_define([OVSDB_CHECK_REPLICATION],
+    AT_CHECK([ovsdb-tool create db1 schema], [0], [stdout], [ignore])
+    AT_CHECK([ovsdb-tool create db2 schema], [0], [stdout], [ignore])
+ 
++   on_exit 'kill `cat *.pid`'
+    AT_CHECK([ovsdb-server --detach --no-chdir --log-file=ovsdb-server1.log --pidfile --remote=punix:db.sock db1], [0], [ignore], [ignore])
+-   on_exit 'test ! -e pid || kill `cat pid`'
+ 
+-   AT_CHECK([ovsdb-server --detach --no-chdir --log-file=ovsdb-server2.log --pidfile=pid2 --remote=punix:db2.sock --unixctl=unixctl2 --sync-from=unix:db.sock --sync-exclude-tables=mydb:b db2], [0], [ignore], [ignore])
+-   on_exit 'test ! -e pid2 || kill `cat pid2`'
++   AT_CHECK([ovsdb-server --detach --no-chdir --log-file=ovsdb-server2.log --pidfile=2.pid --remote=punix:db2.sock --unixctl=unixctl2 --sync-from=unix:db.sock --sync-exclude-tables=mydb:b db2], [0], [ignore], [ignore])
+ 
+    m4_foreach([txn], [$3],
+      [AT_CHECK([ ovsdb-client transact 'txn' ], [0], [stdout], [ignore])
+@@ -1694,6 +1687,7 @@ AT_CLEANUP
+ 
+ #ovsdb-server/set-sync-exclude-tables command
+ AT_SETUP([ovsdb-server/set-sync-exclude-tables])
++on_exit 'kill `cat *.pid`'
+ AT_KEYWORDS([ovsdb server replication set-exclude-tables])
+ AT_SKIP_IF([test $DIFF_SUPPORTS_NORMAL_FORMAT = no])
+ 
+@@ -1702,12 +1696,10 @@ AT_CHECK([ovsdb-tool create db1 schema], [0], [stdout], [ignore])
+ AT_CHECK([ovsdb-tool create db2 schema], [0], [stdout], [ignore])
+ 
+ AT_CHECK([ovsdb-server --detach --no-chdir --log-file=ovsdb-server1.log --pidfile --remote=punix:db.sock db1], [0], [ignore], [ignore])
+-on_exit 'test ! -e pid || kill `cat pid`'
+ 
+-AT_CHECK([ovsdb-server --detach --no-chdir --log-file=ovsdb-server2.log --pidfile=pid2 --remote=punix:db2.sock --unixctl=unixctl2 --sync-from=unix:db.sock db2], [0], [ignore], [ignore])
+-on_exit 'test ! -e pid2 || kill `cat pid2`'
++AT_CHECK([ovsdb-server --detach --no-chdir --log-file=ovsdb-server2.log --pidfile=2.pid --remote=punix:db2.sock --unixctl=unixctl2 --sync-from=unix:db.sock db2], [0], [ignore], [ignore])
+ 
+-AT_CHECK([ovs-appctl -t "`pwd`"/unixctl2 ovsdb-server/set-sync-exclude-tables mydb:b], [0], [ignore], [ignore], [test ! -e pid || kill `cat pid`; test ! -e pid2 || kill `cat pid2`])
++AT_CHECK([ovs-appctl -t "`pwd`"/unixctl2 ovsdb-server/set-sync-exclude-tables mydb:b], [0], [ignore], [ignore])
+ 
+ AT_CHECK([ovsdb-client transact unix:db.sock \
+  '[["mydb",
+@@ -1716,11 +1708,9 @@ AT_CHECK([ovsdb-client transact unix:db.sock \
+       "row": {"number": 0, "name": "zero"}},
+     {"op": "insert",
+       "table": "b",
+-      "row": {"number": 1, "name": "one"}}]]'], [0], [stdout], [ignore],
+- [test ! -e pid || kill `cat pid`; test ! -e pid2 || kill `cat pid2`])
++      "row": {"number": 1, "name": "one"}}]]'], [0], [stdout], [ignore])
+ 
+-AT_CHECK([ovsdb-client dump unix:db.sock], [0], [stdout], [ignore],
+- [test ! -e pid || kill `cat pid`; test ! -e pid2 || kill `cat pid2`])
++AT_CHECK([ovsdb-client dump unix:db.sock], [0], [stdout], [ignore])
+ cat stdout > dump1
+ OVS_WAIT_UNTIL([ ovsdb-client dump unix:db2.sock | grep zero ])
+ AT_CHECK([ovsdb-client dump unix:db2.sock], [0], [stdout], [ignore])
+@@ -1744,16 +1734,15 @@ AT_CLEANUP
+ 
+ #ovsdb-server/connect-active-ovsdb-server
+ AT_SETUP([ovsdb-server/connect-active-server])
++on_exit 'kill `cat *.pid`'
+ AT_KEYWORDS([ovsdb server replication connect-active-server])
+ replication_schema > schema
+ AT_CHECK([ovsdb-tool create db1 schema], [0], [stdout], [ignore])
+ AT_CHECK([ovsdb-tool create db2 schema], [0], [stdout], [ignore])
+ 
+ AT_CHECK([ovsdb-server --detach --no-chdir --log-file=ovsdb-server1.log --pidfile --remote=punix:db.sock db1], [0], [ignore], [ignore])
+-on_exit 'test ! -e pid || kill `cat pid`'
+ 
+-AT_CHECK([ovsdb-server --detach --no-chdir --log-file=ovsdb-server2.log --pidfile=pid2 --remote=punix:db2.sock --unixctl=unixctl2 db2], [0], [ignore], [ignore])
+-on_exit 'test ! -e pid2 || kill `cat pid2`'
++AT_CHECK([ovsdb-server --detach --no-chdir --log-file=ovsdb-server2.log --pidfile=2.pid --remote=punix:db2.sock --unixctl=unixctl2 db2], [0], [ignore], [ignore])
+ 
+ dnl Try to connect without specifying the active server.
+ AT_CHECK([ovs-appctl -t "`pwd`"/unixctl2 ovsdb-server/connect-active-ovsdb-server], [0],
+@@ -1783,6 +1772,7 @@ AT_CLEANUP
+ 
+ #ovsdb-server/disconnect-active-server command
+ AT_SETUP([ovsdb-server/disconnect-active-server])
++on_exit 'kill `cat *.pid`'
+ AT_KEYWORDS([ovsdb server replication disconnect-active-server])
+ AT_SKIP_IF([test $DIFF_SUPPORTS_NORMAL_FORMAT = no])
+ 
+@@ -1791,10 +1781,8 @@ AT_CHECK([ovsdb-tool create db1 schema], [0], [stdout], [ignore])
+ AT_CHECK([ovsdb-tool create db2 schema], [0], [stdout], [ignore])
+ 
+ AT_CHECK([ovsdb-server --detach --no-chdir --log-file=ovsdb-server1.log --pidfile --remote=punix:db.sock db1], [0], [ignore], [ignore])
+-on_exit 'test ! -e pid || kill `cat pid`'
+ 
+-AT_CHECK([ovsdb-server --detach --no-chdir --log-file=ovsdb-server2.log --pidfile=pid2 --remote=punix:db2.sock --unixctl=unixctl2 --sync-from=unix:db.sock db2], [0], [ignore], [ignore])
+-on_exit 'test ! -e pid2 || kill `cat pid2`'
++AT_CHECK([ovsdb-server --detach --no-chdir --log-file=ovsdb-server2.log --pidfile=2.pid --remote=punix:db2.sock --unixctl=unixctl2 --sync-from=unix:db.sock db2], [0], [ignore], [ignore])
+ 
+ AT_CHECK([ovsdb-client transact unix:db.sock \
+ '[["mydb",
+@@ -1840,7 +1828,7 @@ AT_CHECK([uuidfilt output], [0], [7,9c7,8
+ ---
+ > _uuid name number
+ > ----- ---- ------
+-], [ignore], [test ! -e pid || kill `cat pid`; test ! -e pid2 || kill `cat pid2`])
++], [ignore])
+ 
+ dnl The backup server now become active, and can accept write transactions.
+ AT_CHECK([ovsdb-client transact unix:db2.sock \
+@@ -1891,13 +1879,12 @@ dnl Start both 'db1' and 'db2' in backup mode. Let them backup from each
+ dnl other. This is not an supported operation state, but to simulate a start
+ dnl up condition where an HA manger can select which one to be an active
+ dnl server soon after.
+-AT_CHECK([ovsdb-server --detach --no-chdir --log-file=ovsdb-server1.log --pidfile="`pwd`"/pid --remote=punix:db.sock --unixctl="`pwd`"/unixctl db1 --sync-from=unix:db2.sock --active ], [0], [ignore], [ignore])
+-on_exit 'test ! -e pid || kill `cat pid`'
++on_exit 'kill `cat *.pid`'
++AT_CHECK([ovsdb-server --detach --no-chdir --log-file=ovsdb-server1.log --pidfile --remote=punix:db.sock --unixctl="`pwd`"/unixctl db1 --sync-from=unix:db2.sock --active ], [0], [ignore], [ignore])
+ 
+ AT_CHECK([ovs-appctl -t "`pwd`"/unixctl ovsdb-server/connect-active-ovsdb-server])
+ 
+-AT_CHECK([ovsdb-server --detach --no-chdir --log-file=ovsdb-server2.log --pidfile="`pwd`"/pid2 --remote=punix:db2.sock --unixctl="`pwd`"/unixctl2 --sync-from=unix:db.sock db2], [0], [ignore], [ignore])
+-on_exit 'test ! -e pid2 || kill `cat pid2`'
++AT_CHECK([ovsdb-server --detach --no-chdir --log-file=ovsdb-server2.log --pidfile=2.pid --remote=punix:db2.sock --unixctl="`pwd`"/unixctl2 --sync-from=unix:db.sock db2], [0], [ignore], [ignore])
+ 
+ dnl
+ dnl make sure both servers reached the replication state
+@@ -1965,8 +1952,8 @@ AT_CHECK([ovsdb-tool transact db \
+    "row": {"number": 9, "name": "nine"}}]]'], [0], [ignore], [ignore])
+ 
+ dnl Start 'db', then try to be a back up server of itself.
+-AT_CHECK([ovsdb-server --detach --no-chdir --log-file=ovsdb-server.log --pidfile="`pwd`"/pid --remote=punix:db.sock --unixctl="`pwd`"/unixctl db --sync-from=unix:db.sock --active ], [0], [ignore], [ignore])
+-on_exit 'test ! -e pid || kill `cat pid`'
++on_exit 'kill `cat *.pid`'
++AT_CHECK([ovsdb-server --detach --no-chdir --log-file=ovsdb-server.log --pidfile --remote=punix:db.sock --unixctl="`pwd`"/unixctl db --sync-from=unix:db.sock --active ], [0], [ignore], [ignore])
+ 
+ dnl Save the current content
+ AT_CHECK([ovsdb-client dump unix:db.sock], [0], [stdout])
+@@ -1984,6 +1971,7 @@ AT_CHECK([diff dump1 dump2])
+ AT_CLEANUP
+ 
+ AT_SETUP([ovsdb-server/read-only db:ptcp connection])
++on_exit 'kill `cat *.pid`'
+ AT_KEYWORDS([ovsdb server read-only])
+ AT_DATA([schema],
+   [[{"name": "mydb",
+@@ -2072,12 +2060,10 @@ AT_CHECK([ovsdb-tool transact db2 \
+    "row": {"number": 10, "name": "ten"}}]]'], [0], [ignore], [ignore])
+ 
+ dnl Start both 'db1' and 'db2'.
+-AT_CHECK([ovsdb-server --detach --no-chdir --log-file=ovsdb-server1.log --pidfile="`pwd`"/pid --remote=punix:db.sock --unixctl="`pwd`"/unixctl db1 --active ], [0], [ignore], [ignore])
+-on_exit 'test ! -e pid || kill `cat pid`'
+-
++on_exit 'kill `cat *.pid`'
++AT_CHECK([ovsdb-server --detach --no-chdir --log-file=ovsdb-server1.log --pidfile --remote=punix:db.sock --unixctl="`pwd`"/unixctl db1 --active ], [0], [ignore], [ignore])
+ 
+-AT_CHECK([ovsdb-server --detach --no-chdir --log-file=ovsdb-server2.log --pidfile="`pwd`"/pid2 --remote=punix:db2.sock --unixctl="`pwd`"/unixctl2 db2], [0], [ignore], [ignore])
+-on_exit 'test ! -e pid2 || kill `cat pid2`'
++AT_CHECK([ovsdb-server --detach --no-chdir --log-file=ovsdb-server2.log --pidfile=2.pid --remote=punix:db2.sock --unixctl="`pwd`"/unixctl2 db2], [0], [ignore], [ignore])
+ 
+ OVS_WAIT_UNTIL([ovs-appctl -t "`pwd`"/unixctl ovsdb-server/sync-status |grep active])
+ OVS_WAIT_UNTIL([ovs-appctl -t "`pwd`"/unixctl2 ovsdb-server/sync-status |grep active])
+@@ -2177,7 +2163,7 @@ dnl Starting a dummy server only to reserve some tcp port.
+ AT_CHECK([cp db db.tmp])
+ AT_CHECK([ovsdb-server -vfile -vvlog:off --log-file=listener.log  dnl
+             --detach --no-chdir                                   dnl
+-            --pidfile=pid2 --unixctl=unixctl2                     dnl
++            --pidfile=2.pid --unixctl=unixctl2                    dnl
+             --remote=ptcp:0:127.0.0.1                             dnl
+             db.tmp], [0], [stdout], [stderr])
+ PARSE_LISTENING_PORT([listener.log], [BAD_TCP_PORT])
 diff --git a/tests/pmd.at b/tests/pmd.at
-index a2f9d34a2a..0a451f33c6 100644
+index a2f9d34a2a..3962dd2bd9 100644
 --- a/tests/pmd.at
 +++ b/tests/pmd.at
 @@ -199,7 +199,7 @@ pmd thread numa_id <cleared> core_id <cleared>:
@@ -5998,6 +10047,40 @@ index a2f9d34a2a..0a451f33c6 100644
  AT_SETUP([PMD - stats])
  OVS_VSWITCHD_START([add-port br0 p0 -- set Interface p0 ofport_request=7 type=dummy-pmd options:n_rxq=4],
                     [], [], [DUMMY_NUMA])
+@@ -1075,15 +1113,15 @@ AT_SETUP([PMD - dpif configuration])
+ OVS_VSWITCHD_START([], [], [], [--dummy-numa 0,0])
+ AT_CHECK([ovs-vsctl add-port br0 p1 -- set Interface p1 type=dummy-pmd])
+ 
++AT_CHECK([ovs-appctl dpif-netdev/dpif-impl-set dpif_scalar], [0], [dnl
++DPIF implementation set to dpif_scalar.
++])
++
+ AT_CHECK([ovs-vsctl show], [], [stdout])
+ AT_CHECK([ovs-appctl dpif-netdev/dpif-impl-get | grep "dpif_scalar"], [], [dnl
+   dpif_scalar (pmds: 0)
+ ])
+ 
+-AT_CHECK([ovs-appctl dpif-netdev/dpif-impl-set dpif_scalar], [0], [dnl
+-DPIF implementation set to dpif_scalar.
+-])
+-
+ OVS_VSWITCHD_STOP
+ AT_CLEANUP
+ 
+@@ -1092,13 +1130,6 @@ OVS_VSWITCHD_START([], [], [], [--dummy-numa 0,0])
+ AT_CHECK([ovs-vsctl add-port br0 p1 -- set Interface p1 type=dummy-pmd])
+ 
+ AT_CHECK([ovs-vsctl show], [], [stdout])
+-AT_CHECK([ovs-appctl dpif-netdev/subtable-lookup-prio-get | grep generic], [], [dnl
+-  1 : generic
+-])
+-
+-AT_CHECK([ovs-appctl dpif-netdev/subtable-lookup-prio-get | grep autovalidator], [], [dnl
+-  0 : autovalidator
+-])
+ 
+ AT_CHECK([ovs-appctl dpif-netdev/subtable-lookup-prio-set autovalidator 3], [0], [dnl
+ Lookup priority change affected 0 dpcls ports and 0 subtables.
 diff --git a/tests/reconnect.at b/tests/reconnect.at
 index 0f74709f5a..5bca84351c 100644
 --- a/tests/reconnect.at
@@ -6250,7 +10333,7 @@ index 1714273e35..270956d13f 100644
  dnl Delete ip address.
  AT_CHECK([ip addr del 10.0.0.17/24 dev p1-route], [0], [stdout])
 diff --git a/tests/system-traffic.at b/tests/system-traffic.at
-index f22d86e466..1d20366280 100644
+index f22d86e466..36e10aa4a8 100644
 --- a/tests/system-traffic.at
 +++ b/tests/system-traffic.at
 @@ -218,6 +218,7 @@ OVS_TRAFFIC_VSWITCHD_STOP
@@ -6465,6 +10548,139 @@ index f22d86e466..1d20366280 100644
      echo Request $i
      NS_CHECK_EXEC([at_ns1], [wget 10.1.1.64 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
  done
+@@ -6743,6 +6807,132 @@ AT_CHECK([ovs-ofctl dump-flows br0 | grep table=2, | OFPROTO_CLEAR_DURATION_IDLE
+ OVS_TRAFFIC_VSWITCHD_STOP
+ AT_CLEANUP
+ 
++AT_SETUP([conntrack - can match and clear ct_state from outside OVS])
++CHECK_CONNTRACK_LOCAL_STACK()
++OVS_CHECK_TUNNEL_TSO()
++OVS_CHECK_GENEVE()
++
++OVS_TRAFFIC_VSWITCHD_START()
++ADD_BR([br-underlay], [set bridge br-underlay other-config:hwaddr=\"f0:00:00:01:01:02\"])
++
++AT_CHECK([ovs-ofctl add-flow br0 "actions=normal"])
++AT_CHECK([ovs-ofctl add-flow br-underlay "priority=100,ct_state=+trk,actions=ct_clear,resubmit(,0)"])
++AT_CHECK([ovs-ofctl add-flow br-underlay "priority=10,actions=normal"])
++
++ADD_NAMESPACES(at_ns0)
++
++dnl Set up underlay link from host into the namespace using veth pair.
++ADD_VETH(p0, at_ns0, br-underlay, "172.31.1.1/24", "f0:00:00:01:01:01")
++AT_CHECK([ip addr add dev br-underlay "172.31.1.100/24"])
++AT_CHECK([ip link set dev br-underlay up])
++
++dnl Set up tunnel endpoints on OVS outside the namespace and with a native
++dnl linux device inside the namespace.
++ADD_OVS_TUNNEL([geneve], [br0], [at_gnv0], [172.31.1.1], [10.1.1.100/24])
++ADD_NATIVE_TUNNEL([geneve], [ns_gnv0], [at_ns0], [172.31.1.100], [10.1.1.1/24],
++                  [vni 0])
++
++dnl First, check the underlay
++NS_CHECK_EXEC([at_ns0], [ping -q -c 3 -i 0.3 -w 2 172.31.1.100 | FORMAT_PING], [0], [dnl
++3 packets transmitted, 3 received, 0% packet loss, time 0ms
++])
++
++dnl Okay, now check the overlay
++NS_CHECK_EXEC([at_ns0], [ping -q -c 3 -i 0.3 -w 2 10.1.1.100 | FORMAT_PING], [0], [dnl
++3 packets transmitted, 3 received, 0% packet loss, time 0ms
++])
++
++dnl Confirm that the ct_state and ct_clear action found its way to the dp
++AT_CHECK([ovs-appctl dpctl/dump-flows --names | grep ct_clear | sort | dnl
++          grep 'eth(src=f0:00:00:01:01:02,dst=f0:00:00:01:01:01)' | dnl
++          strip_stats | strip_used | dnl
++          sed 's/,packet_type(ns=[[0-9]]*,id=[[0-9]]*),/,/'],
++                     [0], [dnl
++recirc_id(0),in_port(br-underlay),ct_state(+trk),eth(src=f0:00:00:01:01:02,dst=f0:00:00:01:01:01),eth_type(0x0800),ipv4(frag=no), packets:0, bytes:0, used:0.0s, actions:ct_clear,ovs-p0
++])
++
++OVS_TRAFFIC_VSWITCHD_STOP
++AT_CLEANUP
++
++AT_BANNER([IGMP])
++
++AT_SETUP([IGMP - flood under normal action])
++
++OVS_TRAFFIC_VSWITCHD_START()
++ADD_NAMESPACES(at_ns0, at_ns1)
++
++ADD_VETH(p1, at_ns0, br0, "10.1.1.1/24", "f0:00:00:01:01:01")
++ADD_VETH(p2, at_ns1, br0, "10.1.1.2/24", "f0:00:00:01:01:02")
++
++AT_CHECK([ovs-ofctl add-flow br0 "actions=NORMAL"])
++
++NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p1 01 00 5e 01 01 03 dnl
++f0 00 00 01 01 01 08 00 46 c0 00 28 00 00 40 00 01 02 d3 49 45 65 eb 4a e0 dnl
++00 00 16 94 04 00 00 22 00 f9 02 00 00 00 01 04 00 00 00 e0 00 00 fb 00 00 dnl
++00 00 00 00 > /dev/null])
++
++AT_CHECK([ovs-appctl dpctl/dump-flows --names | grep -e .*ipv4 | sort | dnl
++          strip_stats | strip_used | strip_recirc | dnl
++          sed 's/,packet_type(ns=[[0-9]]*,id=[[0-9]]*),/,/'],
++                     [0], [dnl
++recirc_id(<recirc>),in_port(ovs-p1),eth(src=f0:00:00:01:01:01,dst=01:00:5e:01:01:03),eth_type(0x0800),ipv4(frag=no), packets:0, bytes:0, used:never, actions:br0,ovs-p2
++])
++OVS_TRAFFIC_VSWITCHD_STOP
++AT_CLEANUP
++
++AT_SETUP([IGMP - forward with ICMP])
++
++OVS_TRAFFIC_VSWITCHD_START()
++ADD_NAMESPACES(at_ns0, at_ns1)
++
++ADD_VETH(p1, at_ns0, br0, "10.1.1.1/24", "f0:00:00:01:01:01")
++ADD_VETH(p2, at_ns1, br0, "10.1.1.2/24", "f0:00:00:01:01:02")
++
++AT_DATA([flows.txt], [dnl
++table=0, arp actions=NORMAL
++table=0, ip,in_port=1 actions=ct(table=1,zone=64000)
++table=0, in_port=2 actions=output:1
++table=1, ip,ct_state=+trk+inv actions=drop
++table=1  ip,in_port=1,icmp,ct_state=+trk+new actions=output:2
++table=1, in_port=1,ip,ct_state=+trk+new actions=controller(userdata=00.de.ad.be.ef.ca.fe.01)
++table=1, in_port=1,ip,ct_state=+trk+est actions=output:2
++])
++AT_CHECK([ovs-ofctl del-flows br0])
++AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
++
++dnl Send the IGMP, followed by a unicast ICMP - ensure we won't black hole
++
++NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p1 f0 00 00 01 01 02 dnl
++f0 00 00 01 01 01 08 00 46 c0 00 28 00 00 40 00 01 02 d3 49 45 65 eb 4a e0 dnl
++00 00 16 94 04 00 00 22 00 f9 02 00 00 00 01 04 00 00 00 e0 00 00 fb 00 00 dnl
++00 00 00 00 > /dev/null])
++
++NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p1 f0 00 00 01 01 02 dnl
++f0 00 00 01 01 01 08 00 45 00 00 1c 00 01 00 00 40 01 64 dc 0a 01 01 01 0a dnl
++01 01 02 08 00 f7 ff ff ff ff ff > /dev/null])
++
++sleep 1
++
++dnl Prefer the OpenFlow rules, because different datapaths will behave slightly
++dnl differently with respect to the exact dp rules.
++dnl
++dnl This is also why we clear n_bytes / n_packets - some kernels with ipv6
++dnl enabled will bump some of these counters non-deterministically
++
++AT_CHECK([ovs-ofctl dump-flows br0 | grep -v NXST | dnl
++          strip_duration | grep -v arp | grep -v n_packets=0 | dnl
++          grep -v 'in_port=2 actions=output:1' | dnl
++          sed 's/n_bytes=[[0-9]]*/n_bytes=0/
++               s/idle_age=[[0-9]]*/idle_age=0/
++               s/n_packets=[[1-9]]/n_packets=0/' | sort], [0], [dnl
++ cookie=0x0,  table=0, n_packets=0, n_bytes=0, idle_age=0, ip,in_port=1 actions=ct(table=1,zone=64000)
++ cookie=0x0,  table=1, n_packets=0, n_bytes=0, idle_age=0, ct_state=+new+trk,icmp,in_port=1 actions=output:2
++ cookie=0x0,  table=1, n_packets=0, n_bytes=0, idle_age=0, ct_state=+new+trk,ip,in_port=1 actions=controller(userdata=00.de.ad.be.ef.ca.fe.01)
++])
++
++OVS_TRAFFIC_VSWITCHD_STOP
++AT_CLEANUP
++
+ AT_BANNER([802.1ad])
+ 
+ AT_SETUP([802.1ad - vlan_limit])
 diff --git a/tests/system-tso-macros.at b/tests/system-tso-macros.at
 index 406334f3e0..1a80047619 100644
 --- a/tests/system-tso-macros.at
@@ -6521,6 +10737,20 @@ index 0705475606..588a5dea63 100644
          hmap_remove(&hmap, &e->node);
      }
      hmap_destroy(&hmap);
+diff --git a/tests/test-hash.c b/tests/test-hash.c
+index 5d3f8ea43f..aec5f580bb 100644
+--- a/tests/test-hash.c
++++ b/tests/test-hash.c
+@@ -55,6 +55,9 @@ set_bit128(ovs_u128 *values, int bit, int n_bits)
+ static uint64_t
+ get_range128(ovs_u128 *value, int ofs, uint64_t mask)
+ {
++    if (ofs == 0) {
++        return value->u64.lo & mask;
++    }
+     return ((ofs < 64 ? (value->u64.lo >> ofs) : 0) & mask)
+         | ((ofs <= 64 ? (value->u64.hi << (64 - ofs)) : (value->u64.hi >> (ofs - 64)) & mask));
+ }
 diff --git a/tests/test-hindex.c b/tests/test-hindex.c
 index af06be5fcc..cc2b1b8bd9 100644
 --- a/tests/test-hindex.c
@@ -6732,6 +10962,86 @@ index 6f1fb059bc..2c6c444488 100644
          }
      }
  }
+diff --git a/tests/test-rcu.c b/tests/test-rcu.c
+index 965f3c49f3..bb17092bf0 100644
+--- a/tests/test-rcu.c
++++ b/tests/test-rcu.c
+@@ -35,7 +35,7 @@ quiescer_main(void *aux OVS_UNUSED)
+ }
+ 
+ static void
+-test_rcu_quiesce(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
++test_rcu_quiesce(void)
+ {
+     pthread_t quiescer;
+ 
+@@ -48,4 +48,29 @@ test_rcu_quiesce(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+     xpthread_join(quiescer, NULL);
+ }
+ 
+-OVSTEST_REGISTER("test-rcu-quiesce", test_rcu_quiesce);
++static void
++add_count(void *_count)
++{
++    unsigned *count = (unsigned *)_count;
++    (*count) ++;
++}
++
++static void
++test_rcu_barrier(void)
++{
++    unsigned count = 0;
++    for (int i = 0; i < 10; i ++) {
++        ovsrcu_postpone(add_count, &count);
++    }
++
++    ovsrcu_barrier();
++    ovs_assert(count == 10);
++}
++
++static void
++test_rcu(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) {
++    test_rcu_quiesce();
++    test_rcu_barrier();
++}
++
++OVSTEST_REGISTER("test-rcu", test_rcu);
+diff --git a/tests/test-util.c b/tests/test-util.c
+index f0fd042108..7d899fbbfd 100644
+--- a/tests/test-util.c
++++ b/tests/test-util.c
+@@ -43,17 +43,16 @@ check_log_2_floor(uint32_t x, int n)
+ static void
+ test_log_2_floor(struct ovs_cmdl_context *ctx OVS_UNUSED)
+ {
+-    int n;
+-
+-    for (n = 0; n < 32; n++) {
++    for (uint32_t n = 0; n < 32; n++) {
+         /* Check minimum x such that f(x) == n. */
+-        check_log_2_floor(1 << n, n);
++        check_log_2_floor(UINT32_C(1) << n, n);
+ 
+         /* Check maximum x such that f(x) == n. */
+-        check_log_2_floor((1 << n) | ((1 << n) - 1), n);
++        check_log_2_floor((UINT32_C(1) << n) | ((UINT32_C(1) << n) - 1), n);
+ 
+         /* Check a random value in the middle. */
+-        check_log_2_floor((random_uint32() & ((1 << n) - 1)) | (1 << n), n);
++        check_log_2_floor((random_uint32() & ((UINT32_C(1) << n) - 1))
++                          | (UINT32_C(1) << n), n);
+     }
+ 
+     /* log_2_floor(0) is undefined, so don't check it. */
+@@ -86,7 +85,7 @@ test_ctz(struct ovs_cmdl_context *ctx OVS_UNUSED)
+ 
+     for (n = 0; n < 32; n++) {
+         /* Check minimum x such that f(x) == n. */
+-        check_ctz32(1 << n, n);
++        check_ctz32(UINT32_C(1) << n, n);
+ 
+         /* Check maximum x such that f(x) == n. */
+         check_ctz32(UINT32_MAX << n, n);
 diff --git a/tests/tunnel-push-pop.at b/tests/tunnel-push-pop.at
 index 57589758f4..c63344196b 100644
 --- a/tests/tunnel-push-pop.at
@@ -6844,6 +11154,23 @@ index b8ae7caa9b..fd482aa872 100644
  ])
  
  OVS_VSWITCHD_STOP
+diff --git a/utilities/gdb/ovs_gdb.py b/utilities/gdb/ovs_gdb.py
+index 62928d50fc..763ece2a78 100644
+--- a/utilities/gdb/ovs_gdb.py
++++ b/utilities/gdb/ovs_gdb.py
+@@ -1391,7 +1391,8 @@ class CmdDumpPackets(gdb.Command):
+             print("Error, unsupported argument type: {}".format(str(val.type)))
+             return
+ 
+-        tcpdump(pkt_list, args=tcpdump_args)
++        stdout = tcpdump(pkt_list, args=tcpdump_args, getfd=True, quiet=True)
++        gdb.write(stdout.read().decode("utf8", "replace"))
+ 
+     def extract_pkt(self, pkt):
+         pkt_fields = pkt.type.keys()
+diff --git a/utilities/ovs-appctl-bashcomp.bash b/utilities/ovs-appctl-bashcomp.bash
+old mode 100755
+new mode 100644
 diff --git a/utilities/ovs-ofctl.c b/utilities/ovs-ofctl.c
 index ede7f1e61a..6771973ae9 100644
 --- a/utilities/ovs-ofctl.c
@@ -6863,6 +11190,63 @@ index ede7f1e61a..6771973ae9 100644
          const struct ofp_header *error_oh = error->data;
          ovs_be32 error_xid = error_oh->xid;
          enum ofperr ofperr;
+diff --git a/utilities/ovs-save b/utilities/ovs-save
+index fb2025b765..a190902f4d 100755
+--- a/utilities/ovs-save
++++ b/utilities/ovs-save
+@@ -102,7 +102,7 @@ save_interfaces () {
+ get_highest_ofp_version() {
+     ovs-vsctl get bridge "$1" protocols | \
+         sed 's/[][]//g' | sed 's/\ //g' | \
+-            awk -F ',' '{ print (NF>1)? $(NF) : "OpenFlow14" }'
++            awk -F ',' '{ print (NF>0)? $(NF) : "OpenFlow14" }'
+ }
+ 
+ save_flows () {
+diff --git a/utilities/ovs-tcpdump.in b/utilities/ovs-tcpdump.in
+index 82d1bedfa6..7fd26e4055 100755
+--- a/utilities/ovs-tcpdump.in
++++ b/utilities/ovs-tcpdump.in
+@@ -165,6 +165,9 @@ class OVSDB(object):
+         self._idl_conn = idl.Idl(db_sock, schema)
+         OVSDB.wait_for_db_change(self._idl_conn)  # Initial Sync with DB
+ 
++    def close_idl(self):
++        self._idl_conn.close()
++
+     def _get_schema(self):
+         error, strm = Stream.open_block(Stream.open(self._db_sock))
+         if error:
+@@ -403,7 +406,8 @@ def py_which(executable):
+ 
+ 
+ def main():
+-    db_sock = 'unix:@RUNDIR@/db.sock'
++    rundir = os.environ.get('OVS_RUNDIR', '@RUNDIR@')
++    db_sock = 'unix:%s' % os.path.join(rundir, "db.sock")
+     interface = None
+     tcpdargs = []
+ 
+@@ -500,6 +504,8 @@ def main():
+             pass
+         sys.exit(1)
+ 
++    ovsdb.close_idl()
++
+     pipes = _doexec(*([dump_cmd, '-i', mirror_interface] + tcpdargs))
+     try:
+         while pipes.poll() is None:
+@@ -512,6 +518,7 @@ def main():
+         if pipes.poll() is None:
+             pipes.terminate()
+ 
++        ovsdb = OVSDB(db_sock)
+         ovsdb.destroy_mirror(interface, ovsdb.port_bridge(interface))
+         ovsdb.destroy_port(mirror_interface, ovsdb.port_bridge(interface))
+         if tap_created is True:
+diff --git a/utilities/ovs-vsctl-bashcomp.bash b/utilities/ovs-vsctl-bashcomp.bash
+old mode 100755
+new mode 100644
 diff --git a/utilities/ovs-vsctl.c b/utilities/ovs-vsctl.c
 index 37cc72d401..1032089fc2 100644
 --- a/utilities/ovs-vsctl.c
@@ -7286,3 +11670,23 @@ index ab552457d9..99c4adcd53 100644
          del_port(vtepctl_ctx, port);
      }
  
+diff --git a/xenserver/openvswitch-xen.spec.in b/xenserver/openvswitch-xen.spec.in
+index 4d21c6364f..ae22f2f5c4 100644
+--- a/xenserver/openvswitch-xen.spec.in
++++ b/xenserver/openvswitch-xen.spec.in
+@@ -457,6 +457,7 @@ exit 0
+ /usr/share/openvswitch/scripts/ovs-lib
+ /usr/share/openvswitch/scripts/ovs-vtep
+ /usr/share/openvswitch/vswitch.ovsschema
++/usr/share/openvswitch/local-config.ovsschema
+ /usr/share/openvswitch/vtep.ovsschema
+ /usr/sbin/ovs-bugtool
+ /usr/sbin/ovs-vswitchd
+@@ -479,6 +480,7 @@ exit 0
+ /usr/share/man/man1/ovsdb-client.1.gz
+ /usr/share/man/man1/ovsdb-server.1.gz
+ /usr/share/man/man1/ovsdb-tool.1.gz
++/usr/share/man/man5/ovsdb.local-config.5.gz
+ /usr/share/man/man5/ovsdb-server.5.gz
+ /usr/share/man/man5/ovs-vswitchd.conf.db.5.gz
+ /usr/share/man/man5/vtep.5.gz
diff --git a/SPECS/openvswitch2.17.spec b/SPECS/openvswitch2.17.spec
index edfb655..3af1e7f 100644
--- a/SPECS/openvswitch2.17.spec
+++ b/SPECS/openvswitch2.17.spec
@@ -57,7 +57,7 @@ Summary: Open vSwitch
 Group: System Environment/Daemons daemon/database/utilities
 URL: http://www.openvswitch.org/
 Version: 2.17.0
-Release: 15%{?dist}
+Release: 31%{?dist}
 
 # Nearly all of openvswitch is ASL 2.0.  The bugtool is LGPLv2+, and the
 # lib/sflow*.[ch] files are SISSL
@@ -155,7 +155,7 @@ BuildRequires: meson
 BuildRequires: python3-pyelftools
 %endif
 # DPDK driver dependencies
-BuildRequires: zlib-devel numactl-devel
+BuildRequires: zlib-devel numactl-devel libarchive-devel
 %ifarch x86_64
 BuildRequires: rdma-core-devel >= 15 libmnl-devel
 %endif
@@ -684,6 +684,7 @@ exit 0
 %{_datadir}/openvswitch/scripts/ovs-ctl
 %{_datadir}/openvswitch/scripts/ovs-kmod-ctl
 %{_datadir}/openvswitch/scripts/ovs-systemd-reload
+%config %{_datadir}/openvswitch/local-config.ovsschema
 %config %{_datadir}/openvswitch/vswitch.ovsschema
 %config %{_datadir}/openvswitch/vtep.ovsschema
 %{_bindir}/ovs-appctl
@@ -701,6 +702,7 @@ exit 0
 %{_mandir}/man1/ovsdb-server.1*
 %{_mandir}/man1/ovsdb-tool.1*
 %{_mandir}/man5/ovsdb.5*
+%{_mandir}/man5/ovsdb.local-config.5*
 %{_mandir}/man5/ovsdb-server.5.*
 %{_mandir}/man5/ovs-vswitchd.conf.db.5*
 %{_mandir}/man5/vtep.5*
@@ -746,6 +748,140 @@ exit 0
 %endif
 
 %changelog
+* Thu Jul 14 2022 Open vSwitch CI <ovs-ci@redhat.com> - 2.17.0-31
+- Merging upstream branch-2.17 [RH git: bdc6c6696c]
+    Commit list:
+    13ac0bc7c6 tc: Fix misaligned access while creating pedit actions.
+    2c85d737a4 utilities/bashcomp: Fix incorrect file mode.
+
+
+* Wed Jul 06 2022 Timothy Redaelli <tredaelli@redhat.com> - 2.17.0-30
+- rhel: libarchive-devel is needed for DPDK to load compressed firmwares [RH git: 95331d366d]
+    Reported-by: David Marchand (david.marchand@redhat.com)
+
+
+* Fri Jul 01 2022 Open vSwitch CI <ovs-ci@redhat.com> - 2.17.0-29
+- Merging upstream branch-2.17 [RH git: d3c723a17e]
+    Commit list:
+    05e9d2b7a9 Pmd.at: fix dpcls and dpif configuration test cases.
+
+
+* Thu Jun 30 2022 Ilya Maximets <i.maximets@redhat.com> - 2.17.0-28
+- Merging upstream branch-2.17 [RH git: 9738f7f756]
+    Commit list:
+    45ecaa9e57 ovsdb: Add Local_Config schema.
+    
+    Merge:
+     redhat/template.spec.in updated with new files.
+    
+    Signed-off-by: Ilya Maximets <i.maximets@redhat.com>
+
+
+* Wed Jun 29 2022 Open vSwitch CI <ovs-ci@redhat.com> - 2.17.0-27
+- Merging upstream branch-2.17 [RH git: 62ee0c2ec8]
+    Commit list:
+    61d64d3899 dpif-netdev: Fix leak of AVX512 DPIF scratch pad.
+
+
+* Wed Jun 29 2022 Timothy Redaelli <tredaelli@redhat.com> - 2.17.0-26
+- Fix REPO_URL [RH git: cf6a18b2bd]
+
+
+* Wed Jun 29 2022 Open vSwitch CI <ovs-ci@redhat.com> - 2.17.0-25
+- Merging upstream branch-2.17 [RH git: 27d62d7d3f]
+    Commit list:
+    a77ad9693c dpif-netdev: Refactor AVX512 runtime checks. (#2100393)
+
+
+* Tue Jun 28 2022 Open vSwitch CI <ovs-ci@redhat.com> - 2.17.0-24
+- Merging upstream branch-2.17 [RH git: f0e25fe3e6]
+    Commit list:
+    ccea7df578 dpif-netdev-extract-avx512: Protect GCC builtin usage.
+    807f7f994a ovs-tcpdump: Default to OVS_RUNDIR if present.
+    ec13b03ca3 ovsdb: Fix memory leak on error path in ovsdb_file_read__().
+    8b2dff2e34 odp-util: Ignore unknown attributes in parse_key_and_mask_to_match(). (#2089331)
+    13d97f6637 ofproto-dpif: Avoid unneccesary backer revalidation.
+    9b4035d699 lldp: Fix lldp memory leak.
+    d9351febc2 ipfix: Trigger revalidation if ipfix options changes.
+    5419b1de93 conntrack: Fix incorrect bit shift while hashing nat range.
+    1ab5f94a11 packets: Fix misaligned write to MPLS lse.
+    8e00be03c7 tc: Fix misaligned access to stats and time values.
+    3a1f5341ca odp-util: Fix unaligned access to tunnel id.
+    0c54c43b89 ofpbuf: Fix offsetting a NULL pointer in ofpbuf_reserve.
+    98edacb40c drop-stats.at: Fix frequent failures of the recursion too deep test.
+    cbc13ce4f7 odp_util: Fix parse_key_and_mask_to_match() vlan parsing.
+
+
+* Tue Jun 21 2022 Open vSwitch CI <ovs-ci@redhat.com> - 2.17.0-23
+- Merging upstream branch-2.17 [RH git: e2e0aac349]
+    Commit list:
+    73e6ce4925 Prepare for 2.17.3.
+    95979b0f0d Set release date for 2.17.2.
+
+
+* Tue Jun 07 2022 Open vSwitch CI <ovs-ci@redhat.com> - 2.17.0-22
+- Merging upstream branch-2.17 [RH git: ce91947e61]
+    Commit list:
+    250e1a6dd2 ofproto-dpif-xlate: Fix internal CT state for non-recirc traffic.
+    fe870ee072 classifier: Adjust segment boundary to execute prerequisite processing. (#2081773)
+    ec0ec464ba ovs-tcpdump: Fix error when stopping ovs-tcpdump.
+
+
+* Tue May 31 2022 Open vSwitch CI <ovs-ci@redhat.com> - 2.17.0-21
+- Merging upstream branch-2.17 [RH git: 6ff800a303]
+    Commit list:
+    420823e2af ofproto-dpif: Fix meter use-after-free.
+    c762da2623 ovs-rcu: Add ovsrcu_barrier.
+    cd9b6b64f4 dpif-netdev: Fix ALB 'rebalance_intvl' max hard limit.
+    64f6c49d25 dpif-netdev: Fix ALB parameters type mismatch.
+    b11b84ea7f dpdk: Use DPDK 21.11.1 release.
+    d3bf48e9a9 raft: Don't use HMAP_FOR_EACH_SAFE when logging commands.
+
+
+* Thu May 26 2022 Open vSwitch CI <ovs-ci@redhat.com> - 2.17.0-20
+- Merging upstream branch-2.17 [RH git: 77f2886b02]
+    Commit list:
+    e07377bb49 ovsdb: raft: Fix transaction double commit due to lost leadership. (#2046340)
+    5da86cb360 dynamic-string: Fix undefined behavior due to offsetting null pointer.
+    369e688908 Revert "odp-util: Always report ODP_FIT_TOO_LITTLE for IGMP."
+    18341166ed ofproto-dpif-xlate: Fix netdev native tunnel neigh discovery spa.
+    748e4b2b5b ovs-router: Expose the ovs_router_get_netdev_source_address function.
+    34390bb35c ofproto-dpif: Trigger revalidation if ct tp changes.
+
+
+* Wed May 25 2022 Open vSwitch CI <ovs-ci@redhat.com> - 2.17.0-19
+- Merging upstream branch-2.17 [RH git: 993b9ca4b4]
+    Commit list:
+    1adb07e206 Carefully release NBL in Windows
+
+
+* Thu May 19 2022 Open vSwitch CI <ovs-ci@redhat.com> - 2.17.0-18
+- Merging upstream branch-2.17 [RH git: 868b675dfd]
+    Commit list:
+    1ccaba4484 tests: Properly kill ovsdb test processes.
+    260b091c2a ovs-save: Get highest ofp version error.
+    7606bb1210 netdev-linux: Properly access 32-bit aligned rtnl_link_stats64 structs.
+    0688b9f27d treewide: Avoid offsetting NULL pointers.
+    92bcf0a823 treewide: Fix invalid bit shift operations.
+
+
+* Wed May 04 2022 Open vSwitch CI <ovs-ci@redhat.com> - 2.17.0-17
+- Merging upstream branch-2.17 [RH git: e16db3efbf]
+    Commit list:
+    7fa76371de utilities: Handle dumping packets in GDB TUI.
+    8cac8baa8f ofproto-dpif-xlate: Remove mirror assert.
+    e0e8f0c546 netdev-dpdk: Fix tx drops statistic for a down netdev.
+    f9b5f8a781 netdev-dpdk: Remove a leftover lock annotation.
+    4c3976ff2a netdev-dpdk: Refactor the DPDK transmit path.
+
+
+* Wed May 04 2022 Open vSwitch CI <ovs-ci@redhat.com> - 2.17.0-16
+- Merging upstream branch-2.17 [RH git: ca8c5adb3e]
+    Commit list:
+    410b97c839 netdev-offload-dpdk: Fix ethernet type for VLANs.
+    7948312feb netdev-offload-dpdk: Use has_vlan match attribute.
+
+
 * Mon May 02 2022 Open vSwitch CI <ovs-ci@redhat.com> - 2.17.0-15
 - Merging upstream branch-2.17 [RH git: e706ea8148]
     Commit list: