diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..86ef991
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+SOURCES/*.tar.*
+SRPMS
diff --git a/.openvswitch.metadata b/.openvswitch.metadata
new file mode 100644
index 0000000..307e046
--- /dev/null
+++ b/.openvswitch.metadata
@@ -0,0 +1,5 @@
+002450621b33c5690060345b0aac25bc2426d675  SOURCES/docutils-0.12.tar.gz
+15b9809476e3235bb8d1644d82a85d8beb325539  SOURCES/openvswitch-2.16.0.tar.gz
+d34f96421a86004aa5d26ecf975edefd09f948b1  SOURCES/Pygments-1.4.tar.gz
+3a11f130c63b057532ca37fe49c8967d0cbae1d5  SOURCES/Sphinx-1.2.3.tar.gz
+1a6cfbd2cb017ab6915076705d58a37af8fff708  SOURCES/dpdk-20.11.1.tar.xz
diff --git a/SOURCES/.gitignore b/SOURCES/.gitignore
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/SOURCES/.gitignore
diff --git a/SOURCES/openvswitch-2.16.0.patch b/SOURCES/openvswitch-2.16.0.patch
new file mode 100644
index 0000000..7b9c0be
--- /dev/null
+++ b/SOURCES/openvswitch-2.16.0.patch
@@ -0,0 +1,1246 @@
+diff --git a/NEWS b/NEWS
+index 559a51ba3f..f2497d5cec 100644
+--- a/NEWS
++++ b/NEWS
+@@ -1,3 +1,6 @@
++v2.16.1 - xx xxx xxxx
++---------------------
++
+ 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
+--- a/configure.ac
++++ b/configure.ac
+@@ -13,7 +13,7 @@
+ # limitations under the License.
+ 
+ AC_PREREQ(2.63)
+-AC_INIT(openvswitch, 2.16.0, bugs@openvswitch.org)
++AC_INIT(openvswitch, 2.16.1, bugs@openvswitch.org)
+ AC_CONFIG_SRCDIR([datapath/datapath.c])
+ AC_CONFIG_MACRO_DIR([m4])
+ AC_CONFIG_AUX_DIR([build-aux])
+diff --git a/debian/changelog b/debian/changelog
+index 239d210b96..0f521be4d8 100644
+--- a/debian/changelog
++++ b/debian/changelog
+@@ -1,3 +1,9 @@
++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
++
+ openvswitch (2.16.0-1) unstable; urgency=low
+ 
+    * New upstream version
+diff --git a/include/openvswitch/json.h b/include/openvswitch/json.h
+index 73b562e03d..0831a9cee1 100644
+--- a/include/openvswitch/json.h
++++ b/include/openvswitch/json.h
+@@ -50,7 +50,9 @@ enum json_type {
+     JSON_INTEGER,               /* 123. */
+     JSON_REAL,                  /* 123.456. */
+     JSON_STRING,                /* "..." */
+-    JSON_N_TYPES
++    JSON_N_TYPES,
++    JSON_SERIALIZED_OBJECT,     /* Internal type to hold serialized version of
++                                 * data of other types. */
+ };
+ 
+ const char *json_type_to_string(enum json_type);
+@@ -70,7 +72,7 @@ struct json {
+         struct json_array array;
+         long long int integer;
+         double real;
+-        char *string;
++        char *string; /* JSON_STRING or JSON_SERIALIZED_OBJECT. */
+     };
+ };
+ 
+@@ -78,6 +80,7 @@ struct json *json_null_create(void);
+ struct json *json_boolean_create(bool);
+ struct json *json_string_create(const char *);
+ struct json *json_string_create_nocopy(char *);
++struct json *json_serialized_object_create(const struct json *);
+ struct json *json_integer_create(long long int);
+ struct json *json_real_create(double);
+ 
+@@ -99,6 +102,7 @@ void json_object_put_format(struct json *,
+     OVS_PRINTF_FORMAT(3, 4);
+ 
+ const char *json_string(const struct json *);
++const char *json_serialized_object(const struct json *);
+ struct json_array *json_array(const struct json *);
+ struct shash *json_object(const struct json *);
+ bool json_boolean(const struct json *);
+@@ -125,6 +129,7 @@ struct json *json_parser_finish(struct json_parser *);
+ void json_parser_abort(struct json_parser *);
+ 
+ struct json *json_from_string(const char *string);
++struct json *json_from_serialized_object(const struct json *);
+ struct json *json_from_file(const char *file_name);
+ struct json *json_from_stream(FILE *stream);
+ 
+diff --git a/lib/dp-packet.h b/lib/dp-packet.h
+index 08d93c2779..3dc582fbfd 100644
+--- a/lib/dp-packet.h
++++ b/lib/dp-packet.h
+@@ -199,6 +199,7 @@ struct dp_packet *dp_packet_clone_data_with_headroom(const void *, size_t,
+ void dp_packet_resize(struct dp_packet *b, size_t new_headroom,
+                       size_t new_tailroom);
+ static inline void dp_packet_delete(struct dp_packet *);
++static inline void dp_packet_swap(struct dp_packet *, struct dp_packet *);
+ 
+ static inline void *dp_packet_at(const struct dp_packet *, size_t offset,
+                                  size_t size);
+@@ -256,6 +257,18 @@ dp_packet_delete(struct dp_packet *b)
+     }
+ }
+ 
++/* Swaps content of two packets. */
++static inline void
++dp_packet_swap(struct dp_packet *a, struct dp_packet *b)
++{
++    ovs_assert(a->source == DPBUF_MALLOC || a->source == DPBUF_STUB);
++    ovs_assert(b->source == DPBUF_MALLOC || b->source == DPBUF_STUB);
++    struct dp_packet c = *a;
++
++    *a = *b;
++    *b = c;
++}
++
+ /* 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/dpif-netdev.c b/lib/dpif-netdev.c
+index bddce75b63..f9782b596f 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)
+                                flow_hash_5tuple(execute->flow, 0));
+     }
+ 
+-    dp_packet_batch_init_packet(&pp, execute->packet);
++    /* Making a copy because the packet might be stolen during the execution
++     * and caller might still need it.  */
++    struct dp_packet *packet_clone = dp_packet_clone(execute->packet);
++    dp_packet_batch_init_packet(&pp, packet_clone);
+     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)
+         dp_netdev_pmd_unref(pmd);
+     }
+ 
++    if (dp_packet_batch_size(&pp)) {
++        /* 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);
++        dp_packet_delete_batch(&pp, true);
++    }
++
+     return 0;
+ }
+ 
+diff --git a/lib/ipf.c b/lib/ipf.c
+index d9f781147a..665f40fefe 100644
+--- a/lib/ipf.c
++++ b/lib/ipf.c
+@@ -1152,52 +1152,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) {
++                const struct ipf_frag *frag_0 = &rp->list->frag_list[0];
++                void *l4_frag = dp_packet_l4(frag_0->pkt);
++                void *l4_reass = dp_packet_l4(pkt);
++                memcpy(l4_frag, l4_reass, dp_packet_l4_size(frag_0->pkt));
++
+                 for (int i = 0; i <= rp->list->last_inuse_idx; i++) {
+-                    rp->list->frag_list[i].pkt->md.ct_label = pkt->md.ct_label;
+-                    rp->list->frag_list[i].pkt->md.ct_mark = pkt->md.ct_mark;
+-                    rp->list->frag_list[i].pkt->md.ct_state = pkt->md.ct_state;
+-                    rp->list->frag_list[i].pkt->md.ct_zone = pkt->md.ct_zone;
+-                    rp->list->frag_list[i].pkt->md.ct_orig_tuple_ipv6 =
++                    const struct ipf_frag *frag_i = &rp->list->frag_list[i];
++
++                    frag_i->pkt->md.ct_label = pkt->md.ct_label;
++                    frag_i->pkt->md.ct_mark = pkt->md.ct_mark;
++                    frag_i->pkt->md.ct_state = pkt->md.ct_state;
++                    frag_i->pkt->md.ct_zone = pkt->md.ct_zone;
++                    frag_i->pkt->md.ct_orig_tuple_ipv6 =
+                         pkt->md.ct_orig_tuple_ipv6;
+                     if (pkt->md.ct_orig_tuple_ipv6) {
+-                        rp->list->frag_list[i].pkt->md.ct_orig_tuple.ipv6 =
++                        frag_i->pkt->md.ct_orig_tuple.ipv6 =
+                             pkt->md.ct_orig_tuple.ipv6;
+                     } else {
+-                        rp->list->frag_list[i].pkt->md.ct_orig_tuple.ipv4  =
++                        frag_i->pkt->md.ct_orig_tuple.ipv4 =
+                             pkt->md.ct_orig_tuple.ipv4;
+                     }
+-                }
+-
+-                const struct ipf_frag *frag_0 = &rp->list->frag_list[0];
+-                void *l4_frag = dp_packet_l4(frag_0->pkt);
+-                void *l4_reass = dp_packet_l4(pkt);
+-                memcpy(l4_frag, l4_reass, dp_packet_l4_size(frag_0->pkt));
+-
+-                if (v6) {
+-                    struct ovs_16aligned_ip6_hdr *l3_frag
+-                        = dp_packet_l3(frag_0->pkt);
+-                    struct ovs_16aligned_ip6_hdr *l3_reass = dp_packet_l3(pkt);
+-                    l3_frag->ip6_src = l3_reass->ip6_src;
+-                    l3_frag->ip6_dst = l3_reass->ip6_dst;
+-                } else {
+-                    struct ip_header *l3_frag = dp_packet_l3(frag_0->pkt);
+-                    struct ip_header *l3_reass = dp_packet_l3(pkt);
+-                    if (!dp_packet_hwol_is_ipv4(frag_0->pkt)) {
+-                        ovs_be32 reass_ip =
+-                            get_16aligned_be32(&l3_reass->ip_src);
+-                        ovs_be32 frag_ip =
+-                            get_16aligned_be32(&l3_frag->ip_src);
+-
+-                        l3_frag->ip_csum = recalc_csum32(l3_frag->ip_csum,
+-                                                         frag_ip, reass_ip);
+-                        reass_ip = get_16aligned_be32(&l3_reass->ip_dst);
+-                        frag_ip = get_16aligned_be32(&l3_frag->ip_dst);
+-                        l3_frag->ip_csum = recalc_csum32(l3_frag->ip_csum,
+-                                                         frag_ip, reass_ip);
++                    if (v6) {
++                        struct ovs_16aligned_ip6_hdr *l3_frag
++                            = dp_packet_l3(frag_i->pkt);
++                        struct ovs_16aligned_ip6_hdr *l3_reass
++                            = dp_packet_l3(pkt);
++                        l3_frag->ip6_src = l3_reass->ip6_src;
++                        l3_frag->ip6_dst = l3_reass->ip6_dst;
++                    } else {
++                        struct ip_header *l3_frag = dp_packet_l3(frag_i->pkt);
++                        struct ip_header *l3_reass = dp_packet_l3(pkt);
++                        if (!dp_packet_hwol_is_ipv4(frag_i->pkt)) {
++                            ovs_be32 reass_ip =
++                                get_16aligned_be32(&l3_reass->ip_src);
++                            ovs_be32 frag_ip =
++                                get_16aligned_be32(&l3_frag->ip_src);
++
++                            l3_frag->ip_csum = recalc_csum32(l3_frag->ip_csum,
++                                                             frag_ip,
++                                                             reass_ip);
++                            reass_ip = get_16aligned_be32(&l3_reass->ip_dst);
++                            frag_ip = get_16aligned_be32(&l3_frag->ip_dst);
++                            l3_frag->ip_csum = recalc_csum32(l3_frag->ip_csum,
++                                                             frag_ip,
++                                                             reass_ip);
++                        }
++
++                        l3_frag->ip_src = l3_reass->ip_src;
++                        l3_frag->ip_dst = l3_reass->ip_dst;
+                     }
+-
+-                    l3_frag->ip_src = l3_reass->ip_src;
+-                    l3_frag->ip_dst = l3_reass->ip_dst;
+                 }
+ 
+                 ipf_completed_list_add(&ipf->frag_complete_list, rp->list);
+diff --git a/lib/json.c b/lib/json.c
+index 32d25003b8..0baf7c622c 100644
+--- a/lib/json.c
++++ b/lib/json.c
+@@ -146,6 +146,7 @@ json_type_to_string(enum json_type type)
+     case JSON_STRING:
+         return "string";
+ 
++    case JSON_SERIALIZED_OBJECT:
+     case JSON_N_TYPES:
+     default:
+         return "<invalid>";
+@@ -180,6 +181,14 @@ json_string_create(const char *s)
+     return json_string_create_nocopy(xstrdup(s));
+ }
+ 
++struct json *
++json_serialized_object_create(const struct json *src)
++{
++    struct json *json = json_create(JSON_SERIALIZED_OBJECT);
++    json->string = json_to_string(src, JSSF_SORT);
++    return json;
++}
++
+ struct json *
+ json_array_create_empty(void)
+ {
+@@ -309,6 +318,13 @@ json_string(const struct json *json)
+     return json->string;
+ }
+ 
++const char *
++json_serialized_object(const struct json *json)
++{
++    ovs_assert(json->type == JSON_SERIALIZED_OBJECT);
++    return json->string;
++}
++
+ struct json_array *
+ json_array(const struct json *json)
+ {
+@@ -362,6 +378,7 @@ json_destroy(struct json *json)
+             break;
+ 
+         case JSON_STRING:
++        case JSON_SERIALIZED_OBJECT:
+             free(json->string);
+             break;
+ 
+@@ -422,6 +439,9 @@ json_deep_clone(const struct json *json)
+     case JSON_STRING:
+         return json_string_create(json->string);
+ 
++    case JSON_SERIALIZED_OBJECT:
++        return json_serialized_object_create(json);
++
+     case JSON_NULL:
+     case JSON_FALSE:
+     case JSON_TRUE:
+@@ -521,6 +541,7 @@ json_hash(const struct json *json, size_t basis)
+         return json_hash_array(&json->array, basis);
+ 
+     case JSON_STRING:
++    case JSON_SERIALIZED_OBJECT:
+         return hash_string(json->string, basis);
+ 
+     case JSON_NULL:
+@@ -596,6 +617,7 @@ json_equal(const struct json *a, const struct json *b)
+         return json_equal_array(&a->array, &b->array);
+ 
+     case JSON_STRING:
++    case JSON_SERIALIZED_OBJECT:
+         return !strcmp(a->string, b->string);
+ 
+     case JSON_NULL:
+@@ -1072,6 +1094,14 @@ json_from_string(const char *string)
+     return json_parser_finish(p);
+ }
+ 
++/* Parses data of JSON_SERIALIZED_OBJECT to the real JSON. */
++struct json *
++json_from_serialized_object(const struct json *json)
++{
++    ovs_assert(json->type == JSON_SERIALIZED_OBJECT);
++    return json_from_string(json->string);
++}
++
+ /* Reads the file named 'file_name', parses its contents as a JSON object or
+  * array, and returns a newly allocated 'struct json'.  The caller must free
+  * the returned structure with json_destroy() when it is no longer needed.
+@@ -1563,6 +1593,10 @@ json_serialize(const struct json *json, struct json_serializer *s)
+         json_serialize_string(json->string, ds);
+         break;
+ 
++    case JSON_SERIALIZED_OBJECT:
++        ds_put_cstr(ds, json->string);
++        break;
++
+     case JSON_N_TYPES:
+     default:
+         OVS_NOT_REACHED();
+@@ -1696,14 +1730,30 @@ json_serialize_string(const char *string, struct ds *ds)
+ {
+     uint8_t c;
+     uint8_t c2;
++    size_t count;
+     const char *escape;
++    const char *start;
+ 
+     ds_put_char(ds, '"');
++    count = 0;
++    start = string;
+     while ((c = *string++) != '\0') {
+-        escape = chars_escaping[c];
+-        while ((c2 = *escape++) != '\0') {
+-            ds_put_char(ds, c2);
++        if (c >= ' ' && c != '"' && c != '\\') {
++            count++;
++        } else {
++            if (count) {
++                ds_put_buffer(ds, start, count);
++                count = 0;
++            }
++            start = string;
++            escape = chars_escaping[c];
++            while ((c2 = *escape++) != '\0') {
++                ds_put_char(ds, c2);
++            }
+         }
+     }
++    if (count) {
++        ds_put_buffer(ds, start, count);
++    }
+     ds_put_char(ds, '"');
+ }
+diff --git a/lib/netdev-dpdk.c b/lib/netdev-dpdk.c
+index 45a96b9be2..ca92c947a2 100644
+--- a/lib/netdev-dpdk.c
++++ b/lib/netdev-dpdk.c
+@@ -961,14 +961,6 @@ dpdk_eth_dev_port_config(struct netdev_dpdk *dev, int n_rxq, int n_txq)
+ 
+     rte_eth_dev_info_get(dev->port_id, &info);
+ 
+-    /* As of DPDK 19.11, it is not allowed to set a mq_mode for
+-     * virtio PMD driver. */
+-    if (!strcmp(info.driver_name, "net_virtio")) {
+-        conf.rxmode.mq_mode = ETH_MQ_RX_NONE;
+-    } else {
+-        conf.rxmode.mq_mode = ETH_MQ_RX_RSS;
+-    }
+-
+     /* As of DPDK 17.11.1 a few PMDs require to explicitly enable
+      * scatter to support jumbo RX.
+      * Setting scatter for the device is done after checking for
+@@ -1000,6 +992,11 @@ dpdk_eth_dev_port_config(struct netdev_dpdk *dev, int n_rxq, int n_txq)
+     /* Limit configured rss hash functions to only those supported
+      * by the eth device. */
+     conf.rx_adv_conf.rss_conf.rss_hf &= info.flow_type_rss_offloads;
++    if (conf.rx_adv_conf.rss_conf.rss_hf == 0) {
++        conf.rxmode.mq_mode = ETH_MQ_RX_NONE;
++    } else {
++        conf.rxmode.mq_mode = ETH_MQ_RX_RSS;
++    }
+ 
+     /* A device may report more queues than it makes available (this has
+      * been observed for Intel xl710, which reserves some of them for
+diff --git a/lib/netdev-linux.c b/lib/netdev-linux.c
+index 60dd138914..97bd21be4a 100644
+--- a/lib/netdev-linux.c
++++ b/lib/netdev-linux.c
+@@ -627,6 +627,7 @@ netdev_linux_notify_sock(void)
+         if (!error) {
+             size_t i;
+ 
++            nl_sock_listen_all_nsid(sock, true);
+             for (i = 0; i < ARRAY_SIZE(mcgroups); i++) {
+                 error = nl_sock_join_mcgroup(sock, mcgroups[i]);
+                 if (error) {
+@@ -636,7 +637,6 @@ netdev_linux_notify_sock(void)
+                 }
+             }
+         }
+-        nl_sock_listen_all_nsid(sock, true);
+         ovsthread_once_done(&once);
+     }
+ 
+diff --git a/lib/odp-util.c b/lib/odp-util.c
+index 7729a90608..fbdfc7ad83 100644
+--- a/lib/odp-util.c
++++ b/lib/odp-util.c
+@@ -2941,7 +2941,7 @@ odp_nsh_key_from_attr__(const struct nlattr *attr, bool is_mask,
+             const struct ovs_nsh_key_md1 *md1 = nl_attr_get(a);
+             has_md1 = true;
+             memcpy(nsh->context, md1->context, sizeof md1->context);
+-            if (len == 2 * sizeof(*md1)) {
++            if (nsh_mask && (len == 2 * sizeof *md1)) {
+                 const struct ovs_nsh_key_md1 *md1_mask = md1 + 1;
+                 memcpy(nsh_mask->context, md1_mask->context,
+                        sizeof(*md1_mask));
+@@ -4618,7 +4618,7 @@ odp_flow_format(const struct nlattr *key, size_t key_len,
+             }
+             ds_put_char(ds, ')');
+         }
+-        if (!has_ethtype_key) {
++        if (!has_ethtype_key && mask) {
+             const struct nlattr *ma = nl_attr_find__(mask, mask_len,
+                                                      OVS_KEY_ATTR_ETHERTYPE);
+             if (ma) {
+diff --git a/lib/pcap-file.c b/lib/pcap-file.c
+index b30a11c24b..41835f6f4d 100644
+--- a/lib/pcap-file.c
++++ b/lib/pcap-file.c
+@@ -89,6 +89,7 @@ ovs_pcap_open(const char *file_name, const char *mode)
+                    : mode[0] == 'w' ? "writing"
+                    : "appending"),
+                   ovs_strerror(errno));
++        free(p_file);
+         return NULL;
+     }
+ 
+diff --git a/ovsdb/monitor.c b/ovsdb/monitor.c
+index 532dedcb64..ab814cf20e 100644
+--- a/ovsdb/monitor.c
++++ b/ovsdb/monitor.c
+@@ -1231,6 +1231,15 @@ ovsdb_monitor_get_update(
+                                             condition,
+                                             ovsdb_monitor_compose_row_update2);
+                 if (!condition || !condition->conditional) {
++                    if (json) {
++                        struct json *json_serialized;
++
++                        /* Pre-serializing the object to avoid doing this
++                         * for every client. */
++                        json_serialized = json_serialized_object_create(json);
++                        json_destroy(json);
++                        json = json_serialized;
++                    }
+                     ovsdb_monitor_json_cache_insert(dbmon, version, mcs,
+                                                     json);
+                 }
+diff --git a/ovsdb/ovsdb-tool.c b/ovsdb/ovsdb-tool.c
+index 05a0223e71..d4a9e34cc4 100644
+--- a/ovsdb/ovsdb-tool.c
++++ b/ovsdb/ovsdb-tool.c
+@@ -919,7 +919,8 @@ print_raft_header(const struct raft_header *h,
+         if (!uuid_is_zero(&h->snap.eid)) {
+             printf(" prev_eid: %04x\n", uuid_prefix(&h->snap.eid, 4));
+         }
+-        print_data("prev_", h->snap.data, schemap, names);
++        print_data("prev_", raft_entry_get_parsed_data(&h->snap),
++                            schemap, names);
+     }
+ }
+ 
+@@ -973,11 +974,13 @@ raft_header_to_standalone_log(const struct raft_header *h,
+                               struct ovsdb_log *db_log_data)
+ {
+     if (h->snap_index) {
+-        if (!h->snap.data || json_array(h->snap.data)->n != 2) {
++        const struct json *data = raft_entry_get_parsed_data(&h->snap);
++
++        if (!data || json_array(data)->n != 2) {
+             ovs_fatal(0, "Incorrect raft header data array length");
+         }
+ 
+-        struct json_array *pa = json_array(h->snap.data);
++        struct json_array *pa = json_array(data);
+         struct json *schema_json = pa->elems[0];
+         struct ovsdb_error *error = NULL;
+ 
+@@ -1373,7 +1376,7 @@ do_check_cluster(struct ovs_cmdl_context *ctx)
+                 }
+                 struct raft_entry *e = &s->entries[log_idx];
+                 e->term = r->term;
+-                e->data = r->entry.data;
++                raft_entry_set_parsed_data_nocopy(e, r->entry.data);
+                 e->eid = r->entry.eid;
+                 e->servers = r->entry.servers;
+                 break;
+diff --git a/ovsdb/raft-private.c b/ovsdb/raft-private.c
+index 26d39a087f..30760233ee 100644
+--- a/ovsdb/raft-private.c
++++ b/ovsdb/raft-private.c
+@@ -18,11 +18,14 @@
+ 
+ #include "raft-private.h"
+ 
++#include "coverage.h"
+ #include "openvswitch/dynamic-string.h"
+ #include "ovsdb-error.h"
+ #include "ovsdb-parser.h"
+ #include "socket-util.h"
+ #include "sset.h"
++
++COVERAGE_DEFINE(raft_entry_serialize);
+ 
+ /* Addresses of Raft servers. */
+ 
+@@ -281,7 +284,8 @@ void
+ raft_entry_clone(struct raft_entry *dst, const struct raft_entry *src)
+ {
+     dst->term = src->term;
+-    dst->data = json_nullable_clone(src->data);
++    dst->data.full_json = json_nullable_clone(src->data.full_json);
++    dst->data.serialized = json_nullable_clone(src->data.serialized);
+     dst->eid = src->eid;
+     dst->servers = json_nullable_clone(src->servers);
+     dst->election_timer = src->election_timer;
+@@ -291,7 +295,8 @@ void
+ raft_entry_uninit(struct raft_entry *e)
+ {
+     if (e) {
+-        json_destroy(e->data);
++        json_destroy(e->data.full_json);
++        json_destroy(e->data.serialized);
+         json_destroy(e->servers);
+     }
+ }
+@@ -301,8 +306,9 @@ raft_entry_to_json(const struct raft_entry *e)
+ {
+     struct json *json = json_object_create();
+     raft_put_uint64(json, "term", e->term);
+-    if (e->data) {
+-        json_object_put(json, "data", json_clone(e->data));
++    if (raft_entry_has_data(e)) {
++        json_object_put(json, "data",
++                        json_clone(raft_entry_get_serialized_data(e)));
+         json_object_put_format(json, "eid", UUID_FMT, UUID_ARGS(&e->eid));
+     }
+     if (e->servers) {
+@@ -323,9 +329,10 @@ raft_entry_from_json(struct json *json, struct raft_entry *e)
+     struct ovsdb_parser p;
+     ovsdb_parser_init(&p, json, "raft log entry");
+     e->term = raft_parse_required_uint64(&p, "term");
+-    e->data = json_nullable_clone(
++    raft_entry_set_parsed_data(e,
+         ovsdb_parser_member(&p, "data", OP_OBJECT | OP_ARRAY | OP_OPTIONAL));
+-    e->eid = e->data ? raft_parse_required_uuid(&p, "eid") : UUID_ZERO;
++    e->eid = raft_entry_has_data(e)
++             ? raft_parse_required_uuid(&p, "eid") : UUID_ZERO;
+     e->servers = json_nullable_clone(
+         ovsdb_parser_member(&p, "servers", OP_OBJECT | OP_OPTIONAL));
+     if (e->servers) {
+@@ -344,9 +351,72 @@ bool
+ raft_entry_equals(const struct raft_entry *a, const struct raft_entry *b)
+ {
+     return (a->term == b->term
+-            && json_equal(a->data, b->data)
+             && uuid_equals(&a->eid, &b->eid)
+-            && json_equal(a->servers, b->servers));
++            && json_equal(a->servers, b->servers)
++            && json_equal(raft_entry_get_parsed_data(a),
++                          raft_entry_get_parsed_data(b)));
++}
++
++bool
++raft_entry_has_data(const struct raft_entry *e)
++{
++    return e->data.full_json || e->data.serialized;
++}
++
++static void
++raft_entry_data_serialize(struct raft_entry *e)
++{
++    if (!raft_entry_has_data(e) || e->data.serialized) {
++        return;
++    }
++    COVERAGE_INC(raft_entry_serialize);
++    e->data.serialized = json_serialized_object_create(e->data.full_json);
++}
++
++void
++raft_entry_set_parsed_data_nocopy(struct raft_entry *e, struct json *json)
++{
++    ovs_assert(!json || json->type != JSON_SERIALIZED_OBJECT);
++    e->data.full_json = json;
++    e->data.serialized = NULL;
++}
++
++void
++raft_entry_set_parsed_data(struct raft_entry *e, const struct json *json)
++{
++    raft_entry_set_parsed_data_nocopy(e, json_nullable_clone(json));
++}
++
++/* Returns a pointer to the fully parsed json object of the data.
++ * Caller takes the ownership of the result.
++ *
++ * Entry will no longer contain a fully parsed json object.
++ * Subsequent calls for the same raft entry will return NULL. */
++struct json * OVS_WARN_UNUSED_RESULT
++raft_entry_steal_parsed_data(struct raft_entry *e)
++{
++    /* Ensure that serialized version exists. */
++    raft_entry_data_serialize(e);
++
++    struct json *json = e->data.full_json;
++    e->data.full_json = NULL;
++
++    return json;
++}
++
++/* Returns a pointer to the fully parsed json object of the data, if any. */
++const struct json *
++raft_entry_get_parsed_data(const struct raft_entry *e)
++{
++    return e->data.full_json;
++}
++
++/* Returns a pointer to the JSON_SERIALIZED_OBJECT of the data. */
++const struct json *
++raft_entry_get_serialized_data(const struct raft_entry *e)
++{
++    raft_entry_data_serialize(CONST_CAST(struct raft_entry *, e));
++    return e->data.serialized;
+ }
+ 
+ void
+@@ -402,8 +472,8 @@ raft_header_from_json__(struct raft_header *h, struct ovsdb_parser *p)
+          * present, all of them must be. */
+         h->snap_index = raft_parse_optional_uint64(p, "prev_index");
+         if (h->snap_index) {
+-            h->snap.data = json_nullable_clone(
+-                ovsdb_parser_member(p, "prev_data", OP_ANY));
++            raft_entry_set_parsed_data(
++                &h->snap, ovsdb_parser_member(p, "prev_data", OP_ANY));
+             h->snap.eid = raft_parse_required_uuid(p, "prev_eid");
+             h->snap.term = raft_parse_required_uint64(p, "prev_term");
+             h->snap.election_timer = raft_parse_optional_uint64(
+@@ -455,8 +525,9 @@ raft_header_to_json(const struct raft_header *h)
+     if (h->snap_index) {
+         raft_put_uint64(json, "prev_index", h->snap_index);
+         raft_put_uint64(json, "prev_term", h->snap.term);
+-        if (h->snap.data) {
+-            json_object_put(json, "prev_data", json_clone(h->snap.data));
++        if (raft_entry_has_data(&h->snap)) {
++            json_object_put(json, "prev_data",
++                json_clone(raft_entry_get_serialized_data(&h->snap)));
+         }
+         json_object_put_format(json, "prev_eid",
+                                UUID_FMT, UUID_ARGS(&h->snap.eid));
+diff --git a/ovsdb/raft-private.h b/ovsdb/raft-private.h
+index a69e37e5c2..48c6df511f 100644
+--- a/ovsdb/raft-private.h
++++ b/ovsdb/raft-private.h
+@@ -118,7 +118,10 @@ void raft_servers_format(const struct hmap *servers, struct ds *ds);
+  * entry.  */
+ struct raft_entry {
+     uint64_t term;
+-    struct json *data;
++    struct {
++        struct json *full_json;   /* Fully parsed JSON object. */
++        struct json *serialized;  /* JSON_SERIALIZED_OBJECT version of data. */
++    } data;
+     struct uuid eid;
+     struct json *servers;
+     uint64_t election_timer;
+@@ -130,6 +133,13 @@ struct json *raft_entry_to_json(const struct raft_entry *);
+ struct ovsdb_error *raft_entry_from_json(struct json *, struct raft_entry *)
+     OVS_WARN_UNUSED_RESULT;
+ bool raft_entry_equals(const struct raft_entry *, const struct raft_entry *);
++bool raft_entry_has_data(const struct raft_entry *);
++void raft_entry_set_parsed_data(struct raft_entry *, const struct json *);
++void raft_entry_set_parsed_data_nocopy(struct raft_entry *, struct json *);
++struct json *raft_entry_steal_parsed_data(struct raft_entry *)
++    OVS_WARN_UNUSED_RESULT;
++const struct json *raft_entry_get_parsed_data(const struct raft_entry *);
++const struct json *raft_entry_get_serialized_data(const struct raft_entry *);
+ 
+ /* On disk data serialization and deserialization. */
+ 
+diff --git a/ovsdb/raft.c b/ovsdb/raft.c
+index 2fb5156519..ce40c5bc07 100644
+--- a/ovsdb/raft.c
++++ b/ovsdb/raft.c
+@@ -494,11 +494,11 @@ raft_create_cluster(const char *file_name, const char *name,
+         .snap_index = index++,
+         .snap = {
+             .term = term,
+-            .data = json_nullable_clone(data),
+             .eid = uuid_random(),
+             .servers = json_object_create(),
+         },
+     };
++    raft_entry_set_parsed_data(&h.snap, data);
+     shash_add_nocopy(json_object(h.snap.servers),
+                      xasprintf(UUID_FMT, UUID_ARGS(&h.sid)),
+                      json_string_create(local_address));
+@@ -727,10 +727,10 @@ raft_add_entry(struct raft *raft,
+     uint64_t index = raft->log_end++;
+     struct raft_entry *entry = &raft->entries[index - raft->log_start];
+     entry->term = term;
+-    entry->data = data;
+     entry->eid = eid ? *eid : UUID_ZERO;
+     entry->servers = servers;
+     entry->election_timer = election_timer;
++    raft_entry_set_parsed_data_nocopy(entry, data);
+     return index;
+ }
+ 
+@@ -741,13 +741,16 @@ raft_write_entry(struct raft *raft, uint64_t term, struct json *data,
+                  const struct uuid *eid, struct json *servers,
+                  uint64_t election_timer)
+ {
++    uint64_t index = raft_add_entry(raft, term, data, eid, servers,
++                                    election_timer);
++    const struct json *entry_data = raft_entry_get_serialized_data(
++                                      &raft->entries[index - raft->log_start]);
+     struct raft_record r = {
+         .type = RAFT_REC_ENTRY,
+         .term = term,
+         .entry = {
+-            .index = raft_add_entry(raft, term, data, eid, servers,
+-                                    election_timer),
+-            .data = data,
++            .index = index,
++            .data = CONST_CAST(struct json *, entry_data),
+             .servers = servers,
+             .election_timer = election_timer,
+             .eid = eid ? *eid : UUID_ZERO,
+@@ -2161,7 +2164,7 @@ raft_get_eid(const struct raft *raft, uint64_t index)
+ {
+     for (; index >= raft->log_start; index--) {
+         const struct raft_entry *e = raft_get_entry(raft, index);
+-        if (e->data) {
++        if (raft_entry_has_data(e)) {
+             return &e->eid;
+         }
+     }
+@@ -2826,8 +2829,8 @@ raft_truncate(struct raft *raft, uint64_t new_end)
+     return servers_changed;
+ }
+ 
+-static const struct json *
+-raft_peek_next_entry(struct raft *raft, struct uuid *eid)
++static const struct raft_entry *
++raft_peek_next_entry(struct raft *raft)
+ {
+     /* Invariant: log_start - 2 <= last_applied <= commit_index < log_end. */
+     ovs_assert(raft->log_start <= raft->last_applied + 2);
+@@ -2839,32 +2842,20 @@ raft_peek_next_entry(struct raft *raft, struct uuid *eid)
+     }
+ 
+     if (raft->log_start == raft->last_applied + 2) {
+-        *eid = raft->snap.eid;
+-        return raft->snap.data;
++        return &raft->snap;
+     }
+ 
+     while (raft->last_applied < raft->commit_index) {
+         const struct raft_entry *e = raft_get_entry(raft,
+                                                     raft->last_applied + 1);
+-        if (e->data) {
+-            *eid = e->eid;
+-            return e->data;
++        if (raft_entry_has_data(e)) {
++            return e;
+         }
+         raft->last_applied++;
+     }
+     return NULL;
+ }
+ 
+-static const struct json *
+-raft_get_next_entry(struct raft *raft, struct uuid *eid)
+-{
+-    const struct json *data = raft_peek_next_entry(raft, eid);
+-    if (data) {
+-        raft->last_applied++;
+-    }
+-    return data;
+-}
+-
+ /* Updates commit index in raft log. If commit index is already up-to-date
+  * it does nothing and return false, otherwise, returns true. */
+ static bool
+@@ -2878,7 +2869,7 @@ raft_update_commit_index(struct raft *raft, uint64_t new_commit_index)
+         while (raft->commit_index < new_commit_index) {
+             uint64_t index = ++raft->commit_index;
+             const struct raft_entry *e = raft_get_entry(raft, index);
+-            if (e->data) {
++            if (raft_entry_has_data(e)) {
+                 struct raft_command *cmd
+                     = raft_find_command_by_eid(raft, &e->eid);
+                 if (cmd) {
+@@ -3059,7 +3050,9 @@ raft_handle_append_entries(struct raft *raft,
+     for (; i < n_entries; i++) {
+         const struct raft_entry *e = &entries[i];
+         error = raft_write_entry(raft, e->term,
+-                                 json_nullable_clone(e->data), &e->eid,
++                                 json_nullable_clone(
++                                    raft_entry_get_parsed_data(e)),
++                                 &e->eid,
+                                  json_nullable_clone(e->servers),
+                                  e->election_timer);
+         if (error) {
+@@ -3314,20 +3307,29 @@ bool
+ raft_has_next_entry(const struct raft *raft_)
+ {
+     struct raft *raft = CONST_CAST(struct raft *, raft_);
+-    struct uuid eid;
+-    return raft_peek_next_entry(raft, &eid) != NULL;
++    return raft_peek_next_entry(raft) != NULL;
+ }
+ 
+ /* Returns the next log entry or snapshot from 'raft', or NULL if there are
+- * none left to read.  Stores the entry ID of the log entry in '*eid'.  Stores
+- * true in '*is_snapshot' if the returned data is a snapshot, false if it is a
+- * log entry. */
+-const struct json *
+-raft_next_entry(struct raft *raft, struct uuid *eid, bool *is_snapshot)
++ * none left to read.  Stores the entry ID of the log entry in '*eid'.
++ *
++ * The caller takes ownership of the result. */
++struct json * OVS_WARN_UNUSED_RESULT
++raft_next_entry(struct raft *raft, struct uuid *eid)
+ {
+-    const struct json *data = raft_get_next_entry(raft, eid);
+-    *is_snapshot = data == raft->snap.data;
+-    return data;
++    const struct raft_entry *e = raft_peek_next_entry(raft);
++
++    if (!e) {
++        return NULL;
++    }
++
++    raft->last_applied++;
++    *eid = e->eid;
++
++    /* DB will only read each entry once, so we don't need to store the fully
++     * parsed json object any longer.  The serialized version is sufficient
++     * for sending to other cluster members or writing to the log. */
++    return raft_entry_steal_parsed_data(CONST_CAST(struct raft_entry *, e));
+ }
+ 
+ /* Returns the log index of the last-read snapshot or log entry. */
+@@ -3420,6 +3422,7 @@ raft_send_install_snapshot_request(struct raft *raft,
+                                    const struct raft_server *s,
+                                    const char *comment)
+ {
++    const struct json *data = raft_entry_get_serialized_data(&raft->snap);
+     union raft_rpc rpc = {
+         .install_snapshot_request = {
+             .common = {
+@@ -3432,7 +3435,7 @@ raft_send_install_snapshot_request(struct raft *raft,
+             .last_term = raft->snap.term,
+             .last_servers = raft->snap.servers,
+             .last_eid = raft->snap.eid,
+-            .data = raft->snap.data,
++            .data = CONST_CAST(struct json *, data),
+             .election_timer = raft->election_timer, /* use latest value */
+         }
+     };
+@@ -3980,6 +3983,10 @@ raft_write_snapshot(struct raft *raft, struct ovsdb_log *log,
+                     uint64_t new_log_start,
+                     const struct raft_entry *new_snapshot)
+ {
++    /* Ensure that new snapshot contains serialized data object, so it will
++     * not be allocated while serializing the on-stack raft header object. */
++    ovs_assert(raft_entry_get_serialized_data(new_snapshot));
++
+     struct raft_header h = {
+         .sid = raft->sid,
+         .cid = raft->cid,
+@@ -3998,12 +4005,13 @@ raft_write_snapshot(struct raft *raft, struct ovsdb_log *log,
+     /* Write log records. */
+     for (uint64_t index = new_log_start; index < raft->log_end; index++) {
+         const struct raft_entry *e = &raft->entries[index - raft->log_start];
++        const struct json *log_data = raft_entry_get_serialized_data(e);
+         struct raft_record r = {
+             .type = RAFT_REC_ENTRY,
+             .term = e->term,
+             .entry = {
+                 .index = index,
+-                .data = e->data,
++                .data = CONST_CAST(struct json *, log_data),
+                 .servers = e->servers,
+                 .election_timer = e->election_timer,
+                 .eid = e->eid,
+@@ -4093,19 +4101,21 @@ raft_handle_install_snapshot_request__(
+ 
+     /* Case 3: The new snapshot starts past the end of our current log, so
+      * discard all of our current log. */
+-    const struct raft_entry new_snapshot = {
++    struct raft_entry new_snapshot = {
+         .term = rq->last_term,
+-        .data = rq->data,
+         .eid = rq->last_eid,
+-        .servers = rq->last_servers,
++        .servers = json_clone(rq->last_servers),
+         .election_timer = rq->election_timer,
+     };
++    raft_entry_set_parsed_data(&new_snapshot, rq->data);
++
+     struct ovsdb_error *error = raft_save_snapshot(raft, new_log_start,
+                                                    &new_snapshot);
+     if (error) {
+         char *error_s = ovsdb_error_to_string_free(error);
+         VLOG_WARN("could not save snapshot: %s", error_s);
+         free(error_s);
++        raft_entry_uninit(&new_snapshot);
+         return false;
+     }
+ 
+@@ -4120,7 +4130,7 @@ raft_handle_install_snapshot_request__(
+     }
+ 
+     raft_entry_uninit(&raft->snap);
+-    raft_entry_clone(&raft->snap, &new_snapshot);
++    raft->snap = new_snapshot;
+ 
+     raft_get_servers_from_log(raft, VLL_INFO);
+     raft_get_election_timer_from_log(raft);
+@@ -4265,11 +4275,12 @@ raft_store_snapshot(struct raft *raft, const struct json *new_snapshot_data)
+     uint64_t new_log_start = raft->last_applied + 1;
+     struct raft_entry new_snapshot = {
+         .term = raft_get_term(raft, new_log_start - 1),
+-        .data = json_clone(new_snapshot_data),
+         .eid = *raft_get_eid(raft, new_log_start - 1),
+         .servers = json_clone(raft_servers_for_index(raft, new_log_start - 1)),
+         .election_timer = raft->election_timer,
+     };
++    raft_entry_set_parsed_data(&new_snapshot, new_snapshot_data);
++
+     struct ovsdb_error *error = raft_save_snapshot(raft, new_log_start,
+                                                    &new_snapshot);
+     if (error) {
+@@ -4286,6 +4297,9 @@ raft_store_snapshot(struct raft *raft, const struct json *new_snapshot_data)
+     memmove(&raft->entries[0], &raft->entries[new_log_start - raft->log_start],
+             (raft->log_end - new_log_start) * sizeof *raft->entries);
+     raft->log_start = new_log_start;
++    /* It's a snapshot of the current database state, ovsdb-server will not
++     * read it back.  Destroying the parsed json object to not waste memory. */
++    json_destroy(raft_entry_steal_parsed_data(&raft->snap));
+     return NULL;
+ }
+ 
+diff --git a/ovsdb/raft.h b/ovsdb/raft.h
+index 3545c41c2c..599bc0ae86 100644
+--- a/ovsdb/raft.h
++++ b/ovsdb/raft.h
+@@ -132,8 +132,8 @@ bool raft_left(const struct raft *);
+ bool raft_failed(const struct raft *);
+ 
+ /* Reading snapshots and log entries. */
+-const struct json *raft_next_entry(struct raft *, struct uuid *eid,
+-                                   bool *is_snapshot);
++struct json *raft_next_entry(struct raft *, struct uuid *eid)
++    OVS_WARN_UNUSED_RESULT;
+ bool raft_has_next_entry(const struct raft *);
+ 
+ uint64_t raft_get_applied_index(const struct raft *);
+diff --git a/ovsdb/storage.c b/ovsdb/storage.c
+index d727b1eacd..9e32efe582 100644
+--- a/ovsdb/storage.c
++++ b/ovsdb/storage.c
+@@ -268,9 +268,7 @@ ovsdb_storage_read(struct ovsdb_storage *storage,
+     struct json *schema_json = NULL;
+     struct json *txn_json = NULL;
+     if (storage->raft) {
+-        bool is_snapshot;
+-        json = json_nullable_clone(
+-            raft_next_entry(storage->raft, txnid, &is_snapshot));
++        json = raft_next_entry(storage->raft, txnid);
+         if (!json) {
+             return NULL;
+         } else if (json->type != JSON_ARRAY || json->array.n != 2) {
+diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
+index 956a69e1fa..1dad6f62c6 100644
+--- a/tests/ofproto-dpif.at
++++ b/tests/ofproto-dpif.at
+@@ -9695,6 +9695,26 @@ OFPST_TABLE reply (OF1.3) (xid=0x2):
+ OVS_VSWITCHD_STOP
+ AT_CLEANUP
+ 
++AT_SETUP([ofproto-dpif packet-out table meter drop])
++OVS_VSWITCHD_START
++add_of_ports br0 1 2
++
++AT_CHECK([ovs-ofctl -O OpenFlow13 add-meter br0 'meter=1 pktps bands=type=drop rate=1'])
++AT_CHECK([ovs-ofctl -O OpenFlow13 add-flow br0 'in_port=1 action=meter:1,output:2'])
++
++ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=1 packet=50540000000a50540000000908004500001c000000000011a4cd0a0101010a0101020001000400080000 actions=resubmit(,0)"
++ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=1 packet=50540000000a50540000000908004500001c000000000011a4cd0a0101010a0101020001000400080000 actions=resubmit(,0)"
++
++# Check that vswitchd hasn't crashed by dumping the meter added above
++AT_CHECK([ovs-ofctl -O OpenFlow13 dump-meters br0 | ofctl_strip], [0], [dnl
++OFPST_METER_CONFIG reply (OF1.3):
++meter=1 pktps bands=
++type=drop rate=1
++])
++
++OVS_VSWITCHD_STOP
++AT_CLEANUP
++
+ AT_SETUP([ofproto-dpif - ICMPv6])
+ OVS_VSWITCHD_START
+ add_of_ports br0 1
+diff --git a/tests/system-traffic.at b/tests/system-traffic.at
+index f400cfabc9..c4442c183f 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
+ OVS_TRAFFIC_VSWITCHD_STOP
+ AT_CLEANUP
+ 
++AT_SETUP([conntrack - IPv4 Fragmentation + NAT])
++AT_SKIP_IF([test $HAVE_TCPDUMP = no])
++CHECK_CONNTRACK()
++
++OVS_TRAFFIC_VSWITCHD_START(
++   [set-fail-mode br0 secure -- ])
++
++ADD_NAMESPACES(at_ns0, at_ns1)
++
++ADD_VETH(p0, at_ns0, br0, "10.2.1.1/24")
++ADD_VETH(p1, at_ns1, br0, "10.2.1.2/24")
++
++dnl Create a dummy route for NAT
++NS_CHECK_EXEC([at_ns1], [ip addr add 10.1.1.2/32 dev lo])
++NS_CHECK_EXEC([at_ns0], [ip route add 10.1.1.0/24 via 10.2.1.2])
++NS_CHECK_EXEC([at_ns1], [ip route add 10.1.1.0/24 via 10.2.1.1])
++
++dnl Solely for debugging when things go wrong
++NS_EXEC([at_ns0], [tcpdump -l -n -xx -U -i p0 -w p0.pcap >tcpdump.out 2>/dev/null &])
++NS_EXEC([at_ns1], [tcpdump -l -n -xx -U -i p1 -w p1.pcap >tcpdump.out 2>/dev/null &])
++
++AT_DATA([flows.txt], [dnl
++table=0,arp,actions=normal
++table=0,ct_state=-trk,ip,in_port=ovs-p0, actions=ct(table=1, nat)
++table=0,ct_state=-trk,ip,in_port=ovs-p1, actions=ct(table=1, nat)
++table=1,ct_state=+trk+new,ip,in_port=ovs-p0, actions=ct(commit, nat(src=10.1.1.1)),ovs-p1
++table=1,ct_state=+trk+est,ip,in_port=ovs-p0, actions=ovs-p1
++table=1,ct_state=+trk+est,ip,in_port=ovs-p1, actions=ovs-p0
++])
++
++AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
++
++dnl Check connectivity
++NS_CHECK_EXEC([at_ns0], [ping -c 1 10.1.1.2 -M dont -s 4500 | FORMAT_PING], [0], [dnl
++1 packets transmitted, 1 received, 0% packet loss, time 0ms
++])
++
++OVS_TRAFFIC_VSWITCHD_STOP
++AT_CLEANUP
++
+ AT_SETUP([conntrack - resubmit to ct multiple times])
+ CHECK_CONNTRACK()
+ 
+diff --git a/tests/test-json.c b/tests/test-json.c
+index a7ee595e0b..072a537252 100644
+--- a/tests/test-json.c
++++ b/tests/test-json.c
+@@ -22,6 +22,8 @@
+ #include <getopt.h>
+ #include <stdio.h>
+ #include "ovstest.h"
++#include "random.h"
++#include "timeval.h"
+ #include "util.h"
+ 
+ /* --pretty: If set, the JSON output is pretty-printed, instead of printed as
+@@ -157,3 +159,69 @@ test_json_main(int argc, char *argv[])
+ }
+ 
+ OVSTEST_REGISTER("test-json", test_json_main);
++
++static void
++json_string_benchmark_main(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
++{
++    struct {
++        int n;
++        int quote_probablility;
++        int special_probability;
++        int iter;
++    } configs[] = {
++        { 100000,     0, 0, 1000, },
++        { 100000,     2, 1, 1000, },
++        { 100000,    10, 1, 1000, },
++        { 10000000,   0, 0, 100,  },
++        { 10000000,   2, 1, 100,  },
++        { 10000000,  10, 1, 100,  },
++        { 100000000,  0, 0, 10.   },
++        { 100000000,  2, 1, 10,   },
++        { 100000000, 10, 1, 10,   },
++    };
++
++    printf("  SIZE      Q  S            TIME\n");
++    printf("--------------------------------------\n");
++
++    for (int i = 0; i < ARRAY_SIZE(configs); i++) {
++        int iter = configs[i].iter;
++        int n = configs[i].n;
++        char *str = xzalloc(n);
++
++        for (int j = 0; j < n - 1; j++) {
++            int r = random_range(100);
++
++            if (r < configs[i].special_probability) {
++                str[j] = random_range(' ' - 1) + 1;
++            } else if (r < (configs[i].special_probability
++                            + configs[i].quote_probablility)) {
++                str[j] = '"';
++            } else {
++                str[j] = random_range(256 - ' ') + ' ';
++            }
++        }
++
++        printf("%-11d %-2d %-2d: ", n, configs[i].quote_probablility,
++                                       configs[i].special_probability);
++        fflush(stdout);
++
++        struct json *json = json_string_create_nocopy(str);
++        uint64_t start = time_msec();
++
++        char **res = xzalloc(iter * sizeof *res);
++        for (int j = 0; j < iter; j++) {
++            res[j] = json_to_string(json, 0);
++        }
++
++        printf("%16.3lf ms\n", (double) (time_msec() - start) / iter);
++        json_destroy(json);
++        for (int j = 0; j < iter; j++) {
++            free(res[j]);
++        }
++        free(res);
++    }
++
++    exit(0);
++}
++
++OVSTEST_REGISTER("json-string-benchmark", json_string_benchmark_main);
+diff --git a/tests/tunnel-push-pop.at b/tests/tunnel-push-pop.at
+index 48c5de9d19..12fc1ef910 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
+ OVS_VSWITCHD_STOP
+ AT_CLEANUP
+ 
++AT_SETUP([tunnel_push_pop - packet_out debug_slow])
++
++OVS_VSWITCHD_START(
++    [add-port br0 p0 dnl
++     -- set Interface p0 type=dummy ofport_request=1 dnl
++                         other-config:hwaddr=aa:55:aa:55:00:00])
++AT_CHECK([ovs-appctl vlog/set dpif_netdev:dbg])
++AT_CHECK([ovs-vsctl add-br int-br -- set bridge int-br datapath_type=dummy])
++AT_CHECK([ovs-vsctl add-port int-br t2 dnl
++          -- set Interface t2 type=geneve options:remote_ip=1.1.2.92 dnl
++                              options:key=123 ofport_request=2])
++
++dnl First setup dummy interface IP address, then add the route
++dnl so that tnl-port table can get valid IP address for the device.
++AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/24], [0], [OK
++])
++AT_CHECK([ovs-appctl ovs/route/add 1.1.2.92/24 br0], [0], [OK
++])
++AT_CHECK([ovs-ofctl add-flow br0 action=normal])
++
++dnl This ARP reply from p0 has two effects:
++dnl 1. The ARP cache will learn that 1.1.2.92 is at f8:bc:12:44:34:b6.
++dnl 2. The br0 mac learning will learn that f8:bc:12:44:34:b6 is on p0.
++AT_CHECK([
++  ovs-appctl netdev-dummy/receive p0 dnl
++      'recirc_id(0),in_port(2),dnl
++       eth(src=f8:bc:12:44:34:b6,dst=ff:ff:ff:ff:ff:ff),eth_type(0x0806),dnl
++       arp(sip=1.1.2.92,tip=1.1.2.88,op=2,sha=f8:bc:12:44:34:b6,tha=00:00:00:00:00:00)'
++])
++
++AT_CHECK([ovs-vsctl -- set Interface p0 options:tx_pcap=p0.pcap])
++
++packet=50540000000a505400000009123
++encap=f8bc124434b6aa55aa5500000800450000320000400040113406010102580101025c83a917c1001e00000000655800007b00
++
++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])
++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])
++
++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])
++
++dnl Datapath actions should not have tunnel push action.
++AT_CHECK([ovs-appctl dpctl/dump-flows | grep -q tnl_push], [1])
++dnl There should be slow_path action instead.
++AT_CHECK([ovs-appctl dpctl/dump-flows | grep -q 'slow_path(action)'], [0])
++
++OVS_VSWITCHD_STOP
++AT_CLEANUP
++
+ AT_SETUP([tunnel_push_pop - underlay bridge match])
+ 
+ OVS_VSWITCHD_START([add-port br0 p0 -- set Interface p0 type=dummy ofport_request=1 other-config:hwaddr=aa:55:aa:55:00:00])
diff --git a/SPECS/.gitignore b/SPECS/.gitignore
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/SPECS/.gitignore
diff --git a/SPECS/openvswitch2.16.spec b/SPECS/openvswitch2.16.spec
new file mode 100644
index 0000000..863f8fb
--- /dev/null
+++ b/SPECS/openvswitch2.16.spec
@@ -0,0 +1,920 @@
+# Copyright (C) 2009, 2010, 2013, 2014 Nicira Networks, Inc.
+#
+# Copying and distribution of this file, with or without modification,
+# are permitted in any medium without royalty provided the copyright
+# notice and this notice are preserved.  This file is offered as-is,
+# without warranty of any kind.
+#
+# If tests have to be skipped while building, specify the '--without check'
+# option. For example:
+# rpmbuild -bb --without check rhel/openvswitch-fedora.spec
+
+# This defines the base package name's version.
+
+%define pkgname openvswitch2.16
+
+
+%if 0%{?commit:1}
+%global shortcommit %(c=%{commit}; echo ${c:0:7})
+%endif
+
+# Enable PIE, bz#955181
+%global _hardened_build 1
+
+# RHEL-7 doesn't define _rundir macro yet
+# Fedora 15 onwards uses /run as _rundir
+%if 0%{!?_rundir:1}
+%define _rundir /run
+%endif
+
+# FIXME Test "STP - flush the fdb and mdb when topology changed" fails on s390x
+# FIXME 2 tests fails on ppc64le. They will be hopefully fixed before official 2.11
+%ifarch %{ix86} x86_64 aarch64
+%bcond_without check
+%else
+%bcond_with check
+%endif
+# option to run kernel datapath tests, requires building as root!
+%bcond_with check_datapath_kernel
+# option to build with libcap-ng, needed for running OVS as regular user
+%bcond_without libcapng
+# option to build with ipsec support
+%bcond_without ipsec
+
+# Build python2 (that provides python) and python3 subpackages on Fedora
+# Build only python3 (that provides python) subpackage on RHEL8
+# Build only python subpackage on RHEL7
+%if 0%{?rhel} > 7 || 0%{?fedora}
+# On RHEL8 Sphinx is included in buildroot
+%global external_sphinx 1
+%else
+# Don't use external sphinx (RHV doesn't have optional repositories enabled)
+%global external_sphinx 0
+%endif
+
+Name: %{pkgname}
+Summary: Open vSwitch
+Group: System Environment/Daemons daemon/database/utilities
+URL: http://www.openvswitch.org/
+Version: 2.16.0
+Release: 8%{?dist}
+
+# Nearly all of openvswitch is ASL 2.0.  The bugtool is LGPLv2+, and the
+# lib/sflow*.[ch] files are SISSL
+# datapath/ is GPLv2 (although not built into any of the binary packages)
+License: ASL 2.0 and LGPLv2+ and SISSL
+
+%define dpdkver 20.11.1
+%define dpdkdir dpdk
+%define dpdksver %(echo %{dpdkver} | cut -d. -f-2)
+# NOTE: DPDK does not currently build for s390x
+# DPDK on aarch64 is not stable enough to be enabled in FDP
+%if 0%{?rhel} > 7 || 0%{?fedora}
+%define dpdkarches x86_64 ppc64le
+%else
+%define dpdkarches
+%endif
+
+%if 0%{?commit:1}
+Source: https://github.com/openvswitch/ovs/archive/%{commit}.tar.gz#/openvswitch-%{commit}.tar.gz
+%else
+Source: https://github.com/openvswitch/ovs/archive/v%{version}.tar.gz#/openvswitch-%{version}.tar.gz
+%endif
+Source10: https://fast.dpdk.org/rel/dpdk-%{dpdkver}.tar.xz
+
+%define docutilsver 0.12
+%define pygmentsver 1.4
+%define sphinxver   1.2.3
+Source100: https://pypi.io/packages/source/d/docutils/docutils-%{docutilsver}.tar.gz
+Source101: https://pypi.io/packages/source/P/Pygments/Pygments-%{pygmentsver}.tar.gz
+Source102: https://pypi.io/packages/source/S/Sphinx/Sphinx-%{sphinxver}.tar.gz
+
+Patch:     openvswitch-%{version}.patch
+
+# The DPDK is designed to optimize througput of network traffic using, among
+# other techniques, carefully crafted assembly instructions.  As such it
+# needs extensive work to port it to other architectures.
+ExclusiveArch: x86_64 aarch64 ppc64le s390x
+
+# Do not enable this otherwise YUM will break on any upgrade.
+# Provides: openvswitch
+Conflicts: openvswitch < 2.16
+Conflicts: openvswitch-dpdk < 2.16
+Conflicts: openvswitch2.10
+Conflicts: openvswitch2.11
+Conflicts: openvswitch2.12
+Conflicts: openvswitch2.13
+Conflicts: openvswitch2.14
+Conflicts: openvswitch2.15
+
+# FIXME Sphinx is used to generate some manpages, unfortunately, on RHEL, it's
+# in the -optional repository and so we can't require it directly since RHV
+# doesn't have the -optional repository enabled and so TPS fails
+%if %{external_sphinx}
+BuildRequires: python3-sphinx
+%else
+# Sphinx dependencies
+BuildRequires: python-devel
+BuildRequires: python-setuptools
+#BuildRequires: python2-docutils
+BuildRequires: python-jinja2
+BuildRequires: python-nose
+#BuildRequires: python2-pygments
+# docutils dependencies
+BuildRequires: python-imaging
+# pygments dependencies
+BuildRequires: python-nose
+%endif
+
+BuildRequires: gcc gcc-c++ make
+BuildRequires: autoconf automake libtool
+BuildRequires: systemd-units openssl openssl-devel
+BuildRequires: python3-devel python3-setuptools
+BuildRequires: desktop-file-utils
+BuildRequires: groff-base graphviz
+BuildRequires: unbound-devel
+# make check dependencies
+BuildRequires: procps-ng
+%if 0%{?rhel} > 7 || 0%{?fedora}
+BuildRequires: python3-pyOpenSSL
+%endif
+%if %{with check_datapath_kernel}
+BuildRequires: nmap-ncat
+# would be useful but not available in RHEL or EPEL
+#BuildRequires: pyftpdlib
+%endif
+
+%if %{with libcapng}
+BuildRequires: libcap-ng libcap-ng-devel
+%endif
+
+%ifarch %{dpdkarches}
+BuildRequires: meson
+# DPDK driver dependencies
+BuildRequires: zlib-devel numactl-devel
+%ifarch x86_64
+BuildRequires: rdma-core-devel >= 15 libmnl-devel
+%endif
+
+# Required by packaging policy for the bundled DPDK
+Provides: bundled(dpdk) = %{dpdkver}
+%endif
+
+Requires: openssl iproute module-init-tools
+#Upstream kernel commit 4f647e0a3c37b8d5086214128614a136064110c3
+#Requires: kernel >= 3.15.0-0
+Requires: openvswitch-selinux-extra-policy
+
+Requires(pre): shadow-utils
+Requires(post): /bin/sed
+Requires(post): /usr/sbin/usermod
+Requires(post): /usr/sbin/groupadd
+Requires(post): systemd-units
+Requires(preun): systemd-units
+Requires(postun): systemd-units
+Obsoletes: openvswitch-controller <= 0:2.1.0-1
+
+%description
+Open vSwitch provides standard network bridging functions and
+support for the OpenFlow protocol for remote per-flow control of
+traffic.
+
+%package -n python3-%{pkgname}
+Summary: Open vSwitch python3 bindings
+License: ASL 2.0
+Requires: %{pkgname} = %{?epoch:%{epoch}:}%{version}-%{release}
+Provides: python-%{pkgname} = %{?epoch:%{epoch}:}%{version}-%{release}
+
+%description -n python3-%{pkgname}
+Python bindings for the Open vSwitch database
+
+%package test
+Summary: Open vSwitch testing utilities
+License: ASL 2.0
+BuildArch: noarch
+Requires: python3-%{pkgname} = %{?epoch:%{epoch}:}%{version}-%{release}
+Requires: tcpdump
+
+%description test
+Utilities that are useful to diagnose performance and connectivity
+issues in Open vSwitch setup.
+
+%package devel
+Summary: Open vSwitch OpenFlow development package (library, headers)
+License: ASL 2.0
+Requires: %{pkgname} = %{?epoch:%{epoch}:}%{version}-%{release}
+
+%description devel
+This provides shared library, libopenswitch.so and the openvswitch header
+files needed to build an external application.
+
+%if 0%{?rhel} > 7 || 0%{?fedora} > 28
+%package -n network-scripts-%{name}
+Summary: Open vSwitch legacy network service support
+License: ASL 2.0
+Requires: network-scripts
+Supplements: (%{name} and network-scripts)
+
+%description -n network-scripts-%{name}
+This provides the ifup and ifdown scripts for use with the legacy network
+service.
+%endif
+
+%if %{with ipsec}
+%package ipsec
+Summary: Open vSwitch IPsec tunneling support
+License: ASL 2.0
+Requires: python3-%{pkgname} = %{?epoch:%{epoch}:}%{version}-%{release}
+Requires: libreswan
+
+%description ipsec
+This package provides IPsec tunneling support for OVS tunnels.
+%endif
+
+%prep
+%if 0%{?commit:1}
+%setup -q -n ovs-%{commit} -a 10
+%else
+%setup -q -n ovs-%{version} -a 10
+%endif
+%if ! %{external_sphinx}
+%if 0%{?commit:1}
+%setup -n ovs-%{commit} -q -D -T -a 100 -a 101 -a 102
+%else
+%setup -n ovs-%{version} -q -D -T -a 100 -a 101 -a 102
+%endif
+%endif
+
+mv dpdk-*/ %{dpdkdir}/
+
+# FIXME should we propose a way to do that upstream?
+sed -ri "/^subdir\('(usertools|app)'\)/d" %{dpdkdir}/meson.build
+
+%patch -p1
+
+%build
+# Build Sphinx on RHEL
+%if ! %{external_sphinx}
+export PYTHONPATH="${PYTHONPATH:+$PYTHONPATH:}%{_builddir}/pytmp/lib/python"
+for x in docutils-%{docutilsver} Pygments-%{pygmentsver} Sphinx-%{sphinxver}; do
+    pushd "$x"
+    python2 setup.py install --home %{_builddir}/pytmp
+    popd
+done
+
+export PATH="$PATH:%{_builddir}/pytmp/bin"
+%endif
+
+./boot.sh
+
+%ifarch %{dpdkarches}    # build dpdk
+# Lets build DPDK first
+cd %{dpdkdir}
+
+ENABLED_DRIVERS=(
+    bus/pci
+    bus/vdev
+    mempool/ring
+    net/failsafe
+    net/i40e
+    net/ring
+    net/vhost
+    net/virtio
+    net/tap
+)
+
+%ifarch x86_64
+ENABLED_DRIVERS+=(
+    bus/vmbus
+    common/iavf
+    common/mlx5
+    net/bnxt
+    net/enic
+    net/iavf
+    net/ice
+    net/mlx4
+    net/mlx5
+    net/netvsc
+    net/nfp
+    net/qede
+    net/vdev_netvsc
+)
+%endif
+
+%ifarch aarch64 x86_64
+ENABLED_DRIVERS+=(
+    net/e1000
+    net/ixgbe
+)
+%endif
+
+# Since upstream doesn't have a way
+for driver in drivers/*/*/; do
+    driver=${driver#drivers/}
+    driver=${driver%/}
+    [[ " ${ENABLED_DRIVERS[@]} " == *" $driver "* ]] || \
+        disable_drivers="${disable_drivers:+$disable_drivers,}"$driver
+done
+
+#CFLAGS="$(echo %{optflags} | sed -e 's:-Wall::g' -e 's:-march=[[:alnum:]]* ::g') -Wformat -fPIC %{_hardening_ldflags}" \
+%set_build_flags
+%__meson --prefix=%{_builddir}/dpdk-build \
+         --buildtype=plain \
+         -Ddisable_drivers="$disable_drivers" \
+         -Dmachine=default \
+         -Dmax_ethports=128 \
+         -Dmax_numa_nodes=8 \
+         -Dtests=false \
+         %{_vpath_builddir}
+%meson_build
+%__meson install -C %{_vpath_builddir} --no-rebuild
+
+# FIXME currently with LTO enabled OVS tries to link with both static and shared libraries
+rm -v %{_builddir}/dpdk-build/%{_lib}/*.so*
+
+# Generate a list of supported drivers, its hard to tell otherwise.
+cat << EOF > README.DPDK-PMDS
+DPDK drivers included in this package:
+
+EOF
+
+for f in %{_builddir}/dpdk-build/%{_lib}/librte_net_*.a; do
+    basename ${f} | cut -c12- | cut -d. -f1 | tr [:lower:] [:upper:]
+done >> README.DPDK-PMDS
+
+cat << EOF >> README.DPDK-PMDS
+
+For further information about the drivers, see
+http://dpdk.org/doc/guides-%{dpdksver}/nics/index.html
+EOF
+
+cd -
+%endif    # build dpdk
+
+# And now for OVS...
+mkdir build-shared build-static
+pushd build-shared
+ln -s ../configure
+%configure \
+%if %{with libcapng}
+        --enable-libcapng \
+%else
+        --disable-libcapng \
+%endif
+        --disable-static \
+        --enable-shared \
+        --enable-ssl \
+        --with-pkidir=%{_sharedstatedir}/openvswitch/pki
+make %{?_smp_mflags}
+popd
+pushd build-static
+ln -s ../configure
+%ifarch %{dpdkarches}
+PKG_CONFIG_PATH=%{_builddir}/dpdk-build/%{_lib}/pkgconfig \
+%endif
+%configure \
+%if %{with libcapng}
+        --enable-libcapng \
+%else
+        --disable-libcapng \
+%endif
+        --enable-ssl \
+%ifarch %{dpdkarches}
+        --with-dpdk=static \
+%endif
+        --with-pkidir=%{_sharedstatedir}/openvswitch/pki
+make %{?_smp_mflags}
+popd
+
+/usr/bin/python3 build-aux/dpdkstrip.py \
+        --dpdk \
+        < rhel/usr_lib_systemd_system_ovs-vswitchd.service.in \
+        > rhel/usr_lib_systemd_system_ovs-vswitchd.service
+
+%install
+rm -rf $RPM_BUILD_ROOT
+make -C build-shared install-libLTLIBRARIES DESTDIR=$RPM_BUILD_ROOT
+make -C build-static install DESTDIR=$RPM_BUILD_ROOT
+
+install -d -m 0755 $RPM_BUILD_ROOT%{_rundir}/openvswitch
+install -d -m 0750 $RPM_BUILD_ROOT%{_localstatedir}/log/openvswitch
+install -d -m 0755 $RPM_BUILD_ROOT%{_sysconfdir}/openvswitch
+
+install -p -D -m 0644 rhel/usr_lib_udev_rules.d_91-vfio.rules \
+        $RPM_BUILD_ROOT%{_udevrulesdir}/91-vfio.rules
+
+install -p -D -m 0644 \
+        rhel/usr_share_openvswitch_scripts_systemd_sysconfig.template \
+        $RPM_BUILD_ROOT/%{_sysconfdir}/sysconfig/openvswitch
+
+for service in openvswitch ovsdb-server ovs-vswitchd \
+               ovs-delete-transient-ports; do
+        install -p -D -m 0644 \
+                        rhel/usr_lib_systemd_system_${service}.service \
+                        $RPM_BUILD_ROOT%{_unitdir}/${service}.service
+done
+
+%if %{with ipsec}
+install -p -D -m 0644 rhel/usr_lib_systemd_system_openvswitch-ipsec.service \
+                      $RPM_BUILD_ROOT%{_unitdir}/openvswitch-ipsec.service
+%endif
+
+install -m 0755 rhel/etc_init.d_openvswitch \
+        $RPM_BUILD_ROOT%{_datadir}/openvswitch/scripts/openvswitch.init
+
+install -p -D -m 0644 rhel/etc_openvswitch_default.conf \
+        $RPM_BUILD_ROOT/%{_sysconfdir}/openvswitch/default.conf
+
+install -p -D -m 0644 rhel/etc_logrotate.d_openvswitch \
+        $RPM_BUILD_ROOT/%{_sysconfdir}/logrotate.d/openvswitch
+
+install -m 0644 vswitchd/vswitch.ovsschema \
+        $RPM_BUILD_ROOT/%{_datadir}/openvswitch/vswitch.ovsschema
+
+install -d -m 0755 $RPM_BUILD_ROOT/%{_sysconfdir}/sysconfig/network-scripts/
+install -p -m 0755 rhel/etc_sysconfig_network-scripts_ifdown-ovs \
+        $RPM_BUILD_ROOT/%{_sysconfdir}/sysconfig/network-scripts/ifdown-ovs
+install -p -m 0755 rhel/etc_sysconfig_network-scripts_ifup-ovs \
+        $RPM_BUILD_ROOT/%{_sysconfdir}/sysconfig/network-scripts/ifup-ovs
+
+install -d -m 0755 $RPM_BUILD_ROOT%{python3_sitelib}
+cp -a $RPM_BUILD_ROOT/%{_datadir}/openvswitch/python/ovstest \
+        $RPM_BUILD_ROOT%{python3_sitelib}
+
+# Build the JSON C extension for the Python lib (#1417738)
+pushd python
+(
+export CPPFLAGS="-I ../include -I ../build-shared/include"
+export LDFLAGS="%{__global_ldflags} -L $RPM_BUILD_ROOT%{_libdir}"
+%py3_build
+%py3_install
+[ -f "$RPM_BUILD_ROOT/%{python3_sitearch}/ovs/_json$(python3-config --extension-suffix)" ]
+)
+popd
+
+rm -rf $RPM_BUILD_ROOT/%{_datadir}/openvswitch/python/
+
+install -d -m 0755 $RPM_BUILD_ROOT/%{_sharedstatedir}/openvswitch
+
+install -d -m 0755 $RPM_BUILD_ROOT%{_prefix}/lib/firewalld/services/
+
+install -p -D -m 0755 \
+        rhel/usr_share_openvswitch_scripts_ovs-systemd-reload \
+        $RPM_BUILD_ROOT%{_datadir}/openvswitch/scripts/ovs-systemd-reload
+
+touch $RPM_BUILD_ROOT%{_sysconfdir}/openvswitch/conf.db
+# The db needs special permission as IPsec Pre-shared keys are stored in it.
+chmod 0640 $RPM_BUILD_ROOT%{_sysconfdir}/openvswitch/conf.db
+
+touch $RPM_BUILD_ROOT%{_sysconfdir}/openvswitch/system-id.conf
+
+# remove unpackaged files
+rm -f $RPM_BUILD_ROOT/%{_bindir}/ovs-benchmark \
+        $RPM_BUILD_ROOT/%{_bindir}/ovs-docker \
+        $RPM_BUILD_ROOT/%{_bindir}/ovs-parse-backtrace \
+        $RPM_BUILD_ROOT/%{_bindir}/ovs-testcontroller \
+        $RPM_BUILD_ROOT/%{_sbindir}/ovs-vlan-bug-workaround \
+        $RPM_BUILD_ROOT/%{_mandir}/man1/ovs-benchmark.1* \
+        $RPM_BUILD_ROOT/%{_mandir}/man8/ovs-testcontroller.* \
+        $RPM_BUILD_ROOT/%{_mandir}/man8/ovs-vlan-bug-workaround.8*
+
+%if ! %{with ipsec}
+rm -f $RPM_BUILD_ROOT/%{_datadir}/openvswitch/scripts/ovs-monitor-ipsec
+%endif
+
+# remove ovn unpackages files
+rm -f $RPM_BUILD_ROOT%{_bindir}/ovn*
+rm -f $RPM_BUILD_ROOT%{_mandir}/man1/ovn*
+rm -f $RPM_BUILD_ROOT%{_mandir}/man5/ovn*
+rm -f $RPM_BUILD_ROOT%{_mandir}/man7/ovn*
+rm -f $RPM_BUILD_ROOT%{_mandir}/man8/ovn*
+rm -f $RPM_BUILD_ROOT%{_datadir}/openvswitch/ovn*
+rm -f $RPM_BUILD_ROOT%{_datadir}/openvswitch/scripts/ovn*
+rm -f $RPM_BUILD_ROOT%{_includedir}/ovn/*
+
+%check
+%if %{with check}
+    pushd build-static
+    touch resolv.conf
+    export OVS_RESOLV_CONF=$(pwd)/resolv.conf
+    if make check TESTSUITEFLAGS='%{_smp_mflags}' ||
+       make check TESTSUITEFLAGS='--recheck'; then :;
+    else
+        cat tests/testsuite.log
+        exit 1
+    fi
+    popd
+%endif
+%if %{with check_datapath_kernel}
+    pushd build-static
+    if make check-kernel RECHECK=yes; then :;
+    else
+        cat tests/system-kmod-testsuite.log
+        exit 1
+    fi
+    popd
+%endif
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%preun
+%if 0%{?systemd_preun:1}
+    %systemd_preun openvswitch.service
+%else
+    if [ $1 -eq 0 ] ; then
+    # Package removal, not upgrade
+        /bin/systemctl --no-reload disable openvswitch.service >/dev/null 2>&1 || :
+        /bin/systemctl stop openvswitch.service >/dev/null 2>&1 || :
+    fi
+%endif
+
+%pre
+getent group openvswitch >/dev/null || groupadd -r openvswitch
+getent passwd openvswitch >/dev/null || \
+    useradd -r -g openvswitch -d / -s /sbin/nologin \
+    -c "Open vSwitch Daemons" openvswitch
+
+%ifarch %{dpdkarches}
+    getent group hugetlbfs >/dev/null || groupadd hugetlbfs
+    usermod -a -G hugetlbfs openvswitch
+%endif
+exit 0
+
+%post
+if [ $1 -eq 1 ]; then
+    sed -i 's:^#OVS_USER_ID=:OVS_USER_ID=:' /etc/sysconfig/openvswitch
+
+%ifarch %{dpdkarches}
+    sed -i \
+        's@OVS_USER_ID="openvswitch:openvswitch"@OVS_USER_ID="openvswitch:hugetlbfs"@'\
+        /etc/sysconfig/openvswitch
+%endif
+fi
+chown -R openvswitch:openvswitch /etc/openvswitch
+
+%if 0%{?systemd_post:1}
+    %systemd_post openvswitch.service
+%else
+    # Package install, not upgrade
+    if [ $1 -eq 1 ]; then
+        /bin/systemctl daemon-reload >dev/null || :
+    fi
+%endif
+
+%postun
+%if 0%{?systemd_postun:1}
+    %systemd_postun openvswitch.service
+%else
+    /bin/systemctl daemon-reload >/dev/null 2>&1 || :
+%endif
+
+%triggerun -- openvswitch < 2.5.0-22.git20160727%{?dist}
+# old rpm versions restart the service in postun, but
+# due to systemd some preparation is needed.
+if systemctl is-active openvswitch >/dev/null 2>&1 ; then
+    /usr/share/openvswitch/scripts/ovs-ctl stop >/dev/null 2>&1 || :
+    systemctl daemon-reload >/dev/null 2>&1 || :
+    systemctl stop openvswitch ovsdb-server ovs-vswitchd >/dev/null 2>&1 || :
+    systemctl start openvswitch >/dev/null 2>&1 || :
+fi
+exit 0
+
+%files -n python3-%{pkgname}
+%{python3_sitearch}/ovs
+%{python3_sitearch}/ovs-*.egg-info
+%doc LICENSE
+
+%files test
+%{_bindir}/ovs-pcap
+%{_bindir}/ovs-tcpdump
+%{_bindir}/ovs-tcpundump
+%{_mandir}/man1/ovs-pcap.1*
+%{_mandir}/man8/ovs-tcpdump.8*
+%{_mandir}/man1/ovs-tcpundump.1*
+%{_bindir}/ovs-test
+%{_bindir}/ovs-vlan-test
+%{_bindir}/ovs-l3ping
+%{_mandir}/man8/ovs-test.8*
+%{_mandir}/man8/ovs-vlan-test.8*
+%{_mandir}/man8/ovs-l3ping.8*
+%{python3_sitelib}/ovstest
+
+%files devel
+%{_libdir}/*.so
+%{_libdir}/pkgconfig/*.pc
+%{_includedir}/openvswitch/*
+%{_includedir}/openflow/*
+%exclude %{_libdir}/*.a
+%exclude %{_libdir}/*.la
+
+%if 0%{?rhel} > 7 || 0%{?fedora} > 28
+%files -n network-scripts-%{name}
+%{_sysconfdir}/sysconfig/network-scripts/ifup-ovs
+%{_sysconfdir}/sysconfig/network-scripts/ifdown-ovs
+%endif
+
+%files
+%defattr(-,openvswitch,openvswitch)
+%dir %{_sysconfdir}/openvswitch
+%{_sysconfdir}/openvswitch/default.conf
+%config %ghost %verify(not owner group md5 size mtime) %{_sysconfdir}/openvswitch/conf.db
+%ghost %attr(0600,-,-) %verify(not owner group md5 size mtime) %{_sysconfdir}/openvswitch/.conf.db.~lock~
+%config %ghost %{_sysconfdir}/openvswitch/system-id.conf
+%defattr(-,root,root)
+%config(noreplace) %verify(not md5 size mtime) %{_sysconfdir}/sysconfig/openvswitch
+%{_sysconfdir}/bash_completion.d/ovs-appctl-bashcomp.bash
+%{_sysconfdir}/bash_completion.d/ovs-vsctl-bashcomp.bash
+%config(noreplace) %{_sysconfdir}/logrotate.d/openvswitch
+%{_unitdir}/openvswitch.service
+%{_unitdir}/ovsdb-server.service
+%{_unitdir}/ovs-vswitchd.service
+%{_unitdir}/ovs-delete-transient-ports.service
+%{_datadir}/openvswitch/scripts/openvswitch.init
+%{_datadir}/openvswitch/scripts/ovs-check-dead-ifs
+%{_datadir}/openvswitch/scripts/ovs-lib
+%{_datadir}/openvswitch/scripts/ovs-save
+%{_datadir}/openvswitch/scripts/ovs-vtep
+%{_datadir}/openvswitch/scripts/ovs-ctl
+%{_datadir}/openvswitch/scripts/ovs-kmod-ctl
+%{_datadir}/openvswitch/scripts/ovs-systemd-reload
+%config %{_datadir}/openvswitch/vswitch.ovsschema
+%config %{_datadir}/openvswitch/vtep.ovsschema
+%{_bindir}/ovs-appctl
+%{_bindir}/ovs-dpctl
+%{_bindir}/ovs-ofctl
+%{_bindir}/ovs-vsctl
+%{_bindir}/ovsdb-client
+%{_bindir}/ovsdb-tool
+%{_bindir}/ovs-pki
+%{_bindir}/vtep-ctl
+%{_libdir}/*.so.*
+%{_sbindir}/ovs-vswitchd
+%{_sbindir}/ovsdb-server
+%{_mandir}/man1/ovsdb-client.1*
+%{_mandir}/man1/ovsdb-server.1*
+%{_mandir}/man1/ovsdb-tool.1*
+%{_mandir}/man5/ovsdb.5*
+%{_mandir}/man5/ovsdb-server.5.*
+%{_mandir}/man5/ovs-vswitchd.conf.db.5*
+%{_mandir}/man5/vtep.5*
+%{_mandir}/man7/ovsdb-server.7*
+%{_mandir}/man7/ovsdb.7*
+%{_mandir}/man7/ovs-actions.7*
+%{_mandir}/man7/ovs-fields.7*
+%{_mandir}/man8/vtep-ctl.8*
+%{_mandir}/man8/ovs-appctl.8*
+%{_mandir}/man8/ovs-ctl.8*
+%{_mandir}/man8/ovs-dpctl.8*
+%{_mandir}/man8/ovs-kmod-ctl.8.*
+%{_mandir}/man8/ovs-ofctl.8*
+%{_mandir}/man8/ovs-pki.8*
+%{_mandir}/man8/ovs-vsctl.8*
+%{_mandir}/man8/ovs-vswitchd.8*
+%{_mandir}/man8/ovs-parse-backtrace.8*
+%{_udevrulesdir}/91-vfio.rules
+%doc LICENSE NOTICE README.rst NEWS rhel/README.RHEL.rst
+%ifarch %{dpdkarches}
+%doc %{dpdkdir}/README.DPDK-PMDS
+%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-*
+%{_bindir}/ovs-dpctl-top
+%{_sbindir}/ovs-bugtool
+%{_mandir}/man8/ovs-dpctl-top.8*
+%{_mandir}/man8/ovs-bugtool.8*
+%if (0%{?rhel} && 0%{?rhel} <= 7) || (0%{?fedora} && 0%{?fedora} < 29)
+%{_sysconfdir}/sysconfig/network-scripts/ifup-ovs
+%{_sysconfdir}/sysconfig/network-scripts/ifdown-ovs
+%endif
+
+%if %{with ipsec}
+%files ipsec
+%{_datadir}/openvswitch/scripts/ovs-monitor-ipsec
+%{_unitdir}/openvswitch-ipsec.service
+%endif
+
+%changelog
+* 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:
+    facaf5bc71 netdev-linux: Fix a null pointer dereference in netdev_linux_notify_sock().
+    6e203d4873 pcap-file: Fix memory leak in ovs_pcap_open().
+    f50da0b267 odp-util: Fix a null pointer dereference in odp_flow_format().
+    7da752e43f odp-util: Fix a null pointer dereference in odp_nsh_key_from_attr__().
+    bc22b01459 netdev-dpdk: Fix RSS configuration for virtio.
+    81706c5d43 ipf: Fix only nat the first fragment in the reass process.
+
+
+* Wed Sep 08 2021 Open vSwitch CI <ovs-ci@redhat.com> - 2.16.0-7
+- Merging upstream branch-2.16 [RH git: e71f31dfd6]
+    Commit list:
+    242c280f0e dpif-netdev: Fix crash when PACKET_OUT is metered.
+
+
+* Tue Aug 31 2021 Ilya Maximets <i.maximets@redhat.com> - 2.16.0-6
+- ovsdb: monitor: Store serialized json in a json cache. [RH git: bc20330c85] (#1996152)
+    commit 43e66fc27659af2a5c976bdd27fe747b442b5554
+    Author: Ilya Maximets <i.maximets@ovn.org>
+    Date:   Tue Aug 24 21:00:39 2021 +0200
+    
+        Same json from a json cache is typically sent to all the clients,
+        e.g., in case of OVN deployment with ovn-monitor-all=true.
+    
+        There could be hundreds or thousands connected clients and ovsdb
+        will serialize the same json object for each of them before sending.
+    
+        Serializing it once before storing into json cache to speed up
+        processing.
+    
+        This change allows to save a lot of CPU cycles and a bit of memory
+        since we need to store in memory only a string and not the full json
+        object.
+    
+        Testing with ovn-heater on 120 nodes using density-heavy scenario
+        shows reduction of the total CPU time used by Southbound DB processes
+        from 256 minutes to 147.  Duration of unreasonably long poll intervals
+        also reduced dramatically from 7 to 2 seconds:
+    
+                   Count   Min    Max   Median    Mean   95 percentile
+         -------------------------------------------------------------
+          Before   1934   1012   7480   4302.5   4875.3     7034.3
+          After    1909   1004   2730   1453.0   1532.5     2053.6
+    
+        Acked-by: Dumitru Ceara <dceara@redhat.com>
+        Acked-by: Han Zhou <hzhou@ovn.org>
+        Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
+    
+    Reported-at: https://bugzilla.redhat.com/show_bug.cgi?id=1996152
+    Signed-off-by: Ilya Maximets <i.maximets@redhat.com>
+
+
+* Tue Aug 31 2021 Ilya Maximets <i.maximets@redhat.com> - 2.16.0-5
+- raft: Don't keep full json objects in memory if no longer needed. [RH git: 4606423e8b] (#1990058)
+    commit 0de882954032aa37dc943bafd72c33324aa0c95a
+    Author: Ilya Maximets <i.maximets@ovn.org>
+    Date:   Tue Aug 24 21:00:38 2021 +0200
+    
+        raft: Don't keep full json objects in memory if no longer needed.
+    
+        Raft log entries (and raft database snapshot) contains json objects
+        of the data.  Follower receives append requests with data that gets
+        parsed and added to the raft log.  Leader receives execution requests,
+        parses data out of them and adds to the log.  In both cases, later
+        ovsdb-server reads the log with ovsdb_storage_read(), constructs
+        transaction and updates the database.  On followers these json objects
+        in common case are never used again.  Leader may use them to send
+        append requests or snapshot installation requests to followers.
+        However, all these operations (except for ovsdb_storage_read()) are
+        just serializing the json in order to send it over the network.
+    
+        Json objects are significantly larger than their serialized string
+        representation.  For example, the snapshot of the database from one of
+        the ovn-heater scale tests takes 270 MB as a string, but 1.6 GB as
+        a json object from the total 3.8 GB consumed by ovsdb-server process.
+    
+        ovsdb_storage_read() for a given raft entry happens only once in a
+        lifetime, so after this call, we can serialize the json object, store
+        the string representation and free the actual json object that ovsdb
+        will never need again.  This can save a lot of memory and can also
+        save serialization time, because each raft entry for append requests
+        and snapshot installation requests serialized only once instead of
+        doing that every time such request needs to be sent.
+    
+        JSON_SERIALIZED_OBJECT can be used in order to seamlessly integrate
+        pre-serialized data into raft_header and similar json objects.
+    
+        One major special case is creation of a database snapshot.
+        Snapshot installation request received over the network will be parsed
+        and read by ovsdb-server just like any other raft log entry.  However,
+        snapshots created locally with raft_store_snapshot() will never be
+        read back, because they reflect the current state of the database,
+        hence already applied.  For this case we can free the json object
+        right after writing snapshot on disk.
+    
+        Tests performed with ovn-heater on 60 node density-light scenario,
+        where on-disk database goes up to 97 MB, shows average memory
+        consumption of ovsdb-server Southbound DB processes decreased by 58%
+        (from 602 MB to 256 MB per process) and peak memory consumption
+        decreased by 40% (from 1288 MB to 771 MB).
+    
+        Test with 120 nodes on density-heavy scenario with 270 MB on-disk
+        database shows 1.5 GB memory consumption decrease as expected.
+        Also, total CPU time consumed by the Southbound DB process reduced
+        from 296 to 256 minutes.  Number of unreasonably long poll intervals
+        reduced from 2896 down to 1934.
+    
+        Deserialization is also implemented just in case.  I didn't see this
+        function being invoked in practice.
+    
+        Acked-by: Dumitru Ceara <dceara@redhat.com>
+        Acked-by: Han Zhou <hzhou@ovn.org>
+        Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
+    
+    Reported-at: https://bugzilla.redhat.com/show_bug.cgi?id=1990058
+    Signed-off-by: Ilya Maximets <i.maximets@redhat.com>
+
+
+* Tue Aug 31 2021 Ilya Maximets <i.maximets@redhat.com> - 2.16.0-4
+- json: Add support for partially serialized json objects. [RH git: 885e5ce1b5] (#1990058)
+    commit b0bca6f27aae845c3ca8b48d66a7dbd3d978162a
+    Author: Ilya Maximets <i.maximets@ovn.org>
+    Date:   Tue Aug 24 21:00:37 2021 +0200
+    
+        json: Add support for partially serialized json objects.
+    
+        Introducing a new json type JSON_SERIALIZED_OBJECT.  It's not an
+        actual type that can be seen in a json message on a wire, but
+        internal type that is intended to hold a serialized version of
+        some other json object.  For this reason it's defined after the
+        JSON_N_TYPES to not confuse parsers and other parts of the code
+        that relies on compliance with RFC 4627.
+    
+        With this JSON type internal users may construct large JSON objects,
+        parts of which are already serialized.  This way, while serializing
+        the larger object, data from JSON_SERIALIZED_OBJECT can be added
+        directly to the result, without additional processing.
+    
+        This will be used by next commits to add pre-serialized JSON data
+        to the raft_header structure, that can be converted to a JSON
+        before writing the file transaction on disk or sending to other
+        servers.  Same technique can also be used to pre-serialize json_cache
+        for ovsdb monitors, this should allow to not perform serialization
+        for every client and will save some more memory.
+    
+        Since serialized JSON is just a string, reusing the 'json->string'
+        pointer for it.
+    
+        Acked-by: Dumitru Ceara <dceara@redhat.com>
+        Acked-by: Han Zhou <hzhou@ovn.org>
+        Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
+    
+    Reported-at: https://bugzilla.redhat.com/show_bug.cgi?id=1990058
+    Signed-off-by: Ilya Maximets <i.maximets@redhat.com>
+
+
+* Tue Aug 31 2021 Ilya Maximets <i.maximets@redhat.com> - 2.16.0-3
+- json: Optimize string serialization. [RH git: bb1654da63] (#1990069)
+    commit 748010ff304b7cd2c43f4eb98a554433f0df07f9
+    Author: Ilya Maximets <i.maximets@ovn.org>
+    Date:   Tue Aug 24 23:07:22 2021 +0200
+    
+        json: Optimize string serialization.
+    
+        Current string serialization code puts all characters one by one.
+        This is slow because dynamic string needs to perform length checks
+        on every ds_put_char() and it's also doesn't allow compiler to use
+        better memory copy operations, i.e. doesn't allow copying few bytes
+        at once.
+    
+        Special symbols are rare in a typical database.  Quotes are frequent,
+        but not too frequent.  In databases created by ovn-kubernetes, for
+        example, usually there are at least 10 to 50 chars between quotes.
+        So, it's better to count characters that doesn't require escaping
+        and use fast data copy for the whole sequential block.
+    
+        Testing with a synthetic benchmark (included) on my laptop shows
+        following performance improvement:
+    
+           Size      Q  S       Before       After       Diff
+         -----------------------------------------------------
+         100000      0  0 :    0.227 ms     0.142 ms   -37.4 %
+         100000      2  1 :    0.277 ms     0.186 ms   -32.8 %
+         100000      10 1 :    0.361 ms     0.309 ms   -14.4 %
+         10000000    0  0 :   22.720 ms    12.160 ms   -46.4 %
+         10000000    2  1 :   27.470 ms    19.300 ms   -29.7 %
+         10000000    10 1 :   37.950 ms    31.250 ms   -17.6 %
+         100000000   0  0 :  239.600 ms   126.700 ms   -47.1 %
+         100000000   2  1 :  292.400 ms   188.600 ms   -35.4 %
+         100000000   10 1 :  387.700 ms   321.200 ms   -17.1 %
+    
+        Here Q - probability (%) for a character to be a '\"' and
+        S - probability (%) to be a special character ( < 32).
+    
+        Testing with a closer to real world scenario shows overall decrease
+        of the time needed for database compaction by ~5-10 %.  And this
+        change also decreases CPU consumption in general, because string
+        serialization is used in many different places including ovsdb
+        monitors and raft.
+    
+        Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
+        Acked-by: Numan Siddique <numans@ovn.org>
+        Acked-by: Dumitru Ceara <dceara@redhat.com>
+    
+    Reported-at: https://bugzilla.redhat.com/show_bug.cgi?id=1990069
+    Signed-off-by: Ilya Maximets <i.maximets@redhat.com>
+
+
+* Fri Aug 20 2021 Open vSwitch CI <ovs-ci@redhat.com> - 2.16.0-2
+- Merging upstream branch-2.16 [RH git: 7d7567e339]
+    Commit list:
+    0991ea8d19 Prepare for 2.16.1.
+
+
+* Wed Aug 18 2021 Flavio Leitner <fbl@redhat.com> - 2.16.0-1
+- redhat: First 2.16.0 release. [RH git: 0a1c4276cc]
+
+