diff --git a/SOURCES/openvswitch-2.16.0.patch b/SOURCES/openvswitch-2.16.0.patch
index 7b9c0be..75d73e4 100644
--- a/SOURCES/openvswitch-2.16.0.patch
+++ b/SOURCES/openvswitch-2.16.0.patch
@@ -1,16 +1,67 @@
+diff --git a/.ci/linux-prepare.sh b/.ci/linux-prepare.sh
+index c55125cf78..e4d2a187e0 100755
+--- a/.ci/linux-prepare.sh
++++ b/.ci/linux-prepare.sh
+@@ -22,7 +22,6 @@ cd ..
+ 
+ pip3 install --disable-pip-version-check --user \
+     flake8 hacking sphinx pyOpenSSL wheel setuptools
+-pip3 install --user --upgrade docutils
+ pip3 install --user  'meson==0.47.1'
+ 
+ if [ "$M32" ]; then
+diff --git a/.cirrus.yml b/.cirrus.yml
+index 358f2ba256..480fea2421 100644
+--- a/.cirrus.yml
++++ b/.cirrus.yml
+@@ -5,7 +5,7 @@ freebsd_build_task:
+       image_family: freebsd-12-2-snap
+       image_family: freebsd-11-4-snap
+     cpu: 4
+-    memory: 8G
++    memory: 4G
+ 
+   env:
+     DEPENDENCIES: automake libtool gmake gcc wget openssl python3
+diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml
+index e2350c6d9d..7434ad18ec 100644
+--- a/.github/workflows/build-and-test.yml
++++ b/.github/workflows/build-and-test.yml
+@@ -127,7 +127,7 @@ jobs:
+     - name: set up python
+       uses: actions/setup-python@v2
+       with:
+-        python-version: '3.x'
++        python-version: '3.9'
+ 
+     - name: create ci signature file for the dpdk cache key
+       if:   matrix.dpdk != '' || matrix.dpdk_shared != ''
+@@ -215,7 +215,7 @@ jobs:
+     - name: set up python
+       uses: actions/setup-python@v2
+       with:
+-        python-version: '3.x'
++        python-version: '3.9'
+     - name: install dependencies
+       run:  brew install automake libtool
+     - name: prepare
 diff --git a/NEWS b/NEWS
-index 559a51ba3f..f2497d5cec 100644
+index 559a51ba3f..7a6de3da82 100644
 --- a/NEWS
 +++ b/NEWS
-@@ -1,3 +1,6 @@
-+v2.16.1 - xx xxx xxxx
+@@ -1,3 +1,10 @@
++v2.16.2 - xx xxx xxxx
 +---------------------
 +
++v2.16.1 - 21 Oct 2021
++---------------------
++   - Bug fixes
++
  v2.16.0 - 16 Aug 2021
  ---------------------
     - Removed support for 1024-bit Diffie-Hellman key exchange, which is now
 diff --git a/configure.ac b/configure.ac
-index 16b32be965..4def2ebd0a 100644
+index 16b32be965..64c26828f2 100644
 --- a/configure.ac
 +++ b/configure.ac
 @@ -13,7 +13,7 @@
@@ -18,20 +69,257 @@ index 16b32be965..4def2ebd0a 100644
  
  AC_PREREQ(2.63)
 -AC_INIT(openvswitch, 2.16.0, bugs@openvswitch.org)
-+AC_INIT(openvswitch, 2.16.1, bugs@openvswitch.org)
++AC_INIT(openvswitch, 2.16.2, bugs@openvswitch.org)
  AC_CONFIG_SRCDIR([datapath/datapath.c])
  AC_CONFIG_MACRO_DIR([m4])
  AC_CONFIG_AUX_DIR([build-aux])
+diff --git a/datapath-windows/ovsext/Actions.c b/datapath-windows/ovsext/Actions.c
+index e130c2f966..0c18c62548 100644
+--- a/datapath-windows/ovsext/Actions.c
++++ b/datapath-windows/ovsext/Actions.c
+@@ -1112,9 +1112,9 @@ OvsPopFieldInPacketBuf(OvsForwardingContext *ovsFwdCtx,
+      * should split the function and refactor. */
+     if (!bufferData) {
+         EthHdr *ethHdr = (EthHdr *)bufferStart;
+-        /* If the frame is not VLAN make it a no op */
+         if (ethHdr->Type != ETH_TYPE_802_1PQ_NBO) {
+-            return NDIS_STATUS_SUCCESS;
++            OVS_LOG_ERROR("Invalid ethHdr type %u, nbl %p", ethHdr->Type, ovsFwdCtx->curNbl);
++            return NDIS_STATUS_INVALID_PACKET;
+         }
+     }
+     RtlMoveMemory(bufferStart + shiftLength, bufferStart, shiftOffset);
+@@ -1137,6 +1137,9 @@ OvsPopFieldInPacketBuf(OvsForwardingContext *ovsFwdCtx,
+ static __inline NDIS_STATUS
+ OvsPopVlanInPktBuf(OvsForwardingContext *ovsFwdCtx)
+ {
++    NDIS_STATUS status;
++    OVS_PACKET_HDR_INFO* layers = &ovsFwdCtx->layers;
++
+     /*
+      * Declare a dummy vlanTag structure since we need to compute the size
+      * of shiftLength. The NDIS one is a unionized structure.
+@@ -1145,7 +1148,15 @@ OvsPopVlanInPktBuf(OvsForwardingContext *ovsFwdCtx)
+     UINT32 shiftLength = sizeof(vlanTag.TagHeader);
+     UINT32 shiftOffset = sizeof(DL_EUI48) + sizeof(DL_EUI48);
+ 
+-    return OvsPopFieldInPacketBuf(ovsFwdCtx, shiftOffset, shiftLength, NULL);
++    status = OvsPopFieldInPacketBuf(ovsFwdCtx, shiftOffset, shiftLength,
++                                    NULL);
++
++    if (status == NDIS_STATUS_SUCCESS) {
++        layers->l3Offset -= (UINT16) shiftLength;
++        layers->l4Offset -= (UINT16) shiftLength;
++    }
++
++    return status;
+ }
+ 
+ 
+@@ -1516,6 +1527,7 @@ OvsUpdateAddressAndPort(OvsForwardingContext *ovsFwdCtx,
+ 
+     csumInfo.Value = NET_BUFFER_LIST_INFO(ovsFwdCtx->curNbl,
+                                           TcpIpChecksumNetBufferListInfo);
++
+     /*
+      * Adjust the IP header inline as dictated by the action, and also update
+      * the IP and the TCP checksum for the data modified.
+@@ -1524,6 +1536,7 @@ OvsUpdateAddressAndPort(OvsForwardingContext *ovsFwdCtx,
+      * ChecksumUpdate32(). Ignoring this for now, since for the most common
+      * case, we only update the TTL.
+      */
++     /*Only tx direction the checksum value will be reset to be PseudoChecksum*/
+ 
+     if (isSource) {
+         addrField = &ipHdr->saddr;
+@@ -1540,7 +1553,7 @@ OvsUpdateAddressAndPort(OvsForwardingContext *ovsFwdCtx,
+                         ((BOOLEAN)csumInfo.Receive.UdpChecksumSucceeded ||
+                          (BOOLEAN)csumInfo.Receive.UdpChecksumFailed);
+         }
+-        if (l4Offload) {
++        if (isTx && l4Offload) {
+             *checkField = IPPseudoChecksum(&newAddr, &ipHdr->daddr,
+                 tcpHdr ? IPPROTO_TCP : IPPROTO_UDP,
+                 ntohs(ipHdr->tot_len) - ipHdr->ihl * 4);
+@@ -1561,7 +1574,7 @@ OvsUpdateAddressAndPort(OvsForwardingContext *ovsFwdCtx,
+                          (BOOLEAN)csumInfo.Receive.UdpChecksumFailed);
+         }
+ 
+-       if (l4Offload) {
++       if (isTx && l4Offload) {
+             *checkField = IPPseudoChecksum(&ipHdr->saddr, &newAddr,
+                 tcpHdr ? IPPROTO_TCP : IPPROTO_UDP,
+                 ntohs(ipHdr->tot_len) - ipHdr->ihl * 4);
+@@ -1570,7 +1583,7 @@ OvsUpdateAddressAndPort(OvsForwardingContext *ovsFwdCtx,
+ 
+     if (*addrField != newAddr) {
+         UINT32 oldAddr = *addrField;
+-        if (checkField && *checkField != 0 && !l4Offload) {
++        if ((checkField && *checkField != 0) && (!l4Offload || !isTx)) {
+             /* Recompute total checksum. */
+             *checkField = ChecksumUpdate32(*checkField, oldAddr,
+                                             newAddr);
+@@ -1579,11 +1592,12 @@ OvsUpdateAddressAndPort(OvsForwardingContext *ovsFwdCtx,
+             ipHdr->check = ChecksumUpdate32(ipHdr->check, oldAddr,
+                                             newAddr);
+         }
++
+         *addrField = newAddr;
+     }
+ 
+     if (portField && *portField != newPort) {
+-        if (checkField && !l4Offload) {
++        if ((checkField) && (!l4Offload || !isTx)) {
+             /* Recompute total checksum. */
+             *checkField = ChecksumUpdate16(*checkField, *portField,
+                                            newPort);
+@@ -1792,9 +1806,11 @@ OvsExecuteRecirc(OvsForwardingContext *ovsFwdCtx,
+     }
+ 
+     if (newNbl) {
+-        deferredAction = OvsAddDeferredActions(newNbl, key, NULL);
++        deferredAction = OvsAddDeferredActions(newNbl, key, &(ovsFwdCtx->layers),
++                                               NULL);
+     } else {
+-        deferredAction = OvsAddDeferredActions(ovsFwdCtx->curNbl, key, NULL);
++        deferredAction = OvsAddDeferredActions(ovsFwdCtx->curNbl, key,
++                                              &(ovsFwdCtx->layers), NULL);
+     }
+ 
+     if (deferredAction) {
+@@ -1964,7 +1980,7 @@ OvsExecuteSampleAction(OvsForwardingContext *ovsFwdCtx,
+         return STATUS_SUCCESS;
+     }
+ 
+-    if (!OvsAddDeferredActions(newNbl, key, a)) {
++    if (!OvsAddDeferredActions(newNbl, key, &(ovsFwdCtx->layers), a)) {
+         OVS_LOG_INFO(
+             "Deferred actions limit reached, dropping sample action.");
+         OvsCompleteNBL(ovsFwdCtx->switchContext, newNbl, TRUE);
+@@ -2100,6 +2116,7 @@ OvsDoExecuteActions(POVS_SWITCH_CONTEXT switchContext,
+                  */
+                 status = OvsPopVlanInPktBuf(&ovsFwdCtx);
+                 if (status != NDIS_STATUS_SUCCESS) {
++                    OVS_LOG_ERROR("OVS-pop vlan action failed status = %lu", status);
+                     dropReason = L"OVS-pop vlan action failed";
+                     goto dropit;
+                 }
+@@ -2349,7 +2366,7 @@ OvsActionsExecute(POVS_SWITCH_CONTEXT switchContext,
+ 
+     if (status == STATUS_SUCCESS) {
+         status = OvsProcessDeferredActions(switchContext, completionList,
+-                                           portNo, sendFlags, layers);
++                                           portNo, sendFlags, NULL);
+     }
+ 
+     return status;
+diff --git a/datapath-windows/ovsext/Recirc.c b/datapath-windows/ovsext/Recirc.c
+index 2febf060dd..a32b75352b 100644
+--- a/datapath-windows/ovsext/Recirc.c
++++ b/datapath-windows/ovsext/Recirc.c
+@@ -277,16 +277,23 @@ OvsDeferredActionsQueuePush(POVS_DEFERRED_ACTION_QUEUE queue)
+ POVS_DEFERRED_ACTION
+ OvsAddDeferredActions(PNET_BUFFER_LIST nbl,
+                       OvsFlowKey *key,
++                      POVS_PACKET_HDR_INFO layers,
+                       const PNL_ATTR actions)
+ {
+     POVS_DEFERRED_ACTION_QUEUE queue = OvsDeferredActionsQueueGet();
+     POVS_DEFERRED_ACTION deferredAction = NULL;
++    OVS_PACKET_HDR_INFO layersInit = { 0 };
+ 
+     deferredAction = OvsDeferredActionsQueuePush(queue);
+     if (deferredAction) {
+         deferredAction->nbl = nbl;
+         deferredAction->actions = actions;
+         deferredAction->key = *key;
++        if (layers) {
++            deferredAction->layers = *layers;
++        } else {
++            deferredAction->layers = layersInit;
++        }
+     }
+ 
+     return deferredAction;
+@@ -309,9 +316,16 @@ OvsProcessDeferredActions(POVS_SWITCH_CONTEXT switchContext,
+     NDIS_STATUS status = NDIS_STATUS_SUCCESS;
+     POVS_DEFERRED_ACTION_QUEUE queue = OvsDeferredActionsQueueGet();
+     POVS_DEFERRED_ACTION deferredAction = NULL;
++    POVS_PACKET_HDR_INFO layersDeferred = NULL;
+ 
+     /* Process all deferred actions. */
+     while ((deferredAction = OvsDeferredActionsQueuePop(queue)) != NULL) {
++        if (layers) {
++            layersDeferred = layers;
++         } else {
++            layersDeferred = &(deferredAction->layers);
++         }
++
+         if (deferredAction->actions) {
+             status = OvsDoExecuteActions(switchContext,
+                                          completionList,
+@@ -319,7 +333,7 @@ OvsProcessDeferredActions(POVS_SWITCH_CONTEXT switchContext,
+                                          portNo,
+                                          sendFlags,
+                                          &deferredAction->key, NULL,
+-                                         layers, deferredAction->actions,
++                                         layersDeferred, deferredAction->actions,
+                                          NlAttrGetSize(deferredAction->actions));
+         } else {
+             status = OvsDoRecirc(switchContext,
+@@ -327,7 +341,7 @@ OvsProcessDeferredActions(POVS_SWITCH_CONTEXT switchContext,
+                                  deferredAction->nbl,
+                                  &deferredAction->key,
+                                  portNo,
+-                                 layers);
++                                 layersDeferred);
+         }
+     }
+ 
+diff --git a/datapath-windows/ovsext/Recirc.h b/datapath-windows/ovsext/Recirc.h
+index 2b314ce274..74130a4600 100644
+--- a/datapath-windows/ovsext/Recirc.h
++++ b/datapath-windows/ovsext/Recirc.h
+@@ -18,6 +18,7 @@
+ #define __RECIRC_H_ 1
+ 
+ #include "Actions.h"
++#include "NetProto.h"
+ 
+ #define DEFERRED_ACTION_QUEUE_SIZE          10
+ #define DEFERRED_ACTION_EXEC_LEVEL           4
+@@ -26,6 +27,7 @@ typedef struct _OVS_DEFERRED_ACTION {
+     PNET_BUFFER_LIST    nbl;
+     PNL_ATTR            actions;
+     OvsFlowKey          key;
++    OVS_PACKET_HDR_INFO layers;
+ } OVS_DEFERRED_ACTION, *POVS_DEFERRED_ACTION;
+ 
+ /*
+@@ -52,6 +54,7 @@ OvsProcessDeferredActions(POVS_SWITCH_CONTEXT switchContext,
+ POVS_DEFERRED_ACTION
+ OvsAddDeferredActions(PNET_BUFFER_LIST packet,
+                       OvsFlowKey *key,
++                      POVS_PACKET_HDR_INFO layers,
+                       const PNL_ATTR actions);
+ 
+ /*
 diff --git a/debian/changelog b/debian/changelog
-index 239d210b96..0f521be4d8 100644
+index 239d210b96..a0838ea3d7 100644
 --- a/debian/changelog
 +++ b/debian/changelog
-@@ -1,3 +1,9 @@
+@@ -1,3 +1,15 @@
++openvswitch (2.16.2-1) unstable; urgency=low
++   [ Open vSwitch team ]
++   * New upstream version
++
++ -- Open vSwitch team <dev@openvswitch.org>  Thu, 21 Oct 2021 23:58:12 +0200
++
 +openvswitch (2.16.1-1) unstable; urgency=low
 +   [ Open vSwitch team ]
 +   * New upstream version
 +
-+ -- Open vSwitch team <dev@openvswitch.org>  Mon, 16 Aug 2021 22:08:13 +0200
++ -- Open vSwitch team <dev@openvswitch.org>  Thu, 21 Oct 2021 23:58:12 +0200
 +
  openvswitch (2.16.0-1) unstable; urgency=low
  
@@ -84,6 +372,116 @@ index 73b562e03d..0831a9cee1 100644
  struct json *json_from_file(const char *file_name);
  struct json *json_from_stream(FILE *stream);
  
+diff --git a/include/openvswitch/meta-flow.h b/include/openvswitch/meta-flow.h
+index 95e52e3587..045dce8f5f 100644
+--- a/include/openvswitch/meta-flow.h
++++ b/include/openvswitch/meta-flow.h
+@@ -2305,6 +2305,7 @@ void mf_set_flow_value_masked(const struct mf_field *,
+                               const union mf_value *mask,
+                               struct flow *);
+ bool mf_is_tun_metadata(const struct mf_field *);
++bool mf_is_frozen_metadata(const struct mf_field *);
+ bool mf_is_pipeline_field(const struct mf_field *);
+ bool mf_is_set(const struct mf_field *, const struct flow *);
+ void mf_mask_field(const struct mf_field *, struct flow_wildcards *);
+diff --git a/lib/db-ctl-base.c b/lib/db-ctl-base.c
+index 77cc76a9f6..7074561588 100644
+--- a/lib/db-ctl-base.c
++++ b/lib/db-ctl-base.c
+@@ -247,15 +247,15 @@ record_id_equals(const union ovsdb_atom *name, enum ovsdb_atomic_type type,
+                  const char *record_id)
+ {
+     if (type == OVSDB_TYPE_STRING) {
+-        if (!strcmp(name->string, record_id)) {
++        if (!strcmp(name->s->string, record_id)) {
+             return true;
+         }
+ 
+         struct uuid uuid;
+         size_t len = strlen(record_id);
+         if (len >= 4
+-            && uuid_from_string(&uuid, name->string)
+-            && !strncmp(name->string, record_id, len)) {
++            && uuid_from_string(&uuid, name->s->string)
++            && !strncmp(name->s->string, record_id, len)) {
+             return true;
+         }
+ 
+@@ -314,15 +314,19 @@ get_row_by_id(struct ctl_context *ctx,
+             row, id->name_column, key, value);
+ 
+         /* Extract the name from the column. */
+-        const union ovsdb_atom *name;
++        const union ovsdb_atom *name = NULL;
+         if (!id->key) {
+             name = datum->n == 1 ? &datum->keys[0] : NULL;
+         } else {
+-            const union ovsdb_atom key_atom
+-                = { .string = CONST_CAST(char *, id->key) };
+-            unsigned int i = ovsdb_datum_find_key(datum, &key_atom,
+-                                                  OVSDB_TYPE_STRING);
+-            name = i == UINT_MAX ? NULL : &datum->values[i];
++            union ovsdb_atom key_atom = {
++                .s = ovsdb_atom_string_create(CONST_CAST(char *, id->key)) };
++            unsigned int i;
++
++            if (ovsdb_datum_find_key(datum, &key_atom,
++                                     OVSDB_TYPE_STRING, &i)) {
++                name = &datum->values[i];
++            }
++            ovsdb_atom_destroy(&key_atom, OVSDB_TYPE_STRING);
+         }
+         if (!name) {
+             continue;
+@@ -819,14 +823,14 @@ check_condition(const struct ovsdb_idl_table_class *table,
+             goto out;
+         }
+ 
+-        idx = ovsdb_datum_find_key(have_datum,
+-                                   &want_key, column->type.key.type);
+-        if (idx == UINT_MAX && !is_set_operator(operator)) {
++        bool found = ovsdb_datum_find_key(have_datum, &want_key,
++                                          column->type.key.type, &idx);
++        if (!found && !is_set_operator(operator)) {
+             retval = false;
+         } else {
+             struct ovsdb_datum a;
+ 
+-            if (idx != UINT_MAX) {
++            if (found) {
+                 a.n = 1;
+                 a.keys = &have_datum->values[idx];
+                 a.values = NULL;
+@@ -992,9 +996,8 @@ cmd_get(struct ctl_context *ctx)
+                 return;
+             }
+ 
+-            idx = ovsdb_datum_find_key(datum, &key,
+-                                       column->type.key.type);
+-            if (idx == UINT_MAX) {
++            if (!ovsdb_datum_find_key(datum, &key,
++                                      column->type.key.type, &idx)) {
+                 if (must_exist) {
+                     ctl_error(
+                         ctx, "no key \"%s\" in %s record \"%s\" column %s",
+@@ -1375,7 +1378,7 @@ set_column(const struct ovsdb_idl_table_class *table,
+         ovsdb_atom_destroy(&value, column->type.value.type);
+ 
+         ovsdb_datum_union(&datum, ovsdb_idl_read(row, column),
+-                          &column->type, false);
++                          &column->type);
+         ovsdb_idl_txn_verify(row, column);
+         ovsdb_idl_txn_write(row, column, &datum);
+     } else {
+@@ -1514,7 +1517,7 @@ cmd_add(struct ctl_context *ctx)
+             ovsdb_datum_destroy(&old, &column->type);
+             return;
+         }
+-        ovsdb_datum_union(&old, &add, type, false);
++        ovsdb_datum_union(&old, &add, type);
+         ovsdb_datum_destroy(&add, type);
+     }
+     if (old.n > type->n_max) {
 diff --git a/lib/dp-packet.h b/lib/dp-packet.h
 index 08d93c2779..3dc582fbfd 100644
 --- a/lib/dp-packet.h
@@ -115,8 +513,54 @@ index 08d93c2779..3dc582fbfd 100644
  /* If 'b' contains at least 'offset + size' bytes of data, returns a pointer to
   * byte 'offset'.  Otherwise, returns a null pointer. */
  static inline void *
+diff --git a/lib/dpdk-stub.c b/lib/dpdk-stub.c
+index b7d577870d..fe24f9abdf 100644
+--- a/lib/dpdk-stub.c
++++ b/lib/dpdk-stub.c
+@@ -83,7 +83,7 @@ bool
+ dpdk_get_cpu_has_isa(const char *arch OVS_UNUSED,
+                      const char *feature OVS_UNUSED)
+ {
+-    VLOG_ERR_ONCE("DPDK not supported in this version of Open vSwitch, "
++    VLOG_DBG_ONCE("DPDK not supported in this version of Open vSwitch, "
+                   "cannot use CPU flag based optimizations");
+     return false;
+ }
+diff --git a/lib/dpif-netdev-private-dfc.h b/lib/dpif-netdev-private-dfc.h
+index 92092ebec9..3dfc91f0fe 100644
+--- a/lib/dpif-netdev-private-dfc.h
++++ b/lib/dpif-netdev-private-dfc.h
+@@ -59,7 +59,8 @@ extern "C" {
+  * Thread-safety
+  * =============
+  *
+- * Each pmd_thread has its own private exact match cache.
++ * Each pmd_thread has its own private exact match cache and signature match
++ * cache.
+  * If dp_netdev_input is not called from a pmd thread, a mutex is used.
+  */
+ 
+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
++++ b/lib/dpif-netdev-private-thread.h
+@@ -78,10 +78,10 @@ struct dp_netdev_pmd_thread {
+     struct ovs_refcount ref_cnt;    /* Every reference must be refcount'ed. */
+     struct cmap_node node;          /* In 'dp->poll_threads'. */
+ 
+-    /* Per thread exact-match cache.  Note, the instance for cpu core
+-     * NON_PMD_CORE_ID can be accessed by multiple threads, and thusly
+-     * need to be protected by 'non_pmd_mutex'.  Every other instance
+-     * will only be accessed by its own pmd thread. */
++    /* Per thread exact match cache and signature match cache.  Note, the
++     * instance for cpu core NON_PMD_CORE_ID can be accessed by multiple
++     * threads, and thusly need to be protected by 'non_pmd_mutex'.  Every
++     * other instance will only be accessed by its own pmd thread. */
+     OVS_ALIGNED_VAR(CACHE_LINE_SIZE) struct dfc_cache flow_cache;
+ 
+     /* Flow-Table and classifiers
 diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
-index bddce75b63..f9782b596f 100644
+index bddce75b63..d6bee2a5a9 100644
 --- a/lib/dpif-netdev.c
 +++ b/lib/dpif-netdev.c
 @@ -4061,7 +4061,10 @@ dpif_netdev_execute(struct dpif *dpif, struct dpif_execute *execute)
@@ -131,26 +575,143 @@ index bddce75b63..f9782b596f 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 +4074,14 @@ dpif_netdev_execute(struct dpif *dpif, struct dpif_execute *execute)
+@@ -4071,6 +4074,24 @@ dpif_netdev_execute(struct dpif *dpif, struct dpif_execute *execute)
          dp_netdev_pmd_unref(pmd);
      }
  
-+    if (dp_packet_batch_size(&pp)) {
++    if (dp_packet_batch_size(&pp) == 1) {
 +        /* Packet wasn't dropped during the execution.  Swapping content with
 +         * the original packet, because the caller might expect actions to
-+         * modify it. */
-+        dp_packet_swap(execute->packet, packet_clone);
++         * modify it.  Uisng the packet from a batch instead of 'packet_clone'
++         * because it maybe stolen and replaced by other packet, e.g. by
++         * the fragmentation engine. */
++        dp_packet_swap(execute->packet, pp.packets[0]);
++        dp_packet_delete_batch(&pp, true);
++    } else if (dp_packet_batch_size(&pp)) {
++        /* FIXME: We have more packets than expected.  Likely, we got IP
++         * fragments of the reassembled packet.  Dropping them here as we have
++         * no way to get them to the caller.  It might be that all the required
++         * actions with them are already executed, but it also might not be a
++         * case, e.g. if dpif_netdev_execute() called to execute a single
++         * tunnel push. */
 +        dp_packet_delete_batch(&pp, true);
 +    }
 +
      return 0;
  }
  
+diff --git a/lib/dpif-netlink.c b/lib/dpif-netlink.c
+index 34fc042373..5f4b60c5a6 100644
+--- a/lib/dpif-netlink.c
++++ b/lib/dpif-netlink.c
+@@ -84,6 +84,8 @@ enum { MAX_PORTS = USHRT_MAX };
+ #define EPOLLEXCLUSIVE (1u << 28)
+ #endif
+ 
++#define OVS_DP_F_UNSUPPORTED (1 << 31);
++
+ /* This PID is not used by the kernel datapath when using dispatch per CPU,
+  * but it is required to be set (not zero). */
+ #define DPIF_NETLINK_PER_CPU_PID UINT32_MAX
+@@ -382,36 +384,62 @@ dpif_netlink_open(const struct dpif_class *class OVS_UNUSED, const char *name,
+         dp_request.cmd = OVS_DP_CMD_SET;
+     }
+ 
+-    /* The Open vSwitch kernel module has two modes for dispatching upcalls:
+-     * per-vport and per-cpu.
+-     *
+-     * When dispatching upcalls per-vport, the kernel will
+-     * send the upcall via a Netlink socket that has been selected based on the
+-     * vport that received the packet that is causing the upcall.
+-     *
+-     * When dispatching upcall per-cpu, the kernel will send the upcall via
+-     * a Netlink socket that has been selected based on the cpu that received
+-     * the packet that is causing the upcall.
+-     *
+-     * First we test to see if the kernel module supports per-cpu dispatching
+-     * (the preferred method). If it does not support per-cpu dispatching, we
+-     * fall back to the per-vport dispatch mode.
++    /* Some older kernels will not reject unknown features. This will cause
++     * 'ovs-vswitchd' to incorrectly assume a feature is supported. In order to
++     * test for that, we attempt to set a feature that we know is not supported
++     * by any kernel. If this feature is not rejected, we can assume we are
++     * running on one of these older kernels.
+      */
+     dp_request.user_features |= OVS_DP_F_UNALIGNED;
+-    dp_request.user_features &= ~OVS_DP_F_VPORT_PIDS;
+-    dp_request.user_features |= OVS_DP_F_DISPATCH_UPCALL_PER_CPU;
++    dp_request.user_features |= OVS_DP_F_VPORT_PIDS;
++    dp_request.user_features |= OVS_DP_F_UNSUPPORTED;
+     error = dpif_netlink_dp_transact(&dp_request, &dp, &buf);
+     if (error) {
+-        dp_request.user_features &= ~OVS_DP_F_DISPATCH_UPCALL_PER_CPU;
++        /* The Open vSwitch kernel module has two modes for dispatching
++         * upcalls: per-vport and per-cpu.
++         *
++         * When dispatching upcalls per-vport, the kernel will
++         * send the upcall via a Netlink socket that has been selected based on
++         * the vport that received the packet that is causing the upcall.
++         *
++         * When dispatching upcall per-cpu, the kernel will send the upcall via
++         * a Netlink socket that has been selected based on the cpu that
++         * received the packet that is causing the upcall.
++         *
++         * First we test to see if the kernel module supports per-cpu
++         * dispatching (the preferred method). If it does not support per-cpu
++         * dispatching, we fall back to the per-vport dispatch mode.
++         */
++        dp_request.user_features &= ~OVS_DP_F_UNSUPPORTED;
++        dp_request.user_features |= OVS_DP_F_UNALIGNED;
++        dp_request.user_features &= ~OVS_DP_F_VPORT_PIDS;
++        dp_request.user_features |= OVS_DP_F_DISPATCH_UPCALL_PER_CPU;
++        error = dpif_netlink_dp_transact(&dp_request, &dp, &buf);
++        if (error) {
++            dp_request.user_features &= ~OVS_DP_F_DISPATCH_UPCALL_PER_CPU;
++            dp_request.user_features |= OVS_DP_F_VPORT_PIDS;
++            error = dpif_netlink_dp_transact(&dp_request, &dp, &buf);
++        }
++        if (error) {
++            return error;
++        }
++
++        error = open_dpif(&dp, dpifp);
++        dpif_netlink_set_features(*dpifp, OVS_DP_F_TC_RECIRC_SHARING);
++    } else {
++        VLOG_INFO("Kernel does not correctly support feature negotiation. "
++                  "Using standard features.");
++        dp_request.cmd = OVS_DP_CMD_SET;
++        dp_request.user_features = 0;
++        dp_request.user_features |= OVS_DP_F_UNALIGNED;
+         dp_request.user_features |= OVS_DP_F_VPORT_PIDS;
+         error = dpif_netlink_dp_transact(&dp_request, &dp, &buf);
+-    }
+-    if (error) {
+-        return error;
++        if (error) {
++            return error;
++        }
++        error = open_dpif(&dp, dpifp);
+     }
+ 
+-    error = open_dpif(&dp, dpifp);
+-    dpif_netlink_set_features(*dpifp, OVS_DP_F_TC_RECIRC_SHARING);
+     ofpbuf_delete(buf);
+ 
+     if (create) {
 diff --git a/lib/ipf.c b/lib/ipf.c
-index d9f781147a..665f40fefe 100644
+index d9f781147a..507db2aea2 100644
 --- a/lib/ipf.c
 +++ b/lib/ipf.c
-@@ -1152,52 +1152,56 @@ ipf_post_execute_reass_pkts(struct ipf *ipf,
+@@ -943,6 +943,8 @@ ipf_extract_frags_from_batch(struct ipf *ipf, struct dp_packet_batch *pb,
+             ovs_mutex_lock(&ipf->ipf_lock);
+             if (!ipf_handle_frag(ipf, pkt, dl_type, zone, now, hash_basis)) {
+                 dp_packet_batch_refill(pb, pkt, pb_idx);
++            } else {
++                dp_packet_delete(pkt);
+             }
+             ovs_mutex_unlock(&ipf->ipf_lock);
+         } else {
+@@ -1152,52 +1154,56 @@ ipf_post_execute_reass_pkts(struct ipf *ipf,
           * NETDEV_MAX_BURST. */
          DP_PACKET_BATCH_REFILL_FOR_EACH (pb_idx, pb_cnt, pkt, pb) {
              if (rp && pkt == rp->list->reass_execute_ctx) {
@@ -380,6 +941,30 @@ index 32d25003b8..0baf7c622c 100644
 +    }
      ds_put_char(ds, '"');
  }
+diff --git a/lib/meta-flow.c b/lib/meta-flow.c
+index c808d205d5..e03cd8d0c5 100644
+--- a/lib/meta-flow.c
++++ b/lib/meta-flow.c
+@@ -1788,6 +1788,19 @@ mf_is_tun_metadata(const struct mf_field *mf)
+            mf->id < MFF_TUN_METADATA0 + TUN_METADATA_NUM_OPTS;
+ }
+ 
++bool
++mf_is_frozen_metadata(const struct mf_field *mf)
++{
++    if (mf->id >= MFF_TUN_ID && mf->id <= MFF_IN_PORT_OXM) {
++        return true;
++    }
++
++    if (mf->id >= MFF_REG0 && mf->id < MFF_ETH_SRC) {
++        return true;
++    }
++    return false;
++}
++
+ bool
+ mf_is_pipeline_field(const struct mf_field *mf)
+ {
 diff --git a/lib/netdev-dpdk.c b/lib/netdev-dpdk.c
 index 45a96b9be2..ca92c947a2 100644
 --- a/lib/netdev-dpdk.c
@@ -453,6 +1038,743 @@ index 7729a90608..fbdfc7ad83 100644
              const struct nlattr *ma = nl_attr_find__(mask, mask_len,
                                                       OVS_KEY_ATTR_ETHERTYPE);
              if (ma) {
+diff --git a/lib/ovsdb-cs.c b/lib/ovsdb-cs.c
+index 659d49dbf7..fcb6fe1b34 100644
+--- a/lib/ovsdb-cs.c
++++ b/lib/ovsdb-cs.c
+@@ -1833,7 +1833,7 @@ server_column_get_string(const struct server_row *row,
+ {
+     ovs_assert(server_columns[index].type.key.type == OVSDB_TYPE_STRING);
+     const struct ovsdb_datum *d = &row->data[index];
+-    return d->n == 1 ? d->keys[0].string : default_value;
++    return d->n == 1 ? d->keys[0].s->string : default_value;
+ }
+ 
+ static bool
+diff --git a/lib/ovsdb-data.c b/lib/ovsdb-data.c
+index c145f5ad97..6654ed6deb 100644
+--- a/lib/ovsdb-data.c
++++ b/lib/ovsdb-data.c
+@@ -74,7 +74,7 @@ ovsdb_atom_init_default(union ovsdb_atom *atom, enum ovsdb_atomic_type type)
+         break;
+ 
+     case OVSDB_TYPE_STRING:
+-        atom->string = xmemdup("", 1);
++        atom->s = ovsdb_atom_string_create_nocopy(xmemdup("", 1));
+         break;
+ 
+     case OVSDB_TYPE_UUID:
+@@ -136,7 +136,7 @@ ovsdb_atom_is_default(const union ovsdb_atom *atom,
+         return atom->boolean == false;
+ 
+     case OVSDB_TYPE_STRING:
+-        return atom->string[0] == '\0';
++        return atom->s->string[0] == '\0';
+ 
+     case OVSDB_TYPE_UUID:
+         return uuid_is_zero(&atom->uuid);
+@@ -172,7 +172,8 @@ ovsdb_atom_clone(union ovsdb_atom *new, const union ovsdb_atom *old,
+         break;
+ 
+     case OVSDB_TYPE_STRING:
+-        new->string = xstrdup(old->string);
++        new->s = old->s;
++        new->s->n_refs++;
+         break;
+ 
+     case OVSDB_TYPE_UUID:
+@@ -214,7 +215,7 @@ ovsdb_atom_hash(const union ovsdb_atom *atom, enum ovsdb_atomic_type type,
+         return hash_boolean(atom->boolean, basis);
+ 
+     case OVSDB_TYPE_STRING:
+-        return hash_string(atom->string, basis);
++        return hash_string(atom->s->string, basis);
+ 
+     case OVSDB_TYPE_UUID:
+         return hash_int(uuid_hash(&atom->uuid), basis);
+@@ -246,7 +247,7 @@ ovsdb_atom_compare_3way(const union ovsdb_atom *a,
+         return a->boolean - b->boolean;
+ 
+     case OVSDB_TYPE_STRING:
+-        return strcmp(a->string, b->string);
++        return a->s == b->s ? 0 : strcmp(a->s->string, b->s->string);
+ 
+     case OVSDB_TYPE_UUID:
+         return uuid_compare_3way(&a->uuid, &b->uuid);
+@@ -404,7 +405,7 @@ ovsdb_atom_from_json__(union ovsdb_atom *atom,
+ 
+     case OVSDB_TYPE_STRING:
+         if (json->type == JSON_STRING) {
+-            atom->string = xstrdup(json->string);
++            atom->s = ovsdb_atom_string_create(json->string);
+             return NULL;
+         }
+         break;
+@@ -473,7 +474,7 @@ ovsdb_atom_to_json(const union ovsdb_atom *atom, enum ovsdb_atomic_type type)
+         return json_boolean_create(atom->boolean);
+ 
+     case OVSDB_TYPE_STRING:
+-        return json_string_create(atom->string);
++        return json_string_create(atom->s->string);
+ 
+     case OVSDB_TYPE_UUID:
+         return wrap_json("uuid", json_string_create_nocopy(
+@@ -551,14 +552,18 @@ ovsdb_atom_from_string__(union ovsdb_atom *atom,
+             if (s_len < 2 || s[s_len - 1] != '"') {
+                 return xasprintf("%s: missing quote at end of "
+                                  "quoted string", s);
+-            } else if (!json_string_unescape(s + 1, s_len - 2,
+-                                             &atom->string)) {
+-                char *error = xasprintf("%s: %s", s, atom->string);
+-                free(atom->string);
+-                return error;
++            } else {
++                char *res;
++                if (json_string_unescape(s + 1, s_len - 2, &res)) {
++                    atom->s = ovsdb_atom_string_create_nocopy(res);
++                } else {
++                    char *error = xasprintf("%s: %s", s, res);
++                    free(res);
++                    return error;
++                }
+             }
+         } else {
+-            atom->string = xstrdup(s);
++            atom->s = ovsdb_atom_string_create(s);
+         }
+         break;
+ 
+@@ -721,14 +726,14 @@ ovsdb_atom_to_string(const union ovsdb_atom *atom, enum ovsdb_atomic_type type,
+         break;
+ 
+     case OVSDB_TYPE_STRING:
+-        if (string_needs_quotes(atom->string)) {
++        if (string_needs_quotes(atom->s->string)) {
+             struct json json;
+ 
+             json.type = JSON_STRING;
+-            json.string = atom->string;
++            json.string = atom->s->string;
+             json_to_ds(&json, 0, out);
+         } else {
+-            ds_put_cstr(out, atom->string);
++            ds_put_cstr(out, atom->s->string);
+         }
+         break;
+ 
+@@ -750,7 +755,7 @@ ovsdb_atom_to_bare(const union ovsdb_atom *atom, enum ovsdb_atomic_type type,
+                    struct ds *out)
+ {
+     if (type == OVSDB_TYPE_STRING) {
+-        ds_put_cstr(out, atom->string);
++        ds_put_cstr(out, atom->s->string);
+     } else {
+         ovsdb_atom_to_string(atom, type, out);
+     }
+@@ -799,7 +804,7 @@ ovsdb_atom_check_constraints(const union ovsdb_atom *atom,
+                              const struct ovsdb_base_type *base)
+ {
+     if (base->enum_
+-        && ovsdb_datum_find_key(base->enum_, atom, base->type) == UINT_MAX) {
++        && !ovsdb_datum_find_key(base->enum_, atom, base->type, NULL)) {
+         struct ovsdb_error *error;
+         struct ds actual = DS_EMPTY_INITIALIZER;
+         struct ds valid = DS_EMPTY_INITIALIZER;
+@@ -877,7 +882,7 @@ ovsdb_atom_check_constraints(const union ovsdb_atom *atom,
+         return NULL;
+ 
+     case OVSDB_TYPE_STRING:
+-        return check_string_constraints(atom->string, &base->string);
++        return check_string_constraints(atom->s->string, &base->string);
+ 
+     case OVSDB_TYPE_UUID:
+         return NULL;
+@@ -1691,8 +1696,8 @@ ovsdb_datum_from_smap(struct ovsdb_datum *datum, const struct smap *smap)
+     struct smap_node *node;
+     size_t i = 0;
+     SMAP_FOR_EACH (node, smap) {
+-        datum->keys[i].string = xstrdup(node->key);
+-        datum->values[i].string = xstrdup(node->value);
++        datum->keys[i].s = ovsdb_atom_string_create(node->key);
++        datum->values[i].s = ovsdb_atom_string_create(node->value);
+         i++;
+     }
+     ovs_assert(i == datum->n);
+@@ -1784,14 +1789,16 @@ ovsdb_datum_compare_3way(const struct ovsdb_datum *a,
+                                        a->n));
+ }
+ 
+-/* If 'key' is one of the keys in 'datum', returns its index within 'datum',
+- * otherwise UINT_MAX.  'key.type' must be the type of the atoms stored in the
+- * 'keys' array in 'datum'.
++/* If 'key' is one of the keys in 'datum', returns 'true' and sets '*pos' to
++ * its index within 'datum', otherwise returns 'false' and sets '*pos' to the
++ * index where 'key' should have been.  'key.type' must be the type of the
++ * atoms stored in the 'keys' array in 'datum'.
+  */
+-unsigned int
++bool
+ ovsdb_datum_find_key(const struct ovsdb_datum *datum,
+                      const union ovsdb_atom *key,
+-                     enum ovsdb_atomic_type key_type)
++                     enum ovsdb_atomic_type key_type,
++                     unsigned int *pos)
+ {
+     unsigned int low = 0;
+     unsigned int high = datum->n;
+@@ -1803,10 +1810,16 @@ ovsdb_datum_find_key(const struct ovsdb_datum *datum,
+         } else if (cmp > 0) {
+             low = idx + 1;
+         } else {
+-            return idx;
++            if (pos) {
++                *pos = idx;
++            }
++            return true;
+         }
+     }
+-    return UINT_MAX;
++    if (pos) {
++        *pos = low;
++    }
++    return false;
+ }
+ 
+ /* If 'key' and 'value' is one of the key-value pairs in 'datum', returns its
+@@ -1821,10 +1834,11 @@ ovsdb_datum_find_key_value(const struct ovsdb_datum *datum,
+                            const union ovsdb_atom *value,
+                            enum ovsdb_atomic_type value_type)
+ {
+-    unsigned int idx = ovsdb_datum_find_key(datum, key, key_type);
+-    if (idx != UINT_MAX
+-        && value_type != OVSDB_TYPE_VOID
+-        && !ovsdb_atom_equals(&datum->values[idx], value, value_type)) {
++    unsigned int idx;
++
++    if (!ovsdb_datum_find_key(datum, key, key_type, &idx)
++        || (value_type != OVSDB_TYPE_VOID
++            && !ovsdb_atom_equals(&datum->values[idx], value, value_type))) {
+         idx = UINT_MAX;
+     }
+     return idx;
+@@ -1948,38 +1962,68 @@ ovsdb_datum_add_unsafe(struct ovsdb_datum *datum,
+     }
+ }
+ 
++/* Adds 'n' atoms starting from index 'start_idx' from 'src' to the end of
++ * 'dst'.  'dst' should have enough memory allocated to hold the additional
++ * 'n' atoms.  Atoms are not cloned, i.e. 'dst' will reference the same data.
++ * Caller also should take care of the result being sorted. */
++static void
++ovsdb_datum_push_unsafe(struct ovsdb_datum *dst,
++                        const struct ovsdb_datum *src,
++                        unsigned int start_idx, unsigned int n,
++                        const struct ovsdb_type *type)
++{
++    memcpy(&dst->keys[dst->n], &src->keys[start_idx], n * sizeof src->keys[0]);
++    if (type->value.type != OVSDB_TYPE_VOID) {
++        memcpy(&dst->values[dst->n], &src->values[start_idx],
++               n * sizeof src->values[0]);
++    }
++    dst->n += n;
++}
++
+ void
+ ovsdb_datum_union(struct ovsdb_datum *a, const struct ovsdb_datum *b,
+-                  const struct ovsdb_type *type, bool replace)
++                  const struct ovsdb_type *type)
+ {
+-    unsigned int n;
+-    size_t bi;
++    struct ovsdb_datum result;
++    unsigned int copied, pos;
+ 
+-    n = a->n;
+-    for (bi = 0; bi < b->n; bi++) {
+-        unsigned int ai;
++    ovsdb_datum_init_empty(&result);
+ 
+-        ai = ovsdb_datum_find_key(a, &b->keys[bi], type->key.type);
+-        if (ai == UINT_MAX) {
+-            if (n == a->n) {
+-                ovsdb_datum_reallocate(a, type, a->n + (b->n - bi));
+-            }
+-            ovsdb_atom_clone(&a->keys[n], &b->keys[bi], type->key.type);
+-            if (type->value.type != OVSDB_TYPE_VOID) {
+-                ovsdb_atom_clone(&a->values[n], &b->values[bi],
+-                                 type->value.type);
+-            }
+-            n++;
+-        } else if (replace && type->value.type != OVSDB_TYPE_VOID) {
+-            ovsdb_atom_destroy(&a->values[ai], type->value.type);
+-            ovsdb_atom_clone(&a->values[ai], &b->values[bi],
++    copied = 0;
++    for (size_t bi = 0; bi < b->n; bi++) {
++        if (ovsdb_datum_find_key(a, &b->keys[bi], type->key.type, &pos)) {
++            /* Atom with the same key already exists. */
++            continue;
++        }
++        if (!result.keys) {
++            ovsdb_datum_reallocate(&result, type, a->n + (b->n - bi));
++        }
++        if (pos > copied) {
++            /* Need to copy some atoms from 'a' first. */
++            ovsdb_datum_push_unsafe(&result, a, copied, pos - copied, type);
++            copied = pos;
++        }
++        /* Inserting new atom from 'b'. */
++        ovsdb_atom_clone(&result.keys[result.n], &b->keys[bi], type->key.type);
++        if (type->value.type != OVSDB_TYPE_VOID) {
++            ovsdb_atom_clone(&result.values[result.n], &b->values[bi],
+                              type->value.type);
+         }
++        result.n++;
+     }
+-    if (n != a->n) {
+-        a->n = n;
+-        ovs_assert(!ovsdb_datum_sort(a, type->key.type));
++    if (!result.keys) {
++        /* 'a' doesn't need to be changed. */
++        return;
++    }
++    if (a->n > copied) {
++        /* Copying remaining atoms. */
++        ovsdb_datum_push_unsafe(&result, a, copied, a->n - copied, type);
+     }
++    /* All atoms are copied now. */
++    a->n = 0;
++
++    ovsdb_datum_swap(&result, a);
++    ovsdb_datum_destroy(&result, type);
+ }
+ 
+ void
+@@ -1987,26 +2031,55 @@ ovsdb_datum_subtract(struct ovsdb_datum *a, const struct ovsdb_type *a_type,
+                      const struct ovsdb_datum *b,
+                      const struct ovsdb_type *b_type)
+ {
+-    bool changed = false;
+-    size_t i;
++    unsigned int *idx, ai;
++    size_t n_idx;
+ 
+     ovs_assert(a_type->key.type == b_type->key.type);
+     ovs_assert(a_type->value.type == b_type->value.type
+                || b_type->value.type == OVSDB_TYPE_VOID);
+ 
+-    /* XXX The big-O of this could easily be improved. */
+-    for (i = 0; i < a->n; ) {
+-        unsigned int idx = ovsdb_datum_find(a, i, b, b_type);
+-        if (idx != UINT_MAX) {
+-            changed = true;
+-            ovsdb_datum_remove_unsafe(a, i, a_type);
+-        } else {
+-            i++;
++    idx = xmalloc(b->n * sizeof *idx);
++    n_idx = 0;
++    for (size_t bi = 0; bi < b->n; bi++) {
++        ai = ovsdb_datum_find(b, bi, a, b_type);
++        if (ai == UINT_MAX) {
++            /* No such atom in 'a'. */
++            continue;
+         }
++        /* Not destroying right away since ovsdb_datum_find() will use them. */
++        idx[n_idx++] = ai;
+     }
+-    if (changed) {
+-        ovsdb_datum_sort_assert(a, a_type->key.type);
++    if (!n_idx) {
++        free(idx);
++        return;
++    }
++
++    struct ovsdb_datum result;
++
++    ovsdb_datum_init_empty(&result);
++    ovsdb_datum_reallocate(&result, a_type, a->n - n_idx);
++
++    unsigned int start_idx = 0;
++    for (size_t i = 0; i < n_idx; i++) {
++        ai = idx[i];
++
++        /* Destroying atom. */
++        ovsdb_atom_destroy(&a->keys[ai], a_type->key.type);
++        if (a_type->value.type != OVSDB_TYPE_VOID) {
++            ovsdb_atom_destroy(&a->values[ai], a_type->value.type);
++        }
++
++        /* Copy non-removed atoms from 'a' to result. */
++        ovsdb_datum_push_unsafe(&result, a, start_idx, ai - start_idx, a_type);
++        start_idx = idx[i] + 1;
+     }
++    /* Copying remaining atoms. */
++    ovsdb_datum_push_unsafe(&result, a, start_idx, a->n - start_idx, a_type);
++    a->n = 0;
++
++    ovsdb_datum_swap(&result, a);
++    ovsdb_datum_destroy(&result, a_type);
++    free(idx);
+ }
+ 
+ struct ovsdb_symbol_table *
+@@ -2067,6 +2140,64 @@ ovsdb_symbol_table_insert(struct ovsdb_symbol_table *symtab,
+ 
+ /* APIs for Generating and apply diffs.  */
+ 
++/* Find what needs to be added to and removed from 'old' to construct 'new'.
++ *
++ * The 'added' and 'removed' datums are always safe; the orders of keys are
++ * maintained since they are added in order.   */
++void
++ovsdb_datum_added_removed(struct ovsdb_datum *added,
++                          struct ovsdb_datum *removed,
++                          const struct ovsdb_datum *old,
++                          const struct ovsdb_datum *new,
++                          const struct ovsdb_type *type)
++{
++    size_t oi, ni;
++
++    ovsdb_datum_init_empty(added);
++    ovsdb_datum_init_empty(removed);
++    if (!ovsdb_type_is_composite(type)) {
++        ovsdb_datum_clone(removed, old, type);
++        ovsdb_datum_clone(added, new, type);
++        return;
++    }
++
++    /* Generate the diff in O(n) time. */
++    for (oi = ni = 0; oi < old->n && ni < new->n;) {
++        int c = ovsdb_atom_compare_3way(&old->keys[oi], &new->keys[ni],
++                                        type->key.type);
++        if (c < 0) {
++            ovsdb_datum_add_unsafe(removed, &old->keys[oi], &old->values[oi],
++                                   type, NULL);
++            oi++;
++        } else if (c > 0) {
++            ovsdb_datum_add_unsafe(added, &new->keys[ni], &new->values[ni],
++                                   type, NULL);
++            ni++;
++        } else {
++            if (type->value.type != OVSDB_TYPE_VOID &&
++                ovsdb_atom_compare_3way(&old->values[oi], &new->values[ni],
++                                        type->value.type)) {
++                ovsdb_datum_add_unsafe(removed, &old->keys[oi],
++                                       &old->values[oi], type, NULL);
++                ovsdb_datum_add_unsafe(added, &new->keys[ni], &new->values[ni],
++                                       type, NULL);
++            }
++            oi++; ni++;
++        }
++    }
++
++    for (; oi < old->n; oi++) {
++        ovsdb_datum_add_unsafe(removed, &old->keys[oi], &old->values[oi],
++                               type, NULL);
++    }
++
++    for (; ni < new->n; ni++) {
++        ovsdb_datum_add_unsafe(added, &new->keys[ni], &new->values[ni],
++                               type, NULL);
++    }
++}
++
++
+ /* Generate a difference ovsdb_dataum between 'old' and 'new'.
+  * 'new' can be regenerated by applying the difference to the 'old'.
+  *
+@@ -2127,6 +2258,106 @@ ovsdb_datum_diff(struct ovsdb_datum *diff,
+     }
+ }
+ 
++/* Apply 'diff' to 'a'.
++ *
++ * Return NULL if the 'a' is successfully updated, otherwise, return
++ * ovsdb_error. */
++struct ovsdb_error *
++ovsdb_datum_apply_diff_in_place(struct ovsdb_datum *a,
++                                const struct ovsdb_datum *diff,
++                                const struct ovsdb_type *type)
++{
++    struct ovsdb_error *error = NULL;
++    struct ovsdb_datum result;
++    size_t i, new_size;
++    unsigned int *idx, pos;
++    enum {
++        DIFF_OP_ADD,
++        DIFF_OP_REMOVE,
++        DIFF_OP_UPDATE,
++    } *operation;
++
++    if (!ovsdb_type_is_composite(type)) {
++        ovsdb_datum_destroy(a, type);
++        ovsdb_datum_clone(a, diff, type);
++        return NULL;
++    }
++
++    operation = xmalloc(diff->n * sizeof *operation);
++    idx = xmalloc(diff->n * sizeof *idx);
++    new_size = a->n;
++    for (i = 0; i < diff->n; i++) {
++        if (!ovsdb_datum_find_key(a, &diff->keys[i], type->key.type, &pos)) {
++            operation[i] = DIFF_OP_ADD;
++            new_size++;
++        } else if (type->value.type != OVSDB_TYPE_VOID
++                   && !ovsdb_atom_equals(&diff->values[i], &a->values[pos],
++                                         type->value.type)) {
++            operation[i] = DIFF_OP_UPDATE;
++        } else {
++            operation[i] = DIFF_OP_REMOVE;
++            new_size--;
++        }
++        idx[i] = pos;
++    }
++
++    /* Make sure member size of 'new' conforms to type. */
++    if (new_size < type->n_min || new_size > type->n_max) {
++        error = ovsdb_error(NULL, "Datum crated by diff has size error");
++        goto exit;
++    }
++
++    ovsdb_datum_init_empty(&result);
++    ovsdb_datum_reallocate(&result, type, new_size);
++
++    unsigned int copied = 0;
++    for (i = 0; i < diff->n; i++) {
++        pos = idx[i];
++
++        if (copied < pos) {
++            /* Copying all atoms that should go before the current one. */
++            ovsdb_datum_push_unsafe(&result, a, copied, pos - copied, type);
++            copied = pos;
++        }
++
++        switch (operation[i]) {
++        case DIFF_OP_UPDATE:
++        case DIFF_OP_ADD:
++            /* Inserting new atom from 'diff'. */
++            ovsdb_atom_clone(&result.keys[result.n],
++                             &diff->keys[i], type->key.type);
++            if (type->value.type != OVSDB_TYPE_VOID) {
++                ovsdb_atom_clone(&result.values[result.n],
++                                 &diff->values[i], type->value.type);
++            }
++            result.n++;
++            if (operation[i] != DIFF_OP_UPDATE) {
++                break;
++            }
++            /* fall through */
++
++        case DIFF_OP_REMOVE:
++            /* Destroying atom. */
++            ovsdb_atom_destroy(&a->keys[pos], type->key.type);
++            if (type->value.type != OVSDB_TYPE_VOID) {
++                ovsdb_atom_destroy(&a->values[pos], type->value.type);
++            }
++            copied++; /* Skipping removed atom. */
++            break;
++        }
++    }
++    /* Copying remaining atoms. */
++    ovsdb_datum_push_unsafe(&result, a, copied, a->n - copied, type);
++    a->n = 0;
++
++    ovsdb_datum_swap(&result, a);
++    ovsdb_datum_destroy(&result, type);
++exit:
++    free(operation);
++    free(idx);
++    return error;
++}
++
+ /* Apply 'diff' to 'old' to regenerate 'new'.
+  *
+  * Return NULL if the 'new' is successfully generated, otherwise, return
+diff --git a/lib/ovsdb-data.h b/lib/ovsdb-data.h
+index c5a80ee39f..f66ed3472c 100644
+--- a/lib/ovsdb-data.h
++++ b/lib/ovsdb-data.h
+@@ -20,6 +20,7 @@
+ #include "compiler.h"
+ #include "ovsdb-types.h"
+ #include "openvswitch/shash.h"
++#include "util.h"
+ 
+ #ifdef  __cplusplus
+ extern "C" {
+@@ -31,12 +32,33 @@ struct ds;
+ struct ovsdb_symbol_table;
+ struct smap;
+ 
++struct ovsdb_atom_string {
++    char *string;
++    size_t n_refs;
++};
++
++static inline struct ovsdb_atom_string *
++ovsdb_atom_string_create_nocopy(char *str)
++{
++    struct ovsdb_atom_string *s = xzalloc(sizeof *s);
++
++    s->string = str;
++    s->n_refs = 1;
++    return s;
++}
++
++static inline struct ovsdb_atom_string *
++ovsdb_atom_string_create(const char *str)
++{
++    return ovsdb_atom_string_create_nocopy(xstrdup(str));
++}
++
+ /* One value of an atomic type (given by enum ovs_atomic_type). */
+ union ovsdb_atom {
+     int64_t integer;
+     double real;
+     bool boolean;
+-    char *string;
++    struct ovsdb_atom_string *s;
+     struct uuid uuid;
+ };
+ 
+@@ -66,8 +88,9 @@ ovsdb_atom_needs_destruction(enum ovsdb_atomic_type type)
+ static inline void
+ ovsdb_atom_destroy(union ovsdb_atom *atom, enum ovsdb_atomic_type type)
+ {
+-    if (type == OVSDB_TYPE_STRING) {
+-        free(atom->string);
++    if (type == OVSDB_TYPE_STRING && !--atom->s->n_refs) {
++        free(atom->s->string);
++        free(atom->s);
+     }
+ }
+ 
+@@ -209,9 +232,10 @@ bool ovsdb_datum_equals(const struct ovsdb_datum *,
+                         const struct ovsdb_type *);
+ 
+ /* Search. */
+-unsigned int ovsdb_datum_find_key(const struct ovsdb_datum *,
+-                                  const union ovsdb_atom *key,
+-                                  enum ovsdb_atomic_type key_type);
++bool ovsdb_datum_find_key(const struct ovsdb_datum *,
++                          const union ovsdb_atom *key,
++                          enum ovsdb_atomic_type key_type,
++                          unsigned int *pos);
+ unsigned int ovsdb_datum_find_key_value(const struct ovsdb_datum *,
+                                         const union ovsdb_atom *key,
+                                         enum ovsdb_atomic_type key_type,
+@@ -227,14 +251,19 @@ bool ovsdb_datum_excludes_all(const struct ovsdb_datum *,
+                               const struct ovsdb_type *);
+ void ovsdb_datum_union(struct ovsdb_datum *,
+                        const struct ovsdb_datum *,
+-                       const struct ovsdb_type *,
+-                       bool replace);
++                       const struct ovsdb_type *);
+ void ovsdb_datum_subtract(struct ovsdb_datum *a,
+                           const struct ovsdb_type *a_type,
+                           const struct ovsdb_datum *b,
+                           const struct ovsdb_type *b_type);
+ 
+ /* Generate and apply diffs */
++void ovsdb_datum_added_removed(struct ovsdb_datum *added,
++                               struct ovsdb_datum *removed,
++                               const struct ovsdb_datum *old,
++                               const struct ovsdb_datum *new,
++                               const struct ovsdb_type *type);
++
+ void ovsdb_datum_diff(struct ovsdb_datum *diff,
+                       const struct ovsdb_datum *old_datum,
+                       const struct ovsdb_datum *new_datum,
+@@ -246,6 +275,12 @@ struct ovsdb_error *ovsdb_datum_apply_diff(struct ovsdb_datum *new_datum,
+                                            const struct ovsdb_type *type)
+ OVS_WARN_UNUSED_RESULT;
+ 
++struct ovsdb_error * ovsdb_datum_apply_diff_in_place(
++                struct ovsdb_datum *a,
++                const struct ovsdb_datum *diff,
++                const struct ovsdb_type *type)
++OVS_WARN_UNUSED_RESULT;
++
+ /* Raw operations that may not maintain the invariants. */
+ void ovsdb_datum_remove_unsafe(struct ovsdb_datum *, size_t idx,
+                                const struct ovsdb_type *);
+diff --git a/lib/ovsdb-idl.c b/lib/ovsdb-idl.c
+index 2198c69c60..198ba5a828 100644
+--- a/lib/ovsdb-idl.c
++++ b/lib/ovsdb-idl.c
+@@ -1898,8 +1898,7 @@ ovsdb_idl_index_destroy_row(const struct ovsdb_idl_row *row_)
+     BITMAP_FOR_EACH_1 (i, class->n_columns, row->written) {
+         c = &class->columns[i];
+         (c->unparse) (row);
+-        free(row->new_datum[i].values);
+-        free(row->new_datum[i].keys);
++        ovsdb_datum_destroy(&row->new_datum[i], &c->type);
+     }
+     free(row->new_datum);
+     free(row->written);
+@@ -2787,9 +2786,8 @@ ovsdb_idl_txn_extract_mutations(struct ovsdb_idl_row *row,
+                     struct ovsdb_datum *new_datum;
+                     unsigned int pos;
+                     new_datum = map_op_datum(map_op);
+-                    pos = ovsdb_datum_find_key(old_datum,
+-                                               &new_datum->keys[0],
+-                                               key_type);
++                    ovsdb_datum_find_key(old_datum, &new_datum->keys[0],
++                                         key_type, &pos);
+                     if (ovsdb_atom_equals(&new_datum->values[0],
+                                           &old_datum->values[pos],
+                                           value_type)) {
+@@ -2798,11 +2796,9 @@ ovsdb_idl_txn_extract_mutations(struct ovsdb_idl_row *row,
+                     }
+                 } else if (map_op_type(map_op) == MAP_OP_DELETE){
+                     /* Verify that there is a key to delete. */
+-                    unsigned int pos;
+-                    pos = ovsdb_datum_find_key(old_datum,
+-                                               &map_op_datum(map_op)->keys[0],
+-                                               key_type);
+-                    if (pos == UINT_MAX) {
++                    if (!ovsdb_datum_find_key(old_datum,
++                                              &map_op_datum(map_op)->keys[0],
++                                              key_type, NULL)) {
+                         /* No key to delete.  Move on to next update. */
+                         VLOG_WARN("Trying to delete a key that doesn't "
+                                   "exist in the map.");
+@@ -2897,11 +2893,9 @@ ovsdb_idl_txn_extract_mutations(struct ovsdb_idl_row *row,
+                     any_ins = true;
+                 } else { /* SETP_OP_DELETE */
+                     /* Verify that there is a key to delete. */
+-                    unsigned int pos;
+-                    pos = ovsdb_datum_find_key(old_datum,
+-                                               &set_op_datum(set_op)->keys[0],
+-                                               key_type);
+-                    if (pos == UINT_MAX) {
++                    if (!ovsdb_datum_find_key(old_datum,
++                                              &set_op_datum(set_op)->keys[0],
++                                              key_type, NULL)) {
+                         /* No key to delete.  Move on to next update. */
+                         VLOG_WARN("Trying to delete a key that doesn't "
+                                   "exist in the set.");
+@@ -4066,7 +4060,6 @@ ovsdb_idl_txn_write_partial_map(const struct ovsdb_idl_row *row_,
+     struct ovsdb_idl_row *row = CONST_CAST(struct ovsdb_idl_row *, row_);
+     enum ovsdb_atomic_type key_type;
+     enum map_op_type op_type;
+-    unsigned int pos;
+     const struct ovsdb_datum *old_datum;
+ 
+     if (!is_valid_partial_update(row, column, datum)) {
+@@ -4078,8 +4071,11 @@ ovsdb_idl_txn_write_partial_map(const struct ovsdb_idl_row *row_,
+     /* Find out if this is an insert or an update. */
+     key_type = column->type.key.type;
+     old_datum = ovsdb_idl_read(row, column);
+-    pos = ovsdb_datum_find_key(old_datum, &datum->keys[0], key_type);
+-    op_type = pos == UINT_MAX ? MAP_OP_INSERT : MAP_OP_UPDATE;
++    if (ovsdb_datum_find_key(old_datum, &datum->keys[0], key_type, NULL)) {
++        op_type = MAP_OP_UPDATE;
++    } else {
++        op_type = MAP_OP_INSERT;
++    }
+ 
+     ovsdb_idl_txn_add_map_op(row, column, datum, op_type);
+ }
 diff --git a/lib/pcap-file.c b/lib/pcap-file.c
 index b30a11c24b..41835f6f4d 100644
 --- a/lib/pcap-file.c
@@ -465,6 +1787,86 @@ index b30a11c24b..41835f6f4d 100644
          return NULL;
      }
  
+diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
+index a426fcfeb6..1bab22aa5e 100644
+--- a/ofproto/ofproto-dpif-xlate.c
++++ b/ofproto/ofproto-dpif-xlate.c
+@@ -6177,11 +6177,32 @@ static void
+ compose_conntrack_action(struct xlate_ctx *ctx, struct ofpact_conntrack *ofc,
+                          bool is_last_action)
+ {
+-    ovs_u128 old_ct_label_mask = ctx->wc->masks.ct_label;
+-    uint32_t old_ct_mark_mask = ctx->wc->masks.ct_mark;
+-    size_t ct_offset;
+     uint16_t zone;
++    if (ofc->zone_src.field) {
++        union mf_subvalue value;
++        memset(&value, 0xff, sizeof(value));
++
++        zone = mf_get_subfield(&ofc->zone_src, &ctx->xin->flow);
++        if (ctx->xin->frozen_state) {
++            /* If the upcall is a resume of a recirculation, we only need to
++             * unwildcard the fields that are not in the frozen_metadata, as
++             * when the rules update, OVS will generate a new recirc_id,
++             * which will invalidate the megaflow with old the recirc_id.
++             */
++            if (!mf_is_frozen_metadata(ofc->zone_src.field)) {
++                mf_write_subfield_flow(&ofc->zone_src, &value,
++                                       &ctx->wc->masks);
++            }
++        } else {
++            mf_write_subfield_flow(&ofc->zone_src, &value, &ctx->wc->masks);
++        }
++    } else {
++        zone = ofc->zone_imm;
++    }
+ 
++    size_t ct_offset;
++    ovs_u128 old_ct_label_mask = ctx->wc->masks.ct_label;
++    uint32_t old_ct_mark_mask = ctx->wc->masks.ct_mark;
+     /* Ensure that any prior actions are applied before composing the new
+      * conntrack action. */
+     xlate_commit_actions(ctx);
+@@ -6193,11 +6214,6 @@ compose_conntrack_action(struct xlate_ctx *ctx, struct ofpact_conntrack *ofc,
+     do_xlate_actions(ofc->actions, ofpact_ct_get_action_len(ofc), ctx,
+                      is_last_action, false);
+ 
+-    if (ofc->zone_src.field) {
+-        zone = mf_get_subfield(&ofc->zone_src, &ctx->xin->flow);
+-    } else {
+-        zone = ofc->zone_imm;
+-    }
+ 
+     ct_offset = nl_msg_start_nested(ctx->odp_actions, OVS_ACTION_ATTR_CT);
+     if (ofc->flags & NX_CT_F_COMMIT) {
+diff --git a/ovsdb/file.c b/ovsdb/file.c
+index 59220824fa..9f44007d97 100644
+--- a/ovsdb/file.c
++++ b/ovsdb/file.c
+@@ -113,19 +113,17 @@ ovsdb_file_update_row_from_json(struct ovsdb_row *row, bool converting,
+         if (row_contains_diff
+             && !ovsdb_datum_is_default(&row->fields[column->index],
+                                        &column->type)) {
+-            struct ovsdb_datum new_datum;
+-
+-            error = ovsdb_datum_apply_diff(&new_datum,
++            error = ovsdb_datum_apply_diff_in_place(
+                                            &row->fields[column->index],
+                                            &datum, &column->type);
+             ovsdb_datum_destroy(&datum, &column->type);
+             if (error) {
+                 return error;
+             }
+-            ovsdb_datum_swap(&datum, &new_datum);
++        } else {
++            ovsdb_datum_swap(&row->fields[column->index], &datum);
++            ovsdb_datum_destroy(&datum, &column->type);
+         }
+-        ovsdb_datum_swap(&row->fields[column->index], &datum);
+-        ovsdb_datum_destroy(&datum, &column->type);
+     }
+ 
+     return NULL;
 diff --git a/ovsdb/monitor.c b/ovsdb/monitor.c
 index 532dedcb64..ab814cf20e 100644
 --- a/ovsdb/monitor.c
@@ -485,6 +1887,338 @@ index 532dedcb64..ab814cf20e 100644
                      ovsdb_monitor_json_cache_insert(dbmon, version, mcs,
                                                      json);
                  }
+diff --git a/ovsdb/mutation.c b/ovsdb/mutation.c
+index 56edc5f000..03d1c3499e 100644
+--- a/ovsdb/mutation.c
++++ b/ovsdb/mutation.c
+@@ -383,7 +383,7 @@ ovsdb_mutation_set_execute(struct ovsdb_row *row,
+             break;
+ 
+         case OVSDB_M_INSERT:
+-            ovsdb_datum_union(dst, arg, dst_type, false);
++            ovsdb_datum_union(dst, arg, dst_type);
+             error = ovsdb_mutation_check_count(dst, dst_type);
+             break;
+ 
+diff --git a/ovsdb/ovsdb-idlc.in b/ovsdb/ovsdb-idlc.in
+index 61cded16d3..78ebf2dc14 100755
+--- a/ovsdb/ovsdb-idlc.in
++++ b/ovsdb/ovsdb-idlc.in
+@@ -551,20 +551,20 @@ static void
+                 print("    smap_init(&row->%s);" % columnName)
+                 print("    for (size_t i = 0; i < datum->n; i++) {")
+                 print("        smap_add(&row->%s," % columnName)
+-                print("                 datum->keys[i].string,")
+-                print("                 datum->values[i].string);")
++                print("                 datum->keys[i].s->string,")
++                print("                 datum->values[i].s->string);")
+                 print("    }")
+             elif (type.n_min == 1 and type.n_max == 1) or type.is_optional_pointer():
+                 print("")
+                 print("    if (datum->n >= 1) {")
+                 if not type.key.ref_table:
+-                    print("        %s = datum->keys[0].%s;" % (keyVar, type.key.type.to_string()))
++                    print("        %s = datum->keys[0].%s;" % (keyVar, type.key.type.to_rvalue_string()))
+                 else:
+                     print("        %s = %s%s_cast(ovsdb_idl_get_row_arc(row_, &%stable_%s, &datum->keys[0].uuid));" % (keyVar, prefix, type.key.ref_table.name.lower(), prefix, type.key.ref_table.name.lower()))
+ 
+                 if valueVar:
+                     if not type.value.ref_table:
+-                        print("        %s = datum->values[0].%s;" % (valueVar, type.value.type.to_string()))
++                        print("        %s = datum->values[0].%s;" % (valueVar, type.value.type.to_rvalue_string()))
+                     else:
+                         print("        %s = %s%s_cast(ovsdb_idl_get_row_arc(row_, &%stable_%s, &datum->values[0].uuid));" % (valueVar, prefix, type.value.ref_table.name.lower(), prefix, type.value.ref_table.name.lower()))
+                 print("    } else {")
+@@ -592,7 +592,7 @@ static void
+ """ % (prefix, type.key.ref_table.name.lower(), prefix, type.key.ref_table.name.lower(), prefix, type.key.ref_table.name.lower()))
+                     keySrc = "keyRow"
+                 else:
+-                    keySrc = "datum->keys[i].%s" % type.key.type.to_string()
++                    keySrc = "datum->keys[i].%s" % type.key.type.to_rvalue_string()
+                 if type.value and type.value.ref_table:
+                     print("""\
+         struct %s%s *valueRow = %s%s_cast(ovsdb_idl_get_row_arc(row_, &%stable_%s, &datum->values[i].uuid));
+@@ -602,7 +602,7 @@ static void
+ """ % (prefix, type.value.ref_table.name.lower(), prefix, type.value.ref_table.name.lower(), prefix, type.value.ref_table.name.lower()))
+                     valueSrc = "valueRow"
+                 elif valueVar:
+-                    valueSrc = "datum->values[i].%s" % type.value.type.to_string()
++                    valueSrc = "datum->values[i].%s" % type.value.type.to_rvalue_string()
+                 print("        if (!row->n_%s) {" % (columnName))
+ 
+                 print("            %s = xmalloc(%s * sizeof *%s);" % (
+@@ -910,45 +910,45 @@ void
+         'args': ', '.join(['%(type)s%(name)s'
+                            % m for m in members])})
+             if type.n_min == 1 and type.n_max == 1:
+-                print("    union ovsdb_atom key;")
++                print("    union ovsdb_atom *key = xmalloc(sizeof *key);")
+                 if type.value:
+-                    print("    union ovsdb_atom value;")
++                    print("    union ovsdb_atom *value = xmalloc(sizeof *value);")
+                 print("")
+                 print("    datum.n = 1;")
+-                print("    datum.keys = &key;")
+-                print("    " + type.key.assign_c_value_casting_away_const("key.%s" % type.key.type.to_string(), keyVar))
++                print("    datum.keys = key;")
++                print("    " + type.key.copyCValue("key->%s" % type.key.type.to_lvalue_string(), keyVar))
+                 if type.value:
+-                    print("    datum.values = &value;")
+-                    print("    "+ type.value.assign_c_value_casting_away_const("value.%s" % type.value.type.to_string(), valueVar))
++                    print("    datum.values = value;")
++                    print("    " + type.value.copyCValue("value->%s" % type.value.type.to_lvalue_string(), valueVar))
+                 else:
+                     print("    datum.values = NULL;")
+-                txn_write_func = "ovsdb_idl_txn_write_clone"
++                txn_write_func = "ovsdb_idl_txn_write"
+             elif type.is_optional_pointer():
+-                print("    union ovsdb_atom key;")
+                 print("")
+                 print("    if (%s) {" % keyVar)
++                print("        union ovsdb_atom *key = xmalloc(sizeof *key);")
+                 print("        datum.n = 1;")
+-                print("        datum.keys = &key;")
+-                print("        " + type.key.assign_c_value_casting_away_const("key.%s" % type.key.type.to_string(), keyVar))
++                print("        datum.keys = key;")
++                print("        " + type.key.copyCValue("key->%s" % type.key.type.to_lvalue_string(), keyVar))
+                 print("    } else {")
+                 print("        datum.n = 0;")
+                 print("        datum.keys = NULL;")
+                 print("    }")
+                 print("    datum.values = NULL;")
+-                txn_write_func = "ovsdb_idl_txn_write_clone"
++                txn_write_func = "ovsdb_idl_txn_write"
+             elif type.n_max == 1:
+-                print("    union ovsdb_atom key;")
+                 print("")
+                 print("    if (%s) {" % nVar)
++                print("        union ovsdb_atom *key = xmalloc(sizeof *key);")
+                 print("        datum.n = 1;")
+-                print("        datum.keys = &key;")
+-                print("        " + type.key.assign_c_value_casting_away_const("key.%s" % type.key.type.to_string(), "*" + keyVar))
++                print("        datum.keys = key;")
++                print("        " + type.key.copyCValue("key->%s" % type.key.type.to_lvalue_string(), "*" + keyVar))
+                 print("    } else {")
+                 print("        datum.n = 0;")
+                 print("        datum.keys = NULL;")
+                 print("    }")
+                 print("    datum.values = NULL;")
+-                txn_write_func = "ovsdb_idl_txn_write_clone"
++                txn_write_func = "ovsdb_idl_txn_write"
+             else:
+                 print("")
+                 print("    datum.n = %s;" % nVar)
+@@ -958,9 +958,9 @@ void
+                 else:
+                     print("    datum.values = NULL;")
+                 print("    for (size_t i = 0; i < %s; i++) {" % nVar)
+-                print("        " + type.key.copyCValue("datum.keys[i].%s" % type.key.type.to_string(), "%s[i]" % keyVar))
++                print("        " + type.key.copyCValue("datum.keys[i].%s" % type.key.type.to_lvalue_string(), "%s[i]" % keyVar))
+                 if type.value:
+-                    print("        " + type.value.copyCValue("datum.values[i].%s" % type.value.type.to_string(), "%s[i]" % valueVar))
++                    print("        " + type.value.copyCValue("datum.values[i].%s" % type.value.type.to_lvalue_string(), "%s[i]" % valueVar))
+                 print("    }")
+                 if type.value:
+                     valueType = type.value.toAtomicType()
+@@ -996,9 +996,8 @@ void
+ ''' % {'s': structName, 'c': columnName,'coltype':column.type.key.to_const_c_type(prefix),
+         'valtype':column.type.value.to_const_c_type(prefix), 'S': structName.upper(),
+         'C': columnName.upper(), 't': tableName})
+-
+-                print("    "+ type.key.copyCValue("datum->keys[0].%s" % type.key.type.to_string(), "new_key"))
+-                print("    "+ type.value.copyCValue("datum->values[0].%s" % type.value.type.to_string(), "new_value"))
++                print("    " + type.key.copyCValue("datum->keys[0].%s" % type.key.type.to_lvalue_string(), "new_key"))
++                print("    " + type.value.copyCValue("datum->values[0].%s" % type.value.type.to_lvalue_string(), "new_value"))
+                 print('''
+     ovsdb_idl_txn_write_partial_map(&row->header_,
+                                     &%(s)s_col_%(c)s,
+@@ -1022,8 +1021,7 @@ void
+ ''' % {'s': structName, 'c': columnName,'coltype':column.type.key.to_const_c_type(prefix),
+         'valtype':column.type.value.to_const_c_type(prefix), 'S': structName.upper(),
+         'C': columnName.upper(), 't': tableName})
+-
+-                print("    "+ type.key.copyCValue("datum->keys[0].%s" % type.key.type.to_string(), "delete_key"))
++                print("    " + type.key.copyCValue("datum->keys[0].%s" % type.key.type.to_lvalue_string(), "delete_key"))
+                 print('''
+     ovsdb_idl_txn_delete_partial_map(&row->header_,
+                                     &%(s)s_col_%(c)s,
+@@ -1049,8 +1047,7 @@ void
+     datum->values = NULL;
+ ''' % {'s': structName, 'c': columnName,
+         'valtype':column.type.key.to_const_c_type(prefix), 't': tableName})
+-
+-                print("    "+ type.key.copyCValue("datum->keys[0].%s" % type.key.type.to_string(), "new_value"))
++                print("    " + type.key.copyCValue("datum->keys[0].%s" % type.key.type.to_lvalue_string(), "new_value"))
+                 print('''
+     ovsdb_idl_txn_write_partial_set(&row->header_,
+                                     &%(s)s_col_%(c)s,
+@@ -1074,8 +1071,7 @@ void
+ ''' % {'s': structName, 'c': columnName,'coltype':column.type.key.to_const_c_type(prefix),
+         'valtype':column.type.key.to_const_c_type(prefix), 'S': structName.upper(),
+         'C': columnName.upper(), 't': tableName})
+-
+-                print("    "+ type.key.copyCValue("datum->keys[0].%s" % type.key.type.to_string(), "delete_value"))
++                print("    " + type.key.copyCValue("datum->keys[0].%s" % type.key.type.to_lvalue_string(), "delete_value"))
+                 print('''
+     ovsdb_idl_txn_delete_partial_set(&row->header_,
+                                     &%(s)s_col_%(c)s,
+@@ -1143,37 +1139,36 @@ void
+             print("    struct ovsdb_datum datum;")
+             free = []
+             if type.n_min == 1 and type.n_max == 1:
+-                print("    union ovsdb_atom key;")
++                print("    union ovsdb_atom *key = xmalloc(sizeof *key);")
+                 if type.value:
+-                    print("    union ovsdb_atom value;")
++                    print("    union ovsdb_atom *value = xmalloc(sizeof *value);")
+                 print("")
+                 print("    datum.n = 1;")
+-                print("    datum.keys = &key;")
+-                print("    " + type.key.assign_c_value_casting_away_const("key.%s" % type.key.type.to_string(), keyVar, refTable=False))
++                print("    datum.keys = key;")
++                print("    " + type.key.copyCValue("key->%s" % type.key.type.to_lvalue_string(), keyVar, refTable=False))
+                 if type.value:
+-                    print("    datum.values = &value;")
+-                    print("    "+ type.value.assign_c_value_casting_away_const("value.%s" % type.value.type.to_string(), valueVar, refTable=False))
++                    print("    " + type.value.copyCValue("value.%s" % type.value.type.to_lvalue_string(), valueVar, refTable=False))
+                 else:
+                     print("    datum.values = NULL;")
+             elif type.is_optional_pointer():
+-                print("    union ovsdb_atom key;")
+                 print("")
+                 print("    if (%s) {" % keyVar)
++                print("        union ovsdb_atom *key = xmalloc(sizeof *key);")
+                 print("        datum.n = 1;")
+-                print("        datum.keys = &key;")
+-                print("        " + type.key.assign_c_value_casting_away_const("key.%s" % type.key.type.to_string(), keyVar, refTable=False))
++                print("        datum.keys = key;")
++                print("        " + type.key.copyCValue("key->%s" % type.key.type.to_lvalue_string(), keyVar, refTable=False))
+                 print("    } else {")
+                 print("        datum.n = 0;")
+                 print("        datum.keys = NULL;")
+                 print("    }")
+                 print("    datum.values = NULL;")
+             elif type.n_max == 1:
+-                print("    union ovsdb_atom key;")
+                 print("")
+                 print("    if (%s) {" % nVar)
++                print("        union ovsdb_atom *key = xmalloc(sizeof *key);")
+                 print("        datum.n = 1;")
+-                print("        datum.keys = &key;")
+-                print("        " + type.key.assign_c_value_casting_away_const("key.%s" % type.key.type.to_string(), "*" + keyVar, refTable=False))
++                print("        datum.keys = key;")
++                print("        " + type.key.copyCValue("key->%s" % type.key.type.to_lvalue_string(), "*" + keyVar, refTable=False))
+                 print("    } else {")
+                 print("        datum.n = 0;")
+                 print("        datum.keys = NULL;")
+@@ -1182,16 +1177,14 @@ void
+             else:
+                 print("    datum.n = %s;" % nVar)
+                 print("    datum.keys = %s ? xmalloc(%s * sizeof *datum.keys) : NULL;" % (nVar, nVar))
+-                free += ['datum.keys']
+                 if type.value:
+                     print("    datum.values = xmalloc(%s * sizeof *datum.values);" % nVar)
+-                    free += ['datum.values']
+                 else:
+                     print("    datum.values = NULL;")
+                 print("    for (size_t i = 0; i < %s; i++) {" % nVar)
+-                print("        " + type.key.assign_c_value_casting_away_const("datum.keys[i].%s" % type.key.type.to_string(), "%s[i]" % keyVar, refTable=False))
++                print("        " + type.key.copyCValue("datum.keys[i].%s" % type.key.type.to_lvalue_string(), "%s[i]" % keyVar, refTable=False))
+                 if type.value:
+-                    print("        " + type.value.assign_c_value_casting_away_const("datum.values[i].%s" % type.value.type.to_string(), "%s[i]" % valueVar, refTable=False))
++                    print("        " + type.value.copyCValue("datum.values[i].%s" % type.value.type.to_lvalue_string(), "%s[i]" % valueVar, refTable=False))
+                 print("    }")
+                 if type.value:
+                     valueType = type.value.toAtomicType()
+@@ -1211,8 +1204,8 @@ void
+        's': structName,
+        'S': structName.upper(),
+        'c': columnName})
+-            for var in free:
+-                print("    free(%s);" % var)
++            print("    ovsdb_datum_destroy(&datum, &%(s)s_col_%(c)s.type);" \
++                  % {'s': structName, 'c': columnName})
+             print("}")
+ 
+ # Index table related functions
+@@ -1309,8 +1302,8 @@ struct %(s)s *
+ 
+         i = 0;
+         SMAP_FOR_EACH (node, %(c)s) {
+-            datum->keys[i].string = node->key;
+-            datum->values[i].string = node->value;
++            datum->keys[i].s = ovsdb_atom_string_create(node->key);
++            datum->values[i].s = ovsdb_atom_string_create(node->value);
+             i++;
+         }
+         ovsdb_datum_sort_unique(datum, OVSDB_TYPE_STRING, OVSDB_TYPE_STRING);
+@@ -1359,10 +1352,10 @@ struct %(s)s *
+                 print()
+                 print("    datum.n = 1;")
+                 print("    datum.keys = key;")
+-                print("    " + type.key.assign_c_value_casting_away_const("key->%s" % type.key.type.to_string(), keyVar))
++                print("    " + type.key.copyCValue("key->%s" % type.key.type.to_lvalue_string(), keyVar))
+                 if type.value:
+                     print("    datum.values = value;")
+-                    print("    "+ type.value.assign_c_value_casting_away_const("value->%s" % type.value.type.to_string(), valueVar))
++                    print("    " + type.value.copyCValue("value->%s" % type.value.type.to_lvalue_string(), valueVar))
+                 else:
+                     print("    datum.values = NULL;")
+                 txn_write_func = "ovsdb_idl_index_write"
+@@ -1373,7 +1366,7 @@ struct %(s)s *
+                 print("        key = xmalloc(sizeof (union ovsdb_atom));")
+                 print("        datum.n = 1;")
+                 print("        datum.keys = key;")
+-                print("        " + type.key.assign_c_value_casting_away_const("key->%s" % type.key.type.to_string(), keyVar))
++                print("        " + type.key.copyCValue("key->%s" % type.key.type.to_lvalue_string(), keyVar))
+                 print("    } else {")
+                 print("        datum.n = 0;")
+                 print("        datum.keys = NULL;")
+@@ -1387,7 +1380,7 @@ struct %(s)s *
+                 print("        key = xmalloc(sizeof(union ovsdb_atom));")
+                 print("        datum.n = 1;")
+                 print("        datum.keys = key;")
+-                print("        " + type.key.assign_c_value_casting_away_const("key->%s" % type.key.type.to_string(), "*" + keyVar))
++                print("        " + type.key.copyCValue("key->%s" % type.key.type.to_lvalue_string(), "*" + keyVar))
+                 print("    } else {")
+                 print("        datum.n = 0;")
+                 print("        datum.keys = NULL;")
+@@ -1404,9 +1397,9 @@ struct %(s)s *
+                 else:
+                     print("    datum.values = NULL;")
+                 print("    for (i = 0; i < %s; i++) {" % nVar)
+-                print("        " + type.key.copyCValue("datum.keys[i].%s" % type.key.type.to_string(), "%s[i]" % keyVar))
++                print("        " + type.key.copyCValue("datum.keys[i].%s" % type.key.type.to_lvalue_string(), "%s[i]" % keyVar))
+                 if type.value:
+-                    print("        " + type.value.copyCValue("datum.values[i].%s" % type.value.type.to_string(), "%s[i]" % valueVar))
++                    print("        " + type.value.copyCValue("datum.values[i].%s" % type.value.type.to_lvalue_string(), "%s[i]" % valueVar))
+                 print("    }")
+                 if type.value:
+                     valueType = type.value.toAtomicType()
+diff --git a/ovsdb/ovsdb-server.c b/ovsdb/ovsdb-server.c
+index 0b3d2bb714..b34d97e291 100644
+--- a/ovsdb/ovsdb-server.c
++++ b/ovsdb/ovsdb-server.c
+@@ -904,8 +904,8 @@ query_db_string(const struct shash *all_dbs, const char *name,
+ 
+             datum = &row->fields[column->index];
+             for (i = 0; i < datum->n; i++) {
+-                if (datum->keys[i].string[0]) {
+-                    return datum->keys[i].string;
++                if (datum->keys[i].s->string[0]) {
++                    return datum->keys[i].s->string;
+                 }
+             }
+         }
+@@ -1018,7 +1018,7 @@ query_db_remotes(const char *name, const struct shash *all_dbs,
+ 
+             datum = &row->fields[column->index];
+             for (i = 0; i < datum->n; i++) {
+-                add_remote(remotes, datum->keys[i].string);
++                add_remote(remotes, datum->keys[i].s->string);
+             }
+         }
+     } else if (column->type.key.type == OVSDB_TYPE_UUID
 diff --git a/ovsdb/ovsdb-tool.c b/ovsdb/ovsdb-tool.c
 index 05a0223e71..d4a9e34cc4 100644
 --- a/ovsdb/ovsdb-tool.c
@@ -524,6 +2258,67 @@ index 05a0223e71..d4a9e34cc4 100644
                  e->eid = r->entry.eid;
                  e->servers = r->entry.servers;
                  break;
+diff --git a/ovsdb/ovsdb-util.c b/ovsdb/ovsdb-util.c
+index c4075cdae3..6d7be066b6 100644
+--- a/ovsdb/ovsdb-util.c
++++ b/ovsdb/ovsdb-util.c
+@@ -111,13 +111,13 @@ ovsdb_util_read_map_string_column(const struct ovsdb_row *row,
+ 
+     for (i = 0; i < datum->n; i++) {
+         atom_key = &datum->keys[i];
+-        if (!strcmp(atom_key->string, key)) {
++        if (!strcmp(atom_key->s->string, key)) {
+             atom_value = &datum->values[i];
+             break;
+         }
+     }
+ 
+-    return atom_value ? atom_value->string : NULL;
++    return atom_value ? atom_value->s->string : NULL;
+ }
+ 
+ /* Read string-uuid key-values from a map.  Returns the row associated with
+@@ -143,7 +143,7 @@ ovsdb_util_read_map_string_uuid_column(const struct ovsdb_row *row,
+     const struct ovsdb_datum *datum = &row->fields[column->index];
+     for (size_t i = 0; i < datum->n; i++) {
+         union ovsdb_atom *atom_key = &datum->keys[i];
+-        if (!strcmp(atom_key->string, key)) {
++        if (!strcmp(atom_key->s->string, key)) {
+             const union ovsdb_atom *atom_value = &datum->values[i];
+             return ovsdb_table_get_row(ref_table, &atom_value->uuid);
+         }
+@@ -181,7 +181,7 @@ ovsdb_util_read_string_column(const struct ovsdb_row *row,
+     const union ovsdb_atom *atom;
+ 
+     atom = ovsdb_util_read_column(row, column_name, OVSDB_TYPE_STRING);
+-    *stringp = atom ? atom->string : NULL;
++    *stringp = atom ? atom->s->string : NULL;
+     return atom != NULL;
+ }
+ 
+@@ -269,8 +269,10 @@ ovsdb_util_write_string_column(struct ovsdb_row *row, const char *column_name,
+                                const char *string)
+ {
+     if (string) {
+-        const union ovsdb_atom atom = { .string = CONST_CAST(char *, string) };
++        union ovsdb_atom atom = {
++            .s = ovsdb_atom_string_create(CONST_CAST(char *, string)) };
+         ovsdb_util_write_singleton(row, column_name, &atom, OVSDB_TYPE_STRING);
++        ovsdb_atom_destroy(&atom, OVSDB_TYPE_STRING);
+     } else {
+         ovsdb_util_clear_column(row, column_name);
+     }
+@@ -305,8 +307,8 @@ ovsdb_util_write_string_string_column(struct ovsdb_row *row,
+     datum->values = xmalloc(n * sizeof *datum->values);
+ 
+     for (i = 0; i < n; ++i) {
+-        datum->keys[i].string = keys[i];
+-        datum->values[i].string = values[i];
++        datum->keys[i].s = ovsdb_atom_string_create_nocopy(keys[i]);
++        datum->values[i].s = ovsdb_atom_string_create_nocopy(values[i]);
+     }
+ 
+     /* Sort and check constraints. */
 diff --git a/ovsdb/raft-private.c b/ovsdb/raft-private.c
 index 26d39a087f..30760233ee 100644
 --- a/ovsdb/raft-private.c
@@ -997,6 +2792,39 @@ index 3545c41c2c..599bc0ae86 100644
  bool raft_has_next_entry(const struct raft *);
  
  uint64_t raft_get_applied_index(const struct raft *);
+diff --git a/ovsdb/rbac.c b/ovsdb/rbac.c
+index 2986027c90..ff411675f0 100644
+--- a/ovsdb/rbac.c
++++ b/ovsdb/rbac.c
+@@ -53,8 +53,8 @@ ovsdb_find_row_by_string_key(const struct ovsdb_table *table,
+         HMAP_FOR_EACH (row, hmap_node, &table->rows) {
+             const struct ovsdb_datum *datum = &row->fields[column->index];
+             for (size_t i = 0; i < datum->n; i++) {
+-                if (datum->keys[i].string[0] &&
+-                    !strcmp(key, datum->keys[i].string)) {
++                if (datum->keys[i].s->string[0] &&
++                    !strcmp(key, datum->keys[i].s->string)) {
+                     return row;
+                 }
+             }
+@@ -113,7 +113,7 @@ ovsdb_rbac_authorized(const struct ovsdb_row *perms,
+     }
+ 
+     for (i = 0; i < datum->n; i++) {
+-        const char *name = datum->keys[i].string;
++        const char *name = datum->keys[i].s->string;
+         const char *value = NULL;
+         bool is_map;
+ 
+@@ -271,7 +271,7 @@ rbac_column_modification_permitted(const struct ovsdb_column *column,
+     size_t i;
+ 
+     for (i = 0; i < modifiable->n; i++) {
+-        char *name = modifiable->keys[i].string;
++        char *name = modifiable->keys[i].s->string;
+ 
+         if (!strcmp(name, column->name)) {
+             return true;
 diff --git a/ovsdb/storage.c b/ovsdb/storage.c
 index d727b1eacd..9e32efe582 100644
 --- a/ovsdb/storage.c
@@ -1012,6 +2840,187 @@ index d727b1eacd..9e32efe582 100644
          if (!json) {
              return NULL;
          } else if (json->type != JSON_ARRAY || json->array.n != 2) {
+diff --git a/ovsdb/transaction.c b/ovsdb/transaction.c
+index 8ffefcf7c9..dcccc61c05 100644
+--- a/ovsdb/transaction.c
++++ b/ovsdb/transaction.c
+@@ -266,9 +266,9 @@ ovsdb_txn_adjust_atom_refs(struct ovsdb_txn *txn, const struct ovsdb_row *r,
+ 
+ static struct ovsdb_error * OVS_WARN_UNUSED_RESULT
+ ovsdb_txn_adjust_row_refs(struct ovsdb_txn *txn, const struct ovsdb_row *r,
+-                          const struct ovsdb_column *column, int delta)
++                          const struct ovsdb_column *column,
++                          const struct ovsdb_datum *field, int delta)
+ {
+-    const struct ovsdb_datum *field = &r->fields[column->index];
+     struct ovsdb_error *error;
+ 
+     error = ovsdb_txn_adjust_atom_refs(txn, r, column, &column->type.key,
+@@ -291,14 +291,39 @@ update_row_ref_count(struct ovsdb_txn *txn, struct ovsdb_txn_row *r)
+         struct ovsdb_error *error;
+ 
+         if (bitmap_is_set(r->changed, column->index)) {
+-            if (r->old) {
+-                error = ovsdb_txn_adjust_row_refs(txn, r->old, column, -1);
++            if (r->old && !r->new) {
++                error = ovsdb_txn_adjust_row_refs(
++                            txn, r->old, column,
++                            &r->old->fields[column->index], -1);
+                 if (error) {
+                     return OVSDB_WRAP_BUG("error decreasing refcount", error);
+                 }
+-            }
+-            if (r->new) {
+-                error = ovsdb_txn_adjust_row_refs(txn, r->new, column, 1);
++            } else if (!r->old && r->new) {
++                error = ovsdb_txn_adjust_row_refs(
++                            txn, r->new, column,
++                            &r->new->fields[column->index], 1);
++                if (error) {
++                    return error;
++                }
++            } else if (r->old && r->new) {
++                struct ovsdb_datum added, removed;
++
++                ovsdb_datum_added_removed(&added, &removed,
++                                          &r->old->fields[column->index],
++                                          &r->new->fields[column->index],
++                                          &column->type);
++
++                error = ovsdb_txn_adjust_row_refs(
++                            txn, r->old, column, &removed, -1);
++                ovsdb_datum_destroy(&removed, &column->type);
++                if (error) {
++                    ovsdb_datum_destroy(&added, &column->type);
++                    return OVSDB_WRAP_BUG("error decreasing refcount", error);
++                }
++
++                error = ovsdb_txn_adjust_row_refs(
++                            txn, r->new, column, &added, 1);
++                ovsdb_datum_destroy(&added, &column->type);
+                 if (error) {
+                     return error;
+                 }
+diff --git a/python/ovs/db/data.py b/python/ovs/db/data.py
+index 2a2102d6be..99bf80ed62 100644
+--- a/python/ovs/db/data.py
++++ b/python/ovs/db/data.py
+@@ -204,7 +204,7 @@ class Atom(object):
+             else:
+                 return '.boolean = false'
+         elif self.type == ovs.db.types.StringType:
+-            return '.string = "%s"' % escapeCString(self.value)
++            return '.s = %s' % escapeCString(self.value)
+         elif self.type == ovs.db.types.UuidType:
+             return '.uuid = %s' % ovs.ovsuuid.to_c_assignment(self.value)
+ 
+@@ -563,16 +563,41 @@ class Datum(object):
+         if n == 0:
+             return ["static struct ovsdb_datum %s = { .n = 0 };"]
+ 
+-        s = ["static union ovsdb_atom %s_keys[%d] = {" % (name, n)]
+-        for key in sorted(self.values):
+-            s += ["    { %s }," % key.cInitAtom(key)]
+-        s += ["};"]
++        s = []
++        if self.type.key.type == ovs.db.types.StringType:
++            s += ["static struct ovsdb_atom_string %s_key_strings[%d] = {"
++                  % (name, n)]
++            for key in sorted(self.values):
++                s += ['    { .string = "%s", .n_refs = 2 },'
++                      % escapeCString(key.value)]
++            s += ["};"]
++            s += ["static union ovsdb_atom %s_keys[%d] = {" % (name, n)]
++            for i in range(n):
++                s += ["    { .s = &%s_key_strings[%d] }," % (name, i)]
++            s += ["};"]
++        else:
++            s = ["static union ovsdb_atom %s_keys[%d] = {" % (name, n)]
++            for key in sorted(self.values):
++                s += ["    { %s }," % key.cInitAtom(key)]
++            s += ["};"]
+ 
+         if self.type.value:
+-            s = ["static union ovsdb_atom %s_values[%d] = {" % (name, n)]
+-            for k, v in sorted(self.values.items()):
+-                s += ["    { %s }," % v.cInitAtom(v)]
+-            s += ["};"]
++            if self.type.value.type == ovs.db.types.StringType:
++                s += ["static struct ovsdb_atom_string %s_val_strings[%d] = {"
++                      % (name, n)]
++                for k, v in sorted(self.values):
++                    s += ['    { .string = "%s", .n_refs = 2 },'
++                          % escapeCString(v.value)]
++                s += ["};"]
++                s += ["static union ovsdb_atom %s_values[%d] = {" % (name, n)]
++                for i in range(n):
++                    s += ["    { .s = &%s_val_strings[%d] }," % (name, i)]
++                s += ["};"]
++            else:
++                s = ["static union ovsdb_atom %s_values[%d] = {" % (name, n)]
++                for k, v in sorted(self.values.items()):
++                    s += ["    { %s }," % v.cInitAtom(v)]
++                s += ["};"]
+ 
+         s += ["static struct ovsdb_datum %s = {" % name]
+         s += ["    .n = %d," % n]
+diff --git a/python/ovs/db/idl.py b/python/ovs/db/idl.py
+index ecae5e1432..87ee06cdef 100644
+--- a/python/ovs/db/idl.py
++++ b/python/ovs/db/idl.py
+@@ -1505,6 +1505,11 @@ class Transaction(object):
+         if self != self.idl.txn:
+             return self._status
+ 
++        if self.idl.state != Idl.IDL_S_MONITORING:
++            self._status = Transaction.TRY_AGAIN
++            self.__disassemble()
++            return self._status
++
+         # If we need a lock but don't have it, give up quickly.
+         if self.idl.lock_name and not self.idl.has_lock:
+             self._status = Transaction.NOT_LOCKED
+diff --git a/python/ovs/db/types.py b/python/ovs/db/types.py
+index 626ae8fc44..3318a3b6f8 100644
+--- a/python/ovs/db/types.py
++++ b/python/ovs/db/types.py
+@@ -48,6 +48,16 @@ class AtomicType(object):
+     def to_string(self):
+         return self.name
+ 
++    def to_rvalue_string(self):
++        if self == StringType:
++            return 's->' + self.name
++        return self.name
++
++    def to_lvalue_string(self):
++        if self == StringType:
++            return 's'
++        return self.name
++
+     def to_json(self):
+         return self.name
+ 
+@@ -373,18 +383,7 @@ class BaseType(object):
+                 return "%(dst)s = *%(src)s;" % args
+             return ("%(dst)s = %(src)s->header_.uuid;") % args
+         elif self.type == StringType:
+-            return "%(dst)s = xstrdup(%(src)s);" % args
+-        else:
+-            return "%(dst)s = %(src)s;" % args
+-
+-    def assign_c_value_casting_away_const(self, dst, src, refTable=True):
+-        args = {'dst': dst, 'src': src}
+-        if self.ref_table_name:
+-            if not refTable:
+-                return "%(dst)s = *%(src)s;" % args
+-            return ("%(dst)s = %(src)s->header_.uuid;") % args
+-        elif self.type == StringType:
+-            return "%(dst)s = CONST_CAST(char *, %(src)s);" % args
++            return "%(dst)s = ovsdb_atom_string_create(%(src)s);" % args
+         else:
+             return "%(dst)s = %(src)s;" % args
+ 
 diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
 index 956a69e1fa..1dad6f62c6 100644
 --- a/tests/ofproto-dpif.at
@@ -1043,11 +3052,193 @@ index 956a69e1fa..1dad6f62c6 100644
  AT_SETUP([ofproto-dpif - ICMPv6])
  OVS_VSWITCHD_START
  add_of_ports br0 1
+diff --git a/tests/ovsdb-data.at b/tests/ovsdb-data.at
+index 8cd2a26cb3..25c6acdac6 100644
+--- a/tests/ovsdb-data.at
++++ b/tests/ovsdb-data.at
+@@ -846,18 +846,21 @@ OVSDB_CHECK_POSITIVE([generate and apply diff -- integer],
+   [[diff-data '["integer"]' '[0]' '[2]']],
+   [[diff: 2
+ apply diff: 2
++apply diff in place: 2
+ OK]])
+ 
+ OVSDB_CHECK_POSITIVE([generate and apply diff -- boolean],
+   [[diff-data '["boolean"]' '[true]' '[false]']],
+   [[diff: false
+ apply diff: false
++apply diff in place: false
+ OK]])
+ 
+ OVSDB_CHECK_POSITIVE([generate and apply diff -- string],
+   [[diff-data '["string"]' '["AAA"]' '["BBB"]']],
+   [[diff: "BBB"
+ apply diff: "BBB"
++apply diff in place: "BBB"
+ OK]])
+ 
+ dnl Test set modifications.
+@@ -870,15 +873,19 @@ OVSDB_CHECK_POSITIVE([generate and apply diff -- set],
+   ]],
+   [[diff: ["set",[0,2]]
+ apply diff: ["set",[1,2]]
++apply diff in place: ["set",[1,2]]
+ OK
+ diff: 0
+ apply diff: 1
++apply diff in place: 1
+ OK
+ diff: ["set",[0,1]]
+ apply diff: ["set",[0,1]]
++apply diff in place: ["set",[0,1]]
+ OK
+ diff: ["set",[0,1]]
+ apply diff: ["set",[]]
++apply diff in place: ["set",[]]
+ OK]])
+ 
+ dnl Test set modifications causes data to violate set size constrain.
+@@ -898,18 +905,23 @@ OVSDB_CHECK_POSITIVE([generate and apply diff -- map],
+   ]],
+   [[diff: ["map",[["2 gills","1 chopin"],["2 pints","1 quart"]]]
+ apply diff: ["map",[["2 pints","1 quart"]]]
++apply diff in place: ["map",[["2 pints","1 quart"]]]
+ OK
+ diff: ["map",[]]
+ apply diff: ["map",[["2 gills","1 chopin"]]]
++apply diff in place: ["map",[["2 gills","1 chopin"]]]
+ OK
+ diff: ["map",[["2 gills","1 chopin"]]]
+ apply diff: ["map",[]]
++apply diff in place: ["map",[]]
+ OK
+ diff: ["map",[["2 pints","1 quart"]]]
+ apply diff: ["map",[["2 pints","1 quart"]]]
++apply diff in place: ["map",[["2 pints","1 quart"]]]
+ OK
+ diff: ["map",[["2 gills","1 gallon"]]]
+ apply diff: ["map",[["2 gills","1 gallon"]]]
++apply diff in place: ["map",[["2 gills","1 gallon"]]]
+ OK]])
+ 
+ OVSDB_CHECK_NEGATIVE([generate and apply diff with map -- size error],
 diff --git a/tests/system-traffic.at b/tests/system-traffic.at
-index f400cfabc9..c4442c183f 100644
+index f400cfabc9..092de308be 100644
 --- a/tests/system-traffic.at
 +++ b/tests/system-traffic.at
-@@ -3305,6 +3305,46 @@ NS_CHECK_EXEC([at_ns0], [ping6 -s 3200 -q -c 3 -i 0.3 -w 2 fc00::2 | FORMAT_PING
+@@ -1981,6 +1981,111 @@ tcp,orig=(src=10.1.1.3,dst=10.1.1.4,sport=<cleared>,dport=<cleared>),reply=(src=
+ OVS_TRAFFIC_VSWITCHD_STOP
+ AT_CLEANUP
+ 
++AT_SETUP([conntrack - zones from other field])
++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")
++
++dnl Allow any traffic from ns0->ns1. Only allow nd, return traffic from ns1->ns0.
++AT_DATA([flows.txt], [dnl
++priority=1,action=drop
++priority=10,arp,action=normal
++priority=10,icmp,action=normal
++priority=100,in_port=1,tcp,ct_state=-trk,action=ct(zone=5,table=0)
++priority=100,in_port=1,tcp,ct_state=+trk,action=ct(commit,zone=NXM_NX_CT_ZONE[]),2
++priority=100,in_port=2,ct_state=-trk,tcp,action=ct(table=0,zone=5)
++priority=100,in_port=2,ct_state=+trk,ct_zone=5,tcp,action=1
++])
++
++AT_CHECK([ovs-ofctl --bundle add-flows br0 flows.txt])
++
++OVS_START_L7([at_ns1], [http])
++
++dnl HTTP requests from p0->p1 should work fine.
++NS_CHECK_EXEC([at_ns0], [wget 10.1.1.2 -t 3 -T 1 --retry-connrefused -v -o wget0.log])
++
++AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.1.1.2)], [0], [dnl
++tcp,dnl
++orig=(src=10.1.1.1,dst=10.1.1.2,sport=<cleared>,dport=<cleared>),dnl
++reply=(src=10.1.1.2,dst=10.1.1.1,sport=<cleared>,dport=<cleared>),dnl
++zone=5,protoinfo=(state=<cleared>)
++])
++
++dnl This is to test when the zoneid is set by a field variable like
++dnl NXM_NX_CT_ZONE, the OVS xlate should generate a megaflow with a form of
++dnl "ct_zone(5), ...  actions: ct(commit, zone=5)".  The match "ct_zone(5)"
++dnl is needed as if we changes the zoneid into 15 in the following, the old
++dnl "ct_zone(5), ... actions: ct(commit, zone=5)" megaflow will not get hit,
++dnl and OVS will generate a new megaflow with the match "ct_zone(0xf)".
++dnl This will make sure that the new packets are committing to zoneid 15
++dnl rather than old 5.
++AT_CHECK([ovs-appctl dpctl/dump-flows --names filter=in_port=ovs-p0 dnl
++              | grep "+trk" | grep -q "ct_zone(0x5)" ], [0], [])
++
++AT_CHECK([ovs-ofctl mod-flows br0 dnl
++            'priority=100,ct_state=-trk,tcp,in_port="ovs-p0" actions=ct(table=0,zone=15)'])
++
++NS_CHECK_EXEC([at_ns0], [wget 10.1.1.2 -t 3 -T 1 --retry-connrefused -v -o wget0.log])
++
++AT_CHECK([ovs-appctl dpctl/dump-flows --names filter=in_port=ovs-p0 dnl
++              | grep "+trk" | grep -q "ct_zone(0xf)" ], [0], [])
++
++OVS_TRAFFIC_VSWITCHD_STOP
++AT_CLEANUP
++
++AT_SETUP([conntrack - zones from other field, more tests])
++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")
++
++dnl Allow any traffic from ns0->ns1. Only allow nd, return traffic from ns1->ns0.
++AT_DATA([flows.txt], [dnl
++priority=1,action=drop
++priority=10,arp,action=normal
++priority=10,icmp,action=normal
++priority=100,in_port=1,tcp,ct_state=-trk,action=ct(zone=5,table=0,commit,exec(load:0xffff0005->NXM_NX_CT_LABEL[[0..31]]))
++priority=100,in_port=1,tcp,ct_state=+trk,action=ct(commit,zone=NXM_NX_CT_LABEL[[0..15]]),2
++priority=100,in_port=2,ct_state=-trk,tcp,action=ct(table=0,zone=5)
++priority=100,in_port=2,ct_state=+trk,ct_zone=5,tcp,action=1
++])
++
++AT_CHECK([ovs-ofctl --bundle add-flows br0 flows.txt])
++
++OVS_START_L7([at_ns1], [http])
++
++dnl HTTP requests from p0->p1 should work fine.
++NS_CHECK_EXEC([at_ns0], [wget 10.1.1.2 -t 3 -T 1 --retry-connrefused -v -o wget0.log])
++
++AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.1.1.2)], [0], [dnl
++tcp,dnl
++orig=(src=10.1.1.1,dst=10.1.1.2,sport=<cleared>,dport=<cleared>),dnl
++reply=(src=10.1.1.2,dst=10.1.1.1,sport=<cleared>,dport=<cleared>),dnl
++zone=5,labels=0xffff0005,protoinfo=(state=<cleared>)
++])
++
++AT_CHECK([ovs-appctl dpctl/dump-flows --names filter=in_port=ovs-p0 dnl
++              | grep "+trk" | sed 's/0xffff0005\/0xffff/0x5\/0xffff/' dnl
++              | grep -q "ct_label(0x5/0xffff)" ], [0], [])
++
++AT_CHECK([ovs-ofctl mod-flows br0 'priority=100,ct_state=-trk,tcp,in_port="ovs-p0" actions=ct(table=0,zone=15,commit,exec(load:0xffff000f->NXM_NX_CT_LABEL[[0..31]]))'])
++
++NS_CHECK_EXEC([at_ns0], [wget 10.1.1.2 -t 3 -T 1 --retry-connrefused -v -o wget0.log])
++
++AT_CHECK([ovs-appctl dpctl/dump-flows --names filter=in_port=ovs-p0 dnl
++              | grep "+trk" | sed 's/0xffff000f\/0xffff/0xf\/0xffff/' dnl
++              | grep -q "ct_label(0xf/0xffff)" ], [0], [])
++
++OVS_TRAFFIC_VSWITCHD_STOP
++AT_CLEANUP
++
+ AT_SETUP([conntrack - multiple bridges])
+ CHECK_CONNTRACK()
+ OVS_TRAFFIC_VSWITCHD_START(
+@@ -3305,6 +3410,46 @@ NS_CHECK_EXEC([at_ns0], [ping6 -s 3200 -q -c 3 -i 0.3 -w 2 fc00::2 | FORMAT_PING
  OVS_TRAFFIC_VSWITCHD_STOP
  AT_CLEANUP
  
@@ -1177,11 +3368,77 @@ index a7ee595e0b..072a537252 100644
 +}
 +
 +OVSTEST_REGISTER("json-string-benchmark", json_string_benchmark_main);
+diff --git a/tests/test-ovsdb.c b/tests/test-ovsdb.c
+index daa55dab7b..637271619f 100644
+--- a/tests/test-ovsdb.c
++++ b/tests/test-ovsdb.c
+@@ -512,6 +512,18 @@ do_diff_data(struct ovs_cmdl_context *ctx)
+             ovs_fatal(0, "failed to apply diff");
+         }
+ 
++        /* Apply diff to 'old' in place. */
++        error = ovsdb_datum_apply_diff_in_place(&old, &diff, &type);
++        if (error) {
++            char *string = ovsdb_error_to_string_free(error);
++            ovs_fatal(0, "%s", string);
++        }
++
++        /* Test to make sure 'old' equals 'new' now.  */
++        if (!ovsdb_datum_equals(&new, &old, &type)) {
++            ovs_fatal(0, "failed to apply diff in place");
++        }
++
+         /* Print diff */
+         json = ovsdb_datum_to_json(&diff, &type);
+         printf ("diff: ");
+@@ -522,6 +534,11 @@ do_diff_data(struct ovs_cmdl_context *ctx)
+         printf ("apply diff: ");
+         print_and_free_json(json);
+ 
++        /* Print updated 'old' */
++        json = ovsdb_datum_to_json(&old, &type);
++        printf ("apply diff in place: ");
++        print_and_free_json(json);
++
+         ovsdb_datum_destroy(&new, &type);
+         ovsdb_datum_destroy(&old, &type);
+         ovsdb_datum_destroy(&diff, &type);
+@@ -2727,13 +2744,15 @@ print_idl_row_simple2(const struct idltest_simple2 *s, int step)
+     printf("%03d: name=%s smap=[",
+            step, s->name);
+     for (i = 0; i < smap->n; i++) {
+-        printf("[%s : %s]%s", smap->keys[i].string, smap->values[i].string,
+-                i < smap->n-1? ",": "");
++        printf("[%s : %s]%s",
++               smap->keys[i].s->string, smap->values[i].s->string,
++               i < smap->n - 1 ? "," : "");
+     }
+     printf("] imap=[");
+     for (i = 0; i < imap->n; i++) {
+-        printf("[%"PRId64" : %s]%s", imap->keys[i].integer, imap->values[i].string,
+-                i < imap->n-1? ",":"");
++        printf("[%"PRId64" : %s]%s",
++               imap->keys[i].integer, imap->values[i].s->string,
++               i < imap->n - 1 ? "," : "");
+     }
+     printf("]\n");
+ }
+@@ -2802,8 +2821,8 @@ do_idl_partial_update_map_column(struct ovs_cmdl_context *ctx)
+     myTxn = ovsdb_idl_txn_create(idl);
+     smap = idltest_simple2_get_smap(myRow, OVSDB_TYPE_STRING,
+                                     OVSDB_TYPE_STRING);
+-    strcpy(key_to_delete, smap->keys[0].string);
+-    idltest_simple2_update_smap_delkey(myRow, smap->keys[0].string);
++    ovs_strlcpy(key_to_delete, smap->keys[0].s->string, sizeof key_to_delete);
++    idltest_simple2_update_smap_delkey(myRow, smap->keys[0].s->string);
+     ovsdb_idl_txn_commit_block(myTxn);
+     ovsdb_idl_txn_destroy(myTxn);
+     ovsdb_idl_get_initial_snapshot(idl);
 diff --git a/tests/tunnel-push-pop.at b/tests/tunnel-push-pop.at
-index 48c5de9d19..12fc1ef910 100644
+index 48c5de9d19..6364653975 100644
 --- a/tests/tunnel-push-pop.at
 +++ b/tests/tunnel-push-pop.at
-@@ -595,6 +595,62 @@ OVS_WAIT_UNTIL([test `ovs-pcap p0.pcap | grep 50540000000a5054000000091235 | wc
+@@ -595,6 +595,64 @@ OVS_WAIT_UNTIL([test `ovs-pcap p0.pcap | grep 50540000000a5054000000091235 | wc
  OVS_VSWITCHD_STOP
  AT_CLEANUP
  
@@ -1218,20 +3475,22 @@ index 48c5de9d19..12fc1ef910 100644
 +AT_CHECK([ovs-vsctl -- set Interface p0 options:tx_pcap=p0.pcap])
 +
 +packet=50540000000a505400000009123
-+encap=f8bc124434b6aa55aa5500000800450000320000400040113406010102580101025c83a917c1001e00000000655800007b00
++dnl Source port is based on a packet hash, so it may differ depending on the
++dnl compiler flags and CPU type.  Masked with '....'.
++encap=f8bc124434b6aa55aa5500000800450000320000400040113406010102580101025c....17c1001e00000000655800007b00
 +
 +dnl Output to tunnel from a int-br internal port.
 +dnl Checking that the packet arrived and it was correctly encapsulated.
 +AT_CHECK([ovs-ofctl add-flow int-br "in_port=LOCAL,actions=debug_slow,output:2"])
 +AT_CHECK([ovs-appctl netdev-dummy/receive int-br "${packet}4"])
-+OVS_WAIT_UNTIL([test `ovs-pcap p0.pcap | grep "${encap}${packet}4" | wc -l` -ge 1])
++OVS_WAIT_UNTIL([test `ovs-pcap p0.pcap | egrep "${encap}${packet}4" | wc -l` -ge 1])
 +dnl Sending again to exercise the non-miss upcall path.
 +AT_CHECK([ovs-appctl netdev-dummy/receive int-br "${packet}4"])
-+OVS_WAIT_UNTIL([test `ovs-pcap p0.pcap | grep "${encap}${packet}4" | wc -l` -ge 2])
++OVS_WAIT_UNTIL([test `ovs-pcap p0.pcap | egrep "${encap}${packet}4" | wc -l` -ge 2])
 +
 +dnl Output to tunnel from the controller.
 +AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out int-br CONTROLLER "debug_slow,output:2" "${packet}5"])
-+OVS_WAIT_UNTIL([test `ovs-pcap p0.pcap | grep "${encap}${packet}5" | wc -l` -ge 1])
++OVS_WAIT_UNTIL([test `ovs-pcap p0.pcap | egrep "${encap}${packet}5" | wc -l` -ge 1])
 +
 +dnl Datapath actions should not have tunnel push action.
 +AT_CHECK([ovs-appctl dpctl/dump-flows | grep -q tnl_push], [1])
@@ -1244,3 +3503,48 @@ index 48c5de9d19..12fc1ef910 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/utilities/ovs-ctl.in b/utilities/ovs-ctl.in
+index 71800795c0..e6e07f4763 100644
+--- a/utilities/ovs-ctl.in
++++ b/utilities/ovs-ctl.in
+@@ -421,7 +421,9 @@ Less important options for "start", "restart" and "force-reload-kmod":
+   --no-force-corefiles           do not force on core dumps for OVS daemons
+   --no-mlockall                  do not lock all of ovs-vswitchd into memory
+   --ovsdb-server-priority=NICE   set ovsdb-server's niceness (default: $OVSDB_SERVER_PRIORITY)
++  --ovsdb-server-options=OPTIONS additional options for ovsdb-server (example: '-vconsole:dbg -vfile:dbg')
+   --ovs-vswitchd-priority=NICE   set ovs-vswitchd's niceness (default: $OVS_VSWITCHD_PRIORITY)
++  --ovs-vswitchd-options=OPTIONS additional options for ovs-vswitchd (example: '-vconsole:dbg -vfile:dbg')
+   --no-full-hostname             set short hostname instead of full hostname
+   --no-record-hostname           do not attempt to determine/record system
+                                  hostname as part of start command
+diff --git a/vswitchd/bridge.c b/vswitchd/bridge.c
+index cb7c5cb769..c790a56adf 100644
+--- a/vswitchd/bridge.c
++++ b/vswitchd/bridge.c
+@@ -4229,7 +4229,7 @@ bridge_configure_aa(struct bridge *br)
+         union ovsdb_atom atom;
+ 
+         atom.integer = m->isid;
+-        if (ovsdb_datum_find_key(mc, &atom, OVSDB_TYPE_INTEGER) == UINT_MAX) {
++        if (!ovsdb_datum_find_key(mc, &atom, OVSDB_TYPE_INTEGER, NULL)) {
+             VLOG_INFO("Deleting isid=%"PRIu32", vlan=%"PRIu16,
+                       m->isid, m->vlan);
+             bridge_aa_mapping_destroy(m);
+@@ -4826,7 +4826,7 @@ queue_ids_include(const struct ovsdb_datum *queues, int64_t target)
+     union ovsdb_atom atom;
+ 
+     atom.integer = target;
+-    return ovsdb_datum_find_key(queues, &atom, OVSDB_TYPE_INTEGER) != UINT_MAX;
++    return ovsdb_datum_find_key(queues, &atom, OVSDB_TYPE_INTEGER, NULL);
+ }
+ 
+ static void
+@@ -5020,7 +5020,7 @@ bridge_configure_mirrors(struct bridge *br)
+         union ovsdb_atom atom;
+ 
+         atom.uuid = m->uuid;
+-        if (ovsdb_datum_find_key(mc, &atom, OVSDB_TYPE_UUID) == UINT_MAX) {
++        if (!ovsdb_datum_find_key(mc, &atom, OVSDB_TYPE_UUID, NULL)) {
+             mirror_destroy(m);
+         }
+     }
diff --git a/SPECS/openvswitch2.16.spec b/SPECS/openvswitch2.16.spec
index 863f8fb..7b89691 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: 8%{?dist}
+Release: 25%{?dist}
 
 # Nearly all of openvswitch is ASL 2.0.  The bugtool is LGPLv2+, and the
 # lib/sflow*.[ch] files are SISSL
@@ -676,9 +676,11 @@ exit 0
 %doc LICENSE NOTICE README.rst NEWS rhel/README.RHEL.rst
 %ifarch %{dpdkarches}
 %doc %{dpdkdir}/README.DPDK-PMDS
+%attr(750,openvswitch,hugetlbfs) %verify(not owner group) /var/log/openvswitch
+%else
+%attr(750,openvswitch,openvswitch) %verify(not owner group) /var/log/openvswitch
 %endif
 /var/lib/openvswitch
-%attr(750,openvswitch,openvswitch) %verify(not owner group) /var/log/openvswitch
 %ghost %attr(755,root,root) %verify(not owner group) %{_rundir}/openvswitch
 %{_datadir}/openvswitch/bugtool-plugins/
 %{_datadir}/openvswitch/scripts/ovs-bugtool-*
@@ -698,6 +700,321 @@ exit 0
 %endif
 
 %changelog
+* Thu Oct 28 2021 Open vSwitch CI <ovs-ci@redhat.com> - 2.16.0-25
+- Merging upstream branch-2.16 [RH git: f5366890c5]
+    Commit list:
+    c221c8e613 datapath-windows:Reset PseudoChecksum value only for TX direction offload case
+
+
+* Wed Oct 27 2021 Open vSwitch CI <ovs-ci@redhat.com> - 2.16.0-24
+- Merging upstream branch-2.16 [RH git: 4682b76694]
+    Commit list:
+    b79f0369f2 ci: Make linux-prepare trust system installs.
+
+
+* Mon Oct 25 2021 Open vSwitch CI <ovs-ci@redhat.com> - 2.16.0-23
+- Merging upstream branch-2.16 [RH git: cce913794e]
+    Commit list:
+    2a4c87f300 Prepare for 2.16.2.
+    aaa1439b8e Set release date for 2.16.1.
+
+
+* Thu Oct 21 2021 Open vSwitch CI <ovs-ci@redhat.com> - 2.16.0-22
+- Merging upstream branch-2.16 [RH git: 29f01c4fdb]
+    Commit list:
+    108176ab5a github: Stick to python 3.9.
+
+
+* Tue Oct 19 2021 Open vSwitch CI <ovs-ci@redhat.com> - 2.16.0-21
+- Merging upstream branch-2.16 [RH git: 2546fa9646]
+    Commit list:
+    5c5e34603b datapath-windows: add layers when adding the deferred actions
+
+
+* Thu Oct 14 2021 Open vSwitch CI <ovs-ci@redhat.com> - 2.16.0-20
+- Merging upstream branch-2.16 [RH git: d572c95f69]
+    Commit list:
+    458a4f75f3 ofproto-dpif-xlate: Fix zone set from non-frozen-metadata fields.
+
+
+* Wed Oct 13 2021 Open vSwitch CI <ovs-ci@redhat.com> - 2.16.0-19
+- Merging upstream branch-2.16 [RH git: 557ca689f7]
+    Commit list:
+    6d8190584a dpif-netdev: Fix use-after-free on PACKET_OUT of IP fragments.
+    44a66cc1d0 tunnel-push-pop.at: Mask source port in tunnel header.
+
+
+* Tue Oct 12 2021 Open vSwitch CI <ovs-ci@redhat.com> - 2.16.0-18
+- Merging upstream branch-2.16 [RH git: a6c4770398]
+    Commit list:
+    27a5848a33 ovs-ctl: Add missing description for --ovs-vswitchd-options and --ovsdb-server-options to usage().
+    0300d0c0c2 dpdk-stub: Change the ERR log to DBG.
+    cdd6dd821d dpif-netlink: Fix feature negotiation for older kernels.
+    c2682c42cb dpif-netdev: Fix pmd thread comments to include SMC.
+    9377f4a465 python: idl: Avoid sending transactions when the DB is not synced up.
+
+
+* Tue Oct 12 2021 Open vSwitch CI <ovs-ci@redhat.com> - 2.16.0-17
+- Merging upstream branch-2.16 [RH git: c1145b5236]
+    Commit list:
+    0fd17fbb09 ipf: release unhandled packets from the batch
+
+
+* Thu Sep 30 2021 Open vSwitch CI <ovs-ci@redhat.com> - 2.16.0-16
+- Merging upstream branch-2.16 [RH git: 5c05133179]
+    Commit list:
+    3f692fba98 datapath-windows:adjust Offset when processing packet in POP_VLAN action
+
+
+* Wed Sep 29 2021 Dumitru Ceara <dceara@redhat.com> - 2.16.0-15
+- ovsdb-data: Deduplicate string atoms. [RH git: 24e7d1140e] (#2006839)
+    commit 429b114c5aadee24ccfb16ad7d824f45cdcea75a
+    Author: Ilya Maximets <i.maximets@ovn.org>
+    Date:   Wed Sep 22 09:28:50 2021 +0200
+    
+        ovsdb-server spends a lot of time cloning atoms for various reasons,
+        e.g. to create a diff of two rows or to clone a row to the transaction.
+        All atoms, except for strings, contains a simple value that could be
+        copied in efficient way, but duplicating strings every time has a
+        significant performance impact.
+    
+        Introducing a new reference-counted structure 'ovsdb_atom_string'
+        that allows to not copy strings every time, but just increase a
+        reference counter.
+    
+        This change allows to increase transaction throughput in benchmarks
+        up to 2x for standalone databases and 3x for clustered databases, i.e.
+        number of transactions that ovsdb-server can handle per second.
+        It also noticeably reduces memory consumption of ovsdb-server.
+    
+        Next step will be to consolidate this structure with json strings,
+        so we will not need to duplicate strings while converting database
+        objects to json and back.
+    
+        Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
+        Acked-by: Dumitru Ceara <dceara@redhat.com>
+        Acked-by: Mark D. Gray <mark.d.gray@redhat.com>
+    
+    Reported-at: https://bugzilla.redhat.com/show_bug.cgi?id=2006839
+    Signed-off-by: Dumitru Ceara <dceara@redhat.com>
+
+
+* Wed Sep 29 2021 Dumitru Ceara <dceara@redhat.com> - 2.16.0-14
+- ovsdb-data: Add function to apply diff in-place. [RH git: df0e4bda98] (#2006851)
+    commit 32b51326ef9c307b4acd0bacafb0218dd1372f3d
+    Author: Ilya Maximets <i.maximets@ovn.org>
+    Date:   Thu Sep 23 01:47:24 2021 +0200
+    
+        ovsdb_datum_apply_diff() is heavily used in ovsdb transactions, but
+        it's linear in terms of number of comparisons.  And it also clones
+        all the atoms along the way.  In most cases size of a diff is much
+        smaller than the size of the original datum, this allows to perform
+        the same operation in-place with only O(diff->n * log2(old->n))
+        comparisons and O(old->n + diff->n) memory copies with memcpy.
+        Using this function while applying diffs read from the storage gives
+        a significant performance boost and allows to execute much more
+        transactions per second.
+    
+        Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
+        Acked-by: Mark D. Gray <mark.d.gray@redhat.com>
+    
+    Reported-at: https://bugzilla.redhat.com/show_bug.cgi?id=2006851
+    Signed-off-by: Dumitru Ceara <dceara@redhat.com>
+
+
+* Wed Sep 29 2021 Dumitru Ceara <dceara@redhat.com> - 2.16.0-13
+- ovsdb-data: Optimize subtraction of sets. [RH git: 5bace82405] (#2005483)
+    commit bb12b63176389e516ddfefce20dfa165f24430fb
+    Author: Ilya Maximets <i.maximets@ovn.org>
+    Date:   Thu Sep 23 01:47:23 2021 +0200
+    
+        Current algorithm for ovsdb_datum_subtract looks like this:
+    
+          for-each atom in a:
+              if atom in b:
+                  swap(atom, <last atom in 'a'>)
+                  destroy(atom)
+          quicksort(a)
+    
+        Complexity:
+    
+          Na * log2(Nb)  +  (Na - Nb) * log2(Na - Nb)
+            Search          Comparisons for quicksort
+    
+        It's not optimal, especially because Nb << Na in a vast majority of
+        cases.
+    
+        Reversing the search phase to look up atoms from 'b' in 'a', and
+        closing gaps from deleted elements in 'a' by plain memory copy to
+        avoid quicksort.
+    
+        Resulted complexity:
+    
+          Nb * log2(Na)  +    (Na - Nb)
+            Search          Memory copies
+    
+        Subtraction is heavily used while executing database transactions.
+        For example, to remove one port from a logical switch in OVN.
+        Complexity of such operation if original logical switch had 100 ports
+        goes down from
+    
+         100 * log2(1)   = 100 comparisons for search and
+          99 * log2(99)  = 656 comparisons for quicksort
+                           ------------------------------
+                           756 comparisons in total
+        to only
+    
+           1 * log2(100) = 7 comparisons for search
+           + memory copy of 99 * sizeof (union ovsdb_atom) bytes.
+    
+        We could use memmove to close the gaps after removing atoms, but
+        it will lead to 2 memory copies inside the call, while we can perform
+        only one to the temporary 'result' and swap pointers.
+    
+        Performance in cases, where sizes of 'a' and 'b' are comparable,
+        should not change.  Cases with Nb >> Na should not happen in practice.
+    
+        All in all, this change allows ovsdb-server to perform several times
+        more transactions, that removes elements from sets, per second.
+    
+        Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
+        Acked-by: Han Zhou <hzhou@ovn.org>
+        Acked-by: Mark D. Gray <mark.d.gray@redhat.com>
+    
+    Reported-at: https://bugzilla.redhat.com/show_bug.cgi?id=2005483
+    Signed-off-by: Dumitru Ceara <dceara@redhat.com>
+
+
+* Wed Sep 29 2021 Dumitru Ceara <dceara@redhat.com> - 2.16.0-12
+- ovsdb-data: Optimize union of sets. [RH git: e2a4c7d794] (#2005483)
+    commit 51946d22274cd591dc061358fb507056fbd91420
+    Author: Ilya Maximets <i.maximets@ovn.org>
+    Date:   Thu Sep 23 01:47:22 2021 +0200
+    
+        Current algorithm of ovsdb_datum_union looks like this:
+    
+          for-each atom in b:
+              if not bin_search(a, atom):
+                  push(a, clone(atom))
+          quicksort(a)
+    
+        So, the complexity looks like this:
+    
+           Nb * log2(Na)   +    Nb     +   (Na + Nb) * log2(Na + Nb)
+           Comparisons        clones       Comparisons for quicksort
+           for search
+    
+        ovsdb_datum_union() is heavily used in database transactions while
+        new element is added to a set.  For example, if new logical switch
+        port is added to a logical switch in OVN.  This is a very common
+        use case where CMS adds one new port to an existing switch that
+        already has, let's say, 100 ports.  For this case ovsdb-server will
+        have to perform:
+    
+           1 * log2(100)  + 1 clone + 101 * log2(101)
+           Comparisons                Comparisons for
+           for search                   quicksort.
+               ~7           1            ~707
+           Roughly 714 comparisons of atoms and 1 clone.
+    
+        Since binary search can give us position, where new atom should go
+        (it's the 'low' index after the search completion) for free, the
+        logic can be re-worked like this:
+    
+          copied = 0
+          for-each atom in b:
+              desired_position = bin_search(a, atom)
+              push(result, a[ copied : desired_position - 1 ])
+              copied = desired_position
+              push(result, clone(atom))
+          push(result, a[ copied : Na ])
+          swap(a, result)
+    
+        Complexity of this schema:
+    
+           Nb * log2(Na)   +    Nb     +         Na
+           Comparisons        clones       memory copy on push
+           for search
+    
+        'swap' is just a swap of a few pointers.  'push' is not a 'clone',
+        but a simple memory copy of 'union ovsdb_atom'.
+    
+        In general, this schema substitutes complexity of a quicksort
+        with complexity of a memory copy of Na atom structures, where we're
+        not even copying strings that these atoms are pointing to.
+    
+        Complexity in the example above goes down from 714 comparisons
+        to 7 comparisons and memcpy of 100 * sizeof (union ovsdb_atom) bytes.
+    
+        General complexity of a memory copy should always be lower than
+        complexity of a quicksort, especially because these copies usually
+        performed in bulk, so this new schema should work faster for any input.
+    
+        All in all, this change allows to execute several times more
+        transactions per second for transactions that adds new entries to sets.
+    
+        Alternatively, union can be implemented as a linear merge of two
+        sorted arrays, but this will result in O(Na) comparisons, which
+        is more than Nb * log2(Na) in common case, since Na is usually
+        far bigger than Nb.  Linear merge will also mean per-atom memory
+        copies instead of copying in bulk.
+    
+        'replace' functionality of ovsdb_datum_union() had no users, so it
+        just removed.  But it can easily be added back if needed in the future.
+    
+        Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
+        Acked-by: Han Zhou <hzhou@ovn.org>
+        Acked-by: Mark D. Gray <mark.d.gray@redhat.com>
+    
+    Reported-at: https://bugzilla.redhat.com/show_bug.cgi?id=2005483
+    Signed-off-by: Dumitru Ceara <dceara@redhat.com>
+
+
+* Wed Sep 29 2021 Dumitru Ceara <dceara@redhat.com> - 2.16.0-11
+- ovsdb: transaction: Use diffs for strong reference counting. [RH git: 85da133eaa] (#2003203)
+    commit b2712d026eae2d9a5150c2805310eaf506e1f162
+    Author: Ilya Maximets <i.maximets@ovn.org>
+    Date:   Tue Sep 14 00:19:57 2021 +0200
+    
+        Currently, even if one reference added to the set of strong references
+        or removed from it, ovsdb-server will walk through the whole set and
+        re-count references to other rows.  These referenced rows will also be
+        added to the transaction in order to re-count their references.
+    
+        For example, every time Logical Switch Port added to a Logical Switch,
+        OVN Northbound database server will walk through all ports of this
+        Logical Switch, clone their rows, and re-count references.  This is
+        not very efficient.  Instead, it can only increase reference counters
+        for added references and reduce for removed ones.  In many cases this
+        will be only one row affected in the Logical_Switch_Port table.
+    
+        Introducing new function that generates a diff of two datum objects,
+        but stores added and removed atoms separately, so they can be used
+        to increase or decrease row reference counters accordingly.
+    
+        This change allows to perform several times more transactions that
+        adds or removes strong references to/from sets per second, because
+        ovsdb-server no longer clones and re-counts rows that are irrelevant
+        to current transaction.
+    
+        Acked-by: Dumitru Ceara <dceara@redhat.com>
+        Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
+    
+    Reported-at: https://bugzilla.redhat.com/show_bug.cgi?id=2003203
+    Signed-off-by: Dumitru Ceara <dceara@redhat.com>
+
+
+* Mon Sep 27 2021 Open vSwitch CI <ovs-ci@redhat.com> - 2.16.0-10
+- Merging upstream branch-2.16 [RH git: 2114714012]
+    Commit list:
+    547371ecdb cirrus: Reduce memory requirements for FreeBSD VMs.
+
+
+* Thu Sep 23 2021 Timothy Redaelli <tredaelli@redhat.com> - 2.16.0-9
+- redhat: use hugetlbfs group for /var/log/openvswitch when dpdk is enabled [RH git: 4e5928b671] (#2004543)
+    Resolves: #2004543
+
+
 * Thu Sep 16 2021 Open vSwitch CI <ovs-ci@redhat.com> - 2.16.0-8
 - Merging upstream branch-2.16 [RH git: 7332b410fc]
     Commit list: