diff --git a/SOURCES/openvswitch-2.16.0.patch b/SOURCES/openvswitch-2.16.0.patch
index aef4484..7e073c5 100644
--- a/SOURCES/openvswitch-2.16.0.patch
+++ b/SOURCES/openvswitch-2.16.0.patch
@@ -1,7 +1,16 @@
 diff --git a/.ci/linux-build.sh b/.ci/linux-build.sh
-index 863f023888..1e7565b8bb 100755
+index 863f023888..c06e88c577 100755
 --- a/.ci/linux-build.sh
 +++ b/.ci/linux-build.sh
+@@ -216,7 +216,7 @@ fi
+ 
+ if [ "$DPDK" ] || [ "$DPDK_SHARED" ]; then
+     if [ -z "$DPDK_VER" ]; then
+-        DPDK_VER="20.11.1"
++        DPDK_VER="20.11.4"
+     fi
+     install_dpdk $DPDK_VER
+     if [ "$CC" = "clang" ]; then
 @@ -246,8 +246,8 @@ if [ "$ASAN" ]; then
      export ASAN_OPTIONS='detect_leaks=1'
      # -O2 generates few false-positive memory leak reports in test-ovsdb
@@ -86,24 +95,50 @@ index 51d0511080..c7aeede06e 100644
        - python3-sphinx
        - libelf-dev
 diff --git a/Documentation/faq/releases.rst b/Documentation/faq/releases.rst
-index 68c9867b19..64bc577e0b 100644
+index 68c9867b19..d62d575eba 100644
 --- a/Documentation/faq/releases.rst
 +++ b/Documentation/faq/releases.rst
-@@ -205,8 +205,8 @@ Q: What DPDK version does each Open vSwitch release work with?
+@@ -205,10 +205,10 @@ Q: What DPDK version does each Open vSwitch release work with?
      2.10.x       17.11.10
      2.11.x       18.11.9
      2.12.x       18.11.9
 -    2.13.x       19.11.8
 -    2.14.x       19.11.8
+-    2.15.x       20.11.1
+-    2.16.x       20.11.1
 +    2.13.x       19.11.10
 +    2.14.x       19.11.10
-     2.15.x       20.11.1
-     2.16.x       20.11.1
++    2.15.x       20.11.4
++    2.16.x       20.11.4
      ============ ========
+ 
+ Q: Are all the DPDK releases that OVS versions work with maintained?
 diff --git a/Documentation/intro/install/dpdk.rst b/Documentation/intro/install/dpdk.rst
-index d8fa931fab..1dbead3276 100644
+index d8fa931fab..9ce5285c58 100644
 --- a/Documentation/intro/install/dpdk.rst
 +++ b/Documentation/intro/install/dpdk.rst
+@@ -42,7 +42,7 @@ Build requirements
+ In addition to the requirements described in :doc:`general`, building Open
+ vSwitch with DPDK will require the following:
+ 
+-- DPDK 20.11.1
++- DPDK 20.11.4
+ 
+ - A `DPDK supported NIC`_
+ 
+@@ -73,9 +73,9 @@ Install DPDK
+ #. Download the `DPDK sources`_, extract the file and set ``DPDK_DIR``::
+ 
+        $ cd /usr/src/
+-       $ wget https://fast.dpdk.org/rel/dpdk-20.11.1.tar.xz
+-       $ tar xf dpdk-20.11.1.tar.xz
+-       $ export DPDK_DIR=/usr/src/dpdk-stable-20.11.1
++       $ wget https://fast.dpdk.org/rel/dpdk-20.11.4.tar.xz
++       $ tar xf dpdk-20.11.4.tar.xz
++       $ export DPDK_DIR=/usr/src/dpdk-stable-20.11.4
+        $ cd $DPDK_DIR
+ 
+ #. Configure and install DPDK using Meson
 @@ -219,7 +219,7 @@ To verify hugepage configuration::
  
  Mount the hugepages, if not already mounted by default::
@@ -146,15 +181,18 @@ index 95fa7af128..c1a35eb13a 100644
  is discussed here.
  
 diff --git a/NEWS b/NEWS
-index 559a51ba3f..ecaf9ffd78 100644
+index 559a51ba3f..80720f2607 100644
 --- a/NEWS
 +++ b/NEWS
-@@ -1,3 +1,17 @@
+@@ -1,3 +1,20 @@
 +v2.16.3 - xx xxx xxxx
 +---------------------
 +   - Python:
 +     * For SSL support, the use of the pyOpenSSL library has been replaced
 +       with the native 'ssl' module.
++   - DPDK:
++     * OVS validated with DPDK 20.11.4. It is recommended to use this version
++       until further releases.
 +
 +v2.16.2 - 17 Dec 2021
 +---------------------
@@ -1354,6 +1392,18 @@ index 7c4a840cb1..0d5da73c7a 100644
  
      /* Caches the masks to match a packet to, reducing runtime calculations. */
      uint64_t *mf_masks;
+diff --git a/lib/dpif-netdev-private-flow.h b/lib/dpif-netdev-private-flow.h
+index 3030660675..32ad020d90 100644
+--- a/lib/dpif-netdev-private-flow.h
++++ b/lib/dpif-netdev-private-flow.h
+@@ -101,6 +101,7 @@ struct dp_netdev_flow {
+ 
+     bool dead;
+     uint32_t mark;               /* Unique flow mark assigned to a flow */
++    odp_port_t orig_in_port;
+ 
+     /* Statistics. */
+     struct dp_netdev_flow_stats stats;
 diff --git a/lib/dpif-netdev-private-thread.h b/lib/dpif-netdev-private-thread.h
 index a782d9678a..ac4885538c 100644
 --- a/lib/dpif-netdev-private-thread.h
@@ -1374,7 +1424,7 @@ index a782d9678a..ac4885538c 100644
  
      /* Flow-Table and classifiers
 diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
-index bddce75b63..221d10aa52 100644
+index bddce75b63..c56eb184d7 100644
 --- a/lib/dpif-netdev.c
 +++ b/lib/dpif-netdev.c
 @@ -984,7 +984,9 @@ dpif_netdev_subtable_lookup_set(struct unixctl_conn *conn, int argc OVS_UNUSED,
@@ -1425,6 +1475,24 @@ index bddce75b63..221d10aa52 100644
      offload = dp_netdev_alloc_flow_offload(pmd, flow,
                                             DP_NETDEV_FLOW_OFFLOAD_OP_DEL);
      dp_netdev_append_flow_offload(offload);
+@@ -2720,7 +2737,7 @@ static void
+ queue_netdev_flow_put(struct dp_netdev_pmd_thread *pmd,
+                       struct dp_netdev_flow *flow, struct match *match,
+                       const struct nlattr *actions, size_t actions_len,
+-                      odp_port_t orig_in_port, int op)
++                      int op)
+ {
+     struct dp_flow_offload_item *offload;
+ 
+@@ -2740,7 +2757,7 @@ queue_netdev_flow_put(struct dp_netdev_pmd_thread *pmd,
+     offload->actions = xmalloc(actions_len);
+     memcpy(offload->actions, actions, actions_len);
+     offload->actions_len = actions_len;
+-    offload->orig_in_port = orig_in_port;
++    offload->orig_in_port = flow->orig_in_port;
+ 
+     dp_netdev_append_flow_offload(offload);
+ }
 @@ -2758,9 +2775,7 @@ dp_netdev_pmd_remove_flow(struct dp_netdev_pmd_thread *pmd,
      ovs_assert(cls != NULL);
      dpcls_remove(cls, &flow->cr);
@@ -1436,7 +1504,33 @@ index bddce75b63..221d10aa52 100644
      flow->dead = true;
  
      dp_netdev_flow_unref(flow);
-@@ -4061,7 +4076,10 @@ dpif_netdev_execute(struct dpif *dpif, struct dpif_execute *execute)
+@@ -3555,6 +3570,7 @@ dp_netdev_flow_add(struct dp_netdev_pmd_thread *pmd,
+     flow->dead = false;
+     flow->batch = NULL;
+     flow->mark = INVALID_FLOW_MARK;
++    flow->orig_in_port = orig_in_port;
+     *CONST_CAST(unsigned *, &flow->pmd_id) = pmd->core_id;
+     *CONST_CAST(struct flow *, &flow->flow) = match->flow;
+     *CONST_CAST(ovs_u128 *, &flow->ufid) = *ufid;
+@@ -3584,7 +3600,7 @@ dp_netdev_flow_add(struct dp_netdev_pmd_thread *pmd,
+                 dp_netdev_flow_hash(&flow->ufid));
+ 
+     queue_netdev_flow_put(pmd, flow, match, actions, actions_len,
+-                          orig_in_port, DP_NETDEV_FLOW_OFFLOAD_OP_ADD);
++                          DP_NETDEV_FLOW_OFFLOAD_OP_ADD);
+ 
+     if (OVS_UNLIKELY(!VLOG_DROP_DBG((&upcall_rl)))) {
+         struct ds ds = DS_EMPTY_INITIALIZER;
+@@ -3671,7 +3687,7 @@ flow_put_on_pmd(struct dp_netdev_pmd_thread *pmd,
+             ovsrcu_set(&netdev_flow->actions, new_actions);
+ 
+             queue_netdev_flow_put(pmd, netdev_flow, match,
+-                                  put->actions, put->actions_len, ODPP_NONE,
++                                  put->actions, put->actions_len,
+                                   DP_NETDEV_FLOW_OFFLOAD_OP_MOD);
+ 
+             if (stats) {
+@@ -4061,7 +4077,10 @@ dpif_netdev_execute(struct dpif *dpif, struct dpif_execute *execute)
                                 flow_hash_5tuple(execute->flow, 0));
      }
  
@@ -1448,7 +1542,7 @@ index bddce75b63..221d10aa52 100644
      dp_netdev_execute_actions(pmd, &pp, false, execute->flow,
                                execute->actions, execute->actions_len);
      dp_netdev_pmd_flush_output_packets(pmd, true);
-@@ -4071,6 +4089,24 @@ dpif_netdev_execute(struct dpif *dpif, struct dpif_execute *execute)
+@@ -4071,6 +4090,24 @@ dpif_netdev_execute(struct dpif *dpif, struct dpif_execute *execute)
          dp_netdev_pmd_unref(pmd);
      }
  
@@ -1473,7 +1567,7 @@ index bddce75b63..221d10aa52 100644
      return 0;
  }
  
-@@ -8942,9 +8978,12 @@ dpcls_create_subtable(struct dpcls *cls, const struct netdev_flow_key *mask)
+@@ -8942,9 +8979,12 @@ dpcls_create_subtable(struct dpcls *cls, const struct netdev_flow_key *mask)
  
      /* Get the preferred subtable search function for this (u0,u1) subtable.
       * The function is guaranteed to always return a valid implementation, and
@@ -1488,7 +1582,7 @@ index bddce75b63..221d10aa52 100644
  
      cmap_insert(&cls->subtables_map, &subtable->cmap_node, mask->hash);
      /* Add the new subtable at the end of the pvector (with no hits yet) */
-@@ -8973,6 +9012,10 @@ dpcls_find_subtable(struct dpcls *cls, const struct netdev_flow_key *mask)
+@@ -8973,6 +9013,10 @@ dpcls_find_subtable(struct dpcls *cls, const struct netdev_flow_key *mask)
  /* Checks for the best available implementation for each subtable lookup
   * function, and assigns it as the lookup function pointer for each subtable.
   * Returns the number of subtables that have changed lookup implementation.
@@ -1499,7 +1593,7 @@ index bddce75b63..221d10aa52 100644
   */
  static uint32_t
  dpcls_subtable_lookup_reprobe(struct dpcls *cls)
-@@ -8985,10 +9028,13 @@ dpcls_subtable_lookup_reprobe(struct dpcls *cls)
+@@ -8985,10 +9029,13 @@ dpcls_subtable_lookup_reprobe(struct dpcls *cls)
          uint32_t u0_bits = subtable->mf_bits_set_unit0;
          uint32_t u1_bits = subtable->mf_bits_set_unit1;
          void *old_func = subtable->lookup_func;
@@ -1978,9 +2072,23 @@ index 60dd138914..97bd21be4a 100644
      }
  
 diff --git a/lib/netdev-offload-tc.c b/lib/netdev-offload-tc.c
-index 9845e8d3fe..3f7068c8e0 100644
+index 9845e8d3fe..12d0a9af3b 100644
 --- a/lib/netdev-offload-tc.c
 +++ b/lib/netdev-offload-tc.c
