Blob Blame History Raw
diff --git a/.ci/dpdk-prepare.sh b/.ci/dpdk-prepare.sh
index f7e6215dda..4424f9eb97 100755
--- a/.ci/dpdk-prepare.sh
+++ b/.ci/dpdk-prepare.sh
@@ -8,4 +8,4 @@ set -ev
 #     https://github.com/pypa/pip/issues/10655
 pip3 install --disable-pip-version-check --user wheel
 pip3 install --disable-pip-version-check --user pyelftools
-pip3 install --user  'meson==0.53.2'
+pip3 install --user  'meson>=1.4,<1.5'
diff --git a/.ci/linux-build.sh b/.ci/linux-build.sh
index bf9d6241d5..702feeb3bb 100755
--- a/.ci/linux-build.sh
+++ b/.ci/linux-build.sh
@@ -25,7 +25,7 @@ function install_dpdk()
     export PKG_CONFIG_PATH=$DPDK_LIB/pkgconfig/:$PKG_CONFIG_PATH
 
     # Expose dpdk binaries.
-    export PATH=$(pwd)/dpdk-dir/build/bin:$PATH
+    export PATH=$(pwd)/dpdk-dir/bin:$PATH
 
     if [ ! -f "${VERSION_FILE}" ]; then
         echo "Could not find DPDK in $DPDK_INSTALL_DIR"
diff --git a/.ci/linux-prepare.sh b/.ci/linux-prepare.sh
index 5028bdc442..2a191b57fb 100755
--- a/.ci/linux-prepare.sh
+++ b/.ci/linux-prepare.sh
@@ -23,7 +23,7 @@ cd ..
 #     https://github.com/pypa/pip/issues/10655
 pip3 install --disable-pip-version-check --user wheel
 pip3 install --disable-pip-version-check --user \
-    flake8 'hacking>=3.0' netaddr pyparsing sarif-tools sphinx setuptools
+    flake8 netaddr pyparsing sarif-tools sphinx setuptools
 
 # Install python test dependencies
 pip3 install -r python/test_requirements.txt
diff --git a/.cirrus.yml b/.cirrus.yml
index d8a9722809..8db385f002 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -2,7 +2,7 @@ freebsd_build_task:
 
   freebsd_instance:
     matrix:
-      image_family: freebsd-13-2-snap
+      image_family: freebsd-13-3-snap
       image_family: freebsd-14-0-snap
     cpu: 4
     memory: 4G
diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml
index fc75581486..4a012efd94 100644
--- a/.github/workflows/build-and-test.yml
+++ b/.github/workflows/build-and-test.yml
@@ -2,13 +2,16 @@ name: Build and Test
 
 on: [push, pull_request]
 
+env:
+  python_default: 3.12
+
 jobs:
   build-dpdk:
     env:
       dependencies: gcc libbpf-dev libnuma-dev libpcap-dev ninja-build pkgconf
       CC: gcc
-      DPDK_GIT: https://dpdk.org/git/dpdk
-      DPDK_VER: 23.11
+      DPDK_GIT: https://dpdk.org/git/dpdk-stable
+      DPDK_VER: 23.11.1
     name: dpdk gcc
     outputs:
       dpdk_key: ${{ steps.gen_dpdk_key.outputs.key }}
@@ -54,7 +57,7 @@ jobs:
       if: steps.dpdk_cache.outputs.cache-hit != 'true'
       uses: actions/setup-python@v5
       with:
-        python-version: '3.9'
+        python-version: ${{ env.python_default }}
 
     - name: update APT cache
       if: steps.dpdk_cache.outputs.cache-hit != 'true'
@@ -217,7 +220,7 @@ jobs:
     - name: set up python
       uses: actions/setup-python@v5
       with:
-        python-version: '3.9'
+        python-version: ${{ env.python_default }}
 
     - name: cache
       if:   matrix.dpdk != '' || matrix.dpdk_shared != ''
@@ -238,6 +241,14 @@ jobs:
       if:   matrix.m32 != ''
       run:  sudo apt install -y gcc-multilib
 
+    - name: Reduce ASLR entropy
+      if:   matrix.sanitizers != ''
+      # Asan in llvm 14 provided in ubuntu-22.04 is incompatible with
+      # high-entropy ASLR configured in much newer kernels that GitHub
+      # runners are using leading to random crashes:
+      #   https://github.com/actions/runner-images/issues/9491
+      run: sudo sysctl -w vm.mmap_rnd_bits=28
+
     - name: prepare
       run:  ./.ci/linux-prepare.sh
 
@@ -346,7 +357,7 @@ jobs:
     - name: set up python
       uses: actions/setup-python@v5
       with:
-        python-version: '3.9'
+        python-version: ${{ env.python_default }}
 
     - name: get cached dpdk-dir
       uses: actions/cache/restore@v4
@@ -399,7 +410,7 @@ jobs:
     - name: set up python
       uses: actions/setup-python@v5
       with:
-        python-version: '3.9'
+        python-version: ${{ env.python_default }}
     - name: install dependencies
       run:  brew install automake libtool
     - name: prepare
diff --git a/AUTHORS.rst b/AUTHORS.rst
index aa9284fb16..80678854bd 100644
--- a/AUTHORS.rst
+++ b/AUTHORS.rst
@@ -588,6 +588,7 @@ David Evans                     davidjoshuaevans@gmail.com
 David Palma                     palma@onesource.pt
 David van Moolenbroek           dvmoolenbroek@aimvalley.nl
 Derek Cormier                   derek.cormier@lab.ntt.co.jp
+Derrick Lim                     derrick.lim@rakuten.com
 Dhaval Badiani                  dbadiani@vmware.com
 DK Moon
 Ding Zhi                        zhi.ding@6wind.com
diff --git a/Documentation/faq/releases.rst b/Documentation/faq/releases.rst
index 49b987b610..95ce0bf411 100644
--- a/Documentation/faq/releases.rst
+++ b/Documentation/faq/releases.rst
@@ -216,11 +216,11 @@ Q: What DPDK version does each Open vSwitch release work with?
     2.14.x       19.11.13
     2.15.x       20.11.6
     2.16.x       20.11.6
-    2.17.x       21.11.6
-    3.0.x        21.11.6
-    3.1.x        22.11.4
-    3.2.x        22.11.4
-    3.3.x        23.11
+    2.17.x       21.11.7
+    3.0.x        21.11.7
+    3.1.x        22.11.5
+    3.2.x        22.11.5
+    3.3.x        23.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 ad9bdf22c0..93c4a12e75 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 23.11
+- DPDK 23.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-23.11.tar.xz
-       $ tar xf dpdk-23.11.tar.xz
-       $ export DPDK_DIR=/usr/src/dpdk-23.11
+       $ wget https://fast.dpdk.org/rel/dpdk-23.11.1.tar.xz
+       $ tar xf dpdk-23.11.1.tar.xz
+       $ export DPDK_DIR=/usr/src/dpdk-stable-23.11.1
        $ cd $DPDK_DIR
 
 #. Configure and install DPDK using Meson
diff --git a/Documentation/intro/install/general.rst b/Documentation/intro/install/general.rst
index 19e360d47c..7eb3a5d370 100644
--- a/Documentation/intro/install/general.rst
+++ b/Documentation/intro/install/general.rst
@@ -176,10 +176,7 @@ following to obtain better warnings:
 
 - clang, version 3.4 or later
 
-- flake8 along with the hacking flake8 plugin (for Python code). The automatic
-  flake8 check that runs against Python code has some warnings enabled that
-  come from the "hacking" flake8 plugin. If it's not installed, the warnings
-  just won't occur until it's run on a system with "hacking" installed.
+- flake8 (for Python code)
 
 - the python packages listed in "python/test_requirements.txt" (compatible
   with pip). If they are installed, the pytest-based Python unit tests will
diff --git a/Documentation/intro/install/windows.rst b/Documentation/intro/install/windows.rst
index fce099d5dc..efdb8aebce 100644
--- a/Documentation/intro/install/windows.rst
+++ b/Documentation/intro/install/windows.rst
@@ -112,7 +112,7 @@ The following explains the steps in some detail.
   `OpenSSL for Windows <https://wiki.openssl.org/index.php/Binaries>`__
 
   Note down the directory where OpenSSL is installed (e.g.:
-  ``C:/OpenSSL-Win32``) for later use.
+  ``C:/OpenSSL-Win64``) for later use.
 
 .. note::
 
@@ -182,7 +182,7 @@ To configure with SSL support, add the requisite additional options:
        --localstatedir="C:/openvswitch/var"
        --sysconfdir="C:/openvswitch/etc" \
        --with-pthread="C:/pthread" \
-       --enable-ssl --with-openssl="C:/OpenSSL-Win32"
+       --enable-ssl --with-openssl="C:/OpenSSL-Win64"
 
 Finally, to the kernel module also:
 
@@ -194,7 +194,7 @@ Finally, to the kernel module also:
        --localstatedir="C:/openvswitch/var" \
        --sysconfdir="C:/openvswitch/etc" \
        --with-pthread="C:/pthread" \
-       --enable-ssl --with-openssl="C:/OpenSSL-Win32" \
+       --enable-ssl --with-openssl="C:/OpenSSL-Win64" \
        --with-vstudiotarget="<target type>" \
        --with-vstudiotargetver="<target versions>"
 
diff --git a/Makefile.am b/Makefile.am
index 94f488d183..0b7c832469 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -400,16 +400,10 @@ ALL_LOCAL += flake8-check
 #   F811 redefinition of unused <name> from line <N> (only from flake8 v2.0)
 # D*** -- warnings from flake8-docstrings plugin
 # H*** -- warnings from flake8 hacking plugin (custom style checks beyond PEP8)
-#   H231 Python 3.x incompatible 'except x,y:' construct
-#   H232 Python 3.x incompatible octal 077 should be written as 0o77
-#   H233 Python 3.x incompatible use of print operator
-#   H238 old style class declaration, use new style (inherit from `object`)
-FLAKE8_SELECT = H231,H232,H233,H238
 FLAKE8_IGNORE = E121,E123,E125,E126,E127,E128,E129,E131,E203,E722,W503,W504,F811,D,H,I
 flake8-check: $(FLAKE8_PYFILES)
 	$(FLAKE8_WERROR)$(AM_V_GEN) \
 	  src='$^' && \
-	  flake8 $$src --select=$(FLAKE8_SELECT) $(FLAKE8_FLAGS) && \
 	  flake8 $$src --ignore=$(FLAKE8_IGNORE) $(FLAKE8_FLAGS) && \
 	  touch $@
 endif
diff --git a/NEWS b/NEWS
index 8888fb3ec5..f62d06ffb6 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,12 @@
+v3.3.2 - xx xxx xxxx
+--------------------
+
+v3.3.1 - 07 Jun 2024
+--------------------
+   - Bug fixes
+   - DPDK:
+     * OVS validated with DPDK 23.11.1.
+
 v3.3.0 - 16 Feb 2024
 --------------------
    - OVSDB:
diff --git a/configure.ac b/configure.ac
index 05afbb9cc8..4b00d1878b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 AC_PREREQ(2.63)
-AC_INIT(openvswitch, 3.3.0, bugs@openvswitch.org)
+AC_INIT(openvswitch, 3.3.2, bugs@openvswitch.org)
 AC_CONFIG_SRCDIR([vswitchd/ovs-vswitchd.c])
 AC_CONFIG_MACRO_DIR([m4])
 AC_CONFIG_AUX_DIR([build-aux])
diff --git a/debian/changelog b/debian/changelog
index 2049ddaa26..82af184157 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,15 @@
+openvswitch (3.3.2-1) unstable; urgency=low
+   [ Open vSwitch team ]
+   * New upstream version
+
+ -- Open vSwitch team <dev@openvswitch.org>  Fri, 07 Jun 2024 15:58:27 +0200
+
+openvswitch (3.3.1-1) unstable; urgency=low
+   [ Open vSwitch team ]
+   * New upstream version
+
+ -- Open vSwitch team <dev@openvswitch.org>  Fri, 07 Jun 2024 15:58:27 +0200
+
 openvswitch (3.3.0-1) unstable; urgency=low
 
    * New upstream version
diff --git a/include/openvswitch/compiler.h b/include/openvswitch/compiler.h
index 878c5c6a70..ecb91801cc 100644
--- a/include/openvswitch/compiler.h
+++ b/include/openvswitch/compiler.h
@@ -69,6 +69,17 @@
 #define OVS_UNLIKELY(CONDITION) (!!(CONDITION))
 #endif
 
+/* Clang 17's implementation of ubsan enables checking that function pointers
+ * match the type of the called function.  This currently breaks ovs-rcu, which
+ * calls multiple different types of callbacks via a generic void *(void*)
+ * function pointer type.  This macro enables disabling that check for specific
+ * functions. */
+#if __clang__ && __has_feature(undefined_behavior_sanitizer)
+#define OVS_NO_SANITIZE_FUNCTION __attribute__((no_sanitize("function")))
+#else
+#define OVS_NO_SANITIZE_FUNCTION
+#endif
+
 #if __has_feature(c_thread_safety_attributes)
 /* "clang" annotations for thread safety check.
  *
diff --git a/include/sparse/automake.mk b/include/sparse/automake.mk
index c1229870bb..45e6202c52 100644
--- a/include/sparse/automake.mk
+++ b/include/sparse/automake.mk
@@ -1,5 +1,6 @@
 noinst_HEADERS += \
         include/sparse/rte_byteorder.h \
+        include/sparse/immintrin.h \
         include/sparse/xmmintrin.h \
         include/sparse/arpa/inet.h \
         include/sparse/bits/floatn.h \
diff --git a/include/sparse/immintrin.h b/include/sparse/immintrin.h
new file mode 100644
index 0000000000..9a23d7f746
--- /dev/null
+++ b/include/sparse/immintrin.h
@@ -0,0 +1,34 @@
+/* Copyright (c) 2024 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __CHECKER__
+#error "Use this header only with sparse.  It is not a correct implementation."
+#endif
+
+/* Sparse doesn't know some types used by AVX512 and some other headers.
+ * Mark those headers as already included to avoid failures.  This is fragile,
+ * so may need adjustments with compiler changes. */
+#define _AVX512BF16INTRIN_H_INCLUDED
+#define _AVX512BF16VLINTRIN_H_INCLUDED
+#define _AVXNECONVERTINTRIN_H_INCLUDED
+#define _KEYLOCKERINTRIN_H_INCLUDED
+#define __AVX512FP16INTRIN_H_INCLUDED
+#define __AVX512FP16VLINTRIN_H_INCLUDED
+/* GCC >=14 changed the '__AVX512FP16INTRIN_H_INCLUDED' to have only single
+ * underscore.  We need both to keep compatibility between various GCC
+ * versions. */
+#define _AVX512FP16INTRIN_H_INCLUDED
+
+#include_next <immintrin.h>
diff --git a/ipsec/ovs-monitor-ipsec.in b/ipsec/ovs-monitor-ipsec.in
index 7945162f9f..bc7ac55237 100755
--- a/ipsec/ovs-monitor-ipsec.in
+++ b/ipsec/ovs-monitor-ipsec.in
@@ -457,14 +457,30 @@ conn prevent_unencrypted_vxlan
     CERTKEY_PREFIX = "ovs_certkey_"
 
     def __init__(self, libreswan_root_prefix, args):
+        # Collect version infromation
+        self.IPSEC = libreswan_root_prefix + "/usr/sbin/ipsec"
+        proc = subprocess.Popen([self.IPSEC, "--version"],
+                                stdout=subprocess.PIPE,
+                                encoding="latin1")
+        pout, perr = proc.communicate()
+
+        v = re.match("^Libreswan (.*)$", pout)
+        try:
+            version = int(v.group(1).split(".")[0])
+        except:
+            version = 0
+
+        if version >= 4:
+            ipsec_d = args.ipsec_d if args.ipsec_d else "/var/lib/ipsec/nss"
+        else:
+            ipsec_d = args.ipsec_d if args.ipsec_d else "/etc/ipsec.d"
+
         ipsec_conf = args.ipsec_conf if args.ipsec_conf else "/etc/ipsec.conf"
-        ipsec_d = args.ipsec_d if args.ipsec_d else "/etc/ipsec.d"
         ipsec_secrets = (args.ipsec_secrets if args.ipsec_secrets
                         else "/etc/ipsec.secrets")
         ipsec_ctl = (args.ipsec_ctl if args.ipsec_ctl
                         else "/run/pluto/pluto.ctl")
 
-        self.IPSEC = libreswan_root_prefix + "/usr/sbin/ipsec"
         self.IPSEC_CONF = libreswan_root_prefix + ipsec_conf
         self.IPSEC_SECRETS = libreswan_root_prefix + ipsec_secrets
         self.IPSEC_D = "sql:" + libreswan_root_prefix + ipsec_d
diff --git a/lib/bfd.c b/lib/bfd.c
index 9af258917b..b8149e7897 100644
--- a/lib/bfd.c
+++ b/lib/bfd.c
@@ -1130,10 +1130,11 @@ bfd_set_state(struct bfd *bfd, enum state state, enum diag diag)
         if (!VLOG_DROP_INFO(&rl)) {
             struct ds ds = DS_EMPTY_INITIALIZER;
 
-            ds_put_format(&ds, "%s: BFD state change: %s->%s"
-                          " \"%s\"->\"%s\".\n",
+            ds_put_format(&ds, "%s: BFD state change: (bfd.SessionState: %s,"
+                          " bfd.LocalDiag: \"%s\") -> (bfd.SessionState: %s,"
+                          " bfd.LocalDiag: \"%s\")\n",
                           bfd->name, bfd_state_str(bfd->state),
-                          bfd_state_str(state), bfd_diag_str(bfd->diag),
+                          bfd_diag_str(bfd->diag), bfd_state_str(state),
                           bfd_diag_str(diag));
             bfd_put_details(&ds, bfd);
             VLOG_INFO("%s", ds_cstr(&ds));
diff --git a/lib/conntrack.c b/lib/conntrack.c
index 013709bd62..cf6e2919ba 100644
--- a/lib/conntrack.c
+++ b/lib/conntrack.c
@@ -941,6 +941,18 @@ conn_not_found(struct conntrack *ct, struct dp_packet *pkt,
             nc->parent_key = alg_exp->parent_key;
         }
 
+        ovs_mutex_init_adaptive(&nc->lock);
+        atomic_flag_clear(&nc->reclaimed);
+        fwd_key_node->dir = CT_DIR_FWD;
+        rev_key_node->dir = CT_DIR_REV;
+
+        if (zl) {
+            nc->admit_zone = zl->czl.zone;
+            nc->zone_limit_seq = zl->czl.zone_limit_seq;
+        } else {
+            nc->admit_zone = INVALID_ZONE;
+        }
+
         if (nat_action_info) {
             nc->nat_action = nat_action_info->nat_action;
 
@@ -965,21 +977,15 @@ conn_not_found(struct conntrack *ct, struct dp_packet *pkt,
             cmap_insert(&ct->conns, &rev_key_node->cm_node, rev_hash);
         }
 
-        ovs_mutex_init_adaptive(&nc->lock);
-        atomic_flag_clear(&nc->reclaimed);
-        fwd_key_node->dir = CT_DIR_FWD;
-        rev_key_node->dir = CT_DIR_REV;
         cmap_insert(&ct->conns, &fwd_key_node->cm_node, ctx->hash);
         conn_expire_push_front(ct, nc);
         atomic_count_inc(&ct->n_conn);
-        ctx->conn = nc; /* For completeness. */
+
         if (zl) {
-            nc->admit_zone = zl->czl.zone;
-            nc->zone_limit_seq = zl->czl.zone_limit_seq;
             atomic_count_inc(&zl->czl.count);
-        } else {
-            nc->admit_zone = INVALID_ZONE;
         }
+
+        ctx->conn = nc; /* For completeness. */
     }
 
     return nc;
@@ -2290,7 +2296,9 @@ find_addr(const struct conn_key *key, union ct_addr *min,
           uint32_t hash, bool ipv4,
           const struct nat_action_info_t *nat_info)
 {
-    const union ct_addr zero_ip = {0};
+    union ct_addr zero_ip;
+
+    memset(&zero_ip, 0, sizeof zero_ip);
 
     /* All-zero case. */
     if (!memcmp(min, &zero_ip, sizeof *min)) {
@@ -2382,14 +2390,18 @@ nat_get_unique_tuple(struct conntrack *ct, struct conn *conn,
 {
     struct conn_key *fwd_key = &conn->key_node[CT_DIR_FWD].key;
     struct conn_key *rev_key = &conn->key_node[CT_DIR_REV].key;
-    union ct_addr min_addr = {0}, max_addr = {0}, addr = {0};
     bool pat_proto = fwd_key->nw_proto == IPPROTO_TCP ||
                      fwd_key->nw_proto == IPPROTO_UDP ||
                      fwd_key->nw_proto == IPPROTO_SCTP;
     uint16_t min_dport, max_dport, curr_dport;
     uint16_t min_sport, max_sport, curr_sport;
+    union ct_addr min_addr, max_addr, addr;
     uint32_t hash;
 
+    memset(&min_addr, 0, sizeof min_addr);
+    memset(&max_addr, 0, sizeof max_addr);
+    memset(&addr, 0, sizeof addr);
+
     hash = nat_range_hash(fwd_key, ct->hash_basis, nat_info);
     min_addr = nat_info->min_addr;
     max_addr = nat_info->max_addr;
@@ -2572,7 +2584,9 @@ tuple_to_conn_key(const struct ct_dpif_tuple *tuple, uint16_t zone,
         key->src.icmp_type = tuple->icmp_type;
         key->src.icmp_code = tuple->icmp_code;
         key->dst.icmp_id = tuple->icmp_id;
-        key->dst.icmp_type = reverse_icmp_type(tuple->icmp_type);
+        key->dst.icmp_type = (tuple->ip_proto == IPPROTO_ICMP)
+                             ? reverse_icmp_type(tuple->icmp_type)
+                             : reverse_icmp6_type(tuple->icmp_type);
         key->dst.icmp_code = tuple->icmp_code;
     } else {
         key->src.port = tuple->src_port;
@@ -2637,25 +2651,19 @@ conntrack_dump_start(struct conntrack *ct, struct conntrack_dump *dump,
 
     dump->ct = ct;
     *ptot_bkts = 1; /* Need to clean up the callers. */
+    dump->cursor = cmap_cursor_start(&ct->conns);
     return 0;
 }
 
 int
 conntrack_dump_next(struct conntrack_dump *dump, struct ct_dpif_entry *entry)
 {
-    struct conntrack *ct = dump->ct;
     long long now = time_msec();
 
-    for (;;) {
-        struct cmap_node *cm_node = cmap_next_position(&ct->conns,
-                                                       &dump->cm_pos);
-        if (!cm_node) {
-            break;
-        }
-        struct conn_key_node *keyn;
-        struct conn *conn;
+    struct conn_key_node *keyn;
+    struct conn *conn;
 
-        INIT_CONTAINER(keyn, cm_node, cm_node);
+    CMAP_CURSOR_FOR_EACH_CONTINUE (keyn, cm_node, &dump->cursor) {
         if (keyn->dir != CT_DIR_FWD) {
             continue;
         }
diff --git a/lib/conntrack.h b/lib/conntrack.h
index 0a888be455..6339701627 100644
--- a/lib/conntrack.h
+++ b/lib/conntrack.h
@@ -101,8 +101,8 @@ struct conntrack_dump {
     struct conntrack *ct;
     unsigned bucket;
     union {
-        struct cmap_position cm_pos;
         struct hmap_position hmap_pos;
+        struct cmap_cursor cursor;
     };
     bool filter_zone;
     uint16_t zone;
diff --git a/lib/dp-packet.c b/lib/dp-packet.c
index 305822293b..df7bf8e6b3 100644
--- a/lib/dp-packet.c
+++ b/lib/dp-packet.c
@@ -592,6 +592,18 @@ dp_packet_ol_send_prepare(struct dp_packet *p, uint64_t flags)
     if (dp_packet_hwol_is_tunnel_geneve(p) ||
         dp_packet_hwol_is_tunnel_vxlan(p)) {
         tnl_inner = true;
+
+        /* If the TX interface doesn't support UDP tunnel offload but does
+         * support inner checksum offload and an outer UDP checksum is
+         * required, then we can't offload inner checksum either. As that would
+         * invalidate the outer checksum. */
+        if (!(flags & NETDEV_TX_OFFLOAD_OUTER_UDP_CKSUM) &&
+                dp_packet_hwol_is_outer_udp_cksum(p)) {
+            flags &= ~(NETDEV_TX_OFFLOAD_TCP_CKSUM |
+                       NETDEV_TX_OFFLOAD_UDP_CKSUM |
+                       NETDEV_TX_OFFLOAD_SCTP_CKSUM |
+                       NETDEV_TX_OFFLOAD_IPV4_CKSUM);
+        }
     }
 
     if (dp_packet_hwol_tx_ip_csum(p)) {
diff --git a/lib/dp-packet.h b/lib/dp-packet.h
index 2fa17d8140..a75b1c5cdb 100644
--- a/lib/dp-packet.h
+++ b/lib/dp-packet.h
@@ -604,25 +604,6 @@ dp_packet_get_nd_payload(const struct dp_packet *b)
 }
 
 #ifdef DPDK_NETDEV
-static inline void
-dp_packet_set_l2_len(struct dp_packet *b, size_t l2_len)
-{
-    b->mbuf.l2_len = l2_len;
-}
-
-static inline void
-dp_packet_set_l3_len(struct dp_packet *b, size_t l3_len)
-{
-    b->mbuf.l3_len = l3_len;
-}
-
-static inline void
-dp_packet_set_l4_len(struct dp_packet *b, size_t l4_len)
-{
-    b->mbuf.l4_len = l4_len;
-}
-
-
 static inline uint64_t *
 dp_packet_ol_flags_ptr(const struct dp_packet *b)
 {
@@ -642,24 +623,6 @@ dp_packet_flow_mark_ptr(const struct dp_packet *b)
 }
 
 #else
-static inline void
-dp_packet_set_l2_len(struct dp_packet *b OVS_UNUSED, size_t l2_len OVS_UNUSED)
-{
-    /* There is no implementation. */
-}
-
-static inline void
-dp_packet_set_l3_len(struct dp_packet *b OVS_UNUSED, size_t l3_len OVS_UNUSED)
-{
-    /* There is no implementation. */
-}
-
-static inline void
-dp_packet_set_l4_len(struct dp_packet *b OVS_UNUSED, size_t l4_len OVS_UNUSED)
-{
-    /* There is no implementation. */
-}
-
 static inline uint32_t *
 dp_packet_ol_flags_ptr(const struct dp_packet *b)
 {
@@ -1300,6 +1263,14 @@ dp_packet_hwol_set_tunnel_vxlan(struct dp_packet *b)
     *dp_packet_ol_flags_ptr(b) |= DP_PACKET_OL_TX_TUNNEL_VXLAN;
 }
 
+/* Clears tunnel offloading marks. */
+static inline void
+dp_packet_hwol_reset_tunnel(struct dp_packet *b)
+{
+    *dp_packet_ol_flags_ptr(b) &= ~(DP_PACKET_OL_TX_TUNNEL_VXLAN |
+                                    DP_PACKET_OL_TX_TUNNEL_GENEVE);
+}
+
 /* Mark packet 'b' as a tunnel packet with outer IPv4 header. */
 static inline void
 dp_packet_hwol_set_tx_outer_ipv4(struct dp_packet *b)
diff --git a/lib/dpctl.c b/lib/dpctl.c
index 34ee7d0e2d..151848b909 100644
--- a/lib/dpctl.c
+++ b/lib/dpctl.c
@@ -1359,19 +1359,17 @@ static int
 dpctl_del_flow_dpif(struct dpif *dpif, const char *key_s,
                     struct dpctl_params *dpctl_p)
 {
+    struct dpif_port_dump port_dump;
     struct dpif_flow_stats stats;
+    bool ufid_generated = false;
     struct dpif_port dpif_port;
-    struct dpif_port_dump port_dump;
-    struct ofpbuf key;
+    bool ufid_present = false;
+    struct simap port_names;
     struct ofpbuf mask; /* To be ignored. */
-
+    struct ofpbuf key;
     ovs_u128 ufid;
-    bool ufid_generated;
-    bool ufid_present;
-    struct simap port_names;
     int n, error;
 
-    ufid_present = false;
     n = odp_ufid_from_string(key_s, &ufid);
     if (n < 0) {
         dpctl_error(dpctl_p, -n, "parsing flow ufid");
diff --git a/lib/dpdk.c b/lib/dpdk.c
index d76d53f8f1..940c43c070 100644
--- a/lib/dpdk.c
+++ b/lib/dpdk.c
@@ -337,7 +337,9 @@ dpdk_init__(const struct smap *ovs_other_config)
     }
 #endif
 
-    if (args_contains(&args, "-c") || args_contains(&args, "-l")) {
+    if (args_contains(&args, "-c") ||
+        args_contains(&args, "-l") ||
+        args_contains(&args, "--lcores")) {
         auto_determine = false;
     }
 
diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
index 46e24d204d..99ff9b3693 100644
--- a/lib/dpif-netdev.c
+++ b/lib/dpif-netdev.c
@@ -115,6 +115,7 @@ COVERAGE_DEFINE(datapath_drop_lock_error);
 COVERAGE_DEFINE(datapath_drop_userspace_action_error);
 COVERAGE_DEFINE(datapath_drop_tunnel_push_error);
 COVERAGE_DEFINE(datapath_drop_tunnel_pop_error);
+COVERAGE_DEFINE(datapath_drop_tunnel_tso_recirc);
 COVERAGE_DEFINE(datapath_drop_recirc_error);
 COVERAGE_DEFINE(datapath_drop_invalid_port);
 COVERAGE_DEFINE(datapath_drop_invalid_bond);
@@ -8912,6 +8913,34 @@ static void
 dp_netdev_recirculate(struct dp_netdev_pmd_thread *pmd,
                       struct dp_packet_batch *packets)
 {
+    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+    size_t i, size = dp_packet_batch_size(packets);
+    struct dp_packet *packet;
+
+    DP_PACKET_BATCH_REFILL_FOR_EACH (i, size, packet, packets) {
+        if (dp_packet_hwol_is_tunnel_geneve(packet) ||
+                dp_packet_hwol_is_tunnel_vxlan(packet)) {
+
+            if (dp_packet_hwol_is_tso(packet)) {
+                /* Can't perform GSO in the middle of a pipeline. */
+                COVERAGE_INC(datapath_drop_tunnel_tso_recirc);
+                dp_packet_delete(packet);
+                VLOG_WARN_RL(&rl, "Recirculating tunnel packets with "
+                                  "TSO is not supported");
+                continue;
+            }
+            /* Have to fix all the checksums before re-parsing, because the
+             * packet will be treated as having a single set of headers. */
+            dp_packet_ol_send_prepare(packet, 0);
+            /* This packet must not be marked with anything tunnel-related. */
+            dp_packet_hwol_reset_tunnel(packet);
+            /* Clear inner offsets.  Other ones are collateral, but they will
+             * be re-initialized on re-parsing. */
+            dp_packet_reset_offsets(packet);
+        }
+        dp_packet_batch_refill(packets, packet, i);
+    }
+
     dp_netdev_input__(pmd, packets, true, 0);
 }
 
diff --git a/lib/flow.c b/lib/flow.c
index 8e3402388c..dc5fb328d9 100644
--- a/lib/flow.c
+++ b/lib/flow.c
@@ -3420,6 +3420,24 @@ flow_compose(struct dp_packet *p, const struct flow *flow,
             arp->ar_sha = flow->arp_sha;
             arp->ar_tha = flow->arp_tha;
         }
+    } else if (flow->dl_type == htons(ETH_TYPE_NSH)) {
+        struct nsh_hdr *nsh;
+
+        nsh = dp_packet_put_zeros(p, sizeof *nsh);
+        dp_packet_set_l3(p, nsh);
+
+        nsh_set_flags_ttl_len(nsh, flow->nsh.flags, flow->nsh.ttl,
+                              flow->nsh.mdtype == NSH_M_TYPE1
+                              ? NSH_M_TYPE1_LEN : NSH_BASE_HDR_LEN);
+        nsh->next_proto = flow->nsh.np;
+        nsh->md_type = flow->nsh.mdtype;
+        put_16aligned_be32(&nsh->path_hdr, flow->nsh.path_hdr);
+
+        if (flow->nsh.mdtype == NSH_M_TYPE1) {
+            for (size_t i = 0; i < 4; i++) {
+                put_16aligned_be32(&nsh->md1.context[i], flow->nsh.context[i]);
+            }
+        }
     }
 
     if (eth_type_mpls(flow->dl_type)) {
diff --git a/lib/hash.c b/lib/hash.c
index c722f3c3cc..3d574de9b4 100644
--- a/lib/hash.c
+++ b/lib/hash.c
@@ -29,15 +29,16 @@ hash_3words(uint32_t a, uint32_t b, uint32_t c)
 uint32_t
 hash_bytes(const void *p_, size_t n, uint32_t basis)
 {
-    const uint32_t *p = p_;
+    const uint8_t *p = p_;
     size_t orig_n = n;
     uint32_t hash;
 
     hash = basis;
     while (n >= 4) {
-        hash = hash_add(hash, get_unaligned_u32(p));
+        hash = hash_add(hash,
+                        get_unaligned_u32(ALIGNED_CAST(const uint32_t *, p)));
         n -= 4;
-        p += 1;
+        p += 4;
     }
 
     if (n) {
diff --git a/lib/ipf.c b/lib/ipf.c
index 7d74e2c131..2d715f5e9d 100644
--- a/lib/ipf.c
+++ b/lib/ipf.c
@@ -506,13 +506,15 @@ ipf_reassemble_v6_frags(struct ipf_list *ipf_list)
 }
 
 /* Called when a frag list state transitions to another state. This is
- * triggered by new fragment for the list being received.*/
-static void
+* triggered by new fragment for the list being received. Returns a reassembled
+* packet if this fragment has completed one. */
+static struct reassembled_pkt *
 ipf_list_state_transition(struct ipf *ipf, struct ipf_list *ipf_list,
                           bool ff, bool lf, bool v6)
     OVS_REQUIRES(ipf->ipf_lock)
 {
     enum ipf_list_state curr_state = ipf_list->state;
+    struct reassembled_pkt *ret = NULL;
     enum ipf_list_state next_state;
     switch (curr_state) {
     case IPF_LIST_STATE_UNUSED:
@@ -562,12 +564,15 @@ ipf_list_state_transition(struct ipf *ipf, struct ipf_list *ipf_list,
                 ipf_reassembled_list_add(&ipf->reassembled_pkt_list, rp);
                 ipf_expiry_list_remove(ipf_list);
                 next_state = IPF_LIST_STATE_COMPLETED;
+                ret = rp;
             } else {
                 next_state = IPF_LIST_STATE_REASS_FAIL;
             }
         }
     }
     ipf_list->state = next_state;
+
+    return ret;
 }
 
 /* Some sanity checks are redundant, but prudent, in case code paths for
@@ -799,7 +804,8 @@ ipf_is_frag_duped(const struct ipf_frag *frag_list, int last_inuse_idx,
 static bool
 ipf_process_frag(struct ipf *ipf, struct ipf_list *ipf_list,
                  struct dp_packet *pkt, uint16_t start_data_byte,
-                 uint16_t end_data_byte, bool ff, bool lf, bool v6)
+                 uint16_t end_data_byte, bool ff, bool lf, bool v6,
+                 struct reassembled_pkt **rp)
     OVS_REQUIRES(ipf->ipf_lock)
 {
     bool duped_frag = ipf_is_frag_duped(ipf_list->frag_list,
@@ -820,7 +826,7 @@ ipf_process_frag(struct ipf *ipf, struct ipf_list *ipf_list,
             ipf_list->last_inuse_idx++;
             atomic_count_inc(&ipf->nfrag);
             ipf_count(ipf, v6, IPF_NFRAGS_ACCEPTED);
-            ipf_list_state_transition(ipf, ipf_list, ff, lf, v6);
+            *rp = ipf_list_state_transition(ipf, ipf_list, ff, lf, v6);
         } else {
             OVS_NOT_REACHED();
         }
@@ -853,7 +859,8 @@ ipf_list_init(struct ipf_list *ipf_list, struct ipf_list_key *key,
  * to a list of fragemnts. */
 static bool
 ipf_handle_frag(struct ipf *ipf, struct dp_packet *pkt, ovs_be16 dl_type,
-                uint16_t zone, long long now, uint32_t hash_basis)
+                uint16_t zone, long long now, uint32_t hash_basis,
+                struct reassembled_pkt **rp)
     OVS_REQUIRES(ipf->ipf_lock)
 {
     struct ipf_list_key key;
@@ -922,7 +929,7 @@ ipf_handle_frag(struct ipf *ipf, struct dp_packet *pkt, ovs_be16 dl_type,
     }
 
     return ipf_process_frag(ipf, ipf_list, pkt, start_data_byte,
-                            end_data_byte, ff, lf, v6);
+                            end_data_byte, ff, lf, v6, rp);
 }
 
 /* Filters out fragments from a batch of fragments and adjust the batch. */
@@ -941,11 +948,17 @@ ipf_extract_frags_from_batch(struct ipf *ipf, struct dp_packet_batch *pb,
                           ||
                           (dl_type == htons(ETH_TYPE_IPV6) &&
                           ipf_is_valid_v6_frag(ipf, pkt)))) {
+            struct reassembled_pkt *rp = NULL;
 
             ovs_mutex_lock(&ipf->ipf_lock);
-            if (!ipf_handle_frag(ipf, pkt, dl_type, zone, now, hash_basis)) {
+            if (!ipf_handle_frag(ipf, pkt, dl_type, zone, now, hash_basis,
+                                 &rp)) {
                 dp_packet_batch_refill(pb, pkt, pb_idx);
             } else {
+                if (rp && !dp_packet_batch_is_full(pb)) {
+                    dp_packet_batch_refill(pb, rp->pkt, pb_idx);
+                    rp->list->reass_execute_ctx = rp->pkt;
+                }
                 dp_packet_delete(pkt);
             }
             ovs_mutex_unlock(&ipf->ipf_lock);
@@ -1063,6 +1076,9 @@ ipf_send_completed_frags(struct ipf *ipf, struct dp_packet_batch *pb,
     struct ipf_list *ipf_list;
 
     LIST_FOR_EACH_SAFE (ipf_list, list_node, &ipf->frag_complete_list) {
+        if ((ipf_list->key.dl_type == htons(ETH_TYPE_IPV6)) != v6) {
+            continue;
+        }
         if (ipf_send_frags_in_list(ipf, ipf_list, pb, IPF_FRAG_COMPLETED_LIST,
                                    v6, now)) {
             ipf_completed_list_clean(&ipf->frag_lists, ipf_list);
@@ -1096,6 +1112,9 @@ ipf_send_expired_frags(struct ipf *ipf, struct dp_packet_batch *pb,
     size_t lists_removed = 0;
 
     LIST_FOR_EACH_SAFE (ipf_list, list_node, &ipf->frag_exp_list) {
+        if ((ipf_list->key.dl_type == htons(ETH_TYPE_IPV6)) != v6) {
+            continue;
+        }
         if (now <= ipf_list->expiration ||
             lists_removed >= IPF_FRAG_LIST_MAX_EXPIRED) {
             break;
@@ -1116,7 +1135,8 @@ ipf_send_expired_frags(struct ipf *ipf, struct dp_packet_batch *pb,
 /* Adds a reassmebled packet to a packet batch to be processed by the caller.
  */
 static void
-ipf_execute_reass_pkts(struct ipf *ipf, struct dp_packet_batch *pb)
+ipf_execute_reass_pkts(struct ipf *ipf, struct dp_packet_batch *pb,
+                       ovs_be16 dl_type)
 {
     if (ovs_list_is_empty(&ipf->reassembled_pkt_list)) {
         return;
@@ -1127,6 +1147,7 @@ ipf_execute_reass_pkts(struct ipf *ipf, struct dp_packet_batch *pb)
 
     LIST_FOR_EACH_SAFE (rp, rp_list_node, &ipf->reassembled_pkt_list) {
         if (!rp->list->reass_execute_ctx &&
+            rp->list->key.dl_type == dl_type &&
             ipf_dp_packet_batch_add(pb, rp->pkt, false)) {
             rp->list->reass_execute_ctx = rp->pkt;
         }
@@ -1237,7 +1258,7 @@ ipf_preprocess_conntrack(struct ipf *ipf, struct dp_packet_batch *pb,
     }
 
     if (ipf_get_enabled(ipf) || atomic_count_get(&ipf->nfrag)) {
-        ipf_execute_reass_pkts(ipf, pb);
+        ipf_execute_reass_pkts(ipf, pb, dl_type);
     }
 }
 
diff --git a/lib/jhash.c b/lib/jhash.c
index c59b51b611..a8e3f457b9 100644
--- a/lib/jhash.c
+++ b/lib/jhash.c
@@ -96,18 +96,18 @@ jhash_words(const uint32_t *p, size_t n, uint32_t basis)
 uint32_t
 jhash_bytes(const void *p_, size_t n, uint32_t basis)
 {
-    const uint32_t *p = p_;
+    const uint8_t *p = p_;
     uint32_t a, b, c;
 
     a = b = c = 0xdeadbeef + n + basis;
 
     while (n >= 12) {
-        a += get_unaligned_u32(p);
-        b += get_unaligned_u32(p + 1);
-        c += get_unaligned_u32(p + 2);
+        a += get_unaligned_u32(ALIGNED_CAST(const uint32_t *, p));
+        b += get_unaligned_u32(ALIGNED_CAST(const uint32_t *, p + 4));
+        c += get_unaligned_u32(ALIGNED_CAST(const uint32_t *, p + 8));
         jhash_mix(&a, &b, &c);
         n -= 12;
-        p += 3;
+        p += 12;
     }
 
     if (n) {
diff --git a/lib/netdev-dpdk.c b/lib/netdev-dpdk.c
index 45f61930d4..66396d7969 100644
--- a/lib/netdev-dpdk.c
+++ b/lib/netdev-dpdk.c
@@ -464,9 +464,8 @@ struct netdev_dpdk {
         bool attached;
         /* If true, rte_eth_dev_start() was successfully called */
         bool started;
-        bool reset_needed;
-        /* 1 pad byte here. */
         struct eth_addr hwaddr;
+        /* 2 pad bytes here. */
         int mtu;
         int socket_id;
         int buf_size;
@@ -607,6 +606,9 @@ int netdev_dpdk_get_vid(const struct netdev_dpdk *dev);
 struct ingress_policer *
 netdev_dpdk_get_ingress_policer(const struct netdev_dpdk *dev);
 
+static void netdev_dpdk_mbuf_dump(const char *prefix, const char *message,
+                                  const struct rte_mbuf *);
+
 static bool
 is_dpdk_class(const struct netdev_class *class)
 {
@@ -1351,6 +1353,20 @@ dpdk_eth_dev_init(struct netdev_dpdk *dev)
         info.tx_offload_capa &= ~RTE_ETH_TX_OFFLOAD_TCP_CKSUM;
     }
 
+    if (!strcmp(info.driver_name, "net_ice")
+        || !strcmp(info.driver_name, "net_i40e")
+        || !strcmp(info.driver_name, "net_iavf")) {
+        /* FIXME: Driver advertises the capability but doesn't seem
+         * to actually support it correctly.  Can remove this once
+         * the driver is fixed on DPDK side. */
+        VLOG_INFO("%s: disabled Tx outer udp checksum offloads for a "
+                  "net/ice, net/i40e or net/iavf port.",
+                  netdev_get_name(&dev->up));
+        info.tx_offload_capa &= ~RTE_ETH_TX_OFFLOAD_OUTER_UDP_CKSUM;
+        info.tx_offload_capa &= ~RTE_ETH_TX_OFFLOAD_VXLAN_TNL_TSO;
+        info.tx_offload_capa &= ~RTE_ETH_TX_OFFLOAD_GENEVE_TNL_TSO;
+    }
+
     if (info.tx_offload_capa & RTE_ETH_TX_OFFLOAD_IPV4_CKSUM) {
         dev->hw_ol_features |= NETDEV_TX_IPV4_CKSUM_OFFLOAD;
     } else {
@@ -1514,7 +1530,6 @@ common_construct(struct netdev *netdev, dpdk_port_t port_no,
     dev->virtio_features_state = OVS_VIRTIO_F_CLEAN;
     dev->attached = false;
     dev->started = false;
-    dev->reset_needed = false;
 
     ovsrcu_init(&dev->qos_conf, NULL);
 
@@ -2137,13 +2152,11 @@ netdev_dpdk_run(const struct netdev_class *netdev_class OVS_UNUSED)
             if (!pending_reset) {
                 continue;
             }
-            atomic_store_relaxed(&netdev_dpdk_pending_reset[port_id], false);
 
             ovs_mutex_lock(&dpdk_mutex);
             dev = netdev_dpdk_lookup_by_port_id(port_id);
             if (dev) {
                 ovs_mutex_lock(&dev->mutex);
-                dev->reset_needed = true;
                 netdev_request_reconfigure(&dev->up);
                 VLOG_DBG_RL(&rl, "%s: Device reset requested.",
                             netdev_get_name(&dev->up));
@@ -2364,17 +2377,16 @@ netdev_dpdk_set_config(struct netdev *netdev, const struct smap *args,
         struct eth_addr mac;
 
         if (!dpdk_port_is_representor(dev)) {
-            VLOG_WARN_BUF(errp, "'%s' is trying to set the VF MAC '%s' "
-                          "but 'options:dpdk-vf-mac' is only supported for "
-                          "VF representors.",
-                          netdev_get_name(netdev), vf_mac);
+            VLOG_WARN("'%s' is trying to set the VF MAC '%s' "
+                      "but 'options:dpdk-vf-mac' is only supported for "
+                      "VF representors.",
+                      netdev_get_name(netdev), vf_mac);
         } else if (!eth_addr_from_string(vf_mac, &mac)) {
-            VLOG_WARN_BUF(errp, "interface '%s': cannot parse VF MAC '%s'.",
-                          netdev_get_name(netdev), vf_mac);
+            VLOG_WARN("interface '%s': cannot parse VF MAC '%s'.",
+                      netdev_get_name(netdev), vf_mac);
         } else if (eth_addr_is_multicast(mac)) {
-            VLOG_WARN_BUF(errp,
-                          "interface '%s': cannot set VF MAC to multicast "
-                          "address '%s'.", netdev_get_name(netdev), vf_mac);
+            VLOG_WARN("interface '%s': cannot set VF MAC to multicast "
+                      "address '%s'.", netdev_get_name(netdev), vf_mac);
         } else if (!eth_addr_equals(dev->requested_hwaddr, mac)) {
             dev->requested_hwaddr = mac;
             netdev_request_reconfigure(netdev);
@@ -2567,73 +2579,133 @@ static bool
 netdev_dpdk_prep_hwol_packet(struct netdev_dpdk *dev, struct rte_mbuf *mbuf)
 {
     struct dp_packet *pkt = CONTAINER_OF(mbuf, struct dp_packet, mbuf);
-    struct tcp_header *th;
-
-    if (!(mbuf->ol_flags & (RTE_MBUF_F_TX_IP_CKSUM | RTE_MBUF_F_TX_L4_MASK
-                            | RTE_MBUF_F_TX_TCP_SEG))) {
-        mbuf->ol_flags &= ~(RTE_MBUF_F_TX_IPV4 | RTE_MBUF_F_TX_IPV6);
+    void *l2;
+    void *l3;
+    void *l4;
+
+    const uint64_t all_inner_requests = (RTE_MBUF_F_TX_IP_CKSUM |
+                                         RTE_MBUF_F_TX_L4_MASK |
+                                         RTE_MBUF_F_TX_TCP_SEG);
+    const uint64_t all_outer_requests = (RTE_MBUF_F_TX_OUTER_IP_CKSUM |
+                                         RTE_MBUF_F_TX_OUTER_UDP_CKSUM);
+    const uint64_t all_requests = all_inner_requests | all_outer_requests;
+    const uint64_t all_inner_marks = (RTE_MBUF_F_TX_IPV4 |
+                                      RTE_MBUF_F_TX_IPV6);
+    const uint64_t all_outer_marks = (RTE_MBUF_F_TX_OUTER_IPV4 |
+                                      RTE_MBUF_F_TX_OUTER_IPV6 |
+                                      RTE_MBUF_F_TX_TUNNEL_MASK);
+    const uint64_t all_marks = all_inner_marks | all_outer_marks;
+
+    if (!(mbuf->ol_flags & all_requests)) {
+        /* No offloads requested, no marks should be set. */
+        mbuf->ol_flags &= ~all_marks;
+
+        uint64_t unexpected = mbuf->ol_flags & RTE_MBUF_F_TX_OFFLOAD_MASK;
+        if (OVS_UNLIKELY(unexpected)) {
+            VLOG_WARN_RL(&rl, "%s: Unexpected Tx offload flags: %#"PRIx64,
+                         netdev_get_name(&dev->up), unexpected);
+            netdev_dpdk_mbuf_dump(netdev_get_name(&dev->up),
+                                  "Packet with unexpected ol_flags", mbuf);
+            return false;
+        }
         return true;
     }
 
-    /* If packet is vxlan or geneve tunnel packet, calculate outer
-     * l2 len and outer l3 len. Inner l2/l3/l4 len are calculated
-     * before. */
-    if (mbuf->ol_flags &
-        (RTE_MBUF_F_TX_TUNNEL_GENEVE | RTE_MBUF_F_TX_TUNNEL_VXLAN)) {
-        mbuf->outer_l2_len = (char *) dp_packet_l3(pkt) -
-                 (char *) dp_packet_eth(pkt);
-        mbuf->outer_l3_len = (char *) dp_packet_l4(pkt) -
-                 (char *) dp_packet_l3(pkt);
+    const uint64_t tunnel_type = mbuf->ol_flags & RTE_MBUF_F_TX_TUNNEL_MASK;
+    if (OVS_UNLIKELY(tunnel_type &&
+                     tunnel_type != RTE_MBUF_F_TX_TUNNEL_GENEVE &&
+                     tunnel_type != RTE_MBUF_F_TX_TUNNEL_VXLAN)) {
+        VLOG_WARN_RL(&rl, "%s: Unexpected tunnel type: %#"PRIx64,
+                     netdev_get_name(&dev->up), tunnel_type);
+        netdev_dpdk_mbuf_dump(netdev_get_name(&dev->up),
+                              "Packet with unexpected tunnel type", mbuf);
+        return false;
+    }
+
+    if (tunnel_type && (mbuf->ol_flags & all_inner_requests)) {
+        if (mbuf->ol_flags & all_outer_requests) {
+            mbuf->outer_l2_len = (char *) dp_packet_l3(pkt) -
+                                 (char *) dp_packet_eth(pkt);
+            mbuf->outer_l3_len = (char *) dp_packet_l4(pkt) -
+                                 (char *) dp_packet_l3(pkt);
+
+            /* Inner L2 length must account for the tunnel header length. */
+            l2 = dp_packet_l4(pkt);
+            l3 = dp_packet_inner_l3(pkt);
+            l4 = dp_packet_inner_l4(pkt);
+        } else {
+            /* If no outer offloading is requested, clear outer marks. */
+            mbuf->ol_flags &= ~all_outer_marks;
+            mbuf->outer_l2_len = 0;
+            mbuf->outer_l3_len = 0;
+
+            /* Skip outer headers. */
+            l2 = dp_packet_eth(pkt);
+            l3 = dp_packet_inner_l3(pkt);
+            l4 = dp_packet_inner_l4(pkt);
+        }
     } else {
-        mbuf->l2_len = (char *) dp_packet_l3(pkt) -
-               (char *) dp_packet_eth(pkt);
-        mbuf->l3_len = (char *) dp_packet_l4(pkt) -
-               (char *) dp_packet_l3(pkt);
+        if (tunnel_type) {
+            /* No inner offload is requested, fallback to non tunnel
+             * checksum offloads. */
+            mbuf->ol_flags &= ~all_inner_marks;
+            if (mbuf->ol_flags & RTE_MBUF_F_TX_OUTER_IP_CKSUM) {
+                mbuf->ol_flags |= RTE_MBUF_F_TX_IP_CKSUM;
+                mbuf->ol_flags |= RTE_MBUF_F_TX_IPV4;
+            }
+            if (mbuf->ol_flags & RTE_MBUF_F_TX_OUTER_UDP_CKSUM) {
+                mbuf->ol_flags |= RTE_MBUF_F_TX_UDP_CKSUM;
+                mbuf->ol_flags |= mbuf->ol_flags & RTE_MBUF_F_TX_OUTER_IPV4
+                                  ? RTE_MBUF_F_TX_IPV4 : RTE_MBUF_F_TX_IPV6;
+            }
+            mbuf->ol_flags &= ~(all_outer_requests | all_outer_marks);
+        }
         mbuf->outer_l2_len = 0;
         mbuf->outer_l3_len = 0;
-    }
-    th = dp_packet_l4(pkt);
 
-    if (mbuf->ol_flags & RTE_MBUF_F_TX_TCP_SEG) {
-        if (!th) {
-            VLOG_WARN_RL(&rl, "%s: TCP Segmentation without L4 header"
-                         " pkt len: %"PRIu32"", dev->up.name, mbuf->pkt_len);
-            return false;
-        }
+        l2 = dp_packet_eth(pkt);
+        l3 = dp_packet_l3(pkt);
+        l4 = dp_packet_l4(pkt);
     }
 
-    if (mbuf->ol_flags & RTE_MBUF_F_TX_TCP_CKSUM) {
-        if (!th) {
-            VLOG_WARN_RL(&rl, "%s: TCP offloading without L4 header"
-                         " pkt len: %"PRIu32"", dev->up.name, mbuf->pkt_len);
-            return false;
-        }
+    ovs_assert(l4);
+
+    mbuf->l2_len = (char *) l3 - (char *) l2;
+    mbuf->l3_len = (char *) l4 - (char *) l3;
 
-        if (mbuf->ol_flags & (RTE_MBUF_F_TX_TUNNEL_GENEVE |
-            RTE_MBUF_F_TX_TUNNEL_VXLAN)) {
-            mbuf->tso_segsz = dev->mtu - mbuf->l2_len - mbuf->l3_len -
-                              mbuf->l4_len - mbuf->outer_l3_len;
+    if (mbuf->ol_flags & RTE_MBUF_F_TX_TCP_SEG) {
+        struct tcp_header *th = l4;
+        uint16_t link_tso_segsz;
+        int hdr_len;
+
+        mbuf->l4_len = TCP_OFFSET(th->tcp_ctl) * 4;
+        if (tunnel_type) {
+            link_tso_segsz = dev->mtu - mbuf->l2_len - mbuf->l3_len -
+                             mbuf->l4_len - mbuf->outer_l3_len;
         } else {
-            mbuf->l4_len = TCP_OFFSET(th->tcp_ctl) * 4;
-            mbuf->tso_segsz = dev->mtu - mbuf->l3_len - mbuf->l4_len;
+            link_tso_segsz = dev->mtu - mbuf->l3_len - mbuf->l4_len;
         }
 
-        if (mbuf->ol_flags & RTE_MBUF_F_TX_TCP_SEG) {
-            int hdr_len = mbuf->l2_len + mbuf->l3_len + mbuf->l4_len;
-            if (OVS_UNLIKELY((hdr_len +
-                              mbuf->tso_segsz) > dev->max_packet_len)) {
-                VLOG_WARN_RL(&rl, "%s: Oversized TSO packet. hdr: %"PRIu32", "
-                             "gso: %"PRIu32", max len: %"PRIu32"",
-                             dev->up.name, hdr_len, mbuf->tso_segsz,
-                             dev->max_packet_len);
-                return false;
-            }
+        if (mbuf->tso_segsz > link_tso_segsz) {
+            mbuf->tso_segsz = link_tso_segsz;
         }
 
-        if (mbuf->ol_flags & RTE_MBUF_F_TX_IPV4) {
-            mbuf->ol_flags |= RTE_MBUF_F_TX_IP_CKSUM;
+        hdr_len = mbuf->l2_len + mbuf->l3_len + mbuf->l4_len;
+        if (OVS_UNLIKELY((hdr_len + mbuf->tso_segsz) > dev->max_packet_len)) {
+            VLOG_WARN_RL(&rl, "%s: Oversized TSO packet. hdr: %"PRIu32", "
+                         "gso: %"PRIu32", max len: %"PRIu32"",
+                         dev->up.name, hdr_len, mbuf->tso_segsz,
+                         dev->max_packet_len);
+            return false;
         }
     }
+
+    /* If L4 checksum is requested, IPv4 should be requested as well. */
+    if (mbuf->ol_flags & RTE_MBUF_F_TX_L4_MASK
+        && mbuf->ol_flags & RTE_MBUF_F_TX_IPV4) {
+        mbuf->ol_flags |= RTE_MBUF_F_TX_IP_CKSUM;
+    }
+
     return true;
 }
 
@@ -2664,6 +2736,35 @@ netdev_dpdk_prep_hwol_batch(struct netdev_dpdk *dev, struct rte_mbuf **pkts,
     return cnt;
 }
 
+static void
+netdev_dpdk_mbuf_dump(const char *prefix, const char *message,
+                      const struct rte_mbuf *mbuf)
+{
+    static struct vlog_rate_limit dump_rl = VLOG_RATE_LIMIT_INIT(5, 5);
+    char *response = NULL;
+    FILE *stream;
+    size_t size;
+
+    if (VLOG_DROP_DBG(&dump_rl)) {
+        return;
+    }
+
+    stream = open_memstream(&response, &size);
+    if (!stream) {
+        VLOG_ERR("Unable to open memstream for mbuf dump: %s.",
+                 ovs_strerror(errno));
+        return;
+    }
+
+    rte_pktmbuf_dump(stream, mbuf, rte_pktmbuf_pkt_len(mbuf));
+
+    fclose(stream);
+
+    VLOG_DBG(prefix ? "%s: %s:\n%s" : "%s%s:\n%s",
+             prefix ? prefix : "", message, response);
+    free(response);
+}
+
 /* Tries to transmit 'pkts' to txq 'qid' of device 'dev'.  Takes ownership of
  * 'pkts', even in case of failure.
  *
@@ -2680,6 +2781,8 @@ netdev_dpdk_eth_tx_burst(struct netdev_dpdk *dev, int qid,
         VLOG_WARN_RL(&rl, "%s: Output batch contains invalid packets. "
                      "Only %u/%u are valid: %s", netdev_get_name(&dev->up),
                      nb_tx_prep, cnt, rte_strerror(rte_errno));
+        netdev_dpdk_mbuf_dump(netdev_get_name(&dev->up),
+                              "First invalid packet", pkts[nb_tx_prep]);
     }
 
     while (nb_tx != nb_tx_prep) {
@@ -5965,6 +6068,7 @@ static int
 netdev_dpdk_reconfigure(struct netdev *netdev)
 {
     struct netdev_dpdk *dev = netdev_dpdk_cast(netdev);
+    bool pending_reset;
     bool try_rx_steer;
     int err = 0;
 
@@ -5976,6 +6080,9 @@ netdev_dpdk_reconfigure(struct netdev *netdev)
         dev->requested_n_rxq += 1;
     }
 
+    atomic_read_relaxed(&netdev_dpdk_pending_reset[dev->port_id],
+                        &pending_reset);
+
     if (netdev->n_txq == dev->requested_n_txq
         && netdev->n_rxq == dev->requested_n_rxq
         && dev->rx_steer_flags == dev->requested_rx_steer_flags
@@ -5985,7 +6092,7 @@ netdev_dpdk_reconfigure(struct netdev *netdev)
         && dev->txq_size == dev->requested_txq_size
         && eth_addr_equals(dev->hwaddr, dev->requested_hwaddr)
         && dev->socket_id == dev->requested_socket_id
-        && dev->started && !dev->reset_needed) {
+        && dev->started && !pending_reset) {
         /* Reconfiguration is unnecessary */
 
         goto out;
@@ -5994,10 +6101,14 @@ netdev_dpdk_reconfigure(struct netdev *netdev)
 retry:
     dpdk_rx_steer_unconfigure(dev);
 
-    if (dev->reset_needed) {
+    if (pending_reset) {
+        /*
+         * Set false before reset to avoid missing a new reset interrupt event
+         * in a race with event callback.
+         */
+        atomic_store_relaxed(&netdev_dpdk_pending_reset[dev->port_id], false);
         rte_eth_dev_reset(dev->port_id);
         if_notifier_manual_report();
-        dev->reset_needed = false;
     } else {
         rte_eth_dev_stop(dev->port_id);
     }
diff --git a/lib/netdev-dummy.c b/lib/netdev-dummy.c
index cd7e85a818..e8bbf8d514 100644
--- a/lib/netdev-dummy.c
+++ b/lib/netdev-dummy.c
@@ -39,6 +39,7 @@
 #include "pcap-file.h"
 #include "openvswitch/poll-loop.h"
 #include "openvswitch/shash.h"
+#include "ovs-router.h"
 #include "sset.h"
 #include "stream.h"
 #include "unaligned.h"
@@ -2084,11 +2085,20 @@ netdev_dummy_ip4addr(struct unixctl_conn *conn, int argc OVS_UNUSED,
 
     if (netdev && is_dummy_class(netdev->netdev_class)) {
         struct in_addr ip, mask;
+        struct in6_addr ip6;
+        uint32_t plen;
         char *error;
 
-        error = ip_parse_masked(argv[2], &ip.s_addr, &mask.s_addr);
+        error = ip_parse_cidr(argv[2], &ip.s_addr, &plen);
         if (!error) {
+            mask.s_addr = be32_prefix_mask(plen);
             netdev_dummy_add_in4(netdev, ip, mask);
+
+            /* Insert local route entry for the new address. */
+            in6_addr_set_mapped_ipv4(&ip6, ip.s_addr);
+            ovs_router_force_insert(0, &ip6, plen + 96, true, argv[1],
+                                    &in6addr_any, &ip6);
+
             unixctl_command_reply(conn, "OK");
         } else {
             unixctl_command_reply_error(conn, error);
@@ -2118,6 +2128,11 @@ netdev_dummy_ip6addr(struct unixctl_conn *conn, int argc OVS_UNUSED,
 
             mask = ipv6_create_mask(plen);
             netdev_dummy_add_in6(netdev, &ip6, &mask);
+
+            /* Insert local route entry for the new address. */
+            ovs_router_force_insert(0, &ip6, plen, true, argv[1],
+                                    &in6addr_any, &ip6);
+
             unixctl_command_reply(conn, "OK");
         } else {
             unixctl_command_reply_error(conn, error);
diff --git a/lib/netdev-linux.c b/lib/netdev-linux.c
index bf91ef462e..80ccade06a 100644
--- a/lib/netdev-linux.c
+++ b/lib/netdev-linux.c
@@ -2403,6 +2403,7 @@ static int
 netdev_linux_read_stringset_info(struct netdev_linux *netdev, uint32_t *len)
 {
     union {
+        struct ethtool_cmd ecmd;
         struct ethtool_sset_info hdr;
         struct {
             uint64_t pad[2];
@@ -2440,9 +2441,12 @@ netdev_linux_read_definitions(struct netdev_linux *netdev,
     int error = 0;
 
     error = netdev_linux_read_stringset_info(netdev, &len);
-    if (error || !len) {
+    if (error) {
         return error;
+    } else if (!len) {
+        return -EOPNOTSUPP;
     }
+
     strings = xzalloc(sizeof *strings + len * ETH_GSTRING_LEN);
 
     strings->cmd = ETHTOOL_GSTRINGS;
@@ -2725,6 +2729,7 @@ netdev_linux_get_speed_locked(struct netdev_linux *netdev,
                               uint32_t *current, uint32_t *max)
 {
     if (netdev_linux_netnsid_is_remote(netdev)) {
+        *current = *max = 0;
         return EOPNOTSUPP;
     }
 
@@ -2734,6 +2739,8 @@ netdev_linux_get_speed_locked(struct netdev_linux *netdev,
                    ? 0 : netdev->current_speed;
         *max = MIN(UINT32_MAX,
                    netdev_features_to_bps(netdev->supported, 0) / 1000000ULL);
+    } else {
+        *current = *max = 0;
     }
     return netdev->get_features_error;
 }
diff --git a/lib/netdev-native-tnl.c b/lib/netdev-native-tnl.c
index dee9ab344e..0f9f07f44b 100644
--- a/lib/netdev-native-tnl.c
+++ b/lib/netdev-native-tnl.c
@@ -240,35 +240,15 @@ udp_extract_tnl_md(struct dp_packet *packet, struct flow_tnl *tnl,
     return udp + 1;
 }
 
-/* Calculate inner l2 l3 l4 len as tunnel outer header is not
- * encapsulated now. */
 static void
 dp_packet_tnl_ol_process(struct dp_packet *packet,
                          const struct ovs_action_push_tnl *data)
 {
-    struct udp_header *udp = NULL;
-    uint8_t opt_len = 0;
-    struct eth_header *eth = NULL;
     struct ip_header *ip = NULL;
-    struct genevehdr *gnh = NULL;
 
-    /* l2 l3 l4 len refer to inner len, tunnel outer
-     * header is not encapsulated here. */
     if (dp_packet_hwol_l4_mask(packet)) {
         ip = dp_packet_l3(packet);
 
-        if (ip->ip_proto == IPPROTO_TCP) {
-            struct tcp_header *th = dp_packet_l4(packet);
-            dp_packet_set_l4_len(packet, TCP_OFFSET(th->tcp_ctl) * 4);
-        } else if (ip->ip_proto == IPPROTO_UDP) {
-            dp_packet_set_l4_len(packet, UDP_HEADER_LEN);
-        } else if (ip->ip_proto == IPPROTO_SCTP) {
-            dp_packet_set_l4_len(packet, SCTP_HEADER_LEN);
-        }
-
-        dp_packet_set_l3_len(packet, (char *) dp_packet_l4(packet) -
-                                     (char *) dp_packet_l3(packet));
-
         if (data->tnl_type == OVS_VPORT_TYPE_GENEVE ||
             data->tnl_type == OVS_VPORT_TYPE_VXLAN) {
 
@@ -279,32 +259,12 @@ dp_packet_tnl_ol_process(struct dp_packet *packet,
                 dp_packet_hwol_set_tx_ipv6(packet);
             }
         }
+    }
 
-        /* Attention please, tunnel inner l2 len is consist of udp header
-         * len and tunnel header len and inner l2 len. */
-        if (data->tnl_type == OVS_VPORT_TYPE_GENEVE) {
-            eth = (struct eth_header *)(data->header);
-            ip = (struct ip_header *)(eth + 1);
-            udp = (struct udp_header *)(ip + 1);
-            gnh = (struct genevehdr *)(udp + 1);
-            opt_len = gnh->opt_len * 4;
-            dp_packet_hwol_set_tunnel_geneve(packet);
-            dp_packet_set_l2_len(packet, (char *) dp_packet_l3(packet) -
-                                         (char *) dp_packet_eth(packet) +
-                                         GENEVE_BASE_HLEN + opt_len);
-        } else if (data->tnl_type == OVS_VPORT_TYPE_VXLAN) {
-            dp_packet_hwol_set_tunnel_vxlan(packet);
-            dp_packet_set_l2_len(packet, (char *) dp_packet_l3(packet) -
-                                         (char *) dp_packet_eth(packet) +
-                                         VXLAN_HLEN);
-        }
-    } else {
-        /* Mark non-l4 packets as tunneled. */
-        if (data->tnl_type == OVS_VPORT_TYPE_GENEVE) {
-            dp_packet_hwol_set_tunnel_geneve(packet);
-        } else if (data->tnl_type == OVS_VPORT_TYPE_VXLAN) {
-            dp_packet_hwol_set_tunnel_vxlan(packet);
-        }
+    if (data->tnl_type == OVS_VPORT_TYPE_GENEVE) {
+        dp_packet_hwol_set_tunnel_geneve(packet);
+    } else if (data->tnl_type == OVS_VPORT_TYPE_VXLAN) {
+        dp_packet_hwol_set_tunnel_vxlan(packet);
     }
 }
 
@@ -932,9 +892,9 @@ netdev_srv6_build_header(const struct netdev *netdev,
                          const struct netdev_tnl_build_header_params *params)
 {
     const struct netdev_tunnel_config *tnl_cfg;
+    union ovs_16aligned_in6_addr *s;
     const struct in6_addr *segs;
     struct srv6_base_hdr *srh;
-    struct in6_addr *s;
     ovs_be16 dl_type;
     int nr_segs;
     int i;
@@ -978,8 +938,7 @@ netdev_srv6_build_header(const struct netdev *netdev,
         return EOPNOTSUPP;
     }
 
-    s = ALIGNED_CAST(struct in6_addr *,
-                     (char *) srh + sizeof *srh);
+    s = (union ovs_16aligned_in6_addr *) (srh + 1);
     for (i = 0; i < nr_segs; i++) {
         /* Segment list is written to the header in reverse order. */
         memcpy(s, &segs[nr_segs - i - 1], sizeof *s);
@@ -1068,7 +1027,10 @@ netdev_srv6_pop_header(struct dp_packet *packet)
     }
 
     pkt_metadata_init_tnl(md);
-    netdev_tnl_ip_extract_tnl_md(packet, tnl, &hlen);
+    if (!netdev_tnl_ip_extract_tnl_md(packet, tnl, &hlen)) {
+        goto err;
+    }
+
     dp_packet_reset_packet(packet, hlen);
 
     return packet;
diff --git a/lib/netdev-offload-tc.c b/lib/netdev-offload-tc.c
index 921d523177..3be1c08d24 100644
--- a/lib/netdev-offload-tc.c
+++ b/lib/netdev-offload-tc.c
@@ -400,6 +400,8 @@ get_next_available_prio(ovs_be16 protocol)
             return TC_RESERVED_PRIORITY_IPV4;
         } else if (protocol == htons(ETH_P_IPV6)) {
             return TC_RESERVED_PRIORITY_IPV6;
+        } else if (protocol == htons(ETH_P_8021Q)) {
+            return TC_RESERVED_PRIORITY_VLAN;
         }
     }
 
diff --git a/lib/netlink-protocol.h b/lib/netlink-protocol.h
index 6eaa7035a4..e4bb28ac9f 100644
--- a/lib/netlink-protocol.h
+++ b/lib/netlink-protocol.h
@@ -155,6 +155,11 @@ enum {
 #define NLA_TYPE_MASK       ~(NLA_F_NESTED | NLA_F_NET_BYTEORDER)
 #endif
 
+/* Introduced in v4.4. */
+#ifndef NLM_F_DUMP_FILTERED
+#define NLM_F_DUMP_FILTERED 0x20
+#endif
+
 /* These were introduced all together in 2.6.14.  (We want our programs to
  * support the newer kernel features even if compiled with older headers.) */
 #ifndef NETLINK_ADD_MEMBERSHIP
@@ -168,6 +173,11 @@ enum {
 #define NETLINK_LISTEN_ALL_NSID 8
 #endif
 
+/* Strict checking of netlink arguments introduced in Linux kernel v4.20. */
+#ifndef NETLINK_GET_STRICT_CHK
+#define NETLINK_GET_STRICT_CHK 12
+#endif
+
 /* These were introduced all together in 2.6.23.  (We want our programs to
  * support the newer kernel features even if compiled with older headers.) */
 #ifndef CTRL_ATTR_MCAST_GRP_MAX
diff --git a/lib/netlink-socket.c b/lib/netlink-socket.c
index 80da20d9f0..5cb1fc89ae 100644
--- a/lib/netlink-socket.c
+++ b/lib/netlink-socket.c
@@ -205,6 +205,15 @@ nl_sock_create(int protocol, struct nl_sock **sockp)
         }
     }
 
+    /* Strict checking only supported for NETLINK_ROUTE. */
+    if (protocol == NETLINK_ROUTE
+        && setsockopt(sock->fd, SOL_NETLINK, NETLINK_GET_STRICT_CHK,
+                      &one, sizeof one) < 0) {
+        VLOG_RL(&rl, errno == ENOPROTOOPT ? VLL_DBG : VLL_WARN,
+                "netlink: could not enable strict checking (%s)",
+                ovs_strerror(errno));
+    }
+
     retval = get_socket_rcvbuf(sock->fd);
     if (retval < 0) {
         retval = -retval;
diff --git a/lib/odp-execute-avx512.c b/lib/odp-execute-avx512.c
index 50c48bfd47..09eb685cba 100644
--- a/lib/odp-execute-avx512.c
+++ b/lib/odp-execute-avx512.c
@@ -366,6 +366,8 @@ avx512_get_delta(__m256i old_header, __m256i new_header)
                                           0xF, 0xF, 0xF, 0xF);
     v_delta = _mm256_permutexvar_epi32(v_swap32a, v_delta);
 
+    v_delta = _mm256_hadd_epi32(v_delta, v_zeros);
+    v_delta = _mm256_shuffle_epi8(v_delta, v_swap16a);
     v_delta = _mm256_hadd_epi32(v_delta, v_zeros);
     v_delta = _mm256_hadd_epi16(v_delta, v_zeros);
 
@@ -471,7 +473,7 @@ action_avx512_ipv4_set_addrs(struct dp_packet_batch *batch,
          * (v_pkt_masked). */
         __m256i v_new_hdr = _mm256_or_si256(v_key_shuf, v_pkt_masked);
 
-        if (dp_packet_hwol_tx_ip_csum(packet)) {
+        if (dp_packet_hwol_l3_ipv4(packet)) {
             dp_packet_ol_reset_ip_csum_good(packet);
         } else {
             ovs_be16 old_csum = ~nh->ip_csum;
@@ -575,6 +577,9 @@ avx512_ipv6_sum_header(__m512i ip6_header)
                                           0xF, 0xF, 0xF, 0xF);
 
     v_delta = _mm256_permutexvar_epi32(v_swap32a, v_delta);
+
+    v_delta = _mm256_hadd_epi32(v_delta, v_zeros);
+    v_delta = _mm256_shuffle_epi8(v_delta, v_swap16a);
     v_delta = _mm256_hadd_epi32(v_delta, v_zeros);
     v_delta = _mm256_hadd_epi16(v_delta, v_zeros);
 
@@ -736,6 +741,14 @@ action_avx512_set_ipv6(struct dp_packet_batch *batch, const struct nlattr *a)
         }
         /* Write back the modified IPv6 addresses. */
         _mm512_mask_storeu_epi64((void *) nh, 0x1F, v_new_hdr);
+
+        /* Scalar method for setting IPv6 tclass field. */
+        if (key->ipv6_tclass) {
+            uint8_t old_tc = ntohl(get_16aligned_be32(&nh->ip6_flow)) >> 20;
+            uint8_t key_tc = key->ipv6_tclass | (old_tc & ~mask->ipv6_tclass);
+
+            packet_set_ipv6_tc(&nh->ip6_flow, key_tc);
+        }
     }
 }
 #endif /* HAVE_AVX512VBMI */
diff --git a/lib/odp-util.c b/lib/odp-util.c
index 9306c9b4d4..5e4f34cf74 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -1797,8 +1797,8 @@ ovs_parse_tnl_push(const char *s, struct ovs_action_push_tnl *data)
     } else if (ovs_scan_len(s, &n, "srv6(segments_left=%"SCNu8,
                             &segments_left)) {
         struct srv6_base_hdr *srh = (struct srv6_base_hdr *) (ip6 + 1);
+        union ovs_16aligned_in6_addr *segs;
         char seg_s[IPV6_SCAN_LEN + 1];
-        struct in6_addr *segs;
         struct in6_addr seg;
         uint8_t n_segs = 0;
 
@@ -1821,7 +1821,7 @@ ovs_parse_tnl_push(const char *s, struct ovs_action_push_tnl *data)
             return -EINVAL;
         }
 
-        segs = ALIGNED_CAST(struct in6_addr *, srh + 1);
+        segs = (union ovs_16aligned_in6_addr *) (srh + 1);
         segs += segments_left;
 
         while (ovs_scan_len(s, &n, IPV6_SCAN_FMT, seg_s)
diff --git a/lib/ofp-prop.c b/lib/ofp-prop.c
index 0a685750c1..0e54543bdd 100644
--- a/lib/ofp-prop.c
+++ b/lib/ofp-prop.c
@@ -21,6 +21,7 @@
 #include "openvswitch/ofp-errors.h"
 #include "openvswitch/ofp-prop.h"
 #include "openvswitch/vlog.h"
+#include "unaligned.h"
 #include "util.h"
 #include "uuid.h"
 
@@ -190,11 +191,12 @@ ofpprop_parse_be64(const struct ofpbuf *property, ovs_be64 *value)
 enum ofperr
 ofpprop_parse_be128(const struct ofpbuf *property, ovs_be128 *value)
 {
-    ovs_be128 *p = property->msg;
+    ovs_32aligned_be128 *p = property->msg;
+
     if (ofpbuf_msgsize(property) != sizeof *p) {
         return OFPERR_OFPBPC_BAD_LEN;
     }
-    *value = *p;
+    *value = get_32aligned_be128(p);
     return 0;
 }
 
@@ -270,12 +272,13 @@ ofpprop_parse_u64(const struct ofpbuf *property, uint64_t *value)
 enum ofperr
 ofpprop_parse_u128(const struct ofpbuf *property, ovs_u128 *value)
 {
-    ovs_be128 *p = property->msg;
-    if (ofpbuf_msgsize(property) != sizeof *p) {
-        return OFPERR_OFPBPC_BAD_LEN;
+    enum ofperr error = ofpprop_parse_be128(property, (ovs_be128 *) value);
+
+    if (!error) {
+        *value = ntoh128(*(ovs_be128 *) value);
     }
-    *value = ntoh128(*p);
-    return 0;
+
+    return error;
 }
 
 /* Attempts to parse 'property' as a property containing a UUID.  If
diff --git a/lib/ofpbuf.c b/lib/ofpbuf.c
index d3d42b4148..232ebeb97b 100644
--- a/lib/ofpbuf.c
+++ b/lib/ofpbuf.c
@@ -197,12 +197,12 @@ ofpbuf_clone_with_headroom(const struct ofpbuf *b, size_t headroom)
     struct ofpbuf *new_buffer;
 
     new_buffer = ofpbuf_clone_data_with_headroom(b->data, b->size, headroom);
-    if (b->header) {
+    if (new_buffer->data && b->header) {
         ptrdiff_t header_offset = (char *) b->header - (char *) b->data;
 
         new_buffer->header = (char *) new_buffer->data + header_offset;
     }
-    if (b->msg) {
+    if (new_buffer->data && b->msg) {
         ptrdiff_t msg_offset = (char *) b->msg - (char *) b->data;
 
         new_buffer->msg = (char *) new_buffer->data + msg_offset;
diff --git a/lib/ovs-rcu.c b/lib/ovs-rcu.c
index 9e07d9bab6..49afcc55c9 100644
--- a/lib/ovs-rcu.c
+++ b/lib/ovs-rcu.c
@@ -326,7 +326,7 @@ ovsrcu_postpone__(void (*function)(void *aux), void *aux)
     cb->aux = aux;
 }
 
-static bool
+static bool OVS_NO_SANITIZE_FUNCTION
 ovsrcu_call_postponed(void)
 {
     struct ovsrcu_cbset *cbset;
diff --git a/lib/ovs-router.c b/lib/ovs-router.c
index ca014d80ed..3d84c9a30a 100644
--- a/lib/ovs-router.c
+++ b/lib/ovs-router.c
@@ -330,6 +330,20 @@ ovs_router_insert(uint32_t mark, const struct in6_addr *ip_dst, uint8_t plen,
     }
 }
 
+/* The same as 'ovs_router_insert', but it adds the route even if updates
+ * from the system routing table are disabled.  Used for unit tests. */
+void
+ovs_router_force_insert(uint32_t mark, const struct in6_addr *ip_dst,
+                        uint8_t plen, bool local, const char output_bridge[],
+                        const struct in6_addr *gw,
+                        const struct in6_addr *prefsrc)
+{
+    uint8_t priority = local ? plen + 64 : plen;
+
+    ovs_router_insert__(mark, priority, local, ip_dst, plen,
+                        output_bridge, gw, prefsrc);
+}
+
 static void
 rt_entry_delete__(const struct cls_rule *cr)
 {
diff --git a/lib/ovs-router.h b/lib/ovs-router.h
index eb4ff85d9e..d7dc7e55f3 100644
--- a/lib/ovs-router.h
+++ b/lib/ovs-router.h
@@ -34,6 +34,11 @@ void ovs_router_insert(uint32_t mark, const struct in6_addr *ip_dst,
                        uint8_t plen, bool local,
                        const char output_bridge[], const struct in6_addr *gw,
                        const struct in6_addr *prefsrc);
+void ovs_router_force_insert(uint32_t mark, const struct in6_addr *ip_dst,
+                             uint8_t plen, bool local,
+                             const char output_bridge[],
+                             const struct in6_addr *gw,
+                             const struct in6_addr *prefsrc);
 void ovs_router_flush(void);
 
 void ovs_router_disable_system_routing_table(void);
diff --git a/lib/packets.c b/lib/packets.c
index 5803d26f4a..edac30b77b 100644
--- a/lib/packets.c
+++ b/lib/packets.c
@@ -1299,7 +1299,7 @@ packet_set_ipv6_flow_label(ovs_16aligned_be32 *flow_label, ovs_be32 flow_key)
     put_16aligned_be32(flow_label, new_label);
 }
 
-static void
+void
 packet_set_ipv6_tc(ovs_16aligned_be32 *flow_label, uint8_t tc)
 {
     ovs_be32 old_label = get_16aligned_be32(flow_label);
diff --git a/lib/packets.h b/lib/packets.h
index 8b6994809f..a102f81634 100644
--- a/lib/packets.h
+++ b/lib/packets.h
@@ -1635,6 +1635,7 @@ void packet_set_ipv6_addr(struct dp_packet *packet, uint8_t proto,
                           bool recalculate_csum);
 void packet_set_ipv6_flow_label(ovs_16aligned_be32 *flow_label,
                                 ovs_be32 flow_key);
+void packet_set_ipv6_tc(ovs_16aligned_be32 *flow_label, uint8_t tc);
 void packet_set_tcp_port(struct dp_packet *, ovs_be16 src, ovs_be16 dst);
 void packet_set_udp_port(struct dp_packet *, ovs_be16 src, ovs_be16 dst);
 void packet_set_sctp_port(struct dp_packet *, ovs_be16 src, ovs_be16 dst);
diff --git a/lib/route-table.c b/lib/route-table.c
index 9927dcc185..f1fe32714e 100644
--- a/lib/route-table.c
+++ b/lib/route-table.c
@@ -26,6 +26,7 @@
 #include <linux/rtnetlink.h>
 #include <net/if.h>
 
+#include "coverage.h"
 #include "hash.h"
 #include "netdev.h"
 #include "netlink.h"
@@ -44,6 +45,8 @@
 
 VLOG_DEFINE_THIS_MODULE(route_table);
 
+COVERAGE_DEFINE(route_table_dump);
+
 struct route_data {
     /* Copied from struct rtmsg. */
     unsigned char rtm_dst_len;
@@ -80,7 +83,7 @@ static struct nln_notifier *name_notifier = NULL;
 
 static bool route_table_valid = false;
 
-static int route_table_reset(void);
+static void route_table_reset(void);
 static void route_table_handle_msg(const struct route_table_msg *);
 static int route_table_parse(struct ofpbuf *, struct route_table_msg *);
 static void route_table_change(const struct route_table_msg *, void *);
@@ -153,26 +156,22 @@ route_table_wait(void)
     ovs_mutex_unlock(&route_table_mutex);
 }
 
-static int
-route_table_reset(void)
+static bool
+route_table_dump_one_table(unsigned char id)
 {
-    struct nl_dump dump;
-    struct rtgenmsg *rtgenmsg;
     uint64_t reply_stub[NL_DUMP_BUFSIZE / 8];
     struct ofpbuf request, reply, buf;
-
-    route_map_clear();
-    netdev_get_addrs_list_flush();
-    route_table_valid = true;
-    rt_change_seq++;
+    struct rtmsg *rq_msg;
+    bool filtered = true;
+    struct nl_dump dump;
 
     ofpbuf_init(&request, 0);
 
-    nl_msg_put_nlmsghdr(&request, sizeof *rtgenmsg, RTM_GETROUTE,
-                        NLM_F_REQUEST);
+    nl_msg_put_nlmsghdr(&request, sizeof *rq_msg, RTM_GETROUTE, NLM_F_REQUEST);
 
-    rtgenmsg = ofpbuf_put_zeros(&request, sizeof *rtgenmsg);
-    rtgenmsg->rtgen_family = AF_UNSPEC;
+    rq_msg = ofpbuf_put_zeros(&request, sizeof *rq_msg);
+    rq_msg->rtm_family = AF_UNSPEC;
+    rq_msg->rtm_table = id;
 
     nl_dump_start(&dump, NETLINK_ROUTE, &request);
     ofpbuf_uninit(&request);
@@ -182,12 +181,43 @@ route_table_reset(void)
         struct route_table_msg msg;
 
         if (route_table_parse(&reply, &msg)) {
+            struct nlmsghdr *nlmsghdr = nl_msg_nlmsghdr(&reply);
+
+            /* Older kernels do not support filtering. */
+            if (!(nlmsghdr->nlmsg_flags & NLM_F_DUMP_FILTERED)) {
+                filtered = false;
+            }
             route_table_handle_msg(&msg);
         }
     }
     ofpbuf_uninit(&buf);
+    nl_dump_done(&dump);
+
+    return filtered;
+}
+
+static void
+route_table_reset(void)
+{
+    unsigned char tables[] = {
+        RT_TABLE_DEFAULT,
+        RT_TABLE_MAIN,
+        RT_TABLE_LOCAL,
+    };
 
-    return nl_dump_done(&dump);
+    route_map_clear();
+    netdev_get_addrs_list_flush();
+    route_table_valid = true;
+    rt_change_seq++;
+
+    COVERAGE_INC(route_table_dump);
+
+    for (size_t i = 0; i < ARRAY_SIZE(tables); i++) {
+        if (!route_table_dump_one_table(tables[i])) {
+            /* Got unfiltered reply, no need to dump further. */
+            break;
+        }
+    }
 }
 
 /* Return RTNLGRP_IPV4_ROUTE or RTNLGRP_IPV6_ROUTE on success, 0 on parse
@@ -203,6 +233,7 @@ route_table_parse(struct ofpbuf *buf, struct route_table_msg *change)
         [RTA_GATEWAY] = { .type = NL_A_U32, .optional = true },
         [RTA_MARK] = { .type = NL_A_U32, .optional = true },
         [RTA_PREFSRC] = { .type = NL_A_U32, .optional = true },
+        [RTA_TABLE] = { .type = NL_A_U32, .optional = true },
     };
 
     static const struct nl_policy policy6[] = {
@@ -211,6 +242,7 @@ route_table_parse(struct ofpbuf *buf, struct route_table_msg *change)
         [RTA_MARK] = { .type = NL_A_U32, .optional = true },
         [RTA_GATEWAY] = { .type = NL_A_IPV6, .optional = true },
         [RTA_PREFSRC] = { .type = NL_A_IPV6, .optional = true },
+        [RTA_TABLE] = { .type = NL_A_U32, .optional = true },
     };
 
     struct nlattr *attrs[ARRAY_SIZE(policy)];
@@ -232,6 +264,7 @@ route_table_parse(struct ofpbuf *buf, struct route_table_msg *change)
 
     if (parsed) {
         const struct nlmsghdr *nlmsg;
+        uint32_t table_id;
         int rta_oif;      /* Output interface index. */
 
         nlmsg = buf->data;
@@ -247,6 +280,19 @@ route_table_parse(struct ofpbuf *buf, struct route_table_msg *change)
             rtm->rtm_type != RTN_LOCAL) {
             change->relevant = false;
         }
+
+        table_id = rtm->rtm_table;
+        if (attrs[RTA_TABLE]) {
+            table_id = nl_attr_get_u32(attrs[RTA_TABLE]);
+        }
+        /* Do not consider changes in non-standard routing tables. */
+        if (table_id
+            && table_id != RT_TABLE_DEFAULT
+            && table_id != RT_TABLE_MAIN
+            && table_id != RT_TABLE_LOCAL) {
+            change->relevant = false;
+        }
+
         change->nlmsg_type     = nlmsg->nlmsg_type;
         change->rd.rtm_dst_len = rtm->rtm_dst_len + (ipv4 ? 96 : 0);
         change->rd.local = rtm->rtm_type == RTN_LOCAL;
@@ -312,7 +358,9 @@ static void
 route_table_change(const struct route_table_msg *change OVS_UNUSED,
                    void *aux OVS_UNUSED)
 {
-    route_table_valid = false;
+    if (!change || change->relevant) {
+        route_table_valid = false;
+    }
 }
 
 static void
diff --git a/lib/socket-util.c b/lib/socket-util.c
index 3eb3a3816b..b3f541b6db 100644
--- a/lib/socket-util.c
+++ b/lib/socket-util.c
@@ -546,9 +546,15 @@ inet_parse_active(const char *target_, int default_port,
     if (!host) {
         VLOG_ERR("%s: host must be specified", target_);
         ok = false;
+        if (dns_failure) {
+            *dns_failure = false;
+        }
     } else if (!port && default_port < 0) {
         VLOG_ERR("%s: port must be specified", target_);
         ok = false;
+        if (dns_failure) {
+            *dns_failure = false;
+        }
     } else {
         ok = parse_sockaddr_components(ss, host, port, default_port,
                                        target_, resolve_host, dns_failure);
@@ -671,6 +677,9 @@ inet_parse_passive(const char *target_, int default_port,
     if (!port && default_port < 0) {
         VLOG_ERR("%s: port must be specified", target_);
         ok = false;
+        if (dns_failure) {
+            *dns_failure = false;
+        }
     } else {
         ok = parse_sockaddr_components(ss, host, port, default_port,
                                        target_, resolve_host, dns_failure);
diff --git a/lib/table.c b/lib/table.c
index 48d18b6518..b7addbf390 100644
--- a/lib/table.c
+++ b/lib/table.c
@@ -522,7 +522,7 @@ table_print_json__(const struct table *table, const struct table_style *style,
         json_object_put_string(json, "caption", table->caption);
     }
     if (table->timestamp) {
-        json_object_put_nocopy(
+        json_object_put(
             json, "time",
             json_string_create_nocopy(table_format_timestamp__()));
     }
diff --git a/lib/tc.c b/lib/tc.c
index e9bcae4e4b..e55ba3b1bb 100644
--- a/lib/tc.c
+++ b/lib/tc.c
@@ -3056,17 +3056,17 @@ nl_msg_put_flower_rewrite_pedits(struct ofpbuf *request,
                                  struct tc_action *action,
                                  uint32_t action_pc)
 {
-    struct {
+    union {
         struct tc_pedit sel;
-        struct tc_pedit_key keys[MAX_PEDIT_OFFSETS];
-        struct tc_pedit_key_ex keys_ex[MAX_PEDIT_OFFSETS];
-    } sel = {
-        .sel = {
-            .nkeys = 0
-        }
-    };
+        uint8_t buffer[sizeof(struct tc_pedit)
+                       + MAX_PEDIT_OFFSETS * sizeof(struct tc_pedit_key)];
+    } sel;
+    struct tc_pedit_key_ex keys_ex[MAX_PEDIT_OFFSETS];
     int i, j, err;
 
+    memset(&sel, 0, sizeof sel);
+    memset(keys_ex, 0, sizeof keys_ex);
+
     for (i = 0; i < ARRAY_SIZE(flower_pedit_map); i++) {
         struct flower_key_to_pedit *m = &flower_pedit_map[i];
         struct tc_pedit_key *pedit_key = NULL;
@@ -3100,8 +3100,8 @@ nl_msg_put_flower_rewrite_pedits(struct ofpbuf *request,
                 return EOPNOTSUPP;
             }
 
-            pedit_key = &sel.keys[sel.sel.nkeys];
-            pedit_key_ex = &sel.keys_ex[sel.sel.nkeys];
+            pedit_key = &sel.sel.keys[sel.sel.nkeys];
+            pedit_key_ex = &keys_ex[sel.sel.nkeys];
             pedit_key_ex->cmd = TCA_PEDIT_KEY_EX_CMD_SET;
             pedit_key_ex->htype = m->htype;
             pedit_key->off = cur_offset;
@@ -3121,7 +3121,7 @@ nl_msg_put_flower_rewrite_pedits(struct ofpbuf *request,
             }
         }
     }
-    nl_msg_put_act_pedit(request, &sel.sel, sel.keys_ex,
+    nl_msg_put_act_pedit(request, &sel.sel, keys_ex,
                          flower->csum_update_flags ? TC_ACT_PIPE : action_pc);
 
     return 0;
diff --git a/lib/tc.h b/lib/tc.h
index fdbcf4b7cb..8442c8d8b8 100644
--- a/lib/tc.h
+++ b/lib/tc.h
@@ -51,6 +51,7 @@ enum tc_flower_reserved_prio {
     TC_RESERVED_PRIORITY_POLICE,
     TC_RESERVED_PRIORITY_IPV4,
     TC_RESERVED_PRIORITY_IPV6,
+    TC_RESERVED_PRIORITY_VLAN,
     __TC_RESERVED_PRIORITY_MAX
 };
 #define TC_RESERVED_PRIORITY_MAX (__TC_RESERVED_PRIORITY_MAX -1)
diff --git a/lib/vlog.c b/lib/vlog.c
index b2653142f3..59b524b097 100644
--- a/lib/vlog.c
+++ b/lib/vlog.c
@@ -29,6 +29,7 @@
 #include <time.h>
 #include <unistd.h>
 #include "async-append.h"
+#include "backtrace.h"
 #include "coverage.h"
 #include "dirs.h"
 #include "openvswitch/dynamic-string.h"
@@ -410,10 +411,10 @@ vlog_set_log_file__(char *new_log_file_name)
 
     /* Close old log file, if any. */
     ovs_mutex_lock(&log_file_mutex);
+    async_append_destroy(log_writer);
     if (log_fd >= 0) {
         close(log_fd);
     }
-    async_append_destroy(log_writer);
     free(log_file_name);
 
     /* Install new log file. */
@@ -1274,8 +1275,9 @@ vlog_fatal(const struct vlog_module *module, const char *message, ...)
     va_end(args);
 }
 
-/* Logs 'message' to 'module' at maximum verbosity, then calls abort().  Always
- * writes the message to stderr, even if the console destination is disabled.
+/* Attempts to log a stack trace, logs 'message' to 'module' at maximum
+ * verbosity, then calls abort().  Always writes the message to stderr, even
+ * if the console destination is disabled.
  *
  * Choose this function instead of vlog_fatal_valist() if the daemon monitoring
  * facility should automatically restart the current daemon.  */
@@ -1289,6 +1291,10 @@ vlog_abort_valist(const struct vlog_module *module_,
      * message written by the later ovs_abort_valist(). */
     module->levels[VLF_CONSOLE] = VLL_OFF;
 
+    /* Printing the stack trace before the 'message', because the 'message'
+     * will flush the async log queue (VLL_EMER).  With a different order we
+     * would need to flush the queue manually again. */
+    log_backtrace();
     vlog_valist(module, VLL_EMER, message, args);
     ovs_abort_valist(0, message, args);
 }
diff --git a/m4/ax_check_openssl.m4 b/m4/ax_check_openssl.m4
index 281d4dc65e..faa5babde2 100644
--- a/m4/ax_check_openssl.m4
+++ b/m4/ax_check_openssl.m4
@@ -81,7 +81,8 @@ AC_DEFUN([AX_CHECK_OPENSSL], [
                 SSL_INCLUDES="-I$ssldir/include"
                 SSL_LDFLAGS="-L$ssldir/lib"
                 if test "$WIN32" = "yes"; then
-                    SSL_LIBS="-lssleay32 -llibeay32"
+                    SSL_LDFLAGS="$SSL_LDFLAGS -L$ssldir/lib/VC/x64/MT"
+                    SSL_LIBS="-llibssl -llibcrypto"
                     SSL_DIR=/$(echo ${ssldir} | ${SED} -e 's/://')
                 else
                     SSL_LIBS="-lssl -lcrypto"
diff --git a/ofproto/bond.c b/ofproto/bond.c
index cfdf44f854..c31869a4c7 100644
--- a/ofproto/bond.c
+++ b/ofproto/bond.c
@@ -186,7 +186,7 @@ static struct bond_member *choose_output_member(const struct bond *,
                                                 struct flow_wildcards *,
                                                 uint16_t vlan)
     OVS_REQ_RDLOCK(rwlock);
-static void update_recirc_rules__(struct bond *);
+static void update_recirc_rules(struct bond *) OVS_REQ_WRLOCK(rwlock);
 static bool bond_may_recirc(const struct bond *);
 static void bond_update_post_recirc_rules__(struct bond *, bool force)
     OVS_REQ_WRLOCK(rwlock);
@@ -299,7 +299,10 @@ bond_unref(struct bond *bond)
     }
     free(bond->hash);
     bond->hash = NULL;
-    update_recirc_rules__(bond);
+
+    ovs_rwlock_wrlock(&rwlock);
+    update_recirc_rules(bond);
+    ovs_rwlock_unlock(&rwlock);
 
     hmap_destroy(&bond->pr_rule_ops);
     free(bond->primary);
@@ -331,17 +334,8 @@ add_pr_rule(struct bond *bond, const struct match *match,
     hmap_insert(&bond->pr_rule_ops, &pr_op->hmap_node, hash);
 }
 
-/* This function should almost never be called directly.
- * 'update_recirc_rules()' should be called instead.  Since
- * this function modifies 'bond->pr_rule_ops', it is only
- * safe when 'rwlock' is held.
- *
- * However, when the 'bond' is the only reference in the system,
- * calling this function avoid acquiring lock only to satisfy
- * lock annotation. Currently, only 'bond_unref()' calls
- * this function directly.  */
 static void
-update_recirc_rules__(struct bond *bond)
+update_recirc_rules(struct bond *bond) OVS_REQ_WRLOCK(rwlock)
 {
     struct match match;
     struct bond_pr_rule_op *pr_op;
@@ -407,6 +401,15 @@ update_recirc_rules__(struct bond *bond)
 
                 VLOG_ERR("failed to remove post recirculation flow %s", err_s);
                 free(err_s);
+            } else if (bond->hash) {
+                /* If the flow deletion failed, a subsequent call to
+                 * ofproto_dpif_add_internal_flow() would just modify the
+                 * flow preserving its statistics.  Therefore, only reset
+                 * the entry's byte counter if it succeeds. */
+                uint32_t hash = pr_op->match.flow.dp_hash & BOND_MASK;
+                struct bond_entry *entry = &bond->hash[hash];
+
+                entry->pr_tx_bytes = 0;
             }
 
             hmap_remove(&bond->pr_rule_ops, &pr_op->hmap_node);
@@ -421,12 +424,6 @@ update_recirc_rules__(struct bond *bond)
     ofpbuf_uninit(&ofpacts);
 }
 
-static void
-update_recirc_rules(struct bond *bond)
-    OVS_REQ_RDLOCK(rwlock)
-{
-    update_recirc_rules__(bond);
-}
 
 /* Updates 'bond''s overall configuration to 's'.
  *
diff --git a/ofproto/ofproto-dpif-trace.c b/ofproto/ofproto-dpif-trace.c
index b86e7fe07e..e43d9f88c9 100644
--- a/ofproto/ofproto-dpif-trace.c
+++ b/ofproto/ofproto-dpif-trace.c
@@ -102,7 +102,7 @@ oftrace_add_recirc_node(struct ovs_list *recirc_queue,
     node->flow = *flow;
     node->flow.recirc_id = recirc_id;
     node->flow.ct_zone = zone;
-    node->nat_act = ofn;
+    node->nat_act = ofn ? xmemdup(ofn, sizeof *ofn) : NULL;
     node->packet = packet ? dp_packet_clone(packet) : NULL;
 
     return true;
@@ -113,6 +113,7 @@ oftrace_recirc_node_destroy(struct oftrace_recirc_node *node)
 {
     if (node) {
         recirc_free_id(node->recirc_id);
+        free(node->nat_act);
         dp_packet_delete(node->packet);
         free(node);
     }
@@ -845,17 +846,35 @@ ofproto_trace(struct ofproto_dpif *ofproto, const struct flow *flow,
               bool names)
 {
     struct ovs_list recirc_queue = OVS_LIST_INITIALIZER(&recirc_queue);
+    int recirculations = 0;
+
     ofproto_trace__(ofproto, flow, packet, &recirc_queue,
                     ofpacts, ofpacts_len, output, names);
 
     struct oftrace_recirc_node *recirc_node;
     LIST_FOR_EACH_POP (recirc_node, node, &recirc_queue) {
+        if (recirculations++ > 4096) {
+            ds_put_cstr(output, "\n\n");
+            ds_put_char_multiple(output, '=', 79);
+            ds_put_cstr(output, "\nTrace reached the recirculation limit."
+                                "  Sopping the trace here.");
+            ds_put_format(output,
+                          "\nQueued but not processed: %"PRIuSIZE
+                          " recirculations.",
+                          ovs_list_size(&recirc_queue) + 1);
+            oftrace_recirc_node_destroy(recirc_node);
+            break;
+        }
         ofproto_trace_recirc_node(recirc_node, next_ct_states, output);
         ofproto_trace__(ofproto, &recirc_node->flow, recirc_node->packet,
                         &recirc_queue, ofpacts, ofpacts_len, output,
                         names);
         oftrace_recirc_node_destroy(recirc_node);
     }
+    /* Destroy remaining recirculation nodes, if any. */
+    LIST_FOR_EACH_POP (recirc_node, node, &recirc_queue) {
+        oftrace_recirc_node_destroy(recirc_node);
+    }
 }
 
 void
diff --git a/ofproto/ofproto-dpif-trace.h b/ofproto/ofproto-dpif-trace.h
index f579a5ca46..f023b10cdf 100644
--- a/ofproto/ofproto-dpif-trace.h
+++ b/ofproto/ofproto-dpif-trace.h
@@ -73,7 +73,7 @@ struct oftrace_recirc_node {
     uint32_t recirc_id;
     struct flow flow;
     struct dp_packet *packet;
-    const struct ofpact_nat *nat_act;
+    struct ofpact_nat *nat_act;
 };
 
 /* A node within a next_ct_states list. */
diff --git a/ofproto/ofproto-dpif-upcall.c b/ofproto/ofproto-dpif-upcall.c
index b5cbeed878..a046f8a339 100644
--- a/ofproto/ofproto-dpif-upcall.c
+++ b/ofproto/ofproto-dpif-upcall.c
@@ -59,6 +59,7 @@ COVERAGE_DEFINE(handler_duplicate_upcall);
 COVERAGE_DEFINE(revalidate_missed_dp_flow);
 COVERAGE_DEFINE(ukey_dp_change);
 COVERAGE_DEFINE(ukey_invalid_stat_reset);
+COVERAGE_DEFINE(ukey_replace_contention);
 COVERAGE_DEFINE(upcall_flow_limit_grew);
 COVERAGE_DEFINE(upcall_flow_limit_hit);
 COVERAGE_DEFINE(upcall_flow_limit_kill);
@@ -1428,8 +1429,6 @@ upcall_cb(const struct dp_packet *packet, const struct flow *flow, ovs_u128 *ufi
     }
 
     if (upcall.ukey && !ukey_install(udpif, upcall.ukey)) {
-        static struct vlog_rate_limit rll = VLOG_RATE_LIMIT_INIT(1, 1);
-        VLOG_WARN_RL(&rll, "upcall_cb failure: ukey installation fails");
         error = ENOSPC;
     }
 out:
@@ -1927,15 +1926,15 @@ try_ukey_replace(struct umap *umap, struct udpif_key *old_ukey,
             transition_ukey(old_ukey, UKEY_DELETED);
             transition_ukey(new_ukey, UKEY_VISIBLE);
             replaced = true;
+            COVERAGE_INC(upcall_ukey_replace);
+        } else {
+            COVERAGE_INC(handler_duplicate_upcall);
         }
         ovs_mutex_unlock(&old_ukey->mutex);
-    }
-
-    if (replaced) {
-        COVERAGE_INC(upcall_ukey_replace);
     } else {
-        COVERAGE_INC(handler_duplicate_upcall);
+        COVERAGE_INC(ukey_replace_contention);
     }
+
     return replaced;
 }
 
@@ -2973,6 +2972,7 @@ revalidator_sweep__(struct revalidator *revalidator, bool purge)
             /* Handler threads could be holding a ukey lock while it installs a
              * new flow, so don't hang around waiting for access to it. */
             if (ovs_mutex_trylock(&ukey->mutex)) {
+                COVERAGE_INC(upcall_ukey_contention);
                 continue;
             }
             ukey_state = ukey->state;
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index 1cf4d5f7c9..7c49508950 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -3815,6 +3815,8 @@ native_tunnel_output(struct xlate_ctx *ctx, const struct xport *xport,
 
     if (flow->tunnel.ip_src) {
         in6_addr_set_mapped_ipv4(&s_ip6, flow->tunnel.ip_src);
+    } else if (ipv6_addr_is_set(&flow->tunnel.ipv6_src)) {
+        s_ip6 = flow->tunnel.ipv6_src;
     }
 
     err = tnl_route_lookup_flow(ctx, flow, &d_ip6, &s_ip6, &out_dev);
@@ -5078,10 +5080,37 @@ put_controller_user_action(struct xlate_ctx *ctx,
                            bool dont_send, bool continuation,
                            uint32_t recirc_id, int len,
                            enum ofp_packet_in_reason reason,
+                           uint32_t provider_meter_id,
                            uint16_t controller_id)
 {
     struct user_action_cookie cookie;
 
+    /* If the controller action didn't request a meter (indicated by a
+     * 'meter_id' argument other than NX_CTLR_NO_METER), see if one was
+     * configured through the "controller" virtual meter.
+     *
+     * Internally, ovs-vswitchd uses UINT32_MAX to indicate no meter is
+     * configured. */
+    uint32_t meter_id;
+    if (provider_meter_id == UINT32_MAX) {
+        meter_id = ctx->xbridge->ofproto->up.controller_meter_id;
+    } else {
+        meter_id = provider_meter_id;
+    }
+
+    size_t offset;
+    size_t ac_offset;
+    if (meter_id != UINT32_MAX) {
+        /* If controller meter is configured, generate
+         * clone(meter,userspace) action. */
+        offset = nl_msg_start_nested(ctx->odp_actions, OVS_ACTION_ATTR_SAMPLE);
+        nl_msg_put_u32(ctx->odp_actions, OVS_SAMPLE_ATTR_PROBABILITY,
+                       UINT32_MAX);
+        ac_offset = nl_msg_start_nested(ctx->odp_actions,
+                                        OVS_SAMPLE_ATTR_ACTIONS);
+        nl_msg_put_u32(ctx->odp_actions, OVS_ACTION_ATTR_METER, meter_id);
+    }
+
     memset(&cookie, 0, sizeof cookie);
     cookie.type = USER_ACTION_COOKIE_CONTROLLER;
     cookie.ofp_in_port = OFPP_NONE,
@@ -5099,6 +5128,11 @@ put_controller_user_action(struct xlate_ctx *ctx,
     uint32_t pid = dpif_port_get_pid(ctx->xbridge->dpif, odp_port);
     odp_put_userspace_action(pid, &cookie, sizeof cookie, ODPP_NONE,
                              false, ctx->odp_actions, NULL);
+
+    if (meter_id != UINT32_MAX) {
+        nl_msg_end_nested(ctx->odp_actions, ac_offset);
+        nl_msg_end_nested(ctx->odp_actions, offset);
+    }
 }
 
 static void
@@ -5143,32 +5177,6 @@ xlate_controller_action(struct xlate_ctx *ctx, int len,
     }
     recirc_refs_add(&ctx->xout->recircs, recirc_id);
 
-    /* If the controller action didn't request a meter (indicated by a
-     * 'meter_id' argument other than NX_CTLR_NO_METER), see if one was
-     * configured through the "controller" virtual meter.
-     *
-     * Internally, ovs-vswitchd uses UINT32_MAX to indicate no meter is
-     * configured. */
-    uint32_t meter_id;
-    if (provider_meter_id == UINT32_MAX) {
-        meter_id = ctx->xbridge->ofproto->up.controller_meter_id;
-    } else {
-        meter_id = provider_meter_id;
-    }
-
-    size_t offset;
-    size_t ac_offset;
-    if (meter_id != UINT32_MAX) {
-        /* If controller meter is configured, generate clone(meter, userspace)
-         * action. */
-        offset = nl_msg_start_nested(ctx->odp_actions, OVS_ACTION_ATTR_SAMPLE);
-        nl_msg_put_u32(ctx->odp_actions, OVS_SAMPLE_ATTR_PROBABILITY,
-                       UINT32_MAX);
-        ac_offset = nl_msg_start_nested(ctx->odp_actions,
-                                        OVS_SAMPLE_ATTR_ACTIONS);
-        nl_msg_put_u32(ctx->odp_actions, OVS_ACTION_ATTR_METER, meter_id);
-    }
-
     /* Generate the datapath flows even if we don't send the packet-in
      * so that debugging more closely represents normal state. */
     bool dont_send = false;
@@ -5176,12 +5184,7 @@ xlate_controller_action(struct xlate_ctx *ctx, int len,
         dont_send = true;
     }
     put_controller_user_action(ctx, dont_send, false, recirc_id, len,
-                               reason, controller_id);
-
-    if (meter_id != UINT32_MAX) {
-        nl_msg_end_nested(ctx->odp_actions, ac_offset);
-        nl_msg_end_nested(ctx->odp_actions, offset);
-    }
+                               reason, provider_meter_id, controller_id);
 }
 
 /* Creates a frozen state, and allocates a unique recirc id for the given
@@ -5233,6 +5236,7 @@ finish_freezing__(struct xlate_ctx *ctx, uint8_t table)
         put_controller_user_action(ctx, false, true, recirc_id,
                                    ctx->pause->max_len,
                                    ctx->pause->reason,
+                                   ctx->pause->provider_meter_id,
                                    ctx->pause->controller_id);
     } else {
         if (ctx->recirc_update_dp_hash) {
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index f59d69c4d1..fe034f9717 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -3904,15 +3904,21 @@ port_query_by_name(const struct ofproto *ofproto_, const char *devname,
     int error;
 
     if (sset_contains(&ofproto->ghost_ports, devname)) {
-        const char *type = netdev_get_type_from_name(devname);
-
         /* We may be called before ofproto->up.port_by_name is populated with
          * the appropriate ofport.  For this reason, we must get the name and
-         * type from the netdev layer directly. */
-        if (type) {
-            const struct ofport *ofport;
+         * type from the netdev layer directly.
+         * However, when a port deleted, the corresponding netdev is also
+         * removed from netdev_shash. netdev_get_type_from_name returns NULL
+         * in such case and we should try to get type from ofport->netdev. */
+        const char *type = netdev_get_type_from_name(devname);
+        const struct ofport *ofport =
+                        shash_find_data(&ofproto->up.port_by_name, devname);
 
-            ofport = shash_find_data(&ofproto->up.port_by_name, devname);
+        if (!type && ofport && ofport->netdev) {
+            type = netdev_get_type(ofport->netdev);
+        }
+
+        if (type) {
             ofproto_port->ofp_port = ofport ? ofport->ofp_port : OFPP_NONE;
             ofproto_port->name = xstrdup(devname);
             ofproto_port->type = xstrdup(type);
diff --git a/ovsdb/automake.mk b/ovsdb/automake.mk
index eba713bb6d..d484fe9deb 100644
--- a/ovsdb/automake.mk
+++ b/ovsdb/automake.mk
@@ -114,11 +114,13 @@ $(OVSIDL_BUILT): ovsdb/ovsdb-idlc.in python/ovs/dirs.py
 
 # ovsdb-doc
 EXTRA_DIST += ovsdb/ovsdb-doc
+FLAKE8_PYFILES += ovsdb/ovsdb-doc
 OVSDB_DOC = $(run_python) $(srcdir)/ovsdb/ovsdb-doc
 ovsdb/ovsdb-doc: python/ovs/dirs.py
 
 # ovsdb-dot
 EXTRA_DIST += ovsdb/ovsdb-dot.in ovsdb/dot2pic
+FLAKE8_PYFILES += ovsdb/ovsdb-dot.in ovsdb/dot2pic
 noinst_SCRIPTS += ovsdb/ovsdb-dot
 CLEANFILES += ovsdb/ovsdb-dot
 OVSDB_DOT = $(run_python) $(srcdir)/ovsdb/ovsdb-dot.in
diff --git a/ovsdb/dot2pic b/ovsdb/dot2pic
index 2f858e19d5..3db6444de6 100755
--- a/ovsdb/dot2pic
+++ b/ovsdb/dot2pic
@@ -17,6 +17,7 @@
 import getopt
 import sys
 
+
 def dot2pic(src, dst):
     scale = 1.0
     while True:
@@ -49,8 +50,8 @@ def dot2pic(src, dst):
                 dst.write("box at %f,%f wid %f height %f\n"
                           % (x, y, width, height))
         elif command == 'edge':
-            tail = words[1]
-            head = words[2]
+            # tail = words[1]
+            # head = words[2]
             n = int(words[3])
 
             # Extract x,y coordinates.
@@ -114,4 +115,3 @@ else:
 if font_scale:
     print(".ps %+d" % font_scale)
 print(".PE")
-
diff --git a/ovsdb/ovsdb-client.c b/ovsdb/ovsdb-client.c
index 7249805bab..cf2ecfd08a 100644
--- a/ovsdb/ovsdb-client.c
+++ b/ovsdb/ovsdb-client.c
@@ -451,8 +451,9 @@ usage(void)
            "    wait until DATABASE reaches STATE "
            "(\"added\" or \"connected\" or \"removed\")\n"
            "    in DATBASE on SERVER.\n"
-           "\n  dump [SERVER] [DATABASE]\n"
-           "    dump contents of DATABASE on SERVER to stdout\n"
+           "\n  dump [SERVER] [DATABASE] [TABLE]\n"
+           "    dump contents of TABLE (or all tables) in DATABASE on SERVER\n"
+           "    to stdout\n"
            "\n  backup [SERVER] [DATABASE] > SNAPSHOT\n"
            "    dump database contents in the form of a database file\n"
            "\n  [--force] restore [SERVER] [DATABASE] < SNAPSHOT\n"
diff --git a/ovsdb/ovsdb-doc b/ovsdb/ovsdb-doc
index 099770d253..2edf487a28 100755
--- a/ovsdb/ovsdb-doc
+++ b/ovsdb/ovsdb-doc
@@ -14,9 +14,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from datetime import date
 import getopt
-import os
 import sys
 import xml.dom.minidom
 
@@ -24,10 +22,13 @@ import ovs.json
 from ovs.db import error
 import ovs.db.schema
 
-from ovs_build_helpers.nroff import *
+from ovs_build_helpers.nroff import block_xml_to_nroff
+from ovs_build_helpers.nroff import escape_nroff_literal
+from ovs_build_helpers.nroff import text_to_nroff
 
 argv0 = sys.argv[0]
 
+
 def typeAndConstraintsToNroff(column):
     type = column.type.toEnglish(escape_nroff_literal)
     constraints = column.type.constraintsToEnglish(escape_nroff_literal,
@@ -38,6 +39,7 @@ def typeAndConstraintsToNroff(column):
         type += " (must be unique within table)"
     return type
 
+
 def columnGroupToNroff(table, groupXml, documented_columns):
     introNodes = []
     columnNodes = []
@@ -49,7 +51,10 @@ def columnGroupToNroff(table, groupXml, documented_columns):
             if (columnNodes
                 and not (node.nodeType == node.TEXT_NODE
                          and node.data.isspace())):
-                raise error.Error("text follows <column> or <group> inside <group>: %s" % node)
+                raise error.Error(
+                    "text follows <column> or <group> inside <group>: %s"
+                    % node
+                )
             introNodes += [node]
 
     summary = []
@@ -65,15 +70,9 @@ def columnGroupToNroff(table, groupXml, documented_columns):
                 if node.hasAttribute('type'):
                     type_string = node.attributes['type'].nodeValue
                     type_json = ovs.json.from_string(str(type_string))
-                    # py2 -> py3 means str -> bytes and unicode -> str
-                    try:
-                        if type(type_json) in (str, unicode):
-                            raise error.Error("%s %s:%s has invalid 'type': %s" 
-                                              % (table.name, name, key, type_json))
-                    except:
-                        if type(type_json) in (bytes, str):
-                            raise error.Error("%s %s:%s has invalid 'type': %s" 
-                                              % (table.name, name, key, type_json))
+                    if type(type_json) in (bytes, str):
+                        raise error.Error("%s %s:%s has invalid 'type': %s"
+                                          % (table.name, name, key, type_json))
                     type_ = ovs.db.types.BaseType.from_json(type_json)
                 else:
                     type_ = column.type.value
@@ -91,10 +90,11 @@ def columnGroupToNroff(table, groupXml, documented_columns):
                     else:
                         if type_.type != column.type.value.type:
                             type_english = type_.toEnglish()
+                            typeNroff += ", containing "
                             if type_english[0] in 'aeiou':
-                                typeNroff += ", containing an %s" % type_english
+                                typeNroff += "an %s" % type_english
                             else:
-                                typeNroff += ", containing a %s" % type_english
+                                typeNroff += "a %s" % type_english
                         constraints = (
                             type_.constraintsToEnglish(escape_nroff_literal,
                                                        text_to_nroff))
@@ -121,6 +121,7 @@ def columnGroupToNroff(table, groupXml, documented_columns):
             raise error.Error("unknown element %s in <table>" % node.tagName)
     return summary, intro, body
 
+
 def tableSummaryToNroff(summary, level=0):
     s = ""
     for type, name, arg in summary:
@@ -132,6 +133,7 @@ def tableSummaryToNroff(summary, level=0):
             s += ".RE\n"
     return s
 
+
 def tableToNroff(schema, tableXml):
     tableName = tableXml.attributes['name'].nodeValue
     table = schema.tables[tableName]
@@ -156,20 +158,17 @@ def tableToNroff(schema, tableXml):
 
     return s
 
+
 def docsToNroff(schemaFile, xmlFile, erFile, version=None):
     schema = ovs.db.schema.DbSchema.from_json(ovs.json.from_file(schemaFile))
     doc = xml.dom.minidom.parse(xmlFile).documentElement
 
-    schemaDate = os.stat(schemaFile).st_mtime
-    xmlDate = os.stat(xmlFile).st_mtime
-    d = date.fromtimestamp(max(schemaDate, xmlDate))
-
     if doc.hasAttribute('name'):
         manpage = doc.attributes['name'].nodeValue
     else:
         manpage = schema.name
 
-    if version == None:
+    if version is None:
         version = "UNKNOWN"
 
     # Putting '\" p as the first line tells "man" that the manpage
@@ -194,7 +193,6 @@ def docsToNroff(schemaFile, xmlFile, erFile, version=None):
 .PP
 ''' % (manpage, schema.version, version, text_to_nroff(manpage), schema.name)
 
-    tables = ""
     introNodes = []
     tableNodes = []
     summary = []
@@ -237,8 +235,8 @@ Purpose
 """ % (name, text_to_nroff(title))
 
     if erFile:
-        s += """
-.\\" check if in troff mode (TTY)
+        s += r"""
+.\" check if in troff mode (TTY)
 .if t \{
 .bp
 .SH "TABLE RELATIONSHIPS"
@@ -248,8 +246,8 @@ database.  Each node represents a table.  Tables that are part of the
 ``root set'' are shown with double borders.  Each edge leads from the
 table that contains it and points to the table that its value
 represents.  Edges are labeled with their column names, followed by a
-constraint on the number of allowed values: \\fB?\\fR for zero or one,
-\\fB*\\fR for zero or more, \\fB+\\fR for one or more.  Thick lines
+constraint on the number of allowed values: \fB?\fR for zero or one,
+\fB*\fR for zero or more, \fB+\fR for one or more.  Thick lines
 represent strong references; thin lines represent weak references.
 .RS -1in
 """
@@ -263,6 +261,7 @@ represent strong references; thin lines represent weak references.
         s += tableToNroff(schema, node) + "\n"
     return s
 
+
 def usage():
     print("""\
 %(argv0)s: ovsdb schema documentation generator
@@ -278,6 +277,7 @@ The following options are also available:
 """ % {'argv0': argv0})
     sys.exit(0)
 
+
 if __name__ == "__main__":
     try:
         try:
diff --git a/ovsdb/ovsdb-dot.in b/ovsdb/ovsdb-dot.in
index 41b986c0ac..f1eefd49cb 100755
--- a/ovsdb/ovsdb-dot.in
+++ b/ovsdb/ovsdb-dot.in
@@ -1,15 +1,13 @@
 #! @PYTHON3@
 
-from datetime import date
 import ovs.db.error
 import ovs.db.schema
 import getopt
-import os
-import re
 import sys
 
 argv0 = sys.argv[0]
 
+
 def printEdge(tableName, type, baseType, label):
     if baseType.ref_table_name:
         if type.n_min == 0:
@@ -31,38 +29,42 @@ def printEdge(tableName, type, baseType, label):
         options['label'] = '"%s%s"' % (label, arity)
         if baseType.ref_type == 'weak':
             options['style'] = 'dotted'
-        print ("\t%s -> %s [%s];" % (
+        print("\t%s -> %s [%s];" % (
             tableName,
             baseType.ref_table_name,
-            ', '.join(['%s=%s' % (k,v) for k,v in options.items()])))
+            ', '.join(['%s=%s' % (k, v) for k, v in options.items()])))
+
 
 def schemaToDot(schemaFile, arrows):
     schema = ovs.db.schema.DbSchema.from_json(ovs.json.from_file(schemaFile))
 
-    print ("digraph %s {" % schema.name)
-    print ('\trankdir=LR;')
-    print ('\tsize="6.5,4";')
-    print ('\tmargin="0";')
-    print ("\tnode [shape=box];")
+    print("digraph %s {" % schema.name)
+    print('\trankdir=LR;')
+    print('\tsize="6.5,4";')
+    print('\tmargin="0";')
+    print("\tnode [shape=box];")
     if not arrows:
-        print ("\tedge [dir=none, arrowhead=none, arrowtail=none];")
+        print("\tedge [dir=none, arrowhead=none, arrowtail=none];")
     for tableName, table in schema.tables.items():
         options = {}
         if table.is_root:
             options['style'] = 'bold'
-        print ("\t%s [%s];" % (
+        print("\t%s [%s];" % (
             tableName,
-            ', '.join(['%s=%s' % (k,v) for k,v in options.items()])))
+            ', '.join(['%s=%s' % (k, v) for k, v in options.items()])))
         for columnName, column in table.columns.items():
             if column.type.value:
-                printEdge(tableName, column.type, column.type.key, "%s key" % columnName)
-                printEdge(tableName, column.type, column.type.value, "%s value" % columnName)
+                printEdge(tableName, column.type, column.type.key,
+                          "%s key" % columnName)
+                printEdge(tableName, column.type, column.type.value,
+                          "%s value" % columnName)
             else:
                 printEdge(tableName, column.type, column.type.key, columnName)
-    print ("}");
+    print("}")
+
 
 def usage():
-    print ("""\
+    print("""\
 %(argv0)s: compiles ovsdb schemas to graphviz format
 Prints a .dot file that "dot" can render to an entity-relationship diagram
 usage: %(argv0)s [OPTIONS] SCHEMA
@@ -75,12 +77,13 @@ The following options are also available:
 """ % {'argv0': argv0})
     sys.exit(0)
 
+
 if __name__ == "__main__":
     try:
         try:
             options, args = getopt.gnu_getopt(sys.argv[1:], 'hV',
                                               ['no-arrows',
-                                               'help', 'version',])
+                                               'help', 'version'])
         except getopt.GetoptError as geo:
             sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
             sys.exit(1)
@@ -92,7 +95,7 @@ if __name__ == "__main__":
             elif key in ['-h', '--help']:
                 usage()
             elif key in ['-V', '--version']:
-                print ("ovsdb-dot (Open vSwitch) @VERSION@")
+                print("ovsdb-dot (Open vSwitch) @VERSION@")
             else:
                 sys.exit(0)
 
diff --git a/ovsdb/raft.c b/ovsdb/raft.c
index f463afcb3d..ac3d37ac40 100644
--- a/ovsdb/raft.c
+++ b/ovsdb/raft.c
@@ -81,6 +81,7 @@ enum raft_failure_test {
     FT_STOP_RAFT_RPC,
     FT_TRANSFER_LEADERSHIP,
     FT_TRANSFER_LEADERSHIP_AFTER_SEND_APPEND_REQ,
+    FT_TRANSFER_LEADERSHIP_AFTER_STARTING_TO_ADD,
 };
 static enum raft_failure_test failure_test;
 
@@ -280,6 +281,7 @@ struct raft {
     /* Used for joining a cluster. */
     bool joining;                 /* Attempting to join the cluster? */
     struct sset remote_addresses; /* Addresses to try to find other servers. */
+#define RAFT_JOIN_TIMEOUT_MS 1000
     long long int join_timeout;   /* Time to re-send add server request. */
 
     /* Used for leaving a cluster. */
@@ -385,6 +387,7 @@ static void raft_get_servers_from_log(struct raft *, enum vlog_level);
 static void raft_get_election_timer_from_log(struct raft *);
 
 static bool raft_handle_write_error(struct raft *, struct ovsdb_error *);
+static bool raft_has_uncommitted_configuration(const struct raft *);
 
 static void raft_run_reconfigure(struct raft *);
 
@@ -1015,8 +1018,13 @@ raft_conn_update_probe_interval(struct raft *raft, struct raft_conn *r_conn)
      * inactivity probe follower will just try to initiate election
      * indefinitely staying in 'candidate' role.  And the leader will continue
      * to send heartbeats to the dead connection thinking that remote server
-     * is still part of the cluster. */
-    int probe_interval = raft->election_timer + ELECTION_RANGE_MSEC;
+     * is still part of the cluster.
+     *
+     * While joining, the real value of the election timeout is not known to
+     * this server, so using the maximum. */
+    int probe_interval = (raft->joining ? ELECTION_MAX_MSEC
+                                        : raft->election_timer)
+                         + ELECTION_RANGE_MSEC;
 
     jsonrpc_session_set_probe_interval(r_conn->js, probe_interval);
 }
@@ -1083,7 +1091,7 @@ raft_open(struct ovsdb_log *log, struct raft **raftp)
             raft_start_election(raft, false, false);
         }
     } else {
-        raft->join_timeout = time_msec() + 1000;
+        raft->join_timeout = time_msec() + RAFT_JOIN_TIMEOUT_MS;
     }
 
     raft_reset_ping_timer(raft);
@@ -1261,10 +1269,30 @@ raft_transfer_leadership(struct raft *raft, const char *reason)
         return;
     }
 
-    struct raft_server *s;
+    struct raft_server **servers, *s;
+    uint64_t threshold = 0;
+    size_t n = 0, start, i;
+
+    servers = xmalloc(hmap_count(&raft->servers) * sizeof *servers);
+
     HMAP_FOR_EACH (s, hmap_node, &raft->servers) {
-        if (!uuid_equals(&raft->sid, &s->sid)
-            && s->phase == RAFT_PHASE_STABLE) {
+        if (uuid_equals(&raft->sid, &s->sid)
+            || s->phase != RAFT_PHASE_STABLE) {
+            continue;
+        }
+        if (s->match_index > threshold) {
+            threshold = s->match_index;
+        }
+        servers[n++] = s;
+    }
+
+    start = n ? random_range(n) : 0;
+
+retry:
+    for (i = 0; i < n; i++) {
+        s = servers[(start + i) % n];
+
+        if (s->match_index >= threshold) {
             struct raft_conn *conn = raft_find_conn_by_sid(raft, &s->sid);
             if (!conn) {
                 continue;
@@ -1280,7 +1308,10 @@ raft_transfer_leadership(struct raft *raft, const char *reason)
                     .term = raft->term,
                 }
             };
-            raft_send_to_conn(raft, &rpc, conn);
+
+            if (!raft_send_to_conn(raft, &rpc, conn)) {
+                continue;
+            }
 
             raft_record_note(raft, "transfer leadership",
                              "transferring leadership to %s because %s",
@@ -1288,6 +1319,23 @@ raft_transfer_leadership(struct raft *raft, const char *reason)
             break;
         }
     }
+
+    if (n && i == n && threshold) {
+        if (threshold > raft->commit_index) {
+            /* Failed to transfer to servers with the highest 'match_index'.
+             * Try other servers that are not behind the majority. */
+            threshold = raft->commit_index;
+        } else {
+            /* Try any other server.  It is safe, because they either have all
+             * the append requests queued up for them before the leadership
+             * transfer message or their connection is broken and we will not
+             * transfer anyway. */
+            threshold = 0;
+        }
+        goto retry;
+    }
+
+    free(servers);
 }
 
 /* Send a RemoveServerRequest to the rest of the servers in the cluster.
@@ -2078,7 +2126,7 @@ raft_run(struct raft *raft)
                 raft_start_election(raft, true, false);
             }
         } else {
-            raft_start_election(raft, true, false);
+            raft_start_election(raft, hmap_count(&raft->servers) > 1, false);
         }
 
     }
@@ -2088,7 +2136,7 @@ raft_run(struct raft *raft)
     }
 
     if (raft->joining && time_msec() >= raft->join_timeout) {
-        raft->join_timeout = time_msec() + 1000;
+        raft->join_timeout = time_msec() + RAFT_JOIN_TIMEOUT_MS;
         LIST_FOR_EACH (conn, list_node, &raft->conns) {
             raft_send_add_server_request(raft, conn);
         }
@@ -2122,10 +2170,12 @@ raft_run(struct raft *raft)
         raft_reset_ping_timer(raft);
     }
 
+    uint64_t interval = raft->joining
+                        ? RAFT_JOIN_TIMEOUT_MS
+                        : RAFT_TIMER_THRESHOLD(raft->election_timer);
     cooperative_multitasking_set(
         &raft_run_cb, (void *) raft, time_msec(),
-        RAFT_TIMER_THRESHOLD(raft->election_timer)
-        + RAFT_TIMER_THRESHOLD(raft->election_timer) / 10, "raft_run");
+        interval + interval / 10, "raft_run");
 
     /* Do this only at the end; if we did it as soon as we set raft->left or
      * raft->failed in handling the RemoveServerReply, then it could easily
@@ -2696,15 +2746,22 @@ raft_become_follower(struct raft *raft)
      * new configuration.  Our AppendEntries processing will properly update
      * the server configuration later, if necessary.
      *
+     * However, since we're sending replies about a failure to add, those new
+     * servers has to be cleaned up.  Otherwise, they will stuck in a 'CATCHUP'
+     * phase in case this server regains leadership before they join through
+     * the current new leader.  They are not yet in 'raft->servers', so not
+     * part of the shared configuration.
+     *
      * 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) {
+    HMAP_FOR_EACH_POP (s, hmap_node, &raft->add_servers) {
         raft_send_add_server_reply__(raft, &s->sid, s->address, false,
                                      RAFT_SERVER_LOST_LEADERSHIP);
+        raft_server_destroy(s);
     }
     if (raft->remove_server) {
         raft_send_remove_server_reply__(raft, &raft->remove_server->sid,
@@ -2768,6 +2825,13 @@ raft_send_heartbeats(struct raft *raft)
     raft_reset_ping_timer(raft);
 }
 
+static void
+raft_join_complete(struct raft *raft)
+{
+    raft->joining = false;
+    raft_update_probe_intervals(raft);
+}
+
 /* Initializes the fields in 's' that represent the leader's view of the
  * server. */
 static void
@@ -2805,6 +2869,18 @@ raft_become_leader(struct raft *raft)
     raft_reset_election_timer(raft);
     raft_reset_ping_timer(raft);
 
+    if (raft->joining) {
+        /* It is possible that the server committing this one to the list of
+         * servers lost leadership before the entry is committed but after
+         * it was already replicated to majority of servers.  In this case
+         * other servers will recognize this one as a valid cluster member
+         * and may transfer leadership to it and vote for it.  This way
+         * we're becoming a cluster leader without receiving reply for a
+         * join request and will commit addition of this server ourselves. */
+        VLOG_INFO_RL(&rl, "elected as leader while joining");
+        raft_join_complete(raft);
+    }
+
     struct raft_server *s;
     HMAP_FOR_EACH (s, hmap_node, &raft->servers) {
         raft_server_init_leader(raft, s);
@@ -2963,12 +3039,12 @@ raft_update_commit_index(struct raft *raft, uint64_t new_commit_index)
     }
 
     while (raft->commit_index < new_commit_index) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5);
         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) {
@@ -3012,6 +3088,35 @@ raft_update_commit_index(struct raft *raft, uint64_t new_commit_index)
              * reallocate raft->entries, which would invalidate 'e', so
              * this case must be last, after the one for 'e->data'. */
             raft_run_reconfigure(raft);
+        } else if (e->servers && !raft_has_uncommitted_configuration(raft)) {
+            struct ovsdb_error *error;
+            struct raft_server *s;
+            struct hmap servers;
+
+            error = raft_servers_from_json(e->servers, &servers);
+            ovs_assert(!error);
+            HMAP_FOR_EACH (s, hmap_node, &servers) {
+                struct raft_server *server = raft_find_server(raft, &s->sid);
+
+                if (server && server->phase == RAFT_PHASE_COMMITTING) {
+                    /* This server lost leadership while committing
+                     * server 's', but it was committed later by a
+                     * new leader. */
+                    server->phase = RAFT_PHASE_STABLE;
+                }
+
+                if (raft->joining && uuid_equals(&s->sid, &raft->sid)) {
+                    /* Leadership change happened before previous leader
+                     * could commit the change of a servers list, but it
+                     * was replicated and a new leader committed it. */
+                    VLOG_INFO_RL(&rl,
+                        "added to configuration without reply "
+                        "(eid: "UUID_FMT", commit index: %"PRIu64")",
+                        UUID_ARGS(&e->eid), index);
+                    raft_join_complete(raft);
+                }
+            }
+            raft_servers_destroy(&servers);
         }
     }
 
@@ -3938,6 +4043,10 @@ raft_handle_add_server_request(struct raft *raft,
                  "to cluster "CID_FMT, s->nickname, SID_ARGS(&s->sid),
                  rq->address, CID_ARGS(&raft->cid));
     raft_send_append_request(raft, s, 0, "initialize new server");
+
+    if (failure_test == FT_TRANSFER_LEADERSHIP_AFTER_STARTING_TO_ADD) {
+        failure_test = FT_TRANSFER_LEADERSHIP;
+    }
 }
 
 static void
@@ -3952,7 +4061,7 @@ raft_handle_add_server_reply(struct raft *raft,
     }
 
     if (rpy->success) {
-        raft->joining = false;
+        raft_join_complete(raft);
 
         /* It is tempting, at this point, to check that this server is part of
          * the current configuration.  However, this is not necessarily the
@@ -4926,6 +5035,7 @@ raft_get_election_timer_from_log(struct raft *raft)
             break;
         }
     }
+    raft_update_probe_intervals(raft);
 }
 
 static void
@@ -5063,6 +5173,8 @@ raft_unixctl_failure_test(struct unixctl_conn *conn OVS_UNUSED,
     } 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-after-starting-to-add")) {
+        failure_test = FT_TRANSFER_LEADERSHIP_AFTER_STARTING_TO_ADD;
     } else if (!strcmp(test, "transfer-leadership")) {
         failure_test = FT_TRANSFER_LEADERSHIP;
     } else if (!strcmp(test, "clear")) {
diff --git a/python/ovs/db/custom_index.py b/python/ovs/db/custom_index.py
index 587caf5e3e..3fa03d3c95 100644
--- a/python/ovs/db/custom_index.py
+++ b/python/ovs/db/custom_index.py
@@ -90,14 +90,21 @@ class IndexedRows(DictBase, object):
         index = self.indexes[name] = MultiColumnIndex(name)
         return index
 
+    def __getitem__(self, key):
+        return self.data[key][-1]
+
     def __setitem__(self, key, item):
-        self.data[key] = item
+        try:
+            self.data[key].append(item)
+        except KeyError:
+            self.data[key] = [item]
         for index in self.indexes.values():
             index.add(item)
 
     def __delitem__(self, key):
-        val = self.data[key]
-        del self.data[key]
+        val = self.data[key].pop()
+        if len(self.data[key]) == 0:
+            del self.data[key]
         for index in self.indexes.values():
             index.remove(val)
 
diff --git a/python/ovs/db/idl.py b/python/ovs/db/idl.py
index a80da84e7a..b6d5ed6972 100644
--- a/python/ovs/db/idl.py
+++ b/python/ovs/db/idl.py
@@ -35,9 +35,9 @@ ROW_CREATE = "create"
 ROW_UPDATE = "update"
 ROW_DELETE = "delete"
 
-OVSDB_UPDATE = 0
-OVSDB_UPDATE2 = 1
-OVSDB_UPDATE3 = 2
+OVSDB_UPDATE = "update"
+OVSDB_UPDATE2 = "update2"
+OVSDB_UPDATE3 = "update3"
 
 CLUSTERED = "clustered"
 RELAY = "relay"
@@ -77,7 +77,7 @@ class ColumnDefaultDict(dict):
         return item in self.keys()
 
 
-class Monitor(enum.IntEnum):
+class Monitor(enum.Enum):
     monitor = OVSDB_UPDATE
     monitor_cond = OVSDB_UPDATE2
     monitor_cond_since = OVSDB_UPDATE3
@@ -465,23 +465,18 @@ class Idl(object):
                 self.__parse_update(msg.params[2], OVSDB_UPDATE3)
                 self.last_id = msg.params[1]
             elif (msg.type == ovs.jsonrpc.Message.T_NOTIFY
-                    and msg.method == "update2"
-                    and len(msg.params) == 2):
-                # Database contents changed.
-                self.__parse_update(msg.params[1], OVSDB_UPDATE2)
-            elif (msg.type == ovs.jsonrpc.Message.T_NOTIFY
-                    and msg.method == "update"
+                    and msg.method in (OVSDB_UPDATE, OVSDB_UPDATE2)
                     and len(msg.params) == 2):
                 # Database contents changed.
                 if msg.params[0] == str(self.server_monitor_uuid):
-                    self.__parse_update(msg.params[1], OVSDB_UPDATE,
+                    self.__parse_update(msg.params[1], msg.method,
                                         tables=self.server_tables)
                     self.change_seqno = previous_change_seqno
                     if not self.__check_server_db():
                         self.force_reconnect()
                         break
                 else:
-                    self.__parse_update(msg.params[1], OVSDB_UPDATE)
+                    self.__parse_update(msg.params[1], msg.method)
             elif self.handle_monitor_canceled(msg):
                 break
             elif self.handle_monitor_cancel_reply(msg):
@@ -540,7 +535,7 @@ class Idl(object):
                 # Reply to our "monitor" of _Server request.
                 try:
                     self._server_monitor_request_id = None
-                    self.__parse_update(msg.result, OVSDB_UPDATE,
+                    self.__parse_update(msg.result, OVSDB_UPDATE2,
                                         tables=self.server_tables)
                     self.change_seqno = previous_change_seqno
                     if self.__check_server_db():
@@ -579,6 +574,11 @@ class Idl(object):
             elif msg.type == ovs.jsonrpc.Message.T_NOTIFY and msg.id == "echo":
                 # Reply to our echo request.  Ignore it.
                 pass
+            elif (msg.type == ovs.jsonrpc.Message.T_ERROR and
+                  self.state == self.IDL_S_SERVER_MONITOR_REQUESTED and
+                  msg.id == self._server_monitor_request_id):
+                self._server_monitor_request_id = None
+                self.__send_monitor_request()
             elif (msg.type == ovs.jsonrpc.Message.T_ERROR and
                   self.state == (
                       self.IDL_S_DATA_MONITOR_COND_SINCE_REQUESTED) and
@@ -912,7 +912,7 @@ class Idl(object):
         monitor_request = {"columns": columns}
         monitor_requests[table.name] = [monitor_request]
         msg = ovs.jsonrpc.Message.create_request(
-            'monitor', [self._server_db.name,
+            'monitor_cond', [self._server_db.name,
                              str(self.server_monitor_uuid),
                              monitor_requests])
         self._server_monitor_request_id = msg.id
@@ -1013,7 +1013,9 @@ class Idl(object):
             if not row:
                 raise error.Error('Modify non-existing row')
 
+            del table.rows[uuid]
             old_row = self.__apply_diff(table, row, row_update['modify'])
+            table.rows[uuid] = row
             return Notice(ROW_UPDATE, row, Row(self, table, uuid, old_row))
         else:
             raise error.Error('<row-update> unknown operation',
@@ -1044,9 +1046,10 @@ class Idl(object):
                 op = ROW_UPDATE
                 vlog.warn("cannot add existing row %s to table %s"
                           % (uuid, table.name))
+                del table.rows[uuid]
+
             changed |= self.__row_update(table, row, new)
-            if op == ROW_CREATE:
-                table.rows[uuid] = row
+            table.rows[uuid] = row
             if changed:
                 return Notice(ROW_CREATE, row)
         else:
@@ -1058,9 +1061,11 @@ class Idl(object):
                 # XXX rate-limit
                 vlog.warn("cannot modify missing row %s in table %s"
                           % (uuid, table.name))
+            else:
+                del table.rows[uuid]
+
             changed |= self.__row_update(table, row, new)
-            if op == ROW_CREATE:
-                table.rows[uuid] = row
+            table.rows[uuid] = row
             if changed:
                 return Notice(op, row, Row.from_json(self, table, uuid, old))
         return False
@@ -1854,7 +1859,7 @@ class Transaction(object):
                 if row._data is None:
                     op["op"] = "insert"
                     if row._persist_uuid:
-                        op["uuid"] = row.uuid
+                        op["uuid"] = str(row.uuid)
                     else:
                         op["uuid-name"] = _uuid_name_from_uuid(row.uuid)
 
diff --git a/python/ovs/fatal_signal.py b/python/ovs/fatal_signal.py
index cb2e99e87d..16a7e78a03 100644
--- a/python/ovs/fatal_signal.py
+++ b/python/ovs/fatal_signal.py
@@ -16,6 +16,7 @@ import atexit
 import os
 import signal
 import sys
+import threading
 
 import ovs.vlog
 
@@ -112,29 +113,29 @@ def _unlink(file_):
 def _signal_handler(signr, _):
     _call_hooks(signr)
 
-    # Re-raise the signal with the default handling so that the program
-    # termination status reflects that we were killed by this signal.
-    signal.signal(signr, signal.SIG_DFL)
-    os.kill(os.getpid(), signr)
-
 
 def _atexit_handler():
     _call_hooks(0)
 
 
-recurse = False
+mutex = threading.Lock()
 
 
 def _call_hooks(signr):
-    global recurse
-    if recurse:
+    global mutex
+    if not mutex.acquire(blocking=False):
         return
-    recurse = True
 
     for hook, cancel, run_at_exit in _hooks:
         if signr != 0 or run_at_exit:
             hook()
 
+    if signr != 0:
+        # Re-raise the signal with the default handling so that the program
+        # termination status reflects that we were killed by this signal.
+        signal.signal(signr, signal.SIG_DFL)
+        os.kill(os.getpid(), signr)
+
 
 _inited = False
 
@@ -150,7 +151,9 @@ def _init():
                        signal.SIGALRM]
 
         for signr in signals:
-            if signal.getsignal(signr) == signal.SIG_DFL:
+            handler = signal.getsignal(signr)
+            if (handler == signal.SIG_DFL or
+                handler == signal.default_int_handler):
                 signal.signal(signr, _signal_handler)
         atexit.register(_atexit_handler)
 
@@ -165,7 +168,6 @@ def signal_alarm(timeout):
 
     if sys.platform == "win32":
         import time
-        import threading
 
         class Alarm (threading.Thread):
             def __init__(self, timeout):
diff --git a/python/ovs/flow/odp.py b/python/ovs/flow/odp.py
index 7d9b165d46..a8f8c067a9 100644
--- a/python/ovs/flow/odp.py
+++ b/python/ovs/flow/odp.py
@@ -365,29 +365,30 @@ class ODPFlow(Flow):
             is_list=True,
         )
 
-        return {
-            **_decoders,
-            "check_pkt_len": nested_kv_decoder(
-                KVDecoders(
-                    {
-                        "size": decode_int,
-                        "gt": nested_kv_decoder(
-                            KVDecoders(
-                                decoders=_decoders,
-                                default_free=decode_free_output,
-                            ),
-                            is_list=True,
+        _decoders["check_pkt_len"] = nested_kv_decoder(
+            KVDecoders(
+                {
+                    "size": decode_int,
+                    "gt": nested_kv_decoder(
+                        KVDecoders(
+                            decoders=_decoders,
+                            default_free=decode_free_output,
                         ),
-                        "le": nested_kv_decoder(
-                            KVDecoders(
-                                decoders=_decoders,
-                                default_free=decode_free_output,
-                            ),
-                            is_list=True,
+                        is_list=True,
+                    ),
+                    "le": nested_kv_decoder(
+                        KVDecoders(
+                            decoders=_decoders,
+                            default_free=decode_free_output,
                         ),
-                    }
-                )
-            ),
+                        is_list=True,
+                    ),
+                }
+            )
+        )
+
+        return {
+            **_decoders,
         }
 
     @staticmethod
diff --git a/python/ovs/tests/test_odp.py b/python/ovs/tests/test_odp.py
index f19ec386e8..d514e9be32 100644
--- a/python/ovs/tests/test_odp.py
+++ b/python/ovs/tests/test_odp.py
@@ -541,6 +541,35 @@ def test_odp_fields(input_string, expected):
                 ),
             ],
         ),
+        (
+            "actions:check_pkt_len(size=200,gt(check_pkt_len(size=400,gt(4),le(2))),le(check_pkt_len(size=100,gt(1),le(drop))))",  # noqa: E501
+            [
+                KeyValue(
+                    "check_pkt_len",
+                    {
+                        "size": 200,
+                        "gt": [
+                            {
+                                "check_pkt_len": {
+                                    "size": 400,
+                                    "gt": [{"output": {"port": 4}}],
+                                    "le": [{"output": {"port": 2}}],
+                                }
+                            }
+                        ],
+                        "le": [
+                            {
+                                "check_pkt_len": {
+                                    "size": 100,
+                                    "gt": [{"output": {"port": 1}}],
+                                    "le": [{"drop": True}],
+                                }
+                            }
+                        ],
+                    },
+                )
+            ],
+        ),
         (
             "actions:meter(1),hash(l4(0))",
             [
diff --git a/python/test_requirements.txt b/python/test_requirements.txt
index 5043c71e22..a1424506b6 100644
--- a/python/test_requirements.txt
+++ b/python/test_requirements.txt
@@ -1,4 +1,5 @@
 netaddr
+packaging
 pyftpdlib
 pyparsing
 pytest
diff --git a/rhel/usr_lib_systemd_system_ovsdb-server.service b/rhel/usr_lib_systemd_system_ovsdb-server.service
index 49dc06e38c..558632320c 100644
--- a/rhel/usr_lib_systemd_system_ovsdb-server.service
+++ b/rhel/usr_lib_systemd_system_ovsdb-server.service
@@ -29,3 +29,4 @@ ExecStop=/usr/share/openvswitch/scripts/ovs-ctl --no-ovs-vswitchd stop
 ExecReload=/usr/share/openvswitch/scripts/ovs-ctl --no-ovs-vswitchd \
            ${OVS_USER_OPT} \
            --no-monitor restart $OPTIONS
+TimeoutSec=300
diff --git a/tests/atlocal.in b/tests/atlocal.in
index f321bae55f..8565a0bae9 100644
--- a/tests/atlocal.in
+++ b/tests/atlocal.in
@@ -229,18 +229,35 @@ export UBSAN_OPTIONS
 REQUIREMENT_PATH=$abs_top_srcdir/python/test_requirements.txt $PYTHON3 -c '
 import os
 import pathlib
-import pkg_resources
 import sys
 
+PACKAGING = True
+try:
+    from packaging import requirements
+    from importlib import metadata
+except ModuleNotFoundError:
+    PACKAGING = False
+    import pkg_resources
+
 with pathlib.Path(os.path.join(os.getenv("REQUIREMENT_PATH"))).open() as reqs:
-    for req in pkg_resources.parse_requirements(reqs):
-        try:
-            pkg_resources.require(str(req))
-        except pkg_resources.DistributionNotFound:
-            sys.exit(2)
+    if PACKAGING:
+        for req in reqs.readlines():
+            try:
+                r = requirements.Requirement(req.strip())
+                if metadata.version(r.name) not in r.specifier:
+                    raise metadata.PackageNotFoundError
+            except metadata.PackageNotFoundError:
+                sys.exit(2)
+    else:
+        for req in pkg_resources.parse_requirements(reqs):
+            try:
+                pkg_resources.require(str(req))
+            except pkg_resources.DistributionNotFound:
+                sys.exit(2)
 '
 case $? in
     0) HAVE_PYTEST=yes ;;
     2) HAVE_PYTEST=no ;;
-    *) echo "$0: unexpected error probing Python unit test requirements" >&2 ;;
+    *) HAVE_PYTEST=no
+       echo "$0: unexpected error probing Python unit test requirements" >&2 ;;
 esac
diff --git a/tests/dpif-netdev.at b/tests/dpif-netdev.at
index 790b5a43af..bdc24cc307 100644
--- a/tests/dpif-netdev.at
+++ b/tests/dpif-netdev.at
@@ -1091,3 +1091,66 @@ OVS_VSWITCHD_STOP(["dnl
 /Error: unknown miniflow extract implementation superstudy./d
 /Error: invalid study_pkt_cnt value: -pmd./d"])
 AT_CLEANUP
+
+AT_SETUP([datapath - Actions Autovalidator Checksum])
+
+OVS_VSWITCHD_START(add-port br0 p0 -- set Interface p0 type=dummy \
+                   -- add-port br0 p1 -- set Interface p1 type=dummy)
+
+AT_CHECK([ovs-appctl odp-execute/action-impl-set autovalidator], [0], [dnl
+Action implementation set to autovalidator.
+])
+
+dnl Add flows to trigger checksum calculation.
+AT_DATA([flows.txt], [dnl
+  in_port=p0,ip,actions=mod_nw_src=10.1.1.1,p1
+  in_port=p0,ipv6,actions=set_field:fc00::100->ipv6_src,p1
+])
+AT_CHECK([ovs-ofctl del-flows br0])
+AT_CHECK([ovs-ofctl -Oopenflow13 add-flows br0 flows.txt])
+
+dnl Make sure checksum won't be offloaded.
+AT_CHECK([ovs-vsctl set Interface p0 options:ol_ip_csum=false])
+AT_CHECK([ovs-vsctl set Interface p0 options:ol_ip_csum_set_good=false])
+
+AT_CHECK([ovs-vsctl set Interface p1 options:pcap=p1.pcap])
+
+dnl IPv4 packet with values that will trigger carry-over addition for checksum.
+flow_s_v4="
+  eth_src=47:42:86:08:17:50,eth_dst=3e:55:b5:9e:3a:fb,dl_type=0x0800,
+  nw_src=229.167.36.90,nw_dst=130.161.64.186,nw_proto=6,nw_ttl=64,nw_frag=no,
+  tp_src=54392,tp_dst=5201,tcp_flags=ack"
+
+good_frame=$(ovs-ofctl compose-packet --bare "${flow_s_v4}")
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 ${good_frame}])
+
+dnl Checksum should change to 0xAC33 with ip_src changed to 10.1.1.1
+dnl by the datapath while processing the packet.
+flow_expected=$(echo "${flow_s_v4}" | sed 's/229.167.36.90/10.1.1.1/g')
+good_expected=$(ovs-ofctl compose-packet --bare "${flow_expected}")
+AT_CHECK([ovs-pcap p1.pcap > p1.pcap.txt 2>&1])
+AT_CHECK_UNQUOTED([tail -n 1 p1.pcap.txt], [0], [${good_expected}
+])
+
+dnl Repeat similar test for IPv6.
+flow_s_v6="
+  eth_src=8a:bf:7e:2f:05:84,eth_dst=0a:8f:39:4f:e0:73,dl_type=0x86dd,
+  ipv6_src=2f8a:2076:3926:9e7:2d47:4bc9:9c7:17f3,
+  ipv6_dst=7287:10dd:2fb9:41d5:3eb2:2c7a:11b0:6258,
+  ipv6_label=0x51ac,nw_proto=6,nw_ttl=142,nw_frag=no,
+  tp_src=20405,tp_dst=20662,tcp_flags=ack"
+
+good_frame_v6=$(ovs-ofctl compose-packet --bare "${flow_s_v6}")
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 ${good_frame_v6}])
+
+dnl Checksum should change to 0x59FD with ipv6_src changed to fc00::100
+dnl by the datapath while processing the packet.
+flow_expected_v6=$(echo "${flow_s_v6}" | \
+  sed 's/2f8a:2076:3926:9e7:2d47:4bc9:9c7:17f3/fc00::100/g')
+good_expected_v6=$(ovs-ofctl compose-packet --bare "${flow_expected_v6}")
+AT_CHECK([ovs-pcap p1.pcap > p1.pcap.txt 2>&1])
+AT_CHECK_UNQUOTED([tail -n 1 p1.pcap.txt], [0], [${good_expected_v6}
+])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
diff --git a/tests/library.at b/tests/library.at
index 7b4acebb8a..d962e1b3fd 100644
--- a/tests/library.at
+++ b/tests/library.at
@@ -230,7 +230,9 @@ AT_CHECK([ovstest test-util -voff -vfile:info '-vPATTERN:file:%c|%p|%m' --log-fi
   [$exit_status], [], [stderr])
 
 AT_CHECK([sed 's/\(opened log file\) .*/\1/
-s/|[[^|]]*: /|/' test-util.log], [0], [dnl
+s/|[[^|]]*: /|/
+/backtrace/d
+/|.*|/!d' test-util.log], [0], [dnl
 vlog|INFO|opened log file
 util|EMER|assertion false failed in test_assert()
 ])
diff --git a/tests/nsh.at b/tests/nsh.at
index 55296e5593..0040a50b36 100644
--- a/tests/nsh.at
+++ b/tests/nsh.at
@@ -521,51 +521,45 @@ AT_CHECK([
         set interface vxlangpe32 type=vxlan options:exts=gpe options:remote_ip=30.0.0.2 options:packet_type=ptap ofport_request=3020
 
     ovs-appctl netdev-dummy/ip4addr br-p1 10.0.0.1/24
-    ovs-appctl ovs/route/add 10.0.0.0/24 br-p1
     ovs-appctl tnl/arp/set br-p1 10.0.0.1 $HWADDR_BRP1
     ovs-appctl tnl/arp/set br-p1 10.0.0.2 $HWADDR_BRP2
     ovs-appctl tnl/arp/set br-p1 10.0.0.3 $HWADDR_BRP3
 
     ovs-appctl netdev-dummy/ip4addr br-p2 20.0.0.2/24
-    ovs-appctl ovs/route/add 20.0.0.0/24 br-p2
     ovs-appctl tnl/arp/set br-p2 20.0.0.1 $HWADDR_BRP1
     ovs-appctl tnl/arp/set br-p2 20.0.0.2 $HWADDR_BRP2
     ovs-appctl tnl/arp/set br-p2 20.0.0.3 $HWADDR_BRP3
 
     ovs-appctl netdev-dummy/ip4addr br-p3 30.0.0.3/24
-    ovs-appctl ovs/route/add 30.0.0.0/24 br-p3
     ovs-appctl tnl/arp/set br-p3 30.0.0.1 $HWADDR_BRP1
     ovs-appctl tnl/arp/set br-p3 30.0.0.2 $HWADDR_BRP2
     ovs-appctl tnl/arp/set br-p3 30.0.0.3 $HWADDR_BRP3
 ], [0], [stdout])
 
 AT_CHECK([
-    ovs-appctl ovs/route/add 10.0.0.0/24 br-p1
     ovs-appctl tnl/arp/set br-p1 10.0.0.1 $HWADDR_BRP1
     ovs-appctl tnl/arp/set br-p1 10.0.0.2 $HWADDR_BRP2
     ovs-appctl tnl/arp/set br-p1 10.0.0.3 $HWADDR_BRP3
 ], [0], [stdout])
 
 AT_CHECK([
-    ovs-appctl ovs/route/add 20.0.0.0/24 br-p2
     ovs-appctl tnl/arp/set br-p2 20.0.0.1 $HWADDR_BRP1
     ovs-appctl tnl/arp/set br-p2 20.0.0.2 $HWADDR_BRP2
     ovs-appctl tnl/arp/set br-p2 20.0.0.3 $HWADDR_BRP3
 ], [0], [stdout])
 
 AT_CHECK([
-    ovs-appctl ovs/route/add 30.0.0.0/24 br-p3
     ovs-appctl tnl/arp/set br-p3 30.0.0.1 $HWADDR_BRP1
     ovs-appctl tnl/arp/set br-p3 30.0.0.2 $HWADDR_BRP2
     ovs-appctl tnl/arp/set br-p3 30.0.0.3 $HWADDR_BRP3
 ], [0], [stdout])
 
 AT_CHECK([
-    ovs-appctl ovs/route/show | grep User:
+    ovs-appctl ovs/route/show | grep Cached: | sort
 ], [0], [dnl
-User: 10.0.0.0/24 dev br-p1 SRC 10.0.0.1
-User: 20.0.0.0/24 dev br-p2 SRC 20.0.0.2
-User: 30.0.0.0/24 dev br-p3 SRC 30.0.0.3
+Cached: 10.0.0.0/24 dev br-p1 SRC 10.0.0.1 local
+Cached: 20.0.0.0/24 dev br-p2 SRC 20.0.0.2 local
+Cached: 30.0.0.0/24 dev br-p3 SRC 30.0.0.3 local
 ])
 
 AT_CHECK([
diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
index e305e7b9cd..0b23fd6c5e 100644
--- a/tests/ofproto-dpif.at
+++ b/tests/ofproto-dpif.at
@@ -547,6 +547,23 @@ ovs-appctl time/warp 1000 100
 ovs-appctl bond/show > bond3.txt
 AT_CHECK([sed -n '/member p2/,/^$/p' bond3.txt | grep 'hash'], [0], [ignore])
 
+# Check that both ports doing down and back up doesn't break statistics.
+AT_CHECK([ovs-appctl netdev-dummy/set-admin-state p1 down], 0, [OK
+])
+AT_CHECK([ovs-appctl netdev-dummy/set-admin-state p2 down], 0, [OK
+])
+ovs-appctl time/warp 1000 100
+AT_CHECK([ovs-appctl netdev-dummy/set-admin-state p1 up], 0, [OK
+])
+AT_CHECK([ovs-appctl netdev-dummy/set-admin-state p2 up], 0, [OK
+])
+ovs-appctl time/warp 1000 100
+
+AT_CHECK([SEND_TCP_BOND_PKTS([p5], [5], [65500])])
+# We sent 49125 KB of data total in 3 batches.  No hash should have more
+# than that amount of load. Just checking that it is within 5 digits.
+AT_CHECK([ovs-appctl bond/show | grep -E '[[0-9]]{6}'], [1])
+
 OVS_VSWITCHD_STOP()
 AT_CLEANUP
 
@@ -930,6 +947,28 @@ AT_CHECK([tail -1 stdout], [0],
 OVS_VSWITCHD_STOP
 AT_CLEANUP
 
+AT_SETUP([ofproto-dpif - group with ct and dnat recirculation in action list])
+OVS_VSWITCHD_START
+add_of_ports br0 1 10
+AT_CHECK([ovs-ofctl -O OpenFlow12 add-group br0 \
+    'group_id=1234,type=all,bucket=ct(nat(dst=10.10.10.7:80),commit,table=2)'])
+AT_DATA([flows.txt], [dnl
+table=0 ip,ct_state=-trk actions=group:1234
+table=2 ip,ct_state=+trk actions=output:10
+])
+AT_CHECK([ovs-ofctl -O OpenFlow12 add-flows br0 flows.txt])
+AT_CHECK([ovs-appctl ofproto/trace br0 '
+  in_port=1,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:07,dl_type=0x0800,
+  nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_proto=1,nw_tos=0,nw_ttl=128,nw_frag=no,
+  icmp_type=8,icmp_code=0
+'], [0], [stdout])
+AT_CHECK([grep 'Datapath actions' stdout], [0], [dnl
+Datapath actions: ct(commit,nat(dst=10.10.10.7:80)),recirc(0x1)
+Datapath actions: 10
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
 AT_SETUP([ofproto-dpif - group actions have no effect afterwards])
 OVS_VSWITCHD_START
 add_of_ports br0 1 10
@@ -6178,6 +6217,57 @@ AT_CHECK([test 1 = `$PYTHON3 "$top_srcdir/utilities/ovs-pcap.in" p2-tx.pcap | wc
 OVS_VSWITCHD_STOP
 AT_CLEANUP
 
+AT_SETUP([ofproto-dpif - continuation with meters])
+AT_KEYWORDS([continuations pause meters])
+OVS_VSWITCHD_START
+add_of_ports br0 1 2
+
+dnl Add meter with id=1.
+AT_CHECK([ovs-ofctl -O OpenFlow13 add-meter br0 'meter=1 pktps bands=type=drop rate=1'])
+
+AT_DATA([flows.txt], [dnl
+table=0 dl_dst=50:54:00:00:00:0a actions=goto_table(1)
+table=1 dl_dst=50:54:00:00:00:0a actions=controller(pause,meter_id=1)
+])
+AT_CHECK([ovs-ofctl -O OpenFlow13 add-flows br0 flows.txt])
+
+on_exit 'kill $(cat ovs-ofctl.pid)'
+AT_CAPTURE_FILE([ofctl_monitor.log])
+AT_CHECK([ovs-ofctl monitor br0 65534 invalid_ttl -P nxt_packet_in \
+                    --detach --no-chdir --pidfile 2> ofctl_monitor.log])
+
+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(0x1234)'])
+
+OVS_WAIT_UNTIL([test $(wc -l < ofctl_monitor.log) -ge 2])
+OVS_APP_EXIT_AND_WAIT([ovs-ofctl])
+AT_CHECK([cat ofctl_monitor.log], [0], [dnl
+NXT_PACKET_IN (xid=0x0): cookie=0x0 total_len=14 in_port=1 (via action) data_len=14 (unbuffered)
+vlan_tci=0x0000,dl_src=50:54:00:00:00:09,dl_dst=50:54:00:00:00:0a,dl_type=0x1234
+])
+
+AT_CHECK([ovs-appctl revalidator/purge], [0])
+AT_CHECK([ovs-ofctl -O OpenFlow13 dump-flows br0 | ofctl_strip | sort], [0], [dnl
+ n_packets=1, n_bytes=14, dl_dst=50:54:00:00:00:0a actions=goto_table:1
+ table=1, n_packets=1, n_bytes=14, dl_dst=50:54:00:00:00:0a actions=controller(pause,meter_id=1)
+OFPST_FLOW reply (OF1.3):
+])
+
+AT_CHECK([ovs-ofctl -O OpenFlow13 dump-meters br0 | ofctl_strip | sort], [0], [dnl
+OFPST_METER_CONFIG reply (OF1.3):
+meter=1 pktps bands=
+type=drop rate=1
+])
+
+AT_CHECK([ovs-ofctl -O OpenFlow13 meter-stats br0 | strip_timers], [0], [dnl
+OFPST_METER reply (OF1.3) (xid=0x2):
+meter:1 flow_count:0 packet_in_count:1 byte_in_count:14 duration:0.0s bands:
+0: packet_count:0 byte_count:0
+])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
 AT_SETUP([ofproto-dpif - continuation with patch port])
 AT_KEYWORDS([continuations pause resume])
 OVS_VSWITCHD_START(
@@ -7653,12 +7743,14 @@ dummy@ovs-dummy: hit:0 missed:0
     vm1 5/3: (dummy: ifindex=2011)
 ])
 
-dnl set up route to 1.1.2.92 via br0 and action=normal
+dnl Add 1.1.2.92 to br0 and action=normal
 AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/24], [0], [OK
 ])
-AT_CHECK([ovs-appctl ovs/route/add 1.1.2.92/24 br0], [0], [OK
-])
 AT_CHECK([ovs-ofctl add-flow br0 action=normal])
+dnl Checking that a local route for added IP was successfully installed.
+AT_CHECK([ovs-appctl ovs/route/show | grep Cached], [0], [dnl
+Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
+])
 
 dnl Prime ARP Cache for 1.1.2.92
 AT_CHECK([ovs-appctl netdev-dummy/receive p0 'recirc_id(0),in_port(1),eth(src=f8:bc:12:44:34:b6,dst=ff:ff:ff:ff:ff:ff),eth_type(0x0806),arp(sip=1.1.2.92,tip=1.1.2.88,op=2,sha=f8:bc:12:44:34:b6,tha=00:00:00:00:00:00)'])
@@ -7669,10 +7761,13 @@ ovs-vsctl \
    --id=@sf create sflow targets=\"127.0.0.1:$SFLOW_PORT\" agent=127.0.0.1 \
      header=128 sampling=1 polling=0
 
-dnl set up route to 192.168.1.2 via br0
+dnl Add 192.168.1.2 to br0,
 AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 192.168.1.1/16], [0], [OK
 ])
-AT_CHECK([ovs-appctl ovs/route/add 192.168.0.0/16 br0], [0], [OK
+dnl Checking that a local route for added IP was successfully installed.
+AT_CHECK([ovs-appctl ovs/route/show | grep Cached | sort], [0], [dnl
+Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
+Cached: 192.168.0.0/16 dev br0 SRC 192.168.1.1 local
 ])
 
 dnl add rule for int-br to force packet onto tunnel. There is no ifindex
diff --git a/tests/ovsdb-cluster.at b/tests/ovsdb-cluster.at
index 481afc08b3..9d8b4d06a4 100644
--- a/tests/ovsdb-cluster.at
+++ b/tests/ovsdb-cluster.at
@@ -473,6 +473,112 @@ done
 
 AT_CLEANUP
 
+AT_SETUP([OVSDB cluster - leadership change after replication while joining])
+AT_KEYWORDS([ovsdb server negative unix cluster join])
+
+n=5
+AT_CHECK([ovsdb-tool '-vPATTERN:console:%c|%p|%m' create-cluster s1.db dnl
+              $abs_srcdir/idltest.ovsschema unix:s1.raft], [0], [], [stderr])
+cid=$(ovsdb-tool db-cid s1.db)
+schema_name=$(ovsdb-tool schema-name $abs_srcdir/idltest.ovsschema)
+for i in $(seq 2 $n); do
+    AT_CHECK([ovsdb-tool join-cluster s$i.db $schema_name unix:s$i.raft unix:s1.raft])
+done
+
+on_exit 'kill $(cat *.pid)'
+on_exit "
+  for i in \$(ls $(pwd)/s[[0-$n]]); do
+    ovs-appctl --timeout 1 -t \$i cluster/status $schema_name;
+  done
+"
+
+dnl Starting servers one by one asking all exisitng servers to transfer
+dnl leadership after append reply forcing the joining server to try another
+dnl one that will also transfer leadership.  Since transfer is happening
+dnl after the servers update is replicated to other servers, one of the
+dnl other servers will actually commit it.  It may be a new leader from
+dnl one of the old members or the new joining server itself.
+for i in $(seq $n); do
+    dnl Make sure that all already started servers joined the cluster.
+    for j in $(seq $((i - 1)) ); do
+        AT_CHECK([ovsdb_client_wait unix:s$j.ovsdb $schema_name connected])
+    done
+    for j in $(seq $((i - 1)) ); do
+        OVS_WAIT_UNTIL([ovs-appctl -t "$(pwd)"/s$j \
+                          cluster/failure-test \
+                            transfer-leadership-after-sending-append-request \
+                        | grep -q "engaged"])
+    done
+
+    AT_CHECK([ovsdb-server -v -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])
+done
+
+dnl Make sure that all servers joined the cluster.
+for i in $(seq $n); do
+    AT_CHECK([ovsdb_client_wait unix:s$i.ovsdb $schema_name connected])
+done
+
+for i in $(seq $n); do
+    OVS_APP_EXIT_AND_WAIT_BY_TARGET([$(pwd)/s$i], [s$i.pid])
+done
+
+AT_CLEANUP
+
+AT_SETUP([OVSDB cluster - leadership change before replication while joining])
+AT_KEYWORDS([ovsdb server negative unix cluster join])
+
+n=5
+AT_CHECK([ovsdb-tool '-vPATTERN:console:%c|%p|%m' create-cluster s1.db dnl
+              $abs_srcdir/idltest.ovsschema unix:s1.raft], [0], [], [stderr])
+cid=$(ovsdb-tool db-cid s1.db)
+schema_name=$(ovsdb-tool schema-name $abs_srcdir/idltest.ovsschema)
+for i in $(seq 2 $n); do
+    AT_CHECK([ovsdb-tool join-cluster s$i.db $schema_name unix:s$i.raft unix:s1.raft])
+done
+
+on_exit 'kill $(cat *.pid)'
+on_exit "
+  for i in \$(ls $(pwd)/s[[0-$n]]); do
+    ovs-appctl --timeout 1 -t \$i cluster/status $schema_name;
+  done
+"
+
+dnl Starting servers one by one asking all exisitng servers to transfer
+dnl leadership right after starting to add a server.  Joining server will
+dnl need to find a new leader that will also transfer leadership.
+dnl This will continue until the same server will not become a leader
+dnl for the second time and will be able to add a new server.
+for i in $(seq $n); do
+    dnl Make sure that all already started servers joined the cluster.
+    for j in $(seq $((i - 1)) ); do
+        AT_CHECK([ovsdb_client_wait unix:s$j.ovsdb $schema_name connected])
+    done
+    for j in $(seq $((i - 1)) ); do
+        OVS_WAIT_UNTIL([ovs-appctl -t "$(pwd)"/s$j \
+                          cluster/failure-test \
+                            transfer-leadership-after-starting-to-add \
+                        | grep -q "engaged"])
+    done
+
+    AT_CHECK([ovsdb-server -v -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])
+done
+
+dnl Make sure that all servers joined the cluster.
+for i in $(seq $n); do
+    AT_CHECK([ovsdb_client_wait unix:s$i.ovsdb $schema_name connected])
+done
+
+for i in $(seq $n); do
+    OVS_APP_EXIT_AND_WAIT_BY_TARGET([$(pwd)/s$i], [s$i.pid])
+done
+
+AT_CLEANUP
 
 
 OVS_START_SHELL_HELPERS
diff --git a/tests/ovsdb-idl.at b/tests/ovsdb-idl.at
index fb568dd823..c1c53cebe2 100644
--- a/tests/ovsdb-idl.at
+++ b/tests/ovsdb-idl.at
@@ -167,8 +167,17 @@ m4_define([OVSDB_CHECK_IDL_REGISTER_COLUMNS_PY],
    OVSDB_START_IDLTEST
    m4_if([$2], [], [],
      [AT_CHECK([ovsdb-client transact unix:socket $2], [0], [ignore], [ignore])])
-   AT_CHECK([$PYTHON3 $srcdir/test-ovsdb.py  -t10 idl $srcdir/idltest.ovsschema unix:socket ?simple:b,ba,i,ia,r,ra,s,sa,u,ua?simple3:name,uset,uref?simple4:name?simple6:name,weak_ref?link1:i,k,ka,l2?link2:i,l1?singleton:name $3],
-            [0], [stdout], [ignore])
+   m4_define([REGISTER], m4_joinall([?], [],
+     [simple:b,ba,i,ia,r,ra,s,sa,u,ua],
+     [simple3:name,uset,uref],
+     [simple4:name],
+     [simple6:name,weak_ref],
+     [link1:i,k,ka,l2],
+     [link2:i,l1],
+     [indexed:i],
+     [singleton:name]))
+   AT_CHECK([$PYTHON3 $srcdir/test-ovsdb.py -t10 idl $srcdir/idltest.ovsschema \
+                unix:socket REGISTER $3], [0], [stdout], [ignore])
    AT_CHECK([sort stdout | uuidfilt]m4_if([$6],,, [[| $6]]),
             [0], [$4])
    OVSDB_SERVER_SHUTDOWN
@@ -747,6 +756,31 @@ OVSDB_CHECK_IDL([simple idl, conditional, multiple tables],
 009: done
 ]])
 
+OVSDB_CHECK_IDL([indexed idl, modification and removal],
+  [],
+  [['["idltest",
+      {"op": "insert",
+       "table": "indexed",
+       "row": {"i": 123 }}]' \
+    '["idltest",
+      {"op": "update",
+       "table": "indexed",
+       "where": [["i", "==", 123]],
+       "row": {"i": 456}}]' \
+    '["idltest",
+      {"op": "delete",
+       "table": "indexed",
+       "where": [["i", "==", 456]]}]']],
+  [[000: empty
+001: {"error":null,"result":[{"uuid":["uuid","<0>"]}]}
+002: table indexed: i=123 uuid=<0>
+003: {"error":null,"result":[{"count":1}]}
+004: table indexed: i=456 uuid=<0>
+005: {"error":null,"result":[{"count":1}]}
+006: empty
+007: done
+]])
+
 OVSDB_CHECK_IDL([self-linking idl, consistent ops],
   [],
   [['["idltest",
@@ -1119,6 +1153,19 @@ OVSDB_CHECK_IDL_FETCH_COLUMNS([simple idl, initially populated],
 003: done
 ]])
 
+m4_define([OVSDB_CHECK_IDL_WO_MONITOR_COND_C],
+  [AT_SETUP([$1 - C])
+   AT_KEYWORDS([ovsdb server idl monitor $4])
+   OVSDB_START_IDLTEST
+   AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/disable-monitor-cond])
+
+   AT_CHECK([test-ovsdb '-vPATTERN:console:test-ovsdb|%c|%m' -vjsonrpc -t10 idl unix:socket $2],
+            [0], [stdout], [ignore])
+   AT_CHECK([sort stdout | uuidfilt]m4_if([$5],,, [[| $5]]),
+            [0], [$3])
+   OVSDB_SERVER_SHUTDOWN
+   AT_CLEANUP])
+
 m4_define([OVSDB_CHECK_IDL_WO_MONITOR_COND_PY],
   [AT_SETUP([$1 - Python3])
    AT_KEYWORDS([ovsdb server idl Python monitor $4])
@@ -1132,7 +1179,8 @@ m4_define([OVSDB_CHECK_IDL_WO_MONITOR_COND_PY],
    AT_CLEANUP])
 
 m4_define([OVSDB_CHECK_IDL_WO_MONITOR_COND],
-   [OVSDB_CHECK_IDL_WO_MONITOR_COND_PY($@)])
+   [OVSDB_CHECK_IDL_WO_MONITOR_COND_C($@)
+    OVSDB_CHECK_IDL_WO_MONITOR_COND_PY($@)])
 
 
 OVSDB_CHECK_IDL_WO_MONITOR_COND([simple idl disable monitor-cond],
@@ -1274,6 +1322,33 @@ OVSDB_CHECK_IDL_TRACK([track, simple idl, initially populated],
 003: done
 ]])
 
+OVSDB_CHECK_IDL_TRACK([track, indexed idl, modification and removal],
+  [],
+  [['["idltest",
+      {"op": "insert",
+       "table": "indexed",
+       "row": {"i": 123 }}]' \
+    '["idltest",
+      {"op": "update",
+       "table": "indexed",
+       "where": [["i", "==", 123]],
+       "row": {"i": 456}}]' \
+    '["idltest",
+      {"op": "delete",
+       "table": "indexed",
+       "where": [["i", "==", 456]]}]']],
+  [[000: empty
+001: {"error":null,"result":[{"uuid":["uuid","<0>"]}]}
+002: table indexed: inserted row: i=123 uuid=<0>
+002: table indexed: updated columns: i
+003: {"error":null,"result":[{"count":1}]}
+004: table indexed: i=456 uuid=<0>
+004: table indexed: updated columns: i
+005: {"error":null,"result":[{"count":1}]}
+006: empty
+007: done
+]])
+
 dnl This test creates database with weak references and checks that orphan
 dnl rows created for weak references are not available for iteration via
 dnl list of tracked changes.
@@ -2022,6 +2097,36 @@ OVSDB_CHECK_IDL_NOTIFY([simple idl verify notify],
 015: done
 ]])
 
+OVSDB_CHECK_IDL_NOTIFY([indexed idl, modification and removal notify],
+  [['track-notify' \
+    '["idltest",
+      {"op": "insert",
+       "table": "indexed",
+       "row": {"i": 123 }}]' \
+    '["idltest",
+      {"op": "update",
+       "table": "indexed",
+       "where": [["i", "==", 123]],
+       "row": {"i": 456}}]' \
+    '["idltest",
+      {"op": "delete",
+       "table": "indexed",
+       "where": [["i", "==", 456]]}]']],
+  [[000: empty
+000: event:create, row={}, uuid=<0>, updates=None
+000: event:create, row={}, uuid=<1>, updates=None
+001: {"error":null,"result":[{"uuid":["uuid","<2>"]}]}
+002: event:create, row={i=123}, uuid=<2>, updates=None
+002: table indexed: i=123 uuid=<2>
+003: {"error":null,"result":[{"count":1}]}
+004: event:update, row={i=456}, uuid=<2>, updates={i=123}
+004: table indexed: i=456 uuid=<2>
+005: {"error":null,"result":[{"count":1}]}
+006: empty
+006: event:delete, row={i=456}, uuid=<2>, updates=None
+007: done
+]])
+
 # Tests to verify the functionality of the one column compound index.
 # It tests index for one column string and integer indexes.
 # The run of test-ovsdb generates the output of the display of data using the different indexes defined in
diff --git a/tests/ovsdb-server.at b/tests/ovsdb-server.at
index b8ccc4c8e2..ce6d32aee1 100644
--- a/tests/ovsdb-server.at
+++ b/tests/ovsdb-server.at
@@ -936,8 +936,10 @@ AT_CHECK_UNQUOTED(
   [ignore])
 # The error message for being unable to negotiate a shared ciphersuite
 # is 'sslv3 alert handshake failure'. This is not the clearest message.
+# In openssl 3.2.0 all the error messages were updated to replace 'sslv3'
+# with 'ssl/tls'.
 AT_CHECK_UNQUOTED(
-  [grep "sslv3 alert handshake failure" output], [0],
+  [grep -E "(sslv3|ssl/tls) alert handshake failure" output], [0],
   [stdout],
   [ignore])
 OVSDB_SERVER_SHUTDOWN(["
diff --git a/tests/packet-type-aware.at b/tests/packet-type-aware.at
index 14cebf6efa..d634930fd5 100644
--- a/tests/packet-type-aware.at
+++ b/tests/packet-type-aware.at
@@ -142,30 +142,27 @@ AT_CHECK([
 ### Setup GRE tunnels
 AT_CHECK([
     ovs-appctl netdev-dummy/ip4addr br-p1 10.0.0.1/24 &&
-    ovs-appctl ovs/route/add 10.0.0.0/24 br-p1 &&
     ovs-appctl tnl/arp/set br-p1 10.0.0.1 $HWADDR_BRP1 &&
     ovs-appctl tnl/arp/set br-p1 10.0.0.2 $HWADDR_BRP2 &&
     ovs-appctl tnl/arp/set br-p1 10.0.0.3 $HWADDR_BRP3 &&
 
     ovs-appctl netdev-dummy/ip4addr br-p2 20.0.0.2/24 &&
-    ovs-appctl ovs/route/add 20.0.0.0/24 br-p2 &&
     ovs-appctl tnl/arp/set br-p2 20.0.0.1 $HWADDR_BRP1 &&
     ovs-appctl tnl/arp/set br-p2 20.0.0.2 $HWADDR_BRP2 &&
     ovs-appctl tnl/arp/set br-p2 20.0.0.3 $HWADDR_BRP3 &&
 
     ovs-appctl netdev-dummy/ip4addr br-p3 30.0.0.3/24 &&
-    ovs-appctl ovs/route/add 30.0.0.0/24 br-p3 &&
     ovs-appctl tnl/arp/set br-p3 30.0.0.1 $HWADDR_BRP1 &&
     ovs-appctl tnl/arp/set br-p3 30.0.0.2 $HWADDR_BRP2 &&
     ovs-appctl tnl/arp/set br-p3 30.0.0.3 $HWADDR_BRP3
 ], [0], [ignore])
 
 AT_CHECK([
-    ovs-appctl ovs/route/show | grep User:
+    ovs-appctl ovs/route/show | grep Cached: | sort
 ], [0], [dnl
-User: 10.0.0.0/24 dev br-p1 SRC 10.0.0.1
-User: 20.0.0.0/24 dev br-p2 SRC 20.0.0.2
-User: 30.0.0.0/24 dev br-p3 SRC 30.0.0.3
+Cached: 10.0.0.0/24 dev br-p1 SRC 10.0.0.1 local
+Cached: 20.0.0.0/24 dev br-p2 SRC 20.0.0.2 local
+Cached: 30.0.0.0/24 dev br-p3 SRC 30.0.0.3 local
 ])
 
 AT_CHECK([
@@ -681,14 +678,13 @@ AT_CHECK([
 
 AT_CHECK([
     ovs-appctl netdev-dummy/ip4addr br2 10.0.0.1/24 &&
-    ovs-appctl ovs/route/add 10.0.0.0/24 br2 &&
     ovs-appctl tnl/arp/set br2 10.0.0.2 de:af:be:ef:ba:be
 ], [0], [ignore])
 
 AT_CHECK([
-    ovs-appctl ovs/route/show | grep User:
+    ovs-appctl ovs/route/show | grep Cached:
 ], [0], [dnl
-User: 10.0.0.0/24 dev br2 SRC 10.0.0.1
+Cached: 10.0.0.0/24 dev br2 SRC 10.0.0.1 local
 ])
 
 
@@ -955,7 +951,6 @@ AT_CHECK([
 
 AT_CHECK([
     ovs-appctl netdev-dummy/ip4addr br0 20.0.0.1/24 &&
-    ovs-appctl ovs/route/add 20.0.0.2/24 br0 &&
     ovs-appctl tnl/neigh/set br0 20.0.0.1 aa:bb:cc:00:00:01 &&
     ovs-appctl tnl/neigh/set br0 20.0.0.2 aa:bb:cc:00:00:02
 ], [0], [ignore])
@@ -963,9 +958,9 @@ AT_CHECK([
 ovs-appctl time/warp 1000
 
 AT_CHECK([
-    ovs-appctl ovs/route/show | grep User
+    ovs-appctl ovs/route/show | grep Cached:
 ],[0], [dnl
-User: 20.0.0.0/24 dev br0 SRC 20.0.0.1
+Cached: 20.0.0.0/24 dev br0 SRC 20.0.0.1 local
 ])
 
 AT_CHECK([
diff --git a/tests/sendpkt.py b/tests/sendpkt.py
index 49ac45275a..7cbea51654 100755
--- a/tests/sendpkt.py
+++ b/tests/sendpkt.py
@@ -48,28 +48,10 @@ if len(args) < 2:
 if options.packet_type != "eth":
     parser.error('invalid argument to "-t"/"--type". Allowed value is "eth".')
 
-# store the hex bytes with 0x appended at the beginning
-# if not present in the user input and validate the hex bytes
-hex_list = []
-for a in args[1:]:
-    if a[:2] != "0x":
-        hex_byte = "0x" + a
-    else:
-        hex_byte = a
-    try:
-        temp = int(hex_byte, 0)
-    except:
-        parser.error("invalid hex byte " + a)
-
-    if temp > 0xff:
-        parser.error("hex byte " + a + " cannot be greater than 0xff!")
-
-    hex_list.append(temp)
-
-if sys.version_info < (3, 0):
-    pkt = "".join(map(chr, hex_list))
-else:
-    pkt = bytes(hex_list)
+# Strip '0x' prefixes from hex input, combine into a single string and
+# convert to bytes.
+hex_str = "".join([a[2:] if a.startswith("0x") else a for a in args[1:]])
+pkt = bytes.fromhex(hex_str)
 
 try:
     sockfd = socket.socket(socket.AF_PACKET, socket.SOCK_RAW)
diff --git a/tests/system-dpdk-macros.at b/tests/system-dpdk-macros.at
index 7cf9bac170..f8ba766739 100644
--- a/tests/system-dpdk-macros.at
+++ b/tests/system-dpdk-macros.at
@@ -102,7 +102,7 @@ m4_define([OVS_DPDK_CHECK_TESTPMD],
 m4_define([OVS_DPDK_START_TESTPMD],
   [AT_CHECK([lscpu], [], [stdout])
    AT_CHECK([cat stdout | grep "NUMA node(s)" | awk '{c=1; while (c++<$(3)) {printf "512,"}; print "512"}' > NUMA_NODE])
-   eal_options="$DPDK_EAL_OPTIONS --in-memory --socket-mem="$(cat NUMA_NODE)" --single-file-segments --no-pci"
+   eal_options="$DPDK_EAL_OPTIONS --in-memory --socket-mem="$(cat NUMA_NODE)" --single-file-segments --no-pci --file-prefix testpmd"
    options="$1"
    test "$options" != "${options%% -- *}" || options="$options -- "
    eal_options="$eal_options ${options%% -- *}"
diff --git a/tests/system-layer3-tunnels.at b/tests/system-layer3-tunnels.at
index 6fbdedb64f..5dcdd2afae 100644
--- a/tests/system-layer3-tunnels.at
+++ b/tests/system-layer3-tunnels.at
@@ -98,61 +98,6 @@ NS_CHECK_EXEC([at_ns0], [ping -s 3200 -q -c 3 -i 0.3 -W 2 10.1.1.2 | FORMAT_PING
 OVS_TRAFFIC_VSWITCHD_STOP
 AT_CLEANUP
 
-AT_SETUP([layer3 - use non-local port as tunnel endpoint])
-
-OVS_VSWITCHD_START([add-port br0 p0 -- set Interface p0 type=dummy ofport_request=1])
-AT_CHECK([ovs-vsctl add-port br0 vtep0 -- set int vtep0 type=dummy], [0])
-AT_CHECK([ovs-vsctl add-br int-br -- set bridge int-br datapath_type=dummy], [0])
-AT_CHECK([ovs-vsctl add-port int-br t1 -- set Interface t1 type=gre \
-                    options:remote_ip=1.1.2.92 ofport_request=3], [0])
-
-AT_CHECK([ovs-appctl dpif/show], [0], [dnl
-dummy@ovs-dummy: hit:0 missed:0
-  br0:
-    br0 65534/100: (dummy-internal)
-    p0 1/1: (dummy)
-    vtep0 2/2: (dummy)
-  int-br:
-    int-br 65534/3: (dummy-internal)
-    t1 3/4: (gre: remote_ip=1.1.2.92)
-])
-
-AT_CHECK([ovs-appctl netdev-dummy/ip4addr vtep0 1.1.2.88/24], [0], [OK
-])
-AT_CHECK([ovs-appctl ovs/route/add 1.1.2.92/24 vtep0], [0], [OK
-])
-AT_CHECK([ovs-ofctl add-flow br0 action=normal])
-AT_CHECK([ovs-ofctl add-flow int-br action=normal])
-
-dnl Use arp request and reply to achieve tunnel next hop mac binding
-dnl By default, vtep0's MAC address is aa:55:aa:55:00:03
-AT_CHECK([ovs-appctl netdev-dummy/receive vtep0 'recirc_id(0),in_port(2),eth(dst=ff:ff:ff:ff:ff:ff,src=aa:55:aa:55:00:03),eth_type(0x0806),arp(tip=1.1.2.92,sip=1.1.2.88,op=1,sha=aa:55:aa:55:00:03,tha=00:00:00:00:00:00)'])
-AT_CHECK([ovs-appctl netdev-dummy/receive p0 'recirc_id(0),in_port(1),eth(src=f8:bc:12:44:34:b6,dst=aa:55:aa:55:00:03),eth_type(0x0806),arp(sip=1.1.2.92,tip=1.1.2.88,op=2,sha=f8:bc:12:44:34:b6,tha=aa:55:aa:55:00:03)'])
-
-AT_CHECK([ovs-appctl tnl/neigh/show | tail -n+3 | sort], [0], [dnl
-1.1.2.92                                      f8:bc:12:44:34:b6   br0
-])
-
-AT_CHECK([ovs-appctl ovs/route/show | tail -n+2 | sort], [0], [dnl
-User: 1.1.2.0/24 dev vtep0 SRC 1.1.2.88
-])
-
-dnl Check GRE tunnel pop
-AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(1),eth(src=f8:bc:12:44:34:b6,dst=aa:55:aa:55:00:03),eth_type(0x0800),ipv4(src=1.1.2.92,dst=1.1.2.88,proto=47,tos=0,ttl=64,frag=no)'], [0], [stdout])
-
-AT_CHECK([tail -1 stdout], [0],
-  [Datapath actions: tnl_pop(4)
-])
-
-dnl Check GRE tunnel push
-AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(3),eth(dst=f9:bc:12:44:34:b6,src=af:55:aa:55:00:03),eth_type(0x0800),ipv4(src=1.1.3.88,dst=1.1.3.92,proto=1,tos=0,ttl=64,frag=no)'], [0], [stdout])
-AT_CHECK([tail -1 stdout], [0],
-  [Datapath actions: tnl_push(tnl_port(4),header(size=38,type=3,eth(dst=f8:bc:12:44:34:b6,src=aa:55:aa:55:00:03,dl_type=0x0800),ipv4(src=1.1.2.88,dst=1.1.2.92,proto=47,tos=0,ttl=64,frag=0x4000),gre((flags=0x0,proto=0x6558))),out_port(2)),1
-])
-
-OVS_VSWITCHD_STOP
-AT_CLEANUP
-
 AT_SETUP([layer3 - ping over MPLS Bareudp])
 OVS_CHECK_BAREUDP()
 OVS_TRAFFIC_VSWITCHD_START([_ADD_BR([br1])])
diff --git a/tests/system-route.at b/tests/system-route.at
index 114aaebc77..c0ecad6cfb 100644
--- a/tests/system-route.at
+++ b/tests/system-route.at
@@ -64,3 +64,67 @@ Cached: fc00:db8:beef::13/128 dev br0 GW fc00:db8:cafe::1 SRC fc00:db8:cafe::2])
 
 OVS_TRAFFIC_VSWITCHD_STOP
 AT_CLEANUP
+
+dnl Checks that OVS doesn't use routes from non-standard tables.
+AT_SETUP([ovs-route - route tables])
+AT_KEYWORDS([route])
+OVS_TRAFFIC_VSWITCHD_START()
+
+dnl Create tap port.
+on_exit 'ip link del p1-route'
+AT_CHECK([ip tuntap add name p1-route mode tap])
+AT_CHECK([ip link set p1-route up])
+
+dnl Add ip address.
+AT_CHECK([ip addr add 10.0.0.17/24 dev p1-route], [0], [stdout])
+
+dnl Check that OVS catches route updates.
+OVS_WAIT_UNTIL_EQUAL([ovs-appctl ovs/route/show | grep 'p1-route' | sort], [dnl
+Cached: 10.0.0.0/24 dev p1-route SRC 10.0.0.17
+Cached: 10.0.0.17/32 dev p1-route SRC 10.0.0.17 local])
+
+dnl Add a route to the main routing table and check that OVS caches
+dnl this new route.
+AT_CHECK([ip route add 10.0.0.18/32 dev p1-route])
+OVS_WAIT_UNTIL_EQUAL([ovs-appctl ovs/route/show | grep 'p1-route' | sort], [dnl
+Cached: 10.0.0.0/24 dev p1-route SRC 10.0.0.17
+Cached: 10.0.0.17/32 dev p1-route SRC 10.0.0.17 local
+Cached: 10.0.0.18/32 dev p1-route SRC 10.0.0.17])
+
+dnl Add a route to a custom routing table and check that OVS doesn't cache it.
+AT_CHECK([ip route add 10.0.0.19/32 dev p1-route table 42])
+AT_CHECK([ip route show table 42 | grep 'p1-route' | grep -q '10.0.0.19'])
+dnl Give the main thread a chance to act.
+AT_CHECK([ovs-appctl revalidator/wait])
+dnl Check that OVS didn't learn this route.
+AT_CHECK([ovs-appctl ovs/route/show | grep 'p1-route' | sort], [0], [dnl
+Cached: 10.0.0.0/24 dev p1-route SRC 10.0.0.17
+Cached: 10.0.0.17/32 dev p1-route SRC 10.0.0.17 local
+Cached: 10.0.0.18/32 dev p1-route SRC 10.0.0.17
+])
+
+dnl Delete a route from the main table and check that OVS removes the route
+dnl from the cache.
+AT_CHECK([ip route del 10.0.0.18/32 dev p1-route])
+OVS_WAIT_UNTIL_EQUAL([ovs-appctl ovs/route/show | grep 'p1-route' | sort], [dnl
+Cached: 10.0.0.0/24 dev p1-route SRC 10.0.0.17
+Cached: 10.0.0.17/32 dev p1-route SRC 10.0.0.17 local])
+
+dnl Delete a route from a custom routing table and check that the cache
+dnl dosn't change.
+AT_CHECK([ip route del 10.0.0.19/32 dev p1-route table 42])
+dnl Give the main thread a chance to act.
+AT_CHECK([ovs-appctl revalidator/wait])
+dnl Check that the cache is still the same.
+AT_CHECK([ovs-appctl ovs/route/show | grep 'p1-route' | sort], [0], [dnl
+Cached: 10.0.0.0/24 dev p1-route SRC 10.0.0.17
+Cached: 10.0.0.17/32 dev p1-route SRC 10.0.0.17 local
+])
+
+dnl Delete ip address.
+AT_CHECK([ip addr del 10.0.0.17/24 dev p1-route], [0], [stdout])
+dnl Check that routes were removed from OVS.
+OVS_WAIT_UNTIL([test $(ovs-appctl ovs/route/show | grep -c 'p1-route') -eq 0 ])
+
+OVS_TRAFFIC_VSWITCHD_STOP
+AT_CLEANUP
diff --git a/tests/system-traffic.at b/tests/system-traffic.at
index 98e494abf4..3a33707167 100644
--- a/tests/system-traffic.at
+++ b/tests/system-traffic.at
@@ -2359,11 +2359,22 @@ table=20 actions=drop
 AT_CHECK([ovs-ofctl del-flows br0])
 AT_CHECK([ovs-ofctl --bundle add-flows br0 flows.txt])
 
+m4_define([ND_NS_PKT], [m4_join([,],
+  [eth_src=36:b1:ee:7c:01:03,eth_dst=36:b1:ee:7c:01:02,eth_type=0x86dd],
+  [ipv6_src=fe80::f816:3eff:fe04:6604,ipv6_dst=fe80::f816:3eff:fea7:dd0e],
+  [nw_proto=58,nw_ttl=255,nw_frag=no],
+  [icmpv6_type=136,icmpv6_code=0],
+  [nd_options_type=2,nd_tll=36:b1:ee:7c:01:03])])
+
 dnl Send a mismatching neighbor discovery.
-NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 36 b1 ee 7c 01 02 36 b1 ee 7c 01 03 86 dd 60 00 00 00 00 20 3a ff fe 80 00 00 00 00 00 00 f8 16 3e ff fe 04 66 04 fe 80 00 00 00 00 00 00 f8 16 3e ff fe a7 dd 0e 88 00 f1 f2 20 00 00 00 30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 02 01 36 b1 ee 7c 01 03 > /dev/null])
+NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 \
+    $(ovs-ofctl compose-packet --bare 'ND_NS_PKT,nd_target=3000::1')],
+  [0], [ignore])
 
 dnl Send a matching neighbor discovery.
-NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 36 b1 ee 7c 01 02 36 b1 ee 7c 01 03 86 dd 60 00 00 00 00 20 3a ff fe 80 00 00 00 00 00 00 f8 16 3e ff fe 04 66 04 fe 80 00 00 00 00 00 00 f8 16 3e ff fe a7 dd 0e 88 00 fe 5f 20 00 00 00 20 01 00 00 00 00 00 00 00 00 00 01 00 00 03 92 02 01 36 b1 ee 7c 01 03 > /dev/null])
+NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 \
+    $(ovs-ofctl compose-packet --bare 'ND_NS_PKT,nd_target=2001::1:0:392')],
+  [0], [ignore])
 
 AT_CHECK([ovs-appctl dpctl/dump-flows | strip_stats | strip_used | dnl
           strip_key32 | strip_ptype | strip_eth | strip_recirc | dnl
@@ -2375,10 +2386,14 @@ recirc_id(<recirc>),in_port(2),eth_type(0x86dd),ipv6(proto=58,frag=no),icmpv6(ty
 OVS_WAIT_UNTIL([ovs-appctl dpctl/dump-flows | grep ",nd" | wc -l | grep -E ^0])
 
 dnl Send a matching neighbor discovery.
-NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 36 b1 ee 7c 01 02 36 b1 ee 7c 01 03 86 dd 60 00 00 00 00 20 3a ff fe 80 00 00 00 00 00 00 f8 16 3e ff fe 04 66 04 fe 80 00 00 00 00 00 00 f8 16 3e ff fe a7 dd 0e 88 00 fe 5f 20 00 00 00 20 01 00 00 00 00 00 00 00 00 00 01 00 00 03 92 02 01 36 b1 ee 7c 01 03 > /dev/null])
+NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 \
+    $(ovs-ofctl compose-packet --bare 'ND_NS_PKT,nd_target=2001::1:0:392')],
+  [0], [ignore])
 
 dnl Send a mismatching neighbor discovery.
-NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 36 b1 ee 7c 01 02 36 b1 ee 7c 01 03 86 dd 60 00 00 00 00 20 3a ff fe 80 00 00 00 00 00 00 f8 16 3e ff fe 04 66 04 fe 80 00 00 00 00 00 00 f8 16 3e ff fe a7 dd 0e 88 00 f1 f2 20 00 00 00 30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 02 01 36 b1 ee 7c 01 03 > /dev/null])
+NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 \
+    $(ovs-ofctl compose-packet --bare 'ND_NS_PKT,nd_target=3000::1')],
+  [0], [ignore])
 
 AT_CHECK([ovs-appctl dpctl/dump-flows | strip_stats | strip_used | dnl
           strip_key32 | strip_ptype | strip_eth | strip_recirc | dnl
@@ -2407,20 +2422,29 @@ dnl The flow will encap a mpls header to the ip packet
 dnl eth/ip/icmp --> OVS --> eth/mpls/eth/ip/icmp
 AT_CHECK([ovs-ofctl -Oopenflow13 add-flow br0 "table=0,priority=100,dl_type=0x0800 actions=encap(mpls),set_mpls_label:2,encap(ethernet),set_field:00:00:00:00:00:02->dl_dst,set_field:00:00:00:00:00:01->dl_src,ovs-p1"])
 
-rm -rf p1.pcap
-NETNS_DAEMONIZE([at_ns1], [tcpdump -l -n -xx -U -i p1 > p1.pcap], [tcpdump.pid])
-sleep 1
+NETNS_DAEMONIZE([at_ns1],
+  [tcpdump -l -n -xx -U -i p1 -w p1.pcap 2>tcpdump_err], [tcpdump.pid])
+OVS_WAIT_UNTIL([grep "listening" tcpdump_err])
+
+m4_define([ICMP_PKT], [m4_join([,],
+  [eth_src=36:b1:ee:7c:01:03,eth_dst=36:b1:ee:7c:01:02,eth_type=0x0800],
+  [nw_src=10.1.1.1,nw_dst=10.1.1.2],
+  [nw_proto=1,nw_ttl=64,nw_frag=no],
+  [icmp_type=8,icmp_code=0])])
 
-dnl The hex dump is a icmp packet. pkt=eth/ip/icmp
 dnl The packet is sent from p0(at_ns0) interface directed to
-dnl p1(at_ns1) interface
-NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 36 b1 ee 7c 01 02 36 b1 ee 7c 01 03 08 00 45 00 00 54 03 44 40 00 40 01 21 61 0a 01 01 01 0a 01 01 02 08 00 ef ac 7c e4 00 03 5b 2c 1f 61 00 00 00 00 50 0b 02 00 00 00 00 00 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37  > /dev/null])
+dnl p1(at_ns1) interface.
+NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 \
+    $(ovs-ofctl compose-packet --bare 'ICMP_PKT')], [0], [ignore])
 
-dnl Check the expected mpls encapsulated packet on the egress interface
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0000:  *0000 *0000 *0002 *0000 *0000 *0001 *8847 *0000" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0010:  *2140 *36b1 *ee7c *0102 *36b1 *ee7c *0103 *0800" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0020:  *4500 *0054 *0344 *4000 *4001 *2161 *0a01 *0101" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0030:  *0a01 *0102 *0800 *efac *7ce4 *0003 *5b2c *1f61" 2>&1 1>/dev/null])
+dnl Check the expected mpls encapsulated packet on the egress interface.
+m4_define([MPLS_HEADER], [m4_join([,],
+  [eth_src=00:00:00:00:00:01,eth_dst=00:00:00:00:00:02,eth_type=0x8847],
+  [mpls_label=2,mpls_ttl=64,mpls_bos=1])])
+
+OVS_WAIT_UNTIL([ovs-pcap p1.pcap | grep -q "m4_join([], [^],
+    $(ovs-ofctl compose-packet --bare 'MPLS_HEADER'),
+    $(ovs-ofctl compose-packet --bare 'ICMP_PKT'), [\$])"])
 
 OVS_TRAFFIC_VSWITCHD_STOP
 AT_CLEANUP
@@ -2439,20 +2463,29 @@ dnl The flow will encap a mpls header to the ip packet
 dnl eth/ip/icmp --> OVS --> eth/mpls/eth/ip/icmp
 AT_CHECK([ovs-ofctl -Oopenflow13 add-flow br0 "table=0,priority=100,dl_type=0x0800 actions=encap(mpls),set_mpls_label:2,encap(ethernet),set_field:00:00:00:00:00:02->dl_dst,set_field:00:00:00:00:00:01->dl_src,ovs-p1"])
 
-rm -rf p1.pcap
-NETNS_DAEMONIZE([at_ns1], [tcpdump -l -n -xx -U -i p1 > p1.pcap], [tcpdump.pid])
-sleep 1
+NETNS_DAEMONIZE([at_ns1],
+  [tcpdump -l -n -xx -U -i p1 -w p1.pcap 2>tcpdump_err], [tcpdump.pid])
+OVS_WAIT_UNTIL([grep "listening" tcpdump_err])
+
+m4_define([ICMP_PKT], [m4_join([,],
+  [eth_src=36:b1:ee:7c:01:03,eth_dst=36:b1:ee:7c:01:02,eth_type=0x0800],
+  [nw_src=10.1.1.1,nw_dst=10.1.1.2],
+  [nw_proto=1,nw_ttl=64,nw_frag=no],
+  [icmp_type=8,icmp_code=0])])
 
-dnl The hex dump is a icmp packet. pkt=eth/ip/icmp
 dnl The packet is sent from p0(at_ns0) interface directed to
-dnl p1(at_ns1) interface
-NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 36 b1 ee 7c 01 02 36 b1 ee 7c 01 03 08 00 45 00 00 54 03 44 40 00 40 01 21 61 0a 01 01 01 0a 01 01 02 08 00 ef ac 7c e4 00 03 5b 2c 1f 61 00 00 00 00 50 0b 02 00 00 00 00 00 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37  > /dev/null])
+dnl p1(at_ns1) interface.
+NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 \
+    $(ovs-ofctl compose-packet --bare 'ICMP_PKT')], [0], [ignore])
+
+dnl Check the expected mpls encapsulated packet on the egress interface.
+m4_define([MPLS_HEADER], [m4_join([,],
+  [eth_src=00:00:00:00:00:01,eth_dst=00:00:00:00:00:02,eth_type=0x8847],
+  [mpls_label=2,mpls_ttl=64,mpls_bos=1])])
 
-dnl Check the expected mpls encapsulated packet on the egress interface
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0000:  *0000 *0000 *0002 *0000 *0000 *0001 *8847 *0000" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0010:  *2140 *36b1 *ee7c *0102 *36b1 *ee7c *0103 *0800" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0020:  *4500 *0054 *0344 *4000 *4001 *2161 *0a01 *0101" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0030:  *0a01 *0102 *0800 *efac *7ce4 *0003 *5b2c *1f61" 2>&1 1>/dev/null])
+OVS_WAIT_UNTIL([ovs-pcap p1.pcap | grep -q "m4_join([], [^],
+    $(ovs-ofctl compose-packet --bare 'MPLS_HEADER'),
+    $(ovs-ofctl compose-packet --bare 'ICMP_PKT'), [\$])"])
 
 OVS_TRAFFIC_VSWITCHD_STOP
 AT_CLEANUP
@@ -2472,20 +2505,29 @@ dnl The flow will encap a mpls header to the ip packet
 dnl eth/ip/icmp --> OVS --> eth/mpls/eth/ip/icmp
 AT_CHECK([ovs-ofctl -Oopenflow13 add-flow br0 "table=0,priority=100,dl_type=0x0800 actions=encap(mpls_mc),set_mpls_label:2,encap(ethernet),set_field:00:00:00:00:00:02->dl_dst,set_field:00:00:00:00:00:01->dl_src,ovs-p1"])
 
-rm -rf p1.pcap
-NETNS_DAEMONIZE([at_ns1], [tcpdump -l -n -xx -U -i p1 > p1.pcap], [tcpdump.pid])
-sleep 1
+NETNS_DAEMONIZE([at_ns1],
+  [tcpdump -l -n -xx -U -i p1 -w p1.pcap 2>tcpdump_err], [tcpdump.pid])
+OVS_WAIT_UNTIL([grep "listening" tcpdump_err])
+
+m4_define([ICMP_PKT], [m4_join([,],
+  [eth_src=36:b1:ee:7c:01:03,eth_dst=36:b1:ee:7c:01:02,eth_type=0x0800],
+  [nw_src=10.1.1.1,nw_dst=10.1.1.2],
+  [nw_proto=1,nw_ttl=64,nw_frag=no],
+  [icmp_type=8,icmp_code=0])])
 
-dnl The hex dump is a icmp packet. pkt=eth/ip/icmp
 dnl The packet is sent from p0(at_ns0) interface directed to
-dnl p1(at_ns1) interface
-NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 36 b1 ee 7c 01 02 36 b1 ee 7c 01 03 08 00 45 00 00 54 03 44 40 00 40 01 21 61 0a 01 01 01 0a 01 01 02 08 00 ef ac 7c e4 00 03 5b 2c 1f 61 00 00 00 00 50 0b 02 00 00 00 00 00 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37  > /dev/null])
+dnl p1(at_ns1) interface.
+NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 \
+    $(ovs-ofctl compose-packet --bare 'ICMP_PKT')], [0], [ignore])
+
+dnl Check the expected mpls encapsulated packet on the egress interface.
+m4_define([MPLS_HEADER], [m4_join([,],
+  [eth_src=00:00:00:00:00:01,eth_dst=00:00:00:00:00:02,eth_type=0x8848],
+  [mpls_label=2,mpls_ttl=64,mpls_bos=1])])
 
-dnl Check the expected mpls encapsulated packet on the egress interface
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0000:  *0000 *0000 *0002 *0000 *0000 *0001 *8848 *0000" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0010:  *2140 *36b1 *ee7c *0102 *36b1 *ee7c *0103 *0800" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0020:  *4500 *0054 *0344 *4000 *4001 *2161 *0a01 *0101" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0030:  *0a01 *0102 *0800 *efac *7ce4 *0003 *5b2c *1f61" 2>&1 1>/dev/null])
+OVS_WAIT_UNTIL([ovs-pcap p1.pcap | grep -q "m4_join([], [^],
+    $(ovs-ofctl compose-packet --bare 'MPLS_HEADER'),
+    $(ovs-ofctl compose-packet --bare 'ICMP_PKT'), [\$])"])
 
 OVS_TRAFFIC_VSWITCHD_STOP
 AT_CLEANUP
@@ -2504,20 +2546,29 @@ dnl The flow will encap a mpls header to the ip packet
 dnl eth/ip/icmp --> OVS --> eth/mpls/eth/ip/icmp
 AT_CHECK([ovs-ofctl -Oopenflow13 add-flow br0 "table=0,priority=100,dl_type=0x0800 actions=encap(mpls_mc),set_mpls_label:2,encap(ethernet),set_field:00:00:00:00:00:02->dl_dst,set_field:00:00:00:00:00:01->dl_src,ovs-p1"])
 
-rm -rf p1.pcap
-NETNS_DAEMONIZE([at_ns1], [tcpdump -l -n -xx -U -i p1 > p1.pcap], [tcpdump.pid])
-sleep 1
+NETNS_DAEMONIZE([at_ns1],
+  [tcpdump -l -n -xx -U -i p1 -w p1.pcap 2>tcpdump_err], [tcpdump.pid])
+OVS_WAIT_UNTIL([grep "listening" tcpdump_err])
+
+m4_define([ICMP_PKT], [m4_join([,],
+  [eth_src=36:b1:ee:7c:01:03,eth_dst=36:b1:ee:7c:01:02,eth_type=0x0800],
+  [nw_src=10.1.1.1,nw_dst=10.1.1.2],
+  [nw_proto=1,nw_ttl=64,nw_frag=no],
+  [icmp_type=8,icmp_code=0])])
 
-dnl The hex dump is a icmp packet. pkt=eth/ip/icmp
 dnl The packet is sent from p0(at_ns0) interface directed to
-dnl p1(at_ns1) interface
-NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 36 b1 ee 7c 01 02 36 b1 ee 7c 01 03 08 00 45 00 00 54 03 44 40 00 40 01 21 61 0a 01 01 01 0a 01 01 02 08 00 ef ac 7c e4 00 03 5b 2c 1f 61 00 00 00 00 50 0b 02 00 00 00 00 00 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37  > /dev/null])
+dnl p1(at_ns1) interface.
+NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 \
+    $(ovs-ofctl compose-packet --bare 'ICMP_PKT')], [0], [ignore])
+
+dnl Check the expected mpls encapsulated packet on the egress interface.
+m4_define([MPLS_HEADER], [m4_join([,],
+  [eth_src=00:00:00:00:00:01,eth_dst=00:00:00:00:00:02,eth_type=0x8848],
+  [mpls_label=2,mpls_ttl=64,mpls_bos=1])])
 
-dnl Check the expected mpls encapsulated packet on the egress interface
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0000:  *0000 *0000 *0002 *0000 *0000 *0001 *8848 *0000" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0010:  *2140 *36b1 *ee7c *0102 *36b1 *ee7c *0103 *0800" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0020:  *4500 *0054 *0344 *4000 *4001 *2161 *0a01 *0101" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0030:  *0a01 *0102 *0800 *efac *7ce4 *0003 *5b2c *1f61" 2>&1 1>/dev/null])
+OVS_WAIT_UNTIL([ovs-pcap p1.pcap | grep -q "m4_join([], [^],
+    $(ovs-ofctl compose-packet --bare 'MPLS_HEADER'),
+    $(ovs-ofctl compose-packet --bare 'ICMP_PKT'), [\$])"])
 
 OVS_TRAFFIC_VSWITCHD_STOP
 AT_CLEANUP
@@ -2538,24 +2589,30 @@ dnl eth/mpls/eth/ip/icmp --> OVS --> eth/ip/icmp
 
 AT_CHECK([ovs-ofctl -Oopenflow13 add-flow br0 "table=0,priority=100,dl_type=0x8847,mpls_label=2 actions=decap(),decap(packet_type(ns=0,type=0)),ovs-p1"])
 
-rm -rf p1.pcap
-NETNS_DAEMONIZE([at_ns1], [tcpdump -l -n -xx -U -i p1 > p1.pcap], [tcpdump.pid])
-sleep 1
+NETNS_DAEMONIZE([at_ns1],
+  [tcpdump -l -n -xx -U -i p1 -w p1.pcap 2>tcpdump_err], [tcpdump.pid])
+OVS_WAIT_UNTIL([grep "listening" tcpdump_err])
 
-dnl The hex dump is an mpls packet encapsulating ethernet packet. pkt=eth/mpls/eth/ip/icmp
-dnl The packet is sent from p0(at_ns0) interface directed to
-dnl p1(at_ns1) interface
-NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 00 00 00 00 00 02 00 00 00 00 00 01 88 47 00 00 21 40 36 b1 ee 7c 01 02 36 b1 ee 7c 01 03 08 00 45 00 00 54 03 44 40 00 40 01 21 61 0a 01 01 01 0a 01 01 02 08 00 ef ac 7c e4 00 03 5b 2c 1f 61 00 00 00 00 50 0b 02 00 00 00 00 00 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37  > /dev/null])
+m4_define([MPLS_HEADER], [m4_join([,],
+  [eth_src=00:00:00:00:00:01,eth_dst=00:00:00:00:00:02,eth_type=0x8847],
+  [mpls_label=2,mpls_ttl=64,mpls_bos=1])])
 
-dnl Check the expected decapsulated on the egress interface
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0000:  *36b1 *ee7c *0102 *36b1 *ee7c *0103 *0800 *4500" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0010:  *0054 *0344 *4000 *4001 *2161 *0a01 *0101 *0a01" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0020:  *0102 *0800 *efac *7ce4 *0003 *5b2c *1f61 *0000" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0030:  *0000 *500b *0200 *0000 *0000 *1011 *1213 *1415" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0040:  *1617 *1819 *1a1b *1c1d *1e1f *2021 *2223 *2425" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0050:  *2627 *2829 *2a2b *2c2d *2e2f *3031 *3233 *3435" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0060:  *3637" 2>&1 1>/dev/null])
+m4_define([ICMP_PKT], [m4_join([,],
+  [eth_src=36:b1:ee:7c:01:03,eth_dst=36:b1:ee:7c:01:02,eth_type=0x0800],
+  [nw_src=10.1.1.1,nw_dst=10.1.1.2],
+  [nw_proto=1,nw_ttl=64,nw_frag=no],
+  [icmp_type=8,icmp_code=0])])
 
+dnl The packet is an eth/mpls/eth/ip/icmp sent from p0(at_ns0) interface
+dnl directed to p1(at_ns1) interface.
+NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 \
+    "$(ovs-ofctl compose-packet --bare 'MPLS_HEADER')"  \
+    "$(ovs-ofctl compose-packet --bare 'ICMP_PKT')"],
+  [0], [ignore])
+
+dnl Check the expected decapsulated on the egress interface.
+OVS_WAIT_UNTIL([ovs-pcap p1.pcap | grep -q \
+    "^$(ovs-ofctl compose-packet --bare 'ICMP_PKT')\$"])
 
 OVS_TRAFFIC_VSWITCHD_STOP
 AT_CLEANUP
@@ -2575,24 +2632,30 @@ dnl eth/mpls/eth/ip/icmp --> OVS --> eth/ip/icmp
 
 AT_CHECK([ovs-ofctl -Oopenflow13 add-flow br0 "table=0,priority=100,dl_type=0x8847,mpls_label=2 actions=decap(),decap(packet_type(ns=0,type=0)),ovs-p1"])
 
-rm -rf p1.pcap
-NETNS_DAEMONIZE([at_ns1], [tcpdump -l -n -xx -U -i p1 > p1.pcap], [tcpdump.pid])
-sleep 1
+NETNS_DAEMONIZE([at_ns1],
+  [tcpdump -l -n -xx -U -i p1 -w p1.pcap 2>tcpdump_err], [tcpdump.pid])
+OVS_WAIT_UNTIL([grep "listening" tcpdump_err])
 
-dnl The hex dump is an mpls packet encapsulating ethernet packet. pkt=eth/mpls/eth/ip/icmp
-dnl The packet is sent from p0(at_ns0) interface directed to
-dnl p1(at_ns1) interface
-NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 00 00 00 00 00 02 00 00 00 00 00 01 88 47 00 00 21 40 36 b1 ee 7c 01 02 36 b1 ee 7c 01 03 08 00 45 00 00 54 03 44 40 00 40 01 21 61 0a 01 01 01 0a 01 01 02 08 00 ef ac 7c e4 00 03 5b 2c 1f 61 00 00 00 00 50 0b 02 00 00 00 00 00 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37  > /dev/null])
+m4_define([MPLS_HEADER], [m4_join([,],
+  [eth_src=00:00:00:00:00:01,eth_dst=00:00:00:00:00:02,eth_type=0x8847],
+  [mpls_label=2,mpls_ttl=64,mpls_bos=1])])
+
+m4_define([ICMP_PKT], [m4_join([,],
+  [eth_src=36:b1:ee:7c:01:03,eth_dst=36:b1:ee:7c:01:02,eth_type=0x0800],
+  [nw_src=10.1.1.1,nw_dst=10.1.1.2],
+  [nw_proto=1,nw_ttl=64,nw_frag=no],
+  [icmp_type=8,icmp_code=0])])
 
-dnl Check the expected decapsulated on the egress interface
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0000:  *36b1 *ee7c *0102 *36b1 *ee7c *0103 *0800 *4500" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0010:  *0054 *0344 *4000 *4001 *2161 *0a01 *0101 *0a01" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0020:  *0102 *0800 *efac *7ce4 *0003 *5b2c *1f61 *0000" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0030:  *0000 *500b *0200 *0000 *0000 *1011 *1213 *1415" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0040:  *1617 *1819 *1a1b *1c1d *1e1f *2021 *2223 *2425" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0050:  *2627 *2829 *2a2b *2c2d *2e2f *3031 *3233 *3435" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0060:  *3637" 2>&1 1>/dev/null])
+dnl The packet is an eth/mpls/eth/ip/icmp sent from p0(at_ns0) interface
+dnl directed to p1(at_ns1) interface.
+NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 \
+    "$(ovs-ofctl compose-packet --bare 'MPLS_HEADER')"  \
+    "$(ovs-ofctl compose-packet --bare 'ICMP_PKT')"],
+  [0], [ignore])
 
+dnl Check the expected decapsulated on the egress interface.
+OVS_WAIT_UNTIL([ovs-pcap p1.pcap | grep -q \
+    "^$(ovs-ofctl compose-packet --bare 'ICMP_PKT')\$"])
 
 OVS_TRAFFIC_VSWITCHD_STOP
 AT_CLEANUP
@@ -3103,7 +3166,10 @@ AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.1.1.2)], [0], [dnl
 icmp,orig=(src=10.1.1.1,dst=10.1.1.2,id=<cleared>,type=8,code=0),reply=(src=10.1.1.2,dst=10.1.1.1,id=<cleared>,type=0,code=0)
 ])
 
-AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+AT_CHECK([ovs-appctl dpctl/flush-conntrack 'ct_nw_src=10.1.1.1,ct_nw_dst=10.1.1.2'])
+
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.1.1.2)], [0], [dnl
+])
 
 dnl Pings from ns1->ns0 should fail.
 NS_CHECK_EXEC([at_ns1], [ping -q -c 3 -i 0.3 -w 2 10.1.1.1 | FORMAT_PING], [0], [dnl
@@ -3244,6 +3310,11 @@ AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fc00::2)], [0], [dnl
 icmpv6,orig=(src=fc00::1,dst=fc00::2,id=<cleared>,type=128,code=0),reply=(src=fc00::2,dst=fc00::1,id=<cleared>,type=129,code=0)
 ])
 
+AT_CHECK([ovs-appctl dpctl/flush-conntrack 'ct_ipv6_src=fc00::1,ct_ipv6_dst=fc00::2'])
+
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fc00::2)], [0], [dnl
+])
+
 OVS_TRAFFIC_VSWITCHD_STOP
 AT_CLEANUP
 
@@ -6397,11 +6468,11 @@ ADD_NAMESPACES(at_ns0, at_ns1)
 ADD_VETH(p0, at_ns0, br0, "10.1.1.1/24")
 NS_CHECK_EXEC([at_ns0], [ip link set dev p0 address 80:88:88:88:88:88])
 ADD_VETH(p1, at_ns1, br0, "10.1.1.2/24")
+NS_CHECK_EXEC([at_ns1], [ip link set dev p1 address 80:89:89:89:89:89])
 
 dnl Allow any traffic from ns0->ns1. Only allow nd, return traffic from ns1->ns0.
 AT_DATA([flows.txt], [dnl
-in_port=1,tcp,action=ct(commit,zone=1,nat(src=10.1.1.240:34568,random)),2
-in_port=2,ct_state=-trk,tcp,tp_dst=34567,action=ct(table=0,zone=1,nat)
+in_port=1,tcp,action=ct(commit,zone=1,nat(src=10.1.1.240:34568)),2
 in_port=2,ct_state=-trk,tcp,tp_dst=34568,action=ct(table=0,zone=1,nat)
 in_port=2,ct_state=+trk,ct_zone=1,tcp,action=1
 dnl
@@ -6425,17 +6496,28 @@ AT_CHECK([ovs-ofctl --bundle add-flows br0 flows.txt])
 
 dnl HTTP requests from p0->p1 should work fine.
 OVS_START_L7([at_ns1], [http])
-NS_CHECK_EXEC([at_ns0], [wget 10.1.1.2 -t 1 -T 1 --retry-connrefused -v -o wget0.log])
+
+dnl Send a valid SYN to make conntrack pick it up.
+dnl The source port used is 123 to prevent unwanted reuse in the next HTTP request.
+syn_pkt=$(ovs-ofctl compose-packet --bare "eth_src=80:88:88:88:88:88,eth_dst=80:89:89:89:89:89,\
+  dl_type=0x0800,nw_src=10.1.1.1,nw_dst=10.1.1.2,nw_proto=6,nw_ttl=64,nw_frag=no,tcp_flags=syn,\
+  tcp_src=123,tcp_dst=80")
+AT_CHECK([ovs-ofctl packet-out br0 "packet=${syn_pkt} actions=ct(commit,zone=1,nat(src=10.1.1.240:34568))"])
+
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.1.1.2) | uniq], [0], [dnl
+tcp,orig=(src=10.1.1.1,dst=10.1.1.2,sport=<cleared>,dport=<cleared>),reply=(src=10.1.1.2,dst=10.1.1.240,sport=<cleared>,dport=<cleared>),zone=1,protoinfo=(state=<cleared>)
+])
 
 NS_CHECK_EXEC([at_ns0], [wget 10.1.1.2 -t 1 -T 1 --retry-connrefused -v -o wget0.log], [4])
 
-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.1.1.2) | sed -e 's/dst=10.1.1.2[[45]][[0-9]]/dst=10.1.1.2XX/' | uniq], [0], [dnl
-tcp,orig=(src=10.1.1.1,dst=10.1.1.2,sport=<cleared>,dport=<cleared>),reply=(src=10.1.1.2,dst=10.1.1.2XX,sport=<cleared>,dport=<cleared>),zone=1,protoinfo=(state=<cleared>)
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.1.1.2) | uniq], [0], [dnl
+tcp,orig=(src=10.1.1.1,dst=10.1.1.2,sport=<cleared>,dport=<cleared>),reply=(src=10.1.1.2,dst=10.1.1.240,sport=<cleared>,dport=<cleared>),zone=1,protoinfo=(state=<cleared>)
 ])
 
 OVS_TRAFFIC_VSWITCHD_STOP(["dnl
 /Unable to NAT due to tuple space exhaustion - if DoS attack, use firewalling and\/or zone partitioning./d
-/Dropped .* log messages in last .* seconds \(most recently, .* seconds ago\) due to excessive rate/d"])
+/Dropped .* log messages in last .* seconds \(most recently, .* seconds ago\) due to excessive rate/d
+/|WARN|.* execute ct.* failed/d"])
 AT_CLEANUP
 
 AT_SETUP([conntrack - more complex SNAT])
@@ -8215,10 +8297,18 @@ table=2,priority=10  ct_state=+trk+est action=drop
 
 AT_CHECK([ovs-ofctl --bundle add-flows br0 flows.txt])
 
-# sending icmp pkts, first and second
-NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 f0 00 00 01 01 02 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 01 01 02 08 00 f7 ff ff ff ff ff > /dev/null])
+m4_define([ICMP_PKT], [m4_join([,],
+  [eth_src=f0:00:00:01:01:01,eth_dst=f0:00:00:01:01:02,eth_type=0x0800],
+  [nw_src=10.1.1.1,nw_dst=10.1.1.2],
+  [nw_proto=1,nw_ttl=64,nw_frag=no],
+  [icmp_type=8,icmp_code=0])])
+
+# Sending ICMP packets, first and second.
+NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 \
+    $(ovs-ofctl compose-packet --bare 'ICMP_PKT' '')], [0], [ignore])
 
-NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 f0 00 00 01 01 02 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 01 01 02 08 00 f7 ff ff ff ff ff > /dev/null])
+NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 \
+    $(ovs-ofctl compose-packet --bare 'ICMP_PKT' '')], [0], [ignore])
 
 sleep 1
 
@@ -8389,6 +8479,53 @@ AT_CHECK([ovs-pcap client.pcap | grep 000000002010000000002000], [0], [dnl
 OVS_TRAFFIC_VSWITCHD_STOP
 AT_CLEANUP
 
+AT_SETUP([conntrack - Flush many conntrack entries by port])
+CHECK_CONNTRACK()
+OVS_TRAFFIC_VSWITCHD_START()
+
+ADD_NAMESPACES(at_ns0, at_ns1)
+
+ADD_VETH(p0, at_ns0, br0, "10.1.1.1/24")
+ADD_VETH(p1, at_ns1, br0, "10.1.1.2/24")
+
+AT_DATA([flows.txt], [dnl
+priority=100,in_port=1,udp,action=ct(zone=1,commit),2
+])
+
+AT_CHECK([ovs-ofctl --bundle add-flows br0 flows.txt])
+
+dnl 20 packets from port 1 and 1 packet from port 2.
+flow_l3="\
+    eth_src=50:54:00:00:00:09,eth_dst=50:54:00:00:00:0a,dl_type=0x0800,\
+    nw_src=10.1.1.1,nw_dst=10.1.1.2,nw_proto=17,nw_ttl=64,nw_frag=no"
+
+for i in $(seq 1 20); do
+    frame=$(ovs-ofctl compose-packet --bare "$flow_l3, udp_src=1,udp_dst=$i")
+    AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=1 packet=$frame actions=resubmit(,0)"])
+done
+frame=$(ovs-ofctl compose-packet --bare "$flow_l3, udp_src=2,udp_dst=1")
+AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=1 packet=$frame actions=resubmit(,0)"])
+
+: > conntrack
+
+for i in $(seq 1 20); do
+    echo "udp,orig=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=${i}),reply=(src=10.1.1.2,dst=10.1.1.1,sport=${i},dport=1),zone=1" >> conntrack
+done
+echo "udp,orig=(src=10.1.1.1,dst=10.1.1.2,sport=2,dport=1),reply=(src=10.1.1.2,dst=10.1.1.1,sport=1,dport=2),zone=1" >> conntrack
+
+sort conntrack > expout
+
+AT_CHECK([ovs-appctl dpctl/dump-conntrack zone=1 | grep -F "src=10.1.1.1," | sort ], [0], [expout])
+
+dnl Check that flushing conntrack by port 1 flush all ct for port 1 but keeps ct for port 2.
+AT_CHECK([ovs-appctl dpctl/flush-conntrack zone=1 'ct_nw_proto=17,ct_tp_src=1'])
+AT_CHECK([ovs-appctl dpctl/dump-conntrack zone=1 | grep -F "src=10.1.1.1," | sort ], [0], [dnl
+udp,orig=(src=10.1.1.1,dst=10.1.1.2,sport=2,dport=1),reply=(src=10.1.1.2,dst=10.1.1.1,sport=1,dport=2),zone=1
+])
+
+OVS_TRAFFIC_VSWITCHD_STOP
+AT_CLEANUP
+
 AT_BANNER([IGMP])
 
 AT_SETUP([IGMP - flood under normal action])
@@ -8724,21 +8861,29 @@ dnl The flow will encap a nsh header to the TCP syn packet
 dnl eth/ip/tcp --> OVS --> eth/nsh/eth/ip/tcp
 AT_CHECK([ovs-ofctl -Oopenflow13 add-flow br0 "table=0,priority=100,in_port=ovs-p0,ip,actions=encap(nsh(md_type=1)),set_field:0x1234->nsh_spi,set_field:0x11223344->nsh_c1,encap(ethernet),set_field:f2:ff:00:00:00:02->dl_dst,set_field:f2:ff:00:00:00:01->dl_src,ovs-p1"])
 
-NETNS_DAEMONIZE([at_ns1], [tcpdump -l -n -xx -U -i p1 > p1.pcap], [tcpdump.pid])
-sleep 1
+NETNS_DAEMONIZE([at_ns1],
+  [tcpdump -l -n -xx -U -i p1 -w p1.pcap 2>tcpdump_err], [tcpdump.pid])
+OVS_WAIT_UNTIL([grep "listening" tcpdump_err])
 
-dnl The hex dump is a TCP syn packet. pkt=eth/ip/tcp
-dnl The packet is sent from p0(at_ns0) interface directed to
-dnl p1(at_ns1) interface
-NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 f2 00 00 00 00 02 f2 00 00 00 00 01 08 00 45 00 00 28 00 01 00 00 40 06 b0 13 c0 a8 00 0a 0a 00 00 0a 04 00 08 00 00 00 00 c8 00 00 00 00 50 02 20 00 b8 5e 00 00 > /dev/null])
+m4_define([TCP_SYN_PKT], [m4_join([,],
+  [eth_src=f2:00:00:00:00:01,eth_dst=f2:00:00:00:00:02,eth_type=0x0800],
+  [nw_src=192.168.0.10,nw_dst=10.0.0.10],
+  [nw_proto=6,nw_ttl=64,nw_frag=no],
+  [tcp_src=1024,tcp_dst=2048,tcp_flags=syn])])
+
+dnl Send the TCP SYN packet from p0(at_ns0) interface directed to
+dnl p1(at_ns1) interface.
+NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 \
+    $(ovs-ofctl compose-packet --bare 'TCP_SYN_PKT')], [0], [ignore])
+
+m4_define([NSH_HEADER], [m4_join([,],
+  [eth_src=f2:ff:00:00:00:01,eth_dst=f2:ff:00:00:00:02,eth_type=0x894f],
+  [nsh_ttl=63,nsh_np=3,nsh_spi=0x1234,nsh_si=255],
+  [nsh_mdtype=1,nsh_c1=0x11223344])])
 
-dnl Check the expected nsh encapsulated packet on the egress interface
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0000: *f2ff *0000 *0002 *f2ff *0000 *0001 *894f *0fc6" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0010: *0103 *0012 *34ff *1122 *3344 *0000 *0000 *0000" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0020: *0000 *0000 *0000 *f200 *0000 *0002 *f200 *0000" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0030: *0001 *0800 *4500 *0028 *0001 *0000 *4006 *b013" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0040: *c0a8 *000a *0a00 *000a *0400 *0800 *0000 *00c8" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0050: *0000 *0000 *5002 *2000 *b85e *0000" 2>&1 1>/dev/null])
+OVS_WAIT_UNTIL([ovs-pcap p1.pcap | grep -q "m4_join([], [^],
+    $(ovs-ofctl compose-packet --bare 'NSH_HEADER'),
+    $(ovs-ofctl compose-packet --bare 'TCP_SYN_PKT'), [\$])"])
 
 OVS_TRAFFIC_VSWITCHD_STOP
 AT_CLEANUP
@@ -8756,19 +8901,31 @@ dnl The flow will decap a nsh header which in turn carries a TCP syn packet
 dnl eth/nsh/eth/ip/tcp --> OVS --> eth/ip/tcp
 AT_CHECK([ovs-ofctl -Oopenflow13 add-flow br0 "table=0,priority=100,in_port=ovs-p0,dl_type=0x894f, actions=decap(),decap(), ovs-p1"])
 
-NETNS_DAEMONIZE([at_ns1], [tcpdump -l -n -xx -U -i p1 > p1.pcap], [tcpdump.pid])
-sleep 1
+NETNS_DAEMONIZE([at_ns1],
+  [tcpdump -l -n -xx -U -i p1 -w p1.pcap 2>tcpdump_err], [tcpdump.pid])
+OVS_WAIT_UNTIL([grep "listening" tcpdump_err])
 
-dnl The hex dump is NSH packet with TCP syn payload. pkt=eth/nsh/eth/ip/tcp
-dnl The packet is sent from p0(at_ns0) interface directed to
-dnl p1(at_ns1) interface
-NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 f2 ff 00 00 00 02 f2 ff 00 00 00 01 89 4f 02 06 01 03 00 00 64 03 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 f2 00 00 00 00 02 f2 00 00 00 00 01 08 00 45 00 00 28 00 01 00 00 40 06 b0 13 c0 a8 00 0a 0a 00 00 0a 04 00 08 00 00 00 00 c8 00 00 00 00 50 02 20 00 b8 5e 00 00 > /dev/null])
+m4_define([TCP_SYN_PKT], [m4_join([,],
+  [eth_src=f2:00:00:00:00:01,eth_dst=f2:00:00:00:00:02,eth_type=0x0800],
+  [nw_src=192.168.0.10,nw_dst=10.0.0.10],
+  [nw_proto=6,nw_ttl=64,nw_frag=no],
+  [tcp_src=1024,tcp_dst=2048,tcp_flags=syn])])
+
+m4_define([NSH_HEADER], [m4_join([,],
+  [eth_src=f2:ff:00:00:00:01,eth_dst=f2:ff:00:00:00:02,eth_type=0x894f],
+  [nsh_ttl=63,nsh_np=3,nsh_spi=0x1234,nsh_si=255],
+  [nsh_mdtype=1,nsh_c1=0x11223344])])
+
+dnl Send the NSH packet with TCP SYN payload from p0(at_ns0) interface directed
+dnl to p1(at_ns1) interface.
+NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 \
+    "$(ovs-ofctl compose-packet --bare 'NSH_HEADER')" \
+    "$(ovs-ofctl compose-packet --bare 'TCP_SYN_PKT')"],
+  [0], [ignore])
 
 dnl Check the expected de-capsulated TCP packet on the egress interface
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0000: *f200 *0000 *0002 *f200 *0000 *0001 *0800 *4500" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0010: *0028 *0001 *0000 *4006 *b013 *c0a8 *000a *0a00" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0020: *000a *0400 *0800 *0000 *00c8 *0000 *0000 *5002" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0030: *2000 *b85e *0000" 2>&1 1>/dev/null])
+OVS_WAIT_UNTIL([ovs-pcap p1.pcap | grep -q \
+    "^$(ovs-ofctl compose-packet --bare 'TCP_SYN_PKT')\$"])
 
 OVS_TRAFFIC_VSWITCHD_STOP
 AT_CLEANUP
@@ -8788,22 +8945,38 @@ dnl The flow will add another NSH header with nsh_spi=0x101, nsh_si=4,
 dnl nsh_ttl=7 and change the md1 context
 AT_CHECK([ovs-ofctl -Oopenflow13 add-flow br0 "table=0,priority=100,in_port=ovs-p0,dl_type=0x894f,nsh_spi=0x100,nsh_si=0x03,actions=decap(),decap(),encap(nsh(md_type=1)),set_field:0x07->nsh_ttl,set_field:0x0101->nsh_spi,set_field:0x04->nsh_si,set_field:0x100f0e0d->nsh_c1,set_field:0x0c0b0a09->nsh_c2,set_field:0x08070605->nsh_c3,set_field:0x04030201->nsh_c4,encap(ethernet),set_field:f2:ff:00:00:00:02->dl_dst,set_field:f2:ff:00:00:00:01->dl_src,ovs-p1"])
 
-NETNS_DAEMONIZE([at_ns1], [tcpdump -l -n -xx -U -i p1 > p1.pcap], [tcpdump.pid])
-sleep 1
+NETNS_DAEMONIZE([at_ns1],
+  [tcpdump -l -n -xx -U -i p1 -w p1.pcap 2>tcpdump_err], [tcpdump.pid])
+OVS_WAIT_UNTIL([grep "listening" tcpdump_err])
 
-dnl The hex dump is NSH packet with TCP syn payload. pkt=eth/nsh/eth/ip/tcp
-dnl The nsh_ttl is 8, nsh_spi is 0x100 and nsh_si is 3
-dnl The packet is sent from p0(at_ns0) interface directed to
-dnl p1(at_ns1) interface
-NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 f2 ff 00 00 00 02 f2 ff 00 00 00 01 89 4f 02 06 01 03 00 01 00 03 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 f2 00 00 00 00 02 f2 00 00 00 00 01 08 00 45 00 00 28 00 01 00 00 40 06 b0 13 c0 a8 00 0a 0a 00 00 0a 04 00 08 00 00 00 00 c8 00 00 00 00 50 02 20 00 b8 5e 00 00 > /dev/null])
+m4_define([TCP_SYN_PKT], [m4_join([,],
+  [eth_src=f2:00:00:00:00:01,eth_dst=f2:00:00:00:00:02,eth_type=0x0800],
+  [nw_src=192.168.0.10,nw_dst=10.0.0.10],
+  [nw_proto=6,nw_ttl=64,nw_frag=no],
+  [tcp_src=1024,tcp_dst=2048,tcp_flags=syn])])
+
+m4_define([NSH_HEADER_1], [m4_join([,],
+  [eth_src=f2:ff:00:00:00:01,eth_dst=f2:ff:00:00:00:02,eth_type=0x894f],
+  [nsh_ttl=8,nsh_np=3,nsh_spi=0x100,nsh_si=3,nsh_mdtype=1],
+  [nsh_c1=0x01020304,nsh_c2=0x05060708,nsh_c3=0x090a0b0c,nsh_c4=0x0d0e0f10])])
 
-dnl Check the expected NSH packet with new fields in the header
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0000: *f2ff *0000 *0002 *f2ff *0000* 0001 *894f *01c6" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0010: *0103 *0001 *0104 *100f *0e0d *0c0b *0a09 *0807" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0020: *0605 *0403 *0201 *f200 *0000 *0002 *f200 *0000" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0030: *0001 *0800 *4500 *0028 *0001 *0000 *4006 *b013" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0040: *c0a8 *000a *0a00 *000a *0400 *0800 *0000 *00c8" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0050: *0000 *0000 *5002 *2000 *b85e *0000" 2>&1 1>/dev/null])
+dnl Send the NSH packet with TCP SYN payload from p0(at_ns0) interface directed
+dnl to p1(at_ns1) interface.
+dnl The nsh_ttl is 8, nsh_spi is 0x100 and nsh_si is 3.
+NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 \
+    "$(ovs-ofctl compose-packet --bare 'NSH_HEADER_1')" \
+    "$(ovs-ofctl compose-packet --bare 'TCP_SYN_PKT')"],
+  [0], [ignore])
+
+m4_define([NSH_HEADER_2], [m4_join([,],
+  [eth_src=f2:ff:00:00:00:01,eth_dst=f2:ff:00:00:00:02,eth_type=0x894f],
+  [nsh_ttl=7,nsh_np=3,nsh_spi=0x101,nsh_si=4,nsh_mdtype=1],
+  [nsh_c1=0x100f0e0d,nsh_c2=0x0c0b0a09,nsh_c3=0x08070605,nsh_c4=0x04030201])])
+
+dnl Check the expected NSH packet with new fields in the header.
+OVS_WAIT_UNTIL([ovs-pcap p1.pcap | grep -q "m4_join([], [^],
+    $(ovs-ofctl compose-packet --bare 'NSH_HEADER_2'),
+    $(ovs-ofctl compose-packet --bare 'TCP_SYN_PKT'), [\$])"])
 
 OVS_TRAFFIC_VSWITCHD_STOP
 AT_CLEANUP
@@ -8824,31 +8997,50 @@ dnl packet to to at_ns2.
 AT_CHECK([ovs-ofctl -Oopenflow13 add-flow br0 "table=0,priority=100,dl_type=0x894f,nsh_spi=0x100,nsh_si=0x02,actions=ovs-p1"])
 AT_CHECK([ovs-ofctl -Oopenflow13 add-flow br0 "table=0,priority=100,dl_type=0x894f,nsh_spi=0x100,nsh_si=0x01,actions=ovs-p2"])
 
-NETNS_DAEMONIZE([at_ns1], [tcpdump -l -n -xx -U -i p1 > p1.pcap], [tcpdump.pid])
-NETNS_DAEMONIZE([at_ns2], [tcpdump -l -n -xx -U -i p2 > p2.pcap], [tcpdump2.pid])
-sleep 1
-
-dnl First send packet from at_ns0 --> OVS with SPI=0x100 and SI=2
-NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 f2 ff 00 00 00 02 f2 ff 00 00 00 01 89 4f 02 06 01 03 00 01 00 02 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 f2 00 00 00 00 02 f2 00 00 00 00 01 08 00 45 00 00 28 00 01 00 00 40 06 b0 13 c0 a8 00 0a 0a 00 00 0a 04 00 08 00 00 00 00 c8 00 00 00 00 50 02 20 00 b8 5e 00 00 > /dev/null])
-
-dnl Check for the above packet on p1 interface
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0000: *f2ff *0000 *0002 *f2ff *0000 *0001 *894f *0206" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0010: *0103 *0001 *0002 *0102 *0304 *0506 *0708 *090a" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0020: *0b0c *0d0e *0f10 *f200 *0000 *0002 *f200 *0000" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0030: *0001 *0800 *4500 *0028 *0001 *0000 *4006 *b013" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0040: *c0a8 *000a *0a00 *000a *0400 *0800 *0000 *00c8" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p1.pcap | grep -E "0x0050: *0000 *0000 *5002 *2000 *b85e *0000" 2>&1 1>/dev/null])
-
-dnl Send the second packet from at_ns1 --> OVS with SPI=0x100 and SI=1
-NS_CHECK_EXEC([at_ns1], [$PYTHON3 $srcdir/sendpkt.py p1 f2 ff 00 00 00 02 f2 ff 00 00 00 01 89 4f 01 c6 01 03 00 01 00 01 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 f2 00 00 00 00 02 f2 00 00 00 00 01 08 00 45 00 00 28 00 01 00 00 40 06 b0 13 c0 a8 00 0a 0a 00 00 0a 04 00 08 00 00 00 00 c8 00 00 00 00 50 02 20 00 b8 5e 00 00 > /dev/null])
-
-dnl Check for the above packet on p2 interface
-OVS_WAIT_UNTIL([cat p2.pcap | grep -E "0x0000: *f2ff *0000 *0002 *f2ff *0000 *0001 *894f *01c6" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p2.pcap | grep -E "0x0010: *0103 *0001 *0001 *0102 *0304 *0506 *0708 *090a" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p2.pcap | grep -E "0x0020: *0b0c *0d0e *0f10 *f200 *0000 *0002 *f200 *0000" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p2.pcap | grep -E "0x0030: *0001 *0800 *4500 *0028 *0001 *0000 *4006 *b013" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p2.pcap | grep -E "0x0040: *c0a8 *000a *0a00 *000a *0400 *0800 *0000 *00c8" 2>&1 1>/dev/null])
-OVS_WAIT_UNTIL([cat p2.pcap | grep -E "0x0050: *0000 *0000 *5002 *2000 *b85e *0000" 2>&1 1>/dev/null])
+NETNS_DAEMONIZE([at_ns1],
+  [tcpdump -l -n -xx -U -i p1 -w p1.pcap 2>tcpdump_err], [tcpdump.pid])
+OVS_WAIT_UNTIL([grep "listening" tcpdump_err])
+NETNS_DAEMONIZE([at_ns2],
+  [tcpdump -l -n -xx -U -i p2 -w p2.pcap 2>tcpdump2_err], [tcpdump2.pid])
+OVS_WAIT_UNTIL([grep "listening" tcpdump2_err])
+
+m4_define([TCP_SYN_PKT], [m4_join([,],
+  [eth_src=f2:00:00:00:00:01,eth_dst=f2:00:00:00:00:02,eth_type=0x0800],
+  [nw_src=192.168.0.10,nw_dst=10.0.0.10],
+  [nw_proto=6,nw_ttl=64,nw_frag=no],
+  [tcp_src=1024,tcp_dst=2048,tcp_flags=syn])])
+
+dnl First send packet from at_ns0 --> OVS with SPI=0x100 and SI=2.
+m4_define([NSH_HEADER_1], [m4_join([,],
+  [eth_src=f2:ff:00:00:00:01,eth_dst=f2:ff:00:00:00:02,eth_type=0x894f],
+  [nsh_ttl=8,nsh_np=3,nsh_spi=0x100,nsh_si=2,nsh_mdtype=1],
+  [nsh_c1=0x01020304,nsh_c2=0x05060708,nsh_c3=0x090a0b0c,nsh_c4=0x0d0e0f10])])
+
+NS_CHECK_EXEC([at_ns0], [$PYTHON3 $srcdir/sendpkt.py p0 \
+    "$(ovs-ofctl compose-packet --bare 'NSH_HEADER_1')" \
+    "$(ovs-ofctl compose-packet --bare 'TCP_SYN_PKT')"],
+  [0], [ignore])
+
+dnl Check for the above packet on p1 interface.
+OVS_WAIT_UNTIL([ovs-pcap p1.pcap | grep -q "m4_join([], [^],
+    $(ovs-ofctl compose-packet --bare 'NSH_HEADER_1'),
+    $(ovs-ofctl compose-packet --bare 'TCP_SYN_PKT'), [\$])"])
+
+dnl Send the second packet from at_ns1 --> OVS with SPI=0x100 and SI=1.
+m4_define([NSH_HEADER_2], [m4_join([,],
+  [eth_src=f2:ff:00:00:00:01,eth_dst=f2:ff:00:00:00:02,eth_type=0x894f],
+  [nsh_ttl=8,nsh_np=3,nsh_spi=0x100,nsh_si=1,nsh_mdtype=1],
+  [nsh_c1=0x01020304,nsh_c2=0x05060708,nsh_c3=0x090a0b0c,nsh_c4=0x0d0e0f10])])
+
+NS_CHECK_EXEC([at_ns1], [$PYTHON3 $srcdir/sendpkt.py p1 \
+    "$(ovs-ofctl compose-packet --bare 'NSH_HEADER_2')" \
+    "$(ovs-ofctl compose-packet --bare 'TCP_SYN_PKT')"],
+  [0], [ignore])
+
+dnl Check for the above packet on p2 interface.
+OVS_WAIT_UNTIL([ovs-pcap p2.pcap | grep -q "m4_join([], [^],
+    $(ovs-ofctl compose-packet --bare 'NSH_HEADER_2'),
+    $(ovs-ofctl compose-packet --bare 'TCP_SYN_PKT'), [\$])"])
 
 OVS_TRAFFIC_VSWITCHD_STOP
 AT_CLEANUP
diff --git a/tests/test-ovsdb.c b/tests/test-ovsdb.c
index c4ab899d45..41c1525f45 100644
--- a/tests/test-ovsdb.c
+++ b/tests/test-ovsdb.c
@@ -2023,6 +2023,24 @@ print_idl_row_updated_link2(const struct idltest_link2 *l2, int step)
     }
 }
 
+static void
+print_idl_row_updated_indexed(const struct idltest_indexed *ind, int step)
+{
+    struct ds updates = DS_EMPTY_INITIALIZER;
+
+    for (size_t i = 0; i < IDLTEST_INDEXED_N_COLUMNS; i++) {
+        if (idltest_indexed_is_updated(ind, i)) {
+            ds_put_format(&updates, " %s", idltest_indexed_columns[i].name);
+        }
+    }
+    if (updates.length) {
+        print_and_log("%03d: table %s: updated columns:%s",
+                      step, ind->header_.table->class_->name,
+                      ds_cstr(&updates));
+        ds_destroy(&updates);
+    }
+}
+
 static void
 print_idl_row_updated_simple3(const struct idltest_simple3 *s3, int step)
 {
@@ -2172,6 +2190,21 @@ print_idl_row_link2(const struct idltest_link2 *l2, int step, bool terse)
     print_idl_row_updated_link2(l2, step);
 }
 
+static void
+print_idl_row_indexed(const struct idltest_indexed *ind, int step, bool terse)
+{
+    struct ds msg = DS_EMPTY_INITIALIZER;
+
+    ds_put_format(&msg, "i=%"PRId64, ind->i);
+
+    char *row_msg = format_idl_row(&ind->header_, step, ds_cstr(&msg), terse);
+    print_and_log("%s", row_msg);
+    ds_destroy(&msg);
+    free(row_msg);
+
+    print_idl_row_updated_indexed(ind, step);
+}
+
 static void
 print_idl_row_simple3(const struct idltest_simple3 *s3, int step, bool terse)
 {
@@ -2252,6 +2285,7 @@ print_idl_row_singleton(const struct idltest_singleton *sng, int step,
 static void
 print_idl(struct ovsdb_idl *idl, int step, bool terse)
 {
+    const struct idltest_indexed *ind;
     const struct idltest_simple3 *s3;
     const struct idltest_simple4 *s4;
     const struct idltest_simple6 *s6;
@@ -2285,6 +2319,10 @@ print_idl(struct ovsdb_idl *idl, int step, bool terse)
         print_idl_row_simple6(s6, step, terse);
         n++;
     }
+    IDLTEST_INDEXED_FOR_EACH (ind, idl) {
+        print_idl_row_indexed(ind, step, terse);
+        n++;
+    }
     IDLTEST_SINGLETON_FOR_EACH (sng, idl) {
         print_idl_row_singleton(sng, step, terse);
         n++;
@@ -2297,6 +2335,7 @@ print_idl(struct ovsdb_idl *idl, int step, bool terse)
 static void
 print_idl_track(struct ovsdb_idl *idl, int step, bool terse)
 {
+    const struct idltest_indexed *ind;
     const struct idltest_simple3 *s3;
     const struct idltest_simple4 *s4;
     const struct idltest_simple6 *s6;
@@ -2329,6 +2368,10 @@ print_idl_track(struct ovsdb_idl *idl, int step, bool terse)
         print_idl_row_simple6(s6, step, terse);
         n++;
     }
+    IDLTEST_INDEXED_FOR_EACH (ind, idl) {
+        print_idl_row_indexed(ind, step, terse);
+        n++;
+    }
 
     if (!n) {
         print_and_log("%03d: empty", step);
diff --git a/tests/test-ovsdb.py b/tests/test-ovsdb.py
index 48f8ee2d70..67a45f044b 100644
--- a/tests/test-ovsdb.py
+++ b/tests/test-ovsdb.py
@@ -228,6 +228,10 @@ def get_link2_table_printable_row(row):
     return s
 
 
+def get_indexed_table_printable_row(row):
+    return "i=%s" % row.i
+
+
 def get_singleton_table_printable_row(row):
     return "name=%s" % row.name
 
@@ -307,6 +311,14 @@ def print_idl(idl, step, terse=False):
                       terse)
             n += 1
 
+    if "indexed" in idl.tables:
+        ind = idl.tables["indexed"].rows
+        for row in ind.values():
+            print_row("indexed", row, step,
+                      get_indexed_table_printable_row(row),
+                      terse)
+            n += 1
+
     if "singleton" in idl.tables:
         sng = idl.tables["singleton"].rows
         for row in sng.values():
@@ -434,7 +446,7 @@ def idl_set(idl, commands, step):
                 sys.stderr.write('"set" command requires 2 argument\n')
                 sys.exit(1)
 
-            s = txn.insert(idl.tables["simple"], new_uuid=args[0],
+            s = txn.insert(idl.tables["simple"], new_uuid=uuid.UUID(args[0]),
                            persist_uuid=True)
             s.i = int(args[1])
         elif name == "delete":
@@ -690,6 +702,9 @@ def do_idl(schema_file, remote, *commands):
     idl = ovs.db.idl.Idl(remote, schema_helper, leader_only=False)
     if "simple3" in idl.tables:
         idl.index_create("simple3", "simple3_by_name")
+    if "indexed" in idl.tables:
+        idx = idl.index_create("indexed", "indexed_by_i")
+        idx.add_column("i")
 
     if commands:
         remotes = remote.split(',')
diff --git a/tests/test-util.c b/tests/test-util.c
index 7d899fbbfd..5d88d38f26 100644
--- a/tests/test-util.c
+++ b/tests/test-util.c
@@ -1116,12 +1116,16 @@ test_snprintf(struct ovs_cmdl_context *ctx OVS_UNUSED)
 {
     char s[16];
 
+    /* GCC 7+ and Clang 18+ warn about the following calls that truncate
+     * a string using snprintf().  We're testing that truncation works
+     * properly, so temporarily disable the warning. */
 #if __GNUC__ >= 7
-    /* GCC 7+ warns about the following calls that truncate a string using
-     * snprintf().  We're testing that truncation works properly, so
-     * temporarily disable the warning. */
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wformat-truncation"
+#endif
+#if __clang_major__ >= 18
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wformat-truncation"
 #endif
     ovs_assert(snprintf(s, 4, "abcde") == 5);
     ovs_assert(!strcmp(s, "abc"));
@@ -1130,6 +1134,9 @@ test_snprintf(struct ovs_cmdl_context *ctx OVS_UNUSED)
     ovs_assert(!strcmp(s, "abcd"));
 #if __GNUC__ >= 7
 #pragma GCC diagnostic pop
+#endif
+#if __clang_major__ >= 18
+#pragma clang diagnostic pop
 #endif
 
     ovs_assert(snprintf(s, 6, "abcde") == 5);
diff --git a/tests/tunnel-push-pop-ipv6.at b/tests/tunnel-push-pop-ipv6.at
index a8dd28c5b5..f1c5d42f66 100644
--- a/tests/tunnel-push-pop-ipv6.at
+++ b/tests/tunnel-push-pop-ipv6.at
@@ -19,11 +19,12 @@ AT_CHECK([ovs-vsctl add-port int-br3 t3 -- set Interface t3 type=srv6 \
                        options:srv6_flowlabel=compute \
                        ], [0])
 
-dnl First setup dummy interface IP address, then add the route
-dnl so that tnl-port table can get valid IP address for the device.
+dnl Setup dummy interface IP address.
 AT_CHECK([ovs-appctl netdev-dummy/ip6addr br0 2001:cafe::88/24], [0], [OK
 ])
-AT_CHECK([ovs-appctl ovs/route/add 2001:cafe::0/24 br0], [0], [OK
+dnl Checking that a local routes for added IPs were successfully installed.
+AT_CHECK([ovs-appctl ovs/route/show | grep Cached], [0], [dnl
+Cached: 2001:ca00::/24 dev br0 SRC 2001:cafe::88 local
 ])
 AT_CHECK([ovs-appctl tnl/neigh/set br0 2001:cafe::91 aa:55:aa:55:00:01], [0], [OK
 ])
@@ -105,13 +106,15 @@ dummy@ovs-dummy: hit:0 missed:0
     t2 2/6: (ip6gre: remote_ip=2001:cafe::92)
 ])
 
-dnl First setup dummy interface IP address, then add the route
-dnl so that tnl-port table can get valid IP address for the device.
+dnl Setup dummy interface IP addresses.
 AT_CHECK([ovs-appctl netdev-dummy/ip6addr br0 2001:cafe::88/24], [0], [OK
 ])
 AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/24], [0], [OK
 ])
-AT_CHECK([ovs-appctl ovs/route/add 2001:cafe::92/24 br0], [0], [OK
+dnl Checking that a local routes for added IPs were successfully installed.
+AT_CHECK([ovs-appctl ovs/route/show | grep Cached | sort], [0], [dnl
+Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
+Cached: 2001:ca00::/24 dev br0 SRC 2001:cafe::88 local
 ])
 
 AT_CHECK([ovs-ofctl add-flow br0 action=normal])
@@ -179,13 +182,15 @@ dummy@ovs-dummy: hit:0 missed:0
     t3 3/6: (ip6erspan: erspan_dir=1, erspan_hwid=0x7, erspan_ver=2, key=567, remote_ip=2001:cafe::93)
 ])
 
-dnl First setup dummy interface IP address, then add the route
-dnl so that tnl-port table can get valid IP address for the device.
+dnl Setup dummy interface IP addresses.
 AT_CHECK([ovs-appctl netdev-dummy/ip6addr br0 2001:cafe::88/24], [0], [OK
 ])
 AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/24], [0], [OK
 ])
-AT_CHECK([ovs-appctl ovs/route/add 2001:cafe::92/24 br0], [0], [OK
+dnl Checking that a local routes for added IPs were successfully installed.
+AT_CHECK([ovs-appctl ovs/route/show | grep Cached | sort], [0], [dnl
+Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
+Cached: 2001:ca00::/24 dev br0 SRC 2001:cafe::88 local
 ])
 
 AT_CHECK([ovs-ofctl add-flow br0 action=normal])
@@ -316,14 +321,15 @@ srv6_sys (6) ref_cnt=1
 vxlan_sys_4789 (4789) ref_cnt=2
 ])
 
-
-dnl First setup dummy interface IP address, then add the route
-dnl so that tnl-port table can get valid IP address for the device.
+dnl Setup dummy interface IP addresses.
 AT_CHECK([ovs-appctl netdev-dummy/ip6addr br0 2001:cafe::88/24], [0], [OK
 ])
 AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/24], [0], [OK
 ])
-AT_CHECK([ovs-appctl ovs/route/add 2001:cafe::92/24 br0], [0], [OK
+dnl Checking that a local routes for added IPs were successfully installed.
+AT_CHECK([ovs-appctl ovs/route/show | grep Cached | sort], [0], [dnl
+Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
+Cached: 2001:ca00::/24 dev br0 SRC 2001:cafe::88 local
 ])
 
 AT_CHECK([ovs-ofctl add-flow br0 action=normal])
@@ -636,3 +642,177 @@ Listening ports:
 
 OVS_VSWITCHD_STOP
 AT_CLEANUP
+
+AT_SETUP([tunnel_push_pop_ipv6 - local_ip configuration])
+
+OVS_VSWITCHD_START(
+    [add-port br0 p0 \
+     -- set Interface p0 type=dummy ofport_request=1 \
+                         other-config:hwaddr=aa:55:aa:55:00:00])
+AT_CHECK([ovs-appctl vlog/set dpif_netdev:dbg])
+AT_CHECK([ovs-vsctl add-br int-br -- set bridge int-br datapath_type=dummy])
+AT_CHECK([ovs-vsctl add-port int-br t2 \
+          -- set Interface t2 type=geneve \
+                              options:local_ip=2001:beef::88 \
+                              options:remote_ip=2001:cafe::92 \
+                              options:key=123 ofport_request=2])
+
+dnl Setup multiple IP addresses.
+AT_CHECK([ovs-appctl netdev-dummy/ip6addr br0 2001:cafe::88/64], [0], [OK
+])
+AT_CHECK([ovs-appctl netdev-dummy/ip6addr br0 2001:beef::88/64], [0], [OK
+])
+dnl Checking that a local route for added IP was successfully installed.
+AT_CHECK([ovs-appctl ovs/route/show | grep Cached | sort], [0], [dnl
+Cached: 2001:beef::/64 dev br0 SRC 2001:beef::88 local
+Cached: 2001:cafe::/64 dev br0 SRC 2001:cafe::88 local
+])
+AT_CHECK([ovs-ofctl add-flow br0 action=normal])
+AT_CHECK([ovs-ofctl add-flow int-br action=normal])
+
+dnl This Neighbor Advertisement from p0 has two effects:
+dnl 1. The neighbor cache will learn that 2001:cafe::92 is at f8:bc:12:44:34:b6.
+dnl 2. The br0 mac learning will learn that f8:bc:12:44:34:b6 is on p0.
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 dnl
+ 'recirc_id(0),in_port(1),dnl
+  eth(src=f8:bc:12:44:34:b6,dst=aa:55:aa:55:00:00),eth_type(0x86dd),dnl
+  ipv6(src=2001:cafe::92,dst=2001:cafe::88,label=0,proto=58,tclass=0,hlimit=255,frag=no),dnl
+  icmpv6(type=136,code=0),dnl
+  nd(target=2001:cafe::92,sll=00:00:00:00:00:00,tll=f8:bc:12:44:34:b6)'
+])
+
+dnl Check that local_ip is used for encapsulation in the trace.
+AT_CHECK([ovs-appctl ofproto/trace int-br in_port=LOCAL \
+                | grep -E 'tunnel|actions'], [0], [dnl
+     -> output to native tunnel
+     -> tunneling to 2001:cafe::92 via br0
+     -> tunneling from aa:55:aa:55:00:00 2001:beef::88 to f8:bc:12:44:34:b6 2001:cafe::92
+Datapath actions: tnl_push(tnl_port(6081),header(size=70,type=5,dnl
+eth(dst=f8:bc:12:44:34:b6,src=aa:55:aa:55:00:00,dl_type=0x86dd),dnl
+ipv6(src=2001:beef::88,dst=2001:cafe::92,label=0,proto=17,tclass=0x0,hlimit=64),dnl
+udp(src=0,dst=6081,csum=0xffff),geneve(vni=0x7b)),out_port(100)),1
+])
+
+dnl Now check that the packet actually has the local_ip in the header.
+AT_CHECK([ovs-vsctl -- set Interface p0 options:tx_pcap=p0.pcap])
+
+packet=50540000000a5054000000091234
+eth=f8bc124434b6aa55aa55000086dd
+ip6=60000000001e11402001beef0000000000000000000000882001cafe000000000000000000000092
+dnl Source port is based on a packet hash, so it may differ depending on the
+dnl compiler flags and CPU type.  Same for UDP checksum.  Masked with '....'.
+udp=....17c1001e....
+geneve=0000655800007b00
+encap=${eth}${ip6}${udp}${geneve}
+dnl Output to tunnel from a int-br internal port.
+dnl Checking that the packet arrived and it was correctly encapsulated.
+AT_CHECK([ovs-appctl netdev-dummy/receive int-br "${packet}"])
+OVS_WAIT_UNTIL([test $(ovs-pcap p0.pcap | grep -c "${encap}${packet}") -eq 1])
+dnl Sending again to exercise the non-miss upcall path.
+AT_CHECK([ovs-appctl netdev-dummy/receive int-br "${packet}"])
+OVS_WAIT_UNTIL([test $(ovs-pcap p0.pcap | grep -c "${encap}${packet}") -eq 2])
+
+dnl Finally, checking that the datapath flow also has a local_ip.
+AT_CHECK([ovs-appctl dpctl/dump-flows | grep tnl_push \
+            | strip_ufid | strip_used], [0], [dnl
+recirc_id(0),in_port(2),packet_type(ns=0,id=0),dnl
+eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x1234), dnl
+packets:1, bytes:14, used:0.0s, dnl
+actions:tnl_push(tnl_port(6081),header(size=70,type=5,dnl
+eth(dst=f8:bc:12:44:34:b6,src=aa:55:aa:55:00:00,dl_type=0x86dd),dnl
+ipv6(src=2001:beef::88,dst=2001:cafe::92,label=0,proto=17,tclass=0x0,hlimit=64),dnl
+udp(src=0,dst=6081,csum=0xffff),geneve(vni=0x7b)),out_port(100)),1
+])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+dnl This is a regression test for outer header checksum offloading
+dnl with recirculation.
+AT_SETUP([tunnel_push_pop_ipv6 - recirculation after encapsulation])
+
+OVS_VSWITCHD_START(
+    [add-port br0 p0 \
+     -- set Interface p0 type=dummy ofport_request=1 \
+                         other-config:hwaddr=aa:55:aa:55:00:00])
+AT_CHECK([ovs-appctl vlog/set dpif_netdev:dbg])
+AT_CHECK([ovs-vsctl add-br int-br -- set bridge int-br datapath_type=dummy])
+AT_CHECK([ovs-vsctl add-port int-br t2 \
+          -- set Interface t2 type=geneve \
+                              options:remote_ip=2001:cafe::92 \
+                              options:key=123 ofport_request=2])
+
+dnl Setup an IP address.
+AT_CHECK([ovs-appctl netdev-dummy/ip6addr br0 2001:cafe::88/64], [0], [OK
+])
+dnl Checking that a local route for added IP was successfully installed.
+AT_CHECK([ovs-appctl ovs/route/show | grep Cached | sort], [0], [dnl
+Cached: 2001:cafe::/64 dev br0 SRC 2001:cafe::88 local
+])
+
+dnl Add a dp-hash selection group.
+AT_CHECK([ovs-ofctl add-group br0 \
+    'group_id=1234,type=select,selection_method=dp_hash,bucket=weight=1,output:p0'])
+AT_CHECK([ovs-ofctl add-flow br0 in_port=br0,action=group:1234])
+AT_CHECK([ovs-ofctl add-flow br0 in_port=p0,action=normal])
+
+AT_CHECK([ovs-ofctl add-flow int-br action=normal])
+
+dnl This Neighbor Advertisement from p0 has two effects:
+dnl 1. The neighbor cache will learn that 2001:cafe::92 is at f8:bc:12:44:34:b6.
+dnl 2. The br0 mac learning will learn that f8:bc:12:44:34:b6 is on p0.
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 dnl
+ 'recirc_id(0),in_port(1),dnl
+  eth(src=f8:bc:12:44:34:b6,dst=aa:55:aa:55:00:00),eth_type(0x86dd),dnl
+  ipv6(src=2001:cafe::92,dst=2001:cafe::88,label=0,proto=58,tclass=0,hlimit=255,frag=no),dnl
+  icmpv6(type=136,code=0),dnl
+  nd(target=2001:cafe::92,sll=00:00:00:00:00:00,tll=f8:bc:12:44:34:b6)'
+])
+
+dnl Check that selection group is used in the trace.
+AT_CHECK([ovs-appctl ofproto/trace int-br in_port=LOCAL \
+                | grep -E 'tunnel|actions'], [0], [dnl
+     -> output to native tunnel
+     -> tunneling to 2001:cafe::92 via br0
+     -> tunneling from aa:55:aa:55:00:00 2001:cafe::88 to f8:bc:12:44:34:b6 2001:cafe::92
+Datapath actions: tnl_push(tnl_port(6081),header(size=70,type=5,dnl
+eth(dst=f8:bc:12:44:34:b6,src=aa:55:aa:55:00:00,dl_type=0x86dd),dnl
+ipv6(src=2001:cafe::88,dst=2001:cafe::92,label=0,proto=17,tclass=0x0,hlimit=64),dnl
+udp(src=0,dst=6081,csum=0xffff),geneve(vni=0x7b)),out_port(100)),dnl
+hash(l4(0)),recirc(0x1)
+])
+
+dnl Now check that the packet is actually encapsulated and delivered.
+AT_CHECK([ovs-vsctl -- set Interface p0 options:tx_pcap=p0.pcap])
+
+packet=50540000000a5054000000091234
+eth=f8bc124434b6aa55aa55000086dd
+ip6=60000000001e11402001cafe0000000000000000000000882001cafe000000000000000000000092
+dnl Source port is based on a packet hash, so it may differ depending on the
+dnl compiler flags and CPU type.  Same for UDP checksum.  Masked with '....'.
+udp=....17c1001e....
+geneve=0000655800007b00
+encap=${eth}${ip6}${udp}${geneve}
+dnl Output to tunnel from a int-br internal port.
+dnl Checking that the packet arrived and it was correctly encapsulated.
+AT_CHECK([ovs-appctl netdev-dummy/receive int-br "${packet}"])
+OVS_WAIT_UNTIL([test $(ovs-pcap p0.pcap | grep -c "${encap}${packet}") -eq 1])
+dnl Sending again to exercise the non-miss upcall path.
+AT_CHECK([ovs-appctl netdev-dummy/receive int-br "${packet}"])
+OVS_WAIT_UNTIL([test $(ovs-pcap p0.pcap | grep -c "${encap}${packet}") -eq 2])
+
+dnl Finally, checking that the datapath flow is also correct.
+AT_CHECK([ovs-appctl dpctl/dump-flows | grep tnl_push \
+            | strip_ufid | strip_used], [0], [dnl
+recirc_id(0),in_port(2),packet_type(ns=0,id=0),dnl
+eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x1234), dnl
+packets:1, bytes:14, used:0.0s, dnl
+actions:tnl_push(tnl_port(6081),header(size=70,type=5,dnl
+eth(dst=f8:bc:12:44:34:b6,src=aa:55:aa:55:00:00,dl_type=0x86dd),dnl
+ipv6(src=2001:cafe::88,dst=2001:cafe::92,label=0,proto=17,tclass=0x0,hlimit=64),dnl
+udp(src=0,dst=6081,csum=0xffff),geneve(vni=0x7b)),out_port(100)),dnl
+hash(l4(0)),recirc(0x2)
+])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
diff --git a/tests/tunnel-push-pop.at b/tests/tunnel-push-pop.at
index b1440f5904..508737c53e 100644
--- a/tests/tunnel-push-pop.at
+++ b/tests/tunnel-push-pop.at
@@ -30,17 +30,15 @@ dummy@ovs-dummy: hit:0 missed:0
     t4 5/3: (erspan: erspan_dir=flow, erspan_hwid=flow, erspan_idx=flow, erspan_ver=flow, key=56, remote_ip=flow)
 ])
 
-dnl First setup dummy interface IP address, then add the route
-dnl so that tnl-port table can get valid IP address for the device.
+dnl Setup dummy interface IP addresses.
 AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/24], [0], [OK
 ])
 AT_CHECK([ovs-appctl netdev-dummy/ip6addr br0 2001:cafe::88/24], [0], [OK
 ])
-
-AT_CHECK([ovs-appctl ovs/route/add 1.1.2.92/24 br0], [0], [OK
-])
-
-AT_CHECK([ovs-appctl ovs/route/add 1.1.2.92/24 br0 pkt_mark=1234], [0], [OK
+dnl Checking that a local routes for added IPs were successfully installed.
+AT_CHECK([ovs-appctl ovs/route/show | grep Cached | sort], [0], [dnl
+Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
+Cached: 2001:ca00::/24 dev br0 SRC 2001:cafe::88 local
 ])
 
 AT_CHECK([ovs-ofctl add-flow br0 action=normal])
@@ -237,18 +235,21 @@ dummy@ovs-dummy: hit:0 missed:0
     t8 9/2152: (gtpu: key=123, remote_ip=1.1.2.92)
 ])
 
-dnl First setup dummy interface IP address, then add the route
-dnl so that tnl-port table can get valid IP address for the device.
+dnl Setup dummy interface IP addresses.
 AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/24], [0], [OK
 ])
 AT_CHECK([ovs-appctl netdev-dummy/ip6addr br0 2001:cafe::88/24], [0], [OK
 ])
-
-AT_CHECK([ovs-appctl ovs/route/add 1.1.2.92/24 br0], [0], [OK
-])
-
+dnl Add a static route with a mark.
 AT_CHECK([ovs-appctl ovs/route/add 1.1.2.92/24 br0 pkt_mark=1234], [0], [OK
 ])
+dnl Checking that local routes for added IPs and the static route with a mark
+dnl were successfully installed.
+AT_CHECK([ovs-appctl ovs/route/show | grep br0 | sort], [0], [dnl
+Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
+Cached: 2001:ca00::/24 dev br0 SRC 2001:cafe::88 local
+User: 1.1.2.0/24 MARK 1234 dev br0 SRC 1.1.2.88
+])
 
 AT_CHECK([ovs-ofctl add-flow br0 action=normal])
 
@@ -690,12 +691,12 @@ AT_CHECK([ovs-vsctl add-port int-br t2 -- set Interface t2 type=geneve \
                        options:remote_ip=1.1.2.92 options:key=123 ofport_request=2 \
                        ])
 
-dnl First setup dummy interface IP address, then add the route
-dnl so that tnl-port table can get valid IP address for the device.
+dnl Setup dummy interface IP address.
 AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/24], [0], [OK
 ])
-
-AT_CHECK([ovs-appctl ovs/route/add 1.1.2.92/24 br0], [0], [OK
+dnl Checking that a local route for added IP was successfully installed.
+AT_CHECK([ovs-appctl ovs/route/show | grep Cached], [0], [dnl
+Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
 ])
 
 AT_CHECK([ovs-ofctl add-flow br0 action=normal])
@@ -731,11 +732,12 @@ AT_CHECK([ovs-vsctl add-port int-br t2 dnl
           -- set Interface t2 type=geneve options:remote_ip=1.1.2.92 dnl
                               options:key=123 ofport_request=2])
 
-dnl First setup dummy interface IP address, then add the route
-dnl so that tnl-port table can get valid IP address for the device.
+dnl Setup dummy interface IP address.
 AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/24], [0], [OK
 ])
-AT_CHECK([ovs-appctl ovs/route/add 1.1.2.92/24 br0], [0], [OK
+dnl Checking that a local route for added IP was successfully installed.
+AT_CHECK([ovs-appctl ovs/route/show | grep Cached], [0], [dnl
+Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
 ])
 AT_CHECK([ovs-ofctl add-flow br0 action=normal])
 
@@ -777,6 +779,88 @@ AT_CHECK([ovs-appctl dpctl/dump-flows | grep -q 'slow_path(action)'], [0])
 OVS_VSWITCHD_STOP
 AT_CLEANUP
 
+AT_SETUP([tunnel_push_pop - local_ip configuration])
+
+OVS_VSWITCHD_START(
+    [add-port br0 p0 \
+     -- set Interface p0 type=dummy ofport_request=1 \
+                         other-config:hwaddr=aa:55:aa:55:00:00])
+AT_CHECK([ovs-appctl vlog/set dpif_netdev:dbg])
+AT_CHECK([ovs-vsctl add-br int-br -- set bridge int-br datapath_type=dummy])
+AT_CHECK([ovs-vsctl add-port int-br t2 \
+          -- set Interface t2 type=geneve \
+                              options:local_ip=2.2.2.88 \
+                              options:remote_ip=1.1.2.92 \
+                              options:key=123 ofport_request=2])
+
+dnl Setup multiple IP addresses.
+AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/24], [0], [OK
+])
+AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 2.2.2.88/24], [0], [OK
+])
+dnl Checking that a local route for added IP was successfully installed.
+AT_CHECK([ovs-appctl ovs/route/show | grep Cached | sort], [0], [dnl
+Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
+Cached: 2.2.2.0/24 dev br0 SRC 2.2.2.88 local
+])
+AT_CHECK([ovs-ofctl add-flow br0 action=normal])
+AT_CHECK([ovs-ofctl add-flow int-br action=normal])
+
+dnl This ARP reply from p0 has two effects:
+dnl 1. The ARP cache will learn that 1.1.2.92 is at f8:bc:12:44:34:b6.
+dnl 2. The br0 mac learning will learn that f8:bc:12:44:34:b6 is on p0.
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 dnl
+ 'recirc_id(0),in_port(1),dnl
+  eth(src=f8:bc:12:44:34:b6,dst=ff:ff:ff:ff:ff:ff),eth_type(0x0806),dnl
+  arp(sip=1.1.2.92,tip=1.1.2.88,op=2,sha=f8:bc:12:44:34:b6,tha=00:00:00:00:00:00)'
+])
+
+dnl Check that local_ip is used for encapsulation in the trace.
+AT_CHECK([ovs-appctl ofproto/trace int-br in_port=LOCAL \
+                | grep -E 'tunnel|actions'], [0], [dnl
+     -> output to native tunnel
+     -> tunneling to 1.1.2.92 via br0
+     -> tunneling from aa:55:aa:55:00:00 2.2.2.88 to f8:bc:12:44:34:b6 1.1.2.92
+Datapath actions: tnl_push(tnl_port(6081),header(size=50,type=5,dnl
+eth(dst=f8:bc:12:44:34:b6,src=aa:55:aa:55:00:00,dl_type=0x0800),dnl
+ipv4(src=2.2.2.88,dst=1.1.2.92,proto=17,tos=0,ttl=64,frag=0x4000),dnl
+udp(src=0,dst=6081,csum=0x0),geneve(vni=0x7b)),out_port(100)),1
+])
+
+dnl Now check that the packet actually has the local_ip in the header.
+AT_CHECK([ovs-vsctl -- set Interface p0 options:tx_pcap=p0.pcap])
+
+packet=50540000000a5054000000091234
+eth=f8bc124434b6aa55aa5500000800
+ip4=450000320000400040113305020202580101025c
+dnl Source port is based on a packet hash, so it may differ depending on the
+dnl compiler flags and CPU type.  Masked with '....'.
+udp=....17c1001e0000
+geneve=0000655800007b00
+encap=${eth}${ip4}${udp}${geneve}
+dnl Output to tunnel from a int-br internal port.
+dnl Checking that the packet arrived and it was correctly encapsulated.
+AT_CHECK([ovs-appctl netdev-dummy/receive int-br "${packet}"])
+OVS_WAIT_UNTIL([test $(ovs-pcap p0.pcap | grep -c "${encap}${packet}") -eq 1])
+dnl Sending again to exercise the non-miss upcall path.
+AT_CHECK([ovs-appctl netdev-dummy/receive int-br "${packet}"])
+OVS_WAIT_UNTIL([test $(ovs-pcap p0.pcap | grep -c "${encap}${packet}") -eq 2])
+
+dnl Finally, checking that the datapath flow also has a local_ip.
+AT_CHECK([ovs-appctl dpctl/dump-flows | grep tnl_push \
+            | strip_ufid | strip_used], [0], [dnl
+recirc_id(0),in_port(2),packet_type(ns=0,id=0),dnl
+eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x1234), dnl
+packets:1, bytes:14, used:0.0s, dnl
+actions:tnl_push(tnl_port(6081),header(size=50,type=5,dnl
+eth(dst=f8:bc:12:44:34:b6,src=aa:55:aa:55:00:00,dl_type=0x0800),dnl
+ipv4(src=2.2.2.88,dst=1.1.2.92,proto=17,tos=0,ttl=64,frag=0x4000),dnl
+udp(src=0,dst=6081,csum=0x0),geneve(vni=0x7b)),out_port(100)),1
+])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
 AT_SETUP([tunnel_push_pop - underlay bridge match])
 
 OVS_VSWITCHD_START([add-port br0 p0 -- set Interface p0 type=dummy ofport_request=1 other-config:hwaddr=aa:55:aa:55:00:00])
@@ -796,8 +880,11 @@ dummy@ovs-dummy: hit:0 missed:0
 
 AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/24], [0], [OK
 ])
-AT_CHECK([ovs-appctl ovs/route/add 1.1.2.92/24 br0], [0], [OK
+dnl Checking that a local route for added IP was successfully installed.
+AT_CHECK([ovs-appctl ovs/route/show | grep Cached], [0], [dnl
+Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
 ])
+
 AT_CHECK([ovs-ofctl add-flow br0 'arp,priority=1,action=normal'])
 
 dnl Use arp reply to achieve tunnel next hop mac binding
@@ -840,11 +927,12 @@ AT_CHECK([ovs-vsctl add-port int-br t2 dnl
           -- set Interface t2 type=geneve options:remote_ip=1.1.2.92 dnl
                               options:key=123 ofport_request=2])
 
-dnl First setup dummy interface IP address, then add the route
-dnl so that tnl-port table can get valid IP address for the device.
+dnl Setup dummy interface IP address.
 AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/24], [0], [OK
 ])
-AT_CHECK([ovs-appctl ovs/route/add 1.1.2.92/24 br0], [0], [OK
+dnl Checking that a local route for added IP was successfully installed.
+AT_CHECK([ovs-appctl ovs/route/show | grep Cached], [0], [dnl
+Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
 ])
 AT_CHECK([ovs-ofctl add-flow br0 action=normal])
 
@@ -908,10 +996,12 @@ AT_CHECK([ovs-vsctl set port p8  tag=42 dnl
                  -- set port br0 tag=42 dnl
                  -- set port p7  tag=200])
 
-dnl Set IP address and route for br0.
+dnl Set an IP address for br0.
 AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 10.0.0.2/24], [0], [OK
 ])
-AT_CHECK([ovs-appctl ovs/route/add 10.0.0.11/24 br0], [0], [OK
+dnl Checking that a local route for added IP was successfully installed.
+AT_CHECK([ovs-appctl ovs/route/show | grep Cached], [0], [dnl
+Cached: 10.0.0.0/24 dev br0 SRC 10.0.0.2 local
 ])
 
 dnl Send an ARP reply to port b8 on br0, so that packets will be forwarded
@@ -953,10 +1043,12 @@ AT_CHECK([ovs-vsctl add-port ovs-tun0 tun0 dnl
           -- add-port ovs-tun0 p7 dnl
           -- set interface p7 type=dummy ofport_request=7])
 
-dnl Set IP address and route for br0.
+dnl Set an IP address for br0.
 AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 10.0.0.2/24], [0], [OK
 ])
-AT_CHECK([ovs-appctl ovs/route/add 10.0.0.11/24 br0], [0], [OK
+dnl Checking that a local route for added IP was successfully installed.
+AT_CHECK([ovs-appctl ovs/route/show | grep Cached], [0], [dnl
+Cached: 10.0.0.0/24 dev br0 SRC 10.0.0.2 local
 ])
 
 dnl Send an ARP reply to port b8 on br0, so that packets will be forwarded
@@ -993,3 +1085,170 @@ udp(src=0,dst=4789,csum=0x0),vxlan(flags=0x8000000,vni=0x0)),out_port(100)),8),7
 
 OVS_VSWITCHD_STOP
 AT_CLEANUP
+
+AT_SETUP([tunnel_push_pop - use non-local port as tunnel endpoint])
+
+OVS_VSWITCHD_START([add-port br0 p0 \
+                    -- set Interface p0 type=dummy ofport_request=1])
+
+dnl Adding another port separately to ensure that it gets an
+dnl aa:55:aa:55:00:03 MAC address (dummy port number 3).
+AT_CHECK([ovs-vsctl add-port br0 vtep0 \
+            -- set interface vtep0 type=dummy ofport_request=2])
+AT_CHECK([ovs-vsctl \
+          -- add-br int-br \
+          -- set bridge int-br datapath_type=dummy \
+          -- set Interface int-br ofport_request=3])
+AT_CHECK([ovs-vsctl \
+          -- add-port int-br t1 \
+          -- set Interface t1 type=gre ofport_request=4 \
+                              options:remote_ip=1.1.2.92
+])
+
+AT_CHECK([ovs-appctl dpif/show], [0], [dnl
+dummy@ovs-dummy: hit:0 missed:0
+  br0:
+    br0 65534/100: (dummy-internal)
+    p0 1/1: (dummy)
+    vtep0 2/2: (dummy)
+  int-br:
+    int-br 65534/3: (dummy-internal)
+    t1 4/4: (gre: remote_ip=1.1.2.92)
+])
+
+AT_CHECK([ovs-appctl netdev-dummy/ip4addr vtep0 1.1.2.88/24], [0], [OK
+])
+dnl Checking that a local route for added IP was successfully installed.
+AT_CHECK([ovs-appctl ovs/route/show | grep Cached], [0], [dnl
+Cached: 1.1.2.0/24 dev vtep0 SRC 1.1.2.88 local
+])
+
+AT_CHECK([ovs-ofctl add-flow br0 action=normal])
+AT_CHECK([ovs-ofctl add-flow int-br action=normal])
+
+dnl Use arp request and reply to achieve tunnel next hop mac binding.
+dnl By default, vtep0's MAC address is aa:55:aa:55:00:03.
+AT_CHECK([ovs-appctl netdev-dummy/receive vtep0 'recirc_id(0),in_port(2),dnl
+  eth(dst=ff:ff:ff:ff:ff:ff,src=aa:55:aa:55:00:03),eth_type(0x0806),dnl
+  arp(tip=1.1.2.92,sip=1.1.2.88,op=1,sha=aa:55:aa:55:00:03,tha=00:00:00:00:00:00)'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 'recirc_id(0),in_port(1),dnl
+  eth(src=f8:bc:12:44:34:b6,dst=aa:55:aa:55:00:03),eth_type(0x0806),dnl
+  arp(sip=1.1.2.92,tip=1.1.2.88,op=2,sha=f8:bc:12:44:34:b6,tha=aa:55:aa:55:00:03)'])
+
+AT_CHECK([ovs-appctl tnl/neigh/show | tail -n+3 | sort], [0], [dnl
+1.1.2.92                                      f8:bc:12:44:34:b6   br0
+])
+
+dnl Check GRE tunnel pop.
+AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(1),dnl
+  eth(src=f8:bc:12:44:34:b6,dst=aa:55:aa:55:00:03),eth_type(0x0800),dnl
+  ipv4(src=1.1.2.92,dst=1.1.2.88,proto=47,tos=0,ttl=64,frag=no)'],
+[0], [stdout])
+
+AT_CHECK([tail -1 stdout], [0],
+  [Datapath actions: tnl_pop(4)
+])
+
+dnl Check GRE tunnel push.
+AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(3),dnl
+  eth(dst=f9:bc:12:44:34:b6,src=af:55:aa:55:00:03),eth_type(0x0800),dnl
+  ipv4(src=1.1.3.88,dst=1.1.3.92,proto=1,tos=0,ttl=64,frag=no)'],
+[0], [stdout])
+AT_CHECK([tail -1 stdout], [0],
+  [Datapath actions: tnl_push(tnl_port(4),header(size=38,type=3,dnl
+eth(dst=f8:bc:12:44:34:b6,src=aa:55:aa:55:00:03,dl_type=0x0800),dnl
+ipv4(src=1.1.2.88,dst=1.1.2.92,proto=47,tos=0,ttl=64,frag=0x4000),dnl
+gre((flags=0x0,proto=0x6558))),out_port(2)),1
+])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+dnl This is a regression test for outer header checksum offloading
+dnl with recirculation.
+AT_SETUP([tunnel_push_pop - recirculation after encapsulation])
+
+OVS_VSWITCHD_START(
+    [add-port br0 p0 \
+     -- set Interface p0 type=dummy ofport_request=1 \
+                         other-config:hwaddr=aa:55:aa:55:00:00])
+AT_CHECK([ovs-appctl vlog/set dpif_netdev:dbg])
+AT_CHECK([ovs-vsctl add-br int-br -- set bridge int-br datapath_type=dummy])
+AT_CHECK([ovs-vsctl add-port int-br t2 \
+          -- set Interface t2 type=geneve \
+                              options:remote_ip=1.1.2.92 \
+                              options:key=123 ofport_request=2])
+
+dnl Setup an IP address.
+AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/24], [0], [OK
+])
+dnl Checking that a local route for added IP was successfully installed.
+AT_CHECK([ovs-appctl ovs/route/show | grep Cached | sort], [0], [dnl
+Cached: 1.1.2.0/24 dev br0 SRC 1.1.2.88 local
+])
+
+dnl Add a dp-hash selection group.
+AT_CHECK([ovs-ofctl add-group br0 \
+    'group_id=1234,type=select,selection_method=dp_hash,bucket=weight=1,output:p0'])
+AT_CHECK([ovs-ofctl add-flow br0 in_port=br0,action=group:1234])
+AT_CHECK([ovs-ofctl add-flow br0 in_port=p0,action=normal])
+
+AT_CHECK([ovs-ofctl add-flow int-br action=normal])
+
+dnl This ARP reply from p0 has two effects:
+dnl 1. The ARP cache will learn that 1.1.2.92 is at f8:bc:12:44:34:b6.
+dnl 2. The br0 mac learning will learn that f8:bc:12:44:34:b6 is on p0.
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 dnl
+ 'recirc_id(0),in_port(1),dnl
+  eth(src=f8:bc:12:44:34:b6,dst=ff:ff:ff:ff:ff:ff),eth_type(0x0806),dnl
+  arp(sip=1.1.2.92,tip=1.1.2.88,op=2,sha=f8:bc:12:44:34:b6,tha=00:00:00:00:00:00)'
+])
+
+dnl Check that selection group is used in the trace.
+AT_CHECK([ovs-appctl ofproto/trace int-br in_port=LOCAL \
+                | grep -E 'tunnel|actions'], [0], [dnl
+     -> output to native tunnel
+     -> tunneling to 1.1.2.92 via br0
+     -> tunneling from aa:55:aa:55:00:00 1.1.2.88 to f8:bc:12:44:34:b6 1.1.2.92
+Datapath actions: tnl_push(tnl_port(6081),header(size=50,type=5,dnl
+eth(dst=f8:bc:12:44:34:b6,src=aa:55:aa:55:00:00,dl_type=0x0800),dnl
+ipv4(src=1.1.2.88,dst=1.1.2.92,proto=17,tos=0,ttl=64,frag=0x4000),dnl
+udp(src=0,dst=6081,csum=0x0),geneve(vni=0x7b)),out_port(100)),dnl
+hash(l4(0)),recirc(0x1)
+])
+
+dnl Now check that the packet is actually encapsulated and delivered.
+AT_CHECK([ovs-vsctl -- set Interface p0 options:tx_pcap=p0.pcap])
+
+packet=50540000000a5054000000091234
+eth=f8bc124434b6aa55aa5500000800
+ip4=450000320000400040113406010102580101025c
+dnl Source port is based on a packet hash, so it may differ depending on the
+dnl compiler flags and CPU type.  Masked with '....'.
+udp=....17c1001e0000
+geneve=0000655800007b00
+encap=${eth}${ip4}${udp}${geneve}
+dnl Output to tunnel from a int-br internal port.
+dnl Checking that the packet arrived and it was correctly encapsulated.
+AT_CHECK([ovs-appctl netdev-dummy/receive int-br "${packet}"])
+OVS_WAIT_UNTIL([test $(ovs-pcap p0.pcap | grep -c "${encap}${packet}") -eq 1])
+
+dnl Sending again to exercise the non-miss upcall path.
+AT_CHECK([ovs-appctl netdev-dummy/receive int-br "${packet}"])
+OVS_WAIT_UNTIL([test $(ovs-pcap p0.pcap | grep -c "${encap}${packet}") -eq 2])
+
+dnl Finally, checking that the datapath flow is also correct.
+AT_CHECK([ovs-appctl dpctl/dump-flows | grep tnl_push \
+            | strip_ufid | strip_used], [0], [dnl
+recirc_id(0),in_port(2),packet_type(ns=0,id=0),dnl
+eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x1234), dnl
+packets:1, bytes:14, used:0.0s, dnl
+actions:tnl_push(tnl_port(6081),header(size=50,type=5,dnl
+eth(dst=f8:bc:12:44:34:b6,src=aa:55:aa:55:00:00,dl_type=0x0800),dnl
+ipv4(src=1.1.2.88,dst=1.1.2.92,proto=17,tos=0,ttl=64,frag=0x4000),dnl
+udp(src=0,dst=6081,csum=0x0),geneve(vni=0x7b)),out_port(100)),dnl
+hash(l4(0)),recirc(0x2)
+])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
diff --git a/tests/tunnel.at b/tests/tunnel.at
index 282651ac73..9d539ee6f6 100644
--- a/tests/tunnel.at
+++ b/tests/tunnel.at
@@ -524,11 +524,12 @@ dummy@ovs-dummy: hit:0 missed:0
     v2 3/3: (dummy-internal)
 ])
 
-dnl First setup dummy interface IP address, then add the route
-dnl so that tnl-port table can get valid IP address for the device.
+dnl Setup dummy interface IP address.
 AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 172.31.1.1/24], [0], [OK
 ])
-AT_CHECK([ovs-appctl ovs/route/add 172.31.1.0/24 br0], [0], [OK
+dnl Checking that a local route for added IP was successfully installed.
+AT_CHECK([ovs-appctl ovs/route/show | grep Cached], [0], [dnl
+Cached: 172.31.1.0/24 dev br0 SRC 172.31.1.1 local
 ])
 
 dnl change the flow table to bump the internal table version
@@ -1268,6 +1269,18 @@ OVS_APP_EXIT_AND_WAIT([ovs-vswitchd])
 OVS_APP_EXIT_AND_WAIT([ovsdb-server])]
 AT_CLEANUP
 
+AT_SETUP([tunnel - re-create port with different name])
+OVS_VSWITCHD_START(
+  [add-port br0 p0 -- set int p0 type=vxlan options:remote_ip=10.10.10.1])
+
+AT_CHECK([ovs-vsctl --if-exists del-port p0 -- \
+          add-port br0 p1 -- \
+          set int p1 type=vxlan options:remote_ip=10.10.10.1])
+
+OVS_APP_EXIT_AND_WAIT([ovs-vswitchd])
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])]
+AT_CLEANUP
+
 AT_SETUP([tunnel - SRV6 basic])
 OVS_VSWITCHD_START([add-port br0 p1 -- set Interface p1 type=dummy \
                     ofport_request=1 \
@@ -1276,15 +1289,12 @@ OVS_VSWITCHD_START([add-port br0 p1 -- set Interface p1 type=dummy \
                     ofport_request=2])
 OVS_VSWITCHD_DISABLE_TUNNEL_PUSH_POP
 
-dnl First setup dummy interface IP address, then add the route
-dnl so that tnl-port table can get valid IP address for the device.
+dnl Setup dummy interface IP address.
 AT_CHECK([ovs-appctl netdev-dummy/ip6addr br0 fc00::1/64], [0], [OK
 ])
-AT_CHECK([ovs-appctl ovs/route/add fc00::0/64 br0], [0], [OK
-])
-AT_CHECK([ovs-appctl ovs/route/show], [0], [dnl
-Route Table:
-User: fc00::/64 dev br0 SRC fc00::1
+dnl Checking that a local route for added IP was successfully installed.
+AT_CHECK([ovs-appctl ovs/route/show | grep Cached], [0], [dnl
+Cached: fc00::/64 dev br0 SRC fc00::1 local
 ])
 
 AT_DATA([flows.txt], [dnl
diff --git a/tests/vlog.at b/tests/vlog.at
index 785014956e..2768c07400 100644
--- a/tests/vlog.at
+++ b/tests/vlog.at
@@ -8,6 +8,7 @@ AT_CHECK([$PYTHON3 $srcdir/test-vlog.py --log-file log_file \
 
 AT_CHECK([sed -e 's/.*-.*-.*T..:..:..Z |//' \
 -e 's/File ".*", line [[0-9]][[0-9]]*,/File <name>, line <number>,/' \
+-e '/\^/d' \
 stderr_log], [0], [dnl
   0  | module_0 | EMER | emergency
   1  | module_0 | ERR | error
diff --git a/utilities/ovs-pki.in b/utilities/ovs-pki.in
index e0ba910f94..285018e41e 100755
--- a/utilities/ovs-pki.in
+++ b/utilities/ovs-pki.in
@@ -57,6 +57,77 @@ FreeBSD|NetBSD|Darwin)
     ;;
 esac
 
+case $(uname -s) in
+MINGW*|MSYS*)
+    chmod()
+    {
+        local PERM=$1
+        local FILE=$2
+        local INH=
+
+        if test -d "${FILE}"; then
+            # Inheritance rules for folders: apply to a folder itself,
+            # subfolders and files within.
+            INH='(OI)(CI)'
+        fi
+
+        case "${PERM}" in
+        *700 | *600)
+            # Reset all own and inherited ACEs and grant full access to the
+            # "Creator Owner".  We're giving full access even for 0600,
+            # because it doesn't matter for a use case of ovs-pki.
+            icacls "${FILE}" /inheritance:r /grant:r "*S-1-3-0:${INH}F"
+            ;;
+        *750)
+            # Reset all own and inherited ACEs, grant full access to the
+            # "Creator Owner" and a read+execute access to the "Creator Group".
+            icacls "${FILE}" /inheritance:r /grant:r \
+                "*S-1-3-0:${INH}F" "*S-1-3-1:${INH}RX"
+            ;;
+        *)
+            echo >&2 "Unable to set ${PERM} mode for ${FILE}."
+            exit 1
+            ;;
+        esac
+    }
+
+    mkdir()
+    {
+        ARG_P=
+        PERM=
+        for arg; do
+            shift
+            case ${arg} in
+            -m?*)
+                PERM=${arg#??}
+                continue
+                ;;
+            -m)
+                PERM=$1
+                shift
+                continue
+                ;;
+            -p)
+                ARG_P=-p
+                continue
+                ;;
+            *)
+                set -- "$@" "${arg}"
+                ;;
+            esac
+        done
+
+        command mkdir ${ARG_P} $@
+        if [ ${PERM} ]; then
+            for dir; do
+                shift
+                chmod ${PERM} ${dir}
+            done
+        fi
+    }
+    ;;
+esac
+
 for option; do
     # This option-parsing mechanism borrowed from a Autoconf-generated
     # configure script under the following license:
@@ -466,14 +537,24 @@ CN = $cn
 [ v3_req ]
 subjectAltName = DNS:$cn
 EOF
+    # It is important to create private keys in $TMP because umask doesn't
+    # work on Windows and permissions there are inherited from the folder.
+    # umask itself is still needed though to ensure correct permissions
+    # on non-Windows platforms.
     if test $keytype = rsa; then
-        (umask 077 && openssl genrsa -out "$1-privkey.pem" $bits) 1>&3 2>&3 \
-            || exit $?
+        (umask 077 && openssl genrsa -out "$TMP/privkey.pem" $bits) \
+            1>&3 2>&3 || exit $?
     else
         must_exist "$dsaparam"
-        (umask 077 && openssl gendsa -out "$1-privkey.pem" "$dsaparam") \
+        (umask 077 && openssl gendsa -out "$TMP/privkey.pem" "$dsaparam") \
             1>&3 2>&3 || exit $?
     fi
+    # Windows: applying permissions (ACEs) to the file itself, just in case.
+    # 'mv' should technically preserve all the inherited ACEs from a TMP
+    # folder, but it's better to not rely on that.
+    chmod 0600 "$TMP/privkey.pem"
+    mv "$TMP/privkey.pem" "$1-privkey.pem"
+
     openssl req -config "$TMP/req.cnf" -new -text \
         -key "$1-privkey.pem" -out "$1-req.pem" 1>&3 2>&3
 }
diff --git a/utilities/ovs-tcpdump.in b/utilities/ovs-tcpdump.in
index 4cbd9a5d31..eada803bb4 100755
--- a/utilities/ovs-tcpdump.in
+++ b/utilities/ovs-tcpdump.in
@@ -534,29 +534,19 @@ def main():
     ovsdb.close_idl()
 
     pipes = _doexec(*([dump_cmd, '-i', mirror_interface] + tcpdargs))
-    try:
-        while pipes.poll() is None:
-            data = pipes.stdout.readline().strip(b'\n')
-            if len(data) == 0:
-                raise KeyboardInterrupt
-            print(data.decode('utf-8'))
-        raise KeyboardInterrupt
-    except KeyboardInterrupt:
-        # If there is a pipe behind ovs-tcpdump (such as ovs-tcpdump
-        # -i eth0 | grep "192.168.1.1"), the pipe is no longer available
-        # after received Ctrl+C.
-        # If we write data to an unavailable pipe, a pipe error will be
-        # reported, so we turn off stdout to avoid subsequent flushing
-        # of data into the pipe.
-        try:
-            sys.stdout.close()
-        except IOError:
-            pass
+    while pipes.poll() is None:
+        data = pipes.stdout.readline().strip(b'\n')
+        if len(data) == 0:
+            break
+        print(data.decode('utf-8'))
 
-        if pipes.poll() is None:
-            pipes.terminate()
+    try:
+        sys.stdout.close()
+    except IOError:
+        pass
 
-    sys.exit(0)
+    if pipes.poll() is None:
+        pipes.terminate()
 
 
 if __name__ == '__main__':