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-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..44491db3e3 100644
--- a/.github/workflows/build-and-test.yml
+++ b/.github/workflows/build-and-test.yml
@@ -2,6 +2,9 @@ name: Build and Test
 
 on: [push, pull_request]
 
+env:
+  python_default: 3.12
+
 jobs:
   build-dpdk:
     env:
@@ -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/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..4bfb341cf4 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,6 @@
+v3.3.1 - xx xxx xxxx
+--------------------
+
 v3.3.0 - 16 Feb 2024
 --------------------
    - OVSDB:
diff --git a/configure.ac b/configure.ac
index 05afbb9cc8..a3ea65c0fa 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.1, 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..22c767a4ce 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+openvswitch (3.3.1-1) unstable; urgency=low
+   [ Open vSwitch team ]
+   * New upstream version
+
+ -- Open vSwitch team <dev@openvswitch.org>  Fri, 16 Feb 2024 12:25:58 +0100
+
 openvswitch (3.3.0-1) unstable; urgency=low
 
    * New upstream version
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..3c652aeb70 100644
--- a/lib/conntrack.c
+++ b/lib/conntrack.c
@@ -2572,7 +2572,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 +2639,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..3622764c47 100644
--- a/lib/dp-packet.h
+++ b/lib/dp-packet.h
@@ -1300,6 +1300,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/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/netdev-dpdk.c b/lib/netdev-dpdk.c
index 45f61930d4..9249b9e9c6 100644
--- a/lib/netdev-dpdk.c
+++ b/lib/netdev-dpdk.c
@@ -607,6 +607,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 +1354,18 @@ 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")) {
+        /* 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 or net/i40e 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 {
@@ -2364,17 +2379,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);
@@ -2569,21 +2583,57 @@ 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);
+    const uint64_t all_requests = (RTE_MBUF_F_TX_IP_CKSUM |
+                                   RTE_MBUF_F_TX_L4_MASK  |
+                                   RTE_MBUF_F_TX_OUTER_IP_CKSUM  |
+                                   RTE_MBUF_F_TX_OUTER_UDP_CKSUM |
+                                   RTE_MBUF_F_TX_TCP_SEG);
+    const uint64_t all_marks = (RTE_MBUF_F_TX_IPV4 |
+                                RTE_MBUF_F_TX_IPV6 |
+                                RTE_MBUF_F_TX_OUTER_IPV4 |
+                                RTE_MBUF_F_TX_OUTER_IPV6 |
+                                RTE_MBUF_F_TX_TUNNEL_MASK);
+
+    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)) {
+    const uint64_t tunnel_type = mbuf->ol_flags & RTE_MBUF_F_TX_TUNNEL_MASK;
+    if (tunnel_type == RTE_MBUF_F_TX_TUNNEL_GENEVE ||
+        tunnel_type == 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);
+
+        /* If neither inner checksums nor TSO is requested, inner marks
+         * should not be set. */
+        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);
+        }
+    } else if (OVS_UNLIKELY(tunnel_type)) {
+        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;
     } else {
         mbuf->l2_len = (char *) dp_packet_l3(pkt) -
                (char *) dp_packet_eth(pkt);
@@ -2602,15 +2652,14 @@ netdev_dpdk_prep_hwol_packet(struct netdev_dpdk *dev, struct rte_mbuf *mbuf)
         }
     }
 
-    if (mbuf->ol_flags & RTE_MBUF_F_TX_TCP_CKSUM) {
+    if ((mbuf->ol_flags & RTE_MBUF_F_TX_L4_MASK) == 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;
         }
 
-        if (mbuf->ol_flags & (RTE_MBUF_F_TX_TUNNEL_GENEVE |
-            RTE_MBUF_F_TX_TUNNEL_VXLAN)) {
+        if (tunnel_type) {
             mbuf->tso_segsz = dev->mtu - mbuf->l2_len - mbuf->l3_len -
                               mbuf->l4_len - mbuf->outer_l3_len;
         } else {
@@ -2629,11 +2678,14 @@ netdev_dpdk_prep_hwol_packet(struct netdev_dpdk *dev, struct rte_mbuf *mbuf)
                 return false;
             }
         }
+    }
 
-        if (mbuf->ol_flags & RTE_MBUF_F_TX_IPV4) {
-            mbuf->ol_flags |= RTE_MBUF_F_TX_IP_CKSUM;
-        }
+    /* 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 +2716,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 +2761,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) {
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/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/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-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/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/vlog.c b/lib/vlog.c
index b2653142f3..e78c785f7b 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"
@@ -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..87506aa785 100644
--- a/ofproto/ofproto-dpif-trace.c
+++ b/ofproto/ofproto-dpif-trace.c
@@ -845,17 +845,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-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-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/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/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/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..3eaccb13a6 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
 
@@ -6178,6 +6195,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 +7721,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 +7739,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-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/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..0008bc1720 100644
--- a/tests/system-traffic.at
+++ b/tests/system-traffic.at
@@ -3103,7 +3103,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 +3247,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 +6405,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 +6433,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])
@@ -8389,6 +8408,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])
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..efe91479a6 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__':