+@@ -481,10 +481,10 @@ netdev_tc_flow_dump_destroy(struct netdev_flow_dump *dump)
+ 
+ static void
+ parse_flower_rewrite_to_netlink_action(struct ofpbuf *buf,
+-                                       struct tc_flower *flower)
++                                       struct tc_action *action)
+ {
+-    char *mask = (char *) &flower->rewrite.mask;
+-    char *data = (char *) &flower->rewrite.key;
++    char *mask = (char *) &action->rewrite.mask;
++    char *data = (char *) &action->rewrite.key;
+ 
+     for (int type = 0; type < ARRAY_SIZE(set_flower_map); type++) {
+         char *put = NULL;
 @@ -585,8 +585,10 @@ parse_tc_flower_to_stats(struct tc_flower *flower,
      }
  
@@ -1994,7 +2102,77 @@ index 9845e8d3fe..3f7068c8e0 100644
      stats->used = flower->lastused;
  }
  
-@@ -2015,9 +2017,7 @@ netdev_tc_flow_del(struct netdev *netdev OVS_UNUSED,
+@@ -877,7 +879,7 @@ parse_tc_flower_to_match(struct tc_flower *flower,
+             }
+             break;
+             case TC_ACT_PEDIT: {
+-                parse_flower_rewrite_to_netlink_action(buf, flower);
++                parse_flower_rewrite_to_netlink_action(buf, action);
+             }
+             break;
+             case TC_ACT_ENCAP: {
+@@ -1222,8 +1224,8 @@ parse_put_flow_set_masked_action(struct tc_flower *flower,
+     uint64_t set_stub[1024 / 8];
+     struct ofpbuf set_buf = OFPBUF_STUB_INITIALIZER(set_stub);
+     char *set_data, *set_mask;
+-    char *key = (char *) &flower->rewrite.key;
+-    char *mask = (char *) &flower->rewrite.mask;
++    char *key = (char *) &action->rewrite.key;
++    char *mask = (char *) &action->rewrite.mask;
+     const struct nlattr *attr;
+     int i, j, type;
+     size_t size;
+@@ -1265,14 +1267,6 @@ parse_put_flow_set_masked_action(struct tc_flower *flower,
+         }
+     }
+ 
+-    if (!is_all_zeros(&flower->rewrite, sizeof flower->rewrite)) {
+-        if (flower->rewrite.rewrite == false) {
+-            flower->rewrite.rewrite = true;
+-            action->type = TC_ACT_PEDIT;
+-            flower->action_count++;
+-        }
+-    }
+-
+     if (hasmask && !is_all_zeros(set_mask, size)) {
+         VLOG_DBG_RL(&rl, "unsupported sub attribute of set action type %d",
+                     type);
+@@ -1281,6 +1275,8 @@ parse_put_flow_set_masked_action(struct tc_flower *flower,
+     }
+ 
+     ofpbuf_uninit(&set_buf);
++    action->type = TC_ACT_PEDIT;
++    flower->action_count++;
+     return 0;
+ }
+ 
+@@ -1841,7 +1837,25 @@ netdev_tc_flow_put(struct netdev *netdev, struct match *match,
+                 VLOG_DBG_RL(&rl, "Can't find netdev for output port %d", port);
+                 return ENODEV;
+             }
++
++            if (!netdev_flow_api_equals(netdev, outdev)) {
++                VLOG_DBG_RL(&rl,
++                            "Flow API provider mismatch between ingress (%s) "
++                            "and egress (%s) ports",
++                            netdev_get_name(netdev), netdev_get_name(outdev));
++                netdev_close(outdev);
++                return EOPNOTSUPP;
++            }
++
+             action->out.ifindex_out = netdev_get_ifindex(outdev);
++            if (action->out.ifindex_out < 0) {
++                VLOG_DBG_RL(&rl,
++                            "Can't find ifindex for output port %s, error %d",
++                            netdev_get_name(outdev), action->out.ifindex_out);
++                netdev_close(outdev);
++                return -action->out.ifindex_out;
++            }
++
+             action->out.ingress = is_internal_port(netdev_get_type(outdev));
+             action->type = TC_ACT_OUTPUT;
+             flower.action_count++;
+@@ -2015,9 +2029,7 @@ netdev_tc_flow_del(struct netdev *netdev OVS_UNUSED,
      if (stats) {
          memset(stats, 0, sizeof *stats);
          if (!tc_get_flower(&id, &flower)) {
@@ -2006,7 +2184,7 @@ index 9845e8d3fe..3f7068c8e0 100644
      }
  
 diff --git a/lib/odp-util.c b/lib/odp-util.c
-index 7729a90608..65f028ba02 100644
+index 7729a90608..20d663153b 100644
 --- a/lib/odp-util.c
 +++ b/lib/odp-util.c
 @@ -2941,7 +2941,7 @@ odp_nsh_key_from_attr__(const struct nlattr *attr, bool is_mask,
@@ -2027,7 +2205,19 @@ index 7729a90608..65f028ba02 100644
                            &opts, sizeof(opts));
      }
      nl_msg_end_nested(a, tun_key_ofs);
-@@ -4618,7 +4618,7 @@ odp_flow_format(const struct nlattr *key, size_t key_len,
+@@ -4601,6 +4601,11 @@ odp_flow_format(const struct nlattr *key, size_t key_len,
+                     ds_put_char(ds, ',');
+                 }
+                 ds_put_cstr(ds, "eth()");
++            } else if (attr_type == OVS_KEY_ATTR_PACKET_TYPE && is_wildcard) {
++                /* See the above help text, however in the case where the
++                 * packet type is not shown, we still need to display the
++                 * eth() header if the packets type is wildcarded. */
++                has_packet_type_key = false;
+             }
+             ofpbuf_clear(&ofp);
+         }
+@@ -4618,7 +4623,7 @@ odp_flow_format(const struct nlattr *key, size_t key_len,
              }
              ds_put_char(ds, ')');
          }
@@ -3067,10 +3257,140 @@ index 809b405a52..a869b5f390 100644
          goto out;
      }
 diff --git a/lib/tc.c b/lib/tc.c
-index 38a1dfc0eb..a52cd46d99 100644
+index 38a1dfc0eb..df73a43d4c 100644
 --- a/lib/tc.c
 +++ b/lib/tc.c
-@@ -1702,6 +1702,9 @@ static const struct nl_policy stats_policy[] = {
+@@ -568,16 +568,17 @@ nl_parse_flower_vlan(struct nlattr **attrs, struct tc_flower *flower)
+ 
+     flower->key.encap_eth_type[0] =
+         nl_attr_get_be16(attrs[TCA_FLOWER_KEY_ETH_TYPE]);
++    flower->mask.encap_eth_type[0] = CONSTANT_HTONS(0xffff);
+ 
+     if (attrs[TCA_FLOWER_KEY_VLAN_ID]) {
+         flower->key.vlan_id[0] =
+             nl_attr_get_u16(attrs[TCA_FLOWER_KEY_VLAN_ID]);
+-        flower->mask.vlan_id[0] = 0xffff;
++        flower->mask.vlan_id[0] = VLAN_VID_MASK >> VLAN_VID_SHIFT;
+     }
+     if (attrs[TCA_FLOWER_KEY_VLAN_PRIO]) {
+         flower->key.vlan_prio[0] =
+             nl_attr_get_u8(attrs[TCA_FLOWER_KEY_VLAN_PRIO]);
+-        flower->mask.vlan_prio[0] = 0xff;
++        flower->mask.vlan_prio[0] = VLAN_PCP_MASK >> VLAN_PCP_SHIFT;
+     }
+ 
+     if (!attrs[TCA_FLOWER_KEY_VLAN_ETH_TYPE]) {
+@@ -590,17 +591,18 @@ nl_parse_flower_vlan(struct nlattr **attrs, struct tc_flower *flower)
+     }
+ 
+     flower->key.encap_eth_type[1] = flower->key.encap_eth_type[0];
++    flower->mask.encap_eth_type[1] = CONSTANT_HTONS(0xffff);
+     flower->key.encap_eth_type[0] = encap_ethtype;
+ 
+     if (attrs[TCA_FLOWER_KEY_CVLAN_ID]) {
+         flower->key.vlan_id[1] =
+             nl_attr_get_u16(attrs[TCA_FLOWER_KEY_CVLAN_ID]);
+-        flower->mask.vlan_id[1] = 0xffff;
++        flower->mask.vlan_id[1] = VLAN_VID_MASK >> VLAN_VID_SHIFT;
+     }
+     if (attrs[TCA_FLOWER_KEY_CVLAN_PRIO]) {
+         flower->key.vlan_prio[1] =
+             nl_attr_get_u8(attrs[TCA_FLOWER_KEY_CVLAN_PRIO]);
+-        flower->mask.vlan_prio[1] = 0xff;
++        flower->mask.vlan_prio[1] = VLAN_PCP_MASK >> VLAN_PCP_SHIFT;
+     }
+ }
+ 
+@@ -937,24 +939,21 @@ nl_parse_flower_ip(struct nlattr **attrs, struct tc_flower *flower) {
+             key->icmp_code =
+                nl_attr_get_u8(attrs[TCA_FLOWER_KEY_ICMPV4_CODE]);
+             mask->icmp_code =
+-                nl_attr_get_u8(attrs[TCA_FLOWER_KEY_ICMPV4_CODE]);
++                nl_attr_get_u8(attrs[TCA_FLOWER_KEY_ICMPV4_CODE_MASK]);
+         }
+         if (attrs[TCA_FLOWER_KEY_ICMPV4_TYPE_MASK]) {
+-            key->icmp_type =
+-               nl_attr_get_u8(attrs[TCA_FLOWER_KEY_ICMPV4_TYPE_MASK]);
++            key->icmp_type = nl_attr_get_u8(attrs[TCA_FLOWER_KEY_ICMPV4_TYPE]);
+             mask->icmp_type =
+                 nl_attr_get_u8(attrs[TCA_FLOWER_KEY_ICMPV4_TYPE_MASK]);
+         }
+     } else if (ip_proto == IPPROTO_ICMPV6) {
+         if (attrs[TCA_FLOWER_KEY_ICMPV6_CODE_MASK]) {
+-            key->icmp_code =
+-               nl_attr_get_u8(attrs[TCA_FLOWER_KEY_ICMPV6_CODE]);
++            key->icmp_code = nl_attr_get_u8(attrs[TCA_FLOWER_KEY_ICMPV6_CODE]);
+             mask->icmp_code =
+-                 nl_attr_get_u8(attrs[TCA_FLOWER_KEY_ICMPV6_CODE]);
++                 nl_attr_get_u8(attrs[TCA_FLOWER_KEY_ICMPV6_CODE_MASK]);
+         }
+         if (attrs[TCA_FLOWER_KEY_ICMPV6_TYPE_MASK]) {
+-            key->icmp_type =
+-               nl_attr_get_u8(attrs[TCA_FLOWER_KEY_ICMPV6_TYPE_MASK]);
++            key->icmp_type = nl_attr_get_u8(attrs[TCA_FLOWER_KEY_ICMPV6_TYPE]);
+             mask->icmp_type =
+                 nl_attr_get_u8(attrs[TCA_FLOWER_KEY_ICMPV6_TYPE_MASK]);
+         }
+@@ -1006,14 +1005,14 @@ static const struct nl_policy pedit_policy[] = {
+ static int
+ nl_parse_act_pedit(struct nlattr *options, struct tc_flower *flower)
+ {
+-    struct tc_action *action;
++    struct tc_action *action = &flower->actions[flower->action_count++];
+     struct nlattr *pe_attrs[ARRAY_SIZE(pedit_policy)];
+     const struct tc_pedit *pe;
+     const struct tc_pedit_key *keys;
+     const struct nlattr *nla, *keys_ex, *ex_type;
+     const void *keys_attr;
+-    char *rewrite_key = (void *) &flower->rewrite.key;
+-    char *rewrite_mask = (void *) &flower->rewrite.mask;
++    char *rewrite_key = (void *) &action->rewrite.key;
++    char *rewrite_mask = (void *) &action->rewrite.mask;
+     size_t keys_ex_size, left;
+     int type, i = 0, err;
+ 
+@@ -1092,7 +1091,6 @@ nl_parse_act_pedit(struct nlattr *options, struct tc_flower *flower)
+         i++;
+     }
+ 
+-    action = &flower->actions[flower->action_count++];
+     action->type = TC_ACT_PEDIT;
+ 
+     return 0;
+@@ -1487,7 +1485,9 @@ nl_parse_act_ct(struct nlattr *options, struct tc_flower *flower)
+                 if (ipv4_max) {
+                     ovs_be32 addr = nl_attr_get_be32(ipv4_max);
+ 
+-                    action->ct.range.ipv4.max = addr;
++                    if (action->ct.range.ipv4.min != addr) {
++                        action->ct.range.ipv4.max = addr;
++                    }
+                 }
+             } else if (ipv6_min) {
+                 action->ct.range.ip_family = AF_INET6;
+@@ -1496,7 +1496,9 @@ nl_parse_act_ct(struct nlattr *options, struct tc_flower *flower)
+                 if (ipv6_max) {
+                     struct in6_addr addr = nl_attr_get_in6_addr(ipv6_max);
+ 
+-                    action->ct.range.ipv6.max = addr;
++                    if (!ipv6_addr_equals(&action->ct.range.ipv6.min, &addr)) {
++                        action->ct.range.ipv6.max = addr;
++                    }
+                 }
+             }
+ 
+@@ -1504,6 +1506,10 @@ nl_parse_act_ct(struct nlattr *options, struct tc_flower *flower)
+                 action->ct.range.port.min = nl_attr_get_be16(port_min);
+                 if (port_max) {
+                     action->ct.range.port.max = nl_attr_get_be16(port_max);
++                    if (action->ct.range.port.min ==
++                        action->ct.range.port.max) {
++                        action->ct.range.port.max = 0;
++                    }
+                 }
+             }
+         }
+@@ -1702,6 +1708,9 @@ static const struct nl_policy stats_policy[] = {
      [TCA_STATS_BASIC] = { .type = NL_A_UNSPEC,
                            .min_len = sizeof(struct gnet_stats_basic),
                            .optional = false, },
@@ -3080,7 +3400,7 @@ index 38a1dfc0eb..a52cd46d99 100644
  };
  
  static int
-@@ -1714,8 +1717,11 @@ nl_parse_single_action(struct nlattr *action, struct tc_flower *flower,
+@@ -1714,8 +1723,11 @@ nl_parse_single_action(struct nlattr *action, struct tc_flower *flower,
      const char *act_kind;
      struct nlattr *action_attrs[ARRAY_SIZE(act_policy)];
      struct nlattr *stats_attrs[ARRAY_SIZE(stats_policy)];
@@ -3094,7 +3414,7 @@ index 38a1dfc0eb..a52cd46d99 100644
      int err = 0;
  
      if (!nl_parse_nested(action, act_policy, action_attrs,
-@@ -1771,10 +1777,26 @@ nl_parse_single_action(struct nlattr *action, struct tc_flower *flower,
+@@ -1771,10 +1783,26 @@ nl_parse_single_action(struct nlattr *action, struct tc_flower *flower,
          return EPROTO;
      }
  
@@ -3125,7 +3445,44 @@ index 38a1dfc0eb..a52cd46d99 100644
      }
  
      return 0;
-@@ -2545,6 +2567,17 @@ nl_msg_put_flower_rewrite_pedits(struct ofpbuf *request,
+@@ -2399,14 +2427,14 @@ nl_msg_put_act_flags(struct ofpbuf *request) {
+  * first_word_mask/last_word_mask - the mask to use for the first/last read
+  * (as we read entire words). */
+ static void
+-calc_offsets(struct tc_flower *flower, struct flower_key_to_pedit *m,
++calc_offsets(struct tc_action *action, struct flower_key_to_pedit *m,
+              int *cur_offset, int *cnt, ovs_be32 *last_word_mask,
+              ovs_be32 *first_word_mask, ovs_be32 **mask, ovs_be32 **data)
+ {
+     int start_offset, max_offset, total_size;
+     int diff, right_zero_bits, left_zero_bits;
+-    char *rewrite_key = (void *) &flower->rewrite.key;
+-    char *rewrite_mask = (void *) &flower->rewrite.mask;
++    char *rewrite_key = (void *) &action->rewrite.key;
++    char *rewrite_mask = (void *) &action->rewrite.mask;
+ 
+     max_offset = m->offset + m->size;
+     start_offset = ROUND_DOWN(m->offset, 4);
+@@ -2473,7 +2501,8 @@ csum_update_flag(struct tc_flower *flower,
+ 
+ static int
+ nl_msg_put_flower_rewrite_pedits(struct ofpbuf *request,
+-                                 struct tc_flower *flower)
++                                 struct tc_flower *flower,
++                                 struct tc_action *action)
+ {
+     struct {
+         struct tc_pedit sel;
+@@ -2497,7 +2526,7 @@ nl_msg_put_flower_rewrite_pedits(struct ofpbuf *request,
+             continue;
+         }
+ 
+-        calc_offsets(flower, m, &cur_offset, &cnt, &last_word_mask,
++        calc_offsets(action, m, &cur_offset, &cnt, &last_word_mask,
+                      &first_word_mask, &mask, &data);
+ 
+         for (j = 0; j < cnt; j++,  mask++, data++, cur_offset += 4) {
+@@ -2545,6 +2574,40 @@ nl_msg_put_flower_rewrite_pedits(struct ofpbuf *request,
      return 0;
  }
  
@@ -3140,10 +3497,59 @@ index 38a1dfc0eb..a52cd46d99 100644
 +    nl_msg_end_nested(request, act_offset);
 +}
 +
++/* Aggregates all previous successive pedit actions csum_update_flags
++ * to flower->csum_update_flags. Only append one csum action to the
++ * last pedit action. */
++static void
++nl_msg_put_csum_act(struct ofpbuf *request, struct tc_flower *flower,
++                    uint16_t *act_index)
++{
++    size_t act_offset;
++
++    /* No pedit actions or processed already. */
++    if (!flower->csum_update_flags) {
++        return;
++    }
++
++    act_offset = nl_msg_start_nested(request, (*act_index)++);
++    nl_msg_put_act_csum(request, flower->csum_update_flags);
++    nl_msg_put_act_flags(request);
++    nl_msg_end_nested(request, act_offset);
++
++    /* Clear it. So we can have another series of pedit actions. */
++    flower->csum_update_flags = 0;
++}
++
  static int
  nl_msg_put_flower_acts(struct ofpbuf *request, struct tc_flower *flower)
  {
-@@ -2579,6 +2612,11 @@ nl_msg_put_flower_acts(struct ofpbuf *request, struct tc_flower *flower)
+@@ -2561,24 +2624,31 @@ nl_msg_put_flower_acts(struct ofpbuf *request, struct tc_flower *flower)
+ 
+         action = flower->actions;
+         for (i = 0; i < flower->action_count; i++, action++) {
++            if (action->type != TC_ACT_PEDIT) {
++                nl_msg_put_csum_act(request, flower, &act_index);
++            }
+             switch (action->type) {
+             case TC_ACT_PEDIT: {
+                 act_offset = nl_msg_start_nested(request, act_index++);
+-                error = nl_msg_put_flower_rewrite_pedits(request, flower);
++                error = nl_msg_put_flower_rewrite_pedits(request, flower,
++                                                         action);
+                 if (error) {
+                     return error;
+                 }
+                 nl_msg_end_nested(request, act_offset);
+ 
+-                if (flower->csum_update_flags) {
+-                    act_offset = nl_msg_start_nested(request, act_index++);
+-                    nl_msg_put_act_csum(request, flower->csum_update_flags);
+-                    nl_msg_put_act_flags(request);
+-                    nl_msg_end_nested(request, act_offset);
++                if (i == flower->action_count - 1) {
++                    /* If this is the last action check csum calc again. */
++                    nl_msg_put_csum_act(request, flower, &act_index);
+                 }
              }
              break;
              case TC_ACT_ENCAP: {
@@ -3155,7 +3561,7 @@ index 38a1dfc0eb..a52cd46d99 100644
                  act_offset = nl_msg_start_nested(request, act_index++);
                  nl_msg_put_act_tunnel_key_set(request, action->encap.id_present,
                                                action->encap.id,
-@@ -2636,10 +2674,7 @@ nl_msg_put_flower_acts(struct ofpbuf *request, struct tc_flower *flower)
+@@ -2636,10 +2706,7 @@ nl_msg_put_flower_acts(struct ofpbuf *request, struct tc_flower *flower)
              break;
              case TC_ACT_OUTPUT: {
                  if (!released && flower->tunnel) {
@@ -3167,11 +3573,165 @@ index 38a1dfc0eb..a52cd46d99 100644
                      released = true;
                  }
  
+@@ -2901,13 +2968,13 @@ nl_msg_put_flower_options(struct ofpbuf *request, struct tc_flower *flower)
+             FLOWER_PUT_MASKED_VALUE(icmp_code, TCA_FLOWER_KEY_ICMPV6_CODE);
+             FLOWER_PUT_MASKED_VALUE(icmp_type, TCA_FLOWER_KEY_ICMPV6_TYPE);
+         }
+-
+-        FLOWER_PUT_MASKED_VALUE(ct_state, TCA_FLOWER_KEY_CT_STATE);
+-        FLOWER_PUT_MASKED_VALUE(ct_zone, TCA_FLOWER_KEY_CT_ZONE);
+-        FLOWER_PUT_MASKED_VALUE(ct_mark, TCA_FLOWER_KEY_CT_MARK);
+-        FLOWER_PUT_MASKED_VALUE(ct_label, TCA_FLOWER_KEY_CT_LABELS);
+     }
+ 
++    FLOWER_PUT_MASKED_VALUE(ct_state, TCA_FLOWER_KEY_CT_STATE);
++    FLOWER_PUT_MASKED_VALUE(ct_zone, TCA_FLOWER_KEY_CT_ZONE);
++    FLOWER_PUT_MASKED_VALUE(ct_mark, TCA_FLOWER_KEY_CT_MARK);
++    FLOWER_PUT_MASKED_VALUE(ct_label, TCA_FLOWER_KEY_CT_LABELS);
++
+     if (host_eth_type == ETH_P_IP) {
+             FLOWER_PUT_MASKED_VALUE(ipv4.ipv4_src, TCA_FLOWER_KEY_IPV4_SRC);
+             FLOWER_PUT_MASKED_VALUE(ipv4.ipv4_dst, TCA_FLOWER_KEY_IPV4_DST);
+@@ -2980,12 +3047,79 @@ nl_msg_put_flower_options(struct ofpbuf *request, struct tc_flower *flower)
+     return 0;
+ }
+ 
++static void
++log_tc_flower_match(const char *msg,
++                    const struct tc_flower *a,
++                    const struct tc_flower *b)
++{
++    uint8_t key_a[sizeof(struct tc_flower_key)];
++    uint8_t key_b[sizeof(struct tc_flower_key)];
++    struct ds s = DS_EMPTY_INITIALIZER;
++
++    for (int i = 0; i < sizeof a->key; i++) {
++        uint8_t mask_a = ((uint8_t *) &a->mask)[i];
++        uint8_t mask_b = ((uint8_t *) &b->mask)[i];
++
++        key_a[i] = ((uint8_t *) &a->key)[i] & mask_a;
++        key_b[i] = ((uint8_t *) &b->key)[i] & mask_b;
++    }
++    ds_put_cstr(&s, "\nExpected Mask:\n");
++    ds_put_hex(&s, &a->mask, sizeof a->mask);
++    ds_put_cstr(&s, "\nReceived Mask:\n");
++    ds_put_hex(&s, &b->mask, sizeof b->mask);
++    ds_put_cstr(&s, "\nExpected Key:\n");
++    ds_put_hex(&s, &a->key, sizeof a->key);
++    ds_put_cstr(&s, "\nReceived Key:\n");
++    ds_put_hex(&s, &b->key, sizeof b->key);
++    ds_put_cstr(&s, "\nExpected Masked Key:\n");
++    ds_put_hex(&s, key_a, sizeof key_a);
++    ds_put_cstr(&s, "\nReceived Masked Key:\n");
++    ds_put_hex(&s, key_b, sizeof key_b);
++
++    if (a->action_count != b->action_count) {
++        /* If action count is not equal, we print all actions to see which
++         * ones are missing. */
++        const struct tc_action *action;
++        int i;
++
++        ds_put_cstr(&s, "\nExpected Actions:\n");
++        for (i = 0, action = a->actions; i < a->action_count; i++, action++) {
++            ds_put_cstr(&s, " - ");
++            ds_put_hex(&s, action, sizeof *action);
++            ds_put_cstr(&s, "\n");
++        }
++        ds_put_cstr(&s, "Received Actions:\n");
++        for (i = 0, action = b->actions; i < b->action_count; i++, action++) {
++            ds_put_cstr(&s, " - ");
++            ds_put_hex(&s, action, sizeof *action);
++            ds_put_cstr(&s, "\n");
++        }
++    } else {
++        /* Only dump the delta in actions. */
++        const struct tc_action *action_a = a->actions;
++        const struct tc_action *action_b = b->actions;
++
++        for (int i = 0; i < a->action_count; i++, action_a++, action_b++) {
++            if (memcmp(action_a, action_b, sizeof *action_a)) {
++                ds_put_format(&s,
++                              "\nAction %d mismatch:\n - Expected Action: ",
++                              i);
++                ds_put_hex(&s, action_a, sizeof *action_a);
++                ds_put_cstr(&s, "\n - Received Action: ");
++                ds_put_hex(&s, action_b, sizeof *action_b);
++            }
++        }
++    }
++    VLOG_DBG_RL(&error_rl, "%s%s", msg, ds_cstr(&s));
++    ds_destroy(&s);
++}
++
+ static bool
+ cmp_tc_flower_match_action(const struct tc_flower *a,
+                            const struct tc_flower *b)
+ {
+     if (memcmp(&a->mask, &b->mask, sizeof a->mask)) {
+-        VLOG_DBG_RL(&error_rl, "tc flower compare failed mask compare");
++        log_tc_flower_match("tc flower compare failed mask compare:", a, b);
+         return false;
+     }
+ 
+@@ -2998,8 +3132,8 @@ cmp_tc_flower_match_action(const struct tc_flower *a,
+         uint8_t key_b = ((uint8_t *)&b->key)[i] & mask;
+ 
+         if (key_a != key_b) {
+-            VLOG_DBG_RL(&error_rl, "tc flower compare failed key compare at "
+-                        "%d", i);
++            log_tc_flower_match("tc flower compare failed masked key compare:",
++                                a, b);
+             return false;
+         }
+     }
+@@ -3009,14 +3143,15 @@ cmp_tc_flower_match_action(const struct tc_flower *a,
+     const struct tc_action *action_b = b->actions;
+ 
+     if (a->action_count != b->action_count) {
+-        VLOG_DBG_RL(&error_rl, "tc flower compare failed action length check");
++        log_tc_flower_match("tc flower compare failed action length check",
++                            a, b);
+         return false;
+     }
+ 
+     for (int i = 0; i < a->action_count; i++, action_a++, action_b++) {
+         if (memcmp(action_a, action_b, sizeof *action_a)) {
+-            VLOG_DBG_RL(&error_rl, "tc flower compare failed action compare "
+-                        "for %d", i);
++            log_tc_flower_match("tc flower compare failed action compare",
++                                a, b);
+             return false;
+         }
+     }
 diff --git a/lib/tc.h b/lib/tc.h
-index a147ca461d..fb0534a084 100644
+index a147ca461d..d6cdddd169 100644
 --- a/lib/tc.h
 +++ b/lib/tc.h
-@@ -330,7 +330,8 @@ struct tc_flower {
+@@ -256,11 +256,23 @@ struct tc_action {
+             bool force;
+             bool commit;
+         } ct;
++
++        struct {
++            struct tc_flower_key key;
++            struct tc_flower_key mask;
++        } rewrite;
+      };
+ 
+      enum tc_action_type type;
+ };
+ 
++/* assert that if we overflow with a masked write of uint32_t to the last byte
++ * of action.rewrite we overflow inside struct tc_action.
++ * shouldn't happen unless someone moves rewrite to the end of action */
++BUILD_ASSERT_DECL(offsetof(struct tc_action, rewrite)
++                  + MEMBER_SIZEOF(struct tc_action, rewrite)
++                  + sizeof(uint32_t) - 2 < sizeof(struct tc_action));
++
+ enum tc_offloaded_state {
+     TC_OFFLOADED_STATE_UNDEFINED,
+     TC_OFFLOADED_STATE_IN_HW,
+@@ -330,15 +342,10 @@ struct tc_flower {
      int action_count;
      struct tc_action actions[TCA_ACT_MAX_NUM];
  
@@ -3180,7 +3740,29 @@ index a147ca461d..fb0534a084 100644
 +    struct ovs_flow_stats stats_hw;
      uint64_t lastused;
  
-     struct {
+-    struct {
+-        bool rewrite;
+-        struct tc_flower_key key;
+-        struct tc_flower_key mask;
+-    } rewrite;
+-
+     uint32_t csum_update_flags;
+ 
+     bool tunnel;
+@@ -352,13 +359,6 @@ struct tc_flower {
+     enum tc_offload_policy tc_policy;
+ };
+ 
+-/* assert that if we overflow with a masked write of uint32_t to the last byte
+- * of flower.rewrite we overflow inside struct flower.
+- * shouldn't happen unless someone moves rewrite to the end of flower */
+-BUILD_ASSERT_DECL(offsetof(struct tc_flower, rewrite)
+-                  + MEMBER_SIZEOF(struct tc_flower, rewrite)
+-                  + sizeof(uint32_t) - 2 < sizeof(struct tc_flower));
+-
+ int tc_replace_flower(struct tcf_id *id, struct tc_flower *flower);
+ int tc_del_filter(struct tcf_id *id);
+ int tc_get_flower(struct tcf_id *id, struct tc_flower *flower);
 diff --git a/lib/tnl-neigh-cache.c b/lib/tnl-neigh-cache.c
 index 5bda4af7e0..995c88bf17 100644
 --- a/lib/tnl-neigh-cache.c
@@ -6697,9 +7279,133 @@ index 7ef32d13cb..cb0e9df388 100755
  
      flows.write(struct.pack('>LH',
 diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
-index 956a69e1fa..df62bb9e8a 100644
+index 956a69e1fa..325723af19 100644
 --- a/tests/ofproto-dpif.at
 +++ b/tests/ofproto-dpif.at
+@@ -81,11 +81,12 @@ recirc_id(0),in_port(4),packet_type(ns=0,id=0),eth(src=50:54:00:00:00:0b,dst=ff:
+ 
+ ovs-appctl netdev-dummy/set-admin-state p1 up
+ ovs-appctl time/warp 100
+-OVS_WAIT_UNTIL([ovs-appctl bond/show | STRIP_RECIRC_ID | STRIP_ACTIVE_MEMBER_MAC], [0], [dnl
++OVS_WAIT_UNTIL_EQUAL([ovs-appctl bond/show | STRIP_RECIRC_ID | STRIP_ACTIVE_MEMBER_MAC], [dnl
+ ---- bond0 ----
+ bond_mode: active-backup
+ bond may use recirculation: no, <del>
+ bond-hash-basis: 0
++lb_output action: disabled, bond-id: -1
+ updelay: 0 ms
+ downdelay: 0 ms
+ lacp_status: off
+@@ -99,7 +100,6 @@ member p1: enabled
+ 
+ member p2: enabled
+   may_enable: true
+-
+ ])
+ 
+ OVS_VSWITCHD_STOP
+@@ -129,11 +129,12 @@ ovs-appctl time/warp 100
+ OVS_WAIT_UNTIL([test -n "`ovs-appctl bond/show | fgrep 'member p1: disabled'`"])
+ ovs-appctl netdev-dummy/set-admin-state p1 up
+ ovs-appctl time/warp 100
+-OVS_WAIT_UNTIL([ovs-appctl bond/show | STRIP_RECIRC_ID | STRIP_ACTIVE_MEMBER_MAC], [0], [dnl
++OVS_WAIT_UNTIL_EQUAL([ovs-appctl bond/show | STRIP_RECIRC_ID | STRIP_ACTIVE_MEMBER_MAC], [dnl
+ ---- bond0 ----
+ bond_mode: active-backup
+ bond may use recirculation: no, <del>
+ bond-hash-basis: 0
++lb_output action: disabled, bond-id: -1
+ updelay: 0 ms
+ downdelay: 0 ms
+ lacp_status: off
+@@ -150,7 +151,6 @@ member p2: enabled
+ 
+ member p3: enabled
+   may_enable: true
+-
+ ])
+ 
+ dnl Now delete the primary and verify that the output shows that the
+@@ -171,11 +171,12 @@ ovs-vsctl \
+    --id=@p1 create Interface name=p1 type=dummy options:pstream=punix:$OVS_RUNDIR/p1.sock ofport_request=1 -- \
+    set Port bond0 interfaces="$uuids, @p1]"
+ ovs-appctl time/warp 100
+-OVS_WAIT_UNTIL([ovs-appctl bond/show | STRIP_RECIRC_ID | STRIP_ACTIVE_MEMBER_MAC], [0], [dnl
++OVS_WAIT_UNTIL_EQUAL([ovs-appctl bond/show | STRIP_RECIRC_ID | STRIP_ACTIVE_MEMBER_MAC], [dnl
+ ---- bond0 ----
+ bond_mode: active-backup
+ bond may use recirculation: no, <del>
+ bond-hash-basis: 0
++lb_output action: disabled, bond-id: -1
+ updelay: 0 ms
+ downdelay: 0 ms
+ lacp_status: off
+@@ -192,17 +193,17 @@ member p2: enabled
+ 
+ member p3: enabled
+   may_enable: true
+-
+ ])
+ 
+ dnl Switch to another primary
+ ovs-vsctl set port bond0 other_config:bond-primary=p2
+ ovs-appctl time/warp 100
+-OVS_WAIT_UNTIL([ovs-appctl bond/show | STRIP_RECIRC_ID | STRIP_ACTIVE_MEMBER_MAC], [0], [dnl
++OVS_WAIT_UNTIL_EQUAL([ovs-appctl bond/show | STRIP_RECIRC_ID | STRIP_ACTIVE_MEMBER_MAC], [dnl
+ ---- bond0 ----
+ bond_mode: active-backup
+ bond may use recirculation: no, <del>
+ bond-hash-basis: 0
++lb_output action: disabled, bond-id: -1
+ updelay: 0 ms
+ downdelay: 0 ms
+ lacp_status: off
+@@ -211,25 +212,25 @@ active-backup primary: p2
+ <active member mac del>
+ 
+ member p1: enabled
+-  active member
+   may_enable: true
+ 
+ member p2: enabled
++  active member
+   may_enable: true
+ 
+ member p3: enabled
+   may_enable: true
+-
+ ])
+ 
+ dnl Remove the "bond-primary" config directive from the bond.
+ AT_CHECK([ovs-vsctl remove Port bond0 other_config bond-primary])
+ ovs-appctl time/warp 100
+-OVS_WAIT_UNTIL([ovs-appctl bond/show | STRIP_RECIRC_ID | STRIP_ACTIVE_MEMBER_MAC], [0], [dnl
++OVS_WAIT_UNTIL_EQUAL([ovs-appctl bond/show | STRIP_RECIRC_ID | STRIP_ACTIVE_MEMBER_MAC], [dnl
+ ---- bond0 ----
+ bond_mode: active-backup
+ bond may use recirculation: no, <del>
+ bond-hash-basis: 0
++lb_output action: disabled, bond-id: -1
+ updelay: 0 ms
+ downdelay: 0 ms
+ lacp_status: off
+@@ -238,15 +239,14 @@ active-backup primary: <none>
+ <active member mac del>
+ 
+ member p1: enabled
+-  active member
+   may_enable: true
+ 
+ member p2: enabled
++  active member
+   may_enable: true
+ 
+ member p3: enabled
+   may_enable: true
+-
+ ])
+ 
+ OVS_VSWITCHD_STOP
 @@ -4862,6 +4862,54 @@ recirc_id(0),in_port(90),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(proto=6,fr
  OVS_VSWITCHD_STOP
  AT_CLEANUP
@@ -6879,6 +7585,44 @@ index 956a69e1fa..df62bb9e8a 100644
  OVS_VSWITCHD_STOP
  AT_CLEANUP
  
+diff --git a/tests/ovs-macros.at b/tests/ovs-macros.at
+index 66545da572..e6c5bc6e94 100644
+--- a/tests/ovs-macros.at
++++ b/tests/ovs-macros.at
+@@ -259,7 +259,20 @@ dnl Executes shell COMMAND in a loop until it returns zero.  If COMMAND does
+ dnl not return zero within a reasonable time limit, executes the commands
+ dnl in IF-FAILED (if provided) and fails the test.
+ m4_define([OVS_WAIT_UNTIL],
+-  [OVS_WAIT([$1], [$2], [AT_LINE], [until $1])])
++  [AT_FAIL_IF([test "$#" -ge 3])
++   dnl The second argument should not be a number (confused with AT_CHECK ?).
++   AT_FAIL_IF([test "$#" -eq 2 && test "$2" -eq "$2" 2>/dev/null])
++   OVS_WAIT([$1], [$2], [AT_LINE], [until $1])])
++
++dnl OVS_WAIT_UNTIL_EQUAL(COMMAND, OUTPUT)
++dnl
++dnl Executes shell COMMAND in a loop until it returns zero and the output
++dnl equals OUTPUT.  If COMMAND does not return zero or a desired output within
++dnl a reasonable time limit, fails the test.
++m4_define([OVS_WAIT_UNTIL_EQUAL],
++  [AT_FAIL_IF([test "$#" -ge 3])
++   echo "$2" > wait_until_expected
++   OVS_WAIT_UNTIL([$1 | diff -u wait_until_expected - ])])
+ 
+ dnl OVS_WAIT_WHILE(COMMAND, [IF-FAILED])
+ dnl
+@@ -267,7 +280,10 @@ dnl Executes shell COMMAND in a loop until it returns nonzero.  If COMMAND does
+ dnl not return nonzero within a reasonable time limit, executes the commands
+ dnl in IF-FAILED (if provided) and fails the test.
+ m4_define([OVS_WAIT_WHILE],
+-  [OVS_WAIT([if $1; then return 1; else return 0; fi], [$2],
++  [AT_FAIL_IF([test "$#" -ge 3])
++   dnl The second argument should not be a number (confused with AT_CHECK ?).
++   AT_FAIL_IF([test "$#" -eq 2 && test "$2" -eq "$2" 2>/dev/null])
++   OVS_WAIT([if $1; then return 1; else return 0; fi], [$2],
+             [AT_LINE], [while $1])])
+ 
+ dnl OVS_APP_EXIT_AND_WAIT(DAEMON)
 diff --git a/tests/ovs-ofctl.at b/tests/ovs-ofctl.at
 index 604f15c2d1..c93cb9f16c 100644
 --- a/tests/ovs-ofctl.at
@@ -7406,8 +8150,26 @@ index e0e750fde5..512aa87d4c 100644
  AT_CHECK([ovs-appctl dpif-netdev/miniflow-parser-set autovalidator], [0], [dnl
  Miniflow extract implementation set to autovalidator.
  ])
+diff --git a/tests/system-route.at b/tests/system-route.at
+index 1714273e35..270956d13f 100644
+--- a/tests/system-route.at
++++ b/tests/system-route.at
+@@ -14,10 +14,9 @@ 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([ovs-appctl ovs/route/show | grep 'p1-route' | sort], [0], [dnl
+-Cached: 10.0.0.17/24 dev p1-route SRC 10.0.0.17
+-Cached: 10.0.0.17/32 dev p1-route SRC 10.0.0.17 local
+-])
++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 ip address.
+ AT_CHECK([ip addr del 10.0.0.17/24 dev p1-route], [0], [stdout])
 diff --git a/tests/system-traffic.at b/tests/system-traffic.at
-index f400cfabc9..852e2520b4 100644
+index f400cfabc9..5df4ec9166 100644
 --- a/tests/system-traffic.at
 +++ b/tests/system-traffic.at
 @@ -218,6 +218,7 @@ OVS_TRAFFIC_VSWITCHD_STOP
@@ -7705,6 +8467,15 @@ index f400cfabc9..852e2520b4 100644
  AT_SETUP([conntrack - resubmit to ct multiple times])
  CHECK_CONNTRACK()
  
+@@ -5817,7 +5979,7 @@ on_exit 'ovs-appctl revalidator/purge'
+ on_exit 'ovs-appctl dpif/dump-flows br0'
+ 
+ dnl Should work with the virtual IP address through NAT
+-for i in 1 2 3 4 5 6 7 8 9 10 11 12; do
++for i in $(seq 1 50); do
+     echo Request $i
+     NS_CHECK_EXEC([at_ns1], [wget 10.1.1.64 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
+ done
 diff --git a/tests/system-tso-macros.at b/tests/system-tso-macros.at
 index 406334f3e0..1a80047619 100644
 --- a/tests/system-tso-macros.at
@@ -8442,6 +9213,19 @@ index 48c5de9d19..6a597488e6 100644
  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])
+diff --git a/tests/tunnel.at b/tests/tunnel.at
+index b8ae7caa9b..fd482aa872 100644
+--- a/tests/tunnel.at
++++ b/tests/tunnel.at
+@@ -126,7 +126,7 @@ AT_CHECK([ovs-appctl dpif/show | tail -n +3], [0], [dnl
+ AT_CHECK([ovs-appctl dpctl/add-flow "tunnel(dst=1.1.1.1,src=3.3.3.200/255.255.255.0,tp_dst=123,tp_src=1,ttl=64),recirc_id(0),in_port(1),eth(),eth_type(0x0800),ipv4()" "2"])
+ 
+ AT_CHECK([ovs-appctl dpctl/dump-flows | tail -1], [0], [dnl
+-tunnel(src=3.3.3.200/255.255.255.0,dst=1.1.1.1,ttl=64,tp_src=1,tp_dst=123),recirc_id(0),in_port(1),eth_type(0x0800), packets:0, bytes:0, used:never, actions:2
++tunnel(src=3.3.3.200/255.255.255.0,dst=1.1.1.1,ttl=64,tp_src=1,tp_dst=123),recirc_id(0),in_port(1),eth(),eth_type(0x0800), packets:0, bytes:0, used:never, actions:2
+ ])
+ 
+ OVS_VSWITCHD_STOP
 diff --git a/utilities/ovs-ctl.in b/utilities/ovs-ctl.in
 index 71800795c0..e6e07f4763 100644
 --- a/utilities/ovs-ctl.in
diff --git a/SPECS/openvswitch2.16.spec b/SPECS/openvswitch2.16.spec
index 92308ec..0e9c491 100644
--- a/SPECS/openvswitch2.16.spec
+++ b/SPECS/openvswitch2.16.spec
@@ -57,7 +57,7 @@ Summary: Open vSwitch
 Group: System Environment/Daemons daemon/database/utilities
 URL: http://www.openvswitch.org/
 Version: 2.16.0
-Release: 62%{?dist}
+Release: 63%{?dist}
 
 # Nearly all of openvswitch is ASL 2.0.  The bugtool is LGPLv2+, and the
 # lib/sflow*.[ch] files are SISSL
@@ -699,6 +699,23 @@ exit 0
 %endif
 
 %changelog
+* Mon Mar 28 2022 Open vSwitch CI <ovs-ci@redhat.com> - 2.16.0-63
+- Merging upstream branch-2.16 [RH git: a3c48a5aeb]
+    Commit list:
+    c50a0f080d system-traffic.at: Fix flaky DNAT load balancing test.
+    9928344ea7 dpif-netdev: Keep orig_in_port as a field of the flow.
+    aee2e66287 tests: Fix incorrect usage of OVS_WAIT_UNTIL.
+    5881545bd0 odp-util: Fix output for tc to be equal to kernel.
+    4a80c322f9 netdev-offload-tc: Fix IP and port ranges in flower returns.
+    49e0bb72bc netdev-offload-tc: Fix use of ICMP values instead of masks defines.
+    0fb545c7d9 netdev-offload-tc: Always include conntrack information to tc.
+    13a3f57976 netdev-offload-tc: Check for valid netdev ifindex in flow_put.
+    6e72fd96d3 netdev-offload-tc: Set the correct VLAN_VID and VLAN_PCP masks.
+    e43157f303 netdev-offload-tc: Add debug logs on tc rule verify failures.
+    37297e7ee6 tc: Keep header rewrite actions order.
+    823be413ec dpdk: Use DPDK 20.11.4 release
+
+
 * Fri Mar 11 2022 Open vSwitch CI <ovs-ci@redhat.com> - 2.16.0-62
 - Merging upstream branch-2.16 [RH git: 561b178a3d]
     Commit list: