Blob Blame History Raw
diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml
index fc75581486..6f5139304a 100644
--- a/.github/workflows/build-and-test.yml
+++ b/.github/workflows/build-and-test.yml
@@ -238,6 +238,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
 
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/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/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/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..6d02eaba8b 100644
--- a/lib/conntrack.c
+++ b/lib/conntrack.c
@@ -2637,25 +2637,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/netdev-dpdk.c b/lib/netdev-dpdk.c
index 45f61930d4..8c52accff9 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)
 {
@@ -2569,9 +2572,29 @@ 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;
     }
 
@@ -2664,6 +2687,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 +2732,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/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/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-xlate.c b/ofproto/ofproto-dpif-xlate.c
index 1cf4d5f7c9..89f183182e 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);
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..a1393f7f8e 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
 
@@ -7653,12 +7670,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 +7688,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-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-traffic.at b/tests/system-traffic.at
index 98e494abf4..2d12d558ec 100644
--- a/tests/system-traffic.at
+++ b/tests/system-traffic.at
@@ -6388,6 +6388,7 @@ OVS_TRAFFIC_VSWITCHD_STOP
 AT_CLEANUP
 
 AT_SETUP([conntrack - SNAT with port range with exhaustion])
+OVS_CHECK_GITHUB_ACTION()
 CHECK_CONNTRACK()
 CHECK_CONNTRACK_NAT()
 OVS_TRAFFIC_VSWITCHD_START()
@@ -8389,6 +8390,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..3f2cf84292 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,87 @@ 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
diff --git a/tests/tunnel-push-pop.at b/tests/tunnel-push-pop.at
index b1440f5904..97405636f9 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,81 @@ 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
diff --git a/tests/tunnel.at b/tests/tunnel.at
index 282651ac73..71e7c2df4e 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
@@ -1276,15 +1277,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/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
 }