From 91dce2e366d912cdd6b062e6cc2fdf57ccb211eb Mon Sep 17 00:00:00 2001
From: Open vSwitch CI
+diff --git a/lib/netdev-dpdk.c b/lib/netdev-dpdk.c
+index 8f1361e21..55700250d 100644
+--- a/lib/netdev-dpdk.c
++++ b/lib/netdev-dpdk.c
+@@ -1312,6 +1312,16 @@ dpdk_eth_dev_init(struct netdev_dpdk *dev)
+ dev->hw_ol_features &= ~NETDEV_RX_HW_SCATTER;
+ }
+
++ if (!strcmp(info.driver_name, "net_tap")) {
++ /* FIXME: L4 checksum offloading is broken in DPDK net/tap driver.
++ * This workaround can be removed once the fix makes it to a DPDK
++ * LTS release used by OVS. */
++ VLOG_INFO("%s: disabled Tx L4 checksum offloads for a net/tap port.",
++ netdev_get_name(&dev->up));
++ info.tx_offload_capa &= ~RTE_ETH_TX_OFFLOAD_UDP_CKSUM;
++ info.tx_offload_capa &= ~RTE_ETH_TX_OFFLOAD_TCP_CKSUM;
++ }
++
+ if (info.tx_offload_capa & RTE_ETH_TX_OFFLOAD_IPV4_CKSUM) {
+ dev->hw_ol_features |= NETDEV_TX_IPV4_CKSUM_OFFLOAD;
+ } else {
+@@ -2431,6 +2441,7 @@ netdev_dpdk_prep_hwol_packet(struct netdev_dpdk *dev, struct rte_mbuf *mbuf)
+
+ if (!(mbuf->ol_flags & (RTE_MBUF_F_TX_IP_CKSUM | RTE_MBUF_F_TX_L4_MASK
+ | RTE_MBUF_F_TX_TCP_SEG))) {
++ mbuf->ol_flags &= ~(RTE_MBUF_F_TX_IPV4 | RTE_MBUF_F_TX_IPV6);
+ return true;
+ }
+
+diff --git a/lib/netdev-offload-dpdk.c b/lib/netdev-offload-dpdk.c
+index 14bc87771..992627fa2 100644
+--- a/lib/netdev-offload-dpdk.c
++++ b/lib/netdev-offload-dpdk.c
+@@ -2537,15 +2537,15 @@ out:
+ return ret;
+ }
+
+-static int
+-netdev_offload_dpdk_flow_flush(struct netdev *netdev)
++static void
++flush_netdev_flows_in_related(struct netdev *netdev, struct netdev *related)
+ {
+- struct cmap *map = offload_data_map(netdev);
+- struct ufid_to_rte_flow_data *data;
+ unsigned int tid = netdev_offload_thread_id();
++ struct cmap *map = offload_data_map(related);
++ struct ufid_to_rte_flow_data *data;
+
+ if (!map) {
+- return -1;
++ return;
+ }
+
+ CMAP_FOR_EACH (data, node, map) {
+@@ -2556,6 +2556,31 @@ netdev_offload_dpdk_flow_flush(struct netdev *netdev)
+ netdev_offload_dpdk_flow_destroy(data);
+ }
+ }
++}
++
++static bool
++flush_in_vport_cb(struct netdev *vport,
++ odp_port_t odp_port OVS_UNUSED,
++ void *aux)
++{
++ struct netdev *netdev = aux;
++
++ /* Only vports are related to physical devices. */
++ if (netdev_vport_is_vport_class(vport->netdev_class)) {
++ flush_netdev_flows_in_related(netdev, vport);
++ }
++
++ return false;
++}
++
++static int
++netdev_offload_dpdk_flow_flush(struct netdev *netdev)
++{
++ flush_netdev_flows_in_related(netdev, netdev);
++
++ if (!netdev_vport_is_vport_class(netdev->netdev_class)) {
++ netdev_ports_traverse(netdev->dpif_type, flush_in_vport_cb, netdev);
++ }
+
+ return 0;
+ }
+diff --git a/lib/netlink-conntrack.c b/lib/netlink-conntrack.c
+index 4fcde9ba1..492bfcffb 100644
+--- a/lib/netlink-conntrack.c
++++ b/lib/netlink-conntrack.c
+@@ -579,7 +579,8 @@ nl_ct_put_tuple_proto(struct ofpbuf *buf, const struct ct_dpif_tuple *tuple)
+ nl_msg_put_u8(buf, CTA_PROTO_ICMPV6_TYPE, tuple->icmp_type);
+ nl_msg_put_u8(buf, CTA_PROTO_ICMPV6_CODE, tuple->icmp_code);
+ } else if (tuple->ip_proto == IPPROTO_TCP ||
+- tuple->ip_proto == IPPROTO_UDP) {
++ tuple->ip_proto == IPPROTO_UDP ||
++ tuple->ip_proto == IPPROTO_SCTP) {
+ nl_msg_put_be16(buf, CTA_PROTO_SRC_PORT, tuple->src_port);
+ nl_msg_put_be16(buf, CTA_PROTO_DST_PORT, tuple->dst_port);
+ } else {
+diff --git a/ofproto/connmgr.c b/ofproto/connmgr.c
+index b092e9e04..f7f7b1279 100644
+--- a/ofproto/connmgr.c
++++ b/ofproto/connmgr.c
+@@ -1209,7 +1209,7 @@ ofconn_create(struct ofservice *ofservice, struct rconn *rconn,
+ hmap_init(&ofconn->bundles);
+ ofconn->next_bundle_expiry_check = time_msec() + BUNDLE_EXPIRY_INTERVAL;
+
+- ofconn_set_rate_limit(ofconn, settings->rate_limit, settings->burst_limit);
++ ofservice_reconfigure(ofservice, settings);
+
+ ovs_mutex_unlock(&ofproto_mutex);
+ }
+@@ -1915,10 +1915,7 @@ connmgr_count_hidden_rules(const struct connmgr *mgr)
+ }
+
+ /* Creates a new ofservice for 'target' in 'mgr'. Returns 0 if successful,
+- * otherwise a positive errno value.
+- *
+- * ofservice_reconfigure() must be called to fully configure the new
+- * ofservice. */
++ * otherwise a positive errno value. */
+ static void
+ ofservice_create(struct connmgr *mgr, const char *target,
+ const struct ofproto_controller *c)
+@@ -1928,7 +1925,8 @@ ofservice_create(struct connmgr *mgr, const char *target,
+ struct rconn *rconn = NULL;
+ if (!vconn_verify_name(target)) {
+ char *name = ofconn_make_name(mgr, target);
+- rconn = rconn_create(5, 8, c->dscp, c->allowed_versions);
++ rconn = rconn_create(c->probe_interval, c->max_backoff,
++ c->dscp, c->allowed_versions);
+ rconn_connect(rconn, target, name);
+ free(name);
+ } else if (!pvconn_verify_name(target)) {
+@@ -1951,7 +1949,6 @@ ofservice_create(struct connmgr *mgr, const char *target,
+ ofservice->rconn = rconn;
+ ofservice->pvconn = pvconn;
+ ofservice->s = *c;
+- ofservice_reconfigure(ofservice, c);
+
+ VLOG_INFO("%s: added %s controller \"%s\"",
+ mgr->name, ofconn_type_to_string(ofservice->type), target);
+diff --git a/ofproto/ofproto-dpif-xlate-cache.c b/ofproto/ofproto-dpif-xlate-cache.c
+index 9224ee2e6..2e1fcb3a6 100644
+--- a/ofproto/ofproto-dpif-xlate-cache.c
++++ b/ofproto/ofproto-dpif-xlate-cache.c
+@@ -125,7 +125,7 @@ xlate_push_stats_entry(struct xc_entry *entry,
+ case XC_LEARN: {
+ enum ofperr error;
+ error = ofproto_flow_mod_learn(entry->learn.ofm, true,
+- entry->learn.limit, NULL);
++ entry->learn.limit, NULL, stats->used);
+ if (error) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+ VLOG_WARN_RL(&rl, "xcache LEARN action execution failed.");
+diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
+index 47ea0f47e..be4bd6657 100644
+--- a/ofproto/ofproto-dpif-xlate.c
++++ b/ofproto/ofproto-dpif-xlate.c
+@@ -1615,7 +1615,8 @@ xlate_lookup_ofproto_(const struct dpif_backer *backer,
+ }
+
+ ofp_port_t in_port = recirc_id_node->state.metadata.in_port;
+- if (in_port != OFPP_NONE && in_port != OFPP_CONTROLLER) {
++ if (in_port != OFPP_NONE && in_port != OFPP_CONTROLLER &&
++ !uuid_is_zero(&recirc_id_node->state.xport_uuid)) {
+ struct uuid xport_uuid = recirc_id_node->state.xport_uuid;
+ xport = xport_lookup_by_uuid(xcfg, &xport_uuid);
+ if (xport && xport->xbridge && xport->xbridge->ofproto) {
+@@ -1626,11 +1627,19 @@ xlate_lookup_ofproto_(const struct dpif_backer *backer,
+ * that the packet originated from the controller via an OpenFlow
+ * "packet-out". The right thing to do is to find just the
+ * ofproto. There is no xport, which is OK.
++ * Also a zeroed xport_uuid with a valid in_port, means that
++ * the packet originated from OFPP_CONTROLLER passed
++ * through a patch port.
+ *
+ * OFPP_NONE can also indicate that a bond caused recirculation. */
+ struct uuid uuid = recirc_id_node->state.ofproto_uuid;
+ const struct xbridge *bridge = xbridge_lookup_by_uuid(xcfg, &uuid);
++
+ if (bridge && bridge->ofproto) {
++ if (in_port != OFPP_CONTROLLER && in_port != OFPP_NONE &&
++ !get_ofp_port(bridge, in_port)) {
++ goto xport_lookup;
++ }
+ if (errorp) {
+ *errorp = NULL;
+ }
+@@ -1643,6 +1652,7 @@ xlate_lookup_ofproto_(const struct dpif_backer *backer,
+ }
+ }
+
++xport_lookup:
+ xport = xport_lookup(xcfg, tnl_port_should_receive(flow)
+ ? tnl_port_receive(flow)
+ : odp_port_to_ofport(backer, flow->in_port.odp_port));
+@@ -5700,8 +5710,16 @@ xlate_learn_action(struct xlate_ctx *ctx, const struct ofpact_learn *learn)
+ if (!error) {
+ bool success = true;
+ if (ctx->xin->allow_side_effects) {
++ long long int last_used;
++
++ if (ctx->xin->resubmit_stats) {
++ last_used = ctx->xin->resubmit_stats->used;
++ } else {
++ last_used = time_msec();
++ }
+ error = ofproto_flow_mod_learn(ofm, ctx->xin->xcache != NULL,
+- learn->limit, &success);
++ learn->limit, &success,
++ last_used);
+ } else if (learn->limit) {
+ if (!ofm->temp_rule
+ || ofm->temp_rule->state != RULE_INSERTED) {
+diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
+index e22ca757a..ba5706f6a 100644
+--- a/ofproto/ofproto-dpif.c
++++ b/ofproto/ofproto-dpif.c
+@@ -4880,7 +4880,7 @@ packet_xlate(struct ofproto *ofproto_, struct ofproto_packet_out *opo)
+ if (entry->type == XC_LEARN) {
+ struct ofproto_flow_mod *ofm = entry->learn.ofm;
+
+- error = ofproto_flow_mod_learn_refresh(ofm);
++ error = ofproto_flow_mod_learn_refresh(ofm, time_msec());
+ if (error) {
+ goto error_out;
+ }
+diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h
+index 143ded690..9f7b8b6e8 100644
+--- a/ofproto/ofproto-provider.h
++++ b/ofproto/ofproto-provider.h
+@@ -2027,9 +2027,11 @@ enum ofperr ofproto_flow_mod_init_for_learn(struct ofproto *,
+ struct ofproto_flow_mod *)
+ OVS_EXCLUDED(ofproto_mutex);
+ enum ofperr ofproto_flow_mod_learn(struct ofproto_flow_mod *, bool keep_ref,
+- unsigned limit, bool *below_limit)
++ unsigned limit, bool *below_limit,
++ long long int last_used)
+ OVS_EXCLUDED(ofproto_mutex);
+-enum ofperr ofproto_flow_mod_learn_refresh(struct ofproto_flow_mod *ofm);
++enum ofperr ofproto_flow_mod_learn_refresh(struct ofproto_flow_mod *ofm,
++ long long int last_used);
+ enum ofperr ofproto_flow_mod_learn_start(struct ofproto_flow_mod *ofm)
+ OVS_REQUIRES(ofproto_mutex);
+ void ofproto_flow_mod_learn_revert(struct ofproto_flow_mod *ofm)
+diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
+index dbf4958bc..e78c80d11 100644
+--- a/ofproto/ofproto.c
++++ b/ofproto/ofproto.c
+@@ -5472,7 +5472,8 @@ ofproto_flow_mod_init_for_learn(struct ofproto *ofproto,
+ }
+
+ enum ofperr
+-ofproto_flow_mod_learn_refresh(struct ofproto_flow_mod *ofm)
++ofproto_flow_mod_learn_refresh(struct ofproto_flow_mod *ofm,
++ long long int last_used)
+ {
+ enum ofperr error = 0;
+
+@@ -5493,9 +5494,37 @@ ofproto_flow_mod_learn_refresh(struct ofproto_flow_mod *ofm)
+ * this function is executed the rule will be reinstated. */
+ if (rule->state == RULE_REMOVED) {
+ struct cls_rule cr;
++ struct oftable *table = &rule->ofproto->tables[rule->table_id];
++ ovs_version_t tables_version = rule->ofproto->tables_version;
++
++ if (!cls_rule_visible_in_version(&rule->cr, tables_version)) {
++ const struct cls_rule *curr_cls_rule;
++
++ /* Only check for matching classifier rules and their modified
++ * time, instead of also checking all rule metadata, with the goal
++ * of suppressing a learn action update that would replace a more
++ * recent rule in the classifier. */
++ curr_cls_rule = classifier_find_rule_exactly(&table->cls,
++ &rule->cr,
++ tables_version);
++ if (curr_cls_rule) {
++ struct rule *curr_rule = rule_from_cls_rule(curr_cls_rule);
++ long long int curr_last_used;
++
++ ovs_mutex_lock(&curr_rule->mutex);
++ curr_last_used = curr_rule->modified;
++ ovs_mutex_unlock(&curr_rule->mutex);
++
++ if (curr_last_used > last_used) {
++ /* In the case of a newer visible rule, don't recreate the
++ * current rule. */
++ return 0;
++ }
++ }
++ }
+
+- cls_rule_clone(&cr, &rule->cr);
+ ovs_mutex_lock(&rule->mutex);
++ cls_rule_clone(&cr, &rule->cr);
+ error = ofproto_rule_create(rule->ofproto, &cr, rule->table_id,
+ rule->flow_cookie,
+ rule->idle_timeout,
+@@ -5506,6 +5535,7 @@ ofproto_flow_mod_learn_refresh(struct ofproto_flow_mod *ofm)
+ rule->match_tlv_bitmap,
+ rule->ofpacts_tlv_bitmap,
+ &ofm->temp_rule);
++ ofm->temp_rule->modified = last_used;
+ ovs_mutex_unlock(&rule->mutex);
+ if (!error) {
+ ofproto_rule_unref(rule); /* Release old reference. */
+@@ -5513,7 +5543,7 @@ ofproto_flow_mod_learn_refresh(struct ofproto_flow_mod *ofm)
+ } else {
+ /* Refresh the existing rule. */
+ ovs_mutex_lock(&rule->mutex);
+- rule->modified = time_msec();
++ rule->modified = last_used;
+ ovs_mutex_unlock(&rule->mutex);
+ }
+ return error;
+@@ -5565,10 +5595,16 @@ ofproto_flow_mod_learn_finish(struct ofproto_flow_mod *ofm,
+
+ /* Refresh 'ofm->temp_rule', for which the caller holds a reference, if already
+ * in the classifier, insert it otherwise. If the rule has already been
+- * removed from the classifier, a new rule is created using 'ofm->temp_rule' as
+- * a template and the reference to the old 'ofm->temp_rule' is freed. If
+- * 'keep_ref' is true, then a reference to the current rule is held, otherwise
+- * it is released and 'ofm->temp_rule' is set to NULL.
++ * removed from the classifier and replaced by another rule, the 'last_used'
++ * parameter is used to determine whether the newer rule is replaced or kept.
++ * If 'last_used' is greater than the last modified time of an identical rule
++ * in the classifier, then a new rule is created using 'ofm->temp_rule' as a
++ * template and the reference to the old 'ofm->temp_rule' is freed. If the
++ * rule has been removed but another identical rule doesn't exist in the
++ * classifier, then it will be recreated. If the rule hasn't been removed
++ * from the classifier, then 'last_used' is used to update the rules modified
++ * time. If 'keep_ref' is true, then a reference to the current rule is held,
++ * otherwise it is released and 'ofm->temp_rule' is set to NULL.
+ *
+ * If 'limit' != 0, insertion will fail if there are more than 'limit' rules
+ * in the same table with the same cookie. If insertion succeeds,
+@@ -5579,10 +5615,11 @@ ofproto_flow_mod_learn_finish(struct ofproto_flow_mod *ofm,
+ * during the call. */
+ enum ofperr
+ ofproto_flow_mod_learn(struct ofproto_flow_mod *ofm, bool keep_ref,
+- unsigned limit, bool *below_limitp)
++ unsigned limit, bool *below_limitp,
++ long long int last_used)
+ OVS_EXCLUDED(ofproto_mutex)
+ {
+- enum ofperr error = ofproto_flow_mod_learn_refresh(ofm);
++ enum ofperr error = ofproto_flow_mod_learn_refresh(ofm, last_used);
+ struct rule *rule = ofm->temp_rule;
+ bool below_limit = true;
+
+@@ -5615,6 +5652,11 @@ ofproto_flow_mod_learn(struct ofproto_flow_mod *ofm, bool keep_ref,
+
+ error = ofproto_flow_mod_learn_start(ofm);
+ if (!error) {
++ /* ofproto_flow_mod_learn_start may have overwritten
++ * modified with current time. */
++ ovs_mutex_lock(&ofm->temp_rule->mutex);
++ ofm->temp_rule->modified = last_used;
++ ovs_mutex_unlock(&ofm->temp_rule->mutex);
+ error = ofproto_flow_mod_learn_finish(ofm, NULL);
+ }
+ } else {
+diff --git a/ovsdb/condition.c b/ovsdb/condition.c
+index 5a3eb4e8a..4911fbf59 100644
+--- a/ovsdb/condition.c
++++ b/ovsdb/condition.c
+@@ -550,9 +550,14 @@ ovsdb_condition_diff(struct ovsdb_condition *diff,
+ &b->clauses[j]);
+ }
+
+- diff->optimized = a->optimized && b->optimized;
+- if (diff->optimized) {
+- ovsdb_condition_optimize(diff);
++ if (diff->n_clauses) {
++ diff->optimized = a->optimized && b->optimized;
++ if (diff->optimized) {
++ ovsdb_condition_optimize(diff);
++ }
++ } else {
++ free(diff->clauses);
++ diff->clauses = NULL;
+ }
+ }
+
+diff --git a/ovsdb/monitor.c b/ovsdb/monitor.c
+index 01091fabe..f4a9cf8fe 100644
+--- a/ovsdb/monitor.c
++++ b/ovsdb/monitor.c
+@@ -821,6 +821,7 @@ ovsdb_monitor_table_condition_update(
+ ovsdb_condition_destroy(&mtc->new_condition);
+ ovsdb_condition_clone(&mtc->new_condition, &cond);
+ ovsdb_condition_destroy(&cond);
++ ovsdb_condition_destroy(&mtc->diff_condition);
+ ovsdb_condition_diff(&mtc->diff_condition,
+ &mtc->old_condition, &mtc->new_condition);
+ ovsdb_monitor_condition_add_columns(dbmon,
+diff --git a/ovsdb/ovsdb-doc b/ovsdb/ovsdb-doc
+index 10d0c0c13..099770d25 100755
+--- a/ovsdb/ovsdb-doc
++++ b/ovsdb/ovsdb-doc
+@@ -24,7 +24,7 @@ import ovs.json
+ from ovs.db import error
+ import ovs.db.schema
+
+-from build.nroff import *
++from ovs_build_helpers.nroff import *
+
+ argv0 = sys.argv[0]
+
+diff --git a/python/automake.mk b/python/automake.mk
+index 82a508787..84cf2eab5 100644
+--- a/python/automake.mk
++++ b/python/automake.mk
+@@ -66,10 +66,10 @@ ovs_pytests = \
+ # These python files are used at build time but not runtime,
+ # so they are not installed.
+ EXTRA_DIST += \
+- python/build/__init__.py \
+- python/build/extract_ofp_fields.py \
+- python/build/nroff.py \
+- python/build/soutil.py
++ python/ovs_build_helpers/__init__.py \
++ python/ovs_build_helpers/extract_ofp_fields.py \
++ python/ovs_build_helpers/nroff.py \
++ python/ovs_build_helpers/soutil.py
+
+ # PyPI support.
+ EXTRA_DIST += \
+@@ -88,10 +88,10 @@ PYCOV_CLEAN_FILES += $(PYFILES:.py=.py,cover)
+
+ FLAKE8_PYFILES += \
+ $(filter-out python/ovs/compat/% python/ovs/dirs.py,$(PYFILES)) \
+- python/build/__init__.py \
+- python/build/extract_ofp_fields.py \
+- python/build/nroff.py \
+- python/build/soutil.py \
++ python/ovs_build_helpers/__init__.py \
++ python/ovs_build_helpers/extract_ofp_fields.py \
++ python/ovs_build_helpers/nroff.py \
++ python/ovs_build_helpers/soutil.py \
+ python/ovs/dirs.py.template \
+ python/setup.py
+
+@@ -112,11 +112,14 @@ ovs-install-data-local:
+ $(INSTALL_DATA) python/ovs/dirs.py.tmp $(DESTDIR)$(pkgdatadir)/python/ovs/dirs.py
+ rm python/ovs/dirs.py.tmp
+
++.PHONY: python-sdist
+ python-sdist: $(srcdir)/python/ovs/version.py $(ovs_pyfiles) python/ovs/dirs.py
+- (cd python/ && $(PYTHON3) setup.py sdist)
++ cd python/ && $(PYTHON3) -m build --sdist
++
++.PHONY: pypi-upload
++pypi-upload: python-sdist
++ twine upload python/dist/ovs-$(VERSION).tar.gz
+
+-pypi-upload: $(srcdir)/python/ovs/version.py $(ovs_pyfiles) python/ovs/dirs.py
+- (cd python/ && $(PYTHON3) setup.py sdist upload)
+ install-data-local: ovs-install-data-local
+
+ UNINSTALL_LOCAL += ovs-uninstall-local
+diff --git a/python/ovs/db/idl.py b/python/ovs/db/idl.py
+index 9fc2159b0..16ece0334 100644
+--- a/python/ovs/db/idl.py
++++ b/python/ovs/db/idl.py
+@@ -494,6 +494,7 @@ class Idl(object):
+ if not msg.result[0]:
+ self.__clear()
+ self.__parse_update(msg.result[2], OVSDB_UPDATE3)
++ self.last_id = msg.result[1]
+ elif self.state == self.IDL_S_DATA_MONITOR_COND_REQUESTED:
+ self.__clear()
+ self.__parse_update(msg.result, OVSDB_UPDATE2)
+diff --git a/python/build/__init__.py b/python/ovs_build_helpers/__init__.py
+similarity index 100%
+rename from python/build/__init__.py
+rename to python/ovs_build_helpers/__init__.py
+diff --git a/python/build/extract_ofp_fields.py b/python/ovs_build_helpers/extract_ofp_fields.py
+similarity index 100%
+rename from python/build/extract_ofp_fields.py
+rename to python/ovs_build_helpers/extract_ofp_fields.py
+diff --git a/python/build/nroff.py b/python/ovs_build_helpers/nroff.py
+similarity index 100%
+rename from python/build/nroff.py
+rename to python/ovs_build_helpers/nroff.py
+diff --git a/python/build/soutil.py b/python/ovs_build_helpers/soutil.py
+similarity index 100%
+rename from python/build/soutil.py
+rename to python/ovs_build_helpers/soutil.py
+diff --git a/tests/.gitignore b/tests/.gitignore
+index 83b1cb3b4..3a8c45975 100644
+--- a/tests/.gitignore
++++ b/tests/.gitignore
+@@ -3,6 +3,7 @@
+ /Makefile.in
+ /atconfig
+ /atlocal
++/clang-analyzer-results/
+ /idltest.c
+ /idltest.h
+ /idltest.ovsidl
+diff --git a/tests/learn.at b/tests/learn.at
+index d127fed34..d0bcc8363 100644
+--- a/tests/learn.at
++++ b/tests/learn.at
+@@ -836,3 +836,63 @@ AT_CHECK([ovs-vsctl add-br br1 -- set b br1 datapath_type=dummy])
+
+ OVS_VSWITCHD_STOP
+ AT_CLEANUP
++
++AT_SETUP([learning action - flapping learn rule])
++OVS_VSWITCHD_START
++add_of_ports br0 1 2 3
++
++AT_CHECK([ovs-appctl time/stop], [0], [ignore])
++AT_CHECK([[ovs-ofctl add-flow br0 'table=0,priority=2,in_port=1,actions=resubmit(,2)']])
++AT_CHECK([[ovs-ofctl add-flow br0 'table=0,priority=2,in_port=2,actions=resubmit(,2)']])
++AT_CHECK([[ovs-ofctl add-flow br0 'table=2,actions=learn(table=0,hard_timeout=3,priority=1,cookie=0x123,NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],output:OXM_OF_IN_PORT[]),output:3']])
++
++packet="eth(src=50:54:00:00:00:06,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=6,tos=0,ttl=64,frag=no),tcp(src=8,dst=9)"
++
++dnl Run this test a few times in a loop to reduce the likelyhood that it passes by chance.
++for i in 1 2 3; do
++ AT_CHECK([ovs-appctl revalidator/pause], [0])
++ AT_CHECK([ovs-appctl netdev-dummy/receive p2 $packet], [0])
++ AT_CHECK([ovs-appctl time/warp 75], [0], [ignore])
++ AT_CHECK([ovs-appctl netdev-dummy/receive p1 $packet], [0])
++ AT_CHECK([ovs-appctl time/warp 75], [0], [ignore])
++ AT_CHECK([ovs-appctl netdev-dummy/receive p2 $packet], [0])
++ AT_CHECK([ovs-appctl time/warp 75], [0], [ignore])
++ AT_CHECK([ovs-appctl netdev-dummy/receive p1 $packet], [0])
++ AT_CHECK([ovs-appctl time/warp 75], [0], [ignore])
++
++ AT_CHECK([ovs-appctl revalidator/resume], [0])
++ AT_CHECK([ovs-appctl revalidator/wait], [0])
++
++ AT_CHECK([ovs-ofctl --no-stats dump-flows br0 | ofctl_strip | sort | grep 0x123], [0], [dnl
++ cookie=0x123, hard_timeout=3, priority=1,dl_dst=50:54:00:00:00:06 actions=output:1
++ table=2, actions=learn(table=0,hard_timeout=3,priority=1,cookie=0x123,NXM_OF_ETH_DST[[]]=NXM_OF_ETH_SRC[[]],output:OXM_OF_IN_PORT[[]]),output:3
++])
++
++ AT_CHECK([ovs-appctl revalidator/pause], [0])
++ AT_CHECK([ovs-appctl netdev-dummy/receive p1 $packet], [0])
++ AT_CHECK([ovs-appctl time/warp 75], [0], [ignore])
++ AT_CHECK([ovs-appctl netdev-dummy/receive p2 $packet], [0])
++ AT_CHECK([ovs-appctl time/warp 75], [0], [ignore])
++ AT_CHECK([ovs-appctl netdev-dummy/receive p1 $packet], [0])
++ AT_CHECK([ovs-appctl time/warp 75], [0], [ignore])
++ AT_CHECK([ovs-appctl netdev-dummy/receive p2 $packet], [0])
++ AT_CHECK([ovs-appctl time/warp 75], [0], [ignore])
++
++ AT_CHECK([ovs-appctl revalidator/resume], [0])
++ AT_CHECK([ovs-appctl revalidator/wait], [0])
++
++ AT_CHECK([ovs-ofctl --no-stats dump-flows br0 | ofctl_strip | sort | grep 0x123], [0], [dnl
++ cookie=0x123, hard_timeout=3, priority=1,dl_dst=50:54:00:00:00:06 actions=output:2
++ table=2, actions=learn(table=0,hard_timeout=3,priority=1,cookie=0x123,NXM_OF_ETH_DST[[]]=NXM_OF_ETH_SRC[[]],output:OXM_OF_IN_PORT[[]]),output:3
++])
++done
++
++dnl Wait and check for learned rule eviction due to hard timeout.
++AT_CHECK([ovs-appctl time/warp 3200], [0], [ignore])
++
++AT_CHECK([ovs-ofctl --no-stats dump-flows br0 | ofctl_strip | grep 0x123], [0], [dnl
++ table=2, actions=learn(table=0,hard_timeout=3,priority=1,cookie=0x123,NXM_OF_ETH_DST[[]]=NXM_OF_ETH_SRC[[]],output:OXM_OF_IN_PORT[[]]),output:3
++])
++
++OVS_VSWITCHD_STOP
++AT_CLEANUP
+diff --git a/tests/mfex_fuzzy.py b/tests/mfex_fuzzy.py
+index 30028ba7a..50b987064 100755
+--- a/tests/mfex_fuzzy.py
++++ b/tests/mfex_fuzzy.py
+@@ -3,12 +3,15 @@
+ import sys
+ import warnings
+
+-from cryptography.utils import CryptographyDeprecationWarning
+-warnings.filterwarnings(
+- "ignore",
+- category=CryptographyDeprecationWarning,
+- message=r"(blowfish|cast5)",
+-)
++try:
++ from cryptography.utils import CryptographyDeprecationWarning
++ warnings.filterwarnings(
++ "ignore",
++ category=CryptographyDeprecationWarning,
++ message=r"(blowfish|cast5)",
++ )
++except ModuleNotFoundError:
++ pass
+
+ # flake8: noqa: E402
+ from scapy.all import RandMAC, RandIP, PcapWriter, RandIP6, RandShort, fuzz
+diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
+index f242f77f3..a39d0d3ae 100644
+--- a/tests/ofproto-dpif.at
++++ b/tests/ofproto-dpif.at
+@@ -5854,6 +5854,40 @@ OVS_WAIT_UNTIL([check_flows], [ovs-ofctl dump-flows br0])
+ OVS_VSWITCHD_STOP
+ AT_CLEANUP
+
++dnl Checks for regression against a bug in which OVS dropped packets
++dnl originating from a controller passing through a patch port.
++AT_SETUP([ofproto-dpif - packet-out recirculation OFPP_CONTROLLER and patch port])
++OVS_VSWITCHD_START(
++ [add-port br0 patch-br1 -- \
++ set interface patch-br1 type=patch options:peer=patch-br0 -- \
++ add-br br1 -- set bridge br1 datapath-type=dummy fail-mode=secure -- \
++ add-port br1 patch-br0 -- set interface patch-br0 type=patch options:peer=patch-br1
++])
++
++add_of_ports --pcap br1 1
++
++AT_DATA([flows-br0.txt], [dnl
++table=0 icmp actions=output:patch-br1
++])
++AT_CHECK([ovs-ofctl add-flows br0 flows-br0.txt])
++
++AT_DATA([flows-br1.txt], [dnl
++table=0, icmp actions=ct(table=1,zone=1)
++table=1, ct_state=+trk, icmp actions=p1
++])
++AT_CHECK([ovs-ofctl add-flows br1 flows-br1.txt])
++
++packet=50540000000750540000000508004500005c000000008001b94dc0a80001c0a80002080013fc00000000000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f
++AT_CHECK([ovs-ofctl packet-out br0 "in_port=CONTROLLER packet=$packet actions=table"])
++
++OVS_WAIT_UNTIL_EQUAL([ovs-ofctl dump-flows -m br1 | grep "ct_state" | ofctl_strip], [dnl
++ table=1, n_packets=1, n_bytes=106, ct_state=+trk,icmp actions=output:2])
++
++OVS_WAIT_UNTIL([ovs-pcap p1-tx.pcap | grep -q "$packet"])
++
++OVS_VSWITCHD_STOP
++AT_CLEANUP
++
+ AT_SETUP([ofproto-dpif - debug_slow action])
+ OVS_VSWITCHD_START
+ add_of_ports br0 1 2 3
+diff --git a/tests/ofproto.at b/tests/ofproto.at
+index 2fa8486a8..2889f81fb 100644
+--- a/tests/ofproto.at
++++ b/tests/ofproto.at
+@@ -6720,3 +6720,31 @@ recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth_type(0x0806),arp(tip=172.31.1
+
+ OVS_VSWITCHD_STOP
+ AT_CLEANUP
++
++AT_SETUP([ofproto - configure inactivity probe interval])
++
++# Set 6 second inactivity probe interval (default is 5 seconds).
++OVS_VSWITCHD_START([set-controller br0 unix:testcontroller \
++ -- set Controller br0 inactivity_probe=6000], [], [],
++ [-vfile:rconn:dbg])
++
++# Start test openflow controller.
++AT_CHECK([ovs-testcontroller -vsyslog:off --detach --no-chdir --pidfile punix:testcontroller],
++ [0], [ignore])
++on_exit 'kill `cat ovs-testcontroller.pid`'
++OVS_WAIT_UNTIL([test -e testcontroller])
++
++# After 6 seconds of inactivity there should be a log message.
++OVS_WAIT_UNTIL([grep "idle 6 seconds, sending inactivity probe" ovs-vswitchd.log])
++
++# Restart ovs-vswitchd with an empty ovs-vswitchd log file.
++OVS_APP_EXIT_AND_WAIT([ovs-vswitchd])
++mv ovs-vswitchd.log ovs-vswitchd_1.log
++AT_CHECK([ovs-vswitchd --enable-dummy --disable-system --disable-system-route --detach \
++ --no-chdir --pidfile --log-file -vfile:rconn:dbg -vvconn -vofproto_dpif -vunixctl],
++ [0], [], [stderr])
++
++# After 6 seconds of inactivity there should be a log message.
++OVS_WAIT_UNTIL([grep "idle 6 seconds, sending inactivity probe" ovs-vswitchd.log])
++OVS_VSWITCHD_STOP(["/br0<->unix:testcontroller: connection failed/d"])
++AT_CLEANUP
+diff --git a/tests/ovsdb-idl.at b/tests/ovsdb-idl.at
+index df5a9d2fd..1028b0237 100644
+--- a/tests/ovsdb-idl.at
++++ b/tests/ovsdb-idl.at
+@@ -2332,6 +2332,23 @@ CHECK_STREAM_OPEN_BLOCK([Python3], [$PYTHON3 $srcdir/test-stream.py],
+ CHECK_STREAM_OPEN_BLOCK([Python3], [$PYTHON3 $srcdir/test-stream.py],
+ [ssl6], [[[::1]]])
+
++dnl OVSDB_CLUSTER_CHECK_MONITOR_COND_SINCE_TXN_IDS(LOG)
++dnl
++dnl Looks up transaction IDs in the log of OVSDB client application.
++dnl All-zero UUID should not be sent within a monitor request more than once,
++dnl unless some database requests were lost (not replied).
++m4_define([OVSDB_CLUSTER_CHECK_MONITOR_COND_SINCE_TXN_IDS],
++[
++ requests=$(grep -c 'send request' $1)
++ replies=$(grep -c 'received reply' $1)
++
++ if test "$requests" -eq "$replies"; then
++ AT_CHECK([grep 'monitor_cond_since' $1 \
++ | grep -c "00000000-0000-0000-0000-000000000000" | tr -d '\n'],
++ [0], [1])
++ fi
++])
++
+ # same as OVSDB_CHECK_IDL but uses Python IDL implementation with tcp
+ # with multiple remotes to assert the idl connects to the leader of the Raft cluster
+ m4_define([OVSDB_CHECK_IDL_LEADER_ONLY_PY],
+@@ -2347,10 +2364,11 @@ m4_define([OVSDB_CHECK_IDL_LEADER_ONLY_PY],
+ pids=$(cat s2.pid s3.pid s1.pid | tr '\n' ',')
+ echo $pids
+ AT_CHECK([$PYTHON3 $srcdir/test-ovsdb.py -t30 idl-cluster $srcdir/idltest.ovsschema $remotes $pids $3],
+- [0], [stdout], [ignore])
++ [0], [stdout], [stderr])
+ remote=$(ovsdb_cluster_leader $remotes "idltest")
+ leader=$(echo $remote | cut -d'|' -f 1)
+ AT_CHECK([grep -F -- "${leader}" stdout], [0], [ignore])
++ OVSDB_CLUSTER_CHECK_MONITOR_COND_SINCE_TXN_IDS([stderr])
+ AT_CLEANUP])
+
+ OVSDB_CHECK_IDL_LEADER_ONLY_PY([Check Python IDL connects to leader], 3, ['remote'])
+@@ -2393,6 +2411,7 @@ m4_define([OVSDB_CHECK_CLUSTER_IDL_C],
+ AT_CHECK([sort stdout | uuidfilt]m4_if([$7],,, [[| $7]]),
+ [0], [$5])
+ m4_ifval([$8], [AT_CHECK([grep '$8' stderr], [1])], [], [])
++ OVSDB_CLUSTER_CHECK_MONITOR_COND_SINCE_TXN_IDS([stderr])
+ AT_CLEANUP])
+
+ # Same as OVSDB_CHECK_CLUSTER_IDL_C but uses the Python IDL implementation.
+@@ -2413,6 +2432,7 @@ m4_define([OVSDB_CHECK_CLUSTER_IDL_PY],
+ AT_CHECK([sort stdout | uuidfilt]m4_if([$7],,, [[| $7]]),
+ [0], [$5])
+ m4_if([$8], [AT_CHECK([grep '$8' stderr], [1])], [], [])
++ OVSDB_CLUSTER_CHECK_MONITOR_COND_SINCE_TXN_IDS([stderr])
+ AT_CLEANUP])
+
+ m4_define([OVSDB_CHECK_CLUSTER_IDL],
+diff --git a/tests/ovsdb-monitor.at b/tests/ovsdb-monitor.at
+index 12cd2bc31..3e1df18a1 100644
+--- a/tests/ovsdb-monitor.at
++++ b/tests/ovsdb-monitor.at
+@@ -586,6 +586,7 @@ row,action,name,number,_version
+ [[]],
+ [],
+ [[[[["name","==","one"],["name","==","two"]]]],
++ [[[["name","==","two"],["name","==","one"]]]],
+ [[[["name","==","one"]]]],
+ [[[false]]],
+ [[[true]]]])
+diff --git a/tests/pmd.at b/tests/pmd.at
+index 7c333a901..7bdaca9e7 100644
+--- a/tests/pmd.at
++++ b/tests/pmd.at
+@@ -1355,18 +1355,22 @@ AT_CHECK([echo 'table=0,in_port=p1,ip,nw_dst=10.1.0.0/16 actions=ct(commit)' | d
+ ovs-ofctl --bundle replace-flows br0 -])
+ AT_CHECK([ovs-appctl revalidator/wait])
+
++dnl Prevent flows from expiring.
++AT_CHECK([ovs-appctl time/stop])
++
+ AT_CHECK([ovs-appctl netdev-dummy/receive p1 'ipv4(src=10.0.0.1,dst=10.1.0.2,proto=6),tcp(src=1,dst=2)'])
+ OVS_WAIT_UNTIL_EQUAL([ovs-appctl dpctl/dump-flows | sed 's/.*core: [[0-9]]*//' | strip_xout_keep_actions], [
+ recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(dst=10.1.0.2/255.255.0.0,frag=no), packets:0, bytes:0, used:never, actions:ct(commit)
+ recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(dst=10.1.2.2/255.255.255.0,frag=no), packets:0, bytes:0, used:0.0s, actions:ct(commit)])
+
+-dnl Hold the prefix 10.1.2.2/24 by another 10s.
+-AT_CHECK([ovs-appctl netdev-dummy/receive p1 'ipv4(src=10.0.0.1,dst=10.1.2.2,proto=6),tcp(src=1,dst=2)'])
+ dnl Send more 10.1.0.2 to make 10.1.0.0/16 tuple prepend 10.1.2.0/24 tuple in the pvector of subtables.
+ for i in $(seq 0 256); do
+ AT_CHECK([ovs-appctl netdev-dummy/receive p1 'ipv4(src=10.0.0.1,dst=10.1.0.2,proto=6),tcp(src=1,dst=2)'])
+ done
+
++dnl Warp time enough to trigger subtable optimization.
++AT_CHECK([ovs-appctl time/warp 500 2000], [0], [ignore])
++
+ AT_CHECK([echo 'table=0,in_port=p1,ip,nw_dst=10.1.0.0/16 actions=p2' | dnl
+ ovs-ofctl --bundle replace-flows br0 -])
+
+diff --git a/tests/system-traffic.at b/tests/system-traffic.at
+index 808c492a2..418cd32fe 100644
+--- a/tests/system-traffic.at
++++ b/tests/system-traffic.at
+@@ -2516,6 +2516,7 @@ AT_CLEANUP
+
+ AT_SETUP([conntrack - ct flush])
+ CHECK_CONNTRACK()
++CHECK_CONNTRACK_SCTP()
+ OVS_TRAFFIC_VSWITCHD_START()
+
+ ADD_NAMESPACES(at_ns0, at_ns1)
+@@ -2526,10 +2527,8 @@ ADD_VETH(p1, at_ns1, br0, "10.1.1.2/24")
+ AT_DATA([flows.txt], [dnl
+ priority=1,action=drop
+ priority=10,arp,action=normal
+-priority=100,in_port=1,udp,action=ct(commit),2
+-priority=100,in_port=2,udp,action=ct(zone=5,commit),1
+-priority=100,in_port=1,icmp,action=ct(commit),2
+-priority=100,in_port=2,icmp,action=ct(zone=5,commit),1
++priority=100,in_port=1,ip,action=ct(commit),2
++priority=100,in_port=2,ip,action=ct(zone=5,commit),1
+ ])
+
+ AT_CHECK([ovs-ofctl --bundle add-flows br0 flows.txt])
+@@ -2692,6 +2691,25 @@ udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.
+
+ AT_CHECK([FLUSH_CMD])
+
++AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [1])
++
++dnl Test SCTP flush based on port.
++AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=1 packet=50540000000a50540000000908004500003400010000408464410a0101010a01010200010002000000009178f7d30100001470e18ccc00000000000a000a00000000 actions=resubmit(,0)"])
++AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=2 packet=50540000000950540000000a08004500003400010000408464410a0101020a010101000200010000000098f29e470100001470e18ccc00000000000a000a00000000 actions=resubmit(,0)"])
++
++AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1" | sed "s/,protoinfo=.*$//" | sort], [0], [dnl
++sctp,orig=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),reply=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1)
++sctp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5
++])
++
++AT_CHECK([FLUSH_CMD 'ct_nw_src=10.1.1.1,ct_nw_proto=132,ct_tp_src=1,ct_tp_dst=2'])
++
++AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1" | sed "s/,protoinfo=.*$//" | sort], [0], [dnl
++sctp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5
++])
++
++AT_CHECK([FLUSH_CMD 'ct_nw_src=10.1.1.2,ct_nw_proto=132,ct_tp_src=2,ct_tp_dst=1'])
++
+ AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [1])
+ ])
+
+diff --git a/tests/test-barrier.c b/tests/test-barrier.c
+index 3bc5291cc..fb0ab0e69 100644
+--- a/tests/test-barrier.c
++++ b/tests/test-barrier.c
+@@ -14,13 +14,13 @@
+ * limitations under the License.
+ */
+
+-#include rx-steering
++ is supported by hardware.
++ rx-steering
is
++ supported by hardware.
++