public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
From: Dumitru Ceara <dceara@redhat.com>
To: git-commits@fedoraproject.org
Subject: [rpms/ovn] f44: Pick up OVN v26.03.2 release.
Date: Thu, 10 Sep 2026 10:17:51 GMT [thread overview]
Message-ID: <178903547183.1.11980514382714072155.rpms-ovn-1d766d446a01@fedoraproject.org> (raw)
A new commit has been pushed.
Repo : rpms/ovn
Branch : f44
Commit : 1d766d446a011e487e156de5848479c718929636
Author : Dumitru Ceara <dceara@redhat.com>
Date : 2026-09-10T12:17:40+02:00
Stats : +17491/-5107 in 5 file(s)
URL : https://src.fedoraproject.org/rpms/ovn/c/1d766d446a011e487e156de5848479c718929636?branch=f44
Log:
Pick up OVN v26.03.2 release.
We also pick up all current bug fixes from the upstream branch-26.03 up
to and including:
e8d746c542cc ("tests: Support recent vtysh output.")
Signed-off-by: Dumitru Ceara <dceara@redhat.com>
---
diff --git a/.gitignore b/.gitignore
index 361ec1b..2911907 100644
--- a/.gitignore
+++ b/.gitignore
@@ -64,3 +64,5 @@
/openvswitch-da6b84d.tar.gz
/openvswitch-bdb95cc.tar.gz
/ovn-26.03.1.tar.gz
+/openvswitch-45ee6f7.tar.gz
+/ovn-26.03.2.tar.gz
diff --git a/0001-fedora-Use-PROFILE-SYSTEM-in-SSL_CTX_set_cipher_list.patch b/0001-fedora-Use-PROFILE-SYSTEM-in-SSL_CTX_set_cipher_list.patch
index 8a49a7d..f344eff 100644
--- a/0001-fedora-Use-PROFILE-SYSTEM-in-SSL_CTX_set_cipher_list.patch
+++ b/0001-fedora-Use-PROFILE-SYSTEM-in-SSL_CTX_set_cipher_list.patch
@@ -11,15 +11,14 @@ Signed-off-by: Numan Siddique <nusiddiq@redhat.com>
lib/stream-ssl.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
-diff --git a/ovs-bdb95cc1920d4ab66fe062a9470eeb33a51d33e2/lib/stream-ssl.c b/ovs-bdb95cc1920d4ab66fe062a9470eeb33a51d33e2/lib/stream-ssl.c
-/lib/stream-ssl.c
-index fed71801b..78e54c39e 100644
---- a/ovs-bdb95cc1920d4ab66fe062a9470eeb33a51d33e2/lib/stream-ssl.c
-+++ b/ovs-bdb95cc1920d4ab66fe062a9470eeb33a51d33e2/lib/stream-ssl.c
-@@ -1014,7 +1014,7 @@ do_ssl_init(void)
- SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
+diff --git a/ovs-45ee6f7d96dec264df2c044bc9afa61d4ef5af37/lib/stream-ssl.c b/ovs-45ee6f7d96dec264df2c044bc9afa61d4ef5af37/lib/stream-ssl.c
+index 9b468e0fadbe..408e2d1c2ed7 100644
+--- a/ovs-45ee6f7d96dec264df2c044bc9afa61d4ef5af37/lib/stream-ssl.c
++++ b/ovs-45ee6f7d96dec264df2c044bc9afa61d4ef5af37/lib/stream-ssl.c
+@@ -1081,7 +1081,7 @@ do_ssl_init(void)
NULL);
SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);
+ SSL_CTX_set_options(ctx, SSL_OP_NO_TICKET);
- SSL_CTX_set_cipher_list(ctx, "DEFAULT:@SECLEVEL=2");
+ SSL_CTX_set_cipher_list(ctx, "PROFILE=SYSTEM");
diff --git a/ovn.patch b/ovn.patch
index c010e2d..0e31f16 100644
--- a/ovn.patch
+++ b/ovn.patch
@@ -1,5493 +1,17866 @@
-diff --git a/controller/bfd.c b/controller/bfd.c
-index 56bfa49361..3b0c3f6dae 100644
---- a/controller/bfd.c
-+++ b/controller/bfd.c
-@@ -117,14 +117,13 @@ bfd_calculate_active_tunnels(const struct ovsrec_bridge *br_int,
- *
- * If 'our_chassis' is C5 then this function returns empty bfd set.
- */
--bool
-+void
- bfd_calculate_chassis(
- const struct sbrec_chassis *our_chassis,
- const struct sbrec_ha_chassis_group_table *ha_chassis_grp_table,
- struct sset *bfd_chassis)
- {
- const struct sbrec_ha_chassis_group *ha_chassis_grp;
-- bool chassis_is_ha_gw = false;
- SBREC_HA_CHASSIS_GROUP_TABLE_FOR_EACH (ha_chassis_grp,
- ha_chassis_grp_table) {
- bool is_ha_chassis = false;
-@@ -144,7 +143,6 @@ bfd_calculate_chassis(
- sset_add(&grp_chassis, ha_ch->chassis->name);
- if (our_chassis == ha_ch->chassis) {
- is_ha_chassis = true;
-- chassis_is_ha_gw = true;
- bfd_setup_required = true;
- }
- }
-@@ -180,7 +178,6 @@ bfd_calculate_chassis(
- }
- sset_destroy(&grp_chassis);
- }
-- return chassis_is_ha_gw;
+diff --git a/.ci/linux-util.sh b/.ci/linux-util.sh
+index b5bd1f8c9c..0b42aae9d4 100755
+--- a/.ci/linux-util.sh
++++ b/.ci/linux-util.sh
+@@ -49,3 +49,15 @@ function disable_apparmor()
+ sudo aa-teardown || true
+ sudo systemctl disable --now apparmor.service
}
++
++# XXX This should be removed when the following issue is fixed and the
++# correct crun version is packaged into the runner image.
++# https://github.com/actions/runner-images/issues/14473
++function fixup_crun()
++{
++ crun --version
++ sudo curl -L "https://github.com/containers/crun/releases/download/1.28/crun-1.28-linux-amd64" \
++ -o /usr/bin/crun
++ sudo chmod +x /usr/bin/crun
++ echo "New crun version: "$(crun --version)
++}
+diff --git a/.ci/ovn_upgrade_utils.py b/.ci/ovn_upgrade_utils.py
+index f5ae787cbf..c0db4977f5 100755
+--- a/.ci/ovn_upgrade_utils.py
++++ b/.ci/ovn_upgrade_utils.py
+@@ -410,14 +410,28 @@ def ovn_upgrade_save_ovn_debug(binaries_dir):
+ return True
- void
-diff --git a/controller/bfd.h b/controller/bfd.h
-index 3e33848912..f8fece5a58 100644
---- a/controller/bfd.h
-+++ b/controller/bfd.h
-@@ -16,8 +16,6 @@
- #ifndef OVN_BFD_H
- #define OVN_BFD_H 1
-
--#include <stdbool.h>
--
- struct hmap;
- struct ovsdb_idl;
- struct ovsdb_idl_index;
-@@ -38,7 +36,7 @@ void bfd_run(const struct ovsrec_interface_table *,
- const struct sbrec_sb_global_table *,
- const struct ovsrec_open_vswitch_table *);
-
--bool bfd_calculate_chassis(
-+void bfd_calculate_chassis(
- const struct sbrec_chassis *,
- const struct sbrec_ha_chassis_group_table *,
- struct sset *);
-diff --git a/controller/encaps.c b/controller/encaps.c
-index 61f41bf3ac..081fbe6716 100644
---- a/controller/encaps.c
-+++ b/controller/encaps.c
-@@ -25,7 +25,6 @@
- #include "lib/ovn-sb-idl.h"
- #include "lib/ovsdb-idl.h"
- #include "ovn-controller.h"
--#include "smap.h"
- VLOG_DEFINE_THIS_MODULE(encaps);
+-def update_test(old_start, old_end, shift, test_file):
++def _parse_oftable_defines(lines):
++ """Return {name: int_value} for all OFTABLE_ #defines."""
++ result = {}
++ for line in lines:
++ parts = line.split()
++ if len(parts) >= 3 and parts[0] == '#define' \
++ and parts[1].startswith('OFTABLE_'):
++ try:
++ result[parts[1]] = int(parts[2])
++ except ValueError:
++ pass
++ return result
++
++
++def update_test(table_remap, test_file):
+ with open(test_file, encoding='utf-8') as f:
+ content = f.read()
-@@ -44,6 +43,7 @@ encaps_register_ovs_idl(struct ovsdb_idl *ovs_idl)
- ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_name);
- ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_type);
- ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_options);
-+ ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_other_config);
- }
+ def replace_table(match):
+ table_num = int(match.group(1))
+- if old_start <= table_num < old_end:
+- return f"table={table_num + shift}"
++ if table_num in table_remap:
++ return f"table={table_remap[table_num]}"
+ return match.group(0)
- /* Enough context to create a new tunnel, using tunnel_add(). */
-@@ -201,12 +201,14 @@ out:
- }
+ # Replace all table=NUMBER patterns
+@@ -430,44 +444,61 @@ def update_test(old_start, old_end, shift, test_file):
+ def ovn_upgrade_table_numbers_in_tests_patch(config):
+ lflow_h = Path('controller/lflow.h')
- static void
--tunnel_add(struct tunnel_ctx *tc, const struct sbrec_sb_global *sbg,
-- const char *new_chassis_id, const struct sbrec_encap *encap,
-- const char *local_ip,
-+tunnel_add(struct tunnel_ctx *tc,
-+ const struct sbrec_sb_global *sbg,
-+ const struct sbrec_chassis *chassis_rec,
-+ const struct sbrec_encap *encap, const char *local_ip,
- const struct ovsrec_open_vswitch_table *ovs_table)
- {
- struct smap options = SMAP_INITIALIZER(&options);
-+ struct smap other_config = SMAP_INITIALIZER(&other_config);
- smap_add(&options, "remote_ip", encap->ip);
- smap_add(&options, "local_ip", local_ip);
- smap_add(&options, "key", "flow");
-@@ -221,9 +223,9 @@ tunnel_add(struct tunnel_ctx *tc, const struct sbrec_sb_global *sbg,
- * combination of the chassis_name and the remote and local encap-ips to
- * identify a specific tunnel to the remote chassis.
- */
-- tunnel_entry_id = encaps_tunnel_id_create(new_chassis_id, encap->ip,
-+ tunnel_entry_id = encaps_tunnel_id_create(chassis_rec->name, encap->ip,
- local_ip);
-- tunnel_entry_id_old = encaps_tunnel_id_create_legacy(new_chassis_id,
-+ tunnel_entry_id_old = encaps_tunnel_id_create_legacy(chassis_rec->name,
- encap->ip);
- if (csum && (!strcmp(csum, "true") || !strcmp(csum, "false"))) {
- smap_add(&options, "csum", csum);
-@@ -258,7 +260,7 @@ tunnel_add(struct tunnel_ctx *tc, const struct sbrec_sb_global *sbg,
-
- /* Add auth info if ipsec is enabled. */
- if (sbg->ipsec) {
-- smap_add(&options, "remote_name", new_chassis_id);
-+ smap_add(&options, "remote_name", chassis_rec->name);
-
- /* Force NAT-T traversal via configuration */
- /* Two ipsec backends are supported: libreswan and strongswan */
-@@ -276,6 +278,11 @@ tunnel_add(struct tunnel_ctx *tc, const struct sbrec_sb_global *sbg,
- }
- }
+- if not config.file.new_egress.exists():
+- log("No LOG_EGRESS")
++ if not config.file.ofctl_defines.exists():
++ log("No ofctl defines file")
+ return False
-+ if (is_ramp_tunnel(&chassis_rec->other_config)) {
-+ /* Propagate ramp switch flag from chassis to interface. */
-+ smap_add(&other_config, "is-vtep", "true");
-+ }
+ if not lflow_h.exists():
+ log("Controller/lflow.h not found")
+ return False
+
+- with open(config.file.new_egress, encoding='utf-8') as f:
+- new_log_egress = int(f.read().strip())
++ # Get new OFTABLE values (saved from the current version).
++ with open(config.file.ofctl_defines, encoding='utf-8') as f:
++ new_defines = _parse_oftable_defines(f.readlines())
+
+- # Get old values from base version's lflow.h
++ # Get old OFTABLE values from the base version's lflow.h.
+ with open(lflow_h, encoding='utf-8') as f:
+- content = [
+- line.strip() for line in f if line.startswith('#define OFTABLE_')
+- ]
++ old_defines = _parse_oftable_defines(f.readlines())
+
+- old_log_egress, old_save_inport = extract_oftable_values(content)
++ old_log_egress = old_defines.get('OFTABLE_LOG_EGRESS_PIPELINE')
++ old_save_inport = old_defines.get('OFTABLE_SAVE_INPORT')
+
+- if (not old_log_egress or not old_save_inport
+- or old_log_egress == new_log_egress):
+- log(f"No change in test files as old_log_egress={old_log_egress}, "
+- f"old_save_inport={old_save_inport} and "
+- f"new_log_egress={new_log_egress}")
+- # No change needed is success.
+- return True
++ if not old_log_egress or not old_save_inport:
++ log("Could not extract LOG_EGRESS / SAVE_INPORT from base")
++ return False
+
- /* If there's an existing tunnel record that does not need any change,
- * keep it. Otherwise, create a new record (if there was an existing
- * record, the new record will supplant it and encaps_run() will delete
-@@ -312,10 +319,10 @@ tunnel_add(struct tunnel_ctx *tc, const struct sbrec_sb_global *sbg,
- * its name, otherwise generate a new, unique name. */
- char *port_name = (tunnel
- ? xstrdup(tunnel->port->name)
-- : tunnel_create_name(tc, new_chassis_id));
-+ : tunnel_create_name(tc, chassis_rec->name));
- if (!port_name) {
- VLOG_WARN("Unable to allocate unique name for '%s' tunnel",
-- new_chassis_id);
-+ chassis_rec->name);
- goto exit;
- }
++ new_log_egress = new_defines.get('OFTABLE_LOG_EGRESS_PIPELINE')
++
++ # Build {old_value: new_value} remap for all changed tables.
++ table_remap = {}
-@@ -323,6 +330,7 @@ tunnel_add(struct tunnel_ctx *tc, const struct sbrec_sb_global *sbg,
- ovsrec_interface_set_name(iface, port_name);
- ovsrec_interface_set_type(iface, encap->type);
- ovsrec_interface_set_options(iface, &options);
-+ ovsrec_interface_set_other_config(iface, &other_config);
-
- struct ovsrec_port *port = ovsrec_port_insert(tc->ovs_txn);
- ovsrec_port_set_name(port, port_name);
-@@ -338,6 +346,7 @@ exit:
- free(tunnel_entry_id);
- free(tunnel_entry_id_old);
- smap_destroy(&options);
-+ smap_destroy(&other_config);
- }
+- shift = new_log_egress - old_log_egress
++ # Range-based shift for in-pipeline tables [LOG_EGRESS, SAVE_INPORT).
++ # These include hardcoded offsets that are not OFTABLE_ defines.
++ if new_log_egress and new_log_egress != old_log_egress:
++ shift = new_log_egress - old_log_egress
++ for t in range(old_log_egress, old_save_inport):
++ table_remap[t] = t + shift
++
++ # Exact remap for every OFTABLE_ define that changed and is
++ # outside the pipeline range (e.g. CHK_LB_AFFINITY, ECMP_NH).
++ for name, old_val in old_defines.items():
++ if name in new_defines and new_defines[name] != old_val:
++ if old_val not in table_remap:
++ table_remap[old_val] = new_defines[name]
++
++ if not table_remap:
++ log("No table number changes detected")
++ return True
- static bool
-@@ -403,7 +412,7 @@ chassis_tunnel_add(const struct sbrec_chassis *chassis_rec,
- }
- VLOG_DBG("tunnel_add: '%s', local ip: %s", chassis_rec->name,
- this_chassis->encaps[j]->ip);
-- tunnel_add(tc, sbg, chassis_rec->name, chassis_rec->encaps[i],
-+ tunnel_add(tc, sbg, chassis_rec, chassis_rec->encaps[i],
- this_chassis->encaps[j]->ip, ovs_table);
- tuncnt++;
- }
-diff --git a/controller/encaps.h b/controller/encaps.h
-index fa5dc17e5f..0257d08c13 100644
---- a/controller/encaps.h
-+++ b/controller/encaps.h
-@@ -17,6 +17,7 @@
- #define OVN_ENCAPS_H 1
+- log(f"Updating hardcoded table numbers in tests (shift: +{shift} for "
+- f"tables {old_log_egress}-{old_save_inport - 1})")
++ log(f"Updating hardcoded table numbers in tests "
++ f"({len(table_remap)} table(s) remapped)")
- #include <stdbool.h>
-+#include "smap.h"
+ # Update test files
+ for test_file in ['tests/system-ovn.at', 'tests/system-ovn-kmod.at',
+ 'tests/system-ovn-netlink.at']:
+ if Path(test_file).exists():
+ log(f"Updating {test_file}")
+- update_test(old_log_egress, old_save_inport, shift, test_file)
++ update_test(table_remap, test_file)
+ return True
- /*
- * Given there could be multiple tunnels with different IPs to the same
-@@ -68,4 +69,10 @@ bool encaps_tunnel_id_match(const char *tunnel_id, const char *chassis_id,
- void encaps_destroy(void);
+diff --git a/.github/workflows/containers.yml b/.github/workflows/containers.yml
+index 16a869b955..05875bcaf0 100644
+--- a/.github/workflows/containers.yml
++++ b/.github/workflows/containers.yml
+@@ -11,7 +11,8 @@ env:
+ IMAGE_NAMESPACE: ovn-org
+ IMAGE_NAME: ovn-tests
+ CONTAINERS_PATH: ./utilities/containers
+- DEPENDENCIES: podman
++ DEPENDENCIES:
++ PREINSTALLED_DEPENDENCIES: podman
-+static inline bool
-+is_ramp_tunnel(const struct smap *other_config)
-+{
-+ return smap_get_bool(other_config, "is-vtep", false);
-+}
+ jobs:
+ container:
+@@ -26,14 +27,25 @@ jobs:
+ - name: Update APT cache
+ run: sudo apt update
+
++ - name: Ensure pre-installed dependencies
++ run: which ${{ env.PREINSTALLED_DEPENDENCIES }}
+
- #endif /* controller/encaps.h */
-diff --git a/controller/evpn-binding.c b/controller/evpn-binding.c
-index b176706c3f..4da8020e15 100644
---- a/controller/evpn-binding.c
-+++ b/controller/evpn-binding.c
-@@ -32,6 +32,7 @@ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-
- static void collect_evpn_datapaths(const struct hmap *local_datapaths,
- struct hmap *evpn_datapaths);
-+static void evpn_datapaths_clear(struct hmap *evpn_datapaths);
-
- struct evpn_tunnel {
- uint16_t dst_port;
-@@ -62,6 +63,7 @@ evpn_binding_run(const struct evpn_binding_ctx_in *b_ctx_in,
- struct hmapx stale_mc_groups = HMAPX_INITIALIZER(&stale_mc_groups);
- uint32_t hint = OVN_MIN_EVPN_KEY;
-
-+ evpn_datapaths_clear(b_ctx_out->datapaths);
- collect_evpn_datapaths(b_ctx_in->local_datapaths, b_ctx_out->datapaths);
-
- struct evpn_binding *binding;
-@@ -233,7 +235,7 @@ evpn_datapath_find(const struct hmap *evpn_datapaths, uint32_t vni)
- return NULL;
- }
+ - name: Install dependencies
+- run: sudo apt install -y ${{ env.DEPENDENCIES }}
++ run: |
++ if [ -n "${{ env.DEPENDENCIES }}" ]; then
++ sudo apt install -y ${{ env.DEPENDENCIES }}
++ fi
--void
-+static void
- evpn_datapaths_clear(struct hmap *evpn_datapaths)
- {
- struct evpn_datapath *edp;
-diff --git a/controller/evpn-binding.h b/controller/evpn-binding.h
-index 29b85fc162..521db9d6e3 100644
---- a/controller/evpn-binding.h
-+++ b/controller/evpn-binding.h
-@@ -93,7 +93,6 @@ void evpn_vtep_binding_list(struct unixctl_conn *conn, int argc,
- const char *argv[], void *data_);
- const struct evpn_datapath *evpn_datapath_find(
- const struct hmap *evpn_datapaths, uint32_t vni);
--void evpn_datapaths_clear(struct hmap *evpn_datapaths);
- void evpn_datapaths_destroy(struct hmap *evpn_datapaths);
- void evpn_multicast_groups_destroy(struct hmap *multicast_groups);
- void evpn_multicast_group_list(struct unixctl_conn *conn, int argc,
-diff --git a/controller/garp_rarp.c b/controller/garp_rarp.c
-index 9d0c2c2e4b..1cf415a9fa 100644
---- a/controller/garp_rarp.c
-+++ b/controller/garp_rarp.c
-@@ -298,6 +298,7 @@ reset_timers_for_claimed_cr(struct if_status_mgr *mgr)
- SSET_FOR_EACH_SAFE (cr_logical_port, claimed_cr) {
- garp_rarp_node_reset_timers(cr_logical_port);
- sset_delete(claimed_cr, SSET_NODE_FROM_NAME(cr_logical_port));
-+ garp_rarp_data_has_changed = true;
- }
+ - name: Fix /etc/hosts file
+ run: |
+ . .ci/linux-util.sh
+ fix_etc_hosts
- }
-@@ -565,7 +566,7 @@ garp_rarp_get_data(void)
- bool
- garp_rarp_data_changed(void) {
- bool ret = garp_rarp_data_has_changed;
-- garp_rarp_data_has_changed = true;
-+ garp_rarp_data_has_changed = false;
- return ret;
- }
++ - name: Fixup crun
++ run: |
++ . .ci/linux-util.sh
++ fixup_crun
++
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v2
-diff --git a/controller/lflow.c b/controller/lflow.c
-index 35ed6d30b7..382c2aecb9 100644
---- a/controller/lflow.c
-+++ b/controller/lflow.c
-@@ -229,6 +229,27 @@ is_chassis_resident_cb(const void *c_aux_, const char *port_name)
- }
- }
+diff --git a/.github/workflows/ovn-fake-multinode-tests.yml b/.github/workflows/ovn-fake-multinode-tests.yml
+index 1d825a72dd..a652904ddc 100644
+--- a/.github/workflows/ovn-fake-multinode-tests.yml
++++ b/.github/workflows/ovn-fake-multinode-tests.yml
+@@ -21,6 +21,8 @@ jobs:
+ - { repo: "${{ github.repository }}", branch: "${{ github.ref_name }}" }
+ - { repo: ovn-org/ovn, branch: "branch-24.03" }
+ env:
++ DEPENDENCIES:
++ PREINSTALLED_DEPENDENCIES: podman
+ RUNC_CMD: podman
+ OS_IMAGE: "fedora:42"
+ # https://github.com/actions/runner-images/issues/6282
+@@ -51,10 +53,17 @@ jobs:
+ repository: ${{ matrix.cfg.repo }}
+ ref: ${{ matrix.cfg.branch }}
-+static bool
-+lport_id_is_local(const struct lflow_ctx_in *l_ctx_in,
-+ const struct uuid *lflow_uuid,
-+ int64_t dp_id, int64_t port_id)
-+{
-+ /* To/from EVPN VTEP logical port keys are always local. */
-+ if (OVN_IS_EVPN_KEY(port_id)) {
-+ return true;
-+ }
++ - name: Update APT cache
++ run: sudo apt update
+
-+ char buf[16];
-+ get_unique_lport_key(dp_id, port_id, buf, sizeof(buf));
++ - name: Ensure pre-installed dependencies
++ run: which ${{ env.PREINSTALLED_DEPENDENCIES }}
+
-+ if (!sset_contains(l_ctx_in->related_lport_ids, buf)) {
-+ VLOG_DBG("lflow "UUID_FMT" port %s in match is not local, skip",
-+ UUID_ARGS(lflow_uuid), buf);
-+ return false;
-+ }
-+ return true;
-+}
+ - name: Install dependencies
+ run: |
+- sudo apt update || true
+- sudo apt-get install -y podman
++ if [ -n "${{ env.DEPENDENCIES }}" ]; then
++ sudo apt install -y ${{ env.DEPENDENCIES }}
++ fi
+
+ - name: Fix /etc/hosts file
+ run: |
+@@ -68,6 +77,12 @@ jobs:
+ disable_apparmor
+ working-directory: ovn-fake-multinode/ovn
+
++ - name: Fixup crun
++ run: |
++ . .ci/linux-util.sh
++ fixup_crun
++ working-directory: ovn-fake-multinode/ovn
+
- /* Adds the logical flows from the Logical_Flow table to flow tables. */
- static void
- add_logical_flows(struct lflow_ctx_in *l_ctx_in,
-@@ -936,22 +957,15 @@ add_matches_to_flow_table(const struct sbrec_logical_flow *lflow,
-
- struct expr_match *m;
- HMAP_FOR_EACH (m, hmap_node, matches) {
-- match_set_metadata(&m->match, htonll(ldp->datapath->tunnel_key));
-+ int64_t dp_id = ldp->datapath->tunnel_key;
-+ match_set_metadata(&m->match, htonll(dp_id));
- if (ldp->is_switch) {
- unsigned int reg_index
- = (ingress ? MFF_LOG_INPORT : MFF_LOG_OUTPORT) - MFF_REG0;
- int64_t port_id = m->match.flow.regs[reg_index];
-- if (port_id) {
-- int64_t dp_id = ldp->datapath->tunnel_key;
-- char buf[16];
-- get_unique_lport_key(dp_id, port_id, buf, sizeof(buf));
-- if (!sset_contains(l_ctx_in->related_lport_ids, buf)) {
-- VLOG_DBG("lflow "UUID_FMT
-- " port %s in match is not local, skip",
-- UUID_ARGS(&lflow->header_.uuid),
-- buf);
-- continue;
-- }
-+ if (port_id && !lport_id_is_local(l_ctx_in, &lflow->header_.uuid,
-+ dp_id, port_id)) {
-+ continue;
- }
- }
+ - name: Build ovn-fake-multi-node ${{ matrix.cfg.branch }} image
+ run: |
+ set -x
+@@ -104,22 +119,30 @@ jobs:
+ ENABLE_SSL: no
+ CC: gcc
+ OPTS: "--disable-ssl"
+- dependencies: |
++ DEPENDENCIES: |
+ automake libtool gcc bc libjemalloc2 libjemalloc-dev \
+ libssl-dev llvm-dev libelf-dev libnuma-dev libpcap-dev \
+ selinux-policy-dev ncat python3-scapy isc-dhcp-server \
+- podman openvswitch-switch libunbound-dev libunwind-dev
++ openvswitch-switch libunbound-dev libunwind-dev
+ # https://github.com/actions/runner-images/issues/6282
++ PREINSTALLED_DEPENDENCIES: podman
+ XDG_RUNTIME_DIR: ''
-@@ -1090,11 +1104,8 @@ consider_logical_flow__(const struct sbrec_logical_flow *lflow,
- "found, skip", UUID_ARGS(&lflow->header_.uuid), io_port);
- return;
- }
-- char buf[16];
-- get_unique_lport_key(dp->tunnel_key, pb->tunnel_key, buf, sizeof buf);
-- if (!sset_contains(l_ctx_in->related_lport_ids, buf)) {
-- VLOG_DBG("lflow "UUID_FMT" matches inport/outport %s that's not "
-- "local, skip", UUID_ARGS(&lflow->header_.uuid), io_port);
-+ if (!lport_id_is_local(l_ctx_in, &lflow->header_.uuid,
-+ dp->tunnel_key, pb->tunnel_key)) {
- return;
- }
- }
-diff --git a/controller/local_data.c b/controller/local_data.c
-index dda746d73e..af6c75b400 100644
---- a/controller/local_data.c
-+++ b/controller/local_data.c
-@@ -532,6 +532,7 @@ local_nonvif_data_run(const struct ovsrec_bridge *br_int,
- tun->ofport = u16_to_ofp(ofport);
- tun->type = tunnel_type;
- tun->is_ipv6 = ip ? addr_is_ipv6(ip) : false;
-+ tun->is_ramp_tunnel = is_ramp_tunnel(&iface_rec->other_config);
-
- free(hash_id);
- free(ip);
-diff --git a/controller/local_data.h b/controller/local_data.h
-index 948c1a935e..cbb8899eb6 100644
---- a/controller/local_data.h
-+++ b/controller/local_data.h
-@@ -146,6 +146,7 @@ struct chassis_tunnel {
- ofp_port_t ofport;
- enum chassis_tunnel_type type;
- bool is_ipv6;
-+ bool is_ramp_tunnel;
- };
+ steps:
+ - name: Check out ovn
+ uses: actions/checkout@v6
- /* Flow-based tunnel that consolidates multiple endpoints into a single
-diff --git a/controller/mac-cache.c b/controller/mac-cache.c
-index bdf35eeb75..c996fd6b9a 100644
---- a/controller/mac-cache.c
-+++ b/controller/mac-cache.c
-@@ -59,12 +59,11 @@ static void
- mac_cache_update_req_delay(struct hmap *thresholds, uint64_t *req_delay);
+- - name: install required dependencies
+- run: |
+- sudo apt update || true
+- sudo apt install -y ${{ env.dependencies }}
++ - name: Update APT cache
++ run: sudo apt update
++
++ - name: Ensure pre-installed dependencies
++ run: which ${{ env.PREINSTALLED_DEPENDENCIES }}
++
++ - name: Install dependencies
++ run: |
++ if [ -n "${{ env.DEPENDENCIES }}" ]; then
++ sudo apt install -y ${{ env.DEPENDENCIES }}
++ fi
+ sudo apt install -y linux-modules-extra-$(uname -r)
- static struct buffered_packets *
--buffered_packets_find(struct buffered_packets_ctx *ctx,
-+buffered_packets_find(struct cmap *bp_map,
- const struct mac_binding_data *mb_data);
+ - name: Fix /etc/hosts file
+@@ -132,6 +155,11 @@ jobs:
+ . .ci/linux-util.sh
+ disable_apparmor
- static void
--buffered_packets_remove(struct buffered_packets_ctx *ctx,
-- struct buffered_packets *bp);
-+buffered_packets_free(struct buffered_packets *bp);
++ - name: Fixup crun
++ run: |
++ . .ci/linux-util.sh
++ fixup_crun
++
+ - name: Free up disk space
+ run: |
+ . .ci/linux-util.sh
+diff --git a/.github/workflows/ovn-upgrade-tests.yml b/.github/workflows/ovn-upgrade-tests.yml
+index 7bed201ac6..a4f459b431 100644
+--- a/.github/workflows/ovn-upgrade-tests.yml
++++ b/.github/workflows/ovn-upgrade-tests.yml
+@@ -58,6 +58,11 @@ jobs:
+ . .ci/linux-util.sh
+ disable_apparmor
- static void
- buffered_packets_db_lookup(struct buffered_packets *bp,
-@@ -550,24 +549,24 @@ bp_packet_data_destroy(struct bp_packet_data *pd) {
- }
++ - name: Fixup crun
++ run: |
++ . .ci/linux-util.sh
++ fixup_crun
++
+ - name: Download container
+ run: sudo podman pull ghcr.io/ovn-org/ovn-tests:ubuntu
- struct buffered_packets *
--buffered_packets_add(struct buffered_packets_ctx *ctx,
-- struct mac_binding_data mb_data) {
-+buffered_packets_add(struct cmap *bp_map, struct mac_binding_data mb_data) {
- uint32_t hash = mac_binding_data_hash(&mb_data);
-
-- struct buffered_packets *bp = buffered_packets_find(ctx, &mb_data);
-+ struct buffered_packets *bp = buffered_packets_find(bp_map, &mb_data);
- if (!bp) {
-- if (hmap_count(&ctx->buffered_packets) >= MAX_BUFFERED_PACKETS) {
-+ if (cmap_count(bp_map) >= MAX_BUFFERED_PACKETS) {
- return NULL;
- }
+diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
+index 64073b228a..f1a2cd1f7c 100644
+--- a/.github/workflows/test.yml
++++ b/.github/workflows/test.yml
+@@ -23,7 +23,8 @@ jobs:
+ # | !main | Builds - Ubuntu | xxxxxxxxxxxxxxxxx |
+ # +-------+-------------------+-------------------+
+ env:
+- DEPENDENCIES: podman
++ DEPENDENCIES:
++ PREINSTALLED_DEPENDENCIES: podman
+ name: Prepare container
+ if: github.repository_owner == 'ovn-org' || github.event_name != 'schedule'
+ runs-on: ubuntu-24.04
+@@ -34,8 +35,14 @@ jobs:
+ - name: Update APT cache
+ run: sudo apt update
- bp = xmalloc(sizeof *bp);
-- hmap_insert(&ctx->buffered_packets, &bp->hmap_node, hash);
- bp->mb_data = mb_data;
-+ atomic_init(&bp->resolved_mac, 0);
- /* Schedule the freshly added buffered packet to do lookup
- * immediately. */
- bp->lookup_at_ms = 0;
- bp->queue = VECTOR_CAPACITY_INITIALIZER(struct bp_packet_data,
- BUFFER_QUEUE_DEPTH);
-+ cmap_insert(bp_map, &bp->cmap_node, hash);
- }
++ - name: Ensure pre-installed dependencies
++ run: which ${{ env.PREINSTALLED_DEPENDENCIES }}
++
+ - name: Install dependencies
+- run: sudo apt install -y ${{ env.DEPENDENCIES }}
++ run: |
++ if [ -n "${{ env.DEPENDENCIES }}" ]; then
++ sudo apt install -y ${{ env.DEPENDENCIES }}
++ fi
- bp->expire_at_ms = time_msec() + BUFFERED_PACKETS_TIMEOUT_MS;
-@@ -605,25 +604,28 @@ buffered_packets_packet_data_enqueue(struct buffered_packets *bp,
- vector_push(&bp->queue, &pd);
- }
+ - name: Fix /etc/hosts file
+ run: |
+@@ -47,6 +54,11 @@ jobs:
+ . .ci/linux-util.sh
+ disable_apparmor
--void
--buffered_packets_ctx_run(struct buffered_packets_ctx *ctx,
-- const struct hmap *recent_mbs,
-- struct ovsdb_idl_index *sbrec_pb_by_key,
-- struct ovsdb_idl_index *sbrec_dp_by_key,
-- struct ovsdb_idl_index *sbrec_pb_by_name,
-- struct ovsdb_idl_index *sbrec_mb_by_lport_ip) {
-+bool
-+buffered_packets_lookup_run(struct cmap *bp_map, const struct hmap *recent_mbs,
-+ struct ovsdb_idl_index *sbrec_pb_by_key,
-+ struct ovsdb_idl_index *sbrec_dp_by_key,
-+ struct ovsdb_idl_index *sbrec_pb_by_name,
-+ struct ovsdb_idl_index *sbrec_mb_by_lport_ip) {
- struct ds ip = DS_EMPTY_INITIALIZER;
- long long now = time_msec();
-+ bool updated = false;
-
- struct buffered_packets *bp;
-- HMAP_FOR_EACH_SAFE (bp, hmap_node, &ctx->buffered_packets) {
-- struct eth_addr mac = eth_addr_zero;
-- /* Remove expired buffered packets. */
-- if (now > bp->expire_at_ms) {
-- buffered_packets_remove(ctx, bp);
-+ CMAP_FOR_EACH (bp, cmap_node, bp_map) {
-+ uint64_t mac64;
-+ atomic_read(&bp->resolved_mac, &mac64);
-+ /* MAC for given entry was already resolved,
-+ * no need to resolve it again. */
-+ if (mac64) {
- continue;
- }
++ - name: Fixup crun
++ run: |
++ . .ci/linux-util.sh
++ fixup_crun
++
+ - name: Choose image distro
+ if: github.event_name == 'push' || github.event_name == 'pull_request'
+ run: |
+@@ -171,6 +183,11 @@ jobs:
+ . .ci/linux-util.sh
+ disable_apparmor
+
++ - name: Fixup crun
++ run: |
++ . .ci/linux-util.sh
++ fixup_crun
++
+ - name: image cache
+ id: image_cache
+ uses: actions/cache@v5
+diff --git a/NEWS b/NEWS
+index 3aced56731..9f12018dea 100644
+--- a/NEWS
++++ b/NEWS
+@@ -1,5 +1,20 @@
+ OVN v26.03.3 - xx xxx xxxx
+ --------------------------
++ - Reverted fix for Load_Balancer health check replies for baremetal
++ pool members whose backend LSP is type=external. This was causing
++ a packet loop.
++ - Mark tunnel ports as transient (other_config:transient=true) when the
++ local chassis is a member of an HA chassis group.
++ Systems which invoke ovs-ctl --delete-transient-ports during OVS startup
++ (e.g. via the ovs-delete-transient-ports.service on RHEL/Fedora) will
++ automatically remove stale tunnel ports on reboot, preventing them from
++ interfering with BFD and HA failover after a gateway chassis reboot.
++ - Dynamic Routing:
++ * Remove the "other_config:dynamic-routing-arp-prefer-local"
++ option from Logical Switches. EVPN-learned MAC bindings are
++ now written to the SB MAC_Binding table and consumed at the
++ same priority as dynamic entries, making the preference option
++ obsolete.
+
+ OVN v26.03.2 - 16 Jun 2026
+ --------------------------
+@@ -91,7 +106,7 @@ OVN v26.03.0 - 20 Mar 2026
+ and support both ingress (from-lport) and egress (to-lport) traffic flows.
+ Two new options have been added to Logical_Switch_Port which need to
+ be set to false for ports used as network function inport or outport.
+- - "lsp_learn_mac": This can be used to control mac learning on the port.
++ - "lsp_learn_fdb": This can be used to control mac learning on the port.
+ - "receive_multicast": This can used to control whether multicast packets
+ can be received by the port.
+ NOTE:
+diff --git a/TODO.rst b/TODO.rst
+index 75bd2d13b0..17ab5f7350 100644
+--- a/TODO.rst
++++ b/TODO.rst
+@@ -156,9 +156,6 @@ OVN To-do List
+ Otherwise we could try to add duplicated Learned_Routes and the ovnsb
+ commit would fail.
+
+- * Add support for EVPN L3, that involves MAC Binding learning and
+- advertisement.
+-
+ * Datapath sync nodes
+
+ * Migrate data stored in the ovn\_datapath structure to
+diff --git a/controller/automake.mk b/controller/automake.mk
+index d6809df106..f8aa77b745 100644
+--- a/controller/automake.mk
++++ b/controller/automake.mk
+@@ -16,6 +16,8 @@ controller_ovn_controller_SOURCES = \
+ controller/evpn-binding.h \
+ controller/evpn-fdb.c \
+ controller/evpn-fdb.h \
++ controller/evpn-mac-binding-sync.c \
++ controller/evpn-mac-binding-sync.h \
+ controller/ha-chassis.c \
+ controller/ha-chassis.h \
+ controller/if-status.c \
+diff --git a/controller/encaps.c b/controller/encaps.c
+index 081fbe6716..506b5fd622 100644
+--- a/controller/encaps.c
++++ b/controller/encaps.c
+@@ -39,6 +39,7 @@ encaps_register_ovs_idl(struct ovsdb_idl *ovs_idl)
+ ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_name);
+ ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_interfaces);
+ ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_external_ids);
++ ovsdb_idl_add_column(ovs_idl, &ovsrec_port_col_other_config);
+ ovsdb_idl_add_table(ovs_idl, &ovsrec_table_interface);
+ ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_name);
+ ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_type);
+@@ -63,6 +64,7 @@ struct tunnel_ctx {
+ const struct ovsrec_open_vswitch_table *ovs_table;
+ const struct ovsrec_bridge *br_int;
+ const struct sbrec_chassis *this_chassis;
++ bool is_ha_chassis_member;
+ };
+
+ struct tunnel_node {
+@@ -284,9 +286,9 @@ tunnel_add(struct tunnel_ctx *tc,
+ }
-+ struct eth_addr mac = eth_addr_zero;
+ /* If there's an existing tunnel record that does not need any change,
+- * keep it. Otherwise, create a new record (if there was an existing
+- * record, the new record will supplant it and encaps_run() will delete
+- * it). */
++ * (except maybe the transient flag) keep it. Otherwise, create a new
++ * record (if there was an existing record, the new record will
++ * supplant it and encaps_run() will delete it). */
+ struct tunnel_node *tunnel = shash_find_data(&tc->tunnel,
+ tunnel_entry_id);
+ bool old_id_format = false;
+@@ -294,10 +296,18 @@ tunnel_add(struct tunnel_ctx *tc,
+ tunnel = shash_find_data(&tc->tunnel, tunnel_entry_id_old);
+ old_id_format = true;
+ }
++
+ if (tunnel
+ && tunnel->port->n_interfaces == 1
+ && !strcmp(tunnel->port->interfaces[0]->type, encap->type)
+ && smap_equal(&tunnel->port->interfaces[0]->options, &options)) {
++ if (tc->is_ha_chassis_member) {
++ ovsrec_port_update_other_config_setkey(tunnel->port, "transient",
++ "true");
++ } else if (smap_get(&tunnel->port->other_config, "transient")) {
++ ovsrec_port_update_other_config_delkey(tunnel->port, "transient");
++ }
+
- struct mac_binding *mb = mac_binding_find(recent_mbs, &bp->mb_data);
- if (mb) {
- mac = mb->data.mac;
-@@ -634,13 +636,46 @@ buffered_packets_ctx_run(struct buffered_packets_ctx *ctx,
- sbrec_mb_by_lport_ip);
- /* Schedule next lookup even if we found the MAC address,
- * if the address was found this struct will be deleted anyway. */
+ if (old_id_format) {
+ /* We must be upgrading from an older version. We can reuse the
+ * existing tunnel, but needs to update the tunnel's ID to the new
+@@ -338,6 +348,13 @@ tunnel_add(struct tunnel_ctx *tc,
+ const struct smap id = SMAP_CONST1(&id, OVN_TUNNEL_ID, tunnel_entry_id);
+ ovsrec_port_set_external_ids(port, &id);
+
++ if (tc->is_ha_chassis_member) {
++ const struct smap port_other_config = SMAP_CONST1(&port_other_config,
++ "transient",
++ "true");
++ ovsrec_port_set_other_config(port, &port_other_config);
++ }
+
- bp->lookup_at_ms = now + BUFFERED_PACKETS_LOOKUP_MS;
+ ovsrec_bridge_update_ports_addvalue(tc->br_int, port);
+
+ sset_add_and_free(&tc->port_names, port_name);
+@@ -726,7 +743,8 @@ encaps_run(struct ovsdb_idl_txn *ovs_idl_txn,
+ const struct sbrec_sb_global *sbg,
+ const struct ovsrec_open_vswitch_table *ovs_table,
+ const struct sset *transport_zones,
+- const struct ovsrec_bridge_table *bridge_table)
++ const struct ovsrec_bridge_table *bridge_table,
++ bool is_ha_chassis_member)
+ {
+ if (!ovs_idl_txn || !ovnsb_idl_txn || !br_int) {
+ return;
+@@ -773,6 +791,7 @@ encaps_run(struct ovsdb_idl_txn *ovs_idl_txn,
+ .br_int = br_int,
+ .this_chassis = this_chassis,
+ .ovs_table = ovs_table,
++ .is_ha_chassis_member = is_ha_chassis_member,
+ };
+
+ tc.ovs_txn = ovs_idl_txn;
+diff --git a/controller/encaps.h b/controller/encaps.h
+index 0257d08c13..f29a128eba 100644
+--- a/controller/encaps.h
++++ b/controller/encaps.h
+@@ -49,7 +49,8 @@ void encaps_run(struct ovsdb_idl_txn *ovs_idl_txn,
+ const struct sbrec_sb_global *,
+ const struct ovsrec_open_vswitch_table *,
+ const struct sset *transport_zones,
+- const struct ovsrec_bridge_table *bridge_table);
++ const struct ovsrec_bridge_table *bridge_table,
++ bool is_ha_chassis_member);
+
+ bool is_flow_based_tunnels_enabled(
+ const struct ovsrec_open_vswitch_table *ovs_table,
+diff --git a/controller/evpn-arp.c b/controller/evpn-arp.c
+index f4c3514f4e..f5b364cc4b 100644
+--- a/controller/evpn-arp.c
++++ b/controller/evpn-arp.c
+@@ -76,17 +76,6 @@ evpn_arp_run(const struct evpn_arp_ctx_in *arp_ctx_in,
+ updated = true;
+ }
+
+- enum neigh_of_rule_prio priority =
+- smap_get_bool(&arp->ldp->datapath->external_ids,
+- "dynamic-routing-arp-prefer-local",
+- false)
+- ? NEIGH_OF_EVPN_MAC_BINDING_LOW_PRIO
+- : NEIGH_OF_EVPN_MAC_BINDING_HIGH_PRIO;
+- if (arp->priority != priority) {
+- arp->priority = priority;
+- updated = true;
+- }
+-
+ if (updated) {
+ hmapx_add(arp_ctx_out->updated_arps, arp);
}
+diff --git a/controller/evpn-arp.h b/controller/evpn-arp.h
+index 7f3a4c0e44..c6de812d18 100644
+--- a/controller/evpn-arp.h
++++ b/controller/evpn-arp.h
+@@ -53,8 +53,6 @@ struct evpn_arp {
+ uint32_t vni;
+ /* Logical datapath of the switch this was learned on. */
+ const struct local_datapath *ldp;
+- /* Priority to use for this ARP entry at OpenFlow level. */
+- enum neigh_of_rule_prio priority;
+ };
-- if (eth_addr_is_zero(mac)) {
-+ if (!eth_addr_is_zero(mac)) {
-+ atomic_store(&bp->resolved_mac, eth_addr_to_uint64(mac));
-+ updated = true;
+ void evpn_arp_run(const struct evpn_arp_ctx_in *, struct evpn_arp_ctx_out *);
+diff --git a/controller/evpn-mac-binding-sync.c b/controller/evpn-mac-binding-sync.c
+new file mode 100644
+index 0000000000..be62c0f9a6
+--- /dev/null
++++ b/controller/evpn-mac-binding-sync.c
+@@ -0,0 +1,245 @@
++/* Copyright (c) 2026, Red Hat, Inc.
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at:
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++#include <config.h>
++
++#include "openvswitch/poll-loop.h"
++#include "packets.h"
++#include "timeval.h"
++
++#include "evpn-arp.h"
++#include "evpn-mac-binding-sync.h"
++#include "lib/mac-binding-index.h"
++#include "local_data.h"
++#include "mac-cache.h"
++#include "ovn-sb-idl.h"
++#include "ovn-util.h"
++#include "vec.h"
++
++/* Hash a (logical_port, ip) pair. */
++static uint32_t
++evpn_mb_hash(const char *logical_port, const char *ip)
++{
++ uint32_t hash = hash_string(logical_port, 0);
++ return hash_string(ip, hash);
++}
++
++static struct evpn_mb_synced_entry *
++evpn_mb_synced_find(const struct hmap *synced, const char *logical_port,
++ const char *ip)
++{
++ uint32_t hash = evpn_mb_hash(logical_port, ip);
++
++ struct evpn_mb_synced_entry *entry;
++ HMAP_FOR_EACH_WITH_HASH (entry, hmap_node, hash, synced) {
++ if (!strcmp(entry->logical_port, logical_port) &&
++ !strcmp(entry->ip, ip)) {
++ return entry;
+ }
+ }
+
-+ ds_destroy(&ip);
++ return NULL;
++}
++
++static struct evpn_mb_synced_entry *
++evpn_mb_synced_add(struct hmap *synced, const char *logical_port,
++ const char *ip)
++{
++ struct evpn_mb_synced_entry *entry = xmalloc(sizeof *entry);
++ *entry = (struct evpn_mb_synced_entry) {
++ .logical_port = xstrdup(logical_port),
++ .ip = xstrdup(ip),
++ };
++ hmap_insert(synced, &entry->hmap_node, evpn_mb_hash(logical_port, ip));
++ return entry;
++}
++
++static void
++evpn_mb_synced_remove(struct hmap *synced, struct evpn_mb_synced_entry *entry)
++{
++ hmap_remove(synced, &entry->hmap_node);
++ free(entry->logical_port);
++ free(entry->ip);
++ free(entry);
++}
++
++/* Schedule the waker to fire after 'delay_ms' milliseconds. */
++static void
++evpn_mb_sync_waker_schedule(struct evpn_mb_sync_waker *waker,
++ int64_t delay_ms)
++{
++ if (delay_ms < INT64_MAX) {
++ waker->should_schedule = true;
++ waker->next_wake_msec = time_msec() + delay_ms;
++ poll_timer_wait_until(waker->next_wake_msec);
++ }
++}
++
++/* Sync a single EVPN ARP entry to the SB MAC_Binding table for one
++ * router port. Caller must ensure 'ovnsb_idl_txn' is valid.
++ * Returns the remaining time (in ms) until the next timestamp
++ * refresh is needed, or INT64_MAX if none. */
++static int64_t
++sync_evpn_mb_for_router_port(
++ struct ovsdb_idl_txn *ovnsb_idl_txn,
++ struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
++ const struct sbrec_mac_binding_table *mb_table,
++ const struct sbrec_port_binding *router_pb,
++ const struct evpn_arp *arp,
++ struct mac_cache_data *mac_cache_data,
++ struct ed_type_evpn_mac_binding_sync *data,
++ long long timewall_now)
++{
++ char *ip_s = normalize_v46(&arp->ip);
++ struct evpn_mb_synced_entry *existing =
++ evpn_mb_synced_find(&data->synced_entries,
++ router_pb->logical_port, ip_s);
++
++ const struct sbrec_mac_binding *sb_mb = existing
++ ? sbrec_mac_binding_table_get_for_uuid(mb_table, &existing->mb_uuid)
++ : NULL;
++
++ /* Insert or update the SB MAC_Binding row. */
++ const struct sbrec_mac_binding *b =
++ mac_binding_add_to_sb(ovnsb_idl_txn,
++ sbrec_mac_binding_by_lport_ip,
++ router_pb->logical_port,
++ router_pb->datapath,
++ arp->mac, ip_s, false,
++ sb_mb);
++
++ if (!existing) {
++ existing = evpn_mb_synced_add(&data->synced_entries,
++ router_pb->logical_port, ip_s);
++ }
++
++ free(ip_s);
++ existing->mb_uuid = b->header_.uuid;
++ existing->stale = false;
+
-+ return updated;
++ /* Refresh timestamp to prevent aging. */
++ struct mac_cache_threshold *threshold =
++ mac_cache_threshold_find(mac_cache_data,
++ router_pb->datapath->tunnel_key);
++ if (!threshold) {
++ return INT64_MAX;
++ }
++
++ uint64_t since_updated = timewall_now - b->timestamp;
++ if (since_updated >= threshold->cooldown_period) {
++ sbrec_mac_binding_set_timestamp(b, timewall_now);
++ return threshold->cooldown_period;
++ }
++
++ return threshold->cooldown_period - since_updated;
+}
+
+void
-+buffered_packets_run(struct cmap *bp_map, struct vector *rpd)
++evpn_mac_binding_sync_run(
++ struct ovsdb_idl_txn *ovnsb_idl_txn,
++ struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
++ const struct sbrec_mac_binding_table *mb_table,
++ const struct hmap *local_datapaths,
++ const struct hmap *evpn_arps,
++ struct mac_cache_data *mac_cache_data,
++ struct ed_type_evpn_mac_binding_sync *data,
++ struct evpn_mb_sync_waker *waker)
+{
-+ long long now = time_msec();
++ if (!ovnsb_idl_txn) {
++ data->sb_changes_pending = true;
++ return;
++ }
++
++ long long timewall_now = time_wall_msec();
++ int64_t min_next_refresh_ms = INT64_MAX;
+
-+ struct buffered_packets *bp;
-+ CMAP_FOR_EACH (bp, cmap_node, bp_map) {
-+ uint32_t hash = mac_binding_data_hash(&bp->mb_data);
++ /* Mark all synced entries as stale. SB row pointers are resolved
++ * lazily by UUID only where needed (insert/update, timestamp
++ * refresh, delete), avoiding a per-entry index lookup here. */
++ struct evpn_mb_synced_entry *synced_entry;
++ HMAP_FOR_EACH (synced_entry, hmap_node, &data->synced_entries) {
++ synced_entry->stale = true;
++ }
++
++ /* Walk current EVPN ARPs and sync to SB. */
++ const struct evpn_arp *arp;
++ HMAP_FOR_EACH (arp, hmap_node, evpn_arps) {
++ const struct peer_ports *peers;
++ VECTOR_FOR_EACH_PTR (&arp->ldp->peer_ports, peers) {
++ const struct sbrec_port_binding *remote_pb = peers->remote;
++ struct local_datapath *peer_ld =
++ get_local_datapath(local_datapaths,
++ remote_pb->datapath->tunnel_key);
++ if (!peer_ld || peer_ld->is_switch) {
++ continue;
++ }
++
++ uuidset_insert(&data->lsp_peers, &peers->local->header_.uuid);
++
++ int64_t remaining =
++ sync_evpn_mb_for_router_port(ovnsb_idl_txn,
++ sbrec_mac_binding_by_lport_ip,
++ mb_table,
++ remote_pb, arp,
++ mac_cache_data, data,
++ timewall_now);
++ if (remaining < min_next_refresh_ms) {
++ min_next_refresh_ms = remaining;
++ }
++ }
++ }
+
-+ /* Remove expired buffered packets. */
-+ if (now > bp->expire_at_ms) {
-+ cmap_remove(bp_map, &bp->cmap_node, hash);
-+ ovsrcu_postpone(buffered_packets_free, bp);
++ /* Delete stale entries from SB. */
++ HMAP_FOR_EACH_SAFE (synced_entry, hmap_node, &data->synced_entries) {
++ if (!synced_entry->stale) {
+ continue;
+ }
+
-+ uint64_t mac64;
-+ atomic_read(&bp->resolved_mac, &mac64);
-+ if (!mac64) {
- continue;
- }
-
-+ struct eth_addr mac;
-+ eth_addr_from_uint64(mac64, &mac);
++ const struct sbrec_mac_binding *sb_mb =
++ sbrec_mac_binding_table_get_for_uuid(
++ mb_table, &synced_entry->mb_uuid);
++ if (sb_mb) {
++ sbrec_mac_binding_delete(sb_mb);
++ }
+
- struct bp_packet_data *pd;
- VECTOR_FOR_EACH_PTR (&bp->queue, pd) {
- struct dp_packet packet;
-@@ -650,45 +685,25 @@ buffered_packets_ctx_run(struct buffered_packets_ctx *ctx,
- eth->eth_dst = mac;
- }
-
-- vector_push_array(&ctx->ready_packets_data,
-- vector_get_array(&bp->queue),
-+ vector_push_array(rpd, vector_get_array(&bp->queue),
- vector_len(&bp->queue));
- vector_clear(&bp->queue);
-- buffered_packets_remove(ctx, bp);
-- }
--
-- ds_destroy(&ip);
--}
--
--bool
--buffered_packets_ctx_is_ready_to_send(struct buffered_packets_ctx *ctx) {
-- return !vector_is_empty(&ctx->ready_packets_data);
--}
--
--bool
--buffered_packets_ctx_has_packets(struct buffered_packets_ctx *ctx) {
-- return !hmap_is_empty(&ctx->buffered_packets);
--}
-
--void
--buffered_packets_ctx_init(struct buffered_packets_ctx *ctx) {
-- ctx->ready_packets_data = VECTOR_EMPTY_INITIALIZER(struct bp_packet_data);
-- hmap_init(&ctx->buffered_packets);
-+ cmap_remove(bp_map, &bp->cmap_node, hash);
-+ ovsrcu_postpone(buffered_packets_free, bp);
++ evpn_mb_synced_remove(&data->synced_entries, synced_entry);
+ }
- }
-
- void
--buffered_packets_ctx_destroy(struct buffered_packets_ctx *ctx) {
-- struct bp_packet_data *pd;
-- VECTOR_FOR_EACH_PTR (&ctx->ready_packets_data, pd) {
-- bp_packet_data_destroy(pd);
-- }
-- vector_destroy(&ctx->ready_packets_data);
--
-+buffered_packets_map_destroy(struct cmap *bp_map) {
- struct buffered_packets *bp;
-- HMAP_FOR_EACH_SAFE (bp, hmap_node, &ctx->buffered_packets) {
-- buffered_packets_remove(ctx, bp);
-+ CMAP_FOR_EACH (bp, cmap_node, bp_map) {
-+ cmap_remove(bp_map, &bp->cmap_node,
-+ mac_binding_data_hash(&bp->mb_data));
-+ ovsrcu_postpone(buffered_packets_free, bp);
- }
-- hmap_destroy(&ctx->buffered_packets);
+
-+ cmap_destroy(bp_map);
++ /* Schedule the waker for the next timestamp refresh. */
++ if (min_next_refresh_ms < INT64_MAX) {
++ evpn_mb_sync_waker_schedule(waker, min_next_refresh_ms);
++ }
++}
++
++void
++evpn_mac_binding_sync_init(struct ed_type_evpn_mac_binding_sync *data)
++{
++ hmap_init(&data->synced_entries);
++ uuidset_init(&data->lsp_peers);
++ data->sb_changes_pending = false;
++}
++
++void
++evpn_mac_binding_sync_cleanup(struct ed_type_evpn_mac_binding_sync *data)
++{
++ struct evpn_mb_synced_entry *entry;
++ HMAP_FOR_EACH_POP (entry, hmap_node, &data->synced_entries) {
++ free(entry->logical_port);
++ free(entry->ip);
++ free(entry);
++ }
++ hmap_destroy(&data->synced_entries);
++ uuidset_destroy(&data->lsp_peers);
++}
+diff --git a/controller/evpn-mac-binding-sync.h b/controller/evpn-mac-binding-sync.h
+new file mode 100644
+index 0000000000..249059d0e7
+--- /dev/null
++++ b/controller/evpn-mac-binding-sync.h
+@@ -0,0 +1,72 @@
++/* Copyright (c) 2026, Red Hat, Inc.
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at:
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++#ifndef EVPN_MAC_BINDING_SYNC_H
++#define EVPN_MAC_BINDING_SYNC_H 1
++
++#include "openvswitch/hmap.h"
++#include "openvswitch/uuid.h"
++#include "uuidset.h"
++
++struct mac_cache_data;
++struct ovsdb_idl_index;
++struct ovsdb_idl_txn;
++struct sbrec_mac_binding_table;
++
++/* Tracks a MAC_Binding row that was written to SB by the EVPN sync. */
++struct evpn_mb_synced_entry {
++ struct hmap_node hmap_node;
++ char *logical_port; /* Router port name. */
++ char *ip; /* Normalized IP string. */
++ struct uuid mb_uuid; /* UUID of the SB MAC_Binding row.
++ * All-zeros when not yet synced. */
++ bool stale; /* Marked true at start of sync,
++ * cleared when still desired. */
++};
++
++/* Persistent state for the en_evpn_mac_binding_sync engine node. */
++struct ed_type_evpn_mac_binding_sync {
++ /* Contains 'struct evpn_mb_synced_entry'. Tracks which
++ * (logical_port, ip) pairs we have written to SB. */
++ struct hmap synced_entries;
++
++ /* Contains LSP UUIDs belonging to synced entries peers. */
++ struct uuidset lsp_peers;
++
++ /* True when an SB write was skipped because ovnsb_idl_txn was NULL. */
++ bool sb_changes_pending;
++};
++
++/* Timer that periodically wakes the sync node so it can refresh
++ * timestamps on SB MAC_Binding rows it owns. */
++struct evpn_mb_sync_waker {
++ bool should_schedule; /* Whether a wake is pending. */
++ long long next_wake_msec; /* Absolute time of next wake. */
++};
++
++void evpn_mac_binding_sync_run(
++ struct ovsdb_idl_txn *ovnsb_idl_txn,
++ struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
++ const struct sbrec_mac_binding_table *mb_table,
++ const struct hmap *local_datapaths,
++ const struct hmap *evpn_arps,
++ struct mac_cache_data *mac_cache_data,
++ struct ed_type_evpn_mac_binding_sync *data,
++ struct evpn_mb_sync_waker *waker);
++
++void evpn_mac_binding_sync_init(struct ed_type_evpn_mac_binding_sync *);
++void evpn_mac_binding_sync_cleanup(struct ed_type_evpn_mac_binding_sync *);
++
++#endif /* EVPN_MAC_BINDING_SYNC_H */
+diff --git a/controller/garp_rarp.c b/controller/garp_rarp.c
+index 1cf415a9fa..9de8345c7c 100644
+--- a/controller/garp_rarp.c
++++ b/controller/garp_rarp.c
+@@ -365,7 +365,7 @@ send_garp_locally(const struct garp_rarp_ctx_in *r_ctx_in,
+ mac_binding_add_to_sb(r_ctx_in->ovnsb_idl_txn,
+ r_ctx_in->sbrec_mac_binding_by_lport_ip,
+ remote->logical_port, remote->datapath,
+- ea, ds_cstr(&ip_s), update_only);
++ ea, ds_cstr(&ip_s), update_only, NULL);
+ ds_destroy(&ip_s);
+ }
}
+diff --git a/controller/local_data.c b/controller/local_data.c
+index af6c75b400..830344352b 100644
+--- a/controller/local_data.c
++++ b/controller/local_data.c
+@@ -858,7 +858,7 @@ select_default_encap_ip(const struct sbrec_chassis *chassis,
+ default_ip = encap->ip;
+ }
+
+- if (smap_get_bool(&encap->options, "default-encap-ip", false)) {
++ if (smap_get_bool(&encap->options, "is_default", false)) {
+ default_ip = encap->ip;
+ break; /* Found explicit default */
+ }
+diff --git a/controller/mac-cache.c b/controller/mac-cache.c
+index c996fd6b9a..359d4c18fb 100644
+--- a/controller/mac-cache.c
++++ b/controller/mac-cache.c
+@@ -34,6 +34,7 @@ VLOG_DEFINE_THIS_MODULE(mac_cache);
+ #define BUFFER_QUEUE_DEPTH 4
+ #define BUFFERED_PACKETS_TIMEOUT_MS 10000
+ #define BUFFERED_PACKETS_LOOKUP_MS 100
++#define PROBE_MULICAST_THRESHOLD 2
static uint32_t
-@@ -771,12 +786,12 @@ mac_cache_update_req_delay(struct hmap *thresholds, uint64_t *req_delay)
+ mac_binding_data_hash(const struct mac_binding_data *mb_data);
+@@ -178,6 +179,7 @@ mac_binding_add(struct hmap *map, struct mac_binding_data mb_data,
+ mb->data = mb_data;
+ mb->sbrec = smb;
+ mb->timestamp = timestamp;
++ mb->arp_attempts = 0;
+ mac_binding_update_log("Added", &mb_data, false, NULL, 0, 0);
}
- static struct buffered_packets *
--buffered_packets_find(struct buffered_packets_ctx *ctx,
-+buffered_packets_find(struct cmap *bp_map,
- const struct mac_binding_data *mb_data) {
- uint32_t hash = mac_binding_data_hash(mb_data);
-
- struct buffered_packets *bp;
-- HMAP_FOR_EACH_WITH_HASH (bp, hmap_node, hash, &ctx->buffered_packets) {
-+ CMAP_FOR_EACH_WITH_HASH (bp, cmap_node, hash, bp_map) {
- if (mac_binding_data_equals(&bp->mb_data, mb_data)) {
- return bp;
- }
-@@ -786,14 +801,12 @@ buffered_packets_find(struct buffered_packets_ctx *ctx,
- }
+@@ -399,10 +401,9 @@ mac_binding_update_log(const char *action,
- static void
--buffered_packets_remove(struct buffered_packets_ctx *ctx,
-- struct buffered_packets *bp) {
-+buffered_packets_free(struct buffered_packets *bp) {
- struct bp_packet_data *pd;
- VECTOR_FOR_EACH_PTR (&bp->queue, pd) {
- bp_packet_data_destroy(pd);
- }
+ void
+ mac_binding_stats_run(struct vector *stats_vec, uint64_t *req_delay,
+- void *data)
++ void *data, long long timewall_now)
+ {
+ struct mac_cache_data *cache_data = data;
+- long long timewall_now = time_wall_msec();
-- hmap_remove(&ctx->buffered_packets, &bp->hmap_node);
- vector_destroy(&bp->queue);
- free(bp);
+ struct mac_cache_stats *stats;
+ VECTOR_FOR_EACH_PTR (stats_vec, stats) {
+@@ -495,10 +496,10 @@ fdb_update_log(const char *action,
}
-@@ -920,13 +933,29 @@ mac_binding_probe_stats_run(struct vector *stats_vec, uint64_t *req_delay,
- continue;
- }
-- bool is_mb_v4 = IN6_IS_ADDR_V4MAPPED(&mb->data.ip);
-- if ((is_mb_v4 && laddr.n_ipv4_addrs)
-- || (!is_mb_v4 && laddr.n_ipv6_addrs)) {
-- struct in6_addr local =
-- is_mb_v4 ? in6_addr_mapped_ipv4(laddr.ipv4_addrs[0].addr)
-- : laddr.ipv6_addrs[0].addr;
-+ struct in6_addr local = in6addr_any;
-+ if (IN6_IS_ADDR_V4MAPPED(&mb->data.ip)) {
-+ ovs_be32 ip4 = in6_addr_get_mapped_ipv4(&mb->data.ip);
-+ for (size_t i = 0; i < laddr.n_ipv4_addrs; i++) {
-+ struct ipv4_netaddr address = laddr.ipv4_addrs[i];
-+ if (address.network == (ip4 & address.mask)) {
-+ local = in6_addr_mapped_ipv4(address.addr);
-+ break;
-+ }
-+ }
-+ } else {
-+ for (size_t i = 0; i < laddr.n_ipv6_addrs; i++) {
-+ struct ipv6_netaddr address = laddr.ipv6_addrs[i];
-+ struct in6_addr neigh_prefix =
-+ ipv6_addr_bitand(&mb->data.ip, &address.mask);
-+ if (ipv6_addr_equals(&address.network, &neigh_prefix)) {
-+ local = address.addr;
-+ break;
-+ }
-+ }
-+ }
+ void
+-fdb_stats_run(struct vector *stats_vec, uint64_t *req_delay, void *data)
++fdb_stats_run(struct vector *stats_vec, uint64_t *req_delay, void *data,
++ long long timewall_now)
+ {
+ struct mac_cache_data *cache_data = data;
+- long long timewall_now = time_wall_msec();
+
+ struct mac_cache_stats *stats;
+ VECTOR_FOR_EACH_PTR (stats_vec, stats) {
+@@ -877,9 +878,8 @@ mac_binding_probe_stats_process_flow_stats(
+
+ void
+ mac_binding_probe_stats_run(struct vector *stats_vec, uint64_t *req_delay,
+- void *data)
++ void *data, long long timewall_now)
+ {
+- long long timewall_now = time_wall_msec();
+ struct mac_binding_probe_data *probe_data = data;
+ struct mac_cache_data *cache_data = probe_data->cache_data;
+
+@@ -910,6 +910,7 @@ mac_binding_probe_stats_run(struct vector *stats_vec, uint64_t *req_delay,
+ "Not sending ARP/ND request for recently updated",
+ &mb->data, true, threshold, stats->idle_age_ms,
+ since_updated_ms);
++ mb->arp_attempts = 0;
+ continue;
+ }
+
+@@ -956,6 +957,11 @@ mac_binding_probe_stats_run(struct vector *stats_vec, uint64_t *req_delay,
+ }
-+ if (!ipv6_addr_equals(&local, &in6addr_any)) {
+ if (!ipv6_addr_equals(&local, &in6addr_any)) {
++ struct eth_addr eth_dst =
++ mb->arp_attempts < PROBE_MULICAST_THRESHOLD
++ ? mb->data.mac
++ : eth_addr_zero;
++
mac_binding_update_log("Sending ARP/ND request for active",
&mb->data, true, threshold,
stats->idle_age_ms, since_updated_ms);
+@@ -963,8 +969,10 @@ mac_binding_probe_stats_run(struct vector *stats_vec, uint64_t *req_delay,
+ send_self_originated_neigh_packet(probe_data->swconn,
+ sbrec->datapath->tunnel_key,
+ pb->tunnel_key, laddr.ea,
+- &local, &mb->data.ip,
++ eth_dst, &local,
++ &mb->data.ip,
+ OFTABLE_LOCAL_OUTPUT);
++ mb->arp_attempts++;
+ }
+
+ destroy_lport_addresses(&laddr);
diff --git a/controller/mac-cache.h b/controller/mac-cache.h
-index 7edb129d75..365219d334 100644
+index 365219d334..bf9afaf3ad 100644
--- a/controller/mac-cache.h
+++ b/controller/mac-cache.h
-@@ -18,9 +18,9 @@
+@@ -78,6 +78,8 @@ struct mac_binding {
+ const struct sbrec_mac_binding *sbrec;
+ /* User specified timestamp (in ms) */
+ long long timestamp;
++ /* Number of re-ARP attempts for given entry. */
++ size_t arp_attempts;
+ };
- #include <stdint.h>
+ struct fdb_data {
+@@ -199,13 +201,14 @@ mac_binding_stats_process_flow_stats(struct vector *stats_vec,
+ struct ofputil_flow_stats *ofp_stats);
-+#include "cmap.h"
- #include "dp-packet.h"
- #include "openvswitch/hmap.h"
--#include "openvswitch/hmap.h"
- #include "openvswitch/list.h"
- #include "openvswitch/ofpbuf.h"
- #include "openvswitch/ofp-flow.h"
-@@ -115,25 +115,25 @@ struct bp_packet_data {
- };
+ void mac_binding_stats_run(struct vector *stats_vec, uint64_t *req_delay,
+- void *data);
++ void *data, long long timewall_now);
+
+ /* FDB stat processing. */
+ void fdb_stats_process_flow_stats(struct vector *stats_vec,
+ struct ofputil_flow_stats *ofp_stats);
- struct buffered_packets {
-- struct hmap_node hmap_node;
-+ struct cmap_node cmap_node;
+-void fdb_stats_run(struct vector *stats_vec, uint64_t *req_delay, void *data);
++void fdb_stats_run(struct vector *stats_vec, uint64_t *req_delay, void *data,
++ long long timewall_now);
-- struct mac_binding_data mb_data;
-+ struct mac_binding_data mb_data; /* Immutable after insert. */
+ /* Packet buffering. */
+ void bp_packet_data_destroy(struct bp_packet_data *pd);
+@@ -234,6 +237,6 @@ void mac_binding_probe_stats_process_flow_stats(
+ struct ofputil_flow_stats *ofp_stats);
-- /* Queue of packet_data associated with this struct. */
-+ /* Queue of packet_data associated with this struct.
-+ * Handler thread only. */
- struct vector queue;
+ void mac_binding_probe_stats_run(struct vector *stats_vec, uint64_t *req_delay,
+- void *data);
++ void *data, long long timewall_now);
-- /* Timestamp in ms when the buffered packet should expire. */
-+ /* Timestamp in ms when the buffered packet should expire.
-+ * Handler thread only. */
- long long int expire_at_ms;
+ #endif /* controller/mac-cache.h */
+diff --git a/controller/neighbor-of.h b/controller/neighbor-of.h
+index 3d0ba6f3a2..d91074a1ed 100644
+--- a/controller/neighbor-of.h
++++ b/controller/neighbor-of.h
+@@ -21,21 +21,19 @@
+ #include "ofctrl.h"
-- /* Timestamp in ms when the buffered packet should do full SB lookup.*/
-- long long int lookup_at_ms;
--};
-+ /* Resolved MAC address packed as uint64. 0 means unresolved.
-+ * Written by main thread, read by handler thread. */
-+ atomic_uint64_t resolved_mac;
-
--struct buffered_packets_ctx {
-- /* Map of all buffered packets waiting for the MAC address. */
-- struct hmap buffered_packets;
-- /* List of packet data that are ready to be sent. */
-- struct vector ready_packets_data;
-+ /* Timestamp in ms when the buffered packet should do full SB lookup.
-+ * Main thread only. */
-+ long long int lookup_at_ms;
+ /* Priorities of ovn-controller generated flows for various types of MAC
+- * Bindings in different situations. Valid preference orders, based on
+- * the "dynamic-routing-arp-prefer-local" logical switch config and the
+- * SB.Static_MAC_Binding.override_dynamic_mac value are:
++ * Bindings. EVPN-learned MAC bindings are written to the SB MAC_Binding
++ * table and consumed through the normal lflow pipeline at the same
++ * priority as dynamic entries. The EVPN ARP lookup side table (table 113)
++ * uses the dynamic priority as well. Valid preference orders based on
++ * the SB.Static_MAC_Binding.override_dynamic_mac value are:
+ *
+- * - EVPN-learned < static-mac-binding < dynamic-mac-binding
+- * - EVPN-learned < dynamic-mac-binding < static-mac-binding
+- * - static-mac-binding < dynamic-mac-binding < EVPN-learned
+- * - dynamic-mac-binding < static-mac-binding < EVPN-learned
++ * - static-mac-binding < dynamic-mac-binding (+ EVPN)
++ * - dynamic-mac-binding (+ EVPN) < static-mac-binding
+ */
+ enum neigh_of_rule_prio {
+- NEIGH_OF_EVPN_MAC_BINDING_LOW_PRIO = 20,
+ NEIGH_OF_STATIC_MAC_BINDING_LOW_PRIO = 50,
+ NEIGH_OF_DYNAMIC_MAC_BINDING_PRIO = 100,
+ NEIGH_OF_STATIC_MAC_BINDING_HIGH_PRIO = 150,
+- NEIGH_OF_EVPN_MAC_BINDING_HIGH_PRIO = 200,
};
- /* Thresholds. */
-@@ -211,27 +211,23 @@ void fdb_stats_run(struct vector *stats_vec, uint64_t *req_delay, void *data);
- void bp_packet_data_destroy(struct bp_packet_data *pd);
+ void
+diff --git a/controller/neighbor.c b/controller/neighbor.c
+index 57df1e90c9..70d2a50918 100644
+--- a/controller/neighbor.c
++++ b/controller/neighbor.c
+@@ -46,6 +46,9 @@ neighbor_interface_monitor_alloc(enum neighbor_family family,
+ static void neighbor_collect_mac_to_advertise(
+ const struct neighbor_ctx_in *, struct hmap *neighbors,
+ struct sset *advertised_pbs, const struct sbrec_datapath_binding *);
++static void neighbor_collect_fdb_to_advertise(
++ const struct neighbor_ctx_in *, struct hmap *neighbors,
++ struct hmapx *fdb_datapaths, struct local_datapath *);
+ static void neighbor_collect_ip_mac_to_advertise(
+ const struct neighbor_ctx_in *,
+ struct hmap *neighbors_v4, struct hmap *neighbors_v6,
+@@ -175,6 +178,10 @@ neighbor_run(struct neighbor_ctx_in *n_ctx_in,
+ &lo->announced_neighbors,
+ n_ctx_out->advertised_pbs,
+ ld->datapath);
++ neighbor_collect_fdb_to_advertise(n_ctx_in,
++ &lo->announced_neighbors,
++ n_ctx_out->fdb_datapaths,
++ ld);
+ }
+ if (nrm_mode_IP_is_set(mode) && br_v4 && br_v6) {
+ neighbor_collect_ip_mac_to_advertise(n_ctx_in,
+@@ -299,6 +306,56 @@ neighbor_collect_mac_to_advertise(const struct neighbor_ctx_in *n_ctx_in,
+ sbrec_port_binding_index_destroy_row(target);
+ }
- struct buffered_packets *
--buffered_packets_add(struct buffered_packets_ctx *ctx,
-+buffered_packets_add(struct cmap *bp_map,
- struct mac_binding_data mb_data);
-
- void buffered_packets_packet_data_enqueue(struct buffered_packets *bp,
- const struct ofputil_packet_in *pin,
- const struct ofpbuf *continuation);
-
--void buffered_packets_ctx_run(struct buffered_packets_ctx *ctx,
-- const struct hmap *recent_mbs,
-- struct ovsdb_idl_index *sbrec_pb_by_key,
-- struct ovsdb_idl_index *sbrec_dp_by_key,
-- struct ovsdb_idl_index *sbrec_pb_by_name,
-- struct ovsdb_idl_index *sbrec_mb_by_lport_ip);
--
--void buffered_packets_ctx_init(struct buffered_packets_ctx *ctx);
--
--void buffered_packets_ctx_destroy(struct buffered_packets_ctx *ctx);
-+bool buffered_packets_lookup_run(struct cmap *bp_map,
-+ const struct hmap *recent_mbs,
-+ struct ovsdb_idl_index *sbrec_pb_by_key,
-+ struct ovsdb_idl_index *sbrec_dp_by_key,
-+ struct ovsdb_idl_index *sbrec_pb_by_name,
-+ struct ovsdb_idl_index *sbrec_mb_by_lport_ip);
-
--bool buffered_packets_ctx_is_ready_to_send(struct buffered_packets_ctx *ctx);
-+void buffered_packets_run(struct cmap *bp_map, struct vector *rpd);
-
--bool buffered_packets_ctx_has_packets(struct buffered_packets_ctx *ctx);
-+void buffered_packets_map_destroy(struct cmap *bp_map);
-
- void mac_binding_probe_stats_process_flow_stats(
- struct vector *stats_vec,
++/* Walks the SB FDB entries for 'dp' and advertises each learned MAC as an
++ * EVPN Type-2 MAC-only neighbor (into 'neighbors'). Only FDB entries
++ * learned on VIF ports bound to this chassis or on localnet ports are
++ * included. The per-port 'dynamic-routing-advertise' option is honored. */
++static void
++neighbor_collect_fdb_to_advertise(const struct neighbor_ctx_in *n_ctx_in,
++ struct hmap *neighbors,
++ struct hmapx *fdb_datapaths,
++ struct local_datapath *ld)
++{
++ hmapx_add(fdb_datapaths, ld);
++
++ const struct sbrec_datapath_binding *dp = ld->datapath;
++ struct sbrec_fdb *fdb_target =
++ sbrec_fdb_index_init_row(n_ctx_in->sbrec_fdb_by_dp_key);
++ sbrec_fdb_index_set_dp_key(fdb_target, dp->tunnel_key);
++
++ const struct sbrec_fdb *fdb;
++ SBREC_FDB_FOR_EACH_EQUAL (fdb, fdb_target,
++ n_ctx_in->sbrec_fdb_by_dp_key) {
++ const struct sbrec_port_binding *pb =
++ lport_lookup_by_key_with_dp(n_ctx_in->sbrec_pb_by_key,
++ dp, fdb->port_key);
++ if (!pb) {
++ continue;
++ }
++
++ enum en_lport_type type = get_lport_type(pb);
++ if (type == LP_VIF &&
++ !lport_pb_is_chassis_resident(n_ctx_in->chassis, pb)) {
++ continue;
++ }
++
++ if (type != LP_VIF && type != LP_LOCALNET) {
++ continue;
++ }
++
++ struct eth_addr ea;
++ if (!eth_addr_from_string(fdb->mac, &ea)) {
++ continue;
++ }
++
++ if (!advertise_neigh_find(neighbors, ea, &in6addr_any)) {
++ advertise_neigh_add(neighbors, ea, in6addr_any);
++ }
++ }
++
++ sbrec_fdb_index_destroy_row(fdb_target);
++}
++
+ static void
+ neighbor_collect_ip_mac_to_advertise(
+ const struct neighbor_ctx_in *n_ctx_in,
+diff --git a/controller/neighbor.h b/controller/neighbor.h
+index e3adc87d19..cd158a0f17 100644
+--- a/controller/neighbor.h
++++ b/controller/neighbor.h
+@@ -21,6 +21,7 @@
+ #include <net/if.h>
+ #include <stdint.h>
+
++#include "hmapx.h"
+ #include "lib/sset.h"
+ #include "openvswitch/hmap.h"
+
+@@ -48,6 +49,10 @@ struct neighbor_ctx_in {
+ struct ovsdb_idl_index *sbrec_amb_by_dp;
+ /* Index for Port Binding by name. */
+ struct ovsdb_idl_index *sbrec_pb_by_name;
++ /* Index for Port Binding by datapath and tunnel key. */
++ struct ovsdb_idl_index *sbrec_pb_by_key;
++ /* Index for FDB by dp_key. */
++ struct ovsdb_idl_index *sbrec_fdb_by_dp_key;
+ const struct sbrec_chassis *chassis;
+ };
+
+@@ -56,6 +61,9 @@ struct neighbor_ctx_out {
+ struct vector *monitored_interfaces;
+ /* Contains set of port binding names that are currently advertised. */
+ struct sset *advertised_pbs;
++ /* Contains 'struct local_datapath' pointers for datapaths with FDB
++ * advertisement enabled. */
++ struct hmapx *fdb_datapaths;
+ };
+
+ enum neighbor_interface_type {
+diff --git a/controller/ofctrl.c b/controller/ofctrl.c
+index 6f6c6ed39c..e687cf9e27 100644
+--- a/controller/ofctrl.c
++++ b/controller/ofctrl.c
+@@ -3065,16 +3065,6 @@ ofctrl_put(struct ovn_desired_flow_table *lflow_table,
+ mem_stats.oflow_update_usage -= ofctrl_flow_update_size(fup);
+ ovs_list_remove(&fup->list_node);
+ free(fup);
+- } else if (req_cfg == fup->req_cfg) {
+- /* This ofctrl_flow_update is for the same configuration as
+- * 'req_cfg'. Probably, some change to the physical topology
+- * means that we had to revise the OpenFlow flow table even
+- * though the logical topology did not change. Update fp->xid,
+- * so that we don't send a notification that we're up-to-date
+- * until we're really caught up. */
+- VLOG_DBG("advanced xid target for req_cfg=%"PRId64, req_cfg);
+- fup->xid = xid_;
+- goto done;
+ } else {
+ break;
+ }
+@@ -3086,7 +3076,6 @@ ofctrl_put(struct ovn_desired_flow_table *lflow_table,
+ fup->xid = xid_;
+ fup->req_cfg = req_cfg;
+ mem_stats.oflow_update_usage += ofctrl_flow_update_size(fup);
+- done:;
+ } else if (!ovs_list_is_empty(&flow_updates)) {
+ /* Getting up-to-date with 'req_cfg' didn't require any extra flow
+ * table changes, so whenever we get up-to-date with the most recent
diff --git a/controller/ovn-controller.8.xml b/controller/ovn-controller.8.xml
-index 33281a4d66..57e7cf5dd2 100644
+index 57e7cf5dd2..4047919ede 100644
--- a/controller/ovn-controller.8.xml
+++ b/controller/ovn-controller.8.xml
-@@ -531,17 +531,6 @@
- 65535.
+@@ -866,6 +866,26 @@
+ <dd>
+ Reset <code>ovn-controller</code> engine counters.
</dd>
++
++ <dt><code>inc-engine/enable-stopwatch</code> [<var>node</var>]</dt>
++ <dd>
++ Enables the per-node and per-handler stopwatches in the
++ incremental processing engine. If <var>node</var> is specified,
++ only that node's stopwatches are enabled; otherwise all nodes
++ are affected. While enabled, every engine node recompute and
++ change handler invocation is timed and the results can be
++ viewed with the <code>stopwatch/show</code> command.
++ Stopwatches are disabled by default because they add measurable
++ CPU overhead.
++ </dd>
++
++ <dt><code>inc-engine/disable-stopwatch</code> [<var>node</var>]</dt>
++ <dd>
++ Disables the per-node and per-handler stopwatches in the
++ incremental processing engine. If <var>node</var> is specified,
++ only that node's stopwatches are disabled; otherwise all nodes
++ are affected. This is the default state.
++ </dd>
+ </dl>
+ </p>
-- <dt>
-- <code>external_ids:ovn-managed-flow-restore-wait</code> in the
-- <code>Open_vSwitch</code> table
-- </dt>
-- <dd>
-- When set to true, this key indicates that <code>ovn-controller</code>
-- has set the <code>other_config:flow-restore-wait</code> option.
-- The key is set when <code>ovn-controller</code> enables
-- flow-restore-wait and removed when it clears it.
-- </dd>
--
- <dt>
- <code>external_ids:ct-zone-*</code> in the <code>Bridge</code> table
- </dt>
diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
-index dcf67d789c..cdde157761 100644
+index cdde157761..542619aee5 100644
--- a/controller/ovn-controller.c
+++ b/controller/ovn-controller.c
-@@ -211,152 +211,6 @@ static char *get_file_system_id(void)
- free(filename);
- return ret;
- }
--
--/* Set/unset flow-restore-wait, and inc ovs next_cfg if false
-- * When set to true, also sets ovn-managed-flow-restore-wait to true to
-- * indicate ownership */
--static void
--set_flow_restore_wait(struct ovsdb_idl_txn *ovs_idl_txn,
-- const struct ovsrec_open_vswitch *cfg,
-- const struct smap *other_config,
-- const bool val, bool ovn_managed)
--{
-- struct smap new_config;
-- smap_clone(&new_config, other_config);
-- smap_replace(&new_config, "flow-restore-wait", val ? "true": "false");
-- ovsrec_open_vswitch_set_other_config(cfg, &new_config);
-- if (val) {
-- ovsrec_open_vswitch_update_external_ids_setkey(
-- cfg, "ovn-managed-flow-restore-wait", "true");
-- } else if (ovn_managed) {
-- ovsrec_open_vswitch_update_external_ids_delkey(
-- cfg, "ovn-managed-flow-restore-wait");
-- }
-- ovsdb_idl_txn_increment(ovs_idl_txn, &cfg->header_,
-- &ovsrec_open_vswitch_col_next_cfg, true);
-- smap_destroy(&new_config);
--}
--
--static void
--manage_flow_restore_wait(struct ovsdb_idl_txn *ovs_idl_txn,
-- const struct ovsrec_open_vswitch *cfg,
-- uint64_t ofctrl_cur_cfg, uint64_t ovs_next_cfg,
-- int ovs_txn_status, bool is_ha_gw)
--{
-- enum flow_restore_wait_state {
-- FRW_INIT, /* Initial state */
-- FRW_WAIT_TXN_COMPLETE, /* Sent false, waiting txn to complete */
-- FRW_TXN_SUCCESS, /* Txn completed. Waiting for OVS Ack. */
-- FRW_DONE /* Everything completed */
-- };
--
-- static int64_t frw_next_cfg;
-- static enum flow_restore_wait_state frw_state;
-- static bool ofctrl_was_connected = false;
--
-- bool ofctrl_connected = ofctrl_is_connected();
--
-- if (!ovs_idl_txn || !cfg) {
-- return;
-- }
--
-- /* If OVS is stopped/started, make sure flow-restore-wait is toggled. */
-- if (ofctrl_connected && !ofctrl_was_connected) {
-- frw_state = FRW_INIT;
-- }
-- ofctrl_was_connected = ofctrl_connected;
--
-- if (!ofctrl_connected) {
-- return;
-- }
--
-- bool frw = smap_get_bool(&cfg->other_config, "flow-restore-wait", false);
-- bool ovn_managed_once = smap_get_bool(&cfg->external_ids,
-- "ovn-managed-flow-restore-wait",
-- false);
--
-- if (frw && !ovn_managed_once) {
-- /* frw has been set by ovs-ctl. Do not touch. */
-- return;
-- }
--
-- if (!is_ha_gw) {
-- if (frw) {
-- /* frw has once been set by OVN. We are now not an HA chassis
-- * anymore, unset it. */
-- set_flow_restore_wait(ovs_idl_txn, cfg, &cfg->other_config,
-- false, ovn_managed_once);
-- }
-- /* else we are not an HA chassis and frw is false. Ignore it. */
-- return;
-- }
--
-- switch (frw_state) {
-- case FRW_INIT:
-- if (ofctrl_cur_cfg > 0) {
-- set_flow_restore_wait(ovs_idl_txn, cfg, &cfg->other_config,
-- false, ovn_managed_once);
-- frw_state = FRW_WAIT_TXN_COMPLETE;
-- VLOG_INFO("Setting flow-restore-wait=false "
-- "(cur_cfg=%"PRIu64")", ofctrl_cur_cfg);
-- }
-- break;
--
-- case FRW_WAIT_TXN_COMPLETE:
-- /* if (ovs_idl_txn != NULL), the transaction completed.
-- * When the transaction completed, it either failed
-- * (ovs_txn_status == 0) or succeeded (ovs_txn_status != 0). */
-- if (ovs_txn_status == 0) {
-- /* Previous transaction failed. */
-- set_flow_restore_wait(ovs_idl_txn, cfg, &cfg->other_config,
-- false, ovn_managed_once);
-- break;
-- }
-- /* txn succeeded, get next_cfg */
-- frw_next_cfg = ovs_next_cfg;
-- frw_state = FRW_TXN_SUCCESS;
-- /* fall through */
--
-- case FRW_TXN_SUCCESS:
-- if (ovs_next_cfg < frw_next_cfg) {
-- /* DB was reset, next_cfg went backwards. */
-- VLOG_INFO("OVS DB reset (next_cfg %"PRId64" -> %"PRIu64"), "
-- "resetting state",
-- frw_next_cfg, ovs_next_cfg);
-- set_flow_restore_wait(ovs_idl_txn, cfg, &cfg->other_config,
-- false, ovn_managed_once);
-- frw_state = FRW_WAIT_TXN_COMPLETE;
-- break;
-- }
--
-- if (!frw) {
-- if (cfg->cur_cfg >= frw_next_cfg) {
-- set_flow_restore_wait(ovs_idl_txn, cfg, &cfg->other_config,
-- true, ovn_managed_once);
-- frw_state = FRW_DONE;
-- VLOG_INFO("Setting flow-restore-wait=true");
-- }
-- } else {
-- /* The transaction to false succeeded but frw is true.
-- * So, another task already set it to true. */
-- frw_state = FRW_DONE;
-- VLOG_INFO("flow-restore-wait was already true");
-- }
-- break;
-- case FRW_DONE:
-- if (!frw) {
-- /* frw has been removed (e.g. by ovs-ctl restart) or is false
-- * (e.g. txn failed.) */
-- set_flow_restore_wait(ovs_idl_txn, cfg, &cfg->other_config,
-- false, ovn_managed_once);
-- frw_state = FRW_WAIT_TXN_COMPLETE;
-- VLOG_INFO("OVS frw cleared, restarting flow-restore-wait sequence "
-- "(cur_cfg=%"PRIu64")", ofctrl_cur_cfg);
-- }
-- break;
-- }
--}
--
- /* Only set monitor conditions on tables that are available in the
- * server schema.
- */
-@@ -3527,7 +3381,6 @@ en_mac_cache_cleanup(void *data)
+@@ -101,6 +101,7 @@
+ #include "evpn-arp.h"
+ #include "evpn-binding.h"
+ #include "evpn-fdb.h"
++#include "evpn-mac-binding-sync.h"
+
+ VLOG_DEFINE_THIS_MODULE(main);
+
+@@ -333,6 +334,13 @@ update_sb_monitors(struct ovsdb_idl *ovnsb_idl,
+ const struct smap l3 = SMAP_CONST1(&l3, "l3gateway-chassis", id);
+ sbrec_port_binding_add_clause_options(&pb, OVSDB_F_INCLUDES, &l3);
+
++ /* Also monitor ports with periodic RA enabled so that all chassis
++ * can generate RAs for their local VIFs, even when the port is an
++ * l3gateway bound to a different chassis. */
++ const struct smap ra =
++ SMAP_CONST1(&ra, "ipv6_ra_send_periodic", "true");
++ sbrec_port_binding_add_clause_options(&pb, OVSDB_F_INCLUDES, &ra);
++
+ sbrec_controller_event_add_clause_chassis(&ce, OVSDB_F_EQ,
+ &chassis->header_.uuid);
+ sbrec_igmp_group_add_clause_chassis(&igmp, OVSDB_F_EQ,
+@@ -3381,6 +3389,7 @@ en_mac_cache_cleanup(void *data)
struct ed_type_bfd_chassis {
struct sset bfd_chassis;
-- bool is_ha_gw;
++ bool is_computed;
};
static void *
-@@ -3556,9 +3409,8 @@ en_bfd_chassis_run(struct engine_node *node, void *data OVS_UNUSED)
- = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
-
+@@ -3411,6 +3420,7 @@ en_bfd_chassis_run(struct engine_node *node, void *data OVS_UNUSED)
sset_clear(&bfd_chassis->bfd_chassis);
-- bfd_chassis->is_ha_gw = bfd_calculate_chassis(chassis,
-- ha_chassis_grp_table,
-- &bfd_chassis->bfd_chassis);
-+ bfd_calculate_chassis(chassis, ha_chassis_grp_table,
-+ &bfd_chassis->bfd_chassis);
+ bfd_calculate_chassis(chassis, ha_chassis_grp_table,
+ &bfd_chassis->bfd_chassis);
++ bfd_chassis->is_computed = true;
return EN_UPDATED;
}
-@@ -4933,12 +4785,14 @@ pflow_output_sb_port_binding_handler(struct engine_node *node,
- */
- const struct sbrec_port_binding *pb;
- SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb, p_ctx.port_binding_table) {
-+ bool removed = sbrec_port_binding_is_deleted(pb);
-+
- /* Trigger a full recompute if type column is updated. */
-- if (sbrec_port_binding_is_updated(pb, SBREC_PORT_BINDING_COL_TYPE)) {
-+ if (!removed && !sbrec_port_binding_is_new(pb) &&
-+ sbrec_port_binding_is_updated(pb, SBREC_PORT_BINDING_COL_TYPE)) {
- destroy_physical_ctx(&p_ctx);
- return EN_UNHANDLED;
- }
-- bool removed = sbrec_port_binding_is_deleted(pb);
- if (!physical_handle_flows_for_lport(pb, removed, &p_ctx,
- &pfo->flow_table)) {
- destroy_physical_ctx(&p_ctx);
-@@ -6392,7 +6246,6 @@ en_evpn_vtep_binding_clear_tracked_data(void *data_)
- struct ed_type_evpn_vtep_binding *data = data_;
- hmapx_clear(&data->updated_bindings);
- uuidset_clear(&data->removed_bindings);
-- evpn_datapaths_clear(&data->datapaths);
- hmapx_clear(&data->updated_multicast_groups);
- uuidset_clear(&data->removed_multicast_groups);
+@@ -4633,9 +4643,6 @@ static void init_physical_ctx(struct engine_node *node,
+ struct ed_type_evpn_fdb *efdb_data =
+ engine_get_input_data("evpn_fdb", node);
+
+- struct ed_type_evpn_arp *earp_data =
+- engine_get_input_data("evpn_arp", node);
+-
+ parse_encap_ips(ovs_table, &p_ctx->n_encap_ips, &p_ctx->encap_ips);
+ p_ctx->sbrec_port_binding_by_name = sbrec_port_binding_by_name;
+ p_ctx->sbrec_port_binding_by_datapath = sbrec_port_binding_by_datapath;
+@@ -4659,7 +4666,6 @@ static void init_physical_ctx(struct engine_node *node,
+ p_ctx->evpn_bindings = &eb_data->bindings;
+ p_ctx->evpn_multicast_groups = &eb_data->multicast_groups;
+ p_ctx->evpn_fdbs = &efdb_data->fdbs;
+- p_ctx->evpn_arps = &earp_data->arps;
+
+ struct controller_engine_ctx *ctrl_ctx = engine_get_context()->client_ctx;
+ p_ctx->if_mgr = ctrl_ctx->if_mgr;
+@@ -4986,22 +4992,6 @@ pflow_output_fdb_handler(struct engine_node *node, void *data)
+ return EN_HANDLED_UPDATED;
}
-@@ -7265,7 +7118,6 @@ main(int argc, char *argv[])
- struct unixctl_server *unixctl;
- struct ovn_exit_args exit_args = {0};
- struct br_int_remote br_int_remote = {0};
-- static uint64_t next_cfg = 0;
- int retval;
-
- /* Read from system-id-override file once on startup. */
-@@ -7593,7 +7445,6 @@ main(int argc, char *argv[])
-
- /* Main loop. */
- int ovnsb_txn_status = 1;
-- int ovs_txn_status = 1;
- bool sb_monitor_all = false;
- struct tracked_acl_ids *tracked_acl_ids = NULL;
- while (!exit_args.exiting) {
-@@ -7695,11 +7546,6 @@ main(int argc, char *argv[])
- pinctrl_update_swconn(br_int_remote.target,
- br_int_remote.probe_interval);
-
-- if (cfg && ovs_idl_txn && ovs_txn_status == -1) {
-- /* txn was in progress and is now completed */
-- next_cfg = cfg->next_cfg;
-- }
--
- /* Enable ACL matching for double tagged traffic. */
- if (ovs_idl_txn && cfg) {
- int vlan_limit = smap_get_int(
-@@ -7708,6 +7554,24 @@ main(int argc, char *argv[])
- ovsrec_open_vswitch_update_other_config_setkey(
- cfg, "vlan-limit", "0");
- }
-+ /* Clear flow-restore-wait. OVN at one point would set
-+ * flow-restore-wait in order to try to synchronize with
-+ * OVS. However, that resulted in a bug, so that behavior
-+ * was reverted. If upgrading from a version where OVN
-+ * manipulted flow-restore-wait, then flow-restore-wait
-+ * needs to be cleared in order for OVS to function
-+ * properly. This is (hopefully) a temporary measure until
-+ * a more reliable method of synchronizing with OVS is
-+ * devised.
-+ */
-+ if (smap_get_bool(&cfg->external_ids,
-+ "ovn-managed-flow-restore-wait", false) &&
-+ smap_get(&cfg->other_config, "flow-restore-wait")) {
-+ ovsrec_open_vswitch_update_other_config_delkey(
-+ cfg, "flow-restore-wait");
-+ ovsrec_open_vswitch_update_external_ids_delkey(
-+ cfg, "ovn-managed-flow-restore-wait");
-+ }
- }
- static bool chassis_idx_stored = false;
-@@ -8049,13 +7913,6 @@ main(int argc, char *argv[])
- stopwatch_start(OFCTRL_SEQNO_RUN_STOPWATCH_NAME,
- time_msec());
- ofctrl_seqno_run(ofctrl_get_cur_cfg());
-- if (ovs_idl_txn && bfd_chassis_data) {
-- manage_flow_restore_wait(ovs_idl_txn, cfg,
-- ofctrl_get_cur_cfg(),
-- next_cfg, ovs_txn_status,
-- bfd_chassis_data->is_ha_gw);
-- }
+-static enum engine_input_handler_result
+-pflow_output_arp_handler(struct engine_node *node, void *data)
+-{
+- struct ed_type_pflow_output *pfo = data;
+- struct ed_type_runtime_data *rt_data =
+- engine_get_input_data("runtime_data", node);
+- struct ed_type_evpn_arp *ea_data =
+- engine_get_input_data("evpn_arp", node);
+-
+- physical_handle_evpn_arp_changes(&rt_data->local_datapaths,
+- &pfo->flow_table,
+- &ea_data->updated_arps,
+- &ea_data->removed_arps);
+- return EN_HANDLED_UPDATED;
+-}
-
- stopwatch_stop(OFCTRL_SEQNO_RUN_STOPWATCH_NAME,
- time_msec());
- stopwatch_start(IF_STATUS_MGR_RUN_STOPWATCH_NAME,
-@@ -8155,7 +8012,7 @@ main(int argc, char *argv[])
- OVS_NOT_REACHED();
- }
-
-- ovs_txn_status = ovsdb_idl_loop_commit_and_wait(&ovs_idl_loop);
-+ int ovs_txn_status = ovsdb_idl_loop_commit_and_wait(&ovs_idl_loop);
- if (!ovs_txn_status) {
- /* The transaction failed. */
- vif_plug_clear_deleted(
-@@ -8174,9 +8031,6 @@ main(int argc, char *argv[])
- &vif_plug_deleted_iface_ids);
- vif_plug_finish_changed(
- &vif_plug_changed_iface_ids);
-- if (cfg) {
-- next_cfg = cfg->next_cfg;
-- }
- } else if (ovs_txn_status == -1) {
- /* The commit is still in progress */
- } else {
-@@ -8250,7 +8104,7 @@ loop_done:
- }
+ static void *
+ en_controller_output_init(struct engine_node *node OVS_UNUSED,
+ struct engine_arg *arg OVS_UNUSED)
+@@ -5071,6 +5061,14 @@ controller_output_garp_rarp_handler(struct engine_node *node OVS_UNUSED,
+ return EN_HANDLED_UPDATED;
+ }
- ovsdb_idl_loop_commit_and_wait(&ovnsb_idl_loop);
-- ovs_txn_status = ovsdb_idl_loop_commit_and_wait(&ovs_idl_loop);
-+ int ovs_txn_status = ovsdb_idl_loop_commit_and_wait(&ovs_idl_loop);
- if (!ovs_txn_status) {
- /* The transaction failed. */
- vif_plug_clear_deleted(
-diff --git a/controller/physical.c b/controller/physical.c
-index 228f3d171a..fd8a4be7f6 100644
---- a/controller/physical.c
-+++ b/controller/physical.c
-@@ -351,30 +351,35 @@ put_flow_based_remote_port_redirect_overlay(
++static enum engine_input_handler_result
++controller_output_evpn_mac_binding_sync_handler(
++ struct engine_node *node OVS_UNUSED,
++ void *data OVS_UNUSED)
++{
++ return EN_HANDLED_UPDATED;
++}
++
+ /* Handles sbrec_chassis changes.
+ * If a new chassis is added or removed return false, so that
+ * flows are recomputed. For any updates, there is no need for
+@@ -5497,10 +5495,24 @@ en_route_exchange_run(struct engine_node *node, void *data)
+ return EN_STALE;
}
+
++ const struct ovsrec_open_vswitch_table *ovs_table =
++ EN_OVSDB_GET(engine_get_input("OVS_open_vswitch", node));
++ const char *chassis_id = get_ovs_chassis_id(ovs_table);
++ ovs_assert(chassis_id);
++
++ struct ovsdb_idl_index *sbrec_chassis_by_name =
++ engine_ovsdb_node_get_index(
++ engine_get_input("SB_chassis", node),
++ "name");
++ const struct sbrec_chassis *chassis
++ = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
++ ovs_assert(chassis);
++
+ struct route_exchange_ctx_in r_ctx_in = {
+ .ovnsb_idl_txn = engine_get_context()->ovnsb_idl_txn,
+ .sbrec_learned_route_by_datapath = sbrec_learned_route_by_datapath,
+ .sbrec_port_binding_by_name = sbrec_port_binding_by_name,
++ .chassis = chassis,
+ .announce_routes = &route_data->announce_routes,
+ };
+ struct route_exchange_ctx_out r_ctx_out = {
+@@ -5878,6 +5890,9 @@ struct ed_type_neighbor {
+ struct vector monitored_interfaces;
+ /* Contains set of PB names that are currently advertised. */
+ struct sset advertised_pbs;
++ /* Contains 'struct local_datapath' pointers for datapaths with FDB
++ * advertisement enabled. */
++ struct hmapx fdb_datapaths;
+ };
+
+ static void *
+@@ -5890,6 +5905,7 @@ en_neighbor_init(struct engine_node *node OVS_UNUSED,
+ .monitored_interfaces =
+ VECTOR_EMPTY_INITIALIZER(struct neighbor_interface_monitor *),
+ .advertised_pbs = SSET_INITIALIZER(&data->advertised_pbs),
++ .fdb_datapaths = HMAPX_INITIALIZER(&data->fdb_datapaths),
+ };
+ return data;
+ }
+@@ -5902,6 +5918,7 @@ en_neighbor_cleanup(void *data)
+ neighbor_cleanup(&ne_data->monitored_interfaces);
+ vector_destroy(&ne_data->monitored_interfaces);
+ sset_destroy(&ne_data->advertised_pbs);
++ hmapx_destroy(&ne_data->fdb_datapaths);
}
-+/* Add handling for E/W ICMPv4/v6 packets when tunneled packets exceed
-+ * path MTU.
-+ * If packet needs to be tunneled to another node and the physical
-+ * interface used for tunneling has a lower MTU than the packet size,
-+ * or if there is a route exception with a smaller MTU, kernel
-+ * generates an ICMP "Fragmentation Needed" message, but packet
-+ * metadata didn't change. Such packets might have been dropped due
-+ * to required metadata modifications for returned packet.
-+ *
-+ * Mark these packets with MLF_RX_FROM_TUNNEL_BIT for further
-+ * processing. Packets received from a RAMP tunnel should be passed
-+ * through, and errors handled via normal processing path, since
-+ * port metadata is not carried in RAMP packets in VNI.
-+ */
- static void
--add_tunnel_ingress_flows(const struct chassis_tunnel *tun,
-- enum mf_field_id mff_ovn_geneve,
-- struct ovn_desired_flow_table *flow_table,
-- struct ofpbuf *ofpacts)
-+add_tunnel_ingress_pmtud_flows(const struct chassis_tunnel *tun,
-+ struct ofpbuf *ofpacts,
-+ struct ovn_desired_flow_table *flow_table)
- {
-- /* Main ingress flow (priority 100) */
-- struct match match = MATCH_CATCHALL_INITIALIZER;
-- match_set_in_port(&match, tun->ofport);
--
-- ofpbuf_clear(ofpacts);
-- put_decapsulation(mff_ovn_geneve, tun, ofpacts);
-- put_resubmit(OFTABLE_LOCAL_OUTPUT, ofpacts);
-+ if (tun->is_ramp_tunnel) {
-+ return;
-+ }
+ static enum engine_node_state
+@@ -5928,6 +5945,14 @@ en_neighbor_run(struct engine_node *node OVS_UNUSED, void *data)
+ engine_ovsdb_node_get_index(
+ engine_get_input("SB_advertised_mac_binding", node),
+ "datapath");
++ struct ovsdb_idl_index *sbrec_port_binding_by_key =
++ engine_ovsdb_node_get_index(
++ engine_get_input("SB_port_binding", node),
++ "key");
++ struct ovsdb_idl_index *sbrec_fdb_by_dp_key =
++ engine_ovsdb_node_get_index(
++ engine_get_input("SB_fdb", node),
++ "dp_key");
-- ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, 100, 0, &match,
-- ofpacts, hc_uuid);
-+ struct match match = MATCH_CATCHALL_INITIALIZER;
+ const char *chassis_id = get_ovs_chassis_id(ovs_table);
+ ovs_assert(chassis_id);
+@@ -5940,16 +5965,20 @@ en_neighbor_run(struct engine_node *node OVS_UNUSED, void *data)
+ .sbrec_pb_by_dp = sbrec_port_binding_by_datapath,
+ .sbrec_amb_by_dp = sbrec_advertised_mac_binding_by_datapath,
+ .sbrec_pb_by_name = sbrec_port_binding_by_name,
++ .sbrec_pb_by_key = sbrec_port_binding_by_key,
++ .sbrec_fdb_by_dp_key = sbrec_fdb_by_dp_key,
+ .chassis = chassis,
+ };
- /* Set allow rx from tunnel bit */
- put_load(1, MFF_LOG_FLAGS, MLF_RX_FROM_TUNNEL_BIT, 1, ofpacts);
- put_resubmit(OFTABLE_CT_ZONE_LOOKUP, ofpacts);
+ struct neighbor_ctx_out n_ctx_out = {
+ .monitored_interfaces = &ne_data->monitored_interfaces,
+ .advertised_pbs = &ne_data->advertised_pbs,
++ .fdb_datapaths = &ne_data->fdb_datapaths,
+ };
-- /* Add specific flows for E/W ICMPv{4,6} packets if tunnelled packets
-- * do not fit path MTU. */
--
- /* IPv4 ICMP flow (priority 120) */
- match_init_catchall(&match);
- match_set_in_port(&match, tun->ofport);
-@@ -398,6 +403,26 @@ add_tunnel_ingress_flows(const struct chassis_tunnel *tun,
- ofpacts, hc_uuid);
+ neighbor_cleanup(&ne_data->monitored_interfaces);
+ sset_clear(&ne_data->advertised_pbs);
++ hmapx_clear(&ne_data->fdb_datapaths);
+ neighbor_run(&n_ctx_in, &n_ctx_out);
+
+ return EN_UPDATED;
+@@ -6080,6 +6109,35 @@ neighbor_sb_port_binding_handler(struct engine_node *node, void *data)
+ return EN_HANDLED_UNCHANGED;
+ }
+
++static enum engine_input_handler_result
++neighbor_sb_fdb_handler(struct engine_node *node, void *data)
++{
++ /* This handler assumes that local_datapaths have not been added or
++ * removed in this engine run. This is guaranteed because
++ * neighbor_runtime_data_handler() returns EN_UNHANDLED when it detects
++ * a new or removed datapath (TRACKED_RESOURCE_NEW / _REMOVED), which
++ * forces a full recompute of en_neighbor before this handler can run.
++ * If neighbor_runtime_data_handler() is ever changed to handle those
++ * cases incrementally, this handler must be updated to account for
++ * datapaths that were not present when it looked up FDB entries. */
++ struct ed_type_neighbor *ne_data = data;
++ struct ed_type_runtime_data *rt_data =
++ engine_get_input_data("runtime_data", node);
++ const struct sbrec_fdb_table *fdb_table =
++ EN_OVSDB_GET(engine_get_input("SB_fdb", node));
++
++ const struct sbrec_fdb *fdb;
++ SBREC_FDB_TABLE_FOR_EACH_TRACKED (fdb, fdb_table) {
++ struct local_datapath *ld =
++ get_local_datapath(&rt_data->local_datapaths, fdb->dp_key);
++ if (ld && hmapx_contains(&ne_data->fdb_datapaths, ld)) {
++ return EN_UNHANDLED;
++ }
++ }
++
++ return EN_HANDLED_UNCHANGED;
++}
++
+ struct ed_type_neighbor_table_notify {
+ /* For incremental processing this could be tracked per interface in
+ * the future. */
+@@ -6519,6 +6577,176 @@ evpn_arp_vtep_binding_handler(struct engine_node *node, void *data OVS_UNUSED)
+ return EN_UNHANDLED;
}
++/* EVPN MAC binding sync waker: a timer-based input node that
++ * periodically fires EN_UPDATED to trigger the sync node so it
++ * can refresh timestamps on SB MAC_Binding rows. */
++static void *
++en_evpn_mac_binding_sync_waker_init(struct engine_node *node OVS_UNUSED,
++ struct engine_arg *arg OVS_UNUSED)
++{
++ struct evpn_mb_sync_waker *waker = xzalloc(sizeof *waker);
++ return waker;
++}
++
++static enum engine_node_state
++en_evpn_mac_binding_sync_waker_run(struct engine_node *node OVS_UNUSED,
++ void *data)
++{
++ struct evpn_mb_sync_waker *waker = data;
++
++ if (!waker->should_schedule) {
++ return EN_UNCHANGED;
++ }
++
++ if (time_msec() >= waker->next_wake_msec) {
++ waker->should_schedule = false;
++ return EN_UPDATED;
++ }
++
++ poll_timer_wait_until(waker->next_wake_msec);
++ return EN_UNCHANGED;
++}
++
++static void
++en_evpn_mac_binding_sync_waker_cleanup(void *data OVS_UNUSED)
++{
++}
++
++/* EVPN MAC binding sync node: syncs EVPN-learned MAC bindings
++ * to the SB MAC_Binding table so they are distributed to all
++ * chassis. */
++static void *
++en_evpn_mac_binding_sync_init(struct engine_node *node OVS_UNUSED,
++ struct engine_arg *arg OVS_UNUSED)
++{
++ struct ed_type_evpn_mac_binding_sync *data = xmalloc(sizeof *data);
++ evpn_mac_binding_sync_init(data);
++ return data;
++}
++
++static enum engine_node_state
++en_evpn_mac_binding_sync_run(struct engine_node *node, void *data_)
++{
++ struct ed_type_evpn_mac_binding_sync *data = data_;
++
++ struct ed_type_evpn_arp *earp_data =
++ engine_get_input_data("evpn_arp", node);
++ struct ed_type_runtime_data *rt_data =
++ engine_get_input_data("runtime_data", node);
++ struct mac_cache_data *mac_cache_data =
++ engine_get_input_data("mac_cache", node);
++ struct engine_node *sb_mb_node =
++ engine_get_input("SB_mac_binding", node);
++ struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip =
++ engine_ovsdb_node_get_index(sb_mb_node, "lport_ip");
++ const struct sbrec_mac_binding_table *mb_table =
++ EN_OVSDB_GET(sb_mb_node);
++
++ struct evpn_mb_sync_waker *waker =
++ engine_get_input_data("evpn_mac_binding_sync_waker", node);
++
++ uuidset_clear(&data->lsp_peers);
++ data->sb_changes_pending = false;
++
++ evpn_mac_binding_sync_run(engine_get_context()->ovnsb_idl_txn,
++ sbrec_mac_binding_by_lport_ip,
++ mb_table,
++ &rt_data->local_datapaths,
++ &earp_data->arps,
++ mac_cache_data,
++ data, waker);
++
++ return EN_UPDATED;
++}
++
+static void
-+add_tunnel_ingress_flows(const struct chassis_tunnel *tun,
-+ enum mf_field_id mff_ovn_geneve,
-+ struct ovn_desired_flow_table *flow_table,
-+ struct ofpbuf *ofpacts)
++en_evpn_mac_binding_sync_cleanup(void *data_)
+{
-+ /* Main ingress flow (priority 100) */
-+ struct match match = MATCH_CATCHALL_INITIALIZER;
-+ match_set_in_port(&match, tun->ofport);
++ struct ed_type_evpn_mac_binding_sync *data = data_;
++ evpn_mac_binding_sync_cleanup(data);
++}
++
++static enum engine_input_handler_result
++evpn_mac_binding_sync_sb_ro_handler(struct engine_node *node OVS_UNUSED,
++ void *data_)
++{
++ struct ed_type_evpn_mac_binding_sync *data = data_;
++ if (data->sb_changes_pending) {
++ return EN_UNHANDLED;
++ }
++
++ return EN_HANDLED_UNCHANGED;
++}
++
++static enum engine_input_handler_result
++evpn_mac_binding_sync_sb_port_binding_handler(struct engine_node *node,
++ void *data_)
++{
++ struct ed_type_evpn_mac_binding_sync *data = data_;
++ struct ed_type_runtime_data *rt_data =
++ engine_get_input_data("runtime_data", node);
++ const struct sbrec_port_binding_table *port_binding_table =
++ EN_OVSDB_GET(engine_get_input("SB_port_binding", node));
++ struct ovsdb_idl_index *sbrec_port_binding_by_name =
++ engine_ovsdb_node_get_index(
++ engine_get_input("SB_port_binding", node),
++ "name");
++
++ const struct sbrec_port_binding *pb;
++ SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb, port_binding_table) {
++ if (sbrec_port_binding_is_deleted(pb) &&
++ uuidset_contains(&data->lsp_peers, &pb->header_.uuid)) {
++ return EN_UNHANDLED;
++ }
++
++ if (!sbrec_port_binding_is_new(pb) &&
++ uuidset_contains(&data->lsp_peers, &pb->header_.uuid) &&
++ (sbrec_port_binding_is_updated(pb,
++ SBREC_PORT_BINDING_COL_OPTIONS) ||
++ sbrec_port_binding_is_updated(pb, SBREC_PORT_BINDING_COL_TYPE))) {
++ return EN_UNHANDLED;
++ }
++
++ if (!sbrec_port_binding_is_new(pb)) {
++ continue;
++ }
++
++ struct local_datapath *ld = get_local_datapath(
++ &rt_data->local_datapaths, pb->datapath->tunnel_key);
++ if (!ld || !ld->is_switch) {
++ continue;
++ }
++
++ int64_t vni = ovn_smap_get_llong(&ld->datapath->external_ids,
++ "dynamic-routing-vni", -1);
++ if (!ovn_is_valid_vni(vni)) {
++ continue;
++ }
++
++ enum en_lport_type type = get_lport_type(pb);
++ if (type != LP_L3GATEWAY && type != LP_PATCH) {
++ continue;
++ }
+
-+ ofpbuf_clear(ofpacts);
-+ put_decapsulation(mff_ovn_geneve, tun, ofpacts);
-+ put_resubmit(OFTABLE_LOCAL_OUTPUT, ofpacts);
++ const struct sbrec_port_binding *peer =
++ lport_get_peer(pb, sbrec_port_binding_by_name);
++ if (!peer) {
++ continue;
++ }
++
++ struct local_datapath *peer_ld =
++ get_local_datapath(&rt_data->local_datapaths,
++ peer->datapath->tunnel_key);
++ if (!peer_ld || peer_ld->is_switch) {
++ continue;
++ }
+
-+ ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, 100, 0, &match,
-+ ofpacts, hc_uuid);
++ return EN_UNHANDLED;
++ }
+
-+ add_tunnel_ingress_pmtud_flows(tun, ofpacts, flow_table);
++ return EN_HANDLED_UNCHANGED;
+}
+
+ /* Define engine node functions for nodes that represent SB tables.
+ *
+ * en_sb_<TABLE_NAME>_run()
+@@ -6641,6 +6869,8 @@ static ENGINE_NODE(neighbor_exchange_status);
+ static ENGINE_NODE(evpn_vtep_binding, CLEAR_TRACKED_DATA);
+ static ENGINE_NODE(evpn_fdb, CLEAR_TRACKED_DATA);
+ static ENGINE_NODE(evpn_arp, CLEAR_TRACKED_DATA);
++static ENGINE_NODE(evpn_mac_binding_sync_waker);
++static ENGINE_NODE(evpn_mac_binding_sync, SB_WRITE);
+
static void
- put_stack(enum mf_field_id field, struct ofpact_stack *stack)
- {
-@@ -2827,12 +2852,6 @@ fanout_to_chassis_port_based(enum mf_field_id mff_ovn_geneve,
- }
+ inc_proc_ovn_controller_init(
+@@ -6681,6 +6911,8 @@ inc_proc_ovn_controller_init(
+ engine_add_input(&en_route, &en_sb_datapath_binding,
+ route_sb_datapath_binding_handler);
+
++ engine_add_input(&en_route_exchange, &en_ovs_open_vswitch, NULL);
++ engine_add_input(&en_route_exchange, &en_sb_chassis, NULL);
+ engine_add_input(&en_route_exchange, &en_route, NULL);
+ engine_add_input(&en_route_exchange, &en_sb_learned_route,
+ engine_noop_handler);
+@@ -6886,6 +7118,7 @@ inc_proc_ovn_controller_init(
+ neighbor_sb_datapath_binding_handler);
+ engine_add_input(&en_neighbor, &en_sb_port_binding,
+ neighbor_sb_port_binding_handler);
++ engine_add_input(&en_neighbor, &en_sb_fdb, neighbor_sb_fdb_handler);
+ engine_add_input(&en_neighbor_exchange, &en_neighbor, NULL);
+ engine_add_input(&en_neighbor_exchange, &en_host_if_monitor, NULL);
+ engine_add_input(&en_neighbor_exchange, &en_neighbor_table_notify, NULL);
+@@ -6912,12 +7145,27 @@ inc_proc_ovn_controller_init(
+ engine_add_input(&en_evpn_arp, &en_evpn_vtep_binding,
+ evpn_arp_vtep_binding_handler);
+
++ engine_add_input(&en_evpn_mac_binding_sync,
++ &en_evpn_mac_binding_sync_waker, NULL);
++ engine_add_input(&en_evpn_mac_binding_sync, &en_evpn_arp, NULL);
++ /* MAC_Binding data is only used via an index for lookups. */
++ engine_add_input(&en_evpn_mac_binding_sync, &en_sb_mac_binding,
++ engine_noop_handler);
++ /* Runtime data is only used for local_datapaths access. */
++ engine_add_input(&en_evpn_mac_binding_sync, &en_runtime_data,
++ engine_noop_handler);
++ /* MAC cache data is only used for aging threshold lookup. */
++ engine_add_input(&en_evpn_mac_binding_sync, &en_mac_cache,
++ engine_noop_handler);
++ engine_add_input(&en_evpn_mac_binding_sync, &en_sb_ro,
++ evpn_mac_binding_sync_sb_ro_handler);
++ engine_add_input(&en_evpn_mac_binding_sync, &en_sb_port_binding,
++ evpn_mac_binding_sync_sb_port_binding_handler);
++
+ engine_add_input(&en_pflow_output, &en_evpn_vtep_binding,
+ pflow_output_evpn_binding_handler);
+ engine_add_input(&en_pflow_output, &en_evpn_fdb,
+ pflow_output_fdb_handler);
+- engine_add_input(&en_pflow_output, &en_evpn_arp,
+- pflow_output_arp_handler);
+
+ engine_add_input(&en_controller_output, &en_dns_cache,
+ NULL);
+@@ -6933,6 +7181,8 @@ inc_proc_ovn_controller_init(
+ controller_output_route_exchange_handler);
+ engine_add_input(&en_controller_output, &en_garp_rarp,
+ controller_output_garp_rarp_handler);
++ engine_add_input(&en_controller_output, &en_evpn_mac_binding_sync,
++ controller_output_evpn_mac_binding_sync_handler);
+
+ engine_add_input(&en_acl_id, &en_sb_acl_id, NULL);
+ engine_add_input(&en_controller_output, &en_acl_id,
+@@ -7648,13 +7898,29 @@ main(int argc, char *argv[])
+ const struct sbrec_sb_global *sbg =
+ sbrec_sb_global_first(ovnsb_idl_loop.idl);
+ if (chassis && sbg && ovs_feature_set_discovered()) {
++ bool is_ha_chassis_member;
++ if (bfd_chassis_data && bfd_chassis_data->is_computed) {
++ is_ha_chassis_member = sset_contains(
++ &bfd_chassis_data->bfd_chassis,
++ chassis->name);
++ } else {
++ struct sset tmp = SSET_INITIALIZER(&tmp);
++ bfd_calculate_chassis(
++ chassis, sbrec_ha_chassis_group_table_get(
++ ovnsb_idl_loop.idl),
++ &tmp);
++ is_ha_chassis_member = sset_contains(&tmp,
++ chassis->name);
++ sset_destroy(&tmp);
++ }
+ encaps_run(ovs_idl_txn, ovnsb_idl_txn, br_int,
+ sbrec_chassis_table_get(ovnsb_idl_loop.idl),
+ chassis,
+ sbg,
+ ovs_table,
+ &transport_zones,
+- bridge_table);
++ bridge_table,
++ is_ha_chassis_member);
+
+ struct ed_type_route_table_notify *rtn =
+ engine_get_internal_data(&en_route_table_notify);
+diff --git a/controller/physical.c b/controller/physical.c
+index fd8a4be7f6..030010c332 100644
+--- a/controller/physical.c
++++ b/controller/physical.c
+@@ -56,7 +56,6 @@
+ #include "util.h"
+ #include "vswitch-idl.h"
+ #include "hmapx.h"
+-#include "neighbor-of.h"
+
+ VLOG_DEFINE_THIS_MODULE(physical);
+
+@@ -3641,28 +3640,6 @@ physical_consider_evpn_fdb(const struct evpn_fdb *fdb,
+ match, ofpacts, &fdb->flow_uuid);
}
--static bool
--chassis_is_vtep(const struct sbrec_chassis *chassis)
+-static void
+-physical_consider_evpn_arp(const struct hmap *local_datapaths,
+- const struct evpn_arp *arp,
+- struct ovn_desired_flow_table *flow_table)
-{
-- return smap_get_bool(&chassis->other_config, "is-vtep", false);
+- /* Walk connected OVN routers and install neighbor flows for the ARPs
+- * learned on EVPN datapaths.*/
+- const struct peer_ports *peers;
+- VECTOR_FOR_EACH_PTR (&arp->ldp->peer_ports, peers) {
+- const struct sbrec_port_binding *remote_pb = peers->remote;
+- struct local_datapath *peer_ld =
+- get_local_datapath(local_datapaths,
+- remote_pb->datapath->tunnel_key);
+- if (!peer_ld || peer_ld->is_switch) {
+- continue;
+- }
+-
+- consider_neighbor_flow(remote_pb, &arp->flow_uuid, &arp->ip, arp->mac,
+- flow_table, arp->priority, false);
+- }
-}
-
static void
- local_output_pb(int64_t tunnel_key, struct ofpbuf *ofpacts)
+ physical_eval_evpn_flows(const struct physical_ctx *ctx,
+ struct ofpbuf *ofpacts,
+@@ -3670,8 +3647,7 @@ physical_eval_evpn_flows(const struct physical_ctx *ctx,
{
-@@ -3011,19 +3030,19 @@ consider_mc_group(const struct physical_ctx *ctx,
- * otherwise multicast will reach remote ports through localnet
- * port. */
- if (port->chassis) {
-- if (chassis_is_vtep(port->chassis)) {
-+ if (is_ramp_tunnel(&port->chassis->other_config)) {
- sset_add(&vtep_chassis, port->chassis->name);
- } else {
- sset_add(&remote_chassis, port->chassis->name);
- }
- }
- for (size_t j = 0; j < port->n_additional_chassis; j++) {
-- if (chassis_is_vtep(port->additional_chassis[j])) {
-- sset_add(&vtep_chassis,
-- port->additional_chassis[j]->name);
-+ struct sbrec_chassis *additional_chassis =
-+ port->additional_chassis[j];
-+ if (is_ramp_tunnel(&additional_chassis->other_config)) {
-+ sset_add(&vtep_chassis, additional_chassis->name);
- } else {
-- sset_add(&remote_chassis,
-- port->additional_chassis[j]->name);
-+ sset_add(&remote_chassis, additional_chassis->name);
- }
- }
- }
-@@ -3730,6 +3749,24 @@ physical_handle_flows_for_lport(const struct sbrec_port_binding *pb,
- }
+ if (hmap_is_empty(ctx->evpn_bindings) &&
+ hmap_is_empty(ctx->evpn_multicast_groups) &&
+- hmap_is_empty(ctx->evpn_fdbs) &&
+- hmap_is_empty(ctx->evpn_arps)) {
++ hmap_is_empty(ctx->evpn_fdbs)) {
+ return;
}
-+ /* Chassisredirect ports on peer router datapaths may have bridged
-+ * redirect flows that depend on this localnet port
-+ * (put_remote_port_redirect_bridged() calls get_localnet_port()).
-+ * Re-evaluate those CR ports. */
-+ if (type == LP_LOCALNET && !removed && ldp) {
-+ const struct peer_ports *pp;
-+ VECTOR_FOR_EACH_PTR (&ldp->peer_ports, pp) {
-+ const struct sbrec_port_binding *cr_pb =
-+ lport_get_cr_port(p_ctx->sbrec_port_binding_by_name,
-+ pp->remote, NULL);
-+ if (cr_pb) {
-+ ofctrl_remove_flows(flow_table, &cr_pb->header_.uuid);
-+ physical_eval_port_binding(p_ctx, cr_pb, LP_CHASSISREDIRECT,
-+ flow_table);
-+ }
-+ }
-+ }
-+
- if (sbrec_port_binding_is_updated(
- pb, SBREC_PORT_BINDING_COL_ADDITIONAL_CHASSIS) || removed) {
- physical_multichassis_reprocess(pb, p_ctx, flow_table);
-@@ -3943,7 +3980,7 @@ physical_run(struct physical_ctx *p_ctx,
- struct chassis_tunnel *tun;
- HMAP_FOR_EACH (tun, hmap_node, p_ctx->chassis_tunnels) {
- add_tunnel_ingress_flows(tun, p_ctx->mff_ovn_geneve, flow_table,
-- &ofpacts);
-+ &ofpacts);
+@@ -3700,11 +3676,6 @@ physical_eval_evpn_flows(const struct physical_ctx *ctx,
+ HMAP_FOR_EACH (fdb, hmap_node, ctx->evpn_fdbs) {
+ physical_consider_evpn_fdb(fdb, ofpacts, &match, flow_table);
}
+-
+- const struct evpn_arp *arp;
+- HMAP_FOR_EACH (arp, hmap_node, ctx->evpn_arps) {
+- physical_consider_evpn_arp(ctx->local_datapaths, arp, flow_table);
+- }
+ }
- /* Process packets that arrive from flow-based tunnels. */
-@@ -3967,7 +4004,7 @@ physical_run(struct physical_ctx *p_ctx,
- i == GENEVE ? "geneve" : "vxlan");
-
- add_tunnel_ingress_flows(&temp_tunnel, p_ctx->mff_ovn_geneve,
-- flow_table, &ofpacts);
-+ flow_table, &ofpacts);
- }
+ static void
+@@ -3905,26 +3876,6 @@ physical_handle_evpn_fdb_changes(struct ovn_desired_flow_table *flow_table,
}
+ }
+
+-void
+-physical_handle_evpn_arp_changes(const struct hmap *local_datapaths,
+- struct ovn_desired_flow_table *flow_table,
+- const struct hmapx *updated_arps,
+- const struct uuidset *removed_arps)
+-{
+-
+- const struct hmapx_node *node;
+- HMAPX_FOR_EACH (node, updated_arps) {
+- const struct evpn_arp *arp = node->data;
+-
+- ofctrl_remove_flows(flow_table, &arp->flow_uuid);
+- physical_consider_evpn_arp(local_datapaths, arp, flow_table);
+- }
+-
+- const struct uuidset_node *uuidset_node;
+- UUIDSET_FOR_EACH (uuidset_node, removed_arps) {
+- ofctrl_remove_flows(flow_table, &uuidset_node->uuid);
+- }
+-}
+
+ void
+ physical_run(struct physical_ctx *p_ctx,
+diff --git a/controller/physical.h b/controller/physical.h
+index c7a33bd028..21cdaa1c66 100644
+--- a/controller/physical.h
++++ b/controller/physical.h
+@@ -75,7 +75,6 @@ struct physical_ctx {
+ const struct hmap *evpn_bindings;
+ const struct hmap *evpn_multicast_groups;
+ const struct hmap *evpn_fdbs;
+- const struct hmap *evpn_arps;
+ /* Set of port binding names that have been already reprocessed during
+ * the I-P run. */
+@@ -103,8 +102,4 @@ void physical_handle_evpn_binding_changes(
+ void physical_handle_evpn_fdb_changes(struct ovn_desired_flow_table *,
+ const struct hmapx *updated_fdbs,
+ const struct uuidset *removed_fdbs);
+-void physical_handle_evpn_arp_changes(const struct hmap *local_datapaths,
+- struct ovn_desired_flow_table *,
+- const struct hmapx *updated_arps,
+- const struct uuidset *removed_arps);
+ #endif /* controller/physical.h */
diff --git a/controller/pinctrl.c b/controller/pinctrl.c
-index 1a5407183c..bb8d20e7f4 100644
+index bb8d20e7f4..3b7deba481 100644
--- a/controller/pinctrl.c
+++ b/controller/pinctrl.c
-@@ -185,16 +185,15 @@ struct pinctrl {
- static struct pinctrl pinctrl;
-
- static bool pinctrl_is_sb_commited(int64_t commit_cfg, int64_t cur_cfg);
--static void init_buffered_packets_ctx(void);
--static void destroy_buffered_packets_ctx(void);
-+static void init_buffered_packets_map(void);
-+static void destroy_buffered_packets_map(void);
- static void
- run_buffered_binding(const struct sbrec_mac_binding_table *mac_binding_table,
- const struct hmap *local_datapaths,
- struct ovsdb_idl_index *sbrec_port_binding_by_key,
- struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
- struct ovsdb_idl_index *sbrec_port_binding_by_name,
-- struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip)
-- OVS_REQUIRES(pinctrl_mutex);
-+ struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip);
+@@ -65,6 +65,7 @@
+ #include "ovn-sb-idl.h"
+ #include "ovn-dns.h"
+ #include "garp_rarp.h"
++#include "spsc-ring.h"
+
+ VLOG_DEFINE_THIS_MODULE(pinctrl);
+
+@@ -83,24 +84,32 @@ VLOG_DEFINE_THIS_MODULE(pinctrl);
+ * and pinctrl_run().
+ *
+ *
+- * - put_arp/put_nd - These actions stores the IPv4/IPv6 and MAC addresses
++ * - put_arp/put_nd - These actions store the IPv4/IPv6 and MAC addresses
+ * in the 'MAC_Binding' table.
+ * The function 'pinctrl_handle_put_mac_binding()' (which
+- * is called with in the pinctrl_handler thread), stores
+- * the IPv4/IPv6 and MAC addresses in the
+- * hmap - put_mac_bindings.
++ * is called within the pinctrl_handler thread), pushes
++ * the IPv4/IPv6 and MAC addresses directly into a
++ * lock-free SPSC ring buffer ('mac_bindings_ring').
++ * This does not require pinctrl_mutex. For multicast
++ * replies, a special cookie value
++ * (MAC_BINDINGS_MC_COOKIE) is set to signal the main
++ * thread to apply a random delay.
+ *
+- * pinctrl_run(), reads these mac bindings from the hmap
+- * 'put_mac_bindings' and writes to the 'MAC_Binding'
+- * table in the Southbound DB.
++ * pinctrl_run() drains the ring in
++ * run_put_mac_bindings(), inserts entries into the
++ * main-thread-only hmap 'put_mac_bindings' (applying
++ * a random delay for multicast replies to avoid
++ * thundering herd), and writes entries whose delay
++ * has elapsed to the 'MAC_Binding' table in the
++ * Southbound DB.
+ *
+ * - arp/nd_ns - These actions generate an ARP/IPv6 Neighbor solicit
+ * requests. The original packets are buffered and
+ * injected back when put_arp/put_nd resolves
+ * corresponding ARP/IPv6 Neighbor solicit requests.
+- * When pinctrl_run(), writes the mac bindings from the
+- * 'put_mac_bindings' hmap to the MAC_Binding table in
+- * SB DB, run_buffered_binding will add the buffered
++ * When pinctrl_run() writes the mac bindings from the
++ * ring buffer to the MAC_Binding table in SB DB,
++ * run_buffered_binding will add the buffered
+ * packets to buffered_mac_bindings and notify
+ * pinctrl_handler.
+ *
+@@ -197,16 +206,14 @@ run_buffered_binding(const struct sbrec_mac_binding_table *mac_binding_table,
static void pinctrl_handle_put_mac_binding(const struct flow *md,
const struct flow *headers,
-@@ -209,8 +208,7 @@ static void run_put_mac_bindings(
- struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip)
- OVS_REQUIRES(pinctrl_mutex);
- static void wait_put_mac_bindings(void);
--static void send_mac_binding_buffered_pkts(struct rconn *swconn)
+- bool is_arp)
- OVS_REQUIRES(pinctrl_mutex);
-+static void send_mac_binding_buffered_pkts(struct rconn *swconn);
+-static void init_put_mac_bindings(void);
+-static void destroy_put_mac_bindings(void);
++ bool is_arp);
++static void init_mac_bindings(void);
++static void destroy_mac_bindings(void);
+ static void run_put_mac_bindings(
+ struct ovsdb_idl_txn *ovnsb_idl_txn,
+ struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
+ struct ovsdb_idl_index *sbrec_port_binding_by_key,
+- struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip)
+- OVS_REQUIRES(pinctrl_mutex);
++ struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip);
+ static void wait_put_mac_bindings(void);
+ static void send_mac_binding_buffered_pkts(struct rconn *swconn);
- static void pinctrl_activation_strategy_handler(const struct match *md);
+@@ -265,7 +272,8 @@ static void destroy_ipv6_ras(void);
+ static void ipv6_ra_wait(long long int send_ipv6_ra_time);
+ static void prepare_ipv6_ras(
+ const struct shash *local_active_ports_ras,
+- struct ovsdb_idl_index *sbrec_port_binding_by_name)
++ struct ovsdb_idl_index *sbrec_port_binding_by_name,
++ const struct sbrec_chassis *chassis)
+ OVS_REQUIRES(pinctrl_mutex);
+ static void send_ipv6_ras(struct rconn *swconn,
+ long long int *send_ipv6_ra_time)
+@@ -366,23 +374,22 @@ static void run_put_fdb(struct ovsdb_idl_txn *ovnsb_idl_txn,
+ struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac,
+ struct ovsdb_idl_index *sbrec_port_binding_by_key,
+ struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
+- struct fdb *fdb, uint64_t cur_cfg)
+- OVS_REQUIRES(pinctrl_mutex);
++ struct fdb *fdb, uint64_t cur_cfg);
+ static void run_put_fdbs(struct ovsdb_idl_txn *ovnsb_idl_txn,
+ struct ovsdb_idl_index *sbrec_port_binding_by_key,
+ struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
+ struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac,
+- uint64_t cur_cfg)
+- OVS_REQUIRES(pinctrl_mutex);
++ uint64_t cur_cfg);
+ static void wait_put_fdbs(void);
+ static void pinctrl_handle_put_fdb(const struct flow *md,
+- const struct flow *headers)
+- OVS_REQUIRES(pinctrl_mutex);
++ const struct flow *headers);
+
+ static void set_from_ctrl_flag_in_pkt_metadata(struct ofputil_packet_in *);
-@@ -389,6 +387,7 @@ COVERAGE_DEFINE(pinctrl_drop_buffered_packets_map);
+ COVERAGE_DEFINE(pinctrl_drop_put_mac_binding);
++COVERAGE_DEFINE(pinctrl_ring_full_put_mac_binding);
+ COVERAGE_DEFINE(pinctrl_drop_put_fdb);
++COVERAGE_DEFINE(pinctrl_ring_full_put_fdb);
+ COVERAGE_DEFINE(pinctrl_drop_buffered_packets_map);
COVERAGE_DEFINE(pinctrl_drop_controller_event);
COVERAGE_DEFINE(pinctrl_drop_put_vport_binding);
- COVERAGE_DEFINE(pinctrl_notify_main_thread);
-+COVERAGE_DEFINE(pinctrl_notify_handler_thread);
- COVERAGE_DEFINE(pinctrl_total_pin_pkts);
-
- /* DNS query statistics - thread-safe coverage counters */
-@@ -561,7 +560,7 @@ pinctrl_init(void)
+@@ -556,7 +563,7 @@ out:
+ void
+ pinctrl_init(void)
+ {
+- init_put_mac_bindings();
++ init_mac_bindings();
init_send_arps_nds();
init_ipv6_ras();
init_ipv6_prefixd();
-- init_buffered_packets_ctx();
-+ init_buffered_packets_map();
- init_activated_ports();
- init_event_table();
- ip_mcast_snoop_init();
-@@ -1576,18 +1575,18 @@ prepare_ipv6_prefixd(struct ovsdb_idl_txn *ovnsb_idl_txn,
- }
- }
-
--static struct buffered_packets_ctx buffered_packets_ctx;
-+static struct cmap buffered_packets_map;
-
- static void
--init_buffered_packets_ctx(void)
-+init_buffered_packets_map(void)
- {
-- buffered_packets_ctx_init(&buffered_packets_ctx);
-+ cmap_init(&buffered_packets_map);
- }
+@@ -3776,10 +3783,8 @@ process_packet_in(struct rconn *swconn, const struct ofp_header *msg)
+ break;
- static void
--destroy_buffered_packets_ctx(void)
-+destroy_buffered_packets_map(void)
- {
-- buffered_packets_ctx_destroy(&buffered_packets_ctx);
-+ buffered_packets_map_destroy(&buffered_packets_map);
- }
+ case ACTION_OPCODE_PUT_ARP:
+- ovs_mutex_lock(&pinctrl_mutex);
+ pinctrl_handle_put_mac_binding(&pin.flow_metadata.flow, &headers,
+ true);
+- ovs_mutex_unlock(&pinctrl_mutex);
+ break;
- /* Called with in the pinctrl_handler thread context. */
-@@ -1595,7 +1594,6 @@ static void
- pinctrl_handle_buffered_packets(const struct ofputil_packet_in *pin,
- const struct ofpbuf *continuation,
- bool is_arp)
--OVS_REQUIRES(pinctrl_mutex)
- {
- const struct match *md = &pin->flow_metadata;
- struct mac_binding_data mb_data;
-@@ -1612,7 +1610,7 @@ OVS_REQUIRES(pinctrl_mutex)
- md->flow.regs[MFF_LOG_OUTPORT - MFF_REG0],
- ip, eth_addr_zero);
-
-- struct buffered_packets *bp = buffered_packets_add(&buffered_packets_ctx,
-+ struct buffered_packets *bp = buffered_packets_add(&buffered_packets_map,
- mb_data);
- if (!bp) {
- COVERAGE_INC(pinctrl_drop_buffered_packets_map);
-@@ -1642,9 +1640,7 @@ pinctrl_handle_arp(struct rconn *swconn, const struct flow *ip_flow,
- return;
- }
+ case ACTION_OPCODE_DHCP_RELAY_REQ_CHK:
+@@ -3808,16 +3813,12 @@ process_packet_in(struct rconn *swconn, const struct ofp_header *msg)
+ break;
-- ovs_mutex_lock(&pinctrl_mutex);
- pinctrl_handle_buffered_packets(pin, continuation, true);
-- ovs_mutex_unlock(&pinctrl_mutex);
+ case ACTION_OPCODE_PUT_ND:
+- ovs_mutex_lock(&pinctrl_mutex);
+ pinctrl_handle_put_mac_binding(&pin.flow_metadata.flow, &headers,
+ false);
+- ovs_mutex_unlock(&pinctrl_mutex);
+ break;
- /* Compose an ARP packet. */
- uint64_t packet_stub[128 / 8];
-@@ -4005,6 +4001,7 @@ pinctrl_recv(struct rconn *swconn, const struct ofp_header *oh,
- static void
- notify_pinctrl_handler(void)
- {
-+ COVERAGE_INC(pinctrl_notify_handler_thread);
- seq_change(pinctrl_handler_seq);
- }
+ case ACTION_OPCODE_PUT_FDB:
+- ovs_mutex_lock(&pinctrl_mutex);
+ pinctrl_handle_put_fdb(&pin.flow_metadata.flow, &headers);
+- ovs_mutex_unlock(&pinctrl_mutex);
+ break;
-@@ -4079,12 +4076,12 @@ pinctrl_handler(void *arg_)
- send_arp_nd_run(swconn, &send_arp_nd_time);
- send_ipv6_ras(swconn, &send_ipv6_ra_time);
- send_ipv6_prefixd(swconn, &send_prefixd_time);
-- send_mac_binding_buffered_pkts(swconn);
- bfd_monitor_send_msg(swconn, &bfd_time);
- ovs_mutex_unlock(&pinctrl_mutex);
- } else {
- lock_failed = true;
- }
-+ send_mac_binding_buffered_pkts(swconn);
- send_garp_rarp_run(swconn, &send_garp_rarp_time);
- ip_mcast_querier_run(swconn, &send_mcast_query_time);
- }
-@@ -4118,6 +4115,8 @@ pinctrl_handler(void *arg_)
- latch_wait(&pctrl->pinctrl_thread_exit);
+ case ACTION_OPCODE_PUT_DHCPV6_OPTS:
+@@ -4205,13 +4206,11 @@ pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
- ovsrcu_quiesce_start();
-+ /* Wake-up periodicaly for coverage counters sync.*/
-+ poll_timer_wait(1000);
- poll_block();
- }
+ main_seq = seq_read(pinctrl_main_seq);
-@@ -4222,11 +4221,6 @@ pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
- sbrec_port_binding_by_key,
- sbrec_igmp_groups,
- sbrec_ip_multicast_opts);
-- run_buffered_binding(mac_binding_table, local_datapaths,
+- run_put_mac_bindings(ovnsb_idl_txn, sbrec_datapath_binding_by_key,
- sbrec_port_binding_by_key,
-- sbrec_datapath_binding_by_key,
-- sbrec_port_binding_by_name,
- sbrec_mac_binding_by_lport_ip);
- sync_svc_monitors(ovnsb_idl_txn, svc_mon_table, sbrec_port_binding_by_name,
+ run_put_vport_bindings(ovnsb_idl_txn, sbrec_datapath_binding_by_key,
+ sbrec_port_binding_by_key, chassis, cur_cfg);
+ send_garp_rarp_prepare(ecmp_nh_table, chassis, ovs_table);
+- prepare_ipv6_ras(local_active_ports_ras, sbrec_port_binding_by_name);
++ prepare_ipv6_ras(local_active_ports_ras, sbrec_port_binding_by_name,
++ chassis);
+ prepare_ipv6_prefixd(ovnsb_idl_txn, sbrec_port_binding_by_name,
+ local_active_ports_ipv6_pd, chassis,
+ local_datapaths);
+@@ -4225,9 +4224,6 @@ pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
chassis);
bfd_monitor_run(ovnsb_idl_txn, bfd_table, sbrec_port_binding_by_name,
-@@ -4237,6 +4231,12 @@ pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
+ chassis);
+- run_put_fdbs(ovnsb_idl_txn, sbrec_port_binding_by_key,
+- sbrec_datapath_binding_by_key, sbrec_fdb_by_dp_key_mac,
+- cur_cfg);
run_activated_ports(ovnsb_idl_txn, sbrec_datapath_binding_by_key,
sbrec_port_binding_by_key, chassis);
ovs_mutex_unlock(&pinctrl_mutex);
-+
-+ run_buffered_binding(mac_binding_table, local_datapaths,
+@@ -4237,6 +4233,12 @@ pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
+ sbrec_datapath_binding_by_key,
+ sbrec_port_binding_by_name,
+ sbrec_mac_binding_by_lport_ip);
++ run_put_mac_bindings(ovnsb_idl_txn, sbrec_datapath_binding_by_key,
+ sbrec_port_binding_by_key,
-+ sbrec_datapath_binding_by_key,
-+ sbrec_port_binding_by_name,
+ sbrec_mac_binding_by_lport_ip);
++ run_put_fdbs(ovnsb_idl_txn, sbrec_port_binding_by_key,
++ sbrec_datapath_binding_by_key, sbrec_fdb_by_dp_key_mac,
++ cur_cfg);
}
/* Table of ipv6_ra_state structures, keyed on logical port name.
-@@ -4754,7 +4754,7 @@ pinctrl_destroy(void)
- destroy_send_arps_nds();
- destroy_ipv6_ras();
- destroy_ipv6_prefixd();
-- destroy_buffered_packets_ctx();
-+ destroy_buffered_packets_map();
+@@ -4265,7 +4267,7 @@ struct ipv6_ra_state {
+ struct ipv6_ra_config *config;
+ int64_t port_key;
+ int64_t metadata;
+- bool preserved;
++ bool override_local_only;
+ bool delete_me;
+ };
+
+@@ -4579,7 +4581,7 @@ ipv6_ra_send(struct rconn *swconn, struct ipv6_ra_state *ra)
+ put_load(dp_key, MFF_LOG_DATAPATH, 0, 64, &ofpacts);
+ put_load(port_key, MFF_LOG_INPORT, 0, 32, &ofpacts);
+ put_load(1, MFF_LOG_FLAGS, MLF_LOCAL_ONLY_BIT, 1, &ofpacts);
+- if (ra->preserved) {
++ if (ra->override_local_only) {
+ put_load(1, MFF_LOG_FLAGS, MLF_OVERRIDE_LOCAL_ONLY_BIT, 1, &ofpacts);
+ }
+ struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&ofpacts);
+@@ -4638,7 +4640,8 @@ send_ipv6_ras(struct rconn *swconn, long long int *send_ipv6_ra_time)
+ * thread context. */
+ static void
+ prepare_ipv6_ras(const struct shash *local_active_ports_ras,
+- struct ovsdb_idl_index *sbrec_port_binding_by_name)
++ struct ovsdb_idl_index *sbrec_port_binding_by_name,
++ const struct sbrec_chassis *chassis)
+ OVS_REQUIRES(pinctrl_mutex)
+ {
+ struct shash_node *iter;
+@@ -4694,11 +4697,27 @@ prepare_ipv6_ras(const struct shash *local_active_ports_ras,
+ */
+ ra->port_key = peer->tunnel_key;
+ ra->metadata = peer->datapath->tunnel_key;
+- ra->preserved = (!strcmp(pb->type,"l2gateway") ||
+- !strcmp(pb->type,"l3gateway") ||
+- !strcmp(pb->type,"chassisredirect"));
+ ra->delete_me = false;
+
++ /* All periodic RAs are local-only to prevent tunneling to
++ * remote chassis (each chassis generates its own for local
++ * VIFs). MLF_OVERRIDE_LOCAL_ONLY_BIT is set on the active
++ * gateway chassis so the RA passes the localnet anti-leak
++ * check and reaches the physical network. */
++ const char *crp = smap_get(&pb->options, "chassis-redirect-port");
++
++ if (!strcmp(pb->type, "l3gateway") ||
++ !strcmp(pb->type, "l2gateway") ||
++ !strcmp(pb->type, "chassisredirect")) {
++ ra->override_local_only =
++ lport_pb_is_chassis_resident(chassis, pb);
++ } else if (crp) {
++ ra->override_local_only = lport_is_chassis_resident(
++ sbrec_port_binding_by_name, chassis, crp);
++ } else {
++ ra->override_local_only = false;
++ }
++
+ /* pinctrl_handler thread will send the IPv6 RAs. */
+ }
+
+@@ -4724,14 +4743,17 @@ pinctrl_wait(struct ovsdb_idl_txn *ovnsb_idl_txn)
+ {
+ ovs_mutex_lock(&pinctrl_mutex);
+ if (ovnsb_idl_txn) {
+- wait_put_mac_bindings();
+ wait_controller_event();
+ wait_put_vport_bindings();
+- wait_put_fdbs();
+ seq_wait(pinctrl_main_seq, main_seq);
+ }
+ wait_activated_ports();
+ ovs_mutex_unlock(&pinctrl_mutex);
++
++ if (ovnsb_idl_txn) {
++ wait_put_mac_bindings();
++ wait_put_fdbs();
++ }
+ }
+
+ #define PINCTRL_CFG_INTERVAL 100
+@@ -4757,7 +4779,7 @@ pinctrl_destroy(void)
+ destroy_buffered_packets_map();
destroy_activated_ports();
event_table_destroy();
- destroy_put_mac_bindings();
-@@ -4839,30 +4839,24 @@ pinctrl_handle_put_mac_binding(const struct flow *md,
- notify_pinctrl_main();
+- destroy_put_mac_bindings();
++ destroy_mac_bindings();
+ destroy_put_vport_bindings();
+ ip_mcast_snoop_destroy();
+ destroy_svc_monitors();
+@@ -4782,22 +4804,31 @@ pinctrl_destroy(void)
+
+ #define MAX_MAC_BINDING_DELAY_MSEC 50
+ #define MAX_FDB_DELAY_MSEC 50
+-#define MAX_MAC_BINDINGS 1000
++#define MAX_MAC_BINDINGS 1024
++#define MAC_BINDINGS_MC_COOKIE 0xff
+
+-/* Contains "struct mac_binding"s. */
++/* Contains "struct mac_binding"s. This is used by main thread only. */
+ static struct hmap put_mac_bindings;
++/* Contains "struct mac_binding_data". This is populated by pinctrl thread
++ * and consumed by main thread. */
++static struct spsc_ring mac_bindings_ring;
+
+ static void
+-init_put_mac_bindings(void)
++init_mac_bindings(void)
+ {
+ hmap_init(&put_mac_bindings);
++ /* Make the capacity larger than the hmap to account for some in-flight
++ * duplicates. */
++ spsc_ring_init(&mac_bindings_ring, MAX_MAC_BINDINGS * 4,
++ sizeof (struct mac_binding_data));
}
--#define READY_PACKETS_VEC_CAPACITY_THRESHOLD 1024
--
- /* Called with in the pinctrl_handler thread context. */
static void
- send_mac_binding_buffered_pkts(struct rconn *swconn)
+-destroy_put_mac_bindings(void)
++destroy_mac_bindings(void)
+ {
+ mac_bindings_clear(&put_mac_bindings);
+ hmap_destroy(&put_mac_bindings);
++ spsc_ring_destroy(&mac_bindings_ring);
+ }
+
+ /* Called with in the pinctrl_handler thread context. */
+@@ -4805,14 +4836,11 @@ static void
+ pinctrl_handle_put_mac_binding(const struct flow *md,
+ const struct flow *headers,
+ bool is_arp)
- OVS_REQUIRES(pinctrl_mutex)
{
- enum ofp_version version = rconn_get_version(swconn);
- enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version);
-- struct vector *rpd = &buffered_packets_ctx.ready_packets_data;
-+ struct vector rpd = VECTOR_EMPTY_INITIALIZER(struct bp_packet_data);
-+
-+ buffered_packets_run(&buffered_packets_map, &rpd);
-
- struct bp_packet_data *pd;
-- VECTOR_FOR_EACH_PTR (rpd, pd) {
-+ VECTOR_FOR_EACH_PTR (&rpd, pd) {
- queue_msg(swconn, ofputil_encode_resume(&pd->pin, pd->continuation,
- proto));
- bp_packet_data_destroy(pd);
+- if (hmap_count(&put_mac_bindings) >= MAX_MAC_BINDINGS) {
+- COVERAGE_INC(pinctrl_drop_put_mac_binding);
+- return;
+- }
+-
+ struct mac_binding_data mb_data = (struct mac_binding_data) {
++ .cookie = eth_addr_is_multicast(headers->dl_dst)
++ ? MAC_BINDINGS_MC_COOKIE
++ : 0,
+ .dp_key = ntohll(md->metadata),
+ .port_key = md->regs[MFF_LOG_INPORT - MFF_REG0],
+ .mac = headers->dl_src,
+@@ -4825,18 +4853,13 @@ pinctrl_handle_put_mac_binding(const struct flow *md,
+ memcpy(&mb_data.ip, &ip6, sizeof mb_data.ip);
}
-- vector_clear(rpd);
-- if (vector_capacity(rpd) >= READY_PACKETS_VEC_CAPACITY_THRESHOLD) {
-- VLOG_DBG("The ready_packets_data vector capacity (%"PRIuSIZE") "
-- "is over threshold.", vector_capacity(rpd));
-- vector_shrink_to_fit(rpd);
-- }
-+ vector_destroy(&rpd);
+- /* If the ARP reply was unicast we should not delay it,
+- * there won't be any race. */
+- uint32_t delay = eth_addr_is_multicast(headers->dl_dst)
+- ? random_range(MAX_MAC_BINDING_DELAY_MSEC) + 1
+- : 0;
+- long long timestamp = time_msec() + delay;
+- mac_binding_add(&put_mac_bindings, mb_data, NULL, timestamp);
+-
+- /* We can send the buffered packet once the main ovn-controller
+- * thread calls pinctrl_run() and it writes the mac_bindings stored
+- * in 'put_mac_bindings' hmap into the Southbound MAC_Binding table. */
+- notify_pinctrl_main();
++ if (!spsc_ring_push(&mac_bindings_ring, &mb_data)) {
++ /* This shouldn't happen, the main thread drains the ring buffer
++ * every iteration. */
++ COVERAGE_INC(pinctrl_ring_full_put_mac_binding);
++ } else {
++ notify_pinctrl_main();
++ }
}
- static void
-@@ -4931,9 +4925,8 @@ run_buffered_binding(const struct sbrec_mac_binding_table *mac_binding_table,
+ /* Called with in the pinctrl_handler thread context. */
+@@ -4887,7 +4910,7 @@ run_put_mac_binding(struct ovsdb_idl_txn *ovnsb_idl_txn,
+ ipv6_format_mapped(&mb->data.ip, &ip_s);
+ mac_binding_add_to_sb(ovnsb_idl_txn, sbrec_mac_binding_by_lport_ip,
+ pb->logical_port, pb->datapath, mb->data.mac,
+- ds_cstr(&ip_s), false);
++ ds_cstr(&ip_s), false, NULL);
+ ds_destroy(&ip_s);
+ }
+
+@@ -4898,14 +4921,32 @@ run_put_mac_bindings(struct ovsdb_idl_txn *ovnsb_idl_txn,
struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
- struct ovsdb_idl_index *sbrec_port_binding_by_name,
+ struct ovsdb_idl_index *sbrec_port_binding_by_key,
struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip)
- OVS_REQUIRES(pinctrl_mutex)
{
-- if (!buffered_packets_ctx_has_packets(&buffered_packets_ctx)) {
-+ if (cmap_is_empty(&buffered_packets_map)) {
++ long long now = time_msec();
++
++ struct mac_binding_data mb_data;
++ SPSC_RING_FOR_EACH_POP (&mac_bindings_ring, mb_data) {
++ if (hmap_count(&put_mac_bindings) >= MAX_MAC_BINDINGS) {
++ COVERAGE_INC(pinctrl_drop_put_mac_binding);
++ continue;
++ }
++
++ /* If the ARP reply was unicast we should not delay it,
++ * there won't be any race. */
++ uint32_t delay = mb_data.cookie == MAC_BINDINGS_MC_COOKIE
++ ? random_range(MAX_MAC_BINDING_DELAY_MSEC) + 1
++ : 0;
++ /* Clear the cookie, it's needed just to know if it was delayed or
++ * not. */
++ mb_data.cookie = 0;
++
++ mac_binding_add(&put_mac_bindings, mb_data, NULL, now + delay);
++ }
++
+ if (!ovnsb_idl_txn) {
return;
}
-@@ -4979,18 +4972,16 @@ run_buffered_binding(const struct sbrec_mac_binding_table *mac_binding_table,
- mac_binding_add(&recent_mbs, mb_data, smb, 0);
+- long long now = time_msec();
+-
+ struct mac_binding *mb;
+ HMAP_FOR_EACH_SAFE (mb, hmap_node, &put_mac_bindings) {
+ if (now >= mb->timestamp) {
+@@ -4986,7 +5027,6 @@ run_buffered_binding(const struct sbrec_mac_binding_table *mac_binding_table,
+
+ static void
+ wait_put_mac_bindings(void)
+- OVS_REQUIRES(pinctrl_mutex)
+ {
+ struct mac_binding *mb;
+ HMAP_FOR_EACH (mb, hmap_node, &put_mac_bindings) {
+@@ -5151,7 +5191,8 @@ send_arp_nd_update(const struct sbrec_port_binding *pb, const char *nexthop,
+ void
+ send_self_originated_neigh_packet(struct rconn *swconn,
+ uint32_t dp_key, uint32_t port_key,
+- struct eth_addr eth,
++ struct eth_addr eth_src,
++ struct eth_addr eth_dst,
+ struct in6_addr *local,
+ struct in6_addr *target,
+ uint8_t table_id)
+@@ -5160,13 +5201,15 @@ send_self_originated_neigh_packet(struct rconn *swconn,
+ struct dp_packet packet;
+ dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
+ if (!local) {
+- compose_rarp(&packet, eth);
++ compose_rarp(&packet, eth_src);
+ } else if (IN6_IS_ADDR_V4MAPPED(local)) {
+- compose_arp(&packet, ARP_OP_REQUEST, eth, eth_addr_zero, true,
++ compose_arp(&packet, ARP_OP_REQUEST, eth_src, eth_dst,
++ eth_addr_is_zero(eth_dst),
+ in6_addr_get_mapped_ipv4(local),
+ in6_addr_get_mapped_ipv4(target));
+ } else {
+- compose_nd_ns(&packet, eth, local, target);
++ compose_nd_ns(&packet, eth_addr_is_zero(eth_dst), eth_src,
++ eth_dst, local, target);
}
-- buffered_packets_ctx_run(&buffered_packets_ctx, &recent_mbs,
-- sbrec_port_binding_by_key,
-- sbrec_datapath_binding_by_key,
-- sbrec_port_binding_by_name,
-- sbrec_mac_binding_by_lport_ip);
-+ if (buffered_packets_lookup_run(&buffered_packets_map, &recent_mbs,
-+ sbrec_port_binding_by_key,
-+ sbrec_datapath_binding_by_key,
-+ sbrec_port_binding_by_name,
-+ sbrec_mac_binding_by_lport_ip)) {
-+ notify_pinctrl_handler();
-+ }
+ /* Inject GARP request. */
+@@ -5221,13 +5264,15 @@ send_garp_rarp(struct rconn *swconn, struct garp_rarp_node *garp_rarp,
+ send_self_originated_neigh_packet(swconn,
+ garp_rarp->dp_key,
+ garp_rarp->port_key,
+- garp_rarp->ea, &addr, &addr,
++ garp_rarp->ea, eth_addr_zero,
++ &addr, &addr,
+ OFTABLE_LOG_INGRESS_PIPELINE);
+ } else {
+ send_self_originated_neigh_packet(swconn,
+ garp_rarp->dp_key,
+ garp_rarp->port_key,
+- garp_rarp->ea, NULL, NULL,
++ garp_rarp->ea, eth_addr_zero,
++ NULL, NULL,
+ OFTABLE_LOG_INGRESS_PIPELINE);
+ }
- mac_bindings_clear(&recent_mbs);
- hmap_destroy(&recent_mbs);
--
-- if (buffered_packets_ctx_is_ready_to_send(&buffered_packets_ctx)) {
-- notify_pinctrl_handler();
+@@ -6339,7 +6384,8 @@ send_arp_nd(struct rconn *swconn, struct arp_nd_data *e,
+ /* Compose a ARP request packet. */
+ send_self_originated_neigh_packet(swconn,
+ e->dp_key, e->port_key,
+- e->ea, &e->src_ip, &e->dst_ip,
++ e->ea, eth_addr_zero,
++ &e->src_ip, &e->dst_ip,
+ OFTABLE_LOCAL_OUTPUT);
+
+ /* Set the next announcement. At most 5 announcements are sent for a
+@@ -6524,32 +6570,19 @@ pinctrl_handle_nd_ns(struct rconn *swconn, const struct flow *ip_flow,
+
+ in6_generate_lla(ip_flow->dl_src, &ipv6_src);
+
+- /* We might be here without actually currently handling an IPv6 packet.
+- * This can happen in the case where we route IPv4 packets over an IPv6
+- * link (e.g. RFC 5549 / BGP unnumbered, where an IPv4 destination is
+- * resolved via an IPv6 link-local nexthop).
+- *
+- * In that case we have no destination IPv6 address in the trigger packet
+- * to reuse. compose_nd_ns() needs a valid destination so it can derive
+- * the correct solicited-node multicast (ff02::1:ff{addr[13:16]}) for
+- * eth.dst and ip6.dst -- userdata only sets nd.target on the new packet
+- * and does not rewrite ip6.dst, so a wrong ipv6_dst here egresses on the
+- * wire as-is.
+- *
+- * The fallback nd_ns logical flow in S_ROUTER_IN_ARP_REQUEST stores the
+- * actual IPv6 nexthop in xxreg0 (REG_NEXT_HOP_IPV6) before invoking the
+- * nd_ns action, so for the IPv4-over-IPv6 case read xxreg0 from the
+- * trigger packet's flow metadata. */
+- struct in6_addr ipv6_dst = IN6ADDR_EXACT_INIT;
+- if (get_dl_type(ip_flow) == htons(ETH_TYPE_IPV6)) {
+- ipv6_dst = ip_flow->ipv6_dst;
+- } else {
+- ovs_be128 nexthop_be =
+- hton128(flow_get_xxreg(&pin->flow_metadata.flow, 0));
+- memcpy(&ipv6_dst, &nexthop_be, sizeof ipv6_dst);
- }
+- compose_nd_ns(&packet, ip_flow->dl_src, &ipv6_src,
+- &ipv6_dst);
++ /* The nd_ns logical flow in S_ROUTER_IN_ARP_REQUEST stores the actual
++ * IPv6 nexthop in xxreg0 (REG_NEXT_HOP_IPV6) before invoking the
++ * nd_ns action. Read the nexthop and pass it to compose_nd_ns()
++ * which derives the solicited-node multicast for eth.dst and
++ * ip6.dst. */
++ struct in6_addr ipv6_nh = IN6ADDR_EXACT_INIT;
++
++ ovs_be128 nexthop_be =
++ hton128(flow_get_xxreg(&pin->flow_metadata.flow, 0));
++ memcpy(&ipv6_nh, &nexthop_be, sizeof ipv6_nh);
++
++ compose_nd_ns(&packet, true, ip_flow->dl_src, eth_addr_zero, &ipv6_src,
++ &ipv6_nh);
+
+ /* Reload previous packet metadata and set actions from userdata. */
+ set_actions_and_enqueue_msg(swconn, &packet,
+@@ -8878,9 +8911,13 @@ pinctrl_split_buf_action_handler(struct rconn *swconn, struct dp_packet *pkt,
+ ofpbuf_uninit(&ofpacts);
+ }
+
+-#define MAX_FDB_ENTRIES 1000
++#define MAX_FDB_ENTRIES 1024
+
++/* Contains "struct fdb"s. This is used by main thread only. */
+ static struct hmap put_fdbs;
++/* Contains "struct fdb_data". This is populated by pinctrl thread
++ * and consumed by main thread. */
++static struct spsc_ring fdbs_ring;
+
+ /* MAC learning (fdb) related functions. Runs within the main
+ * ovn-controller thread context. */
+@@ -8889,6 +8926,9 @@ static void
+ init_fdb_entries(void)
+ {
+ hmap_init(&put_fdbs);
++ /* Make the capacity larger than the hmap to account for some in-flight
++ * duplicates. */
++ spsc_ring_init(&fdbs_ring, MAX_FDB_ENTRIES * 4, sizeof (struct fdb_data));
}
static void
-@@ -5819,6 +5810,9 @@ ip_mcast_sync(struct ovsdb_idl_txn *ovnsb_idl_txn,
- struct ip_mcast_snoop_cfg cfg;
- bool flush_groups = false;
+@@ -8896,6 +8936,7 @@ destroy_fdb_entries(void)
+ {
+ fdbs_clear(&put_fdbs);
+ hmap_destroy(&put_fdbs);
++ spsc_ring_destroy(&fdbs_ring);
+ }
-+ if (!get_local_datapath(local_datapaths, dp_key)) {
+ static const struct sbrec_fdb *
+@@ -8975,13 +9016,24 @@ run_put_fdbs(struct ovsdb_idl_txn *ovnsb_idl_txn,
+ struct ovsdb_idl_index *sbrec_port_binding_by_key,
+ struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
+ struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac, uint64_t cur_cfg)
+- OVS_REQUIRES(pinctrl_mutex)
+ {
++ long long now = time_msec();
++
++ struct fdb_data fdb_data;
++ SPSC_RING_FOR_EACH_POP (&fdbs_ring, fdb_data) {
++ if (hmap_count(&put_fdbs) >= MAX_FDB_ENTRIES) {
++ COVERAGE_INC(pinctrl_drop_put_fdb);
+ continue;
+ }
- ip_mcast_snoop_cfg_load(&cfg, ip_mcast);
- if (ip_mcast_snoop_state_update(dp_key, &cfg, &flush_groups)) {
- notify = true;
-@@ -6431,7 +6425,7 @@ may_inject_pkts(void)
- !cmap_is_empty(&garp_rarp_get_data()->data) ||
- ipv6_prefixd_should_inject() ||
- !ovs_list_is_empty(&mcast_query_list) ||
-- buffered_packets_ctx_is_ready_to_send(&buffered_packets_ctx) ||
-+ !cmap_is_empty(&buffered_packets_map) ||
- bfd_monitor_should_inject());
- }
-
-@@ -6521,9 +6515,7 @@ pinctrl_handle_nd_ns(struct rconn *swconn, const struct flow *ip_flow,
++
++ uint32_t delay = random_range(MAX_FDB_DELAY_MSEC) + 1;
++ fdb_add(&put_fdbs, fdb_data, now + delay);
++ }
++
+ if (!ovnsb_idl_txn) {
return;
}
-- ovs_mutex_lock(&pinctrl_mutex);
- pinctrl_handle_buffered_packets(pin, continuation, false);
-- ovs_mutex_unlock(&pinctrl_mutex);
+- long long now = time_msec();
+ struct fdb *fdb;
+ HMAP_FOR_EACH_SAFE (fdb, hmap_node, &put_fdbs) {
+ if (fdb->cfg >= 0 && pinctrl_is_sb_commited(fdb->cfg, cur_cfg)) {
+@@ -8997,7 +9049,6 @@ run_put_fdbs(struct ovsdb_idl_txn *ovnsb_idl_txn,
- uint64_t packet_stub[128 / 8];
- struct dp_packet packet;
-@@ -6534,17 +6526,27 @@ pinctrl_handle_nd_ns(struct rconn *swconn, const struct flow *ip_flow,
-
- /* We might be here without actually currently handling an IPv6 packet.
- * This can happen in the case where we route IPv4 packets over an IPv6
-- * link.
-- * In these cases we have no destination IPv6 address from the packet that
-- * we can reuse. But we receive the actual destination IPv6 address via
-- * userdata anyway, so what we pass to compose_nd_ns is irrelevant.
-- * This is just a hope since we do not parse the userdata. If we land here
-- * for whatever reason without being an IPv6 packet and without userdata we
-- * will send out a wrong packet.
-- */
-+ * link (e.g. RFC 5549 / BGP unnumbered, where an IPv4 destination is
-+ * resolved via an IPv6 link-local nexthop).
-+ *
-+ * In that case we have no destination IPv6 address in the trigger packet
-+ * to reuse. compose_nd_ns() needs a valid destination so it can derive
-+ * the correct solicited-node multicast (ff02::1:ff{addr[13:16]}) for
-+ * eth.dst and ip6.dst -- userdata only sets nd.target on the new packet
-+ * and does not rewrite ip6.dst, so a wrong ipv6_dst here egresses on the
-+ * wire as-is.
-+ *
-+ * The fallback nd_ns logical flow in S_ROUTER_IN_ARP_REQUEST stores the
-+ * actual IPv6 nexthop in xxreg0 (REG_NEXT_HOP_IPV6) before invoking the
-+ * nd_ns action, so for the IPv4-over-IPv6 case read xxreg0 from the
-+ * trigger packet's flow metadata. */
- struct in6_addr ipv6_dst = IN6ADDR_EXACT_INIT;
- if (get_dl_type(ip_flow) == htons(ETH_TYPE_IPV6)) {
- ipv6_dst = ip_flow->ipv6_dst;
+ static void
+ wait_put_fdbs(void)
+- OVS_REQUIRES(pinctrl_mutex)
+ {
+ struct fdb *fdb;
+ HMAP_FOR_EACH (fdb, hmap_node, &put_fdbs) {
+@@ -9008,23 +9059,20 @@ wait_put_fdbs(void)
+ /* Called with in the pinctrl_handler thread context. */
+ static void
+ pinctrl_handle_put_fdb(const struct flow *md, const struct flow *headers)
+- OVS_REQUIRES(pinctrl_mutex)
+ {
+- if (hmap_count(&put_fdbs) >= MAX_FDB_ENTRIES) {
+- COVERAGE_INC(pinctrl_drop_put_fdb);
+- return;
+- }
+-
+ struct fdb_data fdb_data = (struct fdb_data) {
+ .dp_key = ntohll(md->metadata),
+ .port_key = md->regs[MFF_LOG_INPORT - MFF_REG0],
+ .mac = headers->dl_src,
+ };
+
+- uint32_t delay = random_range(MAX_FDB_DELAY_MSEC) + 1;
+- long long timestamp = time_msec() + delay;
+- fdb_add(&put_fdbs, fdb_data, timestamp);
+- notify_pinctrl_main();
++ if (!spsc_ring_push(&fdbs_ring, &fdb_data)) {
++ /* This shouldn't happen, the main thread drains the ring buffer
++ * every iteration. */
++ COVERAGE_INC(pinctrl_ring_full_put_fdb);
+ } else {
-+ ovs_be128 nexthop_be =
-+ hton128(flow_get_xxreg(&pin->flow_metadata.flow, 0));
-+ memcpy(&ipv6_dst, &nexthop_be, sizeof ipv6_dst);
++ notify_pinctrl_main();
++ }
+ }
+
+ /* This function sets the register bit 'MLF_FROM_CTRL_BIT'
+diff --git a/controller/pinctrl.h b/controller/pinctrl.h
+index 80384ac9ba..0667ac34ee 100644
+--- a/controller/pinctrl.h
++++ b/controller/pinctrl.h
+@@ -79,7 +79,8 @@ struct ovs_list *get_ports_to_activate_in_engine(void);
+ bool pinctrl_is_port_activated(int64_t dp_key, int64_t port_key);
+ void send_self_originated_neigh_packet(struct rconn *swconn,
+ uint32_t dp_key, uint32_t port_key,
+- struct eth_addr eth,
++ struct eth_addr eth_src,
++ struct eth_addr eth_dst,
+ struct in6_addr *local,
+ struct in6_addr *target,
+ uint8_t table_id);
+diff --git a/controller/route-exchange.c b/controller/route-exchange.c
+index 82fcc20fb0..4ec3499365 100644
+--- a/controller/route-exchange.c
++++ b/controller/route-exchange.c
+@@ -87,7 +87,7 @@ maintained_route_table_add(uint32_t table_id)
+ hmap_insert(&_maintained_route_tables, &mrt->node, hash);
+ }
+
+-static void
++static struct route_entry *
+ route_add_entry(struct hmap *routes,
+ const struct sbrec_learned_route *sb_route,
+ bool stale)
+@@ -103,6 +103,7 @@ route_add_entry(struct hmap *routes,
+ hash = hash_string(sb_route->ip_prefix, hash);
+
+ hmap_insert(routes, &route_e->hmap_node, hash);
++ return route_e;
+ }
+
+ static struct route_entry *
+@@ -145,17 +146,36 @@ sb_sync_learned_routes(const struct vector *learned_routes,
+ struct ovsdb_idl_txn *ovnsb_idl_txn,
+ struct ovsdb_idl_index *sbrec_port_binding_by_name,
+ struct ovsdb_idl_index *sbrec_learned_route_by_datapath,
+- bool *sb_changes_pending)
++ bool *sb_changes_pending,
++ const struct sbrec_chassis *chassis)
+ {
+ struct hmap sync_routes = HMAP_INITIALIZER(&sync_routes);
+ const struct sbrec_learned_route *sb_route;
+- struct route_entry *route_e;
++ struct hmapx lrp_with_dr_port_name =
++ HMAPX_INITIALIZER(&lrp_with_dr_port_name);
+
+ struct sbrec_learned_route *filter =
+ sbrec_learned_route_index_init_row(sbrec_learned_route_by_datapath);
+ sbrec_learned_route_index_set_datapath(filter, datapath);
+ SBREC_LEARNED_ROUTE_FOR_EACH_EQUAL (sb_route, filter,
+ sbrec_learned_route_by_datapath) {
++ const struct sbrec_port_binding *cr_pb =
++ lport_get_cr_port(sbrec_port_binding_by_name,
++ sb_route->logical_port, NULL);
++ struct route_entry *route_e = NULL;
++
++ /* Collect the set of unique logical ports we learned routes on. The
++ * (potentially expensive) dynamic-routing-port-name lookups are
++ * postponed until after the loop so that they are performed once per
++ * logical port instead of once per learned route. */
++ hmapx_add(&lrp_with_dr_port_name,
++ CONST_CAST(void *, sb_route->logical_port));
++
++ if (sb_route->logical_port->chassis == chassis ||
++ (cr_pb && cr_pb->chassis == chassis)) {
++ route_e = route_add_entry(&sync_routes, sb_route, false);
++ }
++
+ /* If the port is not local we don't care about it.
+ * Some other ovn-controller will handle it.
+ * We may not use smap_get since the value might be validly NULL. */
+@@ -163,10 +183,43 @@ sb_sync_learned_routes(const struct vector *learned_routes,
+ sb_route->logical_port->logical_port)) {
+ continue;
+ }
++ if (route_e) {
++ route_e->stale = true;
++ continue;
++ }
+ route_add_entry(&sync_routes, sb_route, true);
+ }
+ sbrec_learned_route_index_destroy_row(filter);
+
++ /* Drop the logical ports that don't have a dynamic-routing-port-name set,
++ * either directly or via their distributed gateway port. */
++ struct hmapx_node *lrp_node;
++ HMAPX_FOR_EACH_SAFE (lrp_node, &lrp_with_dr_port_name) {
++ const struct sbrec_port_binding *lrp = lrp_node->data;
++ const struct sbrec_port_binding *cr_pb =
++ lport_get_cr_port(sbrec_port_binding_by_name, lrp, NULL);
++ const char *dynamic_routing_port_name =
++ smap_get(&lrp->options, "dynamic-routing-port-name");
++ if (!dynamic_routing_port_name && cr_pb) {
++ dynamic_routing_port_name =
++ smap_get(&cr_pb->options, "dynamic-routing-port-name");
++ }
++ if (!dynamic_routing_port_name) {
++ hmapx_delete(&lrp_with_dr_port_name, lrp_node);
++ }
++ }
++
++ if (!hmapx_is_empty(&lrp_with_dr_port_name)) {
++ struct route_entry *route_e;
++ HMAP_FOR_EACH (route_e, hmap_node, &sync_routes) {
++ if (!hmapx_contains(&lrp_with_dr_port_name,
++ route_e->sb_route->logical_port)) {
++ route_e->stale = true;
++ }
++ }
++ }
++ hmapx_destroy(&lrp_with_dr_port_name);
++
+ struct re_nl_received_route_node *learned_route;
+ VECTOR_FOR_EACH_PTR (learned_routes, learned_route) {
+ char *ip_prefix = normalize_v46_prefix(&learned_route->prefix,
+@@ -196,8 +249,9 @@ sb_sync_learned_routes(const struct vector *learned_routes,
+ continue;
+ }
+
+- route_e = route_lookup(&sync_routes, datapath,
+- logical_port, ip_prefix, nexthop);
++ struct route_entry *route_e =
++ route_lookup(&sync_routes, datapath,
++ logical_port, ip_prefix, nexthop);
+ if (route_e) {
+ route_e->stale = false;
+ } else {
+@@ -218,6 +272,7 @@ sb_sync_learned_routes(const struct vector *learned_routes,
+ free(nexthop);
+ }
+
++ struct route_entry *route_e;
+ HMAP_FOR_EACH_POP (route_e, hmap_node, &sync_routes) {
+ if (route_e->stale) {
+ sbrec_learned_route_delete(route_e->sb_route);
+@@ -364,7 +419,8 @@ route_exchange_run(const struct route_exchange_ctx_in *r_ctx_in,
+ r_ctx_in->ovnsb_idl_txn,
+ r_ctx_in->sbrec_port_binding_by_name,
+ sbrec_learned_route_by_datapath,
+- &r_ctx_out->sb_changes_pending);
++ &r_ctx_out->sb_changes_pending,
++ r_ctx_in->chassis);
+ }
+ route_table_add_watch_request(&r_ctx_out->route_table_watches,
+ arte->table_id);
+diff --git a/controller/route-exchange.h b/controller/route-exchange.h
+index e3791c331a..53828a8b9b 100644
+--- a/controller/route-exchange.h
++++ b/controller/route-exchange.h
+@@ -24,6 +24,7 @@ struct route_exchange_ctx_in {
+ struct ovsdb_idl_txn *ovnsb_idl_txn;
+ struct ovsdb_idl_index *sbrec_port_binding_by_name;
+ struct ovsdb_idl_index *sbrec_learned_route_by_datapath;
++ const struct sbrec_chassis *chassis;
+
+ /* Contains struct advertise_datapath_entry */
+ const struct hmap *announce_routes;
+diff --git a/controller/statctrl.c b/controller/statctrl.c
+index f9f0a30f1e..00c0a44503 100644
+--- a/controller/statctrl.c
++++ b/controller/statctrl.c
+@@ -64,7 +64,8 @@ struct stats_node {
+ struct ofputil_flow_stats *ofp_stats);
+ /* Function to process the parsed stats.
+ * This function runs in main thread locked behind mutex. */
+- void (*run)(struct vector *stats, uint64_t *req_delay, void *data);
++ void (*run)(struct vector *stats, uint64_t *req_delay, void *data,
++ long long timewall_now);
+ /* Name of the stats node. */
+ const char *name;
+ };
+@@ -194,6 +195,7 @@ statctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
+
+ bool schedule_updated = false;
+ long long now = time_msec();
++ long long timewall_now = time_wall_msec();
+
+ ovs_mutex_lock(&mutex);
+ statctrl_ctx.new_main_seq = seq_read(statctrl_ctx.main_seq);
+@@ -202,7 +204,8 @@ statctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
+ uint64_t prev_delay = node->request_delay;
+
+ stopwatch_start(node->name, time_msec());
+- node->run(&node->stats, &node->request_delay, node_data[i]);
++ node->run(&node->stats, &node->request_delay, node_data[i],
++ timewall_now);
+ vector_clear(&node->stats);
+ if (vector_capacity(&node->stats) >= STATS_VEC_CAPACITY_THRESHOLD) {
+ VLOG_DBG("The statistics vector for node '%s' capacity "
+diff --git a/debian/ovn-host.init b/debian/ovn-host.init
+index a34d2926cc..55761311ec 100755
+--- a/debian/ovn-host.init
++++ b/debian/ovn-host.init
+@@ -18,7 +18,7 @@ test -x /usr/share/ovn/scripts/ovn-ctl || exit 0
+ _SYSTEMCTL_SKIP_REDIRECT=yes
+ SYSTEMCTL_SKIP_REDIRECT=yes
+
+-. /usr/share/ovn/scripts/ovs-lib
++. /usr/share/ovn/scripts/ovn-lib
+ if [ -e /etc/default/ovn-host ]; then
+ . /etc/default/ovn-host
+ fi
+diff --git a/ic/ovn-ic.c b/ic/ovn-ic.c
+index 1aa8af9b94..e4ad1c0121 100644
+--- a/ic/ovn-ic.c
++++ b/ic/ovn-ic.c
+@@ -1429,9 +1429,7 @@ get_nexthop_from_lport_addresses(bool is_v4,
+ return true;
+ }
+
+- /* ipv6 link local */
+- in6_generate_lla(laddr->ea, nexthop);
+- return true;
++ return false;
+ }
+
+ static bool
+@@ -1758,7 +1756,7 @@ add_lb_vip_to_routes_ad(struct hmap *routes_ad, const char *vip_key,
+ if (!get_nexthop_from_lport_addresses(IN6_IS_ADDR_V4MAPPED(&vip_ip),
+ nexthop_addresses,
+ &nexthop)) {
+- VLOG_WARN_RL(&rl, "Route ad: failed to get nexthop for lb vip");
++ VLOG_DBG("Route ad: failed to get nexthop for lb vip %s", vip_key);
+ goto out;
+ }
+
+@@ -2330,6 +2328,32 @@ advertise_routes(struct ic_context *ctx,
}
- compose_nd_ns(&packet, ip_flow->dl_src, &ipv6_src,
- &ipv6_dst);
-diff --git a/lib/expr.c b/lib/expr.c
-index 288e245c65..3c506c42c0 100644
---- a/lib/expr.c
-+++ b/lib/expr.c
-@@ -2366,7 +2366,7 @@ compare_expr_sort(const void *a_, const void *b_)
+ }
+
++static void
++collect_learned_routes(struct ic_router_info *ic_lr)
++{
++ const struct nbrec_logical_router *lr = ic_lr->lr;
++
++ /* Check static routes of the LR and collect learned routes */
++ for (int i = 0; i < lr->n_static_routes; i++) {
++ const struct nbrec_logical_router_static_route *nb_route
++ = lr->static_routes[i];
++ struct uuid isb_uuid;
++ if (smap_get_uuid(&nb_route->external_ids, "ic-learned-route",
++ &isb_uuid)) {
++ /* It is a learned route */
++ if (!add_to_routes_learned(&ic_lr->routes_learned, nb_route, lr,
++ &isb_uuid)) {
++ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
++ VLOG_WARN_RL(&rl, "Bad format of learned route in NB: "
++ "%s -> %s. Delete it.", nb_route->ip_prefix,
++ nb_route->nexthop);
++ nbrec_logical_router_update_static_routes_delvalue(lr,
++ nb_route);
++ }
++ }
++ }
++}
++
+ static void
+ build_ts_routes_to_adv(struct ic_context *ctx,
+ struct ic_router_info *ic_lr,
+@@ -2451,6 +2475,7 @@ collect_lr_routes(struct ic_context *ctx,
+
+ struct hmap *routes_ad;
+ const struct icnbrec_transit_switch *t_sw;
++ bool routes_built = false;
+ VECTOR_FOR_EACH (&ic_lr->isb_pbs, isb_pb) {
+ key = icnbrec_transit_switch_index_init_row(
+ ctx->icnbrec_transit_switch_by_name);
+@@ -2486,10 +2511,17 @@ collect_lr_routes(struct ic_context *ctx,
+ route_table = "";
+ route_tag = "";
}
++ routes_built = true;
+ build_ts_routes_to_adv(ctx, ic_lr, routes_ad, &ts_port_addrs,
+ nb_global, route_table, route_tag, lrp);
+ destroy_lport_addresses(&ts_port_addrs);
+ }
++ /* If no port binding had valid addresses (e.g. LR disabled
++ * and PB address cleared simultaneously), collect learned routes so
++ * they can be deleted by sync_learned_routes(). */
++ if (!routes_built) {
++ collect_learned_routes(ic_lr);
++ }
+ }
- enum expr_type a_type = a->expr->type;
-- enum expr_type b_type = a->expr->type;
-+ enum expr_type b_type = b->expr->type;
- return a_type < b_type ? -1 : a_type > b_type;
- } else if (a->type == EXPR_T_AND || a->type == EXPR_T_OR) {
- size_t a_len = ovs_list_size(&a->expr->andor);
-diff --git a/lib/ovn-util.h b/lib/ovn-util.h
-index 4ea5db1e22..4ef93c470b 100644
---- a/lib/ovn-util.h
-+++ b/lib/ovn-util.h
-@@ -185,6 +185,7 @@ struct ovsdb_idl_txn *run_idl_loop(struct ovsdb_idl_loop *idl_loop,
- #define OVN_EVPN_KEY_FLAG 31
- #define OVN_MIN_EVPN_KEY (1u << OVN_EVPN_KEY_FLAG)
- #define OVN_MAX_EVPN_KEY (OVN_MAX_DP_GLOBAL_NUM | OVN_MIN_EVPN_KEY)
-+#define OVN_IS_EVPN_KEY(key) (((key) & OVN_MIN_EVPN_KEY) == OVN_MIN_EVPN_KEY)
-
- struct hmap;
- void ovn_destroy_tnlids(struct hmap *tnlids);
-@@ -548,8 +549,8 @@ dynamic_bitmap_last_set(const struct dynamic_bitmap *db)
- continue;
+ static void
+@@ -3893,7 +3925,7 @@ main(int argc, char *argv[])
+ ovsdb_idl_wait(ovnisb_idl_loop.idl);
+
+ /* Force a full recompute next time we become active. */
+- inc_proc_ic_force_recompute_immediate();
++ inc_proc_ic_force_recompute();
}
-- return (BITMAP_ULONG_BITS - 1) - raw_clz64(db->map[i])
-- + (BITMAP_ULONG_BITS * i);
-+ /* raw_clz64() operates on values promoted to uint64_t (64-bit). */
-+ return 63 - raw_clz64(db->map[i]) + (BITMAP_ULONG_BITS * i);
+ if (clear_idl_track) {
+@@ -3944,6 +3976,7 @@ ovn_ic_pause(struct unixctl_conn *conn, int argc OVS_UNUSED,
+ struct ic_state *state = state_;
+ state->paused = true;
+
++ poll_immediate_wake();
+ unixctl_command_reply(conn, NULL);
+ }
+
+diff --git a/lib/actions.c b/lib/actions.c
+index 3fbaed7af6..26df8e2035 100644
+--- a/lib/actions.c
++++ b/lib/actions.c
+@@ -1534,6 +1534,7 @@ encode_ct_lb(const struct ovnact_ct_lb *cl,
}
- return -1;
-diff --git a/northd/automake.mk b/northd/automake.mk
-index 8cd4fb3a13..45ca0337f9 100644
---- a/northd/automake.mk
-+++ b/northd/automake.mk
-@@ -44,8 +44,6 @@ northd_ovn_northd_SOURCES = \
- northd/en-lr-stateful.h \
- northd/en-ls-stateful.c \
- northd/en-ls-stateful.h \
-- northd/en-ls-arp.c \
-- northd/en-ls-arp.h \
- northd/en-sampling-app.c \
- northd/en-sampling-app.h \
- northd/en-acl-ids.c \
-diff --git a/northd/en-lflow.c b/northd/en-lflow.c
-index 704fae7095..752007e03a 100644
---- a/northd/en-lflow.c
-+++ b/northd/en-lflow.c
-@@ -23,7 +23,6 @@
- #include "en-lr-nat.h"
- #include "en-lr-stateful.h"
- #include "en-ls-stateful.h"
--#include "en-ls-arp.h"
- #include "en-multicast.h"
- #include "en-northd.h"
- #include "en-meters.h"
-@@ -62,8 +61,6 @@ lflow_get_input_data(struct engine_node *node,
- engine_get_input_data("lr_stateful", node);
- struct ed_type_ls_stateful *ls_stateful_data =
- engine_get_input_data("ls_stateful", node);
-- struct ed_type_ls_arp *ls_arp_data =
-- engine_get_input_data("ls_arp", node);
- struct multicast_igmp_data *multicat_igmp_data =
- engine_get_input_data("multicast_igmp", node);
- struct ic_learned_svc_monitors_data *ic_learned_svc_monitors_data =
-@@ -90,7 +87,6 @@ lflow_get_input_data(struct engine_node *node,
- lflow_input->ls_port_groups = &pg_data->ls_port_groups;
- lflow_input->lr_stateful_table = &lr_stateful_data->table;
- lflow_input->ls_stateful_table = &ls_stateful_data->table;
-- lflow_input->ls_arp_table = &ls_arp_data->table;
- lflow_input->meter_groups = &sync_meters_data->meter_groups;
- lflow_input->lb_datapaths_map = &northd_data->lb_datapaths_map;
- lflow_input->local_svc_monitors_map =
-@@ -230,31 +226,6 @@ lflow_ls_stateful_handler(struct engine_node *node, void *data)
- return EN_HANDLED_UPDATED;
+ if (!n_active_backends) {
++ ds_destroy(&ds);
+ return;
+ }
+
+diff --git a/lib/automake.mk b/lib/automake.mk
+index 25741cbdf4..e1c644bddf 100644
+--- a/lib/automake.mk
++++ b/lib/automake.mk
+@@ -50,6 +50,8 @@ lib_libovn_la_SOURCES = \
+ lib/lb.h \
+ lib/sparse-array.c \
+ lib/sparse-array.h \
++ lib/spsc-ring.c \
++ lib/spsc-ring.h \
+ lib/stopwatch-names.h \
+ lib/vec.c \
+ lib/vec.h \
+diff --git a/lib/inc-proc-eng.c b/lib/inc-proc-eng.c
+index a4b6c8cde3..bcb0848c3b 100644
+--- a/lib/inc-proc-eng.c
++++ b/lib/inc-proc-eng.c
+@@ -220,6 +220,46 @@ engine_list_stopwatch_cmd(struct unixctl_conn *conn, int argc OVS_UNUSED,
+ ds_destroy(&output_str);
}
--enum engine_input_handler_result
--lflow_ls_arp_handler(struct engine_node *node, void *data)
--{
-- struct ed_type_ls_arp *ls_arp_data =
-- engine_get_input_data("ls_arp", node);
--
-- if (!ls_arp_has_tracked_data(&ls_arp_data->trk_data)) {
-- return EN_UNHANDLED;
-- }
--
-- const struct engine_context *eng_ctx = engine_get_context();
-- struct lflow_data *lflow_data = data;
-- struct lflow_input lflow_input;
--
-- lflow_get_input_data(node, &lflow_input);
-- if (!lflow_handle_ls_arp_changes(eng_ctx->ovnsb_idl_txn,
-- &ls_arp_data->trk_data,
-- &lflow_input,
-- lflow_data->lflow_table)) {
-- return EN_UNHANDLED;
-- }
--
-- return EN_HANDLED_UPDATED;
--}
--
- enum engine_input_handler_result
- lflow_multicast_igmp_handler(struct engine_node *node, void *data)
++static void
++engine_set_stopwatch_cmd(struct unixctl_conn *conn, int argc,
++ const char *argv[], bool enabled)
++{
++ const char *node_name = argc > 1 ? argv[1] : NULL;
++
++ struct engine_node *node;
++ bool found = false;
++ VECTOR_FOR_EACH (&engine_nodes, node) {
++ if (node_name && strcmp(node->name, node_name)) {
++ continue;
++ }
++ node->stopwatch_enabled = enabled;
++ found = true;
++ if (node_name) {
++ break;
++ }
++ }
++
++ if (node_name && !found) {
++ unixctl_command_reply_error(conn, "node not found");
++ return;
++ }
++ unixctl_command_reply(conn, NULL);
++}
++
++static void
++engine_enable_stopwatches_cmd(struct unixctl_conn *conn, int argc,
++ const char *argv[], void *arg OVS_UNUSED)
++{
++ engine_set_stopwatch_cmd(conn, argc, argv, true);
++}
++
++static void
++engine_disable_stopwatches_cmd(struct unixctl_conn *conn, int argc,
++ const char *argv[], void *arg OVS_UNUSED)
++{
++ engine_set_stopwatch_cmd(conn, argc, argv, false);
++}
++
+ static void
+ engine_get_compute_failure_info(struct engine_node *node)
{
-diff --git a/northd/en-lflow.h b/northd/en-lflow.h
-index d2a92e49f7..99bcfda151 100644
---- a/northd/en-lflow.h
-+++ b/northd/en-lflow.h
-@@ -25,8 +25,6 @@ lflow_lr_stateful_handler(struct engine_node *, void *data);
- enum engine_input_handler_result
- lflow_ls_stateful_handler(struct engine_node *node, void *data);
- enum engine_input_handler_result
--lflow_ls_arp_handler(struct engine_node *, void *);
--enum engine_input_handler_result
- lflow_multicast_igmp_handler(struct engine_node *node, void *data);
- enum engine_input_handler_result
- lflow_group_ecmp_route_change_handler(struct engine_node *node, void *data);
-diff --git a/northd/en-ls-arp.c b/northd/en-ls-arp.c
-deleted file mode 100644
-index 021f5054fe..0000000000
---- a/northd/en-ls-arp.c
-+++ /dev/null
-@@ -1,354 +0,0 @@
--/*
-- * Licensed under the Apache License, Version 2.0 (the "License");
-- * you may not use this file except in compliance with the License.
-- * You may obtain a copy of the License at:
-- *
-- * http://www.apache.org/licenses/LICENSE-2.0
-- *
-- * Unless required by applicable law or agreed to in writing, software
-- * distributed under the License is distributed on an "AS IS" BASIS,
-- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- * See the License for the specific language governing permissions and
-- * limitations under the License.
-- */
--
--#include <config.h>
--
--/* OVS includes */
--#include "include/openvswitch/hmap.h"
--#include "openvswitch/util.h"
--#include "openvswitch/vlog.h"
--
--/* OVN includes */
--#include "en-lr-nat.h"
--#include "en-ls-arp.h"
--#include "lib/inc-proc-eng.h"
--#include "lib/ovn-nb-idl.h"
--#include "lib/ovn-sb-idl.h"
--#include "lib/ovn-util.h"
--#include "lflow-mgr.h"
--#include "northd.h"
--
--VLOG_DEFINE_THIS_MODULE(en_ls_arp);
--
--/* Static functions. */
--struct ls_arp_input {
-- const struct ovn_datapaths *ls_datapaths;
-- const struct lr_nat_table *lr_nats;
--};
--
--static struct ls_arp_input
--ls_arp_get_input_data(struct engine_node *node)
--{
-- const struct northd_data *northd_data =
-- engine_get_input_data("northd", node);
-- struct ed_type_lr_nat_data *lr_nat_data =
-- engine_get_input_data("lr_nat", node);
--
-- return (struct ls_arp_input) {
-- .ls_datapaths = &northd_data->ls_datapaths,
-- .lr_nats = &lr_nat_data->lr_nats,
-- };
--}
--
--static void
--ls_arp_record_clear(struct ls_arp_record *ls_arp_record)
--{
-- lflow_ref_destroy(ls_arp_record->lflow_ref);
-- hmapx_destroy(&ls_arp_record->nat_records);
-- free(ls_arp_record);
--}
--
--static void
--ls_arp_table_clear(struct ls_arp_table *table)
--{
-- struct ls_arp_record *ls_arp_record;
-- HMAP_FOR_EACH_POP (ls_arp_record, key_node, &table->entries) {
-- ls_arp_record_clear(ls_arp_record);
-- }
--}
--
--static inline bool
--is_nat_dgp_connected(const struct ovn_nat *nat_entry)
--{
-- return nat_entry->is_valid
-- && nat_entry->l3dgw_port
-- && nat_entry->l3dgw_port->peer
-- && nat_entry->l3dgw_port->peer->od;
--}
--
--static void
--nat_record_data_create(struct ls_arp_record *ls_arp_record,
-- const struct ovn_datapath *od,
-- const struct lr_nat_table *lr_nats)
--{
-- struct ovn_port *op;
-- VECTOR_FOR_EACH (&od->router_ports, op) {
-- const struct ovn_datapath *lr_od = op->peer->od;
-- const struct lr_nat_record *lrnat_rec =
-- lr_nat_table_find_by_uuid(lr_nats, lr_od->key);
--
-- if (!lrnat_rec) {
-- continue;
-- }
--
-- for (size_t i = 0; i < lrnat_rec->n_nat_entries; i++) {
-- const struct ovn_nat *nat_entry = &lrnat_rec->nat_entries[i];
--
-- if (is_nat_dgp_connected(nat_entry)) {
-- hmapx_add(&ls_arp_record->nat_records,
-- (struct lrnat_rec *) lrnat_rec);
-- continue;
-- }
-- }
-- }
--}
--
--static struct ls_arp_record *
--ls_arp_record_lookup_by_od_(const struct ls_arp_table *table,
-- const struct ovn_datapath *od)
--{
-- struct ls_arp_record *ls_arp_record;
-- HMAP_FOR_EACH_WITH_HASH (ls_arp_record, key_node,
-- uuid_hash(&od->nbs->header_.uuid),
-- &table->entries) {
-- if (uuid_equals(&ls_arp_record->nbs_uuid,
-- &od->nbs->header_.uuid)) {
-- return ls_arp_record;
-- }
-- }
--
-- return NULL;
--}
--
--static struct ls_arp_record *
--ls_arp_record_create(struct ls_arp_table *table,
-- const struct ovn_datapath *od,
-- const struct lr_nat_table *lr_nats)
--{
-- struct ls_arp_record *ls_arp_record = xzalloc(sizeof *ls_arp_record);
--
-- ls_arp_record->ls_index = od->sdp->index;
-- ls_arp_record->nbs_uuid = od->nbs->header_.uuid;
--
-- hmapx_init(&ls_arp_record->nat_records);
-- nat_record_data_create(ls_arp_record, od, lr_nats);
--
-- ls_arp_record->lflow_ref = lflow_ref_create();
--
-- hmap_insert(&table->entries, &ls_arp_record->key_node,
-- uuid_hash(&od->nbs->header_.uuid));
--
-- return ls_arp_record;
--}
--
--/* Public functions. */
--void*
--en_ls_arp_init(struct engine_node *node OVS_UNUSED,
-- struct engine_arg *arg OVS_UNUSED)
--{
-- struct ed_type_ls_arp *data = xzalloc(sizeof *data);
--
-- hmap_init(&data->table.entries);
-- hmapx_init(&data->trk_data.crupdated);
-- hmapx_init(&data->trk_data.deleted);
--
-- return data;
--}
--
--void
--en_ls_arp_clear_tracked_data(void *data_)
--{
-- struct ed_type_ls_arp *data = data_;
-- hmapx_clear(&data->trk_data.crupdated);
--
-- struct hmapx_node *n;
-- HMAPX_FOR_EACH_SAFE (n, &data->trk_data.deleted) {
-- ls_arp_record_clear(n->data);
-- hmapx_delete(&data->trk_data.deleted, n);
-- }
-- hmapx_clear(&data->trk_data.deleted);
--}
--
--void
--en_ls_arp_cleanup(void *data_)
--{
-- struct ed_type_ls_arp *data = data_;
--
-- ls_arp_table_clear(&data->table);
-- hmap_destroy(&data->table.entries);
-- hmapx_destroy(&data->trk_data.crupdated);
--
-- struct hmapx_node *n;
-- HMAPX_FOR_EACH_SAFE (n, &data->trk_data.deleted) {
-- ls_arp_record_clear(n->data);
-- hmapx_delete(&data->trk_data.deleted, n);
-- }
-- hmapx_destroy(&data->trk_data.deleted);
--}
--
--enum engine_node_state
--en_ls_arp_run(struct engine_node *node, void *data_)
--{
-- struct ls_arp_input input_data = ls_arp_get_input_data(node);
-- struct ed_type_ls_arp *data = data_;
--
-- ls_arp_table_clear(&data->table);
--
-- const struct ovn_datapath *od;
-- HMAP_FOR_EACH (od, key_node, &input_data.ls_datapaths->datapaths) {
-- /* Filtering ARP entries at logical switch works
-- * when there are physical ports on the switch. */
-- if (hmapx_is_empty(&od->phys_ports)) {
-- continue;
-- }
--
-- ls_arp_record_create(&data->table, od, input_data.lr_nats);
-- }
--
-- return EN_UPDATED;
--}
--
--/* Handler functions. */
--enum engine_input_handler_result
--ls_arp_northd_handler(struct engine_node *node, void *data_)
--{
-- struct northd_data *northd_data = engine_get_input_data("northd", node);
-- if (!northd_has_tracked_data(&northd_data->trk_data)) {
-- return EN_UNHANDLED;
-- }
--
-- if (!northd_has_lswitches_in_tracked_data(&northd_data->trk_data)) {
-- return EN_HANDLED_UNCHANGED;
-- }
--
-- struct northd_tracked_data *nd_changes = &northd_data->trk_data;
-- struct ls_arp_input input_data = ls_arp_get_input_data(node);
-- struct ed_type_ls_arp *data = data_;
-- struct hmapx_node *hmapx_node;
-- struct ls_arp_record *ls_arp_record;
--
-- HMAPX_FOR_EACH (hmapx_node, &nd_changes->trk_switches.crupdated) {
-- const struct ovn_datapath *od = hmapx_node->data;
--
-- ls_arp_record = ls_arp_record_lookup_by_od_(&data->table, od);
--
-- if (!ls_arp_record) {
-- /* Filtering ARP entries at logical switch works
-- * when there are physical ports on the switch. */
-- if (hmapx_is_empty(&od->phys_ports)) {
-- /* NOTE: If the switch used to have physical ports but those
-- * were removed the lr_nat node has recomputed and triggers
-- * the ls_arp_lr_nat_handler() which cannot incrementally
-- * process changes. This implicitly triggers correct
-- * handling of the removal.*/
-- continue;
-- }
-- ls_arp_record = ls_arp_record_create(&data->table,
-- od, input_data.lr_nats);
-- } else {
-- nat_record_data_create(ls_arp_record, od, input_data.lr_nats);
-- }
--
-- hmapx_add(&data->trk_data.crupdated, ls_arp_record);
-- }
--
-- HMAPX_FOR_EACH (hmapx_node, &nd_changes->trk_switches.deleted) {
-- const struct ovn_datapath *od = hmapx_node->data;
--
-- ls_arp_record = ls_arp_record_lookup_by_od_(&data->table, od);
-- if (ls_arp_record) {
-- hmap_remove(&data->table.entries, &ls_arp_record->key_node);
-- hmapx_add(&data->trk_data.deleted, ls_arp_record);
-- }
-- }
--
-- if (ls_arp_has_tracked_data(&data->trk_data)) {
-- return EN_HANDLED_UPDATED;
-- }
--
-- return EN_HANDLED_UNCHANGED;
--}
--
--static void
--nat_odmap_create(struct lr_nat_record *lrnat_rec,
-- struct hmapx *odmap)
--{
-- for (size_t i = 0; i < lrnat_rec->n_nat_entries; i++) {
-- const struct ovn_nat *nat_entry = &lrnat_rec->nat_entries[i];
--
-- if (is_nat_dgp_connected(nat_entry)) {
-- hmapx_add(odmap, nat_entry->l3dgw_port->peer->od);
-- }
-- }
--}
--
--enum engine_input_handler_result
--ls_arp_lr_nat_handler(struct engine_node *node, void *data_)
--{
-- struct ed_type_lr_nat_data *lr_nat_data =
-- engine_get_input_data("lr_nat", node);
-- struct ls_arp_input input_data = ls_arp_get_input_data(node);
--
-- if (!lr_nat_has_tracked_data(&lr_nat_data->trk_data)) {
-- return EN_UNHANDLED;
-- }
--
-- struct ed_type_ls_arp *data = data_;
--
-- struct hmapx_node *hmapx_node;
-- struct ls_arp_record *ls_arp_record;
-- HMAPX_FOR_EACH (hmapx_node, &lr_nat_data->trk_data.crupdated) {
-- struct lr_nat_record *nat_record_p = hmapx_node->data;
--
-- struct hmapx ls_links_map = HMAPX_INITIALIZER(&ls_links_map);
-- nat_odmap_create(nat_record_p, &ls_links_map);
--
-- LS_ARP_TABLE_FOR_EACH (ls_arp_record, &data->table) {
-- struct hmapx_node *nr_node =
-- hmapx_find(&ls_arp_record->nat_records, nat_record_p);
--
-- if (nr_node) {
-- hmapx_add(&data->trk_data.crupdated, ls_arp_record);
-- hmapx_delete(&ls_arp_record->nat_records, nr_node);
-- }
-- }
--
-- struct hmapx_node *crupdated_ls_hmapx;
-- HMAPX_FOR_EACH (crupdated_ls_hmapx, &ls_links_map) {
-- struct ovn_datapath *crupdated_ls = crupdated_ls_hmapx->data;
-- ls_arp_record =
-- ls_arp_record_lookup_by_od_(&data->table, crupdated_ls);
--
-- if (!ls_arp_record) {
-- ls_arp_record = ls_arp_record_create(&data->table,
-- crupdated_ls,
-- input_data.lr_nats);
-- }
--
-- hmapx_add(&data->trk_data.crupdated, ls_arp_record);
-- hmapx_add(&ls_arp_record->nat_records, nat_record_p);
-- }
-- hmapx_destroy(&ls_links_map);
-- }
--
-- HMAPX_FOR_EACH (hmapx_node, &lr_nat_data->trk_data.deleted) {
-- struct lr_nat_record *nr_cur = hmapx_node->data;
--
-- struct ls_arp_record *ar;
-- LS_ARP_TABLE_FOR_EACH (ar, &data->table) {
-- struct hmapx_node *nr_node = hmapx_find(&ar->nat_records, nr_cur);
--
-- if (nr_node) {
-- hmapx_add(&data->trk_data.crupdated, ar);
-- hmapx_delete(&ar->nat_records, nr_node);
-- }
-- }
-- }
--
-- if (ls_arp_has_tracked_data(&data->trk_data)) {
-- return EN_HANDLED_UPDATED;
-- }
--
-- return EN_HANDLED_UNCHANGED;
--}
-diff --git a/northd/en-ls-arp.h b/northd/en-ls-arp.h
-deleted file mode 100644
-index 5eaf913bb6..0000000000
---- a/northd/en-ls-arp.h
-+++ /dev/null
-@@ -1,86 +0,0 @@
--/*
-- * Licensed under the Apache License, Version 2.0 (the "License");
-- * you may not use this file except in compliance with the License.
-- * You may obtain a copy of the License at:
-- *
-- * http://www.apache.org/licenses/LICENSE-2.0
-- *
-- * Unless required by applicable law or agreed to in writing, software
-- * distributed under the License is distributed on an "AS IS" BASIS,
-- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- * See the License for the specific language governing permissions and
-- * limitations under the License.
-- */
--
--#ifndef EN_LS_ARP_H
--#define EN_LS_ARP_H 1
--
--/* OVS includes. */
--#include "lib/hmapx.h"
--#include "openvswitch/hmap.h"
--
--/* OVN includes. */
--#include "lib/inc-proc-eng.h"
--#include "lib/ovn-nb-idl.h"
--#include "lib/ovn-sb-idl.h"
--#include "lib/ovn-util.h"
--#include "lib/stopwatch-names.h"
--
--struct lflow_ref;
--
--struct ls_arp_record {
-- struct hmap_node key_node;
--
-- /* UUID of the NB Logical switch. */
-- struct uuid nbs_uuid;
--
-- /* Index of logical switch item in northd. */
-- size_t ls_index;
--
-- /* 'lflow_ref' is used to reference logical flows generated for
-- * this ls_arp record. */
-- struct lflow_ref *lflow_ref;
--
-- /* lr_nat_record ptrs that trigger this od to rebuild lflow. */
-- struct hmapx nat_records;
--};
--
--struct ls_arp_table {
-- struct hmap entries;
--};
--
--#define LS_ARP_TABLE_FOR_EACH(LS_ARP_REC, TABLE) \
-- HMAP_FOR_EACH (LS_ARP_REC, key_node, \
-- &(TABLE)->entries)
--
--#define LS_ARP_TABLE_FOR_EACH_IN_P(LS_ARP_REC, JOBID, TABLE) \
-- HMAP_FOR_EACH_IN_PARALLEL (LS_ARP_REC, key_node, JOBID, \
-- &(TABLE)->entries)
--
--struct ls_arp_tracked_data {
-- struct hmapx crupdated;
-- struct hmapx deleted;
--};
--
--struct ed_type_ls_arp {
-- struct ls_arp_table table;
-- struct ls_arp_tracked_data trk_data;
--};
--
--void *en_ls_arp_init(struct engine_node *, struct engine_arg *);
--void en_ls_arp_cleanup(void *);
--void en_ls_arp_clear_tracked_data(void *);
--enum engine_node_state en_ls_arp_run(struct engine_node *, void *);
--
--enum engine_input_handler_result
--ls_arp_lr_nat_handler(struct engine_node *, void *);
--enum engine_input_handler_result
--ls_arp_northd_handler(struct engine_node *, void *);
--
--static inline bool
--ls_arp_has_tracked_data(struct ls_arp_tracked_data *trk_data) {
-- return !hmapx_is_empty(&trk_data->crupdated) ||
-- !hmapx_is_empty(&trk_data->deleted);
--}
--
--#endif /* EN_LS_ARP_H */
-diff --git a/northd/en-multicast.c b/northd/en-multicast.c
-index a7dfd71c47..b2775516a9 100644
---- a/northd/en-multicast.c
-+++ b/northd/en-multicast.c
-@@ -710,7 +710,7 @@ ovn_igmp_group_aggregate_ports(struct ovn_igmp_group *igmp_group,
- free(entry);
- }
-
-- if (!vector_is_empty(&igmp_group->datapath->localnet_ports)) {
-+ if (ls_has_localnet_port(igmp_group->datapath)) {
- ovn_multicast_add_ports(mcast_groups, igmp_group->datapath,
- &igmp_group->mcgroup,
- vector_get_array(&igmp_group->datapath->localnet_ports),
-diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
-index 1884f98a07..dffac73090 100644
---- a/northd/inc-proc-northd.c
-+++ b/northd/inc-proc-northd.c
-@@ -34,7 +34,6 @@
- #include "en-lr-stateful.h"
- #include "en-lr-nat.h"
- #include "en-ls-stateful.h"
--#include "en-ls-arp.h"
- #include "en-multicast.h"
- #include "en-northd.h"
- #include "en-lflow.h"
-@@ -176,7 +175,6 @@ static ENGINE_NODE(lb_data, CLEAR_TRACKED_DATA);
- static ENGINE_NODE(lr_nat, CLEAR_TRACKED_DATA);
- static ENGINE_NODE(lr_stateful, CLEAR_TRACKED_DATA);
- static ENGINE_NODE(ls_stateful, CLEAR_TRACKED_DATA);
--static ENGINE_NODE(ls_arp, CLEAR_TRACKED_DATA);
- static ENGINE_NODE(route_policies);
- static ENGINE_NODE(routes);
- static ENGINE_NODE(bfd);
-@@ -310,9 +308,6 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
- ls_stateful_port_group_handler);
- engine_add_input(&en_ls_stateful, &en_nb_acl, ls_stateful_acl_handler);
-
-- engine_add_input(&en_ls_arp, &en_lr_nat, ls_arp_lr_nat_handler);
-- engine_add_input(&en_ls_arp, &en_northd, ls_arp_northd_handler);
--
- engine_add_input(&en_mac_binding_aging, &en_sb_mac_binding, NULL);
- engine_add_input(&en_mac_binding_aging, &en_northd, NULL);
- engine_add_input(&en_mac_binding_aging, &en_mac_binding_aging_waker, NULL);
-@@ -422,7 +417,6 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
- engine_add_input(&en_lflow, &en_port_group, engine_noop_handler);
- engine_add_input(&en_lflow, &en_lr_stateful, lflow_lr_stateful_handler);
- engine_add_input(&en_lflow, &en_ls_stateful, lflow_ls_stateful_handler);
-- engine_add_input(&en_lflow, &en_ls_arp, lflow_ls_arp_handler);
- engine_add_input(&en_lflow, &en_multicast_igmp,
- lflow_multicast_igmp_handler);
- engine_add_input(&en_lflow, &en_sb_acl_id, NULL);
-diff --git a/northd/northd.c b/northd/northd.c
-index a4b32ee505..b948db6884 100644
---- a/northd/northd.c
-+++ b/northd/northd.c
-@@ -50,7 +50,6 @@
- #include "en-lr-nat.h"
- #include "en-lr-stateful.h"
- #include "en-ls-stateful.h"
--#include "en-ls-arp.h"
- #include "en-multicast.h"
- #include "en-sampling-app.h"
- #include "en-datapath-logical-switch.h"
-@@ -143,7 +142,6 @@ static bool vxlan_mode;
- #define REGBIT_IP_FRAG "reg0[19]"
- #define REGBIT_ACL_PERSIST_ID "reg0[20]"
- #define REGBIT_ACL_HINT_ALLOW_PERSISTED "reg0[21]"
--#define REGBIT_EXT_ARP "reg0[22]"
-
- /* Register definitions for switches and routers. */
-
-@@ -571,7 +569,6 @@ ovn_datapath_create(struct hmap *datapaths, const struct uuid *key,
- hmap_insert(datapaths, &od->key_node, uuid_hash(&od->key));
- od->lr_group = NULL;
- hmap_init(&od->ports);
-- hmapx_init(&od->phys_ports);
- sset_init(&od->router_ips);
- od->ls_peers = VECTOR_EMPTY_INITIALIZER(struct ovn_datapath *);
- od->router_ports = VECTOR_EMPTY_INITIALIZER(struct ovn_port *);
-@@ -611,7 +608,6 @@ ovn_datapath_destroy(struct ovn_datapath *od)
- vector_destroy(&od->l3dgw_ports);
- destroy_mcast_info_for_datapath(od);
- destroy_ports_for_datapath(od);
-- hmapx_destroy(&od->phys_ports);
- sset_destroy(&od->router_ips);
- free(od);
- }
-@@ -1244,12 +1240,6 @@ lsp_is_vtep(const struct nbrec_logical_switch_port *nbsp)
- return !strcmp(nbsp->type, "vtep");
+@@ -256,6 +296,10 @@ engine_init(struct engine_node *node, struct engine_arg *arg)
+ engine_set_log_timeout_cmd, NULL);
+ unixctl_command_register("inc-engine/list-stopwatches", "", 0, 1,
+ engine_list_stopwatch_cmd, NULL);
++ unixctl_command_register("inc-engine/enable-stopwatch", "[node]", 0, 1,
++ engine_enable_stopwatches_cmd, NULL);
++ unixctl_command_register("inc-engine/disable-stopwatch", "[node]", 0, 1,
++ engine_disable_stopwatches_cmd, NULL);
}
--static bool
--lsp_is_l2gw(const struct nbrec_logical_switch_port *nbsp)
--{
-- return !strcmp(nbsp->type, "l2gateway");
--}
--
- static bool
- localnet_can_learn_mac(const struct nbrec_logical_switch_port *nbsp)
+ void
+@@ -455,9 +499,13 @@ static enum engine_node_state
+ run_recompute_callback(struct engine_node *node)
{
-@@ -1635,10 +1625,6 @@ join_logical_ports_lsp(struct hmap *ports,
- od->has_vtep_lports = true;
- }
-
-- if (lsp_is_localnet(nbsp) || lsp_is_l2gw(nbsp)) {
-- hmapx_add(&od->phys_ports, op);
-- }
--
- parse_lsp_addrs(op);
+ enum engine_node_state ret;
+- stopwatch_start(node->name, time_msec());
++ if (node->stopwatch_enabled) {
++ stopwatch_start(node->name, time_msec());
++ }
+ ret = node->run(node, node->data);
+- stopwatch_stop(node->name, time_msec());
++ if (node->stopwatch_enabled) {
++ stopwatch_stop(node->name, time_msec());
++ }
+ return ret;
+ }
- op->od = od;
-@@ -1782,7 +1768,7 @@ peer_needs_cr_port_creation(struct ovn_port *op)
+@@ -465,9 +513,13 @@ static enum engine_input_handler_result
+ run_change_handler(struct engine_node *node, struct engine_node_input *input)
{
- if ((op->nbrp->n_gateway_chassis || op->nbrp->ha_chassis_group)
- && vector_len(&op->od->l3dgw_ports) == 1 && op->peer && op->peer->nbsp
-- && vector_is_empty(&op->peer->od->localnet_ports)) {
-+ && !ls_has_localnet_port(op->peer->od)) {
- return true;
- }
-
-@@ -2742,7 +2728,7 @@ ovn_port_update_sbrec(struct ovsdb_idl_txn *ovnsb_txn,
- smap_clone(&options, &op->nbsp->options);
-
- if (queue_id) {
-- if (!vector_is_empty(&op->od->localnet_ports)) {
-+ if (ls_has_localnet_port(op->od)) {
- struct ovn_port *port = vector_get(&op->od->localnet_ports,
- 0, struct ovn_port *);
- const char *physical_network = smap_get(
-@@ -3856,7 +3842,7 @@ should_add_router_port_garp(const struct ovn_port *op, const char *chassis)
- vector_len(&op->peer->od->l3dgw_ports));
- }
- }
-- } else if (chassis && !vector_is_empty(&op->od->localnet_ports)) {
-+ } else if (chassis && ls_has_localnet_port(op->od)) {
- add_router_port_garp = true;
- }
+ enum engine_input_handler_result ret;
+- stopwatch_start(input->change_handler_name, time_msec());
++ if (node->stopwatch_enabled) {
++ stopwatch_start(input->change_handler_name, time_msec());
++ }
+ ret = input->change_handler(node, node->data);
+- stopwatch_stop(input->change_handler_name, time_msec());
++ if (node->stopwatch_enabled) {
++ stopwatch_stop(input->change_handler_name, time_msec());
++ }
+ return ret;
+ }
-@@ -5022,6 +5008,9 @@ northd_handle_ls_changes(struct ovsdb_idl_txn *ovnsb_idl_txn,
- goto fail;
- }
+diff --git a/lib/inc-proc-eng.h b/lib/inc-proc-eng.h
+index 1cb2466b23..ece33e1beb 100644
+--- a/lib/inc-proc-eng.h
++++ b/lib/inc-proc-eng.h
+@@ -282,6 +282,9 @@ struct engine_node {
-+ if (sparse_array_get(&nd->ls_datapaths.dps, synced->sdp->index)) {
-+ goto fail;
-+ }
- struct ovn_datapath *od = ovn_datapath_create(
- &nd->ls_datapaths.datapaths, &new_ls->header_.uuid, new_ls,
- NULL, synced->sdp);
-@@ -5354,6 +5343,9 @@ northd_handle_lr_changes(const struct northd_input *ni,
- if (new_lr->copp || (new_lr->n_ports > 0)) {
- goto fail;
- }
-+ if (sparse_array_get(&nd->lr_datapaths.dps, synced->sdp->index)) {
-+ goto fail;
-+ }
- struct ovn_datapath *od = ovn_datapath_create(
- &nd->lr_datapaths.datapaths, &new_lr->header_.uuid,
- NULL, new_lr, synced->sdp);
-@@ -6154,7 +6146,7 @@ build_lswitch_port_sec_op(struct ovn_port *op, struct lflow_table *lflows,
- WITH_IO_PORT(op->key), WITH_HINT(&op->nbsp->header_));
-
- if (!lsp_is_localnet(op->nbsp) &&
-- vector_is_empty(&op->od->localnet_ports)) {
-+ !ls_has_localnet_port(op->od)) {
- return;
- }
+ /* Indication if the node writes to SB DB. */
+ bool sb_write;
++
++ /* Whether stopwatches are enabled for this node. */
++ bool stopwatch_enabled;
+ };
-@@ -6168,7 +6160,7 @@ build_lswitch_port_sec_op(struct ovn_port *op, struct lflow_table *lflows,
- ds_cstr(match), ds_cstr(actions), op->lflow_ref,
- WITH_IO_PORT(op->key),
- WITH_HINT(&op->nbsp->header_));
-- } else if (!vector_is_empty(&op->od->localnet_ports)) {
-+ } else if (ls_has_localnet_port(op->od)) {
- const struct ovn_port *lp = vector_get(&op->od->localnet_ports, 0,
- struct ovn_port *);
- ds_put_format(match, "outport == %s && inport == %s",
-@@ -10050,88 +10042,6 @@ build_drop_arp_nd_flows_for_unbound_router_ports(struct ovn_port *op,
- ds_destroy(&match);
+ /* Initialize the data for the engine nodes. It calls each node's
+diff --git a/lib/mac-binding-index.c b/lib/mac-binding-index.c
+index ad6be0a4e8..3e49b07f3a 100644
+--- a/lib/mac-binding-index.c
++++ b/lib/mac-binding-index.c
+@@ -52,27 +52,34 @@ mac_binding_lookup(struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
}
--/*
-- * Create ARP filtering flow for od, assumed logical switch,
-- * for the following condition:
-- * Given lswitch has either localnet or l2gateway ports and
-- * router connection ports that requires chassis residence.
-- * ARP requests coming from localnet/l2gateway ports
-- * allowed for processing on resident chassis only.
-- */
--static void
--build_lswitch_arp_chassis_resident(const struct ovn_datapath *od,
-- struct lflow_table *lflows,
-- const struct ls_arp_record *ar)
--{
-- struct hmapx resident_ports = HMAPX_INITIALIZER(&resident_ports);
-- struct ds match = DS_EMPTY_INITIALIZER;
--
-- struct ovn_port *op;
-- VECTOR_FOR_EACH (&od->router_ports, op) {
-- struct ovn_port *op_r = op->peer;
--
-- if (lrp_is_l3dgw(op_r)) {
-- hmapx_add(&resident_ports, op_r->cr_port);
-- } else if (op_r->od->is_gw_router) {
-- hmapx_add(&resident_ports, op_r);
-- }
-- }
--
-- if (!hmapx_is_empty(&od->phys_ports) && !hmapx_is_empty(&resident_ports)) {
-- struct hmapx_node *node;
--
-- HMAPX_FOR_EACH (node, &od->phys_ports) {
-- op = node->data;
--
-- ds_clear(&match);
-- ds_put_format(&match, "arp.op == 1 && inport == %s",
-- op->json_key);
-- ovn_lflow_add(lflows, od, S_SWITCH_IN_CHECK_PORT_SEC, 75,
-- ds_cstr(&match), REGBIT_EXT_ARP " = 1; next;",
-- ar->lflow_ref);
-- }
--
-- HMAPX_FOR_EACH (node, &resident_ports) {
-- op = node->data;
--
-- ds_clear(&match);
-- ds_put_format(&match, REGBIT_EXT_ARP" == 1");
-- if (od_is_centralized(op->od)) {
-- ds_put_format(&match, " && is_chassis_resident(%s)",
-- op->json_key);
-- }
-- ovn_lflow_add(lflows, od, S_SWITCH_IN_APPLY_PORT_SEC, 75,
-- ds_cstr(&match), "next;", ar->lflow_ref);
-- }
--
-- struct hmapx_node *hmapx_node;
-- HMAPX_FOR_EACH (hmapx_node, &ar->nat_records) {
-- struct lr_nat_record *nr = hmapx_node->data;
-- for (size_t i = 0; i < nr->n_nat_entries; i++) {
-- struct ovn_nat *ent = &nr->nat_entries[i];
-- if (!ent->is_valid || !ent->is_distributed ||
-- nat_entry_is_v6(ent)) {
-- continue;
-- }
--
-- ds_clear(&match);
-- ds_put_format(&match, REGBIT_EXT_ARP " == 1 && arp.tpa == %s "
-- "&& is_chassis_resident(\"%s\")",
-- ent->ext_addrs.ipv4_addrs[0].addr_s,
-- ent->nb->logical_port);
-- ovn_lflow_add(lflows, od, S_SWITCH_IN_APPLY_PORT_SEC, 85,
-- ds_cstr(&match), "next;", ar->lflow_ref);
-- }
-- }
--
-- ovn_lflow_add(lflows, od, S_SWITCH_IN_APPLY_PORT_SEC, 70,
-- REGBIT_EXT_ARP" == 1", "drop;", ar->lflow_ref);
-- }
--
-- hmapx_destroy(&resident_ports);
-- ds_destroy(&match);
--}
--
- static bool
- is_vlan_transparent(const struct ovn_datapath *od)
+ /* Update or add an IP-MAC binding for 'logical_port'.
+- * Caller should make sure that 'ovnsb_idl_txn' is valid. */
+-void
++ * Caller should make sure that 'ovnsb_idl_txn' is valid.
++ * Returns the SB row that was inserted or updated, or NULL
++ * if 'update_only' is true and no existing row was found. */
++const struct sbrec_mac_binding *
+ mac_binding_add_to_sb(struct ovsdb_idl_txn *ovnsb_idl_txn,
+ struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
+ const char *logical_port,
+ const struct sbrec_datapath_binding *dp,
+ struct eth_addr ea, const char *ip,
+- bool update_only)
++ bool update_only,
++ const struct sbrec_mac_binding *sb_mb)
{
-@@ -10293,25 +10203,43 @@ build_arp_nd_service_monitor_lflow(const char *svc_monitor_mac,
+ /* Convert ethernet argument to string form for database. */
+ char mac_string[ETH_ADDR_STRLEN + 1];
+ snprintf(mac_string, sizeof mac_string, ETH_ADDR_FMT, ETH_ADDR_ARGS(ea));
+
+- const struct sbrec_mac_binding *b =
+- mac_binding_lookup(sbrec_mac_binding_by_lport_ip,
+- logical_port, ip);
++ const struct sbrec_mac_binding *b = sb_mb
++ ? sb_mb
++ : mac_binding_lookup(sbrec_mac_binding_by_lport_ip,
++ logical_port, ip);
+ if (!b) {
+ if (update_only) {
+- return;
++ return NULL;
+ }
+- b = sbrec_mac_binding_insert(ovnsb_idl_txn);
++
++ /* Make sure the uuid persists between txn inserts. */
++ struct uuid uuid = uuid_random();
++ b = sbrec_mac_binding_insert_persist_uuid(ovnsb_idl_txn, &uuid);
+ sbrec_mac_binding_set_logical_port(b, logical_port);
+ sbrec_mac_binding_set_ip(b, ip);
+ sbrec_mac_binding_set_datapath(b, dp);
+@@ -91,4 +98,6 @@ mac_binding_add_to_sb(struct ovsdb_idl_txn *ovnsb_idl_txn,
+ sbrec_mac_binding_set_timestamp(b, time_wall_msec());
+ }
}
++
++ return b;
}
+diff --git a/lib/mac-binding-index.h b/lib/mac-binding-index.h
+index 3bcb293597..8832eda162 100644
+--- a/lib/mac-binding-index.h
++++ b/lib/mac-binding-index.h
+@@ -27,10 +27,11 @@ const struct sbrec_mac_binding *
+ mac_binding_lookup(struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
+ const char *logical_port, const char *ip);
--/* Ingress table 24: ARP/ND responder, skip requests coming from localnet
-- * ports. (priority 100); see ovn-northd.8.xml for the rationale. */
--
-+/* Ingress table: Lookup FDB. Set flags.localnet for packets arriving from
-+ * localnet ports so that downstream stages (e.g., ARP/ND responder) can
-+ * condition their behavior on whether the packet came from localnet. */
- static void
--build_lswitch_arp_nd_responder_skip_local(struct ovn_port *op,
-- struct lflow_table *lflows,
-- struct ds *match)
-+build_lswitch_from_localnet_op(struct ovn_port *op,
-+ struct lflow_table *lflows,
-+ struct ds *match)
+-void mac_binding_add_to_sb(
++const struct sbrec_mac_binding *mac_binding_add_to_sb(
+ struct ovsdb_idl_txn *,
+ struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
+ const char *logical_port, const struct sbrec_datapath_binding *,
+- struct eth_addr, const char *ip, bool update_only);
++ struct eth_addr, const char *ip, bool update_only,
++ const struct sbrec_mac_binding *sb_mb);
+
+ #endif /* lib/mac-binding-index.h */
+diff --git a/lib/ovn-util.h b/lib/ovn-util.h
+index 4ef93c470b..dc6edf2d93 100644
+--- a/lib/ovn-util.h
++++ b/lib/ovn-util.h
+@@ -629,6 +629,7 @@ dynamic_bitmap_or(struct dynamic_bitmap *db,
{
- ovs_assert(op->nbsp);
-- if (!lsp_is_localnet(op->nbsp) || op->od->has_arp_proxy_port) {
-+ if (!lsp_is_localnet(op->nbsp)) {
- return;
- }
- ds_clear(match);
- ds_put_format(match, "inport == %s", op->json_key);
-- ovn_lflow_add(lflows, op->od, S_SWITCH_IN_ARP_ND_RSP, 100, ds_cstr(match),
-- "next;", op->lflow_ref, WITH_IO_PORT(op->key),
-+ ovn_lflow_add(lflows, op->od, S_SWITCH_IN_LOOKUP_FDB, 50,
-+ ds_cstr(match), "flags.localnet = 1; next;",
-+ op->lflow_ref, WITH_IO_PORT(op->key),
- WITH_HINT(&op->nbsp->header_));
+ ovs_assert(db->capacity == n);
+ bitmap_or(db->map, arg, n);
++ db->n_elems = dynamic_bitmap_count1(db);
}
-+/* On switches with localnet ports, restrict ARP/ND replies for
-+ * localnet-sourced requests to the chassis hosting the target VIF
-+ * (preventing duplicate replies from every hypervisor). Non-localnet
-+ * requests (VIF-to-VIF) are answered unconditionally as before. */
-+static void
-+build_lswitch_arp_nd_local_resp_match(struct ds *match,
-+ const struct ovn_port *op)
+ static inline unsigned long *
+diff --git a/lib/spsc-ring.c b/lib/spsc-ring.c
+new file mode 100644
+index 0000000000..951f1dbfec
+--- /dev/null
++++ b/lib/spsc-ring.c
+@@ -0,0 +1,80 @@
++/* Copyright (c) 2026, Red Hat, Inc.
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at:
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++#include <config.h>
++#include <string.h>
++
++#include "spsc-ring.h"
++#include "util.h"
++
++/* Initializes the SPSC ring, the capacity must be power of 2. */
++void
++spsc_ring_init(struct spsc_ring *r, uint32_t capacity, size_t esize)
+{
-+ if (!ls_has_localnet_port(op->od)) {
-+ return;
-+ }
++ ovs_assert(IS_POW2(capacity));
++ r->buffer = xmalloc(capacity * esize);
++ r->esize = esize;
++ r->mask = capacity - 1;
++ atomic_init(&r->read, 0);
++ atomic_init(&r->write, 0);
++}
+
-+ ds_put_format(match,
-+ " && ((flags.localnet == 1 && is_chassis_resident(%s))"
-+ " || flags.localnet == 0)", op->json_key);
++void
++spsc_ring_destroy(struct spsc_ring *r)
++{
++ free(r->buffer);
++ r->buffer = NULL;
+}
+
- /* Ingress table 24: ARP/ND responder, reply for known IPs.
- * (priority 50). */
- static void
-@@ -10453,6 +10381,8 @@ build_lswitch_arp_nd_responder_known_ips(struct ovn_port *op,
- ds_truncate(match, match_len);
- }
- ds_put_cstr(match, " && eth.dst == ff:ff:ff:ff:ff:ff");
-+ size_t match_arp_len = match->length;
-+ build_lswitch_arp_nd_local_resp_match(match, op);
-
- ds_clear(actions);
- ds_put_format(actions,
-@@ -10484,6 +10414,7 @@ build_lswitch_arp_nd_responder_known_ips(struct ovn_port *op,
- * address is intended to detect situations where the
- * network is not working as configured, so dropping the
- * request would frustrate that intent.) */
-+ ds_truncate(match, match_arp_len);
- ds_put_format(match, " && inport == %s", op->json_key);
- ovn_lflow_add(lflows, op->od, S_SWITCH_IN_ARP_ND_RSP, 100,
- ds_cstr(match), "next;", op->lflow_ref,
-@@ -10523,6 +10454,8 @@ build_lswitch_arp_nd_responder_known_ips(struct ovn_port *op,
- "nd_ns_mcast && ip6.dst == %s && nd.target == %s",
- op->lsp_addrs[i].ipv6_addrs[j].sn_addr_s,
- op->lsp_addrs[i].ipv6_addrs[j].addr_s);
-+ size_t match_nd_len = match->length;
-+ build_lswitch_arp_nd_local_resp_match(match, op);
-
- ds_clear(actions);
- ds_put_format(actions,
-@@ -10549,6 +10482,7 @@ build_lswitch_arp_nd_responder_known_ips(struct ovn_port *op,
-
- /* Do not reply to a solicitation from the port that owns
- * the address (otherwise DAD detection will fail). */
-+ ds_truncate(match, match_nd_len);
- ds_put_format(match, " && inport == %s", op->json_key);
- ovn_lflow_add(lflows, op->od, S_SWITCH_IN_ARP_ND_RSP, 100,
- ds_cstr(match), "next;", op->lflow_ref,
-@@ -10788,7 +10722,7 @@ build_lswitch_dhcp_options_and_response(struct ovn_port *op,
- }
-
- bool is_external = lsp_is_external(op->nbsp);
-- if (is_external && (vector_is_empty(&op->od->localnet_ports) ||
-+ if (is_external && (!ls_has_localnet_port(op->od) ||
- !op->nbsp->ha_chassis_group)) {
- /* If it's an external port and there are no localnet ports
- * and if it doesn't belong to an HA chassis group ignore it. */
-@@ -11213,7 +11147,7 @@ build_lswitch_ip_unicast_lookup(struct ovn_port *op,
-
- if (peer_lrp_is_centralized &&
- !vector_is_empty(&op->peer->od->l3dgw_ports) &&
-- !vector_is_empty(&op->od->localnet_ports)) {
-+ ls_has_localnet_port(op->od)) {
- add_lrp_chassis_resident_check(op->peer, match);
- } else if (op->cr_port) {
- /* If the op has a chassis resident port, it means
-@@ -12597,10 +12531,15 @@ add_ecmp_symmetric_reply_flows(struct lflow_table *lflows,
- ds_cstr(route_match));
- ds_clear(&actions);
- ds_put_format(&actions, "ip.ttl--; flags.loopback = 1; "
-- "eth.src = %s; %s = %s; outport = %s; next;",
-- out_port->lrp_networks.ea_s,
-- is_ipv4_nexthop ? REG_SRC_IPV4 : REG_SRC_IPV6,
-- port_ip, out_port->json_key);
-+ "eth.src = %s; ",
-+ out_port->lrp_networks.ea_s);
-+ if (port_ip) {
-+ ds_put_format(&actions, "%s = %s; ",
-+ is_ipv4_nexthop ? REG_SRC_IPV4 : REG_SRC_IPV6,
-+ port_ip);
-+ }
-+ ds_put_format(&actions, "outport = %s; next;",
-+ out_port->json_key);
- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 10300, ds_cstr(&match),
- ds_cstr(&actions), lflow_ref, WITH_HINT(route->source_hint));
-
-@@ -12740,13 +12679,15 @@ build_ecmp_route_flow(struct lflow_table *lflows,
- ds_put_format(&actions, "%s = ",
- is_ipv4_nexthop ? REG_NEXT_HOP_IPV4 : REG_NEXT_HOP_IPV6);
- ipv6_format_mapped(route->nexthop, &actions);
-- ds_put_format(&actions, "; "
-- "%s = %s; "
-- "eth.src = %s; "
-+ if (route->lrp_addr_s) {
-+ ds_put_format(&actions, "; %s = %s",
-+ is_ipv4_nexthop ? REG_SRC_IPV4 : REG_SRC_IPV6,
-+ route->lrp_addr_s);
-+ }
-+ ds_put_format(&actions,
-+ "; eth.src = %s; "
- "outport = %s; "
- REGBIT_NEXTHOP_IS_IPV4" = %d; ",
-- is_ipv4_nexthop ? REG_SRC_IPV4 : REG_SRC_IPV6,
-- route->lrp_addr_s,
- route->out_port->lrp_networks.ea_s,
- route->out_port->json_key,
- is_ipv4_nexthop);
-@@ -12806,15 +12747,17 @@ add_route(struct lflow_table *lflows, const struct ovn_datapath *od,
- REG_NEXT_HOP_IPV6,
- is_ipv4_prefix ? "4" : "6");
- }
-- ds_put_format(&common_actions, "; "
-- "%s = %s; "
-- "eth.src = %s; "
-+ if (lrp_addr_s) {
-+ ds_put_format(&common_actions, "; %s = %s",
-+ is_ipv4_nexthop ? REG_SRC_IPV4 : REG_SRC_IPV6,
-+ lrp_addr_s);
-+ }
-+ ds_put_format(&common_actions,
-+ "; eth.src = %s; "
- "outport = %s; "
- "flags.loopback = 1; "
- REGBIT_NEXTHOP_IS_IPV4" = %d; "
- "next;",
-- is_ipv4_nexthop ? REG_SRC_IPV4 : REG_SRC_IPV6,
-- lrp_addr_s,
- op->lrp_networks.ea_s,
- op->json_key,
- is_ipv4_nexthop);
-@@ -14727,6 +14670,10 @@ build_neigh_learning_flows_for_lrouter_port(
- op->lrp_networks.ipv4_addrs[i].network_s,
- op->lrp_networks.ipv4_addrs[i].plen,
- op->lrp_networks.ipv4_addrs[i].addr_s);
-+ if (lrp_is_l3dgw(op)) {
-+ ds_put_format(match, " && is_chassis_resident(%s)",
-+ op->cr_port->json_key);
-+ }
- const char *actions_s = REGBIT_LOOKUP_NEIGHBOR_RESULT
- " = lookup_arp(inport, arp.spa, arp.sha); "
- REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" = 1;"
-@@ -14741,6 +14688,10 @@ build_neigh_learning_flows_for_lrouter_port(
- op->json_key,
- op->lrp_networks.ipv4_addrs[i].network_s,
- op->lrp_networks.ipv4_addrs[i].plen);
-+ if (lrp_is_l3dgw(op)) {
-+ ds_put_format(match, " && is_chassis_resident(%s)",
-+ op->cr_port->json_key);
-+ }
- ds_clear(actions);
- ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT
- " = lookup_arp(inport, arp.spa, arp.sha); %snext;",
-@@ -17112,7 +17063,7 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
-
- if (od_is_centralized(op->od) &&
- !vector_is_empty(&op->od->l3dgw_ports) && op->peer
-- && !vector_is_empty(&op->peer->od->localnet_ports)) {
-+ && ls_has_localnet_port(op->peer->od)) {
- add_lrp_chassis_resident_check(op, match);
++/* Producer: copy 'data' into the next available slot.
++ * Returns true on success, false if the ring is full. */
++bool
++spsc_ring_push(struct spsc_ring *r, const void *data)
++{
++ uint32_t wr, rd;
++
++ atomic_read(&r->write, &wr);
++ atomic_read(&r->read, &rd);
++
++ if (wr - rd > r->mask) {
++ return false;
++ }
++
++ memcpy((uint8_t *) r->buffer + (wr & r->mask) * r->esize,
++ data, r->esize);
++ atomic_store(&r->write, wr + 1);
++ return true;
++}
++
++/* Consumer: copy the oldest slot's data into 'data'.
++ * Returns true on success, false if the ring is empty. */
++bool
++spsc_ring_pop(struct spsc_ring *r, void *data)
++{
++ uint32_t rd, wr;
++
++ atomic_read(&r->read, &rd);
++ atomic_read(&r->write, &wr);
++
++ if (rd == wr) {
++ return false;
++ }
++
++ memcpy(data,
++ (uint8_t *) r->buffer + (rd & r->mask) * r->esize,
++ r->esize);
++ atomic_store(&r->read, rd + 1);
++ return true;
++}
+diff --git a/lib/spsc-ring.h b/lib/spsc-ring.h
+new file mode 100644
+index 0000000000..8820892171
+--- /dev/null
++++ b/lib/spsc-ring.h
+@@ -0,0 +1,67 @@
++/* Copyright (c) 2026, Red Hat, Inc.
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at:
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++#ifndef SPSC_RING_H
++#define SPSC_RING_H
++
++#include <stdbool.h>
++#include <stdint.h>
++
++#include "openvswitch/util.h"
++#include "ovs-atomic.h"
++
++/* Single-Producer, Single-Consumer lock-free ring buffer.
++ *
++ * Thread-safety
++ * =============
++ *
++ * Exactly one thread (the producer) may call spsc_ring_push().
++ * Exactly one thread (the consumer) may call spsc_ring_pop().
++ * These two threads may be different and may operate concurrently
++ * without any external synchronization.
++ *
++ * Memory ordering: sequential consistency (the default for OVS
++ * atomic_read/atomic_store) on the read and write indices ensures
++ * that slot data written by the producer is visible to the consumer
++ * after it observes the updated write index (and vice versa for
++ * read index updates freeing slots for reuse).
++ *
++ * Slot data is copied in and out by value (memcpy), so this is
++ * best suited for small, fixed-size structs.
++ *
++ * Capacity must be a power of two. The ring uses unsigned 32-bit
++ * wraparound arithmetic on read/write indices, which is correct as
++ * long as capacity is much smaller than 2^32.
++ */
++struct spsc_ring {
++ void *buffer; /* Pre-allocated slot array. */
++ size_t esize; /* Size of each element in bytes. */
++ uint32_t mask; /* capacity - 1 (for power-of-two modulo). */
++ atomic_uint32_t read; /* Next slot to consume (advanced by consumer). */
++ atomic_uint32_t write; /* Next slot to fill (advanced by producer). */
++};
++
++void spsc_ring_init(struct spsc_ring *, uint32_t capacity, size_t esize);
++void spsc_ring_destroy(struct spsc_ring *);
++bool spsc_ring_push(struct spsc_ring *, const void *data);
++bool spsc_ring_pop(struct spsc_ring *, void *data);
++
++/* Pop each element into VAR until the ring is empty. */
++#define SPSC_RING_FOR_EACH_POP(RING, VAR) \
++ for (bool ITER_VAR(VAR) = spsc_ring_pop(RING, &(VAR)); \
++ ITER_VAR(VAR); \
++ ITER_VAR(VAR) = spsc_ring_pop(RING, &(VAR)))
++
++#endif /* lib/spsc-ring.h */
+diff --git a/northd/en-datapath-logical-switch.c b/northd/en-datapath-logical-switch.c
+index ad6d042bc4..874a608a73 100644
+--- a/northd/en-datapath-logical-switch.c
++++ b/northd/en-datapath-logical-switch.c
+@@ -173,13 +173,6 @@ gather_external_ids(const struct nbrec_logical_switch *nbs,
+ smap_add(external_ids, "dynamic-routing-redistribute",
+ redistribute);
}
-
-@@ -19304,7 +19255,6 @@ struct lswitch_flow_build_info {
- const struct ls_port_group_table *ls_port_groups;
- const struct lr_stateful_table *lr_stateful_table;
- const struct ls_stateful_table *ls_stateful_table;
-- const struct ls_arp_table *ls_arp_table;
- struct lflow_table *lflows;
- const struct shash *meter_groups;
- const struct hmap *lb_dps_map;
-@@ -19440,7 +19390,7 @@ build_lswitch_and_lrouter_iterate_by_lsp(struct ovn_port *op,
- build_mirror_lflows(op, ls_ports, lflows);
- build_lswitch_port_sec_op(op, lflows, actions, match);
- build_lswitch_learn_fdb_op(op, lflows, actions, match);
-- build_lswitch_arp_nd_responder_skip_local(op, lflows, match);
-+ build_lswitch_from_localnet_op(op, lflows, match);
- build_lswitch_arp_nd_responder_known_ips(op, lflows, ls_ports,
- meter_groups, actions, match);
- build_lswitch_dhcp_options_and_response(op, lflows, meter_groups);
-@@ -19506,7 +19456,6 @@ build_lflows_thread(void *arg)
- struct worker_control *control = (struct worker_control *) arg;
- const struct lr_stateful_record *lr_stateful_rec;
- const struct ls_stateful_record *ls_stateful_rec;
-- const struct ls_arp_record *ls_arp_rec;
- struct lswitch_flow_build_info *lsi;
- struct ovn_lb_datapaths *lb_dps;
- struct ovn_datapath *od;
-@@ -19660,20 +19609,6 @@ build_lflows_thread(void *arg)
- lsi->sbrec_acl_id_table);
- }
- }
--
-- for (bnum = control->id;
-- bnum <= lsi->ls_arp_table->entries.mask;
-- bnum += control->pool->size)
-- {
-- LS_ARP_TABLE_FOR_EACH_IN_P (ls_arp_rec, bnum,
-- lsi->ls_arp_table) {
-- od = ovn_datapaths_find_by_index(
-- lsi->ls_datapaths, ls_arp_rec->ls_index);
-- build_lswitch_arp_chassis_resident(od, lsi->lflows,
-- ls_arp_rec);
-- }
-- }
-
- lsi->thread_lflow_counter = thread_lflow_counter;
- }
- post_completed_work(control);
-@@ -19722,7 +19657,6 @@ build_lswitch_and_lrouter_flows(
- const struct ls_port_group_table *ls_pgs,
- const struct lr_stateful_table *lr_stateful_table,
- const struct ls_stateful_table *ls_stateful_table,
-- const struct ls_arp_table *ls_arp_table,
- struct lflow_table *lflows,
+- const char *prefer_evpn_arp_local =
+- smap_get(&nbs->other_config, "dynamic-routing-arp-prefer-local");
+- if (prefer_evpn_arp_local) {
+- smap_add(external_ids, "dynamic-routing-arp-prefer-local",
+- prefer_evpn_arp_local);
+- }
+ }
+
+ /* For backwards-compatibility, also store the NB UUID in
+diff --git a/northd/en-global-config.c b/northd/en-global-config.c
+index d49ddb94ab..afc559160b 100644
+--- a/northd/en-global-config.c
++++ b/northd/en-global-config.c
+@@ -773,6 +773,30 @@ chassis_features_changed(const struct chassis_features *present,
+ return true;
+ }
+
++ if (present->ct_commit_nat_v2 != updated->ct_commit_nat_v2) {
++ return true;
++ }
++
++ if (present->ct_commit_to_zone != updated->ct_commit_to_zone) {
++ return true;
++ }
++
++ if (present->sample_with_reg != updated->sample_with_reg) {
++ return true;
++ }
++
++ if (present->ct_next_zone != updated->ct_next_zone) {
++ return true;
++ }
++
++ if (present->ct_label_flush != updated->ct_label_flush) {
++ return true;
++ }
++
++ if (present->ct_state_save != updated->ct_state_save) {
++ return true;
++ }
++
+ return false;
+ }
+
+diff --git a/northd/en-multicast.c b/northd/en-multicast.c
+index b2775516a9..5148d88408 100644
+--- a/northd/en-multicast.c
++++ b/northd/en-multicast.c
+@@ -153,7 +153,10 @@ multicast_igmp_northd_handler(struct engine_node *node, void *data OVS_UNUSED)
+ return EN_UNHANDLED;
+ }
+
+- if (hmapx_count(&northd_data->trk_data.trk_switches.deleted)) {
++ struct tracked_ovn_ports *trk_lsps = &northd_data->trk_data.trk_lsps;
++ if (hmapx_count(&trk_lsps->created) ||
++ hmapx_count(&trk_lsps->updated) ||
++ hmapx_count(&trk_lsps->deleted)) {
+ return EN_UNHANDLED;
+ }
+
+@@ -171,7 +174,7 @@ multicast_igmp_northd_handler(struct engine_node *node, void *data OVS_UNUSED)
+ * This node also accesses the router ports of the logical router
+ * (od->ports). When these logical router ports gets updated,
+ * en_northd engine recomputes and so does this node.
+- * Note: When we add I-P to handle switch/router port changes, we
++ * Note: When we add I-P to handle router port changes, we
+ * need to revisit this handler.
+ *
+ * */
+diff --git a/northd/northd.c b/northd/northd.c
+index e55260b27d..1ca8dedafa 100644
+--- a/northd/northd.c
++++ b/northd/northd.c
+@@ -7963,13 +7963,32 @@ build_acls(const struct ls_stateful_record *ls_stateful_rec,
+ * We need to set ct_mark.blocked=0 to let the connection continue,
+ * which will be done by ct_commit in the "stateful" stage.
+ * Subsequent packets will hit the flow at priority 0 that just
+- * uses "next;". */
++ * uses "next;".
++ *
++ * When tiered ACLs are configured, this default-allow must only
++ * be hit after the tier loop has reached the highest configured
++ * tier; otherwise a drop ACL at a higher tier would be bypassed
++ * for an already-blocked connection (it would short-circuit on
++ * the first tier-0 visit, set VERDICT_ALLOW, and skip the rest
++ * of the tier loop). Gate the match on REG_ACL_TIER == max_tier
++ * so the rule only matches once every tier has been evaluated. */
+ ds_clear(&match);
+- ds_put_format(&match, "ip && ct.est && ct_mark.blocked == 1");
++ ds_put_cstr(&match, "ip && ct.est && ct_mark.blocked == 1");
++ if (ls_stateful_rec->max_acl_tier.ingress_pre_lb) {
++ ds_put_format(&match, " && " REG_ACL_TIER " == %"PRIu64,
++ ls_stateful_rec->max_acl_tier.ingress_pre_lb);
++ }
+ ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL_EVAL, 1,
+ ds_cstr(&match),
+ REGBIT_ACL_VERDICT_ALLOW" = 1; next;",
+ lflow_ref);
++
++ ds_clear(&match);
++ ds_put_cstr(&match, "ip && ct.est && ct_mark.blocked == 1");
++ if (ls_stateful_rec->max_acl_tier.egress) {
++ ds_put_format(&match, " && " REG_ACL_TIER " == %"PRIu64,
++ ls_stateful_rec->max_acl_tier.egress);
++ }
+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL_EVAL, 1,
+ ds_cstr(&match),
+ REGBIT_ACL_VERDICT_ALLOW" = 1; next;",
+@@ -8768,7 +8787,8 @@ build_lb_health_check_response_lflows(
+ const struct ovn_lb_datapaths *lb_dps,
+ const struct ovn_datapaths *lr_datapaths,
const struct shash *meter_groups,
- const struct hmap *lb_dps_map,
-@@ -19759,7 +19693,6 @@ build_lswitch_and_lrouter_flows(
- lsiv[index].ls_port_groups = ls_pgs;
- lsiv[index].lr_stateful_table = lr_stateful_table;
- lsiv[index].ls_stateful_table = ls_stateful_table;
-- lsiv[index].ls_arp_table = ls_arp_table;
- lsiv[index].meter_groups = meter_groups;
- lsiv[index].lb_dps_map = lb_dps_map;
- lsiv[index].local_svc_monitor_map =
-@@ -19796,7 +19729,6 @@ build_lswitch_and_lrouter_flows(
+- struct ds *match)
++ struct ds *match,
++ struct ds *action)
+ {
+ /* For each LB backend that is monitored by a source_ip belonging
+ * to a real LRP, install rule that punts service check replies to the
+@@ -8815,6 +8835,7 @@ build_lb_health_check_response_lflows(
+ }
+
+ ds_clear(match);
++ ds_clear(action);
+
+ /* icmp6 type 1 and icmp4 type 3 are included in the match, because
+ * the controller is using them to detect unreachable ports. */
+@@ -9418,9 +9439,12 @@ arp_nd_ns_match(const char *ips, int addr_family, struct ds *match)
+ ds_put_cstr(match, FLAGBIT_NOT_VXLAN " && ");
+
+ if (addr_family == AF_INET) {
+- ds_put_format(match, "arp.op == 1 && arp.tpa == %s", ips);
++ ds_put_format(match,
++ "eth.dst == ff:ff:ff:ff:ff:ff && "
++ "arp.op == 1 && arp.tpa == %s", ips);
+ } else {
+- ds_put_format(match, "nd_ns && nd.target == %s", ips);
++ ds_put_format(match,
++ "eth.mcast && nd_ns && nd.target == %s", ips);
+ }
+ }
+
+@@ -9474,6 +9498,7 @@ build_lswitch_rport_arp_req_flow(
+ {
+ struct ds match = DS_EMPTY_INITIALIZER;
+ struct ds m = DS_EMPTY_INITIALIZER;
++ struct ds m_garp = DS_EMPTY_INITIALIZER;
+ struct ds actions = DS_EMPTY_INITIALIZER;
+
+ arp_nd_ns_match(ips, addr_family, &m);
+@@ -9495,16 +9520,27 @@ build_lswitch_rport_arp_req_flow(
+ patch_op->cr_port->json_key);
+ }
+
++ if (addr_family == AF_INET) {
++ ds_clone(&m_garp, &m);
++ ds_put_format(&m_garp, " && arp.spa == %s", ips);
++ }
++
+ /* Send a the packet to the router pipeline. If the switch has non-router
+ * ports then flood it there as well.
+ */
+ if (vector_len(&od->router_ports) != od->nbs->n_ports) {
+ ds_put_format(&actions, "clone {outport = %s; output; }; "
+- "outport = \""MC_FLOOD_L2"\"; output;",
++ "outport = \""MC_UNKNOWN"\"; output;",
+ patch_op->json_key);
+ ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, priority,
+ ds_cstr(&match), ds_cstr(&actions), lflow_ref,
+ WITH_HINT(stage_hint));
++ if (addr_family == AF_INET) {
++ ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, priority + 10,
++ ds_cstr(&m_garp),
++ "outport = \""MC_FLOOD_L2"\"; output;",
++ lflow_ref, WITH_HINT(stage_hint));
++ }
} else {
- const struct lr_stateful_record *lr_stateful_rec;
- const struct ls_stateful_record *ls_stateful_rec;
-- const struct ls_arp_record *ls_arp_rec;
- struct ovn_lb_datapaths *lb_dps;
- struct ovn_datapath *od;
- struct ovn_port *op;
-@@ -19809,7 +19741,6 @@ build_lswitch_and_lrouter_flows(
- .ls_port_groups = ls_pgs,
- .lr_stateful_table = lr_stateful_table,
- .ls_stateful_table = ls_stateful_table,
-- .ls_arp_table = ls_arp_table,
- .lflows = lflows,
- .meter_groups = meter_groups,
- .lb_dps_map = lb_dps_map,
-@@ -19906,12 +19837,6 @@ build_lswitch_and_lrouter_flows(
- lsi.sbrec_acl_id_table);
+ ds_put_format(&actions, "outport = %s; output;", patch_op->json_key);
+ ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, priority,
+@@ -9519,7 +9555,7 @@ build_lswitch_rport_arp_req_flow(
+ ds_clear(&actions);
+ if (vector_len(&od->router_ports) != od->nbs->n_ports) {
+ ds_put_format(&actions, "clone {outport = %s; output; }; "
+- "outport = \""MC_FLOOD_L2"\"; output;",
++ "outport = \""MC_UNKNOWN"\"; output;",
+ patch_op->cr_port->json_key);
+ ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, priority,
+ ds_cstr(&match), ds_cstr(&actions), lflow_ref,
+@@ -9535,6 +9571,7 @@ build_lswitch_rport_arp_req_flow(
+
+ ds_destroy(&m);
+ ds_destroy(&match);
++ ds_destroy(&m_garp);
+ ds_destroy(&actions);
+ }
+
+@@ -9661,18 +9698,22 @@ build_lswitch_rport_arp_req_flows_for_lbnats(
+ continue;
}
-- LS_ARP_TABLE_FOR_EACH (ls_arp_rec, ls_arp_table) {
-- od = ovn_datapaths_find_by_index(lsi.ls_datapaths,
-- ls_arp_rec->ls_index);
-- build_lswitch_arp_chassis_resident(od, lsi.lflows, ls_arp_rec);
-- }
--
- ds_destroy(&lsi.match);
- ds_destroy(&lsi.actions);
++ if (nat->gateway_port && nat->gateway_port != op->nbrp) {
++ continue;
++ }
++
+ /* Check if the ovn port has a network configured on which we could
+ * expect ARP requests/NS for the DNAT external_ip.
+ */
+ if (nat_entry_is_v6(nat_entry)) {
+- if (!sset_contains(&lr_stateful_rec->lb_ips->ips_v6,
++ if (!sset_contains(&lr_stateful_rec->lb_ips->ips_v6_reachable,
+ nat->external_ip)) {
+ build_lswitch_rport_arp_req_flow(
+ nat->external_ip, AF_INET6, sw_op, sw_od, 80, lflows,
+ stage_hint, lflow_ref);
+ }
+ } else {
+- if (!sset_contains(&lr_stateful_rec->lb_ips->ips_v4,
++ if (!sset_contains(&lr_stateful_rec->lb_ips->ips_v4_reachable,
+ nat->external_ip)) {
+ build_lswitch_rport_arp_req_flow(
+ nat->external_ip, AF_INET, sw_op, sw_od, 80, lflows,
+@@ -9701,18 +9742,22 @@ build_lswitch_rport_arp_req_flows_for_lbnats(
+
+ const struct nbrec_nat *nat = nat_entry->nb;
+
++ if (nat->gateway_port && nat->gateway_port != op->nbrp) {
++ continue;
++ }
++
+ /* Check if the ovn port has a network configured on which we could
+ * expect ARP requests/NS for the SNAT external_ip.
+ */
+ if (nat_entry_is_v6(nat_entry)) {
+- if (!sset_contains(&lr_stateful_rec->lb_ips->ips_v6,
++ if (!sset_contains(&lr_stateful_rec->lb_ips->ips_v6_reachable,
+ nat->external_ip)) {
+ build_lswitch_rport_arp_req_flow(
+ nat->external_ip, AF_INET6, sw_op, sw_od, 80, lflows,
+ stage_hint, lflow_ref);
+ }
+ } else {
+- if (!sset_contains(&lr_stateful_rec->lb_ips->ips_v4,
++ if (!sset_contains(&lr_stateful_rec->lb_ips->ips_v4_reachable,
+ nat->external_ip)) {
+ build_lswitch_rport_arp_req_flow(
+ nat->external_ip, AF_INET, sw_op, sw_od, 80, lflows,
+@@ -9991,13 +10036,6 @@ build_drop_arp_nd_flows_for_unbound_router_ports(struct ovn_port *op,
+ {
+ struct ds match = DS_EMPTY_INITIALIZER;
+
+- /* With the early inport rewrite installed at
+- * S_SWITCH_IN_CHECK_PORT_SEC, packets from the external LSP arrive
+- * here with MFF_LOG_INPORT == op (the external LSP), not the
+- * localnet port (which was the value at table 0). The match is
+- * therefore keyed on op->json_key. The 'port' (localnet) argument
+- * is still used for incremental processing tagging through
+- * WITH_IO_PORT below. */
+ for (size_t i = 0; i < op->n_lsp_addrs; i++) {
+ struct ovn_port *rp;
+ VECTOR_FOR_EACH (&op->od->router_ports, rp) {
+@@ -10008,7 +10046,7 @@ build_drop_arp_nd_flows_for_unbound_router_ports(struct ovn_port *op,
+ &match, "inport == %s && eth.src == %s"
+ " && !is_chassis_resident(%s)"
+ " && arp.tpa == %s && arp.op == 1",
+- op->json_key,
++ port->json_key,
+ op->lsp_addrs[i].ea_s, op->json_key,
+ rp->lsp_addrs[k].ipv4_addrs[l].addr_s);
+ ovn_lflow_add(lflows, op->od, S_SWITCH_IN_EXTERNAL_PORT,
+@@ -10024,7 +10062,7 @@ build_drop_arp_nd_flows_for_unbound_router_ports(struct ovn_port *op,
+ &match, "inport == %s && eth.src == %s"
+ " && !is_chassis_resident(%s)"
+ " && nd_ns && ip6.dst == {%s, %s} && nd.target == %s",
+- op->json_key,
++ port->json_key,
+ op->lsp_addrs[i].ea_s, op->json_key,
+ rp->lsp_addrs[k].ipv6_addrs[l].addr_s,
+ rp->lsp_addrs[k].ipv6_addrs[l].sn_addr_s,
+@@ -10042,7 +10080,7 @@ build_drop_arp_nd_flows_for_unbound_router_ports(struct ovn_port *op,
+ &match, "inport == %s && eth.src == %s"
+ " && eth.dst == %s"
+ " && !is_chassis_resident(%s)",
+- op->json_key,
++ port->json_key,
+ op->lsp_addrs[i].ea_s, rp->lsp_addrs[k].ea_s,
+ op->json_key);
+ ovn_lflow_add(lflows, op->od, S_SWITCH_IN_EXTERNAL_PORT, 100,
+@@ -10745,20 +10783,24 @@ build_lswitch_dhcp_options_and_response(struct ovn_port *op,
+ }
+
+ for (size_t i = 0; i < op->n_lsp_addrs; i++) {
+- /* For both regular VIF and type=external LSPs we pass the LSP
+- * itself (op) as the inport. For external ports, the inport
+- * rewrite added in build_lswitch_external_lsp_inport_rewrite()
+- * at S_SWITCH_IN_CHECK_PORT_SEC has already substituted
+- * MFF_LOG_INPORT from the localnet port to the external LSP by
+- * the time we reach S_SWITCH_IN_DHCP_OPTIONS. So a single set
+- * of DHCP lflows keyed on the external LSP is enough; we no
+- * longer need to enumerate every localnet port. */
+- build_dhcpv4_options_flows(op, &op->lsp_addrs[i], op,
+- is_external, meter_groups,
+- lflows, op->lflow_ref);
+- build_dhcpv6_options_flows(op, &op->lsp_addrs[i], op,
+- is_external, meter_groups,
+- lflows, op->lflow_ref);
++ if (is_external) {
++ struct ovn_port *lp;
++ VECTOR_FOR_EACH (&op->od->localnet_ports, lp) {
++ build_dhcpv4_options_flows(
++ op, &op->lsp_addrs[i], lp, is_external,
++ meter_groups, lflows, op->lflow_ref);
++ build_dhcpv6_options_flows(
++ op, &op->lsp_addrs[i], lp, is_external,
++ meter_groups, lflows, op->lflow_ref);
++ }
++ } else {
++ build_dhcpv4_options_flows(op, &op->lsp_addrs[i], op,
++ is_external, meter_groups,
++ lflows, op->lflow_ref);
++ build_dhcpv6_options_flows(op, &op->lsp_addrs[i], op,
++ is_external, meter_groups,
++ lflows, op->lflow_ref);
++ }
}
-@@ -19995,7 +19920,6 @@ void build_lflows(struct ovsdb_idl_txn *ovnsb_txn,
- input_data->ls_port_groups,
- input_data->lr_stateful_table,
- input_data->ls_stateful_table,
-- input_data->ls_arp_table,
- lflows,
- input_data->meter_groups,
- input_data->lb_datapaths_map,
-@@ -20441,42 +20365,6 @@ lflow_handle_ls_stateful_changes(struct ovsdb_idl_txn *ovnsb_txn,
- return true;
}
--bool
--lflow_handle_ls_arp_changes(struct ovsdb_idl_txn *ovnsb_txn,
-- struct ls_arp_tracked_data *trk_data,
-- struct lflow_input *lflow_input,
-- struct lflow_table *lflows)
+@@ -10837,51 +10879,6 @@ build_lswitch_external_port(struct ovn_port *op,
+ }
+ }
+
+-/* For each external LSP on a switch with a localnet port, rewrite
+- * MFF_LOG_INPORT from the localnet port to the external LSP when
+- * eth.src matches one of the external port's MACs. This makes
+- * downstream stages observe inport == <external_lsp> for traffic
+- * originating from that baremetal MAC. Intentionally placed at
+- * S_SWITCH_IN_CHECK_PORT_SEC priority 75 so it fires before the
+- * existing priority-70 generic port-sec rules but does not collide
+- * with the priority-100 disabled-port drop. */
+-static void
+-build_lswitch_external_lsp_inport_rewrite(struct ovn_port *op,
+- struct lflow_table *lflows,
+- struct ds *match,
+- struct ds *actions)
-{
-- struct hmapx_node *hmapx_node;
--
-- HMAPX_FOR_EACH (hmapx_node, &trk_data->crupdated) {
-- const struct ls_arp_record *ls_arp_record = hmapx_node->data;
-- const struct ovn_datapath *od =
-- ovn_datapaths_find_by_index(lflow_input->ls_datapaths,
-- ls_arp_record->ls_index);
-- lflow_ref_unlink_lflows(ls_arp_record->lflow_ref);
--
-- build_lswitch_arp_chassis_resident(od, lflows, ls_arp_record);
--
-- bool handled = lflow_ref_sync_lflows(
-- ls_arp_record->lflow_ref, lflows, ovnsb_txn,
-- lflow_input->dps,
-- lflow_input->ovn_internal_version_changed,
-- lflow_input->sbrec_logical_flow_table,
-- lflow_input->sbrec_logical_dp_group_table);
-- if (!handled) {
-- return false;
+- ovs_assert(op->nbsp);
+- if (!lsp_is_external(op->nbsp)) {
+- return;
+- }
+- if (!ls_has_localnet_port(op->od)) {
+- return;
+- }
+- /* Also set flags.localnet here. The existing S_SWITCH_IN_LOOKUP_FDB
+- * lflow generated by build_lswitch_learn_fdb_op() sets
+- * flags.localnet = 1 only when inport == <localnet> at that table;
+- * once we have rewritten inport to the external LSP, that match no
+- * longer fires. Copy the assignment into our rewrite action so
+- * downstream stages keyed on flags.localnet == 1 continue to work
+- * for the external LSP case. */
+- struct ovn_port *lp;
+- VECTOR_FOR_EACH (&op->od->localnet_ports, lp) {
+- for (size_t i = 0; i < op->n_lsp_addrs; i++) {
+- ds_clear(match);
+- ds_clear(actions);
+- ds_put_format(match, "inport == %s && eth.src == %s",
+- lp->json_key, op->lsp_addrs[i].ea_s);
+- ds_put_format(actions,
+- "flags.localnet = 1; inport = %s; next;",
+- op->json_key);
+- ovn_lflow_add(lflows, op->od, S_SWITCH_IN_CHECK_PORT_SEC, 75,
+- ds_cstr(match), ds_cstr(actions),
+- op->lflow_ref);
- }
- }
+-}
-
-- HMAPX_FOR_EACH (hmapx_node, &trk_data->deleted) {
-- struct ls_arp_record *ls_arp_record = hmapx_node->data;
-- lflow_ref_unlink_lflows(ls_arp_record->lflow_ref);
+ /* Ingress table 30: Destination lookup, broadcast and multicast handling
+ * (priority 70 - 100). */
+ static void
+@@ -10942,7 +10939,7 @@ build_lswitch_destination_lookup_bmcast(struct ovn_datapath *od,
+ "broadcast-arps-to-all-routers", true)) {
+ ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 72,
+ "eth.mcast && (arp.op == 1 || nd_ns)",
+- "outport = \""MC_FLOOD_L2"\"; output;",
++ "outport = \""MC_UNKNOWN"\"; output;",
+ lflow_ref);
+ }
+
+@@ -11538,7 +11535,7 @@ bfd_table_sync(struct ovsdb_idl_txn *ovnsb_txn,
+ }
+
+ HMAP_FOR_EACH_POP (bfd_e, hmap_node, &sync_bfd_connections) {
+- if (bfd_e->stale) {
++ if (bfd_e->stale && bfd_e->sb_bt) {
+ sbrec_bfd_delete(bfd_e->sb_bt);
+ }
+ bfd_erase_entry(bfd_e);
+@@ -12002,11 +11999,11 @@ parsed_route_lookup(struct hmap *routes, size_t hash,
+ continue;
+ }
+
+- if (pr->nexthop && ipv6_addr_equals(pr->nexthop, new_pr->nexthop)) {
++ if (pr->nexthop && !ipv6_addr_equals(pr->nexthop, new_pr->nexthop)) {
+ continue;
+ }
+
+- if (memcmp(&pr->prefix, &new_pr->prefix, sizeof(struct in6_addr))) {
++ if (!ipv6_addr_equals(&pr->prefix, &new_pr->prefix)) {
+ continue;
+ }
+
+@@ -12043,6 +12040,15 @@ parsed_route_lookup(struct hmap *routes, size_t hash,
+ continue;
+ }
+
++ if (!sset_equals(&pr->ecmp_selection_fields,
++ &new_pr->ecmp_selection_fields)) {
++ continue;
++ }
++
++ if (pr->tracked_port != new_pr->tracked_port) {
++ continue;
++ }
++
+ return pr;
+ }
+
+@@ -12183,7 +12189,6 @@ parsed_route_add(const struct ovn_datapath *od,
+ hmap_insert(routes, &new_pr->key_node, hash);
+ return new_pr;
+ } else {
+- pr->stale = false;
+ parsed_route_free(new_pr);
+ return pr;
+ }
+@@ -12351,13 +12356,6 @@ build_parsed_routes(const struct ovn_datapath *od, const struct hmap *lr_ports,
+ struct simap *route_tables,
+ struct hmap *bfd_active_connections)
+ {
+- struct parsed_route *pr;
+- HMAP_FOR_EACH (pr, key_node, routes) {
+- if (pr->od == od) {
+- pr->stale = true;
+- }
- }
-
-- return true;
--}
+ for (size_t i = 0; i < od->nbr->n_static_routes; i++) {
+ parsed_routes_add_static(od, lr_ports, od->nbr->static_routes[i],
+ bfd_connections, routes, route_tables,
+@@ -12368,15 +12366,6 @@ build_parsed_routes(const struct ovn_datapath *od, const struct hmap *lr_ports,
+ HMAP_FOR_EACH (op, dp_node, &od->ports) {
+ parsed_routes_add_connected(od, op, routes);
+ }
-
- static bool
- mirror_needs_update(const struct nbrec_mirror *nb_mirror,
- const struct sbrec_mirror *sb_mirror)
-diff --git a/northd/northd.h b/northd/northd.h
-index 7f2b4c9004..98ea4c8a17 100644
---- a/northd/northd.h
-+++ b/northd/northd.h
-@@ -29,7 +29,6 @@
- #include "simap.h"
- #include "ovs-thread.h"
- #include "en-lr-stateful.h"
--#include "en-ls-arp.h"
- #include "vec.h"
- #include "datapath-sync.h"
- #include "sparse-array.h"
-@@ -271,7 +270,6 @@ struct lflow_input {
- const struct ls_port_group_table *ls_port_groups;
- const struct lr_stateful_table *lr_stateful_table;
- const struct ls_stateful_table *ls_stateful_table;
-- const struct ls_arp_table *ls_arp_table;
- const struct shash *meter_groups;
- const struct hmap *lb_datapaths_map;
- const struct sset *bfd_ports;
-@@ -476,9 +474,6 @@ struct ovn_datapath {
- * Valid only if it is logical router datapath. NULL otherwise. */
- struct lrouter_group *lr_group;
-
-- /* Set of localnet or l2gw ports. */
-- struct hmapx phys_ports;
+- HMAP_FOR_EACH_SAFE (pr, key_node, routes) {
+- if (!pr->stale) {
+- continue;
+- }
-
- /* Map of ovn_port objects belonging to this datapath.
- * This map doesn't include derived ports. */
- struct hmap ports;
-@@ -513,6 +508,12 @@ ovn_datapath_is_stale(const struct ovn_datapath *od)
- return !od->nbr && !od->nbs;
- };
+- hmap_remove(routes, &pr->key_node);
+- parsed_route_free(pr);
+- }
+ }
+
+ static char *
+@@ -12868,9 +12857,13 @@ op_put_v4_networks(struct ds *ds, const struct ovn_port *op, bool add_bcast)
+ }
+
+ ds_put_cstr(ds, "{");
+- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
++ for (size_t i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
+ ds_put_format(ds, "%s, ", op->lrp_networks.ipv4_addrs[i].addr_s);
+- if (add_bcast) {
++ /* A /31 point-to-point link (RFC 3021) has no broadcast address:
++ * both addresses in the range are usable hosts. Including the
++ * computed "broadcast" here would drop legitimate traffic from the
++ * /31 peer, so skip it for /31 networks. */
++ if (add_bcast && op->lrp_networks.ipv4_addrs[i].plen != 31) {
+ ds_put_format(ds, "%s, ", op->lrp_networks.ipv4_addrs[i].bcast_s);
+ }
+ }
+@@ -13209,6 +13202,28 @@ build_gw_lrouter_nat_flows_for_lb(struct lrouter_nat_lb_flows_ctx *ctx,
+ bitmap_free(dp_non_meter);
+ }
-+static inline bool
-+ls_has_localnet_port(const struct ovn_datapath *od)
++static bool
++lrouter_lb_vip_is_unsnat_ip(const struct ovn_datapath *od,
++ const struct lr_nat_record *lrnat_rec,
++ const struct ovn_lb_vip *lb_vip)
+{
-+ return !vector_is_empty(&od->localnet_ports);
++ const char *vip = lb_vip->vip_str;
++
++ if (sset_contains(&lrnat_rec->external_ips, vip)) {
++ return true;
++ }
++
++ /* A port-less bypass would also match replies sent to a force-SNAT
++ * address and prevent them from reaching the UNSNAT flow. */
++ return lb_vip->port_str
++ && (lport_addresses_contains_ip(
++ &lrnat_rec->dnat_force_snat_addrs, 1, vip)
++ || lport_addresses_contains_ip(
++ &lrnat_rec->lb_force_snat_addrs, 1, vip)
++ || (lrnat_rec->lb_force_snat_router_ip
++ && sset_contains(&od->router_ips, vip)));
+}
+
- /* Pipeline stages. */
- #define PIPELINE_STAGES \
- /* Logical switch ingress stages. */ \
-@@ -974,10 +975,6 @@ bool lflow_handle_ls_stateful_changes(struct ovsdb_idl_txn *,
- struct ls_stateful_tracked_data *,
- struct lflow_input *,
- struct lflow_table *lflows);
--bool lflow_handle_ls_arp_changes(struct ovsdb_idl_txn *,
-- struct ls_arp_tracked_data *,
-- struct lflow_input *,
-- struct lflow_table *lflows);
- bool northd_handle_sb_port_binding_changes(
- const struct sbrec_port_binding_table *, struct hmap *ls_ports,
- struct hmap *lr_ports);
-diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
-index 4d6370da6b..7108ec7984 100644
---- a/northd/ovn-northd.8.xml
-+++ b/northd/ovn-northd.8.xml
-@@ -310,15 +310,6 @@
- </p>
- </li>
+ static void
+ build_lrouter_nat_flows_for_lb(
+ struct ovn_lb_vip *lb_vip,
+@@ -13358,16 +13373,15 @@ build_lrouter_nat_flows_for_lb(
+ bitmap_set1(aff_dp_bitmap[type], index);
+ }
+
+- if (sset_contains(&lrnat_rec->external_ips, lb_vip->vip_str)) {
+- /* The load balancer vip is also present in the NAT entries.
+- * So add a high priority lflow to advance the the packet
+- * destined to the vip (and the vip port if defined)
+- * in the S_ROUTER_IN_UNSNAT stage.
++ if (lrouter_lb_vip_is_unsnat_ip(od, lrnat_rec, lb_vip)) {
++ /* The load balancer VIP is also present in an UNSNAT flow.
++ * Add a high priority lflow to advance packets destined to the
++ * VIP (and the VIP port if defined) in S_ROUTER_IN_UNSNAT.
+ * There seems to be an issue with ovs-vswitchd. When the new
+- * connection packet destined for the lb vip is received,
+- * it is dnat'ed in the S_ROUTER_IN_DNAT stage in the dnat
++ * connection packet destined for the LB VIP is received,
++ * it is DNATed in the S_ROUTER_IN_DNAT stage in the DNAT
+ * conntrack zone. For the next packet, if it goes through
+- * unsnat stage, the conntrack flags are not set properly, and
++ * UNSNAT stage, the conntrack flags are not set properly, and
+ * it doesn't hit the established state flows in
+ * S_ROUTER_IN_DNAT stage. */
+ ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 120,
+@@ -13586,7 +13600,7 @@ build_lrouter_flows_for_lb(struct ovn_lb_datapaths *lb_dps,
+
+ build_lb_health_check_response_lflows(
+ lflows, lb, lb_vip, &lb->vips_nb[i], lb_dps, lr_datapaths,
+- meter_groups, match);
++ meter_groups, match, action);
+
+ if (!build_empty_lb_event_flow(lb_vip, lb, match, action)) {
+ continue;
+@@ -14588,6 +14602,7 @@ build_neigh_learning_flows_for_lrouter(
+ * REGBIT_LOOKUP_NEIGHBOR_RESULT bit. If
+ * "always_learn_from_arp_request" is set to false,
+ * REGBIT_LOOKUP_NEIGHBOR_IP_RESULT bit is set.
++ * The lookup backs up to eth.src in case when nd.tll == 0.
+ *
+ * For IPv6 ND NS packets, table LOOKUP_NEIGHBOR does a lookup
+ * for the (ip6.src, nd.sll) in the mac binding table using the
+@@ -14625,6 +14640,14 @@ build_neigh_learning_flows_for_lrouter(
+ ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100, "nd_na",
+ ds_cstr(actions), lflow_ref);
+
++ ds_clear(actions);
++ ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT
++ " = lookup_nd(inport, nd.target, eth.src); %s%snext;",
++ learn_from_arp_request ? "" :
++ REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" = 1; ", flood);
++ ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 105,
++ "nd_na && nd.tll == 0", ds_cstr(actions), lflow_ref);
++
+ if (!learn_from_arp_request) {
+ /* Add flow to skip GARP LLA if we don't know it already.
+ * From RFC 2461, section 4.4, Neighbor Advertisement Message
+@@ -14642,16 +14665,18 @@ build_neigh_learning_flows_for_lrouter(
+ ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 110,
+ "nd_na && ip6.src == fe80::/10 && ip6.dst == ff00::/8",
+ ds_cstr(actions), lflow_ref);
+- }
+
+- ds_clear(actions);
+- ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT
+- " = lookup_nd(inport, ip6.src, nd.sll); %snext;",
+- learn_from_arp_request ? "" :
+- REGBIT_LOOKUP_NEIGHBOR_IP_RESULT
+- " = lookup_nd_ip(inport, ip6.src); ");
+- ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100, "nd_ns",
+- ds_cstr(actions), lflow_ref);
++ ds_clear(actions);
++ ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT
++ " = lookup_nd(inport, nd.target, eth.src); "
++ REGBIT_LOOKUP_NEIGHBOR_IP_RESULT
++ " = lookup_nd_ip(inport, nd.target); %snext;",
++ flood);
++ ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 115,
++ "nd_na && nd.tll == 0 && "
++ "ip6.src == fe80::/10 && ip6.dst == ff00::/8",
++ ds_cstr(actions), lflow_ref);
++ }
+
+ /* For other packet types, we can skip neighbor learning.
+ * So set REGBIT_LOOKUP_NEIGHBOR_RESULT to 1. */
+@@ -14716,7 +14741,7 @@ build_neigh_learning_flows_for_lrouter_port(
+ "always_learn_from_arp_request", true);
+
+ /* Check if we need to learn mac-binding from ARP requests. */
+- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
++ for (size_t i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
+ if (!learn_from_arp_request) {
+ /* ARP request to this address should always get learned,
+ * so add a priority-110 flow to set
+@@ -14762,6 +14787,57 @@ build_neigh_learning_flows_for_lrouter_port(
+ WITH_HINT(&op->nbrp->header_));
+ }
+
++ /* Check if we need to learn mac-binding from ND NS. */
++ for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
++ if (in6_is_lla(&op->lrp_networks.ipv6_addrs[i].addr)) {
++ continue;
++ }
++
++ if (!learn_from_arp_request) {
++ /* ND NS request to this address should always get learned,
++ * so add a priority-110 flow to set
++ * REGBIT_LOOKUP_NEIGHBOR_IP_RESULT to 1. */
++ ds_clear(match);
++ ds_put_format(match,
++ "inport == %s && ip6.src == %s/%u && "
++ "nd.target == %s && nd_ns",
++ op->json_key,
++ op->lrp_networks.ipv6_addrs[i].network_s,
++ op->lrp_networks.ipv6_addrs[i].plen,
++ op->lrp_networks.ipv6_addrs[i].addr_s);
++ if (lrp_is_l3dgw(op)) {
++ ds_put_format(match, " && is_chassis_resident(%s)",
++ op->cr_port->json_key);
++ }
++ const char *actions_s = REGBIT_LOOKUP_NEIGHBOR_RESULT
++ " = lookup_nd(inport, ip6.src, nd.sll); "
++ REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" = 1;"
++ " next;";
++ ovn_lflow_add(lflows, op->od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 110,
++ ds_cstr(match), actions_s, lflow_ref,
++ WITH_HINT(&op->nbrp->header_));
++ }
++ ds_clear(match);
++ ds_put_format(match,
++ "inport == %s && ip6.src == %s/%u && nd_ns",
++ op->json_key,
++ op->lrp_networks.ipv6_addrs[i].network_s,
++ op->lrp_networks.ipv6_addrs[i].plen);
++ if (lrp_is_l3dgw(op)) {
++ ds_put_format(match, " && is_chassis_resident(%s)",
++ op->cr_port->json_key);
++ }
++ ds_clear(actions);
++ ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT
++ " = lookup_nd(inport, ip6.src, nd.sll); %snext;",
++ learn_from_arp_request ? "" :
++ REGBIT_LOOKUP_NEIGHBOR_IP_RESULT
++ " = lookup_nd_ip(inport, ip6.src); ");
++ ovn_lflow_add(lflows, op->od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100,
++ ds_cstr(match), ds_cstr(actions), lflow_ref,
++ WITH_HINT(&op->nbrp->header_));
++ }
++
+ if (lrp_is_l3dgw(op)) {
+ ds_clear(match);
+ ds_put_format(match, "inport == %s && (nd_na || nd_ns) && "
+@@ -15107,6 +15183,14 @@ route_policies_lookup(struct hmap *route_policies, size_t hash,
+ continue;
+ }
+
++ if (rp->chain_id != new_rp->chain_id) {
++ continue;
++ }
++
++ if (rp->jump_chain_id != new_rp->jump_chain_id) {
++ continue;
++ }
++
+ if (rp->n_valid_nexthops != new_rp->n_valid_nexthops) {
+ continue;
+ }
+@@ -15172,12 +15256,6 @@ build_route_policies(struct ovn_datapath *od, const struct hmap *lr_ports,
+ {
+ struct route_policy *rp;
+
+- HMAP_FOR_EACH (rp, key_node, route_policies) {
+- if (rp->nbr == od->nbr) {
+- rp->stale = true;
+- }
+- }
+-
+ /* Create chain numeric ids for policies with chain name set */
+ for (int i = 0; i < od->nbr->n_policies; i++) {
+ const struct nbrec_logical_router_policy *rule = od->nbr->policies[i];
+@@ -15284,7 +15362,6 @@ build_route_policies(struct ovn_datapath *od, const struct hmap *lr_ports,
+ new_rp->rule = rule;
+ new_rp->n_valid_nexthops = n_valid_nexthops;
+ new_rp->valid_nexthops = valid_nexthops;
+- new_rp->nbr = od->nbr;
+ new_rp->chain_id = chain_id;
+ new_rp->jump_chain_id = jump_chain_id;
+
+@@ -15293,21 +15370,10 @@ build_route_policies(struct ovn_datapath *od, const struct hmap *lr_ports,
+ if (!rp) {
+ hmap_insert(route_policies, &new_rp->key_node, hash);
+ } else {
+- rp->stale = false;
+ free(valid_nexthops);
+ free(new_rp);
+ }
+ }
+-
+- HMAP_FOR_EACH_SAFE (rp, key_node, route_policies) {
+- if (!rp->stale) {
+- continue;
+- }
+-
+- hmap_remove(route_policies, &rp->key_node);
+- free(rp->valid_nexthops);
+- free(rp);
+- }
+ }
+
+ /* Logical router ingress table POLICY: Policy.
+@@ -16209,53 +16275,10 @@ build_lr_gateway_redirect_flows_for_nats(
+ static void
+ build_arp_request_flows_for_lrouter(
+ struct ovn_datapath *od, struct lflow_table *lflows,
+- struct ds *match, struct ds *actions,
+ const struct shash *meter_groups,
+ struct lflow_ref *lflow_ref)
+ {
+ ovs_assert(od->nbr);
+- for (int i = 0; i < od->nbr->n_static_routes; i++) {
+- const struct nbrec_logical_router_static_route *route;
+-
+- route = od->nbr->static_routes[i];
+- struct in6_addr gw_ip6;
+- unsigned int plen;
+- char *error = ipv6_parse_cidr(route->nexthop, &gw_ip6, &plen);
+- if (error || plen != 128) {
+- free(error);
+- continue;
+- }
+-
+- ds_clear(match);
+- ds_put_format(match, "eth.dst == 00:00:00:00:00:00 && "
+- REGBIT_NEXTHOP_IS_IPV4" == 0 && "
+- REG_NEXT_HOP_IPV6 " == %s",
+- route->nexthop);
+- struct in6_addr sn_addr;
+- struct eth_addr eth_dst;
+- in6_addr_solicited_node(&sn_addr, &gw_ip6);
+- ipv6_multicast_to_ethernet(ð_dst, &sn_addr);
+-
+- char sn_addr_s[INET6_ADDRSTRLEN + 1];
+- ipv6_string_mapped(sn_addr_s, &sn_addr);
+-
+- ds_clear(actions);
+- ds_put_format(actions,
+- "nd_ns { "
+- "eth.dst = "ETH_ADDR_FMT"; "
+- "ip6.dst = %s; "
+- "nd.target = %s; "
+- "output; "
+- "}; next;", ETH_ADDR_ARGS(eth_dst), sn_addr_s,
+- route->nexthop);
+-
+- ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 200,
+- ds_cstr(match), ds_cstr(actions), lflow_ref,
+- WITH_CTRL_METER(copp_meter_get(COPP_ND_NS_RESOLVE,
+- od->nbr->copp,
+- meter_groups)),
+- WITH_HINT(&route->header_));
+- }
+
+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 100,
+ "eth.dst == 00:00:00:00:00:00 && "
+@@ -16735,17 +16758,9 @@ build_lrouter_ipv4_default_ttl_expired_flows(
+ return;
+ }
+
+- struct ds ip_ds = DS_EMPTY_INITIALIZER;
+ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
+ ds_clear(match);
+ ds_clear(actions);
+- ds_clear(&ip_ds);
+- if (lrp_is_l3dgw(op)) {
+- ds_put_cstr(&ip_ds, "ip4.dst <-> ip4.src");
+- } else {
+- ds_put_format(&ip_ds, "ip4.dst = ip4.src; ip4.src = %s",
+- op->lrp_networks.ipv4_addrs[i].addr_s);
+- }
+ ds_put_format(match,
+ "inport == %s && ip4 && "
+ "ip4.src == %s/%d && "
+@@ -16753,23 +16768,28 @@ build_lrouter_ipv4_default_ttl_expired_flows(
+ op->json_key,
+ op->lrp_networks.ipv4_addrs[i].network_s,
+ op->lrp_networks.ipv4_addrs[i].plen);
++ if (lrp_is_l3dgw(op)) {
++ ds_put_format(match, " && is_chassis_resident(%s)",
++ op->cr_port->json_key);
++ }
++
+ ds_put_format(actions,
+ "icmp4 {"
+- "eth.dst <-> eth.src; "
++ "eth.dst = eth.src; eth.src = %s; "
+ "icmp4.type = 11; /* Time exceeded */ "
+ "icmp4.code = 0; /* TTL exceeded in transit */ "
+- "%s ; ip.ttl = 254; "
++ "ip4.dst = ip4.src; ip4.src = %s; ip.ttl = 254; "
+ "outport = %s; flags.loopback = 1; output; };",
+- ds_cstr(&ip_ds), op->json_key);
++ op->lrp_networks.ea_s,
++ op->lrp_networks.ipv4_addrs[i].addr_s, op->json_key);
++
+ ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 31,
+ ds_cstr(match), ds_cstr(actions), lflow_ref,
+ WITH_CTRL_METER(copp_meter_get(COPP_ICMP4_ERR,
+ op->od->nbr->copp,
+ meter_groups)),
+ WITH_HINT(&op->nbrp->header_));
+-
+ }
+- ds_destroy(&ip_ds);
+ ds_clear(match);
+ ds_clear(actions);
+
+@@ -16780,15 +16800,19 @@ build_lrouter_ipv4_default_ttl_expired_flows(
+ "inport == %s && ip4 && "
+ "ip.ttl == {0, 1} && !ip.later_frag",
+ op->json_key);
++ if (lrp_is_l3dgw(op)) {
++ ds_put_format(match, " && is_chassis_resident(%s)",
++ op->cr_port->json_key);
++ }
+ ds_put_format(actions,
+ "icmp4 {"
+- "eth.dst <-> eth.src; "
++ "eth.dst = eth.src; eth.src = %s; "
+ "icmp4.type = 11; /* Time exceeded */ "
+ "icmp4.code = 0; /* TTL exceeded in transit */ "
+ "ip4.dst = ip4.src; ip4.src = %s; ip.ttl = 254; "
+- "outport = %s; flags.loopback = 1; output; };",
+- op->lrp_networks.ipv4_addrs[0].addr_s,
+- op->json_key);
++ "outport = %s; flags.loopback = 1; output; };",
++ op->lrp_networks.ea_s, op->lrp_networks.ipv4_addrs[0].addr_s,
++ op->json_key);
+ ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 30,
+ ds_cstr(match), ds_cstr(actions), lflow_ref,
+ WITH_CTRL_METER(copp_meter_get(COPP_ICMP4_ERR,
+@@ -16811,17 +16835,9 @@ build_lrouter_ipv6_default_ttl_expired_flows(
+ return;
+ }
+
+- struct ds ip_ds = DS_EMPTY_INITIALIZER;
+ for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs - 1; i++) {
+ ds_clear(match);
+ ds_clear(actions);
+- ds_clear(&ip_ds);
+- if (lrp_is_l3dgw(op)) {
+- ds_put_cstr(&ip_ds, "ip6.dst <-> ip6.src");
+- } else {
+- ds_put_format(&ip_ds, "ip6.dst = ip6.src; ip6.src = %s",
+- op->lrp_networks.ipv6_addrs[i].addr_s);
+- }
+ ds_put_format(match,
+ "inport == %s && ip6 && "
+ "ip6.src == %s/%d && "
+@@ -16829,14 +16845,20 @@ build_lrouter_ipv6_default_ttl_expired_flows(
+ op->json_key,
+ op->lrp_networks.ipv6_addrs[i].network_s,
+ op->lrp_networks.ipv6_addrs[i].plen);
++ if (lrp_is_l3dgw(op)) {
++ ds_put_format(match, " && is_chassis_resident(%s)",
++ op->cr_port->json_key);
++ }
+ ds_put_format(actions,
+ "icmp6 {"
+- "eth.dst <-> eth.src; "
+- "%s ; ip.ttl = 254; "
++ "eth.dst = eth.src; eth.src = %s; "
++ "ip6.dst = ip6.src; ip6.src = %s; ip.ttl = 254; "
+ "icmp6.type = 3; /* Time exceeded */ "
+ "icmp6.code = 0; /* TTL exceeded in transit */ "
+ "outport = %s; flags.loopback = 1; output; };",
+- ds_cstr(&ip_ds), op->json_key);
++ op->lrp_networks.ea_s,
++ op->lrp_networks.ipv6_addrs[i].addr_s, op->json_key);
++
+ ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 31,
+ ds_cstr(match), ds_cstr(actions), lflow_ref,
+ WITH_CTRL_METER(copp_meter_get(COPP_ICMP6_ERR,
+@@ -16844,7 +16866,6 @@ build_lrouter_ipv6_default_ttl_expired_flows(
+ meter_groups)),
+ WITH_HINT(&op->nbrp->header_));
+ }
+- ds_destroy(&ip_ds);
+ ds_clear(match);
+ ds_clear(actions);
+
+@@ -16855,14 +16876,18 @@ build_lrouter_ipv6_default_ttl_expired_flows(
+ "inport == %s && ip6 && "
+ "ip.ttl == {0, 1} && !ip.later_frag",
+ op->json_key);
++ if (lrp_is_l3dgw(op)) {
++ ds_put_format(match, " && is_chassis_resident(%s)",
++ op->cr_port->json_key);
++ }
+ ds_put_format(actions,
+ "icmp6 {"
+- "eth.dst <-> eth.src; "
++ "eth.dst = eth.src; eth.src = %s; "
+ "ip6.dst = ip6.src; ip6.src = %s; "
+ "ip.ttl = 254; icmp6.type = 3; /* Time exceeded */ "
+ "icmp6.code = 0; /* TTL exceeded in transit */ "
+ "outport = %s; flags.loopback = 1; output; };",
+- op->lrp_networks.ipv6_addrs[0].addr_s,
++ op->lrp_networks.ea_s, op->lrp_networks.ipv6_addrs[0].addr_s,
+ op->json_key);
+ ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 30,
+ ds_cstr(match), ds_cstr(actions), lflow_ref,
+@@ -19404,9 +19429,7 @@ build_lswitch_and_lrouter_iterate_by_lr(struct ovn_datapath *od,
+ build_gateway_redirect_flows_for_lrouter(od, lsi->lflows, &lsi->match,
+ &lsi->actions,
+ od->datapath_lflows);
+- build_arp_request_flows_for_lrouter(od, lsi->lflows, &lsi->match,
+- &lsi->actions,
+- lsi->meter_groups,
++ build_arp_request_flows_for_lrouter(od, lsi->lflows, lsi->meter_groups,
+ od->datapath_lflows);
+ build_ecmp_stateful_egr_flows_for_lrouter(od, lsi->lflows,
+ od->datapath_lflows);
+@@ -19454,7 +19477,6 @@ build_lswitch_and_lrouter_iterate_by_lsp(struct ovn_port *op,
+ meter_groups, actions, match);
+ build_lswitch_dhcp_options_and_response(op, lflows, meter_groups);
+ build_lswitch_external_port(op, lflows);
+- build_lswitch_external_lsp_inport_rewrite(op, lflows, match, actions);
+ build_lswitch_icmp_packet_toobig_admin_flows(op, lflows, match, actions);
+ build_lswitch_ip_unicast_lookup(op, lflows, actions,
+ match);
+@@ -20119,9 +20141,6 @@ lflow_handle_northd_port_changes(struct ovsdb_idl_txn *ovnsb_txn,
+ if (!handled) {
+ return false;
+ }
+-
+- /* SB port_binding is not deleted, so don't update SB multicast
+- * groups. */
+ }
+
+ HMAPX_FOR_EACH (hmapx_node, &trk_lsps->created) {
+@@ -20129,20 +20148,6 @@ lflow_handle_northd_port_changes(struct ovsdb_idl_txn *ovnsb_txn,
+ /* Make sure 'op' is an lsp and not lrp. */
+ ovs_assert(op->nbsp);
+
+- if (!lsp_can_receive_multicast(op->nbsp)) {
+- continue;
+- }
+-
+- const struct sbrec_multicast_group *sbmc_flood =
+- mcast_group_lookup(lflow_input->sbrec_mcast_group_by_name_dp,
+- MC_FLOOD, op->od->sdp->sb_dp);
+- const struct sbrec_multicast_group *sbmc_flood_l2 =
+- mcast_group_lookup(lflow_input->sbrec_mcast_group_by_name_dp,
+- MC_FLOOD_L2, op->od->sdp->sb_dp);
+- const struct sbrec_multicast_group *sbmc_unknown =
+- mcast_group_lookup(lflow_input->sbrec_mcast_group_by_name_dp,
+- MC_UNKNOWN, op->od->sdp->sb_dp);
+-
+ struct ds match = DS_EMPTY_INITIALIZER;
+ struct ds actions = DS_EMPTY_INITIALIZER;
+ build_lswitch_and_lrouter_iterate_by_lsp(op, lflow_input->ls_ports,
+@@ -20177,30 +20182,6 @@ lflow_handle_northd_port_changes(struct ovsdb_idl_txn *ovnsb_txn,
+ if (!handled) {
+ return false;
+ }
+-
+- /* Update SB multicast groups for the new port. */
+- if (!sbmc_flood) {
+- sbmc_flood = create_sb_multicast_group(ovnsb_txn,
+- op->od->sdp->sb_dp, MC_FLOOD, OVN_MCAST_FLOOD_TUNNEL_KEY);
+- }
+- sbrec_multicast_group_update_ports_addvalue(sbmc_flood, op->sb);
+-
+- if (!sbmc_flood_l2) {
+- sbmc_flood_l2 = create_sb_multicast_group(ovnsb_txn,
+- op->od->sdp->sb_dp, MC_FLOOD_L2,
+- OVN_MCAST_FLOOD_L2_TUNNEL_KEY);
+- }
+- sbrec_multicast_group_update_ports_addvalue(sbmc_flood_l2, op->sb);
+-
+- if (op->has_unknown) {
+- if (!sbmc_unknown) {
+- sbmc_unknown = create_sb_multicast_group(ovnsb_txn,
+- op->od->sdp->sb_dp, MC_UNKNOWN,
+- OVN_MCAST_UNKNOWN_TUNNEL_KEY);
+- }
+- sbrec_multicast_group_update_ports_addvalue(sbmc_unknown,
+- op->sb);
+- }
+ }
+
+ return true;
+diff --git a/northd/northd.h b/northd/northd.h
+index f7e99090bf..1a94ec7dc5 100644
+--- a/northd/northd.h
++++ b/northd/northd.h
+@@ -209,8 +209,6 @@ struct route_policy {
+ const struct nbrec_logical_router_policy *rule;
+ size_t n_valid_nexthops;
+ char **valid_nexthops;
+- const struct nbrec_logical_router *nbr;
+- bool stale;
+ uint32_t chain_id;
+ uint32_t jump_chain_id;
+ };
+diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
+index 7108ec7984..3d03381a7a 100644
+--- a/northd/ovn-northd.8.xml
++++ b/northd/ovn-northd.8.xml
+@@ -210,6 +210,45 @@
+ <p> Reset <code>ovn-northd</code> engine counters. </p>
+ </dd>
+
++ <dt><code>inc-engine/recompute</code></dt>
++ <dd>
++ Triggers a full recompute of the incremental processing engine
++ on the next iteration.
++ </dd>
++
++ <dt><code>inc-engine/compute-log-timeout</code> <var>msecs</var></dt>
++ <dd>
++ Sets the timeout in milliseconds for logging engine compute
++ events.
++ </dd>
++
++ <dt><code>inc-engine/list-stopwatches</code> [<var>node</var>]</dt>
++ <dd>
++ Lists the engine nodes and their change handler names. If
++ <var>node</var> is specified, only the handlers for that engine
++ node are listed.
++ </dd>
++
++ <dt><code>inc-engine/enable-stopwatch</code> [<var>node</var>]</dt>
++ <dd>
++ Enables the per-node and per-handler stopwatches in the
++ incremental processing engine. If <var>node</var> is specified,
++ only that node's stopwatches are enabled; otherwise all nodes
++ are affected. While enabled, every engine node recompute and
++ change handler invocation is timed and the results can be
++ viewed with the <code>stopwatch/show</code> command.
++ Stopwatches are disabled by default because they add measurable
++ CPU overhead.
++ </dd>
++
++ <dt><code>inc-engine/disable-stopwatch</code> [<var>node</var>]</dt>
++ <dd>
++ Disables the per-node and per-handler stopwatches in the
++ incremental processing engine. If <var>node</var> is specified,
++ only that node's stopwatches are disabled; otherwise all nodes
++ are affected. This is the default state.
++ </dd>
++
+ </dl>
+ </p>
+
+@@ -2359,6 +2398,14 @@ output;
+ </p>
+ </li>
+
++ <li>
++ Priority-90 flows for each IPv4 address/VIP/NAT address owned by a
++ router port connected to the switch. These flows match GARP packets
++ for the specific IP addresses. Matched packets are forwarded to the
++ <code>MC_FLOOD_L2</code> multicast group which contains all non-router
++ logical ports.
++ </li>
++
+ <li>
+ Priority-90 flows for transit switches that forward registered
+ IP multicast traffic to their corresponding multicast group , which
+@@ -2422,8 +2469,8 @@ output;
+ router port connected to the switch. These flows match ARP requests
+ and ND packets for the specific IP addresses. Matched packets are
+ forwarded only to the router that owns the IP address and to the
+- <code>MC_FLOOD_L2</code> multicast group which contains all non-router
+- logical ports.
++ <code>MC_UNKNOWN</code> multicast group which contains all enabled
++ logical ports that accept unknown destination packets.
+ </li>
+
+ <li>
+@@ -2446,7 +2493,7 @@ output;
+ <li>
+ A priority-72 flow that outputs all ARP requests and ND NS (Neighbor
+ Solicitation) packets with an Ethernet broadcast or multicast
+- <code>eth.dst</code> to the <code>MC_FLOOD_L2</code> multicast group
++ <code>eth.dst</code> to the <code>MC_UNKNOWN</code> multicast group
+ if <code>other_config:broadcast-arps-to-all-routers=false</code>.
+ </li>
+
+@@ -3317,6 +3364,31 @@ next;
+ </pre>
+ </li>
+
++ <li>
++ <p>
++ A higher priority-105 flow which matches on IPv6 Neighbor Discovery
++ advertisement packet with <code>nd.tll == 0</code> and applies the
++ actions if the option <code>>always_learn_from_arp_request</code>
++ is <code>>true</code>:
++ </p>
++
++ <pre>
++ reg9[2] = lookup_nd(inport, nd.target, eth.src);
++ next;
++ </pre>
++
++ <p>
++ If the option <code>always_learn_from_arp_request</code>> is
++ <code>false</code>, a priority-115 flow is added instead:
++ </p>
++
++ <pre>
++ reg9[2] = lookup_nd(inport, nd.target, eth.src);
++ reg9[3] = lookup_nd_ip(inport, nd.target);
++ next;
++ </pre>
++ </li>
++
+ <li>
+ <p>
+ A priority-100 flow which matches on IPv6 Neighbor Discovery
+@@ -3628,7 +3700,9 @@ icmp6_error {
+ </li>
+ <li>
+ <code>ip4.src</code> is the broadcast address of any IP network
+- known to the router.
++ known to the router. Point-to-point (<code>/31</code>, RFC 3021)
++ networks have no broadcast address and are excluded, so that
++ traffic from a <code>/31</code> peer is not dropped.
+ </li>
+ </ul>
+ </li>
+@@ -4024,7 +4098,10 @@ nd.tll = <var>external_mac</var>;
+ == <var>P</var> && ip.ttl == {0, 1} &&
+ !ip.later_frag</code> matches packets whose TTL has expired, with the
+ following actions to send an ICMP time exceeded reply for IPv4 and
+- IPv6 respectively:
++ IPv6 respectively. If the router port <code>P</code> is a
++ distributed gateway router port, then the
++ <code>is_chassis_resident(cr-P)</code> is also added in the match
++ condition:
+ </p>
+
+ <pre>
+@@ -5741,30 +5818,6 @@ arp {
+ };
+ </pre>
+
+- <p>
+- Unknown MAC address. For each IPv6 static route associated with the
+- router with the nexthop IP: <var>G</var>, a priority-200 flow
+- for IPv6 packets with match
+- <code>eth.dst == 00:00:00:00:00:00 &&
+- xxreg0 == <var>G</var></code>
+- with the following actions is added:
+- </p>
+-
+- <pre>
+-nd_ns {
+- eth.dst = <var>E</var>;
+- ip6.dst = <var>I</var>
+- nd.target = <var>G</var>;
+- output;
+-};
+- </pre>
+-
+- <p>
+- Where <var>E</var> is the multicast mac derived from the Gateway IP,
+- <var>I</var> is the solicited-node multicast address corresponding
+- to the target address <var>G</var>.
+- </p>
+-
+ <p>
+ Unknown MAC address. A priority-100 flow for IPv6 packets with match
+ <code>eth.dst == 00:00:00:00:00:00</code> has the following actions:
+diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
+index 22ef3dabe9..789cf2ee00 100644
+--- a/northd/ovn-northd.c
++++ b/northd/ovn-northd.c
+@@ -983,6 +983,9 @@ main(int argc, char *argv[])
+ ovsdb_idl_omit_alert(ovnsb_idl_loop.idl,
+ &sbrec_advertised_mac_binding_columns[i]);
+ }
++ for (size_t i = 0; i < SBREC_DNS_N_COLUMNS; i++) {
++ ovsdb_idl_omit_alert(ovnsb_idl_loop.idl, &sbrec_dns_columns[i]);
++ }
+
+ unixctl_command_register("sb-connection-status", "", 0, 0,
+ ovn_conn_show, ovnsb_idl_loop.idl);
+@@ -1172,7 +1175,7 @@ main(int argc, char *argv[])
+ ovsdb_idl_wait(ovnsb_idl_loop.idl);
+
+ /* Force a full recompute next time we become active. */
+- inc_proc_northd_force_recompute_immediate();
++ inc_proc_northd_force_recompute();
+ }
+
+ if (clear_idl_track) {
+@@ -1240,6 +1243,7 @@ ovn_northd_pause(struct unixctl_conn *conn, int argc OVS_UNUSED,
+ struct northd_state *state = state_;
+ state->paused = true;
+
++ poll_immediate_wake();
+ unixctl_command_reply(conn, NULL);
+ }
+
+diff --git a/ovn-nb.xml b/ovn-nb.xml
+index 64fdf25e4e..422f028aea 100644
+--- a/ovn-nb.xml
++++ b/ovn-nb.xml
+@@ -988,24 +988,6 @@
+ </p>
+ </column>
+
+- <column name="other_config" key="dynamic-routing-arp-prefer-local"
+- type='{"type": "boolean"}'>
+- <p>
+- This option defines the preference of ARP/ND lookup. If set to
+- true OVN routers connected to EVPN Logical Switches on which remote
+- neighbor entries have been learned (Type-2 MAC+IP EVPN routes) will
+- give precedence to any ARP/ND entries they might have in the SB
+- <code>Mac_Binding</code> table before trying to resolve the MAC
+- address via the <code>ovn-controller</code> local EVPN ARP/ND cache.
+- The option defaults to false.
+- </p>
+-
+- <p>
+- Only relevant if <ref column="other_config" key="dynamic-routing-vni"
+- table="Logical_switch"/> is set to valid VNI.
+- </p>
+- </column>
+-
+ <column name="other_config" key="dynamic-routing-redistribute"
+ type='{"type": "string"}'>
+ <p>
+@@ -1015,9 +997,13 @@
+
+ <p>
+ If <code>fdb</code> is specified then ovn-controller will advertise
+- all workloads that are local to the chassis. The applies to VIFs,
++ all workloads that are local to the chassis. This applies to VIFs,
+ container ports, virtual ports, connected DGPs and connected GW
+- routers.
++ routers. Additionally, dynamically learned FDB entries (from ports
++ with <code>unknown</code> addresses) are also advertised. For VIF
++ ports, only entries learned on the local chassis are advertised;
++ for localnet ports, entries are advertised on all chassis where the
++ datapath is local.
+ </p>
+
+ <p>
+@@ -1643,7 +1629,7 @@
+ Default: <code>false</code>.
+ </column>
+
+- <column name="options" key="lsp_learn_mac"
++ <column name="options" key="lsp_learn_fdb"
+ type='{"type": "boolean"}'>
+ If set to <code>false</code>, source MAC address is not learnt by FDB
+ when packets are received on this port. The default value is
+diff --git a/ovn-sb.xml b/ovn-sb.xml
+index 92496b9115..7e6db20ab9 100644
+--- a/ovn-sb.xml
++++ b/ovn-sb.xml
+@@ -4106,6 +4106,14 @@ tcp.flags = RST;
+ </li>
+ </ol>
+
++ <p>
++ In EVPN deployments, <code>ovn-controller</code> also writes rows to
++ this table for MAC bindings learned through EVPN Type-2 routes (via
++ FRR and the kernel neighbor table). These entries are keyed by the
++ router port connected to the EVPN logical switch and behave identically
++ to ARP/ND-learned bindings for consumption and aging purposes.
++ </p>
++
+ <column name="logical_port">
+ The logical port on which the binding was discovered.
+ </column>
+diff --git a/tests/atlocal.in b/tests/atlocal.in
+index 477d56a0fd..5f523e51fc 100644
+--- a/tests/atlocal.in
++++ b/tests/atlocal.in
+@@ -150,17 +150,8 @@ find_command()
+ fi
+ }
+
+-# Set HAVE_NC
+-find_command nc
+-
+-# Determine correct netcat option to quit on stdin EOF
+-if nc --version 2>&1 | grep -q nmap.org; then
+- # Nmap netcat
+- NC_EOF_OPT="--send-only -w 5"
+-else
+- # BSD netcat
+- NC_EOF_OPT="-q 1 -w 5"
+-fi
++# Set HAVE_NCAT
++find_command ncat
+
+ # Set HAVE_TCPDUMP
+ find_command tcpdump
+diff --git a/tests/automake.mk b/tests/automake.mk
+index 2dfc0bfa7e..875a07c903 100644
+--- a/tests/automake.mk
++++ b/tests/automake.mk
+@@ -289,6 +289,7 @@ tests_ovstest_SOURCES = \
+ tests/test-utils.h \
+ tests/test-ovn.c \
+ tests/test-sparse-array.c \
++ tests/test-spsc-ring.c \
+ tests/test-vector.c \
+ controller/test-lflow-cache.c \
+ controller/test-vif-plug.c \
+diff --git a/tests/checkpatch.at b/tests/checkpatch.at
+index b874b1ea6e..352a5f4d2b 100755
+--- a/tests/checkpatch.at
++++ b/tests/checkpatch.at
+@@ -656,6 +656,15 @@ try_checkpatch \
+ ovn-nbctl lsp-add ls
+ "
+
++try_checkpatch \
++ "COMMON_PATCH_HEADER([tests/something.at])
++ +ovn_as az1 ovn-nbctl lsp-add ls
++ " \
++ "WARNING: ovn-nbctl return should be checked Consider adding check or check_uuid in front.
++ #8 FILE: tests/something.at:1:
++ ovn_as az1 ovn-nbctl lsp-add ls
++"
++
+ # Verify that existing check makes checkpatch happy.
+ try_checkpatch \
+ "COMMON_PATCH_HEADER([tests/something.at])
+diff --git a/tests/multinode-macros.at b/tests/multinode-macros.at
+index 4a74d51277..a19b6eae94 100644
+--- a/tests/multinode-macros.at
++++ b/tests/multinode-macros.at
+@@ -41,6 +41,30 @@ m4_define([M_START_TCPDUMP],
+ ]
+ )
+
++m4_define([M_START_TCPDUMPS_RECURSIVE_], [
++ m4_if(m4_eval($# >= 3), [1], [dnl
++ names="$names $3"
++ echo "Running podman exec $1 tcpdump -l $2 >$3.tcpdump 2>$3.stderr"
++ podman exec $1 tcpdump -l $2 >$3.tcpdump 2>$3.stderr &
++ echo "podman exec $1 ps -ef | grep -v grep | grep tcpdump && podman exec $1 killall tcpdump" >> cleanup
++ M_START_TCPDUMPS_RECURSIVE_(m4_shift(m4_shift(m4_shift($@))))
++ ])
++ ]
++)
++
++# M_START_TCPDUMPS([fake node1], [params1], [name1],
++# [fake node2], [params2], [name2], ...)
++# Start multiple tcpdump. Useful to speed up when many tcpdump
++# must be started as waiting for "listening" takes usually 1 second.
++m4_define([M_START_TCPDUMPS],
++ [
++ names=""
++ M_START_TCPDUMPS_RECURSIVE_($@)
++ for name in $names; do
++ OVS_WAIT_UNTIL([grep -q "listening" ${name}.stderr])
++ done
++ ]
++)
+
+ # M_FORMAT_CT([ip-addr])
+ #
+@@ -50,13 +74,12 @@ m4_define([M_START_TCPDUMP],
+ m4_define([M_FORMAT_CT],
+ [[grep -F "dst=$1," | sed -e 's/id=[0-9]*/id=<cleared>/g' -e 's/state=[0-9_A-Z]*/state=<cleared>/g' | sort | uniq | sed -e 's/zone=[[0-9]]*/zone=<cleared>/' -e 's/mark=[[0-9]]*/mark=<cleared>/' ]])
+
+-# M_FORMAT_CURL([ip-addr], [port])
++# OVN_CURL([CURL_ARGS])
+ #
+-# Strip content from the piped input which would differ from test to test
+-# and limit the output to the rows containing 'ip-addr' and 'port'.
+-#
+-m4_define([M_FORMAT_CURL],
+- [[sed 's/\(.*\)Connected to $1 ($1) port $2/Connected to $1 ($1) port $2\n/' | sed 's/\(.*\)200 OK/200 OK\n/' | grep -i -e connected -e "200 OK" | uniq ]])
++# Execute curl with version-agnostic -w output formatting.
++# On unsuccessful connections, output also contains "Connected to () port -1" and the http
++# code (e.g. 000) from the -w format, but those are harmless as failure detection uses grep.
++m4_define([OVN_CURL], [curl -s -S -o /dev/null -w "Connected to %{remote_ip} (%{remote_ip}) port %{remote_port}\n%{http_code}\n" $1 2>&1 | sed "s/^200$/200 OK/"])
+
+ # CHECK_VRF()
+ #
+@@ -480,6 +503,30 @@ m_is_fedora() {
+ m_central_as grep -qi fedora /etc/os-release
+ }
+
++# Run ovs-vsctl using Host socket
++host_ovs_vsctl() {
++ # Discover host OVS socket on first call
++ if [[ -z "$HOST_OVS_SOCK" ]]; then
++ for sock in /run/openvswitch/db.sock /var/run/openvswitch/db.sock /usr/local/var/run/openvswitch/db.sock; do
++ if [[ -S "$sock" ]]; then
++ HOST_OVS_SOCK=$sock
++ break
++ fi
++ done
++ # Fallback on unusual prefix: discover from running process
++ if [[ -z "$HOST_OVS_SOCK" ]]; then
++ HOST_OVS_SOCK=$(ps aux | grep '[o]vsdb-server' | grep -oP 'punix:\K[^, ]+' | while read s; do
++ [[ -S "$s" ]] && [[ "$s" != *"$OVS_RUNDIR"* ]] && echo "$s" && break
++ done)
++ fi
++ if [[ -z "$HOST_OVS_SOCK" ]]; then
++ echo "ERROR: Could not find host OVS socket" >&2
++ AT_FAIL_IF([:])
++ fi
++ fi
++ ovs-vsctl --db=unix:$HOST_OVS_SOCK "$@"
++}
++
+ # M_START_L4_SERVER([fake_node], [namespace], [ip_addr], [port], [reply_string], [pidfile])
+ #
+ # Helper to properly start l4 server in inside 'fake_node''s namespace'.
+diff --git a/tests/multinode.at b/tests/multinode.at
+index 069f2a677d..9aec200650 100644
+--- a/tests/multinode.at
++++ b/tests/multinode.at
+@@ -173,7 +173,7 @@ M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route add 10.0.0.0/24 dev sw0p1])
+ M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route add default via 10.0.0.1 dev sw0p1])
+
+ m_as ovn-chassis-1 ip route change 170.168.0.0/16 mtu 1400 dev eth1
+-M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping6 -c 5 -s 1450 -M do 2000::3 2>&1 | grep -q -i "message too long"])
++M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -6 -c 5 -s 1450 -M do 2000::3 2>&1 | grep -q -i "message too long"])
+
+ M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -q -c 3 -i 0.3 -w 2 172.20.1.2 | FORMAT_PING], \
+ [0], [dnl
+@@ -399,7 +399,7 @@ M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route add 10.0.0.0/24 dev sw0p1])
+ M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route add default via 10.0.0.1 dev sw0p1])
+
+ m_as ovn-chassis-1 ip route change 170.168.0.0/16 mtu 1400 dev eth1
+-M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping6 -c 5 -s 1450 -M do 2000::3 2>&1 | grep -q -i "message too long"])
++M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -6 -c 5 -s 1450 -M do 2000::3 2>&1 | grep -q -i "message too long"])
+
+ M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route flush dev sw0p1])
+ M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route add 10.0.0.0/24 dev sw0p1])
+@@ -625,7 +625,7 @@ M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route add 10.0.0.0/24 dev sw0p1])
+ M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route add default via 10.0.0.1 dev sw0p1])
+
+ m_as ovn-chassis-1 ip route change 170.168.0.0/16 mtu 1400 dev eth1
+-M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping6 -c 5 -s 1450 -M do 2000::3 2>&1 | grep -q -i "message too long"])
++M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -6 -c 5 -s 1450 -M do 2000::3 2>&1 | grep -q -i "message too long"])
+
+ M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route flush dev sw0p1])
+ M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route add 10.0.0.0/24 dev sw0p1])
+@@ -1124,8 +1124,8 @@ check multinode_nbctl set logical_switch_port ln-sw1 tag_request=101
+
+ check multinode_nbctl --wait=hv sync
+
+-M_START_TCPDUMP([ovn-chassis-1], [-c 2 -neei genev_sys_6081 icmp], [ch1_genev])
+-M_START_TCPDUMP([ovn-chassis-1], [-c 2 -neei eth2 icmp], [ch1_eth2])
++M_START_TCPDUMPS([ovn-chassis-1], [-c 2 -neei genev_sys_6081 icmp], [ch1_genev],
++ [ovn-chassis-1], [-c 2 -neei eth2 icmp], [ch1_eth2])
+
+ M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.4 | FORMAT_PING], \
+ [0], [dnl
+@@ -1144,8 +1144,8 @@ m_as ovn-chassis-1 killall tcpdump
+ rm -f *.tcpdump
+ rm -f *.stderr
+
+-M_START_TCPDUMP([ovn-chassis-1], [-c 2 -neei genev_sys_6081 icmp], [ch1_genev])
+-M_START_TCPDUMP([ovn-chassis-1], [-c 2 -neei eth2 icmp], [ch1_eth2])
++M_START_TCPDUMPS([ovn-chassis-1], [-c 2 -neei genev_sys_6081 icmp], [ch1_genev],
++ [ovn-chassis-1], [-c 2 -neei eth2 icmp], [ch1_eth2])
+
+ M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -q -c 3 -i 0.3 -w 2 20.0.0.3 | FORMAT_PING], \
+ [0], [dnl
+@@ -1169,8 +1169,8 @@ m_as ovn-chassis-1 killall tcpdump
+ rm -f *.tcpdump
+ rm -f *.stderr
+
+-M_START_TCPDUMP([ovn-chassis-1], [-c 2 -neei genev_sys_6081 icmp], [ch1_genev])
+-M_START_TCPDUMP([ovn-chassis-1], [-c 2 -neei eth2 icmp], [ch1_eth2])
++M_START_TCPDUMPS([ovn-chassis-1], [-c 2 -neei genev_sys_6081 icmp], [ch1_genev],
++ [ovn-chassis-1], [-c 2 -neei eth2 icmp], [ch1_eth2])
+
+ M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.4 | FORMAT_PING], \
+ [0], [dnl
+@@ -1189,8 +1189,8 @@ m_as ovn-chassis-1 killall tcpdump
+ rm -f *.tcpdump
+ rm -f *.stderr
+
+-M_START_TCPDUMP([ovn-chassis-1], [-c 2 -neei genev_sys_6081 icmp], [ch1_genev])
+-M_START_TCPDUMP([ovn-chassis-1], [-c 2 -neei eth2 icmp], [ch1_eth2])
++M_START_TCPDUMPS([ovn-chassis-1], [-c 2 -neei genev_sys_6081 icmp], [ch1_genev],
++ [ovn-chassis-1], [-c 2 -neei eth2 icmp], [ch1_eth2])
+
+ M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -q -c 3 -i 0.3 -w 2 20.0.0.3 | FORMAT_PING], \
+ [0], [dnl
+@@ -1213,8 +1213,8 @@ rm -f *.stderr
+ check multinode_nbctl --wait=hv lsp-del ln-sw1
+ # Traffic from sw0p1 to sw1p1 should be tunneled.
+
+-M_START_TCPDUMP([ovn-chassis-1], [-c 2 -neei genev_sys_6081 icmp], [ch1_genev])
+-M_START_TCPDUMP([ovn-chassis-1], [-c 2 -neei eth2 icmp], [ch1_eth2])
++M_START_TCPDUMPS([ovn-chassis-1], [-c 2 -neei genev_sys_6081 icmp], [ch1_genev],
++ [ovn-chassis-1], [-c 2 -neei eth2 icmp], [ch1_eth2])
+
+ M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -q -c 3 -i 0.3 -w 2 20.0.0.3 | FORMAT_PING], \
+ [0], [dnl
+@@ -1245,9 +1245,9 @@ m_as ovn-gw-1 ip netns exec sw0-p10 ip link set sw0-p10 address 32:31:8c:da:64:4
+ # sw0p1 -> br-int -> localnet port (vlan tagged 100) -> br-ex -> eth2 of ovn-chassis-1 to
+ # eth2 of ovn-gw-1 -> br-ex -> sw0-p10
+
+-M_START_TCPDUMP([ovn-chassis-1], [-c 2 -neei genev_sys_6081 icmp], [ch1_genev])
+-M_START_TCPDUMP([ovn-chassis-1], [-c 2 -neei eth2 icmp], [ch1_eth2])
+-M_START_TCPDUMP([ovn-gw-1], [-c 2 -neei eth2 icmp], [gw1_eth2])
++M_START_TCPDUMPS([ovn-chassis-1], [-c 2 -neei genev_sys_6081 icmp], [ch1_genev],
++ [ovn-chassis-1], [-c 2 -neei eth2 icmp], [ch1_eth2],
++ [ovn-gw-1], [-c 2 -neei eth2 icmp], [gw1_eth2])
+
+ M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.10 | FORMAT_PING], \
+ [0], [dnl
+@@ -1283,12 +1283,12 @@ check multinode_nbctl --wait=hv lr-nat-add lr0 dnat_and_snat 172.20.0.110 10.0.0
+ # -> ln-public -> br-ex -> eth2 -> ovn-chassis-1 -> br-ex -> ln-public -> br-int ->
+ # -> DNAT 172.20.0.110 to 10.0.0.3 -> sw0-p1 with src ip 172.20.0.100 and dst ip 10.0.0.3.
+
+-M_START_TCPDUMP([ovn-chassis-2], [-c 2 -neei genev_sys_6081 icmp], [ch2_genev])
+-M_START_TCPDUMP([ovn-chassis-2], [-c 2 -neei eth2 icmp], [ch2_eth2])
+-M_START_TCPDUMP([ovn-gw-1], [-c 2 -neei genev_sys_6081 icmp], [gw1_geneve])
+-M_START_TCPDUMP([ovn-gw-1], [-c 2 -neei eth2 icmp], [gw1_eth2])
+-M_START_TCPDUMP([ovn-chassis-1], [-c 2 -neei genev_sys_6081 icmp], [ch1_genev])
+-M_START_TCPDUMP([ovn-chassis-1], [-c 2 -neei eth2 icmp], [ch1_eth2])
++M_START_TCPDUMPS([ovn-chassis-2], [-c 2 -neei genev_sys_6081 icmp], [ch2_genev],
++ [ovn-chassis-2], [-c 2 -neei eth2 icmp], [ch2_eth2],
++ [ovn-gw-1], [-c 2 -neei genev_sys_6081 icmp], [gw1_geneve],
++ [ovn-gw-1], [-c 2 -neei eth2 icmp], [gw1_eth2],
++ [ovn-chassis-1], [-c 2 -neei genev_sys_6081 icmp], [ch1_genev],
++ [ovn-chassis-1], [-c 2 -neei eth2 icmp], [ch1_eth2])
+
+ M_NS_CHECK_EXEC([ovn-chassis-2], [sw1p1], [ping -q -c 3 -i 0.3 -w 2 172.20.0.110 | FORMAT_PING], \
+ [0], [dnl
+@@ -1334,12 +1334,12 @@ check multinode_nbctl --wait=hv lr-nat-add lr0 dnat_and_snat 172.20.0.110 10.0.0
+ # sw1-p1 in ovn-chassis-2 -> tunnel -> ovn-gw-1 -> In ovn-gw-1 SNAT 20.0.0.3 to 172.20.0.100 ->
+ # DNAT 172.20.0.110 -> 10.0.0.3 -> tunnel -> ovn-chassis-1 -> br-int -> sw0p1
+
+-M_START_TCPDUMP([ovn-chassis-2], [-c 2 -neei genev_sys_6081 icmp], [ch2_genev])
+-M_START_TCPDUMP([ovn-chassis-2], [-c 2 -neei eth2 icmp], [ch2_eth2])
+-M_START_TCPDUMP([ovn-gw-1], [-c 4 -neei genev_sys_6081 icmp], [gw1_geneve])
+-M_START_TCPDUMP([ovn-gw-1], [-c 4 -neei eth2 icmp], [gw1_eth2])
+-M_START_TCPDUMP([ovn-chassis-1], [-c 2 -neei genev_sys_6081 icmp], [ch1_genev])
+-M_START_TCPDUMP([ovn-chassis-1], [-c 2 -neei eth2 icmp], [ch1_eth2])
++M_START_TCPDUMPS([ovn-chassis-2], [-c 2 -neei genev_sys_6081 icmp], [ch2_genev],
++ [ovn-chassis-2], [-c 2 -neei eth2 icmp], [ch2_eth2],
++ [ovn-gw-1], [-c 4 -neei genev_sys_6081 icmp], [gw1_geneve],
++ [ovn-gw-1], [-c 4 -neei eth2 icmp], [gw1_eth2],
++ [ovn-chassis-1], [-c 2 -neei genev_sys_6081 icmp], [ch1_genev],
++ [ovn-chassis-1], [-c 2 -neei eth2 icmp], [ch1_eth2])
+
+ M_NS_CHECK_EXEC([ovn-chassis-2], [sw1p1], [ping -q -c 3 -i 0.3 -w 2 172.20.0.110 | FORMAT_PING], \
+ [0], [dnl
+@@ -1528,14 +1528,14 @@ OVS_WAIT_UNTIL([m_as ovn-chassis-2 ip netns exec sw0p2 ss -tulpn | grep LISTEN |
+ m_as ovn-gw-1 ovs-appctl dpctl/flush-conntrack
+ m_as ovn-gw-2 ovs-appctl dpctl/flush-conntrack
+
+-M_NS_EXEC([ovn-chassis-3], [publicp1], [sh -c 'curl -v 172.16.0.100:80 --retry 0 --connect-timeout 1 --max-time 1 --local-port 59002 2> curl.out'])
+-M_NS_CHECK_EXEC([ovn-chassis-3], [publicp1], [sh -c 'cat -v curl.out' | M_FORMAT_CURL([172.16.0.100], [80])], [0], [dnl
++M_NS_EXEC([ovn-chassis-3], [publicp1], [sh -c 'OVN_CURL(172.16.0.100:80 --retry 0 --connect-timeout 1 --max-time 1 --local-port 59002) > curl.out'])
++M_NS_CHECK_EXEC([ovn-chassis-3], [publicp1], [sh -c 'cat -v curl.out'], [0], [dnl
+ Connected to 172.16.0.100 (172.16.0.100) port 80
+ 200 OK
+ ])
+
+-M_NS_EXEC([ovn-chassis-4], [publicp2], [sh -c 'curl -v 172.16.0.100:80 --retry 0 --connect-timeout 1 --max-time 1 --local-port 59003 2> curl.out'])
+-M_NS_CHECK_EXEC([ovn-chassis-4], [publicp2], [sh -c 'cat -v curl.out' | M_FORMAT_CURL([172.16.0.100], [80])], [0], [dnl
++M_NS_EXEC([ovn-chassis-4], [publicp2], [sh -c 'OVN_CURL(172.16.0.100:80 --retry 0 --connect-timeout 1 --max-time 1 --local-port 59003) > curl.out'])
++M_NS_CHECK_EXEC([ovn-chassis-4], [publicp2], [sh -c 'cat -v curl.out'], [0], [dnl
+ Connected to 172.16.0.100 (172.16.0.100) port 80
+ 200 OK
+ ])
+@@ -1565,7 +1565,7 @@ m_as ovn-chassis-2 ovs-appctl dpctl/flush-conntrack
+ m_as ovn-gw-1 ovs-appctl dpctl/flush-conntrack
+ m_as ovn-gw-2 ovs-appctl dpctl/flush-conntrack
+
+-M_NS_EXEC([ovn-chassis-3], [publicp1], [sh -c 'curl -v -O 172.16.0.100:80/download_file --retry 0 --connect-timeout 1 --max-time 1 --local-port 59004 2>curl.out'])
++M_NS_EXEC([ovn-chassis-3], [publicp1], [sh -c 'OVN_CURL(172.16.0.100:80/download_file --retry 0 --connect-timeout 1 --max-time 1 --local-port 59004) > curl.out'])
+
+ gw1_ct=$(m_as ovn-gw-1 ovs-appctl dpctl/dump-conntrack | sed ':a;N;$!ba;s/\n/\\n/g')
+ gw2_ct=$(m_as ovn-gw-2 ovs-appctl dpctl/dump-conntrack | sed ':a;N;$!ba;s/\n/\\n/g')
+@@ -1574,7 +1574,7 @@ chassis2_ct=$(m_as ovn-chassis-2 ovs-appctl dpctl/dump-conntrack | sed ':a;N;$!b
+ chassis1_flow=$(m_as ovn-chassis-1 ovs-dpctl dump-flows | sed ':a;N;$!ba;s/\n/\\n/g')
+ chassis2_flow=$(m_as ovn-chassis-2 ovs-dpctl dump-flows | sed ':a;N;$!ba;s/\n/\\n/g')
+
+-OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 ip netns exec publicp1 cat -v curl.out | M_FORMAT_CURL([172.16.0.100], [80])], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 ip netns exec publicp1 cat -v curl.out], [0], [dnl
+ Connected to 172.16.0.100 (172.16.0.100) port 80
+ 200 OK
+ ])
+@@ -1630,7 +1630,7 @@ m_as ovn-gw-1 ovs-appctl dpctl/flush-conntrack
+ m_as ovn-gw-2 ovs-appctl dpctl/flush-conntrack
+
+ # Check the flows again for a new source port
+-M_NS_EXEC([ovn-chassis-3], [publicp1], [sh -c 'curl -v -O 172.16.0.100:80/download_file --retry 0 --connect-timeout 1 --max-time 1 --local-port 59005 2>curl.out'])
++M_NS_EXEC([ovn-chassis-3], [publicp1], [sh -c 'OVN_CURL(172.16.0.100:80/download_file --retry 0 --connect-timeout 1 --max-time 1 --local-port 59005) > curl.out'])
+
+ gw1_ct=$(m_as ovn-gw-1 ovs-appctl dpctl/dump-conntrack | sed ':a;N;$!ba;s/\n/\\n/g')
+ gw2_ct=$(m_as ovn-gw-2 ovs-appctl dpctl/dump-conntrack | sed ':a;N;$!ba;s/\n/\\n/g')
+@@ -1639,7 +1639,7 @@ chassis2_ct=$(m_as ovn-chassis-2 ovs-appctl dpctl/dump-conntrack | sed ':a;N;$!b
+ chassis1_flow=$(m_as ovn-chassis-1 ovs-dpctl dump-flows | sed ':a;N;$!ba;s/\n/\\n/g')
+ chassis2_flow=$(m_as ovn-chassis-2 ovs-dpctl dump-flows | sed ':a;N;$!ba;s/\n/\\n/g')
+
+-OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 ip netns exec publicp1 cat -v curl.out | M_FORMAT_CURL([172.16.0.100], [80])], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 ip netns exec publicp1 cat -v curl.out], [0], [dnl
+ Connected to 172.16.0.100 (172.16.0.100) port 80
+ 200 OK
+ ])
+@@ -1685,7 +1685,7 @@ m_as ovn-gw-1 ovs-appctl dpctl/flush-conntrack
+ m_as ovn-gw-2 ovs-appctl dpctl/flush-conntrack
+
+ # Start a new test using the second DGP as origin (public2)
+-M_NS_EXEC([ovn-chassis-4], [publicp2], [sh -c 'curl -v -O 172.16.0.100:80/download_file --retry 0 --connect-timeout 1 --max-time 1 --local-port 59006 2>curl.out'])
++M_NS_EXEC([ovn-chassis-4], [publicp2], [sh -c 'OVN_CURL(172.16.0.100:80/download_file --retry 0 --connect-timeout 1 --max-time 1 --local-port 59006) > curl.out'])
+
+ gw1_ct=$(m_as ovn-gw-1 ovs-appctl dpctl/dump-conntrack | sed ':a;N;$!ba;s/\n/\\n/g')
+ gw2_ct=$(m_as ovn-gw-2 ovs-appctl dpctl/dump-conntrack | sed ':a;N;$!ba;s/\n/\\n/g')
+@@ -1694,7 +1694,7 @@ chassis2_ct=$(m_as ovn-chassis-2 ovs-appctl dpctl/dump-conntrack | sed ':a;N;$!b
+ chassis1_flow=$(m_as ovn-chassis-1 ovs-dpctl dump-flows | sed ':a;N;$!ba;s/\n/\\n/g')
+ chassis2_flow=$(m_as ovn-chassis-2 ovs-dpctl dump-flows | sed ':a;N;$!ba;s/\n/\\n/g')
+
+-OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-4 ip netns exec publicp2 cat -v curl.out | M_FORMAT_CURL([172.16.0.100], [80])], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-4 ip netns exec publicp2 cat -v curl.out], [0], [dnl
+ Connected to 172.16.0.100 (172.16.0.100) port 80
+ 200 OK
+ ])
+@@ -1750,7 +1750,7 @@ m_as ovn-gw-1 ovs-appctl dpctl/flush-conntrack
+ m_as ovn-gw-2 ovs-appctl dpctl/flush-conntrack
+
+ # Check the flows again for a new source port using the second DGP as origin (public2)
+-M_NS_EXEC([ovn-chassis-4], [publicp2], [sh -c 'curl -v -O 172.16.0.100:80/download_file --retry 0 --connect-timeout 1 --max-time 1 --local-port 59007 2>curl.out'])
++M_NS_EXEC([ovn-chassis-4], [publicp2], [sh -c 'OVN_CURL(172.16.0.100:80/download_file --retry 0 --connect-timeout 1 --max-time 1 --local-port 59007) > curl.out'])
+
+ gw1_ct=$(m_as ovn-gw-1 ovs-appctl dpctl/dump-conntrack | sed ':a;N;$!ba;s/\n/\\n/g')
+ gw2_ct=$(m_as ovn-gw-2 ovs-appctl dpctl/dump-conntrack | sed ':a;N;$!ba;s/\n/\\n/g')
+@@ -1759,7 +1759,7 @@ chassis2_ct=$(m_as ovn-chassis-2 ovs-appctl dpctl/dump-conntrack | sed ':a;N;$!b
+ chassis1_flow=$(m_as ovn-chassis-1 ovs-dpctl dump-flows | sed ':a;N;$!ba;s/\n/\\n/g')
+ chassis2_flow=$(m_as ovn-chassis-2 ovs-dpctl dump-flows | sed ':a;N;$!ba;s/\n/\\n/g')
+
+-OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-4 ip netns exec publicp2 cat -v curl.out | M_FORMAT_CURL([172.16.0.100], [80])], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-4 ip netns exec publicp2 cat -v curl.out], [0], [dnl
+ Connected to 172.16.0.100 (172.16.0.100) port 80
+ 200 OK
+ ])
+@@ -1810,26 +1810,26 @@ fi
+
+ # Check multiple requests coming from DGP's public1 and public2
+
+-M_NS_EXEC([ovn-chassis-4], [publicp2], [sh -c 'curl -v -O 172.16.0.100:80/download_file --retry 0 --connect-timeout 1 --max-time 1 2>curl.out'])
+-OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-4 cat -v curl.out | M_FORMAT_CURL([172.16.0.100], [80])], [0], [dnl
++M_NS_EXEC([ovn-chassis-4], [publicp2], [sh -c 'OVN_CURL(172.16.0.100:80/download_file --retry 0 --connect-timeout 1 --max-time 1) > curl.out'])
++OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-4 ip netns exec publicp2 cat -v curl.out], [0], [dnl
+ Connected to 172.16.0.100 (172.16.0.100) port 80
+ 200 OK
+ ])
+
+-M_NS_EXEC([ovn-chassis-3], [publicp1], [sh -c 'curl -v -O 172.16.0.100:80/download_file --retry 0 --connect-timeout 1 --max-time 1 2>curl.out'])
+-OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 cat -v curl.out | M_FORMAT_CURL([172.16.0.100], [80])], [0], [dnl
++M_NS_EXEC([ovn-chassis-3], [publicp1], [sh -c 'OVN_CURL(172.16.0.100:80/download_file --retry 0 --connect-timeout 1 --max-time 1) > curl.out'])
++OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 ip netns exec publicp1 cat -v curl.out], [0], [dnl
+ Connected to 172.16.0.100 (172.16.0.100) port 80
+ 200 OK
+ ])
+
+-M_NS_EXEC([ovn-chassis-4], [publicp2], [sh -c 'curl -v -O 172.16.0.100:80/download_file --retry 0 --connect-timeout 1 --max-time 1 2>curl.out'])
+-OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-4 cat -v curl.out | M_FORMAT_CURL([172.16.0.100], [80])], [0], [dnl
++M_NS_EXEC([ovn-chassis-4], [publicp2], [sh -c 'OVN_CURL(172.16.0.100:80/download_file --retry 0 --connect-timeout 1 --max-time 1) > curl.out'])
++OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-4 ip netns exec publicp2 cat -v curl.out], [0], [dnl
+ Connected to 172.16.0.100 (172.16.0.100) port 80
+ 200 OK
+ ])
+
+-M_NS_EXEC([ovn-chassis-3], [publicp1], [sh -c 'curl -v -O 172.16.0.100:80/download_file --retry 0 --connect-timeout 1 --max-time 1 2>curl.out'])
+-OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 cat -v curl.out | M_FORMAT_CURL([172.16.0.100], [80])], [0], [dnl
++M_NS_EXEC([ovn-chassis-3], [publicp1], [sh -c 'OVN_CURL(172.16.0.100:80/download_file --retry 0 --connect-timeout 1 --max-time 1) > curl.out'])
++OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 ip netns exec publicp1 cat -v curl.out], [0], [dnl
+ Connected to 172.16.0.100 (172.16.0.100) port 80
+ 200 OK
+ ])
+@@ -1849,13 +1849,13 @@ m_as ovn-gw-1 ovs-appctl dpctl/flush-conntrack
+ m_as ovn-gw-2 ovs-appctl dpctl/flush-conntrack
+
+ # Check end-to-end request using a new port for VIP
+-M_NS_EXEC([ovn-chassis-3], [publicp1], [sh -c 'curl -v -O 172.16.0.100:9000/download_file --retry 0 --connect-timeout 1 --max-time 1 --local-port 59008 2>curl.out'])
++M_NS_EXEC([ovn-chassis-3], [publicp1], [sh -c 'OVN_CURL(172.16.0.100:9000/download_file --retry 0 --connect-timeout 1 --max-time 1 --local-port 59008) > curl.out'])
+ OVS_WAIT_FOR_OUTPUT([m_as ovn-gw-1 ovs-appctl dpctl/dump-conntrack | M_FORMAT_CT(20.0.1.3) | \
+ grep tcp | sed -E -e 's/10.0.1.3|10.0.1.4/<cleared>/g' | sort], [0], [dnl
+ tcp,orig=(src=20.0.1.3,dst=<cleared>,sport=59008,dport=80),reply=(src=<cleared>,dst=20.0.1.3,sport=80,dport=59008),zone=<cleared>,protoinfo=(state=<cleared>)
+ ])
+
+-OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 cat -v curl.out | M_FORMAT_CURL([172.16.0.100], [9000])], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 ip netns exec publicp1 cat -v curl.out], [0], [dnl
+ Connected to 172.16.0.100 (172.16.0.100) port 9000
+ 200 OK
+ ])
+@@ -1864,13 +1864,13 @@ m_as ovn-gw-1 ovs-appctl dpctl/flush-conntrack
+ m_as ovn-gw-2 ovs-appctl dpctl/flush-conntrack
+
+ # Check end-to-end request using a new port for VIP
+-M_NS_EXEC([ovn-chassis-4], [publicp2], [sh -c 'curl -v -O 172.16.0.100:9000/download_file --retry 0 --connect-timeout 1 --max-time 1 --local-port 59008 2>curl.out'])
++M_NS_EXEC([ovn-chassis-4], [publicp2], [sh -c 'OVN_CURL(172.16.0.100:9000/download_file --retry 0 --connect-timeout 1 --max-time 1 --local-port 59008) > curl.out'])
+ OVS_WAIT_FOR_OUTPUT([m_as ovn-gw-2 ovs-appctl dpctl/dump-conntrack | M_FORMAT_CT(30.0.1.3) | \
+ grep tcp | sed -E -e 's/10.0.1.3|10.0.1.4/<cleared>/g' | sort], [0], [dnl
+ tcp,orig=(src=30.0.1.3,dst=<cleared>,sport=59008,dport=80),reply=(src=<cleared>,dst=30.0.1.3,sport=80,dport=59008),zone=<cleared>,protoinfo=(state=<cleared>)
+ ])
+
+-OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 cat -v curl.out | M_FORMAT_CURL([172.16.0.100], [9000])], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-4 ip netns exec publicp2 cat -v curl.out], [0], [dnl
+ Connected to 172.16.0.100 (172.16.0.100) port 9000
+ 200 OK
+ ])
+@@ -2083,7 +2083,7 @@ m_as ovn-gw-3 ovs-appctl dpctl/flush-conntrack
+ m_as ovn-gw-4 ovs-appctl dpctl/flush-conntrack
+
+ # Check direct backend traffic using the same LB ports
+-M_NS_EXEC([ovn-chassis-3], [sw1p1], [sh -c 'curl -v -O 10.0.2.3:80/download_file --retry 0 --connect-timeout 1 --max-time 1 --local-port 59013 2>curl.out'])
++M_NS_EXEC([ovn-chassis-3], [sw1p1], [sh -c 'OVN_CURL(10.0.2.3:80/download_file --retry 0 --connect-timeout 1 --max-time 1 --local-port 59013) > curl.out'])
+
+ gw1_ct=$(m_as ovn-gw-1 ovs-appctl dpctl/dump-conntrack | sed ':a;N;$!ba;s/\n/\\n/g')
+ gw2_ct=$(m_as ovn-gw-2 ovs-appctl dpctl/dump-conntrack | sed ':a;N;$!ba;s/\n/\\n/g')
+@@ -2106,7 +2106,7 @@ sed 's/\(.*\)timed out/timed out\n/' | sed 's/\(.*\)connect timeout/timed out\n/
+ timed out
+ ])
+ else
+-OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 cat -v curl.out | M_FORMAT_CURL([10.0.2.3], [80])], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 cat -v curl.out], [0], [dnl
+ Connected to 10.0.2.3 (10.0.2.3) port 80
+ 200 OK
+ ])
+@@ -2118,7 +2118,7 @@ m_as ovn-gw-2 ovs-appctl dpctl/flush-conntrack
+ m_as ovn-gw-3 ovs-appctl dpctl/flush-conntrack
+ m_as ovn-gw-4 ovs-appctl dpctl/flush-conntrack
+
+-M_NS_EXEC([ovn-chassis-3], [sw1p1], [sh -c 'curl -v -O 10.0.2.4:80/download_file --retry 0 --connect-timeout 1 --max-time 1 --local-port 59014 2>curl.out'])
++M_NS_EXEC([ovn-chassis-3], [sw1p1], [sh -c 'OVN_CURL(10.0.2.4:80/download_file --retry 0 --connect-timeout 1 --max-time 1 --local-port 59014) > curl.out'])
+
+ gw1_ct=$(m_as ovn-gw-1 ovs-appctl dpctl/dump-conntrack | sed ':a;N;$!ba;s/\n/\\n/g')
+ gw2_ct=$(m_as ovn-gw-2 ovs-appctl dpctl/dump-conntrack | sed ':a;N;$!ba;s/\n/\\n/g')
+@@ -2141,14 +2141,14 @@ sed 's/\(.*\)timed out/timed out\n/' | sed 's/\(.*\)connect timeout/timed out\n/
+ timed out
+ ])
+ else
+-OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 cat -v curl.out | M_FORMAT_CURL([10.0.2.4], [80])], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 cat -v curl.out], [0], [dnl
+ Connected to 10.0.2.4 (10.0.2.4) port 80
+ 200 OK
+ ])
+ fi
+
+ # Check the flows again for the LB VIP
+-M_NS_EXEC([ovn-chassis-3], [sw1p1], [sh -c 'curl -v 172.16.0.100:80 --retry 0 --connect-timeout 1 --max-time 1 --local-port 59015 2>curl.out'])
++M_NS_EXEC([ovn-chassis-3], [sw1p1], [sh -c 'OVN_CURL(172.16.0.100:80 --retry 0 --connect-timeout 1 --max-time 1 --local-port 59015) > curl.out'])
+
+ curl_timeout=$(m_as ovn-chassis-3 cat -v curl.out | grep -i -e "timed out" -e "timeout" -c)
+
+@@ -2159,14 +2159,14 @@ sed 's/\(.*\)timed out/timed out\n/' | sed 's/\(.*\)connect timeout/timed out\n/
+ timed out
+ ])
+ else
+-OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 cat -v curl.out | M_FORMAT_CURL([172.16.0.100], [80])], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 cat -v curl.out], [0], [dnl
+ Connected to 172.16.0.100 (172.16.0.100) port 80
+ 200 OK
+ ])
+ fi
+
+ # Check the flows again for the LB VIP
+-M_NS_EXEC([ovn-chassis-3], [sw1p1], [sh -c 'curl -v 172.16.0.100:80 --retry 0 --connect-timeout 1 --max-time 1 --local-port 59016 2>curl.out'])
++M_NS_EXEC([ovn-chassis-3], [sw1p1], [sh -c 'OVN_CURL(172.16.0.100:80 --retry 0 --connect-timeout 1 --max-time 1 --local-port 59016) > curl.out'])
+
+ curl_timeout=$(m_as ovn-chassis-3 cat -v curl.out | grep -i -e "timed out" -e "timeout" -c)
+
+@@ -2177,7 +2177,7 @@ sed 's/\(.*\)timed out/timed out\n/' | sed 's/\(.*\)connect timeout/timed out\n/
+ timed out
+ ])
+ else
+-OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 cat -v curl.out | M_FORMAT_CURL([172.16.0.100], [80])], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 cat -v curl.out], [0], [dnl
+ Connected to 172.16.0.100 (172.16.0.100) port 80
+ 200 OK
+ ])
+@@ -2189,11 +2189,11 @@ check multinode_nbctl set load_balancer lb0 options:use_stateless_nat=true
+ check multinode_nbctl --wait=sb set load_balancer lb0 selection_fields="ip_src,tp_src,ip_dst,tp_dst"
+
+ # Check the flows again for the LB VIP - always needs to be successful regardless of the datapath (one or two gw chassis)
+-M_NS_EXEC([ovn-chassis-3], [sw1p1], [sh -c 'curl -v -O 172.16.0.100:80/download_file --retry 0 --connect-timeout 1 --max-time 1 2>curl.out'])
++M_NS_EXEC([ovn-chassis-3], [sw1p1], [sh -c 'OVN_CURL(172.16.0.100:80/download_file --retry 0 --connect-timeout 1 --max-time 1) > curl.out'])
+ M_NS_EXEC([ovn-chassis-1], [sw0p1], [sh -c 'ss -nn >connections.out'])
+ M_NS_EXEC([ovn-chassis-2], [sw0p2], [sh -c 'ss -nn >connections.out'])
+
+-OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 cat -v curl.out | M_FORMAT_CURL([172.16.0.100], [80])], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 cat -v curl.out], [0], [dnl
+ Connected to 172.16.0.100 (172.16.0.100) port 80
+ 200 OK
+ ])
+@@ -2206,11 +2206,11 @@ OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-2 cat connections.out | grep "FIN-WAIT-2"
+ 0
+ ])
+
+-M_NS_EXEC([ovn-chassis-3], [sw1p1], [sh -c 'curl -v -O 172.16.0.100:80/download_file --retry 0 --connect-timeout 1 --max-time 1 2>curl.out'])
++M_NS_EXEC([ovn-chassis-3], [sw1p1], [sh -c 'OVN_CURL(172.16.0.100:80/download_file --retry 0 --connect-timeout 1 --max-time 1) > curl.out'])
+ M_NS_EXEC([ovn-chassis-1], [sw0p1], [sh -c 'ss -nn >connections.out'])
+ M_NS_EXEC([ovn-chassis-2], [sw0p2], [sh -c 'ss -nn >connections.out'])
+
+-OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 cat -v curl.out | M_FORMAT_CURL([172.16.0.100], [80])], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 cat -v curl.out], [0], [dnl
+ Connected to 172.16.0.100 (172.16.0.100) port 80
+ 200 OK
+ ])
+@@ -2223,11 +2223,11 @@ OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-2 cat connections.out | grep "FIN-WAIT-2"
+ 0
+ ])
+
+-M_NS_EXEC([ovn-chassis-3], [sw1p1], [sh -c 'curl -v -O 172.16.0.100:80/download_file --retry 0 --connect-timeout 1 --max-time 1 2>curl.out'])
++M_NS_EXEC([ovn-chassis-3], [sw1p1], [sh -c 'OVN_CURL(172.16.0.100:80/download_file --retry 0 --connect-timeout 1 --max-time 1) > curl.out'])
+ M_NS_EXEC([ovn-chassis-1], [sw0p1], [sh -c 'ss -nn >connections.out'])
+ M_NS_EXEC([ovn-chassis-2], [sw0p2], [sh -c 'ss -nn >connections.out'])
+
+-OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 cat -v curl.out | M_FORMAT_CURL([172.16.0.100], [80])], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 cat -v curl.out], [0], [dnl
+ Connected to 172.16.0.100 (172.16.0.100) port 80
+ 200 OK
+ ])
+@@ -2240,11 +2240,11 @@ OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-2 cat connections.out | grep "FIN-WAIT-2"
+ 0
+ ])
+
+-M_NS_EXEC([ovn-chassis-3], [sw1p1], [sh -c 'curl -v -O 172.16.0.100:80/download_file --retry 0 --connect-timeout 1 --max-time 1 2>curl.out'])
++M_NS_EXEC([ovn-chassis-3], [sw1p1], [sh -c 'OVN_CURL(172.16.0.100:80/download_file --retry 0 --connect-timeout 1 --max-time 1) > curl.out'])
+ M_NS_EXEC([ovn-chassis-1], [sw0p1], [sh -c 'ss -nn >connections.out'])
+ M_NS_EXEC([ovn-chassis-2], [sw0p2], [sh -c 'ss -nn >connections.out'])
+
+-OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 cat -v curl.out | M_FORMAT_CURL([172.16.0.100], [80])], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 cat -v curl.out], [0], [dnl
+ Connected to 172.16.0.100 (172.16.0.100) port 80
+ 200 OK
+ ])
+@@ -2258,7 +2258,7 @@ OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-2 cat connections.out | grep "FIN-WAIT-2"
+ ])
+
+ # Direct backend traffic using the same LB ports needs to be dropped
+-M_NS_EXEC([ovn-chassis-3], [sw1p1], [sh -c 'curl -v -O 10.0.2.3:80/download_file --retry 0 --connect-timeout 1 --max-time 1 2>curl.out'])
++M_NS_EXEC([ovn-chassis-3], [sw1p1], [sh -c 'OVN_CURL(10.0.2.3:80/download_file --retry 0 --connect-timeout 1 --max-time 1) > curl.out'])
+
+ OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 ip netns exec sw1p1 cat -v curl.out | \
+ sed 's/\(.*\)timed out/timed out\n/' | sed 's/\(.*\)connect timeout/timed out\n/' | grep -i -e "timed out" | uniq], [0], [dnl
+@@ -2266,7 +2266,7 @@ timed out
+ ])
+
+ # check again using another source ports
+-M_NS_EXEC([ovn-chassis-3], [sw1p1], [sh -c 'curl -v -O 10.0.2.3:80/download_file --retry 0 --connect-timeout 1 --max-time 1 2>curl.out'])
++M_NS_EXEC([ovn-chassis-3], [sw1p1], [sh -c 'OVN_CURL(10.0.2.3:80/download_file --retry 0 --connect-timeout 1 --max-time 1) > curl.out'])
+
+ OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 ip netns exec sw1p1 cat -v curl.out | \
+ sed 's/\(.*\)timed out/timed out\n/' | sed 's/\(.*\)connect timeout/timed out\n/' | grep -i -e "timed out" | uniq], [0], [dnl
+@@ -2287,7 +2287,7 @@ m_as ovn-gw-2 ovs-appctl dpctl/flush-conntrack
+ m_as ovn-gw-3 ovs-appctl dpctl/flush-conntrack
+ m_as ovn-gw-4 ovs-appctl dpctl/flush-conntrack
+
+-M_NS_EXEC([ovn-chassis-3], [sw1p1], [sh -c 'curl -v -O 10.0.2.4:8080/download_file --retry 0 --connect-timeout 1 --max-time 1 --local-port 59017 2>curl.out'])
++M_NS_EXEC([ovn-chassis-3], [sw1p1], [sh -c 'OVN_CURL(10.0.2.4:8080/download_file --retry 0 --connect-timeout 1 --max-time 1 --local-port 59017) > curl.out'])
+
+ gw1_ct=$(m_as ovn-gw-1 ovs-appctl dpctl/dump-conntrack | sed ':a;N;$!ba;s/\n/\\n/g')
+ gw2_ct=$(m_as ovn-gw-2 ovs-appctl dpctl/dump-conntrack | sed ':a;N;$!ba;s/\n/\\n/g')
+@@ -2310,7 +2310,7 @@ sed 's/\(.*\)timed out/timed out\n/' | sed 's/\(.*\)connect timeout/timed out\n/
+ timed out
+ ])
+ else
+-OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 cat -v curl.out | M_FORMAT_CURL([10.0.2.4], [8080])], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 cat -v curl.out], [0], [dnl
+ Connected to 10.0.2.4 (10.0.2.4) port 8080
+ 200 OK
+ ])
+@@ -2323,7 +2323,7 @@ m_as ovn-gw-3 ovs-appctl dpctl/flush-conntrack
+ m_as ovn-gw-4 ovs-appctl dpctl/flush-conntrack
+
+ # Check again
+-M_NS_EXEC([ovn-chassis-3], [sw1p1], [sh -c 'curl -v -O 10.0.2.4:8080/download_file --retry 0 --connect-timeout 1 --max-time 1 --local-port 59018 2>curl.out'])
++M_NS_EXEC([ovn-chassis-3], [sw1p1], [sh -c 'OVN_CURL(10.0.2.4:8080/download_file --retry 0 --connect-timeout 1 --max-time 1 --local-port 59018) > curl.out'])
+
+ gw1_ct=$(m_as ovn-gw-1 ovs-appctl dpctl/dump-conntrack | sed ':a;N;$!ba;s/\n/\\n/g')
+ gw2_ct=$(m_as ovn-gw-2 ovs-appctl dpctl/dump-conntrack | sed ':a;N;$!ba;s/\n/\\n/g')
+@@ -2346,7 +2346,7 @@ sed 's/\(.*\)timed out/timed out\n/' | sed 's/\(.*\)connect timeout/timed out\n/
+ timed out
+ ])
+ else
+-OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 cat -v curl.out | M_FORMAT_CURL([10.0.2.4], [8080])], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 cat -v curl.out], [0], [dnl
+ Connected to 10.0.2.4 (10.0.2.4) port 8080
+ 200 OK
+ ])
+@@ -2535,8 +2535,8 @@ M_NS_CHECK_EXEC([ovn-chassis-1], [external], [ping -q -c 5 -i 0.3 -w 2 10:200::2
+
+ M_NS_CHECK_EXEC([ovn-chassis-1], [mgmt], [ip a a 172.16.100.2/24 dev mgmt])
+ M_NS_DAEMONIZE([ovn-chassis-1], [pod10], [nc -e /bin/cat -v -l -o server.log 10.100.200.10 2324], [pod10.pid])
+-M_START_TCPDUMP([ovn-chassis-1], [-neei pod10-p ip], [pod10])
+-M_START_TCPDUMP([ovn-chassis-1], [-neei mgmt-p ip], [mgmt])
++M_START_TCPDUMPS([ovn-chassis-1], [-neei pod10-p ip], [pod10],
++ [ovn-chassis-1], [-neei mgmt-p ip], [mgmt])
+
+ m_as ovn-chassis-1 sh -c 'echo -e "Hello\nHello" > msg.expected'
+ check m_as ovn-chassis-1 ovn-nbctl --policy="src-ip" lr-route-add tr 10.100.200.0/24 10.100.200.11
+@@ -2666,14 +2666,14 @@ done
+
+ m_wait_for_ports_up
+
+-M_START_TCPDUMP([ovn-chassis-1], [-neei genev_sys_6081 arp or ip], [ch1_genev])
+-M_START_TCPDUMP([ovn-chassis-1], [-neei migrator-p arp or ip], [ch1_migrator])
+-M_START_TCPDUMP([ovn-chassis-2], [-neei genev_sys_6081 arp or ip], [ch2_genev])
+-for i in 1 2 3; do
+- M_START_TCPDUMP([ovn-chassis-${i}], [-neei sw0-port${i}-p arp or ip], [ch${i}_sw0-port${i}])
+-done
+-M_START_TCPDUMP([ovn-chassis-3], [-neei genev_sys_6081 arp or ip], [ch3_genev])
+-M_START_TCPDUMP([ovn-chassis-3], [-neei migrator-p arp or ip], [ch3_migrator])
++M_START_TCPDUMPS([ovn-chassis-1], [-neei genev_sys_6081 arp or ip], [ch1_genev],
++ [ovn-chassis-1], [-neei migrator-p arp or ip], [ch1_migrator],
++ [ovn-chassis-2], [-neei genev_sys_6081 arp or ip], [ch2_genev],
++ [ovn-chassis-1], [-neei sw0-port1-p arp or ip], [ch1_sw0-port1],
++ [ovn-chassis-2], [-neei sw0-port2-p arp or ip], [ch2_sw0-port2],
++ [ovn-chassis-3], [-neei sw0-port3-p arp or ip], [ch3_sw0-port3],
++ [ovn-chassis-3], [-neei genev_sys_6081 arp or ip], [ch3_genev],
++ [ovn-chassis-3], [-neei migrator-p arp or ip], [ch3_migrator])
+
+ AS_BOX([Migration with vifs])
+ for i in 1 2 3; do
+@@ -2984,44 +2984,186 @@ OVS_WAIT_UNTIL([m_as ovn-gw-2 ip netns exec frr-ns ping -W 1 -c 1 172.16.10.2])
+
+ AT_CLEANUP
+
++AT_SETUP([ovn multinode dynamic-routing - BGP learned routes with router filter name and multiple DGPs])
++
++# This is the multinode counterpart of the system test
++# "dynamic-routing - BGP learned routes with router filter name and multiple
++# DGPs": a single logical router with dynamic routing enabled has two
++# distributed gateway ports scheduled on different chassis. Both LRPs are used
++# for dynamic routing and each has its own dynamic-routing-port-name filter
++# resolving to the interface local to the chassis hosting the port.
++#
++# Each chassis learns a route for the same prefix from its own VRF, so both
++# gateway ports learn it. Because every LRP has a dynamic-routing-port-name
++# that resolves to a local interface, neither route is flushed: the route
++# learned on ovn-gw-1 (lrp-local-bgp-port) and the route learned on ovn-gw-2
++# (lrp-dgp-dummy) both remain.
++#
++# Topology:
++#
++# public ls-dummy
++# (local-bgp-port, ovn-gw-1) (ovn-gw-2)
++# | |
++# lrp-local-bgp-port lrp-dgp-dummy
++# (DGP @ ovn-gw-1, (DGP @ ovn-gw-2,
++# port-name local-bgp-port) port-name dummy-bgp-port)
++# \ /
++# +------------- lr-frr -----------+
++# (dynamic-routing, vrf-id $vrf)
++
++# Check that ovn-fake-multinode setup is up and running.
++check_fake_multinode_setup
++
++CHECK_VRF()
++
++# Delete the multinode NB and OVS resources before starting the test.
++cleanup_multinode_resources
++
++CHECK_VRF()
++
++vrf=1000
++
++# Create the VRF manually on both gateway chassis (dynamic-routing-maintain-vrf
++# is false, so OVN does not create it) and learn/read routes from table $vrf.
++for gw in ovn-gw-1 ovn-gw-2; do
++ check m_as $gw ip link add vrf-bgp type vrf table $vrf
++ on_exit "m_as $gw ip link del vrf-bgp"
++ check m_as $gw ip link set vrf-bgp up
++done
++
++# Create the logical router with dynamic routing in VRF $vrf.
++check multinode_nbctl lr-add lr-frr
++check multinode_nbctl set Logical_Router lr-frr \
++ options:dynamic-routing=true \
++ options:dynamic-routing-vrf-id=$vrf \
++ options:dynamic-routing-redistribute=static
++
++# The DGP that is used for dynamic routing, scheduled on ovn-gw-1.
++check multinode_nbctl lrp-add lr-frr lrp-local-bgp-port 00:00:00:00:00:03 20.0.0.3/24 \
++ -- set Logical_Router_Port lrp-local-bgp-port \
++ options:dynamic-routing-maintain-vrf=false \
++ -- set Logical_Router_Port lrp-local-bgp-port \
++ options:routing-protocol-redirect=local-bgp-port
++check multinode_nbctl lrp-set-gateway-chassis lrp-local-bgp-port ovn-gw-1
++
++check multinode_nbctl ls-add public
++check multinode_nbctl lsp-add-router-port public public-lr-frr lrp-local-bgp-port
++check multinode_nbctl lsp-add public local-bgp-port \
++ -- lsp-set-addresses local-bgp-port unknown
++
++# The second DGP used for dynamic routing, scheduled on ovn-gw-2. bgp-dummy is
++# not an OVN logical port, so the dynamic-routing-port-name filter is resolved
++# through the dynamic-routing-port-mapping configured on ovn-gw-2 below.
++check multinode_nbctl lrp-add lr-frr lrp-dgp-dummy 00:00:00:00:00:04 20.0.1.3/24 \
++ -- set Logical_Router_Port lrp-dgp-dummy \
++ options:dynamic-routing-maintain-vrf=false
++check multinode_nbctl lrp-set-gateway-chassis lrp-dgp-dummy ovn-gw-2
++
++check multinode_nbctl ls-add ls-dummy
++check multinode_nbctl lsp-add-router-port ls-dummy lsp-dummy lrp-dgp-dummy
++
++check multinode_nbctl --wait=hv sync
++
++# Bind local-bgp-port on ovn-gw-1 and move its interface into the VRF. The
++# dynamic-routing-port-name filter will be resolved to this interface name.
++check m_as ovn-gw-1 ovs-vsctl add-port br-int local-bgp-port \
++ -- set Interface local-bgp-port type=internal \
++ -- set Interface local-bgp-port external_ids:iface-id=local-bgp-port
++on_exit "m_as ovn-gw-1 ovs-vsctl del-port br-int local-bgp-port"
++check m_as ovn-gw-1 ip link set local-bgp-port master vrf-bgp
++check m_as ovn-gw-1 ip link set local-bgp-port address 00:00:00:00:00:03
++check m_as ovn-gw-1 ip addr add dev local-bgp-port 20.0.0.3/24
++check m_as ovn-gw-1 ip link set local-bgp-port up
++
++# ovn-gw-2 hosts lrp-dgp-dummy. Add an interface to its VRF so a route can be
++# present in the VRF table and get learned on this gateway port, and map the
++# dynamic-routing-port-name (dummy-bgp-port) to this interface so the filter
++# resolves locally on ovn-gw-2.
++check m_as ovn-gw-2 ip link add bgp-dummy type dummy
++on_exit "m_as ovn-gw-2 ip link del bgp-dummy"
++check m_as ovn-gw-2 ip link set bgp-dummy master vrf-bgp
++check m_as ovn-gw-2 ip addr add dev bgp-dummy 20.0.1.3/24
++check m_as ovn-gw-2 ip link set bgp-dummy up
++
++check m_as ovn-gw-2 ovs-vsctl set open . \
++ external-ids:dynamic-routing-port-mapping="dummy-bgp-port=bgp-dummy"
++on_exit "m_as ovn-gw-2 ovs-vsctl remove open . external-ids dynamic-routing-port-mapping"
++
++m_wait_for_ports_up
++check multinode_nbctl --wait=hv sync
++
++# Both gateway ports are flagged for dynamic routing.
++m_check_row_count Port_Binding 1 logical_port=cr-lrp-local-bgp-port 'options:dynamic-routing=true'
++m_check_row_count Port_Binding 1 logical_port=cr-lrp-dgp-dummy 'options:dynamic-routing=true'
++
++# Simulate a route learned via a dynamic routing protocol in each chassis' VRF
++# for the same prefix.
++check m_as ovn-gw-1 ip route add 10.10.3.0/24 via 20.0.0.25 vrf vrf-bgp proto bgp
++check m_as ovn-gw-2 ip route add 10.10.3.0/24 via 20.0.1.25 vrf vrf-bgp proto bgp
++
++# Each chassis learns the route on its own gateway port, so there are two
++# Learned_Route rows for the prefix.
++m_wait_row_count Learned_Route 2 ip_prefix=10.10.3.0/24
++
++# Configure a dynamic-routing-port-name filter on both LRPs, each resolving to
++# the interface local to the chassis hosting the port: lrp-local-bgp-port ->
++# local-bgp-port (ovn-gw-1) and lrp-dgp-dummy -> dummy-bgp-port (ovn-gw-2, via
++# the port-mapping configured above).
++check multinode_nbctl set Logical_Router_Port lrp-local-bgp-port \
++ options:dynamic-routing-port-name=local-bgp-port
++check multinode_nbctl --wait=hv set Logical_Router_Port lrp-dgp-dummy \
++ options:dynamic-routing-port-name=dummy-bgp-port
++
++local_lp=$(m_fetch_column port_binding _uuid logical_port=lrp-local-bgp-port)
++dummy_lp=$(m_fetch_column port_binding _uuid logical_port=lrp-dgp-dummy)
++
++# Both filters resolve to a local interface, so no route is flushed: the route
++# learned on ovn-gw-1 (lrp-local-bgp-port) and the route learned on ovn-gw-2
++# (lrp-dgp-dummy) both remain.
++m_wait_row_count Learned_Route 2 ip_prefix=10.10.3.0/24
++m_wait_row_count Learned_Route 1 ip_prefix=10.10.3.0/24 logical_port=$local_lp
++m_wait_row_count Learned_Route 1 ip_prefix=10.10.3.0/24 logical_port=$dummy_lp
++
++AT_CLEANUP
++
+ AT_SETUP([HA: Check for missing garp on leader when BFD goes back up])
+ # Network topology
+-# ┌────────────────────────────────────────────────────────────────────────────────────────────────────────┐
+-# │ │
+-# │ ┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐ │
+-# │ │ ovn-chassis-1 │ │ ovn-gw-1 │ │ ovn-gw-2 │ │ ovn-chassis-2 │ │
+-# │ └─────────┬─────────┘ └───────────────────┘ └───────────────────┘ └───────────────────┘ │
+-# │ ┌─────────┴─────────┐ │
+-# │ │ inside1 │ │
+-# │ │ 192.168.1.1/24 │ │
+-# │ └─────────┬─────────┘ │
+-# │ ┌─────────┴─────────┐ │
+-# │ │ inside │ │
+-# │ └─────────┬─────────┘ │
+-# │ ┌─────────┴─────────┐ │
+-# │ │ 192.168.1.254 │ │
+-# │ │ R1 │ │
+-# │ │ 192.168.0.254 │ │
+-# │ └─────────┬─────────┘ │
+-# │ └------eth1---------------┬--------eth1-----------┐ │
+-# │ ┌──────────┴────────┐ ┌─────────┴─────────┐ │
+-# │ │ 192.168.1.254 │ │ 192.168.1.254 │ │
+-# │ │ R1 │ │ R1 │ │
+-# │ │ 192.168.0.254 │ │ 192.168.0.254 │ │
+-# │ └─────────┬─────────┘ └─────────┬─────────┘ │
+-# │ │ │ ┌───────────────────┐ │
+-# │ ┌─────────┴─────────┐ ┌─────────┴─────────┐ │ 192.168.0.1 │ │
+-# │ │ outside │ │ outside │ │ ext1 │ │
+-# │ └─────────┬─────────┘ └─────────┬─────────┘ └─────────┬─────────┘ │
+-# │ ┌─────────┴─────────┐ ┌─────────┴─────────┐ ┌─────────┴─────────┐ │
+-# │ │ ln-outside │ │ ln-outside │ │ ln-ext1 │ │
+-# │ └─────────┬─────────┘ └─────────┬─────────┘ └─────────┬─────────┘ │
+-# │ ┌─────────┴─────────┐ ┌─────────┴─────────┐ ┌─────────┴─────────┐ │
+-# │ │ br-ex │ │ br-ex │ │ br-ex │ │
+-# │ └─────────┬─────────┘ └─────────┬─────────┘ └─────────┬─────────┘ │
+-# │ └---------eth2-----------┴-------eth2-------------┘ │
+-# │ │
+-# └────────────────────────────────────────────────────────────────────────────────────────────────────────┘
++# ┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
++# │ │
++# │ ┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐ │
++# │ │ ovn-chassis-1 │ │ ovn-chassis-2 │ │ ovn-gw-1 │ │ ovn-gw-2 │ │ ovn-chassis-3 │ │
++# │ └─────────┬─────────┘ └─────────┬─────────┘ └───────────────────┘ └───────────────────┘ └───────────────────┘ │
++# │ ┌─────────┴─────────┐ ┌─────────┴─────────┐ │
++# │ │ inside1 │ │ inside2 │ │
++# │ │ 192.168.1.1/24 │ │ 192.168.1.2/24 │ │
++# │ └─────────┬─────────┘ └─────────┬─────────┘ │
++# │ ┌─┴────────────────────────┴─┐ │
++# │ │ inside │ │
++# │ └──────────────┬─────────────┘ │
++# │ ┌─────────┴─────────┐ │
++# │ │ 192.168.1.254 │ │
++# │ │ R1 │ │
++# │ │ 192.168.0.254 │ │
++# │ └─────────┬─────────┘ │
++# │ └------eth1---------------------------┬--------eth1-----------┐ │
++# │ ┌──────────┴────────┐ ┌─────────┴─────────┐ │
++# │ │ 192.168.1.254 │ │ 192.168.1.254 │ │
++# │ │ R1 │ │ R1 │ │
++# │ │ 192.168.0.254 │ │ 192.168.0.254 │ │
++# │ └─────────┬─────────┘ └─────────┬─────────┘ │
++# │ │ │ ┌───────────────────┐ │
++# │ ┌─────────┴─────────┐ ┌─────────┴─────────┐ │ 192.168.0.1 │ │
++# │ │ outside │ │ outside │ │ ext1 │ │
++# │ └─────────┬─────────┘ └─────────┬─────────┘ └─────────┬─────────┘ │
++# │ ┌─────────┴─────────┐ ┌─────────┴─────────┐ ┌─────────┴─────────┐ │
++# │ │ ln-outside │ │ ln-outside │ │ ln-ext1 │ │
++# │ └─────────┬─────────┘ └─────────┬─────────┘ └─────────┬─────────┘ │
++# │ ┌─────────┴─────────┐ ┌─────────┴─────────┐ ┌─────────┴─────────┐ │
++# │ │ br-ex │ │ br-ex │ │ br-ex │ │
++# │ └─────────┬─────────┘ └─────────┬─────────┘ └─────────┬─────────┘ │
++# │ └---------eth2-----------┴-------eth2-------------┘ │
++# │ │
++# └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
+
+ # The goal of this test is the check that GARP are properly generated by higest priority traffic when
+ # BFD goes down, and back up, and this whether the BFD event is due either to some bfd packet lost
+@@ -3030,6 +3172,12 @@ AT_SETUP([HA: Check for missing garp on leader when BFD goes back up])
+ # So gw3 should in this test neither send garp or receive packets.
+ #
+ # Enable vconn so we can check the GARP from a log perspective.
++on_exit "m_as ovn-gw-1 ovn-appctl vlog/set info"
++on_exit "m_as ovn-gw-1 ovn-appctl vlog/enable-rate-limit"
++on_exit "m_as ovn-gw-2 ovn-appctl vlog/set info"
++on_exit "m_as ovn-gw-2 ovn-appctl vlog/enable-rate-limit"
++on_exit "m_as ovn-gw-3 ovn-appctl vlog/set info"
++on_exit "m_as ovn-gw-3 ovn-appctl vlog/enable-rate-limit"
+ m_as ovn-gw-1 ovn-appctl vlog/set vconn:dbg
+ m_as ovn-gw-2 ovn-appctl vlog/set vconn:dbg
+ m_as ovn-gw-3 ovn-appctl vlog/set vconn:dbg
+@@ -3037,38 +3185,74 @@ m_as ovn-gw-1 ovn-appctl vlog/disable-rate-limit
+ m_as ovn-gw-2 ovn-appctl vlog/disable-rate-limit
+ m_as ovn-gw-3 ovn-appctl vlog/disable-rate-limit
+
++# Decrease revalidation time on ovs switch simulating ToR.
++check host_ovs_vsctl set Open_vSwitch . other_config:max-revalidator=100
++on_exit "check host_ovs_vsctl remove Open_vSwitch . other_config max-revalidator"
++
+ check_fake_multinode_setup
+
+ # Delete the multinode NB and OVS resources before starting the test.
+ cleanup_multinode_resources
+
+ ip_ch1=$(m_as ovn-chassis-1 ip a show dev eth1 | grep "inet " | awk '{print $2}'| cut -d '/' -f1)
++ip_ch2=$(m_as ovn-chassis-2 ip a show dev eth1 | grep "inet " | awk '{print $2}'| cut -d '/' -f1)
+ ip_gw1=$(m_as ovn-gw-1 ip a show dev eth1 | grep "inet " | awk '{print $2}'| cut -d '/' -f1)
+ ip_gw2=$(m_as ovn-gw-2 ip a show dev eth1 | grep "inet " | awk '{print $2}'| cut -d '/' -f1)
+ ip_gw3=$(m_as ovn-gw-3 ip a show dev eth1 | grep "inet " | awk '{print $2}'| cut -d '/' -f1)
+
+-from_gw1_to_gw2=$(m_as ovn-gw-1 ovs-vsctl --bare --columns=name find interface options:remote_ip=$ip_gw2)
+-from_gw1_to_gw3=$(m_as ovn-gw-1 ovs-vsctl --bare --columns=name find interface options:remote_ip=$ip_gw3)
+-from_gw1_to_ch1=$(m_as ovn-gw-1 ovs-vsctl --bare --columns=name find interface options:remote_ip=$ip_ch1)
+-from_gw2_to_gw1=$(m_as ovn-gw-2 ovs-vsctl --bare --columns=name find interface options:remote_ip=$ip_gw1)
+-from_gw2_to_gw3=$(m_as ovn-gw-2 ovs-vsctl --bare --columns=name find interface options:remote_ip=$ip_gw3)
+-from_gw2_to_ch1=$(m_as ovn-gw-2 ovs-vsctl --bare --columns=name find interface options:remote_ip=$ip_ch1)
+-from_ch1_to_gw1=$(m_as ovn-chassis-1 ovs-vsctl --bare --columns=name find interface options:remote_ip=$ip_gw1)
+-from_ch1_to_gw2=$(m_as ovn-chassis-1 ovs-vsctl --bare --columns=name find interface options:remote_ip=$ip_gw2)
++get_geneve_names_gw1()
++{
++ from_gw1_to_gw2=$(m_as ovn-gw-1 ovs-vsctl --bare --columns=name find interface options:remote_ip=$ip_gw2)
++ from_gw1_to_gw3=$(m_as ovn-gw-1 ovs-vsctl --bare --columns=name find interface options:remote_ip=$ip_gw3)
++ from_gw1_to_ch1=$(m_as ovn-gw-1 ovs-vsctl --bare --columns=name find interface options:remote_ip=$ip_ch1)
++ from_gw1_to_ch2=$(m_as ovn-gw-1 ovs-vsctl --bare --columns=name find interface options:remote_ip=$ip_ch2)
++}
++
++get_geneve_names_gw2()
++{
++ from_gw2_to_gw1=$(m_as ovn-gw-2 ovs-vsctl --bare --columns=name find interface options:remote_ip=$ip_gw1)
++ from_gw2_to_gw3=$(m_as ovn-gw-2 ovs-vsctl --bare --columns=name find interface options:remote_ip=$ip_gw3)
++ from_gw2_to_ch1=$(m_as ovn-gw-2 ovs-vsctl --bare --columns=name find interface options:remote_ip=$ip_ch1)
++ from_gw2_to_ch2=$(m_as ovn-gw-2 ovs-vsctl --bare --columns=name find interface options:remote_ip=$ip_ch2)
++}
++
++get_geneve_names_chassis1()
++{
++ from_ch1_to_gw1=$(m_as ovn-chassis-1 ovs-vsctl --bare --columns=name find interface options:remote_ip=$ip_gw1)
++ from_ch1_to_gw2=$(m_as ovn-chassis-1 ovs-vsctl --bare --columns=name find interface options:remote_ip=$ip_gw2)
++}
++
++get_geneve_names_chassis2()
++{
++ from_ch2_to_gw1=$(m_as ovn-chassis-2 ovs-vsctl --bare --columns=name find interface options:remote_ip=$ip_gw1)
++ from_ch2_to_gw2=$(m_as ovn-chassis-2 ovs-vsctl --bare --columns=name find interface options:remote_ip=$ip_gw2)
++}
++
++get_geneve_names_gw1
++get_geneve_names_gw2
++get_geneve_names_chassis1
++get_geneve_names_chassis2
+
+ m_as ovn-chassis-1 ip link del hv1-vif1-p
+-m_as ovn-chassis-2 ip link del ext1-p
++m_as ovn-chassis-2 ip link del hv2-vif1-p
++m_as ovn-chassis-3 ip link del ext1-p
+
+ OVS_WAIT_UNTIL([m_as ovn-chassis-1 ip link show | grep -q genev_sys])
+ OVS_WAIT_UNTIL([m_as ovn-chassis-2 ip link show | grep -q genev_sys])
++OVS_WAIT_UNTIL([m_as ovn-chassis-3 ip link show | grep -q genev_sys])
+ OVS_WAIT_UNTIL([m_as ovn-gw-1 ip link show | grep -q genev_sys])
+ OVS_WAIT_UNTIL([m_as ovn-gw-2 ip link show | grep -q genev_sys])
+ OVS_WAIT_UNTIL([m_as ovn-gw-3 ip link show | grep -q genev_sys])
+
++# Use "aggressive" bfd parameters
++check multinode_nbctl set NB_Global . options:"bfd-min-rx"=500
++check multinode_nbctl set NB_Global . options:"bfd-min-tx"=100
++
+ check multinode_nbctl ls-add inside
+ check multinode_nbctl ls-add outside
+ check multinode_nbctl ls-add ext
+ check multinode_nbctl lsp-add inside inside1 -- lsp-set-addresses inside1 "f0:00:c0:a8:01:01 192.168.1.1"
++check multinode_nbctl lsp-add inside inside2 -- lsp-set-addresses inside2 "f0:00:c0:a8:01:02 192.168.1.2"
+ check multinode_nbctl lsp-add ext ext1 -- lsp-set-addresses ext1 "00:00:c0:a8:00:01 192.168.0.1"
+
+ multinode_nbctl create Logical_Router name=R1
+@@ -3100,12 +3284,14 @@ m_as ovn-gw-3 ovs-vsctl remove open . external_ids garp-max-timeout-sec
+
+ m_as ovn-chassis-1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-ex
+ m_as ovn-chassis-2 ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-ex
++m_as ovn-chassis-3 ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-ex
+ m_as ovn-gw-1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-ex
+ m_as ovn-gw-2 ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-ex
+ m_as ovn-gw-3 ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-ex
+
+ m_as ovn-chassis-1 /data/create_fake_vm.sh inside1 hv1-vif1 f0:00:c0:a8:01:01 1500 192.168.1.1 24 192.168.1.254 2000::1/64 2000::a
+-m_as ovn-chassis-2 /data/create_fake_vm.sh ext1 ext1 00:00:c0:a8:00:01 1500 192.168.0.1 24 192.168.0.254 1000::3/64 1000::a
++m_as ovn-chassis-2 /data/create_fake_vm.sh inside2 hv2-vif1 f0:00:c0:a8:01:02 1500 192.168.1.2 24 192.168.1.254 2000::2/64 2000::a
++m_as ovn-chassis-3 /data/create_fake_vm.sh ext1 ext1 00:00:c0:a8:00:01 1500 192.168.0.1 24 192.168.0.254 1000::3/64 1000::a
+
+ # There should be one ha_chassis_group with the name "R1_outside"
+ m_check_row_count HA_Chassis_Group 1 name=R1_outside
+@@ -3120,6 +3306,8 @@ gw1_chassis=$(m_fetch_column Chassis _uuid name=ovn-gw-1)
+ gw2_chassis=$(m_fetch_column Chassis _uuid name=ovn-gw-2)
+ gw3_chassis=$(m_fetch_column Chassis _uuid name=ovn-gw-3)
+
++gw1_eth1_ip_mask=$(m_as ovn-gw-1 ip -4 -o addr show eth1 | awk '{print $4}')
++
+ wait_bfd_enabled() {
+ chassis=$1
+ interface=$2
+@@ -3160,54 +3348,68 @@ for chassis in $from_ch1_to_gw1 $from_ch1_to_gw2; do
+ wait_bfd_enabled ovn-chassis-1 $chassis
+ done
+
++# check BFD enablement on tunnel ports from ovn-chassis-2 ###########
++for chassis in $from_ch2_to_gw1 $from_ch2_to_gw2; do
++ echo "checking ovn-chassis-2 -> $chassis"
++ wait_bfd_enabled ovn-chassis-2 $chassis
++done
++
+ # Make sure there is no nft table left. Do not use nft directly as might not be installed in container.
+ gw1_pid=$(podman inspect -f '{{.State.Pid}}' ovn-gw-1)
+ nsenter --net=/proc/$gw1_pid/ns/net nft list tables | grep ovn-test && nsenter --net=/proc/$gw1_pid/ns/net nft delete table ip ovn-test
+-on_exit "nsenter --net=/proc/$gw1_pid/ns/net nft list tables | grep ovn-test && nsenter --net=/proc/$gw1_pid/ns/net nft delete table ip ovn-test"
++on_exit "if [[ -d "/proc/$gw1_pid" ]]; then nsenter --net=/proc/$gw1_pid/ns/net nft list tables | grep ovn-test && nsenter --net=/proc/$gw1_pid/ns/net nft delete table ip ovn-test; fi"
+
+-for chassis in $from_gw1_to_gw2 $from_gw1_to_gw3 $from_gw1_to_ch1; do
++for chassis in $from_gw1_to_gw2 $from_gw1_to_gw3 $from_gw1_to_ch1 $from_gw1_to_ch2; do
+ wait_bfd_up ovn-gw-1 $chassis
+ done
+-for chassis in $from_gw2_to_gw1 $from_gw2_to_gw3 $from_gw2_to_ch1; do
++for chassis in $from_gw2_to_gw1 $from_gw2_to_gw3 $from_gw2_to_ch1 $from_gw2_to_ch2; do
+ wait_bfd_up ovn-gw-2 $chassis
+ done
+ for chassis in $from_ch1_to_gw1 $from_ch1_to_gw2; do
+ wait_bfd_up ovn-chassis-1 $chassis
+ done
++for chassis in $from_ch2_to_gw1 $from_ch2_to_gw2; do
++ wait_bfd_up ovn-chassis-2 $chassis
++done
+
+ m_wait_row_count Port_Binding 1 logical_port=cr-R1_outside chassis=$gw1_chassis
+ check multinode_nbctl --wait=hv sync
+
+ start_tcpdump() {
+ echo "$(date +%H:%M:%S.%03N) Starting tcpdump"
+- M_START_TCPDUMP([ovn-chassis-1], [-neei hv1-vif1-p], [ch1])
+- M_START_TCPDUMP([ovn-chassis-2], [-neei eth2], [ch2])
+- M_START_TCPDUMP([ovn-gw-1], [-neei eth2], [gw1])
+- M_START_TCPDUMP([ovn-gw-1], [-neei eth2 -Q out], [gw1_out])
+- M_START_TCPDUMP([ovn-gw-2], [-neei eth2], [gw2])
+- M_START_TCPDUMP([ovn-gw-2], [-neei eth2 -Q out], [gw2_out])
+- M_START_TCPDUMP([ovn-gw-3], [-neei eth2], [gw3])
+- M_START_TCPDUMP([ovn-gw-3], [-neei eth2 -Q out], [gw3_out])
++ M_START_TCPDUMPS([ovn-chassis-1], [-neei hv1-vif1-p], [ch1],
++ [ovn-chassis-2], [-neei hv2-vif1-p], [ch2],
++ [ovn-chassis-3], [-neei eth2], [ch3],
++ [ovn-gw-1], [-neei eth2], [gw1],
++ [ovn-gw-1], [-neei eth2 -Q out], [gw1_out],
++ [ovn-gw-2], [-neei eth2], [gw2],
++ [ovn-gw-2], [-neei eth2 -Q out], [gw2_out],
++ [ovn-gw-3], [-neei eth2], [gw3],
++ [ovn-gw-3], [-neei eth2 -Q out], [gw3_out],
++ [ovn-gw-1], [-neei eth1], [gw1_eth1],
++ [ovn-gw-2], [-neei eth1], [gw2_eth1],
++ [ovn-chassis-1], [-neei eth1], [ch1_eth1],
++ [ovn-chassis-2], [-neei eth1], [ch2_eth1])
+ }
+
+ stop_tcpdump() {
+ echo "$(date +%H:%M:%S.%03N) Stopping tcpdump"
+- m_kill 'ovn-gw-1 ovn-gw-2 ovn-gw-3 ovn-chassis-1 ovn-chassis-2' tcpdump
++ m_kill 'ovn-gw-1 ovn-gw-2 ovn-gw-3 ovn-chassis-1 ovn-chassis-2 ovn-chassis-3' tcpdump
+ }
+
+-# Send packets from chassis2 (ext1) to chassis1
++# Send packets from ovn-chassis-3 (ext1) to ovn-chassis-1
+ send_background_packets() {
+ echo "$(date +%H:%M:%S.%03N) Sending packets in Background"
+ start_tcpdump
+- M_NS_DAEMONIZE([ovn-chassis-2], [ext1], [ping -f -i 0.1 192.168.1.1], [ping.pid])
++ M_NS_DAEMONIZE([ovn-chassis-3], [ext1], [ping -qf -i 0.01 192.168.1.1], [ping.pid])
+ }
+
+ stop_sending_background_packets() {
+ echo "$(date +%H:%M:%S.%03N) Stopping Background process"
+ m_as ovn-chassis-1 ps -ef | grep -v grep | grep -q ping && \
+ m_as ovn-chassis-1 echo "Stopping ping on ovn-chassis-1" && killall ping
+- m_as ovn-chassis-2 ps -ef | grep -v grep | grep -q ping && \
+- m_as ovn-chassis-2 echo "Stopping ping on ovn-chassis-2" && killall ping
++ m_as ovn-chassis-3 ps -ef | grep -v grep | grep -q ping && \
++ m_as ovn-chassis-3 echo "Stopping ping on ovn-chassis-3" && killall ping
+ stop_tcpdump
+ }
+
+@@ -3216,8 +3418,8 @@ check_for_new_garps() {
+ expecting_garp=$2
+ n_new_garps=$(cat ${hv}_out.tcpdump | grep -c "f0:00:c0:a8:00:fe > Broadcast, ethertype ARP (0x0806), length 42: Request who-has 192.168.0.254 tell 192.168.0.254, length 28")
+
+- if [ "$expecting_garp" == "true" ]; then
+- AS_BOX([$(date +%H:%M:%S.%03N) Waiting/checking for garp from $hv - Starting with $n_new_garps])
++ if [[ "$expecting_garp" == "true" ]]; then
++ echo "$(date +%H:%M:%S.%03N) Waiting/checking for garp from $hv - Starting with $n_new_garps"
+ OVS_WAIT_UNTIL([
+ n_garps=$n_new_garps
+ n_new_garps=$(cat ${hv}_out.tcpdump | grep -c "f0:00:c0:a8:00:fe > Broadcast, ethertype ARP (0x0806), length 42: Request who-has 192.168.0.254 tell 192.168.0.254, length 28")
+@@ -3225,7 +3427,7 @@ check_for_new_garps() {
+ test "$n_garps" -ne "$n_new_garps"
+ ])
+ else
+- AS_BOX([$(date +%H:%M:%S.%03N) Checking no garp from ${hv}])
++ echo "$(date +%H:%M:%S.%03N) Checking no garp from ${hv}"
+ # Waiting a few seconds to get a chance to see unexpected garps.
+ sleep 3
+ n_garps=$(cat ${hv}_out.tcpdump | grep -c "f0:00:c0:a8:00:fe > Broadcast, ethertype ARP (0x0806), length 42: Request who-has 192.168.0.254 tell 192.168.0.254, length 28")
+@@ -3241,8 +3443,8 @@ check_for_new_echo_pkts() {
+ n_new_echo_req=$(cat ${hv}.tcpdump | grep -c "$mac_src > $mac_dst, ethertype IPv4 (0x0800), length 98: 192.168.0.1 > 192.168.1.1: ICMP echo request")
+ n_new_echo_rep=$(cat ${hv}.tcpdump | grep -c "$mac_dst > $mac_src, ethertype IPv4 (0x0800), length 98: 192.168.1.1 > 192.168.0.1: ICMP echo reply")
+
+- if [ "$expecting_pkts" == "true" ]; then
+- AS_BOX([$(date +%H:%M:%S.%03N) Waiting/checking for echo pkts through ${hv}])
++ if [[ "$expecting_pkts" == "true" ]]; then
++ echo "$(date +%H:%M:%S.%03N) Waiting/checking for echo pkts through ${hv}"
+ echo "Starting with $n_new_echo_req requests and $n_new_echo_rep replies so far on ${hv}."
+ OVS_WAIT_UNTIL([
+ n_echo_req=$n_new_echo_req
+@@ -3252,8 +3454,8 @@ check_for_new_echo_pkts() {
+ echo "We saw $n_new_echo_req requests and $n_new_echo_rep replies so far on ${hv}."
+ test "$n_echo_req" -ne "$n_new_echo_req" && test "$n_echo_rep" -ne "$n_new_echo_rep"
+ ])
+- else
+- AS_BOX([$(date +%H:%M:%S.%03N) Checking no pkts from ${hv}])
++ elif [[ "$expecting_pkts" == "false" ]]; then
++ echo "$(date +%H:%M:%S.%03N) Checking no pkts from ${hv}"
+ # Waiting a few seconds to get a chance to see unexpected pkts.
+ sleep 3
+ n_echo_req=$(cat ${hv}.tcpdump | grep -c "$mac_src > $mac_dst, ethertype IPv4 (0x0800), length 98: 192.168.0.1 > 192.168.1.1: ICMP echo request")
+@@ -3267,26 +3469,46 @@ dump_statistics() {
+ n1=$(m_as ovn-gw-1 grep -c Changing /var/log/ovn/ovn-controller.log)
+ n2=$(m_as ovn-gw-2 grep -c Changing /var/log/ovn/ovn-controller.log)
+ n3=$(m_as ovn-gw-3 grep -c Changing /var/log/ovn/ovn-controller.log)
+- ch1_req=$(grep -c "ICMP echo request" ch1.tcpdump)
+- ch1_rep=$(grep -c "ICMP echo reply" ch1.tcpdump)
+- ch2_req=$(grep -c "ICMP echo request" ch2.tcpdump)
+- ch2_rep=$(grep -c "ICMP echo reply" ch2.tcpdump)
+- gw1_req=$(grep -c "ICMP echo request" gw1.tcpdump)
+- gw1_rep=$(grep -c "ICMP echo reply" gw1.tcpdump)
+- gw2_req=$(grep -c "ICMP echo request" gw2.tcpdump)
+- gw2_rep=$(grep -c "ICMP echo reply" gw2.tcpdump)
+- gw3_req=$(grep -c "ICMP echo request" gw3.tcpdump)
+- gw3_rep=$(grep -c "ICMP echo reply" gw3.tcpdump)
+- echo "$n1 claims in gw1, $n2 in gw2 and $n3 on gw3"
+- echo "ch2_request=$ch2_req gw1_request=$gw1_req gw2_request=$gw2_req gw3_request=$gw3_req ch1_request=$ch1_req ch1_reply=$ch1_rep gw1_reply=$gw1_rep gw2_reply=$gw2_rep gw3_reply=$gw3_rep ch2_reply=$ch2_rep"
++
++ # Read request and reply in one command to avoid having e.g. more replies than requests.
++ read -r ch1_req ch1_rep <<< $(awk '/ICMP echo request/{req++} /ICMP echo reply/{rep++} END {print req+0, rep+0}' ch1.tcpdump)
++ read -r ch2_req ch2_rep <<< $(awk '/ICMP echo request/{req++} /ICMP echo reply/{rep++} END {print req+0, rep+0}' ch2.tcpdump)
++ read -r ch3_req ch3_rep <<< $(awk '/ICMP echo request/{req++} /ICMP echo reply/{rep++} END {print req+0, rep+0}' ch3.tcpdump)
++ read -r gw1_req gw1_rep <<< $(awk '/ICMP echo request/{req++} /ICMP echo reply/{rep++} END {print req+0, rep+0}' gw1.tcpdump)
++ read -r gw2_req gw2_rep <<< $(awk '/ICMP echo request/{req++} /ICMP echo reply/{rep++} END {print req+0, rep+0}' gw2.tcpdump)
++ read -r gw3_req gw3_rep <<< $(awk '/ICMP echo request/{req++} /ICMP echo reply/{rep++} END {print req+0, rep+0}' gw3.tcpdump)
++
++ echo "$n1 claims in gw1, $n2 in gw2 and $n3 on gw3" >&2
++ echo "ch3_req=$ch3_req gw_req=($gw1_req + $gw2_req +$gw3_req) ch1_req=$ch1_req ch1_rep=$ch1_rep gw_rep=($gw1_rep + $gw2_rep + $gw3_rep) ch3_rep=$ch3_rep ch2=($ch2_req+$ch2_rep)" >&2
++ echo "$((ch3_req - ch3_rep))"
+ }
+
+-check_migration_between_gw1_and_gw2() {
+- action=$1
+- send_background_packets
++add_port() {
++ bridge=$1
++ interface=$2
++ address=$3
++ echo "Adding $bridge $interface $address"
++
++ pid=$(podman inspect -f '{{.State.Pid}}' ovn-gw-1)
++ ln -sf /proc/$pid/ns/net /var/run/netns/$pid
++ port=$(host_ovs_vsctl --data=bare --no-heading --columns=name find interface \
++ external_ids:container_id=ovn-gw-1 external_ids:container_iface="$interface")
++ port="${port:0:13}"
++ ip link del "${port}_l" 2>/dev/null || true
++ ip link del "${port}_c" 2>/dev/null || true
++ check ip link add "${port}_l" type veth peer name "${port}_c"
++ ip link set "${port}_l" up
++ ip link set "${port}_c" netns $pid name "$interface"
++ podman exec ovn-gw-1 ip link set "$interface" up
++ if [[ -n "$address" ]]; then
++ podman exec ovn-gw-1 ip addr add "$address" dev "$interface"
++ fi
++}
+
++prepare() {
++ send_background_packets
+ # We make sure gw1 is leader since enough time that it generated all its garps.
+- AS_BOX([$(date +%H:%M:%S.%03N) Waiting all garps sent by gw1])
++ echo "$(date +%H:%M:%S.%03N) Waiting all garps sent by gw1"
+ n_new_garps=$(cat gw1_out.tcpdump | grep -c "f0:00:c0:a8:00:fe > Broadcast, ethertype ARP (0x0806), length 42: Request who-has 192.168.0.254 tell 192.168.0.254, length 28")
+ OVS_WAIT_UNTIL([
+ n_garps=$n_new_garps
+@@ -3297,135 +3519,305 @@ check_migration_between_gw1_and_gw2() {
+ test "$n_garps" -eq "$n_new_garps"
+ ])
+
+- # All packets should go through gw1, and none through gw2 or gw3.
+- check_for_new_echo_pkts gw1 "00:00:c0:a8:00:01" "f0:00:c0:a8:00:fe" "true"
+- check_for_new_echo_pkts gw2 "00:00:c0:a8:00:01" "f0:00:c0:a8:00:fe" "false"
+- check_for_new_echo_pkts gw3 "00:00:c0:a8:00:01" "f0:00:c0:a8:00:fe" "false"
+-
++ # All packets should go through gw1, none through gw2 or gw3, and all through ch1.
++ check_packets "true" "false" "false" "true"
+ flap_count_gw_1=$(m_as ovn-gw-1 ovs-vsctl get interface $from_gw1_to_gw2 bfd_status | sed 's/.*flap_count=\"\([[0-9]]*\).*/\1/g')
+ flap_count_gw_2=$(m_as ovn-gw-2 ovs-vsctl get interface $from_gw2_to_gw1 bfd_status | sed 's/.*flap_count=\"\([[0-9]]*\).*/\1/g')
++}
+
+- if [ test "$action" == "stop_bfd" ]; then
+- AS_BOX([$(date +%H:%M:%S.%03N) Blocking bfd on gw1 (from $ip_gw1 to $ip_gw2)])
+- nsenter --net=/proc/$gw1_pid/ns/net nft add table ip ovn-test
+- nsenter --net=/proc/$gw1_pid/ns/net nft 'add chain ip ovn-test INPUT { type filter hook input priority 0; policy accept; }'
+- # Drop BFD from gw-1 to gw-2: geneve port (6081), inner port 3784 (0xec8), Session state Up, Init, Down.
+- nsenter --net=/proc/$gw1_pid/ns/net nft add rule ip ovn-test INPUT ip daddr $ip_gw1 ip saddr $ip_gw2 udp dport 6081 '@th,416,16 == 0x0ec8 @th,472,8 == 0xc0 counter drop'
+- nsenter --net=/proc/$gw1_pid/ns/net nft add rule ip ovn-test INPUT ip daddr $ip_gw1 ip saddr $ip_gw2 udp dport 6081 '@th,416,16 == 0x0ec8 @th,472,8 == 0x80 counter drop'
+- nsenter --net=/proc/$gw1_pid/ns/net nft add rule ip ovn-test INPUT ip daddr $ip_gw1 ip saddr $ip_gw2 udp dport 6081 '@th,416,16 == 0x0ec8 @th,472,8 == 0x40 counter drop'
+-
+- # We do not check that packets go through gw2 as BFD between chassis-2 and gw1 is still up
+- fi
+-
+- if [ test "$action" == "kill_gw2" ]; then
+- AS_BOX([$(date +%H:%M:%S.%03N) Killing gw2 ovn-controller])
+- on_exit 'm_as ovn-gw-2 /usr/share/openvswitch/scripts/ovs-ctl status ||
+- m_as ovn-gw-2 /usr/share/openvswitch/scripts/ovs-ctl start --system-id=ovn-gw-2'
+- on_exit 'm_as ovn-gw-2 /usr/share/ovn/scripts/ovn-ctl status_controller ||
+- m_as ovn-gw-2 /usr/share/ovn/scripts/ovn-ctl start_controller ${CONTROLLER_SSL_ARGS}'
+-
+- m_as ovn-gw-2 kill -9 $(m_as ovn-gw-2 cat /run/ovn/ovn-controller.pid)
+- m_as ovn-gw-2 kill -9 $(m_as ovn-gw-2 cat /run/openvswitch/ovs-vswitchd.pid)
+- m_as ovn-gw-2 kill -9 $(m_as ovn-gw-2 cat /run/openvswitch/ovsdb-server.pid)
+- # Also delete datapath (flows)
+- m_as ovn-gw-2 ovs-dpctl del-dp system@ovs-system
+- fi
++check_loss_after_flap()
++{
++ dead=$1
++ max_expected_loss=$2
+
+- if [ test "$action" == "kill_gw1" ]; then
+- AS_BOX([$(date +%H:%M:%S.%03N) Killing gw1 ovn-controller])
+- on_exit 'm_as ovn-gw-1 /usr/share/openvswitch/scripts/ovs-ctl status ||
+- m_as ovn-gw-1 /usr/share/openvswitch/scripts/ovs-ctl start --system-id=ovn-gw-1'
+- on_exit 'm_as ovn-gw-1 /usr/share/ovn/scripts/ovn-ctl status_controller ||
+- m_as ovn-gw-1 /usr/share/ovn/scripts/ovn-ctl start_controller ${CONTROLLER_SSL_ARGS}'
+-
+- m_as ovn-gw-1 kill -9 $(m_as ovn-gw-1 cat /run/ovn/ovn-controller.pid)
+- m_as ovn-gw-1 kill -9 $(m_as ovn-gw-1 cat /run/openvswitch/ovs-vswitchd.pid)
+- m_as ovn-gw-1 kill -9 $(m_as ovn-gw-1 cat /run/openvswitch/ovsdb-server.pid)
+- # Also delete datapath (flows)
+- m_as ovn-gw-1 ovs-dpctl del-dp system@ovs-system
+- fi
+-
+- if [ test "$action" == "kill_gw2" ]; then
+- AS_BOX([$(date +%H:%M:%S.%03N) Waiting for flap count between gw1 and gw2 to increase])
++ if [[ "$dead" == "gw2" ]]; then
++ echo "$(date +%H:%M:%S.%03N) Waiting for flap count between gw1 and gw2 to increase"
+ OVS_WAIT_UNTIL([
+- new_flap_count=$(m_as ovn-gw-1 ovs-vsctl get interfac $from_gw1_to_gw2 bfd_status | sed 's/.*flap_count=\"\([[0-9]]*\).*/\1/g')
++ new_flap_count=$(m_as ovn-gw-1 ovs-vsctl get interface $from_gw1_to_gw2 bfd_status | sed 's/.*flap_count=\"\([[0-9]]*\).*/\1/g')
+ echo "Comparing $new_flap_count versus $flap_count_gw_1"
+ test "$new_flap_count" -gt "$((flap_count_gw_1))"
+ ])
+ else
+- AS_BOX([$(date +%H:%M:%S.%03N) Waiting for flap count between gw2 and gw1 to increase])
++ echo "$(date +%H:%M:%S.%03N) Waiting for flap count between gw2 and gw1 to increase"
+ OVS_WAIT_UNTIL([
+- new_flap_count=$(m_as ovn-gw-2 ovs-vsctl get interfac $from_gw2_to_gw1 bfd_status | sed 's/.*flap_count=\"\([[0-9]]*\).*/\1/g')
++ new_flap_count=$(m_as ovn-gw-2 ovs-vsctl get interface $from_gw2_to_gw1 bfd_status | sed 's/.*flap_count=\"\([[0-9]]*\).*/\1/g')
+ echo "Comparing $new_flap_count versus $flap_count_gw_2"
+ test "$new_flap_count" -gt "$((flap_count_gw_2))"
+ ])
+-
+ fi
+- AS_BOX([$(date +%H:%M:%S.%03N) Flapped!])
+
++ echo "$(date +%H:%M:%S.%03N) Flapped!"
+ # Wait a few more second for the fight.
++ sleep 4
++
++ echo "$(date +%H:%M:%S.%03N) Statistics after flapping"
++ lost=$(dump_statistics)
++ echo "===> $lost packet lost while handling migration"
++ AT_CHECK([test "$lost" -le "$max_expected_loss"])
++}
++
++final_check()
++{
++ action=$1
++ max_expected_loss_after_restoration=$2
++
++ # Wait a little more to get packets while network is restored
+ sleep 2
+- AS_BOX([$(date +%H:%M:%S.%03N) Statistics after flapping])
+- dump_statistics
+-
+- if [ test "$action" == "stop_bfd" ]; then
+- # gw1 still alive and gw2 tried to claim => gw1 should restart generating garps.
+- check_for_new_garps gw1 "true"
+- check_for_new_garps gw2 "false"
+- check_for_new_garps gw3 "false"
+- check_for_new_echo_pkts gw1 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe "true"
+- check_for_new_echo_pkts gw2 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe "false"
+- check_for_new_echo_pkts gw3 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe "false"
+- check_for_new_echo_pkts ch1 f0:00:c0:a8:01:fe f0:00:c0:a8:01:01 "true"
+- AS_BOX([$(date +%H:%M:%S.%03N) Unblocking bfd on gw1])
+- nsenter --net=/proc/$gw1_pid/ns/net nft -a list ruleset
+- nsenter --net=/proc/$gw1_pid/ns/net nft delete table ip ovn-test
+- fi
++ echo "$(date +%H:%M:%S.%03N) Statistics after network restored (after $action)"
++ new_lost=$(dump_statistics)
++ echo "===> $((new_lost - lost)) packets lost during network restoration"
++ AT_CHECK([test "$((new_lost - lost))" -le "$max_expected_loss_after_restoration"])
++ stop_sending_background_packets
++}
+
+- if [ test "$action" == "kill_gw2" ]; then
+- # gw1 still alive, but gw2 did not try to claim => gw1 should not generate new garps.
+- check_for_new_garps gw1 "false"
+- check_for_new_garps gw2 "false"
+- check_for_new_garps gw3 "false"
+- check_for_new_echo_pkts gw1 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe "true"
+- check_for_new_echo_pkts gw2 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe "false"
+- check_for_new_echo_pkts gw3 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe "false"
+- check_for_new_echo_pkts ch1 f0:00:c0:a8:01:fe f0:00:c0:a8:01:01 "true"
+- AS_BOX([$(date +%H:%M:%S.%03N) Restarting gw2 ovn-vswitchd])
+- m_as ovn-gw-2 /usr/share/openvswitch/scripts/ovs-ctl start --system-id=ovn-gw-2
+-
+- AS_BOX([$(date +%H:%M:%S.%03N) Restarting gw2 ovn-controller])
+- m_as ovn-gw-2 /usr/share/ovn/scripts/ovn-ctl start_controller ${CONTROLLER_SSL_ARGS}
+- fi
++check_garps()
++{
++ check_for_new_garps gw1 "$1"
++ check_for_new_garps gw2 "$2"
++ check_for_new_garps gw3 "$3"
++}
++
++check_packets()
++{
++ check_for_new_echo_pkts gw1 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe "$1"
++ check_for_new_echo_pkts gw2 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe "$2"
++ check_for_new_echo_pkts gw3 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe "$3"
++ check_for_new_echo_pkts ch1 f0:00:c0:a8:01:fe f0:00:c0:a8:01:01 "$4"
++}
++
++start_openvswitch()
++{
++ chassis=$1
++ echo "$(date +%H:%M:%S.%03N) Simulate openvswitch start: start ovsdb, delete transient ports, start ovs-vswitchd"
++ m_as $chassis /usr/share/openvswitch/scripts/ovs-ctl start --system-id=$chassis --no-ovs-vswitchd
++ m_as $chassis /usr/share/openvswitch/scripts/ovs-ctl delete-transient-ports
++ m_as $chassis /usr/share/openvswitch/scripts/ovs-ctl start --system-id=$chassis --no-ovsdb-server
++}
++
++check_migration_between_gw1_and_gw2_bfd_stop()
++{
++ AS_BOX([$(date +%H:%M:%S.%03N) Testing migration after bfd_stop])
++ max_expected_loss1=$1
++ max_expected_loss2=$2
++ prepare
++
++ echo "$(date +%H:%M:%S.%03N) Blocking bfd on gw1 (from $ip_gw1 to $ip_gw2)"
++ nsenter --net=/proc/$gw1_pid/ns/net nft add table ip ovn-test
++ nsenter --net=/proc/$gw1_pid/ns/net nft 'add chain ip ovn-test INPUT { type filter hook input priority 0; policy accept; }'
++ # Drop BFD from gw-1 to gw-2: geneve port (6081), inner port 3784 (0xec8), Session state Up, Init, Down.
++ nsenter --net=/proc/$gw1_pid/ns/net nft add rule ip ovn-test INPUT ip daddr $ip_gw1 ip saddr $ip_gw2 udp dport 6081 '@th,416,16 == 0x0ec8 @th,472,8 == 0xc0 counter drop'
++ nsenter --net=/proc/$gw1_pid/ns/net nft add rule ip ovn-test INPUT ip daddr $ip_gw1 ip saddr $ip_gw2 udp dport 6081 '@th,416,16 == 0x0ec8 @th,472,8 == 0x80 counter drop'
++ nsenter --net=/proc/$gw1_pid/ns/net nft add rule ip ovn-test INPUT ip daddr $ip_gw1 ip saddr $ip_gw2 udp dport 6081 '@th,416,16 == 0x0ec8 @th,472,8 == 0x40 counter drop'
++
++ check_loss_after_flap "gw1" $max_expected_loss1
++
++ # gw1 still alive and gw2 tried to claim => gw1 should restart generating garps.
++ check_garps "true" "false" "false"
++ check_packets "true" "false" "false" "true"
++
++ echo "$(date +%H:%M:%S.%03N) Unblocking bfd on gw1"
++ nsenter --net=/proc/$gw1_pid/ns/net nft -a list ruleset
++ nsenter --net=/proc/$gw1_pid/ns/net nft delete table ip ovn-test
++
++ # The network is now restored => packets should go through gw1 and reach chassis-1.
++ check_packets "true" "false" "false" "true"
++ final_check "bfd_stop" $max_expected_loss2
++}
++
++check_migration_between_gw1_and_gw2_kill_gw2() {
++ AS_BOX([$(date +%H:%M:%S.%03N) Check migration after killing gw2 ovn-controller & vswitchd])
++ max_expected_loss1=$1
++ max_expected_loss2=$2
++ prepare
++
++ gw2_ip_mask=$(m_as ovn-gw-2 ip -4 -o addr show eth1 | awk '{print $4}')
++ on_exit 'm_as ovn-gw-2 /usr/share/openvswitch/scripts/ovs-ctl status ||
++ m_as ovn-gw-2 /usr/share/openvswitch/scripts/ovs-ctl start --system-id=ovn-gw-2'
++ on_exit 'm_as ovn-gw-2 /usr/share/ovn/scripts/ovn-ctl status_controller ||
++ m_as ovn-gw-2 /usr/share/ovn/scripts/ovn-ctl start_controller ${CONTROLLER_SSL_ARGS}'
++ on_exit 'm_as ovn-gw-2 ip addr add $gw2_ip_mask dev eth1'
++ on_exit 'm_as ovn-gw-2 ip link set eth1 up'
++
++ m_as ovn-gw-2 kill -9 $(m_as ovn-gw-2 cat /run/ovn/ovn-controller.pid)
++ m_as ovn-gw-2 kill -9 $(m_as ovn-gw-2 cat /run/openvswitch/ovs-vswitchd.pid)
++ m_as ovn-gw-2 kill -9 $(m_as ovn-gw-2 cat /run/openvswitch/ovsdb-server.pid)
++ m_as ovn-gw-2 ovs-dpctl del-dp system@ovs-system
++
++ check_loss_after_flap "gw2" $max_expected_loss1
++
++ # gw1 still alive, but gw2 did not try to claim => gw1 should not generate new garps.
++ check_garps "false" "false" "false"
++ check_packets "true" "false" "false" "true"
++
++ start_openvswitch ovn-gw-2
++
++ echo "$(date +%H:%M:%S.%03N) Restarting gw2 ovn-controller"
++ m_as ovn-gw-2 /usr/share/ovn/scripts/ovn-ctl start_controller ${CONTROLLER_SSL_ARGS}
++ get_geneve_names_gw2
++
++ # The network is now restored => packets should go through gw1 and reach chassis-1.
++ check_packets "true" "false" "false" "true"
++ final_check "kill_gw2" $max_expected_loss2
++}
++
++check_migration_between_gw1_and_gw2_kill_gw1() {
++ AS_BOX([$(date +%H:%M:%S.%03N) Killing gw1 ovn-controller and ovs-vswitchd])
++ max_expected_loss1=$1
++ max_expected_loss2=$2
++ prepare
++
++ on_exit 'm_as ovn-gw-1 /usr/share/openvswitch/scripts/ovs-ctl status ||
++ m_as ovn-gw-1 /usr/share/openvswitch/scripts/ovs-ctl start --system-id=ovn-gw-1'
++ on_exit 'm_as ovn-gw-1 /usr/share/ovn/scripts/ovn-ctl status_controller ||
++ m_as ovn-gw-1 /usr/share/ovn/scripts/ovn-ctl start_controller ${CONTROLLER_SSL_ARGS}'
++
++ m_as ovn-gw-1 kill -9 $(m_as ovn-gw-1 cat /run/ovn/ovn-controller.pid)
++ m_as ovn-gw-1 kill -9 $(m_as ovn-gw-1 cat /run/openvswitch/ovs-vswitchd.pid)
++ m_as ovn-gw-1 kill -9 $(m_as ovn-gw-1 cat /run/openvswitch/ovsdb-server.pid)
++ # Also delete datapath (flows)
++ m_as ovn-gw-1 ovs-dpctl del-dp system@ovs-system
++
++ check_loss_after_flap "gw1" $max_expected_loss1
++
++ # gw1 died => gw2 should generate garps.
++ check_garps "false" "true" "false"
++ check_packets "false" "true" "false" "true"
++ start_openvswitch ovn-gw-1
++
++ # Wait some long time before restarting ovn-controller
++ sleep 10
++
++ # gw2 should still be handling packets as OVN not restarted on gw1
++ check_packets "false" "true" "false" "true"
++
++ echo "$(date +%H:%M:%S.%03N) Restarting gw1 ovn-controller after killing gw1"
++ m_as ovn-gw-1 /usr/share/ovn/scripts/ovn-ctl start_controller ${CONTROLLER_SSL_ARGS}
++ get_geneve_names_gw1
++
++ # The network is now restored => packets should go through gw1 and reach chassis-1.
++ check_packets "true" "false" "false" "true"
++ final_check "kill_gw1" $max_expected_loss2
++}
++
++check_migration_between_gw1_and_gw2_reboot_gw1() {
++ AS_BOX([$(date +%H:%M:%S.%03N) Rebooting ovn-gw-1])
++ max_expected_loss1=$1
++ max_expected_loss2=$2
++ prepare
++
++ podman stop -t 0 ovn-gw-1
++ (exec 3>&- 4>&- 5>&- 6>&-; podman start ovn-gw-1)
++
++ # As ovn-gw-1 got stopped and restarted, its ports might get deleted. Add them back.
++ add_port br-ovn-ext eth2
++ add_port br-ovn eth1 $gw1_eth1_ip_mask
++
++ M_START_TCPDUMPS([ovn-gw-1], [-neei eth2], [gw1], [ovn-gw-1], [-neei eth2 -Q out], [gw1_out])
++ check_loss_after_flap "gw1" $max_expected_loss1
+
+- if [ test "$action" == "kill_gw1" ]; then
+- # gw1 died => gw2 should generate garps.
+- check_for_new_garps gw1 "false"
+- check_for_new_garps gw2 "true"
+- check_for_new_garps gw3 "false"
+- check_for_new_echo_pkts gw1 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe "false"
+- check_for_new_echo_pkts gw2 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe "true"
+- check_for_new_echo_pkts gw3 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe "false"
+- check_for_new_echo_pkts ch1 f0:00:c0:a8:01:fe f0:00:c0:a8:01:01 "true"
+- AS_BOX([$(date +%H:%M:%S.%03N) Restarting gw1 ovn-vswitchd])
+- m_as ovn-gw-1 /usr/share/openvswitch/scripts/ovs-ctl start --system-id=ovn-gw-1
+-
+- AS_BOX([$(date +%H:%M:%S.%03N) Restarting gw1 ovn-controller])
+- m_as ovn-gw-1 /usr/share/ovn/scripts/ovn-ctl start_controller ${CONTROLLER_SSL_ARGS}
++ # gw1 died => gw2 should generate garps.
++ check_garps "false" "true" "false"
++ check_packets "false" "true" "false" "true"
++
++ start_openvswitch ovn-gw-1
++ M_START_TCPDUMPS([ovn-gw-1], [-neei eth1], [gw1_eth1])
++
++ # Wait some long time before restarting ovn-controller
++ sleep 10
++
++ # gw2 should still be handling packets as OVN not restarted on gw1
++ check_packets "false" "true" "false" "true"
++
++ echo "$(date +%H:%M:%S.%03N) Restarting gw1 ovn-controller after rebooting gw1"
++ m_as ovn-gw-1 /usr/share/ovn/scripts/ovn-ctl start_controller ${CONTROLLER_SSL_ARGS}
++ get_geneve_names_gw1
++
++ # The network is now restored => packets should go through gw1 and reach chassis-1.
++ check_packets "true" "false" "false" "true"
++ final_check "reboot_gw1" $max_expected_loss2
++}
++
++check_compute_restart() {
++ AS_BOX([$(date +%H:%M:%S.%03N) Killing ovn-chassis-1 ovn-controller and ovs-vswitchd])
++ max_expected_loss=$1
++ prepare
++
++ # Kill ovn-chassis-1
++ echo "$(date +%H:%M:%S.%03N) Killing chassis-1"
++ on_exit 'm_as ovn-chassis-1 /usr/share/openvswitch/scripts/ovs-ctl status ||
++ m_as ovn-chassis-1 /usr/share/openvswitch/scripts/ovs-ctl start --system-id=ovn-chassis-1'
++ on_exit 'm_as ovn-chassis-1 /usr/share/ovn/scripts/ovn-ctl status_controller ||
++ m_as ovn-chassis-1 /usr/share/ovn/scripts/ovn-ctl start_controller ${CONTROLLER_SSL_ARGS}'
++
++ m_as ovn-chassis-1 kill -9 $(m_as ovn-chassis-1 cat /run/ovn/ovn-controller.pid)
++ m_as ovn-chassis-1 kill -9 $(m_as ovn-chassis-1 cat /run/openvswitch/ovs-vswitchd.pid)
++ m_as ovn-chassis-1 kill -9 $(m_as ovn-chassis-1 cat /run/openvswitch/ovsdb-server.pid)
++
++ # Now restart chassis-1
++ flap_count=$(m_as ovn-gw-2 ovs-vsctl get interface $from_gw2_to_ch1 bfd_status | sed 's/.*flap_count=\"\([[0-9]]*\).*/\1/g')
++ start_openvswitch ovn-chassis-1
++
++ echo "$(date +%H:%M:%S.%03N) Waiting for flap count between gw-1 and chassis-1 to increase"
++ OVS_WAIT_UNTIL([
++ new_flap_count=$(m_as ovn-gw-1 ovs-vsctl get interface $from_gw1_to_ch1 bfd_status | sed 's/.*flap_count=\"\([[0-9]]*\).*/\1/g')
++ echo "Comparing $new_flap_count versus $flap_count"
++ test "$new_flap_count" -gt "$((flap_count))"
++ ])
++
++ echo "$(date +%H:%M:%S.%03N) Restarting ovn-chassis-1 ovn-controller."
++ m_as ovn-chassis-1 /usr/share/ovn/scripts/ovn-ctl start_controller ${CONTROLLER_SSL_ARGS}
++ get_geneve_names_chassis1
++
++ wait_bfd_up ovn-chassis-1 $from_ch1_to_gw1
++
++ # Wait a long time to catch losses
++ sleep 5
++ lost=0
++ final_check "compute" $max_expected_loss
++}
++
++check_update_ovn_ovs() {
++ chassis=$1
++ ovn=$2
++ bfd=$3
++ max_expected_loss=$4
++
++ AS_BOX([$(date +%H:%M:%S.%03N) Check migration after restarting $chassis ovs-vswitchd $ovn])
++ prepare
++ lost=0
++
++ echo "$(date +%H:%M:%S.%03N) Restarting OVS $ovn on $chassis"
++ if [[ "$ovn" == "ovn" ]]; then
++ m_as $chassis /usr/share/ovn/scripts/ovn-ctl stop_controller ${CONTROLLER_SSL_ARGS} --restart
++ m_as $chassis /usr/share/openvswitch/scripts/ovs-ctl restart --system-id=$chassis --no-ovs-vswitchd
++ m_as $chassis /usr/share/openvswitch/scripts/ovs-ctl restart --system-id=$chassis --no-ovsdb-server
++ m_as $chassis /usr/share/ovn/scripts/ovn-ctl start_controller ${CONTROLLER_SSL_ARGS}
++ else
++ m_as $chassis /usr/share/openvswitch/scripts/ovs-ctl restart --system-id=$chassis
+ fi
+
++ if [[ "$bfd" == "bfd" ]]; then
++ for c in $from_gw1_to_gw2 $from_gw1_to_gw3 $from_gw1_to_ch1 $from_gw1_to_ch2; do
++ wait_bfd_up ovn-gw-1 $c
++ done
++ for c in $from_ch1_to_gw1 $from_ch1_to_gw2; do
++ wait_bfd_up ovn-chassis-1 $c
++ done
++ fi
+ # The network is now restored => packets should go through gw1 and reach chassis-1.
+- check_for_new_echo_pkts gw1 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe "true"
+- check_for_new_echo_pkts gw2 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe "false"
+- check_for_new_echo_pkts gw3 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe "false"
+- check_for_new_echo_pkts ch1 f0:00:c0:a8:01:fe f0:00:c0:a8:01:01 "true"
+- AS_BOX([$(date +%H:%M:%S.%03N) Statistics after network restored])
+- dump_statistics
+- stop_sending_background_packets
++ # When packet loss is expected (e.g. in HA) packet might temporarily go
++ # through gw2. Ignore this.
++ if [[ "$max_expected_loss" -gt 0 ]]; then
++ check_packets "true" "ignore" "false" "true"
++ final_check "ovs_update" $max_expected_loss
++ else
++ check_packets "true" "false" "false" "true"
++ final_check "ovs_update" $max_expected_loss
++ fi
+ }
+
+ start_tcpdump
+-AS_BOX([$(date +%H:%M:%S.%03N) Sending packet from hv1-vif1(inside1) to ext1])
++echo "$(date +%H:%M:%S.%03N) Sending packet from hv1-vif1(inside1) to ext1"
+ M_NS_CHECK_EXEC([ovn-chassis-1], [hv1-vif1], [ping -c3 -q -i 0.1 192.168.0.1 | FORMAT_PING],
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+@@ -3433,7 +3825,7 @@ M_NS_CHECK_EXEC([ovn-chassis-1], [hv1-vif1], [ping -c3 -q -i 0.1 192.168.0.1 | F
+ stop_tcpdump
+
+ # It should have gone through gw1 and not gw2
+-AS_BOX([$(date +%H:%M:%S.%03N) Checking it went through gw1 and not gw2])
++echo "$(date +%H:%M:%S.%03N) Checking it went through gw1 and not gw2"
+ AT_CHECK([cat gw2.tcpdump | grep "ICMP echo"], [1], [dnl
+ ])
+
+@@ -3446,17 +3838,43 @@ f0:00:c0:a8:00:fe > 00:00:c0:a8:00:01, ethertype IPv4 (0x0800), length 98: 192.1
+ 00:00:c0:a8:00:01 > f0:00:c0:a8:00:fe, ethertype IPv4 (0x0800), length 98: 192.168.0.1 > 192.168.1.1: ICMP echo reply,
+ ])
+
+-# We stop bfd between gw1 & gw2, but keep gw1 & gw2 running.
+-check_migration_between_gw1_and_gw2 "stop_bfd"
++lost=0
++# We stop bfd between gw1 & gw2, but keep gw1 & gw2 running. We should not lose packets.
++check_migration_between_gw1_and_gw2_bfd_stop 1 1
+
+ # We simulate death of gw2. It should not have any effect.
+-check_migration_between_gw1_and_gw2 "kill_gw2"
++check_migration_between_gw1_and_gw2_kill_gw2 1 1
++
++# We simulate restart of both OVS & OVN gw1. gw2 should take over.
++# Expect around 1500 msec (mult x min_rx) + 1000 (for sending GARP) drop time when gw1 dies.
++# Expect BFD up on gw1 max 1 second after on gw2 and 1 more second for GARP when it recovers.
++check_migration_between_gw1_and_gw2_kill_gw1 300 200
+
+ # We simulate death of gw1. gw2 should take over.
+-check_migration_between_gw1_and_gw2 "kill_gw1"
++check_migration_between_gw1_and_gw2_reboot_gw1 300 200
++
++# We simulate ovs update on gw1. When ovs is stopped, flows should still be handled by Kernel datapath.
++# When OVS is restarted, BFD should go down immediately, and gw2 might start handling packets.
++check_update_ovn_ovs ovn-gw-1 "" bfd 300
++check_update_ovn_ovs ovn-gw-1 ovn bfd 300
++check_update_ovn_ovs ovn-chassis-1 "" bfd 300
++check_update_ovn_ovs ovn-chassis-1 ovn bfd 300
++
++# We simulate restart of ovn-chassis-1. We expect up to 3 sec loss.
++# 1 sec for chassis-1 to send Down, 1 sec for chassis-1 to send Init and 1 sec for gw1 to send up.
++check_compute_restart 300
++
++# Now change config tp L3 GW
++check multinode_nbctl clear Logical_Router_Port R1_outside gateway_chassis
++check multinode_nbctl set Logical_Router R1 options:chassis=ovn-gw-1
++check multinode_nbctl --wait=hv sync
++m_check_row_count HA_Chassis_Group 0 name=R1_outside
++check_update_ovn_ovs ovn-gw-1 "" "" 0
++check_update_ovn_ovs ovn-chassis-1 "" "" 0
++check_update_ovn_ovs ovn-gw-1 ovn "" 0
++check_update_ovn_ovs ovn-chassis-1 ovn "" 0
+
+ AT_CLEANUP
+-])
+
+ AT_SETUP([ovn multinode bgp L2 EVPN])
+ check_fake_multinode_setup
+@@ -3573,29 +3991,29 @@ check m_as ovn-gw-2 ip netns exec frr-ns ip addr add dev br-10 10.0.0.82/24
+ check m_as ovn-gw-2 ip netns exec frr-ns ip -6 addr add dev br-10 10::82/64 nodad
+
+ AS_BOX([Checking EVPN MACs and IPs on External BGP host])
+-OVS_WAIT_FOR_OUTPUT([m_as ovn-gw-1 ip netns exec frr-ns vtysh --vty_socket /run/frr/frr-ns -c 'show evpn mac vni all'], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([m_as ovn-gw-1 ip netns exec frr-ns vtysh --vty_socket /run/frr/frr-ns -c 'show evpn mac vni all' | tr -s ' '], [0], [dnl
+
+ VNI 10 #MACs (local and remote) 2
+
+ Flags: N=sync-neighs, I=local-inactive, P=peer-active, X=peer-proxy
+-MAC Type Flags Intf/Remote ES/VTEP VLAN Seq #'s
+-00:00:00:00:00:01 remote 42.42.10.12 0/0
+-00:00:00:00:01:00 local evpn_host_peer 0/0
++MAC Type Flags Intf/Remote ES/VTEP VLAN Seq #'s
++00:00:00:00:00:01 remote 42.42.10.12 0/0
++00:00:00:00:01:00 local evpn_host_peer 0/0
+ ])
+
+-OVS_WAIT_FOR_OUTPUT([m_as ovn-gw-2 ip netns exec frr-ns vtysh --vty_socket /run/frr/frr-ns -c 'show evpn mac vni all'], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([m_as ovn-gw-2 ip netns exec frr-ns vtysh --vty_socket /run/frr/frr-ns -c 'show evpn mac vni all' | tr -s ' '], [0], [dnl
+
+ VNI 10 #MACs (local and remote) 2
+
+ Flags: N=sync-neighs, I=local-inactive, P=peer-active, X=peer-proxy
+-MAC Type Flags Intf/Remote ES/VTEP VLAN Seq #'s
+-00:00:00:00:00:02 remote 42.42.10.22 0/0
+-00:00:00:00:02:00 local evpn_host_peer 0/0
++MAC Type Flags Intf/Remote ES/VTEP VLAN Seq #'s
++00:00:00:00:00:02 remote 42.42.10.22 0/0
++00:00:00:00:02:00 local evpn_host_peer 0/0
+ ])
+
+ # Check that the fabric learned both FDB and IP routes for the workloads.
+ OVS_WAIT_FOR_OUTPUT([m_as ovn-gw-1 ip netns exec frr-ns vtysh --vty_socket /run/frr/frr-ns -c 'show bgp l2vpn evpn route' | \
+- grep --no-group-separator -A1 00:00:00:00:00:01], [0], [dnl
++ grep --no-group-separator -A1 00:00:00:00:00:01 | sed 's/ RD [[0-9.:]]*//g'], [0], [dnl
+ *> [[2]]:[[0]]:[[48]]:[[00:00:00:00:00:01]]
+ 42.42.10.12 0 4210000000 i
+ *> [[2]]:[[0]]:[[48]]:[[00:00:00:00:00:01]]:[[32]]:[[10.0.0.11]]
+@@ -3604,7 +4022,7 @@ OVS_WAIT_FOR_OUTPUT([m_as ovn-gw-1 ip netns exec frr-ns vtysh --vty_socket /run/
+ 42.42.10.12 0 4210000000 i
+ ])
+ OVS_WAIT_FOR_OUTPUT([m_as ovn-gw-2 ip netns exec frr-ns vtysh --vty_socket /run/frr/frr-ns -c 'show bgp l2vpn evpn route' | \
+- grep --no-group-separator -A1 00:00:00:00:00:02], [0], [dnl
++ grep --no-group-separator -A1 00:00:00:00:00:02 | sed 's/ RD [[0-9.:]]*//g'], [0], [dnl
+ *> [[2]]:[[0]]:[[48]]:[[00:00:00:00:00:02]]
+ 42.42.10.22 0 4210000000 i
+ *> [[2]]:[[0]]:[[48]]:[[00:00:00:00:00:02]]:[[32]]:[[10.0.0.12]]
+@@ -3691,46 +4109,16 @@ check multinode_nbctl --wait=hv \
+ -- lsp-add ls-int w-int2 \
+ -- lsp-set-addresses w-int2 "00:00:00:02:00:02 20.0.0.12 2000::12"
+
+-rtr_dp_key=$(m_fetch_column Datapath tunnel_key external_ids:name=lr)
+-rtr_port_key=$(m_fetch_column Port_Binding tunnel_key logical_port=lr-ls)
+-
+ check m_as ovn-gw-1 /data/create_fake_vm.sh w-int1 w-int1 00:00:00:02:00:01 1500 20.0.0.11 24 20.0.0.1 2000::11/64 2000::1
+ check m_as ovn-gw-2 /data/create_fake_vm.sh w-int2 w-int2 00:00:00:02:00:02 1500 20.0.0.12 24 20.0.0.1 2000::12/64 2000::1
+ m_wait_for_ports_up
+
+-# Check that flows are created for the type-2 EVPN MAC+IP routes, in the
+-# router pipeline.
+-AT_CHECK_UNQUOTED([m_as ovn-gw-1 ovs-ofctl dump-flows br-int table=OFTABLE_MAC_BINDING | grep priority | grep -vE "reg[[04]]=0xfe80" | \
+- awk '{print $7, $8}' | sort], [0], [dnl
+-priority=100,reg0=0xa00000b,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:00:00:00:00:00:01,load:0x1->NXM_NX_REG10[[6]]
+-priority=100,reg0=0xa00000c,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:00:00:00:00:00:02,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg0=0xa000029,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:00:00:00:00:01:00,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg4=0x100000,reg5=0,reg6=0,reg7=0x41,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:00:00:00:00:01:00,load:0x1->NXM_NX_REG10[[6]]
+-])
+-
+-AT_CHECK_UNQUOTED([m_as ovn-gw-1 ovs-ofctl dump-flows br-int table=OFTABLE_MAC_LOOKUP | grep priority | grep -vE "reg[[04]]=0xfe80" | \
+- awk '{print $7, $8}' | sort], [0], [dnl
+-priority=100,arp,reg0=0xa00000b,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=00:00:00:00:00:01 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=100,arp,reg0=0xa00000c,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=00:00:00:00:00:02 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,arp,reg0=0xa000029,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=00:00:00:00:01:00 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,icmp6,reg0=0x100000,reg1=0,reg2=0,reg3=0x41,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=00:00:00:00:01:00,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
+-])
+-
+-AT_CHECK_UNQUOTED([m_as ovn-gw-2 ovs-ofctl dump-flows br-int table=OFTABLE_MAC_BINDING | grep priority | grep -vE "reg[[04]]=0xfe80" | \
+- awk '{print $7, $8}' | sort], [0], [dnl
+-priority=100,reg0=0xa00000b,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:00:00:00:00:00:01,load:0x1->NXM_NX_REG10[[6]]
+-priority=100,reg0=0xa00000c,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:00:00:00:00:00:02,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg0=0xa00002a,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:00:00:00:00:02:00,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg4=0x100000,reg5=0,reg6=0,reg7=0x42,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:00:00:00:00:02:00,load:0x1->NXM_NX_REG10[[6]]
+-])
+-
+-AT_CHECK_UNQUOTED([m_as ovn-gw-2 ovs-ofctl dump-flows br-int table=OFTABLE_MAC_LOOKUP | grep priority | grep -vE "reg[[04]]=0xfe80" | \
+- awk '{print $7, $8}' | sort], [0], [dnl
+-priority=100,arp,reg0=0xa00000b,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=00:00:00:00:00:01 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=100,arp,reg0=0xa00000c,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=00:00:00:00:00:02 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,arp,reg0=0xa00002a,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=00:00:00:00:02:00 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,icmp6,reg0=0x100000,reg1=0,reg2=0,reg3=0x42,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=00:00:00:00:02:00,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
+-])
++# Check that SB MAC Bindings are created for the type-2 EVPN MAC+IP routes,
++# in the router pipeline.
++m_wait_row_count mac_binding 1 ip="10.0.0.41" logical_port="lr-ls"
++m_wait_row_count mac_binding 1 ip="10.0.0.42" logical_port="lr-ls"
++m_wait_row_count mac_binding 1 ip="10\:\:41" logical_port="lr-ls"
++m_wait_row_count mac_binding 1 ip="10\:\:42" logical_port="lr-ls"
+
+ AS_BOX([Check traffic to "fabric" hosts - ping from internal hosts])
+ OVS_WAIT_UNTIL([m_as ovn-gw-1 ip netns exec w-int1 ping -W 1 -c 1 10.0.0.41])
+@@ -3756,22 +4144,22 @@ OVS_WAIT_FOR_OUTPUT([m_as ovn-gw-2 ovs-ofctl dump-flows br-int table=OFTABLE_GET
+ check multinode_nbctl --wait=hv lsp-del w1 -- lsp-del w2
+
+ AS_BOX([Checking EVPN MACs on External BGP host])
+-OVS_WAIT_FOR_OUTPUT([m_as ovn-gw-1 ip netns exec frr-ns vtysh --vty_socket /run/frr/frr-ns -c 'show evpn mac vni all'], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([m_as ovn-gw-1 ip netns exec frr-ns vtysh --vty_socket /run/frr/frr-ns -c 'show evpn mac vni all' | tr -s ' '], [0], [dnl
+
+ VNI 10 #MACs (local and remote) 1
+
+ Flags: N=sync-neighs, I=local-inactive, P=peer-active, X=peer-proxy
+-MAC Type Flags Intf/Remote ES/VTEP VLAN Seq #'s
+-00:00:00:00:01:00 local evpn_host_peer 0/0
++MAC Type Flags Intf/Remote ES/VTEP VLAN Seq #'s
++00:00:00:00:01:00 local evpn_host_peer 0/0
+ ])
+
+-OVS_WAIT_FOR_OUTPUT([m_as ovn-gw-2 ip netns exec frr-ns vtysh --vty_socket /run/frr/frr-ns -c 'show evpn mac vni all'], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([m_as ovn-gw-2 ip netns exec frr-ns vtysh --vty_socket /run/frr/frr-ns -c 'show evpn mac vni all' | tr -s ' '], [0], [dnl
+
+ VNI 10 #MACs (local and remote) 1
+
+ Flags: N=sync-neighs, I=local-inactive, P=peer-active, X=peer-proxy
+-MAC Type Flags Intf/Remote ES/VTEP VLAN Seq #'s
+-00:00:00:00:02:00 local evpn_host_peer 0/0
++MAC Type Flags Intf/Remote ES/VTEP VLAN Seq #'s
++00:00:00:00:02:00 local evpn_host_peer 0/0
+ ])
+
+ # Check that the fabric un-learned both FDB and IP routes for the workloads.
+@@ -3980,43 +4368,43 @@ check m_as ovn-gw-2 ip netns exec frr-ns ip -6 addr add dev br-10 10::82/64 noda
+ check m_as ovn-gw-2 ip netns exec frr-ns ip -6 addr add dev br-20 11::82/64 nodad
+
+ AS_BOX([Checking EVPN MACs and IPs on External BGP host])
+-OVS_WAIT_FOR_OUTPUT([m_as ovn-gw-1 ip netns exec frr-ns vtysh --vty_socket /run/frr/frr-ns -c 'show evpn mac vni all'], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([m_as ovn-gw-1 ip netns exec frr-ns vtysh --vty_socket /run/frr/frr-ns -c 'show evpn mac vni all' | tr -s ' '], [0], [dnl
+
+ VNI 10 #MACs (local and remote) 2
+
+ Flags: N=sync-neighs, I=local-inactive, P=peer-active, X=peer-proxy
+-MAC Type Flags Intf/Remote ES/VTEP VLAN Seq #'s
+-00:00:00:00:00:01 remote 42.42.10.12 0/0
+-00:00:00:00:01:00 local evpn_host_peer0 0/0
++MAC Type Flags Intf/Remote ES/VTEP VLAN Seq #'s
++00:00:00:00:00:01 remote 42.42.10.12 0/0
++00:00:00:00:01:00 local evpn_host_peer0 0/0
+
+ VNI 20 #MACs (local and remote) 2
+
+ Flags: N=sync-neighs, I=local-inactive, P=peer-active, X=peer-proxy
+-MAC Type Flags Intf/Remote ES/VTEP VLAN Seq #'s
+-00:00:00:00:11:00 local evpn_host_peer1 0/0
+-00:00:00:00:00:11 remote 42.42.10.12 0/0
++MAC Type Flags Intf/Remote ES/VTEP VLAN Seq #'s
++00:00:00:00:11:00 local evpn_host_peer1 0/0
++00:00:00:00:00:11 remote 42.42.10.12 0/0
+ ])
+
+-OVS_WAIT_FOR_OUTPUT([m_as ovn-gw-2 ip netns exec frr-ns vtysh --vty_socket /run/frr/frr-ns -c 'show evpn mac vni all'], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([m_as ovn-gw-2 ip netns exec frr-ns vtysh --vty_socket /run/frr/frr-ns -c 'show evpn mac vni all' | tr -s ' '], [0], [dnl
+
+ VNI 10 #MACs (local and remote) 2
+
+ Flags: N=sync-neighs, I=local-inactive, P=peer-active, X=peer-proxy
+-MAC Type Flags Intf/Remote ES/VTEP VLAN Seq #'s
+-00:00:00:00:00:02 remote 42.42.10.22 0/0
+-00:00:00:00:02:00 local evpn_host_peer0 0/0
++MAC Type Flags Intf/Remote ES/VTEP VLAN Seq #'s
++00:00:00:00:00:02 remote 42.42.10.22 0/0
++00:00:00:00:02:00 local evpn_host_peer0 0/0
+
+ VNI 20 #MACs (local and remote) 2
+
+ Flags: N=sync-neighs, I=local-inactive, P=peer-active, X=peer-proxy
+-MAC Type Flags Intf/Remote ES/VTEP VLAN Seq #'s
+-00:00:00:00:12:00 local evpn_host_peer1 0/0
+-00:00:00:00:00:12 remote 42.42.10.22 0/0
++MAC Type Flags Intf/Remote ES/VTEP VLAN Seq #'s
++00:00:00:00:12:00 local evpn_host_peer1 0/0
++00:00:00:00:00:12 remote 42.42.10.22 0/0
+ ])
+
+ # Check that the fabric learned both FDB and IP routes for the workloads.
+ OVS_WAIT_FOR_OUTPUT([m_as ovn-gw-1 ip netns exec frr-ns vtysh --vty_socket /run/frr/frr-ns -c 'show bgp l2vpn evpn route' | \
+- grep --no-group-separator -A1 00:00:00:00:00:01], [0], [dnl
++ grep --no-group-separator -A1 00:00:00:00:00:01 | sed 's/ RD [[0-9.:]]*//g'], [0], [dnl
+ *> [[2]]:[[0]]:[[48]]:[[00:00:00:00:00:01]]
+ 42.42.10.12 0 4210000000 i
+ *> [[2]]:[[0]]:[[48]]:[[00:00:00:00:00:01]]:[[32]]:[[10.0.0.11]]
+@@ -4025,7 +4413,7 @@ OVS_WAIT_FOR_OUTPUT([m_as ovn-gw-1 ip netns exec frr-ns vtysh --vty_socket /run/
+ 42.42.10.12 0 4210000000 i
+ ])
+ OVS_WAIT_FOR_OUTPUT([m_as ovn-gw-1 ip netns exec frr-ns vtysh --vty_socket /run/frr/frr-ns -c 'show bgp l2vpn evpn route' | \
+- grep --no-group-separator -A1 00:00:00:00:00:11], [0], [dnl
++ grep --no-group-separator -A1 00:00:00:00:00:11 | sed 's/ RD [[0-9.:]]*//g'], [0], [dnl
+ *> [[2]]:[[0]]:[[48]]:[[00:00:00:00:00:11]]
+ 42.42.10.12 0 4210000000 i
+ *> [[2]]:[[0]]:[[48]]:[[00:00:00:00:00:11]]:[[32]]:[[11.0.0.11]]
+@@ -4034,7 +4422,7 @@ OVS_WAIT_FOR_OUTPUT([m_as ovn-gw-1 ip netns exec frr-ns vtysh --vty_socket /run/
+ 42.42.10.12 0 4210000000 i
+ ])
+ OVS_WAIT_FOR_OUTPUT([m_as ovn-gw-2 ip netns exec frr-ns vtysh --vty_socket /run/frr/frr-ns -c 'show bgp l2vpn evpn route' | \
+- grep --no-group-separator -A1 00:00:00:00:00:02], [0], [dnl
++ grep --no-group-separator -A1 00:00:00:00:00:02 | sed 's/ RD [[0-9.:]]*//g'], [0], [dnl
+ *> [[2]]:[[0]]:[[48]]:[[00:00:00:00:00:02]]
+ 42.42.10.22 0 4210000000 i
+ *> [[2]]:[[0]]:[[48]]:[[00:00:00:00:00:02]]:[[32]]:[[10.0.0.12]]
+@@ -4043,7 +4431,7 @@ OVS_WAIT_FOR_OUTPUT([m_as ovn-gw-2 ip netns exec frr-ns vtysh --vty_socket /run/
+ 42.42.10.22 0 4210000000 i
+ ])
+ OVS_WAIT_FOR_OUTPUT([m_as ovn-gw-2 ip netns exec frr-ns vtysh --vty_socket /run/frr/frr-ns -c 'show bgp l2vpn evpn route' | \
+- grep --no-group-separator -A1 00:00:00:00:00:12], [0], [dnl
++ grep --no-group-separator -A1 00:00:00:00:00:12 | sed 's/ RD [[0-9.:]]*//g'], [0], [dnl
+ *> [[2]]:[[0]]:[[48]]:[[00:00:00:00:00:12]]
+ 42.42.10.22 0 4210000000 i
+ *> [[2]]:[[0]]:[[48]]:[[00:00:00:00:00:12]]:[[32]]:[[11.0.0.12]]
+@@ -4112,66 +4500,22 @@ check multinode_nbctl --wait=hv \
+ -- lsp-add ls1-int w1-int2 \
+ -- lsp-set-addresses w1-int2 "00:00:00:12:00:02 21.0.0.12 2001::12"
+
+-rtr_dp_key0=$(m_fetch_column Datapath tunnel_key external_ids:name=lr0)
+-rtr_dp_key1=$(m_fetch_column Datapath tunnel_key external_ids:name=lr1)
+-rtr_port_key0=$(m_fetch_column Port_Binding tunnel_key logical_port=lr0-ls)
+-rtr_port_key1=$(m_fetch_column Port_Binding tunnel_key logical_port=lr1-ls)
+-
+ check m_as ovn-gw-1 /data/create_fake_vm.sh w0-int1 w0-int1 00:00:00:02:00:01 1500 20.0.0.11 24 20.0.0.1 2000::11/64 2000::1
+ check m_as ovn-gw-1 /data/create_fake_vm.sh w1-int1 w1-int1 00:00:00:12:00:01 1500 21.0.0.11 24 21.0.0.1 2001::11/64 2001::1
+ check m_as ovn-gw-2 /data/create_fake_vm.sh w0-int2 w0-int2 00:00:00:02:00:02 1500 20.0.0.12 24 20.0.0.1 2000::12/64 2000::1
+ check m_as ovn-gw-2 /data/create_fake_vm.sh w1-int2 w1-int2 00:00:00:12:00:02 1500 21.0.0.12 24 21.0.0.1 2001::12/64 2001::1
+ m_wait_for_ports_up
+
+-# Check that flows are created for the type-2 EVPN MAC+IP routes, in the
+-# router pipeline.
+-AT_CHECK_UNQUOTED([m_as ovn-gw-1 ovs-ofctl dump-flows br-int table=OFTABLE_MAC_BINDING | grep priority | grep -vE "reg[[04]]=0xfe80" | \
+- awk '{print $7, $8}' | sort], [0], [dnl
+-priority=100,reg0=0xa00000b,reg15=0x$rtr_port_key0,metadata=0x$rtr_dp_key0 actions=mod_dl_dst:00:00:00:00:00:01,load:0x1->NXM_NX_REG10[[6]]
+-priority=100,reg0=0xa00000c,reg15=0x$rtr_port_key0,metadata=0x$rtr_dp_key0 actions=mod_dl_dst:00:00:00:00:00:02,load:0x1->NXM_NX_REG10[[6]]
+-priority=100,reg0=0xb00000b,reg15=0x$rtr_port_key1,metadata=0x$rtr_dp_key1 actions=mod_dl_dst:00:00:00:00:00:11,load:0x1->NXM_NX_REG10[[6]]
+-priority=100,reg0=0xb00000c,reg15=0x$rtr_port_key1,metadata=0x$rtr_dp_key1 actions=mod_dl_dst:00:00:00:00:00:12,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg0=0xa000029,reg15=0x$rtr_port_key0,metadata=0x$rtr_dp_key0 actions=mod_dl_dst:00:00:00:00:01:00,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg0=0xb000029,reg15=0x$rtr_port_key1,metadata=0x$rtr_dp_key1 actions=mod_dl_dst:00:00:00:00:11:00,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg4=0x100000,reg5=0,reg6=0,reg7=0x41,reg15=0x$rtr_port_key0,metadata=0x$rtr_dp_key0 actions=mod_dl_dst:00:00:00:00:01:00,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg4=0x110000,reg5=0,reg6=0,reg7=0x41,reg15=0x$rtr_port_key1,metadata=0x$rtr_dp_key1 actions=mod_dl_dst:00:00:00:00:11:00,load:0x1->NXM_NX_REG10[[6]]
+-])
+-
+-AT_CHECK_UNQUOTED([m_as ovn-gw-1 ovs-ofctl dump-flows br-int table=OFTABLE_MAC_LOOKUP | grep priority | grep -vE "reg[[04]]=0xfe80" | \
+- awk '{print $7, $8}' | sort], [0], [dnl
+-priority=100,arp,reg0=0xa00000b,reg14=0x$rtr_port_key0,metadata=0x$rtr_dp_key0,dl_src=00:00:00:00:00:01 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=100,arp,reg0=0xa00000c,reg14=0x$rtr_port_key0,metadata=0x$rtr_dp_key0,dl_src=00:00:00:00:00:02 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=100,arp,reg0=0xb00000b,reg14=0x$rtr_port_key1,metadata=0x$rtr_dp_key1,dl_src=00:00:00:00:00:11 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=100,arp,reg0=0xb00000c,reg14=0x$rtr_port_key1,metadata=0x$rtr_dp_key1,dl_src=00:00:00:00:00:12 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,arp,reg0=0xa000029,reg14=0x$rtr_port_key0,metadata=0x$rtr_dp_key0,dl_src=00:00:00:00:01:00 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,arp,reg0=0xb000029,reg14=0x$rtr_port_key1,metadata=0x$rtr_dp_key1,dl_src=00:00:00:00:11:00 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,icmp6,reg0=0x100000,reg1=0,reg2=0,reg3=0x41,reg14=0x$rtr_port_key0,metadata=0x$rtr_dp_key0,dl_src=00:00:00:00:01:00,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,icmp6,reg0=0x110000,reg1=0,reg2=0,reg3=0x41,reg14=0x$rtr_port_key1,metadata=0x$rtr_dp_key1,dl_src=00:00:00:00:11:00,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
+-])
+-
+-AT_CHECK_UNQUOTED([m_as ovn-gw-2 ovs-ofctl dump-flows br-int table=OFTABLE_MAC_BINDING | grep priority | grep -vE "reg[[04]]=0xfe80" | \
+- awk '{print $7, $8}' | sort], [0], [dnl
+-priority=100,reg0=0xa00000b,reg15=0x$rtr_port_key0,metadata=0x$rtr_dp_key0 actions=mod_dl_dst:00:00:00:00:00:01,load:0x1->NXM_NX_REG10[[6]]
+-priority=100,reg0=0xa00000c,reg15=0x$rtr_port_key0,metadata=0x$rtr_dp_key0 actions=mod_dl_dst:00:00:00:00:00:02,load:0x1->NXM_NX_REG10[[6]]
+-priority=100,reg0=0xb00000b,reg15=0x$rtr_port_key1,metadata=0x$rtr_dp_key1 actions=mod_dl_dst:00:00:00:00:00:11,load:0x1->NXM_NX_REG10[[6]]
+-priority=100,reg0=0xb00000c,reg15=0x$rtr_port_key1,metadata=0x$rtr_dp_key1 actions=mod_dl_dst:00:00:00:00:00:12,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg0=0xa00002a,reg15=0x$rtr_port_key0,metadata=0x$rtr_dp_key0 actions=mod_dl_dst:00:00:00:00:02:00,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg0=0xb00002a,reg15=0x$rtr_port_key1,metadata=0x$rtr_dp_key1 actions=mod_dl_dst:00:00:00:00:12:00,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg4=0x100000,reg5=0,reg6=0,reg7=0x42,reg15=0x$rtr_port_key0,metadata=0x$rtr_dp_key0 actions=mod_dl_dst:00:00:00:00:02:00,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg4=0x110000,reg5=0,reg6=0,reg7=0x42,reg15=0x$rtr_port_key1,metadata=0x$rtr_dp_key1 actions=mod_dl_dst:00:00:00:00:12:00,load:0x1->NXM_NX_REG10[[6]]
+-])
+-
+-AT_CHECK_UNQUOTED([m_as ovn-gw-2 ovs-ofctl dump-flows br-int table=OFTABLE_MAC_LOOKUP | grep priority | grep -vE "reg[[04]]=0xfe80" | \
+- awk '{print $7, $8}' | sort], [0], [dnl
+-priority=100,arp,reg0=0xa00000b,reg14=0x$rtr_port_key0,metadata=0x$rtr_dp_key0,dl_src=00:00:00:00:00:01 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=100,arp,reg0=0xa00000c,reg14=0x$rtr_port_key0,metadata=0x$rtr_dp_key0,dl_src=00:00:00:00:00:02 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=100,arp,reg0=0xb00000b,reg14=0x$rtr_port_key1,metadata=0x$rtr_dp_key1,dl_src=00:00:00:00:00:11 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=100,arp,reg0=0xb00000c,reg14=0x$rtr_port_key1,metadata=0x$rtr_dp_key1,dl_src=00:00:00:00:00:12 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,arp,reg0=0xa00002a,reg14=0x$rtr_port_key0,metadata=0x$rtr_dp_key0,dl_src=00:00:00:00:02:00 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,arp,reg0=0xb00002a,reg14=0x$rtr_port_key1,metadata=0x$rtr_dp_key1,dl_src=00:00:00:00:12:00 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,icmp6,reg0=0x100000,reg1=0,reg2=0,reg3=0x42,reg14=0x$rtr_port_key0,metadata=0x$rtr_dp_key0,dl_src=00:00:00:00:02:00,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,icmp6,reg0=0x110000,reg1=0,reg2=0,reg3=0x42,reg14=0x$rtr_port_key1,metadata=0x$rtr_dp_key1,dl_src=00:00:00:00:12:00,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
+-])
++# Check that SB MAC Bindings are created for the type-2 EVPN MAC+IP routes,
++# in the router pipeline.
++m_wait_row_count mac_binding 1 ip="10.0.0.41" logical_port="lr0-ls"
++m_wait_row_count mac_binding 1 ip="10.0.0.42" logical_port="lr0-ls"
++m_wait_row_count mac_binding 1 ip="11.0.0.41" logical_port="lr1-ls"
++m_wait_row_count mac_binding 1 ip="11.0.0.42" logical_port="lr1-ls"
++m_wait_row_count mac_binding 1 ip="10\:\:41" logical_port="lr0-ls"
++m_wait_row_count mac_binding 1 ip="10\:\:42" logical_port="lr0-ls"
++m_wait_row_count mac_binding 1 ip="11\:\:41" logical_port="lr1-ls"
++m_wait_row_count mac_binding 1 ip="11\:\:42" logical_port="lr1-ls"
+
+ AS_BOX([Check traffic to "fabric" hosts - ping from internal hosts])
+ OVS_WAIT_UNTIL([m_as ovn-gw-1 ip netns exec w0-int1 ping -W 1 -c 1 10.0.0.41])
+@@ -4211,34 +4555,34 @@ OVS_WAIT_FOR_OUTPUT([m_as ovn-gw-2 ovs-ofctl dump-flows br-int table=OFTABLE_GET
+ check multinode_nbctl --wait=hv lsp-del w01 -- lsp-del w02 -- lsp-del w11 -- lsp-del w12
+
+ AS_BOX([Checking EVPN MACs on External BGP host])
+-OVS_WAIT_FOR_OUTPUT([m_as ovn-gw-1 ip netns exec frr-ns vtysh --vty_socket /run/frr/frr-ns -c 'show evpn mac vni all'], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([m_as ovn-gw-1 ip netns exec frr-ns vtysh --vty_socket /run/frr/frr-ns -c 'show evpn mac vni all' | tr -s ' '], [0], [dnl
+
+ VNI 10 #MACs (local and remote) 1
+
+ Flags: N=sync-neighs, I=local-inactive, P=peer-active, X=peer-proxy
+-MAC Type Flags Intf/Remote ES/VTEP VLAN Seq #'s
+-00:00:00:00:01:00 local evpn_host_peer0 0/0
++MAC Type Flags Intf/Remote ES/VTEP VLAN Seq #'s
++00:00:00:00:01:00 local evpn_host_peer0 0/0
+
+ VNI 20 #MACs (local and remote) 1
+
+ Flags: N=sync-neighs, I=local-inactive, P=peer-active, X=peer-proxy
+-MAC Type Flags Intf/Remote ES/VTEP VLAN Seq #'s
+-00:00:00:00:11:00 local evpn_host_peer1 0/0
++MAC Type Flags Intf/Remote ES/VTEP VLAN Seq #'s
++00:00:00:00:11:00 local evpn_host_peer1 0/0
+ ])
+
+-OVS_WAIT_FOR_OUTPUT([m_as ovn-gw-2 ip netns exec frr-ns vtysh --vty_socket /run/frr/frr-ns -c 'show evpn mac vni all'], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([m_as ovn-gw-2 ip netns exec frr-ns vtysh --vty_socket /run/frr/frr-ns -c 'show evpn mac vni all' | tr -s ' '], [0], [dnl
+
+ VNI 10 #MACs (local and remote) 1
+
+ Flags: N=sync-neighs, I=local-inactive, P=peer-active, X=peer-proxy
+-MAC Type Flags Intf/Remote ES/VTEP VLAN Seq #'s
+-00:00:00:00:02:00 local evpn_host_peer0 0/0
++MAC Type Flags Intf/Remote ES/VTEP VLAN Seq #'s
++00:00:00:00:02:00 local evpn_host_peer0 0/0
+
+ VNI 20 #MACs (local and remote) 1
+
+ Flags: N=sync-neighs, I=local-inactive, P=peer-active, X=peer-proxy
+-MAC Type Flags Intf/Remote ES/VTEP VLAN Seq #'s
+-00:00:00:00:12:00 local evpn_host_peer1 0/0
++MAC Type Flags Intf/Remote ES/VTEP VLAN Seq #'s
++00:00:00:00:12:00 local evpn_host_peer1 0/0
+ ])
+
+ # Check that the fabric un-learned both FDB and IP routes for the workloads.
+@@ -4881,6 +5225,9 @@ check multinode_nbctl lrp-set-gateway-chassis ro-public ovn-gw-1
+ check multinode_nbctl lrp-set-redirect-type ro-public bridged
+
+ m_as ovn-gw-1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-ex
++
++on_exit 'm_as ovn-chassis-1 ovs-vsctl remove open . external-ids ovn-chassis-mac-mappings'
++on_exit 'm_as ovn-gw-1 ovs-vsctl remove open . external-ids ovn-chassis-mac-mappings'
+ m_as ovn-chassis-1 ovs-vsctl set open . external-ids:ovn-chassis-mac-mappings="public:aa:bb:cc:dd:01:01"
+ m_as ovn-gw-1 ovs-vsctl set open . external-ids:ovn-chassis-mac-mappings="public:aa:bb:cc:dd:02:01"
+
+@@ -4959,13 +5306,13 @@ M_NS_CHECK_EXEC([ovn-chassis-1], [ls1p1], [ping -q -c 3 -i 0.3 -w 2 172.18.86.10
+ ])
+ m_wait_row_count mac_binding 1 ip="172.18.86.102" logical_port="lr1-pub"
+
+-M_NS_CHECK_EXEC([ovn-chassis-1], [ls1p1], [ping6 -q -c 3 -i 0.3 -w 2 6812:86::101 | FORMAT_PING], \
++M_NS_CHECK_EXEC([ovn-chassis-1], [ls1p1], [ping -6 -q -c 3 -i 0.3 -w 2 6812:86::101 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+ m_wait_row_count mac_binding 1 ip="6812\:86\:\:101" logical_port="lr1-pub"
+
+-M_NS_CHECK_EXEC([ovn-chassis-1], [ls1p1], [ping6 -q -c 3 -i 0.3 -w 2 6812:86::102 | FORMAT_PING], \
++M_NS_CHECK_EXEC([ovn-chassis-1], [ls1p1], [ping -6 -q -c 3 -i 0.3 -w 2 6812:86::102 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+@@ -4990,13 +5337,13 @@ M_NS_CHECK_EXEC([ovn-chassis-2], [ovn-ext2], [ping -q -c 3 -i 0.3 -w 2 172.18.86
+ ])
+ m_wait_row_count mac_binding 1 ip="172.18.86.101" logical_port="lr1-pub"
+
+-M_NS_CHECK_EXEC([ovn-chassis-1], [ovn-ext1], [ping6 -q -c 3 -i 0.3 -w 2 6812:86::11 | FORMAT_PING], \
++M_NS_CHECK_EXEC([ovn-chassis-1], [ovn-ext1], [ping -6 -q -c 3 -i 0.3 -w 2 6812:86::11 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+ m_wait_row_count mac_binding 1 ip="6812\:86\:\:101" logical_port="lr1-pub"
+
+-M_NS_CHECK_EXEC([ovn-chassis-2], [ovn-ext2], [ping6 -q -c 3 -i 0.3 -w 2 6812:86::11 | FORMAT_PING], \
++M_NS_CHECK_EXEC([ovn-chassis-2], [ovn-ext2], [ping -6 -q -c 3 -i 0.3 -w 2 6812:86::11 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+@@ -5006,7 +5353,7 @@ m_wait_row_count mac_binding 1 ip="6812\:86\:\:102" logical_port="lr1-pub"
+ m_central_as ovn-sbctl --all destroy mac_binding
+ M_NS_CHECK_EXEC([ovn-chassis-2], [ovn-ext2], [ip -6 neigh del 6812:86::11 dev ext2 lladdr 0a:0a:56:33:02:11])
+
+-M_NS_CHECK_EXEC([ovn-chassis-2], [ovn-ext2], [ping6 -q -c 3 -i 0.3 -w 2 6812:86::11 | FORMAT_PING], \
++M_NS_CHECK_EXEC([ovn-chassis-2], [ovn-ext2], [ping -6 -q -c 3 -i 0.3 -w 2 6812:86::11 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+@@ -5136,8 +5483,8 @@ M_START_L4_SERVER([ovn-chassis-2], [ls1p3], [192.168.1.3], [10880], [ls1p3], [ls
+ # Capture traffic to verify load balancing occurs locally without east-west traffic
+ for i in {1..2}; do
+ node_name="ovn-chassis-$i"
+- M_START_TCPDUMP([$node_name], [-c 2 -neei genev_sys_6081 port 10880], [ch${i}_genev])
+- M_START_TCPDUMP([$node_name], [-c 2 -neei eth2 port 10880], [ch${i}_eth2])
++ M_START_TCPDUMPS([$node_name], [-c 2 -neei genev_sys_6081 port 10880], [ch${i}_genev],
++ [$node_name], [-c 2 -neei eth2 port 10880], [ch${i}_eth2])
+ done
+
+ AT_CHECK([m_as ovn-gw-1 /bin/bash -c 'for i in $(seq 500); \
+diff --git a/tests/ovn-controller-vtep.at b/tests/ovn-controller-vtep.at
+index ffd394a992..1f9079b97c 100644
+--- a/tests/ovn-controller-vtep.at
++++ b/tests/ovn-controller-vtep.at
+@@ -426,7 +426,7 @@ AT_CHECK([ovn-nbctl lsp-set-addresses vif2 f0:ab:cd:ef:02:02])
+ AT_CHECK([ovn-nbctl --wait=sb sync])
+ AT_CHECK([ovn-sbctl chassis-add ch2 vxlan 1.2.3.7])
+ AT_CHECK([ovn-sbctl lsp-bind vif2 ch2])
+-AT_CHECK([vtep-ctl --columns=MAC list Ucast_Macs_Remote | cut -d ':' -f2- | tr -d ' ' | sort], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([vtep-ctl --columns=MAC list Ucast_Macs_Remote | cut -d ':' -f2- | tr -d ' ' | sort], [0], [dnl
+
+ "f0:ab:cd:ef:01:03"
+ "f0:ab:cd:ef:02:02"
+diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at
+index 3156754fa4..72f855e0a9 100644
+--- a/tests/ovn-controller.at
++++ b/tests/ovn-controller.at
+@@ -758,6 +758,7 @@ check as northd ovn-appctl -t ovn-northd resume
+ # Since northd is restarted, port type is back to localport, so ls is not local datapath anymore.
+ OVN_CLEANUP([hv1
+ /No distributed-port option set for chassisredirect port lsp1/d
++/No port binding record for distributed port/d
+ ignored_dp=ls1
+ ])
+ AT_CLEANUP
+@@ -3730,6 +3731,10 @@ check ovn-sbctl chassis-add hv2 geneve 192.168.0.12 \
+ check ovn-sbctl chassis-add hv3 vxlan 192.168.0.14 \
+ -- set chassis hv3 other_config:is-remote=true
+
++# Wait for tunnels to hv2 and hv3 to be created.
++OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv1"], ["hv2"])
++OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv1"], ["hv3"])
++
+ check ovn-nbctl --wait=hv sync
+
+ chassis_cookie() {
+@@ -3791,6 +3796,9 @@ check_uuid ovn-sbctl --id=@encap create Encap type=geneve ip=192.168.0.13 \
+ -- add Chassis hv2 encaps @encap
+ check ovn-sbctl set chassis hv2 other_config:is-remote=true
+
++AT_CHECK([as hv1 ovs-vsctl wait-until interface ovn-hv2-0 'ofport>0'])
++AT_CHECK([as hv1 ovs-vsctl wait-until interface ovn-hv2-1 'ofport>0'])
++
+ check ovn-nbctl --wait=hv sync
+
+ # Wait for both tunnels to be created by checking ovn-chassis-id external_ids.
+@@ -4112,3 +4120,60 @@ done
+ OVN_CLEANUP([hv1])
+ AT_CLEANUP
+ ])
++
++OVN_FOR_EACH_NORTHD([
++AT_SETUP([ovn-controller - transient flag for HA chassis])
++AT_KEYWORDS([ovn])
++ovn_start
++
++net_add n1
++sim_add hv1
++as hv1
++check ovs-vsctl add-br br-phys
++ovn_attach n1 br-phys 192.168.0.1
++
++sim_add hv2
++as hv2
++check ovs-vsctl add-br br-phys
++ovn_attach n1 br-phys 192.168.0.2
++
++check ovn-nbctl ls-add ls1
++check ovn-nbctl lr-add lr1
++check ovn-nbctl lrp-add lr1 lr1-ls1 00:00:00:00:00:01 10.0.0.1/24
++check ovn-nbctl lsp-add ls1 ls1-lr1 -- lsp-set-type ls1-lr1 router \
++ -- lsp-set-addresses ls1-lr1 router -- lsp-set-options ls1-lr1 router-port=lr1-ls1
++
++OVS_WAIT_UNTIL([as hv1 ovs-vsctl get interface ovn-hv2-0 type])
++OVS_WAIT_UNTIL([as hv2 ovs-vsctl get interface ovn-hv1-0 type])
++
++# Verify tunnel ports do not have transient flag set.
++AT_CHECK([as hv1 ovs-vsctl --bare get port ovn-hv2-0 other_config], [0], [dnl
++{}
++])
++AT_CHECK([as hv2 ovs-vsctl --bare get port ovn-hv1-0 other_config], [0], [dnl
++{}
++])
++
++check ovn-nbctl ha-chassis-group-add hagrp
++check ovn-nbctl ha-chassis-group-add-chassis hagrp hv1 10
++check ovn-nbctl ha-chassis-group-add-chassis hagrp hv2 20
++
++hagrp_uuid=$(ovn-nbctl --bare --columns _uuid find ha_chassis_group name=hagrp)
++check ovn-nbctl set logical_router_port lr1-ls1 ha_chassis_group=$hagrp_uuid
++
++wait_row_count HA_Chassis_Group 1 name=hagrp
++wait_row_count HA_Chassis 2
++
++# Verify tunnel ports now have transient=true.
++OVS_WAIT_UNTIL([test "$(as hv1 ovs-vsctl get port ovn-hv2-0 other_config:transient)" = '"true"'])
++OVS_WAIT_UNTIL([test "$(as hv2 ovs-vsctl get port ovn-hv1-0 other_config:transient)" = '"true"'])
++
++check ovn-nbctl ha-chassis-group-remove-chassis hagrp hv2
++
++# Verify transient flag is cleared on both HVs.
++OVS_WAIT_UNTIL([test "$(as hv1 ovs-vsctl --bare get port ovn-hv2-0 other_config)" = '{}'])
++OVS_WAIT_UNTIL([test "$(as hv2 ovs-vsctl --bare get port ovn-hv1-0 other_config)" = '{}'])
++
++OVN_CLEANUP([hv1],[hv2])
++AT_CLEANUP
++])
+diff --git a/tests/ovn-ic.at b/tests/ovn-ic.at
+index 8bb5a41774..b7559ebb8b 100644
+--- a/tests/ovn-ic.at
++++ b/tests/ovn-ic.at
+@@ -47,7 +47,7 @@ ovn_az_attach az1 n1 br-phys 192.168.1.1
+ check ovs-vsctl set open . external-ids:ovn-is-interconn=true
+
+ az_uuid=$(fetch_column ic-sb:availability-zone _uuid name="az1")
+-ovn_as az1 ovn-nbctl set NB_Global . name="az2"
++check ovn_as az1 ovn-nbctl set NB_Global . name="az2"
+
+ check ovn-ic-nbctl --wait=sb sync
+ check_column "$az_uuid" ic-sb:availability-zone _uuid name="az2"
+@@ -228,7 +228,7 @@ check ovn-ic-nbctl --wait=sb sync
+
+ ovn_as az1
+
+-check_row_count ic-sb:Route 3 ip_prefix=192.168.0.0/16
++wait_row_count ic-sb:Route 3 ip_prefix=192.168.0.0/16
+
+ # remove transit switch 1 (from az1) and check if its route is deleted
+ # same route from another AZ and ts should remain, as
+@@ -347,11 +347,12 @@ for i in 1 2; do
+ check ovn-nbctl --wait=sb lsp-add-router-port ts1 lsp$i lrp$i
+ check ovn-ic-nbctl --wait=sb sync
+
+- check \
+- grep -q "connected route advertisement was suppressed! NB lrp" \
+- az$i/ic/ovn-ic.log
++ OVS_WAIT_UNTIL([grep -q "connected route advertisement was suppressed! NB lrp" az$i/ic/ovn-ic.log])
+ done
+
++# Make sure last az2 changes got handled by ic in az1
++check ovn-ic-nbctl --wait=sb sync
++
+ for i in 1 2; do
+ az=az$i
+ ovn_as $az
+@@ -442,7 +443,7 @@ check ovn-nbctl lr-add lr1
+ check ovn-nbctl lrp-add lr1 lrp-lr1-ts1 aa:aa:aa:aa:aa:01 169.254.100.1/24
+ check ovn-nbctl --wait=hv lsp-add-router-port ts1 lsp-ts1-lr1 lrp-lr1-ts1
+ OVS_WAIT_UNTIL([ovn_as az2 ovn-nbctl show | grep lsp-ts1-lr1])
+-ovn_as az2 ovn-nbctl lsp-set-options lsp-ts1-lr1 requested-chassis=gw1
++check ovn_as az2 ovn-nbctl lsp-set-options lsp-ts1-lr1 requested-chassis=gw1
+
+ OVS_WAIT_FOR_OUTPUT([ovn_as az2 ovn-nbctl show | uuidfilt], [0], [dnl
+ switch <0> (ts1)
+@@ -458,7 +459,7 @@ lsp-ts1-lr1,remote
+ check ovn-nbctl lrp-set-gateway-chassis lrp-lr1-ts1 gw1
+ OVS_WAIT_UNTIL([ovn_as az2 ovn-sbctl show | grep lsp-ts1-lr1])
+
+-ovn_as az2 ovn-nbctl lsp-set-options lsp-ts1-lr1 requested-chassis=""
++check ovn_as az2 ovn-nbctl lsp-set-options lsp-ts1-lr1 requested-chassis=""
+ check ovn-nbctl lrp-del-gateway-chassis lrp-lr1-ts1 gw1
+ OVS_WAIT_WHILE([ovn_as az2 ovn-sbctl show | grep lsp-ts1-lr1])
+
+@@ -527,7 +528,7 @@ Route Table <main>:
+ ])
+
+ # Delete route in AZ1, AZ2's learned route should be deleted.
+-ovn_as az1 ovn-nbctl lr-route-del lr1 10.11.1.0/24
++check ovn_as az1 ovn-nbctl lr-route-del lr1 10.11.1.0/24
+ ovn-ic-nbctl --wait=sb sync
+ ovn-ic-nbctl --wait=sb sync
+ AT_CHECK([ovn_as az2 ovn-nbctl lr-route-list lr2 | grep -c learned], [1], [dnl
+@@ -535,7 +536,7 @@ AT_CHECK([ovn_as az2 ovn-nbctl lr-route-list lr2 | grep -c learned], [1], [dnl
+ ])
+
+ # Add the route back
+-ovn_as az1 ovn-nbctl lr-route-add lr1 10.11.1.0/24 169.254.0.1
++check ovn_as az1 ovn-nbctl lr-route-add lr1 10.11.1.0/24 169.254.0.1
+ ovn-ic-nbctl --wait=sb sync
+ ovn-ic-nbctl --wait=sb sync
+ AT_CHECK([ovn_as az2 ovn-nbctl lr-route-list lr2 | grep -c learned], [0], [dnl
+@@ -543,7 +544,7 @@ AT_CHECK([ovn_as az2 ovn-nbctl lr-route-list lr2 | grep -c learned], [0], [dnl
+ ])
+
+ # Disable route-learning for AZ1
+-ovn_as az1 ovn-nbctl set nb_global . options:ic-route-learn=false
++check ovn_as az1 ovn-nbctl set nb_global . options:ic-route-learn=false
+ ovn-ic-nbctl --wait=sb sync
+ AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr1], [0], [dnl
+ IPv4 Routes
+@@ -556,7 +557,7 @@ Route Table <main>:
+ AT_CHECK([ovn_as az2 ovn-nbctl lr-route-list lr2 | grep learned], [0], [ignore])
+
+ # Disable route-advertising for AZ1
+-ovn_as az1 ovn-nbctl set nb_global . options:ic-route-adv=false
++check ovn_as az1 ovn-nbctl set nb_global . options:ic-route-adv=false
+
+ # AZ2 shouldn't have the route learned, because AZ1 should have stopped
+ # advertising.
+@@ -570,11 +571,11 @@ Route Table <main>:
+ ])
+
+ # Add default route in AZ1
+-ovn_as az1 ovn-nbctl lr-route-add lr1 0.0.0.0/0 169.254.0.3
++check ovn_as az1 ovn-nbctl lr-route-add lr1 0.0.0.0/0 169.254.0.3
+
+ # Re-enable router-advertising & learn for AZ1
+-ovn_as az1 ovn-nbctl set nb_global . options:ic-route-adv=true
+-ovn_as az1 ovn-nbctl set nb_global . options:ic-route-learn=true
++check ovn_as az1 ovn-nbctl set nb_global . options:ic-route-adv=true
++check ovn_as az1 ovn-nbctl set nb_global . options:ic-route-learn=true
+ check ovn-ic-nbctl --wait=sb sync
+ check ovn-ic-nbctl --wait=sb sync
+
+@@ -588,30 +589,30 @@ Route Table <main>:
+ ])
+
+ # Enable default route advertising in AZ1
+-ovn_as az1 ovn-nbctl set nb_global . options:ic-route-adv-default=true
++check ovn_as az1 ovn-nbctl set nb_global . options:ic-route-adv-default=true
+ OVS_WAIT_UNTIL([ovn-ic-sbctl list route | grep 0.0.0.0])
+
+ # Enable default route learning in AZ2
+-ovn_as az2 ovn-nbctl set nb_global . options:ic-route-learn-default=true
++check ovn_as az2 ovn-nbctl set nb_global . options:ic-route-learn-default=true
+ OVS_WAIT_UNTIL([ovn_as az2 ovn-nbctl lr-route-list lr2 | grep learned | grep 0.0.0.0])
+
+ # Test directly connected subnet route advertising.
+-ovn_as az1 ovn-nbctl lrp-add lr1 lrp-lr1-ls1 aa:aa:aa:aa:bb:01 "192.168.0.1/24"
++check ovn_as az1 ovn-nbctl lrp-add lr1 lrp-lr1-ls1 aa:aa:aa:aa:bb:01 "192.168.0.1/24"
+ OVS_WAIT_UNTIL([ovn_as az2 ovn-nbctl lr-route-list lr2 | grep learned | grep 192.168])
+
+ # Delete the directly connected subnet from AZ1, learned route should be
+ # removed from AZ2.
+-ovn_as az1 ovn-nbctl lrp-del lrp-lr1-ls1
++check ovn_as az1 ovn-nbctl lrp-del lrp-lr1-ls1
+ OVS_WAIT_WHILE([ovn_as az2 ovn-nbctl lr-route-list lr2 | grep learned | grep 192.168])
+
+ # Test denylist routes
+ # Add back the directly connected 192.168 route.
+-ovn_as az1 ovn-nbctl lrp-add lr1 lrp-lr1-ls1 aa:aa:aa:aa:bb:01 "192.168.0.1/24"
++check ovn_as az1 ovn-nbctl lrp-add lr1 lrp-lr1-ls1 aa:aa:aa:aa:bb:01 "192.168.0.1/24"
+ OVS_WAIT_UNTIL([ovn_as az2 ovn-nbctl lr-route-list lr2 | grep learned | grep 192.168])
+ # Ensure AZ1 learned AZ2's 10.11.2.0 route as well.
+ OVS_WAIT_UNTIL([ovn_as az1 ovn-nbctl lr-route-list lr1 | grep learned | grep 10.11])
+ # Now black list 10.11.0.0/16 and 192.168.0.0/16 in AZ2.
+-ovn_as az2 ovn-nbctl set nb_global . options:ic-route-denylist="10.11.0.0/16,192.168.0.0/16"
++check ovn_as az2 ovn-nbctl set nb_global . options:ic-route-denylist="10.11.0.0/16,192.168.0.0/16"
+ # AZ2 shouldn't learn 192.168 route any more.
+ OVS_WAIT_WHILE([ovn_as az2 ovn-nbctl lr-route-list lr2 | grep learned | grep 192.168])
+ # AZ1 shouldn't learn 10.11 any more.
+@@ -619,8 +620,8 @@ OVS_WAIT_WHILE([ovn_as az1 ovn-nbctl lr-route-list lr1 | grep learned | grep 10.
+
+ # cleanup
+ ovn-ic-nbctl --if-exists ts-del ts1
+-ovn_as az1 ovn-nbctl lr-del lr1
+-ovn_as az2 ovn-nbctl lr-del lr2
++check ovn_as az1 ovn-nbctl lr-del lr1
++check ovn_as az2 ovn-nbctl lr-del lr2
+
+ # Create new transit switches and LRs. Test topology is next:
+ # logical router (lr11) - transit switch (ts11) - logical router (lr12)
+@@ -651,9 +652,9 @@ for i in 1 2; do
+ done
+
+ # Create directly-connected routes
+-ovn_as az2 ovn-nbctl --wait=sb lrp-add lr12 lrp-lr12-ls2 aa:aa:aa:aa:bb:01 "192.168.0.1/24"
+-ovn_as az2 ovn-nbctl lr-route-add lr12 10.10.10.0/24 192.168.0.10
+-ovn_as az1 ovn-nbctl --wait=sb sync
++check ovn_as az2 ovn-nbctl --wait=sb lrp-add lr12 lrp-lr12-ls2 aa:aa:aa:aa:bb:01 "192.168.0.1/24"
++check ovn_as az2 ovn-nbctl lr-route-add lr12 10.10.10.0/24 192.168.0.10
++check ovn_as az1 ovn-nbctl --wait=sb sync
+
+ echo az1
+ ovn_as az1 ovn-nbctl show
+@@ -674,10 +675,10 @@ AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr21], [0], [])
+
+ # cleanup
+ ovn-ic-nbctl --if-exists ts-del ts1
+-ovn_as az1 ovn-nbctl lr-del lr11
+-ovn_as az1 ovn-nbctl lr-del lr21
+-ovn_as az2 ovn-nbctl lr-del lr12
+-ovn_as az2 ovn-nbctl lr-del lr22
++check ovn_as az1 ovn-nbctl lr-del lr11
++check ovn_as az1 ovn-nbctl lr-del lr21
++check ovn_as az2 ovn-nbctl lr-del lr12
++check ovn_as az2 ovn-nbctl lr-del lr22
+
+ # check routes origin advertisement and learning
+
+@@ -1103,8 +1104,8 @@ Route Table rtb1:
+
+ # move routes from <main> route table to rtb1
+ for i in 1 2; do
+- ovn_as az$i ovn-nbctl lr-route-del lr$i 10.11.$i.0/24 169.254.0.1
+- ovn_as az$i ovn-nbctl --route-table=rtb1 lr-route-add lr$i 10.11.$i.0/24 169.254.0.1
++ check ovn_as az$i ovn-nbctl lr-route-del lr$i 10.11.$i.0/24 169.254.0.1
++ check ovn_as az$i ovn-nbctl --route-table=rtb1 lr-route-add lr$i 10.11.$i.0/24 169.254.0.1
+ done
+
+ check ovn-ic-nbctl --wait=sb sync
+@@ -1143,15 +1144,15 @@ Route Table rtb1:
+ ])
+
+ # Delete route in AZ1, AZ2's learned route should be deleted.
+-ovn_as az1 ovn-nbctl --route-table=rtb1 lr-route-del lr1 10.11.1.0/24
++check ovn_as az1 ovn-nbctl --route-table=rtb1 lr-route-del lr1 10.11.1.0/24
+ OVS_WAIT_WHILE([ovn_as az2 ovn-nbctl --route-table=rtb1 lr-route-list lr2 | grep learned])
+
+ # Add the route back
+-ovn_as az1 ovn-nbctl --route-table=rtb1 lr-route-add lr1 10.11.1.0/24 169.254.0.1
++check ovn_as az1 ovn-nbctl --route-table=rtb1 lr-route-add lr1 10.11.1.0/24 169.254.0.1
+ OVS_WAIT_UNTIL([ovn_as az2 ovn-nbctl lr-route-list lr2 | grep learned])
+
+ # Disable route-learning for AZ1
+-ovn_as az1 ovn-nbctl set nb_global . options:ic-route-learn=false
++check ovn_as az1 ovn-nbctl set nb_global . options:ic-route-learn=false
+ OVS_WAIT_WHILE([ovn_as az1 ovn-nbctl lr-route-list lr1 | grep learned])
+ AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr1], [0], [dnl
+ IPv4 Routes
+@@ -1171,17 +1172,17 @@ Route Table rtb1:
+ ])
+
+ # Disable route-advertising for AZ1
+-ovn_as az1 ovn-nbctl set nb_global . options:ic-route-adv=false
++check ovn_as az1 ovn-nbctl set nb_global . options:ic-route-adv=false
+
+ # AZ2 shouldn't have the route learned, because AZ1 have stopped advertising.
+ OVS_WAIT_WHILE([ovn_as az2 ovn-nbctl lr-route-list lr2 | grep learned])
+
+ # Add default route in AZ1
+-ovn_as az1 ovn-nbctl --route-table=rtb1 lr-route-add lr1 0.0.0.0/0 169.254.0.3
++check ovn_as az1 ovn-nbctl --route-table=rtb1 lr-route-add lr1 0.0.0.0/0 169.254.0.3
+
+ # Re-enable router-advertising & learn for AZ1
+-ovn_as az1 ovn-nbctl set nb_global . options:ic-route-adv=true
+-ovn_as az1 ovn-nbctl set nb_global . options:ic-route-learn=true
++check ovn_as az1 ovn-nbctl set nb_global . options:ic-route-adv=true
++check ovn_as az1 ovn-nbctl set nb_global . options:ic-route-learn=true
+
+ for i in 1 2; do
+ OVS_WAIT_UNTIL([ovn_as az$i ovn-nbctl lr-route-list lr$i | grep learned])
+@@ -1191,16 +1192,16 @@ done
+ AT_CHECK([ovn-ic-sbctl find route ip_prefix="0.0.0.0/0"], [0], [])
+
+ # Enable default route advertising in AZ1, ensure it advertised, but not learned
+-ovn_as az1 ovn-nbctl set nb_global . options:ic-route-adv-default=true
++check ovn_as az1 ovn-nbctl set nb_global . options:ic-route-adv-default=true
+ OVS_WAIT_UNTIL([ovn-ic-sbctl find route ip_prefix="0.0.0.0/0" route_table=rtb1 | grep 0.0.0.0])
+ OVS_WAIT_WHILE([ovn_as az2 ovn-nbctl --route-table=rtb1 lr-route-list lr2 | grep learned | grep 0.0.0.0])
+
+ # Enable default route learning in AZ2
+-ovn_as az2 ovn-nbctl set nb_global . options:ic-route-learn-default=true
++check ovn_as az2 ovn-nbctl set nb_global . options:ic-route-learn-default=true
+ OVS_WAIT_UNTIL([ovn_as az2 ovn-nbctl --route-table=rtb1 lr-route-list lr2 | grep learned | grep 0.0.0.0])
+
+ # Test directly connected subnet route advertising. Route should go to <main> route table.
+-ovn_as az1 ovn-nbctl lrp-add lr1 lrp-lr1-ls1 aa:aa:aa:aa:bb:01 "192.168.0.1/24"
++check ovn_as az1 ovn-nbctl lrp-add lr1 lrp-lr1-ls1 aa:aa:aa:aa:bb:01 "192.168.0.1/24"
+ OVS_WAIT_UNTIL([ovn-ic-sbctl find route ip_prefix="192.168.0.1/24" route_table="\"\"" | grep 192.168.0.1/24])
+ OVS_WAIT_UNTIL([ovn_as az2 ovn-nbctl lr-route-list lr2 | grep learned | grep 192.168])
+ AT_CHECK([ovn_as az2 ovn-nbctl lr-route-list lr2], [0], [dnl
+@@ -1217,12 +1218,12 @@ Route Table rtb1:
+
+ # Delete the directly connected subnet from AZ1, learned route should be
+ # removed from AZ2.
+-ovn_as az1 ovn-nbctl lrp-del lrp-lr1-ls1
++check ovn_as az1 ovn-nbctl lrp-del lrp-lr1-ls1
+ OVS_WAIT_WHILE([ovn_as az2 ovn-nbctl lr-route-list lr2 | grep learned | grep 192.168])
+
+ # Test denylist routes
+ # Add back the directly connected 192.168 route.
+-ovn_as az1 ovn-nbctl lrp-add lr1 lrp-lr1-ls1 aa:aa:aa:aa:bb:01 "192.168.0.1/24"
++check ovn_as az1 ovn-nbctl lrp-add lr1 lrp-lr1-ls1 aa:aa:aa:aa:bb:01 "192.168.0.1/24"
+ OVS_WAIT_UNTIL([ovn_as az2 ovn-nbctl lr-route-list lr2 | grep learned | grep 192.168])
+ # Now add 10.11.0.0/16 and 192.168.0.0/16 to denylist in AZ2.
+ check ovn_as az2 ovn-nbctl set nb_global . options:ic-route-denylist="10.11.0.0/16,192.168.0.0/16"
+@@ -1319,13 +1320,13 @@ for i in 1 2; do
+ done
+
+ # Create directly-connected and static routes in VPC1
+-ovn_as az2 ovn-nbctl lrp-add lr12 lrp-lr12 aa:aa:aa:aa:bb:01 "192.168.0.1/24"
+-ovn_as az2 ovn-nbctl --route-table=rtb1 lr-route-add lr12 10.10.10.0/24 192.168.0.10
+-ovn_as az2 ovn-nbctl --route-table=rtb2 lr-route-add lr12 10.10.10.0/24 192.168.0.11
+-ovn_as az2 ovn-nbctl --route-table=rtb3 lr-route-add lr12 10.10.10.0/24 192.168.0.12
++check ovn_as az2 ovn-nbctl lrp-add lr12 lrp-lr12 aa:aa:aa:aa:bb:01 "192.168.0.1/24"
++check ovn_as az2 ovn-nbctl --route-table=rtb1 lr-route-add lr12 10.10.10.0/24 192.168.0.10
++check ovn_as az2 ovn-nbctl --route-table=rtb2 lr-route-add lr12 10.10.10.0/24 192.168.0.11
++check ovn_as az2 ovn-nbctl --route-table=rtb3 lr-route-add lr12 10.10.10.0/24 192.168.0.12
+
+ # Create directly-connected route in VPC2
+-ovn_as az2 ovn-nbctl --wait=sb lrp-add lr22 lrp-lr22 aa:aa:aa:aa:bb:01 "192.168.0.1/24"
++check ovn_as az2 ovn-nbctl --wait=sb lrp-add lr22 lrp-lr22 aa:aa:aa:aa:bb:01 "192.168.0.1/24"
+ check ovn-ic-nbctl --wait=sb sync
+ check ovn-ic-nbctl --wait=sb sync
+ # Test direct routes from lr12 were learned to lr11
+@@ -1440,13 +1441,13 @@ for i in 1 2; do
+ done
+
+ # Create directly-connected and static routes in VPC1
+-ovn_as az2 ovn-nbctl lrp-add lr12 lrp-lr12 aa:aa:aa:aa:bb:01 "2001:db8:200::1/64"
+-ovn_as az2 ovn-nbctl --route-table=rtb1 lr-route-add lr12 2001:db8:aaaa::/64 2001:db8:200::10
+-ovn_as az2 ovn-nbctl --route-table=rtb2 lr-route-add lr12 2001:db8:aaaa::/64 2001:db8:200::11
+-ovn_as az2 ovn-nbctl --route-table=rtb3 lr-route-add lr12 2001:db8:aaaa::/64 2001:db8:200::12
++check ovn_as az2 ovn-nbctl lrp-add lr12 lrp-lr12 aa:aa:aa:aa:bb:01 "2001:db8:200::1/64"
++check ovn_as az2 ovn-nbctl --route-table=rtb1 lr-route-add lr12 2001:db8:aaaa::/64 2001:db8:200::10
++check ovn_as az2 ovn-nbctl --route-table=rtb2 lr-route-add lr12 2001:db8:aaaa::/64 2001:db8:200::11
++check ovn_as az2 ovn-nbctl --route-table=rtb3 lr-route-add lr12 2001:db8:aaaa::/64 2001:db8:200::12
+
+ # Create directly-connected route in VPC2
+-ovn_as az2 ovn-nbctl --wait=sb lrp-add lr22 lrp-lr22 aa:aa:aa:aa:bb:01 "2001:db8:200::1/64"
++check ovn_as az2 ovn-nbctl --wait=sb lrp-add lr22 lrp-lr22 aa:aa:aa:aa:bb:01 "2001:db8:200::1/64"
+
+ # Test direct routes from lr12 were learned to lr11
+ #
+@@ -1595,9 +1596,9 @@ for i in 1 2; do
+ done
+
+ # Create directly-connected routes
+-ovn_as az1 ovn-nbctl lrp-add lr11 lrp-lr11 aa:aa:aa:aa:bb:01 "192.168.0.1/24"
+-ovn_as az2 ovn-nbctl lrp-add lr21 lrp-lr21 aa:aa:aa:aa:bc:01 "192.168.1.1/24"
+-ovn_as az2 ovn-nbctl --wait=sb lrp-add lr22 lrp-lr22 aa:aa:aa:aa:bc:02 "192.168.2.1/24"
++check ovn_as az1 ovn-nbctl lrp-add lr11 lrp-lr11 aa:aa:aa:aa:bb:01 "192.168.0.1/24"
++check ovn_as az2 ovn-nbctl lrp-add lr21 lrp-lr21 aa:aa:aa:aa:bc:01 "192.168.1.1/24"
++check ovn_as az2 ovn-nbctl --wait=sb lrp-add lr22 lrp-lr22 aa:aa:aa:aa:bc:02 "192.168.2.1/24"
+
+ # Test direct routes from lr21 and lr22 were learned to lr11
+ check ovn-ic-nbctl --wait=sb sync
+@@ -2737,10 +2738,10 @@ for i in 1 2; do
+ done
+
+ # Create directly-connected route in VPC1
+-ovn_as az2 ovn-nbctl lrp-add lr12 lrp-lr12 aa:aa:aa:aa:bb:01 "192.168.0.1/24"
++check ovn_as az2 ovn-nbctl lrp-add lr12 lrp-lr12 aa:aa:aa:aa:bb:01 "192.168.0.1/24"
+
+ # Create directly-connected route in VPC2
+-ovn_as az2 ovn-nbctl --wait=sb lrp-add lr22 lrp-lr22 aa:aa:aa:aa:cc:01 "192.168.1.1/24"
++check ovn_as az2 ovn-nbctl --wait=sb lrp-add lr22 lrp-lr22 aa:aa:aa:aa:cc:01 "192.168.1.1/24"
+
+ check ovn-ic-nbctl --wait=sb sync
+
+@@ -2799,14 +2800,14 @@ OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr21 | grep 192.168 |
+ ])
+
+ # VPC1: Create a route tag for VPC1 mark its own routes via lrp-lr12
+-ovn_as az2 ovn-nbctl set logical_router_port lrp-lr12-tspeer options:ic-route-tag=vpc1
++check ovn_as az2 ovn-nbctl set logical_router_port lrp-lr12-tspeer options:ic-route-tag=vpc1
+
+ # Test advertised routes with the vpc1 route tag present
+ ovn-ic-sbctl list route
+ wait_row_count ic-sb:Route 1 ip_prefix=192.168.0.1/24 nexthop=169.254.103.12 origin=connected external_ids:ic-route-tag=vpc1
+
+ # Filter routes on VPC1 lrp-lr11-tspeer with the route tag vpc1 present
+-ovn_as az1 ovn-nbctl set logical_router_port lrp-lr11-tspeer options:ic-route-filter-tag=vpc1
++check ovn_as az1 ovn-nbctl set logical_router_port lrp-lr11-tspeer options:ic-route-filter-tag=vpc1
+
+ # Test remaining direct routes from lr12 were learned to lr11
+ # Routes from the 169.254.101.2 nexthop have been filtered
+@@ -2818,7 +2819,7 @@ OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep 192.168 |
+ ])
+
+ # Remove the route tag used for advertisement
+-ovn_as az2 ovn-nbctl remove logical_router_port lrp-lr12-tspeer options ic-route-tag=vpc1
++check ovn_as az2 ovn-nbctl remove logical_router_port lrp-lr12-tspeer options ic-route-tag=vpc1
+
+ # Test advertised routes with the vpc1 route tag present
+ ovn-ic-sbctl list route
+@@ -2834,8 +2835,8 @@ OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep 192.168 |
+ ])
+
+ # Remove route tag filter and add route tag on lrp-lr12-tspeer advertised routes
+-ovn_as az1 ovn-nbctl remove logical_router_port lrp-lr11-tspeer options ic-route-filter-tag=vpc1
+-ovn_as az2 ovn-nbctl set logical_router_port lrp-lr12-tspeer options:ic-route-tag=vpc1
++check ovn_as az1 ovn-nbctl remove logical_router_port lrp-lr11-tspeer options ic-route-filter-tag=vpc1
++check ovn_as az2 ovn-nbctl set logical_router_port lrp-lr12-tspeer options:ic-route-tag=vpc1
+
+ # Test if all the routes are learned because we no longer have the filter tag option enabled
+ OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep 192.168 |
+@@ -2854,7 +2855,7 @@ AT_CLEANUP
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([spine-leaf: 3 AZs, 3 HVs, 3 LSs, connected via transit spine switch])
+ AT_KEYWORDS([spine leaf])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+
+ ovn_init_ic_db
+
+@@ -2873,6 +2874,14 @@ ovn_as az1 check ovn-nbctl ls-add ls1
+ ovn_as az2 check ovn-nbctl ls-add ls2
+ ovn_as az3 check ovn-nbctl ls-add ls3
+
++# Wait for all azs to be registered and sync ts changes.
++OVS_WAIT_FOR_OUTPUT([ovn-ic-sbctl show], [0], [dnl
++availability-zone az1
++availability-zone az2
++availability-zone az3
++])
++check ovn-ic-nbctl --wait=sb sync
++
+ # Connect ls1 to spine.
+ ovn_as az1
+ check ovn-nbctl lsp-add spine spine-to-ls1
+@@ -3389,6 +3398,25 @@ OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep 192.168 |
+ 192.168.0.0/24 169.254.101.22
+ ])
+
++# Do the same but with ovn-ic handling both northd and isb change at the same time.
++echo az1/ic going to sleep
++AT_CHECK([kill -STOP $(cat az1/ic/ovn-ic.pid)])
++on_exit "test -e az1/ic/ovn-ic.pid && kill -CONT $(cat az1/ic/ovn-ic.pid)"
++
++check ovn_as az1 ovn-nbctl --wait=sb set logical_router lr11 enable=false
++echo az1/ic going to wake up
++AT_CHECK([kill -CONT $(cat az1/ic/ovn-ic.pid)])
++
++OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep 192.168 |
++ grep learned | awk '{print $1, $2}' | sort ], [0], [dnl
++])
++
++check ovn_as az1 ovn-nbctl set logical_router lr11 enable=true
++OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep 192.168 |
++ grep learned | awk '{print $1, $2}' | sort ], [0], [dnl
++192.168.0.0/24 169.254.101.22
++])
++
+ OVN_CLEANUP_IC([az1], [az2])
+
+ AT_CLEANUP
+@@ -3410,6 +3438,13 @@ done
+ # Create new transit switches and LRs. Test topology is next:
+ # logical router (lr11) - transit switch (ts1) - logical router (lr12)
+
++# Wait for all azs to be registered and sync ts changes.
++OVS_WAIT_FOR_OUTPUT([ovn-ic-sbctl show], [0], [dnl
++availability-zone az1
++availability-zone az2
++])
++check ovn-ic-nbctl --wait=sb sync
++
+ # Create lr11, lr12 and connect them to ts1.
+ for i in 1 2; do
+ ovn_as az$i
+@@ -3774,8 +3809,12 @@ check ovn-nbctl set load_balancer az2_lb1 ip_port_mappings:3.3.3.1=lport3_az2:3.
+ check ovn-nbctl --wait=sb set load_balancer az2_lb1 ip_port_mappings:1.1.1.1=lport1_az1:1.1.1.9:az1
+
+ check_row_count sb:Service_Monitor 2
++# Sync IC DB (AZ2->ISB)
++check ovn-ic-nbctl --wait=sb sync
+ check_row_count ic-sb:Service_Monitor 3
+
++# Sync IC DB (ISB->AZ1)
++check ovn-ic-nbctl --wait=sb sync
+ ovn_as az1
+ check_row_count sb:Service_Monitor 3
+
+@@ -3811,6 +3850,7 @@ check ovn-nbctl lsp-add az3_ls1 lport6_az3
+ check ovn-nbctl --wait=sb lsp-add az3_ls1 lport7_az3
+
+ # Check there is one Service Monitor learned from az1.
++check ovn-ic-nbctl --wait=sb sync
+ check_row_count sb:Service_Monitor 1
+
+ check_column "5.5.5.1" sb:Service_Monitor ip logical_port=lport5_az3
+@@ -3841,6 +3881,10 @@ AT_CHECK([ovn-nbctl --wait=sb \
+ check ovn-nbctl set load_balancer az2_lb2 ip_port_mappings:2.2.2.1=lport2_az1:2.2.2.9:az1
+ check ovn-nbctl --wait=sb set load_balancer az2_lb2 ip_port_mappings:6.6.6.1=lport6_az3:6.6.6.9:az3
+
++# Sync IC DB (twice: one for AZ2->ISB and one for ISB->AZx)
++check ovn-ic-nbctl --wait=sb sync
++check ovn-ic-nbctl --wait=sb sync
++
+ ovn_as az1
+ check_row_count sb:Service_Monitor 4
+ check_column true sb:Service_Monitor ic_learned logical_port=lport2_az1
+@@ -3896,8 +3940,12 @@ AT_CHECK([ovn-sbctl lflow-list az1_ls1 | grep ls_in_lb | grep backends | ovn_str
+ ])
+
+ # Check the status in the interconet db.
++# Sync IC DB (AZ1->ISB)
++check ovn-ic-nbctl --wait=sb sync
+ check_column offline ic-sb:Service_Monitor status logical_port=lport1_az1
+
++# Sync IC DB (ISB->AZx)
++check ovn-ic-nbctl --wait=sb sync
+ ovn_as az2
+ check_column offline sb:Service_Monitor status logical_port=lport1_az1
+ AT_CHECK([ovn-sbctl lflow-list az2_ls1 | grep ls_in_lb | grep backends | ovn_strip_lflows], [0], [dnl
+@@ -3909,9 +3957,13 @@ AT_CHECK([ovn-sbctl lflow-list az2_ls1 | grep ls_in_lb | grep backends | ovn_str
+ ovn_as az3
+ svc_lport6_uuid=$(ovn-sbctl -d bare --no-headings --columns _uuid find Service_Monitor logical_port=lport6_az3)
+ check ovn-sbctl set Service_Monitor $svc_lport6_uuid status="offline"
++# Sync IC DB (AZ3->ISB)
++check ovn-ic-nbctl --wait=sb sync
+ check_column offline ic-sb:Service_Monitor status logical_port=lport6_az3
+
+ ovn_as az2
++# Sync IC DB (ISB->AZx)
++check ovn-ic-nbctl --wait=sb sync
+ check_column offline sb:Service_Monitor status logical_port=lport6_az3
+
+ ovn_as az2
+@@ -3920,6 +3972,8 @@ ovn-sbctl list service_m
+ svc_lport3_uuid=$(ovn-sbctl -d bare --no-headings --columns _uuid find Service_Monitor logical_port=lport3_az2)
+ check ovn-sbctl set Service_Monitor $svc_lport3_uuid status="offline"
+ check_column offline sb:Service_Monitor status logical_port=lport3_az2
++# Sync IC DB (AZ2->ISB)
++check ovn-ic-nbctl --wait=sb sync
+ check_column offline ic-sb:Service_Monitor status logical_port=lport3_az2
+
+ ovn_as az2
+@@ -3929,6 +3983,8 @@ ovn_as az1
+ svc_lport2_uuid=$(ovn-sbctl -d bare --no-headings --columns _uuid find Service_Monitor logical_port=lport2_az1)
+ check ovn-sbctl set Service_Monitor $svc_lport2_uuid status="offline"
+ check_column offline sb:Service_Monitor status logical_port=lport2_az1
++# Sync IC DB (AZ1->ISB)
++check ovn-ic-nbctl --wait=sb sync
+ check_column offline ic-sb:Service_Monitor status logical_port=lport2_az1
+
+ ovn_as az3
+@@ -3936,9 +3992,13 @@ svc_lport5_uuid=$(ovn-sbctl -d bare --no-headings --columns _uuid find Service_M
+ check ovn-sbctl set Service_Monitor $svc_lport5_uuid status="offline"
+
+ check_column offline sb:Service_Monitor status logical_port=lport5_az3
++# Sync IC DB (AZ3->ISB)
++check ovn-ic-nbctl --wait=sb sync
+ check_column offline ic-sb:Service_Monitor status logical_port=lport5_az3
+
+ ovn_as az1
++# Sync IC DB (ISB->AZx)
++check ovn-ic-nbctl --wait=sb sync
+ check_column offline sb:Service_Monitor status logical_port=lport5_az3
+
+ ovn_as az1
+@@ -3957,8 +4017,12 @@ check ovn-nbctl --wait=sb set load_balancer az1_lb1 ip_port_mappings:2.2.2.1=lpo
+ check_row_count sb:Service_Monitor 0 logical_port=lport3_az2
+
+ # Check deletion from ICSB.
++# Sync IC DB (AZ1->ISB)
++check ovn-ic-nbctl --wait=sb sync
+ check_row_count ic-sb:Service_Monitor 0 logical_port=lport3_az2
+
++# Sync IC DB (ISB->AZx)
++check ovn-ic-nbctl --wait=sb sync
+ ovn_as az3
+ svc_lport6_uuid=$(ovn-sbctl -d bare --no-headings --columns _uuid find Service_Monitor logical_port=lport6_az3)
+ check ovn-sbctl set Service_Monitor $svc_lport6_uuid status="online"
+@@ -3982,20 +4046,28 @@ svc_lport1_uuid=$(ovn-sbctl -d bare --no-headings --columns _uuid find Service_M
+ lport1_uuid=$(ovn-sbctl -d bare --no-headings --columns _uuid find Port_Binding logical_port=lport1_az1)
+ check ovn-sbctl set Port_Binding $lport1_uuid up=true
+ check ovn-sbctl set Service_Monitor $svc_lport1_uuid status="online"
++# Sync IC DB (AZ1->ISB)
++check ovn-ic-nbctl --wait=sb sync
+ check_column online ic-sb:Service_Monitor status logical_port=lport1_az1
+
+ svc_lport2_uuid=$(ovn-sbctl -d bare --no-headings --columns _uuid find Service_Monitor logical_port=lport2_az1)
+ lport2_uuid=$(ovn-sbctl -d bare --no-headings --columns _uuid find Port_Binding logical_port=lport2_az1)
+ check ovn-sbctl set Port_Binding $lport2_uuid up=true
+ check ovn-sbctl set Service_Monitor $svc_lport2_uuid status="online"
++# Sync IC DB (AZ1->ISB)
++check ovn-ic-nbctl --wait=sb sync
+ check_column online ic-sb:Service_Monitor status logical_port=lport2_az1
+
+ ovn_as az1
++check ovn-nbctl --wait=sb sync
+ AT_CHECK([ovn-sbctl lflow-list az1_ls1 | grep ls_in_lb | grep backends | ovn_strip_lflows], [0], [dnl
+ table=??(ls_in_lb ), priority=120 , match=(ct.new && ip4.dst == 10.10.10.1 && reg1[[16..23]] == 6 && reg1[[0..15]] == 80), action=(reg4 = 10.10.10.1; reg2[[0..15]] = 80; ct_lb_mark(backends=1.1.1.1:10880,2.2.2.1:10880);)
+ ])
+
+ ovn_as az2
++# Sync IC DB (ISB->AZx)
++check ovn-ic-nbctl --wait=sb sync
++check ovn-nbctl --wait=sb sync
+ AT_CHECK([ovn-sbctl lflow-list az2_ls1 | grep ls_in_lb | grep backends | ovn_strip_lflows], [0], [dnl
+ table=??(ls_in_lb ), priority=120 , match=(ct.new && ip4.dst == 20.20.20.1 && reg1[[16..23]] == 6 && reg1[[0..15]] == 80), action=(reg4 = 20.20.20.1; reg2[[0..15]] = 80; ct_lb_mark(backends=1.1.1.1:10880);)
+ table=??(ls_in_lb ), priority=120 , match=(ct.new && ip4.dst == 20.20.20.2 && reg1[[16..23]] == 6 && reg1[[0..15]] == 80), action=(reg4 = 20.20.20.2; reg2[[0..15]] = 80; ct_lb_mark(backends=2.2.2.1:10880,6.6.6.1:10880);)
+@@ -4014,6 +4086,8 @@ check ovn-nbctl --wait=sb lb-del az1_lb1
+ check_row_count sb:Service_Monitor 2
+ check_row_count sb:Service_Monitor 1 logical_port=lport1_az1
+ check_row_count sb:Service_Monitor 1 logical_port=lport2_az1
++# Sync IC DB (AZ1->ISB)
++check ovn-ic-nbctl --wait=sb sync
+ check_row_count ic-sb:Service_Monitor 3
+
+ ovn_as az2
+@@ -4022,6 +4096,10 @@ ovn_as az2
+ # and in az2 there will be two service monitor for 3.3.3.1 and 1.1.1.1
+ check ovn-nbctl --wait=sb lb-del az2_lb2
+
++# Sync IC DB (AZ2->ISB)
++check ovn-ic-nbctl --wait=sb sync
++# Sync IC DB (ISB->AZx)
++check ovn-ic-nbctl --wait=sb sync
+ ovn_as az1
+ # Check that the ic_learned records have been deleted
+ check_row_count sb:Service_Monitor 1
+@@ -4036,6 +4114,8 @@ check_row_count sb:Service_Monitor 0
+ ovn_as az2
+ check ovn-nbctl --wait=sb lb-del az2_lb1
+ check_row_count sb:Service_Monitor 0
++# Sync IC DB (AZ2->ISB)
++check ovn-ic-nbctl --wait=sb sync
+ check_row_count ic-sb:Service_Monitor 0
+
+ # We expect something around 20 Service_Monitor updates.
+@@ -4062,6 +4142,18 @@ check ovn-nbctl set nb_global . options:ic-route-learn=true
+ # Enable route advertising at AZ level
+ check ovn-nbctl set nb_global . options:ic-route-adv=true
+
++# Add ts before creating fake az, so we can use "ovn-ic-nbctl --wait=sb sync"
++# to ensure ts are properly added. Fake az does not run hence does not handle
++# ovn-ic-nbctl --wait=sb sync.
++check ovn-ic-nbctl ts-add ts1
++check ovn-ic-nbctl ts-add ts2
++
++# Wait for ts to be added in az1.
++OVS_WAIT_FOR_OUTPUT([ovn-ic-sbctl show | grep az1], [0], [dnl
++availability-zone az1
++])
++check ovn-ic-nbctl --wait=sb sync
++
+ # Create a fake availability zone to announce a route from.
+ # This route will not have an lr-id in its external_ids, simulating
+ # the behavior of the legacy code. The system is expected to
+@@ -4071,9 +4163,6 @@ fake_az_uuid=$(fetch_column ic-sb:availability-zone _uuid name="fake_az")
+ check_uuid ovn-ic-sbctl create Gateway name=fake_az_gw availability_zone=$fake_az_uuid \
+ encap=@encap -- --id=@encap create encap type=geneve ip="192.168.0.2"
+
+-check ovn-ic-nbctl ts-add ts1
+-check ovn-ic-nbctl ts-add ts2
+-
+ check ovn-nbctl lr-add lr1
+ check ovn-nbctl lrp-add lr1 lrp-lr1-ts1 aa:aa:aa:aa:aa:02 169.254.100.1/24
+ check ovn-nbctl lsp-add-router-port ts1 lsp-ts1-lr1 lrp-lr1-ts1
+@@ -4090,7 +4179,8 @@ AT_CHECK([ovn-ic-sbctl find Route nexthop="169.254.100.2" | grep external_ids],
+ external_ids : {}
+ ])
+
+-AT_CHECK([ovn-nbctl lr-route-list lr1], [0], [dnl
++# We cannot run "ovn-ic-nbctl --wait=sb sync" due to fake az.
++OVS_WAIT_FOR_OUTPUT([ovn-nbctl lr-route-list lr1], [0], [dnl
+ IPv4 Routes
+ Route Table <main>:
+ 172.31.0.0/24 169.254.100.2 dst-ip (learned)
+@@ -4111,6 +4201,13 @@ for i in 1 2; do
+ check ovn-nbctl set nb_global . options:ic-route-adv=true
+ done
+
++# Wait for all azs to be registered and sync ts changes.
++OVS_WAIT_FOR_OUTPUT([ovn-ic-sbctl show], [0], [dnl
++availability-zone az1
++availability-zone az2
++])
++check ovn-ic-nbctl --wait=sb sync
++
+ # Create routers and connect to transit switch
+ for i in 1 2; do
+ ovn_as az$i
+@@ -4299,20 +4396,29 @@ ovn_init_ic_db
+ ovn_start az1
+ ovn_start az2
+
+-check ovn-ic-nbctl --wait=sb sync
+-AT_CHECK([ovn-ic-sbctl show], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([ovn-ic-sbctl show], [0], [dnl
+ availability-zone az1
+ availability-zone az2
+ ])
++check ovn-ic-nbctl --wait=sb sync
+
+ ovn_as az1
+ check ovn-ic-nbctl tr-add tr0
+ wait_row_count Datapath_Binding 1
++
++# Make sure that ISB PB data are written in IC SB.
++check ovn-ic-nbctl --wait=sb sync
++# Then ensure ovn-northd did run.
++check ovn-nbctl --wait=sb sync
+ az1_tunnel_key=$(ovn_as az1 ovn-sbctl --bare --columns=tunnel_key find Datapath_Binding)
+ check_row_count nb:Logical_Router 1 options:requested-tnl-key="${az1_tunnel_key}"
+
+ ovn_as az2
+ wait_row_count Datapath_Binding 1
++# Make sure that ISB PB data are written in IC SB.
++check ovn-ic-nbctl --wait=sb sync
++# Then ensure ovn-northd did run.
++check ovn-nbctl --wait=sb sync
+ az2_tunnel_key=$(ovn_as az2 ovn-sbctl --bare --columns=tunnel_key find Datapath_Binding)
+ check_row_count nb:Logical_Router 1 options:requested-tnl-key="${az2_tunnel_key}"
+ check test "${az1_tunnel_key}"=="${az2_tunnel_key}"
+@@ -4332,13 +4438,11 @@ net_add n2
+ ovn_start az1
+ ovn_start az2
+
+-OVS_WAIT_UNTIL([test 2 = `ovn-ic-sbctl show | wc -l`])
+-
+-check ovn-ic-nbctl --wait=sb sync
+-AT_CHECK([ovn-ic-sbctl show], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([ovn-ic-sbctl show], [0], [dnl
+ availability-zone az1
+ availability-zone az2
+ ])
++check ovn-ic-nbctl --wait=sb sync
+
+ sim_add hv1
+ as hv1
+@@ -4390,11 +4494,11 @@ net_add n2
+ ovn_start az1
+ ovn_start az2
+
+-check ovn-ic-nbctl --wait=sb sync
+-AT_CHECK([ovn-ic-sbctl show], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([ovn-ic-sbctl show], [0], [dnl
+ availability-zone az1
+ availability-zone az2
+ ])
++check ovn-ic-nbctl --wait=sb sync
+
+ sim_add hv1
+ as hv1
+@@ -4442,11 +4546,11 @@ net_add n2
+ ovn_start az1
+ ovn_start az2
+
+-check ovn-ic-nbctl --wait=sb sync
+-AT_CHECK([ovn-ic-sbctl show], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([ovn-ic-sbctl show], [0], [dnl
+ availability-zone az1
+ availability-zone az2
+ ])
++check ovn-ic-nbctl --wait=sb sync
+
+ sim_add hv1
+ as hv1
+@@ -4499,13 +4603,11 @@ net_add n2
+ ovn_start az1
+ ovn_start az2
+
+-OVS_WAIT_UNTIL([test 2 = `ovn-ic-sbctl show | wc -l`])
+-
+-check ovn-ic-nbctl --wait=sb sync
+-AT_CHECK([ovn-ic-sbctl show], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([ovn-ic-sbctl show], [0], [dnl
+ availability-zone az1
+ availability-zone az2
+ ])
++check ovn-ic-nbctl --wait=sb sync
+
+ sim_add hv1
+ as hv1
+@@ -4561,6 +4663,13 @@ for i in 1 2; do
+ check ovn-nbctl set nb_global . options:ic-route-adv=true
+ done
+
++# Wait for all azs to be registered and sync ts changes.
++OVS_WAIT_FOR_OUTPUT([ovn-ic-sbctl show], [0], [dnl
++availability-zone az1
++availability-zone az2
++])
++check ovn-ic-nbctl --wait=sb sync
++
+ # Create new transit switches and LRs. Test topology is next:
+ #
+ # logical router (lr11) - transit switch (ts11) - logical router (lr12)
+@@ -4591,7 +4700,98 @@ OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep 192.168 |
+ ])
+
+ OVN_CLEANUP_IC([az1], [az2])
++AT_CLEANUP
++])
++
++OVN_FOR_EACH_NORTHD([
++AT_SETUP([interconnection - LLA])
++AT_KEYWORDS([LLA])
++# Logical network:
++#┌──────────────────────────────────────────────────┬────────────────────────────────────────────────────┐
++#│ AZ1 │ AZ2 │
++#├──────────────────────────────────────────────────┼────────────────────────────────────────────────────┤
++#│ LS11-LR11 (fd11::254) - LR11 - (2001:db8::1) ── TS1 ── (2001:db8::2) - LR12 - (fd12::254) - LR12-LS12 │
++#│ LS21-LR21 (fd21::254) - LR21 - (fe80:10::1) ─────┼──── (fe80:10::2) - LR22 - (fd22::254) - LR22-LS22 │
++#└──────────────────────────────────────────────────┴────────────────────────────────────────────────────┘
++# LR11/LR12: trigger nexthop filtering: they have a global IPv6 address on the TS port that gets removed,
++# causing their stub subnet (fd1x::/64), static route (fd99::/64), and lb vip (fd31::100)
++# to have no valid nexthop and not be advertised to IC-SB.
++# LR21/LR22: verify non-EUI-64 LLA nexthops (fe80:10::x) work correctly; their subnets (fd2x::/64) are advertised and learned.
++
++ovn_init_ic_db
++net_add n1
++
++for i in 1 2; do
++ ovn_start az$i
++ sim_add hv$i
++ as hv$i
++ check ovs-vsctl add-br br-phys
++ ovn_az_attach az$i n1 br-phys 192.168.$i.1 16
++ ovn_as az$i
++ check ovs-vsctl set open . external-ids:ovn-is-interconn=true
++ check ovn-nbctl set nb_global . options:ic-route-learn=true
++ check ovn-nbctl set nb_global . options:ic-route-learn-lb=true
++ check ovn-nbctl set nb_global . options:ic-route-adv=true
++ check ovn-nbctl set nb_global . options:ic-route-adv-lb=true
++done
++
++check ovn-ic-nbctl ts-add ts1
++check ovn_as az1 ovn-nbctl wait-until logical_switch ts1
++check ovn_as az2 ovn-nbctl wait-until logical_switch ts1
++
++for i in 1 2; do
++ ovn_as az$i
++ check ovn-nbctl lr-add lr1$i \
++ -- lrp-add lr1$i lr1$i-ts1 00:aa:aa:aa:aa:1$i 2001:db8::$i/64 \
++ -- lrp-add lr1$i lr1$i-ls1$i 00:00:00:00:0$i:fe fd1$i::254/64 \
++ -- lsp-add-router-port ts1 ts1-lr1$i lr1$i-ts1
++ check ovn-nbctl lr-add lr2$i \
++ -- lrp-add lr2$i lr2$i-ts1 aa:aa:aa:aa:aa:2$i fe80:10::$i/64 \
++ -- lsp-add-router-port ts1 ts1-lr2$i lr2$i-ts1
++
++ check ovn-nbctl --wait=sb lrp-add lr2$i lr2$i-ls2$i 00:00:00:00:0$i:0$i fd2$i::254/64
++
++done
++
++wait_for_ports_up
++check ovn-ic-nbctl --wait=sb sync
++check ovn_as az1 ovn-nbctl --wait=hv sync
++check ovn_as az2 ovn-nbctl --wait=hv sync
++
++# Non-EUI-64 LLA nexthops must be advertised to IC-SB and learned normally.
++OVS_WAIT_UNTIL([ovn_as az1 ovn-nbctl lr-route-list lr21 | grep learned | \
++ grep fd22::])
++AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr21 | awk '/learned/{print $1, $2}'], [0], [dnl
++fd22::/64 fe80:10::2
++])
++OVS_WAIT_UNTIL([ovn_as az2 ovn-nbctl lr-route-list lr22 | grep learned | \
++ grep fd21::])
++AT_CHECK([ovn_as az2 ovn-nbctl lr-route-list lr22 | awk '/learned/{print $1, $2}'], [0], [dnl
++fd21::/64 fe80:10::1
++])
++
++check ovn_as az1 ovn-nbctl remove logical_router_port lr11-ts1 networks "2001\:db8\:\:1/64"
++
++check ovn_as az1 ovn-nbctl lr-route-add lr11 fd99::/64 2001:db8::2
++
++check ovn_as az1 ovn-nbctl lb-add lb_v6 [[fd31::100]]:80 "[[fd31::1]]:80"
++check ovn_as az1 ovn-nbctl lr-lb-add lr11 lb_v6
++
++check ovn-ic-nbctl --wait=sb sync
+
++# ic-sb route should not contain lla next hops.
++# Such routes used to be created and deleted in ic-sb.
++# Do not use OVS_WAIT_WHILE or WAIT_UNTIL as the wrong (lla) route appears and disappears.
++for i in $(seq 1 50); do
++ AT_CHECK([ovn-ic-sbctl list route | grep "fe80:" | sort], [0], [dnl
++nexthop : "fe80:10::1"
++nexthop : "fe80:10::2"
++])
++done
++
++OVN_CLEANUP_SBOX([hv1])
++OVN_CLEANUP_SBOX([hv2])
++OVN_CLEANUP_IC([az1], [az2])
+ AT_CLEANUP
+ ])
+
+@@ -4612,6 +4812,13 @@ for i in 1 2; do
+ check ovn-nbctl set nb_global . options:ic-route-adv=true
+ done
+
++# Wait for all azs to be registered and sync ts changes.
++OVS_WAIT_FOR_OUTPUT([ovn-ic-sbctl show], [0], [dnl
++availability-zone az1
++availability-zone az2
++])
++check ovn-ic-nbctl --wait=sb sync
++
+ for i in 1 2; do
+ ovn_as az$i
+
+diff --git a/tests/ovn-inc-proc-graph-dump.at b/tests/ovn-inc-proc-graph-dump.at
+index ef91014a64..c9a434b615 100644
+--- a/tests/ovn-inc-proc-graph-dump.at
++++ b/tests/ovn-inc-proc-graph-dump.at
+@@ -394,6 +394,7 @@ digraph "Incremental-Processing-Engine" {
+ runtime_data -> neighbor [[label="neighbor_runtime_data_handler"]];
+ SB_datapath_binding -> neighbor [[label="neighbor_sb_datapath_binding_handler"]];
+ SB_port_binding -> neighbor [[label="neighbor_sb_port_binding_handler"]];
++ SB_fdb -> neighbor [[label="neighbor_sb_fdb_handler"]];
+ host_if_monitor [[style=filled, shape=box, fillcolor=white, label="host_if_monitor"]];
+ neighbor_table_notify [[style=filled, shape=box, fillcolor=white, label="neighbor_table_notify"]];
+ neighbor_exchange_status [[style=filled, shape=box, fillcolor=white, label="neighbor_exchange_status"]];
+@@ -412,9 +413,6 @@ digraph "Incremental-Processing-Engine" {
+ evpn_fdb [[style=filled, shape=box, fillcolor=white, label="evpn_fdb"]];
+ neighbor_exchange -> evpn_fdb [[label=""]];
+ evpn_vtep_binding -> evpn_fdb [[label="evpn_fdb_vtep_binding_handler"]];
+- evpn_arp [[style=filled, shape=box, fillcolor=white, label="evpn_arp"]];
+- neighbor_exchange -> evpn_arp [[label=""]];
+- evpn_vtep_binding -> evpn_arp [[label="evpn_arp_vtep_binding_handler"]];
+ pflow_output [[style=filled, shape=box, fillcolor=white, label="pflow_output"]];
+ non_vif_data -> pflow_output [[label=""]];
+ northd_options -> pflow_output [[label=""]];
+@@ -434,7 +432,6 @@ digraph "Incremental-Processing-Engine" {
+ SB_sb_global -> pflow_output [[label="pflow_output_debug_handler"]];
+ evpn_vtep_binding -> pflow_output [[label="pflow_output_evpn_binding_handler"]];
+ evpn_fdb -> pflow_output [[label="pflow_output_fdb_handler"]];
+- evpn_arp -> pflow_output [[label="pflow_output_arp_handler"]];
+ mac_cache [[style=filled, shape=box, fillcolor=white, label="mac_cache"]];
+ runtime_data -> mac_cache [[label="mac_cache_runtime_data_handler"]];
+ SB_mac_binding -> mac_cache [[label="mac_cache_sb_mac_binding_handler"]];
+@@ -458,6 +455,8 @@ digraph "Incremental-Processing-Engine" {
+ route_table_notify [[style=filled, shape=box, fillcolor=white, label="route_table_notify"]];
+ route_exchange_status [[style=filled, shape=box, fillcolor=white, label="route_exchange_status"]];
+ route_exchange [[style=filled, shape=box, fillcolor=white, label="route_exchange"]];
++ OVS_open_vswitch -> route_exchange [[label=""]];
++ SB_chassis -> route_exchange [[label=""]];
+ route -> route_exchange [[label=""]];
+ SB_learned_route -> route_exchange [[label="engine_noop_handler"]];
+ SB_port_binding -> route_exchange [[label="engine_noop_handler"]];
+@@ -471,6 +470,18 @@ digraph "Incremental-Processing-Engine" {
+ SB_datapath_binding -> garp_rarp [[label="garp_rarp_sb_datapath_binding_handler"]];
+ SB_mac_binding -> garp_rarp [[label="engine_noop_handler"]];
+ runtime_data -> garp_rarp [[label="garp_rarp_runtime_data_handler"]];
++ evpn_mac_binding_sync_waker [[style=filled, shape=box, fillcolor=white, label="evpn_mac_binding_sync_waker"]];
++ evpn_arp [[style=filled, shape=box, fillcolor=white, label="evpn_arp"]];
++ neighbor_exchange -> evpn_arp [[label=""]];
++ evpn_vtep_binding -> evpn_arp [[label="evpn_arp_vtep_binding_handler"]];
++ evpn_mac_binding_sync [[style=filled, shape=box, fillcolor=white, label="evpn_mac_binding_sync"]];
++ evpn_mac_binding_sync_waker -> evpn_mac_binding_sync [[label=""]];
++ evpn_arp -> evpn_mac_binding_sync [[label=""]];
++ SB_mac_binding -> evpn_mac_binding_sync [[label="engine_noop_handler"]];
++ runtime_data -> evpn_mac_binding_sync [[label="engine_noop_handler"]];
++ mac_cache -> evpn_mac_binding_sync [[label="engine_noop_handler"]];
++ sb_ro -> evpn_mac_binding_sync [[label="evpn_mac_binding_sync_sb_ro_handler"]];
++ SB_port_binding -> evpn_mac_binding_sync [[label="evpn_mac_binding_sync_sb_port_binding_handler"]];
+ SB_acl_id [[style=filled, shape=box, fillcolor=white, label="SB_acl_id"]];
+ acl_id [[style=filled, shape=box, fillcolor=white, label="acl_id"]];
+ SB_acl_id -> acl_id [[label=""]];
+@@ -482,6 +493,7 @@ digraph "Incremental-Processing-Engine" {
+ bfd_chassis -> controller_output [[label="controller_output_bfd_chassis_handler"]];
+ route_exchange -> controller_output [[label="controller_output_route_exchange_handler"]];
+ garp_rarp -> controller_output [[label="controller_output_garp_rarp_handler"]];
++ evpn_mac_binding_sync -> controller_output [[label="controller_output_evpn_mac_binding_sync_handler"]];
+ acl_id -> controller_output [[label="controller_output_acl_id_handler"]];
+ }
+ ])
+diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at
+index cc41bbd77b..b92494de5d 100644
+--- a/tests/ovn-macros.at
++++ b/tests/ovn-macros.at
+@@ -274,7 +274,11 @@ m4_define([CHECK_FLOWS_AFTER_RECOMPUTE], [
+ check ovn-nbctl --wait=hv sync
+ fi
+ DUMP_FLOWS([$sbox], [flows-$hv-after])
+- diff flows-$hv-before flows-$hv-after > flow-diff
++ # Avoid learned flows i.e. flows with idle_timeout and no learn.
++ diff flows-$hv-before flows-$hv-after |
++ grep "^[<>]" |
++ awk '(!/idle_timeout=/ && !/hard_timeout=/) || /learn/' > flow-diff
++
+ AT_CHECK([wc -l < flow-diff], [0], [0
+ ])
+ fi
+@@ -354,7 +358,10 @@ m4_define([CHECK_AFTER_RECOMPUTE], [
+ ])
+
+ # Compare and store flow differences before and after recompute
+- diff -u flows-$hv-before flows-$hv-after | grep "^+ \|^- " > flow-diff-$hv
++ # Avoid learned flows i.e. flows with idle_timeout and no learn.
++ diff -u flows-$hv-before flows-$hv-after |
++ grep "^+ \|^- " |
++ awk '(!/idle_timeout=/ && !/hard_timeout=/) || /learn/' > flow-diff-$hv
+ if [[ -n "$related_ports" ]]; then
+ tag=$(ovn-sbctl --bare --columns tag list port_binding $related_ports)
+ if [[ -n "$tag" ]]; then
+@@ -536,6 +543,11 @@ m4_define([OVN_CLEANUP_IC],[
+ fi
+ ])
+
++m4_define([CHECK_SCAPY],[
++ AT_SKIP_IF([test $HAVE_SCAPY = no])
++ start_scapy_server
++])
++
+ m4_divert_push([PREPARE_TESTS])
+
+ # ovn_init_db DATABASE [AZ]
+@@ -1294,7 +1306,6 @@ hex_to_mac() {
+ }
+
+ send_garp() {
+- AT_SKIP_IF([test $HAVE_SCAPY = no])
+ local hv=$1 inport=$2 op=$3 eth_src=$4 eth_dst=$5 spa=$6 tpa=$7
+
+ local packet=$(fmt_pkt "Ether(dst='${eth_dst}', src='${eth_src}')/ \
+@@ -1380,10 +1391,6 @@ ovn_trace_client() {
+ #
+ fmt_pkt() {
+ ctlfile=$ovs_base/scapy.ctl
+- if [[ ! -S $ctlfile ]]; then
+- start_scapy_server
+- fi
+- while [[ ! -S $ctlfile ]]; do sleep 0.1; done
+ ovs-appctl -t $ctlfile payload "$1"
+ }
+
+@@ -1396,6 +1403,7 @@ start_scapy_server() {
+ flock -n $lockfile "$top_srcdir"/tests/scapy-server.py \
+ --pidfile=$pidfile --unixctl=$ctlfile --log-file=$logfile --detach \
+ && on_exit "test -e \"$pidfile\" && ovs-appctl -t $ctlfile exit"
++ OVS_WAIT_UNTIL([test -S "$ctlfile" && ovs-appctl -t "$ctlfile" version])
+ }
+
+ sleep_northd() {
+@@ -1570,7 +1578,6 @@ m4_define([TAG_TEST_NOT_UPGRADABLE], [
+
+ m4_define([OVN_CHECK_SCAPY_EDNS_CLIENT_SUBNET_SUPPORT],
+ [
+- AT_SKIP_IF([test $HAVE_SCAPY = no])
+ AT_SKIP_IF([! echo "from scapy.layers.dns import EDNS0ClientSubnet" | python 2>&1 > /dev/null])
+ ])
+
+diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
+index e31daa1e99..09d4801331 100644
+--- a/tests/ovn-northd.at
++++ b/tests/ovn-northd.at
+@@ -12,6 +12,7 @@ m4_define([_DUMP_DB_TABLES], [
+ ovn-sbctl list meter >> $1
+ ovn-sbctl list meter_band >> $1
+ ovn-sbctl list port_group >> $1
++ ovn-sbctl list multicast_group >> $1
+ ovn-sbctl list bfd >> $1
+ ovn-sbctl dump-flows > lflows_$1
+ ])
+@@ -858,7 +859,13 @@ Status: paused
+ AS_BOX([Verify that ovn-northd is paused])
+ # Now ovn-northd won't respond by adding a datapath, because it's paused.
+ check ovn-nbctl ls-add sw0
++cnt1=$(as northd ovn-appctl -t ovn-northd coverage/read-counter poll_zero_timeout)
+ check sleep 5
++cnt2=$(as northd ovn-appctl -t ovn-northd coverage/read-counter poll_zero_timeout)
++delta=$((cnt2 - cnt1))
++echo "$cnt1 poll_zero_timeout before sleep(5), and $cnt2 after"
++AT_CHECK([test $((cnt2 - cnt1)) -lt 100])
++
+ check_row_count Datapath_Binding 0
+
+ # Do not resume both main and backup right after each other
+@@ -1682,122 +1689,6 @@ OVN_CLEANUP_NORTHD
+ AT_CLEANUP
+ ])
+
+-OVN_FOR_EACH_NORTHD_NO_HV_PARALLELIZATION([
+-AT_SETUP([Load balancer health check reply lflow for type=external backend on localnet LS])
+-ovn_start
+-
+-# Topology:
+-#
+-# lr0 --(lr0-sw0)-- sw0 (regular tenant LS, 10.0.0.0/24)
+-# `-- vm-port (type="", regular VIF backend)
+-#
+-# lr0 --(lr0-prov)-- prov (provider LS with localnet)
+-# |-- prov-localnet (type=localnet)
+-# `-- bm-port (type=external, baremetal pool member)
+-#
+-# A baremetal LB pool member's LSP is type=external; replies to HM probes
+-# re-enter br-int via the localnet port, so MFF_LOG_INPORT carries the
+-# localnet LSP's tunnel_key and the original
+-# "inport == <bm-port> && ... ; handle_svc_check(inport);"
+-# reply lflow never matches. pinctrl_find_svc_monitor() is keyed on
+-# (dp_key, port_key) where port_key = backend LSP's tunnel_key, so
+-# MFF_LOG_INPORT must hold that tunnel_key when the controller op fires.
+-#
+-# Fix: install an inport-rewrite lflow at S_SWITCH_IN_CHECK_PORT_SEC
+-# priority 75 keyed on (inport == <localnet> && eth.src == <bm_mac>)
+-# which assigns:
+-# flags.localnet = 1; inport = "<bm-port>"; next;
+-# Once that fires, every downstream stage (including the original
+-# per-backend handle_svc_check lflow at S_SWITCH_IN_L2_LKUP and the
+-# generic per-LS svc_monitor_mac lflow) sees inport == <bm-port> and
+-# works without further modification.
+-
+-check ovn-nbctl lr-add lr0
+-check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:01:01 10.0.0.1/24
+-check ovn-nbctl lrp-add lr0 lr0-prov 00:00:00:00:02:01 10.0.50.1/24
+-
+-check ovn-nbctl ls-add sw0
+-check ovn-nbctl --wait=sb lsp-add sw0 sw0-lr0 \
+- -- lsp-set-type sw0-lr0 router \
+- -- lsp-set-options sw0-lr0 router-port=lr0-sw0 \
+- -- lsp-set-addresses sw0-lr0 router
+-check ovn-nbctl --wait=sb lsp-add sw0 vm-port \
+- -- lsp-set-addresses vm-port "00:00:00:00:01:02 10.0.0.10"
+-
+-check ovn-nbctl ls-add prov
+-check ovn-nbctl --wait=sb lsp-add prov prov-lr0 \
+- -- lsp-set-type prov-lr0 router \
+- -- lsp-set-options prov-lr0 router-port=lr0-prov \
+- -- lsp-set-addresses prov-lr0 router
+-check ovn-nbctl --wait=sb lsp-add prov prov-localnet \
+- -- lsp-set-type prov-localnet localnet \
+- -- lsp-set-options prov-localnet network_name=physnet1 \
+- -- lsp-set-addresses prov-localnet unknown
+-check ovn-nbctl --wait=sb lsp-add prov bm-port \
+- -- lsp-set-type bm-port external \
+- -- lsp-set-addresses bm-port "00:00:00:00:02:0a 10.0.50.10"
+-
+-check ovn-sbctl chassis-add hv1 geneve 127.0.0.1
+-check ovn-sbctl lsp-bind vm-port hv1
+-check ovn-sbctl lsp-bind bm-port hv1
+-
+-# LB has both a regular-VIF backend on sw0 and a type=external backend on prov.
+-check ovn-nbctl lb-add lb1 192.168.0.10:80 10.0.0.10:80,10.0.50.10:80 tcp
+-check ovn-nbctl --wait=sb set load_balancer lb1 \
+- ip_port_mappings:10.0.0.10=vm-port:10.0.0.1
+-check ovn-nbctl --wait=sb set load_balancer lb1 \
+- ip_port_mappings:10.0.50.10=bm-port:10.0.50.1
+-
+-check_uuid ovn-nbctl --wait=sb -- --id=@hc create Load_Balancer_Health_Check \
+- vip="192.168.0.10\:80" -- add Load_Balancer lb1 health_check @hc
+-
+-check ovn-nbctl lr-lb-add lr0 lb1
+-check ovn-nbctl ls-lb-add sw0 lb1
+-check ovn-nbctl ls-lb-add prov lb1
+-check ovn-nbctl --wait=sb sync
+-
+-# Regular backend on sw0: original "inport == <vm-port>" / "handle_svc_check(inport);"
+-# behavior unchanged.
+-AT_CAPTURE_FILE([sw0_lflows])
+-ovn-sbctl dump-flows sw0 | grep ls_in_l2_lkup | grep handle_svc_check \
+- > sw0_lflows
+-AT_CHECK([cat sw0_lflows | ovn_strip_lflows], [0], [dnl
+- table=??(ls_in_l2_lkup ), priority=110 , match=(eth.dst == $svc_monitor_mac && (tcp || icmp || icmp6)), action=(handle_svc_check(inport);)
+- table=??(ls_in_l2_lkup ), priority=110 , match=(inport == "vm-port" && ip4.dst == 10.0.0.1 && ip4.src == 10.0.0.10 && eth.dst == 00:00:00:00:01:01 && tcp.src == 80), action=(handle_svc_check(inport);)
+-])
+-
+-# type=external backend on prov (localnet LS): the per-backend reply
+-# lflow keeps the ORIGINAL inport-based match because the
+-# inport-rewrite at S_SWITCH_IN_CHECK_PORT_SEC has already substituted
+-# MFF_LOG_INPORT to <bm-port> by the time the packet reaches L2_LKUP.
+-AT_CAPTURE_FILE([prov_lflows])
+-ovn-sbctl dump-flows prov | grep ls_in_l2_lkup | grep handle_svc_check \
+- > prov_lflows
+-AT_CHECK([cat prov_lflows | ovn_strip_lflows], [0], [dnl
+- table=??(ls_in_l2_lkup ), priority=110 , match=(eth.dst == $svc_monitor_mac && (tcp || icmp || icmp6)), action=(handle_svc_check(inport);)
+- table=??(ls_in_l2_lkup ), priority=110 , match=(inport == "bm-port" && ip4.dst == 10.0.50.1 && ip4.src == 10.0.50.10 && eth.dst == 00:00:00:00:02:01 && tcp.src == 80), action=(handle_svc_check(inport);)
+-])
+-
+-# v6 inport-rewrite lflow at ls_in_check_port_sec priority 75: turns
+-# (inport == <prov-localnet> && eth.src == <bm_mac>) into
+-# (flags.localnet = 1; inport = "<bm-port>"; next;)
+-AT_CAPTURE_FILE([prov_rewrite_lflows])
+-ovn-sbctl dump-flows prov | grep ls_in_check_port_sec \
+- | grep 'priority=75 ' | grep 'inport = ' \
+- > prov_rewrite_lflows
+-AT_CHECK([cat prov_rewrite_lflows | ovn_strip_lflows], [0], [dnl
+- table=??(ls_in_check_port_sec), priority=75 , match=(inport == "prov-localnet" && eth.src == 00:00:00:00:02:0a), action=(flags.localnet = 1; inport = "bm-port"; next;)
+-])
+-
+-# Delete the localnet port and verify the inport-rewrite lflow disappears.
+-check ovn-nbctl --wait=sb lsp-del prov-localnet
+-AT_CHECK([ovn-sbctl dump-flows prov | grep ls_in_check_port_sec \
+- | grep 'priority=75 ' | grep 'inport = '], [1])
+-
+-OVN_CLEANUP_NORTHD
+-AT_CLEANUP
+-])
+-
+ OVN_FOR_EACH_NORTHD_NO_HV([
+ AT_SETUP([Load balancer VIP in NAT entries])
+ AT_SKIP_IF([test $HAVE_PYTHON = no])
+@@ -1874,6 +1765,48 @@ OVN_CLEANUP_NORTHD
+ AT_CLEANUP
+ ])
+
++OVN_FOR_EACH_NORTHD_NO_HV([
++AT_SETUP([Load balancer VIP in force-SNAT addresses])
++ovn_start
++
++check ovn-nbctl ls-add public
++check ovn-nbctl lr-add lr0
++check ovn-nbctl set logical_router lr0 options:chassis=ch1
++check ovn-nbctl lrp-add lr0 lr0-public 00:00:00:00:00:01 \
++ 192.0.2.1/24
++check ovn-nbctl lsp-add-router-port public public-lr0 lr0-public
++
++check ovn-nbctl lb-add lb0 192.0.2.1:30663 198.51.100.10:5201
++check ovn-nbctl lb-add lb1 192.0.2.1 198.51.100.11
++check ovn-nbctl lr-lb-add lr0 lb0
++check ovn-nbctl lr-lb-add lr0 lb1
++check ovn-nbctl lr-nat-add lr0 snat 169.254.0.47 198.51.100.0/24
++check ovn-nbctl --wait=sb sync
++
++ovn-sbctl dump-flows lr0 > sbflows
++AT_CAPTURE_FILE([sbflows])
++AT_CHECK([grep "lr_in_unsnat.*priority=120" sbflows], [1])
++
++check ovn-nbctl --wait=sb set logical_router lr0 \
++ options:lb_force_snat_ip=192.0.2.1
++
++AT_CHECK([ovn-sbctl dump-flows lr0 | \
++ grep "lr_in_unsnat.*priority=120" | ovn_strip_lflows], [0], [dnl
++ table=??(lr_in_unsnat ), priority=120 , match=(ip4 && ip4.dst == 192.0.2.1 && tcp && tcp.dst == 30663), action=(next;)
++])
++
++check ovn-nbctl --wait=sb set logical_router lr0 \
++ options:lb_force_snat_ip=router_ip
++
++AT_CHECK([ovn-sbctl dump-flows lr0 | \
++ grep "lr_in_unsnat.*priority=120" | ovn_strip_lflows], [0], [dnl
++ table=??(lr_in_unsnat ), priority=120 , match=(ip4 && ip4.dst == 192.0.2.1 && tcp && tcp.dst == 30663), action=(next;)
++])
++
++OVN_CLEANUP_NORTHD
++AT_CLEANUP
++])
++
+ OVN_FOR_EACH_NORTHD_NO_HV([
+ AT_SETUP([LRP same IP as VIP or SNAT])
+ ovn_start
+@@ -2374,6 +2307,33 @@ OVN_CLEANUP_NORTHD
+ AT_CLEANUP
+ ])
+
++OVN_FOR_EACH_NORTHD_NO_HV([
++AT_SETUP([router LRP /31 L3 admission control])
++ovn_start
++
++# A /31 point-to-point link (RFC 3021) has no broadcast address; both
++# addresses of the /31 are usable hosts. The L3 admission control flow must
++# therefore not drop the computed "broadcast" address, otherwise traffic from
++# the /31 peer is dropped.
++check ovn-nbctl lr-add lr
++check ovn-nbctl lrp-add lr lrp31 00:00:00:00:00:01 10.0.0.0/31
++check ovn-nbctl lrp-add lr lrp24 00:00:00:00:00:03 10.0.2.1/24
++
++check ovn-nbctl --wait=sb sync
++ovn-sbctl dump-flows lr > lrflows
++AT_CAPTURE_FILE([lrflows])
++
++# The /31 peer (10.0.0.1) must not appear as a dropped broadcast source;
++# only the wider /24 network keeps its broadcast address.
++AT_CHECK([grep "lr_in_ip_input" lrflows | grep "priority=100" | grep "reg9" | ovn_strip_lflows], [0], [dnl
++ table=??(lr_in_ip_input ), priority=100 , match=(ip4.src == {10.0.0.0} && reg9[[0]] == 0), action=(drop;)
++ table=??(lr_in_ip_input ), priority=100 , match=(ip4.src == {10.0.2.1, 10.0.2.255} && reg9[[0]] == 0), action=(drop;)
++])
++
++OVN_CLEANUP_NORTHD
++AT_CLEANUP
++])
++
+ # This test case tests that when a logical switch has load balancers associated
+ # (with VIPs configured), the below logical flow is added by ovn-northd.
+ # table=ls_out_pre_lb, priority=100, match=(ip), action=(reg0[[0]] = 1; next;)
+@@ -4378,6 +4338,11 @@ check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+ bfd_route_policy_uuid=$(fetch_column nb:bfd _uuid logical_port=r0-sw9)
+ AT_CHECK([ovn-nbctl list logical_router_policy | sed s/,//g | grep -q "$bfd_route_policy_uuid"])
+
++# Check that the logical router port can be deleted and the bfd
++# recompute does not crash northd.
++check ovn-nbctl --wait=sb lrp-del r0-sw2
++check ovn-nbctl --wait=sb sync
++
+ OVN_CLEANUP_NORTHD
+ AT_CLEANUP
+ ])
+@@ -5842,6 +5807,34 @@ OVN_CLEANUP_NORTHD
+ AT_CLEANUP
+ ])
+
++OVN_FOR_EACH_NORTHD_NO_HV_PARALLELIZATION([
++AT_SETUP([ovn -- ARP flows for distributed gw ports with NAT])
++ovn_start
++
++check ovn-sbctl chassis-add gw1 geneve 127.0.0.1
++check ovn-nbctl ls-add sw
++check ovn-nbctl lr-add ro1
++
++check ovn-nbctl lrp-add ro1 ro1-lrp1 00:00:00:00:00:01 10.0.10.1/24
++check ovn-nbctl lsp-add-router-port sw sw-ro1 ro1-lrp1
++check ovn-nbctl lrp-set-gateway-chassis ro1-lrp1 gw1
++
++check ovn-nbctl lrp-add ro1 ro1-lrp2 00:00:00:00:00:02 10.0.20.1/24
++check ovn-nbctl lsp-add-router-port sw sw-ro2 ro1-lrp2
++check ovn-nbctl lrp-set-gateway-chassis ro1-lrp2 gw1
++
++check ovn-nbctl --gateway-port=ro1-lrp2 lr-nat-add ro1 dnat_and_snat 10.0.10.100 10.0.20.2
++check ovn-nbctl --wait=sb sync
++
++ovn-sbctl lflow-list sw > ls1_lflows
++AT_CHECK([grep "ls_in_l2_lkup" ls1_lflows | grep 10.0.10.100 | ovn_strip_lflows], [0], [dnl
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 10.0.10.100), action=(outport = "sw-ro2"; output;)
++])
++
++OVN_CLEANUP_NORTHD
++AT_CLEANUP
++])
++
+ OVN_FOR_EACH_NORTHD_NO_HV_PARALLELIZATION([
+ AT_SETUP([ovn -- ARP flows for unreachable addresses - NAT and LB])
+ ovn_start
+@@ -5884,8 +5877,9 @@ AT_CHECK([grep "ls_in_l2_lkup" ls1_lflows | ovn_strip_lflows], [0], [dnl
+ table=??(ls_in_l2_lkup ), priority=71 , match=(eth.mcast && ip), action=(outport = "_MC_flood_l2"; output;)
+ table=??(ls_in_l2_lkup ), priority=72 , match=(eth.mcast && (nd_na || nd_rs || nd_ra)), action=(outport = "_MC_flood"; output;)
+ table=??(ls_in_l2_lkup ), priority=75 , match=(eth.src == {00:00:00:00:01:01} && eth.dst == ff:ff:ff:ff:ff:ff && (arp.op == 1 || rarp.op == 3 || nd_ns)), action=(outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.1.1), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && nd_ns && nd.target == fe80::200:ff:fe00:101), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 192.168.1.1), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.mcast && nd_ns && nd.target == fe80::200:ff:fe00:101), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 192.168.1.1 && arp.spa == 192.168.1.1), action=(outport = "_MC_flood_l2"; output;)
+ ])
+
+ ovn-sbctl lflow-list ls2 > ls2_lflows
+@@ -5900,8 +5894,9 @@ AT_CHECK([grep "ls_in_l2_lkup" ls2_lflows | ovn_strip_lflows], [0], [dnl
+ table=??(ls_in_l2_lkup ), priority=71 , match=(eth.mcast && ip), action=(outport = "_MC_flood_l2"; output;)
+ table=??(ls_in_l2_lkup ), priority=72 , match=(eth.mcast && (nd_na || nd_rs || nd_ra)), action=(outport = "_MC_flood"; output;)
+ table=??(ls_in_l2_lkup ), priority=75 , match=(eth.src == {00:00:00:00:02:01} && eth.dst == ff:ff:ff:ff:ff:ff && (arp.op == 1 || rarp.op == 3 || nd_ns)), action=(outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.2.1), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && nd_ns && nd.target == fe80::200:ff:fe00:201), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 192.168.2.1), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.mcast && nd_ns && nd.target == fe80::200:ff:fe00:201), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 192.168.2.1 && arp.spa == 192.168.2.1), action=(outport = "_MC_flood_l2"; output;)
+ ])
+
+ AS_BOX([Adding some reachable NAT addresses])
+@@ -5924,10 +5919,13 @@ AT_CHECK([grep "ls_in_l2_lkup" ls1_lflows | ovn_strip_lflows], [0], [dnl
+ table=??(ls_in_l2_lkup ), priority=71 , match=(eth.mcast && ip), action=(outport = "_MC_flood_l2"; output;)
+ table=??(ls_in_l2_lkup ), priority=72 , match=(eth.mcast && (nd_na || nd_rs || nd_ra)), action=(outport = "_MC_flood"; output;)
+ table=??(ls_in_l2_lkup ), priority=75 , match=(eth.src == {00:00:00:00:01:01} && eth.dst == ff:ff:ff:ff:ff:ff && (arp.op == 1 || rarp.op == 3 || nd_ns)), action=(outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.1.1), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && nd_ns && nd.target == fe80::200:ff:fe00:101), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 10.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 10.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 192.168.1.1), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.mcast && nd_ns && nd.target == fe80::200:ff:fe00:101), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 10.0.0.100 && arp.spa == 10.0.0.100), action=(outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 10.0.0.200 && arp.spa == 10.0.0.200), action=(outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 192.168.1.1 && arp.spa == 192.168.1.1), action=(outport = "_MC_flood_l2"; output;)
+ ])
+
+ ovn-sbctl lflow-list ls2 > ls2_lflows
+@@ -5942,10 +5940,13 @@ AT_CHECK([grep "ls_in_l2_lkup" ls2_lflows | ovn_strip_lflows], [0], [dnl
+ table=??(ls_in_l2_lkup ), priority=71 , match=(eth.mcast && ip), action=(outport = "_MC_flood_l2"; output;)
+ table=??(ls_in_l2_lkup ), priority=72 , match=(eth.mcast && (nd_na || nd_rs || nd_ra)), action=(outport = "_MC_flood"; output;)
+ table=??(ls_in_l2_lkup ), priority=75 , match=(eth.src == {00:00:00:00:02:01} && eth.dst == ff:ff:ff:ff:ff:ff && (arp.op == 1 || rarp.op == 3 || nd_ns)), action=(outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.2.1), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 20.0.0.100), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 20.0.0.200), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && nd_ns && nd.target == fe80::200:ff:fe00:201), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 192.168.2.1), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 20.0.0.100), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 20.0.0.200), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.mcast && nd_ns && nd.target == fe80::200:ff:fe00:201), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 192.168.2.1 && arp.spa == 192.168.2.1), action=(outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 20.0.0.100 && arp.spa == 20.0.0.100), action=(outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 20.0.0.200 && arp.spa == 20.0.0.200), action=(outport = "_MC_flood_l2"; output;)
+ ])
+
+ AS_BOX([Adding some unreachable NAT addresses])
+@@ -5968,12 +5969,17 @@ AT_CHECK([grep "ls_in_l2_lkup" ls1_lflows | ovn_strip_lflows], [0], [dnl
+ table=??(ls_in_l2_lkup ), priority=71 , match=(eth.mcast && ip), action=(outport = "_MC_flood_l2"; output;)
+ table=??(ls_in_l2_lkup ), priority=72 , match=(eth.mcast && (nd_na || nd_rs || nd_ra)), action=(outport = "_MC_flood"; output;)
+ table=??(ls_in_l2_lkup ), priority=75 , match=(eth.src == {00:00:00:00:01:01} && eth.dst == ff:ff:ff:ff:ff:ff && (arp.op == 1 || rarp.op == 3 || nd_ns)), action=(outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.1.1), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 30.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 30.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && nd_ns && nd.target == fe80::200:ff:fe00:101), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 10.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 10.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 192.168.1.1), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 30.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 30.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.mcast && nd_ns && nd.target == fe80::200:ff:fe00:101), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 10.0.0.100 && arp.spa == 10.0.0.100), action=(outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 10.0.0.200 && arp.spa == 10.0.0.200), action=(outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 192.168.1.1 && arp.spa == 192.168.1.1), action=(outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 30.0.0.100 && arp.spa == 30.0.0.100), action=(outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 30.0.0.200 && arp.spa == 30.0.0.200), action=(outport = "_MC_flood_l2"; output;)
+ ])
+
+ ovn-sbctl lflow-list ls2 > ls2_lflows
+@@ -5988,12 +5994,17 @@ AT_CHECK([grep "ls_in_l2_lkup" ls2_lflows | ovn_strip_lflows], [0], [dnl
+ table=??(ls_in_l2_lkup ), priority=71 , match=(eth.mcast && ip), action=(outport = "_MC_flood_l2"; output;)
+ table=??(ls_in_l2_lkup ), priority=72 , match=(eth.mcast && (nd_na || nd_rs || nd_ra)), action=(outport = "_MC_flood"; output;)
+ table=??(ls_in_l2_lkup ), priority=75 , match=(eth.src == {00:00:00:00:02:01} && eth.dst == ff:ff:ff:ff:ff:ff && (arp.op == 1 || rarp.op == 3 || nd_ns)), action=(outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.2.1), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 20.0.0.100), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 20.0.0.200), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 40.0.0.100), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 40.0.0.200), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && nd_ns && nd.target == fe80::200:ff:fe00:201), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 192.168.2.1), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 20.0.0.100), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 20.0.0.200), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 40.0.0.100), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 40.0.0.200), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.mcast && nd_ns && nd.target == fe80::200:ff:fe00:201), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 192.168.2.1 && arp.spa == 192.168.2.1), action=(outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 20.0.0.100 && arp.spa == 20.0.0.100), action=(outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 20.0.0.200 && arp.spa == 20.0.0.200), action=(outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 40.0.0.100 && arp.spa == 40.0.0.100), action=(outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 40.0.0.200 && arp.spa == 40.0.0.200), action=(outport = "_MC_flood_l2"; output;)
+ ])
+
+ AS_BOX([Adding load balancer reachable VIPs to ro1])
+@@ -6013,13 +6024,19 @@ AT_CHECK([grep "ls_in_l2_lkup" ls1_lflows | ovn_strip_lflows], [0], [dnl
+ table=??(ls_in_l2_lkup ), priority=71 , match=(eth.mcast && ip), action=(outport = "_MC_flood_l2"; output;)
+ table=??(ls_in_l2_lkup ), priority=72 , match=(eth.mcast && (nd_na || nd_rs || nd_ra)), action=(outport = "_MC_flood"; output;)
+ table=??(ls_in_l2_lkup ), priority=75 , match=(eth.src == {00:00:00:00:01:01} && eth.dst == ff:ff:ff:ff:ff:ff && (arp.op == 1 || rarp.op == 3 || nd_ns)), action=(outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.1.1), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.1.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 30.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 30.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && nd_ns && nd.target == fe80::200:ff:fe00:101), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 10.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 10.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 192.168.1.1), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 192.168.1.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 30.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 30.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.mcast && nd_ns && nd.target == fe80::200:ff:fe00:101), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 10.0.0.100 && arp.spa == 10.0.0.100), action=(outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 10.0.0.200 && arp.spa == 10.0.0.200), action=(outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 192.168.1.1 && arp.spa == 192.168.1.1), action=(outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 192.168.1.100 && arp.spa == 192.168.1.100), action=(outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 30.0.0.100 && arp.spa == 30.0.0.100), action=(outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 30.0.0.200 && arp.spa == 30.0.0.200), action=(outport = "_MC_flood_l2"; output;)
+ ])
+
+ AS_BOX([Adding load balancer unreachable VIPs to ro1])
+@@ -6037,13 +6054,19 @@ AT_CHECK([grep "ls_in_l2_lkup" ls1_lflows | ovn_strip_lflows], [0], [dnl
+ table=??(ls_in_l2_lkup ), priority=71 , match=(eth.mcast && ip), action=(outport = "_MC_flood_l2"; output;)
+ table=??(ls_in_l2_lkup ), priority=72 , match=(eth.mcast && (nd_na || nd_rs || nd_ra)), action=(outport = "_MC_flood"; output;)
+ table=??(ls_in_l2_lkup ), priority=75 , match=(eth.src == {00:00:00:00:01:01} && eth.dst == ff:ff:ff:ff:ff:ff && (arp.op == 1 || rarp.op == 3 || nd_ns)), action=(outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.1.1), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.1.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 30.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 30.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && nd_ns && nd.target == fe80::200:ff:fe00:101), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 10.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 10.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 192.168.1.1), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 192.168.1.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 30.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 30.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.mcast && nd_ns && nd.target == fe80::200:ff:fe00:101), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 10.0.0.100 && arp.spa == 10.0.0.100), action=(outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 10.0.0.200 && arp.spa == 10.0.0.200), action=(outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 192.168.1.1 && arp.spa == 192.168.1.1), action=(outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 192.168.1.100 && arp.spa == 192.168.1.100), action=(outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 30.0.0.100 && arp.spa == 30.0.0.100), action=(outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 30.0.0.200 && arp.spa == 30.0.0.200), action=(outport = "_MC_flood_l2"; output;)
+ ])
+
+ # Make sure that there is no flow for VIP 192.168.4.100 as ro1-ls1 doesn't
+@@ -6068,18 +6091,33 @@ AT_CHECK([grep "ls_in_l2_lkup" ls1_lflows | ovn_strip_lflows], [0], [dnl
+ table=??(ls_in_l2_lkup ), priority=71 , match=(eth.mcast && ip), action=(outport = "_MC_flood_l2"; output;)
+ table=??(ls_in_l2_lkup ), priority=72 , match=(eth.mcast && (nd_na || nd_rs || nd_ra)), action=(outport = "_MC_flood"; output;)
+ table=??(ls_in_l2_lkup ), priority=75 , match=(eth.src == {00:00:00:00:01:01} && eth.dst == ff:ff:ff:ff:ff:ff && (arp.op == 1 || rarp.op == 3 || nd_ns)), action=(outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.1.1), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.1.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 30.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 30.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && nd_ns && nd.target == fe80::200:ff:fe00:101), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 10.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 10.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 192.168.1.1), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 192.168.1.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 30.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 30.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.mcast && nd_ns && nd.target == fe80::200:ff:fe00:101), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 10.0.0.100 && arp.spa == 10.0.0.100), action=(outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 10.0.0.200 && arp.spa == 10.0.0.200), action=(outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 192.168.1.1 && arp.spa == 192.168.1.1), action=(outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 192.168.1.100 && arp.spa == 192.168.1.100), action=(outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 30.0.0.100 && arp.spa == 30.0.0.100), action=(outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 30.0.0.200 && arp.spa == 30.0.0.200), action=(outport = "_MC_flood_l2"; output;)
+ ])
+
+
+ # Make sure that there is no flow for VIP 192.168.4.100 as it is unreachable.
+-AT_CHECK([grep "ls_in_l2_lkup" ls1_lflows | grep "192.168.4.100" | grep -v clone | grep "_MC_flood" -c], [1], [0
++AT_CHECK([grep "ls_in_l2_lkup" ls1_lflows | grep "192.168.4.100" -c], [1], [0
++])
++
++AS_BOX([Adding an dnat_and_snat for LB unreachable VIP on ro1])
++check ovn-nbctl --wait=sb lr-nat-add ro1 dnat_and_snat 192.168.4.100 192.168.1.100
++
++ovn-sbctl lflow-list ls1 > ls1_lflows
++AT_CHECK([grep "ls_in_l2_lkup" ls1_lflows | grep "192.168.4.100" | ovn_strip_lflows], [0], [dnl
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 192.168.4.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 192.168.4.100 && arp.spa == 192.168.4.100), action=(outport = "_MC_flood_l2"; output;)
+ ])
+
+ OVN_CLEANUP_NORTHD
+@@ -7485,8 +7523,6 @@ AT_CHECK([grep -e "lr_in_arp_request" lr0flows | ovn_strip_lflows], [0], [dnl
+ table=??(lr_in_arp_request ), priority=0 , match=(1), action=(next;)
+ table=??(lr_in_arp_request ), priority=100 , match=(eth.dst == 00:00:00:00:00:00 && reg9[[9]] == 0), action=(nd_ns { nd.target = xxreg0; output; }; next;)
+ table=??(lr_in_arp_request ), priority=100 , match=(eth.dst == 00:00:00:00:00:00 && reg9[[9]] == 1), action=(arp { eth.dst = ff:ff:ff:ff:ff:ff; arp.spa = reg5; arp.tpa = reg0; arp.op = 1; output; }; next;)
+- table=??(lr_in_arp_request ), priority=200 , match=(eth.dst == 00:00:00:00:00:00 && reg9[[9]] == 0 && xxreg0 == 2001:db8::10), action=(nd_ns { eth.dst = 33:33:ff:00:00:10; ip6.dst = ff02::1:ff00:10; nd.target = 2001:db8::10; output; }; next;)
+- table=??(lr_in_arp_request ), priority=200 , match=(eth.dst == 00:00:00:00:00:00 && reg9[[9]] == 0 && xxreg0 == 2001:db8::20), action=(nd_ns { eth.dst = 33:33:ff:00:00:20; ip6.dst = ff02::1:ff00:20; nd.target = 2001:db8::20; output; }; next;)
+ ])
+
+ # ECMP symmetric reply with IPv4 prefix + IPv6 nexthop.
+@@ -8088,17 +8124,17 @@ check ovn-nbctl lsp-add S1 S1-VIF
+ check ovn-nbctl lsp-set-addresses S1-VIF "02:ac:10:01:00:02 unknown"
+ check ovn-nbctl --wait=sb sync
+
+-AT_CHECK([ovn-sbctl lflow-list S1 | grep ls_in_l2_lkup | grep -q 'match=(eth.mcast && (arp.op == 1 || nd_ns)), action=(outport = "_MC_flood_l2"; output;)'], [1])
++AT_CHECK([ovn-sbctl lflow-list S1 | grep ls_in_l2_lkup | grep -q 'match=(eth.mcast && (arp.op == 1 || nd_ns)), action=(outport = "_MC_unknown"; output;)'], [1])
+
+ check ovn-nbctl --wait=sb set Logical_Switch S1 \
+ other_config:broadcast-arps-to-all-routers=false
+
+-AT_CHECK([ovn-sbctl lflow-list S1 | grep ls_in_l2_lkup | grep -q 'match=(eth.mcast && (arp.op == 1 || nd_ns)), action=(outport = "_MC_flood_l2"; output;)'], [0], [], [ignore])
++AT_CHECK([ovn-sbctl lflow-list S1 | grep ls_in_l2_lkup | grep -q 'match=(eth.mcast && (arp.op == 1 || nd_ns)), action=(outport = "_MC_unknown"; output;)'], [0], [], [ignore])
+
+ check ovn-nbctl --wait=sb set Logical_Switch S1 \
+ other_config:broadcast-arps-to-all-routers=true
+
+-AT_CHECK([ovn-sbctl lflow-list S1 | grep ls_in_l2_lkup | grep -q 'match=(eth.mcast && (arp.op == 1 || nd_ns)), action=(outport = "_MC_flood_l2"; output;)'], [1])
++AT_CHECK([ovn-sbctl lflow-list S1 | grep ls_in_l2_lkup | grep -q 'match=(eth.mcast && (arp.op == 1 || nd_ns)), action=(outport = "_MC_unknown"; output;)'], [1])
+
+ OVN_CLEANUP_NORTHD
+ AT_CLEANUP
+@@ -8378,14 +8414,14 @@ AT_CHECK([grep -e 'lr_in_ip_input ' lrflows | grep -e 'igmp' -e 'mld' -e 'ip.
+ table=??(lr_in_ip_input ), priority=120 , match=((mldv1 || mldv2) && ip.ttl == 1), action=(next;)
+ table=??(lr_in_ip_input ), priority=120 , match=(igmp && ip.ttl == 1), action=(next;)
+ table=??(lr_in_ip_input ), priority=29 , match=(ip.ttl == {0, 1}), action=(drop;)
+- table=??(lr_in_ip_input ), priority=30 , match=(inport == "lrp1" && ip4 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp4 {eth.dst <-> eth.src; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst = ip4.src; ip4.src = 10.10.10.1; ip.ttl = 254; outport = "lrp1"; flags.loopback = 1; output; };)
+- table=??(lr_in_ip_input ), priority=30 , match=(inport == "lrp1" && ip6 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp6 {eth.dst <-> eth.src; ip6.dst = ip6.src; ip6.src = 1010::1; ip.ttl = 254; icmp6.type = 3; /* Time exceeded */ icmp6.code = 0; /* TTL exceeded in transit */ outport = "lrp1"; flags.loopback = 1; output; };)
+- table=??(lr_in_ip_input ), priority=30 , match=(inport == "lrp2" && ip4 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp4 {eth.dst <-> eth.src; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst = ip4.src; ip4.src = 20.20.20.1; ip.ttl = 254; outport = "lrp2"; flags.loopback = 1; output; };)
+- table=??(lr_in_ip_input ), priority=30 , match=(inport == "lrp2" && ip6 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp6 {eth.dst <-> eth.src; ip6.dst = ip6.src; ip6.src = 2020::1; ip.ttl = 254; icmp6.type = 3; /* Time exceeded */ icmp6.code = 0; /* TTL exceeded in transit */ outport = "lrp2"; flags.loopback = 1; output; };)
+- table=??(lr_in_ip_input ), priority=31 , match=(inport == "lrp1" && ip4 && ip4.src == 10.10.10.0/24 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp4 {eth.dst <-> eth.src; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst = ip4.src; ip4.src = 10.10.10.1 ; ip.ttl = 254; outport = "lrp1"; flags.loopback = 1; output; };)
+- table=??(lr_in_ip_input ), priority=31 , match=(inport == "lrp1" && ip6 && ip6.src == 1010::/64 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp6 {eth.dst <-> eth.src; ip6.dst = ip6.src; ip6.src = 1010::1 ; ip.ttl = 254; icmp6.type = 3; /* Time exceeded */ icmp6.code = 0; /* TTL exceeded in transit */ outport = "lrp1"; flags.loopback = 1; output; };)
+- table=??(lr_in_ip_input ), priority=31 , match=(inport == "lrp2" && ip4 && ip4.src == 20.20.20.0/24 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp4 {eth.dst <-> eth.src; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst = ip4.src; ip4.src = 20.20.20.1 ; ip.ttl = 254; outport = "lrp2"; flags.loopback = 1; output; };)
+- table=??(lr_in_ip_input ), priority=31 , match=(inport == "lrp2" && ip6 && ip6.src == 2020::/64 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp6 {eth.dst <-> eth.src; ip6.dst = ip6.src; ip6.src = 2020::1 ; ip.ttl = 254; icmp6.type = 3; /* Time exceeded */ icmp6.code = 0; /* TTL exceeded in transit */ outport = "lrp2"; flags.loopback = 1; output; };)
++ table=??(lr_in_ip_input ), priority=30 , match=(inport == "lrp1" && ip4 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp4 {eth.dst = eth.src; eth.src = 00:00:00:00:00:01; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst = ip4.src; ip4.src = 10.10.10.1; ip.ttl = 254; outport = "lrp1"; flags.loopback = 1; output; };)
++ table=??(lr_in_ip_input ), priority=30 , match=(inport == "lrp1" && ip6 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp6 {eth.dst = eth.src; eth.src = 00:00:00:00:00:01; ip6.dst = ip6.src; ip6.src = 1010::1; ip.ttl = 254; icmp6.type = 3; /* Time exceeded */ icmp6.code = 0; /* TTL exceeded in transit */ outport = "lrp1"; flags.loopback = 1; output; };)
++ table=??(lr_in_ip_input ), priority=30 , match=(inport == "lrp2" && ip4 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp4 {eth.dst = eth.src; eth.src = 00:00:00:00:00:02; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst = ip4.src; ip4.src = 20.20.20.1; ip.ttl = 254; outport = "lrp2"; flags.loopback = 1; output; };)
++ table=??(lr_in_ip_input ), priority=30 , match=(inport == "lrp2" && ip6 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp6 {eth.dst = eth.src; eth.src = 00:00:00:00:00:02; ip6.dst = ip6.src; ip6.src = 2020::1; ip.ttl = 254; icmp6.type = 3; /* Time exceeded */ icmp6.code = 0; /* TTL exceeded in transit */ outport = "lrp2"; flags.loopback = 1; output; };)
++ table=??(lr_in_ip_input ), priority=31 , match=(inport == "lrp1" && ip4 && ip4.src == 10.10.10.0/24 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp4 {eth.dst = eth.src; eth.src = 00:00:00:00:00:01; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst = ip4.src; ip4.src = 10.10.10.1; ip.ttl = 254; outport = "lrp1"; flags.loopback = 1; output; };)
++ table=??(lr_in_ip_input ), priority=31 , match=(inport == "lrp1" && ip6 && ip6.src == 1010::/64 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp6 {eth.dst = eth.src; eth.src = 00:00:00:00:00:01; ip6.dst = ip6.src; ip6.src = 1010::1; ip.ttl = 254; icmp6.type = 3; /* Time exceeded */ icmp6.code = 0; /* TTL exceeded in transit */ outport = "lrp1"; flags.loopback = 1; output; };)
++ table=??(lr_in_ip_input ), priority=31 , match=(inport == "lrp2" && ip4 && ip4.src == 20.20.20.0/24 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp4 {eth.dst = eth.src; eth.src = 00:00:00:00:00:02; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst = ip4.src; ip4.src = 20.20.20.1; ip.ttl = 254; outport = "lrp2"; flags.loopback = 1; output; };)
++ table=??(lr_in_ip_input ), priority=31 , match=(inport == "lrp2" && ip6 && ip6.src == 2020::/64 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp6 {eth.dst = eth.src; eth.src = 00:00:00:00:00:02; ip6.dst = ip6.src; ip6.src = 2020::1; ip.ttl = 254; icmp6.type = 3; /* Time exceeded */ icmp6.code = 0; /* TTL exceeded in transit */ outport = "lrp2"; flags.loopback = 1; output; };)
+ table=??(lr_in_ip_input ), priority=32 , match=(ip.ttl == {0, 1} && !ip.later_frag && (ip4.mcast || ip6.mcast)), action=(drop;)
+ ])
+
+@@ -8405,6 +8441,73 @@ OVN_CLEANUP_NORTHD
+ AT_CLEANUP
+ ])
+
++OVN_FOR_EACH_NORTHD_NO_HV([
++AT_SETUP([TTL exceeded -- l3dgw port uses correct source IP])
++AT_KEYWORDS([ttl-exceeded l3dgw])
++ovn_start
++
++check ovn-nbctl lr-add lr0
++check ovn-nbctl ls-add sw0
++check ovn-nbctl ls-add public
++
++check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24 2001:db8::1/64
++check ovn-nbctl lsp-add-router-port sw0 sw0-lr0 lr0-sw0
++
++check ovn-nbctl lrp-add lr0 lr0-public 00:00:00:00:ff:02 172.168.0.10/24 2001:db8:1::1/64
++check ovn-nbctl lsp-add-router-port public public-lr0 lr0-public
++
++check ovn-sbctl chassis-add gw1 geneve 127.0.0.1
++check ovn-nbctl lrp-set-gateway-chassis lr0-public gw1
++
++check ovn-nbctl --wait=sb sync
++
++ovn-sbctl dump-flows lr0 > lr0flows
++AT_CAPTURE_FILE([lr0flows])
++
++dnl The priority-31 TTL expired flow for the l3dgw port must use explicit
++dnl ip4.src assignment (the router port IP), not ip4.dst <-> ip4.src swap.
++AT_CHECK([grep 'priority=31.*lr0-public.*ip4.*ip.ttl' lr0flows | ovn_strip_lflows], [0], [dnl
++ table=??(lr_in_ip_input ), priority=31 , match=(inport == "lr0-public" && ip4 && ip4.src == 172.168.0.0/24 && ip.ttl == {0, 1} && !ip.later_frag && is_chassis_resident("cr-lr0-public")), action=(icmp4 {eth.dst = eth.src; eth.src = 00:00:00:00:ff:02; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst = ip4.src; ip4.src = 172.168.0.10; ip.ttl = 254; outport = "lr0-public"; flags.loopback = 1; output; };)
++])
++
++dnl Non-l3dgw port should also use explicit assignment (unchanged behavior).
++AT_CHECK([grep 'priority=31.*lr0-sw0.*ip4.*ip.ttl' lr0flows | ovn_strip_lflows], [0], [dnl
++ table=??(lr_in_ip_input ), priority=31 , match=(inport == "lr0-sw0" && ip4 && ip4.src == 10.0.0.0/24 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp4 {eth.dst = eth.src; eth.src = 00:00:00:00:ff:01; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 254; outport = "lr0-sw0"; flags.loopback = 1; output; };)
++])
++
++dnl IPv6: l3dgw port must use explicit ip6.src assignment, not swap.
++AT_CHECK([grep 'priority=31.*lr0-public.*ip6.*ip.ttl' lr0flows | ovn_strip_lflows], [0], [dnl
++ table=??(lr_in_ip_input ), priority=31 , match=(inport == "lr0-public" && ip6 && ip6.src == 2001:db8:1::/64 && ip.ttl == {0, 1} && !ip.later_frag && is_chassis_resident("cr-lr0-public")), action=(icmp6 {eth.dst = eth.src; eth.src = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = 2001:db8:1::1; ip.ttl = 254; icmp6.type = 3; /* Time exceeded */ icmp6.code = 0; /* TTL exceeded in transit */ outport = "lr0-public"; flags.loopback = 1; output; };)
++])
++
++dnl IPv6: non-l3dgw port (unchanged behavior).
++AT_CHECK([grep 'priority=31.*lr0-sw0.*ip6.*ip.ttl' lr0flows | ovn_strip_lflows], [0], [dnl
++ table=??(lr_in_ip_input ), priority=31 , match=(inport == "lr0-sw0" && ip6 && ip6.src == 2001:db8::/64 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp6 {eth.dst = eth.src; eth.src = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = 2001:db8::1; ip.ttl = 254; icmp6.type = 3; /* Time exceeded */ icmp6.code = 0; /* TTL exceeded in transit */ outport = "lr0-sw0"; flags.loopback = 1; output; };)
++])
++
++AT_CHECK([grep 'priority=30.*lr0-public.*ip4.*ip.ttl' lr0flows | ovn_strip_lflows], [0], [dnl
++ table=??(lr_in_ip_input ), priority=30 , match=(inport == "lr0-public" && ip4 && ip.ttl == {0, 1} && !ip.later_frag && is_chassis_resident("cr-lr0-public")), action=(icmp4 {eth.dst = eth.src; eth.src = 00:00:00:00:ff:02; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst = ip4.src; ip4.src = 172.168.0.10; ip.ttl = 254; outport = "lr0-public"; flags.loopback = 1; output; };)
++])
++
++dnl Non-l3dgw port should also use explicit assignment (unchanged behavior).
++AT_CHECK([grep 'priority=30.*lr0-sw0.*ip4.*ip.ttl' lr0flows | ovn_strip_lflows], [0], [dnl
++ table=??(lr_in_ip_input ), priority=30 , match=(inport == "lr0-sw0" && ip4 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp4 {eth.dst = eth.src; eth.src = 00:00:00:00:ff:01; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 254; outport = "lr0-sw0"; flags.loopback = 1; output; };)
++])
++
++dnl IPv6: l3dgw port must use explicit ip6.src assignment, not swap.
++AT_CHECK([grep 'priority=30.*lr0-public.*ip6.*ip.ttl' lr0flows | ovn_strip_lflows], [0], [dnl
++ table=??(lr_in_ip_input ), priority=30 , match=(inport == "lr0-public" && ip6 && ip.ttl == {0, 1} && !ip.later_frag && is_chassis_resident("cr-lr0-public")), action=(icmp6 {eth.dst = eth.src; eth.src = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = 2001:db8:1::1; ip.ttl = 254; icmp6.type = 3; /* Time exceeded */ icmp6.code = 0; /* TTL exceeded in transit */ outport = "lr0-public"; flags.loopback = 1; output; };)
++])
++
++dnl IPv6: non-l3dgw port (unchanged behavior).
++AT_CHECK([grep 'priority=30.*lr0-sw0.*ip6.*ip.ttl' lr0flows | ovn_strip_lflows], [0], [dnl
++ table=??(lr_in_ip_input ), priority=30 , match=(inport == "lr0-sw0" && ip6 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp6 {eth.dst = eth.src; eth.src = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = 2001:db8::1; ip.ttl = 254; icmp6.type = 3; /* Time exceeded */ icmp6.code = 0; /* TTL exceeded in transit */ outport = "lr0-sw0"; flags.loopback = 1; output; };)
++])
++
++OVN_CLEANUP_NORTHD
++AT_CLEANUP
++])
++
+ OVN_FOR_EACH_NORTHD_NO_HV([
+ AT_SETUP([IP multicast flood without IGMP relay])
+ AT_KEYWORDS([mcast_relay])
+@@ -8980,7 +9083,7 @@ AT_SETUP([LR neighbor lookup and learning flows])
+ ovn_start
+
+ # Create logical routers
+-check ovn-nbctl --wait=sb lr-add lr0
++check ovn-nbctl --wait=sb lr-add lr0 -- lrp-add lr0 lrp 00:00:00:00:00:01 192.168.0.1/24 fd10::1/96
+
+ ovn-sbctl dump-flows lr0 > lrflows
+ AT_CAPTURE_FILE([lrflows])
+@@ -8995,8 +9098,35 @@ AT_CHECK([cat lrflows | grep -e lr_in_lookup_neighbor -e lr_in_learn_neighbor |
+ table=??(lr_in_learn_neighbor), priority=95 , match=(nd_ns && (ip6.src == 0 || nd.sll == 0)), action=(next;)
+ table=??(lr_in_lookup_neighbor), priority=0 , match=(1), action=(reg9[[2]] = 1; next;)
+ table=??(lr_in_lookup_neighbor), priority=100 , match=(arp.op == 2), action=(reg9[[2]] = lookup_arp(inport, arp.spa, arp.sha); next;)
++ table=??(lr_in_lookup_neighbor), priority=100 , match=(inport == "lrp" && arp.spa == 192.168.0.0/24 && arp.op == 1), action=(reg9[[2]] = lookup_arp(inport, arp.spa, arp.sha); next;)
++ table=??(lr_in_lookup_neighbor), priority=100 , match=(inport == "lrp" && ip6.src == fd10::/96 && nd_ns), action=(reg9[[2]] = lookup_nd(inport, ip6.src, nd.sll); next;)
+ table=??(lr_in_lookup_neighbor), priority=100 , match=(nd_na), action=(reg9[[2]] = lookup_nd(inport, nd.target, nd.tll); next;)
+- table=??(lr_in_lookup_neighbor), priority=100 , match=(nd_ns), action=(reg9[[2]] = lookup_nd(inport, ip6.src, nd.sll); next;)
++ table=??(lr_in_lookup_neighbor), priority=105 , match=(nd_na && nd.tll == 0), action=(reg9[[2]] = lookup_nd(inport, nd.target, eth.src); next;)
++])
++
++check ovn-nbctl --wait=sb set Logical_Router lr0 options:always_learn_from_arp_request=false
++
++ovn-sbctl dump-flows lr0 > lrflows
++AT_CAPTURE_FILE([lrflows])
++
++AT_CHECK([cat lrflows | grep -e lr_in_lookup_neighbor -e lr_in_learn_neighbor | ovn_strip_lflows], [0], [dnl
++ table=??(lr_in_learn_neighbor), priority=0 , match=(1), action=(drop;)
++ table=??(lr_in_learn_neighbor), priority=100 , match=(reg9[[2]] == 1 || reg9[[3]] == 0), action=(mac_cache_use; next;)
++ table=??(lr_in_learn_neighbor), priority=90 , match=(arp), action=(put_arp(inport, arp.spa, arp.sha); next;)
++ table=??(lr_in_learn_neighbor), priority=90 , match=(nd_na), action=(put_nd(inport, nd.target, nd.tll); next;)
++ table=??(lr_in_learn_neighbor), priority=90 , match=(nd_ns), action=(put_nd(inport, ip6.src, nd.sll); next;)
++ table=??(lr_in_learn_neighbor), priority=95 , match=(nd_na && nd.tll == 0), action=(put_nd(inport, nd.target, eth.src); next;)
++ table=??(lr_in_learn_neighbor), priority=95 , match=(nd_ns && (ip6.src == 0 || nd.sll == 0)), action=(next;)
++ table=??(lr_in_lookup_neighbor), priority=0 , match=(1), action=(reg9[[2]] = 1; next;)
++ table=??(lr_in_lookup_neighbor), priority=100 , match=(arp.op == 2), action=(reg9[[2]] = lookup_arp(inport, arp.spa, arp.sha); reg9[[3]] = 1; next;)
++ table=??(lr_in_lookup_neighbor), priority=100 , match=(inport == "lrp" && arp.spa == 192.168.0.0/24 && arp.op == 1), action=(reg9[[2]] = lookup_arp(inport, arp.spa, arp.sha); reg9[[3]] = lookup_arp_ip(inport, arp.spa); next;)
++ table=??(lr_in_lookup_neighbor), priority=100 , match=(inport == "lrp" && ip6.src == fd10::/96 && nd_ns), action=(reg9[[2]] = lookup_nd(inport, ip6.src, nd.sll); reg9[[3]] = lookup_nd_ip(inport, ip6.src); next;)
++ table=??(lr_in_lookup_neighbor), priority=100 , match=(nd_na), action=(reg9[[2]] = lookup_nd(inport, nd.target, nd.tll); reg9[[3]] = 1; next;)
++ table=??(lr_in_lookup_neighbor), priority=105 , match=(nd_na && nd.tll == 0), action=(reg9[[2]] = lookup_nd(inport, nd.target, eth.src); reg9[[3]] = 1; next;)
++ table=??(lr_in_lookup_neighbor), priority=110 , match=(inport == "lrp" && arp.spa == 192.168.0.0/24 && arp.tpa == 192.168.0.1 && arp.op == 1), action=(reg9[[2]] = lookup_arp(inport, arp.spa, arp.sha); reg9[[3]] = 1; next;)
++ table=??(lr_in_lookup_neighbor), priority=110 , match=(inport == "lrp" && ip6.src == fd10::/96 && nd.target == fd10::1 && nd_ns), action=(reg9[[2]] = lookup_nd(inport, ip6.src, nd.sll); reg9[[3]] = 1; next;)
++ table=??(lr_in_lookup_neighbor), priority=110 , match=(nd_na && ip6.src == fe80::/10 && ip6.dst == ff00::/8), action=(reg9[[2]] = lookup_nd(inport, nd.target, nd.tll); reg9[[3]] = lookup_nd_ip(inport, nd.target); next;)
++ table=??(lr_in_lookup_neighbor), priority=115 , match=(nd_na && nd.tll == 0 && ip6.src == fe80::/10 && ip6.dst == ff00::/8), action=(reg9[[2]] = lookup_nd(inport, nd.target, eth.src); reg9[[3]] = lookup_nd_ip(inport, nd.target); next;)
+ ])
+
+ OVN_CLEANUP_NORTHD
+@@ -11312,6 +11442,78 @@ OVN_CLEANUP_NORTHD
+ AT_CLEANUP
+ ])
+
++OVN_FOR_EACH_NORTHD_NO_HV([
++AT_SETUP([Tiered ACL default-allow fallback is gated by tier])
++AT_KEYWORDS([acl])
++
++dnl When tiered ACLs are in use, the priority-1 default-allow flow that
++dnl unblocks previously-blocked connections must not match until the
++dnl tier loop reaches the highest configured tier; otherwise a drop ACL
++dnl at a higher tier would be bypassed for an already-blocked connection.
++
++ovn_start
++
++check ovn-nbctl ls-add ls
++check ovn-nbctl lsp-add ls lsp
++check ovn-nbctl --wait=sb sync
++
++# Baseline: no ACLs => no stateful flows at all in acl_eval, so no
++# priority-1 default-allow either.
++AT_CHECK([ovn-sbctl lflow-list ls | grep -w ls_in_acl_eval | grep 'ip && ct.est && ct_mark.blocked == 1' | ovn_strip_lflows], [0], [])
++AT_CHECK([ovn-sbctl lflow-list ls | grep -w ls_out_acl_eval | grep 'ip && ct.est && ct_mark.blocked == 1' | ovn_strip_lflows], [0], [])
++
++# Add a stateful, untiered ACL on each direction. The priority-1
++# default-allow appears with no tier guard.
++check ovn-nbctl acl-add ls from-lport 1000 "tcp" allow-related
++check ovn-nbctl --wait=sb acl-add ls to-lport 1000 "tcp" allow-related
++
++AT_CHECK([ovn-sbctl lflow-list ls | grep -w ls_in_acl_eval | grep 'ip && ct.est && ct_mark.blocked == 1' | ovn_strip_lflows], [0], [dnl
++ table=??(ls_in_acl_eval ), priority=1 , match=(ip && ct.est && ct_mark.blocked == 1), action=(reg8[[16]] = 1; next;)
++])
++AT_CHECK([ovn-sbctl lflow-list ls | grep -w ls_out_acl_eval | grep 'ip && ct.est && ct_mark.blocked == 1' | ovn_strip_lflows], [0], [dnl
++ table=??(ls_out_acl_eval ), priority=1 , match=(ip && ct.est && ct_mark.blocked == 1), action=(reg8[[16]] = 1; next;)
++])
++
++# Move both ACLs to tier 1. Each fallback flow should now be guarded
++# by REG_ACL_TIER == 1 so it only matches after the tier loop completes.
++in_uuid=$(fetch_column nb:ACL _uuid direction=from-lport)
++out_uuid=$(fetch_column nb:ACL _uuid direction=to-lport)
++check ovn-nbctl set ACL $in_uuid tier=1
++check ovn-nbctl --wait=sb set ACL $out_uuid tier=1
++
++AT_CHECK([ovn-sbctl lflow-list ls | grep -w ls_in_acl_eval | grep 'ip && ct.est && ct_mark.blocked == 1' | ovn_strip_lflows], [0], [dnl
++ table=??(ls_in_acl_eval ), priority=1 , match=(ip && ct.est && ct_mark.blocked == 1 && reg8[[30..31]] == 1), action=(reg8[[16]] = 1; next;)
++])
++AT_CHECK([ovn-sbctl lflow-list ls | grep -w ls_out_acl_eval | grep 'ip && ct.est && ct_mark.blocked == 1' | ovn_strip_lflows], [0], [dnl
++ table=??(ls_out_acl_eval ), priority=1 , match=(ip && ct.est && ct_mark.blocked == 1 && reg8[[30..31]] == 1), action=(reg8[[16]] = 1; next;)
++])
++
++# Move the ingress ACL to tier 3 while the egress ACL stays at tier 1.
++# The two stages should pick up their per-stage max independently.
++check ovn-nbctl --wait=sb set ACL $in_uuid tier=3
++
++AT_CHECK([ovn-sbctl lflow-list ls | grep -w ls_in_acl_eval | grep 'ip && ct.est && ct_mark.blocked == 1' | ovn_strip_lflows], [0], [dnl
++ table=??(ls_in_acl_eval ), priority=1 , match=(ip && ct.est && ct_mark.blocked == 1 && reg8[[30..31]] == 3), action=(reg8[[16]] = 1; next;)
++])
++AT_CHECK([ovn-sbctl lflow-list ls | grep -w ls_out_acl_eval | grep 'ip && ct.est && ct_mark.blocked == 1' | ovn_strip_lflows], [0], [dnl
++ table=??(ls_out_acl_eval ), priority=1 , match=(ip && ct.est && ct_mark.blocked == 1 && reg8[[30..31]] == 1), action=(reg8[[16]] = 1; next;)
++])
++
++# Drop both ACLs back to tier 0; the tier guard should disappear.
++check ovn-nbctl set ACL $in_uuid tier=0
++check ovn-nbctl --wait=sb set ACL $out_uuid tier=0
++
++AT_CHECK([ovn-sbctl lflow-list ls | grep -w ls_in_acl_eval | grep 'ip && ct.est && ct_mark.blocked == 1' | ovn_strip_lflows], [0], [dnl
++ table=??(ls_in_acl_eval ), priority=1 , match=(ip && ct.est && ct_mark.blocked == 1), action=(reg8[[16]] = 1; next;)
++])
++AT_CHECK([ovn-sbctl lflow-list ls | grep -w ls_out_acl_eval | grep 'ip && ct.est && ct_mark.blocked == 1' | ovn_strip_lflows], [0], [dnl
++ table=??(ls_out_acl_eval ), priority=1 , match=(ip && ct.est && ct_mark.blocked == 1), action=(reg8[[16]] = 1; next;)
++])
++
++OVN_CLEANUP_NORTHD
++AT_CLEANUP
++])
++
+ OVN_FOR_EACH_NORTHD_NO_HV([
+ AT_SETUP([ACL "pass" logical flows])
+ AT_KEYWORDS([acl])
+@@ -11856,6 +12058,53 @@ ignored_dp=lr0])
+ AT_CLEANUP
+ ])
+
++OVN_FOR_EACH_NORTHD_NO_HV([
++AT_SETUP([SB DNS incremental processing])
++ovn_start
++
++check ovn-nbctl ls-add sw0
++dns_uuid=$(ovn-nbctl create DNS records={})
++check ovn-nbctl set DNS $dns_uuid records:vm1.ovn.org="10.0.0.4"
++check ovn-nbctl set Logical_Switch sw0 dns_records="$dns_uuid"
++check ovn-nbctl --wait=sb sync
++
++# ovn-northd syncs the NB DNS record to the SB DNS table.
++wait_row_count sb:DNS 1
++sb_dns_uuid=$(fetch_column sb:DNS _uuid)
++AT_CHECK([ovn-sbctl get DNS $sb_dns_uuid records:vm1.ovn.org], [0], [dnl
++"10.0.0.4"
++])
++
++# ovn-northd is the only writer of the SB DNS table and disables IDL
++# alerting for all of its columns, so changes to that table never wake
++# ovn-northd up - not even the write-back of its own transaction. A direct
++# SB write is therefore not noticed at all: the northd node neither
++# recomputes nor computes.
++check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
++check ovn-sbctl set DNS $sb_dns_uuid records:vm2.ovn.org="10.0.0.5"
++check ovn-nbctl --wait=sb sync
++check_engine_stats northd norecompute nocompute
++check_engine_stats lflow norecompute nocompute
++
++# The externally added record is consequently still there.
++AT_CHECK([ovn-sbctl get DNS $sb_dns_uuid records:vm2.ovn.org], [0], [dnl
++"10.0.0.5"
++])
++
++# sync_dns_entries() rewrites the records column from the NB contents, so a
++# recompute drops the stale entry without changing anything else.
++CHECK_NO_CHANGE_AFTER_RECOMPUTE
++wait_row_count sb:DNS 0 records:vm2.ovn.org='"10.0.0.5"'
++wait_row_count sb:DNS 1 records:vm1.ovn.org='"10.0.0.4"'
++
++# Dropping the NB record removes the SB one as well.
++check ovn-nbctl clear Logical_Switch sw0 dns_records
++check ovn-nbctl --wait=sb sync
++wait_row_count sb:DNS 0
++
++AT_CLEANUP
++])
++
+ OVN_FOR_EACH_NORTHD_NO_HV([
+ AT_SETUP([ACL/Meter incremental processing - no northd recompute])
+ ovn_start
+@@ -13299,7 +13548,7 @@ CHECK_NO_CHANGE_AFTER_RECOMPUTE
+ check ovn-nbctl lb-add lb1 172.168.0.140 10.0.0.20
+ check ovn-nbctl lb-add lb2 172.168.0.150:80 10.0.0.40:8080
+ check ovn-nbctl lr-lb-add lr0 lb1
+-check ovn-nbctl lr-lb-add lr0 lb2
++check ovn-nbctl --wait=sb lr-lb-add lr0 lb2
+
+ # lflow engine should recompute since the nat ip 172.168.0.140
+ # is a lb vip.
+@@ -14419,15 +14668,15 @@ AT_CHECK([grep "lr_in_ip_input" lr0flows | ovn_strip_lflows], [0], [dnl
+ table=??(lr_in_ip_input ), priority=100 , match=(ip4.src_mcast ||ip4.src == 255.255.255.255 || ip4.src == 127.0.0.0/8 || ip4.dst == 127.0.0.0/8 || ip4.src == 0.0.0.0/8 || ip4.dst == 0.0.0.0/8), action=(drop;)
+ table=??(lr_in_ip_input ), priority=120 , match=(inport == "lr0-public" && ip4.src == 172.168.0.100), action=(next;)
+ table=??(lr_in_ip_input ), priority=29 , match=(ip.ttl == {0, 1}), action=(drop;)
+- table=??(lr_in_ip_input ), priority=30 , match=(inport == "lr0-public" && ip4 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp4 {eth.dst <-> eth.src; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst = ip4.src; ip4.src = 172.168.0.10; ip.ttl = 254; outport = "lr0-public"; flags.loopback = 1; output; };)
+- table=??(lr_in_ip_input ), priority=30 , match=(inport == "lr0-public" && ip6 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp6 {eth.dst <-> eth.src; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 254; icmp6.type = 3; /* Time exceeded */ icmp6.code = 0; /* TTL exceeded in transit */ outport = "lr0-public"; flags.loopback = 1; output; };)
+- table=??(lr_in_ip_input ), priority=30 , match=(inport == "lr0-sw0" && ip4 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp4 {eth.dst <-> eth.src; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 254; outport = "lr0-sw0"; flags.loopback = 1; output; };)
+- table=??(lr_in_ip_input ), priority=30 , match=(inport == "lr0-sw0" && ip6 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp6 {eth.dst <-> eth.src; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 254; icmp6.type = 3; /* Time exceeded */ icmp6.code = 0; /* TTL exceeded in transit */ outport = "lr0-sw0"; flags.loopback = 1; output; };)
+- table=??(lr_in_ip_input ), priority=30 , match=(inport == "lr0-sw1" && ip4 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp4 {eth.dst <-> eth.src; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 254; outport = "lr0-sw1"; flags.loopback = 1; output; };)
+- table=??(lr_in_ip_input ), priority=30 , match=(inport == "lr0-sw1" && ip6 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp6 {eth.dst <-> eth.src; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff03; ip.ttl = 254; icmp6.type = 3; /* Time exceeded */ icmp6.code = 0; /* TTL exceeded in transit */ outport = "lr0-sw1"; flags.loopback = 1; output; };)
+- table=??(lr_in_ip_input ), priority=31 , match=(inport == "lr0-public" && ip4 && ip4.src == 172.168.0.0/24 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp4 {eth.dst <-> eth.src; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst <-> ip4.src ; ip.ttl = 254; outport = "lr0-public"; flags.loopback = 1; output; };)
+- table=??(lr_in_ip_input ), priority=31 , match=(inport == "lr0-sw0" && ip4 && ip4.src == 10.0.0.0/24 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp4 {eth.dst <-> eth.src; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst = ip4.src; ip4.src = 10.0.0.1 ; ip.ttl = 254; outport = "lr0-sw0"; flags.loopback = 1; output; };)
+- table=??(lr_in_ip_input ), priority=31 , match=(inport == "lr0-sw1" && ip4 && ip4.src == 20.0.0.0/24 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp4 {eth.dst <-> eth.src; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst = ip4.src; ip4.src = 20.0.0.1 ; ip.ttl = 254; outport = "lr0-sw1"; flags.loopback = 1; output; };)
++ table=??(lr_in_ip_input ), priority=30 , match=(inport == "lr0-public" && ip4 && ip.ttl == {0, 1} && !ip.later_frag && is_chassis_resident("cr-lr0-public")), action=(icmp4 {eth.dst = eth.src; eth.src = 00:00:00:00:ff:02; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst = ip4.src; ip4.src = 172.168.0.10; ip.ttl = 254; outport = "lr0-public"; flags.loopback = 1; output; };)
++ table=??(lr_in_ip_input ), priority=30 , match=(inport == "lr0-public" && ip6 && ip.ttl == {0, 1} && !ip.later_frag && is_chassis_resident("cr-lr0-public")), action=(icmp6 {eth.dst = eth.src; eth.src = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 254; icmp6.type = 3; /* Time exceeded */ icmp6.code = 0; /* TTL exceeded in transit */ outport = "lr0-public"; flags.loopback = 1; output; };)
++ table=??(lr_in_ip_input ), priority=30 , match=(inport == "lr0-sw0" && ip4 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp4 {eth.dst = eth.src; eth.src = 00:00:00:00:ff:01; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 254; outport = "lr0-sw0"; flags.loopback = 1; output; };)
++ table=??(lr_in_ip_input ), priority=30 , match=(inport == "lr0-sw0" && ip6 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp6 {eth.dst = eth.src; eth.src = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 254; icmp6.type = 3; /* Time exceeded */ icmp6.code = 0; /* TTL exceeded in transit */ outport = "lr0-sw0"; flags.loopback = 1; output; };)
++ table=??(lr_in_ip_input ), priority=30 , match=(inport == "lr0-sw1" && ip4 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp4 {eth.dst = eth.src; eth.src = 00:00:00:00:ff:03; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 254; outport = "lr0-sw1"; flags.loopback = 1; output; };)
++ table=??(lr_in_ip_input ), priority=30 , match=(inport == "lr0-sw1" && ip6 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp6 {eth.dst = eth.src; eth.src = 00:00:00:00:ff:03; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff03; ip.ttl = 254; icmp6.type = 3; /* Time exceeded */ icmp6.code = 0; /* TTL exceeded in transit */ outport = "lr0-sw1"; flags.loopback = 1; output; };)
++ table=??(lr_in_ip_input ), priority=31 , match=(inport == "lr0-public" && ip4 && ip4.src == 172.168.0.0/24 && ip.ttl == {0, 1} && !ip.later_frag && is_chassis_resident("cr-lr0-public")), action=(icmp4 {eth.dst = eth.src; eth.src = 00:00:00:00:ff:02; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst = ip4.src; ip4.src = 172.168.0.10; ip.ttl = 254; outport = "lr0-public"; flags.loopback = 1; output; };)
++ table=??(lr_in_ip_input ), priority=31 , match=(inport == "lr0-sw0" && ip4 && ip4.src == 10.0.0.0/24 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp4 {eth.dst = eth.src; eth.src = 00:00:00:00:ff:01; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 254; outport = "lr0-sw0"; flags.loopback = 1; output; };)
++ table=??(lr_in_ip_input ), priority=31 , match=(inport == "lr0-sw1" && ip4 && ip4.src == 20.0.0.0/24 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp4 {eth.dst = eth.src; eth.src = 00:00:00:00:ff:03; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 254; outport = "lr0-sw1"; flags.loopback = 1; output; };)
+ table=??(lr_in_ip_input ), priority=32 , match=(ip.ttl == {0, 1} && !ip.later_frag && (ip4.mcast || ip6.mcast)), action=(drop;)
+ table=??(lr_in_ip_input ), priority=50 , match=(eth.bcast), action=(drop;)
+ table=??(lr_in_ip_input ), priority=60 , match=(ip4.dst == {10.0.0.1}), action=(drop;)
+@@ -14536,11 +14785,15 @@ AT_CHECK([grep "ls_in_l2_lkup" publicflows | ovn_strip_lflows], [0], [dnl
+ table=??(ls_in_l2_lkup ), priority=71 , match=(eth.mcast && ip), action=(outport = "_MC_flood_l2"; output;)
+ table=??(ls_in_l2_lkup ), priority=72 , match=(eth.mcast && (nd_na || nd_rs || nd_ra)), action=(outport = "_MC_flood"; output;)
+ table=??(ls_in_l2_lkup ), priority=75 , match=(eth.src == {00:00:00:00:ff:02, 30:54:00:00:00:03} && eth.dst == ff:ff:ff:ff:ff:ff && (arp.op == 1 || rarp.op == 3 || nd_ns)), action=(outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 172.168.0.10), action=(clone {outport = "public-lr0"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 172.168.0.100), action=(clone {outport = "public-lr0"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 172.168.0.110), action=(clone {outport = "public-lr0"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 172.168.0.120), action=(clone {outport = "public-lr0"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && nd_ns && nd.target == fe80::200:ff:fe00:ff02), action=(clone {outport = "public-lr0"; output; }; outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 172.168.0.10), action=(clone {outport = "public-lr0"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 172.168.0.100), action=(clone {outport = "public-lr0"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 172.168.0.110), action=(clone {outport = "public-lr0"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 172.168.0.120), action=(clone {outport = "public-lr0"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.mcast && nd_ns && nd.target == fe80::200:ff:fe00:ff02), action=(clone {outport = "public-lr0"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 172.168.0.10 && arp.spa == 172.168.0.10), action=(outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 172.168.0.100 && arp.spa == 172.168.0.100), action=(outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 172.168.0.110 && arp.spa == 172.168.0.110), action=(outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 172.168.0.120 && arp.spa == 172.168.0.120), action=(outport = "_MC_flood_l2"; output;)
+ ])
+
+ AT_CHECK([grep -Fe "172.168.0.110" -e "172.168.0.120" -e "10.0.0.3" -e "20.0.0.3" -e "30:54:00:00:00:03" -e "sw0-port1" lr0flows | ovn_strip_lflows], [0], [dnl
+@@ -14571,8 +14824,10 @@ AT_CHECK([grep -Fe "172.168.0.110" -e "172.168.0.120" -e "10.0.0.3" -e "20.0.0.3
+ AT_CHECK([grep -Fe "172.168.0.110" -e "172.168.0.120" -e "10.0.0.3" -e "20.0.0.3" -e "30:54:00:00:00:03" -e "sw0-port1" publicflows | ovn_strip_lflows], [0], [dnl
+ table=??(ls_in_l2_lkup ), priority=50 , match=(eth.dst == 30:54:00:00:00:03 && is_chassis_resident("sw0-port1")), action=(outport = "public-lr0"; output;)
+ table=??(ls_in_l2_lkup ), priority=75 , match=(eth.src == {00:00:00:00:ff:02, 30:54:00:00:00:03} && eth.dst == ff:ff:ff:ff:ff:ff && (arp.op == 1 || rarp.op == 3 || nd_ns)), action=(outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 172.168.0.110), action=(clone {outport = "public-lr0"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 172.168.0.120), action=(clone {outport = "public-lr0"; output; }; outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 172.168.0.110), action=(clone {outport = "public-lr0"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 172.168.0.120), action=(clone {outport = "public-lr0"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 172.168.0.110 && arp.spa == 172.168.0.110), action=(outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 172.168.0.120 && arp.spa == 172.168.0.120), action=(outport = "_MC_flood_l2"; output;)
+ ])
+ }
+
+@@ -14605,15 +14860,15 @@ AT_CHECK([grep "lr_in_ip_input" lr0flows | ovn_strip_lflows], [0], [dnl
+ table=??(lr_in_ip_input ), priority=100 , match=(ip4.src_mcast ||ip4.src == 255.255.255.255 || ip4.src == 127.0.0.0/8 || ip4.dst == 127.0.0.0/8 || ip4.src == 0.0.0.0/8 || ip4.dst == 0.0.0.0/8), action=(drop;)
+ table=??(lr_in_ip_input ), priority=120 , match=(inport == "lr0-public" && ip4.src == 172.168.0.100), action=(next;)
+ table=??(lr_in_ip_input ), priority=29 , match=(ip.ttl == {0, 1}), action=(drop;)
+- table=??(lr_in_ip_input ), priority=30 , match=(inport == "lr0-public" && ip4 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp4 {eth.dst <-> eth.src; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst = ip4.src; ip4.src = 172.168.0.10; ip.ttl = 254; outport = "lr0-public"; flags.loopback = 1; output; };)
+- table=??(lr_in_ip_input ), priority=30 , match=(inport == "lr0-public" && ip6 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp6 {eth.dst <-> eth.src; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 254; icmp6.type = 3; /* Time exceeded */ icmp6.code = 0; /* TTL exceeded in transit */ outport = "lr0-public"; flags.loopback = 1; output; };)
+- table=??(lr_in_ip_input ), priority=30 , match=(inport == "lr0-sw0" && ip4 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp4 {eth.dst <-> eth.src; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 254; outport = "lr0-sw0"; flags.loopback = 1; output; };)
+- table=??(lr_in_ip_input ), priority=30 , match=(inport == "lr0-sw0" && ip6 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp6 {eth.dst <-> eth.src; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 254; icmp6.type = 3; /* Time exceeded */ icmp6.code = 0; /* TTL exceeded in transit */ outport = "lr0-sw0"; flags.loopback = 1; output; };)
+- table=??(lr_in_ip_input ), priority=30 , match=(inport == "lr0-sw1" && ip4 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp4 {eth.dst <-> eth.src; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 254; outport = "lr0-sw1"; flags.loopback = 1; output; };)
+- table=??(lr_in_ip_input ), priority=30 , match=(inport == "lr0-sw1" && ip6 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp6 {eth.dst <-> eth.src; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff03; ip.ttl = 254; icmp6.type = 3; /* Time exceeded */ icmp6.code = 0; /* TTL exceeded in transit */ outport = "lr0-sw1"; flags.loopback = 1; output; };)
+- table=??(lr_in_ip_input ), priority=31 , match=(inport == "lr0-public" && ip4 && ip4.src == 172.168.0.0/24 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp4 {eth.dst <-> eth.src; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst <-> ip4.src ; ip.ttl = 254; outport = "lr0-public"; flags.loopback = 1; output; };)
+- table=??(lr_in_ip_input ), priority=31 , match=(inport == "lr0-sw0" && ip4 && ip4.src == 10.0.0.0/24 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp4 {eth.dst <-> eth.src; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst = ip4.src; ip4.src = 10.0.0.1 ; ip.ttl = 254; outport = "lr0-sw0"; flags.loopback = 1; output; };)
+- table=??(lr_in_ip_input ), priority=31 , match=(inport == "lr0-sw1" && ip4 && ip4.src == 20.0.0.0/24 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp4 {eth.dst <-> eth.src; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst = ip4.src; ip4.src = 20.0.0.1 ; ip.ttl = 254; outport = "lr0-sw1"; flags.loopback = 1; output; };)
++ table=??(lr_in_ip_input ), priority=30 , match=(inport == "lr0-public" && ip4 && ip.ttl == {0, 1} && !ip.later_frag && is_chassis_resident("cr-lr0-public")), action=(icmp4 {eth.dst = eth.src; eth.src = 00:00:00:00:ff:02; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst = ip4.src; ip4.src = 172.168.0.10; ip.ttl = 254; outport = "lr0-public"; flags.loopback = 1; output; };)
++ table=??(lr_in_ip_input ), priority=30 , match=(inport == "lr0-public" && ip6 && ip.ttl == {0, 1} && !ip.later_frag && is_chassis_resident("cr-lr0-public")), action=(icmp6 {eth.dst = eth.src; eth.src = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 254; icmp6.type = 3; /* Time exceeded */ icmp6.code = 0; /* TTL exceeded in transit */ outport = "lr0-public"; flags.loopback = 1; output; };)
++ table=??(lr_in_ip_input ), priority=30 , match=(inport == "lr0-sw0" && ip4 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp4 {eth.dst = eth.src; eth.src = 00:00:00:00:ff:01; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 254; outport = "lr0-sw0"; flags.loopback = 1; output; };)
++ table=??(lr_in_ip_input ), priority=30 , match=(inport == "lr0-sw0" && ip6 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp6 {eth.dst = eth.src; eth.src = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 254; icmp6.type = 3; /* Time exceeded */ icmp6.code = 0; /* TTL exceeded in transit */ outport = "lr0-sw0"; flags.loopback = 1; output; };)
++ table=??(lr_in_ip_input ), priority=30 , match=(inport == "lr0-sw1" && ip4 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp4 {eth.dst = eth.src; eth.src = 00:00:00:00:ff:03; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 254; outport = "lr0-sw1"; flags.loopback = 1; output; };)
++ table=??(lr_in_ip_input ), priority=30 , match=(inport == "lr0-sw1" && ip6 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp6 {eth.dst = eth.src; eth.src = 00:00:00:00:ff:03; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff03; ip.ttl = 254; icmp6.type = 3; /* Time exceeded */ icmp6.code = 0; /* TTL exceeded in transit */ outport = "lr0-sw1"; flags.loopback = 1; output; };)
++ table=??(lr_in_ip_input ), priority=31 , match=(inport == "lr0-public" && ip4 && ip4.src == 172.168.0.0/24 && ip.ttl == {0, 1} && !ip.later_frag && is_chassis_resident("cr-lr0-public")), action=(icmp4 {eth.dst = eth.src; eth.src = 00:00:00:00:ff:02; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst = ip4.src; ip4.src = 172.168.0.10; ip.ttl = 254; outport = "lr0-public"; flags.loopback = 1; output; };)
++ table=??(lr_in_ip_input ), priority=31 , match=(inport == "lr0-sw0" && ip4 && ip4.src == 10.0.0.0/24 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp4 {eth.dst = eth.src; eth.src = 00:00:00:00:ff:01; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 254; outport = "lr0-sw0"; flags.loopback = 1; output; };)
++ table=??(lr_in_ip_input ), priority=31 , match=(inport == "lr0-sw1" && ip4 && ip4.src == 20.0.0.0/24 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp4 {eth.dst = eth.src; eth.src = 00:00:00:00:ff:03; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 254; outport = "lr0-sw1"; flags.loopback = 1; output; };)
+ table=??(lr_in_ip_input ), priority=32 , match=(ip.ttl == {0, 1} && !ip.later_frag && (ip4.mcast || ip6.mcast)), action=(drop;)
+ table=??(lr_in_ip_input ), priority=50 , match=(eth.bcast), action=(drop;)
+ table=??(lr_in_ip_input ), priority=60 , match=(ip4.dst == {10.0.0.1}), action=(drop;)
+@@ -14716,16 +14971,20 @@ AT_CHECK([grep "ls_in_l2_lkup" publicflows | ovn_strip_lflows], [0], [dnl
+ table=??(ls_in_l2_lkup ), priority=71 , match=(eth.mcast && ip), action=(outport = "_MC_flood_l2"; output;)
+ table=??(ls_in_l2_lkup ), priority=72 , match=(eth.mcast && (nd_na || nd_rs || nd_ra)), action=(outport = "_MC_flood"; output;)
+ table=??(ls_in_l2_lkup ), priority=75 , match=(eth.src == {00:00:00:00:ff:02, 30:54:00:00:00:03} && eth.dst == ff:ff:ff:ff:ff:ff && (arp.op == 1 || rarp.op == 3 || nd_ns)), action=(outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 172.168.0.10 && !is_chassis_resident("cr-public-lr0")), action=(clone {outport = "cr-public-lr0"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 172.168.0.10 && is_chassis_resident("cr-public-lr0")), action=(clone {outport = "public-lr0"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 172.168.0.100 && !is_chassis_resident("cr-public-lr0")), action=(clone {outport = "cr-public-lr0"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 172.168.0.100 && is_chassis_resident("cr-public-lr0")), action=(clone {outport = "public-lr0"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 172.168.0.110 && !is_chassis_resident("cr-public-lr0")), action=(clone {outport = "cr-public-lr0"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 172.168.0.110 && is_chassis_resident("cr-public-lr0")), action=(clone {outport = "public-lr0"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 172.168.0.120 && !is_chassis_resident("cr-public-lr0")), action=(clone {outport = "cr-public-lr0"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 172.168.0.120 && is_chassis_resident("cr-public-lr0")), action=(clone {outport = "public-lr0"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && nd_ns && nd.target == fe80::200:ff:fe00:ff02 && !is_chassis_resident("cr-public-lr0")), action=(clone {outport = "cr-public-lr0"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && nd_ns && nd.target == fe80::200:ff:fe00:ff02 && is_chassis_resident("cr-public-lr0")), action=(clone {outport = "public-lr0"; output; }; outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 172.168.0.10 && !is_chassis_resident("cr-public-lr0")), action=(clone {outport = "cr-public-lr0"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 172.168.0.10 && is_chassis_resident("cr-public-lr0")), action=(clone {outport = "public-lr0"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 172.168.0.100 && !is_chassis_resident("cr-public-lr0")), action=(clone {outport = "cr-public-lr0"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 172.168.0.100 && is_chassis_resident("cr-public-lr0")), action=(clone {outport = "public-lr0"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 172.168.0.110 && !is_chassis_resident("cr-public-lr0")), action=(clone {outport = "cr-public-lr0"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 172.168.0.110 && is_chassis_resident("cr-public-lr0")), action=(clone {outport = "public-lr0"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 172.168.0.120 && !is_chassis_resident("cr-public-lr0")), action=(clone {outport = "cr-public-lr0"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 172.168.0.120 && is_chassis_resident("cr-public-lr0")), action=(clone {outport = "public-lr0"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.mcast && nd_ns && nd.target == fe80::200:ff:fe00:ff02 && !is_chassis_resident("cr-public-lr0")), action=(clone {outport = "cr-public-lr0"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.mcast && nd_ns && nd.target == fe80::200:ff:fe00:ff02 && is_chassis_resident("cr-public-lr0")), action=(clone {outport = "public-lr0"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 172.168.0.10 && arp.spa == 172.168.0.10), action=(outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 172.168.0.100 && arp.spa == 172.168.0.100), action=(outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 172.168.0.110 && arp.spa == 172.168.0.110), action=(outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 172.168.0.120 && arp.spa == 172.168.0.120), action=(outport = "_MC_flood_l2"; output;)
+ ])
+
+ AT_CHECK([grep -Fe "172.168.0.110" -e "172.168.0.120" -e "10.0.0.3" -e "20.0.0.3" -e "30:54:00:00:00:03" -e "sw0-port1" lr0flows | ovn_strip_lflows], [0], [dnl
+@@ -14750,10 +15009,12 @@ AT_CHECK([grep -Fe "172.168.0.110" -e "172.168.0.120" -e "10.0.0.3" -e "20.0.0.3
+ AT_CHECK([grep -Fe "172.168.0.110" -e "172.168.0.120" -e "10.0.0.3" -e "20.0.0.3" -e "30:54:00:00:00:03" -e "sw0-port1" publicflows | ovn_strip_lflows], [0], [dnl
+ table=??(ls_in_l2_lkup ), priority=50 , match=(eth.dst == 30:54:00:00:00:03 && is_chassis_resident("sw0-port1")), action=(outport = "public-lr0"; output;)
+ table=??(ls_in_l2_lkup ), priority=75 , match=(eth.src == {00:00:00:00:ff:02, 30:54:00:00:00:03} && eth.dst == ff:ff:ff:ff:ff:ff && (arp.op == 1 || rarp.op == 3 || nd_ns)), action=(outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 172.168.0.110 && !is_chassis_resident("cr-public-lr0")), action=(clone {outport = "cr-public-lr0"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 172.168.0.110 && is_chassis_resident("cr-public-lr0")), action=(clone {outport = "public-lr0"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 172.168.0.120 && !is_chassis_resident("cr-public-lr0")), action=(clone {outport = "cr-public-lr0"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 172.168.0.120 && is_chassis_resident("cr-public-lr0")), action=(clone {outport = "public-lr0"; output; }; outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 172.168.0.110 && !is_chassis_resident("cr-public-lr0")), action=(clone {outport = "cr-public-lr0"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 172.168.0.110 && is_chassis_resident("cr-public-lr0")), action=(clone {outport = "public-lr0"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 172.168.0.120 && !is_chassis_resident("cr-public-lr0")), action=(clone {outport = "cr-public-lr0"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 172.168.0.120 && is_chassis_resident("cr-public-lr0")), action=(clone {outport = "public-lr0"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 172.168.0.110 && arp.spa == 172.168.0.110), action=(outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 172.168.0.120 && arp.spa == 172.168.0.120), action=(outport = "_MC_flood_l2"; output;)
+ ])
+ }
+
+@@ -14817,8 +15078,10 @@ AT_CHECK([grep -Fe "172.168.0.110" -e "172.168.0.120" -e "10.0.0.3" -e "20.0.0.3
+ AT_CHECK([grep -Fe "172.168.0.110" -e "172.168.0.120" -e "10.0.0.3" -e "20.0.0.3" -e "30:54:00:00:00:03" -e "sw0-port1" publicflows | ovn_strip_lflows], [0], [dnl
+ table=??(ls_in_l2_lkup ), priority=50 , match=(eth.dst == 30:54:00:00:00:03 && is_chassis_resident("sw0-port1")), action=(outport = "public-lr0"; output;)
+ table=??(ls_in_l2_lkup ), priority=75 , match=(eth.src == {00:00:00:00:ff:02, 30:54:00:00:00:03} && eth.dst == ff:ff:ff:ff:ff:ff && (arp.op == 1 || rarp.op == 3 || nd_ns)), action=(outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 172.168.0.110), action=(clone {outport = "public-lr0"; output; }; outport = "_MC_flood_l2"; output;)
+- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 172.168.0.120), action=(clone {outport = "public-lr0"; output; }; outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 172.168.0.110), action=(clone {outport = "public-lr0"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 172.168.0.120), action=(clone {outport = "public-lr0"; output; }; outport = "_MC_unknown"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 172.168.0.110 && arp.spa == 172.168.0.110), action=(outport = "_MC_flood_l2"; output;)
++ table=??(ls_in_l2_lkup ), priority=90 , match=(flags[[1]] == 0 && eth.dst == ff:ff:ff:ff:ff:ff && arp.op == 1 && arp.tpa == 172.168.0.120 && arp.spa == 172.168.0.120), action=(outport = "_MC_flood_l2"; output;)
+ ])
+
+ OVN_CLEANUP_NORTHD
+@@ -14841,6 +15104,8 @@ check ovn-nbctl ls-add ls -- \
+ check ovn-nbctl lsp-add ls lsp-bgp -- \
+ lsp-set-addresses lsp-bgp unknown
+
++check ovn-nbctl --wait=sb sync
++
+ # Function that ensures that no redirect rules are installed.
+ check_no_redirect() {
+ AT_CHECK([ovn-sbctl dump-flows ls | grep ls_in_l2_lkup | grep -E "tcp.dst == 179|tcp.src == 179" | wc -l], [0], [0
+@@ -15480,6 +15745,7 @@ AT_CLEANUP
+ AT_SETUP([RBAC -- Recover builtin role and permissions])
+ ovn_start
+
++wait_row_count RBAC_Role 1 name=ovn-controller
+ RBR_BUILTIN_PNAMES=$(fetch_column RBAC_Role permissions name=ovn-controller | uuidfilt | sed -e 's/<[[^<>]]>*//g' | tr -d '=,{}')
+
+ declare -A RBR_BUILTIN_PERM
+@@ -15704,6 +15970,101 @@ OVN_CLEANUP([hv1], [hv2])
+ AT_CLEANUP
+ ])
+
++OVN_FOR_EACH_NORTHD_NO_HV([
++AT_SETUP([Multicast group incremental processing])
++ovn_start
++
++dnl Helper: verify that Multicast_Group 'mc_name' on datapath 'dp_name'
++dnl contains exactly the listed ports.
++dnl Usage: check_mc_group_ports MC_NAME DP_NAME PORT1 [PORT2 ...]
++check_mc_group_ports() {
++ mc_group_name=$1; shift
++ mc_group_dp=$(fetch_column Datapath_Binding _uuid external_ids:name=$1); shift
++ mc_group_expected=""
++ for port_name; do
++ mc_group_expected="$mc_group_expected $(fetch_column Port_Binding _uuid logical_port=$port_name)"
++ done
++ mc_group_expected=$(echo $mc_group_expected | tr ' ' '\n' | sort | xargs)
++
++ OVS_WAIT_UNTIL([
++ mc_group_found=$(fetch_column Multicast_Group ports name=$mc_group_name datapath=$mc_group_dp)
++ test "$mc_group_found" = "$mc_group_expected"
++ ])
++}
++
++check ovn-nbctl --wait=sb ls-add ls0
++check ovn-nbctl --wait=sb lsp-add-localnet-port ls0 ln0 physnet0
++
++dnl Baseline: localnet port only.
++check_mc_group_ports _MC_flood ls0 ln0
++check_mc_group_ports _MC_flood_l2 ls0 ln0
++CHECK_NO_CHANGE_AFTER_RECOMPUTE
++
++dnl Add an enabled VIF.
++check ovn-nbctl --wait=sb lsp-add ls0 lsp0
++check_mc_group_ports _MC_flood ls0 ln0 lsp0
++check_mc_group_ports _MC_flood_l2 ls0 ln0 lsp0
++CHECK_NO_CHANGE_AFTER_RECOMPUTE
++
++dnl Add a disabled VIF; it must NOT appear in multicast groups.
++check ovn-nbctl --wait=sb \
++ lsp-add ls0 lsp1 \
++ -- set logical_switch_port lsp1 enabled=false
++check_mc_group_ports _MC_flood ls0 ln0 lsp0
++check_mc_group_ports _MC_flood_l2 ls0 ln0 lsp0
++CHECK_NO_CHANGE_AFTER_RECOMPUTE
++
++dnl Enable the disabled VIF.
++check ovn-nbctl --wait=sb set logical_switch_port lsp1 enabled=true
++check_mc_group_ports _MC_flood ls0 ln0 lsp0 lsp1
++check_mc_group_ports _MC_flood_l2 ls0 ln0 lsp0 lsp1
++CHECK_NO_CHANGE_AFTER_RECOMPUTE
++
++dnl Disable an enabled VIF.
++check ovn-nbctl --wait=sb set logical_switch_port lsp0 enabled=false
++check_mc_group_ports _MC_flood ls0 ln0 lsp1
++check_mc_group_ports _MC_flood_l2 ls0 ln0 lsp1
++CHECK_NO_CHANGE_AFTER_RECOMPUTE
++
++dnl Re-enable it.
++check ovn-nbctl --wait=sb set logical_switch_port lsp0 enabled=true
++check_mc_group_ports _MC_flood ls0 ln0 lsp0 lsp1
++check_mc_group_ports _MC_flood_l2 ls0 ln0 lsp0 lsp1
++CHECK_NO_CHANGE_AFTER_RECOMPUTE
++
++dnl Delete an enabled VIF.
++check ovn-nbctl --wait=sb lsp-del lsp0
++check_mc_group_ports _MC_flood ls0 ln0 lsp1
++check_mc_group_ports _MC_flood_l2 ls0 ln0 lsp1
++CHECK_NO_CHANGE_AFTER_RECOMPUTE
++
++dnl Add a router and connect it to ls0. The router LSP should appear
++dnl in _MC_flood but NOT in _MC_flood_l2 (router ports are excluded
++dnl from _MC_flood_l2).
++check ovn-nbctl --wait=sb \
++ lr-add lr0 \
++ -- lrp-add lr0 lr0-ls0 00:00:00:00:01:00 10.0.0.254/24 \
++ -- lsp-add-router-port ls0 ls0-lr0 lr0-ls0
++check_mc_group_ports _MC_flood ls0 ln0 lsp1 ls0-lr0
++check_mc_group_ports _MC_flood_l2 ls0 ln0 lsp1
++CHECK_NO_CHANGE_AFTER_RECOMPUTE
++
++dnl Add a second switch connected to the router, with a localnet port
++dnl and a VIF.
++check ovn-nbctl --wait=sb \
++ ls-add ls1 \
++ -- lsp-add-localnet-port ls1 ln1 physnet1 \
++ -- lrp-add lr0 lr0-ls1 00:00:00:00:02:00 10.0.1.254/24 \
++ -- lsp-add-router-port ls1 ls1-lr0 lr0-ls1 \
++ -- lsp-add ls1 lsp2
++check_mc_group_ports _MC_flood ls1 ln1 ls1-lr0 lsp2
++check_mc_group_ports _MC_flood_l2 ls1 ln1 lsp2
++CHECK_NO_CHANGE_AFTER_RECOMPUTE
++
++OVN_CLEANUP_NORTHD
++AT_CLEANUP
++])
++
+ OVN_FOR_EACH_NORTHD_NO_HV([
+ AT_SETUP([Transit router - remote ports])
+ ovn_start
+@@ -18882,7 +19243,7 @@ check ovn-nbctl ls-lb-add ls1 lb1_ipv4
+ check ovn-nbctl lsp-add ls1 lport1
+ check ovn-nbctl lsp-add ls1 sw0-p1
+
+-check ovn-nbctl set load_balancer lb1_ipv4 ip_port_mappings:192.168.0.1=lport1:192.168.0.99
++check ovn-nbctl --wait=sb set load_balancer lb1_ipv4 ip_port_mappings:192.168.0.1=lport1:192.168.0.99
+
+ # Check that the service monitor was created correctly.
+ check_row_count sb:Service_Monitor 1
+@@ -18895,19 +19256,19 @@ check_column false sb:Service_Monitor remote logical_port=lport1
+
+ # Empty src_ip.
+ check ovn-nbctl clear load_balancer lb1_ipv4 ip_port_mappings
+-check ovn-nbctl set load_balancer lb1_ipv4 ip_port_mappings:192.168.0.1=lport1:
++check ovn-nbctl --wait=sb set load_balancer lb1_ipv4 ip_port_mappings:192.168.0.1=lport1:
+ check_row_count sb:Service_Monitor 0
+
+ # Incorrect ip_address.
+-check ovn-nbctl set load_balancer lb1_ipv4 ip_port_mappings:invalid=lport2_az1:2.2.2.9
++check ovn-nbctl --wait=sb set load_balancer lb1_ipv4 ip_port_mappings:invalid=lport2_az1:2.2.2.9
+ check_row_count sb:Service_Monitor 0
+-check ovn-nbctl set load_balancer lb1_ipv4 ip_port_mappings:2.2.2.1=lport2_az1:invalid
++check ovn-nbctl --wait=sb set load_balancer lb1_ipv4 ip_port_mappings:2.2.2.1=lport2_az1:invalid
+ check_row_count sb:Service_Monitor 0
+-check ovn-nbctl set load_balancer lb1_ipv4 ip_port_mappings:2.2.2.1=:2.2.2.9
++check ovn-nbctl --wait=sb set load_balancer lb1_ipv4 ip_port_mappings:2.2.2.1=:2.2.2.9
+ check_row_count sb:Service_Monitor 0
+ OVS_WAIT_UNTIL([test $(grep -c "Invalid svc mon src IP" northd/ovn-northd.log) -eq 4])
+
+-check ovn-nbctl set load_balancer lb1_ipv4 ip_port_mappings:192.168.0.1=lport1:192.168.0.99:az_name
++check ovn-nbctl --wait=sb set load_balancer lb1_ipv4 ip_port_mappings:192.168.0.1=lport1:192.168.0.99:az_name
+
+ check_row_count sb:Service_Monitor 1
+ check_column "192.168.0.1" sb:Service_Monitor ip logical_port=lport1
+@@ -18929,7 +19290,7 @@ AT_CHECK([ovn-sbctl get Service_Monitor ${hc_uuid} options:failure_count],
+ ])
+
+ # Empty availability zone name.
+-check ovn-nbctl set load_balancer lb1_ipv4 ip_port_mappings:192.168.0.1=lport1:192.168.0.99:
++check ovn-nbctl --wait=sb set load_balancer lb1_ipv4 ip_port_mappings:192.168.0.1=lport1:192.168.0.99:
+ check_row_count sb:Service_Monitor 0
+ OVS_WAIT_UNTIL([grep "Empty AZ name specified" northd/ovn-northd.log])
+
+@@ -18953,7 +19314,7 @@ AT_CHECK([cat lr1_lflow | grep lr_in_dnat | grep priority=120 | ovn_strip_lflows
+ table=??(lr_in_dnat ), priority=120 , match=(ct.new && !ct.rel && ip4 && ip4.dst == 1.1.1.1 && reg1[[16..23]] == 6 && reg1[[0..15]] == 80), action=(ct_lb_mark_local(backends="lport1":192.168.0.1:10880);)
+ ])
+
+-check ovn-nbctl set load_balancer lb_distubuted ip_port_mappings:192.168.0.2=lport2
++check ovn-nbctl --wait=sb set load_balancer lb_distubuted ip_port_mappings:192.168.0.2=lport2
+ ovn-sbctl lflow-list lr1 > lr1_lflow
+ AT_CHECK([cat lr1_lflow | grep lr_in_dnat | grep priority=120 | ovn_strip_lflows], [0], [dnl
+ table=??(lr_in_dnat ), priority=120 , match=(ct.new && !ct.rel && ip4 && ip4.dst == 1.1.1.1 && reg1[[16..23]] == 6 && reg1[[0..15]] == 80), action=(ct_lb_mark_local(backends="lport1":192.168.0.1:10880,"lport2":192.168.0.2:10880);)
+@@ -19377,10 +19738,10 @@ check ovn-nbctl ls-add sw0
+ check ovn-nbctl lsp-add sw0 sw0-nf-p1
+ check ovn-nbctl lsp-add sw0 sw0-nf-p2
+ check ovn-nbctl set logical_switch_port sw0-nf-p1 \
+- options:receive_multicast=false options:lsp_learn_mac=false \
++ options:receive_multicast=false options:lsp_learn_fdb=false \
+ options:is-nf=true options:nf-linked-port=sw0-nf-p2
+ check ovn-nbctl set logical_switch_port sw0-nf-p2 \
+- options:receive_multicast=false options:lsp_learn_mac=false \
++ options:receive_multicast=false options:lsp_learn_fdb=false \
+ options:is-nf=true options:nf-linked-port=sw0-nf-p1
+ check ovn-nbctl nf-add nf0 101 sw0-nf-p1 sw0-nf-p2
+ check ovn-nbctl nfg-add nfg0 201 inline nf0
+@@ -19499,10 +19860,10 @@ AS_BOX([Create another NF and add it to a to-lport ACL.])
+ check ovn-nbctl lsp-add sw0 sw0-nf-p3
+ check ovn-nbctl lsp-add sw0 sw0-nf-p4
+ check ovn-nbctl set logical_switch_port sw0-nf-p3 \
+- options:receive_multicast=false options:lsp_learn_mac=false \
++ options:receive_multicast=false options:lsp_learn_fdb=false \
+ options:is-nf=true options:nf-linked-port=sw0-nf-p4
+ check ovn-nbctl set logical_switch_port sw0-nf-p4 \
+- options:receive_multicast=false options:lsp_learn_mac=false \
++ options:receive_multicast=false options:lsp_learn_fdb=false \
+ options:is-nf=true options:nf-linked-port=sw0-nf-p3
+ check ovn-nbctl nf-add nf1 102 sw0-nf-p3 sw0-nf-p4
+ check ovn-nbctl nfg-add nfg1 202 inline nf1
+@@ -19580,16 +19941,16 @@ check ovn-nbctl lsp-add sw1 sw1-nf-p2 sw0-nf-p2 100
+ check ovn-nbctl lsp-add sw1 sw1-nf-p3 sw0-nf-p3 100
+ check ovn-nbctl lsp-add sw1 sw1-nf-p4 sw0-nf-p4 100
+ check ovn-nbctl set logical_switch_port sw1-nf-p1 \
+- options:receive_multicast=false options:lsp_learn_mac=false \
++ options:receive_multicast=false options:lsp_learn_fdb=false \
+ options:is-nf=true options:nf-linked-port=sw1-nf-p2
+ check ovn-nbctl set logical_switch_port sw1-nf-p2 \
+- options:receive_multicast=false options:lsp_learn_mac=false \
++ options:receive_multicast=false options:lsp_learn_fdb=false \
+ options:is-nf=true options:nf-linked-port=sw1-nf-p1
+ check ovn-nbctl set logical_switch_port sw1-nf-p3 \
+- options:receive_multicast=false options:lsp_learn_mac=false \
++ options:receive_multicast=false options:lsp_learn_fdb=false \
+ options:is-nf=true options:nf-linked-port=sw1-nf-p4
+ check ovn-nbctl set logical_switch_port sw1-nf-p4 \
+- options:receive_multicast=false options:lsp_learn_mac=false \
++ options:receive_multicast=false options:lsp_learn_fdb=false \
+ options:is-nf=true options:nf-linked-port=sw1-nf-p3
+ check ovn-nbctl --wait=sb sync
+
+@@ -19996,6 +20357,35 @@ OVN_CLEANUP_NORTHD
+ AT_CLEANUP
+ ])
+
++OVN_FOR_EACH_NORTHD_NO_HV([
++AT_SETUP([ovn-northd - broadcast restriction on rport ARP/ND req flows in l2_lkup])
++AT_KEYWORDS([ovn])
++ovn_start
++
++check ovn-nbctl lr-add lr0
++check ovn-nbctl lrp-add lr0 lr0-ls0 00:00:00:00:00:01 10.0.0.1/24 aef0::1/64
++
++check ovn-nbctl ls-add ls0
++check ovn-nbctl lsp-add-router-port ls0 ls0-lr0 lr0-ls0
++
++check ovn-nbctl lsp-add ls0 lsp1 \
++ -- lsp-set-addresses lsp1 "00:00:00:00:00:02 10.0.0.2 aef0::2"
++
++check ovn-nbctl --wait=sb sync
++
++dnl Priority 80 IPv4 ARP request flow must restrict to broadcast.
++AT_CHECK([ovn-sbctl dump-flows ls0 | grep ls_in_l2_lkup | grep "priority=80" | grep "arp.tpa == 10.0.0.1" | grep -q "eth.dst == ff:ff:ff:ff:ff:ff"])
++
++dnl Priority 90 GARP flow must also restrict to broadcast.
++AT_CHECK([ovn-sbctl dump-flows ls0 | grep ls_in_l2_lkup | grep "priority=90" | grep "arp.spa == 10.0.0.1" | grep -q "eth.dst == ff:ff:ff:ff:ff:ff"])
++
++dnl Priority 80 IPv6 ND_NS flow must restrict to multicast.
++AT_CHECK([ovn-sbctl dump-flows ls0 | grep ls_in_l2_lkup | grep "priority=80" | grep "nd.target == aef0::1" | grep -q "eth.mcast"])
++
++OVN_CLEANUP_NORTHD
++AT_CLEANUP
++])
++
+ OVN_FOR_EACH_NORTHD_NO_HV([
+ AT_SETUP([LS EVPN Advertised_MAC_Binding sync])
+ ovn_start
+@@ -20733,3 +21123,99 @@ check_column "$global_svc_mon_mac" sb:Service_Monitor src_mac port=2
+ OVN_CLEANUP_NORTHD
+ AT_CLEANUP
+ ])
++
++OVN_FOR_EACH_NORTHD_NO_HV([
++AT_SETUP([ovn-northd - engine stopwatches enable/disable])
++AT_KEYWORDS([ovn])
++ovn_start
++
++dnl By default, engine stopwatches are disabled. Trigger a recompute to
++dnl ensure the engine runs, then verify that no samples were collected.
++check as northd ovn-appctl -t ovn-northd inc-engine/recompute
++check ovn-nbctl --wait=sb sync
++AT_CHECK([as northd ovn-appctl -t ovn-northd stopwatch/show northd], [0], [dnl
++Statistics for 'northd'
++ Total samples: 0
++ Maximum: 0 msec
++ Minimum: 0 msec
++ 95th percentile: 0.000000 msec
++ Short term average: 0.000000 msec
++ Long term average: 0.000000 msec
++])
++
++dnl Enable stopwatches only for the "northd" node.
++check as northd ovn-appctl -t ovn-northd inc-engine/enable-stopwatch northd
++check as northd ovn-appctl -t ovn-northd inc-engine/recompute
++check ovn-nbctl --wait=sb sync
++
++dnl The "northd" stopwatch should have collected samples.
++OVS_WAIT_UNTIL([
++ stats=$(as northd ovn-appctl -t ovn-northd stopwatch/show northd)
++ echo "$stats" | grep -q 'Total samples: [[1-9]]'
++])
++
++dnl The "lr_nat" stopwatch should still have no samples.
++AT_CHECK([as northd ovn-appctl -t ovn-northd stopwatch/show lr_nat], [0], [dnl
++Statistics for 'lr_nat'
++ Total samples: 0
++ Maximum: 0 msec
++ Minimum: 0 msec
++ 95th percentile: 0.000000 msec
++ Short term average: 0.000000 msec
++ Long term average: 0.000000 msec
++])
++
++dnl Enable stopwatches globally (no argument).
++check as northd ovn-appctl -t ovn-northd inc-engine/enable-stopwatch
++check as northd ovn-appctl -t ovn-northd stopwatch/reset
++check as northd ovn-appctl -t ovn-northd inc-engine/recompute
++check ovn-nbctl --wait=sb sync
++
++dnl Now "lr_nat" should also have collected samples.
++OVS_WAIT_UNTIL([
++ stats=$(as northd ovn-appctl -t ovn-northd stopwatch/show lr_nat)
++ echo "$stats" | grep -q 'Total samples: [[1-9]]'
++])
++
++dnl Disable stopwatches only for the "northd" node.
++check as northd ovn-appctl -t ovn-northd inc-engine/disable-stopwatch northd
++check as northd ovn-appctl -t ovn-northd stopwatch/reset
++check as northd ovn-appctl -t ovn-northd inc-engine/recompute
++check ovn-nbctl --wait=sb sync
++
++dnl Verify "northd" has no new samples but "lr_nat" still collects.
++AT_CHECK([as northd ovn-appctl -t ovn-northd stopwatch/show northd], [0], [dnl
++Statistics for 'northd'
++ Total samples: 0
++ Maximum: 0 msec
++ Minimum: 0 msec
++ 95th percentile: 0.000000 msec
++ Short term average: 0.000000 msec
++ Long term average: 0.000000 msec
++])
++OVS_WAIT_UNTIL([
++ stats=$(as northd ovn-appctl -t ovn-northd stopwatch/show lr_nat)
++ echo "$stats" | grep -q 'Total samples: [[1-9]]'
++])
++
++dnl Disable stopwatches globally and verify no samples are collected.
++check as northd ovn-appctl -t ovn-northd inc-engine/disable-stopwatch
++check as northd ovn-appctl -t ovn-northd stopwatch/reset
++check as northd ovn-appctl -t ovn-northd inc-engine/recompute
++check ovn-nbctl --wait=sb sync
++AT_CHECK([as northd ovn-appctl -t ovn-northd stopwatch/show northd], [0], [dnl
++Statistics for 'northd'
++ Total samples: 0
++ Maximum: 0 msec
++ Minimum: 0 msec
++ 95th percentile: 0.000000 msec
++ Short term average: 0.000000 msec
++ Long term average: 0.000000 msec
++])
++
++dnl Error case: non-existent node name.
++AT_CHECK([as northd ovn-appctl -t ovn-northd inc-engine/enable-stopwatch nonexistent_node], [2], [], [ignore])
++
++OVN_CLEANUP_NORTHD
++AT_CLEANUP
++])
+diff --git a/tests/ovn-util.at b/tests/ovn-util.at
+index 004799b4c6..76bb568e73 100644
+--- a/tests/ovn-util.at
++++ b/tests/ovn-util.at
+@@ -1,5 +1,4 @@
+ AT_SETUP([ovn-detrace - simple scenario])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
+ ovn_start
+ net_add n1
+
+diff --git a/tests/ovn.at b/tests/ovn.at
+index 22d0e21766..84d4f78203 100644
+--- a/tests/ovn.at
++++ b/tests/ovn.at
+@@ -1621,10 +1621,10 @@ nd_na_router { eth.src = 12:34:56:78:9a:bc; nd.tll = 12:34:56:78:9a:bc; outport
+
+ # get_nd
+ get_nd(outport, ip6.dst);
+- encodes as push:NXM_NX_XXREG1[[]],push:NXM_NX_IPV6_DST[[]],pop:NXM_NX_XXREG1[[]],set_field:00:00:00:00:00:00->eth_dst,resubmit(,66),pop:NXM_NX_XXREG1[[]]
++ encodes as push:NXM_NX_XXREG1[[]],push:NXM_NX_IPV6_DST[[]],pop:NXM_NX_XXREG1[[]],set_field:00:00:00:00:00:00->eth_dst,resubmit(,OFTABLE_MAC_BINDING),pop:NXM_NX_XXREG1[[]]
+ has prereqs eth.type == 0x86dd
+ get_nd(inport, xxreg0);
+- encodes as push:NXM_NX_REG15[[]],push:NXM_NX_XXREG1[[]],push:NXM_NX_XXREG0[[]],push:NXM_NX_REG14[[]],pop:NXM_NX_REG15[[]],pop:NXM_NX_XXREG1[[]],set_field:00:00:00:00:00:00->eth_dst,resubmit(,66),pop:NXM_NX_XXREG1[[]],pop:NXM_NX_REG15[[]]
++ encodes as push:NXM_NX_REG15[[]],push:NXM_NX_XXREG1[[]],push:NXM_NX_XXREG0[[]],push:NXM_NX_REG14[[]],pop:NXM_NX_REG15[[]],pop:NXM_NX_XXREG1[[]],set_field:00:00:00:00:00:00->eth_dst,resubmit(,OFTABLE_MAC_BINDING),pop:NXM_NX_XXREG1[[]],pop:NXM_NX_REG15[[]]
+ get_nd;
+ Syntax error at `;' expecting `('.
+ get_nd();
+@@ -1711,10 +1711,10 @@ reg0[[0]] = lookup_nd(inport, ip6.src, ip6.dst);
+
+ # lookup_nd_ip
+ reg2[[0]] = lookup_nd_ip(inport, ip6.dst);
+- encodes as push:NXM_NX_REG15[[]],push:NXM_NX_XXREG1[[]],push:NXM_NX_IPV6_DST[[]],push:NXM_NX_REG14[[]],pop:NXM_NX_REG15[[]],pop:NXM_NX_XXREG1[[]],push:NXM_OF_ETH_DST[[]],set_field:0/0x40->reg10,resubmit(,66),move:NXM_NX_REG10[[6]]->NXM_NX_XXREG0[[32]],pop:NXM_OF_ETH_DST[[]],pop:NXM_NX_XXREG1[[]],pop:NXM_NX_REG15[[]]
++ encodes as push:NXM_NX_REG15[[]],push:NXM_NX_XXREG1[[]],push:NXM_NX_IPV6_DST[[]],push:NXM_NX_REG14[[]],pop:NXM_NX_REG15[[]],pop:NXM_NX_XXREG1[[]],push:NXM_OF_ETH_DST[[]],set_field:0/0x40->reg10,resubmit(,OFTABLE_MAC_BINDING),move:NXM_NX_REG10[[6]]->NXM_NX_XXREG0[[32]],pop:NXM_OF_ETH_DST[[]],pop:NXM_NX_XXREG1[[]],pop:NXM_NX_REG15[[]]
+ has prereqs eth.type == 0x86dd
+ reg3[[0]] = lookup_nd_ip(inport, nd.target);
+- encodes as push:NXM_NX_REG15[[]],push:NXM_NX_XXREG1[[]],push:NXM_NX_ND_TARGET[[]],push:NXM_NX_REG14[[]],pop:NXM_NX_REG15[[]],pop:NXM_NX_XXREG1[[]],push:NXM_OF_ETH_DST[[]],set_field:0/0x40->reg10,resubmit(,66),move:NXM_NX_REG10[[6]]->NXM_NX_XXREG0[[0]],pop:NXM_OF_ETH_DST[[]],pop:NXM_NX_XXREG1[[]],pop:NXM_NX_REG15[[]]
++ encodes as push:NXM_NX_REG15[[]],push:NXM_NX_XXREG1[[]],push:NXM_NX_ND_TARGET[[]],push:NXM_NX_REG14[[]],pop:NXM_NX_REG15[[]],pop:NXM_NX_XXREG1[[]],push:NXM_OF_ETH_DST[[]],set_field:0/0x40->reg10,resubmit(,OFTABLE_MAC_BINDING),move:NXM_NX_REG10[[6]]->NXM_NX_XXREG0[[0]],pop:NXM_OF_ETH_DST[[]],pop:NXM_NX_XXREG1[[]],pop:NXM_NX_REG15[[]]
+ has prereqs (icmp6.type == 0x87 || icmp6.type == 0x88) && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd) && icmp6.code == 0 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd) && ip.ttl == 0xff && (eth.type == 0x800 || eth.type == 0x86dd)
+
+ lookup_nd_ip;
+@@ -2449,6 +2449,16 @@ AT_CLEANUP
+ AT_SETUP([Sparse array operations])
+ check ovstest test-sparse-array add
+ check ovstest test-sparse-array remove-replace
++check ovstest test-sparse-array bitmap-or
++AT_CLEANUP
++
++AT_SETUP([SPSC ring buffer])
++check ovstest test-spsc-ring basic
++check ovstest test-spsc-ring fifo
++check ovstest test-spsc-ring full
++check ovstest test-spsc-ring wraparound
++check ovstest test-spsc-ring for-each-pop
++check ovstest test-spsc-ring struct
+ AT_CLEANUP
+
+ AT_SETUP([Parse MAC])
+@@ -2524,7 +2534,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([ovn -- allows ACLs to match against vlan-transparent double tagged traffic L3 fields])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ for i in 1 2; do
+@@ -2607,7 +2617,7 @@ OVN_FOR_EACH_NORTHD_FLOW_TUNNEL([
+ AT_SETUP([3 HVs, 1 LS, 3 lports/HV])
+ AT_KEYWORDS([ovnarp])
+ AT_KEYWORDS([slowtest])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ # Create hypervisors hv[123].
+@@ -2625,7 +2635,7 @@ for i in 1 2 3; do
+ ovn_attach n1 br-phys 192.168.0.$i
+
+ for j in 1 2 3; do
+- check ovs-vsctl add-port br-int vif$i$j -- set Interface vif$i$j external-ids:iface-id=lp$i$j options:tx_pcap=hv$i/vif$i$j-tx.pcap options:rxq_pcap=hv$i/vif$i$j-rx.pcap ofport-request=$i$j
++ check ovs-vsctl add-port br-int vif$i$j -- set Interface vif$i$j external-ids:iface-id=lp$i$j options:tx_pcap=hv$i/vif$i$j-tx.pcap options:rxq_pcap=hv$i/vif$i$j-rx.pcap
+ check ovn-nbctl lsp-add lsw0 lp$i$j
+ if test $j = 1; then
+ check ovn-nbctl lsp-set-addresses lp$i$j "f0:00:00:00:00:$i$j 192.168.0.$i$j" unknown
+@@ -2896,7 +2906,7 @@ for i in 1 2; do
+ ovn_attach n1 br-phys 192.168.0.$i
+
+ for j in 1 2; do
+- ovs-vsctl add-port br-int vif$i$j -- set Interface vif$i$j external-ids:iface-id=lp$i$j options:tx_pcap=hv$i/vif$i$j-tx.pcap options:rxq_pcap=hv$i/vif$i$j-rx.pcap ofport-request=$i$j
++ ovs-vsctl add-port br-int vif$i$j -- set Interface vif$i$j external-ids:iface-id=lp$i$j options:tx_pcap=hv$i/vif$i$j-tx.pcap options:rxq_pcap=hv$i/vif$i$j-rx.pcap
+ check ovn-nbctl lsp-add lsw0 lp$i$j
+ ip_addrs="192.168.0.$i$j"
+ check ovn-nbctl lsp-set-addresses lp$i$j "f0:00:00:00:00:$i$j $ip_addrs"
+@@ -3343,8 +3353,7 @@ for i in 1 2; do
+ ovs-vsctl add-port br-int vif$i$j -- \
+ set Interface vif$i$j external-ids:iface-id=lp$i$j \
+ options:tx_pcap=hv$i/vif$i$j-tx.pcap \
+- options:rxq_pcap=hv$i/vif$i$j-rx.pcap \
+- ofport-request=$i$j
++ options:rxq_pcap=hv$i/vif$i$j-rx.pcap
+
+ lsp_name=lp$i$j
+ ls_name=$(lsp_to_ls $lsp_name)
+@@ -3512,8 +3521,7 @@ for hv in 1 2; do
+ ovs-vsctl add-port br-int vif-$hv-$ls -- \
+ set Interface vif-$hv-$ls external-ids:iface-id=$lsp_name \
+ options:tx_pcap=hv-$hv/vif-$hv-$ls-tx.pcap \
+- options:rxq_pcap=hv-$hv/vif-$hv-$ls-rx.pcap \
+- ofport-request=$hv$ls
++ options:rxq_pcap=hv-$hv/vif-$hv-$ls-rx.pcap
+
+ check ovn-nbctl lsp-add ls-${ls}0 $lsp_name
+ check ovn-nbctl lsp-set-addresses $lsp_name f0:00:00:00:00:${hv}${ls}
+@@ -3654,8 +3662,7 @@ for hv in 1 2; do
+ ovs-vsctl add-port br-int vif-$hv-$ls-$peer -- \
+ set Interface vif-$hv-$ls-$peer external-ids:iface-id=$lsp_name \
+ options:tx_pcap=hv-$hv/vif-$hv-$ls-$peer-tx.pcap \
+- options:rxq_pcap=hv-$hv/vif-$hv-$ls-$peer-rx.pcap \
+- ofport-request=$hv$ls$peer
++ options:rxq_pcap=hv-$hv/vif-$hv-$ls-$peer-rx.pcap
+
+ check ovn-nbctl lsp-add ls-${ls}0 $lsp_name
+ check ovn-nbctl lsp-set-addresses $lsp_name f0:00:00:00:0${peer}:${hv}${ls}
+@@ -3807,8 +3814,7 @@ for i in 1 2; do
+ ovs-vsctl add-port br-int vif-$i -- \
+ set Interface vif-$i external-ids:iface-id=lp-$i \
+ options:tx_pcap=hv-$i/vif-$i-tx.pcap \
+- options:rxq_pcap=hv-$i/vif-$i-rx.pcap \
+- ofport-request=$i
++ options:rxq_pcap=hv-$i/vif-$i-rx.pcap
+
+ lsp_name=lp-$i
+ check ovn-nbctl lsp-add ls-$i $lsp_name
+@@ -3894,7 +3900,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([VLAN transparency, passthru=true, ARP responder disabled])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ net_add net
+@@ -3918,8 +3924,7 @@ for i in 1 2; do
+ as hv-$i
+ check ovs-vsctl add-port br-int vif$i -- set Interface vif$i external-ids:iface-id=lsp$i \
+ options:tx_pcap=vif$i-tx.pcap \
+- options:rxq_pcap=vif$i-rx.pcap \
+- ofport-request=$i
++ options:rxq_pcap=vif$i-rx.pcap
+ done
+
+ wait_for_ports_up
+@@ -3974,7 +3979,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([VLAN transparency, passthru=true, ND/NA responder disabled])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ net_add net
+@@ -3992,8 +3997,7 @@ done
+ for i in 1 2; do
+ check ovs-vsctl add-port br-int vif$i -- set Interface vif$i external-ids:iface-id=lsp$i \
+ options:tx_pcap=vif$i-tx.pcap \
+- options:rxq_pcap=vif$i-rx.pcap \
+- ofport-request=$i
++ options:rxq_pcap=vif$i-rx.pcap
+ done
+
+ wait_for_ports_up
+@@ -4065,8 +4069,7 @@ for i in 1 2; do
+ as hv-$i
+ ovs-vsctl add-port br-int vif$i -- set Interface vif$i external-ids:iface-id=lsp$i \
+ options:tx_pcap=vif$i-tx.pcap \
+- options:rxq_pcap=vif$i-rx.pcap \
+- ofport-request=$i
++ options:rxq_pcap=vif$i-rx.pcap
+ OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lsp$i` = xup])
+
+ # Patch port might be created after ports are reported up
+@@ -4134,8 +4137,7 @@ for i in 1 2; do
+ as hv-$i
+ check ovs-vsctl add-port br-int vif$i -- set Interface vif$i external-ids:iface-id=lsp$i \
+ options:tx_pcap=vif$i-tx.pcap \
+- options:rxq_pcap=vif$i-rx.pcap \
+- ofport-request=$i
++ options:rxq_pcap=vif$i-rx.pcap
+ wait_for_ports_up lsp$i
+
+ # Patch port might be created after ports are reported up
+@@ -4217,8 +4219,7 @@ for i in 1 2; do
+ as hv-$i
+ ovs-vsctl add-port br-int vif$i -- set Interface vif$i external-ids:iface-id=lsp$i \
+ options:tx_pcap=vif$i-tx.pcap \
+- options:rxq_pcap=vif$i-rx.pcap \
+- ofport-request=$i
++ options:rxq_pcap=vif$i-rx.pcap
+ OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lsp$i` = xup])
+
+ # Patch port might be created after ports are reported up
+@@ -4276,8 +4277,7 @@ ovn_attach physnet br-phys 192.168.0.1
+ for i in 1 2; do
+ ovs-vsctl add-port br-int vif$i -- set Interface vif$i external-ids:iface-id=lsp$i \
+ options:tx_pcap=vif$i-tx.pcap \
+- options:rxq_pcap=vif$i-rx.pcap \
+- ofport-request=$i
++ options:rxq_pcap=vif$i-rx.pcap
+ OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lsp$i` = xup])
+ done
+
+@@ -4325,8 +4325,7 @@ ovn_attach physnet br-phys 192.168.0.1
+ for i in 1 2; do
+ ovs-vsctl add-port br-int vif$i -- set Interface vif$i external-ids:iface-id=lsp$i \
+ options:tx_pcap=vif$i-tx.pcap \
+- options:rxq_pcap=vif$i-rx.pcap \
+- ofport-request=$i
++ options:rxq_pcap=vif$i-rx.pcap
+ OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lsp$i` = xup])
+
+ : > $i.expected
+@@ -4439,8 +4438,7 @@ for tag in 10 20; do
+ ovs-vsctl add-port br-int vif-$tag-$i -- \
+ set Interface vif-$tag-$i external-ids:iface-id=lp-$tag-$i \
+ options:tx_pcap=hv-$tag-$i/vif-$tag-$i-tx.pcap \
+- options:rxq_pcap=hv-$tag-$i/vif-$tag-$i-rx.pcap \
+- ofport-request=$tag$i
++ options:rxq_pcap=hv-$tag-$i/vif-$tag-$i-rx.pcap
+
+ lsp_name=lp-$tag-$i
+ check ovn-nbctl lsp-add ls1 $lsp_name
+@@ -4523,7 +4521,7 @@ AT_CLEANUP
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([vtep: 3 HVs, 1 VIFs/HV, 1 GW, 1 LS])
+ AT_KEYWORDS([vtep])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ # Configure the Northbound database
+@@ -4566,14 +4564,14 @@ sim_add hv1
+ as hv1
+ ovs-vsctl add-br br-phys
+ ovn_attach n1 br-phys 192.168.0.1
+-ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lp1 options:tx_pcap=hv1/vif1-tx.pcap options:rxq_pcap=hv1/vif1-rx.pcap ofport-request=1
++ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lp1 options:tx_pcap=hv1/vif1-tx.pcap options:rxq_pcap=hv1/vif1-rx.pcap
+
+ # Create hypervisor hv2 connected to n1
+ sim_add hv2
+ as hv2
+ ovs-vsctl add-br br-phys
+ ovn_attach n1 br-phys 192.168.0.2
+-ovs-vsctl add-port br-int vif2 -- set Interface vif2 external-ids:iface-id=lp2 options:tx_pcap=hv2/vif2-tx.pcap options:rxq_pcap=hv2/vif2-rx.pcap ofport-request=1
++ovs-vsctl add-port br-int vif2 -- set Interface vif2 external-ids:iface-id=lp2 options:tx_pcap=hv2/vif2-tx.pcap options:rxq_pcap=hv2/vif2-rx.pcap
+
+
+ # Start the vtep emulator with a leg in both networks
+@@ -4610,7 +4608,7 @@ sim_add hv3
+ as hv3
+ ovs-vsctl add-br br-phys
+ net_attach n2 br-phys
+-ovs-vsctl add-port br-phys vif3 -- set Interface vif3 options:tx_pcap=hv3/vif3-tx.pcap options:rxq_pcap=hv3/vif3-rx.pcap ofport-request=1
++ovs-vsctl add-port br-phys vif3 -- set Interface vif3 options:tx_pcap=hv3/vif3-tx.pcap options:rxq_pcap=hv3/vif3-rx.pcap
+
+ # vtep is quite slow setting up all flows
+ # We need to wait for flow flooding to br-vtep_n2, vx1 and vx2
+@@ -4727,6 +4725,10 @@ check as hv1 ovs-vsctl remove interface vif1 external_ids iface-id
+ hv_uuid=$(fetch_column Chassis _uuid name=hv1)
+ wait_row_count Port_Binding 0 chassis="$hv_uuid" type="''"
+
++# Ensure ovn-controller handled the change, or next garp might be received
++# while flows are still installed, and extra packet will be received.
++check ovn-nbctl --wait=hv sync
++
+ # Cleanup MAC_Bindings after previous checks.
+ check ovn-sbctl --all destroy MAC_Binding
+
+@@ -4882,14 +4884,14 @@ sim_add hv1
+ as hv1
+ ovs-vsctl add-br br-phys
+ ovn_attach n1 br-phys 192.168.0.1
+-ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lp1 options:tx_pcap=hv1/vif1-tx.pcap options:rxq_pcap=hv1/vif1-rx.pcap ofport-request=1
++ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lp1 options:tx_pcap=hv1/vif1-tx.pcap options:rxq_pcap=hv1/vif1-rx.pcap
+
+ # Create hypervisor hv2 connected to n1
+ sim_add hv2
+ as hv2
+ ovs-vsctl add-br br-phys
+ ovn_attach n1 br-phys 192.168.0.2
+-ovs-vsctl add-port br-int vif2 -- set Interface vif2 external-ids:iface-id=lp2 options:tx_pcap=hv2/vif2-tx.pcap options:rxq_pcap=hv2/vif2-rx.pcap ofport-request=1
++ovs-vsctl add-port br-int vif2 -- set Interface vif2 external-ids:iface-id=lp2 options:tx_pcap=hv2/vif2-tx.pcap options:rxq_pcap=hv2/vif2-rx.pcap
+
+ # Create hypervisor hv_gw connected to n1 and n2
+ # connect br-phys bridge to n1; connect hv-gw bridge to n2
+@@ -4906,7 +4908,7 @@ sim_add hv3
+ as hv3
+ ovs-vsctl add-br br-phys
+ net_attach n2 br-phys
+-ovs-vsctl add-port br-phys vif3 -- set Interface vif3 options:tx_pcap=hv3/vif3-tx.pcap options:rxq_pcap=hv3/vif3-rx.pcap ofport-request=1
++ovs-vsctl add-port br-phys vif3 -- set Interface vif3 options:tx_pcap=hv3/vif3-tx.pcap options:rxq_pcap=hv3/vif3-rx.pcap
+
+
+ # Pre-populate the hypervisors' ARP tables so that we don't lose any
+@@ -5018,7 +5020,7 @@ AT_CLEANUP
+ OVN_FOR_EACH_NORTHD_FLOW_TUNNEL([
+ AT_SETUP([3 HVs, 3 LS, 3 lports/LS, 1 LR])
+ AT_KEYWORDS([slowtest])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ # Logical network:
+@@ -5112,8 +5114,7 @@ for i in 1 2 3; do
+ -- set Interface vif$i$j$k \
+ external-ids:iface-id=lp$i$j$k \
+ options:tx_pcap=hv$hv/vif$i$j$k-tx.pcap \
+- options:rxq_pcap=hv$hv/vif$i$j$k-rx.pcap \
+- ofport-request=$i$j$k
++ options:rxq_pcap=hv$hv/vif$i$j$k-rx.pcap
+ done
+ done
+ done
+@@ -5186,7 +5187,7 @@ test_ip() {
+ # SPA and TPA are each 8 hex digits.
+ test_arp() {
+ echo "$@"
+- local inport=$1 sha=$2 spa=$3 tpa=$4 reply_ha=$5
++ local inport=$1 sha=$2 spa=$3 tpa=$4 rip=$5 reply_ha=$6
+ local request=$(fmt_pkt "Ether(dst='ff:ff:ff:ff:ff:ff', src='${sha}')/ \
+ ARP(hwsrc='${sha}', hwdst='ff:ff:ff:ff:ff:ff', psrc='${spa}', pdst='${tpa}')")
+ hv=hv`vif_to_hv $inport`
+@@ -5200,9 +5201,19 @@ test_arp() {
+ for k in 1 2 3; do
+ # 192.168.33.254 is configured to the switch patch port for lrp33,
+ # so no ARP flooding expected for it.
+- if test $i$j$k != $inport && test $tpa != 192.168.33.254; then
+- echo $request >> $i$j$k.expected
++ if test $i$j$k = $inport; then
++ continue
++ fi
++
++ if test $tpa = 192.168.33.254; then
++ continue
+ fi
++
++ if test $rip = $tpa && test $j$k != 11; then
++ continue
++ fi
++
++ echo $request >> $i$j$k.expected
+ done
+ done
+
+@@ -5342,9 +5353,9 @@ for i in 1 2 3; do
+ otherip=192.168.$i$j.55 # Some other IP in subnet
+ externalip=1.2.3.4 # Some other IP not in subnet
+
+- test_arp $i$j$k $smac $sip $rip $rmac #4
+- test_arp $i$j$k $smac $otherip $rip $rmac #5
+- test_arp $i$j$k $smac $sip $otherip #6
++ test_arp $i$j$k $smac $sip $rip $rip $rmac #4
++ test_arp $i$j$k $smac $otherip $rip $rip $rmac #5
++ test_arp $i$j$k $smac $sip $otherip $rip #6
+
+ # When rip is 192.168.33.254, ARP request from externalip won't be
+ # filtered, because 192.168.33.254 is configured to switch peer port
+@@ -5353,9 +5364,9 @@ for i in 1 2 3; do
+ if test $i = 3 && test $j = 3; then
+ lrp33_rsp=$rmac
+ fi
+- test_arp $i$j$k $smac $externalip $rip $lrp33_rsp #7
++ test_arp $i$j$k $smac $externalip $rip $rip $lrp33_rsp #7
+
+- # MAC binding should be learned from ARP request.
++ ## MAC binding should be learned from ARP request.
+ echo lrp$i$j,$sip,$smac >> mac_bindings.expected
+
+ # mac_binding is learned and overwritten so only the last one remains.
+@@ -5454,7 +5465,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD_FLOW_TUNNEL([
+ AT_SETUP([IP relocation using GARP request])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ # Logical network:
+@@ -5525,8 +5536,7 @@ for i in 1 2; do
+ -- set Interface vif$i$j \
+ external-ids:iface-id=lp$i$j \
+ options:tx_pcap=hv$hv/vif$i$j-tx.pcap \
+- options:rxq_pcap=hv$hv/vif$i$j-rx.pcap \
+- ofport-request=$i$j
++ options:rxq_pcap=hv$hv/vif$i$j-rx.pcap
+ done
+ done
+
+@@ -5699,6 +5709,9 @@ spa6="fd00::abcd:1"
+ test_na 11 $sha $spa6
+ wait_row_count MAC_Binding 1 ip=\"$spa6\" mac=\"$sha\"
+
++# Wait for mac binding related flows to be installed.
++check ovn-nbctl --wait=hv sync
++
+ sha="f0:00:00:00:00:12"
+ lla6="fe80::abcd:1"
+
+@@ -5712,10 +5725,140 @@ OVN_CLEANUP([hv1], [hv2])
+ AT_CLEANUP
+ ])
+
++OVN_FOR_EACH_NORTHD([
++AT_SETUP([MAC binding learning from ARP request and ND NS])
++CHECK_SCAPY
++ovn_start
++
++dnl Logical network:
++dnl
++dnl One logical router lr0 connected to two logical switches.
++dnl ls-ext has a localnet port ln0 (physnet0) and a router attachment
++dnl for lrp0. ls-int has one VIF lsp1 on hv1.
++dnl
++dnl lrp0 is a distributed gateway port (172.18.0.3/16, fd00::3/64),
++dnl resident on hv1. On hv2 the LS ARP/ND responder for lrp0 is
++dnl conditioned with is_chassis_resident("cr-lrp0"), so ARP/NS
++dnl injected via the localnet on hv2 bypasses the LS responder and
++dnl reaches the router pipeline where lr_in_lookup_neighbor runs.
++
++check ovn-nbctl lr-add lr0
++check ovn-nbctl ls-add ls-ext
++check ovn-nbctl ls-add ls-int
++
++check ovn-nbctl lrp-add lr0 lrp0 00:00:00:00:ff:01 \
++ 172.18.0.3/16 fd00::3/64
++check ovn-nbctl lsp-add-router-port ls-ext lrp0-attach lrp0
++
++check ovn-nbctl lrp-add lr0 lrp1 00:00:00:00:ff:02 192.168.1.1/24
++check ovn-nbctl lsp-add-router-port ls-int lrp1-attach lrp1
++
++check ovn-nbctl lsp-add ls-int lsp1 \
++ -- lsp-set-addresses lsp1 "f0:00:00:00:00:01 192.168.1.10"
++
++check ovn-nbctl lsp-add-localnet-port ls-ext ln0 physnet0
++check ovn-nbctl lrp-set-gateway-chassis lrp0 hv1 20
++
++net_add n1
++for i in 1 2; do
++ sim_add hv$i
++ as hv$i
++ ovs-vsctl add-br br-phys
++ ovn_attach n1 br-phys 192.168.0.$i
++ as hv$i ovs-vsctl set open . external-ids:ovn-bridge-mappings=physnet0:br-phys
++done
++
++check as hv1 ovs-vsctl \
++ -- add-port br-int vif1 \
++ -- set Interface vif1 external_ids:iface-id=lsp1
++
++dnl Add a dummy port on hv2's br-phys to inject external packets.
++check as hv2 ovs-vsctl \
++ -- add-port br-phys ext1 \
++ -- set Interface ext1 type=internal
++
++OVN_POPULATE_ARP
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
++
++dnl External device's addresses.
++sha=f0:00:00:00:10:01
++spa=172.18.0.100
++spa6=fd00::100
++
++dnl Router port addresses (target of ARP request / ND NS).
++router_ip=172.18.0.3
++router_ip6=fd00::3
++
++AS_BOX([always_learn_from_arp_request=false])
++check ovn-nbctl --wait=hv set logical_router lr0 \
++ options:always_learn_from_arp_request=false
++
++AS_BOX([ARP request targeting router own IPv4])
++packet=$(dump_arp 1 $sha ff:ff:ff:ff:ff:ff $spa $router_ip 00:00:00:00:00:00)
++as hv2 ovs-appctl netdev-dummy/receive ext1 $packet
++wait_row_count MAC_Binding 1 ip="$spa" mac=\"$sha\"
++
++check ovn-sbctl --all destroy mac_binding
++check ovn-nbctl --wait=hv sync
++
++AS_BOX([ND NS targeting router own IPv6])
++packet=$(dump_ns 33:33:ff:00:00:03 $sha ff02::1:ff00:3 $spa6 $router_ip6)
++check as hv2 ovs-appctl netdev-dummy/receive ext1 $packet
++wait_row_count MAC_Binding 1 ip=\"$spa6\" mac=\"$sha\"
++
++check ovn-sbctl --all destroy mac_binding
++check ovn-nbctl --wait=hv sync
++
++AS_BOX([GARP for unknown IP - no binding expected])
++unknown_ip=172.18.0.200
++packet=$(dump_arp 1 $sha ff:ff:ff:ff:ff:ff $unknown_ip $unknown_ip 00:00:00:00:00:00)
++check as hv2 ovs-appctl netdev-dummy/receive ext1 $packet
++check ovn-nbctl --wait=hv sync
++sleep 1
++check_row_count MAC_Binding 0 ip="$unknown_ip"
++
++AS_BOX([ND NS for unknown IPv6 - no binding expected])
++unknown_ip6=fd00::200
++packet=$(dump_ns 33:33:ff:00:02:00 $sha ff02::1:ff00:200 $spa6 $unknown_ip6)
++check as hv2 ovs-appctl netdev-dummy/receive ext1 $packet
++check ovn-nbctl --wait=hv sync
++sleep 1
++check_row_count MAC_Binding 0 ip=\"$spa6\"
++
++AS_BOX([Update existing ARP binding with new MAC])
++check ovn-sbctl --all destroy mac_binding
++check ovn-nbctl --wait=hv sync
++
++packet=$(dump_arp 1 $sha ff:ff:ff:ff:ff:ff $spa $router_ip 00:00:00:00:00:00)
++check as hv2 ovs-appctl netdev-dummy/receive ext1 $packet
++wait_row_count MAC_Binding 1 ip="$spa" mac=\"$sha\"
++check ovn-nbctl --wait=hv sync
++
++sha2=f0:00:00:00:10:02
++packet=$(dump_arp 1 $sha2 ff:ff:ff:ff:ff:ff $spa $router_ip 00:00:00:00:00:00)
++check as hv2 ovs-appctl netdev-dummy/receive ext1 $packet
++wait_row_count MAC_Binding 1 ip="$spa" mac=\"$sha2\"
++
++AS_BOX([Update existing ND binding with new MAC])
++packet=$(dump_ns 33:33:ff:00:00:03 $sha ff02::1:ff00:3 $spa6 $router_ip6)
++check as hv2 ovs-appctl netdev-dummy/receive ext1 $packet
++wait_row_count MAC_Binding 1 ip=\"$spa6\" mac=\"$sha\"
++check ovn-nbctl --wait=hv sync
++
++packet=$(dump_ns 33:33:ff:00:00:03 $sha2 ff02::1:ff00:3 $spa6 $router_ip6)
++check as hv2 ovs-appctl netdev-dummy/receive ext1 $packet
++wait_row_count MAC_Binding 1 ip=\"$spa6\" mac=\"$sha2\"
++
++OVN_CLEANUP([hv1], [hv2])
++
++AT_CLEANUP
++])
++
+ # 3 hypervisors, one logical switch, 3 logical ports per hypervisor
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([portsecurity : 3 HVs, 1 LS, 3 lports/HV])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ # Create hypervisors hv[123].
+@@ -5733,7 +5876,7 @@ for i in 1 2 3; do
+ ovs-vsctl add-br br-phys
+ ovn_attach n1 br-phys 192.168.0.$i
+ for j in 1 2 3; do
+- ovs-vsctl add-port br-int vif$i$j -- set Interface vif$i$j external-ids:iface-id=lp$i$j options:tx_pcap=hv$i/vif$i$j-tx.pcap options:rxq_pcap=hv$i/vif$i$j-rx.pcap ofport-request=$i$j
++ ovs-vsctl add-port br-int vif$i$j -- set Interface vif$i$j external-ids:iface-id=lp$i$j options:tx_pcap=hv$i/vif$i$j-tx.pcap options:rxq_pcap=hv$i/vif$i$j-rx.pcap
+ check ovn-nbctl lsp-add lsw0 lp$i$j
+ if test $j = 1; then
+ check ovn-nbctl lsp-set-addresses lp$i$j "f0:00:00:00:00:$i$j 192.168.0.$i$j" unknown
+@@ -6184,8 +6327,7 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ sim_add hv2
+ as hv2
+@@ -6194,8 +6336,7 @@ ovn_attach n1 br-phys 192.168.0.2
+ ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=ls2-lp1 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif1-rx.pcap
+
+
+ # Pre-populate the hypervisors' ARP tables so that we don't lose any
+@@ -6273,7 +6414,7 @@ AT_CLEANUP
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([1 HV, 1 LS, 2 lport/LS, 1 LR])
+ AT_KEYWORDS([router-admin-state])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ # Logical network:
+@@ -6307,14 +6448,12 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int vif1 -- \
+ set interface vif1 external-ids:iface-id=ls1-lp1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ ovs-vsctl -- add-port br-int vif2 -- \
+ set interface vif2 external-ids:iface-id=ls1-lp2 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+
+ wait_for_ports_up
+@@ -6383,7 +6522,7 @@ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([1 HV, 2 LSs, 1 lport/LS, 1 LR])
+ AT_KEYWORDS([router-admin-state])
+ TAG_UNSTABLE
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ # Logical network:
+@@ -6423,14 +6562,12 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int vif1 -- \
+ set interface vif1 external-ids:iface-id=ls1-lp1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ ovs-vsctl -- add-port br-int vif2 -- \
+ set interface vif2 external-ids:iface-id=ls2-lp1 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+ wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+@@ -6474,7 +6611,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([2 HVs, 3 LS, 1 lport/LS, 2 peer LRs, static routes])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ # Logical network:
+@@ -6536,14 +6673,12 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=foo1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=alice1 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+ sim_add hv2
+ as hv2
+@@ -6552,8 +6687,7 @@ ovn_attach n1 br-phys 192.168.0.2
+ ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=bob1 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif1-rx.pcap
+
+
+ # Pre-populate the hypervisors' ARP tables so that we don't lose any
+@@ -6674,7 +6808,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([2 HVs, 3 LRs connected via LS, static routes])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ # Logical network:
+@@ -6754,14 +6888,12 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=foo1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=alice1 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+ sim_add hv2
+ as hv2
+@@ -6770,8 +6902,7 @@ ovn_attach n1 br-phys 192.168.0.2
+ ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=bob1 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif1-rx.pcap
+
+
+ # Pre-populate the hypervisors' ARP tables so that we don't lose any
+@@ -6902,26 +7033,22 @@ ovn_attach n1 br-phys 192.168.0.1
+ check ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ check ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=ls1-lp2 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+ check ovs-vsctl -- add-port br-int hv1-vif3 -- \
+ set interface hv1-vif3 external-ids:iface-id=ls2-lp1 \
+ options:tx_pcap=hv1/vif3-tx.pcap \
+- options:rxq_pcap=hv1/vif3-rx.pcap \
+- ofport-request=3
++ options:rxq_pcap=hv1/vif3-rx.pcap
+
+ check ovs-vsctl -- add-port br-int hv1-vif4 -- \
+ set interface hv1-vif4 external-ids:iface-id=ls2-lp2 \
+ options:tx_pcap=hv1/vif4-tx.pcap \
+- options:rxq_pcap=hv1/vif4-rx.pcap \
+- ofport-request=4
++ options:rxq_pcap=hv1/vif4-rx.pcap
+
+ as hv1 ovs-appctl vlog/set dbg
+
+@@ -7571,7 +7698,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([dhcpv6 : 1 HV, 2 LS, 5 LSPs])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ check ovn-nbctl ls-add ls1
+@@ -7618,32 +7745,27 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=ls1-lp2 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+ ovs-vsctl -- add-port br-int hv1-vif3 -- \
+ set interface hv1-vif3 external-ids:iface-id=ls2-lp1 \
+ options:tx_pcap=hv1/vif3-tx.pcap \
+- options:rxq_pcap=hv1/vif3-rx.pcap \
+- ofport-request=3
++ options:rxq_pcap=hv1/vif3-rx.pcap
+
+ ovs-vsctl -- add-port br-int hv1-vif4 -- \
+ set interface hv1-vif4 external-ids:iface-id=ls2-lp2 \
+ options:tx_pcap=hv1/vif4-tx.pcap \
+- options:rxq_pcap=hv1/vif4-rx.pcap \
+- ofport-request=4
++ options:rxq_pcap=hv1/vif4-rx.pcap
+
+ ovs-vsctl -- add-port br-int hv1-vif5 -- \
+ set interface hv1-vif5 external-ids:iface-id=ls1-lp3 \
+ options:tx_pcap=hv1/vif5-tx.pcap \
+- options:rxq_pcap=hv1/vif5-rx.pcap \
+- ofport-request=5
++ options:rxq_pcap=hv1/vif5-rx.pcap
+
+ OVN_POPULATE_ARP
+
+@@ -7908,7 +8030,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([2 HVs, 2 LRs connected via LS, gateway router])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ # Logical network:
+@@ -7929,8 +8051,7 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=foo1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+
+ sim_add hv2
+@@ -7940,8 +8061,7 @@ ovn_attach n1 br-phys 192.168.0.2
+ ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=alice1 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif1-rx.pcap
+
+ # Pre-populate the hypervisors' ARP tables so that we don't lose any
+ # packets for ARP resolution (native tunneling doesn't queue packets
+@@ -8093,7 +8213,7 @@ AT_CLEANUP
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([spine-leaf: 1 HV, 3 LSs, connected via spine switch])
+ AT_KEYWORDS([spine leaf])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ # Logical network:
+@@ -8153,30 +8273,25 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int vif1 -- \
+ set interface vif1 external-ids:iface-id=ls1-lp1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+ ovs-vsctl -- add-port br-int vif2 -- \
+ set interface vif2 external-ids:iface-id=ls1-lp2 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+ ovs-vsctl -- add-port br-int vif3 -- \
+ set interface vif3 external-ids:iface-id=ls2-lp1 \
+ options:tx_pcap=hv1/vif3-tx.pcap \
+- options:rxq_pcap=hv1/vif3-rx.pcap \
+- ofport-request=3
++ options:rxq_pcap=hv1/vif3-rx.pcap
+ ovs-vsctl -- add-port br-int vif4 -- \
+ set interface vif4 external-ids:iface-id=ls2-lp2 \
+ options:tx_pcap=hv1/vif4-tx.pcap \
+- options:rxq_pcap=hv1/vif4-rx.pcap \
+- ofport-request=4
++ options:rxq_pcap=hv1/vif4-rx.pcap
+
+ ovs-vsctl -- add-port br-int vif5 -- \
+ set interface vif5 external-ids:iface-id=ls3-lp1 \
+ options:tx_pcap=hv1/vif5-tx.pcap \
+- options:rxq_pcap=hv1/vif5-rx.pcap \
+- ofport-request=5
++ options:rxq_pcap=hv1/vif5-rx.pcap
+
+ wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+@@ -8290,7 +8405,7 @@ AT_CLEANUP
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([spine-leaf: 3 HVs, 3 LSs, connected via distributed spine switch])
+ AT_KEYWORDS([spine leaf])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ # Logical network:
+@@ -8350,13 +8465,11 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int vif1 -- \
+ set interface vif1 external-ids:iface-id=ls1-lp1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+ ovs-vsctl -- add-port br-int vif2 -- \
+ set interface vif2 external-ids:iface-id=ls1-lp2 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+ sim_add hv2
+ as hv2
+@@ -8365,13 +8478,11 @@ ovn_attach n1 br-phys 192.168.0.2
+ ovs-vsctl -- add-port br-int vif3 -- \
+ set interface vif3 external-ids:iface-id=ls2-lp1 \
+ options:tx_pcap=hv2/vif3-tx.pcap \
+- options:rxq_pcap=hv2/vif3-rx.pcap \
+- ofport-request=3
++ options:rxq_pcap=hv2/vif3-rx.pcap
+ ovs-vsctl -- add-port br-int vif4 -- \
+ set interface vif4 external-ids:iface-id=ls2-lp2 \
+ options:tx_pcap=hv2/vif4-tx.pcap \
+- options:rxq_pcap=hv2/vif4-rx.pcap \
+- ofport-request=4
++ options:rxq_pcap=hv2/vif4-rx.pcap
+
+ sim_add hv3
+ as hv3
+@@ -8380,8 +8491,7 @@ ovn_attach n1 br-phys 192.168.0.3
+ ovs-vsctl -- add-port br-int vif5 -- \
+ set interface vif5 external-ids:iface-id=ls3-lp1 \
+ options:tx_pcap=hv3/vif5-tx.pcap \
+- options:rxq_pcap=hv3/vif5-rx.pcap \
+- ofport-request=5
++ options:rxq_pcap=hv3/vif5-rx.pcap
+
+ OVN_POPULATE_ARP
+
+@@ -8432,6 +8542,13 @@ check as hv1 ovs-appctl netdev-dummy/receive vif1 $packet
+ wait_row_count FDB 3
+ check ovn-nbctl --wait=hv sync
+
++# No modifications expected.
++AT_CHECK([echo $packet > expected])
++
++# Check that it is delivered where needed.
++OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected])
++OVN_CHECK_PACKETS([hv3/vif5-tx.pcap], [expected])
++
+ # It's a little problematic to trace the other side, but we can check datapath
+ # actions. Note: 'actions:br-phys' is a stray tunnel packet destined for the
+ # other node, but OVS from the 'main' namespace didn't learn addresses yet,
+@@ -8449,17 +8566,12 @@ actions:tnl_pop(genev_sys_6081)
+ actions:vif5
+ ])
+
+-# No modifications expected.
+-AT_CHECK([echo $packet > expected])
+-
+ AT_CHECK([touch empty])
+
+-# Check that it is delivered where needed and not delivered where not.
++# Check that the packet is not delivered where it should not be.
+ OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [empty])
+ OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [empty])
+-OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected])
+ OVN_CHECK_PACKETS([hv2/vif4-tx.pcap], [empty])
+-OVN_CHECK_PACKETS([hv3/vif5-tx.pcap], [expected])
+
+ # Trace a reply packet.
+ reply=$(fmt_pkt "Ether(dst='${src_mac}', src='${dst_mac}')/ \
+@@ -8534,7 +8646,7 @@ AT_CLEANUP
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([icmp_reply: 1 HVs, 2 LSs, 1 lport/LS, 1 LR])
+ AT_KEYWORDS([router-icmp-reply])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ # Logical network:
+@@ -8574,14 +8686,12 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int vif1 -- \
+ set interface vif1 external-ids:iface-id=ls1-lp1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ ovs-vsctl -- add-port br-int vif2 -- \
+ set interface vif2 external-ids:iface-id=ls2-lp1 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+ # Allow some time for ovn-northd and ovn-controller to catch up.
+ wait_for_ports_up
+@@ -8715,20 +8825,17 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int vif1 -- \
+ set interface vif1 external-ids:iface-id=ls1-lp1 \
+ options:tx_pcap=pbr-hv/vif1-tx.pcap \
+- options:rxq_pcap=pbr-hv/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=pbr-hv/vif1-rx.pcap
+
+ ovs-vsctl -- add-port br-int vif2 -- \
+ set interface vif2 external-ids:iface-id=ls2-lp1 \
+ options:tx_pcap=pbr-hv/vif2-tx.pcap \
+- options:rxq_pcap=pbr-hv/vif2-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=pbr-hv/vif2-rx.pcap
+
+ ovs-vsctl -- add-port br-int vif3 -- \
+ set interface vif3 external-ids:iface-id=ls3-lp1 \
+ options:tx_pcap=pbr-hv/vif3-tx.pcap \
+- options:rxq_pcap=pbr-hv/vif3-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=pbr-hv/vif3-rx.pcap
+
+ ls1_ro_mac=00:00:00:01:02:f1
+ ls1_ro_ip=192.168.1.1
+@@ -8981,20 +9088,17 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int vif1 -- \
+ set interface vif1 external-ids:iface-id=ls1-lp1 \
+ options:tx_pcap=pbr-hv/vif1-tx.pcap \
+- options:rxq_pcap=pbr-hv/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=pbr-hv/vif1-rx.pcap
+
+ ovs-vsctl -- add-port br-int vif2 -- \
+ set interface vif2 external-ids:iface-id=ls2-lp1 \
+ options:tx_pcap=pbr-hv/vif2-tx.pcap \
+- options:rxq_pcap=pbr-hv/vif2-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=pbr-hv/vif2-rx.pcap
+
+ ovs-vsctl -- add-port br-int vif3 -- \
+ set interface vif3 external-ids:iface-id=ls3-lp1 \
+ options:tx_pcap=pbr-hv/vif3-tx.pcap \
+- options:rxq_pcap=pbr-hv/vif3-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=pbr-hv/vif3-rx.pcap
+
+ ls1_ro_mac=00:00:00:01:02:f1
+ ls1_ro_ip=2001::1
+@@ -9218,13 +9322,13 @@ ovs-vsctl add-br br-phys
+ ovn_attach n1 br-phys 192.168.0.2
+
+ # Add vif1 to hv1 and lsw0, turn on l2 port security on vif1.
+-ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lp1 options:tx_pcap=hv1/vif1-tx.pcap options:rxq_pcap=hv1/vif1-rx.pcap ofport-request=1
++ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lp1 options:tx_pcap=hv1/vif1-tx.pcap options:rxq_pcap=hv1/vif1-rx.pcap
+ check ovn-nbctl lsp-add lsw0 lp1
+ check ovn-nbctl lsp-set-addresses lp1 "fa:16:3e:94:05:98 192.168.0.3 fd81:ce49:a948:0:f816:3eff:fe94:598"
+ check ovn-nbctl lsp-set-port-security lp1 "fa:16:3e:94:05:98 192.168.0.3 fd81:ce49:a948:0:f816:3eff:fe94:598"
+
+ # Add vif2 to hv1 and lsw0, turn on l2 port security on vif2.
+-ovs-vsctl add-port br-int vif2 -- set Interface vif2 external-ids:iface-id=lp2 options:tx_pcap=hv1/vif2-tx.pcap options:rxq_pcap=hv1/vif2-rx.pcap ofport-request=2
++ovs-vsctl add-port br-int vif2 -- set Interface vif2 external-ids:iface-id=lp2 options:tx_pcap=hv1/vif2-tx.pcap options:rxq_pcap=hv1/vif2-rx.pcap
+ check ovn-nbctl lsp-add lsw0 lp2
+ check ovn-nbctl lsp-set-addresses lp2 "fa:16:3e:a1:f9:ae 192.168.0.4 fd81:ce49:a948:0:f816:3eff:fea1:f9ae"
+ check ovn-nbctl lsp-set-port-security lp2 "fa:16:3e:a1:f9:ae 192.168.0.4 fd81:ce49:a948:0:f816:3eff:fea1:f9ae"
+@@ -9656,20 +9760,17 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=foo1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=foo2 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+ ovs-vsctl -- add-port br-int hv1-vif3 -- \
+ set interface hv1-vif3 external-ids:iface-id=alice1 \
+ options:tx_pcap=hv1/vif3-tx.pcap \
+- options:rxq_pcap=hv1/vif3-rx.pcap \
+- ofport-request=3
++ options:rxq_pcap=hv1/vif3-rx.pcap
+
+ # Allow some time for ovn-northd and ovn-controller to catch up.
+ wait_for_ports_up
+@@ -9756,8 +9857,7 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ OVN_POPULATE_ARP
+ sleep 2
+@@ -9877,13 +9977,11 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int hv1-ls1lp2 -- \
+ set interface hv1-ls1lp2 external-ids:iface-id=ls1lp2 \
+ options:tx_pcap=hv1/ls1lp2-tx.pcap \
+- options:rxq_pcap=hv1/ls1lp2-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/ls1lp2-rx.pcap
+ ovs-vsctl -- add-port br-int hv1-ls2lp2 -- \
+ set interface hv1-ls2lp2 external-ids:iface-id=ls2lp2 \
+ options:tx_pcap=hv1/ls2lp2-tx.pcap \
+- options:rxq_pcap=hv1/ls2lp2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/ls2lp2-rx.pcap
+
+ # Allow some time for ovn-northd and ovn-controller to catch up.
+ wait_for_ports_up
+@@ -10186,7 +10284,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([ARP/ND from localnet -- proxy reply on resident chassis only])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ dnl Create logical switch with localnet port.
+@@ -10369,11 +10467,7 @@ check ovn-nbctl --wait=hv set Logical_Switch_Port ln1 \
+ options:localnet_learn_fdb=true
+ test_arp_nd_localnet
+
+-OVN_CLEANUP([hv1
+-/left allocated/d
+-], [hv2
+-/left allocated/d
+-])
++OVN_CLEANUP([hv1], [hv2])
+ AT_CLEANUP
+ ])
+
+@@ -11080,8 +11174,8 @@ sim_add hv
+ as hv
+ check ovs-vsctl add-br br-phys
+ ovn_attach n1 br-phys 192.168.0.1
+-check ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lp1 options:tx_pcap=vif1-tx.pcap options:rxq_pcap=vif1-rx.pcap ofport-request=1
+-check ovs-vsctl add-port br-int vif2 -- set Interface vif2 external-ids:iface-id=lp2 options:tx_pcap=vif2-tx.pcap options:rxq_pcap=vif2-rx.pcap ofport-request=2
++check ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lp1 options:tx_pcap=vif1-tx.pcap options:rxq_pcap=vif1-rx.pcap
++check ovs-vsctl add-port br-int vif2 -- set Interface vif2 external-ids:iface-id=lp2 options:tx_pcap=vif2-tx.pcap options:rxq_pcap=vif2-rx.pcap
+ wait_for_ports_up lp1 lp2
+
+ # Extracts nw_tos from the final flow from ofproto/trace output and prints
+@@ -11456,14 +11550,12 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int vm1 -- \
+ set interface vm1 external-ids:iface-id=vm1 \
+ options:tx_pcap=hv1/vm1-tx.pcap \
+- options:rxq_pcap=hv1/vm1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vm1-rx.pcap
+
+ ovs-vsctl -- add-port br-int bar3 -- \
+ set interface bar3 external-ids:iface-id=bar3 \
+ options:tx_pcap=hv1/bar3-tx.pcap \
+- options:rxq_pcap=hv1/bar3-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/bar3-rx.pcap
+
+ sim_add hv2
+ as hv2
+@@ -11472,8 +11564,7 @@ ovn_attach n1 br-phys 192.168.0.2
+ ovs-vsctl -- add-port br-int vm2 -- \
+ set interface vm2 external-ids:iface-id=vm2 \
+ options:tx_pcap=hv2/vm2-tx.pcap \
+- options:rxq_pcap=hv2/vm2-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vm2-rx.pcap
+
+ # Pre-populate the hypervisors' ARP tables so that we don't lose any
+ # packets for ARP resolution (native tunneling doesn't queue packets
+@@ -11706,14 +11797,12 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=foo1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=bar1 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+ sim_add hv2
+ as hv2
+@@ -11722,8 +11811,7 @@ ovn_attach n1 br-phys 192.168.0.2
+ ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=alice1 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif1-rx.pcap
+
+ sim_add hv3
+ as hv3
+@@ -11732,8 +11820,7 @@ ovn_attach n1 br-phys 192.168.0.3
+ ovs-vsctl -- add-port br-int hv3-vif1 -- \
+ set interface hv3-vif1 external-ids:iface-id=bob1 \
+ options:tx_pcap=hv3/vif1-tx.pcap \
+- options:rxq_pcap=hv3/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv3/vif1-rx.pcap
+
+
+ check_uuid ovn-nbctl create Logical_Router name=R1
+@@ -11912,14 +11999,12 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=ls1-lp2 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+ OVN_POPULATE_ARP
+ wait_for_ports_up
+@@ -12518,6 +12603,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([dns lookup : EDNS])
++CHECK_SCAPY
+ OVN_CHECK_SCAPY_EDNS_CLIENT_SUBNET_SUPPORT()
+ ovn_start
+
+@@ -12589,8 +12675,7 @@ ovn_attach n1 br-phys 192.168.0.1
+ check ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=foo1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ sim_add gw1
+ as gw1
+@@ -12609,8 +12694,7 @@ ovn_attach n1 br-phys 192.168.0.3
+ check ovs-vsctl -- add-port br-int ext1-vif1 -- \
+ set interface ext1-vif1 external-ids:iface-id=outside1 \
+ options:tx_pcap=ext1/vif1-tx.pcap \
+- options:rxq_pcap=ext1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=ext1/vif1-rx.pcap
+
+ check ovn-nbctl --wait=hv set NB_Global . options:"bfd-mult"=15
+ # Pre-populate the hypervisors' ARP tables so that we don't lose any
+@@ -12811,6 +12895,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([4 HV, 3 LS, 2 LR, packet test with HA distributed router gateway port])
++CHECK_SCAPY
+ ovn_start
+
+ net_add n1
+@@ -12822,8 +12907,7 @@ ovn_attach n1 br-phys 192.168.0.1
+ check ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=foo1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ sim_add gw1
+ as gw1
+@@ -12850,8 +12934,7 @@ ovn_attach n1 br-phys 192.168.0.3
+ check ovs-vsctl -- add-port br-int ext1-vif1 -- \
+ set interface ext1-vif1 external-ids:iface-id=outside1 \
+ options:tx_pcap=ext1/vif1-tx.pcap \
+- options:rxq_pcap=ext1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=ext1/vif1-rx.pcap
+
+ check ovn-nbctl --wait=hv set NB_Global . options:"bfd-mult"=15
+ # Pre-populate the hypervisors' ARP tables so that we don't lose any
+@@ -12971,6 +13054,13 @@ test_ip_packet()
+ send_na ext1 ext1-vif1 "00:00:00:00:30:$i" "33:33:00:00:00:01" "fd10::$i" "ff02::1"
+ done
+
++ # On slow systems, processing all those PACKET_IN2 might be slow, and
++ # heavily delay further PACKET_IN2.
++ # So, wait for all (v6) PACKET_IN2 to be handled before going on.
++ for i in $(seq 11 $((n_external_ip + 10))); do
++ wait_row_count MAC_Binding 1 ip="fd10\:\:$i"
++ done
++
+ # Send ip packet between foo1 and outside1
+ src_mac="f00000010203" # foo1 mac
+ dst_mac="000001010203" # foo-R0 mac (internal router leg)
+@@ -12995,7 +13085,7 @@ test_ip_packet()
+ check as ext1 ovs-appctl netdev-dummy/receive ext1-vif1 $arp_reply
+
+ OVS_WAIT_UNTIL([
+- test `as $active_gw ovs-ofctl dump-flows br-int | grep table=OFTABLE_MAC_BINDING | \
++ test `as $active_gw ovs-ofctl dump-flows br-int table=OFTABLE_MAC_BINDING | \
+ grep actions=mod_dl_dst:f0:00:00:01:02:04 | wc -l` -eq 1
+ ])
+
+@@ -13031,7 +13121,7 @@ grep actions=mod_dl_dst:f0:00:00:01:02:04 | wc -l` -eq 1
+ }
+
+ # Wait for BFD to be up, then for ovn-controller to handle that change
+-ovn_wait_for_bfd_up hv1 gw1 gw2
++ovn_wait_for_bfd_up hv1 gw1 gw2 gw3
+ check ovn-nbctl --wait=hv sync
+ test_ip_packet gw1 gw2
+
+@@ -13106,8 +13196,7 @@ ovn_attach n1 br-phys 192.168.0.1
+ check ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=foo1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ sim_add hv2
+ as hv2
+@@ -13121,8 +13210,7 @@ ovn_attach n1 br-phys 192.168.0.3
+ check ovs-vsctl -- add-port br-int hv3-vif1 -- \
+ set interface hv3-vif1 external-ids:iface-id=outside1 \
+ options:tx_pcap=hv3/vif1-tx.pcap \
+- options:rxq_pcap=hv3/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv3/vif1-rx.pcap
+
+ # Pre-populate the hypervisors' ARP tables so that we don't lose any
+ # packets for ARP resolution (native tunneling doesn't queue packets
+@@ -13346,8 +13434,7 @@ AT_CHECK_UNQUOTED([ovn_trace --minimal foo 'inport == "foo1" && eth.src == f0:00
+ as hv1 ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=alice1 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+ check ovn-nbctl lsp-add alice alice1 \
+ -- lsp-set-addresses alice1 "f0:00:00:01:02:05 172.16.1.4"
+@@ -13356,8 +13443,7 @@ check ovn-nbctl lsp-add alice alice1 \
+ as hv2 ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=foo2 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif1-rx.pcap
+
+ check ovn-nbctl lsp-add foo foo2 \
+ -- lsp-set-addresses foo2 "f0:00:00:01:02:06 192.168.1.3"
+@@ -13485,11 +13571,9 @@ as hv1 reset_pcap_file snoopvif hv1/snoopvif
+
+ # Add OVS ports for foo1 and foo2 on hv3
+ ovs-vsctl -- add-port br-int hv3-vif1 -- \
+- set interface hv3-vif1 external-ids:iface-id=foo1 \
+- ofport-request=1
++ set interface hv3-vif1 external-ids:iface-id=foo1
+ ovs-vsctl -- add-port br-int hv3-vif2 -- \
+- set interface hv3-vif2 external-ids:iface-id=foo2 \
+- ofport-request=2
++ set interface hv3-vif2 external-ids:iface-id=foo2
+
+ # Add bridge-mapping on hv3
+ AT_CHECK([as hv3 ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=physnet1:br-phys])
+@@ -13560,8 +13644,7 @@ start_daemon ovn-controller
+ check ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=foo1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ sim_add hv2
+ as hv2
+@@ -13576,8 +13659,7 @@ ovn_attach n1 br-phys 192.168.0.3
+ check ovs-vsctl -- add-port br-int hv3-vif1 -- \
+ set interface hv3-vif1 external-ids:iface-id=outside1 \
+ options:tx_pcap=hv3/vif1-tx.pcap \
+- options:rxq_pcap=hv3/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv3/vif1-rx.pcap
+ ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings="phys:br-phys"
+
+ sim_add hv4
+@@ -13826,7 +13908,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([IPv6 ND Router Solicitation responder])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ AT_KEYWORDS([ovn-nd_ra])
+ ovn_start
+
+@@ -13874,20 +13956,17 @@ check ovn-nbctl acl-add lsw0 to-lport 1002 'outport == "lp3" && ip6 && icmp6' a
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=lp1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=lp2 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+ ovs-vsctl -- add-port br-int hv1-vif3 -- \
+ set interface hv1-vif3 external-ids:iface-id=lp3 \
+ options:tx_pcap=hv1/vif3-tx.pcap \
+- options:rxq_pcap=hv1/vif3-rx.pcap \
+- ofport-request=3
++ options:rxq_pcap=hv1/vif3-rx.pcap
+
+ # Allow some time for ovn-northd and ovn-controller to catch up.
+ wait_for_ports_up
+@@ -14127,8 +14206,7 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=foo1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ sim_add hv2
+ as hv2
+@@ -14137,8 +14215,7 @@ ovn_attach n1 br-phys 192.168.0.2
+ ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=alice1 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif1-rx.pcap
+
+
+ # Pre-populate the hypervisors' ARP tables so that we don't lose any
+@@ -14180,6 +14257,96 @@ OVN_CLEANUP([hv1],[hv2])
+ AT_CLEANUP
+ ])
+
++OVN_FOR_EACH_NORTHD([
++AT_SETUP([/31 router port (RFC 3021)])
++CHECK_SCAPY
++ovn_start
++
++# Logical network:
++# 2 LS 'sw0' and 'sw1' connected via router R1.
++# R1 connects to 'sw0' with a /31 point-to-point network (RFC 3021). The
++# host 'sw0p1' uses 10.0.0.1, which is the address that OVN previously
++# computed as the /31 "broadcast" and dropped in the L3 admission control.
++# This test verifies that traffic sourced from the /31 peer is forwarded.
++
++check ovn-nbctl lr-add R1
++
++check ovn-nbctl ls-add sw0
++check ovn-nbctl ls-add sw1
++
++# Connect sw0 to R1 with a /31 network. The router owns 10.0.0.0/31.
++check ovn-nbctl lrp-add R1 sw0 00:00:00:01:02:03 10.0.0.0/31
++check ovn-nbctl lsp-add sw0 rp-sw0 -- set Logical_Switch_Port rp-sw0 \
++ type=router options:router-port=sw0 addresses=\"00:00:00:01:02:03\"
++
++# Connect sw1 to R1.
++check ovn-nbctl lrp-add R1 sw1 00:00:00:01:02:04 20.0.0.1/24
++check ovn-nbctl lsp-add sw1 rp-sw1 -- set Logical_Switch_Port rp-sw1 \
++ type=router options:router-port=sw1 addresses=\"00:00:00:01:02:04\"
++
++# Create logical port sw0p1 in sw0. Its IP (10.0.0.1) is the /31 peer of
++# the router port and equals the address OVN treated as the broadcast.
++check ovn-nbctl lsp-add sw0 sw0p1 \
++-- lsp-set-addresses sw0p1 "f0:00:00:01:02:03 10.0.0.1"
++
++# Create logical port sw1p1 in sw1.
++check ovn-nbctl lsp-add sw1 sw1p1 \
++-- lsp-set-addresses sw1p1 "f0:00:00:01:02:04 20.0.0.2"
++
++# Create two hypervisors and OVS ports corresponding to logical ports.
++net_add n1
++
++sim_add hv1
++as hv1
++check ovs-vsctl add-br br-phys
++ovn_attach n1 br-phys 192.168.0.1
++check ovs-vsctl -- add-port br-int hv1-vif1 -- \
++ set interface hv1-vif1 external-ids:iface-id=sw0p1 \
++ options:tx_pcap=hv1/vif1-tx.pcap \
++ options:rxq_pcap=hv1/vif1-rx.pcap \
++ ofport-request=1
++
++sim_add hv2
++as hv2
++check ovs-vsctl add-br br-phys
++ovn_attach n1 br-phys 192.168.0.2
++check ovs-vsctl -- add-port br-int hv2-vif1 -- \
++ set interface hv2-vif1 external-ids:iface-id=sw1p1 \
++ options:tx_pcap=hv2/vif1-tx.pcap \
++ options:rxq_pcap=hv2/vif1-rx.pcap \
++ ofport-request=1
++
++# Pre-populate the hypervisors' ARP tables so that we don't lose any
++# packets for ARP resolution (native tunneling doesn't queue packets
++# for ARP resolution).
++OVN_POPULATE_ARP
++
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
++
++# Send an IP packet from sw0p1 (10.0.0.1, the /31 peer) to sw1p1 (20.0.0.2).
++# Before the /31 fix this packet was dropped by the L3 admission control,
++# which treated 10.0.0.1 as the broadcast address of the 10.0.0.0/31 network.
++packet=$(fmt_pkt "Ether(dst='00:00:00:01:02:03', \
++ src='f0:00:00:01:02:03') / \
++ IP(src='10.0.0.1', dst='20.0.0.2', ttl=64) / \
++ UDP(sport=53, dport=4369)")
++as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
++
++# Packet to expect at 'sw1p1'.
++packet=$(fmt_pkt "Ether(dst='f0:00:00:01:02:04', \
++ src='00:00:00:01:02:04') / \
++ IP(src='10.0.0.1', dst='20.0.0.2', ttl=63) / \
++ UDP(sport=53, dport=4369)")
++echo $packet > expected
++
++OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
++
++OVN_CLEANUP([hv1],[hv2])
++
++AT_CLEANUP
++])
++
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([2 HVs, 1 lport/HV, localport ports])
+ ovn_start
+@@ -14202,13 +14369,13 @@ for i in 1 2; do
+ set Interface vif01 external-ids:iface-id=lp01 \
+ options:tx_pcap=hv${i}/vif${i}0-tx.pcap \
+ options:rxq_pcap=hv${i}/vif${i}0-rx.pcap \
+- ofport-request=${i}0
++ ofport-request=10${i}0
+
+ ovs-vsctl add-port br-int vif${i}1 -- \
+ set Interface vif${i}1 external-ids:iface-id=lp${i}1 \
+ options:tx_pcap=hv${i}/vif${i}1-tx.pcap \
+ options:rxq_pcap=hv${i}/vif${i}1-rx.pcap \
+- ofport-request=${i}1
++ ofport-request=10${i}1
+
+ check ovn-nbctl lsp-add ls1 lp${i}1
+ check ovn-nbctl lsp-set-addresses lp${i}1 f0:00:00:00:00:${i}1
+@@ -14305,8 +14472,8 @@ for i in 1 2; do
+ done
+
+ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int |awk '/table=OFTABLE_LOG_TO_PHY/ && !/actions=drop/{print substr($8, 16, length($8))}' |sort -n], [0], [dnl
+-10
+-11
++1010
++1011
+ ])
+
+ # remove the localport from br-int and re-create it
+@@ -14314,16 +14481,16 @@ as hv1
+ check ovs-vsctl del-port vif01
+ check ovn-nbctl --wait=hv sync
+ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int |awk '/table=OFTABLE_LOG_TO_PHY/ && !/actions=drop/{print substr($8, 16, length($8))}' |sort -n], [0], [dnl
+-11
++1011
+ ])
+
+ as hv1
+ check ovs-vsctl add-port br-int vif01 \
+- -- set Interface vif01 external-ids:iface-id=lp01
++ -- set Interface vif01 external-ids:iface-id=lp01 ofport-request=1012
+ check ovn-nbctl --wait=hv sync
+-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int |awk '/table=OFTABLE_LOG_TO_PHY/ && !/actions=drop/{print substr($8, 16, length($8))}' |sort -n], [0], [dnl
+-2
+-11
++AT_CHECK_UNQUOTED([as hv1 ovs-ofctl dump-flows br-int |awk '/table=OFTABLE_LOG_TO_PHY/ && !/actions=drop/{print substr($8, 16, length($8))}' |sort -n], [0], [dnl
++1011
++1012
+ ])
+
+ OVN_CLEANUP([hv1],[hv2])
+@@ -14334,7 +14501,7 @@ AT_CLEANUP
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([localport suppress gARP])
+ AT_SKIP_IF([test $HAVE_TCPDUMP = no])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ net_add n1
+@@ -14399,7 +14566,7 @@ AT_CLEANUP
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([localport doesn't suppress ARP directed to external port])
+ AT_SKIP_IF([test $HAVE_TCPDUMP = no])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+
+ ovn_start
+ net_add n1
+@@ -14580,8 +14747,7 @@ for i in 1 2; do
+ ovs-vsctl -- add-port br-int hv$i-vif1 -- \
+ set interface hv$i-vif1 external-ids:iface-id=inside$i \
+ options:tx_pcap=hv$i/vif1-tx.pcap \
+- options:rxq_pcap=hv$i/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv$i/vif1-rx.pcap
+
+ check ovn-nbctl lsp-add inside inside$i \
+ -- lsp-set-addresses inside$i "f0:00:00:01:22:$i 192.168.1.10$i"
+@@ -14921,8 +15087,7 @@ for i in 1 2; do
+ ovs-vsctl -- add-port br-int hv$i-vif1 -- \
+ set interface hv$i-vif1 external-ids:iface-id=inside$i \
+ options:tx_pcap=hv$i/vif1-tx.pcap \
+- options:rxq_pcap=hv$i/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv$i/vif1-rx.pcap
+ done
+
+ # Re-add gw2
+@@ -15036,7 +15201,11 @@ grep "dl_dst=00:00:02:01:02:04" | wc -l], [0], [[0
+ # check that the chassis redirect port has been reclaimed by the gw1 chassis
+ wait_row_count Port_Binding 1 logical_port=cr-outside chassis=$gw1_chassis
+
+-OVN_CLEANUP([gw1],[gw2],[hv1],[hv2])
++OVN_CLEANUP([gw1
++/Incorrect your_disc/d
++],[gw2
++/Incorrect your_disc/d
++],[hv1],[hv2])
+
+ AT_CLEANUP
+ ])
+@@ -15211,8 +15380,7 @@ ovn_attach n1 br-phys 192.168.0.11
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=inside1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ check ovn-nbctl lsp-add inside inside1 \
+ -- lsp-set-addresses inside1 "f0:00:00:01:22:01 192.168.1.101"
+@@ -15285,7 +15453,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([allow IPv6 RA / NA / MLD by default])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+ net_add n1
+ sim_add hv1
+@@ -15478,8 +15646,7 @@ ovn_attach n1 br-phys 192.168.0.2
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=sw0_ip6-port1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+ ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+
+ sim_add hv2
+@@ -15490,8 +15657,7 @@ ovn_attach n1 br-phys 192.168.0.3
+ ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=sw1_ip6-port1 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif1-rx.pcap
+ ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+
+ check ovn-nbctl --wait=sb sync
+@@ -16225,7 +16391,7 @@ AT_CLEANUP
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([localnet connectivity with multiple requested-chassis])
+ AT_KEYWORDS([multi-chassis])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ net_add n1
+@@ -16512,11 +16678,7 @@ echo $request >> hv2/n1.expected
+
+ check_packets
+
+-# Wait for MAC address of migrator to be on hv1 related port in main switch.
+-# Hence the MAC will not migrate back unexpectedly later.
+-p1=$(as main ovs-ofctl show n1 | grep hv1_br-phys | awk '{print int($1)}')
+ p2=$(as main ovs-ofctl show n1 | grep hv2_br-phys | awk '{print int($1)}')
+-OVS_WAIT_UNTIL([test x`as main ovs-appctl fdb/show n1 | grep 00:00:00:00:00:ff | awk '{print $1}'` = x$p1])
+
+ # Complete migration: destination is bound
+ check ovn-nbctl lsp-set-options migrator requested-chassis=hv2
+@@ -16529,13 +16691,14 @@ wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+ OVS_WAIT_UNTIL([test `as hv2 ovs-vsctl get Interface migrator external_ids:ovn-installed` = '"true"'])
+
+-# advertise new location of the port through localnet port
+-send_garp hv2 migrator 2 "00:00:00:00:00:ff" "ff:ff:ff:ff:ff:ff" 10.0.0.100 10.0.0.100
+-
+ reset_env
+
++# advertise new location of the port through localnet port
+ # Wait for MAC address of migrator to be on hv2 port in main switch
+-OVS_WAIT_UNTIL([test x`as main ovs-appctl fdb/show n1 | grep 00:00:00:00:00:ff | awk '{print $1}'` = x$p2])
++OVS_WAIT_UNTIL([
++ send_garp hv2 migrator 2 "00:00:00:00:00:ff" "ff:ff:ff:ff:ff:ff" 10.0.0.100 10.0.0.100
++ test x`as main ovs-appctl fdb/show n1 | grep 00:00:00:00:00:ff | awk '{print $1}'` = x$p2
++])
+
+ # check that...
+ # unicast from Third doesn't arrive to hv1:Migrator
+@@ -16614,7 +16777,7 @@ m4_define([MULTICHASSIS_PATH_MTU_DISCOVERY_TEST],
+ [OVN_FOR_EACH_NORTHD([
+ AT_SETUP([localnet connectivity with multiple requested-chassis, path mtu discovery (ip=$1, tunnel=$2, mtu=$3)])
+ AT_KEYWORDS([multi-chassis])
+- AT_SKIP_IF([test $HAVE_SCAPY = no])
++ CHECK_SCAPY
+
+ ovn_start
+
+@@ -16667,13 +16830,11 @@ m4_define([MULTICHASSIS_PATH_MTU_DISCOVERY_TEST],
+ as hv1 check ovs-vsctl -- add-port br-int first -- \
+ set Interface first external-ids:iface-id=first \
+ options:tx_pcap=hv1/first-tx.pcap \
+- options:rxq_pcap=hv1/first-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/first-rx.pcap
+ as hv2 check ovs-vsctl -- add-port br-int second -- \
+ set Interface second external-ids:iface-id=second \
+ options:tx_pcap=hv2/second-tx.pcap \
+- options:rxq_pcap=hv2/second-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv2/second-rx.pcap
+
+ # Create interfaces for multichassis ports on both hv1 and hv2
+ for hv in hv1 hv2; do
+@@ -16681,8 +16842,7 @@ m4_define([MULTICHASSIS_PATH_MTU_DISCOVERY_TEST],
+ as $hv check ovs-vsctl -- add-port br-int multi${i} -- \
+ set Interface multi${i} external-ids:iface-id=multi${i} \
+ options:tx_pcap=$hv/multi${i}-tx.pcap \
+- options:rxq_pcap=$hv/multi${i}-rx.pcap \
+- ofport-request=${i}00
++ options:rxq_pcap=$hv/multi${i}-rx.pcap
+ done
+ done
+
+@@ -16950,7 +17110,7 @@ m4_define([ACTIVATION_STRATEGY_TEST],
+ AT_SETUP([options:activation-strategy=$1 for logical port])
+ AT_KEYWORDS([multi-chassis])
+ AT_KEYWORDS([slowtest])
+- AT_SKIP_IF([test $HAVE_SCAPY = no])
++ CHECK_SCAPY
+ ovn_start
+
+ net_add n1
+@@ -17644,7 +17804,7 @@ as hv1
+ ovs-vsctl set Open_vSwitch . external-ids:hostname=hv1-hostname
+ ovs-vsctl add-br br-phys
+ ovn_attach n1 br-phys 192.168.0.11
+-ovs-vsctl -- add-port br-int hv1-vif0 -- set Interface hv1-vif0 ofport-request=1
++ovs-vsctl -- add-port br-int hv1-vif0
+
+ wait_row_count Chassis 1 name=hv1
+ hv1_hostname=$(fetch_column Chassis hostname name=hv1)
+@@ -17723,8 +17883,7 @@ for i in 1 2 ; do
+ ovs-vsctl -- add-port br-int hv$i-vif1 -- \
+ set interface hv$i-vif1 external-ids:iface-id=sw-p$i \
+ options:tx_pcap=hv$i/vif1-tx.pcap \
+- options:rxq_pcap=hv$i/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv$i/vif1-rx.pcap
+ done
+
+ OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up sw-p1` = xup])
+@@ -17976,8 +18135,7 @@ for i in 1 2 ; do
+ check ovs-vsctl -- add-port br-int hv$i-vif1 -- \
+ set interface hv$i-vif1 external-ids:iface-id=sw-p$i \
+ options:tx_pcap=hv$i/vif1-tx.pcap \
+- options:rxq_pcap=hv$i/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv$i/vif1-rx.pcap
+ done
+
+ wait_for_ports_up
+@@ -18085,29 +18243,53 @@ OVN_CLEANUP([hv1],[hv2])
+ AT_CLEANUP
+ ])
+
+-
++dnl OVN_TEST_PERIODIC_RA_LOCALNET([mode])
++dnl
++dnl Tests that periodic IPv6 RAs on a gateway or distributed gateway port
++dnl are forwarded through localnet ports to the provider network.
++dnl
++dnl mode: "l3gateway" - LR with options:chassis (gateway router)
++dnl "dgp" - LRP with gateway_chassis (distributed gateway port)
++m4_define([OVN_TEST_PERIODIC_RA_LOCALNET], [dnl
+ OVN_FOR_EACH_NORTHD([
+-AT_SETUP([IPv6 periodic gateway RA enabled for localnet adjacent switch ports])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++AT_SETUP([IPv6 periodic $1 RA enabled for localnet adjacent switch ports])
++CHECK_SCAPY
+ ovn_start
+
+ net_add n1
++# Separate localnet networks per chassis.
++net_add n-phys1
++net_add n-phys2
++
+ sim_add hv1
+-sim_add hv2
+ as hv1
+ check ovs-vsctl add-br br-phys
+-check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+ ovn_attach n1 br-phys 192.168.0.2
++check ovs-vsctl add-br br-ln
++check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys1:br-ln
++net_attach n-phys1 br-ln
++
++sim_add hv2
+ as hv2
+ check ovs-vsctl add-br br-phys
+-check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+ ovn_attach n1 br-phys 192.168.0.3
++check ovs-vsctl add-br br-ln
++check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys2:br-ln
++net_attach n-phys2 br-ln
+
+-check ovn-nbctl lr-add ro -- set Logical_Router ro options:chassis="hv1"
++check ovn-nbctl lr-add ro
+ check ovn-nbctl lrp-add ro ro-sw 00:00:00:00:00:01
+
++m4_if([$1], [l3gateway], [
++check ovn-nbctl set Logical_Router ro options:chassis="hv1"
++], [
++check ovn-nbctl lrp-set-gateway-chassis ro-sw hv1
++])
++
+ check ovn-nbctl ls-add sw
+-check ovn-nbctl lsp-add-localnet-port sw ln phys
++# Two localnet ports, each mapped to a different physical network.
++check ovn-nbctl lsp-add-localnet-port sw ln1 phys1
++check ovn-nbctl lsp-add-localnet-port sw ln2 phys2
+
+ check ovn-nbctl lsp-add-router-port sw sw-ro ro-sw
+ check ovn-nbctl lsp-add sw sw-p1
+@@ -18115,7 +18297,14 @@ check ovn-nbctl lsp-set-addresses sw-p1 "00:00:00:00:00:02 aef0::200:ff:fe00:2"
+ check ovn-nbctl lsp-add sw sw-p2
+ check ovn-nbctl lsp-set-addresses sw-p2 "00:00:00:00:00:03 aef0::200:ff:fe00:3"
+
+-AT_CHECK([ovn-sbctl get Port_Binding ro-sw type | tr -d '\n'],[0],[l3gateway])
++check ovn-nbctl --wait=sb sync
++
++m4_if([$1], [l3gateway], [dnl
++AT_CHECK([ovn-sbctl get Port_Binding ro-sw type | tr -d '\n'], [0], [l3gateway])
++], [dnl
++AT_CHECK([ovn-sbctl get Port_Binding ro-sw type | tr -d '\n'], [0], [patch])
++AT_CHECK([ovn-sbctl get Port_Binding cr-ro-sw type | tr -d '\n'], [0], [chassisredirect])
++])
+
+ check ovn-nbctl set Logical_Router_Port ro-sw ipv6_ra_configs:send_periodic=true
+ check ovn-nbctl set Logical_Router_Port ro-sw ipv6_ra_configs:address_mode=slaac
+@@ -18127,63 +18316,65 @@ for i in 1 2 ; do
+ check ovs-vsctl -- add-port br-int hv$i-vif1 -- \
+ set interface hv$i-vif1 external-ids:iface-id=sw-p$i \
+ options:tx_pcap=hv$i/vif1-tx.pcap \
+- options:rxq_pcap=hv$i/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv$i/vif1-rx.pcap
+ done
+
+ wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+
+ ra_received() {
+- $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $1 | sed '/^ffffffffffff/d' | wc -l
++ $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" ${1} | sed '/^ffffffffffff/d' | wc -l
+ }
+
+-ra_test() {
+- interface=$1
+- shift 1
++ra_check() {
++ local hv=${1}
++ local interface=${2}
+ local ra_packet=$(fmt_pkt "
+ Ether(src='00:00:00:00:00:01', dst='33:33:00:00:00:01') /
+ IPv6(dst='ff02::1', src='fe80::200:ff:fe00:1') /
+ ICMPv6ND_RA(chlim=255, prf=0, routerlifetime=65535) /
+ ICMPv6NDOptSrcLLAddr(lladdr='00:00:00:00:00:01')
+ ")
+- intname="$interface"
+-
+- for i in hv1 hv2 ; do
+- if echo "$interface" | grep -q -v "br"; then
+- intname="$i-$interface"
+- fi
+- echo $intname
+- as $i reset_pcap_file $intname $i/$interface
+-
+- OVS_WAIT_WHILE([test 0 = $(ra_received $i/$interface-tx.pcap)])
++ local intname="$interface"
++ if echo "$interface" | grep -q -v "br"; then
++ intname="$hv-$interface"
++ fi
++ echo $intname
++ as $hv reset_pcap_file $intname $hv/$interface
+
+- $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $i/$interface-tx.pcap > packets
+- sed -i '/^ffffffffffff/d' packets
++ OVS_WAIT_WHILE([test 0 = $(ra_received $hv/$interface-tx.pcap)])
+
+- echo ${ra_packet} | cut -c -112 > expout
+- AT_CHECK([head -1 packets | cut -c -112], [0], [expout])
++ $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $hv/$interface-tx.pcap > packets
++ sed -i '/^ffffffffffff/d' packets
+
+- # Skip ICMPv6 checksum.
+- echo ${ra_packet} | cut -c 117- > expout
+- AT_CHECK([head -1 packets | cut -c 117-], [0], [expout])
++ echo ${ra_packet} | cut -c -112 > expout
++ AT_CHECK([head -1 packets | cut -c -112], [0], [expout])
+
+- rm -f packets
+- as $i reset_pcap_file $intname $i/$interface
+- done
++ # Skip ICMPv6 checksum.
++ echo ${ra_packet} | cut -c 117- > expout
++ AT_CHECK([head -1 packets | cut -c 117-], [0], [expout])
+
+- rm -f expected
++ rm -f packets
+ }
+
+-# check that RAs are sent
+-ra_test vif1
++# Check that RAs are sent to VIFs on both nodes.
++ra_check hv1 vif1
++ra_check hv2 vif1
+
+-# check that RAs are recived on br-phys
+-ra_test br-phys
++# Check that the active gateway chassis (hv1) sends RAs to its localnet.
++ra_check hv1 br-ln
++
++# hv2 is not an active gateway, so it must not send RAs to localnet.
++# We already checked pcaps for 3 other ports and found RAs, if there were
++# any sent, they would be already in the pcap.
++AT_CHECK([test 0 = $(ra_received hv2/br-ln-tx.pcap)])
+
+ OVN_CLEANUP([hv1],[hv2])
+ AT_CLEANUP
+-])
++])])
++
++OVN_TEST_PERIODIC_RA_LOCALNET([l3gateway])
++OVN_TEST_PERIODIC_RA_LOCALNET([dgp])
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([ACL reject rule test])
+@@ -18325,8 +18516,7 @@ for i in 1 2 3; do
+ set interface vif$i$j \
+ external-ids:iface-id=sw0-p$i$j \
+ options:tx_pcap=hv$i/vif$i$j-tx.pcap \
+- options:rxq_pcap=hv$i/vif$i$j-rx.pcap \
+- ofport-request=$i$j
++ options:rxq_pcap=hv$i/vif$i$j-rx.pcap
+ done
+ done
+
+@@ -18402,8 +18592,7 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=sw0-p1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ sim_add hv2
+ as hv2
+@@ -18412,8 +18601,7 @@ ovn_attach n1 br-phys 192.168.0.2
+ ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=sw0-p2 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif1-rx.pcap
+
+ check ovn-nbctl ls-add sw0
+
+@@ -18522,14 +18710,12 @@ ovn_attach n1 br-phys 192.168.1.11
+ ovs-vsctl -- add-port br-int vif1 -- \
+ set interface vif1 external-ids:iface-id=ls1-lp1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ ovs-vsctl -- add-port br-int vif2 -- \
+ set interface vif2 external-ids:iface-id=ls2-lp1 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+ ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
+
+@@ -18755,25 +18941,21 @@ ovn-appctl -t ovn-controller vlog/set file:dbg
+ ovs-vsctl -- add-port br-int vif1 -- \
+ set interface vif1 external-ids:iface-id=ls1-lp1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+ ovs-vsctl -- add-port br-int vif2 -- \
+ set interface vif2 external-ids:iface-id=ls1-lp2 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+ # Add two ports as mirroring target
+ ovs-vsctl -- add-port br-int mirror1 -- \
+ set interface mirror1 external-ids:mirror-id=sink1 \
+ options:tx_pcap=hv1/mirror1-tx.pcap \
+- options:rxq_pcap=hv1/mirror1-rx.pcap \
+- ofport-request=3
++ options:rxq_pcap=hv1/mirror1-rx.pcap
+ ovs-vsctl -- add-port br-int mirror2 -- \
+ set interface mirror2 external-ids:mirror-id=sink2 \
+ options:tx_pcap=hv1/mirror2-tx.pcap \
+- options:rxq_pcap=hv1/mirror2-rx.pcap \
+- ofport-request=4
++ options:rxq_pcap=hv1/mirror2-rx.pcap
+
+ # Create a NB mirror use DB 'create' command.
+ uuid1=$(ovn-nbctl create mirror name=mirror-from-lp1 type=local sink=sink1 filter=from-lport)
+@@ -19200,8 +19382,7 @@ ovn_attach n1 br-phys 192.168.0.5
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ sim_add hv2
+ as hv2
+@@ -19210,8 +19391,7 @@ ovn_attach n1 br-phys 192.168.0.6
+ ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=ls2-lp1 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv2/vif1-rx.pcap
+
+ sim_add hv3
+ as hv3
+@@ -19220,8 +19400,7 @@ ovn_attach n1 br-phys 192.168.0.7
+ ovs-vsctl -- add-port br-int hv3-vif1 -- \
+ set interface hv3-vif1 external-ids:iface-id=ls2-lp2 \
+ options:tx_pcap=hv3/vif1-tx.pcap \
+- options:rxq_pcap=hv3/vif1-rx.pcap \
+- ofport-request=3
++ options:rxq_pcap=hv3/vif1-rx.pcap
+
+
+ # Pre-populate the hypervisors' ARP tables.
+@@ -19558,8 +19737,7 @@ for i in 1 2 3; do
+ -- set Interface vif$i$j$k \
+ external-ids:iface-id=lp$i$j$k \
+ options:tx_pcap=hv$hv/vif$i$j$k-tx.pcap \
+- options:rxq_pcap=hv$hv/vif$i$j$k-rx.pcap \
+- ofport-request=$i$j$k
++ options:rxq_pcap=hv$hv/vif$i$j$k-rx.pcap
+ done
+ done
+ done
+@@ -19778,8 +19956,7 @@ for i in 1 2 3; do
+ -- set Interface vif$i$j$k \
+ external-ids:iface-id=lp$i$j$k \
+ options:tx_pcap=hv$hv/vif$i$j$k-tx.pcap \
+- options:rxq_pcap=hv$hv/vif$i$j$k-rx.pcap \
+- ofport-request=$i$j$k
++ options:rxq_pcap=hv$hv/vif$i$j$k-rx.pcap
+ done
+ done
+ done
+@@ -20071,14 +20248,12 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=ls1-lp2 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+ check_uuid ovn-nbctl create Address_Set name=set1 \
+ addresses=\"10.0.0.4\",\"10.0.0.5\",\"10.0.0.6\"
+@@ -20371,14 +20546,12 @@ ovn_attach n1 br-phys 192.168.0.1
+ check ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ check ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=ls1-lp2 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+ # test_ip INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT...
+ #
+@@ -20644,14 +20817,12 @@ ovn_attach n1 br-phys 192.168.0.1
+ check ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ check ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=ls1-lp2 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+ check_uuid ovn-nbctl create address_set name=as1 addresses="10.0.0.1"
+ check_uuid ovn-nbctl create address_set name=as2 addresses="10.0.0.11,10.0.0.12"
+@@ -20715,7 +20886,7 @@ for i in 1 2 3; do
+ ovs-vsctl add-br br-phys
+ ovn_attach n1 br-phys 192.168.0.$i
+
+- ovs-vsctl add-port br-int vif${i}1 -- set Interface vif${i}1 external-ids:iface-id=lp${i}1 options:tx_pcap=hv$i/vif${i}1-tx.pcap options:rxq_pcap=hv$i/vif${i}1-rx.pcap ofport-request=${i}1
++ ovs-vsctl add-port br-int vif${i}1 -- set Interface vif${i}1 external-ids:iface-id=lp${i}1 options:tx_pcap=hv$i/vif${i}1-tx.pcap options:rxq_pcap=hv$i/vif${i}1-rx.pcap
+ check ovn-nbctl lsp-add lsw0 lp${i}1
+ check ovn-nbctl lsp-set-addresses lp${i}1 "f0:00:00:00:00:${i}1 192.168.0.${i}1" unknown
+ done
+@@ -20975,8 +21146,7 @@ for i in 1 2; do
+ set interface vif$i \
+ external-ids:iface-id=sw$i-p${i}0 \
+ options:tx_pcap=hv$i/vif$i-tx.pcap \
+- options:rxq_pcap=hv$i/vif$i-rx.pcap \
+- ofport-request=$i
++ options:rxq_pcap=hv$i/vif$i-rx.pcap
+
+ ovn-appctl -t ovn-controller vlog/set file:dbg:pinctrl
+ done
+@@ -21208,8 +21378,7 @@ for i in 1 2; do
+ set interface vif$i \
+ external-ids:iface-id=sw$i-p${i}0 \
+ options:tx_pcap=hv$i/vif$i-tx.pcap \
+- options:rxq_pcap=hv$i/vif$i-rx.pcap \
+- ofport-request=$i
++ options:rxq_pcap=hv$i/vif$i-rx.pcap
+ done
+
+ check ovn-nbctl lr-add lr0
+@@ -21287,8 +21456,7 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=sw1-p1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ sim_add hv2
+ as hv2
+@@ -21297,8 +21465,7 @@ ovn_attach n1 br-phys 192.168.0.2
+ ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=sw2-p1 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif1-rx.pcap
+
+ OVN_POPULATE_ARP
+
+@@ -21404,8 +21571,7 @@ ovs-vsctl add-br br-phys
+ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-phys hv1-ext1 -- \
+ set interface hv1-ext1 options:tx_pcap=hv1/ext1-tx.pcap \
+- options:rxq_pcap=hv1/ext1-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/ext1-rx.pcap
+ ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+
+ as hv2
+@@ -21413,8 +21579,7 @@ ovs-vsctl add-br br-phys
+ ovn_attach n1 br-phys 192.168.0.2
+ ovs-vsctl -- add-port br-phys hv2-ext2 -- \
+ set interface hv2-ext2 options:tx_pcap=hv2/ext2-tx.pcap \
+- options:rxq_pcap=hv2/ext2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv2/ext2-rx.pcap
+ ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+
+ as hv3
+@@ -21422,8 +21587,7 @@ ovs-vsctl add-br br-phys
+ ovn_attach n1 br-phys 192.168.0.3
+ ovs-vsctl -- add-port br-phys hv3-ext3 -- \
+ set interface hv3-ext3 options:tx_pcap=hv3/ext3-tx.pcap \
+- options:rxq_pcap=hv3/ext3-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv3/ext3-rx.pcap
+ ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+
+ # No DHCPv4/v6 flows for the external port - ls1-lp_ext1 - 10.0.0.6 in hv1 and
+@@ -21492,10 +21656,6 @@ grep controller | grep tp_src=546 | grep \
+ check ovn-nbctl --wait=hv lsp-add-localnet-port ls1 ln-public phys
+
+ ln_public_key=$(fetch_column Port_Binding tunnel_key logical_port=ln-public)
+-# DHCP lflows for external ports now match on the external LSP's inport
+-# (after the inport-rewrite at table 0), so the OF flow's reg14 value is
+-# the external LSP's tunnel_key, not the localnet's.
+-lp_ext1_key=$(fetch_column Port_Binding tunnel_key logical_port=ls1-lp_ext1)
+
+ # The ls1-lp_ext1 should be bound to hv1 as only hv1 is part of the
+ # ha chassis group.
+@@ -21507,13 +21667,13 @@ wait_for_ports_up ls1-lp_ext1
+ (ovn-sbctl dump-flows lr0; ovn-sbctl dump-flows ls1) > sbflows
+ as hv1 ovs-ofctl dump-flows br-int > brintflows
+ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | \
+-grep controller | grep "0a.00.00.06" | grep reg14=0x$lp_ext1_key | \
++grep controller | grep "0a.00.00.06" | grep reg14=0x$ln_public_key | \
+ wc -l], [0], [1
+ ])
+ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | \
+ grep controller | grep tp_src=546 | grep \
+ "ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | \
+-grep reg14=0x$lp_ext1_key | wc -l], [0], [1
++grep reg14=0x$ln_public_key | wc -l], [0], [1
+ ])
+
+ # There should be no DHCPv4/v6 flows for ls1-lp_ext1 on hv2
+@@ -21757,16 +21917,15 @@ ovn-sbctl find port_binding logical_port=ls1-lp_ext1
+ wait_row_count Port_Binding 1 logical_port=ls1-lp_ext1 chassis=$hv2_uuid
+ wait_for_ports_up ls1-lp_ext1
+
+-# There should be OF flows for DHCP4/v6 for the ls1-lp_ext1 port in hv2.
+-# reg14 carries the external LSP's tunnel_key (after inport rewrite).
++# There should be OF flows for DHCP4/v6 for the ls1-lp_ext1 port in hv2
+ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | \
+-grep controller | grep "0a.00.00.06" | grep reg14=0x$lp_ext1_key | \
++grep controller | grep "0a.00.00.06" | grep reg14=0x$ln_public_key | \
+ wc -l], [0], [1
+ ])
+ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | \
+ grep controller | grep tp_src=546 | grep \
+ "ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | \
+-grep reg14=0x$lp_ext1_key | wc -l], [0], [1
++grep reg14=0x$ln_public_key | wc -l], [0], [1
+ ])
+
+ # There should be no DHCPv4/v6 flows for ls1-lp_ext1 on hv1
+@@ -21776,7 +21935,7 @@ grep controller | grep "0a.00.00.06" | wc -l], [0], [0
+ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | \
+ grep controller | grep tp_src=546 | grep \
+ "ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | \
+-grep reg14=0x$lp_ext1_key | wc -l], [0], [0
++grep reg14=0x$ln_public_key | wc -l], [0], [0
+ ])
+
+ # Send DHCPDISCOVER again for hv1/ext1. The DHCP response should come from
+@@ -22166,8 +22325,7 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=sw1-p1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ sim_add hv2
+ as hv2
+@@ -22176,8 +22334,7 @@ ovn_attach n1 br-phys 192.168.0.2
+ ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=sw2-p1 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif1-rx.pcap
+
+ OVN_POPULATE_ARP
+
+@@ -22314,13 +22471,11 @@ ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=sw0-port1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+ ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=sw0-port2 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+ # IPv4 outgoing traffic generated inside the cluster
+ test_ip_packet_larger() {
+@@ -22689,7 +22844,7 @@ AT_CLEANUP
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([IP packet buffering])
+ AT_KEYWORDS([ip-buffering])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ # Logical network:
+@@ -22750,8 +22905,7 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=sw0-p0 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ sim_add hv2
+ as hv2
+@@ -22760,13 +22914,11 @@ ovn_attach n1 br-phys 192.168.0.2
+ ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=sw1-p0 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv2/vif1-rx.pcap
+ ovs-vsctl -- add-port br-int hv2-vif2 -- \
+ set interface hv2-vif2 external-ids:iface-id=sw0-p1 \
+ options:tx_pcap=hv2/vif2-tx.pcap \
+- options:rxq_pcap=hv2/vif2-rx.pcap \
+- ofport-request=3
++ options:rxq_pcap=hv2/vif2-rx.pcap
+
+ check_uuid ovn-nbctl create Logical_Router name=lr0
+ check ovn-nbctl ls-add sw0
+@@ -23323,8 +23475,7 @@ for i in 1 2; do
+ ovs-vsctl add-port br-int vif$i$i -- \
+ set Interface vif$i$i external-ids:iface-id=lp$i$i \
+ options:tx_pcap=hv$i/vif$i$i-tx.pcap \
+- options:rxq_pcap=hv$i/vif$i$i-rx.pcap \
+- ofport-request=$i$i
++ options:rxq_pcap=hv$i/vif$i$i-rx.pcap
+
+ lsp_name=lp$i$i
+ ls_name=$(lsp_to_ls $lsp_name)
+@@ -23427,7 +23578,7 @@ AT_CLEANUP
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([virtual ports])
+ AT_KEYWORDS([virtual ports])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ send_nd_ns() {
+@@ -23461,18 +23612,15 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=sw0-p1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+ ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=sw0-p3 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/vif2-rx.pcap
+ ovs-vsctl -- add-port br-int hv1-vif3 -- \
+ set interface hv1-vif3 \
+ options:tx_pcap=hv1/vif3-tx.pcap \
+- options:rxq_pcap=hv1/vif3-rx.pcap \
+- ofport-request=3
++ options:rxq_pcap=hv1/vif3-rx.pcap
+
+ ovn-appctl -t ovn-controller vlog/set dbg
+
+@@ -23483,13 +23631,11 @@ ovn_attach n1 br-phys 192.168.0.2
+ ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=sw0-p2 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif1-rx.pcap
+ ovs-vsctl -- add-port br-int hv2-vif2 -- \
+ set interface hv2-vif2 external-ids:iface-id=sw1-p1 \
+ options:tx_pcap=hv2/vif2-tx.pcap \
+- options:rxq_pcap=hv2/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv2/vif2-rx.pcap
+
+ check ovn-nbctl ls-add sw0
+
+@@ -24044,8 +24190,7 @@ for i in 1 2; do
+ set interface vif$i$j \
+ external-ids:iface-id=sw0-p$i$j \
+ options:tx_pcap=hv$i/vif$i$j-tx.pcap \
+- options:rxq_pcap=hv$i/vif$i$j-rx.pcap \
+- ofport-request=$i$j
++ options:rxq_pcap=hv$i/vif$i$j-rx.pcap
+ done
+ done
+
+@@ -24056,8 +24201,7 @@ ovs-vsctl -- add-port br-int vif33 -- \
+ set interface vif33 \
+ external-ids:iface-id=sw1-p0 \
+ options:tx_pcap=hv$i/vif33-tx.pcap \
+- options:rxq_pcap=hv$i/vif33-rx.pcap \
+- ofport-request=33
++ options:rxq_pcap=hv$i/vif33-rx.pcap
+
+ check ovn-nbctl --event lb-add lb0 192.168.1.100:80 ""
+ check ovn-nbctl ls-lb-add sw0 lb0
+@@ -24207,23 +24351,19 @@ ovn_attach n1 br-phys 192.168.0.1
+ check ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=sw1-p11 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+ check ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=sw1-p12 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif2-rx.pcap
+ check ovs-vsctl -- add-port br-int hv1-vif3 -- \
+ set interface hv1-vif3 external-ids:iface-id=sw2-p1 \
+ options:tx_pcap=hv1/vif3-tx.pcap \
+- options:rxq_pcap=hv1/vif3-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif3-rx.pcap
+ check ovs-vsctl -- add-port br-int hv1-vif4 -- \
+ set interface hv1-vif4 external-ids:iface-id=sw3-p1 \
+ options:tx_pcap=hv1/vif4-tx.pcap \
+- options:rxq_pcap=hv1/vif4-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif4-rx.pcap
+ ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+
+ sim_add hv2
+@@ -24233,23 +24373,19 @@ ovn_attach n1 br-phys 192.168.0.2
+ check ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=sw1-p21 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif1-rx.pcap
+ check ovs-vsctl -- add-port br-int hv2-vif2 -- \
+ set interface hv2-vif2 external-ids:iface-id=sw1-p22 \
+ options:tx_pcap=hv2/vif2-tx.pcap \
+- options:rxq_pcap=hv2/vif2-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif2-rx.pcap
+ check ovs-vsctl -- add-port br-int hv2-vif3 -- \
+ set interface hv2-vif3 external-ids:iface-id=sw2-p2 \
+ options:tx_pcap=hv2/vif3-tx.pcap \
+- options:rxq_pcap=hv2/vif3-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif3-rx.pcap
+ check ovs-vsctl -- add-port br-int hv2-vif4 -- \
+ set interface hv2-vif4 external-ids:iface-id=sw3-p2 \
+ options:tx_pcap=hv2/vif4-tx.pcap \
+- options:rxq_pcap=hv2/vif4-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif4-rx.pcap
+ ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+
+ AT_CAPTURE_FILE([exp])
+@@ -24930,23 +25066,19 @@ ovn_attach n1 br-phys 192.168.0.1
+ check ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=sw1-p11 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+ check ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=sw1-p12 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif2-rx.pcap
+ check ovs-vsctl -- add-port br-int hv1-vif3 -- \
+ set interface hv1-vif3 external-ids:iface-id=sw2-p1 \
+ options:tx_pcap=hv1/vif3-tx.pcap \
+- options:rxq_pcap=hv1/vif3-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif3-rx.pcap
+ check ovs-vsctl -- add-port br-int hv1-vif4 -- \
+ set interface hv1-vif4 external-ids:iface-id=sw3-p1 \
+ options:tx_pcap=hv1/vif4-tx.pcap \
+- options:rxq_pcap=hv1/vif4-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif4-rx.pcap
+ check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+
+ sim_add hv2
+@@ -24956,23 +25088,19 @@ ovn_attach n1 br-phys 192.168.0.2
+ check ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=sw1-p21 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif1-rx.pcap
+ check ovs-vsctl -- add-port br-int hv2-vif2 -- \
+ set interface hv2-vif2 external-ids:iface-id=sw1-p22 \
+ options:tx_pcap=hv2/vif2-tx.pcap \
+- options:rxq_pcap=hv2/vif2-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif2-rx.pcap
+ check ovs-vsctl -- add-port br-int hv2-vif3 -- \
+ set interface hv2-vif3 external-ids:iface-id=sw2-p2 \
+ options:tx_pcap=hv2/vif3-tx.pcap \
+- options:rxq_pcap=hv2/vif3-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif3-rx.pcap
+ check ovs-vsctl -- add-port br-int hv2-vif4 -- \
+ set interface hv2-vif4 external-ids:iface-id=sw3-p2 \
+ options:tx_pcap=hv2/vif4-tx.pcap \
+- options:rxq_pcap=hv2/vif4-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif4-rx.pcap
+ check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+
+ wait_for_ports_up
+@@ -25605,11 +25733,7 @@ OVS_WAIT_UNTIL(
+ 'hv2/br-phys-1_n1-tx.pcap expected_empty' ],
+ [$at_diff -F'^---' exp rcv])
+
+-OVN_CLEANUP([hv1
+-/left allocated/d
+-], [hv2
+-/left allocated/d
+-])
++OVN_CLEANUP([hv1], [hv2])
+ AT_CLEANUP
+ ])
+
+@@ -25706,8 +25830,7 @@ m4_define([DVR_N_S_ARP_HANDLING],
+ ovs-vsctl add-port br-int vif$i$i -- \
+ set Interface vif$i$i external-ids:iface-id=lp$i$i \
+ options:tx_pcap=hv$i/vif$i$i-tx.pcap \
+- options:rxq_pcap=hv$i/vif$i$i-rx.pcap \
+- ofport-request=$i$i
++ options:rxq_pcap=hv$i/vif$i$i-rx.pcap
+
+ lsp_name=lp$i$i
+ ls_name=$(lsp_to_ls $lsp_name)
+@@ -25750,8 +25873,7 @@ m4_define([DVR_N_S_ARP_HANDLING],
+ as hv4 ovs-vsctl add-port br-int vif-north -- \
+ set Interface vif-north external-ids:iface-id=lp-north \
+ options:tx_pcap=hv4/vif-north-tx.pcap \
+- options:rxq_pcap=hv4/vif-north-rx.pcap \
+- ofport-request=44
++ options:rxq_pcap=hv4/vif-north-rx.pcap
+
+ check ovn-nbctl lr-add router
+ check ovn-nbctl lrp-add router router-to-ls1 00:00:01:01:02:03 192.168.1.3/24
+@@ -25983,8 +26105,7 @@ m4_define([DVR_N_S_PING],
+ ovs-vsctl add-port br-int vif$i$i -- \
+ set Interface vif$i$i external-ids:iface-id=lp$i$i \
+ options:tx_pcap=hv$i/vif$i$i-tx.pcap \
+- options:rxq_pcap=hv$i/vif$i$i-rx.pcap \
+- ofport-request=$i$i
++ options:rxq_pcap=hv$i/vif$i$i-rx.pcap
+
+ lsp_name=lp$i$i
+ ls_name=$(lsp_to_ls $lsp_name)
+@@ -26027,8 +26148,7 @@ m4_define([DVR_N_S_PING],
+ as hv4 ovs-vsctl add-port br-int vif-north -- \
+ set Interface vif-north external-ids:iface-id=lp-north \
+ options:tx_pcap=hv4/vif-north-tx.pcap \
+- options:rxq_pcap=hv4/vif-north-rx.pcap \
+- ofport-request=44
++ options:rxq_pcap=hv4/vif-north-rx.pcap
+
+ check ovn-nbctl lr-add router
+ check ovn-nbctl lrp-add router router-to-ls1 00:00:01:01:02:03 192.168.1.3/24
+@@ -26219,7 +26339,7 @@ DVR_N_S_PING([vxlan])
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([ARP lookup before learning])
+ AT_KEYWORDS([virtual ports])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ send_arp_reply() {
+@@ -26237,13 +26357,11 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=sw0-p1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+ ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=sw0-p3 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+ sim_add hv2
+ as hv2
+@@ -26252,8 +26370,7 @@ ovn_attach n1 br-phys 192.168.0.2
+ ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=sw1-p1 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif1-rx.pcap
+
+ check ovn-nbctl ls-add sw0
+
+@@ -26463,8 +26580,7 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl add-port br-int vif11 -- \
+ set Interface vif11 external-ids:iface-id=lp11 \
+ options:tx_pcap=hv1/vif11-tx.pcap \
+- options:rxq_pcap=hv1/vif11-rx.pcap \
+- ofport-request=11
++ options:rxq_pcap=hv1/vif11-rx.pcap
+
+ lsp_name=lp11
+
+@@ -26569,7 +26685,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([Disabling RARP/GARP announcements from Router options])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ net_add n1
+@@ -26726,8 +26842,7 @@ for i in 1 2; do
+ ovs-vsctl add-port br-int vif$i$i -- \
+ set Interface vif$i$i external-ids:iface-id=lp$i$i \
+ options:tx_pcap=hv$i/vif$i$i-tx.pcap \
+- options:rxq_pcap=hv$i/vif$i$i-rx.pcap \
+- ofport-request=$i$i
++ options:rxq_pcap=hv$i/vif$i$i-rx.pcap
+
+ lsp_name=lp$i$i
+ ls_name=$(lsp_to_ls $lsp_name)
+@@ -26770,8 +26885,7 @@ as hv4 ovn_attach n1 br-phys 192.168.0.4
+ as hv4 ovs-vsctl add-port br-int vif-north -- \
+ set Interface vif-north external-ids:iface-id=lp-north \
+ options:tx_pcap=hv4/vif-north-tx.pcap \
+- options:rxq_pcap=hv4/vif-north-rx.pcap \
+- ofport-request=44
++ options:rxq_pcap=hv4/vif-north-rx.pcap
+
+ check ovn-nbctl lr-add router
+ check ovn-nbctl lrp-add router router-to-ls1 00:00:01:01:02:03 192.168.1.3/24
+@@ -26979,13 +27093,11 @@ ovn_attach n1 br-phys 192.168.0.1
+ check ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=sw0-p1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+ check ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=sw0-p2 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+ sim_add hv2
+ as hv2
+@@ -26994,8 +27106,7 @@ ovn_attach n1 br-phys 192.168.0.2
+ check ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=sw1-p1 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif1-rx.pcap
+
+ check ovn-nbctl ls-add sw0
+
+@@ -27173,13 +27284,11 @@ ovn_attach n1 br-phys 192.168.0.1
+ check ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=sw0-p1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+ check ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=sw0-p2 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+ sim_add hv2
+ as hv2
+@@ -27188,8 +27297,7 @@ ovn_attach n1 br-phys 192.168.0.2
+ check ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=sw1-p1 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif1-rx.pcap
+
+ check ovn-nbctl ls-add sw0
+
+@@ -27364,13 +27472,11 @@ ovn_attach n1 br-phys 192.168.0.1
+ check ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=sw0-p1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+ check ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=sw0-p2 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+ sim_add hv2
+ as hv2
+@@ -27379,8 +27485,7 @@ ovn_attach n1 br-phys 192.168.0.2
+ check ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=sw1-p1 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif1-rx.pcap
+
+ check ovn-nbctl ls-add sw0
+ check ovn-nbctl lsp-add sw0 sw0-p1
+@@ -27517,13 +27622,11 @@ ovn_attach n1 br-phys 192.168.0.1
+ check ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=sw0-p1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+ check ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=sw0-p2 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+ sim_add hv2
+ as hv2
+@@ -27532,8 +27635,7 @@ ovn_attach n1 br-phys 192.168.0.2
+ check ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=sw1-p1 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif1-rx.pcap
+
+ check ovn-nbctl ls-add sw0
+ check ovn-nbctl lsp-add sw0 sw0-p1
+@@ -27681,13 +27783,11 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=sw0-p1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+ ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=sw0-p2 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+ sim_add hv2
+ as hv2
+@@ -27696,8 +27796,7 @@ ovn_attach n1 br-phys 192.168.0.2
+ ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=sw1-p1 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif1-rx.pcap
+
+ check ovn-nbctl ls-add sw0
+
+@@ -27862,24 +27961,21 @@ as hv1
+ check ovs-vsctl -- add-port br-int hv1-vif0 -- \
+ set interface hv1-vif0 external-ids:iface-id=sw-agg-ext \
+ options:tx_pcap=hv1/vif0-tx.pcap \
+- options:rxq_pcap=hv1/vif0-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif0-rx.pcap
+
+ # Bind a "VM" connected to sw1 on hv1.
+ as hv1
+ check ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=sw1-p1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ # Bind a "VM" connected to sw1 on hv2.
+ as hv2
+ check ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=sw1-p2 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=3
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+ OVN_POPULATE_ARP
+ wait_for_ports_up
+@@ -27914,18 +28010,6 @@ for var in sw_dp_uuid sw_dp_key sw1_dp_key r1_dp_key r1_tnl_key r2_tnl_key \
+ echo "$var=$value"
+ done
+
+-as hv1
+-AT_CAPTURE_FILE([offlows])
+-OVS_WAIT_FOR_OUTPUT([
+- ovs-ofctl dump-flows br-int > offlows
+- for match in "$match_send_rtr1" "$match_send_rtr2"; do
+- grep -E "$match_arp_req.*$match" offlows | grep -c 'n_packets=[[1-9]]'
+- done
+- :
+-], [0], [1
+-0
+-])
+-
+ # Inject ND_NS for ofirst router owned IP address.
+ src_ipv6=00100000000000000000000000000254
+ dst_ipv6=00100000000000000000000000000001
+@@ -28247,7 +28331,7 @@ as hv1
+ ovs-vsctl add-br br-phys
+ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl add-port br-int vif1 -- \
+- set interface vif1 external-ids:iface-id=lp1 ofport-request=1
++ set interface vif1 external-ids:iface-id=lp1
+
+ check ovn-nbctl ls-add lsw0
+ check ovn-nbctl lsp-add lsw0 lp1
+@@ -28323,20 +28407,17 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=lsp11 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=lsp21 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+ ovs-vsctl -- add-port br-int hv1-vif3 -- \
+ set interface hv1-vif3 external-ids:iface-id=lsp22 \
+ options:tx_pcap=hv1/vif3-tx.pcap \
+- options:rxq_pcap=hv1/vif3-rx.pcap \
+- ofport-request=3
++ options:rxq_pcap=hv1/vif3-rx.pcap
+
+ # wait for earlier changes to take effect
+ check ovn-nbctl --wait=hv sync
+@@ -28444,20 +28525,17 @@ as hv1
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=lsp11 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=lsp21 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+ ovs-vsctl -- add-port br-int hv1-vif3 -- \
+ set interface hv1-vif3 external-ids:iface-id=lsp22 \
+ options:tx_pcap=hv1/vif3-tx.pcap \
+- options:rxq_pcap=hv1/vif3-rx.pcap \
+- ofport-request=3
++ options:rxq_pcap=hv1/vif3-rx.pcap
+
+ # wait for earlier changes to take effect
+ check ovn-nbctl --wait=hv sync
+@@ -28527,20 +28605,17 @@ as hv2
+ ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=lsp11 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif1-rx.pcap
+
+ ovs-vsctl -- add-port br-int hv2-vif2 -- \
+ set interface hv2-vif2 external-ids:iface-id=lsp21 \
+ options:tx_pcap=hv2/vif2-tx.pcap \
+- options:rxq_pcap=hv2/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv2/vif2-rx.pcap
+
+ ovs-vsctl -- add-port br-int hv2-vif3 -- \
+ set interface hv2-vif3 external-ids:iface-id=lsp22 \
+ options:tx_pcap=hv2/vif3-tx.pcap \
+- options:rxq_pcap=hv2/vif3-rx.pcap \
+- ofport-request=3
++ options:rxq_pcap=hv2/vif3-rx.pcap
+
+ # wait for earlier changes to take effect
+ check ovn-nbctl --wait=hv sync
+@@ -28672,20 +28747,17 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=lsp11 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=lsp21 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+ ovs-vsctl -- add-port br-int hv1-vif3 -- \
+ set interface hv1-vif3 external-ids:iface-id=lsp22 \
+ options:tx_pcap=hv1/vif3-tx.pcap \
+- options:rxq_pcap=hv1/vif3-rx.pcap \
+- ofport-request=3
++ options:rxq_pcap=hv1/vif3-rx.pcap
+
+ # wait for earlier changes to take effect
+ check ovn-nbctl --wait=hv sync
+@@ -28833,14 +28905,12 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=lsp11 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=lsp21 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+ # wait for earlier changes to take effect
+ check ovn-nbctl --wait=hv sync
+@@ -28970,8 +29040,7 @@ for i in $(seq 1 4); do
+ ovs-vsctl -- add-port br-int hv1-vif${i} -- \
+ set interface hv1-vif${i} external-ids:iface-id=lsp${i}1 \
+ options:tx_pcap=hv1/vif${i}-tx.pcap \
+- options:rxq_pcap=hv1/vif${i}-rx.pcap \
+- ofport-request=${i}
++ options:rxq_pcap=hv1/vif${i}-rx.pcap
+ done
+
+ # wait for earlier changes to take effect
+@@ -29082,8 +29151,7 @@ for i in $(seq 1 4); do
+ ovs-vsctl -- add-port br-int hv1-vif${i} -- \
+ set interface hv1-vif${i} external-ids:iface-id=lsp${i}1 \
+ options:tx_pcap=hv1/vif${i}-tx.pcap \
+- options:rxq_pcap=hv1/vif${i}-rx.pcap \
+- ofport-request=${i}
++ options:rxq_pcap=hv1/vif${i}-rx.pcap
+ done
+
+ wait_for_ports_up
+@@ -29169,21 +29237,21 @@ sim_add hv1
+ as hv1
+ check ovs-vsctl add-br br-phys
+ ovn_attach n1 br-phys 192.168.0.1
+-check ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lsp11 options:tx_pcap=hv1/vif1-tx.pcap options:rxq_pcap=hv1/vif1-rx.pcap ofport-request=1
++check ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lsp11 options:tx_pcap=hv1/vif1-tx.pcap options:rxq_pcap=hv1/vif1-rx.pcap
+
+ # Create hypervisor hv2 connected to n1
+ sim_add hv2
+ as hv2
+ check ovs-vsctl add-br br-phys
+ ovn_attach n1 br-phys 192.168.0.2
+-check ovs-vsctl add-port br-int vif2 -- set Interface vif2 external-ids:iface-id=lsp21 options:tx_pcap=hv2/vif2-tx.pcap options:rxq_pcap=hv2/vif2-rx.pcap ofport-request=1
++check ovs-vsctl add-port br-int vif2 -- set Interface vif2 external-ids:iface-id=lsp21 options:tx_pcap=hv2/vif2-tx.pcap options:rxq_pcap=hv2/vif2-rx.pcap
+
+ # Create hypervisor hv3 connected to n1
+ sim_add hv3
+ as hv3
+ check ovs-vsctl add-br br-phys
+ ovn_attach n1 br-phys 192.168.0.3
+-check ovs-vsctl add-port br-int vif3 -- set Interface vif3 external-ids:iface-id=lsp22 options:tx_pcap=hv3/vif3-tx.pcap options:rxq_pcap=hv3/vif3-rx.pcap ofport-request=1
++check ovs-vsctl add-port br-int vif3 -- set Interface vif3 external-ids:iface-id=lsp22 options:tx_pcap=hv3/vif3-tx.pcap options:rxq_pcap=hv3/vif3-rx.pcap
+
+ # Add a forwarding group on ls2 with lsp21 and lsp22 as child ports
+ # virtual IP - 172.16.1.11, virtual MAC - 00:11:de:ad:be:ef
+@@ -29334,8 +29402,7 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=lsp \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ # One logical switch with IPv4 and IPv6 load balancers that hairpin the
+ # traffic.
+@@ -29726,8 +29793,8 @@ sim_add hv1
+ as hv1
+ ovs-vsctl add-br br-int
+ ovs-vsctl add-port br-int p1 \
+- -- set Interface p1 external-ids:iface-id=lsp1 \
+- -- set Interface p1 ofport-request=1
++ -- set Interface p1 external-ids:iface-id=lsp1
++
+ OVS_WAIT_UNTIL([ovs-vsctl list Interface p1])
+
+ ovn_start
+@@ -29796,13 +29863,11 @@ ovn_attach n1 br-phys 192.168.0.1
+ check ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=sw0-port1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+ check ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=sw0-port2 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+ as hv1 check ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=public:br-phys
+
+@@ -30136,13 +30201,11 @@ ovn_attach n1 br-phys 192.168.0.1
+ check ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=sw0-p1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+ check ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=sw0-p2 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+ sim_add hv2
+ as hv2
+@@ -30151,8 +30214,7 @@ ovn_attach n1 br-phys 192.168.0.2
+ ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=sw1-p1 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif1-rx.pcap
+
+ check ovn-nbctl ls-add sw0
+
+@@ -30306,8 +30368,7 @@ as hv1
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=sw0-p1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ wait_for_ports_up sw0-p1
+
+@@ -30326,8 +30387,7 @@ as hv2
+ ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=sw0-p2 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif1-rx.pcap
+
+ wait_for_ports_up sw0-p2
+
+@@ -30370,8 +30430,7 @@ as hv1
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=sw0-p1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ wait_for_ports_up sw0-p1
+
+@@ -30390,8 +30449,7 @@ as hv2
+ ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=sw0-p2 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif1-rx.pcap
+
+ wait_for_ports_up sw0-p2
+
+@@ -30431,8 +30489,7 @@ as hv1
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=sw0-p1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ check ovn-nbctl ls-add sw0
+ check ovn-nbctl lsp-add sw0 sw0-p1
+@@ -30513,15 +30570,13 @@ as hv1
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=sw0-p1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ as hv1
+ ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=sw0-p2 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+ OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p1) = xup])
+ OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p2) = xup])
+@@ -30918,8 +30973,7 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=p1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ sim_add hv2
+ as hv2
+@@ -30928,8 +30982,7 @@ ovn_attach n1 br-phys 192.168.0.2
+ ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=p2 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif1-rx.pcap
+
+
+ # Pre-populate the hypervisors' ARP tables so that we don't lose any
+@@ -31107,12 +31160,10 @@ check ovn-nbctl lsp-add sw lsp2
+ as hv1
+ ovs-vsctl \
+ -- add-port br-int vif1 \
+- -- set Interface vif1 external_ids:iface-id=lsp1 \
+- ofport-request=1
++ -- set Interface vif1 external_ids:iface-id=lsp1
+ ovs-vsctl \
+ -- add-port br-int vif2 \
+- -- set Interface vif2 external_ids:iface-id=lsp2 \
+- ofport-request=2
++ -- set Interface vif2 external_ids:iface-id=lsp2
+
+ # Wait for ports to be bound.
+ wait_row_count Chassis 1 name=hv1
+@@ -31142,10 +31193,10 @@ as hv1
+ ovs-vsctl \
+ -- add-port br-int vif1 \
+ -- set Interface vif1 external_ids:iface-id=lsp1 \
+- ofport-request=3 \
++ ofport-request=1003 \
+ -- add-port br-int vif2 \
+ -- set Interface vif2 external_ids:iface-id=lsp2 \
+- ofport-request=4
++ ofport-request=1004
+
+ as hv1 ovn-appctl -t ovn-controller debug/resume
+ check ovn-nbctl --wait=hv sync
+@@ -31153,10 +31204,10 @@ check ovn-nbctl --wait=hv sync
+ AS_BOX([check output flows for new interfaces])
+ as hv1 ovs-ofctl dump-flows br-int table=OFTABLE_LOG_TO_PHY > offlows_table65_2.txt
+ AT_CAPTURE_FILE([offlows_table65_2.txt])
+-AT_CHECK_UNQUOTED([grep -c "output:3" offlows_table65_2.txt], [0], [dnl
++AT_CHECK_UNQUOTED([grep -c "output:1003" offlows_table65_2.txt], [0], [dnl
+ 1
+ ])
+-AT_CHECK_UNQUOTED([grep -c "output:4" offlows_table65_2.txt], [0], [dnl
++AT_CHECK_UNQUOTED([grep -c "output:1004" offlows_table65_2.txt], [0], [dnl
+ 1
+ ])
+
+@@ -31175,8 +31226,7 @@ ovn_attach n1 br-phys 192.168.0.10
+ as hv1
+ ovs-vsctl \
+ -- add-port br-int vif1 \
+- -- set Interface vif1 external_ids:iface-id=lsp1 \
+- ofport-request=1
++ -- set Interface vif1 external_ids:iface-id=lsp1
+
+ check ovn-nbctl ls-add ls1 \
+ -- ls-add ls2 \
+@@ -31204,8 +31254,7 @@ AS_BOX([readd OVS VIF])
+ as hv1
+ ovs-vsctl \
+ -- add-port br-int vif1 \
+- -- set Interface vif1 external_ids:iface-id=lsp1 \
+- ofport-request=1
++ -- set Interface vif1 external_ids:iface-id=lsp1
+ wait_row_count Port_Binding 1 logical_port=lsp1 chassis=$ch
+
+ AS_BOX([readd OVN container port])
+@@ -31277,14 +31326,12 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=p1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=p2 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+ # Pre-populate the hypervisors' ARP tables so that we don't lose any
+ # packets for ARP resolution (native tunneling doesn't queue packets
+@@ -31505,13 +31552,11 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=sw0-port1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+ ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=sw3-port1 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/vif2-rx.pcap
+ AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=physnet1:br-phys])
+ ovs-vsctl set open . external_ids:ovn-enable-lflow-cache=false
+
+@@ -31522,8 +31567,7 @@ ovn_attach n1 br-phys 192.168.0.2
+ ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=sw0-port2 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif1-rx.pcap
+ AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=physnet1:br-phys])
+ ovs-vsctl set open . external_ids:ovn-enable-lflow-cache=false
+
+@@ -31534,8 +31578,7 @@ ovn_attach n1 br-phys 192.168.0.3
+ ovs-vsctl -- add-port br-int hv3-vif1 -- \
+ set interface hv3-vif1 external-ids:iface-id=sw1-port1 \
+ options:tx_pcap=hv3/vif1-tx.pcap \
+- options:rxq_pcap=hv3/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv3/vif1-rx.pcap
+ AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=physnet1:br-phys])
+ ovs-vsctl set open . external_ids:ovn-enable-lflow-cache=false
+
+@@ -31619,8 +31662,7 @@ check ovn-nbctl --wait=hv sync
+ as hv2
+ ovs-vsctl -- add-port br-phys hv2-phys1 -- \
+ set interface hv2-phys1 options:tx_pcap=hv2/phys1-tx.pcap \
+- options:rxq_pcap=hv2/phys1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/phys1-rx.pcap
+
+ send_arp_request() {
+ local eth_src=$1 spa=$2 tpa=$3
+@@ -31766,14 +31808,12 @@ ovn_attach n1 br-phys 192.168.0.1
+ check ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=lsp1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ check ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=lsp2 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+ # Default drop
+ check ovn-nbctl acl-add ls1 to-lport 1000 \
+@@ -31874,14 +31914,12 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=lsp1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=lsp2 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+ # Default drop
+ check ovn-nbctl acl-add ls1 to-lport 1000 \
+@@ -31964,8 +32002,7 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=lsp1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+@@ -32097,7 +32134,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([multiple encap ips selection based on VIF's encap_ip - L2])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+ net_add n1
+
+@@ -32179,10 +32216,17 @@ check_packet_tunnel() {
+ as $hv
+ echo "vif$src -> vif$dst should go through tunnel $local_encap_ip -> $remote_encap_ip"
+ if test x$flow_based_tunnel == xtrue; then
+- tunnel_ofport=$(ovs-vsctl --bare --column=ofport list interface ovn-geneve)
++ AT_CHECK([ovs-vsctl wait-until interface ovn-geneve 'ofport>0'])
++ tunnel_ofport=$(ovs-vsctl get interface ovn-geneve ofport)
+ else
+- tunnel_ofport=$(ovs-vsctl --bare --column=ofport find interface options:local_ip=$local_encap_ip options:remote_ip=$remote_encap_ip)
++ OVS_WAIT_UNTIL([
++ tunnel_ofport=$(ovs-vsctl --bare --columns ofport find Interface options:local_ip=$local_encap_ip options:remote_ip=$remote_encap_ip)
++ echo "ofport=$tunnel_ofport" >&2
++ test -n "$tunnel_ofport" && test "$tunnel_ofport" -ge 0])
+ fi
++ # Once ofport is in ovs db, ensure ovn-controller handles it.
++ check ovn-nbctl --wait=hv sync
++
+ AT_CHECK([test $(ovs-appctl ofproto/trace br-int in_port=vif$src $packet | grep "output:" | awk -F ':' '{ print $2 }') == $tunnel_ofport])
+ if test x$flow_based_tunnel == xtrue; then
+ trace_output=$(ovs-appctl ofproto/trace br-int in_port=vif$src $packet)
+@@ -32218,7 +32262,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([multiple encap ips selection based on VIF's encap_ip - L3])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+ net_add n1
+
+@@ -32285,10 +32329,16 @@ check_packet_tunnel() {
+ echo "vif$src -> vif$dst should go through tunnel $local_encap_ip -> $remote_encap_ip"
+
+ if test x$flow_based_tunnel == xtrue; then
+- tunnel_ofport=$(ovs-vsctl --bare --column=ofport list interface ovn-geneve)
++ AT_CHECK([ovs-vsctl wait-until interface ovn-geneve 'ofport>0'])
++ tunnel_ofport=$(ovs-vsctl get interface ovn-geneve ofport)
+ else
+- tunnel_ofport=$(ovs-vsctl --bare --column=ofport find interface options:local_ip=$local_encap_ip options:remote_ip=$remote_encap_ip)
++ OVS_WAIT_UNTIL([
++ tunnel_ofport=$(ovs-vsctl --bare --columns ofport find Interface options:local_ip=$local_encap_ip options:remote_ip=$remote_encap_ip)
++ echo "ofport=$tunnel_ofport" >&2
++ test -n "$tunnel_ofport" && test "$tunnel_ofport" -ge 0])
+ fi
++ check ovn-nbctl --wait=hv sync
++
+ AT_CHECK([test $(ovs-appctl ofproto/trace br-int in_port=vif$src $packet | grep "output:" | awk -F ':' '{ print $2 }') == $tunnel_ofport])
+ if test x$flow_based_tunnel == xtrue; then
+ trace_output=$(ovs-appctl ofproto/trace br-int in_port=vif$src $packet)
+@@ -32360,7 +32410,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([multiple encap ips selection for localnet packets])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+ net_add n1
+
+@@ -32435,11 +32485,16 @@ check_packet_tunnel() {
+ as $hv
+ echo "vif$src -> vif$dst should go through tunnel $local_encap_ip -> $remote_encap_ip"
+ if test x$flow_based_tunnel == xtrue; then
+- tunnel_ofport=$(ovs-vsctl --bare --column=ofport list interface ovn-geneve)
++ AT_CHECK([ovs-vsctl wait-until interface ovn-geneve 'ofport>0'])
++ tunnel_ofport=$(ovs-vsctl get interface ovn-geneve ofport)
+ else
+- tunnel_ofport=$(ovs-vsctl --bare --column=ofport find interface options:local_ip=$local_encap_ip options:remote_ip=$remote_encap_ip)
++ OVS_WAIT_UNTIL([
++ tunnel_ofport=$(ovs-vsctl --bare --columns ofport find Interface options:local_ip=$local_encap_ip options:remote_ip=$remote_encap_ip)
++ echo "ofport=$tunnel_ofport" >&2
++ test -n "$tunnel_ofport" && test "$tunnel_ofport" -ge 0])
+ fi
+- ovs-appctl ofproto/trace br-ext in_port=vif$src $packet
++ check ovn-nbctl --wait=hv sync
++
+ AT_CHECK([test $(ovs-appctl ofproto/trace br-ext in_port=vif$src $packet | grep "output:" | awk -F ':' '{ print $2 }') == $tunnel_ofport])
+ if test x$flow_based_tunnel == xtrue; then
+ trace_output=$(ovs-appctl ofproto/trace br-ext in_port=vif$src $packet)
+@@ -32489,6 +32544,57 @@ AT_CLEANUP
+ ])
+
+
++OVN_FOR_EACH_NORTHD([
++AT_SETUP([default encap-ip selection for multicast with flow-based tunnels])
++CHECK_SCAPY
++ovn_start
++net_add n1
++
++check ovn-nbctl ls-add ls1
++
++for i in 1 2; do
++ sim_add hv$i
++ as hv$i
++ ovs-vsctl add-br br-phys
++ ovn_attach n1 br-phys 192.168.0.${i}1
++ check ovs-vsctl set open . external_ids:ovn-encap-ip=192.168.0.${i}1,192.168.0.${i}2
++ check ovs-vsctl set open . external_ids:ovn-enable-flow-based-tunnels=true
++ check ovs-vsctl set open . external_ids:ovn-encap-ip-default=192.168.0.${i}2
++
++ check ovs-vsctl add-port br-int vif${i}1 -- set Interface vif${i}1 \
++ external_ids:iface-id=lsp$i \
++ options:tx_pcap=hv$i/vif${i}1-tx.pcap \
++ options:rxq_pcap=hv$i/vif${i}1-rx.pcap
++ check ovn-nbctl lsp-add ls1 lsp$i -- \
++ lsp-set-addresses lsp$i "f0:00:00:00:00:${i}1 10.0.0.${i}1"
++done
++
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
++
++dnl Use an unknown target IP so the ARP request is not intercepted by OVN's
++dnl ARP responder and is flooded via the multicast group instead.
++packet=$(fmt_pkt "Ether(dst='ff:ff:ff:ff:ff:ff', src='f0:00:00:00:00:11') / \
++ ARP(op=1, hwsrc='f0:00:00:00:00:11', psrc='10.0.0.11', \
++ hwdst='00:00:00:00:00:00', pdst='10.0.0.99')")
++
++as hv1
++AT_CHECK([ovs-vsctl wait-until interface ovn-geneve 'ofport>0'])
++
++dnl Broadcast ARP for an unknown target triggers the multicast flood path
++dnl which calls fanout_to_chassis_flow_based(). The tunnel endpoints must
++dnl use the configured default encap IPs, not the first ones in the list.
++OVS_WAIT_UNTIL([
++ trace_output=$(ovs-appctl ofproto/trace br-int in_port=vif11 $packet)
++ echo "$trace_output" | grep -q "set_field:192.168.0.22->tun_dst"
++])
++AT_CHECK([echo "$trace_output" | grep -q "set_field:192.168.0.12->tun_src"])
++
++OVN_CLEANUP([hv1],[hv2])
++AT_CLEANUP
++])
++
++
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([Load Balancer LS hairpin OF flows])
+ ovn_start
+@@ -32502,13 +32608,11 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=sw0-p1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+ ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=sw1-p1 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/vif2-rx.pcap
+ ovs-vsctl set open . external_ids:ovn-check-ct-label-for-lb-hairpin=false
+
+ sim_add hv2
+@@ -32518,13 +32622,11 @@ ovn_attach n1 br-phys 192.168.0.2
+ ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=sw0-p2 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif1-rx.pcap
+ ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=sw1-p2 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+ check ovn-nbctl --wait=hv ls-add sw0
+ check ovn-nbctl lsp-add sw0 sw0-p1 -- lsp-set-addresses sw0-p1 00:00:00:00:00:01
+@@ -33038,12 +33140,10 @@ check ovn-nbctl lsp-add sw0 sw0-p2
+ as hv1
+ ovs-vsctl \
+ -- add-port br-int vif1 \
+- -- set Interface vif1 external_ids:iface-id=sw0-p1 \
+- ofport-request=1
++ -- set Interface vif1 external_ids:iface-id=sw0-p1
+ ovs-vsctl \
+ -- add-port br-int vif2 \
+- -- set Interface vif2 external_ids:iface-id=sw0-p2 \
+- ofport-request=2
++ -- set Interface vif2 external_ids:iface-id=sw0-p2
+
+ # Wait for port to be bound.
+ wait_row_count Chassis 1 name=hv1
+@@ -33069,9 +33169,12 @@ check ovs-vsctl set interface vif2 external_ids:iface-id=foo
+ # is not true.
+ wait_row_count Port_Binding 1 logical_port=sw0-p2 'chassis=[[]]'
+
+-as hv1 ovs-ofctl dump-flows br-int table=OFTABLE_PHY_TO_LOG > offlows_table0.txt
+ AT_CAPTURE_FILE([offlows_table0.txt])
+-AT_CHECK_UNQUOTED([grep -c "in_port=2" offlows_table0.txt], [1], [dnl
++
++# Wait for flow - note that we can't use ovn-nbctl sync as northd is stopped.
++OVS_WAIT_FOR_OUTPUT([
++as hv1 ovs-ofctl dump-flows br-int table=OFTABLE_PHY_TO_LOG > offlows_table0.txt
++grep -c "in_port=2" offlows_table0.txt], [1], [dnl
+ 0
+ ])
+
+@@ -33103,9 +33206,10 @@ check ovn-sbctl set SB_Global . options:northd_internal_version=$northd_version
+ # It should claim sw0-p2
+ wait_row_count Port_Binding 1 logical_port=sw0-p2 chassis=$ch
+
+-as hv1 ovs-ofctl dump-flows br-int table=OFTABLE_PHY_TO_LOG > offlows_table0.txt
+ AT_CAPTURE_FILE([offlows_table0.txt])
+-AT_CHECK_UNQUOTED([grep -c "in_port=2" offlows_table0.txt], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([
++as hv1 ovs-ofctl dump-flows br-int table=OFTABLE_PHY_TO_LOG > offlows_table0.txt
++grep -c "in_port=2" offlows_table0.txt], [0], [dnl
+ 1
+ ])
+
+@@ -33149,9 +33253,10 @@ as hv1
+ check ovs-vsctl set open . external_ids:ovn-remote=$ovn_remote
+
+ wait_row_count Port_Binding 1 logical_port=sw0-p2 'chassis=[[]]'
+-as hv1 ovs-ofctl dump-flows br-int table=OFTABLE_PHY_TO_LOG > offlows_table0.txt
+ AT_CAPTURE_FILE([offlows_table0.txt])
+-AT_CHECK_UNQUOTED([grep -c "in_port=2" offlows_table0.txt], [1], [dnl
++OVS_WAIT_FOR_OUTPUT([
++as hv1 ovs-ofctl dump-flows br-int table=OFTABLE_PHY_TO_LOG > offlows_table0.txt
++grep -c "in_port=2" offlows_table0.txt], [1], [dnl
+ 0
+ ])
+
+@@ -33265,8 +33370,7 @@ ovn_attach n1 br-phys 192.168.0.10
+ as hv1
+ ovs-vsctl \
+ -- add-port br-int vif1 \
+- -- set Interface vif1 external_ids:iface-id=sw0-port1 \
+- ofport-request=1
++ -- set Interface vif1 external_ids:iface-id=sw0-port1
+
+ check ovn-nbctl ls-add sw0
+ check ovn-nbctl lsp-add sw0 sw0-port1
+@@ -33348,8 +33452,7 @@ as hv1
+ for i in 1 2; do
+ ovs-vsctl \
+ -- add-port br-int vif$i \
+- -- set Interface vif$i external_ids:iface-id=sw0-p$i \
+- ofport-request=$i
++ -- set Interface vif$i external_ids:iface-id=sw0-p$i
+ done
+
+ check as hv1
+@@ -33484,18 +33587,15 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=sw0-p1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+ ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=sw1-p2 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/vif2-rx.pcap
+ ovs-vsctl -- add-port br-int hv1-vif3 -- \
+ set interface hv1-vif3 external-ids:iface-id=sw0-p3 \
+ options:tx_pcap=hv1/vif3-tx.pcap \
+- options:rxq_pcap=hv1/vif3-rx.pcap \
+- ofport-request=3
++ options:rxq_pcap=hv1/vif3-rx.pcap
+
+ sim_add hv2
+ as hv2
+@@ -33504,13 +33604,11 @@ ovn_attach n1 br-phys 192.168.0.2
+ ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=sw0-p2 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif1-rx.pcap
+ ovs-vsctl -- add-port br-int hv2-vif2 -- \
+ set interface hv2-vif2 external-ids:iface-id=sw1-p1 \
+ options:tx_pcap=hv2/vif2-tx.pcap \
+- options:rxq_pcap=hv2/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv2/vif2-rx.pcap
+
+ sim_add hv3
+ as hv3
+@@ -33519,13 +33617,11 @@ ovn_attach n1 br-phys 192.168.0.3
+ ovs-vsctl -- add-port br-int hv3-vif1 -- \
+ set interface hv3-vif1 external-ids:iface-id=sw0-p4 \
+ options:tx_pcap=hv3/vif1-tx.pcap \
+- options:rxq_pcap=hv3/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv3/vif1-rx.pcap
+ ovs-vsctl -- add-port br-int hv3-vif2 -- \
+ set interface hv3-vif2 external-ids:iface-id=sw1-p3 \
+ options:tx_pcap=hv3/vif2-tx.pcap \
+- options:rxq_pcap=hv3/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv3/vif2-rx.pcap
+
+ OVN_POPULATE_ARP
+
+@@ -34078,8 +34174,8 @@ sim_add hv1
+ as hv1
+ ovs-vsctl add-br br-phys
+ ovn_attach n1 br-phys 192.168.0.1
+-ovs-vsctl -- add-port br-int vm1 -- set interface vm1 ofport-request=1
+-ovs-vsctl -- add-port br-int vm2 -- set interface vm1 ofport-request=2
++ovs-vsctl -- add-port br-int vm1
++ovs-vsctl -- add-port br-int vm2
+
+ check ovn-nbctl ls-add ls
+ check ovn-nbctl lsp-add ls vm1
+@@ -34773,8 +34869,7 @@ check ovn-nbctl lsp-add sw1 sw1-lp1 \
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=sw1-lp1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ # Install static routes to drop traffic
+ check ovn-nbctl lr-route-add lr1 20.0.0.0/24 discard
+@@ -34867,7 +34962,7 @@ ls2_ro_ip4=192.168.2.1
+ ls2_ro_ip6=fd12::1
+ check ovn-nbctl lrp-add lr1 ls2 $ls2_ro_mac $ls2_ro_ip4/24 $ls2_ro_ip6/64
+ check ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
+- type=router options:router-port=ls1 addresses=\"router\"
++ type=router options:router-port=ls2 addresses=\"router\"
+
+ # Create logical port ls2-lp1 in ls2
+ ls2_p1_mac=00:00:00:01:02:04
+@@ -34889,14 +34984,12 @@ ovn_attach n1 br-phys 192.16.0.1
+ ovs-vsctl -- add-port br-int vif1 -- \
+ set interface vif1 external-ids:iface-id=ls1-lp1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ ovs-vsctl -- add-port br-int vif2 -- \
+ set interface vif2 external-ids:iface-id=ls2-lp1 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+
+ # Two proxy arp/ndp configurations with and wihout mac both dual stack
+@@ -34983,7 +35076,7 @@ AT_CHECK([ovn-sbctl dump-flows |
+ grep "${arp_proxy_ls2[[0]]}" |
+ ovn_strip_lflows], [0], [dnl
+ table=??(ls_in_l2_lkup ), priority=50 , dnl
+-match=(eth.dst == { 00:00:00:02:02:f1, 00:00:00:01:02:f1 }), dnl
++match=(eth.dst == { 00:00:00:02:02:f1, 00:00:00:01:02:f2 }), dnl
+ action=(outport = "rp-ls2"; output;)
+ ])
+
+@@ -34993,8 +35086,8 @@ AT_CHECK([ovn-sbctl dump-flows |
+ grep "${arp_proxy_ls2[[0]]}" |
+ ovn_strip_lflows], [0], [dnl
+ table=??(lr_in_admission ), priority=50 , dnl
+-match=(eth.dst == { 00:00:00:02:02:f1, 00:00:00:01:02:f1 } && dnl
+-inport == "ls1"), action=(xreg0[[0..47]] = 00:00:00:01:02:f1; next;)
++match=(eth.dst == { 00:00:00:02:02:f1, 00:00:00:01:02:f2 } && dnl
++inport == "ls2"), action=(xreg0[[0..47]] = 00:00:00:01:02:f2; next;)
+ ])
+
+
+@@ -35230,27 +35323,23 @@ done
+ as hv1 ovs-vsctl add-port br-int vif11 -- \
+ set Interface vif11 external-ids:iface-id=lp11 \
+ options:tx_pcap=hv1/vif11-tx.pcap \
+- options:rxq_pcap=hv1/vif11-rx.pcap \
+- ofport-request=11
++ options:rxq_pcap=hv1/vif11-rx.pcap
+ OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lp11` = xup])
+
+ as hv5 ovs-vsctl add-port br-int vif-north1 -- \
+ set Interface vif-north1 external-ids:iface-id=lp-north1 \
+ options:tx_pcap=hv5/vif-north1-tx.pcap \
+- options:rxq_pcap=hv5/vif-north1-rx.pcap \
+- ofport-request=44
++ options:rxq_pcap=hv5/vif-north1-rx.pcap
+
+ as hv5 ovs-vsctl add-port br-int vif-north2 -- \
+ set Interface vif-north2 external-ids:iface-id=lp-north2 \
+ options:tx_pcap=hv5/vif-north2-tx.pcap \
+- options:rxq_pcap=hv5/vif-north2-rx.pcap \
+- ofport-request=45
++ options:rxq_pcap=hv5/vif-north2-rx.pcap
+
+ as hv5 ovs-vsctl add-port br-int vif-north3 -- \
+ set Interface vif-north3 external-ids:iface-id=lp-north3 \
+ options:tx_pcap=hv5/vif-north3-tx.pcap \
+- options:rxq_pcap=hv5/vif-north3-rx.pcap \
+- ofport-request=46
++ options:rxq_pcap=hv5/vif-north3-rx.pcap
+
+ check ovn-nbctl lrp-set-gateway-chassis DR-S1 hv2
+ check ovn-nbctl lrp-set-gateway-chassis DR-S2 hv3
+@@ -35693,10 +35782,10 @@ grep sw0-port1 -c], [0], [1
+ ])
+
+ check as hv1 ovs-vsctl add-port br-int vif13 \
+- -- set interface vif13 external_ids:iface-id=sw0-port2 ofport-request=13
++ -- set interface vif13 external_ids:iface-id=sw0-port2 ofport-request=1013
+
+ check ovn-nbctl --wait=hv sync
+-OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br-int table=OFTABLE_PHY_TO_LOG | grep "in_port=13" | wc -l` -eq 1])
++OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br-int table=OFTABLE_PHY_TO_LOG | grep "in_port=1013" | wc -l` -eq 1])
+
+ # There should be ct zone for sw0-port2 (localport).
+ AT_CHECK([as hv1 ovn-appctl -t ovn-controller ct-zone-list | \
+@@ -35765,7 +35854,7 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl add-port br-int localport \
+ -- set interface localport \
+ external_ids:iface-id=sw0-localport \
+- ofport-request=13
++ ofport-request=1013
+ ovs-vsctl add-port br-int vm1 \
+ -- set interface vm1 external_ids:iface-id=sw0-vm1
+
+@@ -35776,7 +35865,7 @@ ovn_attach n1 br-phys 192.168.0.2
+ ovs-vsctl add-port br-int localport \
+ -- set interface localport \
+ external_ids:iface-id=sw0-localport \
+- ofport-request=13
++ ofport-request=1013
+ ovs-vsctl add-port br-int vm2 \
+ -- set interface vm2 external_ids:iface-id=sw0-vm2
+
+@@ -35788,13 +35877,13 @@ OVS_WAIT_UNTIL([
+ as hv1 \
+ ovs-ofctl -O OpenFlow15 dump-flows br-int table=OFTABLE_PHY_TO_LOG \
+ | tee 1-hv1-br-int-table-0.txt \
+- && grep -q in_port=13 1-hv1-br-int-table-0.txt
++ && grep -q in_port=1013 1-hv1-br-int-table-0.txt
+ ])
+ OVS_WAIT_UNTIL([
+ as hv2 \
+ ovs-ofctl -O OpenFlow15 dump-flows br-int table=OFTABLE_PHY_TO_LOG \
+ | tee 1-hv2-br-int-table-0.txt \
+- && grep in_port=13 1-hv2-br-int-table-0.txt
++ && grep in_port=1013 1-hv2-br-int-table-0.txt
+ ])
+
+ # Confirm that the controllers did not attempt to claim the localport
+@@ -36096,8 +36185,7 @@ check ovn-nbctl lsp-set-addresses lp11 "00:00:11:11:44:44 20.0.0.10"
+ ovs-vsctl add-port br-int vif11 -- \
+ set Interface vif11 external-ids:iface-id=lp11 \
+ options:tx_pcap=hv1/vif11-tx.pcap \
+- options:rxq_pcap=hv1/vif11-rx.pcap \
+- ofport-request=11
++ options:rxq_pcap=hv1/vif11-rx.pcap
+ OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up lp11) = xup])
+
+ check ovn-nbctl lrp-set-gateway-chassis lr0-ext-ls0 hv1
+@@ -36246,7 +36334,7 @@ check ovn-nbctl lsp-add sw0 sw0p2 -- lsp-set-addresses sw0p2 "00:00:00:00:00:04
+
+ as hv1
+ ovs-vsctl -- add-port br-int hv1-vif0 -- \
+-set Interface hv1-vif0 external-ids:iface-id=sw0p1 ofport-request=1
++set Interface hv1-vif0 external-ids:iface-id=sw0p1
+
+ wait_for_ports_up sw0p1
+
+@@ -36378,7 +36466,7 @@ check_port_sec_offlows hv2 OFTABLE_CHK_OUT_PORT_SEC
+
+ as hv2
+ ovs-vsctl -- add-port br-int hv2-vif0 -- \
+-set Interface hv2-vif0 external-ids:iface-id=sw0p2 ofport-request=1
++set Interface hv2-vif0 external-ids:iface-id=sw0p2
+
+ wait_for_ports_up
+ # There should be no changes in hv1
+@@ -36626,13 +36714,11 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int vif0 -- \
+ set interface vif0 external-ids:iface-id=vif0 \
+ options:tx_pcap=hv1/vif0-tx.pcap \
+- options:rxq_pcap=hv1/vif0-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif0-rx.pcap
+ ovs-vsctl -- add-port br-phys ext0 -- \
+ set interface ext0 \
+ options:tx_pcap=hv1/ext0-tx.pcap \
+- options:rxq_pcap=hv1/ext0-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/ext0-rx.pcap
+ ovs-vsctl set open . external_ids:ovn-bridge-mappings=physnet1:br-phys
+
+ send_packet() {
+@@ -36674,7 +36760,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([MAC binding aging])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ net_add n1
+@@ -36711,13 +36797,11 @@ ovs-vsctl add-br br-phys
+ ovs-vsctl -- add-port br-int vif1 -- \
+ set interface vif1 external-ids:iface-id=vif1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+ ovs-vsctl -- add-port br-phys ext1 -- \
+ set interface ext1 \
+ options:tx_pcap=hv1/ext1-tx.pcap \
+- options:rxq_pcap=hv1/ext1-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/ext1-rx.pcap
+ ovs-vsctl set open . external_ids:ovn-bridge-mappings=physnet1:br-phys
+ ovn-appctl -t ovn-controller vlog/set mac_cache:file:dbg pinctrl:file:dbg
+
+@@ -36729,13 +36813,11 @@ ovs-vsctl add-br br-phys
+ ovs-vsctl -- add-port br-int vif2 -- \
+ set interface vif2 external-ids:iface-id=vif2 \
+ options:tx_pcap=hv2/vif2-tx.pcap \
+- options:rxq_pcap=hv2/vif2-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif2-rx.pcap
+ ovs-vsctl -- add-port br-phys ext2 -- \
+ set interface ext2 \
+ options:tx_pcap=hv2/ext2-tx.pcap \
+- options:rxq_pcap=hv2/ext2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv2/ext2-rx.pcap
+ ovs-vsctl set open . external_ids:ovn-bridge-mappings=physnet1:br-phys
+ ovn-appctl -t ovn-controller vlog/set mac_cache:file:dbg pinctrl:file:dbg
+
+@@ -36933,7 +37015,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([MAC binding aging - port deletion])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ net_add n1
+@@ -37027,7 +37109,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([MAC binding aging - persistence of the active entry])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ net_add n1
+@@ -37155,7 +37237,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([MAC binding aging - probing])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ aging_th=10
+@@ -37225,8 +37307,8 @@ OVS_WAIT_UNTIL([test $(ovs-ofctl dump-flows br-int table=OFTABLE_MAC_CACHE_USE |
+ send_udp hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:2a 192.168.20.100 192.168.10.100
+ send_udp hv1 vif2 00:00:00:00:20:00 00:00:00:00:10:2b 192.168.10.100 192.168.20.100
+ # Wait for ARP requests to be generated.
+-dump_arp 1 00:00:00:00:20:00 ff:ff:ff:ff:ff:ff 192.168.20.1 192.168.20.100 00:00:00:00:00:00 > expected1
+-dump_arp 1 00:00:00:00:10:00 ff:ff:ff:ff:ff:ff 192.168.10.1 192.168.10.100 00:00:00:00:00:00 > expected2
++dump_arp 1 00:00:00:00:20:00 00:00:00:00:10:1b 192.168.20.1 192.168.20.100 00:00:00:00:10:1b > expected1
++dump_arp 1 00:00:00:00:10:00 00:00:00:00:10:1a 192.168.10.1 192.168.10.100 00:00:00:00:10:1a > expected2
+ OVN_CHECK_PACKETS_CONTAIN([hv1/vif1-tx.pcap], [expected2])
+
+ # Check MAC_Binding timestamp is updated receiving the ARP replay.
+@@ -37235,8 +37317,8 @@ send_garp hv1 vif2 2 00:00:00:00:10:1b 00:00:00:00:20:00 192.168.20.100 192.168.
+ OVS_WAIT_UNTIL([test $(fetch_column Mac_Binding timestamp ip=192.168.10.100) -gt $ts0])
+
+ # Refresh OFTABLE_MAC_BINDING entires.
+-dump_arp 1 00:00:00:00:20:00 ff:ff:ff:ff:ff:ff 192.168.20.1 192.168.20.100 00:00:00:00:00:00 >> expected1
+-dump_arp 1 00:00:00:00:10:00 ff:ff:ff:ff:ff:ff 192.168.10.1 192.168.10.100 00:00:00:00:00:00 >> expected2
++dump_arp 1 00:00:00:00:20:00 00:00:00:00:10:1b 192.168.20.1 192.168.20.100 00:00:00:00:10:1b >> expected1
++dump_arp 1 00:00:00:00:10:00 00:00:00:00:10:1a 192.168.10.1 192.168.10.100 00:00:00:00:10:1a >> expected2
+ OVS_WAIT_UNTIL([test $(ovs-ofctl dump-flows br-int table=OFTABLE_MAC_CACHE_USE | \
+ awk '/arp_spa=192.168.20.100/{print substr($6,10,1)}') -ge $((aging_th/2))])
+ send_udp hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:2a 192.168.20.100 192.168.10.100
+@@ -37263,8 +37345,8 @@ OVS_WAIT_UNTIL([test $(ovs-ofctl dump-flows br-int table=OFTABLE_MAC_CACHE_USE |
+ send_udp6 hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:2a fd12::64 fd11::64
+ send_udp6 hv1 vif2 00:00:00:00:20:00 00:00:00:00:10:2b fd11::64 fd12::64
+
+-dump_ns 33:33:ff:00:00:64 00:00:00:00:10:00 ff02::1:ff00:64 fd11::1 fd11::64 > expected2
+-dump_ns 33:33:ff:00:00:64 00:00:00:00:10:00 ff02::1:ff00:64 fd11::1 fd11::64 >> expected2
++dump_ns 00:00:00:00:10:1a 00:00:00:00:10:00 fd11::64 fd11::1 fd11::64 > expected2
++dump_ns 00:00:00:00:10:1a 00:00:00:00:10:00 fd11::64 fd11::1 fd11::64 >> expected2
+ OVN_CHECK_PACKETS_CONTAIN([hv1/vif1-tx.pcap], [expected2])
+
+ send_na hv1 vif1 00:00:00:00:10:1a 00:00:00:00:10:00 fd11::64 fd11::1
+@@ -37277,7 +37359,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([MAC binding aging - probing multi-subnet source IP])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ aging_th=10
+@@ -37365,16 +37447,16 @@ send_udp6 hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:2b fd11::64 fd12::64
+
+ # Verify ARP probes use the correct source IPs from matching subnets.
+ # ARP for 10.10.10.100 must use source IP 10.10.10.1 (first subnet).
+-dump_arp 1 00:00:00:00:10:00 ff:ff:ff:ff:ff:ff 10.10.10.1 10.10.10.100 00:00:00:00:00:00 > expected
++dump_arp 1 00:00:00:00:10:00 00:00:00:00:10:1a 10.10.10.1 10.10.10.100 00:00:00:00:10:1a > expected
+ # ARP for 42.42.42.253 must use source IP 42.42.42.1 (second subnet).
+-dump_arp 1 00:00:00:00:10:00 ff:ff:ff:ff:ff:ff 42.42.42.1 42.42.42.253 00:00:00:00:00:00 >> expected
++dump_arp 1 00:00:00:00:10:00 00:00:00:00:10:1b 42.42.42.1 42.42.42.253 00:00:00:00:10:1b >> expected
+ OVN_CHECK_PACKETS_CONTAIN([hv1/vif1-tx.pcap], [expected])
+
+ # Verify NS probes use the correct source IPs from matching subnets.
+ # NS for fd11::64 must use source IP fd11::1 (first IPv6 subnet).
+-dump_ns 33:33:ff:00:00:64 00:00:00:00:10:00 ff02::1:ff00:64 fd11::1 fd11::64 > expected_v6
++dump_ns 00:00:00:00:10:1a 00:00:00:00:10:00 fd11::64 fd11::1 fd11::64 > expected_v6
+ # NS for fd12::64 must use source IP fd12::1 (second IPv6 subnet).
+-dump_ns 33:33:ff:00:00:64 00:00:00:00:10:00 ff02::1:ff00:64 fd12::1 fd12::64 >> expected_v6
++dump_ns 00:00:00:00:10:1b 00:00:00:00:10:00 fd12::64 fd12::1 fd12::64 >> expected_v6
+ OVN_CHECK_PACKETS_CONTAIN([hv1/vif1-tx.pcap], [expected_v6])
+
+ # Send ARP/NA replies and check MAC_Binding UUIDs remain consistent.
+@@ -37392,9 +37474,116 @@ OVN_CLEANUP([hv1])
+ AT_CLEANUP
+ ])
+
++OVN_FOR_EACH_NORTHD([
++AT_SETUP([MAC binding aging - probing unicast to broadcast transition])
++CHECK_SCAPY
++ovn_start
++
++aging_th=5
++net_add n1
++sim_add hv1
++as hv1
++check ovs-vsctl add-br br-phys
++ovn_attach n1 br-phys 192.168.0.1
++ovn-appctl -t ovn-controller vlog/set mac_cache:file:dbg pinctrl:file:dbg
++
++check ovn-nbctl \
++ -- ls-add ls1 \
++ -- lr-add lr \
++ -- set logical_router lr options:mac_binding_age_threshold=$aging_th \
++ -- lrp-add lr lr-ls1 00:00:00:00:10:00 10.10.10.1/24 42.42.42.1/24 \
++ fd11::1/64 fd12::1/64 \
++ -- lsp-add-router-port ls1 ls1-lr lr-ls1 \
++ -- lsp-add ls1 vif1 \
++ -- lsp-set-addresses vif1 "unknown"
++
++check ovs-vsctl \
++ -- add-port br-int vif1 \
++ -- set interface vif1 external-ids:iface-id=vif1 \
++ options:tx_pcap=hv1/vif1-tx.pcap options:rxq_pcap=hv1/vif1-rx.pcap
++
++OVN_POPULATE_ARP
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
++
++# Wait for pinctrl thread to be connected.
++OVS_WAIT_UNTIL([grep pinctrl hv1/ovn-controller.log | grep -q connected])
++
++# Create one IPv4 and one IPv6 MAC binding.
++send_garp hv1 vif1 2 00:00:00:00:10:1a ff:ff:ff:ff:ff:ff 10.10.10.100 10.10.10.100
++wait_row_count mac_binding 1 ip="10.10.10.100" logical_port="lr-ls1"
++
++send_na hv1 vif1 00:00:00:00:10:1a 00:00:00:00:10:00 fd11::64 fd11::1
++wait_row_count mac_binding 1 ip=\"fd11::64\" logical_port=\"lr-ls1\"
++
++# The first 2 probes are unicast (arp_attempts 0-1); the entry then falls
++# back to broadcast ARP / multicast NS.
++dump_arp 1 00:00:00:00:10:00 ff:ff:ff:ff:ff:ff 10.10.10.1 10.10.10.100 00:00:00:00:00:00 > expected_bcast
++OVN_CHECK_PACKETS_CONTAIN([hv1/vif1-tx.pcap], [expected_bcast])
++
++dump_ns 33:33:ff:00:00:64 00:00:00:00:10:00 ff02::1:ff00:64 fd11::1 fd11::64 > expected_mcast
++OVN_CHECK_PACKETS_CONTAIN([hv1/vif1-tx.pcap], [expected_mcast])
++
++dump_arp 1 00:00:00:00:10:00 00:00:00:00:10:1a 10.10.10.1 10.10.10.100 00:00:00:00:10:1a > expected_ucast_v4
++OVN_CHECK_PACKETS_CONTAIN([hv1/vif1-tx.pcap], [expected_ucast_v4])
++
++dump_ns 00:00:00:00:10:1a 00:00:00:00:10:00 fd11::64 fd11::1 fd11::64 > expected_ucast_v6
++OVN_CHECK_PACKETS_CONTAIN([hv1/vif1-tx.pcap], [expected_ucast_v6])
++
++# Verify the reset-to-zero mechanism using a distinct pair of neighbours
++# kept alive (never re-created) for the whole check. A single entry emits
++# at most ARP_BROADCAST_THRESHOLD (2) unicast probes before falling back to
++# broadcast, so a 4th unicast probe proves arp_attempts was reset.
++send_garp hv1 vif1 2 00:00:00:00:10:1b ff:ff:ff:ff:ff:ff 10.10.10.101 10.10.10.101
++wait_row_count mac_binding 1 ip="10.10.10.101" logical_port="lr-ls1"
++v4_uuid=$(fetch_column Mac_Binding _uuid ip=10.10.10.101)
++
++send_na hv1 vif1 00:00:00:00:10:1b 00:00:00:00:10:00 fd11::65 fd11::1
++wait_row_count mac_binding 1 ip=\"fd11::65\" logical_port=\"lr-ls1\"
++v6_uuid=$(fetch_column Mac_Binding _uuid ip=\"fd11::65\")
++
++# Keep the entries active (Tx towards them) and let a couple of unicast
++# probes go out (arp_attempts reaches 1, below the broadcast threshold).
++send_udp hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:2a 10.10.10.101 42.42.42.100
++send_udp6 hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:2a fd11::65 fd12::100
++
++dump_arp 1 00:00:00:00:10:00 00:00:00:00:10:1b 10.10.10.1 10.10.10.101 00:00:00:00:10:1b > ucast_v4_101.pkt
++dump_ns 00:00:00:00:10:1b 00:00:00:00:10:00 fd11::65 fd11::1 fd11::65 > ucast_v6_65.pkt
++OVS_WAIT_UNTIL([test $($PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap | \
++ grep -Fc "$(cat ucast_v4_101.pkt)") -ge 2])
++OVS_WAIT_UNTIL([test $($PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap | \
++ grep -Fc "$(cat ucast_v6_65.pkt)") -ge 2])
++
++# The neighbours answer, refreshing the rows in place (resetting
++# arp_attempts). Confirm the timestamps advanced.
++v4_ts=$(fetch_column Mac_Binding timestamp ip=10.10.10.101)
++v6_ts=$(fetch_column Mac_Binding timestamp ip=\"fd11::65\")
++send_garp hv1 vif1 2 00:00:00:00:10:1b 00:00:00:00:10:00 10.10.10.101 10.10.10.1
++send_na hv1 vif1 00:00:00:00:10:1b 00:00:00:00:10:00 fd11::65 fd11::1
++OVS_WAIT_UNTIL([test $(fetch_column Mac_Binding timestamp ip=10.10.10.101) -gt $v4_ts])
++OVS_WAIT_UNTIL([test $(fetch_column Mac_Binding timestamp ip=\"fd11::65\") -gt $v6_ts])
++
++# Probing must restart from unicast: wait for a 4th unicast ARP/NS probe
++# while the rows are still the original ones (same UUID, never re-created).
++dump_arp 1 00:00:00:00:10:00 00:00:00:00:10:1b 10.10.10.1 10.10.10.101 00:00:00:00:10:1b > ucast_v4.pkt
++dump_ns 00:00:00:00:10:1b 00:00:00:00:10:00 fd11::65 fd11::1 fd11::65 > ucast_v6.pkt
++OVS_WAIT_UNTIL([
++ send_udp hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:2a 10.10.10.101 42.42.42.100
++ send_udp6 hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:2a fd11::65 fd12::100
++ test "$(fetch_column Mac_Binding _uuid ip=10.10.10.101)" = "$v4_uuid" && \
++ test "$(fetch_column Mac_Binding _uuid ip=\"fd11::65\")" = "$v6_uuid" && \
++ test $($PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap | \
++ grep -Fc "$(cat ucast_v4.pkt)") -ge 4 && \
++ test $($PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap | \
++ grep -Fc "$(cat ucast_v6.pkt)") -ge 4])
++
++OVN_CLEANUP([hv1])
++AT_CLEANUP
++])
++
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([MAC binding aging - probing distributed GW router])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ # Check that during mac probing only the chassis that has currently claimed
+@@ -37451,8 +37640,8 @@ wait_row_count mac_binding 0 ip="$ext_ip" logical_port="lr-ls"
+
+ # hv1 should have sent out arps for mac binding refresh
+ arp_req=$(fmt_pkt \
+- "Ether(dst='ff:ff:ff:ff:ff:ff', src='$lrp_mac')/ \
+- ARP(hwsrc='$lrp_mac', hwdst='00:00:00:00:00:00', psrc='$lrp_ip', pdst='$ext_ip')")
++ "Ether(dst='$ext_mac', src='$lrp_mac')/ \
++ ARP(hwsrc='$lrp_mac', hwdst='$ext_mac', psrc='$lrp_ip', pdst='$ext_ip')")
+ echo $arp_req >> hv1_snoopvif.expected
+ OVN_CHECK_PACKETS_CONTAIN([hv1/snoopvif-tx.pcap], [hv1_snoopvif.expected])
+
+@@ -37466,7 +37655,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([MAC binding aging - probing GW router Dynamic Neigh])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ send_imcp_echo_req() {
+@@ -37562,7 +37751,8 @@ send_imcp_echo_req hv1 public 00:00:00:00:10:00 00:00:00:00:10:1a 192.168.20.2 1
+ OVS_WAIT_UNTIL([test $(grep -c "Sending ARP/ND.*ip: 192.168.20.2" hv1/ovn-controller.log) -eq 2])
+
+ check ovn-nbctl --wait=hv acl-del join
+-wait_row_count mac_binding 0
++wait_row_count mac_binding 0 ip="192.168.10.100" logical_port="gw-public"
++wait_row_count mac_binding 0 ip="192.168.20.2" logical_port="gw-join"
+
+ send_icmp6_echo_req hv1 public 00:00:00:00:10:00 00:00:00:00:50:01 fd12::2 fd11::64
+ wait_row_count mac_binding 1 mac=\"00:00:00:00:30:00\" ip=\"fd12::2\"
+@@ -37579,7 +37769,7 @@ sleep $((aging_th / 2))
+ send_icmp6_echo_req hv1 public 00:00:00:00:10:00 00:00:00:00:50:01 fd12::2 fd11::64
+ OVS_WAIT_UNTIL([test $(grep -c "Sending ARP/ND.*ip: fd12::2" hv1/ovn-controller.log) -eq 2])
+
+-wait_row_count mac_binding 0
++wait_row_count mac_binding 0 ip=\"fd12::2\" logical_port="gw-join"
+
+ OVN_CLEANUP([hv1])
+ AT_CLEANUP
+@@ -37597,8 +37787,7 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=sw0-p1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ check ovn-nbctl ls-add sw0
+ check ovn-nbctl lr-add ro0
+@@ -38231,8 +38420,7 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ sim_add hv2
+ as hv2
+@@ -38241,8 +38429,7 @@ ovn_attach n1 br-phys 192.168.0.2
+ ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=ls2-lp1 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif1-rx.pcap
+
+
+ # Allow some time for ovn-northd and ovn-controller to catch up.
+@@ -38484,14 +38671,14 @@ dnl Ensure hairpin flows are correct.
+ as hv1
+ AT_CHECK([ovs-ofctl dump-flows br-int | grep table=OFTABLE_CHK_LB_HAIRPIN | ofctl_strip_all], [0], [dnl
+ table=OFTABLE_CHK_LB_HAIRPIN, priority=100,ct_mark=0x2/0x2,tcp,reg2=0x10cd/0xffff,reg4=0x2b2b2b01,nw_src=85.85.85.1,nw_dst=85.85.85.1,tp_dst=8501 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=OFTABLE_CHK_LB_HAIRPIN_REPLY,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.1,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68, priority=100,ct_mark=0x2/0x2,tcp,reg2=0x10ce/0xffff,reg4=0x2b2b2b02,nw_src=85.85.85.21,nw_dst=85.85.85.21,tp_dst=8502 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.2,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68, priority=100,ct_mark=0x2/0x2,tcp,reg2=0x10ce/0xffff,reg4=0x2b2b2b02,nw_src=85.85.85.22,nw_dst=85.85.85.22,tp_dst=8502 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.2,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68, priority=100,ct_mark=0x2/0x2,tcp,reg2=0x10cf/0xffff,reg4=0x2b2b2b03,nw_src=85.85.85.31,nw_dst=85.85.85.31,tp_dst=8503 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.3,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68, priority=100,ct_mark=0x2/0x2,tcp,reg2=0x10cf/0xffff,reg4=0x2b2b2b03,nw_src=85.85.85.32,nw_dst=85.85.85.32,tp_dst=8503 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.3,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68, priority=100,ct_mark=0x2/0x2,tcp,reg2=0x10d0/0xffff,reg4=0x2b2b2b04,nw_src=85.85.85.41,nw_dst=85.85.85.41,tp_dst=8504 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.4,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68, priority=100,ct_mark=0x2/0x2,tcp,reg2=0x10d0/0xffff,reg4=0x2b2b2b04,nw_src=85.85.85.42,nw_dst=85.85.85.42,tp_dst=8504 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.4,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68, priority=100,ct_mark=0x2/0x2,tcp,reg2=0x10d0/0xffff,reg4=0x2b2b2b04,nw_src=85.85.85.43,nw_dst=85.85.85.43,tp_dst=8504 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.4,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68, priority=100,ct_mark=0x2/0x2,tcp,reg2=0x10d0/0xffff,reg4=0x2b2b2b04,nw_src=85.85.85.44,nw_dst=85.85.85.44,tp_dst=8504 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.4,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=OFTABLE_CHK_LB_HAIRPIN, priority=100,ct_mark=0x2/0x2,tcp,reg2=0x10ce/0xffff,reg4=0x2b2b2b02,nw_src=85.85.85.21,nw_dst=85.85.85.21,tp_dst=8502 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=OFTABLE_CHK_LB_HAIRPIN_REPLY,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.2,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=OFTABLE_CHK_LB_HAIRPIN, priority=100,ct_mark=0x2/0x2,tcp,reg2=0x10ce/0xffff,reg4=0x2b2b2b02,nw_src=85.85.85.22,nw_dst=85.85.85.22,tp_dst=8502 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=OFTABLE_CHK_LB_HAIRPIN_REPLY,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.2,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=OFTABLE_CHK_LB_HAIRPIN, priority=100,ct_mark=0x2/0x2,tcp,reg2=0x10cf/0xffff,reg4=0x2b2b2b03,nw_src=85.85.85.31,nw_dst=85.85.85.31,tp_dst=8503 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=OFTABLE_CHK_LB_HAIRPIN_REPLY,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.3,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=OFTABLE_CHK_LB_HAIRPIN, priority=100,ct_mark=0x2/0x2,tcp,reg2=0x10cf/0xffff,reg4=0x2b2b2b03,nw_src=85.85.85.32,nw_dst=85.85.85.32,tp_dst=8503 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=OFTABLE_CHK_LB_HAIRPIN_REPLY,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.3,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=OFTABLE_CHK_LB_HAIRPIN, priority=100,ct_mark=0x2/0x2,tcp,reg2=0x10d0/0xffff,reg4=0x2b2b2b04,nw_src=85.85.85.41,nw_dst=85.85.85.41,tp_dst=8504 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=OFTABLE_CHK_LB_HAIRPIN_REPLY,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.4,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=OFTABLE_CHK_LB_HAIRPIN, priority=100,ct_mark=0x2/0x2,tcp,reg2=0x10d0/0xffff,reg4=0x2b2b2b04,nw_src=85.85.85.42,nw_dst=85.85.85.42,tp_dst=8504 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=OFTABLE_CHK_LB_HAIRPIN_REPLY,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.4,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=OFTABLE_CHK_LB_HAIRPIN, priority=100,ct_mark=0x2/0x2,tcp,reg2=0x10d0/0xffff,reg4=0x2b2b2b04,nw_src=85.85.85.43,nw_dst=85.85.85.43,tp_dst=8504 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=OFTABLE_CHK_LB_HAIRPIN_REPLY,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.4,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=OFTABLE_CHK_LB_HAIRPIN, priority=100,ct_mark=0x2/0x2,tcp,reg2=0x10d0/0xffff,reg4=0x2b2b2b04,nw_src=85.85.85.44,nw_dst=85.85.85.44,tp_dst=8504 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=OFTABLE_CHK_LB_HAIRPIN_REPLY,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=43.43.43.4,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+ ])
+
+ dnl Change Chassis_Template_Var mappings
+@@ -39322,8 +39509,7 @@ done
+
+ for i in 1 2; do
+ as hv-$i
+- ovs-vsctl add-port br-int vif$i -- set Interface vif$i external-ids:iface-id=lsp$i \
+- ofport-request=$i
++ ovs-vsctl add-port br-int vif$i -- set Interface vif$i external-ids:iface-id=lsp$i
+ OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lsp$i` = xup])
+
+ # Patch port might be created after ports are reported up
+@@ -39357,16 +39543,14 @@ check ovn-nbctl lsp-add ls0 lsp3
+ check ovn-nbctl lsp-set-addresses lsp3 f0:00:00:00:00:03
+ as hv-1
+ ovs-vsctl add-port br-int vif3 -- \
+- set Interface vif3 external-ids:iface-id=lsp3 \
+- ofport-request=3
++ set Interface vif3 external-ids:iface-id=lsp3
+ OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lsp3` = xup])
+
+ check ovn-nbctl lsp-add ls0 lsp4
+ check ovn-nbctl lsp-set-addresses lsp4 f0:00:00:00:00:04
+ as hv-1
+ ovs-vsctl add-port br-int vif4 -- \
+- set Interface vif4 external-ids:iface-id=lsp4 \
+- ofport-request=4
++ set Interface vif4 external-ids:iface-id=lsp4
+ OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lsp4` = xup])
+
+ check ovn-nbctl set Logical_Switch_Port lsp3 options:qos_min_rate=700000
+@@ -39442,8 +39626,7 @@ check ovn-nbctl lsp-add ls1 lsp5
+ check ovn-nbctl lsp-set-addresses lsp5 f0:00:00:00:00:05
+ as hv-1
+ ovs-vsctl add-port br-int vif5 -- \
+- set Interface vif5 external-ids:iface-id=lsp5 \
+- ofport-request=5
++ set Interface vif5 external-ids:iface-id=lsp5
+ OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lsp5` = xup])
+
+ # Delete and add back public1
+@@ -39489,7 +39672,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([FDB aging])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ net_add n1
+@@ -39510,13 +39693,11 @@ ovs-vsctl add-br br-phys
+ ovs-vsctl -- add-port br-int vif1 -- \
+ set interface vif1 external-ids:iface-id=vif1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+ ovs-vsctl -- add-port br-phys ext0 -- \
+ set interface ext0 \
+ options:tx_pcap=hv1/ext0-tx.pcap \
+- options:rxq_pcap=hv1/ext0-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/ext0-rx.pcap
+ ovs-vsctl set open . external_ids:ovn-bridge-mappings=physnet1:br-phys
+ ovn-appctl -t ovn-controller vlog/set mac_cache:file:dbg pinctrl:file:dbg
+
+@@ -39566,7 +39747,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([FDB aging - persistence of the active entry])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ net_add n1
+@@ -39588,13 +39769,11 @@ ovs-vsctl add-br br-phys
+ ovs-vsctl -- add-port br-int vif1 -- \
+ set interface vif1 external-ids:iface-id=vif1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+ ovs-vsctl -- add-port br-phys ext0 -- \
+ set interface ext0 \
+ options:tx_pcap=hv1/ext0-tx.pcap \
+- options:rxq_pcap=hv1/ext0-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/ext0-rx.pcap
+ ovs-vsctl set open . external_ids:ovn-bridge-mappings=physnet1:br-phys
+ ovn-appctl -t ovn-controller vlog/set mac_cache:file:dbg pinctrl:file:dbg
+
+@@ -39646,7 +39825,7 @@ AT_CLEANUP
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([DNAT_SNAT and LB traffic])
+ AT_KEYWORDS([dnat-snat-lb])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ test_ip_req_packet() {
+@@ -39774,8 +39953,7 @@ check ovn-nbctl lsp-add ls1 lsp1
+ check ovn-nbctl lsp-set-addresses lsp1 f0:00:00:00:00:03
+ as hv-1
+ ovs-vsctl add-port br-int vif1 -- \
+- set Interface vif1 external-ids:iface-id=lsp1 \
+- ofport-request=3
++ set Interface vif1 external-ids:iface-id=lsp1
+
+ OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lsp1` = xup])
+
+@@ -39788,8 +39966,7 @@ check ovn-nbctl lsp-add ls2 lsp2
+ check ovn-nbctl lsp-set-addresses lsp2 f0:00:00:00:00:05
+ as hv-1
+ ovs-vsctl add-port br-int vif2 -- \
+- set Interface vif2 external-ids:iface-id=lsp2 \
+- ofport-request=5
++ set Interface vif2 external-ids:iface-id=lsp2
+ OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lsp2` = xup])
+
+ # Sleep ovs to postpone ofport notification to ovn
+@@ -39816,7 +39993,10 @@ wake_up_controller hv-1
+ # Make sure ovn-controller is still OK
+ check ovn-nbctl --wait=hv sync
+ OVS_WAIT_UNTIL([test $(as hv-1 ovs-vsctl list qos | grep -c linux-htb) -eq 1])
+-OVN_CLEANUP([hv-1],[hv-2])
++OVN_CLEANUP([hv-1
++public1
++ignored_dp=ls1
++],[hv-2])
+ AT_CLEANUP
+ ])
+
+@@ -40078,7 +40258,7 @@ AT_CLEANUP
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([virtual port claim race condition])
+ AT_KEYWORDS([virtual ports])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ net_add n1
+@@ -40091,13 +40271,11 @@ ovn-appctl vlog/set dbg
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=sw0-p1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+ ovs-vsctl -- add-port br-int hv1-vif3 -- \
+ set interface hv1-vif3 \
+ options:tx_pcap=hv1/vif3-tx.pcap \
+- options:rxq_pcap=hv1/vif3-rx.pcap \
+- ofport-request=3
++ options:rxq_pcap=hv1/vif3-rx.pcap
+
+ ovn-appctl -t ovn-controller vlog/set dbg
+
+@@ -40158,7 +40336,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([pod to pod with localnet_learn_fdb])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+
+ # 10 VIFs, 5 per HV: vif11, vif12, vif13, vif14 and vif15 on hv1.
+ # vif11 will exchange packets with vif21, vif12 w/ vif22 and so on.
+@@ -40189,14 +40367,12 @@ for hv in 1 2; do
+ ovs-vsctl -- add-port br-int vif${hv}${i} -- \
+ set interface vif${hv}${i} external-ids:iface-id=vif${hv}${i} \
+ options:tx_pcap=hv${hv}/vif${hv}${i}-tx.pcap \
+- options:rxq_pcap=hv${hv}/vif${hv}${i}-rx.pcap \
+- ofport-request=$i
++ options:rxq_pcap=hv${hv}/vif${hv}${i}-rx.pcap
+ done
+ ovs-vsctl -- add-port br-phys ext0 -- \
+ set interface ext0 \
+ options:tx_pcap=hv${hv}/ext0-tx.pcap \
+- options:rxq_pcap=hv${hv}/ext0-rx.pcap \
+- ofport-request=6
++ options:rxq_pcap=hv${hv}/ext0-rx.pcap
+ ovs-vsctl set open . external_ids:ovn-bridge-mappings=physnet1:br-phys
+ done
+
+@@ -40991,7 +41167,7 @@ AT_CLEANUP
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([QoS packet marking])
+ AT_KEYWORDS([ovn-qos-pkt-marking])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ check ovn-nbctl lr-add lr0
+@@ -41026,18 +41202,15 @@ ovn_attach n1 br-phys 192.168.0.1
+ check ovs-vsctl add-port br-int vif0 -- \
+ set Interface vif0 external-ids:iface-id=lp0 \
+ options:tx_pcap=hv/vif0-tx.pcap \
+- options:rxq_pcap=hv/vif0-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv/vif0-rx.pcap
+ check ovs-vsctl add-port br-int vif1 -- \
+ set Interface vif1 external-ids:iface-id=lp1 \
+ options:tx_pcap=hv/vif1-tx.pcap \
+- options:rxq_pcap=hv/vif1-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv/vif1-rx.pcap
+ check ovs-vsctl add-port br-int vif2 -- \
+ set Interface vif2 external-ids:iface-id=lp2 \
+ options:tx_pcap=hv/vif2-tx.pcap \
+- options:rxq_pcap=hv/vif2-rx.pcap \
+- ofport-request=3
++ options:rxq_pcap=hv/vif2-rx.pcap
+
+ # Create QoS rules for packet marking.
+ check ovn-nbctl qos-add ls0 from-lport 100 "inport==\"lp0\" && udp" mark=48
+@@ -41082,7 +41255,7 @@ for i in 1 2; do
+ ovs-vsctl add-br br-phys
+ ovn_attach n1 br-phys 192.168.0.$i
+ check ovs-vsctl -- add-port br-int vif1 -- \
+- set Interface vif1 ofport-request=8
++ set Interface vif1 ofport-request=1008
+ done
+
+ check ovn-nbctl ls-add lsw0
+@@ -41103,11 +41276,11 @@ wait_column "$hv1_uuid" Port_Binding chassis logical_port=lsp1
+ wait_column "$hv1_uuid" Port_Binding chassis logical_port=sw0-port1.1
+
+ # check that flows is installed
+-OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=OFTABLE_PHY_TO_LOG |grep priority=150|grep dl_vlan=7| grep -c in_port=8], [0],[dnl
++OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=OFTABLE_PHY_TO_LOG |grep priority=150|grep dl_vlan=7| grep -c in_port=1008], [0],[dnl
+ 1
+ ])
+
+-OVS_WAIT_FOR_OUTPUT([as hv2 ovs-ofctl dump-flows br-int table=OFTABLE_PHY_TO_LOG |grep priority=150|grep dl_vlan=7| grep -c in_port=8], [1],[dnl
++OVS_WAIT_FOR_OUTPUT([as hv2 ovs-ofctl dump-flows br-int table=OFTABLE_PHY_TO_LOG |grep priority=150|grep dl_vlan=7| grep -c in_port=1008], [1],[dnl
+ 0
+ ])
+
+@@ -41124,10 +41297,10 @@ wait_for_ports_up lsp1
+ wait_for_ports_up sw0-port1.1
+ wait_column "$hv2_uuid" Port_Binding chassis logical_port=lsp1
+
+-OVS_WAIT_FOR_OUTPUT([as hv2 ovs-ofctl dump-flows br-int table=OFTABLE_PHY_TO_LOG |grep priority=150|grep dl_vlan=7| grep -c in_port=8], [0],[dnl
++OVS_WAIT_FOR_OUTPUT([as hv2 ovs-ofctl dump-flows br-int table=OFTABLE_PHY_TO_LOG |grep priority=150|grep dl_vlan=7| grep -c in_port=1008], [0],[dnl
+ 1
+ ])
+-OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=OFTABLE_PHY_TO_LOG |grep priority=150|grep dl_vlan=7| grep -c in_port=8], [0],[dnl
++OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=OFTABLE_PHY_TO_LOG |grep priority=150|grep dl_vlan=7| grep -c in_port=1008], [0],[dnl
+ 1
+ ])
+
+@@ -41166,13 +41339,11 @@ ovn_attach n1 br-phys 192.168.0.1
+ ovs-vsctl -- add-port br-int vif0 -- \
+ set interface vif0 external-ids:iface-id=vif0 \
+ options:tx_pcap=hv1/vif0-tx.pcap \
+- options:rxq_pcap=hv1/vif0-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif0-rx.pcap
+ ovs-vsctl -- add-port br-phys ext0 -- \
+ set interface ext0 \
+ options:tx_pcap=hv1/ext0-tx.pcap \
+- options:rxq_pcap=hv1/ext0-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/ext0-rx.pcap
+
+ ovs-vsctl set open . external_ids:ovn-bridge-mappings=physnet1:br-phys
+
+@@ -41484,25 +41655,21 @@ as hv1
+ ovs-vsctl add-port br-int vif11 -- \
+ set Interface vif11 external-ids:iface-id=sw0-p1 \
+ options:tx_pcap=hv1/vif11-tx.pcap \
+- options:rxq_pcap=hv1/vif11-rx.pcap \
+- ofport-request=11
++ options:rxq_pcap=hv1/vif11-rx.pcap
+ ovs-vsctl add-port br-int vif12 -- \
+ set Interface vif12 external-ids:iface-id=sw1-p1 \
+ options:tx_pcap=hv1/vif12-tx.pcap \
+- options:rxq_pcap=hv1/vif12-rx.pcap \
+- ofport-request=12
++ options:rxq_pcap=hv1/vif12-rx.pcap
+
+ as hv2
+ ovs-vsctl add-port br-int vif21 -- \
+ set Interface vif21 external-ids:iface-id=sw0-p2 \
+ options:tx_pcap=hv1/vif21-tx.pcap \
+- options:rxq_pcap=hv1/vif21-rx.pcap \
+- ofport-request=21
++ options:rxq_pcap=hv1/vif21-rx.pcap
+ ovs-vsctl add-port br-int vif22 -- \
+ set Interface vif22 external-ids:iface-id=sw1-p2 \
+ options:tx_pcap=hv1/vif22-tx.pcap \
+- options:rxq_pcap=hv1/vif22-rx.pcap \
+- ofport-request=22
++ options:rxq_pcap=hv1/vif22-rx.pcap
+
+ check ovn-nbctl --wait=hv sync
+ wait_for_ports_up
+@@ -41695,7 +41862,7 @@ dnl don't match on IPv6 source and destination addresses for
+ dnl simple switching.
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([IPv6 switching - megaflow check for IPv6 src/dst matches])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ check ovn-nbctl ls-add sw0
+@@ -41718,13 +41885,11 @@ ovn_attach n1 br-phys 192.168.0.1
+ check ovs-vsctl add-port br-int vif1 -- \
+ set Interface vif1 external-ids:iface-id=vm0 \
+ options:tx_pcap=hv/vif1-tx.pcap \
+- options:rxq_pcap=hv/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv/vif1-rx.pcap
+ check ovs-vsctl add-port br-int vif2 -- \
+ set Interface vif2 external-ids:iface-id=vm1 \
+ options:tx_pcap=hv/vif2-tx.pcap \
+- options:rxq_pcap=hv/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv/vif2-rx.pcap
+
+ check ovn-nbctl --wait=sb sync
+ wait_for_ports_up
+@@ -41861,7 +42026,7 @@ dnl This test checks that the megaflows translated by ovs-vswitchd don't
+ dnl have extensive matches on external IP addresses for simple routing.
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([IPv4/v6 routing to external - megaflow check for src/dst matches])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ check ovn-nbctl ls-add sw0
+@@ -41894,8 +42059,7 @@ check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+ check ovs-vsctl add-port br-int vif1 -- \
+ set Interface vif1 external-ids:iface-id=vm0 \
+ options:tx_pcap=hv/vif1-tx.pcap \
+- options:rxq_pcap=hv/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv/vif1-rx.pcap
+
+ check ovn-nbctl --wait=sb sync
+ wait_for_ports_up
+@@ -42501,8 +42665,7 @@ ovn-appctl vlog/set dbg
+ check ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=sw0-p1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ check ovn-nbctl ls-add sw0
+ check ovn-nbctl lsp-add sw0 sw0-p1
+@@ -42564,7 +42727,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([2 HVs, 2 LS, 1 lport/LS, 2 peer LRs, IPv4 over IPv6])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ # Logical network:
+@@ -42623,8 +42786,7 @@ ovn_attach n1 br-phys 192.168.0.1
+ check ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ sim_add hv2
+ as hv2
+@@ -42633,8 +42795,7 @@ ovn_attach n1 br-phys 192.168.0.2
+ check ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=ls2-lp1 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif1-rx.pcap
+
+
+ # Pre-populate the hypervisors' ARP tables so that we don't lose any
+@@ -42684,7 +42845,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([2 HVs, 2 LS, 1 lport/LS, LRs connected via LS, IPv4 over IPv6])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ # Logical network:
+@@ -42751,8 +42912,7 @@ ovn_attach n1 br-phys 192.168.0.1
+ check ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ sim_add hv2
+ as hv2
+@@ -42761,8 +42921,7 @@ ovn_attach n1 br-phys 192.168.0.2
+ check ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=ls2-lp1 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif1-rx.pcap
+
+
+ # Pre-populate the hypervisors' ARP tables so that we don't lose any
+@@ -42812,7 +42971,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([2 HVs, 2 LS, 1 lport/LS, LRs connected via LS, IPv4 over IPv6, static mac])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ # Logical network:
+@@ -42885,8 +43044,7 @@ ovn_attach n1 br-phys 192.168.0.1
+ check ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ sim_add hv2
+ as hv2
+@@ -42895,8 +43053,7 @@ ovn_attach n1 br-phys 192.168.0.2
+ check ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=ls2-lp1 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif1-rx.pcap
+
+
+ # Pre-populate the hypervisors' ARP tables so that we don't lose any
+@@ -42946,7 +43103,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([2 HVs, 2 LS, 1 lport/LS, LRs connected via LS, IPv4 over IPv6, dynamic])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ # Logical network:
+@@ -43015,8 +43172,7 @@ ovn_attach n1 br-phys 192.168.0.1
+ check ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ sim_add hv2
+ as hv2
+@@ -43025,8 +43181,7 @@ ovn_attach n1 br-phys 192.168.0.2
+ check ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=ls2-lp1 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif1-rx.pcap
+
+
+ # Pre-populate the hypervisors' ARP tables so that we don't lose any
+@@ -43076,7 +43231,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([2 HVs, 2 LS, 1 lport/LS, LRs connected via LS, IPv6 over IPv4, dynamic])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ # Logical network:
+@@ -43145,8 +43300,7 @@ ovn_attach n1 br-phys 192.168.0.1
+ check ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ sim_add hv2
+ as hv2
+@@ -43155,8 +43309,7 @@ ovn_attach n1 br-phys 192.168.0.2
+ check ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=ls2-lp1 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif1-rx.pcap
+
+
+ # Pre-populate the hypervisors' ARP tables so that we don't lose any
+@@ -43206,7 +43359,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([2 HVs, 2 LS, 1 lport/LS, LRs connected via LS, IPv4 over IPv6, ECMP])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ # Logical network:
+@@ -43288,8 +43441,7 @@ ovn_attach n1 br-phys 192.168.0.1
+ check ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ sim_add hv2
+ as hv2
+@@ -43298,8 +43450,7 @@ ovn_attach n1 br-phys 192.168.0.2
+ check ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=ls2-lp1 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif1-rx.pcap
+
+
+ # Pre-populate the hypervisors' ARP tables so that we don't lose any
+@@ -43349,7 +43500,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([2 HVs, 2 LS, 1 lport/LS, 2 peer LRs, IPv6 over IPv4])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ # Logical network:
+@@ -43408,8 +43559,7 @@ ovn_attach n1 br-phys 192.168.0.1
+ check ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ sim_add hv2
+ as hv2
+@@ -43418,8 +43568,7 @@ ovn_attach n1 br-phys 192.168.0.2
+ check ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=ls2-lp1 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif1-rx.pcap
+
+
+ # Pre-populate the hypervisors' ARP tables so that we don't lose any
+@@ -43469,7 +43618,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([DNS reply packet with ACL to drop it])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+ net_add n1
+ sim_add hv1
+@@ -43480,13 +43629,11 @@ ovn_attach n1 br-phys 192.168.0.11
+ ovs-vsctl -- add-port br-int vif1 -- \
+ set interface vif1 external-ids:iface-id=sw0p1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+ ovs-vsctl -- add-port br-int vif2 -- \
+ set interface vif2 external-ids:iface-id=sw0p2 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+- options:rxq_pcap=hv1/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv1/vif2-rx.pcap
+
+ check ovn-nbctl ls-add sw0
+ check ovn-nbctl lsp-add sw0 sw0p1 -- \
+@@ -43851,8 +43998,7 @@ as hv1
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=sw0-port1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ wait_for_ports_up sw0-port1
+
+@@ -43921,8 +44067,7 @@ as hv2
+ ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ set interface hv2-vif1 external-ids:iface-id=sw1-port1 \
+ options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv2/vif1-rx.pcap
+
+ wait_for_ports_up sw1-port1
+
+@@ -43953,8 +44098,7 @@ as hv2
+ ovs-vsctl -- add-port br-int hv2-vif2 -- \
+ set interface hv2-vif2 external-ids:iface-id=sw0-port2 \
+ options:tx_pcap=hv2/vif2-tx.pcap \
+- options:rxq_pcap=hv2/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv2/vif2-rx.pcap
+
+ wait_for_ports_up sw0-port2
+
+@@ -44029,8 +44173,7 @@ as gw2
+ ovs-vsctl -- add-port br-int gw2-vif2 -- \
+ set interface gw2-vif2 external-ids:iface-id=sw1-port1 \
+ options:tx_pcap=gw2/vif2-tx.pcap \
+- options:rxq_pcap=gw2/vif2-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=gw2/vif2-rx.pcap
+
+ wait_for_ports_up sw1-port1
+ trigger_recompute
+@@ -44060,8 +44203,7 @@ as hv2
+ ovs-vsctl -- add-port br-int hv2-vif3 -- \
+ set interface hv2-vif3 external-ids:iface-id=public-p1 \
+ options:tx_pcap=hv2/vif3-tx.pcap \
+- options:rxq_pcap=hv2/vif3-rx.pcap \
+- ofport-request=2
++ options:rxq_pcap=hv2/vif3-rx.pcap
+
+ wait_for_ports_up public-p1
+ trigger_recompute
+@@ -44080,7 +44222,7 @@ AT_CLEANUP
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([Load balancer hairpin flows])
+ AT_KEYWORDS([lb])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ net_add n1
+@@ -44158,7 +44300,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([lb_force_snat_ip=router_ip select correct network for snat])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ check ovn-nbctl lr-add lr
+@@ -44238,7 +44380,7 @@ AT_CLEANUP
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([virtual port claim postpone])
+ AT_KEYWORDS([virtual ports])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ net_add n1
+@@ -44335,6 +44477,7 @@ AT_CLEANUP
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([virtual port - parent port re-create])
+ AT_KEYWORDS([virtual ports])
++CHECK_SCAPY
+ ovn_start
+
+ net_add n1
+@@ -44347,8 +44490,7 @@ check ovn-appctl vlog/set dbg
+ check ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=sw0-p1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+- options:rxq_pcap=hv1/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv1/vif1-rx.pcap
+
+ check ovs-appctl -t ovn-controller vlog/set dbg
+
+@@ -44438,10 +44580,10 @@ create_logical_topology() {
+ check ovn-nbctl lsp-add $sw $sw-nf-p$i -- lsp-set-addresses $sw-nf-p$i "f0:00:00:00:01:0$i"
+ done
+ check ovn-nbctl set logical_switch_port $sw-nf-p1 \
+- options:receive_multicast=false options:lsp_learn_mac=false \
++ options:receive_multicast=false options:lsp_learn_fdb=false \
+ options:is-nf=true options:nf-linked-port=$sw-nf-p2
+ check ovn-nbctl set logical_switch_port $sw-nf-p2 \
+- options:receive_multicast=false options:lsp_learn_mac=false \
++ options:receive_multicast=false options:lsp_learn_fdb=false \
+ options:is-nf=true options:nf-linked-port=$sw-nf-p1
+ check ovn-nbctl nf-add nf0 1 $sw-nf-p1 $sw-nf-p2
+ check ovn-nbctl nfg-add nfg0 1 inline nf0
+@@ -44628,10 +44770,10 @@ create_logical_topology() {
+ check ovn-nbctl lsp-add $sw $sw-nf-p$i -- lsp-set-addresses $sw-nf-p$i "f0:00:00:00:01:0$i"
+ done
+ check ovn-nbctl set logical_switch_port $sw-nf-p1 \
+- options:receive_multicast=false options:lsp_learn_mac=false \
++ options:receive_multicast=false options:lsp_learn_fdb=false \
+ options:is-nf=true options:nf-linked-port=$sw-nf-p2
+ check ovn-nbctl set logical_switch_port $sw-nf-p2 \
+- options:receive_multicast=false options:lsp_learn_mac=false \
++ options:receive_multicast=false options:lsp_learn_fdb=false \
+ options:is-nf=true options:nf-linked-port=$sw-nf-p1
+ check ovn-nbctl nf-add nf0 1 $sw-nf-p1 $sw-nf-p2
+ check ovn-nbctl nfg-add nfg0 1 inline nf0
+@@ -44807,7 +44949,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([Unicast ARP when proxy ARP is configured])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ check ovn-nbctl ls-add ls1
+@@ -44879,9 +45021,215 @@ OVN_CLEANUP([hv1])
+ AT_CLEANUP
+ ])
+
++OVN_FOR_EACH_NORTHD([
++AT_SETUP([Unicast ARP for router port not flooded to multicast group])
++ovn_start
++
++check ovn-nbctl ls-add ls1
++check ovn-nbctl lr-add lr1
++check ovn-nbctl lrp-add lr1 lr1-ls1 00:00:00:00:00:01 10.0.0.1/16
++check ovn-nbctl lsp-add-router-port ls1 ls1-lr1 lr1-ls1
++
++dnl Create 220 VIF ports in batch to make sure we don't trigger the 4096
++dnl resubmit limit due to incorrect forwarding to multicast flood groups.
++nbctl_cmd=""
++for i in $(seq 1 220); do
++ idx=$(printf "%04d" $i)
++ hex_hi=$(printf "%02x" $(($i / 256)))
++ hex_lo=$(printf "%02x" $(($i % 256)))
++ mac="f0:00:00:00:${hex_hi}:${hex_lo}"
++ nbctl_cmd="$nbctl_cmd -- lsp-add ls1 vif-$idx"
++ nbctl_cmd="$nbctl_cmd -- lsp-set-addresses vif-$idx \"$mac\""
++done
++eval check ovn-nbctl $nbctl_cmd
++
++net_add n1
++sim_add hv1
++as hv1
++ovs-vsctl add-br br-phys
++ovn_attach n1 br-phys 192.168.0.1
++
++vsctl_cmd=""
++for i in $(seq 1 220); do
++ idx=$(printf "%04d" $i)
++ vsctl_cmd="$vsctl_cmd -- add-port br-int vif-$idx "
++ vsctl_cmd="$vsctl_cmd -- set Interface vif-$idx external-ids:iface-id=vif-$idx"
++done
++eval check ovs-vsctl $vsctl_cmd
++
++OVN_POPULATE_ARP
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
++
++dnl Check that unicast GARP requests incorrectly spoofing the router port IP
++dnl don't cause the 4096 resubmit limit to be hit.
++ofport=$(as hv1 ovs-vsctl get Interface vif-0001 ofport)
++AT_CHECK([as hv1 ovs-appctl ofproto/trace br-int \
++ "in_port=$ofport,dl_src=f0:00:00:00:00:01,dl_dst=00:00:00:00:00:01,\
++ arp,arp_op=1,arp_spa=10.0.0.1,arp_tpa=10.0.0.1,\
++ arp_sha=f0:00:00:00:00:01,arp_tha=00:00:00:00:00:01"], [0], [stdout])
++AT_CHECK([grep "Translation failed" stdout], [1])
++AT_CHECK([grep "packet is dropped" stdout], [1])
++
++dnl Also check valid unicast ARP requests for the router port don't cause
++dnl the 4096 resubmit limit to be hit.
++AT_CHECK([as hv1 ovs-appctl ofproto/trace br-int \
++ "in_port=$ofport,dl_src=f0:00:00:00:00:01,dl_dst=00:00:00:00:00:01,\
++ arp,arp_op=1,arp_spa=10.0.0.2,arp_tpa=10.0.0.1,\
++ arp_sha=f0:00:00:00:00:01,arp_tha=00:00:00:00:00:01"], [0], [stdout])
++AT_CHECK([grep "Translation failed" stdout], [1])
++AT_CHECK([grep "packet is dropped" stdout], [1])
++
++OVN_CLEANUP([hv1])
++AT_CLEANUP
++])
++
++OVN_FOR_EACH_NORTHD([
++AT_SETUP([Unicast ARP for router IP with proxy ARP - resubmit overflow])
++ovn_start
++
++dnl Create a logical switch and router, connected via a router port.
++check ovn-nbctl ls-add ls1
++check ovn-nbctl lr-add lr1
++check ovn-nbctl lrp-add lr1 lr1-ls1 00:00:00:00:00:01 10.0.0.1/24
++check ovn-nbctl lsp-add-router-port ls1 ls1-lr1 lr1-ls1
++
++dnl Configure proxy ARP on the router port with a range that includes the
++dnl router IP (10.0.0.1 is within 10.0.0.0/24).
++check ovn-nbctl set logical_switch_port ls1-lr1 \
++ options:arp_proxy="10.0.0.0/24"
++
++dnl Add a VIF port that will send the unicast ARP.
++check ovn-nbctl lsp-add ls1 vm1 -- \
++ lsp-set-addresses vm1 "00:00:00:00:00:02 10.0.0.2"
++
++dnl Add 220 additional VIF ports to make MC_FLOOD_L2 large enough
++dnl that flooding to it would exceed the OVS 4096 resubmit limit.
++nbctl_cmd=""
++for i in $(seq 1 220); do
++ idx=$(printf "%04d" $i)
++ hex_hi=$(printf "%02x" $(($i / 256)))
++ hex_lo=$(printf "%02x" $(($i % 256)))
++ mac="f0:00:00:00:${hex_hi}:${hex_lo}"
++ nbctl_cmd="$nbctl_cmd -- lsp-add ls1 vif-$idx"
++ nbctl_cmd="$nbctl_cmd -- lsp-set-addresses vif-$idx \"$mac\""
++done
++eval check ovn-nbctl $nbctl_cmd
++
++net_add n1
++sim_add hv1
++as hv1
++ovs-vsctl add-br br-phys
++ovn_attach n1 br-phys 192.168.0.1
++
++dnl Bind the sender VIF port.
++ovs-vsctl add-port br-int vif-vm1 -- \
++ set Interface vif-vm1 external-ids:iface-id=vm1 \
++ options:tx_pcap=hv1/vif-vm1-tx.pcap \
++ options:rxq_pcap=hv1/vif-vm1-rx.pcap
++
++dnl Bind all 220 additional VIF ports so they join MC_FLOOD_L2.
++vsctl_cmd=""
++for i in $(seq 1 220); do
++ idx=$(printf "%04d" $i)
++ vsctl_cmd="$vsctl_cmd -- add-port br-int vif-$idx"
++ vsctl_cmd="$vsctl_cmd -- set Interface vif-$idx external-ids:iface-id=vif-$idx"
++done
++eval check ovs-vsctl $vsctl_cmd
++
++OVN_POPULATE_ARP
++
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
++
++dnl Trace a unicast ARP request from vm1 for the router IP,
++dnl eth.dst is the router MAC (unicast).
++dnl
++dnl The router port should answer with an ARP reply, since
++dnl the packet is unicast. The proxy ARP is bypassed.
++dnl
++dnl Verify via ofproto/trace that the packet is not dropped.
++packet=""
++AT_CHECK([as hv1 ovs-appctl ofproto/trace br-int \
++ "in_port=vif-vm1,dl_src=00:00:00:00:00:02,dl_dst=00:00:00:00:00:01, \
++ arp,arp_spa=10.0.0.2,arp_tpa=10.0.0.1,arp_op=1, \
++ arp_sha=00:00:00:00:00:02,arp_tha=00:00:00:00:00:01"], [0], [stdout])
++AT_CHECK([grep "Translation failed" stdout], [1])
++AT_CHECK([grep "packet is dropped" stdout], [1])
++
++OVN_CLEANUP([hv1])
++AT_CLEANUP
++])
++
++OVN_FOR_EACH_NORTHD([
++AT_SETUP([Unicast ND_NS for router IPv6 with proxy ARP - resubmit overflow])
++ovn_start
++
++check ovn-nbctl ls-add ls1
++check ovn-nbctl lr-add lr1
++check ovn-nbctl lrp-add lr1 lr1-ls1 00:00:00:00:00:01 10.0.0.1/24 aef0::1/64
++check ovn-nbctl lsp-add-router-port ls1 ls1-lr1 lr1-ls1
++
++dnl Configure proxy ARP/ND on the router port with ranges that include
++dnl the router IPs (10.0.0.1 is within 10.0.0.0/24, aef0::1 is within
++dnl aef0::/64).
++check ovn-nbctl set logical_switch_port ls1-lr1 \
++ options:arp_proxy="10.0.0.0/24 aef0::/64"
++
++check ovn-nbctl lsp-add ls1 vm1 -- \
++ lsp-set-addresses vm1 "00:00:00:00:00:02 10.0.0.2 aef0::2"
++
++dnl Add 220 additional VIF ports to make MC_FLOOD_L2/MC_UNKNOWN large enough
++dnl that flooding to it would exceed the OVS 4096 resubmit limit.
++nbctl_cmd=""
++for i in $(seq 1 220); do
++ idx=$(printf "%04d" $i)
++ nbctl_cmd="$nbctl_cmd -- lsp-add ls1 vif-$idx"
++ nbctl_cmd="$nbctl_cmd -- lsp-set-addresses vif-$idx unknown"
++done
++eval check ovn-nbctl $nbctl_cmd
++
++net_add n1
++sim_add hv1
++as hv1
++ovs-vsctl add-br br-phys
++ovn_attach n1 br-phys 192.168.0.1
++
++ovs-vsctl add-port br-int vif-vm1 -- \
++ set Interface vif-vm1 external-ids:iface-id=vm1 \
++ options:tx_pcap=hv1/vif-vm1-tx.pcap \
++ options:rxq_pcap=hv1/vif-vm1-rx.pcap
++
++vsctl_cmd=""
++for i in $(seq 1 220); do
++ idx=$(printf "%04d" $i)
++ vsctl_cmd="$vsctl_cmd -- add-port br-int vif-$idx"
++ vsctl_cmd="$vsctl_cmd -- set Interface vif-$idx external-ids:iface-id=vif-$idx"
++done
++eval check ovs-vsctl $vsctl_cmd
++
++OVN_POPULATE_ARP
++
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
++
++dnl Trace a unicast ND_NS from vm1 for the router IPv6 address.
++dnl eth.dst is the router MAC (unicast, NOT solicited-node multicast).
++AT_CHECK([as hv1 ovs-appctl ofproto/trace br-int \
++ "in_port=vif-vm1,dl_src=00:00:00:00:00:02,dl_dst=00:00:00:00:00:01, \
++ icmp6,ipv6_src=aef0::2,ipv6_dst=aef0::1,nw_ttl=255, \
++ icmpv6_type=135,icmpv6_code=0, \
++ nd_target=aef0::1,nd_sll=00:00:00:00:00:02"], [0], [stdout])
++AT_CHECK([grep "Translation failed" stdout], [1])
++AT_CHECK([grep "packet is dropped" stdout], [1])
++
++OVN_CLEANUP([hv1])
++AT_CLEANUP
++])
++
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([Port security - VRRPv3 ARP/ND])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+ net_add n1
+ sim_add hv1
+@@ -45892,7 +46240,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([Drop unknown eth type on router ports])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ ovn_start
+
+ net_add n1
+@@ -46097,6 +46445,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([IPv4 over v6 Neigh solicitation test])
++CHECK_SCAPY
+ ovn_start
+
+ net_add n1
+@@ -46126,8 +46475,7 @@ check ovn-nbctl lr-nat-add lr0 dnat_and_snat 172.168.0.110 10.0.0.3
+ ovs-vsctl -- add-port br-int vif1 -- \
+ set interface vif1 external-ids:iface-id=sw0-port1 \
+ options:tx_pcap=hv/vif1-tx.pcap \
+- options:rxq_pcap=hv/vif1-rx.pcap \
+- ofport-request=1
++ options:rxq_pcap=hv/vif1-rx.pcap
+
+ wait_for_ports_up
+
+@@ -46157,3 +46505,166 @@ OVN_CLEANUP([hv])
+ AT_CLEANUP
+ ])
+
++OVN_FOR_EACH_NORTHD([
++AT_SETUP([IPv6 static route ND NS resolution])
++CHECK_SCAPY
++ovn_start
++
++net_add n1
++sim_add hv
++ovs-vsctl add-br br-phys
++ovn_attach n1 br-phys 192.168.0.1
++ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
++
++check ovn-nbctl ls-add sw0
++check ovn-nbctl lsp-add sw0 sw0-port1
++check ovn-nbctl lsp-set-addresses sw0-port1 "50:54:00:00:00:01 2001:db8::3"
++
++check ovn-nbctl lr-add lr0
++check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 2001:db8::1/64
++check ovn-nbctl lsp-add-router-port sw0 sw0-lr0 lr0-sw0
++
++check ovn-nbctl ls-add public
++check ovn-nbctl lrp-add lr0 lr0-public 00:00:20:20:12:13 2001:db8:1::1/64
++check ovn-nbctl lsp-add-router-port public public-lr0 lr0-public
++
++# localnet port
++check ovn-nbctl lsp-add-localnet-port public ln-public phys
++
++check ovn-nbctl lrp-set-gateway-chassis lr0-public hv 20
++
++# Add an IPv6 static route with an IPv6 nexthop whose MAC is unknown.
++check ovn-nbctl --wait=hv lr-route-add lr0 2001:db8:2::/64 2001:db8:1::cafe
++
++ovs-vsctl -- add-port br-int vif1 -- \
++ set interface vif1 external-ids:iface-id=sw0-port1 \
++ options:tx_pcap=hv/vif1-tx.pcap \
++ options:rxq_pcap=hv/vif1-rx.pcap
++
++wait_for_ports_up
++
++# Send an IPv6 packet from sw0-port1 destined for an address behind the
++# static route. The nexthop 2001:db8:1::cafe has no MAC binding, so the
++# router must emit an ND NS to resolve it.
++packet=$(fmt_pkt "Ether(dst='00:00:00:00:ff:01', src='50:54:00:00:00:01')/ \
++ IPv6(src='2001:db8::3', dst='2001:db8:2::42', hlim=64)/ \
++ ICMPv6EchoRequest()")
++check as hv ovs-appctl netdev-dummy/receive vif1 $packet
++
++# Verify the ND NS has correct solicited-node multicast addresses derived
++# from the nexthop 2001:db8:1::cafe:
++# eth.dst = 33:33:ff:00:ca:fe (multicast MAC from solicited-node)
++# ip6.dst = ff02::1:ff00:cafe (solicited-node multicast)
++# nd.target = 2001:db8:1::cafe (the actual nexthop)
++nd_ns=$(fmt_pkt "Ether(dst='33:33:ff:00:ca:fe', src='00:00:20:20:12:13')/ \
++ IPv6(src='fe80::200:20ff:fe20:1213', \
++ dst='ff02::1:ff00:cafe')/ \
++ ICMPv6ND_NS(tgt='2001:db8:1::cafe')/ \
++ ICMPv6NDOptSrcLLAddr(lladdr='00:00:20:20:12:13')")
++
++echo $nd_ns > expected_nd_ns
++OVN_CHECK_PACKETS_CONTAIN([hv/br-phys_n1-tx.pcap], [expected_nd_ns])
++
++OVN_CLEANUP([hv])
++AT_CLEANUP
++])
++
++OVN_FOR_EACH_NORTHD([
++AT_SETUP([Traffic from external port not looped back to localnet])
++
++ovn_start
++CHECK_SCAPY()
++net_add n1
++
++sim_add hv1
++as hv1
++check ovs-vsctl add-br br-phys
++ovn_attach n1 br-phys 192.168.0.1
++check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
++
++# Topology:
++# A logical switch with mcast_snoop enabled, a localnet port (sw-ln),
++# a regular VIF port (sw-p1) to make the datapath local, and an
++# external port (sw-ext) representing a baremetal node.
++
++check ovn-nbctl ls-add sw \
++ -- lsp-add sw sw-ln \
++ -- lsp-set-type sw-ln localnet \
++ -- lsp-set-addresses sw-ln unknown \
++ -- lsp-set-options sw-ln network_name=phys mcast_flood_reports=true
++
++check ovn-nbctl lsp-add sw sw-p1 \
++ -- lsp-set-addresses sw-p1 "00:00:00:00:00:10 10.0.0.10"
++
++check ovn-nbctl lsp-add sw sw-ext \
++ -- lsp-set-type sw-ext external \
++ -- lsp-set-addresses sw-ext "50:54:00:00:00:01"
++
++check ovn-nbctl set logical_switch sw \
++ other_config:mcast_snoop=true \
++ other_config:mcast_querier=false
++
++check ovs-vsctl -- add-port br-int hv1-vif1 -- \
++ set interface hv1-vif1 external-ids:iface-id=sw-p1 \
++ options:tx_pcap=hv1/vif1-tx.pcap \
++ options:rxq_pcap=hv1/vif1-rx.pcap
++
++check ovn-sbctl lsp-bind sw-ext hv1
++
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
++
++OVS_WAIT_UNTIL([grep pinctrl hv1/ovn-controller.log | grep -c connected])
++
++as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
++
++# Unicast unknown. The destination MAC is not known to any LSP so
++# the packet hits S_SWITCH_IN_L2_UNKNOWN and gets flooded to
++# MC_UNKNOWN (which includes the localnet port).
++packet=$(fmt_pkt \
++ "Ether(dst='aa:bb:cc:dd:ee:ff', src='50:54:00:00:00:01') / \
++ IP(src='10.0.0.1', dst='10.0.0.99') / \
++ UDP(sport=1234, dport=5678) / Raw(b'test')")
++check as hv1 ovs-appctl netdev-dummy/receive br-phys_n1 $packet
++
++# MLDv2 report. Simulates a baremetal node sending an MLD report
++# during PXE boot via the provisioning network.
++mld_packet=$(fmt_pkt \
++ "Ether(dst='33:33:00:00:00:16', src='50:54:00:00:00:01') / \
++ IPv6(src='fe80::1', dst='ff02::16', hlim=1) / \
++ IPv6ExtHdrHopByHop(options=RouterAlert(value=0)) / \
++ ICMPv6MLReport2(records=ICMPv6MLDMultAddrRec(rtype=4, \
++ dst='ff0a:dead:beef::1'))")
++check as hv1 ovs-appctl netdev-dummy/receive br-phys_n1 $mld_packet
++
++# IGMPv3 report. Same scenario but with IPv4 multicast.
++igmp_packet=$(fmt_pkt \
++ "Ether(dst='01:00:5e:00:00:16', src='50:54:00:00:00:01') / \
++ IP(src='10.0.0.1', dst='224.0.0.22', ttl=1, \
++ options=IPOption_Router_Alert()) / \
++ IGMPv3(type=0x22) / \
++ IGMPv3mr(records=IGMPv3gr(rtype=4, maddr='239.1.2.3'))")
++check as hv1 ovs-appctl netdev-dummy/receive br-phys_n1 $igmp_packet
++
++# Wait for pinctrl to fully process the IGMP/MLD reports and sync them
++# to the SB database. This guarantees that pinctrl has also re-injected
++# the reports for flooding to mrouters (where the loopback would occur).
++# The unicast unknown packet (sent first) was processed synchronously in
++# the datapath, so it is certainly done by now.
++wait_row_count IGMP_Group 1 address=239.1.2.3
++wait_row_count IGMP_Group 1 address='"ff0a:dead:beef::1"'
++
++# None of the packets must have been sent back to the localnet port.
++AT_CHECK([$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" \
++ hv1/br-phys_n1-tx.pcap > tx_packets.txt])
++AT_CHECK([grep -q $packet tx_packets.txt], [1])
++AT_CHECK([grep -q $mld_packet tx_packets.txt], [1])
++AT_CHECK([grep -q $igmp_packet tx_packets.txt], [1])
++
++# Check if ovs did prevent any loop.
++AT_CHECK([grep "skipping output to input port" \
++ hv1/ovs-vswitchd.log], [1])
++
++OVN_CLEANUP([hv1])
++AT_CLEANUP
++])
+diff --git a/tests/ovs-macros.at b/tests/ovs-macros.at
+index 6e6e520802..a0d1332e3e 100644
+--- a/tests/ovs-macros.at
++++ b/tests/ovs-macros.at
+@@ -388,7 +388,7 @@ on_exit () {
+ (echo "test -e ovn-nb/ovn-nb.sock && $1"; cat cleanup;) > cleanup.tmp
+ elif [ echo "$1" | grep -qe '^ovn-sbctl' ]; then
+ (echo "test -e ovn-sb/ovn-sb.sock && $1"; cat cleanup) > cleanup.tmp
+- elif [ echo "$1" | grep -qe '^ovs-ofctl' -qe '^ovs-dpctl' ]; then
++ elif [ echo "$1" | grep -qe '^ovs-ofctl' ]; then
+ (echo "test -e $OVS_RUNDIR/ovs-vswitchd.pid && $1"; cat cleanup) > cleanup.tmp
+ else
+ (echo "$1"; cat cleanup) > cleanup.tmp
+diff --git a/tests/scapy-server.py b/tests/scapy-server.py
+index e9f2429ee8..004a492659 100755
+--- a/tests/scapy-server.py
++++ b/tests/scapy-server.py
+@@ -11,6 +11,7 @@ import binascii
+ from socket import * # noqa: F401,F403
+ from scapy.all import * # noqa: F401,F403
+ from scapy.all import raw
++from scapy.contrib.igmpv3 import * # noqa: F401,F403
+
+
+ vlog = ovs.vlog.Vlog("scapy-server")
+diff --git a/tests/system-common-macros.at b/tests/system-common-macros.at
+index 1559c4e8d1..9912912de2 100644
+--- a/tests/system-common-macros.at
++++ b/tests/system-common-macros.at
+@@ -161,23 +161,32 @@ m4_define([NETNS_START_TCPDUMP],
+ ]
+ )
+
+-# NETNS_START_DHCPD([namespace], [iface], [dhcpd.conf])
++# NETNS_START_DHCPD([namespace], [iface], [dhcpd.conf], [extra_dhcp_args],
++# [extra_tcpdump_args])
+ #
+-# Helper to properly start dhcpd
++# Helper to properly start dhcpd. extra_dhcp_args is optional ("-6" if not set)
++# extra_tcpdump_args is also optional.
+ m4_define([NETNS_START_DHCPD],
+ [
+- DHCP_TEST_DIR="$ovs_base/dhcp-test"
+- mkdir -p $DHCP_TEST_DIR
+-
+- mv $3 $DHCP_TEST_DIR/dhcpd.conf
+-
+- touch $DHCP_TEST_DIR/dhcpd.leases
+- chown root:dhcpd $DHCP_TEST_DIR $DHCP_TEST_DIR/dhcpd.leases
+- chmod 775 $DHCP_TEST_DIR
+- chmod 664 $DHCP_TEST_DIR/dhcpd.leases
+-
+- NETNS_START_TCPDUMP([$1], [-nni $2], [$1])
+- NETNS_DAEMONIZE([$1], [dhcpd -6 -f -lf $DHCP_TEST_DIR/dhcpd.leases -cf $DHCP_TEST_DIR/dhcpd.conf $2 > dhcpd.log 2>&1], [dhcpd.pid])
++ DHCP_MACRO_TEST_DIR="$ovs_base/dhcp-test"
++ dhcp_macro_arg="$4"
++ if test "x$dhcp_macro_arg" = "x"; then
++ EXTRA_DHCP_MACRO_ARGS="-6"
++ else
++ EXTRA_DHCP_MACRO_ARGS="$dhcp_macro_arg"
++ fi
++ mkdir -p $DHCP_MACRO_TEST_DIR
++
++ mv $3 $DHCP_MACRO_TEST_DIR/dhcpd.conf
++
++ touch $DHCP_MACRO_TEST_DIR/dhcpd.leases
++ chown root:dhcpd $DHCP_MACRO_TEST_DIR $DHCP_MACRO_TEST_DIR/dhcpd.leases
++ chmod 775 $DHCP_MACRO_TEST_DIR
++ chmod 664 $DHCP_MACRO_TEST_DIR/dhcpd.leases
++
++ NETNS_START_TCPDUMP([$1], [-nni $2 $5], [$1])
++ NETNS_DAEMONIZE([$1], [dhcpd $EXTRA_DHCP_MACRO_ARGS -d -f -lf $DHCP_MACRO_TEST_DIR/dhcpd.leases -cf $DHCP_MACRO_TEST_DIR/dhcpd.conf $2 > dhcpd.log 2>&1], [dhcpd.pid])
++ OVS_WAIT_UNTIL([grep -q "Server starting service" dhcpd.log])
+ ]
+ )
+
+@@ -185,9 +194,9 @@ m4_define([NETNS_START_DHCPD],
+ #
+ # Start a "persistent" UDP echo server within 'namespace', listening on 'port'
+ # and echoing back any data it receives. 'ip-version' is either "4" or "6".
+-# The server keeps running across multiple client connections (nc's "-k").
++# The server keeps running across multiple client connections (Ncat's "-k").
+ #
+-# nc's "-k" makes it fork child processes that keep the listening port open, so
++# Ncat's "-k" makes it fork child processes that keep the listening port open, so
+ # killing only the parent leaves those children running and hangs the whole
+ # test cleanup (especially on failures). This macro therefore installs a
+ # cleanup hook that kills the child processes (pkill -P) before the parent.
+@@ -197,7 +206,7 @@ m4_define([NETNS_START_DHCPD],
+ # test.
+ #
+ m4_define([NETNS_START_UDP_ECHO],
+- [ip netns exec $1 nc -$2 -k -l -u -p $3 -d 0.1 -c '/bin/cat' & echo $! > $4
++ [ip netns exec $1 ncat -$2 -k -l -u -p $3 -d 0.1 -c 'cat' & echo $! > $4
+ echo "pkill -P \`cat $4\` && kill \`cat $4\`" >> cleanup
+ ]
+ )
+@@ -483,8 +492,8 @@ m4_define([NS_CHECK_CONNECTIVITY__],
+ check ovs-appctl revalidator/wait
+
+ # Start a simple TCP and UDP echo server that replies with "ack <received_msg>".
+- NETNS_DAEMONIZE($ns_dst, [nc $proto -l -p 1235 -d 0.1 -c '/bin/cat'], [nc-$ns_dst-$ip-tcp.pid])
+- NETNS_DAEMONIZE($ns_dst, [nc $proto -l -u -p 1234 -d 0.1 -c '/bin/cat'], [nc-$ns_dst-$ip-udp.pid])
++ NETNS_DAEMONIZE($ns_dst, [ncat $proto -l -p 1235 -d 0.1 -c 'cat'], [nc-$ns_dst-$ip-tcp.pid])
++ NETNS_DAEMONIZE($ns_dst, [ncat $proto -l -u -p 1234 -d 0.1 -c 'cat'], [nc-$ns_dst-$ip-udp.pid])
+
+ # Ensure that the destination NS can be pinged on the specified IP
+ NS_CHECK_EXEC([$ns_src], [ping $proto -q -c 3 -i 0.3 -w 2 $ip | FORMAT_PING], \
+@@ -493,12 +502,13 @@ m4_define([NS_CHECK_CONNECTIVITY__],
+ ])
+
+ # Exchange multiple messages over TCP and UDP to verify connectivity
+- # Note(mkalcok): Server replies are printed to file, because STDOUT is captured by the nc.
++ # Note(mkalcok): Server replies are printed to file, because Ncat captures
++ # STDOUT.
+ :> ./tcp_data
+ :> ./udp_data
+
+- NETNS_DAEMONIZE($ns_src, [nc $proto $ip 1235 -d 0.1 -c 'for i in $(seq 1 3); do echo "tcp_data $i"; read msg; echo "$msg" >>./tcp_data; done'], [nc_$ns_src-$ip-tcp.pid])
+- NETNS_DAEMONIZE($ns_src, [nc $proto -u $ip 1234 -d 0.1 -c 'for i in $(seq 1 3); do echo "udp_data $i"; read msg; echo "$msg" >>./udp_data; done'], [nc_$ns_src-$ip-udp.pid])
++ NETNS_DAEMONIZE($ns_src, [ncat $proto $ip 1235 -d 0.1 -c 'for i in $(seq 1 3); do echo "tcp_data $i"; read msg; echo "$msg" >>./tcp_data; done'], [nc_$ns_src-$ip-tcp.pid])
++ NETNS_DAEMONIZE($ns_src, [ncat $proto -u $ip 1234 -d 0.1 -c 'for i in $(seq 1 3); do echo "udp_data $i"; read msg; echo "$msg" >>./udp_data; done'], [nc_$ns_src-$ip-udp.pid])
+
+ OVS_WAIT_FOR_OUTPUT([cat ./tcp_data], [0], [dnl
+ tcp_data 1
+diff --git a/tests/system-kmod-macros.at b/tests/system-kmod-macros.at
+index f3b03f572a..9351cfa82a 100644
+--- a/tests/system-kmod-macros.at
++++ b/tests/system-kmod-macros.at
+@@ -22,8 +22,10 @@ m4_define([OVS_TRAFFIC_VSWITCHD_START],
+ [modprobe mod && on_exit 'modprobe -r mod' || echo "Module mod not loaded."
+ ])
+ on_exit 'ovs-dpctl show | grep ovs-system && ovs-dpctl del-dp ovs-system'
+- on_exit 'test -e $OVS_RUNDIR/ovs-vswitchd.pid && ovs-appctl dpctl/flush-conntrack'
+ _OVS_VSWITCHD_START([])
++ # Do 'on_exit ovs-appctl ...' after 'on_exit kill_ovs_vswitchd' (from _OVS_VSWITCHD_START)
++ # so it is executed before.
++ on_exit 'test -e $OVS_RUNDIR/ovs-vswitchd.pid && ovs-appctl dpctl/flush-conntrack'
+ dnl Add bridges, ports, etc.
+ AT_CHECK([ovs-vsctl -- _ADD_BR([br0]) -- $1 m4_if([$2], [], [], [| uuidfilt])], [0], [$2])
+ if test OVN_MONITOR_ALL = yes; then
+diff --git a/tests/system-ovn-kmod.at b/tests/system-ovn-kmod.at
+index 53fc457342..4d556a2420 100644
+--- a/tests/system-ovn-kmod.at
++++ b/tests/system-ovn-kmod.at
+@@ -107,8 +107,8 @@ check ovn-nbctl lr-lb-add R2 lb0-no-aff
+ check ovn-nbctl lr-lb-add R2 lb10-no-aff
+
+ # Start webservers in 'foo1', 'bar1'.
+-NETNS_DAEMONIZE([foo1], [nc -l -k 192.168.1.2 80], [nc-foo1.pid])
+-NETNS_DAEMONIZE([bar1], [nc -l -k 192.168.2.2 80], [nc-bar1.pid])
++NETNS_DAEMONIZE([foo1], [ncat -l -k 192.168.1.2 80], [nc-foo1.pid])
++NETNS_DAEMONIZE([bar1], [ncat -l -k 192.168.2.2 80], [nc-bar1.pid])
+
+ # Wait for ovn-controller to catch up.
+ check ovn-nbctl --wait=hv sync
+@@ -122,7 +122,7 @@ grep 'nat(dst=192.168.2.2:80)'])
+ dnl Should work with the virtual IP address through NAT
+ OVS_WAIT_FOR_OUTPUT([
+ for i in $(seq 1 5); do
+- NS_EXEC([alice1], [nc -z 172.16.1.100 8080])
++ NS_EXEC([alice1], [ncat -z 172.16.1.100 8080])
+ done
+
+ dnl Each server should have at least one connection.
+@@ -140,7 +140,7 @@ check ovn-nbctl --wait=sb set load_balancer lb10 options:affinity_timeout=60
+
+ for i in $(seq 1 15); do
+ echo Request $i
+- NS_CHECK_EXEC([alice1], [nc -z 172.16.1.100 8080])
++ NS_CHECK_EXEC([alice1], [ncat -z 172.16.1.100 8080])
+ done
+
+ dnl here we should have just one entry in the ct table
+@@ -161,7 +161,7 @@ n2=$(ovs-ofctl dump-flows br-int table=$dnat |awk '/priority=150,ct_state=\+new\
+ echo $?
+ }
+ AT_CHECK([test $(check_affinity_flows) -eq 0])
+-NS_CHECK_EXEC([alice1], [nc -z 172.16.1.100 8081])
++NS_CHECK_EXEC([alice1], [ncat -z 172.16.1.100 8081])
+
+ # Flush conntrack entries for easier output parsing of next test.
+ AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+@@ -181,7 +181,7 @@ check ovn-nbctl lr-lb-add R2 lb11-no-aff
+ # check we use both backends
+ OVS_WAIT_FOR_OUTPUT([
+ for i in $(seq 1 5); do
+- NS_EXEC([alice1], [nc -z 172.16.1.101 8080])
++ NS_EXEC([alice1], [ncat -z 172.16.1.101 8080])
+ ovs-ofctl del-flows br-int table=OFTABLE_CHK_LB_AFFINITY
+ done
+
+@@ -191,7 +191,7 @@ OVS_WAIT_FOR_OUTPUT([
+ tcp,orig=(src=172.16.1.2,dst=172.16.1.101,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
+ tcp,orig=(src=172.16.1.2,dst=172.16.1.101,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
+ ])
+-NS_CHECK_EXEC([alice1], [nc -z 172.16.1.101 8081])
++NS_CHECK_EXEC([alice1], [ncat -z 172.16.1.101 8081])
+
+ dnl There shouldn't be any DP flow with both +est and ct_tuple4. Otherwise HW
+ dnl offload may not work.
+@@ -200,7 +200,7 @@ AT_CHECK([ovs-appctl dpctl/dump-flows | grep 'ct_tuple4' | grep '+est'], [1], [i
+ # Flush conntrack entries for easier output parsing of next test.
+ AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+
+-NETNS_DAEMONIZE([bar2], [nc -l -k 192.168.2.3 80], [nc-bar2.pid])
++NETNS_DAEMONIZE([bar2], [ncat -l -k 192.168.2.3 80], [nc-bar2.pid])
+
+ check ovn-nbctl lb-add lb2 192.168.2.100:8080 192.168.2.2:80,192.168.2.3:80
+ check ovn-nbctl lb-add lb20 192.168.2.120:8080 192.168.2.2:80,192.168.2.3:80
+@@ -215,14 +215,14 @@ check ovn-nbctl ls-lb-add foo lb20-no-aff
+
+ for i in $(seq 1 15); do
+ echo Request $i
+- NS_CHECK_EXEC([foo1], [nc -z 192.168.2.100 8080])
++ NS_CHECK_EXEC([foo1], [ncat -z 192.168.2.100 8080])
+ done
+
+ AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(192.168.2.100) |
+ sed -e 's/zone=[[0-9]]*/zone=<cleared>/; s/src=192.168.2.[[0-9]]/src=192.168.2.<cleared>/'], [0], [dnl
+ tcp,orig=(src=192.168.1.2,dst=192.168.2.100,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.<cleared>,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
+ ])
+-NS_CHECK_EXEC([foo1], [nc -z 192.168.2.100 8081])
++NS_CHECK_EXEC([foo1], [ncat -z 192.168.2.100 8081])
+
+ # Flush conntrack entries for easier output parsing of next test.
+ AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+@@ -242,7 +242,7 @@ AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+
+ OVS_WAIT_FOR_OUTPUT([
+ for i in $(seq 1 5); do
+- NS_EXEC([foo1], [nc -z 192.168.2.101 8080])
++ NS_EXEC([foo1], [ncat -z 192.168.2.101 8080])
+ ovs-ofctl del-flows br-int table=OFTABLE_CHK_LB_AFFINITY
+ done
+
+@@ -252,7 +252,7 @@ OVS_WAIT_FOR_OUTPUT([
+ tcp,orig=(src=192.168.1.2,dst=192.168.2.101,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.2,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
+ tcp,orig=(src=192.168.1.2,dst=192.168.2.101,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.3,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
+ ])
+-NS_CHECK_EXEC([foo1], [nc -z 192.168.2.101 8081])
++NS_CHECK_EXEC([foo1], [ncat -z 192.168.2.101 8081])
+
+ NS_CHECK_EXEC([foo1], [ip neigh add 192.168.1.200 lladdr 00:00:01:01:02:03 dev foo1], [0])
+ check ovn-nbctl lb-add lb4 192.168.1.100:8080 192.168.1.2:80
+@@ -275,7 +275,7 @@ AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+
+ for i in $(seq 1 15); do
+ echo Request $i
+- NS_CHECK_EXEC([foo1], [nc -z 192.168.1.100 8080])
++ NS_CHECK_EXEC([foo1], [ncat -z 192.168.1.100 8080])
+ done
+
+ dnl Each server should have at least one connection.
+@@ -285,7 +285,7 @@ tcp,orig=(src=192.168.1.2,dst=192.168.1.100,sport=<cleared>,dport=<cleared>),rep
+ tcp,orig=(src=192.168.1.2,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=192.168.1.200,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
+ tcp,orig=(src=192.168.1.200,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=192.168.1.200,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
+ ])
+-NS_CHECK_EXEC([foo1], [nc -z 192.168.1.100 8081])
++NS_CHECK_EXEC([foo1], [ncat -z 192.168.1.100 8081])
+
+ dnl There shouldn't be any DP flow with both +est and ct_tuple4. Otherwise HW
+ dnl offload may not work.
+@@ -416,13 +416,13 @@ OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-groups br-int | \
+ grep 'nat(dst=\[[fd11::2\]]:80)'])
+
+ # Start webservers in 'foo1', 'bar1'.
+-NETNS_DAEMONIZE([foo1], [nc -l -k fd11::2 80], [nc-foo1.pid])
+-NETNS_DAEMONIZE([bar1], [nc -l -k fd12::2 80], [nc-bar1.pid])
++NETNS_DAEMONIZE([foo1], [ncat -l -k fd11::2 80], [nc-foo1.pid])
++NETNS_DAEMONIZE([bar1], [ncat -l -k fd12::2 80], [nc-bar1.pid])
+
+ dnl Should work with the virtual IP address through NAT
+ OVS_WAIT_FOR_OUTPUT([
+ for i in $(seq 1 5); do
+- NS_EXEC([alice1], [nc -z fd30::1 8080])
++ NS_EXEC([alice1], [ncat -z fd30::1 8080])
+ done
+
+ dnl Each server should have at least one connection.
+@@ -431,7 +431,7 @@ OVS_WAIT_FOR_OUTPUT([
+ tcp,orig=(src=fd72::2,dst=fd30::1,sport=<cleared>,dport=<cleared>),reply=(src=fd11::2,dst=fd72::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
+ tcp,orig=(src=fd72::2,dst=fd30::1,sport=<cleared>,dport=<cleared>),reply=(src=fd12::2,dst=fd72::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
+ ])
+-NS_CHECK_EXEC([alice1], [nc -z fd30::1 8081])
++NS_CHECK_EXEC([alice1], [ncat -z fd30::1 8081])
+
+ # Flush conntrack entries for easier output parsing of next test.
+ AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+@@ -441,7 +441,7 @@ check ovn-nbctl --wait=sb set load_balancer lb10 options:affinity_timeout=60
+
+ for i in $(seq 1 15); do
+ echo Request $i
+- NS_CHECK_EXEC([alice1], [nc -z fd30::1 8080])
++ NS_CHECK_EXEC([alice1], [ncat -z fd30::1 8080])
+ done
+
+ dnl here we should have just one entry in the ct table
+@@ -462,7 +462,7 @@ n2=$(ovs-ofctl dump-flows br-int table=$dnat |awk '/priority=150,ct_state=\+new\
+ echo $?
+ }
+ AT_CHECK([test $(check_affinity_flows) -eq 0])
+-NS_CHECK_EXEC([alice1], [nc -z fd30::1 8081])
++NS_CHECK_EXEC([alice1], [ncat -z fd30::1 8081])
+
+ # Flush conntrack entries for easier output parsing of next test.
+ AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+@@ -482,7 +482,7 @@ check ovn-nbctl lr-lb-add R2 lb11-no-aff
+ # check we use both backends
+ OVS_WAIT_FOR_OUTPUT([
+ for i in $(seq 1 5); do
+- NS_EXEC([alice1], [nc -z fd30::2 8080])
++ NS_EXEC([alice1], [ncat -z fd30::2 8080])
+ ovs-ofctl del-flows br-int table=OFTABLE_CHK_LB_AFFINITY
+ done
+
+@@ -492,12 +492,12 @@ OVS_WAIT_FOR_OUTPUT([
+ tcp,orig=(src=fd72::2,dst=fd30::2,sport=<cleared>,dport=<cleared>),reply=(src=fd11::2,dst=fd72::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
+ tcp,orig=(src=fd72::2,dst=fd30::2,sport=<cleared>,dport=<cleared>),reply=(src=fd12::2,dst=fd72::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
+ ])
+-NS_CHECK_EXEC([alice1], [nc -z fd30::2 8081])
++NS_CHECK_EXEC([alice1], [ncat -z fd30::2 8081])
+
+ # Flush conntrack entries for easier output parsing of next test.
+ AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+
+-NETNS_DAEMONIZE([bar2], [nc -l -k fd12::3 80], [nc-bar2.pid])
++NETNS_DAEMONIZE([bar2], [ncat -l -k fd12::3 80], [nc-bar2.pid])
+
+ check ovn-nbctl lb-add lb2 [[fd12::a]]:8080 [[fd12::2]]:80,[[fd12::3]]:80
+ check ovn-nbctl lb-add lb20 [[fd12::2a]]:8080 [[fd12::2]]:80,[[fd12::3]]:80
+@@ -512,7 +512,7 @@ check ovn-nbctl ls-lb-add foo lb20-no-aff
+
+ for i in $(seq 1 15); do
+ echo Request $i
+- NS_CHECK_EXEC([foo1], [nc -z fd12::a 8080])
++ NS_CHECK_EXEC([foo1], [ncat -z fd12::a 8080])
+ done
+
+ dnl here we should have just one entry in the ct table
+@@ -520,7 +520,7 @@ AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd12::a) | grep -v fe80 |
+ sed -e 's/zone=[[0-9]]*/zone=<cleared>/; s/src=fd12::[[0-9]]/src=fd12::<cleared>/'], [0], [dnl
+ tcp,orig=(src=fd11::2,dst=fd12::a,sport=<cleared>,dport=<cleared>),reply=(src=fd12::<cleared>,dst=fd11::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
+ ])
+-NS_CHECK_EXEC([foo1], [nc -z fd12::a 8081])
++NS_CHECK_EXEC([foo1], [ncat -z fd12::a 8081])
+
+ AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+
+@@ -537,7 +537,7 @@ check ovn-nbctl ls-lb-add foo lb30-no-aff
+
+ OVS_WAIT_FOR_OUTPUT([
+ for i in $(seq 1 5); do
+- NS_EXEC([foo1], [nc -z fd12::b 8080])
++ NS_EXEC([foo1], [ncat -z fd12::b 8080])
+ ovs-ofctl del-flows br-int table=OFTABLE_CHK_LB_AFFINITY
+ done
+
+@@ -546,7 +546,7 @@ OVS_WAIT_FOR_OUTPUT([
+ tcp,orig=(src=fd11::2,dst=fd12::b,sport=<cleared>,dport=<cleared>),reply=(src=fd12::2,dst=fd11::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
+ tcp,orig=(src=fd11::2,dst=fd12::b,sport=<cleared>,dport=<cleared>),reply=(src=fd12::3,dst=fd11::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
+ ])
+-NS_CHECK_EXEC([foo1], [nc -z fd12::b 8081])
++NS_CHECK_EXEC([foo1], [ncat -z fd12::b 8081])
+
+ NS_CHECK_EXEC([foo1], [ip -6 neigh add fd11::b lladdr 00:00:01:01:02:03 dev foo1], [0])
+ check ovn-nbctl --wait=sb lb-add lb4 [[fd11::a]]:8080 [[fd11::2]]:80
+@@ -569,7 +569,7 @@ AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+
+ OVS_WAIT_FOR_OUTPUT([
+ for i in $(seq 1 5); do
+- NS_EXEC([foo1], [nc -z fd11::a 8080])
++ NS_EXEC([foo1], [ncat -z fd11::a 8080])
+ done
+
+ dnl Each server should have at least one connection.
+@@ -579,7 +579,7 @@ tcp,orig=(src=fd11::2,dst=fd11::2,sport=<cleared>,dport=<cleared>),reply=(src=fd
+ tcp,orig=(src=fd11::2,dst=fd11::a,sport=<cleared>,dport=<cleared>),reply=(src=fd11::2,dst=fd11::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
+ tcp,orig=(src=fd11::b,dst=fd11::2,sport=<cleared>,dport=<cleared>),reply=(src=fd11::2,dst=fd11::b,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
+ ])
+-NS_CHECK_EXEC([foo1], [nc -z fd11::a 8081])
++NS_CHECK_EXEC([foo1], [ncat -z fd11::a 8081])
+
+ OVN_CLEANUP_CONTROLLER([hv1])
+
+@@ -595,7 +595,7 @@ AT_CLEANUP
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([SNAT in separate zone from DNAT])
+
+-AT_SKIP_IF([test $HAVE_NC = no])
++AT_SKIP_IF([test $HAVE_NCAT = no])
+ CHECK_CONNTRACK()
+ CHECK_CONNTRACK_NAT()
+ ovn_start
+@@ -663,7 +663,7 @@ wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+
+ # Create service that listens for TCP and UDP
+-NETNS_DAEMONIZE([vm2], [nc -l -k 1235], [nc0.pid])
++NETNS_DAEMONIZE([vm2], [ncat -l -k 1235], [nc0.pid])
+
+ test_icmp() {
+ # Make sure that a ping works as expected
+@@ -687,8 +687,8 @@ icmp,orig=(src=173.0.1.2,dst=172.16.0.102,id=<cleared>,type=8,code=0),reply=(src
+ }
+
+ test_udp() {
+- NETNS_DAEMONIZE([vm2], [nc -l -u 1234], [nc1.pid])
+- NS_CHECK_EXEC([vm1], [nc -u 30.0.0.1 1234 -p 1222 -z])
++ NETNS_DAEMONIZE([vm2], [ncat -l -u 1234], [nc1.pid])
++ NS_CHECK_EXEC([vm1], [ncat -u 30.0.0.1 1234 -p 1222 -z])
+ kill $(cat nc1.pid)
+
+ AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | \
+@@ -704,7 +704,7 @@ udp,orig=(src=173.0.1.2,dst=172.16.0.102,sport=<cleared>,dport=<cleared>),reply=
+ }
+
+ test_tcp() {
+- NS_CHECK_EXEC([vm1], [nc 30.0.0.1 1235 -z])
++ NS_CHECK_EXEC([vm1], [ncat 30.0.0.1 1235 -z])
+
+ AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | \
+ sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+@@ -891,7 +891,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([Load Balancer LS hairpin IPv4 UDP - larger than MTU])
+-AT_SKIP_IF([test $HAVE_NC = no])
++AT_SKIP_IF([test $HAVE_NCAT = no])
+ AT_KEYWORDS([lb])
+
+ ovn_start
+@@ -931,12 +931,12 @@ printf %8000s > datafile
+ printf %32000s > frag_test_srv.expected
+ printf %16000s > frag_test_client.expected
+
+-NETNS_DAEMONIZE([lsp], [nc -e /bin/cat -u -v -l 42.42.42.1 2021 -o udp_frag_test_srv.rcvd], [lsp-nc.pid])
++NETNS_DAEMONIZE([lsp], [ncat -e "$(command -v cat)" -u -v -l 42.42.42.1 2021 -o udp_frag_test_srv.rcvd], [lsp-nc.pid])
+
+ # Send 2 client requests with the same port so that the 2nd one
+ # is categorized as ct.est and not ct.new.
+-NS_CHECK_EXEC([lsp], [(cat datafile; sleep 3) | nc -u 88.88.88.88 4040 -p 20000 -o udp_frag_test_c1.recvd], [0], [ignore], [ignore])
+-NS_CHECK_EXEC([lsp], [(cat datafile; sleep 3) | nc -u 88.88.88.88 4040 -p 20000 -o udp_frag_test_c2.recvd], [0], [ignore], [ignore])
++NS_CHECK_EXEC([lsp], [(cat datafile; sleep 3) | ncat -u 88.88.88.88 4040 -p 20000 -o udp_frag_test_c1.recvd], [0], [ignore], [ignore])
++NS_CHECK_EXEC([lsp], [(cat datafile; sleep 3) | ncat -u 88.88.88.88 4040 -p 20000 -o udp_frag_test_c2.recvd], [0], [ignore], [ignore])
+
+ check cmp frag_test_srv.expected udp_frag_test_srv.rcvd
+ check cmp frag_test_client.expected udp_frag_test_c1.recvd
+@@ -954,7 +954,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([Load Balancer LS hairpin IPv6 UDP - larger than MTU])
+-AT_SKIP_IF([test $HAVE_NC = no])
++AT_SKIP_IF([test $HAVE_NCAT = no])
+ AT_KEYWORDS([lb])
+
+ ovn_start
+@@ -993,12 +993,12 @@ printf %8000s > datafile
+ printf %32000s > frag_test_srv.expected
+ printf %16000s > frag_test_client.expected
+
+-NETNS_DAEMONIZE([lsp], [nc -e /bin/cat -u -v -l 4200::1 2021 -o udp_frag_test_srv.rcvd], [lsp-nc.pid])
++NETNS_DAEMONIZE([lsp], [ncat -e "$(command -v cat)" -u -v -l 4200::1 2021 -o udp_frag_test_srv.rcvd], [lsp-nc.pid])
+
+ # Send 2 client requests with the same port so that the 2nd one
+ # is categorized as ct.est and not ct.new.
+-NS_CHECK_EXEC([lsp], [(cat datafile; sleep 3) | nc -6 -u 8800::0088 4040 -p 20000 -o udp_frag_test_c1.recvd], [0], [ignore], [ignore])
+-NS_CHECK_EXEC([lsp], [(cat datafile; sleep 3) | nc -6 -u 8800::0088 4040 -p 20000 -o udp_frag_test_c2.recvd], [0], [ignore], [ignore])
++NS_CHECK_EXEC([lsp], [(cat datafile; sleep 3) | ncat -6 -u 8800::0088 4040 -p 20000 -o udp_frag_test_c1.recvd], [0], [ignore], [ignore])
++NS_CHECK_EXEC([lsp], [(cat datafile; sleep 3) | ncat -6 -u 8800::0088 4040 -p 20000 -o udp_frag_test_c2.recvd], [0], [ignore], [ignore])
+
+ check cmp frag_test_srv.expected udp_frag_test_srv.rcvd
+ check cmp frag_test_client.expected udp_frag_test_c1.recvd
+@@ -1536,14 +1536,14 @@ NETNS_START_TCPDUMP([pod], [-vnne "ip or icmp"], [pod])
+ echo -e "Hello UDP\nHello UDP" > udp.expected
+ echo -e "Hello TCP\nHello TCP" > tcp.expected
+
+-NETNS_DAEMONIZE([pod], [nc -e /bin/cat -v -l -u -o server_udp.log 192.168.100.5 2323], [nc1.pid])
+-NETNS_DAEMONIZE([pod], [nc -e /bin/cat -v -l -o server_tcp.log 192.168.100.5 2324], [nc2.pid])
++NETNS_DAEMONIZE([pod], [ncat -e "$(command -v cat)" -v -l -u -o server_udp.log 192.168.100.5 2323], [nc1.pid])
++NETNS_DAEMONIZE([pod], [ncat -e "$(command -v cat)" -v -l -o server_tcp.log 192.168.100.5 2324], [nc2.pid])
+
+-NS_CHECK_EXEC([mgmt], [(echo "Hello UDP"; sleep 3) | nc -u -s 172.16.100.2 -o client_udp.log 172.16.0.5 5656], [0], [ignore], [ignore])
++NS_CHECK_EXEC([mgmt], [(echo "Hello UDP"; sleep 3) | ncat -u -s 172.16.100.2 -o client_udp.log 172.16.0.5 5656], [0], [ignore], [ignore])
+ check cmp server_udp.log udp.expected
+ check cmp client_udp.log udp.expected
+
+-NS_CHECK_EXEC([mgmt], [(echo "Hello TCP"; sleep 3) | nc -s 172.16.100.2 -o client_tcp.log 172.16.0.5 5657], [0], [ignore], [ignore])
++NS_CHECK_EXEC([mgmt], [(echo "Hello TCP"; sleep 3) | ncat -s 172.16.100.2 -o client_tcp.log 172.16.0.5 5657], [0], [ignore], [ignore])
+ check cmp server_tcp.log tcp.expected
+ check cmp client_tcp.log tcp.expected
+
+@@ -1559,7 +1559,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([load balancing in gateway router - ensure hw-offload with LB and SNAT])
+-AT_SKIP_IF([test $HAVE_NC = no])
++AT_SKIP_IF([test $HAVE_NCAT = no])
+ AT_KEYWORDS([lb])
+
+ ovn_start
+@@ -1604,7 +1604,7 @@ ADD_NAMESPACES(vm2)
+ ADD_VETH(vm2, vm2, br-int, "42.42.42.1/24", "00:00:00:00:00:02", "42.42.42.2")
+
+ dnl Start a server on vm1.
+-NETNS_DAEMONIZE([vm1], [nc -l -k 41.41.41.1 8080], [vm1.pid])
++NETNS_DAEMONIZE([vm1], [ncat -l -k 41.41.41.1 8080], [vm1.pid])
+
+ dnl Wait for ovn-controller to catch up.
+ wait_for_ports_up
+@@ -1612,7 +1612,7 @@ check ovn-nbctl --wait=hv sync
+
+ ovs-appctl revalidator/pause
+ on_exit 'ovs-appctl revalidator/resume'
+-NS_CHECK_EXEC([vm2], [nc -z 42.42.42.2 8080], 0, [ignore], [ignore])
++NS_CHECK_EXEC([vm2], [ncat -z 42.42.42.2 8080], 0, [ignore], [ignore])
+
+ dnl For each DP flow with ct action, make sure a CT entry exists as established
+ dnl for the zone the ct action is taken. Otherwise it means HW offload is
+diff --git a/tests/system-ovn.at b/tests/system-ovn.at
+index ccae652c2d..3d55795636 100644
+--- a/tests/system-ovn.at
++++ b/tests/system-ovn.at
+@@ -275,13 +275,13 @@ check ovn-nbctl --wait=hv sync
+ OVS_WAIT_UNTIL([ovs-ofctl dump-flows br-int | grep 'nat(src=fd30::1)'])
+
+ # 'alice1' should be able to ping 'foo1' directly.
+-NS_CHECK_EXEC([alice1], [ping6 -q -c 3 -i 0.3 -w 2 fd11::2 | FORMAT_PING], \
++NS_CHECK_EXEC([alice1], [ping -6 -q -c 3 -i 0.3 -w 2 fd11::2 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+
+ # North-South DNAT: 'alice1' should also be able to ping 'foo1' via fd30::2
+-NS_CHECK_EXEC([alice1], [ping6 -q -c 3 -i 0.3 -w 2 fd30::2 | FORMAT_PING], \
++NS_CHECK_EXEC([alice1], [ping -6 -q -c 3 -i 0.3 -w 2 fd30::2 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+@@ -295,7 +295,7 @@ icmpv6,orig=(src=fd21::2,dst=fd30::2,id=<cleared>,type=128,code=0),reply=(src=fd
+
+ # South-North SNAT: 'bar1' pings 'alice1'. But 'alice1' receives traffic
+ # from fd30::1
+-NS_CHECK_EXEC([bar1], [ping6 -q -c 3 -i 0.3 -w 2 fd21::2 | FORMAT_PING], \
++NS_CHECK_EXEC([bar1], [ping -6 -q -c 3 -i 0.3 -w 2 fd21::2 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+@@ -316,7 +316,7 @@ check ovn-nbctl --wait=hv sync
+ AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+
+ # East-west DNAT and SNAT: 'bar1' pings fd30::2. 'foo1' receives it.
+-NS_CHECK_EXEC([bar1], [ping6 -q -c 3 -i 0.3 -w 2 fd30::2 | FORMAT_PING], \
++NS_CHECK_EXEC([bar1], [ping -6 -q -c 3 -i 0.3 -w 2 fd30::2 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+@@ -918,7 +918,7 @@ check ovn-nbctl --wait=hv sync
+ OVS_WAIT_UNTIL([ovs-ofctl dump-flows br-int | grep 'nat(src=fd40::4)'])
+
+ # North-South DNAT: 'alice1' should be able to ping 'foo1' via fd30::2
+-NS_CHECK_EXEC([alice1], [ping6 -q -c 3 -i 0.3 -w 2 fd40::2 | FORMAT_PING], \
++NS_CHECK_EXEC([alice1], [ping -6 -q -c 3 -i 0.3 -w 2 fd40::2 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+@@ -936,7 +936,7 @@ icmpv6,orig=(src=fd30::3,dst=fd11::2,id=<cleared>,type=128,code=0),reply=(src=fd
+ ])
+
+ # North-South DNAT: 'bob1' should be able to ping 'foo1' via fd40::3
+-NS_CHECK_EXEC([bob1], [ping6 -q -c 3 -i 0.3 -w 2 fd40::3 | FORMAT_PING], \
++NS_CHECK_EXEC([bob1], [ping -6 -q -c 3 -i 0.3 -w 2 fd40::3 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+@@ -955,7 +955,7 @@ icmpv6,orig=(src=fd30::4,dst=fd11::2,id=<cleared>,type=128,code=0),reply=(src=fd
+
+ # South-North SNAT: 'bar1' pings 'bob1'. But 'bob1' receives traffic
+ # from fd40::4
+-NS_CHECK_EXEC([bar1], [ping6 -q -c 3 -i 0.3 -w 2 fd30::4 | FORMAT_PING], \
++NS_CHECK_EXEC([bar1], [ping -6 -q -c 3 -i 0.3 -w 2 fd30::4 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+@@ -968,7 +968,7 @@ icmpv6,orig=(src=fd12::2,dst=fd30::4,id=<cleared>,type=128,code=0),reply=(src=fd
+
+ # South-North SNAT: 'foo1' pings 'alice1'. But 'alice1' receives traffic
+ # from fd40::1
+-NS_CHECK_EXEC([foo1], [ping6 -q -c 3 -i 0.3 -w 2 fd30::3 | FORMAT_PING], \
++NS_CHECK_EXEC([foo1], [ping -6 -q -c 3 -i 0.3 -w 2 fd30::3 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+@@ -1179,7 +1179,7 @@ NS_CHECK_EXEC([alice1], [ping -q -c 3 -i 0.3 -w 2 30.0.0.2 | FORMAT_PING], \
+ ])
+
+ # North-South DNAT: 'alice16' should be able to ping 'foo16' via fd30::2
+-NS_CHECK_EXEC([alice16], [ping6 -q -c 3 -i 0.3 -w 2 fd40::2 | FORMAT_PING], \
++NS_CHECK_EXEC([alice16], [ping -6 -q -c 3 -i 0.3 -w 2 fd40::2 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+@@ -1212,7 +1212,7 @@ NS_CHECK_EXEC([bob1], [ping -q -c 3 -i 0.3 -w 2 30.0.0.3 | FORMAT_PING], \
+ ])
+
+ # North-South DNAT: 'bob16' should be able to ping 'foo16' via fd40::3
+-NS_CHECK_EXEC([bob16], [ping6 -q -c 3 -i 0.3 -w 2 fd40::3 | FORMAT_PING], \
++NS_CHECK_EXEC([bob16], [ping -6 -q -c 3 -i 0.3 -w 2 fd40::3 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+@@ -1247,7 +1247,7 @@ NS_CHECK_EXEC([bar1], [ping -q -c 3 -i 0.3 -w 2 172.16.1.4 | FORMAT_PING], \
+ ])
+ # South-North SNAT: 'bar16' pings 'bob16'. But 'bob16' receives traffic
+ # from fd40::4
+-NS_CHECK_EXEC([bar16], [ping6 -q -c 3 -i 0.3 -w 2 fd30::4 | FORMAT_PING], \
++NS_CHECK_EXEC([bar16], [ping -6 -q -c 3 -i 0.3 -w 2 fd30::4 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+@@ -1271,7 +1271,7 @@ NS_CHECK_EXEC([foo1], [ping -q -c 3 -i 0.3 -w 2 172.16.1.3 | FORMAT_PING], \
+
+ # South-North SNAT: 'foo16' pings 'alice16'. But 'alice16' receives traffic
+ # from fd40::1
+-NS_CHECK_EXEC([foo16], [ping6 -q -c 3 -i 0.3 -w 2 fd30::3 | FORMAT_PING], \
++NS_CHECK_EXEC([foo16], [ping -6 -q -c 3 -i 0.3 -w 2 fd30::3 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+@@ -1400,7 +1400,7 @@ check ovn-nbctl --wait=hv sync
+
+ OVS_WAIT_FOR_OUTPUT([
+ for i in `seq 1 20`; do
+- ip netns exec foo1 wget 30.0.0.1 -t 5 -T 1 --retry-connrefused -v -o wget$i.log;
++ ip netns exec foo1 wget http://30.0.0.1 -t 5 -T 1 --retry-connrefused -v -o wget$i.log;
+ done
+ ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | \
+ sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+@@ -1419,7 +1419,7 @@ dnl in 0.3% of cases, so run a few times.
+
+ OVS_WAIT_FOR_OUTPUT([
+ for i in `seq 1 20`; do
+- ip netns exec foo1 wget 30.0.0.1 -t 5 -T 1 --retry-connrefused -v -o wget$i.log;
++ ip netns exec foo1 wget http://30.0.0.1 -t 5 -T 1 --retry-connrefused -v -o wget$i.log;
+ done
+ ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | \
+ sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+@@ -1432,7 +1432,7 @@ dnl Should work with the virtual IP 30.0.0.3 address through NAT
+ dnl Each server should have at least one connection.
+ OVS_WAIT_FOR_OUTPUT([
+ for i in `seq 1 20`; do
+- ip netns exec foo1 wget 30.0.0.3 -t 5 -T 1 --retry-connrefused -v -o wget$i.log;
++ ip netns exec foo1 wget http://30.0.0.3 -t 5 -T 1 --retry-connrefused -v -o wget$i.log;
+ done
+ ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.3) | \
+ sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+@@ -1445,7 +1445,7 @@ dnl Test load-balancing that includes L4 ports in NAT.
+ dnl Each server should have at least one connection.
+ OVS_WAIT_FOR_OUTPUT([
+ for i in `seq 1 20`; do
+- ip netns exec foo1 wget 30.0.0.2:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log;
++ ip netns exec foo1 wget http://30.0.0.2:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log;
+ done
+ ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.2) | \
+ sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+@@ -1467,7 +1467,7 @@ dnl Test load-balancing that includes L4 ports in NAT.
+ dnl Each server should have at least one connection.
+ OVS_WAIT_FOR_OUTPUT([
+ for i in `seq 1 20`; do
+- ip netns exec foo1 wget 30.0.0.2:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log;
++ ip netns exec foo1 wget http://30.0.0.2:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log;
+ done
+ ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.2) | \
+ sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+@@ -1481,7 +1481,7 @@ AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+ echo "foo" > foo
+ for i in `seq 1 20`; do
+ echo Request $i
+- ip netns exec foo1 nc -p 30000 30.0.0.2 8000 < foo
++ ip netns exec foo1 ncat -p 30000 30.0.0.2 8000 < foo
+ done
+
+ dnl Only one backend should be chosen.
+@@ -1496,7 +1496,7 @@ OVS_WAIT_UNTIL([
+ AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+ for i in `seq 1 20`; do
+ echo Request $i
+- ip netns exec foo1 nc 30.0.0.2 8000 < foo
++ ip netns exec foo1 ncat 30.0.0.2 8000 < foo
+ done
+
+ dnl Only one backend should be chosen as eth_src and ip_src is fixed.
+@@ -1726,7 +1726,7 @@ AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+ echo "foo" > foo
+ for i in `seq 1 20`; do
+ echo Request $i
+- ip netns exec foo1 nc -6 -p 30000 fd03::2 8000 < foo
++ ip netns exec foo1 ncat -6 -p 30000 fd03::2 8000 < foo
+ done
+
+ # Only one backend should be chosen. Since the source port is fixed,
+@@ -1742,7 +1742,7 @@ OVS_WAIT_UNTIL([
+ AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+ for i in `seq 1 20`; do
+ echo Request $i
+- ip netns exec foo1 nc -6 fd03::2 8000 < foo
++ ip netns exec foo1 ncat -6 fd03::2 8000 < foo
+ done
+
+ dnl Only one backend should be chosen as eth_src and ip_src is fixed.
+@@ -1894,7 +1894,7 @@ dnl Test load-balancing that includes L4 ports in NAT.
+ dnl Each server should have at least one connection.
+ OVS_WAIT_FOR_OUTPUT([
+ for i in $(seq 1 20); do
+- NS_EXEC([foo1], [wget 30.0.0.1:8080 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
++ NS_EXEC([foo1], [wget http://30.0.0.1:8080 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
+ done
+ ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | \
+ sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+@@ -1929,7 +1929,7 @@ AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+ # Each server should have at least one connection.
+ OVS_WAIT_FOR_OUTPUT([
+ for i in $(seq 1 20); do
+- NS_EXEC([foo1], [wget 30.0.0.1:8080 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
++ NS_EXEC([foo1], [wget http://30.0.0.1:8080 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
+ done
+ ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | \
+ sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+@@ -1956,7 +1956,7 @@ AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+ echo "foo" > foo
+ for i in $(seq 1 20); do
+ echo Request $i
+- NS_EXEC([foo1], [nc -p 30000 30.0.0.1 8080 < foo])
++ NS_EXEC([foo1], [ncat -p 30000 30.0.0.1 8080 < foo])
+ done
+
+ # Only one backend should be chosen.
+@@ -1968,7 +1968,7 @@ tcp,orig=(src=192.168.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(s
+
+ for i in $(seq 1 20); do
+ echo Request $i
+- NS_EXEC([foo1], [nc -6 -p 30000 fd03::2 8090 < foo])
++ NS_EXEC([foo1], [ncat -6 -p 30000 fd03::2 8090 < foo])
+ done
+
+ # Only one backend should be chosen.
+@@ -1993,7 +1993,7 @@ done
+
+ AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+ for p in $(seq 3 20); do
+- NS_EXEC([foo1], [nc -s 192.168.1.$p 30.0.0.1 8080 < foo])
++ NS_EXEC([foo1], [ncat -s 192.168.1.$p 30.0.0.1 8080 < foo])
+ done
+
+ bar1_ct=$(ovs-appctl dpctl/dump-conntrack | grep 172.16.1.2 -c)
+@@ -2006,7 +2006,7 @@ AT_CHECK([test $bar3_ct -gt 0])
+
+ for p in $(seq 3 20); do
+ h=$(printf '%02x' $p)
+- NS_EXEC([foo1], [nc -6 -s fd01::$h fd03::2 8090 < foo])
++ NS_EXEC([foo1], [ncat -6 -s fd01::$h fd03::2 8090 < foo])
+ done
+
+ bar1_ct=$(ovs-appctl dpctl/dump-conntrack | grep fd02::2 -c)
+@@ -2093,7 +2093,7 @@ OVS_START_L7([foo4], [http])
+ dnl Should work with the virtual IP address through NAT
+ OVS_WAIT_FOR_OUTPUT([
+ for i in `seq 1 10`; do
+- NS_EXEC([foo1], [wget 30.0.0.1 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
++ NS_EXEC([foo1], [wget http://30.0.0.1 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
+ done
+
+ dnl Each server should have at least one connection.
+@@ -2107,7 +2107,7 @@ tcp,orig=(src=192.168.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(s
+ dnl Test load-balancing that includes L4 ports in NAT.
+ OVS_WAIT_FOR_OUTPUT([
+ for i in `seq 1 10`; do
+- NS_EXEC([foo1], [wget 30.0.0.2:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
++ NS_EXEC([foo1], [wget http://30.0.0.2:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
+ done
+
+ dnl Each server should have at least one connection.
+@@ -2347,7 +2347,7 @@ check ovs-appctl dpctl/flush-conntrack
+ dnl Should work with the virtual IP address through NAT
+ OVS_WAIT_FOR_OUTPUT([
+ for i in `seq 1 10`; do
+- NS_EXEC([alice1], [wget 30.0.0.1 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
++ NS_EXEC([alice1], [wget http://30.0.0.1 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
+ done
+
+ dnl Each server should have at least one connection.
+@@ -2361,7 +2361,7 @@ check ovs-appctl dpctl/flush-conntrack
+ OVS_WAIT_FOR_OUTPUT([
+ dnl Test load-balancing that includes L4 ports in NAT.
+ for i in `seq 1 10`; do
+- NS_EXEC([alice1], [wget 30.0.0.2:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
++ NS_EXEC([alice1], [wget http://30.0.0.2:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
+ done
+
+ dnl Each server should have at least one connection.
+@@ -2410,7 +2410,7 @@ tcp,orig=(src=172.16.1.2,dst=192.168.2.2,sport=<cleared>,dport=<cleared>),reply=
+ dnl Test load-balancing that includes L4 ports in NAT.
+ OVS_WAIT_FOR_OUTPUT_UNQUOTED([
+ for i in `seq 1 10`; do
+- NS_EXEC([alice1], [wget 30.0.0.2:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
++ NS_EXEC([alice1], [wget http://30.0.0.2:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
+ done
+
+ dnl Each server should have at least one connection.
+@@ -2451,7 +2451,7 @@ tcp,orig=(src=172.16.1.2,dst=192.168.2.2,sport=<cleared>,dport=<cleared>),reply=
+
+ OVS_WAIT_FOR_OUTPUT_UNQUOTED([
+ for i in `seq 1 10`; do
+- NS_EXEC([alice1], [wget 30.0.0.2:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
++ NS_EXEC([alice1], [wget http://30.0.0.2:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
+ done
+
+ dnl Each server should have at least one connection.
+@@ -2536,7 +2536,7 @@ check ovn-nbctl --wait=hv sync
+
+ for i in $(seq 1 5); do
+ echo Request $i
+- NS_CHECK_EXEC([client], [wget 172.16.1.100 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
++ NS_CHECK_EXEC([client], [wget http://172.16.1.100 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
+ done
+
+ # Now send the traffic from client to the VIP - 172.16.1.150
+@@ -2545,7 +2545,7 @@ check ovn-nbctl --wait=hv sync
+
+ for i in $(seq 1 5); do
+ echo Request $i
+- NS_CHECK_EXEC([client], [wget 172.16.1.150 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
++ NS_CHECK_EXEC([client], [wget http://172.16.1.150 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
+ done
+
+ OVN_CLEANUP_CONTROLLER([hv1])
+@@ -2851,7 +2851,7 @@ tcp,orig=(src=172.16.1.3,dst=192.168.2.2,sport=<cleared>,dport=<cleared>),reply=
+
+ OVS_WAIT_FOR_OUTPUT_UNQUOTED([
+ for i in `seq 1 10`; do
+- NS_EXEC([alice1], [wget 30.0.0.1 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
++ NS_EXEC([alice1], [wget http://30.0.0.1 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
+ done
+
+ dnl Each server should have at least one connection.
+@@ -3227,7 +3227,7 @@ tcp,orig=(src=fd72::3,dst=fd12::2,sport=<cleared>,dport=<cleared>),reply=(src=fd
+
+ OVS_WAIT_FOR_OUTPUT_UNQUOTED([
+ for i in `seq 1 10`; do
+- NS_EXEC([alice1], [wget 30.0.0.1 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
++ NS_EXEC([alice1], [wget http://30.0.0.1 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
+ NS_EXEC([alice16], [wget http://[[fd30::1]] -t 5 -T 1 --retry-connrefused -v -o wget${i}_6.log])
+ done
+
+@@ -3355,7 +3355,7 @@ OVS_START_L7([bar1], [http])
+ dnl Should work with the virtual IP address through NAT
+ OVS_WAIT_FOR_OUTPUT_UNQUOTED([
+ for i in `seq 1 10`; do
+- NS_EXEC([alice1], [wget 172.16.1.10 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
++ NS_EXEC([alice1], [wget http://172.16.1.10 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
+ done
+
+ dnl Each server should have at least one connection.
+@@ -3368,7 +3368,7 @@ tcp,orig=(src=172.16.1.2,dst=172.16.1.10,sport=<cleared>,dport=<cleared>),reply=
+ dnl Test load-balancing that includes L4 ports in NAT.
+ OVS_WAIT_FOR_OUTPUT_UNQUOTED([
+ for i in `seq 1 10`; do
+- NS_EXEC([alice1], [wget 172.16.1.11:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
++ NS_EXEC([alice1], [wget http://172.16.1.11:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
+ done
+
+ dnl Each server should have at least one connection.
+@@ -4574,7 +4574,7 @@ udp2_pid=$(cat udp2.pid)
+
+ wait_row_count Service_Monitor 2 status=online
+
+-NETNS_DAEMONIZE(sw-ext-p1, [nc -4 -u 172.16.0.42 53 -d 0.1 -c 'for i in $(seq 1 5); do echo "udp_data $i"; read msg; echo "$msg" >>./udp_data_1; done'], [udp1_client.pid])
++NETNS_DAEMONIZE(sw-ext-p1, [ncat -4 -u 172.16.0.42 53 -d 0.1 -c 'for i in $(seq 1 5); do echo "udp_data $i"; read msg; echo "$msg" >>./udp_data_1; done'], [udp1_client.pid])
+
+ OVS_WAIT_FOR_OUTPUT([cat ./udp_data_1], [0], [dnl
+ udp_data 1
+@@ -4585,14 +4585,14 @@ udp_data 5
+ ])
+
+ # Stop UDP service in sw0-p1.
+-# Due to the way UDP nc with "-k" forks, we need to kill all
++# Due to the way UDP Ncat with "-k" forks, we need to kill all
+ # children processes and then then the parent.
+ NS_CHECK_EXEC([sw0-p1], [pkill -P $udp1_pid && kill $udp1_pid])
+
+ wait_row_count Service_Monitor 1 logical_port=sw0-p1 status=offline
+ wait_row_count Service_Monitor 1 logical_port=sw0-p2 status=online
+
+-NETNS_DAEMONIZE(sw-ext-p1, [nc -4 -u 172.16.0.42 53 -d 0.1 -c 'for i in $(seq 1 5); do echo "udp_data $i"; read msg; echo "$msg" >>./udp_data_2; done'], [udp2_client.pid])
++NETNS_DAEMONIZE(sw-ext-p1, [ncat -4 -u 172.16.0.42 53 -d 0.1 -c 'for i in $(seq 1 5); do echo "udp_data $i"; read msg; echo "$msg" >>./udp_data_2; done'], [udp2_client.pid])
+
+ OVS_WAIT_FOR_OUTPUT([cat ./udp_data_2], [0], [dnl
+ udp_data 1
+@@ -4766,7 +4766,7 @@ udp2_pid=$(cat udp2.pid)
+
+ wait_row_count Service_Monitor 2 status=online
+
+-NETNS_DAEMONIZE(sw-ext-p1, [nc -6 -u fd20::42 53 -d 0.1 -c 'for i in $(seq 1 5); do echo "udp_data $i"; read msg; echo "$msg" >>./udp_data_1; done'], [udp1_client.pid])
++NETNS_DAEMONIZE(sw-ext-p1, [ncat -6 -u fd20::42 53 -d 0.1 -c 'for i in $(seq 1 5); do echo "udp_data $i"; read msg; echo "$msg" >>./udp_data_1; done'], [udp1_client.pid])
+
+ OVS_WAIT_FOR_OUTPUT([cat ./udp_data_1], [0], [dnl
+ udp_data 1
+@@ -4777,14 +4777,14 @@ udp_data 5
+ ])
+
+ # Stop UDP service in sw0-p1.
+-# Due to the way UDP nc with "-k" forks, we need to kill all
++# Due to the way UDP Ncat with "-k" forks, we need to kill all
+ # children processes and then then the parent.
+ NS_CHECK_EXEC([sw0-p1], [pkill -P $udp1_pid && kill $udp1_pid])
+
+ wait_row_count Service_Monitor 1 logical_port=sw0-p1 status=offline
+ wait_row_count Service_Monitor 1 logical_port=sw0-p2 status=online
+
+-NETNS_DAEMONIZE(sw-ext-p1, [nc -6 -u fd20::42 53 -d 0.1 -c 'for i in $(seq 1 5); do echo "udp_data $i"; read msg; echo "$msg" >>./udp_data_2; done'], [udp2_client.pid])
++NETNS_DAEMONIZE(sw-ext-p1, [ncat -6 -u fd20::42 53 -d 0.1 -c 'for i in $(seq 1 5); do echo "udp_data $i"; read msg; echo "$msg" >>./udp_data_2; done'], [udp2_client.pid])
+
+ OVS_WAIT_FOR_OUTPUT([cat ./udp_data_2], [0], [dnl
+ udp_data 1
+@@ -4921,7 +4921,7 @@ OVS_WAIT_UNTIL(
+ #dnl Each server should have at least one connection.
+ OVS_WAIT_FOR_OUTPUT([
+ for i in `seq 1 10`; do
+- NS_EXEC([sw0-p2], [wget 10.0.0.10 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
++ NS_EXEC([sw0-p2], [wget http://10.0.0.10 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
+ done
+
+ ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.0.0.10) | \
+@@ -4946,7 +4946,7 @@ ovs-appctl dpctl/flush-conntrack
+ # From sw0-p2 send traffic to vip - 10.0.0.10
+ for i in `seq 1 20`; do
+ echo Request $i
+- NS_CHECK_EXEC([sw0-p2], [wget 10.0.0.10 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
++ NS_CHECK_EXEC([sw0-p2], [wget http://10.0.0.10 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
+ done
+
+ AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.0.0.10) | \
+@@ -5203,7 +5203,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([Load Balancer LS hairpin IPv4])
+-AT_SKIP_IF([test $HAVE_NC = no])
++AT_SKIP_IF([test $HAVE_NCAT = no])
+ AT_SKIP_IF([test $HAVE_TCPDUMP = no])
+ AT_KEYWORDS([lb])
+
+@@ -5249,21 +5249,21 @@ ADD_VETH(lsp, lsp, br-int, "42.42.42.1/24", "00:00:00:00:00:01", \
+ check ovn-nbctl --wait=hv -t 3 sync
+
+ # Start IPv4 TCP server on lsp.
+-NETNS_DAEMONIZE([lsp], [nc -l -k 42.42.42.1 4041], [lsp0.pid])
++NETNS_DAEMONIZE([lsp], [ncat -l -k 42.42.42.1 4041], [lsp0.pid])
+
+ # Check that IPv4 TCP hairpin connection succeeds on both VIPs.
+-NS_CHECK_EXEC([lsp], [nc 88.88.88.88 8080 -z], [0], [ignore], [ignore])
+-NS_CHECK_EXEC([lsp], [nc 88.88.88.89 8080 -z], [0], [ignore], [ignore])
+-NS_CHECK_EXEC([lsp], [nc 88.88.88.90 4041 -z], [0], [ignore], [ignore])
++NS_CHECK_EXEC([lsp], [ncat 88.88.88.88 8080 -z], [0], [ignore], [ignore])
++NS_CHECK_EXEC([lsp], [ncat 88.88.88.89 8080 -z], [0], [ignore], [ignore])
++NS_CHECK_EXEC([lsp], [ncat 88.88.88.90 4041 -z], [0], [ignore], [ignore])
+
+ # Capture IPv4 UDP hairpinned packets.
+ filter="dst 42.42.42.1 and dst port 2021 and udp"
+ NETNS_START_TCPDUMP([lsp], [-nn -c 3 -i lsp ${filter}], [lsp])
+
+ # Generate IPv4 UDP hairpin traffic.
+-NS_CHECK_EXEC([lsp], [echo a | nc -u 88.88.88.88 4040], [ignore], [ignore], [ignore])
+-NS_CHECK_EXEC([lsp], [echo a | nc -u 88.88.88.89 4040], [ignore], [ignore], [ignore])
+-NS_CHECK_EXEC([lsp], [echo a | nc -u 88.88.88.90 2021], [ignore], [ignore], [ignore])
++NS_CHECK_EXEC([lsp], [echo a | ncat -u 88.88.88.88 4040], [ignore], [ignore], [ignore])
++NS_CHECK_EXEC([lsp], [echo a | ncat -u 88.88.88.89 4040], [ignore], [ignore], [ignore])
++NS_CHECK_EXEC([lsp], [echo a | ncat -u 88.88.88.90 2021], [ignore], [ignore], [ignore])
+
+ # Check hairpin traffic.
+ OVS_WAIT_UNTIL([
+@@ -5278,8 +5278,8 @@ check ovn-nbctl --apply-after-lb acl-add pg0 from-lport 1002 "ip4 && udp" allow
+ check ovn-nbctl --wait=hv sync
+
+ ## Check that IPv4 TCP hairpin connection succeeds on both VIPs.
+-NS_CHECK_EXEC([lsp], [nc 88.88.88.88 8080 -z], [0], [ignore], [ignore])
+-NS_CHECK_EXEC([lsp], [nc 88.88.88.89 8080 -z], [0], [ignore], [ignore])
++NS_CHECK_EXEC([lsp], [ncat 88.88.88.88 8080 -z], [0], [ignore], [ignore])
++NS_CHECK_EXEC([lsp], [ncat 88.88.88.89 8080 -z], [0], [ignore], [ignore])
+
+ OVN_CLEANUP_CONTROLLER([hv1])
+
+@@ -5293,7 +5293,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([enable-stateless-acl-with-lb usage])
+-AT_SKIP_IF([test $HAVE_NC = no])
++AT_SKIP_IF([test $HAVE_NCAT = no])
+
+ CHECK_CONNTRACK()
+ ovn_start
+@@ -5396,19 +5396,19 @@ zone_lsp2_id=$(ovn-appctl -t ovn-controller ct-zone-list | grep lsp2 | cut -d '
+ AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+
+ # Start IPv4 TCP and UDP server on lsp1.
+-NETNS_DAEMONIZE([lsp1], [nc -l -k 192.168.0.101 4041], [lsp1_tcp.pid])
+-NETNS_DAEMONIZE([lsp1], [nc -u -l 192.168.0.101 4042], [lsp1_udp.pid])
++NETNS_DAEMONIZE([lsp1], [ncat -l -k 192.168.0.101 4041], [lsp1_tcp.pid])
++NETNS_DAEMONIZE([lsp1], [ncat -u -l 192.168.0.101 4042], [lsp1_udp.pid])
+
+ # Create another server without load balancer to check that it
+ # does not create conntrack records.
+-NETNS_DAEMONIZE([lsp1], [nc -l -k 192.168.0.101 4043], [lsp1_non_lb.pid])
++NETNS_DAEMONIZE([lsp1], [ncat -l -k 192.168.0.101 4043], [lsp1_non_lb.pid])
+
+ # Send the packet to VIP from private network.
+ NETNS_START_TCPDUMP([lsp1], [-nnnei lsp1], [lsp1])
+-NS_CHECK_EXEC([lsp1], [nc -z 192.168.0.1 8080], [0], [ignore], [ignore])
++NS_CHECK_EXEC([lsp1], [ncat -z 192.168.0.1 8080], [0], [ignore], [ignore])
+
+ # Udp connections
+-NS_CHECK_EXEC([lsp1], [echo a | nc -u 192.168.0.1 8081], [ignore], [ignore], [ignore])
++NS_CHECK_EXEC([lsp1], [echo a | ncat -u 192.168.0.1 8081], [ignore], [ignore], [ignore])
+
+ # Check conntrack zone of lsp1 has tcp entry for lb
+ AT_CHECK([ovs-appctl dpctl/dump-conntrack zone=$zone_lsp1_id | \
+@@ -5421,7 +5421,7 @@ udp,orig=(src=192.168.0.101,dst=192.168.0.1,sport=<cleared>,dport=<cleared>),rep
+ AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+
+ # Do the same sending a packet.
+-NS_CHECK_EXEC([lsp1], [echo a | nc 192.168.0.1 8080], [0], [ignore], [ignore])
++NS_CHECK_EXEC([lsp1], [echo a | ncat 192.168.0.1 8080], [0], [ignore], [ignore])
+
+ # Check conntrack zone of lsp1 has tcp entry for lb
+ AT_CHECK([ovs-appctl dpctl/dump-conntrack zone=$zone_lsp1_id | \
+@@ -5432,7 +5432,7 @@ tcp,orig=(src=192.168.0.101,dst=192.168.0.1,sport=<cleared>,dport=<cleared>),rep
+ AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+
+ # Check that internal traffic not related to lb doesn't create conntrack records
+-NS_CHECK_EXEC([external], [nc -z 192.168.0.101 4043], [0], [])
++NS_CHECK_EXEC([external], [ncat -z 192.168.0.101 4043], [0], [])
+
+ AT_CHECK([ovs-appctl dpctl/dump-conntrack zone=$zone_lsp1_id | FORMAT_CT(192.168.0.101) | sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl])
+
+@@ -5448,7 +5448,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([Load Balancer LS hairpin IPv6])
+-AT_SKIP_IF([test $HAVE_NC = no])
++AT_SKIP_IF([test $HAVE_NCAT = no])
+ AT_KEYWORDS([lb])
+
+ ovn_start
+@@ -5491,21 +5491,21 @@ ADD_VETH(lsp, lsp, br-int, "4200::1/64", "00:00:00:00:00:01", "4200::00ff", "nod
+ check ovn-nbctl --wait=hv -t 3 sync
+
+ # Start IPv6 TCP server on lsp.
+-NETNS_DAEMONIZE([lsp], [nc -l -k 4200::1 4041], [lsp0.pid])
++NETNS_DAEMONIZE([lsp], [ncat -l -k 4200::1 4041], [lsp0.pid])
+
+ # Check that IPv6 TCP hairpin connection succeeds on both VIPs.
+-NS_CHECK_EXEC([lsp], [nc 8800::0088 8080 -z], [0], [ignore], [ignore])
+-NS_CHECK_EXEC([lsp], [nc 8800::0089 8080 -z], [0], [ignore], [ignore])
+-NS_CHECK_EXEC([lsp], [nc 8800::0090 4041 -z], [0], [ignore], [ignore])
++NS_CHECK_EXEC([lsp], [ncat 8800::0088 8080 -z], [0], [ignore], [ignore])
++NS_CHECK_EXEC([lsp], [ncat 8800::0089 8080 -z], [0], [ignore], [ignore])
++NS_CHECK_EXEC([lsp], [ncat 8800::0090 4041 -z], [0], [ignore], [ignore])
+
+ # Capture IPv6 UDP hairpinned packets.
+ filter="dst 4200::1 and dst port 2021 and udp"
+ NETNS_START_TCPDUMP([lsp], [-nn -c 3 -i lsp $filter], [lsp])
+
+ # Generate IPv6 UDP hairpin traffic.
+-NS_CHECK_EXEC([lsp], [echo a | nc -u 8800::0088 4040], [ignore], [ignore], [ignore])
+-NS_CHECK_EXEC([lsp], [echo a | nc -u 8800::0089 4040], [ignore], [ignore], [ignore])
+-NS_CHECK_EXEC([lsp], [echo a | nc -u 8800::0090 2021], [ignore], [ignore], [ignore])
++NS_CHECK_EXEC([lsp], [echo a | ncat -u 8800::0088 4040], [ignore], [ignore], [ignore])
++NS_CHECK_EXEC([lsp], [echo a | ncat -u 8800::0089 4040], [ignore], [ignore], [ignore])
++NS_CHECK_EXEC([lsp], [echo a | ncat -u 8800::0090 2021], [ignore], [ignore], [ignore])
+
+ # Check hairpin traffic.
+ OVS_WAIT_UNTIL([
+@@ -5520,8 +5520,8 @@ check ovn-nbctl --apply-after-lb acl-add pg0 from-lport 1000 "ip6" drop
+ check ovn-nbctl --wait=hv sync
+
+ # Check that IPv6 TCP hairpin connection succeeds on both VIPs.
+-NS_CHECK_EXEC([lsp], [nc 8800::0088 8080 -z], [0], [ignore], [ignore])
+-NS_CHECK_EXEC([lsp], [nc 8800::0089 8080 -z], [0], [ignore], [ignore])
++NS_CHECK_EXEC([lsp], [ncat 8800::0088 8080 -z], [0], [ignore], [ignore])
++NS_CHECK_EXEC([lsp], [ncat 8800::0089 8080 -z], [0], [ignore], [ignore])
+
+ OVN_CLEANUP_CONTROLLER([hv1])
+
+@@ -5535,7 +5535,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([ACL reject])
+-AT_SKIP_IF([test $HAVE_NC = no])
++AT_SKIP_IF([test $HAVE_NCAT = no])
+ AT_KEYWORDS([lb])
+
+ ovn_start
+@@ -5613,12 +5613,12 @@ ADD_VETH(sw1-p1-rej, sw1-p1-rej, br-int, "20.0.0.3/24", "40:54:00:00:00:03", \
+ NETNS_START_TCPDUMP([sw0-p1-rej], [-nn -i sw0-p1-rej tcp], [sw0-p1-rej-ip4])
+
+ OVS_WAIT_UNTIL([
+- ip netns exec sw0-p1-rej nc -vz 10.0.0.4 80 2>&1 | grep -i 'connection refused'
++ ip netns exec sw0-p1-rej ncat -vz 10.0.0.4 80 2>&1 | grep -i 'connection refused'
+ ])
+
+ # Now send traffic to port 84
+ OVS_WAIT_UNTIL([
+- ip netns exec sw0-p1-rej nc -vz 10.0.0.4 84 2>&1 | grep -i 'connection refused'
++ ip netns exec sw0-p1-rej ncat -vz 10.0.0.4 84 2>&1 | grep -i 'connection refused'
+ ])
+
+ OVS_WAIT_UNTIL([
+@@ -5636,7 +5636,7 @@ OVS_WAIT_UNTIL([
+ NETNS_START_TCPDUMP([sw0-p2-rej], [-nn -i sw0-p2-rej tcp port 80], [sw0-p2-rej-ip6])
+
+ OVS_WAIT_UNTIL([
+- ip netns exec sw0-p2-rej nc -vz6 aef0::3 80 2>&1 | grep -i 'connection refused'
++ ip netns exec sw0-p2-rej ncat -vz6 aef0::3 80 2>&1 | grep -i 'connection refused'
+ ])
+
+
+@@ -5650,7 +5650,7 @@ check ovn-nbctl acl-add sw1 to-lport 1004 "ip" allow-related
+ check ovn-nbctl --log acl-add pg0 to-lport 1004 "outport == @pg0 && ip && tcp && tcp.dst == 84" reject
+
+ OVS_WAIT_UNTIL([
+- ip netns exec sw1-p1-rej nc -vz 10.0.0.4 84 2>&1 | grep -i 'connection refused'
++ ip netns exec sw1-p1-rej ncat -vz 10.0.0.4 84 2>&1 | grep -i 'connection refused'
+ ])
+
+
+@@ -5660,7 +5660,7 @@ NETNS_START_TCPDUMP([sw0-p1-rej], [-nn -i sw0-p1-rej icmp], [sw0-p1-rej-icmp])
+
+ printf '.%.0s' {1..100} > foo
+ OVS_WAIT_UNTIL([
+- ip netns exec sw0-p1-rej nc -u 10.0.0.4 90 < foo
++ ip netns exec sw0-p1-rej ncat -u 10.0.0.4 90 < foo
+ c=$(cat sw0-p1-rej-icmp.tcpdump | grep \
+ "10.0.0.4 > 10.0.0.3: ICMP 10.0.0.4 udp port 90 unreachable" | uniq | wc -l)
+ test $c -ge 1
+@@ -5673,7 +5673,7 @@ NETNS_START_TCPDUMP([sw0-p1-rej], [-nn -i sw0-p1-rej udp port 94], [sw0-p1-rej-u
+ NETNS_START_TCPDUMP([sw0-p1-rej], [-nn -i sw0-p1-rej icmp], [sw0-p1-rej-icmp])
+
+ OVS_WAIT_UNTIL([
+- ip netns exec sw0-p1-rej nc -u 10.0.0.4 94 < foo
++ ip netns exec sw0-p1-rej ncat -u 10.0.0.4 94 < foo
+ c=$(cat sw0-p1-rej-icmp.tcpdump | grep \
+ "10.0.0.4 > 10.0.0.3: ICMP 10.0.0.4 udp port 94 unreachable" | uniq | wc -l)
+ test $c -ge 1
+@@ -5684,7 +5684,7 @@ NETNS_START_TCPDUMP([sw0-p2-rej], [-nn -i sw0-p2-rej udp port 90], [sw0-p2-rej-i
+ NETNS_START_TCPDUMP([sw0-p2-rej], [-nn -i sw0-p2-rej icmp6], [sw0-p2-rej-icmp6])
+
+ OVS_WAIT_UNTIL([
+- ip netns exec sw0-p2-rej nc -u -6 aef0::3 90 < foo
++ ip netns exec sw0-p2-rej ncat -u -6 aef0::3 90 < foo
+ c=$(cat sw0-p2-rej-icmp6.tcpdump | grep \
+ "IP6 aef0::3 > aef0::4: ICMP6, destination unreachable, unreachable port, \
+ aef0::3 udp port 90" | uniq | wc -l)
+@@ -5698,7 +5698,7 @@ NETNS_START_TCPDUMP([sw0-p2-rej], [-nn -i sw0-p2-rej udp port 94], [sw0-p2-rej-i
+ NETNS_START_TCPDUMP([sw0-p2-rej], [-nn -i sw0-p2-rej icmp6], [sw0-p2-rej-icmp6])
+
+ OVS_WAIT_UNTIL([
+- ip netns exec sw0-p2-rej nc -u -6 aef0::3 94 < foo
++ ip netns exec sw0-p2-rej ncat -u -6 aef0::3 94 < foo
+ c=$(cat sw0-p2-rej-icmp6.tcpdump | grep \
+ "IP6 aef0::3 > aef0::4: ICMP6, destination unreachable, unreachable port, \
+ aef0::3 udp port 94" | uniq | wc -l)
+@@ -5711,11 +5711,11 @@ check ovn-nbctl pg-add pg0 sw0-p1-rej sw0-p2-rej
+ check ovn-nbctl --log acl-add pg0 from-lport 1004 "inport == @pg0 && ip && (tcp || udp)" reject
+
+ OVS_WAIT_UNTIL([
+- ip netns exec sw0-p1-rej nc -vz 10.0.0.4 80 2>&1 | grep -i 'connection refused'
++ ip netns exec sw0-p1-rej ncat -vz 10.0.0.4 80 2>&1 | grep -i 'connection refused'
+ ])
+
+ OVS_WAIT_UNTIL([
+- ip netns exec sw0-p2-rej nc -vz6 aef0::3 80 2>&1 | grep -i 'connection refused'
++ ip netns exec sw0-p2-rej ncat -vz6 aef0::3 80 2>&1 | grep -i 'connection refused'
+ ])
+
+ rm -f *.tcpdump
+@@ -5724,7 +5724,7 @@ NETNS_START_TCPDUMP([sw0-p1-rej], [-nn -i sw0-p1-rej icmp], [sw0-p1-rej-icmp])
+
+ printf '.%.0s' {1..100} > foo
+ OVS_WAIT_UNTIL([
+- ip netns exec sw0-p1-rej nc -u 10.0.0.4 90 < foo
++ ip netns exec sw0-p1-rej ncat -u 10.0.0.4 90 < foo
+ c=$(cat sw0-p1-rej-icmp.tcpdump | grep \
+ "10.0.0.4 > 10.0.0.3: ICMP 10.0.0.4 udp port 90 unreachable" | uniq | wc -l)
+ test $c -ge 1
+@@ -5736,7 +5736,7 @@ rm -f *.tcpdump
+ NETNS_START_TCPDUMP([sw0-p2-rej], [-nn -i sw0-p2-rej icmp6], [sw0-p2-rej-icmp6])
+
+ OVS_WAIT_UNTIL([
+- ip netns exec sw0-p2-rej nc -u -6 aef0::3 90 < foo
++ ip netns exec sw0-p2-rej ncat -u -6 aef0::3 90 < foo
+ c=$(cat sw0-p2-rej-icmp6.tcpdump | grep \
+ "IP6 aef0::3 > aef0::4: ICMP6, destination unreachable, unreachable port, \
+ aef0::3 udp port 90" | uniq | wc -l)
+@@ -5756,7 +5756,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([ACL after lb - reject])
+-AT_SKIP_IF([test $HAVE_NC = no])
++AT_SKIP_IF([test $HAVE_NCAT = no])
+ AT_KEYWORDS([lb])
+
+ ovn_start
+@@ -5834,12 +5834,12 @@ ADD_VETH(sw1-p1-rej, sw1-p1-rej, br-int, "20.0.0.3/24", "40:54:00:00:00:03", \
+ NETNS_START_TCPDUMP([sw0-p1-rej], [-nn -i sw0-p1-rej tcp], [sw0-p1-rej-ip4])
+
+ OVS_WAIT_UNTIL([
+- ip netns exec sw0-p1-rej nc -vz 10.0.0.4 80 2>&1 | grep -i 'connection refused'
++ ip netns exec sw0-p1-rej ncat -vz 10.0.0.4 80 2>&1 | grep -i 'connection refused'
+ ])
+
+ # Now send traffic to port 84
+ OVS_WAIT_UNTIL([
+- ip netns exec sw0-p1-rej nc -vz 10.0.0.4 84 2>&1 | grep -i 'connection refused'
++ ip netns exec sw0-p1-rej ncat -vz 10.0.0.4 84 2>&1 | grep -i 'connection refused'
+ ])
+
+ OVS_WAIT_UNTIL([
+@@ -5857,7 +5857,7 @@ OVS_WAIT_UNTIL([
+ NETNS_START_TCPDUMP([sw0-p2-rej], [-nn -i sw0-p2-rej tcp port 80], [sw0-p2-rej-ip6])
+
+ OVS_WAIT_UNTIL([
+- ip netns exec sw0-p2-rej nc -vz6 aef0::3 80 2>&1 | grep -i 'connection refused'
++ ip netns exec sw0-p2-rej ncat -vz6 aef0::3 80 2>&1 | grep -i 'connection refused'
+ ])
+
+
+@@ -5874,7 +5874,7 @@ check ovn-nbctl acl-add sw1 to-lport 1004 "ip" allow-related
+ check ovn-nbctl --log acl-add pg0 to-lport 1004 "outport == @pg0 && ip && tcp && tcp.dst == 84" reject
+
+ OVS_WAIT_UNTIL([
+- ip netns exec sw1-p1-rej nc -vz 10.0.0.4 84 2>&1 | grep -i 'connection refused'
++ ip netns exec sw1-p1-rej ncat -vz 10.0.0.4 84 2>&1 | grep -i 'connection refused'
+ ])
+
+ # Now test for IPv4 UDP.
+@@ -5883,7 +5883,7 @@ NETNS_START_TCPDUMP([sw0-p1-rej], [-nn -i sw0-p1-rej icmp], [sw0-p1-rej-icmp])
+
+ printf '.%.0s' {1..100} > foo
+ OVS_WAIT_UNTIL([
+- ip netns exec sw0-p1-rej nc -u 10.0.0.4 90 < foo
++ ip netns exec sw0-p1-rej ncat -u 10.0.0.4 90 < foo
+ c=$(cat sw0-p1-rej-icmp.tcpdump | grep \
+ "10.0.0.4 > 10.0.0.3: ICMP 10.0.0.4 udp port 90 unreachable" | uniq | wc -l)
+ test $c -ge 1
+@@ -5896,7 +5896,7 @@ NETNS_START_TCPDUMP([sw0-p1-rej], [-nn -i sw0-p1-rej udp port 94], [sw0-p1-rej-u
+ NETNS_START_TCPDUMP([sw0-p1-rej], [-nn -i sw0-p1-rej icmp], [sw0-p1-rej-icmp])
+
+ OVS_WAIT_UNTIL([
+- ip netns exec sw0-p1-rej nc -u 10.0.0.4 94 < foo
++ ip netns exec sw0-p1-rej ncat -u 10.0.0.4 94 < foo
+ c=$(cat sw0-p1-rej-icmp.tcpdump | grep \
+ "10.0.0.4 > 10.0.0.3: ICMP 10.0.0.4 udp port 94 unreachable" | uniq | wc -l)
+ test $c -ge 1
+@@ -5907,7 +5907,7 @@ NETNS_START_TCPDUMP([sw0-p2-rej], [-nn -i sw0-p2-rej udp port 90], [sw0-p2-rej-i
+ NETNS_START_TCPDUMP([sw0-p2-rej], [-nn -i sw0-p2-rej icmp6], [sw0-p2-rej-icmp6])
+
+ OVS_WAIT_UNTIL([
+- ip netns exec sw0-p2-rej nc -u -6 aef0::3 90 < foo
++ ip netns exec sw0-p2-rej ncat -u -6 aef0::3 90 < foo
+ c=$(cat sw0-p2-rej-icmp6.tcpdump | grep \
+ "IP6 aef0::3 > aef0::4: ICMP6, destination unreachable, unreachable port, \
+ aef0::3 udp port 90" | uniq | wc -l)
+@@ -5921,7 +5921,7 @@ NETNS_START_TCPDUMP([sw0-p2-rej], [-nn -i sw0-p2-rej udp port 94], [sw0-p2-rej-i
+ NETNS_START_TCPDUMP([sw0-p2-rej], [-nn -i sw0-p2-rej icmp6], [sw0-p2-rej-icmp6])
+
+ OVS_WAIT_UNTIL([
+- ip netns exec sw0-p2-rej nc -u -6 aef0::3 94 < foo
++ ip netns exec sw0-p2-rej ncat -u -6 aef0::3 94 < foo
+ c=$(cat sw0-p2-rej-icmp6.tcpdump | grep \
+ "IP6 aef0::3 > aef0::4: ICMP6, destination unreachable, unreachable port, \
+ aef0::3 udp port 94" | uniq | wc -l)
+@@ -5934,11 +5934,11 @@ check ovn-nbctl pg-add pg0 sw0-p1-rej sw0-p2-rej
+ check ovn-nbctl --log --apply-after-lb acl-add pg0 from-lport 1004 "inport == @pg0 && ip && (tcp || udp)" reject
+
+ OVS_WAIT_UNTIL([
+- ip netns exec sw0-p1-rej nc -vz 10.0.0.4 80 2>&1 | grep -i 'connection refused'
++ ip netns exec sw0-p1-rej ncat -vz 10.0.0.4 80 2>&1 | grep -i 'connection refused'
+ ])
+
+ OVS_WAIT_UNTIL([
+- ip netns exec sw0-p2-rej nc -vz6 aef0::3 80 2>&1 | grep -i 'connection refused'
++ ip netns exec sw0-p2-rej ncat -vz6 aef0::3 80 2>&1 | grep -i 'connection refused'
+ ])
+
+ kill $(pidof tcpdump)
+@@ -5948,7 +5948,7 @@ NETNS_START_TCPDUMP([sw0-p1-rej], [-nn -i sw0-p1-rej icmp], [sw0-p1-rej-icmp])
+
+ printf '.%.0s' {1..100} > foo
+ OVS_WAIT_UNTIL([
+- ip netns exec sw0-p1-rej nc -u 10.0.0.4 90 < foo
++ ip netns exec sw0-p1-rej ncat -u 10.0.0.4 90 < foo
+ c=$(cat sw0-p1-rej-icmp.tcpdump | grep \
+ "10.0.0.4 > 10.0.0.3: ICMP 10.0.0.4 udp port 90 unreachable" | uniq | wc -l)
+ test $c -ge 1
+@@ -5960,7 +5960,7 @@ rm -f *.tcpdump
+ NETNS_START_TCPDUMP([sw0-p2-rej], [-nn -i sw0-p2-rej icmp6], [sw0-p2-rej-icmp6])
+
+ OVS_WAIT_UNTIL([
+- ip netns exec sw0-p2-rej nc -u -6 aef0::3 90 < foo
++ ip netns exec sw0-p2-rej ncat -u -6 aef0::3 90 < foo
+ c=$(cat sw0-p2-rej-icmp6.tcpdump | grep \
+ "IP6 aef0::3 > aef0::4: ICMP6, destination unreachable, unreachable port, \
+ aef0::3 udp port 90" | uniq | wc -l)
+@@ -6096,7 +6096,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([Load balancer for container ports])
+-AT_SKIP_IF([test $HAVE_NC = no])
++AT_SKIP_IF([test $HAVE_NCAT = no])
+ AT_KEYWORDS([lb])
+
+ ovn_start
+@@ -6172,22 +6172,22 @@ NS_CHECK_EXEC([sw0-p2-lbc], [ip addr add 20.0.0.4/24 dev sw1p2], [0])
+ NS_CHECK_EXEC([sw0-p2-lbc], [ip route delete default via 10.0.0.1 dev sw0-p2-lbc], [0])
+ NS_CHECK_EXEC([sw0-p2-lbc], [ip route add default via 20.0.0.1 dev sw1p2], [0])
+
+-# Start nc server on sw1p2 (sw0-p2-lbc is the parent)
+-NETNS_DAEMONIZE([sw0-p2-lbc], [nc -l -k 20.0.0.4 80], [nc0.pid])
++# Start Ncat server on sw1p2 (sw0-p2-lbc is the parent)
++NETNS_DAEMONIZE([sw0-p2-lbc], [ncat -l -k 20.0.0.4 80], [nc0.pid])
+
+ # Send the packet to backend
+-NS_CHECK_EXEC([sw0-p1-lbc], [nc -z 20.0.0.4 80], [0], [ignore], [ignore])
++NS_CHECK_EXEC([sw0-p1-lbc], [ncat -z 20.0.0.4 80], [0], [ignore], [ignore])
+
+ # Send the packet to VIP.
+-NS_CHECK_EXEC([sw0-p1-lbc], [nc -z 30.0.0.10 80], [0], [ignore], [ignore])
++NS_CHECK_EXEC([sw0-p1-lbc], [ncat -z 30.0.0.10 80], [0], [ignore], [ignore])
+
+ # Now add an ACL in sw1.
+ check ovn-nbctl --wait=hv acl-add sw1 to-lport 2002 "ip" allow-related
+ # Send the packet to backend
+-NS_CHECK_EXEC([sw0-p1-lbc], [nc -z 20.0.0.4 80], [0], [ignore], [ignore])
++NS_CHECK_EXEC([sw0-p1-lbc], [ncat -z 20.0.0.4 80], [0], [ignore], [ignore])
+
+ # Send the packet to VIP.
+-NS_CHECK_EXEC([sw0-p1-lbc], [nc -z 30.0.0.10 80], [0], [ignore], [ignore])
++NS_CHECK_EXEC([sw0-p1-lbc], [ncat -z 30.0.0.10 80], [0], [ignore], [ignore])
+
+ OVN_CLEANUP_CONTROLLER([hv1])
+
+@@ -6202,7 +6202,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([load-balancer and firewall tuple conflict IPv4])
+-AT_SKIP_IF([test $HAVE_NC = no])
++AT_SKIP_IF([test $HAVE_NCAT = no])
+ AT_KEYWORDS([ovnlb])
+
+ CHECK_CONNTRACK()
+@@ -6248,13 +6248,13 @@ wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+
+ # Start IPv4 TCP server on vm1.
+-NETNS_DAEMONIZE([vm1], [nc -k -l 42.42.42.2 4242], [nc-vm1.pid])
++NETNS_DAEMONIZE([vm1], [ncat -k -l 42.42.42.2 4242], [nc-vm1.pid])
+
+ # Make sure connecting to the VIP works.
+-NS_CHECK_EXEC([vm2], [nc 66.66.66.66 666 -p 2000 -z], [0], [ignore], [ignore])
++NS_CHECK_EXEC([vm2], [ncat 66.66.66.66 666 -p 2000 -z], [0], [ignore], [ignore])
+
+ # Start IPv4 TCP connection to VIP from vm2.
+-NS_CHECK_EXEC([vm2], [nc 66.66.66.66 666 -p 2001 -z], [0], [ignore], [ignore])
++NS_CHECK_EXEC([vm2], [ncat 66.66.66.66 666 -p 2001 -z], [0], [ignore], [ignore])
+
+ # Check conntrack. We expect two entries:
+ # - one in vm1's zone (firewall)
+@@ -6272,7 +6272,7 @@ tcp,orig=(src=42.42.42.3,dst=66.66.66.66,sport=<clnt_s_port>,dport=666),reply=(s
+
+ # Start IPv4 TCP connection to backend IP from vm2 which would require
+ # additional source port translation to avoid a tuple conflict.
+-NS_CHECK_EXEC([vm2], [nc 42.42.42.2 4242 -p 2001 -z], [0], [ignore], [ignore])
++NS_CHECK_EXEC([vm2], [ncat 42.42.42.2 4242 -p 2001 -z], [0], [ignore], [ignore])
+
+ # Check conntrack. We expect three entries:
+ # - one in vm1's zone (firewall) - reused from the previous connection.
+@@ -6302,7 +6302,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([load-balancer and firewall tuple conflict IPv6])
+-AT_SKIP_IF([test $HAVE_NC = no])
++AT_SKIP_IF([test $HAVE_NCAT = no])
+ AT_KEYWORDS([ovnlb])
+
+ CHECK_CONNTRACK()
+@@ -6348,13 +6348,13 @@ wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+
+ # Start IPv6 TCP server on vm1.
+-NETNS_DAEMONIZE([vm1], [nc -k -l 4242::2 4242], [nc-vm1.pid])
++NETNS_DAEMONIZE([vm1], [ncat -k -l 4242::2 4242], [nc-vm1.pid])
+
+ # Make sure connecting to the VIP works.
+-NS_CHECK_EXEC([vm2], [nc 6666::1 666 -p 2000 -z], [0], [ignore], [ignore])
++NS_CHECK_EXEC([vm2], [ncat 6666::1 666 -p 2000 -z], [0], [ignore], [ignore])
+
+ # Start IPv6 TCP connection to VIP from vm2.
+-NS_CHECK_EXEC([vm2], [nc 6666::1 666 -p 2001 -z], [0], [ignore], [ignore])
++NS_CHECK_EXEC([vm2], [ncat 6666::1 666 -p 2001 -z], [0], [ignore], [ignore])
+
+ # Check conntrack. We expect two entries:
+ # - one in vm1's zone (firewall)
+@@ -6372,7 +6372,7 @@ tcp,orig=(src=4242::3,dst=6666::1,sport=<clnt_s_port>,dport=666),reply=(src=4242
+
+ # Start IPv6 TCP connection to backend IP from vm2 which would require
+ # additional source port translation to avoid a tuple conflict.
+-NS_CHECK_EXEC([vm2], [nc 4242::2 4242 -p 2001 -z], [0], [ignore], [ignore])
++NS_CHECK_EXEC([vm2], [ncat 4242::2 4242 -p 2001 -z], [0], [ignore], [ignore])
+
+ # Check conntrack. We expect three entries:
+ # - one in vm1's zone (firewall) - reused from the previous connection.
+@@ -6588,8 +6588,8 @@ check ovn-nbctl --wait=hv sync
+
+ on_exit 'ovs-ofctl dump-flows br-int'
+
+-NETNS_DAEMONIZE([alice1], [nc -l -k 80], [alice1.pid])
+-NS_CHECK_EXEC([bob1], [nc -z 10.0.0.2 80], [0])
++NETNS_DAEMONIZE([alice1], [ncat -l -k 80], [alice1.pid])
++NS_CHECK_EXEC([bob1], [ncat -z 10.0.0.2 80], [0])
+ NS_CHECK_EXEC([bob1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.2 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+@@ -6625,7 +6625,7 @@ check ovn-nbctl set Logical_Switch_Port r2-ext \
+ # Wait for ovn-controller before sending traffic
+ check ovn-nbctl --wait=hv sync
+
+-NS_CHECK_EXEC([bob1], [nc -z 10.0.0.2 80], [0])
++NS_CHECK_EXEC([bob1], [ncat -z 10.0.0.2 80], [0])
+ NS_CHECK_EXEC([bob1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.2 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+@@ -6656,8 +6656,8 @@ AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+ NS_CHECK_EXEC([bob1], [ip r d default via 172.16.0.2 dev bob1], [0])
+ NS_CHECK_EXEC([bob1], [ip r a default via 172.16.0.3 dev bob1], [0])
+
+-NETNS_DAEMONIZE([bob1], [nc -l -k 8080], [bob1.pid])
+-NS_CHECK_EXEC([alice1], [(echo "Packet 1"; sleep 1; echo "Packet 2") | nc 172.16.0.1 8080], [0])
++NETNS_DAEMONIZE([bob1], [ncat --no-shutdown -l -k 8080], [bob1.pid])
++NS_CHECK_EXEC([alice1], [(echo "Packet 1"; sleep 1; echo "Packet 2") | ncat 172.16.0.1 8080], [0])
+ NS_CHECK_EXEC([alice1], [ping -q -c 3 -i 0.3 -w 2 172.16.0.1 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+@@ -6674,8 +6674,8 @@ tcp,orig=(src=10.0.0.2,dst=172.16.0.1,sport=<cleared>,dport=<cleared>),reply=(sr
+ check ovn-nbctl --policy="src-ip" lr-route-del R1 10.0.0.0/24 20.0.0.3
+ check ovn-nbctl --wait=hv sync
+ AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+-NETNS_DAEMONIZE([bob1], [nc -l -k 8081], [bob2.pid])
+-NS_CHECK_EXEC([alice1], [(echo "Packet 1"; sleep 1; echo "Packet 2") | nc 172.16.0.1 8081], [0])
++NETNS_DAEMONIZE([bob1], [ncat --no-shutdown -l -k 8081], [bob2.pid])
++NS_CHECK_EXEC([alice1], [(echo "Packet 1"; sleep 1; echo "Packet 2") | ncat 172.16.0.1 8081], [0])
+ NS_CHECK_EXEC([alice1], [ping -q -c 3 -i 0.3 -w 2 172.16.0.1 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+@@ -6817,8 +6817,8 @@ check ovn-nbctl --wait=hv sync
+
+ on_exit 'ovs-ofctl dump-flows br-int'
+
+-NETNS_DAEMONIZE([alice1], [nc -6 -l -k 80], [alice1.pid])
+-NS_CHECK_EXEC([bob1], [nc -6 -z fd01::2 80], [0])
++NETNS_DAEMONIZE([alice1], [ncat -6 -l -k 80], [alice1.pid])
++NS_CHECK_EXEC([bob1], [ncat -6 -z fd01::2 80], [0])
+ NS_CHECK_EXEC([bob1], [ping -q -c 3 -i 0.3 -w 2 fd01::2 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+@@ -6853,7 +6853,7 @@ check ovn-nbctl set Logical_Router_Port R2_ext mac='"00:00:10:01:02:04"'
+ check ovn-nbctl --wait=hv set Logical_Switch_Port r2-ext \
+ type=router options:router-port=R2_ext addresses='"00:00:10:01:02:04"'
+
+-NS_CHECK_EXEC([bob1], [nc -6 -z fd01::2 80], [0])
++NS_CHECK_EXEC([bob1], [ncat -6 -z fd01::2 80], [0])
+ NS_CHECK_EXEC([bob1], [ping -q -c 3 -i 0.3 -w 2 fd01::2 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+@@ -6883,8 +6883,8 @@ test $(ovs-ofctl dump-flows br-int | grep -c 'table=OFTABLE_ECMP_NH, n_packets')
+
+ AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+
+-NETNS_DAEMONIZE([bob1], [nc -6 -l -k 8080], [bob1.pid])
+-NS_CHECK_EXEC([alice1], [(echo "Packet 1"; sleep 1; echo "Packet 2") | nc -6 fd07::1 8080], [0])
++NETNS_DAEMONIZE([bob1], [ncat --no-shutdown -6 -l -k 8080], [bob1.pid])
++NS_CHECK_EXEC([alice1], [(echo "Packet 1"; sleep 1; echo "Packet 2") | ncat -6 fd07::1 8080], [0])
+ NS_CHECK_EXEC([alice1], [ping -q -c 3 -i 0.3 -w 2 fd07::1 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+@@ -6901,8 +6901,8 @@ tcp,orig=(src=fd01::2,dst=fd07::1,sport=<cleared>,dport=<cleared>),reply=(src=fd
+ check ovn-nbctl --policy="src-ip" lr-route-del R1 fd01::/126 fd02::3
+ check ovn-nbctl --wait=hv sync
+ AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+-NETNS_DAEMONIZE([bob1], [nc -6 -l -k 8081], [bob2.pid])
+-NS_CHECK_EXEC([alice1], [(echo "Packet 1"; sleep 1; echo "Packet 2") | nc -6 fd07::1 8081], [0])
++NETNS_DAEMONIZE([bob1], [ncat --no-shutdown -6 -l -k 8081], [bob2.pid])
++NS_CHECK_EXEC([alice1], [(echo "Packet 1"; sleep 1; echo "Packet 2") | ncat -6 fd07::1 8081], [0])
+ NS_CHECK_EXEC([alice1], [ping -q -c 3 -i 0.3 -w 2 fd07::1 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+@@ -7815,7 +7815,7 @@ AT_CLEANUP
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([ovn -- CoPP])
+ AT_SKIP_IF([test $HAVE_TCPDUMP = no])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+ AT_KEYWORDS([ovn-copp])
+
+ ovn_start
+@@ -7877,8 +7877,9 @@ reject: acl-meter
+ ])
+
+ ip netns exec sw01 scapy -H <<-EOF
+-p = IP(src="192.168.1.2", dst="192.168.1.1") / UDP(dport = 12345) / Raw(b"X"*64)
+-send (p, iface='sw01', loop = 0, verbose = 0, count = 20)
++p = Ether(src="f0:00:00:01:02:03", dst="00:00:01:01:02:03") / \
++ IP(src="192.168.1.2", dst="192.168.1.1") / UDP(dport = 12345) / Raw(b"X"*64)
++sendp(p, iface='sw01', loop = 0, verbose = 0, count = 20)
+ EOF
+
+ # 1pps
+@@ -7894,8 +7895,9 @@ NETNS_START_TCPDUMP([sw01], [-n -i sw01 icmp -Q in], [reject])
+ check ovn-nbctl --may-exist --wait=hv meter-add acl-meter drop 5 pktps 0
+ OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow15 meter-stats br-int | grep -q packet_count:0])
+ ip netns exec sw01 scapy -H <<-EOF
+-p = IP(src="192.168.1.2", dst="192.168.1.1") / UDP(dport = 12345) / Raw(b"X"*64)
+-send (p, iface='sw01', loop = 0, verbose = 0, count = 40)
++p = Ether(src="f0:00:00:01:02:03", dst="00:00:01:01:02:03") / \
++ IP(src="192.168.1.2", dst="192.168.1.1") / UDP(dport = 12345) / Raw(b"X"*64)
++sendp(p, iface='sw01', loop = 0, verbose = 0, count = 40)
+ EOF
+
+ # 10pps
+@@ -7911,8 +7913,9 @@ NETNS_START_TCPDUMP([sw01], [-n -i sw01 icmp -Q in], [reject])
+ check ovn-nbctl --wait=hv copp-del copp0 reject
+
+ ip netns exec sw01 scapy -H <<-EOF
+-p = IP(src="192.168.1.2", dst="192.168.1.1") / UDP(dport = 12345) / Raw(b"X"*64)
+-send (p, iface='sw01', loop = 0, verbose = 0, count = 20)
++p = Ether(src="f0:00:00:01:02:03", dst="00:00:01:01:02:03") / \
++ IP(src="192.168.1.2", dst="192.168.1.1") / UDP(dport = 12345) / Raw(b"X"*64)
++sendp(p, iface='sw01', loop = 0, verbose = 0, count = 20)
+ EOF
+
+ OVS_WAIT_UNTIL([
+@@ -7929,8 +7932,9 @@ arp-resolve: arp-meter
+ ])
+
+ ip netns exec sw01 scapy -H <<-EOF
+-p = IP(src="192.168.1.2", dst="172.16.1.100") / TCP(dport = 80, flags="S") / Raw(b"X"*64)
+-send (p, iface='sw01', loop = 0, verbose = 0, count = 100)
++p = Ether(src="f0:00:00:01:02:03", dst="00:00:01:01:02:03") / \
++ IP(src="192.168.1.2", dst="172.16.1.100") / TCP(dport = 80, flags="S") / Raw(b"X"*64)
++sendp(p, iface='sw01', loop = 0, verbose = 0, count = 100)
+ EOF
+
+ # 1pps
+@@ -7948,8 +7952,9 @@ icmp4-error: icmp-meter
+
+ NETNS_START_TCPDUMP([sw01], [-n -i sw01 icmp], [icmp])
+ ip netns exec sw01 scapy -H <<-EOF
+-p = IP(src="192.168.1.2", dst="172.16.1.100", ttl=1) / TCP(dport = 8080, flags="S") / Raw(b"X"*64)
+-send (p, iface='sw01', loop = 0, verbose = 0, count = 100)
++p = Ether(src="f0:00:00:01:02:03", dst="00:00:01:01:02:03") / \
++ IP(src="192.168.1.2", dst="172.16.1.100", ttl=1) / TCP(dport = 8080, flags="S") / Raw(b"X"*64)
++sendp(p, iface='sw01', loop = 0, verbose = 0, count = 100)
+ EOF
+
+ # 1pps
+@@ -8540,7 +8545,7 @@ wait_for_ports_up
+ # Start webservers in 'sw1-p1'
+ OVS_START_L7([sw1p1], [http])
+
+-AT_CHECK([ip netns exec sw0p1 wget 20.0.0.3 -t 3 -T 1], [0], [ignore], [ignore])
++AT_CHECK([ip netns exec sw0p1 wget http://20.0.0.3 -t 3 -T 1], [0], [ignore], [ignore])
+
+ # Clear the apply-after-lb option for the ACL
+ check ovn-nbctl acl-del sw1 from-lport 1001 'inport == "sw1p1" && ip4'
+@@ -8548,7 +8553,7 @@ check ovn-nbctl acl-add sw1 from-lport 1001 'inport == "sw1p1" && ip4' drop
+
+ check ovn-nbctl --wait=hv sync
+
+-AT_CHECK([ip netns exec sw0p1 wget 20.0.0.3 -t 3 -T 1], [0], [ignore], [ignore])
++AT_CHECK([ip netns exec sw0p1 wget http://20.0.0.3 -t 3 -T 1], [0], [ignore], [ignore])
+
+ OVN_CLEANUP_CONTROLLER([hv1])
+
+@@ -8949,8 +8954,8 @@ start_daemon ovn-controller
+ # sw0 -- R1 -- sw1
+ # Logical port 'sw01' in switch 'sw0'.
+ # Logical port 'sw11' in switch 'sw1'.
+-# nc server running in sw01
+-# nc client running on sw11
++# Ncat server running in sw01
++# Ncat client running on sw11
+
+ check ovn-nbctl lr-add R1
+ check ovn-nbctl ls-add sw0
+@@ -8979,7 +8984,7 @@ ADD_VETH(sw11, sw11, br-int, "192.168.2.2/24", "f0:00:00:02:02:03", \
+ check ovn-nbctl lsp-add sw1 sw11 \
+ -- lsp-set-addresses sw11 "f0:00:00:02:02:03 192.168.2.2"
+
+-NETNS_DAEMONIZE([sw01], [nc -k -l 8000], [nc-sw01.pid])
++NETNS_DAEMONIZE([sw01], [ncat -k -l 8000], [nc-sw01.pid])
+
+ test_ping() {
+ NS_CHECK_EXEC([$1], [ping -q -c 1 $2 -w 2 | FORMAT_PING], \
+@@ -8992,7 +8997,7 @@ test_ping() {
+ check ovn-nbctl --wait=hv lr-nat-add R1 snat 172.16.1.21 192.168.2.0/24
+
+ echo "foo" > foo
+-NS_CHECK_EXEC([sw11], [nc 192.168.1.2 8000 -z], [0], [ignore], [ignore])
++NS_CHECK_EXEC([sw11], [ncat 192.168.1.2 8000 -z], [0], [ignore], [ignore])
+ test_ping sw11 192.168.1.2
+
+ # Ensure nat has been hit
+@@ -9009,7 +9014,7 @@ AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+
+ # SNAT and DNAT. using Logical IP
+ check ovn-nbctl --wait=hv lr-nat-add R1 dnat_and_snat 172.16.1.2 192.168.1.2
+-NS_CHECK_EXEC([sw11], [nc 192.168.1.2 8000 -z], [0], [ignore], [ignore])
++NS_CHECK_EXEC([sw11], [ncat 192.168.1.2 8000 -z], [0], [ignore], [ignore])
+ test_ping sw11 192.168.1.2
+
+ # Ensure conntrack entry is present
+@@ -9023,7 +9028,7 @@ tcp,orig=(src=192.168.2.2,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),reply
+ AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+
+ # SNAT and DNAT. using floating IP
+-NS_CHECK_EXEC([sw11], [nc 172.16.1.2 8000 -z], [0], [ignore], [ignore])
++NS_CHECK_EXEC([sw11], [ncat 172.16.1.2 8000 -z], [0], [ignore], [ignore])
+ test_ping sw11 172.16.1.2
+
+ # Ensure conntrack entry is present
+@@ -9168,7 +9173,7 @@ NS_CHECK_EXEC([ls1p1], [ping -q -c 3 -i 0.3 -w 2 172.16.1.1 | FORMAT_PING], \
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+
+-NS_CHECK_EXEC([ls1p1], [ping6 -q -c 3 -i 0.3 -w 2 1711::1 | FORMAT_PING], \
++NS_CHECK_EXEC([ls1p1], [ping -6 -q -c 3 -i 0.3 -w 2 1711::1 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+@@ -9429,8 +9434,8 @@ test_related_traffic() {
+ NETNS_START_TCPDUMP([server], [-U -i server -w server.pcap], [tcpdump1])
+
+ # Setup a dummy UDP listeners so we don't get "port unreachable".
+- NETNS_DAEMONIZE([client], [nc -l -u 1], [nc0.pid])
+- NETNS_DAEMONIZE([server], [nc -l -u 2], [nc1.pid])
++ NETNS_DAEMONIZE([client], [ncat -l -u 1], [nc0.pid])
++ NETNS_DAEMONIZE([server], [ncat -l -u 2], [nc1.pid])
+
+ # Send UDP client -> server
+ check ovs-ofctl packet-out br-int "in_port=ovs-client,packet=$client_udp,actions=resubmit(,0)"
+@@ -9545,7 +9550,7 @@ AT_CLEANUP
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([load-balancer template IPv4])
+ AT_SKIP_IF([test $HAVE_TCPDUMP = no])
+-AT_SKIP_IF([test $HAVE_NC = no])
++AT_SKIP_IF([test $HAVE_NCAT = no])
+ AT_KEYWORDS([ovnlb templates])
+
+ CHECK_CONNTRACK()
+@@ -9641,28 +9646,28 @@ name: 'vport4' value: '999'
+ ])
+
+ # Start IPv4 TCP server on vm1.
+-NETNS_DAEMONIZE([vm1], [nc -k -l 42.42.42.2 4242], [nc-vm1.pid])
++NETNS_DAEMONIZE([vm1], [ncat -k -l 42.42.42.2 4242], [nc-vm1.pid])
+
+ NETNS_START_TCPDUMP([vm1],
+ [-n -i vm1 -nnqleX -c6 udp and dst 42.42.42.2 and dst port 4343],
+ [vm1])
+
+ # Make sure connecting to the VIP works (hairpin, via ls and via lr).
+-NS_CHECK_EXEC([vm1], [nc 66.66.66.66 666 -z], [0], [ignore], [ignore])
+-NS_CHECK_EXEC([vm2], [nc 66.66.66.66 666 -z], [0], [ignore], [ignore])
+-NS_CHECK_EXEC([vm3], [nc 66.66.66.66 666 -z], [0], [ignore], [ignore])
++NS_CHECK_EXEC([vm1], [ncat 66.66.66.66 666 -z], [0], [ignore], [ignore])
++NS_CHECK_EXEC([vm2], [ncat 66.66.66.66 666 -z], [0], [ignore], [ignore])
++NS_CHECK_EXEC([vm3], [ncat 66.66.66.66 666 -z], [0], [ignore], [ignore])
+
+-NS_CHECK_EXEC([vm1], [echo a | nc -u 66.66.66.66 777], [ignore], [ignore], [ignore])
+-NS_CHECK_EXEC([vm2], [echo a | nc -u 66.66.66.66 777], [ignore], [ignore], [ignore])
+-NS_CHECK_EXEC([vm3], [echo a | nc -u 66.66.66.66 777], [ignore], [ignore], [ignore])
++NS_CHECK_EXEC([vm1], [echo a | ncat -u 66.66.66.66 777], [ignore], [ignore], [ignore])
++NS_CHECK_EXEC([vm2], [echo a | ncat -u 66.66.66.66 777], [ignore], [ignore], [ignore])
++NS_CHECK_EXEC([vm3], [echo a | ncat -u 66.66.66.66 777], [ignore], [ignore], [ignore])
+
+-NS_CHECK_EXEC([vm1], [nc 66.66.66.66 888 -z], [0], [ignore], [ignore])
+-NS_CHECK_EXEC([vm2], [nc 66.66.66.66 888 -z], [0], [ignore], [ignore])
+-NS_CHECK_EXEC([vm3], [nc 66.66.66.66 888 -z], [0], [ignore], [ignore])
++NS_CHECK_EXEC([vm1], [ncat 66.66.66.66 888 -z], [0], [ignore], [ignore])
++NS_CHECK_EXEC([vm2], [ncat 66.66.66.66 888 -z], [0], [ignore], [ignore])
++NS_CHECK_EXEC([vm3], [ncat 66.66.66.66 888 -z], [0], [ignore], [ignore])
+
+-NS_CHECK_EXEC([vm1], [echo a | nc -u 66.66.66.66 999], [ignore], [ignore], [ignore])
+-NS_CHECK_EXEC([vm2], [echo a | nc -u 66.66.66.66 999], [ignore], [ignore], [ignore])
+-NS_CHECK_EXEC([vm3], [echo a | nc -u 66.66.66.66 999], [ignore], [ignore], [ignore])
++NS_CHECK_EXEC([vm1], [echo a | ncat -u 66.66.66.66 999], [ignore], [ignore], [ignore])
++NS_CHECK_EXEC([vm2], [echo a | ncat -u 66.66.66.66 999], [ignore], [ignore], [ignore])
++NS_CHECK_EXEC([vm3], [echo a | ncat -u 66.66.66.66 999], [ignore], [ignore], [ignore])
+
+ OVS_WAIT_UNTIL([
+ requests=`grep "UDP" -c vm1.tcpdump`
+@@ -9683,7 +9688,7 @@ AT_CLEANUP
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([load-balancer template IPv6])
+ AT_SKIP_IF([test $HAVE_TCPDUMP = no])
+-AT_SKIP_IF([test $HAVE_NC = no])
++AT_SKIP_IF([test $HAVE_NCAT = no])
+ AT_KEYWORDS([ovnlb templates])
+
+ CHECK_CONNTRACK()
+@@ -9779,28 +9784,28 @@ name: 'vport4' value: '999'
+ ])
+
+ # Start IPv6 TCP server on vm1.
+-NETNS_DAEMONIZE([vm1], [nc -k -l 4242::2 4242], [nc-vm1.pid])
++NETNS_DAEMONIZE([vm1], [ncat -k -l 4242::2 4242], [nc-vm1.pid])
+
+ NETNS_START_TCPDUMP([vm1],
+ [-n -i vm1 -nnqleX -c6 udp and dst 4242::2 and dst port 4343],
+ [vm1])
+
+ # Make sure connecting to the VIP works (hairpin, via ls and via lr).
+-NS_CHECK_EXEC([vm1], [nc 6666::1 666 -z], [0], [ignore], [ignore])
+-NS_CHECK_EXEC([vm2], [nc 6666::1 666 -z], [0], [ignore], [ignore])
+-NS_CHECK_EXEC([vm3], [nc 6666::1 666 -z], [0], [ignore], [ignore])
++NS_CHECK_EXEC([vm1], [ncat 6666::1 666 -z], [0], [ignore], [ignore])
++NS_CHECK_EXEC([vm2], [ncat 6666::1 666 -z], [0], [ignore], [ignore])
++NS_CHECK_EXEC([vm3], [ncat 6666::1 666 -z], [0], [ignore], [ignore])
+
+-NS_CHECK_EXEC([vm1], [echo a | nc -u 6666::1 777], [ignore], [ignore], [ignore])
+-NS_CHECK_EXEC([vm2], [echo a | nc -u 6666::1 777], [ignore], [ignore], [ignore])
+-NS_CHECK_EXEC([vm3], [echo a | nc -u 6666::1 777], [ignore], [ignore], [ignore])
++NS_CHECK_EXEC([vm1], [echo a | ncat -u 6666::1 777], [ignore], [ignore], [ignore])
++NS_CHECK_EXEC([vm2], [echo a | ncat -u 6666::1 777], [ignore], [ignore], [ignore])
++NS_CHECK_EXEC([vm3], [echo a | ncat -u 6666::1 777], [ignore], [ignore], [ignore])
+
+-NS_CHECK_EXEC([vm1], [nc 6666::1 888 -z], [0], [ignore], [ignore])
+-NS_CHECK_EXEC([vm2], [nc 6666::1 888 -z], [0], [ignore], [ignore])
+-NS_CHECK_EXEC([vm3], [nc 6666::1 888 -z], [0], [ignore], [ignore])
++NS_CHECK_EXEC([vm1], [ncat 6666::1 888 -z], [0], [ignore], [ignore])
++NS_CHECK_EXEC([vm2], [ncat 6666::1 888 -z], [0], [ignore], [ignore])
++NS_CHECK_EXEC([vm3], [ncat 6666::1 888 -z], [0], [ignore], [ignore])
+
+-NS_CHECK_EXEC([vm1], [echo a | nc -u 6666::1 999], [ignore], [ignore], [ignore])
+-NS_CHECK_EXEC([vm2], [echo a | nc -u 6666::1 999], [ignore], [ignore], [ignore])
+-NS_CHECK_EXEC([vm3], [echo a | nc -u 6666::1 999], [ignore], [ignore], [ignore])
++NS_CHECK_EXEC([vm1], [echo a | ncat -u 6666::1 999], [ignore], [ignore], [ignore])
++NS_CHECK_EXEC([vm2], [echo a | ncat -u 6666::1 999], [ignore], [ignore], [ignore])
++NS_CHECK_EXEC([vm3], [echo a | ncat -u 6666::1 999], [ignore], [ignore], [ignore])
+
+ OVS_WAIT_UNTIL([
+ requests=`grep "UDP" -c vm1.tcpdump`
+@@ -10009,7 +10014,7 @@ zone_id=$(ovn-appctl -t ovn-controller ct-zone-list | grep foo1 | cut -d ' ' -f2
+
+ OVS_START_L7([bar1], [http])
+
+-AT_CHECK([ip netns exec foo1 wget 192.168.2.2 -t 3 -T 1], [0], [ignore], [ignore])
++AT_CHECK([ip netns exec foo1 wget http://192.168.2.2 -t 3 -T 1], [0], [ignore], [ignore])
+
+ # check conntrack zone has tcp entry
+ AT_CHECK([ovs-appctl dpctl/dump-conntrack zone=$zone_id | \
+@@ -10021,7 +10026,7 @@ tcp,orig=(src=192.168.1.2,dst=192.168.2.2,sport=<cleared>,dport=<cleared>),reply
+ AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+
+ # now check with VIP
+-AT_CHECK([ip netns exec foo1 wget 30.30.30.30 -t 3 -T 1], [0], [ignore], [ignore])
++AT_CHECK([ip netns exec foo1 wget http://30.30.30.30 -t 3 -T 1], [0], [ignore], [ignore])
+
+ # check conntrack zone has tcp entry
+ AT_CHECK([ovs-appctl dpctl/dump-conntrack zone=$zone_id | \
+@@ -10039,7 +10044,7 @@ check ovn-nbctl ls-lb-del foo lb1
+ check ovn-nbctl acl-add foo from-lport 1 1 allow-stateless
+ check ovn-nbctl --wait=hv acl-add foo to-lport 1 1 allow-stateless
+
+-AT_CHECK([ip netns exec foo1 wget 192.168.2.2 -t 3 -T 1], [0], [ignore], [ignore])
++AT_CHECK([ip netns exec foo1 wget http://192.168.2.2 -t 3 -T 1], [0], [ignore], [ignore])
+
+ # check conntrack zone has no tcp entry
+ AT_CHECK([ovs-appctl dpctl/dump-conntrack zone=$zone_id | \
+@@ -10540,13 +10545,13 @@ tcp,orig=(src=192.168.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(s
+
+ OVS_WAIT_FOR_OUTPUT([
+ for i in `seq 1 20`; do
+- ip netns exec foo1 wget 30.0.0.1 -t 5 -T 1 --retry-connrefused -v -o wget$i.log;
++ ip netns exec foo1 wget http://30.0.0.1 -t 5 -T 1 --retry-connrefused -v -o wget$i.log;
+ done
+ ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [LB1_CT_ENTRIES])
+
+ OVS_WAIT_FOR_OUTPUT([
+ for i in `seq 1 20`; do
+- ip netns exec foo1 wget 30.0.0.2:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log;
++ ip netns exec foo1 wget http://30.0.0.2:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log;
+ done
+ ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.2) | sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+ tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.2,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
+@@ -10556,7 +10561,7 @@ tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(s
+
+ OVS_WAIT_FOR_OUTPUT([
+ for i in `seq 1 20`; do
+- ip netns exec foo1 wget 30.0.0.3 -t 5 -T 1 --retry-connrefused -v -o wget$i.log;
++ ip netns exec foo1 wget http://30.0.0.3 -t 5 -T 1 --retry-connrefused -v -o wget$i.log;
+ done
+ ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.3) | sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+ tcp,orig=(src=192.168.1.2,dst=30.0.0.3,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.2,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
+@@ -10623,7 +10628,7 @@ check ovn-nbctl ls-lb-add foo lb1
+
+ OVS_WAIT_FOR_OUTPUT([
+ for i in `seq 1 20`; do
+- ip netns exec foo1 wget 30.0.0.1 -t 5 -T 1 --retry-connrefused -v -o wget$i.log;
++ ip netns exec foo1 wget http://30.0.0.1 -t 5 -T 1 --retry-connrefused -v -o wget$i.log;
+ done
+ ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [LB1_CT_ENTRIES])
+
+@@ -11161,12 +11166,12 @@ check ovn-nbctl lsp-add bar bar1 \
+ check ovn-nbctl --wait=hv sync
+
+ # Force ipv6 nd neighbour solicitation
+-NS_EXEC([foo1], [ping6 -c 1 fd12::2])
++NS_EXEC([foo1], [ping -6 -c 1 fd12::2])
+
+ NETNS_START_TCPDUMP([foo1], [-vvvv -ttt -nn -e -i foo1 'ether dst 0a:58:a9:fe:01:01 and icmp6'], [foo1-icmp6])
+
+ # 'foo1' should be able to ping 'bar1'
+-NS_CHECK_EXEC([foo1], [ping6 -q -c 3 -i 0.3 -w 2 fd12::2 | FORMAT_PING], \
++NS_CHECK_EXEC([foo1], [ping -6 -q -c 3 -i 0.3 -w 2 fd12::2 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+@@ -11176,12 +11181,12 @@ OVS_WAIT_UNTIL([
+ ])
+
+ # Force ipv6 nd neighbour solicitation
+-NS_EXEC([foo2], [ping6 -c 1 fd12::2])
++NS_EXEC([foo2], [ping -6 -c 1 fd12::2])
+
+ NETNS_START_TCPDUMP([foo2], [-vvvv -ttt -nn -e -i foo2 'ether dst 0a:58:a9:fe:01:01 and icmp6'], [foo2-icmp6])
+
+ # 'foo2' should be able to ping 'bar1'
+-NS_CHECK_EXEC([foo2], [ping6 -q -c 3 -i 0.3 -w 2 fd12::2 | FORMAT_PING], \
++NS_CHECK_EXEC([foo2], [ping -6 -q -c 3 -i 0.3 -w 2 fd12::2 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+@@ -11191,12 +11196,12 @@ OVS_WAIT_UNTIL([
+ ])
+
+ # Force ipv6 nd neighbour solicitation
+-NS_EXEC([foo3], [ping6 -c 1 fd12::2])
++NS_EXEC([foo3], [ping -6 -c 1 fd12::2])
+
+ NETNS_START_TCPDUMP([foo3], [-vvvv -ttt -nn -e -i foo3 'ether dst 0a:58:a9:fe:01:01 and icmp6'], [foo3-icmp6])
+
+ # 'foo3' should be able to ping 'bar1'
+-NS_CHECK_EXEC([foo3], [ping6 -q -c 3 -i 0.3 -w 2 fd12::2 | FORMAT_PING], \
++NS_CHECK_EXEC([foo3], [ping -6 -q -c 3 -i 0.3 -w 2 fd12::2 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+@@ -11629,7 +11634,7 @@ check ovn-nbctl --wait=hv lsp-attach-mirror bar1 mirror1
+
+ NETNS_START_TCPDUMP([mirror], [-c 3 -neei mirror proto GRE], [gre_mirror6])
+
+-NS_CHECK_EXEC([foo1], [ping6 -q -c 3 -i 0.3 -w 2 2002::2 | FORMAT_PING], \
++NS_CHECK_EXEC([foo1], [ping -6 -q -c 3 -i 0.3 -w 2 2002::2 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+@@ -11660,7 +11665,7 @@ check ovn-nbctl --wait=hv lsp-attach-mirror bar1 mirror3
+
+ NETNS_START_TCPDUMP([mirror], [-c 3 -neei mirror ip6[[42:2]]=0x88be], [erspan_mirror6])
+
+-NS_CHECK_EXEC([foo1], [ping6 -q -c 3 -i 0.3 -w 2 2002::2 | FORMAT_PING], \
++NS_CHECK_EXEC([foo1], [ping -6 -q -c 3 -i 0.3 -w 2 2002::2 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+@@ -11674,7 +11679,7 @@ check ovn-nbctl --wait=hv set mirror $uuid type=gre
+
+ NETNS_START_TCPDUMP([mirror], [-c 3 -neei mirror proto GRE], [gre2_mirror6])
+
+-NS_CHECK_EXEC([foo1], [ping6 -q -c 3 -i 0.3 -w 2 2002::2 | FORMAT_PING], \
++NS_CHECK_EXEC([foo1], [ping -6 -q -c 3 -i 0.3 -w 2 2002::2 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+@@ -11887,7 +11892,7 @@ NS_CHECK_EXEC([ln], [ip route add 192.168.0.0/24 via 10.0.0.1])
+
+ OVS_START_L7([sw-vm1], [http])
+
+-NS_CHECK_EXEC([ln], [wget 172.16.0.1 -t 5 -T 1 --retry-connrefused -v -o wget.log])
++NS_CHECK_EXEC([ln], [wget http://172.16.0.1 -t 5 -T 1 --retry-connrefused -v -o wget.log])
+
+ AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.0.1) | \
+ sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+@@ -12031,7 +12036,7 @@ check ovn-nbctl --wait=hv sync
+
+ for i in $(seq 1 5); do
+ echo Request $i
+- NS_CHECK_EXEC([public-vm], [wget 172.16.1.150 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
++ NS_CHECK_EXEC([public-vm], [wget http://172.16.1.150 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
+ done
+
+ OVS_WAIT_FOR_OUTPUT([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.150) | wc -l ], [0], [dnl
+@@ -12051,7 +12056,7 @@ check ovn-nbctl --wait=hv sync
+
+ for i in $(seq 1 5); do
+ echo Request $i
+- NS_CHECK_EXEC([public-vm], [wget 172.16.1.151 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
++ NS_CHECK_EXEC([public-vm], [wget http://172.16.1.151 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
+ done
+
+ OVS_WAIT_FOR_OUTPUT([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.151) | wc -l ], [0], [dnl
+@@ -12077,7 +12082,7 @@ AT_CLEANUP
+
+ AT_SETUP([load balancing in gateway router - SCTP])
+ AT_SKIP_IF([test $HAVE_SCTP = no])
+-AT_SKIP_IF([test $HAVE_NC = no])
++AT_SKIP_IF([test $HAVE_NCAT = no])
+ AT_KEYWORDS([ovnlb sctp])
+
+ # Make sure the SCTP kernel module is loaded.
+@@ -12193,7 +12198,7 @@ on_exit "ovs-ofctl -O OpenFlow13 dump-flows br-int"
+ dnl Should work with the virtual IP address through NAT
+ for i in `seq 1 20`; do
+ echo Request $i
+- NS_CHECK_EXEC([alice1], [nc --sctp --recv-only 30.0.0.1 12345 > client$i.log])
++ NS_CHECK_EXEC([alice1], [ncat --sctp --recv-only 30.0.0.1 12345 > client$i.log])
+ done
+
+ dnl Each server should have at least one connection.
+@@ -12207,7 +12212,7 @@ sctp,orig=(src=172.16.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(s
+ dnl Test load-balancing that includes L4 ports in NAT.
+ for i in `seq 1 20`; do
+ echo Request $i
+- NS_CHECK_EXEC([alice1], [nc --sctp --recv-only 30.0.0.2 8000 > clients$i.log])
++ NS_CHECK_EXEC([alice1], [ncat --sctp --recv-only 30.0.0.2 8000 > clients$i.log])
+ done
+
+ dnl Each server should have at least one connection.
+@@ -12250,7 +12255,7 @@ OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-flows br-int table=$(ovn-debug lflo
+ dnl Test load-balancing that includes L4 ports in NAT.
+ for i in `seq 1 20`; do
+ echo Request $i
+- NS_CHECK_EXEC([alice1], [nc --sctp --recv-only 30.0.0.2 8000 > clients$i.log])
++ NS_CHECK_EXEC([alice1], [ncat --sctp --recv-only 30.0.0.2 8000 > clients$i.log])
+ done
+
+ dnl Each server should have at least one connection.
+@@ -12281,7 +12286,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([load balancing affinity sessions - auto clear learnt flows])
+-AT_SKIP_IF([test $HAVE_NC = no])
++AT_SKIP_IF([test $HAVE_NCAT = no])
+ AT_KEYWORDS([lb])
-- <li>
-- For each logical switch that has connected physical ports
-- (localnet or l2gateway) and is also connected to a distributed router,
-- filtering rules are added for ARP requests coming from localnet or
-- l2gateway ports, allowed for processing on gateway chassis.
-- The <code>REGBIT_EXT_ARP</code> register is set for all ARP requests
-- originating from physical ports with priority 75 flow.
-- </li>
--
- <li>
- For each (enabled) vtep logical port, a priority 70 flow is added which
- matches on all packets and applies the action
-@@ -405,13 +396,6 @@
- One priority-0 fallback flow that matches all packets and advances to
- the next table.
- </li>
--
-- <li>
-- Priority 75: Allows <code>REGBIT_EXT_ARP</code> packets only on gateway
-- chassis and chassis with distributed NAT entries.
-- Priority 70: Drops <code>REGBIT_EXT_ARP</code> packets on non-gateway
-- chassis (complements the priority 75 flow).
-- </li>
- </ul>
-
- <h3>Ingress Table 2: Mirror </h3>
-@@ -488,6 +472,21 @@
- </ul>
- </li>
+ ovn_start
+@@ -12313,12 +12318,12 @@ check ovn-nbctl lb-add lb-test 43.43.43.43:80 42.42.42.1:8080,42.42.42.2:8080 tc
+ dnl Start a server on vm1.
+ ADD_NAMESPACES(vm1)
+ ADD_VETH(vm1, vm1, br-int, "42.42.42.1/24", "00:00:00:00:00:01", "42.42.42.3")
+-NETNS_DAEMONIZE([vm1], [nc -l -k 42.42.42.1 8080], [vm1.pid])
++NETNS_DAEMONIZE([vm1], [ncat -l -k 42.42.42.1 8080], [vm1.pid])
-+ <li>
-+ <p>
-+ For each localnet logical port <var>p</var>, a priority-50
-+ fallback flow is added with the match
-+ <code>inport == <var>p</var></code> and action
-+ <code>flags.localnet = 1; next;</code>. This marks traffic
-+ arriving from localnet ports so that downstream stages (e.g.,
-+ ARP/ND responder) can condition their behavior. When FDB
-+ learning is enabled on the localnet port, the priority-100
-+ flow described above already sets <code>flags.localnet</code>,
-+ so this priority-50 flow only takes effect when FDB learning
-+ is not configured.
-+ </p>
-+ </li>
-+
- <li>
- One priority-0 fallback flow that matches all packets and advances to
- the next table.
-@@ -1734,12 +1733,16 @@
- </p>
+ dnl Start a server on vm2.
+ ADD_NAMESPACES(vm2)
+ ADD_VETH(vm2, vm2, br-int, "42.42.42.2/24", "00:00:00:00:00:02", "42.42.42.3")
+-NETNS_DAEMONIZE([vm2], [nc -l -k 42.42.42.2 8080], [vm2.pid])
++NETNS_DAEMONIZE([vm2], [ncat -l -k 42.42.42.2 8080], [vm2.pid])
- <p>
-- Note that ARP requests received from <code>localnet</code> logical
-- inports can either go directly to VMs, in which case the VM responds or
-- can hit an ARP responder for a logical router port if the packet is used
-- to resolve a logical router port next hop address. In either case,
-- logical switch ARP responder rules will not be hit. It contains these
-- logical flows:
-+ ARP/ND requests received from <code>localnet</code> logical inports
-+ do hit the ARP/ND responder, but the response is limited to the
-+ chassis that hosts the target VIF. This is achieved by adding
-+ a <code>flags.localnet</code> check to the priority-50 reply flows
-+ (see below): when the request arrives from a localnet port
-+ (<code>flags.localnet == 1</code>), only the chassis on which the
-+ target port is resident will reply. When the request arrives from
-+ a non-localnet port (<code>flags.localnet == 0</code>), the
-+ response is unconditional, preserving VIF-to-VIF proxy ARP/ND
-+ behavior. It contains these logical flows:
- </p>
-
- <ul>
-@@ -1750,18 +1753,10 @@
- router ingress pipeline.
- </li>
- <li>
-- If the logical switch has no router ports with options:arp_proxy
-- configured add a priority-100 flows to skip the ARP responder if inport
-- is of type <code>localnet</code> advances directly to the next table.
-- ARP requests sent to <code>localnet</code> ports can be received by
-- multiple hypervisors. Now, because the same mac binding rules are
-- downloaded to all hypervisors, each of the multiple hypervisors will
-- respond. This will confuse L2 learning on the source of the ARP
-- requests. ARP requests received on an inport of type
-- <code>router</code> are not expected to hit any logical switch ARP
-- responder flows. However, no skip flows are installed for these
-- packets, as there would be some additional flow cost for this and the
-- value appears limited.
-+ ARP/ND requests received on an inport of type <code>router</code> are
-+ not expected to hit any logical switch ARP responder flows. However,
-+ no skip flows are installed for these packets, as there would be some
-+ additional flow cost for this and the value appears limited.
- </li>
+ dnl Wait for ovn-controller to catch up.
+ wait_for_ports_up
+@@ -12326,7 +12331,7 @@ check ovn-nbctl --wait=hv sync
- <li>
-@@ -1816,6 +1811,18 @@ flags.loopback = 1;
- output;
- </pre>
+ dnl Test the connection.
+ OVS_WAIT_UNTIL([
+- ip netns exec vm1 nc -z 43.43.43.43 80 &> /dev/null
++ ip netns exec vm1 ncat -z 43.43.43.43 80 &> /dev/null
+ ])
-+ <p>
-+ On logical switches that have a localnet port, the match for
-+ these flows includes an additional condition:
-+ <code>((flags.localnet == 1 &&
-+ is_chassis_resident(<var>port</var>)) ||
-+ flags.localnet == 0)</code>.
-+ This ensures that when an ARP request arrives from a localnet
-+ port, only the chassis hosting the target VIF responds. When
-+ the request arrives from a non-localnet port, the response is
-+ unconditional, preserving VIF-to-VIF proxy ARP behavior.
-+ </p>
-+
- <p>
- These flows are omitted for logical ports (other than router ports or
- <code>localport</code> ports) that are down (unless <code>
-@@ -1877,6 +1884,19 @@ nd_na_router {
- };
- </pre>
+ OVS_WAIT_UNTIL([test $(ovs-ofctl dump-flows br-int | grep 'table=OFTABLE_CHK_LB_AFFINITY, n_packets' -c) -eq 1])
+@@ -12601,18 +12606,18 @@ ADD_NAMESPACES(server)
+ ADD_VETH(server, server, br-int, "2000::10/64", "f0:00:0f:01:02:03", \
+ "2000::1", "nodad", "192.168.200.10/24", "192.168.200.1")
-+ <p>
-+ On logical switches that have a localnet port, the match for
-+ these flows includes an additional condition:
-+ <code>((flags.localnet == 1 &&
-+ is_chassis_resident(<var>port</var>)) ||
-+ flags.localnet == 0)</code>.
-+ This ensures that when an ND solicitation arrives from a
-+ localnet port, only the chassis hosting the target VIF
-+ responds. When the solicitation arrives from a non-localnet
-+ port, the response is unconditional, preserving VIF-to-VIF
-+ proxy ND behavior.
-+ </p>
-+
- <p>
- These flows are omitted for logical ports (other than router ports or
- <code>localport</code> ports) that are down (unless <code>
-@@ -1896,8 +1916,8 @@ nd_na_router {
+-NETNS_DAEMONIZE([server], [nc -l -u 192.168.200.10 4242 > /dev/null], [serverv4.pid])
+-NETNS_DAEMONIZE([server], [nc -l -u 2000::10 4243 > /dev/null], [serverv6.pid])
++NETNS_DAEMONIZE([server], [ncat -l -u 192.168.200.10 4242 > /dev/null], [serverv4.pid])
++NETNS_DAEMONIZE([server], [ncat -l -u 2000::10 4243 > /dev/null], [serverv6.pid])
- <li>
- <p>
-- Priority-100 flows with match criteria like the ARP and ND flows
-- above, except that they only match packets from the
-+ Priority-100 flows with match criteria similar to the ARP and ND
-+ flows above, except that they only match packets from the
- <code>inport</code> that owns the IP addresses in question, with
- action <code>next;</code>. These flows prevent OVN from replying to,
- for example, an ARP request emitted by a VM for its own IP address.
-diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
-index 0ed2eb17ad..29333c3ec1 100644
---- a/northd/ovn-northd.c
-+++ b/northd/ovn-northd.c
-@@ -1111,10 +1111,18 @@ main(int argc, char *argv[])
-
- /* Make sure we don't bump the next_cfg when we shouldn't.
- * This should prevent ovn-nbctl sync calls to return before
-- * the SB updates are actually done. */
-+ * the SB updates are actually done.
-+ *
-+ * Track that the abort was intentional so we can distinguish
-+ * it from a real commit failure below; otherwise the abort
-+ * would feed back into the force-recompute path, creating a
-+ * busy loop whenever NB.nb_cfg keeps advancing but the
-+ * engine produces no SB activity. */
-+ bool ovnsb_txn_aborted_intentionally = false;
- if (!activity && ovnsb_txn &&
- ovnsb_idl_loop.cur_cfg != ovnsb_idl_loop.next_cfg) {
- ovsdb_idl_txn_abort(ovnsb_txn);
-+ ovnsb_txn_aborted_intentionally = true;
- }
-
- /* If there are any errors, we force a full recompute in order
-@@ -1125,7 +1133,8 @@ main(int argc, char *argv[])
- inc_proc_northd_force_recompute_immediate();
- }
-
-- if (!ovsdb_idl_loop_commit_and_wait(&ovnsb_idl_loop)) {
-+ if (!ovsdb_idl_loop_commit_and_wait(&ovnsb_idl_loop) &&
-+ !ovnsb_txn_aborted_intentionally) {
- VLOG_INFO("OVNSB commit failed, "
- "force recompute next time.");
- inc_proc_northd_force_recompute_immediate();
-diff --git a/ovn-nb.xml b/ovn-nb.xml
-index 954ed11661..64fdf25e4e 100644
---- a/ovn-nb.xml
-+++ b/ovn-nb.xml
-@@ -2749,7 +2749,7 @@ or
-
- <column name="options" key="distributed">
- Option enables distributed load balancing across compute nodes,
-- ensuring traffic is always routed to local backends — eliminating
-+ ensuring traffic is always routed to local backends, eliminating
- east-west traffic between nodes.
- Required configuration: <ref column="ip_port_mappings"/>.
- NOTE: The addressing of the underlay network must not overlap with the
-diff --git a/tests/multinode-macros.at b/tests/multinode-macros.at
-index 596de4c3af..4a74d51277 100644
---- a/tests/multinode-macros.at
-+++ b/tests/multinode-macros.at
-@@ -41,28 +41,6 @@ m4_define([M_START_TCPDUMP],
- ]
- )
+ NETNS_START_TCPDUMP([client], [-l -U -i client -vnne udp], [client])
+ NETNS_START_TCPDUMP([server], [-l -U -i server -vnne udp], [server])
--m4_define([M_START_TCPDUMPS_RECURSIVE_], [
-- m4_if(m4_eval($# > 3), [1], [dnl
-- names="$names $3"
-- echo "Running podman exec $1 tcpdump -l $2 >$3.tcpdump 2>$3.stderr"
-- podman exec $1 tcpdump -l $2 >$3.tcpdump 2>$3.stderr &
-- echo "podman exec $1 ps -ef | grep -v grep | grep tcpdump && podman exec $1 killall tcpdump" >> cleanup
-- M_START_TCPDUMPS_RECURSIVE_(m4_shift(m4_shift(m4_shift($@))))
-- ])
-- ]
--)
--
--# Start Multiple tcpdump. Useful to speed up when many tcpdump
--# must be started as waiting for "listening" takes usually 1 second.
--m4_define([M_START_TCPDUMPS],
-- [
-- names=""
-- M_START_TCPDUMPS_RECURSIVE_($@)
-- for name in $names; do
-- OVS_WAIT_UNTIL([grep -q "listening" ${name}.stderr])
-- done
-- ]
--)
+ check ovs-appctl dpctl/flush-conntrack
- # M_FORMAT_CT([ip-addr])
- #
-@@ -502,30 +480,6 @@ m_is_fedora() {
- m_central_as grep -qi fedora /etc/os-release
+-NS_CHECK_EXEC([client], [nc -z -u 192.168.100.20 4242], [ignore], [ignore], [ignore])
++NS_CHECK_EXEC([client], [ncat -z -u 192.168.100.20 4242], [ignore], [ignore], [ignore])
+ OVS_WAIT_UNTIL([grep -q "192.168.200.10" server.tcpdump])
+
+-NS_CHECK_EXEC([client], [nc -z -u 1000::20 4243])
++NS_CHECK_EXEC([client], [ncat -z -u 1000::20 4243])
+ OVS_WAIT_UNTIL([grep -q "2000::10" server.tcpdump])
+
+ zone_id=$(ovn-appctl -t ovn-controller ct-zone-list | grep lr_dnat | cut -d ' ' -f2)
+@@ -12704,6 +12709,7 @@ check ovn-nbctl --wait=hv sync
+
+ DHCP_TEST_DIR="/tmp/dhcp-test"
+ rm -rf $DHCP_TEST_DIR
++on_exit "rm -rf $DHCP_TEST_DIR"
+ mkdir $DHCP_TEST_DIR
+ cat > $DHCP_TEST_DIR/dhcpd.conf <<EOF
+ subnet 172.16.1.0 netmask 255.255.255.0 {
+@@ -12723,32 +12729,22 @@ subnet 192.168.2.0 netmask 255.255.255.0 {
+ max-lease-time 120;
}
+ EOF
+-cat > $DHCP_TEST_DIR/dhclien.conf <<EOF
++cat > $DHCP_TEST_DIR/dhclient.conf <<EOF
+ timeout 2
+ EOF
+
+-touch $DHCP_TEST_DIR/dhcpd.leases
+-chown root:dhcpd $DHCP_TEST_DIR $DHCP_TEST_DIR/dhcpd.leases
+-chmod 775 $DHCP_TEST_DIR
+-chmod 664 $DHCP_TEST_DIR/dhcpd.leases
++NETNS_START_DHCPD([server], [s1], [$DHCP_TEST_DIR/dhcpd.conf], [-4], [-nvv])
--# Run ovs-vsctl using Host socket
--host_ovs_vsctl() {
-- # Discover host OVS socket on first call
-- if [[ -z "$HOST_OVS_SOCK" ]]; then
-- for sock in /run/openvswitch/db.sock /var/run/openvswitch/db.sock /usr/local/var/run/openvswitch/db.sock; do
-- if [[ -S "$sock" ]]; then
-- HOST_OVS_SOCK=$sock
-- break
-- fi
-- done
-- # Fallback on unusual prefix: discover from running process
-- if [[ -z "$HOST_OVS_SOCK" ]]; then
-- HOST_OVS_SOCK=$(ps aux | grep '[o]vsdb-server' | grep -oP 'punix:\K[^, ]+' | while read s; do
-- [[ -S "$s" ]] && [[ "$s" != *"$OVS_RUNDIR"* ]] && echo "$s" && break
-- done)
-- fi
-- if [[ -z "$HOST_OVS_SOCK" ]]; then
-- echo "ERROR: Could not find host OVS socket" >&2
-- AT_FAIL_IF([:])
-- fi
-- fi
-- ovs-vsctl --db=unix:$HOST_OVS_SOCK "$@"
--}
-
- # M_START_L4_SERVER([fake_node], [namespace], [ip_addr], [port], [reply_string], [pidfile])
- #
- # Helper to properly start l4 server in inside 'fake_node''s namespace'.
-diff --git a/tests/multinode.at b/tests/multinode.at
-index d07660797c..069f2a677d 100644
---- a/tests/multinode.at
-+++ b/tests/multinode.at
-@@ -2986,42 +2986,42 @@ AT_CLEANUP
+-NETNS_DAEMONIZE([server], [dhcpd -4 -f -cf $DHCP_TEST_DIR/dhcpd.conf s1 > dhcpd.log 2>&1], [dhcpd.pid])
+-
+-NS_CHECK_EXEC([server], [tcpdump -l -nvv -i s1 udp > pkt.pcap 2>tcpdump_err &])
+-OVS_WAIT_UNTIL([grep "listening" tcpdump_err])
+-on_exit 'kill $(pidof tcpdump)'
+-
+-NS_CHECK_EXEC([sw01], [dhclient -1 -q -lf $DHCP_TEST_DIR/dhclient-sw01.lease -pf $DHCP_TEST_DIR/dhclient-sw01.pid -cf $DHCP_TEST_DIR/dhclien.conf sw01])
+-NS_CHECK_EXEC([sw11], [dhclient -1 -q -lf $DHCP_TEST_DIR/dhclient-sw11.lease -pf $DHCP_TEST_DIR/dhclient-sw11.pid -cf $DHCP_TEST_DIR/dhclien.conf sw11])
++NS_CHECK_EXEC([sw01], [dhclient -1 -q -lf $DHCP_TEST_DIR/dhclient-sw01.lease -pf $DHCP_TEST_DIR/dhclient-sw01.pid -cf $DHCP_TEST_DIR/dhclient.conf sw01])
++NS_CHECK_EXEC([sw11], [dhclient -1 -q -lf $DHCP_TEST_DIR/dhclient-sw11.lease -pf $DHCP_TEST_DIR/dhclient-sw11.pid -cf $DHCP_TEST_DIR/dhclient.conf sw11])
- AT_SETUP([HA: Check for missing garp on leader when BFD goes back up])
- # Network topology
--# ┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
--# │ │
--# │ ┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐ │
--# │ │ ovn-chassis-1 │ │ ovn-chassis-2 │ │ ovn-gw-1 │ │ ovn-gw-2 │ │ ovn-chassis-3 │ │
--# │ └─────────┬─────────┘ └─────────┬─────────┘ └───────────────────┘ └───────────────────┘ └───────────────────┘ │
--# │ ┌─────────┴─────────┐ ┌─────────┴─────────┐ │
--# │ │ inside1 │ │ inside2 │ │
--# │ │ 192.168.1.1/24 │ │ 192.168.1.2/24 │ │
--# │ └─────────┬─────────┘ └─────────┬─────────┘ │
--# │ ┌─┴────────────────────────┴─┐ │
--# │ │ inside │ │
--# │ └──────────────┬─────────────┘ │
--# │ ┌─────────┴─────────┐ │
--# │ │ 192.168.1.254 │ │
--# │ │ R1 │ │
--# │ │ 192.168.0.254 │ │
--# │ └─────────┬─────────┘ │
--# │ └------eth1---------------------------┬--------eth1-----------┐ │
--# │ ┌──────────┴────────┐ ┌─────────┴─────────┐ │
--# │ │ 192.168.1.254 │ │ 192.168.1.254 │ │
--# │ │ R1 │ │ R1 │ │
--# │ │ 192.168.0.254 │ │ 192.168.0.254 │ │
--# │ └─────────┬─────────┘ └─────────┬─────────┘ │
--# │ │ │ ┌───────────────────┐ │
--# │ ┌─────────┴─────────┐ ┌─────────┴─────────┐ │ 192.168.0.1 │ │
--# │ │ outside │ │ outside │ │ ext1 │ │
--# │ └─────────┬─────────┘ └─────────┬─────────┘ └─────────┬─────────┘ │
--# │ ┌─────────┴─────────┐ ┌─────────┴─────────┐ ┌─────────┴─────────┐ │
--# │ │ ln-outside │ │ ln-outside │ │ ln-ext1 │ │
--# │ └─────────┬─────────┘ └─────────┬─────────┘ └─────────┬─────────┘ │
--# │ ┌─────────┴─────────┐ ┌─────────┴─────────┐ ┌─────────┴─────────┐ │
--# │ │ br-ex │ │ br-ex │ │ br-ex │ │
--# │ └─────────┬─────────┘ └─────────┬─────────┘ └─────────┬─────────┘ │
--# │ └---------eth2-----------┴-------eth2-------------┘ │
--# │ │
--# └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
-+# ┌────────────────────────────────────────────────────────────────────────────────────────────────────────┐
-+# │ │
-+# │ ┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐ │
-+# │ │ ovn-chassis-1 │ │ ovn-gw-1 │ │ ovn-gw-2 │ │ ovn-chassis-2 │ │
-+# │ └─────────┬─────────┘ └───────────────────┘ └───────────────────┘ └───────────────────┘ │
-+# │ ┌─────────┴─────────┐ │
-+# │ │ inside1 │ │
-+# │ │ 192.168.1.1/24 │ │
-+# │ └─────────┬─────────┘ │
-+# │ ┌─────────┴─────────┐ │
-+# │ │ inside │ │
-+# │ └─────────┬─────────┘ │
-+# │ ┌─────────┴─────────┐ │
-+# │ │ 192.168.1.254 │ │
-+# │ │ R1 │ │
-+# │ │ 192.168.0.254 │ │
-+# │ └─────────┬─────────┘ │
-+# │ └------eth1---------------┬--------eth1-----------┐ │
-+# │ ┌──────────┴────────┐ ┌─────────┴─────────┐ │
-+# │ │ 192.168.1.254 │ │ 192.168.1.254 │ │
-+# │ │ R1 │ │ R1 │ │
-+# │ │ 192.168.0.254 │ │ 192.168.0.254 │ │
-+# │ └─────────┬─────────┘ └─────────┬─────────┘ │
-+# │ │ │ ┌───────────────────┐ │
-+# │ ┌─────────┴─────────┐ ┌─────────┴─────────┐ │ 192.168.0.1 │ │
-+# │ │ outside │ │ outside │ │ ext1 │ │
-+# │ └─────────┬─────────┘ └─────────┬─────────┘ └─────────┬─────────┘ │
-+# │ ┌─────────┴─────────┐ ┌─────────┴─────────┐ ┌─────────┴─────────┐ │
-+# │ │ ln-outside │ │ ln-outside │ │ ln-ext1 │ │
-+# │ └─────────┬─────────┘ └─────────┬─────────┘ └─────────┬─────────┘ │
-+# │ ┌─────────┴─────────┐ ┌─────────┴─────────┐ ┌─────────┴─────────┐ │
-+# │ │ br-ex │ │ br-ex │ │ br-ex │ │
-+# │ └─────────┬─────────┘ └─────────┬─────────┘ └─────────┬─────────┘ │
-+# │ └---------eth2-----------┴-------eth2-------------┘ │
-+# │ │
-+# └────────────────────────────────────────────────────────────────────────────────────────────────────────┘
+ OVS_WAIT_UNTIL([
+- total_pkts=$(cat pkt.pcap | wc -l)
++ total_pkts=$(grep "BOOTP/DHCP" server.tcpdump | wc -l)
+ test ${total_pkts} -ge 8
+ ])
- # The goal of this test is the check that GARP are properly generated by higest priority traffic when
- # BFD goes down, and back up, and this whether the BFD event is due either to some bfd packet lost
-@@ -3030,12 +3030,6 @@ AT_SETUP([HA: Check for missing garp on leader when BFD goes back up])
- # So gw3 should in this test neither send garp or receive packets.
- #
- # Enable vconn so we can check the GARP from a log perspective.
--on_exit "podman exec ovn-gw-1 ovn-appctl vlog/set info"
--on_exit "podman exec ovn-gw-1 ovn-appctl vlog/enable-rate-limit"
--on_exit "podman exec ovn-gw-2 ovn-appctl vlog/set info"
--on_exit "podman exec ovn-gw-2 ovn-appctl vlog/enable-rate-limit"
--on_exit "podman exec ovn-gw-3 ovn-appctl vlog/set info"
--on_exit "podman exec ovn-gw-3 ovn-appctl vlog/enable-rate-limit"
- m_as ovn-gw-1 ovn-appctl vlog/set vconn:dbg
- m_as ovn-gw-2 ovn-appctl vlog/set vconn:dbg
- m_as ovn-gw-3 ovn-appctl vlog/set vconn:dbg
-@@ -3043,17 +3037,12 @@ m_as ovn-gw-1 ovn-appctl vlog/disable-rate-limit
- m_as ovn-gw-2 ovn-appctl vlog/disable-rate-limit
- m_as ovn-gw-3 ovn-appctl vlog/disable-rate-limit
+-on_exit 'kill `cat $DHCP_TEST_DIR/dhclient-sw01.pid` &&
+-kill `cat $DHCP_TEST_DIR/dhclient-sw11.pid` && rm -rf $DHCP_TEST_DIR'
++on_exit "test -e $DHCP_TEST_DIR/dhclient-sw01.pid && kill \`cat $DHCP_TEST_DIR/dhclient-sw01.pid\` 2>/dev/null
++test -e $DHCP_TEST_DIR/dhclient-sw11.pid && kill \`cat $DHCP_TEST_DIR/dhclient-sw11.pid\` 2>/dev/null"
--# Decrease revalidation time on ovs switch simulating ToR.
--check host_ovs_vsctl set Open_vSwitch . other_config:max-revalidator=100
--on_exit "check host_ovs_vsctl remove Open_vSwitch . other_config max-revalidator"
--
- check_fake_multinode_setup
+ NS_CHECK_EXEC([sw01], [ip addr show sw01 | grep -oP '(?<=inet\s)\d+(\.\d+){3}'], [0], [dnl
+ 192.168.1.10
+@@ -12827,10 +12823,10 @@ ADD_VETH(vm2, vm2, br-int, "192.168.200.20/24", "f0:00:0f:01:02:04", "192.168.20
+ ADD_NAMESPACES(vm3)
+ ADD_VETH(vm3, vm3, br-int, "192.168.200.30/24", "f0:00:0f:01:02:05", "192.168.200.1")
- # Delete the multinode NB and OVS resources before starting the test.
- cleanup_multinode_resources
+-NETNS_DAEMONIZE([vm0], [nc -l -k 192.168.200.10 4242], [server0.pid])
+-NETNS_DAEMONIZE([vm1], [nc -l -k 192.168.100.10 4242], [server1.pid])
+-NETNS_DAEMONIZE([vm2], [nc -l -k 192.168.200.20 4242], [server2.pid])
+-NETNS_DAEMONIZE([vm3], [nc -l -k 192.168.200.30 4242], [server3.pid])
++NETNS_DAEMONIZE([vm0], [ncat -l -k 192.168.200.10 4242], [server0.pid])
++NETNS_DAEMONIZE([vm1], [ncat -l -k 192.168.100.10 4242], [server1.pid])
++NETNS_DAEMONIZE([vm2], [ncat -l -k 192.168.200.20 4242], [server2.pid])
++NETNS_DAEMONIZE([vm3], [ncat -l -k 192.168.200.30 4242], [server3.pid])
- ip_ch1=$(m_as ovn-chassis-1 ip a show dev eth1 | grep "inet " | awk '{print $2}'| cut -d '/' -f1)
--ip_ch2=$(m_as ovn-chassis-2 ip a show dev eth1 | grep "inet " | awk '{print $2}'| cut -d '/' -f1)
- ip_gw1=$(m_as ovn-gw-1 ip a show dev eth1 | grep "inet " | awk '{print $2}'| cut -d '/' -f1)
- ip_gw2=$(m_as ovn-gw-2 ip a show dev eth1 | grep "inet " | awk '{print $2}'| cut -d '/' -f1)
- ip_gw3=$(m_as ovn-gw-3 ip a show dev eth1 | grep "inet " | awk '{print $2}'| cut -d '/' -f1)
-@@ -3061,35 +3050,25 @@ ip_gw3=$(m_as ovn-gw-3 ip a show dev eth1 | grep "inet " | awk '{print $2}'| cut
- from_gw1_to_gw2=$(m_as ovn-gw-1 ovs-vsctl --bare --columns=name find interface options:remote_ip=$ip_gw2)
- from_gw1_to_gw3=$(m_as ovn-gw-1 ovs-vsctl --bare --columns=name find interface options:remote_ip=$ip_gw3)
- from_gw1_to_ch1=$(m_as ovn-gw-1 ovs-vsctl --bare --columns=name find interface options:remote_ip=$ip_ch1)
--from_gw1_to_ch2=$(m_as ovn-gw-1 ovs-vsctl --bare --columns=name find interface options:remote_ip=$ip_ch2)
- from_gw2_to_gw1=$(m_as ovn-gw-2 ovs-vsctl --bare --columns=name find interface options:remote_ip=$ip_gw1)
- from_gw2_to_gw3=$(m_as ovn-gw-2 ovs-vsctl --bare --columns=name find interface options:remote_ip=$ip_gw3)
- from_gw2_to_ch1=$(m_as ovn-gw-2 ovs-vsctl --bare --columns=name find interface options:remote_ip=$ip_ch1)
--from_gw2_to_ch2=$(m_as ovn-gw-2 ovs-vsctl --bare --columns=name find interface options:remote_ip=$ip_ch2)
- from_ch1_to_gw1=$(m_as ovn-chassis-1 ovs-vsctl --bare --columns=name find interface options:remote_ip=$ip_gw1)
- from_ch1_to_gw2=$(m_as ovn-chassis-1 ovs-vsctl --bare --columns=name find interface options:remote_ip=$ip_gw2)
--from_ch2_to_gw1=$(m_as ovn-chassis-2 ovs-vsctl --bare --columns=name find interface options:remote_ip=$ip_gw1)
--from_ch2_to_gw2=$(m_as ovn-chassis-2 ovs-vsctl --bare --columns=name find interface options:remote_ip=$ip_gw2)
+ check_snat() {
+ check ovn-nbctl lr-nat-del lr
+@@ -12841,9 +12837,9 @@ check_snat() {
- m_as ovn-chassis-1 ip link del hv1-vif1-p
--m_as ovn-chassis-2 ip link del hv2-vif1-p
--m_as ovn-chassis-3 ip link del ext1-p
-+m_as ovn-chassis-2 ip link del ext1-p
+ check ovs-appctl dpctl/flush-conntrack
- OVS_WAIT_UNTIL([m_as ovn-chassis-1 ip link show | grep -q genev_sys])
- OVS_WAIT_UNTIL([m_as ovn-chassis-2 ip link show | grep -q genev_sys])
--OVS_WAIT_UNTIL([m_as ovn-chassis-3 ip link show | grep -q genev_sys])
- OVS_WAIT_UNTIL([m_as ovn-gw-1 ip link show | grep -q genev_sys])
- OVS_WAIT_UNTIL([m_as ovn-gw-2 ip link show | grep -q genev_sys])
- OVS_WAIT_UNTIL([m_as ovn-gw-3 ip link show | grep -q genev_sys])
+- NS_CHECK_EXEC([vm0], [nc -z 192.168.100.10 4242 -p 2000])
+- NS_CHECK_EXEC([vm0], [nc -z 192.168.100.10 4242 -p 2001])
+- NS_CHECK_EXEC([vm0], [nc -z 192.168.100.10 4242 -p 2002])
++ NS_CHECK_EXEC([vm0], [ncat -z 192.168.100.10 4242 -p 2000])
++ NS_CHECK_EXEC([vm0], [ncat -z 192.168.100.10 4242 -p 2001])
++ NS_CHECK_EXEC([vm0], [ncat -z 192.168.100.10 4242 -p 2002])
--# Use "aggressive" bfd parameters
--check multinode_nbctl set NB_Global . options:"bfd-min-rx"=500
--check multinode_nbctl set NB_Global . options:"bfd-min-tx"=100
- check multinode_nbctl ls-add inside
- check multinode_nbctl ls-add outside
- check multinode_nbctl ls-add ext
- check multinode_nbctl lsp-add inside inside1 -- lsp-set-addresses inside1 "f0:00:c0:a8:01:01 192.168.1.1"
--check multinode_nbctl lsp-add inside inside2 -- lsp-set-addresses inside2 "f0:00:c0:a8:01:02 192.168.1.2"
- check multinode_nbctl lsp-add ext ext1 -- lsp-set-addresses ext1 "00:00:c0:a8:00:01 192.168.0.1"
+ snat_id=$(ovn-appctl -t ovn-controller ct-zone-list | grep lr_snat | cut -d ' ' -f2)
+ AT_CHECK_UNQUOTED([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(192.168.100.10) | grep "zone=$snat_id"], [0], [dnl
+@@ -12862,9 +12858,9 @@ check_dnat() {
- multinode_nbctl create Logical_Router name=R1
-@@ -3121,14 +3100,12 @@ m_as ovn-gw-3 ovs-vsctl remove open . external_ids garp-max-timeout-sec
+ check ovs-appctl dpctl/flush-conntrack
- m_as ovn-chassis-1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-ex
- m_as ovn-chassis-2 ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-ex
--m_as ovn-chassis-3 ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-ex
- m_as ovn-gw-1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-ex
- m_as ovn-gw-2 ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-ex
- m_as ovn-gw-3 ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-ex
+- NS_CHECK_EXEC([vm1], [nc -z 192.168.100.100 4242 -p 2000])
+- NS_CHECK_EXEC([vm1], [nc -z 192.168.100.100 4242 -p 2001])
+- NS_CHECK_EXEC([vm1], [nc -z 192.168.100.100 4242 -p 2002])
++ NS_CHECK_EXEC([vm1], [ncat -z 192.168.100.100 4242 -p 2000])
++ NS_CHECK_EXEC([vm1], [ncat -z 192.168.100.100 4242 -p 2001])
++ NS_CHECK_EXEC([vm1], [ncat -z 192.168.100.100 4242 -p 2002])
- m_as ovn-chassis-1 /data/create_fake_vm.sh inside1 hv1-vif1 f0:00:c0:a8:01:01 1500 192.168.1.1 24 192.168.1.254 2000::1/64 2000::a
--m_as ovn-chassis-2 /data/create_fake_vm.sh inside2 hv2-vif1 f0:00:c0:a8:01:02 1500 192.168.1.2 24 192.168.1.254 2000::2/64 2000::a
--m_as ovn-chassis-3 /data/create_fake_vm.sh ext1 ext1 00:00:c0:a8:00:01 1500 192.168.0.1 24 192.168.0.254 1000::3/64 1000::a
-+m_as ovn-chassis-2 /data/create_fake_vm.sh ext1 ext1 00:00:c0:a8:00:01 1500 192.168.0.1 24 192.168.0.254 1000::3/64 1000::a
+ dnat_id=$(ovn-appctl -t ovn-controller ct-zone-list | grep lr_dnat | cut -d ' ' -f2)
+ AT_CHECK_UNQUOTED([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(192.168.100.100) | grep "zone=$dnat_id"], [0], [dnl
+@@ -12953,10 +12949,10 @@ ADD_VETH(vm2, vm2, br-int, "2000::20/64", "f0:00:0f:01:02:04", "2000::1", "nodad
+ ADD_NAMESPACES(vm3)
+ ADD_VETH(vm3, vm3, br-int, "2000::30/64", "f0:00:0f:01:02:05", "2000::1", "nodad")
- # There should be one ha_chassis_group with the name "R1_outside"
- m_check_row_count HA_Chassis_Group 1 name=R1_outside
-@@ -3183,67 +3160,53 @@ for chassis in $from_ch1_to_gw1 $from_ch1_to_gw2; do
- wait_bfd_enabled ovn-chassis-1 $chassis
- done
+-NETNS_DAEMONIZE([vm0], [nc -lk 2000::10 4242 > /dev/null], [server0.pid])
+-NETNS_DAEMONIZE([vm1], [nc -lk 1000::10 4242 > /dev/null], [server1.pid])
+-NETNS_DAEMONIZE([vm2], [nc -lk 2000::20 4242 > /dev/null], [server2.pid])
+-NETNS_DAEMONIZE([vm3], [nc -lk 2000::30 4242 > /dev/null], [server3.pid])
++NETNS_DAEMONIZE([vm0], [ncat -lk 2000::10 4242 > /dev/null], [server0.pid])
++NETNS_DAEMONIZE([vm1], [ncat -lk 1000::10 4242 > /dev/null], [server1.pid])
++NETNS_DAEMONIZE([vm2], [ncat -lk 2000::20 4242 > /dev/null], [server2.pid])
++NETNS_DAEMONIZE([vm3], [ncat -lk 2000::30 4242 > /dev/null], [server3.pid])
--# check BFD enablement on tunnel ports from ovn-chassis-2 ###########
--for chassis in $from_ch2_to_gw1 $from_ch2_to_gw2; do
-- echo "checking ovn-chassis-2 -> $chassis"
-- wait_bfd_enabled ovn-chassis-2 $chassis
--done
--
- # Make sure there is no nft table left. Do not use nft directly as might not be installed in container.
- gw1_pid=$(podman inspect -f '{{.State.Pid}}' ovn-gw-1)
- nsenter --net=/proc/$gw1_pid/ns/net nft list tables | grep ovn-test && nsenter --net=/proc/$gw1_pid/ns/net nft delete table ip ovn-test
--on_exit "if [[ -d "/proc/$gw1_pid" ]]; then nsenter --net=/proc/$gw1_pid/ns/net nft list tables | grep ovn-test && nsenter --net=/proc/$gw1_pid/ns/net nft delete table ip ovn-test; fi"
-+on_exit "nsenter --net=/proc/$gw1_pid/ns/net nft list tables | grep ovn-test && nsenter --net=/proc/$gw1_pid/ns/net nft delete table ip ovn-test"
+ check_snat() {
+ check ovn-nbctl lr-nat-del lr
+@@ -12967,9 +12963,9 @@ check_snat() {
--for chassis in $from_gw1_to_gw2 $from_gw1_to_gw3 $from_gw1_to_ch1 $from_gw1_to_ch2; do
-+for chassis in $from_gw1_to_gw2 $from_gw1_to_gw3 $from_gw1_to_ch1; do
- wait_bfd_up ovn-gw-1 $chassis
- done
--for chassis in $from_gw2_to_gw1 $from_gw2_to_gw3 $from_gw2_to_ch1 $from_gw2_to_ch2; do
-+for chassis in $from_gw2_to_gw1 $from_gw2_to_gw3 $from_gw2_to_ch1; do
- wait_bfd_up ovn-gw-2 $chassis
- done
- for chassis in $from_ch1_to_gw1 $from_ch1_to_gw2; do
- wait_bfd_up ovn-chassis-1 $chassis
- done
--for chassis in $from_ch2_to_gw1 $from_ch2_to_gw2; do
-- wait_bfd_up ovn-chassis-2 $chassis
--done
+ check ovs-appctl dpctl/flush-conntrack
- m_wait_row_count Port_Binding 1 logical_port=cr-R1_outside chassis=$gw1_chassis
- check multinode_nbctl --wait=hv sync
+- NS_CHECK_EXEC([vm0], [nc -z 1000::10 4242 -p 2000])
+- NS_CHECK_EXEC([vm0], [nc -z 1000::10 4242 -p 2001])
+- NS_CHECK_EXEC([vm0], [nc -z 1000::10 4242 -p 2002])
++ NS_CHECK_EXEC([vm0], [ncat -z 1000::10 4242 -p 2000])
++ NS_CHECK_EXEC([vm0], [ncat -z 1000::10 4242 -p 2001])
++ NS_CHECK_EXEC([vm0], [ncat -z 1000::10 4242 -p 2002])
- start_tcpdump() {
- echo "$(date +%H:%M:%S.%03N) Starting tcpdump"
-- M_START_TCPDUMPS([ovn-chassis-1], [-neei hv1-vif1-p], [ch1],
-- [ovn-chassis-2], [-neei hv2-vif1-p], [ch2],
-- [ovn-chassis-3], [-neei eth2], [ch3],
-- [ovn-gw-1], [-neei eth2], [gw1],
-- [ovn-gw-1], [-neei eth2 -Q out], [gw1_out],
-- [ovn-gw-2], [-neei eth2], [gw2],
-- [ovn-gw-2], [-neei eth2 -Q out], [gw2_out],
-- [ovn-gw-3], [-neei eth2], [gw3],
-- [ovn-gw-3], [-neei eth2 -Q out], [gw3_out],
-- [ovn-gw-1], [-neei eth1], [gw1_eth1],
-- [ovn-gw-2], [-neei eth1], [gw2_eth1],
-- [ovn-chassis-1], [-neei eth1], [ch1_eth1],
-- [ovn-chassis-2], [-neei eth1], [ch2_eth1])
-+ M_START_TCPDUMP([ovn-chassis-1], [-neei hv1-vif1-p], [ch1])
-+ M_START_TCPDUMP([ovn-chassis-2], [-neei eth2], [ch2])
-+ M_START_TCPDUMP([ovn-gw-1], [-neei eth2], [gw1])
-+ M_START_TCPDUMP([ovn-gw-1], [-neei eth2 -Q out], [gw1_out])
-+ M_START_TCPDUMP([ovn-gw-2], [-neei eth2], [gw2])
-+ M_START_TCPDUMP([ovn-gw-2], [-neei eth2 -Q out], [gw2_out])
-+ M_START_TCPDUMP([ovn-gw-3], [-neei eth2], [gw3])
-+ M_START_TCPDUMP([ovn-gw-3], [-neei eth2 -Q out], [gw3_out])
- }
+ snat_id=$(ovn-appctl -t ovn-controller ct-zone-list | grep lr_snat | cut -d ' ' -f2)
+ AT_CHECK_UNQUOTED([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(1000::10) | grep "zone=$snat_id"], [0], [dnl
+@@ -12988,9 +12984,9 @@ check_dnat() {
- stop_tcpdump() {
- echo "$(date +%H:%M:%S.%03N) Stopping tcpdump"
-- m_kill 'ovn-gw-1 ovn-gw-2 ovn-gw-3 ovn-chassis-1 ovn-chassis-2 ovn-chassis-3' tcpdump
-+ m_kill 'ovn-gw-1 ovn-gw-2 ovn-gw-3 ovn-chassis-1 ovn-chassis-2' tcpdump
- }
+ check ovs-appctl dpctl/flush-conntrack
--# Send packets from ovn-chassis-3 (ext1) to ovn-chassis-1
-+# Send packets from chassis2 (ext1) to chassis1
- send_background_packets() {
- echo "$(date +%H:%M:%S.%03N) Sending packets in Background"
- start_tcpdump
-- M_NS_DAEMONIZE([ovn-chassis-3], [ext1], [ping -f -i 0.1 192.168.1.1], [ping.pid])
-+ M_NS_DAEMONIZE([ovn-chassis-2], [ext1], [ping -f -i 0.1 192.168.1.1], [ping.pid])
- }
+- NS_CHECK_EXEC([vm1], [nc -z 1000::100 4242 -p 2000])
+- NS_CHECK_EXEC([vm1], [nc -z 1000::100 4242 -p 2001])
+- NS_CHECK_EXEC([vm1], [nc -z 1000::100 4242 -p 2002])
++ NS_CHECK_EXEC([vm1], [ncat -z 1000::100 4242 -p 2000])
++ NS_CHECK_EXEC([vm1], [ncat -z 1000::100 4242 -p 2001])
++ NS_CHECK_EXEC([vm1], [ncat -z 1000::100 4242 -p 2002])
- stop_sending_background_packets() {
- echo "$(date +%H:%M:%S.%03N) Stopping Background process"
- m_as ovn-chassis-1 ps -ef | grep -v grep | grep -q ping && \
- m_as ovn-chassis-1 echo "Stopping ping on ovn-chassis-1" && killall ping
-- m_as ovn-chassis-3 ps -ef | grep -v grep | grep -q ping && \
-+ m_as ovn-chassis-2 ps -ef | grep -v grep | grep -q ping && \
- m_as ovn-chassis-2 echo "Stopping ping on ovn-chassis-2" && killall ping
- stop_tcpdump
- }
-@@ -3253,8 +3216,8 @@ check_for_new_garps() {
- expecting_garp=$2
- n_new_garps=$(cat ${hv}_out.tcpdump | grep -c "f0:00:c0:a8:00:fe > Broadcast, ethertype ARP (0x0806), length 42: Request who-has 192.168.0.254 tell 192.168.0.254, length 28")
+ dnat_id=$(ovn-appctl -t ovn-controller ct-zone-list | grep lr_dnat | cut -d ' ' -f2)
+ AT_CHECK_UNQUOTED([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(1000::100) | grep "zone=$dnat_id"], [0], [dnl
+@@ -13141,15 +13137,19 @@ sock.close()
+ ])
-- if [[ "$expecting_garp" == "true" ]]; then
-- echo "$(date +%H:%M:%S.%03N) Waiting/checking for garp from $hv - Starting with $n_new_garps"
-+ if [ "$expecting_garp" == "true" ]; then
-+ AS_BOX([$(date +%H:%M:%S.%03N) Waiting/checking for garp from $hv - Starting with $n_new_garps])
- OVS_WAIT_UNTIL([
- n_garps=$n_new_garps
- n_new_garps=$(cat ${hv}_out.tcpdump | grep -c "f0:00:c0:a8:00:fe > Broadcast, ethertype ARP (0x0806), length 42: Request who-has 192.168.0.254 tell 192.168.0.254, length 28")
-@@ -3262,7 +3225,7 @@ check_for_new_garps() {
- test "$n_garps" -ne "$n_new_garps"
- ])
- else
-- echo "$(date +%H:%M:%S.%03N) Checking no garp from ${hv}"
-+ AS_BOX([$(date +%H:%M:%S.%03N) Checking no garp from ${hv}])
- # Waiting a few seconds to get a chance to see unexpected garps.
- sleep 3
- n_garps=$(cat ${hv}_out.tcpdump | grep -c "f0:00:c0:a8:00:fe > Broadcast, ethertype ARP (0x0806), length 42: Request who-has 192.168.0.254 tell 192.168.0.254, length 28")
-@@ -3278,8 +3241,8 @@ check_for_new_echo_pkts() {
- n_new_echo_req=$(cat ${hv}.tcpdump | grep -c "$mac_src > $mac_dst, ethertype IPv4 (0x0800), length 98: 192.168.0.1 > 192.168.1.1: ICMP echo request")
- n_new_echo_rep=$(cat ${hv}.tcpdump | grep -c "$mac_dst > $mac_src, ethertype IPv4 (0x0800), length 98: 192.168.1.1 > 192.168.0.1: ICMP echo reply")
+ AT_DATA([server.py], [dnl
++import socket
+ import sys
+ import time
-- if [[ "$expecting_pkts" == "true" ]]; then
-- echo "$(date +%H:%M:%S.%03N) Waiting/checking for echo pkts through ${hv}"
-+ if [ "$expecting_pkts" == "true" ]; then
-+ AS_BOX([$(date +%H:%M:%S.%03N) Waiting/checking for echo pkts through ${hv}])
- echo "Starting with $n_new_echo_req requests and $n_new_echo_rep replies so far on ${hv}."
- OVS_WAIT_UNTIL([
- n_echo_req=$n_new_echo_req
-@@ -3290,7 +3253,7 @@ check_for_new_echo_pkts() {
- test "$n_echo_req" -ne "$n_new_echo_req" && test "$n_echo_rep" -ne "$n_new_echo_rep"
- ])
- else
-- echo "$(date +%H:%M:%S.%03N) Checking no pkts from ${hv}"
-+ AS_BOX([$(date +%H:%M:%S.%03N) Checking no pkts from ${hv}])
- # Waiting a few seconds to get a chance to see unexpected pkts.
- sleep 3
- n_echo_req=$(cat ${hv}.tcpdump | grep -c "$mac_src > $mac_dst, ethertype IPv4 (0x0800), length 98: 192.168.0.1 > 192.168.1.1: ICMP echo request")
-@@ -3308,44 +3271,22 @@ dump_statistics() {
- ch1_rep=$(grep -c "ICMP echo reply" ch1.tcpdump)
- ch2_req=$(grep -c "ICMP echo request" ch2.tcpdump)
- ch2_rep=$(grep -c "ICMP echo reply" ch2.tcpdump)
-- ch3_req=$(grep -c "ICMP echo request" ch3.tcpdump)
-- ch3_rep=$(grep -c "ICMP echo reply" ch3.tcpdump)
- gw1_req=$(grep -c "ICMP echo request" gw1.tcpdump)
- gw1_rep=$(grep -c "ICMP echo reply" gw1.tcpdump)
- gw2_req=$(grep -c "ICMP echo request" gw2.tcpdump)
- gw2_rep=$(grep -c "ICMP echo reply" gw2.tcpdump)
- gw3_req=$(grep -c "ICMP echo request" gw3.tcpdump)
- gw3_rep=$(grep -c "ICMP echo reply" gw3.tcpdump)
-- echo "$n1 claims in gw1, $n2 in gw2 and $n3 on gw3" >&2
-- echo "ch3_req=$ch3_req gw_req=($gw1_req + $gw2_req +$gw3_req) ch1_req=$ch1_req ch1_rep=$ch1_rep gw_rep=($gw1_rep + $gw2_rep + $gw3_rep) ch3_rep=$ch3_rep ch2=($ch2_req+$ch2_rep)" >&2
-- echo "$((ch3_req - ch3_rep))"
--}
--
--add_port() {
-- bridge=$1
-- interface=$2
-- address=$3
-- echo "Adding $bridge $interface $address"
--
-- pid=$(podman inspect -f '{{.State.Pid}}' ovn-gw-1)
-- ln -sf /proc/$pid/ns/net /var/run/netns/$pid
-- port=$(host_ovs_vsctl --data=bare --no-heading --columns=name find interface \
-- external_ids:container_id=ovn-gw-1 external_ids:container_iface="$interface")
-- port="${port:0:13}"
-- ip link add "${port}_l" type veth peer name "${port}_c"
-- ip link set "${port}_l" up
-- ip link set "${port}_c" netns $pid
-- ip netns exec $pid ip link set dev "${port}_c" name "$interface"
-- ip netns exec $pid ip link set "$interface" up
-- if [[ -n "$address" ]]; then
-- ip netns exec $pid ip addr add "$address" dev "$interface"
-- fi
-+ echo "$n1 claims in gw1, $n2 in gw2 and $n3 on gw3"
-+ echo "ch2_request=$ch2_req gw1_request=$gw1_req gw2_request=$gw2_req gw3_request=$gw3_req ch1_request=$ch1_req ch1_reply=$ch1_rep gw1_reply=$gw1_rep gw2_reply=$gw2_rep gw3_reply=$gw3_rep ch2_reply=$ch2_rep"
- }
+-line = sys.stdin.readline()
+-if "bye" in line:
+- time.sleep(1)
++port = int(sys.argv[[1]])
++sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
++sock.bind(("0.0.0.0", port))
--prepare() {
-+check_migration_between_gw1_and_gw2() {
-+ action=$1
- send_background_packets
-+
- # We make sure gw1 is leader since enough time that it generated all its garps.
-- echo $(date +%H:%M:%S.%03N) Waiting all garps sent by gw1
-+ AS_BOX([$(date +%H:%M:%S.%03N) Waiting all garps sent by gw1])
- n_new_garps=$(cat gw1_out.tcpdump | grep -c "f0:00:c0:a8:00:fe > Broadcast, ethertype ARP (0x0806), length 42: Request who-has 192.168.0.254 tell 192.168.0.254, length 28")
- OVS_WAIT_UNTIL([
- n_garps=$n_new_garps
-@@ -3361,269 +3302,130 @@ prepare() {
- check_for_new_echo_pkts gw2 "00:00:c0:a8:00:01" "f0:00:c0:a8:00:fe" "false"
- check_for_new_echo_pkts gw3 "00:00:c0:a8:00:01" "f0:00:c0:a8:00:fe" "false"
+-sys.stdout.write(line)
+-sys.stdout.flush()
++while True:
++ data, addr = sock.recvfrom(65535)
++ if b"bye" in data:
++ time.sleep(1)
++ sock.sendto(data, addr)
+ ])
-- # All packets should go through gw1, and none through gw2 or gw3.
-- check_packets "true" "false" "false" "true"
- flap_count_gw_1=$(m_as ovn-gw-1 ovs-vsctl get interface $from_gw1_to_gw2 bfd_status | sed 's/.*flap_count=\"\([[0-9]]*\).*/\1/g')
- flap_count_gw_2=$(m_as ovn-gw-2 ovs-vsctl get interface $from_gw2_to_gw1 bfd_status | sed 's/.*flap_count=\"\([[0-9]]*\).*/\1/g')
--}
+ dnl Wait for ovn-controller to catch up.
+@@ -13172,12 +13172,12 @@ dnl And wait for it to be up and running.
+ OVS_WAIT_UNTIL([ovs-ofctl dump-ipfix-flow br-int | grep -q '1 ids'])
--check_loss_after_flap()
--{
-- dead=$1
-- max_expected_loss=$2
-+ if [ test "$action" == "stop_bfd" ]; then
-+ AS_BOX([$(date +%H:%M:%S.%03N) Blocking bfd on gw1 (from $ip_gw1 to $ip_gw2)])
-+ nsenter --net=/proc/$gw1_pid/ns/net nft add table ip ovn-test
-+ nsenter --net=/proc/$gw1_pid/ns/net nft 'add chain ip ovn-test INPUT { type filter hook input priority 0; policy accept; }'
-+ # Drop BFD from gw-1 to gw-2: geneve port (6081), inner port 3784 (0xec8), Session state Up, Init, Down.
-+ nsenter --net=/proc/$gw1_pid/ns/net nft add rule ip ovn-test INPUT ip daddr $ip_gw1 ip saddr $ip_gw2 udp dport 6081 '@th,416,16 == 0x0ec8 @th,472,8 == 0xc0 counter drop'
-+ nsenter --net=/proc/$gw1_pid/ns/net nft add rule ip ovn-test INPUT ip daddr $ip_gw1 ip saddr $ip_gw2 udp dport 6081 '@th,416,16 == 0x0ec8 @th,472,8 == 0x80 counter drop'
-+ nsenter --net=/proc/$gw1_pid/ns/net nft add rule ip ovn-test INPUT ip daddr $ip_gw1 ip saddr $ip_gw2 udp dport 6081 '@th,416,16 == 0x0ec8 @th,472,8 == 0x40 counter drop'
-+
-+ # We do not check that packets go through gw2 as BFD between chassis-2 and gw1 is still up
-+ fi
-+
-+ if [ test "$action" == "kill_gw2" ]; then
-+ AS_BOX([$(date +%H:%M:%S.%03N) Killing gw2 ovn-controller])
-+ on_exit 'm_as ovn-gw-2 /usr/share/openvswitch/scripts/ovs-ctl status ||
-+ m_as ovn-gw-2 /usr/share/openvswitch/scripts/ovs-ctl start --system-id=ovn-gw-2'
-+ on_exit 'm_as ovn-gw-2 /usr/share/ovn/scripts/ovn-ctl status_controller ||
-+ m_as ovn-gw-2 /usr/share/ovn/scripts/ovn-ctl start_controller ${CONTROLLER_SSL_ARGS}'
-+
-+ m_as ovn-gw-2 kill -9 $(m_as ovn-gw-2 cat /run/ovn/ovn-controller.pid)
-+ m_as ovn-gw-2 kill -9 $(m_as ovn-gw-2 cat /run/openvswitch/ovs-vswitchd.pid)
-+ m_as ovn-gw-2 kill -9 $(m_as ovn-gw-2 cat /run/openvswitch/ovsdb-server.pid)
-+ # Also delete datapath (flows)
-+ m_as ovn-gw-2 ovs-dpctl del-dp system@ovs-system
-+ fi
-+
-+ if [ test "$action" == "kill_gw1" ]; then
-+ AS_BOX([$(date +%H:%M:%S.%03N) Killing gw1 ovn-controller])
-+ on_exit 'm_as ovn-gw-1 /usr/share/openvswitch/scripts/ovs-ctl status ||
-+ m_as ovn-gw-1 /usr/share/openvswitch/scripts/ovs-ctl start --system-id=ovn-gw-1'
-+ on_exit 'm_as ovn-gw-1 /usr/share/ovn/scripts/ovn-ctl status_controller ||
-+ m_as ovn-gw-1 /usr/share/ovn/scripts/ovn-ctl start_controller ${CONTROLLER_SSL_ARGS}'
-+
-+ m_as ovn-gw-1 kill -9 $(m_as ovn-gw-1 cat /run/ovn/ovn-controller.pid)
-+ m_as ovn-gw-1 kill -9 $(m_as ovn-gw-1 cat /run/openvswitch/ovs-vswitchd.pid)
-+ m_as ovn-gw-1 kill -9 $(m_as ovn-gw-1 cat /run/openvswitch/ovsdb-server.pid)
-+ # Also delete datapath (flows)
-+ m_as ovn-gw-1 ovs-dpctl del-dp system@ovs-system
-+ fi
+ dnl Start UDP echo server on vm2.
+-NETNS_DAEMONIZE([vm2], [nc -e "$PYTHON ./server.py" -k -u -v -l -m 1 1000], [nc-vm2-1000.pid])
+-NETNS_DAEMONIZE([vm2], [nc -e "$PYTHON ./server.py" -k -u -v -l -m 1 1010], [nc-vm2-1010.pid])
+-NETNS_DAEMONIZE([vm2], [nc -e "$PYTHON ./server.py" -k -u -v -l -m 1 2000], [nc-vm2-2000.pid])
+-NETNS_DAEMONIZE([vm2], [nc -e "$PYTHON ./server.py" -k -u -v -l -m 1 2010], [nc-vm2-2010.pid])
+-NETNS_DAEMONIZE([vm2], [nc -e "$PYTHON ./server.py" -k -u -v -l -m 1 3000], [nc-vm2-3000.pid])
+-NETNS_DAEMONIZE([vm2], [nc -e "$PYTHON ./server.py" -k -u -v -l -m 1 3010], [nc-vm2-3010.pid])
++NETNS_DAEMONIZE([vm2], [$PYTHON3 ./server.py 1000], [server-vm2-1000.pid])
++NETNS_DAEMONIZE([vm2], [$PYTHON3 ./server.py 1010], [server-vm2-1010.pid])
++NETNS_DAEMONIZE([vm2], [$PYTHON3 ./server.py 2000], [server-vm2-2000.pid])
++NETNS_DAEMONIZE([vm2], [$PYTHON3 ./server.py 2010], [server-vm2-2010.pid])
++NETNS_DAEMONIZE([vm2], [$PYTHON3 ./server.py 3000], [server-vm2-3000.pid])
++NETNS_DAEMONIZE([vm2], [$PYTHON3 ./server.py 3010], [server-vm2-3010.pid])
+ NETNS_START_TCPDUMP([vm2], [-i vm2 -vnne], [vm2])
-- if [[ "$dead" == "gw2" ]]; then
-- echo "$(date +%H:%M:%S.%03N) Waiting for flap count between gw1 and gw2 to increase"
-+ if [ test "$action" == "kill_gw2" ]; then
-+ AS_BOX([$(date +%H:%M:%S.%03N) Waiting for flap count between gw1 and gw2 to increase])
- OVS_WAIT_UNTIL([
- new_flap_count=$(m_as ovn-gw-1 ovs-vsctl get interfac $from_gw1_to_gw2 bfd_status | sed 's/.*flap_count=\"\([[0-9]]*\).*/\1/g')
- echo "Comparing $new_flap_count versus $flap_count_gw_1"
- test "$new_flap_count" -gt "$((flap_count_gw_1))"
- ])
- else
-- echo "$(date +%H:%M:%S.%03N) Waiting for flap count between gw2 and gw1 to increase])"
-+ AS_BOX([$(date +%H:%M:%S.%03N) Waiting for flap count between gw2 and gw1 to increase])
- OVS_WAIT_UNTIL([
- new_flap_count=$(m_as ovn-gw-2 ovs-vsctl get interfac $from_gw2_to_gw1 bfd_status | sed 's/.*flap_count=\"\([[0-9]]*\).*/\1/g')
- echo "Comparing $new_flap_count versus $flap_count_gw_2"
- test "$new_flap_count" -gt "$((flap_count_gw_2))"
- ])
-+
- fi
-+ AS_BOX([$(date +%H:%M:%S.%03N) Flapped!])
+ dnl Send traffic (2 packets) to the UDP LB1 (hits the from-lport ACL).
+@@ -13335,10 +13335,10 @@ dnl And wait for it to be up and running.
+ OVS_WAIT_UNTIL([ovs-ofctl dump-ipfix-flow br-int | grep -q '1 ids'])
-- echo "$(date +%H:%M:%S.%03N) Flapped!"
- # Wait a few more second for the fight.
-- sleep 4
--
-- echo "$(date +%H:%M:%S.%03N) Statistics after flapping"
-- lost=$(dump_statistics)
-- echo "===> $lost packet lost while handling migration"
-- AT_CHECK([test "$lost" -le "$max_expected_loss"])
--}
--
--final_check()
--{
-- action=$1
-- lost=$2
-- max_expected_loss_after_restoration=$3
--
-- # Wait a little more to get packets while network is restored
- sleep 2
-- echo "$(date +%H:%M:%S.%03N) Statistics after network restored (after $action)"
-- new_lost=$(dump_statistics)
-- echo "===> $((new_lost - lost)) packets lost during network restoration"
-- AT_CHECK([test "$((new_lost - lost))" -le "$max_expected_loss_after_restoration"])
-- stop_sending_background_packets
--}
--
--check_garps()
--{
-- check_for_new_garps gw1 "$1"
-- check_for_new_garps gw2 "$2"
-- check_for_new_garps gw3 "$3"
--}
--
--check_packets()
--{
-- check_for_new_echo_pkts gw1 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe "$1"
-- check_for_new_echo_pkts gw2 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe "$2"
-- check_for_new_echo_pkts gw3 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe "$3"
-- check_for_new_echo_pkts ch1 f0:00:c0:a8:01:fe f0:00:c0:a8:01:01 "$4"
--}
--
--check_migration_between_gw1_and_gw2_bfd_stop()
--{
-- AS_BOX([$(date +%H:%M:%S.%03N) Testing migration after bfd_stop])
-- max_expected_max_expected_loss1=$1
-- max_expected_max_expected_loss2=$2
-- prepare
--
-- echo "$(date +%H:%M:%S.%03N) Blocking bfd on gw1 (from $ip_gw1 to $ip_gw2)"
-- nsenter --net=/proc/$gw1_pid/ns/net nft add table ip ovn-test
-- nsenter --net=/proc/$gw1_pid/ns/net nft 'add chain ip ovn-test INPUT { type filter hook input priority 0; policy accept; }'
-- # Drop BFD from gw-1 to gw-2: geneve port (6081), inner port 3784 (0xec8), Session state Up, Init, Down.
-- nsenter --net=/proc/$gw1_pid/ns/net nft add rule ip ovn-test INPUT ip daddr $ip_gw1 ip saddr $ip_gw2 udp dport 6081 '@th,416,16 == 0x0ec8 @th,472,8 == 0xc0 counter drop'
-- nsenter --net=/proc/$gw1_pid/ns/net nft add rule ip ovn-test INPUT ip daddr $ip_gw1 ip saddr $ip_gw2 udp dport 6081 '@th,416,16 == 0x0ec8 @th,472,8 == 0x80 counter drop'
-- nsenter --net=/proc/$gw1_pid/ns/net nft add rule ip ovn-test INPUT ip daddr $ip_gw1 ip saddr $ip_gw2 udp dport 6081 '@th,416,16 == 0x0ec8 @th,472,8 == 0x40 counter drop'
--
-- check_loss_after_flap "gw1" $max_expected_max_expected_loss1
--
-- # gw1 still alive and gw2 tried to claim => gw1 should restart generating garps.
-- check_garps "true" "false" "false"
-- check_packets "true" "false" "false" "true"
--
-- echo "$(date +%H:%M:%S.%03N) Unblocking bfd on gw1"
-- nsenter --net=/proc/$gw1_pid/ns/net nft -a list ruleset
-- nsenter --net=/proc/$gw1_pid/ns/net nft delete table ip ovn-test
--
-- # The network is now restored => packets should go through gw1 and reach chassis-1.
-- check_packets "true" "false" "false" "true"
-- final_check "bfd_stop" $lost $max_expected_max_expected_loss2
--}
--
--check_migration_between_gw1_and_gw2_kill_gw2() {
-- AS_BOX([$(date +%H:%M:%S.%03N) Check migration after killing gw2 ovn-controller & vswitchd])
-- max_expected_loss1=$1
-- max_expected_loss2=$2
-- prepare
--
-- on_exit 'm_as ovn-gw-2 /usr/share/openvswitch/scripts/ovs-ctl status ||
-- m_as ovn-gw-2 /usr/share/openvswitch/scripts/ovs-ctl start --system-id=ovn-gw-2'
-- on_exit 'm_as ovn-gw-2 /usr/share/ovn/scripts/ovn-ctl status_controller ||
-- m_as ovn-gw-2 /usr/share/ovn/scripts/ovn-ctl start_controller ${CONTROLLER_SSL_ARGS}'
--
-- m_as ovn-gw-2 kill -9 $(m_as ovn-gw-2 cat /run/ovn/ovn-controller.pid)
-- m_as ovn-gw-2 kill -9 $(m_as ovn-gw-2 cat /run/openvswitch/ovs-vswitchd.pid)
-- m_as ovn-gw-2 kill -9 $(m_as ovn-gw-2 cat /run/openvswitch/ovsdb-server.pid)
-- m_as ovn-gw-2 ovs-dpctl del-dp system@ovs-system
--
-- check_loss_after_flap "gw2" $max_expected_loss1
--
-- # gw1 still alive, but gw2 did not try to claim => gw1 should not generate new garps.
-- check_garps "false" "false" "false"
-- check_packets "true" "fals" "false" "true"
--
-- echo "$(date +%H:%M:%S.%03N) Restarting gw2 ovn-vswitchd]"
-- m_as ovn-gw-2 /usr/share/openvswitch/scripts/ovs-ctl start --system-id=ovn-gw-2
--
-- echo "$(date +%H:%M:%S.%03N) Restarting gw2 ovn-controller"
-- m_as ovn-gw-2 /usr/share/ovn/scripts/ovn-ctl start_controller ${CONTROLLER_SSL_ARGS}
--
-- # The network is now restored => packets should go through gw1 and reach chassis-1.
-- check_packets "true" "false" "false" "true"
-- final_check "kill_gw2" $lost $max_expected_loss2
--}
--
--check_migration_between_gw1_and_gw2_update_ovs() {
-- AS_BOX([$(date +%H:%M:%S.%03N) Check migration after restarting gw1 ovs-vswitchd ("update")])
-- max_expected_loss1=$1
-- max_expected_loss2=$2
-- prepare
--
-- m_as ovn-gw-1 /usr/share/openvswitch/scripts/ovs-ctl restart --system-id=ovn-gw-1
--
-- check_loss_after_flap "gw1" $max_expected_loss1
--
-- # The network is now restored => packets should go through gw1 and reach chassis-1.
-- check_packets "true" "false" "false" "true"
-- final_check "ovs_update" $lost $max_expected_loss2
--}
--
--check_migration_between_gw1_and_gw2_kill_gw1() {
-- AS_BOX([$(date +%H:%M:%S.%03N) Killing gw1 ovn-controller and ovs-vswitchd])
-- max_expected_loss1=$1
-- max_expected_loss2=$2
-- prepare
--
-- on_exit 'm_as ovn-gw-1 /usr/share/openvswitch/scripts/ovs-ctl status ||
-- m_as ovn-gw-1 /usr/share/openvswitch/scripts/ovs-ctl start --system-id=ovn-gw-1'
-- on_exit 'm_as ovn-gw-1 /usr/share/ovn/scripts/ovn-ctl status_controller ||
-- m_as ovn-gw-1 /usr/share/ovn/scripts/ovn-ctl start_controller ${CONTROLLER_SSL_ARGS}'
--
-- m_as ovn-gw-1 kill -9 $(m_as ovn-gw-1 cat /run/ovn/ovn-controller.pid)
-- m_as ovn-gw-1 kill -9 $(m_as ovn-gw-1 cat /run/openvswitch/ovs-vswitchd.pid)
-- m_as ovn-gw-1 kill -9 $(m_as ovn-gw-1 cat /run/openvswitch/ovsdb-server.pid)
-- # Also delete datapath (flows)
-- m_as ovn-gw-1 ovs-dpctl del-dp system@ovs-system
--
-- check_loss_after_flap "gw1" $max_expected_loss1
--
-- # gw1 died => gw2 should generate garps.
-- check_garps "false" "true" "false"
-- check_packets "false" "true" "false" "true"
-- echo "$(date +%H:%M:%S.%03N) Restarting gw1 ovn-vswitchd after killing gw1"
-- m_as ovn-gw-1 /usr/share/openvswitch/scripts/ovs-ctl start --system-id=ovn-gw-1
--
-- # Wait some long time before restarting ovn-controller
-- sleep 10
--
-- # gw2 should still be handling packets as OVN not restarted on gw1
-- check_packets "false" "true" "false" "true"
--
-- echo "$(date +%H:%M:%S.%03N) Restarting gw1 ovn-controller after killing gw1"
-- m_as ovn-gw-1 /usr/share/ovn/scripts/ovn-ctl start_controller ${CONTROLLER_SSL_ARGS}
--
-- # The network is now restored => packets should go through gw1 and reach chassis-1.
-- check_packets "true" "false" "false" "true"
-- final_check "kill_gw1" $lost $max_expected_loss2
--}
--
--check_migration_between_gw1_and_gw2_reboot_gw1() {
-- ip_gw1_eth1=$(podman exec ovn-gw-1 ip -brief address show eth1 | awk '{print $3}' | cut -d/ -f1)
-- cidr=$(podman exec ovn-gw-1 ip -brief address show eth1 | awk '{print $3}' | cut -d/ -f2)
-- AS_BOX([$(date +%H:%M:%S.%03N) Rebooting ovn-gw-1 with $ip_gw1_eth1/$cidr])
-- max_expected_loss1=$1
-- max_expected_loss2=$2
-- prepare
--
-- podman stop -t 0 ovn-gw-1
-- (exec 3>&- 4>&- 5>&- 6>&-; podman start ovn-gw-1)
--
-- # As ovn-gw-1 got stopped and restarted, its ports (added by fake
-- # multinode) got deleted. Add them back.
-- add_port br-ovn eth1 $ip_gw1_eth1/$cidr
-- add_port br-ovn-ext eth2
-- M_START_TCPDUMPS([ovn-gw-1], [-neei eth2], [gw1], [ovn-gw-1], [-neei eth1], [gw1_eth1], [ovn-gw-1], [-neei eth2 -Q out], [gw1_out])
-- check_loss_after_flap "gw1" $max_expected_loss1
--
-- # gw1 died => gw2 should generate garps.
-- check_garps "false" "true" "false"
-- check_packets "false" "true" "false" "true"
--
-- echo "$(date +%H:%M:%S.%03N) Restarting gw1 ovn-vswitchd after rebooting gw1"
-- m_as ovn-gw-1 /usr/share/openvswitch/scripts/ovs-ctl start --system-id=ovn-gw-1
--
-- # Wait some long time before restarting ovn-controller
-- sleep 10
-+ AS_BOX([$(date +%H:%M:%S.%03N) Statistics after flapping])
-+ dump_statistics
-+
-+ if [ test "$action" == "stop_bfd" ]; then
-+ # gw1 still alive and gw2 tried to claim => gw1 should restart generating garps.
-+ check_for_new_garps gw1 "true"
-+ check_for_new_garps gw2 "false"
-+ check_for_new_garps gw3 "false"
-+ check_for_new_echo_pkts gw1 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe "true"
-+ check_for_new_echo_pkts gw2 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe "false"
-+ check_for_new_echo_pkts gw3 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe "false"
-+ check_for_new_echo_pkts ch1 f0:00:c0:a8:01:fe f0:00:c0:a8:01:01 "true"
-+ AS_BOX([$(date +%H:%M:%S.%03N) Unblocking bfd on gw1])
-+ nsenter --net=/proc/$gw1_pid/ns/net nft -a list ruleset
-+ nsenter --net=/proc/$gw1_pid/ns/net nft delete table ip ovn-test
-+ fi
+ dnl Start UDP echo server on vm2.
+-NETNS_DAEMONIZE([vm2], [nc -e /bin/cat -k -u -v -l -m 1 1000], [nc-vm2-1000.pid])
++NETNS_DAEMONIZE([vm2], [ncat -e "$(command -v cat)" -k -u -v -l -m 1 1000], [nc-vm2-1000.pid])
+
+ dnl Send traffic to the UDP server (hits both ACL tiers).
+-NS_CHECK_EXEC([vm1], [echo a | nc --send-only -u 42.42.42.3 1000])
++NS_CHECK_EXEC([vm1], [echo a | ncat --send-only -u 42.42.42.3 1000])
+
+ dnl Wait until OVS sampled all expected packets:
+ dnl - first packet sampled by both tiers
+@@ -13453,10 +13453,10 @@ dnl And wait for it to be up and running.
+ OVS_WAIT_UNTIL([ovs-ofctl dump-ipfix-flow br-int | grep -q '1 ids'])
+
+ dnl Start UDP echo server on vm2.
+-NETNS_DAEMONIZE([vm2], [nc -e /bin/cat -k -u -v -l -m 1 1000], [nc-vm2-1000.pid])
++NETNS_DAEMONIZE([vm2], [ncat -e "$(command -v cat)" -k -u -v -l -m 1 1000], [nc-vm2-1000.pid])
+
+ dnl Send traffic to the UDP server (hits both ACL tiers).
+-NS_CHECK_EXEC([vm1], [echo a | nc --send-only -u 42.42.42.3 1000])
++NS_CHECK_EXEC([vm1], [echo a | ncat --send-only -u 42.42.42.3 1000])
+
+ dnl Wait until OVS sampled all expected packets:
+ dnl In this case, we only expect a single sampled packet.
+@@ -13674,8 +13674,8 @@ check ovn-nbctl --wait=hv sync
+ check_row_count nb:ACL 2
+ check_row_count nb:Sample 2
+
+-NETNS_DAEMONIZE([vm1], [nc -l -u 1234], [udp1.pid])
+-NETNS_DAEMONIZE([vm2], [nc -l -u 1235], [udp2.pid])
++NETNS_DAEMONIZE([vm1], [ncat -l -u 1234], [udp1.pid])
++NETNS_DAEMONIZE([vm2], [ncat -l -u 1235], [udp2.pid])
-- # gw2 should still be handling packets as OVN not restarted on gw1
-- check_packets "false" "true" "false" "true"
-+ if [ test "$action" == "kill_gw2" ]; then
-+ # gw1 still alive, but gw2 did not try to claim => gw1 should not generate new garps.
-+ check_for_new_garps gw1 "false"
-+ check_for_new_garps gw2 "false"
-+ check_for_new_garps gw3 "false"
-+ check_for_new_echo_pkts gw1 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe "true"
-+ check_for_new_echo_pkts gw2 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe "false"
-+ check_for_new_echo_pkts gw3 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe "false"
-+ check_for_new_echo_pkts ch1 f0:00:c0:a8:01:fe f0:00:c0:a8:01:01 "true"
-+ AS_BOX([$(date +%H:%M:%S.%03N) Restarting gw2 ovn-vswitchd])
-+ m_as ovn-gw-2 /usr/share/openvswitch/scripts/ovs-ctl start --system-id=ovn-gw-2
-+
-+ AS_BOX([$(date +%H:%M:%S.%03N) Restarting gw2 ovn-controller])
-+ m_as ovn-gw-2 /usr/share/ovn/scripts/ovn-ctl start_controller ${CONTROLLER_SSL_ARGS}
-+ fi
+ dnl Start an IPFIX collector.
+ DAEMONIZE([nfcapd -B 1024000 -w . -p 4242 2> collector.err], [collector.pid])
+@@ -13695,12 +13695,12 @@ OVS_WAIT_UNTIL([ovs-ofctl dump-ipfix-flow br-int | grep -q '2 ids'])
-- echo "$(date +%H:%M:%S.%03N) Restarting gw1 ovn-controller after rebooting gw1"
-- m_as ovn-gw-1 /usr/share/ovn/scripts/ovn-ctl start_controller ${CONTROLLER_SSL_ARGS}
-+ if [ test "$action" == "kill_gw1" ]; then
-+ # gw1 died => gw2 should generate garps.
-+ check_for_new_garps gw1 "false"
-+ check_for_new_garps gw2 "true"
-+ check_for_new_garps gw3 "false"
-+ check_for_new_echo_pkts gw1 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe "false"
-+ check_for_new_echo_pkts gw2 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe "true"
-+ check_for_new_echo_pkts gw3 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe "false"
-+ check_for_new_echo_pkts ch1 f0:00:c0:a8:01:fe f0:00:c0:a8:01:01 "true"
-+ AS_BOX([$(date +%H:%M:%S.%03N) Restarting gw1 ovn-vswitchd])
-+ m_as ovn-gw-1 /usr/share/openvswitch/scripts/ovs-ctl start --system-id=ovn-gw-1
-+
-+ AS_BOX([$(date +%H:%M:%S.%03N) Restarting gw1 ovn-controller])
-+ m_as ovn-gw-1 /usr/share/ovn/scripts/ovn-ctl start_controller ${CONTROLLER_SSL_ARGS}
-+ fi
+ # Create the connection in the CT table.
+ echo "Hello" > Hello
+-NS_EXEC([vm1], [nc -p 1234 -u 43.43.43.2 1235 < Hello])
+-NS_EXEC([vm2], [nc -p 1235 -u 42.42.42.2 1234 < Hello])
++NS_EXEC([vm1], [ncat -p 1234 -u 43.43.43.2 1235 < Hello])
++NS_EXEC([vm2], [ncat -p 1235 -u 42.42.42.2 1234 < Hello])
+ # Send some more packets.
+ for i in $(seq 10); do
+-NS_EXEC([vm1], [nc -p 1234 -u 43.43.43.2 1235 < Hello])
+-NS_EXEC([vm2], [nc -p 1235 -u 42.42.42.2 1234 < Hello])
++NS_EXEC([vm1], [ncat -p 1234 -u 43.43.43.2 1235 < Hello])
++NS_EXEC([vm2], [ncat -p 1235 -u 42.42.42.2 1234 < Hello])
+ done
- # The network is now restored => packets should go through gw1 and reach chassis-1.
-- check_packets "true" "false" "false" "true"
-- final_check "kill_gw1" $lost $max_expected_loss2
--}
--
--check_compute_restart() {
-- AS_BOX([$(date +%H:%M:%S.%03N) Killing ovn-chassis-1 ovn-controller and ovs-vswitchd])
-- max_expected_loss=$1
-- prepare
--
-- # Kill ovn-chassis-1
-- echo "$(date +%H:%M:%S.%03N) Killing chassis-1"
-- on_exit 'm_as ovn-chassis-1 /usr/share/openvswitch/scripts/ovs-ctl status ||
-- m_as ovn-chassis-1 /usr/share/openvswitch/scripts/ovs-ctl start --system-id=ovn-chassis-1'
-- on_exit 'm_as ovn-chassis-1 /usr/share/ovn/scripts/ovn-ctl status_controller ||
-- m_as ovn-chassis-1 /usr/share/ovn/scripts/ovn-ctl start_controller ${CONTROLLER_SSL_ARGS}'
--
-- m_as ovn-chassis-1 kill -9 $(m_as ovn-chassis-1 cat /run/ovn/ovn-controller.pid)
-- m_as ovn-chassis-1 kill -9 $(m_as ovn-chassis-1 cat /run/openvswitch/ovs-vswitchd.pid)
-- m_as ovn-chassis-1 kill -9 $(m_as ovn-chassis-1 cat /run/openvswitch/ovsdb-server.pid)
--
-- # Now restart chassis-1
-- flap_count=$(m_as ovn-gw-2 ovs-vsctl get interfac $from_gw2_to_ch1 bfd_status | sed 's/.*flap_count=\"\([[0-9]]*\).*/\1/g')
-- echo "$(date +%H:%M:%S.%03N) Restarting ovn-chassis-1 ovn-vswitchd."
-- m_as ovn-chassis-1 /usr/share/openvswitch/scripts/ovs-ctl start --system-id=ovn-chassis-1
--
-- echo "$(date +%H:%M:%S.%03N) Waiting for flap count between gw-1 and chassis-1 to increase"
-- OVS_WAIT_UNTIL([
-- new_flap_count=$(m_as ovn-gw-1 ovs-vsctl get interfac $from_gw1_to_ch1 bfd_status | sed 's/.*flap_count=\"\([[0-9]]*\).*/\1/g')
-- echo "Comparing $new_flap_count versus $flap_count"
-- test "$new_flap_count" -gt "$((flap_count))"
-- ])
--
-- wait_bfd_up ovn-chassis-1 $from_ch1_to_gw1
--
-- echo "$(date +%H:%M:%S.%03N) Restarting ovn-chassis-1 ovn-controller."
-- m_as ovn-chassis-1 /usr/share/ovn/scripts/ovn-ctl start_controller ${CONTROLLER_SSL_ARGS}
--
-- # Wait a long time to catch losses
-- sleep 5
-- final_check "compute" 0 $max_expected_loss
-+ check_for_new_echo_pkts gw1 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe "true"
-+ check_for_new_echo_pkts gw2 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe "false"
-+ check_for_new_echo_pkts gw3 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe "false"
-+ check_for_new_echo_pkts ch1 f0:00:c0:a8:01:fe f0:00:c0:a8:01:01 "true"
-+ AS_BOX([$(date +%H:%M:%S.%03N) Statistics after network restored])
-+ dump_statistics
-+ stop_sending_background_packets
- }
+ OVS_WAIT_UNTIL([ovs-ofctl dump-ipfix-flow br-int | grep 'id 100' | grep -q 'sampled pkts=21'])
+@@ -13831,7 +13831,7 @@ AT_CLEANUP
- start_tcpdump
--echo "$(date +%H:%M:%S.%03N) Sending packet from hv1-vif1(inside1) to ext1"
-+AS_BOX([$(date +%H:%M:%S.%03N) Sending packet from hv1-vif1(inside1) to ext1])
- M_NS_CHECK_EXEC([ovn-chassis-1], [hv1-vif1], [ping -c3 -q -i 0.1 192.168.0.1 | FORMAT_PING],
- [0], [dnl
- 3 packets transmitted, 3 received, 0% packet loss, time 0ms
-@@ -3631,7 +3433,7 @@ M_NS_CHECK_EXEC([ovn-chassis-1], [hv1-vif1], [ping -c3 -q -i 0.1 192.168.0.1 | F
- stop_tcpdump
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([Routing protocol redirect - l3 gateway])
+-AT_SKIP_IF([test $HAVE_NC = no])
++AT_SKIP_IF([test $HAVE_NCAT = no])
- # It should have gone through gw1 and not gw2
--echo "$(date +%H:%M:%S.%03N) Checking it went through gw1 and not gw2"
-+AS_BOX([$(date +%H:%M:%S.%03N) Checking it went through gw1 and not gw2])
- AT_CHECK([cat gw2.tcpdump | grep "ICMP echo"], [1], [dnl
- ])
+ ovn_start
+ OVS_TRAFFIC_VSWITCHD_START()
+@@ -13911,52 +13911,52 @@ OVS_WAIT_UNTIL([NS_EXEC([ext-foo], [ip a show dev ext-foo | grep "fe80::" | grep
-@@ -3644,29 +3446,17 @@ f0:00:c0:a8:00:fe > 00:00:c0:a8:00:01, ethertype IPv4 (0x0800), length 98: 192.1
- 00:00:c0:a8:00:01 > f0:00:c0:a8:00:fe, ethertype IPv4 (0x0800), length 98: 192.168.0.1 > 192.168.1.1: ICMP echo reply,
- ])
+ # Verify that BGP control plane traffic is delivered to the "bgp-daemon"
+ # interface on both IPv4 and IPv6 LLA addresses
+-NETNS_DAEMONIZE([bgp-daemon], [nc -l -k 172.16.1.1 179], [bgp_v4.pid])
+-NS_CHECK_EXEC([ext-foo], [echo "BGP IPv4 server traffic" | nc --send-only 172.16.1.1 179])
++NETNS_DAEMONIZE([bgp-daemon], [ncat -l -k 172.16.1.1 179], [bgp_v4.pid])
++NS_CHECK_EXEC([ext-foo], [echo "BGP IPv4 server traffic" | ncat --send-only 172.16.1.1 179])
--# We stop bfd between gw1 & gw2, but keep gw1 & gw2 running. We should not lose packets.
--check_migration_between_gw1_and_gw2_bfd_stop 1 1
-+# We stop bfd between gw1 & gw2, but keep gw1 & gw2 running.
-+check_migration_between_gw1_and_gw2 "stop_bfd"
+-NETNS_DAEMONIZE([bgp-daemon], [nc -l -6 -k fe80::200:2ff:fe01:203%bgp-daemon 179], [bgp_v6.pid])
+-NS_CHECK_EXEC([ext-foo], [echo "BGP IPv6 server traffic" | nc --send-only -6 fe80::200:2ff:fe01:203%ext-foo 179])
++NETNS_DAEMONIZE([bgp-daemon], [ncat -l -6 -k fe80::200:2ff:fe01:203%bgp-daemon 179], [bgp_v6.pid])
++NS_CHECK_EXEC([ext-foo], [echo "BGP IPv6 server traffic" | ncat --send-only -6 fe80::200:2ff:fe01:203%ext-foo 179])
- # We simulate death of gw2. It should not have any effect.
--check_migration_between_gw1_and_gw2_kill_gw2 1 1
--
--# We simulate ovs update on gw1. When ovs is stopped, flows should still be handled by Kernel datapath.
--# When OVS is restarted, BFD should go down immediately, and gw2 will start handling packets.
--# There will be packet losses as gw2 will usually see BFD from gw1 up (and hence relase port) before gw1 sees
--# BFD up (and claim port).
--check_migration_between_gw1_and_gw2_update_ovs 20 1
--
--# We simulate restart of both OVS & OVN gw1. gw2 should take over.
--check_migration_between_gw1_and_gw2_kill_gw1 40 20
-+check_migration_between_gw1_and_gw2 "kill_gw2"
+ # Perform same set of checks as above for BFD daemon.
+ # We need to manually check that the message arrived on the receiving end as Ncat will
+ # produce false positive results over UDP due to lack of ICMP port unreachable messages
+ # from LRP's IP.
+-NETNS_DAEMONIZE([bgp-daemon], [nc -l -u 172.16.1.1 3784 > bgp-daemon_bfd_v4.out], [bfd_v4.pid])
+-NS_CHECK_EXEC([ext-foo], [echo "from ext-foo: BFD IPv4 server traffic" | nc -u 172.16.1.1 3784])
++NETNS_DAEMONIZE([bgp-daemon], [ncat -l -u 172.16.1.1 3784 > bgp-daemon_bfd_v4.out], [bfd_v4.pid])
++NS_CHECK_EXEC([ext-foo], [echo "from ext-foo: BFD IPv4 server traffic" | ncat -u 172.16.1.1 3784])
+ AT_CHECK([cat bgp-daemon_bfd_v4.out], [0], [dnl
+ from ext-foo: BFD IPv4 server traffic
+ ])
- # We simulate death of gw1. gw2 should take over.
--check_migration_between_gw1_and_gw2_reboot_gw1 40 20
--
--# We simulate restart of ovn-chassis-1. We expect for ~4 sec losses as we wait for bfd up before starting
--# ovn-controller (1 sec to restart ovs, 2 sec for bfd to go up, 1 sec to detect it).
--check_compute_restart 40
-+check_migration_between_gw1_and_gw2 "kill_gw1"
+-NETNS_DAEMONIZE([bgp-daemon], [nc -l -6 -u fe80::200:2ff:fe01:203%bgp-daemon 3784 > bgp-daemon_bfd_v6.out], [bfd_v6.pid])
+-NS_CHECK_EXEC([ext-foo], [echo "from ext-foo: BFD IPv6 server traffic" | nc -u -6 fe80::200:2ff:fe01:203%ext-foo 3784])
++NETNS_DAEMONIZE([bgp-daemon], [ncat -l -6 -u fe80::200:2ff:fe01:203%bgp-daemon 3784 > bgp-daemon_bfd_v6.out], [bfd_v6.pid])
++NS_CHECK_EXEC([ext-foo], [echo "from ext-foo: BFD IPv6 server traffic" | ncat -u -6 fe80::200:2ff:fe01:203%ext-foo 3784])
+ AT_CHECK([cat bgp-daemon_bfd_v6.out], [0], [dnl
+ from ext-foo: BFD IPv6 server traffic
+ ])
- AT_CLEANUP
-+])
+ # Verify connection in other direction. i.e when BGP daemon running on "bgp-daemon" port
+ # makes a client connection to its peer
+-NETNS_DAEMONIZE([ext-foo], [nc -l -k 172.16.1.100 179], [reply_bgp_v4.pid])
+-NS_CHECK_EXEC([bgp-daemon], [echo "BGP IPv4 client traffic" | nc --send-only 172.16.1.100 179])
++NETNS_DAEMONIZE([ext-foo], [ncat -l -k 172.16.1.100 179], [reply_bgp_v4.pid])
++NS_CHECK_EXEC([bgp-daemon], [echo "BGP IPv4 client traffic" | ncat --send-only 172.16.1.100 179])
- AT_SETUP([ovn multinode bgp L2 EVPN])
- check_fake_multinode_setup
-@@ -3837,12 +3627,17 @@ check multinode_nbctl --wait=hv \
- -- acl-add ls from-lport 100 "ip" allow-related \
- -- acl-add ls to-lport 100 "ip" allow-related
-
--dnl Verify fabric-to-workload pings still work with stateful ACL.
-+dnl Verify fabric-to-workload pings still work with stateful ACL (both directions).
- OVS_WAIT_UNTIL([m_as ovn-gw-1 ip netns exec fabric_workload ping -W 1 -c 1 10.0.0.11])
- OVS_WAIT_UNTIL([m_as ovn-gw-1 ip netns exec fabric_workload ping -6 -W 1 -c 1 10::11])
- OVS_WAIT_UNTIL([m_as ovn-gw-2 ip netns exec fabric_workload ping -W 1 -c 1 10.0.0.12])
- OVS_WAIT_UNTIL([m_as ovn-gw-2 ip netns exec fabric_workload ping -6 -W 1 -c 1 10::12])
-
-+OVS_WAIT_UNTIL([m_as ovn-gw-1 ip netns exec w1 ping -W 1 -c 1 10.0.0.41])
-+OVS_WAIT_UNTIL([m_as ovn-gw-1 ip netns exec w1 ping -6 -W 1 -c 1 10::41])
-+OVS_WAIT_UNTIL([m_as ovn-gw-2 ip netns exec w2 ping -W 1 -c 1 10.0.0.42])
-+OVS_WAIT_UNTIL([m_as ovn-gw-2 ip netns exec w2 ping -6 -W 1 -c 1 10::42])
-+
- dnl Also add a load balancer and verify pings still work.
- check multinode_nbctl --wait=hv \
- -- lb-add lb1 10.0.0.100:80 10.0.0.11:80 \
-@@ -3853,6 +3648,11 @@ OVS_WAIT_UNTIL([m_as ovn-gw-1 ip netns exec fabric_workload ping -6 -W 1 -c 1 10
- OVS_WAIT_UNTIL([m_as ovn-gw-2 ip netns exec fabric_workload ping -W 1 -c 1 10.0.0.12])
- OVS_WAIT_UNTIL([m_as ovn-gw-2 ip netns exec fabric_workload ping -6 -W 1 -c 1 10::12])
-
-+OVS_WAIT_UNTIL([m_as ovn-gw-1 ip netns exec w1 ping -W 1 -c 1 10.0.0.41])
-+OVS_WAIT_UNTIL([m_as ovn-gw-1 ip netns exec w1 ping -6 -W 1 -c 1 10::41])
-+OVS_WAIT_UNTIL([m_as ovn-gw-2 ip netns exec w2 ping -W 1 -c 1 10.0.0.42])
-+OVS_WAIT_UNTIL([m_as ovn-gw-2 ip netns exec w2 ping -6 -W 1 -c 1 10::42])
-+
- dnl Cleanup ACL and LB.
- check multinode_nbctl --wait=hv \
- -- acl-del ls \
-diff --git a/tests/ovn-controller-vtep.at b/tests/ovn-controller-vtep.at
-index 35402b00aa..ffd394a992 100644
---- a/tests/ovn-controller-vtep.at
-+++ b/tests/ovn-controller-vtep.at
-@@ -635,6 +635,10 @@ AT_CHECK([ovs-ofctl dump-flows br-int table=OFTABLE_PHY_TO_LOG | grep 'priority=
- priority=110,tun_id=0x<>,in_port=<> actions=move:NXM_NX_TUN_ID[[0..23]]->OXM_OF_METADATA[[0..23]],load:0x<>->NXM_NX_REG14[[0..14]],load:0x<>->NXM_NX_REG10[[1]],resubmit(,OFTABLE_LOG_INGRESS_PIPELINE)
+-NETNS_DAEMONIZE([ext-foo], [nc -l -6 -k fe80::210:10ff:fe01:213%ext-foo 179], [reply_bgp_v6.pid])
+-NS_CHECK_EXEC([bgp-daemon], [echo "BGP IPv6 client traffic" | nc --send-only -6 fe80::210:10ff:fe01:213%bgp-daemon 179])
++NETNS_DAEMONIZE([ext-foo], [ncat -l -6 -k fe80::210:10ff:fe01:213%ext-foo 179], [reply_bgp_v6.pid])
++NS_CHECK_EXEC([bgp-daemon], [echo "BGP IPv6 client traffic" | ncat --send-only -6 fe80::210:10ff:fe01:213%bgp-daemon 179])
+
+ # Perform same checks in other direction for BFD daemon
+-NETNS_DAEMONIZE([ext-foo], [nc -l -u 172.16.1.100 3784 > ext-foo_bfd_v4.out], [reply_bfd_v4.pid])
+-NS_CHECK_EXEC([bgp-daemon], [echo "from bgp-daemon: BFD IPv4 client traffic" | nc -u 172.16.1.100 3784])
++NETNS_DAEMONIZE([ext-foo], [ncat -l -u 172.16.1.100 3784 > ext-foo_bfd_v4.out], [reply_bfd_v4.pid])
++NS_CHECK_EXEC([bgp-daemon], [echo "from bgp-daemon: BFD IPv4 client traffic" | ncat -u 172.16.1.100 3784])
+ AT_CHECK([cat ext-foo_bfd_v4.out], [0], [dnl
+ from bgp-daemon: BFD IPv4 client traffic
])
-+# Skip processing ICMP "packet too big" errors in this table if the packet came from a VTEP tunnel.
-+AT_CHECK([ovs-ofctl dump-flows br-int table=OFTABLE_PHY_TO_LOG | \
-+ grep -E 'icmp_type=3,icmp_code=4|icmp_type=2,icmp_code=0'], [1], [])
-+
- OVN_CONTROLLER_VTEP_STOP([], vtep1)
- OVN_CLEANUP([hv1])
- AT_CLEANUP
-diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at
-index c98de9bc46..2fce19e7ba 100644
---- a/tests/ovn-controller.at
-+++ b/tests/ovn-controller.at
-@@ -962,7 +962,7 @@ AT_CLEANUP
+-NETNS_DAEMONIZE([ext-foo], [nc -l -6 -u fe80::210:10ff:fe01:213%ext-foo 3784 > ext-foo_bfd_v6.out], [reply_bfd_v6.pid])
+-NS_CHECK_EXEC([bgp-daemon], [echo "from bgp-daemon: BFD IPv6 client traffic" | nc -u -6 fe80::210:10ff:fe01:213%bgp-daemon 3784])
++NETNS_DAEMONIZE([ext-foo], [ncat -l -6 -u fe80::210:10ff:fe01:213%ext-foo 3784 > ext-foo_bfd_v6.out], [reply_bfd_v6.pid])
++NS_CHECK_EXEC([bgp-daemon], [echo "from bgp-daemon: BFD IPv6 client traffic" | ncat -u -6 fe80::210:10ff:fe01:213%bgp-daemon 3784])
+ AT_CHECK([cat ext-foo_bfd_v6.out], [0], [dnl
+ from bgp-daemon: BFD IPv6 client traffic
])
+ # Verify that hosts on the internal network can reach external networks
+-NETNS_DAEMONIZE([ext-foo], [nc -l -k 172.16.1.100 2222], [nc_external.pid])
+-NS_CHECK_EXEC([bar1], [echo "TCP test" | nc -w 1 --send-only 172.16.1.100 2222])
++NETNS_DAEMONIZE([ext-foo], [ncat -l -k 172.16.1.100 2222], [nc_external.pid])
++NS_CHECK_EXEC([bar1], [echo "TCP test" | ncat -w 1 --send-only 172.16.1.100 2222])
+
+ OVN_CLEANUP_CONTROLLER([hv1])
+
+@@ -13970,7 +13970,7 @@ AT_CLEANUP
+
OVN_FOR_EACH_NORTHD([
--AT_SETUP([ovn-controller - ovn IP check path ports])
-+AT_SETUP([ovn-controller - ovn IP check patch ports])
- AT_KEYWORDS([ovn-ip-patch-ports])
+ AT_SETUP([Routing protocol redirect - DGP])
+-AT_SKIP_IF([test $HAVE_NC = no])
++AT_SKIP_IF([test $HAVE_NCAT = no])
ovn_start
-@@ -1003,10 +1003,110 @@ check as hv1 ovn-appctl -t ovn-controller inc-engine/clear-stats
- check ovn-nbctl --wait=hv lsp-set-type ls0-rp localnet
- check_controller_engine_stats hv1 pflow_output recompute nocompute
-
-+# Check that adding a new port with a non-default type does not trigger
-+# a pflow_output recompute.
-+check as hv1 ovn-appctl -t ovn-controller inc-engine/clear-stats
-+check ovn-nbctl --wait=hv lsp-add ls0 lsp-remote -- lsp-set-type lsp-remote remote
-+check_controller_engine_stats hv1 pflow_output norecompute compute
-+
-+# Check that deleting a port with a non-default type does not trigger
-+# a pflow_output recompute.
-+check as hv1 ovn-appctl -t ovn-controller inc-engine/clear-stats
-+check ovn-nbctl --wait=hv lsp-del lsp-remote
-+check_controller_engine_stats hv1 pflow_output norecompute compute
-+
- OVN_CLEANUP([hv1])
- AT_CLEANUP
- ])
+ OVS_TRAFFIC_VSWITCHD_START()
+@@ -14051,52 +14051,52 @@ OVS_WAIT_UNTIL([NS_EXEC([ext-foo], [ip a show dev ext-foo | grep "fe80::" | grep
-+OVN_FOR_EACH_NORTHD([
-+AT_SETUP([ovn-controller - localnet port change and chassisredirect bridged redirect])
-+AT_KEYWORDS([ovn-localnet-cr-bridged])
-+
-+ovn_start
-+
-+net_add n1
-+
-+sim_add hv1
-+as hv1
-+check ovs-vsctl add-br br-phys
-+check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
-+ovn_attach n1 br-phys 192.168.0.1
-+
-+sim_add hv2
-+as hv2
-+check ovs-vsctl add-br br-phys
-+check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
-+ovn_attach n1 br-phys 192.168.0.2
-+
-+dnl Create the full topology with localnet ports, router,
-+dnl gateway chassis, and bridged redirect. Bind VIFs so the
-+dnl datapaths are local.
-+check ovn-nbctl \
-+ -- ls-add ls1 \
-+ -- lsp-add-localnet-port ls1 ln1 phys \
-+ -- set logical_switch_port ln1 tag_request=101 \
-+ -- lsp-add ls1 lp1 \
-+ -- lsp-set-addresses lp1 "00:00:00:00:00:01 192.168.1.10" \
-+ -- lsp-add ls1 lp2 \
-+ -- lsp-set-addresses lp2 "00:00:00:00:00:02 192.168.1.11" \
-+ -- lsp-add-router-port ls1 ls1-to-router router-to-ls1 \
-+ -- ls-add ls-underlay \
-+ -- lsp-add-localnet-port ls-underlay ln-underlay phys \
-+ -- set logical_switch_port ln-underlay tag_request=1000 \
-+ -- lsp-add-router-port ls-underlay underlay-to-router router-to-underlay \
-+ -- lr-add lr1 \
-+ -- lrp-add lr1 router-to-ls1 00:00:01:01:02:03 192.168.1.1/24 \
-+ -- lrp-add lr1 router-to-underlay 00:00:01:01:02:07 172.31.0.1/24 \
-+ -- lrp-set-gateway-chassis router-to-underlay hv1 \
-+ -- lrp-set-redirect-type router-to-underlay bridged
-+
-+check as hv1 ovs-vsctl add-port br-int vif0 \
-+ -- set Interface vif0 external_ids:iface-id=lp1
-+check as hv2 ovs-vsctl add-port br-int vif1 \
-+ -- set Interface vif1 external_ids:iface-id=lp2
-+
-+wait_for_ports_up
-+check ovn-nbctl --wait=hv sync
-+
-+dnl Verify initial state: hv2 has the CR bridged redirect flow.
-+router_dp_key=$(printf "%x" $(fetch_column datapath tunnel_key external_ids:name=lr1))
-+cr_key=$(printf "%x" $(fetch_column port_binding tunnel_key logical_port=cr-router-to-underlay))
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=OFTABLE_LOCAL_OUTPUT | grep -q "reg15=0x${cr_key},metadata=0x${router_dp_key}"])
-+
-+dnl Delete the localnet port on ls-underlay.
-+check ovn-nbctl --wait=hv lsp-del ln-underlay
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=OFTABLE_LOCAL_OUTPUT | grep "reg15=0x${cr_key},metadata=0x${router_dp_key}"], [1])
-+
-+dnl Pre-create the patch port that the new localnet port
-+dnl would need. This way, when the localnet port binding arrives,
-+dnl patch_run() sees the patch port already exists and non_vif_data
-+dnl does NOT change, forcing pflow_output to handle the
-+dnl change incrementally instead of recomputing.
-+check as hv2 ovs-vsctl \
-+ -- add-port br-int patch-br-int-to-ln-underlay \
-+ -- set Interface patch-br-int-to-ln-underlay \
-+ type=patch options:peer=patch-ln-underlay-to-br-int
-+check as hv2 ovs-vsctl \
-+ -- add-port br-phys patch-ln-underlay-to-br-int \
-+ -- set Interface patch-ln-underlay-to-br-int \
-+ type=patch options:peer=patch-br-int-to-ln-underlay
-+check ovn-nbctl --wait=hv sync
-+
-+dnl Re-add the localnet port. The patch port already exists,
-+dnl so non_vif_data should not change, and pflow_output should
-+dnl be handled incrementally.
-+check ovn-nbctl --wait=hv \
-+ -- lsp-add-localnet-port ls-underlay ln-underlay phys \
-+ -- set logical_switch_port ln-underlay tag_request=1000
-+
-+dnl Verify the CR bridged redirect flow is back.
-+OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=OFTABLE_LOCAL_OUTPUT | grep -q "reg15=0x${cr_key},metadata=0x${router_dp_key}"])
-+
-+OVN_CLEANUP([hv1], [hv2])
-+AT_CLEANUP
-+])
-+
- AT_SETUP([ovn-controller - I-P for address set update: no conjunction])
- AT_KEYWORDS([as-i-p])
+ # Verify that BGP control plane traffic is delivered to the "bgp-daemon"
+ # interface on both IPv4 and IPv6 LLA addresses
+-NETNS_DAEMONIZE([bgp-daemon], [nc -l -k 172.16.1.1 179], [bgp_v4.pid])
+-NS_CHECK_EXEC([ext-foo], [echo "BGP IPv4 server traffic" | nc --send-only 172.16.1.1 179])
++NETNS_DAEMONIZE([bgp-daemon], [ncat -l -k 172.16.1.1 179], [bgp_v4.pid])
++NS_CHECK_EXEC([ext-foo], [echo "BGP IPv4 server traffic" | ncat --send-only 172.16.1.1 179])
-diff --git a/tests/ovn-inc-proc-graph-dump.at b/tests/ovn-inc-proc-graph-dump.at
-index 25ff200a40..ef91014a64 100644
---- a/tests/ovn-inc-proc-graph-dump.at
-+++ b/tests/ovn-inc-proc-graph-dump.at
-@@ -172,9 +172,6 @@ digraph "Incremental-Processing-Engine" {
- northd -> ls_stateful [[label="ls_stateful_northd_handler"]];
- port_group -> ls_stateful [[label="ls_stateful_port_group_handler"]];
- NB_acl -> ls_stateful [[label="ls_stateful_acl_handler"]];
-- ls_arp [[style=filled, shape=box, fillcolor=white, label="ls_arp"]];
-- lr_nat -> ls_arp [[label="ls_arp_lr_nat_handler"]];
-- northd -> ls_arp [[label="ls_arp_northd_handler"]];
- SB_igmp_group [[style=filled, shape=box, fillcolor=white, label="SB_igmp_group"]];
- multicast_igmp [[style=filled, shape=box, fillcolor=white, label="multicast_igmp"]];
- northd -> multicast_igmp [[label="multicast_igmp_northd_handler"]];
-@@ -196,7 +193,6 @@ digraph "Incremental-Processing-Engine" {
- port_group -> lflow [[label="engine_noop_handler"]];
- lr_stateful -> lflow [[label="lflow_lr_stateful_handler"]];
- ls_stateful -> lflow [[label="lflow_ls_stateful_handler"]];
-- ls_arp -> lflow [[label="lflow_ls_arp_handler"]];
- multicast_igmp -> lflow [[label="lflow_multicast_igmp_handler"]];
- SB_acl_id -> lflow [[label=""]];
- ic_learned_svc_monitors -> lflow [[label="lflow_ic_learned_svc_mons_handler"]];
-diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at
-index 39f03ba62b..cc41bbd77b 100644
---- a/tests/ovn-macros.at
-+++ b/tests/ovn-macros.at
-@@ -1314,6 +1314,38 @@ send_na() {
- check as $hv ovs-appctl netdev-dummy/receive $dev $packet >&2
- }
+-NETNS_DAEMONIZE([bgp-daemon], [nc -l -6 -k fe80::200:2ff:fe01:203%bgp-daemon 179], [bgp_v6.pid])
+-NS_CHECK_EXEC([ext-foo], [echo "BGP IPv6 server traffic" | nc --send-only -6 fe80::200:2ff:fe01:203%ext-foo 179])
++NETNS_DAEMONIZE([bgp-daemon], [ncat -l -6 -k fe80::200:2ff:fe01:203%bgp-daemon 179], [bgp_v6.pid])
++NS_CHECK_EXEC([ext-foo], [echo "BGP IPv6 server traffic" | ncat --send-only -6 fe80::200:2ff:fe01:203%ext-foo 179])
-+send_udp() {
-+ local hv=$1 dev=$2 hdst=$3 hsrc=$4 idst=$5 isrc=$6
-+ local packet=$(fmt_pkt "Ether(dst='${hdst}', src='${hsrc}')/ \
-+ IP(dst='${idst}', src='${isrc}')/UDP()")
-+ check as $hv ovs-appctl netdev-dummy/receive $dev $packet
-+}
-+
-+send_udp6() {
-+ local hv=$1 dev=$2 hdst=$3 hsrc=$4 idst=$5 isrc=$6
-+ local packet=$(fmt_pkt "Ether(dst='${hdst}', src='${hsrc}')/ \
-+ IPv6(dst='${idst}', src='${isrc}')/UDP()")
-+ check as $hv ovs-appctl netdev-dummy/receive $dev $packet
-+}
-+
-+dump_arp() {
-+ local op=$1 eth_src=$2 eth_dst=$3 spa=$4 tpa=$5 hwdst=$6
-+
-+ local packet=$(fmt_pkt "Ether(dst='${eth_dst}', src='${eth_src}')/ \
-+ ARP(op=$op, hwsrc='${eth_src}', hwdst='${hwdst}', \
-+ psrc='${spa}', pdst='${tpa}')")
-+ echo $packet
-+}
-+
-+dump_ns() {
-+ local hdst=$1 hsrc=$2 idst=$3 isrc=$4 tgt=$5
-+ local packet=$(fmt_pkt "Ether(dst='${hdst}', src='${hsrc}')/ \
-+ IPv6(dst='${idst}', src='${isrc}')/ \
-+ ICMPv6ND_NS(tgt='${tgt}')/ \
-+ ICMPv6NDOptSrcLLAddr(lladdr='${hsrc}')")
-+ echo $packet
-+}
-+
- # Wrapper on top of ovn-trace, stripping some things and storing the trace
- # output to a file called 'trace'. For now it strips the rows starting
- # with a '#'. This should correspond to the flow key and might be displayed
-diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
-index f1349f2133..80edbb284e 100644
---- a/tests/ovn-northd.at
-+++ b/tests/ovn-northd.at
-@@ -7448,6 +7448,9 @@ AT_CHECK([grep lr_in_admission lrflows | grep cr-DR | ovn_strip_lflows], [0], [d
- ])
- # Check the flows in lr_in_lookup_neighbor stage
- AT_CHECK([grep lr_in_lookup_neighbor lrflows | grep cr-DR | ovn_strip_lflows], [0], [dnl
-+ table=??(lr_in_lookup_neighbor), priority=100 , match=(inport == "DR-S1" && arp.spa == 172.16.1.0/24 && arp.op == 1 && is_chassis_resident("cr-DR-S1")), action=(reg9[[2]] = lookup_arp(inport, arp.spa, arp.sha); next;)
-+ table=??(lr_in_lookup_neighbor), priority=100 , match=(inport == "DR-S2" && arp.spa == 172.16.2.0/24 && arp.op == 1 && is_chassis_resident("cr-DR-S2")), action=(reg9[[2]] = lookup_arp(inport, arp.spa, arp.sha); next;)
-+ table=??(lr_in_lookup_neighbor), priority=100 , match=(inport == "DR-S3" && arp.spa == 172.16.3.0/24 && arp.op == 1 && is_chassis_resident("cr-DR-S3")), action=(reg9[[2]] = lookup_arp(inport, arp.spa, arp.sha); next;)
- table=??(lr_in_lookup_neighbor), priority=120 , match=(inport == "DR-S1" && (nd_na || nd_ns) && eth.mcast && !is_chassis_resident("cr-DR-S1")), action=(reg9[[2]] = 1; next;)
- table=??(lr_in_lookup_neighbor), priority=120 , match=(inport == "DR-S2" && (nd_na || nd_ns) && eth.mcast && !is_chassis_resident("cr-DR-S2")), action=(reg9[[2]] = 1; next;)
- table=??(lr_in_lookup_neighbor), priority=120 , match=(inport == "DR-S3" && (nd_na || nd_ns) && eth.mcast && !is_chassis_resident("cr-DR-S3")), action=(reg9[[2]] = 1; next;)
-@@ -7636,7 +7639,9 @@ AT_CHECK([grep -e "ls_in_.*_fdb.*S1-vm1" S1flows | ovn_strip_lflows], [0], [dnl
- ])
-
- #Verify the flows for a non-default port type (localnet port)
--AT_CHECK([grep -e "ls_in_.*_fdb.*S1-localnet" S1flows], [1], [])
-+AT_CHECK([grep -e "ls_in_.*_fdb.*S1-localnet" S1flows | ovn_strip_lflows], [0], [dnl
-+ table=??(ls_in_lookup_fdb ), priority=50 , match=(inport == "S1-localnet"), action=(flags.localnet = 1; next;)
-+])
+ # Perform same set of checks as above for BFD daemon.
+ # We need to manually check that the message arrived on the receiving end as Ncat will
+ # produce false positive results over UDP due to lack of ICMP port unreachable messages
+ # from LRP's IP.
+-NETNS_DAEMONIZE([bgp-daemon], [nc -l -u 172.16.1.1 3784 > bgp-daemon_bfd_v4.out], [bfd_v4.pid])
+-NS_CHECK_EXEC([ext-foo], [echo "from ext-foo: BFD IPv4 server traffic" | nc -u 172.16.1.1 3784])
++NETNS_DAEMONIZE([bgp-daemon], [ncat -l -u 172.16.1.1 3784 > bgp-daemon_bfd_v4.out], [bfd_v4.pid])
++NS_CHECK_EXEC([ext-foo], [echo "from ext-foo: BFD IPv4 server traffic" | ncat -u 172.16.1.1 3784])
+ AT_CHECK([cat bgp-daemon_bfd_v4.out], [0], [dnl
+ from ext-foo: BFD IPv4 server traffic
+ ])
- OVN_CLEANUP_NORTHD
- AT_CLEANUP
-@@ -9945,6 +9950,7 @@ AT_CHECK([ovn-nbctl --wait=sb sync])
- # Check MAC learning flows with 'localnet_learn_fdb' default (false)
- AT_CHECK([ovn-sbctl dump-flows ls0 | grep -e 'ls_in_\(put\|lookup\)_fdb' | ovn_strip_lflows], [0], [dnl
- table=??(ls_in_lookup_fdb ), priority=0 , match=(1), action=(next;)
-+ table=??(ls_in_lookup_fdb ), priority=50 , match=(inport == "ln_port"), action=(flags.localnet = 1; next;)
- table=??(ls_in_put_fdb ), priority=0 , match=(1), action=(next;)
- ])
-
-@@ -9953,6 +9959,7 @@ AT_CHECK([ovn-nbctl --wait=sb lsp-set-options ln_port localnet_learn_fdb=true])
- AT_CHECK([ovn-sbctl dump-flows ls0 | grep -e 'ls_in_\(put\|lookup\)_fdb' | ovn_strip_lflows], [0], [dnl
- table=??(ls_in_lookup_fdb ), priority=0 , match=(1), action=(next;)
- table=??(ls_in_lookup_fdb ), priority=100 , match=(inport == "ln_port"), action=(flags.localnet = 1; reg0[[11]] = lookup_fdb(inport, eth.src); next;)
-+ table=??(ls_in_lookup_fdb ), priority=50 , match=(inport == "ln_port"), action=(flags.localnet = 1; next;)
- table=??(ls_in_put_fdb ), priority=0 , match=(1), action=(next;)
- table=??(ls_in_put_fdb ), priority=100 , match=(inport == "ln_port" && reg0[[11]] == 0), action=(put_fdb(inport, eth.src); next;)
- ])
-@@ -9961,6 +9968,7 @@ AT_CHECK([ovn-sbctl dump-flows ls0 | grep -e 'ls_in_\(put\|lookup\)_fdb' | ovn_s
- AT_CHECK([ovn-nbctl --wait=sb lsp-set-options ln_port localnet_learn_fdb=false])
- AT_CHECK([ovn-sbctl dump-flows ls0 | grep -e 'ls_in_\(put\|lookup\)_fdb' | ovn_strip_lflows], [0], [dnl
- table=??(ls_in_lookup_fdb ), priority=0 , match=(1), action=(next;)
-+ table=??(ls_in_lookup_fdb ), priority=50 , match=(inport == "ln_port"), action=(flags.localnet = 1; next;)
- table=??(ls_in_put_fdb ), priority=0 , match=(1), action=(next;)
- ])
-
-@@ -10310,6 +10318,110 @@ OVN_CLEANUP_NORTHD
- AT_CLEANUP
+-NETNS_DAEMONIZE([bgp-daemon], [nc -l -6 -u fe80::200:2ff:fe01:203%bgp-daemon 3784 > bgp-daemon_bfd_v6.out], [bfd_v6.pid])
+-NS_CHECK_EXEC([ext-foo], [echo "from ext-foo: BFD IPv6 server traffic" | nc -u -6 fe80::200:2ff:fe01:203%ext-foo 3784])
++NETNS_DAEMONIZE([bgp-daemon], [ncat -l -6 -u fe80::200:2ff:fe01:203%bgp-daemon 3784 > bgp-daemon_bfd_v6.out], [bfd_v6.pid])
++NS_CHECK_EXEC([ext-foo], [echo "from ext-foo: BFD IPv6 server traffic" | ncat -u -6 fe80::200:2ff:fe01:203%ext-foo 3784])
+ AT_CHECK([cat bgp-daemon_bfd_v6.out], [0], [dnl
+ from ext-foo: BFD IPv6 server traffic
])
-+OVN_FOR_EACH_NORTHD_NO_HV([
-+AT_SETUP([ARP/ND responder for localnet-sourced requests])
-+ovn_start
-+
-+dnl Switch with localnet port.
-+check ovn-nbctl ls-add ls1
-+check ovn-nbctl lsp-add-localnet-port ls1 ln1 physnet1
-+check ovn-nbctl lsp-add ls1 vm1 \
-+ -- lsp-set-addresses vm1 "00:00:00:00:00:01 10.0.0.1 fd01::1"
-+check ovn-nbctl lsp-add ls1 vm2 \
-+ -- lsp-set-addresses vm2 "00:00:00:00:00:02 10.0.0.2 fd01::2"
-+
-+dnl Switch without localnet port.
-+check ovn-nbctl ls-add ls2
-+check ovn-nbctl --wait=sb lsp-add ls2 vm3 \
-+ -- lsp-set-addresses vm3 "00:00:00:00:00:03 10.0.0.3 fd01::3"
-+
-+AS_BOX([FDB learning disabled])
-+
-+dnl ls1: ls_in_lookup_fdb should have priority 0 default +
-+dnl priority 50 flags.localnet.
-+AT_CHECK([ovn-sbctl dump-flows ls1 | grep -e 'ls_in_lookup_fdb' | ovn_strip_lflows], [0], [dnl
-+ table=??(ls_in_lookup_fdb ), priority=0 , match=(1), action=(next;)
-+ table=??(ls_in_lookup_fdb ), priority=50 , match=(inport == "ln1"), action=(flags.localnet = 1; next;)
-+])
-+
-+dnl ls1: ls_in_arp_rsp should include flags.localnet condition for
-+dnl priority 50 ARP/ND reply flows but NOT for priority 100 self-reply
-+dnl flows (since those match on inport == VIF, flags.localnet is always 0).
-+AT_CHECK([ovn-sbctl dump-flows ls1 | grep -e 'ls_in_arp_rsp' | ovn_strip_lflows], [0], [dnl
-+ table=??(ls_in_arp_rsp ), priority=0 , match=(1), action=(next;)
-+ table=??(ls_in_arp_rsp ), priority=100 , match=(arp.tpa == 10.0.0.1 && arp.op == 1 && eth.dst == ff:ff:ff:ff:ff:ff && inport == "vm1"), action=(next;)
-+ table=??(ls_in_arp_rsp ), priority=100 , match=(arp.tpa == 10.0.0.2 && arp.op == 1 && eth.dst == ff:ff:ff:ff:ff:ff && inport == "vm2"), action=(next;)
-+ table=??(ls_in_arp_rsp ), priority=100 , match=(nd_ns_mcast && ip6.dst == ff02::1:ff00:1 && nd.target == fd01::1 && inport == "vm1"), action=(next;)
-+ table=??(ls_in_arp_rsp ), priority=100 , match=(nd_ns_mcast && ip6.dst == ff02::1:ff00:2 && nd.target == fd01::2 && inport == "vm2"), action=(next;)
-+ table=??(ls_in_arp_rsp ), priority=50 , match=(arp.tpa == 10.0.0.1 && arp.op == 1 && eth.dst == ff:ff:ff:ff:ff:ff && ((flags.localnet == 1 && is_chassis_resident("vm1")) || flags.localnet == 0)), action=(eth.dst = eth.src; eth.src = 00:00:00:00:00:01; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = 00:00:00:00:00:01; arp.tpa = arp.spa; arp.spa = 10.0.0.1; outport = inport; flags.loopback = 1; output;)
-+ table=??(ls_in_arp_rsp ), priority=50 , match=(arp.tpa == 10.0.0.2 && arp.op == 1 && eth.dst == ff:ff:ff:ff:ff:ff && ((flags.localnet == 1 && is_chassis_resident("vm2")) || flags.localnet == 0)), action=(eth.dst = eth.src; eth.src = 00:00:00:00:00:02; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = 00:00:00:00:00:02; arp.tpa = arp.spa; arp.spa = 10.0.0.2; outport = inport; flags.loopback = 1; output;)
-+ table=??(ls_in_arp_rsp ), priority=50 , match=(nd_ns_mcast && ip6.dst == ff02::1:ff00:1 && nd.target == fd01::1 && ((flags.localnet == 1 && is_chassis_resident("vm1")) || flags.localnet == 0)), action=(nd_na { eth.src = 00:00:00:00:00:01; ip6.src = fd01::1; nd.target = fd01::1; nd.tll = 00:00:00:00:00:01; outport = inport; flags.loopback = 1; output; };)
-+ table=??(ls_in_arp_rsp ), priority=50 , match=(nd_ns_mcast && ip6.dst == ff02::1:ff00:2 && nd.target == fd01::2 && ((flags.localnet == 1 && is_chassis_resident("vm2")) || flags.localnet == 0)), action=(nd_na { eth.src = 00:00:00:00:00:02; ip6.src = fd01::2; nd.target = fd01::2; nd.tll = 00:00:00:00:00:02; outport = inport; flags.loopback = 1; output; };)
-+])
-+
-+dnl ls2: ls_in_arp_rsp should NOT include flags.localnet condition.
-+AT_CHECK([ovn-sbctl dump-flows ls2 | grep -e 'ls_in_arp_rsp' | ovn_strip_lflows], [0], [dnl
-+ table=??(ls_in_arp_rsp ), priority=0 , match=(1), action=(next;)
-+ table=??(ls_in_arp_rsp ), priority=100 , match=(arp.tpa == 10.0.0.3 && arp.op == 1 && eth.dst == ff:ff:ff:ff:ff:ff && inport == "vm3"), action=(next;)
-+ table=??(ls_in_arp_rsp ), priority=100 , match=(nd_ns_mcast && ip6.dst == ff02::1:ff00:3 && nd.target == fd01::3 && inport == "vm3"), action=(next;)
-+ table=??(ls_in_arp_rsp ), priority=50 , match=(arp.tpa == 10.0.0.3 && arp.op == 1 && eth.dst == ff:ff:ff:ff:ff:ff), action=(eth.dst = eth.src; eth.src = 00:00:00:00:00:03; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = 00:00:00:00:00:03; arp.tpa = arp.spa; arp.spa = 10.0.0.3; outport = inport; flags.loopback = 1; output;)
-+ table=??(ls_in_arp_rsp ), priority=50 , match=(nd_ns_mcast && ip6.dst == ff02::1:ff00:3 && nd.target == fd01::3), action=(nd_na { eth.src = 00:00:00:00:00:03; ip6.src = fd01::3; nd.target = fd01::3; nd.tll = 00:00:00:00:00:03; outport = inport; flags.loopback = 1; output; };)
-+])
-+
-+dnl ls2: ls_in_lookup_fdb should only have priority 0 default,
-+dnl no priority 50 flags.localnet.
-+AT_CHECK([ovn-sbctl dump-flows ls2 | grep -e 'ls_in_lookup_fdb' | ovn_strip_lflows], [0], [dnl
-+ table=??(ls_in_lookup_fdb ), priority=0 , match=(1), action=(next;)
-+])
-+
-+AS_BOX([Enable FDB learning on ln1])
-+check ovn-nbctl --wait=sb lsp-set-options ln1 localnet_learn_fdb=true
-+
-+dnl ls1: ls_in_lookup_fdb should have priority 100 FDB +
-+dnl priority 50 fallback.
-+AT_CHECK([ovn-sbctl dump-flows ls1 | grep -e 'ls_in_lookup_fdb' | ovn_strip_lflows], [0], [dnl
-+ table=??(ls_in_lookup_fdb ), priority=0 , match=(1), action=(next;)
-+ table=??(ls_in_lookup_fdb ), priority=100 , match=(inport == "ln1"), action=(flags.localnet = 1; reg0[[11]] = lookup_fdb(inport, eth.src); next;)
-+ table=??(ls_in_lookup_fdb ), priority=50 , match=(inport == "ln1"), action=(flags.localnet = 1; next;)
-+])
-+
-+dnl ls1: ls_in_arp_rsp should be unchanged.
-+AT_CHECK([ovn-sbctl dump-flows ls1 | grep -e 'ls_in_arp_rsp' | ovn_strip_lflows], [0], [dnl
-+ table=??(ls_in_arp_rsp ), priority=0 , match=(1), action=(next;)
-+ table=??(ls_in_arp_rsp ), priority=100 , match=(arp.tpa == 10.0.0.1 && arp.op == 1 && eth.dst == ff:ff:ff:ff:ff:ff && inport == "vm1"), action=(next;)
-+ table=??(ls_in_arp_rsp ), priority=100 , match=(arp.tpa == 10.0.0.2 && arp.op == 1 && eth.dst == ff:ff:ff:ff:ff:ff && inport == "vm2"), action=(next;)
-+ table=??(ls_in_arp_rsp ), priority=100 , match=(nd_ns_mcast && ip6.dst == ff02::1:ff00:1 && nd.target == fd01::1 && inport == "vm1"), action=(next;)
-+ table=??(ls_in_arp_rsp ), priority=100 , match=(nd_ns_mcast && ip6.dst == ff02::1:ff00:2 && nd.target == fd01::2 && inport == "vm2"), action=(next;)
-+ table=??(ls_in_arp_rsp ), priority=50 , match=(arp.tpa == 10.0.0.1 && arp.op == 1 && eth.dst == ff:ff:ff:ff:ff:ff && ((flags.localnet == 1 && is_chassis_resident("vm1")) || flags.localnet == 0)), action=(eth.dst = eth.src; eth.src = 00:00:00:00:00:01; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = 00:00:00:00:00:01; arp.tpa = arp.spa; arp.spa = 10.0.0.1; outport = inport; flags.loopback = 1; output;)
-+ table=??(ls_in_arp_rsp ), priority=50 , match=(arp.tpa == 10.0.0.2 && arp.op == 1 && eth.dst == ff:ff:ff:ff:ff:ff && ((flags.localnet == 1 && is_chassis_resident("vm2")) || flags.localnet == 0)), action=(eth.dst = eth.src; eth.src = 00:00:00:00:00:02; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = 00:00:00:00:00:02; arp.tpa = arp.spa; arp.spa = 10.0.0.2; outport = inport; flags.loopback = 1; output;)
-+ table=??(ls_in_arp_rsp ), priority=50 , match=(nd_ns_mcast && ip6.dst == ff02::1:ff00:1 && nd.target == fd01::1 && ((flags.localnet == 1 && is_chassis_resident("vm1")) || flags.localnet == 0)), action=(nd_na { eth.src = 00:00:00:00:00:01; ip6.src = fd01::1; nd.target = fd01::1; nd.tll = 00:00:00:00:00:01; outport = inport; flags.loopback = 1; output; };)
-+ table=??(ls_in_arp_rsp ), priority=50 , match=(nd_ns_mcast && ip6.dst == ff02::1:ff00:2 && nd.target == fd01::2 && ((flags.localnet == 1 && is_chassis_resident("vm2")) || flags.localnet == 0)), action=(nd_na { eth.src = 00:00:00:00:00:02; ip6.src = fd01::2; nd.target = fd01::2; nd.tll = 00:00:00:00:00:02; outport = inport; flags.loopback = 1; output; };)
-+])
-+
-+AS_BOX([Disable FDB learning])
-+check ovn-nbctl --wait=sb lsp-set-options ln1 localnet_learn_fdb=false
-+
-+AT_CHECK([ovn-sbctl dump-flows ls1 | grep -e 'ls_in_lookup_fdb' | ovn_strip_lflows], [0], [dnl
-+ table=??(ls_in_lookup_fdb ), priority=0 , match=(1), action=(next;)
-+ table=??(ls_in_lookup_fdb ), priority=50 , match=(inport == "ln1"), action=(flags.localnet = 1; next;)
-+])
-+
-+AT_CHECK([ovn-sbctl dump-flows ls1 | grep -e 'ls_in_arp_rsp' | ovn_strip_lflows], [0], [dnl
-+ table=??(ls_in_arp_rsp ), priority=0 , match=(1), action=(next;)
-+ table=??(ls_in_arp_rsp ), priority=100 , match=(arp.tpa == 10.0.0.1 && arp.op == 1 && eth.dst == ff:ff:ff:ff:ff:ff && inport == "vm1"), action=(next;)
-+ table=??(ls_in_arp_rsp ), priority=100 , match=(arp.tpa == 10.0.0.2 && arp.op == 1 && eth.dst == ff:ff:ff:ff:ff:ff && inport == "vm2"), action=(next;)
-+ table=??(ls_in_arp_rsp ), priority=100 , match=(nd_ns_mcast && ip6.dst == ff02::1:ff00:1 && nd.target == fd01::1 && inport == "vm1"), action=(next;)
-+ table=??(ls_in_arp_rsp ), priority=100 , match=(nd_ns_mcast && ip6.dst == ff02::1:ff00:2 && nd.target == fd01::2 && inport == "vm2"), action=(next;)
-+ table=??(ls_in_arp_rsp ), priority=50 , match=(arp.tpa == 10.0.0.1 && arp.op == 1 && eth.dst == ff:ff:ff:ff:ff:ff && ((flags.localnet == 1 && is_chassis_resident("vm1")) || flags.localnet == 0)), action=(eth.dst = eth.src; eth.src = 00:00:00:00:00:01; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = 00:00:00:00:00:01; arp.tpa = arp.spa; arp.spa = 10.0.0.1; outport = inport; flags.loopback = 1; output;)
-+ table=??(ls_in_arp_rsp ), priority=50 , match=(arp.tpa == 10.0.0.2 && arp.op == 1 && eth.dst == ff:ff:ff:ff:ff:ff && ((flags.localnet == 1 && is_chassis_resident("vm2")) || flags.localnet == 0)), action=(eth.dst = eth.src; eth.src = 00:00:00:00:00:02; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = 00:00:00:00:00:02; arp.tpa = arp.spa; arp.spa = 10.0.0.2; outport = inport; flags.loopback = 1; output;)
-+ table=??(ls_in_arp_rsp ), priority=50 , match=(nd_ns_mcast && ip6.dst == ff02::1:ff00:1 && nd.target == fd01::1 && ((flags.localnet == 1 && is_chassis_resident("vm1")) || flags.localnet == 0)), action=(nd_na { eth.src = 00:00:00:00:00:01; ip6.src = fd01::1; nd.target = fd01::1; nd.tll = 00:00:00:00:00:01; outport = inport; flags.loopback = 1; output; };)
-+ table=??(ls_in_arp_rsp ), priority=50 , match=(nd_ns_mcast && ip6.dst == ff02::1:ff00:2 && nd.target == fd01::2 && ((flags.localnet == 1 && is_chassis_resident("vm2")) || flags.localnet == 0)), action=(nd_na { eth.src = 00:00:00:00:00:02; ip6.src = fd01::2; nd.target = fd01::2; nd.tll = 00:00:00:00:00:02; outport = inport; flags.loopback = 1; output; };)
-+])
-+
-+OVN_CLEANUP_NORTHD
-+AT_CLEANUP
-+])
-+
- OVN_FOR_EACH_NORTHD_NO_HV([
- AT_SETUP([Address set incremental processing])
- ovn_start
-@@ -14294,7 +14406,7 @@ AT_CHECK([grep -Fe "172.168.0.110" -e "172.168.0.120" -e "10.0.0.3" -e "20.0.0.3
- table=??(lr_out_undnat ), priority=100 , match=(ip && ip4.src == 20.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_dnat;)
+ # Verify connection in other direction. i.e when BGP daemon running on "bgp-daemon" port
+ # makes a client connection to its peer
+-NETNS_DAEMONIZE([ext-foo], [nc -l -k 172.16.1.100 179], [reply_bgp_v4.pid])
+-NS_CHECK_EXEC([bgp-daemon], [echo "BGP IPv4 client traffic" | nc --send-only 172.16.1.100 179])
++NETNS_DAEMONIZE([ext-foo], [ncat -l -k 172.16.1.100 179], [reply_bgp_v4.pid])
++NS_CHECK_EXEC([bgp-daemon], [echo "BGP IPv4 client traffic" | ncat --send-only 172.16.1.100 179])
+
+-NETNS_DAEMONIZE([ext-foo], [nc -l -6 -k fe80::210:10ff:fe01:213%ext-foo 179], [reply_bgp_v6.pid])
+-NS_CHECK_EXEC([bgp-daemon], [echo "BGP IPv6 client traffic" | nc --send-only -6 fe80::210:10ff:fe01:213%bgp-daemon 179])
++NETNS_DAEMONIZE([ext-foo], [ncat -l -6 -k fe80::210:10ff:fe01:213%ext-foo 179], [reply_bgp_v6.pid])
++NS_CHECK_EXEC([bgp-daemon], [echo "BGP IPv6 client traffic" | ncat --send-only -6 fe80::210:10ff:fe01:213%bgp-daemon 179])
+
+ # Perform same checks in other direction for BFD daemon
+-NETNS_DAEMONIZE([ext-foo], [nc -l -u 172.16.1.100 3784 > ext-foo_bfd_v4.out], [reply_bfd_v4.pid])
+-NS_CHECK_EXEC([bgp-daemon], [echo "from bgp-daemon: BFD IPv4 client traffic" | nc -u 172.16.1.100 3784])
++NETNS_DAEMONIZE([ext-foo], [ncat -l -u 172.16.1.100 3784 > ext-foo_bfd_v4.out], [reply_bfd_v4.pid])
++NS_CHECK_EXEC([bgp-daemon], [echo "from bgp-daemon: BFD IPv4 client traffic" | ncat -u 172.16.1.100 3784])
+ AT_CHECK([cat ext-foo_bfd_v4.out], [0], [dnl
+ from bgp-daemon: BFD IPv4 client traffic
])
--AT_CHECK([grep -Fe "172.168.0.110" -e "172.168.0.120" -e "10.0.0.3" -e "20.0.0.3" -e "30:54:00:00:00:03" -e "sw0-port1" publicflows | ovn_strip_lflows | grep -v "reg0.*22"], [0], [dnl
-+AT_CHECK([grep -Fe "172.168.0.110" -e "172.168.0.120" -e "10.0.0.3" -e "20.0.0.3" -e "30:54:00:00:00:03" -e "sw0-port1" publicflows | ovn_strip_lflows], [0], [dnl
- table=??(ls_in_l2_lkup ), priority=50 , match=(eth.dst == 30:54:00:00:00:03 && is_chassis_resident("sw0-port1")), action=(outport = "public-lr0"; output;)
- table=??(ls_in_l2_lkup ), priority=75 , match=(eth.src == {00:00:00:00:ff:02, 30:54:00:00:00:03} && eth.dst == ff:ff:ff:ff:ff:ff && (arp.op == 1 || rarp.op == 3 || nd_ns)), action=(outport = "_MC_flood_l2"; output;)
- table=??(ls_in_l2_lkup ), priority=80 , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 172.168.0.110), action=(clone {outport = "public-lr0"; output; }; outport = "_MC_flood_l2"; output;)
-@@ -16064,6 +16176,53 @@ OVN_CLEANUP_NORTHD
- AT_CLEANUP
+-NETNS_DAEMONIZE([ext-foo], [nc -l -6 -u fe80::210:10ff:fe01:213%ext-foo 3784 > ext-foo_bfd_v6.out], [reply_bfd_v6.pid])
+-NS_CHECK_EXEC([bgp-daemon], [echo "from bgp-daemon: BFD IPv6 client traffic" | nc -u -6 fe80::210:10ff:fe01:213%bgp-daemon 3784])
++NETNS_DAEMONIZE([ext-foo], [ncat -l -6 -u fe80::210:10ff:fe01:213%ext-foo 3784 > ext-foo_bfd_v6.out], [reply_bfd_v6.pid])
++NS_CHECK_EXEC([bgp-daemon], [echo "from bgp-daemon: BFD IPv6 client traffic" | ncat -u -6 fe80::210:10ff:fe01:213%bgp-daemon 3784])
+ AT_CHECK([cat ext-foo_bfd_v6.out], [0], [dnl
+ from bgp-daemon: BFD IPv6 client traffic
])
-+OVN_FOR_EACH_NORTHD_NO_HV([
-+AT_SETUP([dynamic-routing - learned routes on unnumbered LRP])
-+AT_KEYWORDS([dynamic-routing])
-+ovn_start
-+
-+check ovn-nbctl lr-add lr0
-+check ovn-nbctl set Logical_Router lr0 option:dynamic-routing=true
-+check ovn-nbctl lrp-add lr0 lrp1 00:00:00:00:ff:01 10.0.0.1/24
-+check ovn-nbctl lrp-add lr0 lrp2 00:00:00:00:ff:02
-+check ovn-nbctl --wait=sb lrp-add lr0 lrp3 00:00:00:00:ff:03
-+datapath=$(fetch_column datapath_binding _uuid external_ids:name=lr0)
-+lrp2=$(fetch_column port_binding _uuid logical_port=lrp2)
-+lrp3=$(fetch_column port_binding _uuid logical_port=lrp3)
-+
-+check_uuid ovn-sbctl create Learned_Route \
-+ datapath=$datapath \
-+ logical_port=$lrp2 \
-+ ip_prefix=42.42.42.42/32 \
-+ nexthop=192.168.2.42
-+check ovn-nbctl --wait=sb sync
-+
-+ovn-sbctl dump-flows lr0 > lr0flows
-+AT_CHECK([grep -F "42.42.42.42" lr0flows | ovn_strip_lflows], [0], [dnl
-+ table=??(lr_in_ip_routing ), priority=258 , match=(reg7 == 0 && ip4.dst == 42.42.42.42/32), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 192.168.2.42; eth.src = 00:00:00:00:ff:02; outport = "lrp2"; flags.loopback = 1; reg9[[9]] = 1; next;)
-+])
-+
-+check_uuid ovn-sbctl create Learned_Route \
-+ datapath=$datapath \
-+ logical_port=$lrp3 \
-+ ip_prefix=42.42.42.42/32 \
-+ nexthop=192.168.3.42
-+check ovn-nbctl --wait=sb sync
-+
-+ovn-sbctl dump-flows lr0 > lr0flows
-+AT_CHECK([grep -w "lr_in_ip_routing" lr0flows | grep -F "42.42.42.42" | ovn_strip_lflows], [0], [dnl
-+ table=??(lr_in_ip_routing ), priority=258 , match=(reg7 == 0 && ip4.dst == 42.42.42.42/32), action=(ip.ttl--; flags.loopback = 1; reg8[[0..15]] = 1; reg8[[16..31]] = select(1, 2);)
-+])
-+
-+AT_CHECK([grep "lr_in_ip_routing_ecmp" lr0flows | grep "42" | sed -e 's/reg8\[[16..31\]] == [[12]]/reg8\[[16..31\]] == ??/g' | ovn_strip_lflows], [0], [dnl
-+ table=??(lr_in_ip_routing_ecmp), priority=100 , match=(reg8[[0..15]] == 1 && reg8[[16..31]] == ??), action=(reg0 = 192.168.2.42; eth.src = 00:00:00:00:ff:02; outport = "lrp2"; reg9[[9]] = 1; next;)
-+ table=??(lr_in_ip_routing_ecmp), priority=100 , match=(reg8[[0..15]] == 1 && reg8[[16..31]] == ??), action=(reg0 = 192.168.3.42; eth.src = 00:00:00:00:ff:03; outport = "lrp3"; reg9[[9]] = 1; next;)
-+])
-+
-+OVN_CLEANUP_NORTHD
-+AT_CLEANUP
-+])
-+
- OVN_FOR_EACH_NORTHD_NO_HV([
- AT_SETUP([dynamic-routing - route learning cleanup - router recreation])
- AT_KEYWORDS([dynamic-routing])
-@@ -18467,6 +18626,26 @@ OVN_CLEANUP_NORTHD
- AT_CLEANUP
+ # Verify that hosts on the internal network can reach external networks
+-NETNS_DAEMONIZE([ext-foo], [nc -l -k 172.16.1.100 2222], [nc_external.pid])
+-NS_CHECK_EXEC([bar1], [echo "TCP test" | nc -w 1 --send-only 172.16.1.100 2222])
++NETNS_DAEMONIZE([ext-foo], [ncat -l -k 172.16.1.100 2222], [nc_external.pid])
++NS_CHECK_EXEC([bar1], [echo "TCP test" | ncat -w 1 --send-only 172.16.1.100 2222])
+
+ OVN_CLEANUP_CONTROLLER([hv1])
+
+@@ -14370,7 +14370,7 @@ test
+ acl_uuid=$(fetch_column nb:ACL _uuid priority=1000)
+
+ # Update the ACL so that it no longer matches our client-server traffic
+-check ovn-nbctl set ACL $acl_uuid match="\"ip4.dst == 192.168.1.3\""
++check ovn-nbctl --wait=hv set ACL $acl_uuid match="\"ip4.dst == 192.168.1.3\""
+
+ # Send another packet from the client to the server.
+ AT_CHECK([printf "test\n" > /tmp/myfifo], [0], [dnl
+@@ -15027,14 +15027,14 @@ check ovn-nbctl --ecmp-symmetric-reply lr-route-add R1 172.16.2.0/24 172.16.1.3
+
+ wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+-NETNS_DAEMONIZE([alice], [nc -l -k 80], [alice.pid])
+-NETNS_DAEMONIZE([peter], [nc -l -k 80], [peter.pid])
++NETNS_DAEMONIZE([alice], [ncat -l -k 80], [alice.pid])
++NETNS_DAEMONIZE([peter], [ncat -l -k 80], [peter.pid])
+
+ NS_CHECK_EXEC([bob], [ping -q -c 3 -i 0.3 -w 2 192.168.1.2 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
])
+-NS_CHECK_EXEC([bob], [nc -z 192.168.1.2 80], [0])
++NS_CHECK_EXEC([bob], [ncat -z 192.168.1.2 80], [0])
-+OVN_FOR_EACH_NORTHD_NO_HV([
-+AT_SETUP([Datapath incremental processing index reuse])
-+ovn_start
-+
-+check ovn-nbctl --wait=sb ls-add sw-old
-+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
-+check ovn-nbctl --wait=sb ls-del sw-old -- ls-add sw-new
-+check_engine_compute northd recompute
-+CHECK_NO_CHANGE_AFTER_RECOMPUTE
-+
-+check ovn-nbctl --wait=sb lr-add lr-old
-+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
-+check ovn-nbctl --wait=sb lr-del lr-old -- lr-add lr-new
-+check_engine_compute northd recompute
-+CHECK_NO_CHANGE_AFTER_RECOMPUTE
-+
-+OVN_CLEANUP_NORTHD
-+AT_CLEANUP
-+])
-+
- OVN_FOR_EACH_NORTHD_NO_HV([
- AT_SETUP([Synced logical switch and router incremental procesesing])
- ovn_start
-@@ -19345,6 +19524,7 @@ check ovn-nbctl ls-add $nfsw
- for i in {1..4}; do
- port=$nfsw-p$i
- check ovn-nbctl lsp-add $nfsw $port
-+ check ovn-nbctl --wait=sb sync
- check ovn-sbctl set port_binding $port up=true
- check ovn-nbctl lsp-add $sw child-$i $port 100
- done
-@@ -19693,177 +19873,6 @@ OVN_CLEANUP_NORTHD
- AT_CLEANUP
+ wait_row_count ECMP_Nexthop 2
+ wait_column 'f0:00:00:01:02:04' ECMP_Nexthop mac nexthop='172.16.1.2'
+@@ -15055,7 +15055,7 @@ NS_CHECK_EXEC([bob], [ping -q -c 3 -i 0.3 -w 2 192.168.1.2 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
])
+-NS_CHECK_EXEC([bob], [nc -z 192.168.1.2 80], [0])
++NS_CHECK_EXEC([bob], [ncat -z 192.168.1.2 80], [0])
--OVN_FOR_EACH_NORTHD_NO_HV([
--AT_SETUP([Logical Switch ARP filtering])
--ovn_start
--
--check ovn-nbctl lr-add lr1
--check ovn-nbctl lrp-add lr1 down_link f0:00:00:00:00:f1 192.168.1.1/24
--
--check ovn-nbctl ls-add ls1
--check ovn-nbctl lsp-add ls1 up_link
--check ovn-nbctl lsp-add ls1 down_vif1
--check ovn-nbctl lsp-add ls1 down_vif2
--check ovn-nbctl lsp-add ls1 down_ext
--
--check ovn-nbctl set Logical_Switch_Port up_link \
-- type=router \
-- options:router-port=down_link \
-- addresses=router
--
--check ovn-nbctl lsp-set-addresses down_vif1 'f0:00:00:00:00:01 192.168.1.101'
--check ovn-nbctl lsp-set-addresses down_vif2 'f0:00:00:00:00:02 192.168.1.102'
--check ovn-nbctl lrp-set-gateway-chassis down_link hv1
--check ovn-nbctl --wait=sb sync
--AT_CHECK([ovn-sbctl lflow-list ls1 | grep ls_in_apply_port_sec | ovn_strip_lflows], [0], [dnl
-- table=??(ls_in_apply_port_sec), priority=0 , match=(1), action=(next;)
-- table=??(ls_in_apply_port_sec), priority=50 , match=(reg0[[15]] == 1), action=(drop;)
--])
--
--# Check localnet port addings trigger ls-arp flow
--check ovn-nbctl --wait=sb lsp-set-type down_ext localnet
--AT_CHECK([ovn-sbctl lflow-list ls1 | grep ls_in_apply_port_sec | ovn_strip_lflows], [0], [dnl
-- table=??(ls_in_apply_port_sec), priority=0 , match=(1), action=(next;)
-- table=??(ls_in_apply_port_sec), priority=50 , match=(reg0[[15]] == 1), action=(drop;)
-- table=??(ls_in_apply_port_sec), priority=70 , match=(reg0[[22]] == 1), action=(drop;)
-- table=??(ls_in_apply_port_sec), priority=75 , match=(reg0[[22]] == 1 && is_chassis_resident("cr-down_link")), action=(next;)
--])
--
--# Check nat adding to dgr attached to logical switch trigger ls-arp flow.
--check ovn-nbctl lr-nat-add lr1 dnat_and_snat 192.168.0.4 10.0.0.4
--check ovn-nbctl lr-nat-add lr1 dnat_and_snat 192.168.0.3 10.0.0.3 down_vif1 f0:00:00:00:00:03
--check ovn-nbctl --wait=sb sync
--AT_CHECK([ovn-sbctl lflow-list ls1 | grep ls_in_apply_port_sec | ovn_strip_lflows], [0], [dnl
-- table=??(ls_in_apply_port_sec), priority=0 , match=(1), action=(next;)
-- table=??(ls_in_apply_port_sec), priority=50 , match=(reg0[[15]] == 1), action=(drop;)
-- table=??(ls_in_apply_port_sec), priority=70 , match=(reg0[[22]] == 1), action=(drop;)
-- table=??(ls_in_apply_port_sec), priority=75 , match=(reg0[[22]] == 1 && is_chassis_resident("cr-down_link")), action=(next;)
-- table=??(ls_in_apply_port_sec), priority=85 , match=(reg0[[22]] == 1 && arp.tpa == 192.168.0.3 && is_chassis_resident("down_vif1")), action=(next;)
--])
--
--check ovn-nbctl --wait=sb lr-nat-del lr1 dnat_and_snat 192.168.0.3
--AT_CHECK([ovn-sbctl lflow-list ls1 | grep ls_in_apply_port_sec | ovn_strip_lflows], [0], [dnl
-- table=??(ls_in_apply_port_sec), priority=0 , match=(1), action=(next;)
-- table=??(ls_in_apply_port_sec), priority=50 , match=(reg0[[15]] == 1), action=(drop;)
-- table=??(ls_in_apply_port_sec), priority=70 , match=(reg0[[22]] == 1), action=(drop;)
-- table=??(ls_in_apply_port_sec), priority=75 , match=(reg0[[22]] == 1 && is_chassis_resident("cr-down_link")), action=(next;)
--])
--
--# Check changing logical port type to l2gateway.
--check ovn-nbctl --wait=sb lsp-set-type down_ext l2gateway
--AT_CHECK([ovn-sbctl lflow-list ls1 | grep ls_in_apply_port_sec | ovn_strip_lflows], [0], [dnl
-- table=??(ls_in_apply_port_sec), priority=0 , match=(1), action=(next;)
-- table=??(ls_in_apply_port_sec), priority=50 , match=(reg0[[15]] == 1), action=(drop;)
-- table=??(ls_in_apply_port_sec), priority=70 , match=(reg0[[22]] == 1), action=(drop;)
-- table=??(ls_in_apply_port_sec), priority=75 , match=(reg0[[22]] == 1 && is_chassis_resident("cr-down_link")), action=(next;)
--])
--
--# Check changing logical port type to vif.
--check ovn-nbctl --wait=sb lsp-set-type down_ext ''
--AT_CHECK([ovn-sbctl lflow-list ls1 | grep ls_in_apply_port_sec | ovn_strip_lflows], [0], [dnl
-- table=??(ls_in_apply_port_sec), priority=0 , match=(1), action=(next;)
-- table=??(ls_in_apply_port_sec), priority=50 , match=(reg0[[15]] == 1), action=(drop;)
--])
--
--# Check changing logical port type back to localnet.
--check ovn-nbctl --wait=sb lsp-set-type down_ext localnet
--AT_CHECK([ovn-sbctl lflow-list ls1 | grep ls_in_apply_port_sec | ovn_strip_lflows], [0], [dnl
-- table=??(ls_in_apply_port_sec), priority=0 , match=(1), action=(next;)
-- table=??(ls_in_apply_port_sec), priority=50 , match=(reg0[[15]] == 1), action=(drop;)
-- table=??(ls_in_apply_port_sec), priority=70 , match=(reg0[[22]] == 1), action=(drop;)
-- table=??(ls_in_apply_port_sec), priority=75 , match=(reg0[[22]] == 1 && is_chassis_resident("cr-down_link")), action=(next;)
--])
--
--# Check changing removing logical port.
--check ovn-nbctl --wait=sb lsp-del down_ext
--AT_CHECK([ovn-sbctl lflow-list ls1 | grep ls_in_apply_port_sec | ovn_strip_lflows], [0], [dnl
-- table=??(ls_in_apply_port_sec), priority=0 , match=(1), action=(next;)
-- table=??(ls_in_apply_port_sec), priority=50 , match=(reg0[[15]] == 1), action=(drop;)
--])
--
--OVN_CLEANUP_NORTHD
--AT_CLEANUP
--])
--
--OVN_FOR_EACH_NORTHD_NO_HV([
--AT_SETUP([Logical Switch ARP filtering - only distributed NATs])
--ovn_start
--
--check ovn-sbctl chassis-add ch1 geneve 127.0.0.1
--
--check ovn-nbctl ls-add sw0
--check ovn-nbctl lsp-add sw0 sw0-port1
--check ovn-nbctl lsp-set-addresses sw0-port1 "50:54:00:00:00:03 10.0.0.3 10.0.0.5"
--check ovn-nbctl lsp-add sw0 sw0-port2
--check ovn-nbctl lsp-set-addresses sw0-port2 "50:54:00:00:00:04 10.0.0.4 10.0.0.6"
--
--# Create a logical router and attach both logical switches
--check ovn-nbctl lr-add lr0
--check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24 1000::a/64
--check ovn-nbctl lsp-add-router-port sw0 sw0-lr0 lr0-sw0
--
--
--check ovn-nbctl ls-add public
--check ovn-nbctl lrp-add lr0 lr0-public 00:00:20:20:12:13 172.168.0.100/24
--check ovn-nbctl lsp-add public public-lr0 -- set Logical_Switch_Port public-lr0 \
-- type=router options:router-port=lr0-public \
-- -- lsp-set-addresses public-lr0 router
--
--check ovn-nbctl lrp-set-gateway-chassis lr0-public hv1
--
--# localnet port
--check ovn-nbctl lsp-add-localnet-port public ln-public public
--
--check ovn-nbctl lr-nat-add lr0 dnat_and_snat 172.168.0.50 10.0.0.3 sw0-port1 f0:00:00:00:00:03
--check ovn-nbctl lr-nat-add lr0 dnat_and_snat 172.168.0.55 10.0.0.5 sw0-port1 f0:00:00:00:00:03
--check ovn-nbctl lr-nat-add lr0 dnat_and_snat 172.168.0.60 10.0.0.4 sw0-port2 f0:00:00:00:00:04
--check ovn-nbctl lr-nat-add lr0 dnat_and_snat 172.168.0.66 10.0.0.6 sw0-port2 f0:00:00:00:00:04
--
--check ovn-nbctl --wait=sb sync
--
--AT_CHECK([ovn-sbctl lflow-list public | grep ls_in_apply_port_sec | ovn_strip_lflows], [0], [dnl
-- table=??(ls_in_apply_port_sec), priority=0 , match=(1), action=(next;)
-- table=??(ls_in_apply_port_sec), priority=50 , match=(reg0[[15]] == 1), action=(drop;)
-- table=??(ls_in_apply_port_sec), priority=70 , match=(reg0[[22]] == 1), action=(drop;)
-- table=??(ls_in_apply_port_sec), priority=75 , match=(reg0[[22]] == 1 && is_chassis_resident("cr-lr0-public")), action=(next;)
-- table=??(ls_in_apply_port_sec), priority=85 , match=(reg0[[22]] == 1 && arp.tpa == 172.168.0.50 && is_chassis_resident("sw0-port1")), action=(next;)
-- table=??(ls_in_apply_port_sec), priority=85 , match=(reg0[[22]] == 1 && arp.tpa == 172.168.0.55 && is_chassis_resident("sw0-port1")), action=(next;)
-- table=??(ls_in_apply_port_sec), priority=85 , match=(reg0[[22]] == 1 && arp.tpa == 172.168.0.60 && is_chassis_resident("sw0-port2")), action=(next;)
-- table=??(ls_in_apply_port_sec), priority=85 , match=(reg0[[22]] == 1 && arp.tpa == 172.168.0.66 && is_chassis_resident("sw0-port2")), action=(next;)
--])
--
--ovn-nbctl show
--ovn-sbctl show
--
--check ovn-nbctl clear logical_router_port lr0-public gateway_chassis
--check ovn-nbctl --wait=sb sync
--
--AT_CHECK([ovn-sbctl lflow-list public | grep ls_in_apply_port_sec | ovn_strip_lflows], [0], [dnl
-- table=??(ls_in_apply_port_sec), priority=0 , match=(1), action=(next;)
-- table=??(ls_in_apply_port_sec), priority=50 , match=(reg0[[15]] == 1), action=(drop;)
--])
--
--check ovn-nbctl --wait=sb ha-chassis-group-add hagrp1
--check ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch1 30
--hagrp1_uuid=$(fetch_column nb:ha_chassis_group _uuid name=hagrp1)
--check ovn-nbctl --wait=sb set logical_router_port lr0-public ha_chassis_group=$hagrp1_uuid
--check ovn-nbctl --wait=sb sync
--
--AT_CHECK([ovn-sbctl lflow-list public | grep ls_in_apply_port_sec | ovn_strip_lflows], [0], [dnl
-- table=??(ls_in_apply_port_sec), priority=0 , match=(1), action=(next;)
-- table=??(ls_in_apply_port_sec), priority=50 , match=(reg0[[15]] == 1), action=(drop;)
-- table=??(ls_in_apply_port_sec), priority=70 , match=(reg0[[22]] == 1), action=(drop;)
-- table=??(ls_in_apply_port_sec), priority=75 , match=(reg0[[22]] == 1 && is_chassis_resident("cr-lr0-public")), action=(next;)
-- table=??(ls_in_apply_port_sec), priority=85 , match=(reg0[[22]] == 1 && arp.tpa == 172.168.0.50 && is_chassis_resident("sw0-port1")), action=(next;)
-- table=??(ls_in_apply_port_sec), priority=85 , match=(reg0[[22]] == 1 && arp.tpa == 172.168.0.55 && is_chassis_resident("sw0-port1")), action=(next;)
-- table=??(ls_in_apply_port_sec), priority=85 , match=(reg0[[22]] == 1 && arp.tpa == 172.168.0.60 && is_chassis_resident("sw0-port2")), action=(next;)
-- table=??(ls_in_apply_port_sec), priority=85 , match=(reg0[[22]] == 1 && arp.tpa == 172.168.0.66 && is_chassis_resident("sw0-port2")), action=(next;)
--])
--
--OVN_CLEANUP_NORTHD
--AT_CLEANUP
--])
--
- OVN_FOR_EACH_NORTHD_NO_HV([
- AT_SETUP([IGMP northd crash])
- ovn_start
-@@ -20084,14 +20093,6 @@ AT_CHECK([cat lr1_lflows_before | grep lr_in_dnat | grep priority=120 | ovn_stri
- table=??(lr_in_dnat ), priority=120 , match=(ct.new && !ct.rel && ip6 && ip6.dst == 2000::1 && reg1[[16..23]] == 6 && reg1[[0..15]] == 80 && is_chassis_resident("cr-lr1-up")), action=(ct_lb_mark(backends=[[2001:db8:abcd:1::2]]:10882);)
+ wait_row_count ECMP_Nexthop 2
+ check_column 'f0:00:00:01:02:04' ECMP_Nexthop mac nexthop='172.16.1.2'
+@@ -15100,7 +15100,7 @@ NS_CHECK_EXEC([bob], [ping -q -c 3 -i 0.3 -w 2 192.168.1.2 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
])
+-NS_CHECK_EXEC([bob], [nc -z 192.168.1.2 80], [0])
++NS_CHECK_EXEC([bob], [ncat -z 192.168.1.2 80], [0])
--AT_CHECK([cat outside_lflows_before | grep ls_in_check_port_sec | grep priority=75 | ovn_strip_lflows], [0], [dnl
-- table=??(ls_in_check_port_sec), priority=75 , match=(arp.op == 1 && inport == "outside"), action=(reg0[[22]] = 1; next;)
--])
--
--AT_CHECK([cat outside_lflows_before | grep ls_in_apply_port_sec | grep priority=75 | ovn_strip_lflows], [0], [dnl
-- table=??(ls_in_apply_port_sec), priority=75 , match=(reg0[[22]] == 1 && is_chassis_resident("cr-lr1-up")), action=(next;)
--])
--
- check ovn-nbctl clear logical_router_port $lr1_up_uuid ha_chassis_group
- check ovn-nbctl ha-chassis-group-del gateway
- check ovn-nbctl ha-chassis-group-add gateway2
-@@ -20143,14 +20144,6 @@ AT_CHECK([cat lr1_lflows_after | grep lr_in_dnat | grep priority=120 | ovn_strip
- table=??(lr_in_dnat ), priority=120 , match=(ct.new && !ct.rel && ip6 && ip6.dst == 2000::1 && reg1[[16..23]] == 6 && reg1[[0..15]] == 80 && is_chassis_resident("cr-lr1-up")), action=(ct_lb_mark(backends=[[2001:db8:abcd:1::2]]:10882);)
+ AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.2.10) | \
+ sed -e 's/zone=[[0-9]]*/zone=<cleared>/' |
+@@ -15139,13 +15139,13 @@ NS_CHECK_EXEC([bob], [ping -q -c 3 -i 0.3 -w 2 192.168.1.2 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
])
+-NS_CHECK_EXEC([bob], [nc -z 192.168.1.2 80], [0])
++NS_CHECK_EXEC([bob], [ncat -z 192.168.1.2 80], [0])
--AT_CHECK([cat outside_lflows_after | grep ls_in_check_port_sec | grep priority=75 | ovn_strip_lflows], [0], [dnl
-- table=??(ls_in_check_port_sec), priority=75 , match=(arp.op == 1 && inport == "outside"), action=(reg0[[22]] = 1; next;)
--])
--
--AT_CHECK([cat outside_lflows_after | grep ls_in_apply_port_sec | grep priority=75 | ovn_strip_lflows], [0], [dnl
-- table=??(ls_in_apply_port_sec), priority=75 , match=(reg0[[22]] == 1), action=(next;)
--])
--
- check ovn-nbctl --wait=sb set load_balancer lb1_ipv6 options:distributed=true
-
- ovn-sbctl lflow-list outside > outside_lflows_after
-diff --git a/tests/ovn-performance.at b/tests/ovn-performance.at
-index 2bccbb06dd..114917832b 100644
---- a/tests/ovn-performance.at
-+++ b/tests/ovn-performance.at
-@@ -386,6 +386,13 @@ for i in 1 2; do
- done
+ NS_CHECK_EXEC([bob], [ping -q -c 3 -i 0.3 -w 2 192.168.2.2 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+-NS_CHECK_EXEC([bob], [nc -z 192.168.2.2 80], [0])
++NS_CHECK_EXEC([bob], [ncat -z 192.168.2.2 80], [0])
+
+ AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.2.10) | \
+ sed -e 's/zone=[[0-9]]*/zone=<cleared>/' |
+@@ -15276,15 +15276,15 @@ net.ipv6.conf.all.forwarding = 1
+ ])
+
+ check ovn-nbctl --wait=hv sync
+-NETNS_DAEMONIZE([alice], [nc -6 -l -k 80], [alice.pid])
+-NETNS_DAEMONIZE([peter], [nc -6 -l -k 80], [peter.pid])
++NETNS_DAEMONIZE([alice], [ncat -6 -l -k 80], [alice.pid])
++NETNS_DAEMONIZE([peter], [ncat -6 -l -k 80], [peter.pid])
+
+-NS_CHECK_EXEC([bob], [ping6 -q -c 3 -i 0.3 -w 2 fd11::2 | FORMAT_PING], \
++NS_CHECK_EXEC([bob], [ping -6 -q -c 3 -i 0.3 -w 2 fd11::2 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+
+-NS_CHECK_EXEC([bob], [nc -6 -z fd11::2 80], [0])
++NS_CHECK_EXEC([bob], [ncat -6 -z fd11::2 80], [0])
+
+ wait_row_count ECMP_Nexthop 2
+ wait_column 'f0:00:00:01:02:04' ECMP_Nexthop mac nexthop='"fd12::2"'
+@@ -15305,7 +15305,7 @@ NS_CHECK_EXEC([bob], [ping -6 -q -c 3 -i 0.3 -w 2 fd11::2 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+-NS_CHECK_EXEC([bob], [nc -6 -z fd11::2 80], [0])
++NS_CHECK_EXEC([bob], [ncat -6 -z fd11::2 80], [0])
+
+ wait_row_count ECMP_Nexthop 2
+ check_column 'f0:00:00:01:02:04' ECMP_Nexthop mac nexthop='"fd12::2"'
+@@ -15350,7 +15350,7 @@ NS_CHECK_EXEC([bob], [ping -6 -q -c 3 -i 0.3 -w 2 fd11::2 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+-NS_CHECK_EXEC([bob], [nc -6 -z fd11::2 80], [0])
++NS_CHECK_EXEC([bob], [ncat -6 -z fd11::2 80], [0])
+
+ AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd13::a) | \
+ sed -e 's/zone=[[0-9]]*/zone=<cleared>/' |
+@@ -15389,13 +15389,13 @@ NS_CHECK_EXEC([bob], [ping -6 -q -c 3 -i 0.3 -w 2 fd11::2 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+-NS_CHECK_EXEC([bob], [nc -6 -z fd11::2 80], [0])
++NS_CHECK_EXEC([bob], [ncat -6 -z fd11::2 80], [0])
+
+ NS_CHECK_EXEC([bob], [ping -6 -q -c 3 -i 0.3 -w 2 fd14::2 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+-NS_CHECK_EXEC([bob], [nc -6 -z fd14::2 80], [0])
++NS_CHECK_EXEC([bob], [ncat -6 -z fd14::2 80], [0])
+
+ AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd13::a) | \
+ sed -e 's/zone=[[0-9]]*/zone=<cleared>/' |
+@@ -17733,8 +17733,8 @@ NETNS_START_TCPDUMP([foo1], [-neei foo1 src 20.0.0.2 and tcp], [foo1])
+ NETNS_START_TCPDUMP([bar1], [-neei bar1 src 20.0.0.2 and tcp], [bar1])
+
+ check ovs-appctl dpctl/flush-conntrack
+-for i in $(seq 10); do
+- NS_CHECK_EXEC([alice1], [wget 172.16.1.1:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
++for i in $(seq 20); do
++ NS_CHECK_EXEC([alice1], [wget http://172.16.1.1:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
done
-+# Check that adding a new port with a non-default type does not trigger
-+# a physical_run.
-+OVN_CONTROLLER_EXPECT_NO_HIT(
-+ [hv1 hv2], [physical_run],
-+ [ovn-nbctl --wait=hv lsp-add ls1 lsp-remote -- lsp-set-type lsp-remote remote]
-+)
-+
- for i in 1 2; do
- j=$((i%2 + 1))
- as=as$i
-diff --git a/tests/ovn.at b/tests/ovn.at
-index e6cb5995af..5e0ed2635e 100644
---- a/tests/ovn.at
-+++ b/tests/ovn.at
-@@ -10184,6 +10184,199 @@ OVN_CLEANUP([hv1])
- AT_CLEANUP
+ OVS_WAIT_UNTIL([
+@@ -17950,7 +17950,7 @@ test_fragmented_traffic() {
+
+ check ovs-appctl dpctl/flush-conntrack
+
+- NETNS_DAEMONIZE([server], [nc -l -u 172.16.1.2 4242 > /dev/null], [server.pid])
++ NETNS_DAEMONIZE([server], [ncat -l -u 172.16.1.2 4242 > /dev/null], [server.pid])
+
+ # Collect ICMP packets on client side
+ NETNS_START_TCPDUMP([client], [-U -i client -vnne udp], [tcpdump-client])
+@@ -18258,6 +18258,54 @@ f0:00:0f:16:01:20 dev $LO_NAME master $BR_NAME static
+ f0:00:0f:16:01:20 dev $LO_NAME vlan 1 master $BR_NAME static
])
-+OVN_FOR_EACH_NORTHD([
-+AT_SETUP([ARP/ND from localnet -- proxy reply on resident chassis only])
-+AT_SKIP_IF([test $HAVE_SCAPY = no])
-+ovn_start
-+
-+dnl Create logical switch with localnet port.
-+check ovn-nbctl ls-add ls1
-+check ovn-nbctl lsp-add-localnet-port ls1 ln1 physnet1
-+check ovn-nbctl lsp-add ls1 vm1 \
-+ -- lsp-set-addresses vm1 "f0:00:00:00:00:01 10.0.0.1 fd01::1"
-+check ovn-nbctl lsp-add ls1 vm2 \
-+ -- lsp-set-addresses vm2 "f0:00:00:00:00:02 10.0.0.2 fd01::2"
-+
-+dnl Two hypervisors with bridge-mappings.
-+net_add n1
++AS_BOX([L2 EVPN learned FDB advertising])
+
-+sim_add hv1
-+as hv1
-+ovs-vsctl \
-+ -- add-br br-phys \
-+ -- add-br br-eth0
-+ovn_attach n1 br-phys 192.168.0.1
-+check ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=physnet1:br-eth0
-+check ovs-vsctl add-port br-eth0 snoopvif1 \
-+ -- set Interface snoopvif1 options:tx_pcap=hv1/snoopvif-tx.pcap \
-+ options:rxq_pcap=hv1/snoopvif-rx.pcap
-+check ovs-vsctl add-port br-int vm1 \
-+ -- set Interface vm1 external-ids:iface-id=vm1 \
-+ options:tx_pcap=hv1/vm1-tx.pcap \
-+ options:rxq_pcap=hv1/vm1-rx.pcap
++# Add a port with "unknown" addresses and FDB learning enabled.
++# redistribute=fdb is already set from the previous section.
++check ovn-nbctl --wait=hv \
++ -- lsp-add ls-evpn wl-unknown \
++ -- lsp-set-addresses wl-unknown "unknown"
+
-+sim_add hv2
-+as hv2
-+ovs-vsctl \
-+ -- add-br br-phys \
-+ -- add-br br-eth0
-+ovn_attach n1 br-phys 192.168.0.2
-+check ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=physnet1:br-eth0
-+check ovs-vsctl add-port br-eth0 snoopvif2 \
-+ -- set Interface snoopvif2 options:tx_pcap=hv2/snoopvif-tx.pcap \
-+ options:rxq_pcap=hv2/snoopvif-rx.pcap
-+check ovs-vsctl add-port br-int vm2 \
-+ -- set Interface vm2 external-ids:iface-id=vm2 \
-+ options:tx_pcap=hv2/vm2-tx.pcap \
-+ options:rxq_pcap=hv2/vm2-rx.pcap
-+
-+wait_for_ports_up vm1 vm2
-+OVN_POPULATE_ARP
++ADD_NAMESPACES(wl-unknown)
++ADD_VETH(wl-unknown, wl-unknown, br-int, "172.16.1.30/24", "f0:00:0f:16:01:30")
+check ovn-nbctl --wait=hv sync
++wait_for_ports_up wl-unknown
+
-+dnl Helper: construct ARP request.
-+build_arp_request() {
-+ local sha=$1 spa=$2 tpa=$3
-+ fmt_pkt "Ether(dst='ff:ff:ff:ff:ff:ff', src='${sha}')/ \
-+ ARP(hwsrc='${sha}', hwdst='ff:ff:ff:ff:ff:ff', \
-+ psrc='${spa}', pdst='${tpa}')"
-+}
-+
-+dnl Helper: construct expected ARP reply.
-+build_arp_reply() {
-+ local req_sha=$1 req_spa=$2 reply_sha=$3 reply_spa=$4
-+ fmt_pkt "Ether(dst='${req_sha}', src='${reply_sha}')/ \
-+ ARP(op=2, hwsrc='${reply_sha}', hwdst='${req_sha}', \
-+ psrc='${reply_spa}', pdst='${req_spa}')"
-+}
-+
-+dnl Helper: construct ND solicitation.
-+build_nd_ns() {
-+ local sha=$1 spa=$2 tpa=$3 sol_mcast=$4
-+ fmt_pkt "Ether(dst='33:33:ff:00:00:0${tpa##*:}', src='${sha}')/ \
-+ IPv6(src='${spa}', dst='${sol_mcast}')/ \
-+ ICMPv6ND_NS(tgt='${tpa}')/ \
-+ ICMPv6NDOptSrcLLAddr(lladdr='${sha}')"
-+}
-+
-+dnl Helper: construct expected ND advertisement.
-+build_nd_na() {
-+ local req_sha=$1 req_spa=$2 reply_sha=$3 reply_tgt=$4
-+ fmt_pkt "Ether(dst='${req_sha}', src='${reply_sha}')/ \
-+ IPv6(src='${reply_tgt}', dst='${req_spa}')/ \
-+ ICMPv6ND_NA(tgt='${reply_tgt}', R=0, S=1, O=1)/ \
-+ ICMPv6NDOptDstLLAddr(lladdr='${reply_sha}')"
-+}
++# Trigger FDB learning by sending traffic from the namespace.
++NS_CHECK_EXEC([wl-unknown], [ping -c 1 -W 1 172.16.1.10 > /dev/null 2>&1 || true])
+
-+test_arp_nd_localnet() {
-+ AS_BOX([ARP from localnet on hv1 for vm1 - expect reply])
-+ as hv1 reset_pcap_file snoopvif1 hv1/snoopvif
-+ as hv2 reset_pcap_file snoopvif2 hv2/snoopvif
-+ as hv1 reset_pcap_file vm1 hv1/vm1
-+ as hv2 reset_pcap_file vm2 hv2/vm2
-+
-+ dnl ARP request from br-eth0 on hv1 for vm1 (10.0.0.1).
-+ dnl vm1 is resident on hv1, so hv1 should reply.
-+ local arp_req=$(build_arp_request "f0:00:00:00:00:99" "10.0.0.99" "10.0.0.1")
-+ as hv1 ovs-appctl netdev-dummy/receive snoopvif1 $arp_req
-+ local arp_rep=$(build_arp_reply "f0:00:00:00:00:99" "10.0.0.99" \
-+ "f0:00:00:00:00:01" "10.0.0.1")
-+ echo $arp_rep > expected_arp_reply
-+ OVN_CHECK_PACKETS_CONTAIN([hv1/snoopvif-tx.pcap], [expected_arp_reply])
-+
-+ AS_BOX([ARP from localnet on hv2 for vm1 - expect no reply])
-+ as hv2 reset_pcap_file snoopvif2 hv2/snoopvif
-+
-+ dnl ARP request from br-eth0 on hv2 for vm1 (10.0.0.1).
-+ dnl vm1 is NOT resident on hv2, so hv2 should NOT reply.
-+ dnl To avoid relying on sleep, we also send an ARP request for vm2
-+ dnl (which IS resident on hv2) and wait for that reply. This proves
-+ dnl the pipeline is running and any reply for vm1 would have appeared.
-+ as hv2 ovs-appctl netdev-dummy/receive snoopvif2 $arp_req
-+
-+ local arp_req_vm2=$(build_arp_request "f0:00:00:00:00:99" "10.0.0.99" "10.0.0.2")
-+ as hv2 ovs-appctl netdev-dummy/receive snoopvif2 $arp_req_vm2
-+ local arp_rep_vm2=$(build_arp_reply "f0:00:00:00:00:99" "10.0.0.99" \
-+ "f0:00:00:00:00:02" "10.0.0.2")
-+ echo $arp_rep_vm2 > expected_arp_vm2
-+ OVN_CHECK_PACKETS_CONTAIN([hv2/snoopvif-tx.pcap], [expected_arp_vm2])
-+
-+ dnl Now verify that no ARP reply for vm1 was generated on hv2.
-+ AT_CHECK([$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv2/snoopvif-tx.pcap | \
-+ grep -c "$arp_rep"], [1], [dnl
-+0
-+])
++# Wait for the FDB entry to appear in the SB database.
++learned_mac=f0:00:0f:16:01:30
++wait_row_count fdb 1 mac='"'$learned_mac'"'
+
-+ AS_BOX([ARP from vm2 VIF for vm1 - expect proxy reply])
-+ as hv2 reset_pcap_file vm2 hv2/vm2
-+ local arp_req2=$(build_arp_request "f0:00:00:00:00:02" "10.0.0.2" "10.0.0.1")
-+ as hv2 ovs-appctl netdev-dummy/receive vm2 $arp_req2
-+ local arp_rep2=$(build_arp_reply "f0:00:00:00:00:02" "10.0.0.2" \
-+ "f0:00:00:00:00:01" "10.0.0.1")
-+ echo $arp_rep2 > expected_arp_proxy
-+ OVN_CHECK_PACKETS_CONTAIN([hv2/vm2-tx.pcap], [expected_arp_proxy])
-+
-+ AS_BOX([ND from localnet on hv1 for vm1 - expect reply])
-+ as hv1 reset_pcap_file snoopvif1 hv1/snoopvif
-+ as hv2 reset_pcap_file snoopvif2 hv2/snoopvif
-+
-+ dnl ND solicitation from br-eth0 on hv1 for vm1 IPv6 (fd01::1).
-+ dnl vm1 is resident on hv1, so hv1 should reply.
-+ local nd_ns=$(build_nd_ns "f0:00:00:00:00:99" "fd01::99" "fd01::1" "ff02::1:ff00:1")
-+ as hv1 ovs-appctl netdev-dummy/receive snoopvif1 $nd_ns
-+ local nd_na=$(build_nd_na "f0:00:00:00:00:99" "fd01::99" \
-+ "f0:00:00:00:00:01" "fd01::1")
-+ echo $nd_na > expected_nd_reply
-+ OVN_CHECK_PACKETS_CONTAIN([hv1/snoopvif-tx.pcap], [expected_nd_reply])
-+
-+ AS_BOX([ND from localnet on hv2 for vm1 - expect no reply])
-+ as hv2 reset_pcap_file snoopvif2 hv2/snoopvif
-+
-+ dnl ND solicitation from br-eth0 on hv2 for vm1 IPv6 (fd01::1).
-+ dnl vm1 is NOT resident on hv2, so hv2 should NOT reply.
-+ dnl Same technique: send ND for vm2 (resident) and wait for that reply.
-+ as hv2 ovs-appctl netdev-dummy/receive snoopvif2 $nd_ns
-+
-+ local nd_ns_vm2=$(build_nd_ns "f0:00:00:00:00:99" "fd01::99" "fd01::2" "ff02::1:ff00:2")
-+ as hv2 ovs-appctl netdev-dummy/receive snoopvif2 $nd_ns_vm2
-+ local nd_na_vm2=$(build_nd_na "f0:00:00:00:00:99" "fd01::99" \
-+ "f0:00:00:00:00:02" "fd01::2")
-+ echo $nd_na_vm2 > expected_nd_vm2
-+ OVN_CHECK_PACKETS_CONTAIN([hv2/snoopvif-tx.pcap], [expected_nd_vm2])
-+
-+ dnl Now verify that no ND advertisement for vm1 was generated on hv2.
-+ AT_CHECK([$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv2/snoopvif-tx.pcap | \
-+ grep -c "$nd_na"], [1], [dnl
-+0
++# The dynamically learned FDB MAC should be advertised on the loopback.
++OVS_WAIT_FOR_OUTPUT_UNQUOTED([bridge fdb show | grep $LO_NAME | grep "$learned_mac" | sort], [0], [dnl
++$learned_mac dev $LO_NAME master $BR_NAME static
++$learned_mac dev $LO_NAME vlan 1 master $BR_NAME static
+])
+
-+ AS_BOX([ND from vm2 VIF for vm1 - expect proxy reply])
-+ as hv2 reset_pcap_file vm2 hv2/vm2
-+ local nd_ns2=$(build_nd_ns "f0:00:00:00:00:02" "fd01::2" "fd01::1" "ff02::1:ff00:1")
-+ as hv2 ovs-appctl netdev-dummy/receive vm2 $nd_ns2
-+ local nd_na2=$(build_nd_na "f0:00:00:00:00:02" "fd01::2" \
-+ "f0:00:00:00:00:01" "fd01::1")
-+ echo $nd_na2 > expected_nd_proxy
-+ OVN_CHECK_PACKETS_CONTAIN([hv2/vm2-tx.pcap], [expected_nd_proxy])
-+}
-+
-+AS_BOX([FDB learning disabled])
-+test_arp_nd_localnet
-+
-+AS_BOX([FDB learning enabled])
-+dnl Use 'set' instead of 'lsp-set-options' to preserve network_name.
-+check ovn-nbctl --wait=hv set Logical_Switch_Port ln1 \
++# Also verify that FDB entries learned on localnet ports are advertised.
++# The localnet port already has addresses="unknown" from lsp-add-localnet-port.
++check ovn-nbctl --wait=hv set Logical_Switch_Port ln_port \
+ options:localnet_learn_fdb=true
-+test_arp_nd_localnet
+
-+OVN_CLEANUP([hv1
-+/left allocated/d
-+], [hv2
-+/left allocated/d
-+])
-+AT_CLEANUP
++# Send traffic from the physical side to trigger localnet FDB learning.
++ADD_NAMESPACES(ext-host)
++ADD_VETH(ext-host, ext-host, br-ext, "172.16.1.40/24", "aa:bb:cc:dd:ee:ff")
++NS_CHECK_EXEC([ext-host], [ping -c 1 -W 1 172.16.1.10 > /dev/null 2>&1 || true])
++
++ln_learned_mac=aa:bb:cc:dd:ee:ff
++wait_row_count fdb 1 mac='"'$ln_learned_mac'"'
++OVS_WAIT_FOR_OUTPUT_UNQUOTED([bridge fdb show | grep $LO_NAME | grep "$ln_learned_mac" | sort], [0], [dnl
++$ln_learned_mac dev $LO_NAME master $BR_NAME static
++$ln_learned_mac dev $LO_NAME vlan 1 master $BR_NAME static
+])
+
- OVN_FOR_EACH_NORTHD([
- AT_SETUP([send reverse arp for router without ipv4 address])
- ovn_start
-@@ -14170,8 +14363,8 @@ AT_CHECK([
- test 0 -eq $pkts
- ])
-
--spa=$(ip_to_hex 10 0 0 1)
--tpa=$(ip_to_hex 10 0 0 100)
-+spa=10.0.0.1
-+tpa=10.0.0.100
- send_garp hv1 vif1 1 "00:00:00:00:00:01" "ff:ff:ff:ff:ff:ff" $spa $tpa
-
- dnl traffic from localport should not be sent to localnet
-@@ -31350,6 +31543,13 @@ check ovn-nbctl lrp-set-gateway-chassis lr0-public hv1 20
-
- # Create NAT entries for the ports
-
-+# sw0-port1
-+check ovn-nbctl lr-nat-add lr0 dnat_and_snat 172.16.0.110 10.0.0.3 sw0-port1 30:54:00:00:00:03
-+check ovn-nbctl lr-nat-add lr0 dnat_and_snat 3000::c 1000::3 sw0-port1 40:54:00:00:00:03
-+# sw1-port1
-+check ovn-nbctl lr-nat-add lr0 dnat_and_snat 172.16.0.120 20.0.0.3 sw1-port1 30:54:00:00:00:04
-+check ovn-nbctl lr-nat-add lr0 dnat_and_snat 3000::d 2000::3 sw1-port1 40:54:00:00:00:04
-+
- # Add snat entriess
- check ovn-nbctl lr-nat-add lr0 snat 172.16.0.100 10.0.0.0/24
- check ovn-nbctl lr-nat-add lr0 snat 172.16.0.101 10.0.0.10
-@@ -31458,19 +31658,6 @@ test_arp_response 000020201213 $(ip_to_hex 172 16 0 102) hv1 hv2 hv3
- test_arp_response 000030303233 $(ip_to_hex 172 16 0 200) hv3 hv1 hv2
- test_arp_response 000030303233 $(ip_to_hex 172 16 0 201) hv3 hv1 hv2
-
--# Add some extra dnat_and_snat, that should generate extra flows for external ARPs.
--# sw0-port1
--check ovn-nbctl lr-nat-add lr0 dnat_and_snat 172.16.0.110 10.0.0.3 sw0-port1 30:54:00:00:00:03
--check ovn-nbctl lr-nat-add lr0 dnat_and_snat 3000::c 1000::3 sw0-port1 40:54:00:00:00:03
--# sw1-port1
--check ovn-nbctl lr-nat-add lr0 dnat_and_snat 172.16.0.120 20.0.0.3 sw1-port1 30:54:00:00:00:04
--check ovn-nbctl lr-nat-add lr0 dnat_and_snat 3000::d 2000::3 sw1-port1 40:54:00:00:00:04
--check ovn-nbctl --wait=hv sync
--
--# Send ARP request for the IP which belongs to gw_router
--test_arp_response 000030303233 $(ip_to_hex 172 16 0 200) hv3 hv1 hv2
--test_arp_response 000030303233 $(ip_to_hex 172 16 0 201) hv3 hv1 hv2
--
- # Make hv3 claim the cr-lr0-public
- check ovn-nbctl lrp-set-gateway-chassis lr0-public hv1 20
- check ovn-nbctl lrp-set-gateway-chassis lr0-public hv2 30
-@@ -36535,15 +36722,6 @@ AT_CHECK([ovn-nbctl --wait=hv sync])
- OVS_WAIT_UNTIL([grep pinctrl hv1/ovn-controller.log | grep -c connected])
- OVS_WAIT_UNTIL([grep pinctrl hv2/ovn-controller.log | grep -c connected])
-
--send_udp() {
-- hv=$1
-- dev=$2
-- byte=$3
--
-- packet=$(fmt_pkt "Ether(dst='00:00:00:00:10:00', src='00:00:00:00:10:${byte}')/ \
-- IP(dst='192.168.20.${byte}', src='192.168.10.${byte}')/UDP()")
-- as $hv ovs-appctl netdev-dummy/receive $dev $packet
--}
- # Check if the option is not present by default
- AT_CHECK([fetch_column nb:logical_router options name="gw-1" | grep -q mac_binding_age_threshold], [1])
- AT_CHECK([fetch_column nb:logical_router options name="gw-2" | grep -q mac_binding_age_threshold], [1])
-@@ -36577,8 +36755,8 @@ $sorted_table
- timestamp=$(fetch_column mac_binding timestamp ip="192.168.10.20")
- binding_uuid=$(fetch_column mac_binding _uuid ip="192.168.10.20")
-
--send_udp hv1 ext1 10
--send_udp hv2 ext2 20
-+send_udp hv1 ext1 00:00:00:00:10:00 00:00:00:00:10:10 192.168.20.10 192.168.10.10
-+send_udp hv2 ext2 00:00:00:00:10:00 00:00:00:00:10:20 192.168.20.20 192.168.10.20
-
- OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=OFTABLE_MAC_CACHE_USE | grep "192.168.10.10" | grep -q "n_packets=1"])
- OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=OFTABLE_MAC_CACHE_USE | grep "192.168.10.20" | grep -q "n_packets=1"])
-@@ -36589,9 +36767,9 @@ AT_CHECK([fetch_column nb:logical_router options | grep -q mac_binding_age_thres
- AT_CHECK([ovn-nbctl --wait=sb sync])
++# Cleanup.
++check ovn-sbctl --all destroy FDB
++check ovn-nbctl --wait=hv lsp-del wl-unknown \
++ -- remove Logical_Switch_Port ln_port options localnet_learn_fdb
++
+ check ovn-nbctl --wait=hv lsp-del workload2
+ OVS_WAIT_FOR_OUTPUT_UNQUOTED([bridge fdb show | grep $LO_NAME | grep "f0:00:0f:16:01" | sort], [0], [dnl
+ f0:00:0f:16:01:10 dev $LO_NAME master $BR_NAME static
+@@ -18304,9 +18352,9 @@ rtr_dp_key=$(fetch_column Datapath tunnel_key external_ids:name=lr)
+ rtr_port_key=$(fetch_column Port_Binding tunnel_key logical_port=lr-ls-evpn)
- # Wait send few packets for "192.168.10.20" to indicate that it is still in use
--send_udp hv2 ext2 20
-+send_udp hv2 ext2 00:00:00:00:10:00 00:00:00:00:10:20 192.168.20.20 192.168.10.20
- sleep 1
--send_udp hv2 ext2 20
-+send_udp hv2 ext2 00:00:00:00:10:00 00:00:00:00:10:20 192.168.20.20 192.168.10.20
-
- # Set the timeout for OVS_WAIT* functions to 10 seconds
- OVS_CTL_TIMEOUT=10
-@@ -36869,18 +37047,10 @@ sleep 2
- send_garp hv1 vif1 2 00:00:00:00:10:10 ff:ff:ff:ff:ff:ff 192.168.10.10 192.168.10.10
- wait_row_count mac_binding 1 ip="192.168.10.10" logical_port="lr-ls1"
-
--send_udp() {
-- local hv=$1 dev=$2 byte=$3
--
-- packet=$(fmt_pkt "Ether(dst='00:00:00:00:10:00', src='00:00:00:00:10:${byte}')/ \
-- IP(dst='192.168.20.${byte}', src='192.168.10.${byte}')/UDP()")
-- check as $hv ovs-appctl netdev-dummy/receive $dev $packet
--}
--
- uuid=$(fetch_column mac_binding _uuid ip="192.168.10.10" logical_port="lr-ls1")
- for i in $(seq 12); do
- # Keep one entry alive by sending traffic that uses it.
-- send_udp hv1 vif1 10
-+ send_udp hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:10 192.168.20.10 192.168.10.10
- sleep 1
- # The entry must not expire.
- check_row_count mac_binding 1 ip="192.168.10.10" logical_port="lr-ls1"
-@@ -36963,38 +37133,6 @@ AT_SETUP([MAC binding aging - probing])
- AT_SKIP_IF([test $HAVE_SCAPY = no])
- ovn_start
+ # Simulate remote workload ARPs (type-2 MAC+IP EVPN route).
+-# ovn-controller needs to add OF rules for ARP lookup but no rules for
+-# MAC_CACHE use. These entries do not age out automatically, their lifetime
+-# is controlled by the BGP-EVPN control plane.
++# EVPN-learned MAC bindings are written to the SB MAC_Binding table and
++# distributed to all chassis. These entries do not age out automatically,
++# their lifetime is controlled by the BGP-EVPN control plane.
+ check ip neigh add dev $BR_NAME 172.16.1.50 lladdr f0:00:0f:16:10:50 nud noarp extern_learn
+ check ip neigh add dev $BR_NAME 172.16.1.60 lladdr f0:00:0f:16:10:60 nud noarp extern_learn
+ check ip neigh add dev $BR_NAME 172.16.1.70 lladdr f0:00:0f:16:10:70 nud noarp extern_learn
+@@ -18324,66 +18372,51 @@ OVS_WAIT_FOR_OUTPUT_UNQUOTED([ovn-appctl evpn/vtep-arp-list | cut -d',' -f2- | s
+ VNI: 10, MAC: f0:00:0f:16:10:70, IP: 172:16::70, dp_key: $dp_key
+ ])
--send_udp() {
-- local hv=$1 dev=$2 hdst=$3 hsrc=$4 idst=$5 isrc=$6
-- local packet=$(fmt_pkt "Ether(dst='${hdst}', src='${hsrc}')/ \
-- IP(dst='${idst}', src='${isrc}')/UDP()")
-- as $hv ovs-appctl netdev-dummy/receive $dev $packet
--}
--
--send_udp6() {
-- local hv=$1 dev=$2 hdst=$3 hsrc=$4 idst=$5 isrc=$6
-- local packet=$(fmt_pkt "Ether(dst='${hdst}', src='${hsrc}')/ \
-- IPv6(dst='${idst}', src='${isrc}')/UDP()")
-- as $hv ovs-appctl netdev-dummy/receive $dev $packet
--}
--
--dump_arp() {
-- local op=$1 eth_src=$2 eth_dst=$3 spa=$4 tpa=$5 hwdst=$6
+-AS_BOX([Check dynamic-routing-arp-prefer-local=true])
+-check ovn-nbctl --wait=hv set Logical_Switch ls-evpn other_config:dynamic-routing-arp-prefer-local=true
++dnl Verify EVPN-learned MAC bindings appear in the SB MAC_Binding table.
++wait_row_count MAC_Binding 1 ip=172.16.1.50
++wait_row_count MAC_Binding 1 ip=172.16.1.60
++wait_row_count MAC_Binding 1 ip=172.16.1.70
++wait_row_count MAC_Binding 1 ip="172\:16\:\:50"
++wait_row_count MAC_Binding 1 ip="172\:16\:\:60"
++wait_row_count MAC_Binding 1 ip="172\:16\:\:70"
+
+-AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=OFTABLE_MAC_BINDING | grep priority | \
+- awk '{print $[7], $[8]}' | sort], [0], [dnl
+-priority=100,reg0=0xac10010a,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:01:10,load:0x1->NXM_NX_REG10[[6]]
+-priority=20,reg0=0xac100132,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:50,load:0x1->NXM_NX_REG10[[6]]
+-priority=20,reg0=0xac10013c,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:60,load:0x1->NXM_NX_REG10[[6]]
+-priority=20,reg0=0xac100146,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:70,load:0x1->NXM_NX_REG10[[6]]
+-priority=20,reg4=0x1720016,reg5=0,reg6=0,reg7=0x50,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:50,load:0x1->NXM_NX_REG10[[6]]
+-priority=20,reg4=0x1720016,reg5=0,reg6=0,reg7=0x60,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:60,load:0x1->NXM_NX_REG10[[6]]
+-priority=20,reg4=0x1720016,reg5=0,reg6=0,reg7=0x70,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:70,load:0x1->NXM_NX_REG10[[6]]
+-])
-
-- local packet=$(fmt_pkt "Ether(dst='${eth_dst}', src='${eth_src}')/ \
-- ARP(op=$op, hwsrc='${eth_src}', hwdst='${hwdst}', \
-- psrc='${spa}', pdst='${tpa}')")
-- echo $packet
--}
+-AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=OFTABLE_MAC_LOOKUP | grep priority | \
+- awk '{print $[7], $[8]}' | sort], [0], [dnl
+-priority=100,arp,reg0=0xac10010a,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:01:10 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=20,arp,reg0=0xac100132,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=20,arp,reg0=0xac10013c,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=20,arp,reg0=0xac100146,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=20,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x50,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=20,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x60,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=20,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x70,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
+-])
-
--dump_ns() {
-- local hdst=$1 hsrc=$2 idst=$3 isrc=$4 tgt=$5
-- local packet=$(fmt_pkt "Ether(dst='${hdst}', src='${hsrc}')/ \
-- IPv6(dst='${idst}', src='${isrc}')/ \
-- ICMPv6ND_NS(tgt='${tgt}')/
-- ICMPv6NDOptSrcLLAddr(lladdr='${hsrc}')")
-- echo $packet
--}
+-AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=OFTABLE_MAC_CACHE_USE | grep priority | \
+- awk '{print $[7], $[8]}' | sort], [0], [dnl
+-priority=100,arp,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:01:10,arp_spa=172.16.1.10,arp_op=2 actions=drop
+-priority=100,ip,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:01:10,nw_src=172.16.1.10 actions=drop
+-])
-
- aging_th=10
- net_add n1
- sim_add hv1
-@@ -37112,6 +37250,123 @@ OVN_CLEANUP([hv1])
- AT_CLEANUP
+-AS_BOX([Check dynamic-routing-arp-prefer-local=false])
+-check ovn-nbctl --wait=hv set Logical_Switch ls-evpn other_config:dynamic-routing-arp-prefer-local=false
++AS_BOX([Check EVPN MAC binding and lookup flows])
+
+ AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=OFTABLE_MAC_BINDING | grep priority | \
+ awk '{print $[7], $[8]}' | sort], [0], [dnl
+ priority=100,reg0=0xac10010a,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:01:10,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg0=0xac100132,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:50,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg0=0xac10013c,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:60,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg0=0xac100146,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:70,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg4=0x1720016,reg5=0,reg6=0,reg7=0x50,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:50,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg4=0x1720016,reg5=0,reg6=0,reg7=0x60,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:60,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg4=0x1720016,reg5=0,reg6=0,reg7=0x70,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:70,load:0x1->NXM_NX_REG10[[6]]
++priority=100,reg0=0xac100132,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:50,load:0x1->NXM_NX_REG10[[6]]
++priority=100,reg0=0xac10013c,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:60,load:0x1->NXM_NX_REG10[[6]]
++priority=100,reg0=0xac100146,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:70,load:0x1->NXM_NX_REG10[[6]]
++priority=100,reg4=0x1720016,reg5=0,reg6=0,reg7=0x50,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:50,load:0x1->NXM_NX_REG10[[6]]
++priority=100,reg4=0x1720016,reg5=0,reg6=0,reg7=0x60,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:60,load:0x1->NXM_NX_REG10[[6]]
++priority=100,reg4=0x1720016,reg5=0,reg6=0,reg7=0x70,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:70,load:0x1->NXM_NX_REG10[[6]]
])
-+OVN_FOR_EACH_NORTHD([
-+AT_SETUP([MAC binding aging - probing multi-subnet source IP])
-+AT_SKIP_IF([test $HAVE_SCAPY = no])
-+ovn_start
-+
-+aging_th=10
-+net_add n1
-+sim_add hv1
-+as hv1
-+check ovs-vsctl add-br br-phys
-+ovn_attach n1 br-phys 192.168.0.1
-+ovn-appctl -t ovn-controller vlog/set mac_cache:file:dbg pinctrl:file:dbg
-+
-+check ovn-nbctl \
-+ -- ls-add ls1 \
-+ -- lr-add lr \
-+ -- set logical_router lr options:mac_binding_age_threshold=$aging_th \
-+ -- lrp-add lr lr-ls1 00:00:00:00:10:00 10.10.10.1/24 42.42.42.1/24 \
-+ fd11::1/64 fd12::1/64 \
-+ -- lsp-add-router-port ls1 ls1-lr lr-ls1 \
-+ -- lsp-add ls1 vif1 \
-+ -- lsp-set-addresses vif1 "unknown"
-+
-+check ovs-vsctl \
-+ -- add-port br-int vif1 \
-+ -- set interface vif1 external-ids:iface-id=vif1 \
-+ options:tx_pcap=hv1/vif1-tx.pcap options:rxq_pcap=hv1/vif1-rx.pcap
-+
-+OVN_POPULATE_ARP
-+wait_for_ports_up
-+check ovn-nbctl --wait=hv sync
-+
-+# Wait for pinctrl thread to be connected.
-+OVS_WAIT_UNTIL([grep pinctrl hv1/ovn-controller.log | grep -q connected])
-+
-+# Create MAC bindings in both IPv4 subnets.
-+send_garp hv1 vif1 2 00:00:00:00:10:1a ff:ff:ff:ff:ff:ff 10.10.10.100 10.10.10.100
-+wait_row_count mac_binding 1 ip="10.10.10.100" logical_port="lr-ls1"
-+
-+send_garp hv1 vif1 2 00:00:00:00:10:1b ff:ff:ff:ff:ff:ff 42.42.42.253 42.42.42.253
-+wait_row_count mac_binding 1 ip="42.42.42.253" logical_port="lr-ls1"
-+
-+# Create MAC bindings in both IPv6 subnets.
-+send_na hv1 vif1 00:00:00:00:10:1a 00:00:00:00:10:00 fd11::64 fd11::1
-+wait_row_count mac_binding 1 ip=\"fd11::64\" logical_port=\"lr-ls1\"
-+
-+send_na hv1 vif1 00:00:00:00:10:1b 00:00:00:00:10:00 fd12::64 fd12::1
-+wait_row_count mac_binding 1 ip=\"fd12::64\" logical_port=\"lr-ls1\"
-+
-+# Record UUIDs for all MAC bindings.
-+uuid_v4_1=$(fetch_column Mac_Binding _uuid ip=10.10.10.100)
-+uuid_v4_2=$(fetch_column Mac_Binding _uuid ip=42.42.42.253)
-+uuid_v6_1=$(fetch_column Mac_Binding _uuid ip=\"fd11::64\")
-+uuid_v6_2=$(fetch_column Mac_Binding _uuid ip=\"fd12::64\")
-+
-+# Send IPv4 and IPv6 UDP traffic to refresh entries in OFTABLE_MAC_BINDING.
-+# Use different src MACs than the ones from GARP/NA to avoid resetting
-+# OFTABLE_MAC_CACHE_USE idle_age (which would keep the timestamp fresh
-+# and suppress probing via the cooldown mechanism).
-+send_udp hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:2a 42.42.42.253 10.10.10.100
-+send_udp hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:2b 10.10.10.100 42.42.42.253
-+send_udp6 hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:2a fd12::64 fd11::64
-+send_udp6 hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:2b fd11::64 fd12::64
-+
-+OVS_WAIT_UNTIL([ovs-ofctl dump-flows br-int table=OFTABLE_MAC_BINDING | \
-+ sed 's/reg15=0x.,metadata=0x./reg15=<cleared>,metadata=<cleared>/g' | \
-+ grep -q "reg0=0xa0a0a64,reg15=<cleared>,metadata=<cleared> actions=mod_dl_dst:00:00:00:00:10:1a"])
-+OVS_WAIT_UNTIL([ovs-ofctl dump-flows br-int table=OFTABLE_MAC_BINDING | \
-+ sed 's/reg15=0x.,metadata=0x./reg15=<cleared>,metadata=<cleared>/g' | \
-+ grep -q "reg0=0x2a2a2afd,reg15=<cleared>,metadata=<cleared> actions=mod_dl_dst:00:00:00:00:10:1b"])
-+
-+# Wait until all entries in OFTABLE_MAC_CACHE_USE are stale.
-+OVS_WAIT_UNTIL([test $(ovs-ofctl dump-flows br-int table=OFTABLE_MAC_CACHE_USE | \
-+ awk '/nw_src=10.10.10.100/{print substr($6,10,1)}') -ge $((aging_th/2))])
-+OVS_WAIT_UNTIL([test $(ovs-ofctl dump-flows br-int table=OFTABLE_MAC_CACHE_USE | \
-+ awk '/nw_src=42.42.42.253/{print substr($6,10,1)}') -ge $((aging_th/2))])
-+OVS_WAIT_UNTIL([test $(ovs-ofctl dump-flows br-int table=OFTABLE_MAC_CACHE_USE | \
-+ awk '/ipv6_src=fd11::64/{print substr($6,10,1)}') -ge $((aging_th/2))])
-+OVS_WAIT_UNTIL([test $(ovs-ofctl dump-flows br-int table=OFTABLE_MAC_CACHE_USE | \
-+ awk '/ipv6_src=fd12::64/{print substr($6,10,1)}') -ge $((aging_th/2))])
-+
-+# Send traffic to trigger probing for all entries.
-+# Again use different src MACs to only hit OFTABLE_MAC_BINDING (not MAC_CACHE_USE).
-+send_udp hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:2a 42.42.42.253 10.10.10.100
-+send_udp hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:2b 10.10.10.100 42.42.42.253
-+send_udp6 hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:2a fd12::64 fd11::64
-+send_udp6 hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:2b fd11::64 fd12::64
-+
-+# Verify ARP probes use the correct source IPs from matching subnets.
-+# ARP for 10.10.10.100 must use source IP 10.10.10.1 (first subnet).
-+dump_arp 1 00:00:00:00:10:00 ff:ff:ff:ff:ff:ff 10.10.10.1 10.10.10.100 00:00:00:00:00:00 > expected
-+# ARP for 42.42.42.253 must use source IP 42.42.42.1 (second subnet).
-+dump_arp 1 00:00:00:00:10:00 ff:ff:ff:ff:ff:ff 42.42.42.1 42.42.42.253 00:00:00:00:00:00 >> expected
-+OVN_CHECK_PACKETS_CONTAIN([hv1/vif1-tx.pcap], [expected])
-+
-+# Verify NS probes use the correct source IPs from matching subnets.
-+# NS for fd11::64 must use source IP fd11::1 (first IPv6 subnet).
-+dump_ns 33:33:ff:00:00:64 00:00:00:00:10:00 ff02::1:ff00:64 fd11::1 fd11::64 > expected_v6
-+# NS for fd12::64 must use source IP fd12::1 (second IPv6 subnet).
-+dump_ns 33:33:ff:00:00:64 00:00:00:00:10:00 ff02::1:ff00:64 fd12::1 fd12::64 >> expected_v6
-+OVN_CHECK_PACKETS_CONTAIN([hv1/vif1-tx.pcap], [expected_v6])
-+
-+# Send ARP/NA replies and check MAC_Binding UUIDs remain consistent.
-+send_garp hv1 vif1 2 00:00:00:00:10:1a 00:00:00:00:10:00 10.10.10.100 10.10.10.1
-+send_garp hv1 vif1 2 00:00:00:00:10:1b 00:00:00:00:10:00 42.42.42.253 42.42.42.1
-+send_na hv1 vif1 00:00:00:00:10:1a 00:00:00:00:10:00 fd11::64 fd11::1
-+send_na hv1 vif1 00:00:00:00:10:1b 00:00:00:00:10:00 fd12::64 fd12::1
-+
-+check_column "$uuid_v4_1" Mac_Binding _uuid ip=10.10.10.100
-+check_column "$uuid_v4_2" Mac_Binding _uuid ip=42.42.42.253
-+check_column "$uuid_v6_1" Mac_Binding _uuid ip=\"fd11::64\"
-+check_column "$uuid_v6_2" Mac_Binding _uuid ip=\"fd12::64\"
-+
-+OVN_CLEANUP([hv1])
-+AT_CLEANUP
-+])
-+
- OVN_FOR_EACH_NORTHD([
- AT_SETUP([MAC binding aging - probing distributed GW router])
- AT_SKIP_IF([test $HAVE_SCAPY = no])
-@@ -44599,160 +44854,6 @@ OVN_CLEANUP([hv1])
- AT_CLEANUP
+ AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=OFTABLE_MAC_LOOKUP | grep priority | \
+ awk '{print $[7], $[8]}' | sort], [0], [dnl
+ priority=100,arp,reg0=0xac10010a,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:01:10 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,arp,reg0=0xac100132,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,arp,reg0=0xac10013c,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,arp,reg0=0xac100146,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x50,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x60,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x70,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
++priority=100,arp,reg0=0xac100132,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50 actions=load:0x1->NXM_NX_REG10[[6]]
++priority=100,arp,reg0=0xac10013c,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60 actions=load:0x1->NXM_NX_REG10[[6]]
++priority=100,arp,reg0=0xac100146,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70 actions=load:0x1->NXM_NX_REG10[[6]]
++priority=100,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x50,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
++priority=100,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x60,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
++priority=100,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x70,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
])
--OVN_FOR_EACH_NORTHD([
--AT_SETUP([GARP delivery: gw and external ports])
--AT_SKIP_IF([test $HAVE_SCAPY = no])
--ovn_start
--
--# Configure initial environment
--# LR1: down_link <-> LS1: up_link
--# set lr_down: gateway port (chassis redirect) bound to hv1
--# LS1: down_vif1 - vif port bound to hv1
--# down_vif2 - vif port bound to hv2
--# down_ext - outer (port will be iterated as localnet, l2gateway)
--#
--# Test: send GARP request from virtual ports (down_vif1, down_vif2)
--# ensure mac_binding is always updated.
--# (Fixing the issue: mac_binding is only updated for packets came from
--# down_link's resident chassis)
--# send GARP request from from localnet.
--# ensure mac_binding is updated only if localnet bound to same hv as l3dgw
--
--check ovn-nbctl lr-add lr1
--check ovn-nbctl lrp-add lr1 down_link f0:00:00:00:00:f1 192.168.1.1/24
--
--check ovn-nbctl ls-add ls1
--check ovn-nbctl lsp-add ls1 up_link
--check ovn-nbctl lsp-add ls1 down_vif1
--check ovn-nbctl lsp-add ls1 down_vif2
--check ovn-nbctl lsp-add ls1 down_ext
--
--check ovn-nbctl set Logical_Switch_Port up_link \
-- type=router \
-- options:router-port=down_link \
-- addresses=router
--
--check ovn-nbctl lsp-set-addresses down_vif1 'f0:00:00:00:00:01 192.168.1.101'
--check ovn-nbctl lsp-set-addresses down_vif2 'f0:00:00:00:00:02 192.168.1.102'
--
--check ovn-nbctl lsp-set-type down_ext localnet
--check ovn-nbctl lsp-set-options down_ext network_name=physnet1
--check ovn-nbctl lrp-set-gateway-chassis down_link hv1
--
--net_add n1
--
--# Create hypervisor hv1 connected to n1
--sim_add hv1
--as hv1
--ovs-vsctl add-br br-phys
--ovn_attach n1 br-phys 192.168.0.1
--ovs-vsctl add-port br-int vif1 -- \
-- set Interface vif1 external-ids:iface-id=down_vif1 \
-- options:tx_pcap=hv1/vif1-tx.pcap options:rxq_pcap=hv1/vif1-rx.pcap
--
--# Create hypervisor hv2 connected to n1, add localnet here
--sim_add hv2
--as hv2
--ovs-vsctl add-br br-phys
--ovs-vsctl add-br br-eth0
--ovn_attach n1 br-phys 192.168.0.2
--ovs-vsctl add-port br-int vif2 -- \
-- set Interface vif2 external-ids:iface-id=down_vif2 \
-- options:tx_pcap=hv2/vif2-tx.pcap options:rxq_pcap=hv2/vif2-rx.pcap
--
--ovs-vsctl set Open_vSwitch . external_ids:ovn-bridge-mappings="physnet1:br-eth0"
--
--ovs-vsctl add-port br-eth0 vif_ext -- \
-- set Interface vif_ext options:tx_pcap=hv2/vif_ext-tx.pcap \
-- options:rxq_pcap=hv2/vif_ext-rx.pcap
--
--# Pre-populate the hypervisors' ARP tables so that we don't lose any
--# packets for ARP resolution (native tunneling doesn't queue packets
--# for ARP resolution).
--OVN_POPULATE_ARP
--
--wait_for_ports_up
--check ovn-nbctl --wait=hv sync
--
--# Annonce 192.168.1.222 from localnet in hv2
--# result: drop, hv2 is not gateway chassis for down_link
--sha=02:00:00:00:00:ee
--tha=00:00:00:00:00:00
--spa=192.168.1.222
--tpa=$spa
--garp=$(fmt_pkt "Ether(dst='ff:ff:ff:ff:ff:ff', src='${sha}')/ \
-- ARP(hwsrc='${sha}', hwdst='${tha}', psrc='${spa}', pdst='${tpa}')")
--as hv2 ovs-appctl netdev-dummy/receive vif_ext $garp
--
--# Make hv2 gateway chassis
--# Annonce 192.168.1.223 from localnet in hv2
--# result: ok, hv2 is gateway chassis for down_link
--#
--check ovn-nbctl lrp-set-gateway-chassis down_link hv2
--
--wait_row_count Port_Binding 1 logical_port=cr-down_link 'chassis!=[[]]'
--check ovn-nbctl --wait=hv sync
--
--sha=02:00:00:00:00:ee
--tha=00:00:00:00:00:00
--spa=192.168.1.223
--tpa=$spa
--garp=$(fmt_pkt "Ether(dst='ff:ff:ff:ff:ff:ff', src='${sha}')/ \
-- ARP(hwsrc='${sha}', hwdst='${tha}', psrc='${spa}', pdst='${tpa}')")
--as hv2 ovs-appctl netdev-dummy/receive vif_ext $garp
--
--# Annonce 192.168.1.111, 112 from vif1, vif2 in hv1, hv2
--# result: ok, vif1, vif2 are virtual ports, restrictions are not applied.
--sha=f0:00:00:00:00:01
--tha=00:00:00:00:00:00
--spa=192.168.1.111
--tpa=0.0.0.0
--garp=$(fmt_pkt "Ether(dst='ff:ff:ff:ff:ff:ff', src='${sha}')/ \
-- ARP(hwsrc='${sha}', hwdst='${tha}', psrc='${spa}', pdst='${tpa}')")
--as hv1 ovs-appctl netdev-dummy/receive vif1 $garp
--
--sha=f0:00:00:00:00:02
--tha=00:00:00:00:00:00
--spa=192.168.1.112
--tpa=0.0.0.0
--garp=$(fmt_pkt "Ether(dst='ff:ff:ff:ff:ff:ff', src='${sha}')/ \
-- ARP(hwsrc='${sha}', hwdst='${tha}', psrc='${spa}', pdst='${tpa}')")
--as hv2 ovs-appctl netdev-dummy/receive vif2 $garp
--
--# Set down_ext type to l2gateway
--# Annonce 192.168.1.113, 114 from vif1, vif2 in hv1, hv2
--# result: ok, vif1, vif2 are virtual ports, restrictions are not applied.
--check ovn-nbctl --wait=hv lsp-set-type down_ext l2gateway
--
--sha=f0:00:00:00:00:01
--tha=00:00:00:00:00:00
--spa=192.168.1.113
--tpa=0.0.0.0
--garp=$(fmt_pkt "Ether(dst='ff:ff:ff:ff:ff:ff', src='${sha}')/ \
-- ARP(hwsrc='${sha}', hwdst='${tha}', psrc='${spa}', pdst='${tpa}')")
--as hv1 ovs-appctl netdev-dummy/receive vif1 $garp
--
--sha=f0:00:00:00:00:02
--tha=00:00:00:00:00:00
--spa=192.168.1.114
--tpa=0.0.0.0
--garp=$(fmt_pkt "Ether(dst='ff:ff:ff:ff:ff:ff', src='${sha}')/ \
-- ARP(hwsrc='${sha}', hwdst='${tha}', psrc='${spa}', pdst='${tpa}')")
--as hv2 ovs-appctl netdev-dummy/receive vif2 $garp
--
--wait_row_count MAC_Binding 1 ip="192.168.1.111" mac='"f0:00:00:00:00:01"' logical_port='"down_link"'
--wait_row_count MAC_Binding 1 ip="192.168.1.112" mac='"f0:00:00:00:00:02"' logical_port='"down_link"'
--wait_row_count MAC_Binding 1 ip="192.168.1.113" mac='"f0:00:00:00:00:01"' logical_port='"down_link"'
--wait_row_count MAC_Binding 1 ip="192.168.1.114" mac='"f0:00:00:00:00:02"' logical_port='"down_link"'
--wait_row_count MAC_Binding 1 ip="192.168.1.223" mac='"02:00:00:00:00:ee"' logical_port='"down_link"'
--wait_row_count MAC_Binding 0 ip="192.168.1.222" mac='"02:00:00:00:00:ee"' logical_port='"down_link"'
--
--check ovn-nbctl --wait=hv lsp-set-type down_ext localnet
+ AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=OFTABLE_MAC_CACHE_USE | grep priority | \
+ awk '{print $[7], $[8]}' | sort], [0], [dnl
+ priority=100,arp,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:01:10,arp_spa=172.16.1.10,arp_op=2 actions=drop
++priority=100,arp,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50,arp_spa=172.16.1.50,arp_op=2 actions=drop
++priority=100,arp,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60,arp_spa=172.16.1.60,arp_op=2 actions=drop
++priority=100,arp,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70,arp_spa=172.16.1.70,arp_op=2 actions=drop
+ priority=100,ip,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:01:10,nw_src=172.16.1.10 actions=drop
++priority=100,ip,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50,nw_src=172.16.1.50 actions=drop
++priority=100,ip,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60,nw_src=172.16.1.60 actions=drop
++priority=100,ip,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70,nw_src=172.16.1.70 actions=drop
++priority=100,ipv6,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50,ipv6_src=172:16::50 actions=drop
++priority=100,ipv6,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60,ipv6_src=172:16::60 actions=drop
++priority=100,ipv6,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70,ipv6_src=172:16::70 actions=drop
+ ])
+
+ # Check that the recompute won't change the UUIDs and flows.
+@@ -18447,23 +18480,23 @@ OVS_WAIT_FOR_OUTPUT_UNQUOTED([ovn-appctl evpn/vtep-arp-list | cut -d',' -f2- | s
+ AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=OFTABLE_MAC_BINDING | grep priority | \
+ awk '{print $[7], $[8]}' | sort], [0], [dnl
+ priority=100,reg0=0xac10010a,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:01:10,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg0=0xac100132,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:50,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg0=0xac10013c,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:60,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg0=0xac100146,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:70,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg4=0x1720016,reg5=0,reg6=0,reg7=0x50,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:50,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg4=0x1720016,reg5=0,reg6=0,reg7=0x60,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:60,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg4=0x1720016,reg5=0,reg6=0,reg7=0x70,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:70,load:0x1->NXM_NX_REG10[[6]]
++priority=100,reg0=0xac100132,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:50,load:0x1->NXM_NX_REG10[[6]]
++priority=100,reg0=0xac10013c,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:60,load:0x1->NXM_NX_REG10[[6]]
++priority=100,reg0=0xac100146,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:70,load:0x1->NXM_NX_REG10[[6]]
++priority=100,reg4=0x1720016,reg5=0,reg6=0,reg7=0x50,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:50,load:0x1->NXM_NX_REG10[[6]]
++priority=100,reg4=0x1720016,reg5=0,reg6=0,reg7=0x60,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:60,load:0x1->NXM_NX_REG10[[6]]
++priority=100,reg4=0x1720016,reg5=0,reg6=0,reg7=0x70,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:70,load:0x1->NXM_NX_REG10[[6]]
+ ])
+
+ AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=OFTABLE_MAC_LOOKUP | grep priority | \
+ awk '{print $[7], $[8]}' | sort], [0], [dnl
+ priority=100,arp,reg0=0xac10010a,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:01:10 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,arp,reg0=0xac100132,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,arp,reg0=0xac10013c,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,arp,reg0=0xac100146,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x50,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x60,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x70,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
++priority=100,arp,reg0=0xac100132,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50 actions=load:0x1->NXM_NX_REG10[[6]]
++priority=100,arp,reg0=0xac10013c,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60 actions=load:0x1->NXM_NX_REG10[[6]]
++priority=100,arp,reg0=0xac100146,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70 actions=load:0x1->NXM_NX_REG10[[6]]
++priority=100,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x50,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
++priority=100,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x60,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
++priority=100,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x70,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
+ ])
+
+ check ovn-nbctl --wait=hv lr-del lr
+@@ -18841,9 +18874,9 @@ rtr_dp_key=$(fetch_column Datapath tunnel_key external_ids:name=lr)
+ rtr_port_key=$(fetch_column Port_Binding tunnel_key logical_port=lr-ls-evpn)
+
+ # Simulate remote workload ARPs (type-2 MAC+IP EVPN route).
+-# ovn-controller needs to add OF rules for ARP lookup but no rules for
+-# MAC_CACHE use. These entries do not age out automatically, their lifetime
+-# is controlled by the BGP-EVPN control plane.
++# EVPN-learned MAC bindings are written to the SB MAC_Binding table and
++# distributed to all chassis. These entries do not age out automatically,
++# their lifetime is controlled by the BGP-EVPN control plane.
+ check ip neigh add dev $BR_NAME 172.16.1.50 lladdr f0:00:0f:16:10:50 nud noarp extern_learn
+ check ip neigh add dev $BR_NAME 172.16.1.60 lladdr f0:00:0f:16:10:60 nud noarp extern_learn
+ check ip neigh add dev $BR_NAME 172.16.1.70 lladdr f0:00:0f:16:10:70 nud noarp extern_learn
+@@ -18861,66 +18894,51 @@ OVS_WAIT_FOR_OUTPUT_UNQUOTED([ovn-appctl evpn/vtep-arp-list | cut -d',' -f2- | s
+ VNI: 10, MAC: f0:00:0f:16:10:70, IP: 172:16::70, dp_key: $dp_key
+ ])
+
+-AS_BOX([Check dynamic-routing-arp-prefer-local=true])
+-check ovn-nbctl --wait=hv set Logical_Switch ls-evpn other_config:dynamic-routing-arp-prefer-local=true
+-
+-AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=OFTABLE_MAC_BINDING | grep priority | \
+- awk '{print $[7], $[8]}' | sort], [0], [dnl
+-priority=100,reg0=0xac10010a,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:01:10,load:0x1->NXM_NX_REG10[[6]]
+-priority=20,reg0=0xac100132,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:50,load:0x1->NXM_NX_REG10[[6]]
+-priority=20,reg0=0xac10013c,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:60,load:0x1->NXM_NX_REG10[[6]]
+-priority=20,reg0=0xac100146,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:70,load:0x1->NXM_NX_REG10[[6]]
+-priority=20,reg4=0x1720016,reg5=0,reg6=0,reg7=0x50,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:50,load:0x1->NXM_NX_REG10[[6]]
+-priority=20,reg4=0x1720016,reg5=0,reg6=0,reg7=0x60,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:60,load:0x1->NXM_NX_REG10[[6]]
+-priority=20,reg4=0x1720016,reg5=0,reg6=0,reg7=0x70,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:70,load:0x1->NXM_NX_REG10[[6]]
+-])
-
--OVN_CLEANUP([hv1],[hv2])
--AT_CLEANUP
+-AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=OFTABLE_MAC_LOOKUP | grep priority | \
+- awk '{print $[7], $[8]}' | sort], [0], [dnl
+-priority=100,arp,reg0=0xac10010a,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:01:10 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=20,arp,reg0=0xac100132,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=20,arp,reg0=0xac10013c,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=20,arp,reg0=0xac100146,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=20,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x50,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=20,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x60,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=20,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x70,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
+-])
++dnl Verify EVPN-learned MAC bindings appear in the SB MAC_Binding table.
++wait_row_count MAC_Binding 1 ip=172.16.1.50
++wait_row_count MAC_Binding 1 ip=172.16.1.60
++wait_row_count MAC_Binding 1 ip=172.16.1.70
++wait_row_count MAC_Binding 1 ip="172\:16\:\:50"
++wait_row_count MAC_Binding 1 ip="172\:16\:\:60"
++wait_row_count MAC_Binding 1 ip="172\:16\:\:70"
+
+-AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=OFTABLE_MAC_CACHE_USE | grep priority | \
+- awk '{print $[7], $[8]}' | sort], [0], [dnl
+-priority=100,arp,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:01:10,arp_spa=172.16.1.10,arp_op=2 actions=drop
+-priority=100,ip,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:01:10,nw_src=172.16.1.10 actions=drop
-])
-
- OVN_FOR_EACH_NORTHD([
- AT_SETUP([Port security - VRRPv3 ARP/ND])
- AT_SKIP_IF([test $HAVE_SCAPY = no])
-@@ -45968,3 +46069,66 @@ AT_CHECK([grep -q "WARN.*dynamic-routing" hv/ovn-controller.log], [1])
- OVN_CLEANUP([hv])
- AT_CLEANUP
+-AS_BOX([Check dynamic-routing-arp-prefer-local=false])
+-check ovn-nbctl --wait=hv set Logical_Switch ls-evpn other_config:dynamic-routing-arp-prefer-local=false
++AS_BOX([Check EVPN MAC binding and lookup flows])
+
+ AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=OFTABLE_MAC_BINDING | grep priority | \
+ awk '{print $[7], $[8]}' | sort], [0], [dnl
+ priority=100,reg0=0xac10010a,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:01:10,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg0=0xac100132,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:50,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg0=0xac10013c,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:60,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg0=0xac100146,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:70,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg4=0x1720016,reg5=0,reg6=0,reg7=0x50,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:50,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg4=0x1720016,reg5=0,reg6=0,reg7=0x60,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:60,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg4=0x1720016,reg5=0,reg6=0,reg7=0x70,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:70,load:0x1->NXM_NX_REG10[[6]]
++priority=100,reg0=0xac100132,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:50,load:0x1->NXM_NX_REG10[[6]]
++priority=100,reg0=0xac10013c,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:60,load:0x1->NXM_NX_REG10[[6]]
++priority=100,reg0=0xac100146,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:70,load:0x1->NXM_NX_REG10[[6]]
++priority=100,reg4=0x1720016,reg5=0,reg6=0,reg7=0x50,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:50,load:0x1->NXM_NX_REG10[[6]]
++priority=100,reg4=0x1720016,reg5=0,reg6=0,reg7=0x60,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:60,load:0x1->NXM_NX_REG10[[6]]
++priority=100,reg4=0x1720016,reg5=0,reg6=0,reg7=0x70,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:70,load:0x1->NXM_NX_REG10[[6]]
])
-+
-+OVN_FOR_EACH_NORTHD([
-+AT_SETUP([IPv4 over v6 Neigh solicitation test])
-+ovn_start
-+
-+net_add n1
-+sim_add hv
-+ovs-vsctl add-br br-phys
-+ovn_attach n1 br-phys 192.168.0.1
-+ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
-+
-+check ovn-nbctl ls-add sw0
-+check ovn-nbctl lsp-add sw0 sw0-port1
-+check ovn-nbctl lsp-set-addresses sw0-port1 "50:54:00:00:00:01 10.0.0.3"
-+
-+check ovn-nbctl lr-add lr0
-+check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24 1000::1/64
-+check ovn-nbctl lsp-add-router-port sw0 sw0-lr0 lr0-sw0
-+
-+check ovn-nbctl ls-add public
-+check ovn-nbctl lrp-add lr0 lr0-public 00:00:20:20:12:13
-+check ovn-nbctl lsp-add-router-port public public-lr0 lr0-public
-+
-+# localnet port
-+check ovn-nbctl lsp-add-localnet-port public ln-public phys
-+
-+check ovn-nbctl lrp-set-gateway-chassis lr0-public hv 20
-+check ovn-nbctl lr-nat-add lr0 dnat_and_snat 172.168.0.110 10.0.0.3
-+
-+ovs-vsctl -- add-port br-int vif1 -- \
-+ set interface vif1 external-ids:iface-id=sw0-port1 \
-+ options:tx_pcap=hv/vif1-tx.pcap \
-+ options:rxq_pcap=hv/vif1-rx.pcap \
-+ ofport-request=1
-+
-+wait_for_ports_up
-+
-+# Add a learnt route manually
-+dp_uuid=$(fetch_column datapath _uuid external_ids:name=lr0)
-+lrp_uuid=$(fetch_column port_binding _uuid logical_port=lr0-public)
-+
-+check_uuid ovn-sbctl create learned_route datapath=$dp_uuid logical_port=$lrp_uuid \
-+ip_prefix=0.0.0.0/0 nexthop='"fe80::42:ff:fe00:1ff"'
-+
-+check ovn-nbctl --wait=hv sync
-+
-+# Send an IPv4 packet from sw0-port1 destined to outside
-+packet=$(fmt_pkt "Ether(dst='00:00:00:00:ff:01', src='50:54:00:00:00:01')/ \
-+ IP(dst='8.8.8.8', src='10.0.0.3')/ICMP()")
-+check as hv ovs-appctl netdev-dummy/receive vif1 $packet
-+
-+nd_ns=$(fmt_pkt "Ether(dst='33:33:ff:00:01:ff', src='00:00:20:20:12:13')/ \
-+ IPv6(src='fe80::200:20ff:fe20:1213', \
-+ dst='ff02::1:ff00:1ff')/ICMPv6ND_NS(tgt='fe80::42:ff:fe00:1ff')/\
-+ ICMPv6NDOptSrcLLAddr(lladdr='00:00:20:20:12:13')")
-+
-+echo $nd_ns > expected_nd_ns
-+OVN_CHECK_PACKETS_CONTAIN([hv/br-phys_n1-tx.pcap], [expected_nd_ns])
-+
-+OVN_CLEANUP([hv])
-+AT_CLEANUP
-+])
-+
-diff --git a/tests/system-dpdk-macros.at b/tests/system-dpdk-macros.at
-index 0d0a191305..6eb6888386 100644
---- a/tests/system-dpdk-macros.at
-+++ b/tests/system-dpdk-macros.at
-@@ -43,7 +43,7 @@ m4_define([OVS_TRAFFIC_VSWITCHD_START],
- [OVS_DPDK_PRE_CHECK()
- OVS_WAIT_WHILE([ip link show ovs-netdev])
- _OVS_VSWITCHD_START([--disable-system],
-- [-- set Open_vSwitch . other_config:dpdk-init=true other_config:dpdk-extra="--log-level=pmd.*:error --no-pci"])
-+ [-- set Open_vSwitch . other_config:dpdk-init=true other_config:pmd-cpu-mask=0x1 other_config:dpdk-extra="--log-level=pmd.*:error --no-pci"])
- dnl Add bridges, ports, etc.
- OVS_WAIT_WHILE([ip link show br0])
- AT_CHECK([ovs-vsctl -- _ADD_BR([br0]) -- $1 m4_if([$2], [], [], [| uuidfilt])], [0], [$2])
-diff --git a/tests/system-ovn.at b/tests/system-ovn.at
-index 582ed194b5..6747782ece 100644
---- a/tests/system-ovn.at
-+++ b/tests/system-ovn.at
-@@ -4616,8 +4616,7 @@ OVN_CLEANUP_NORTHD
- as
- OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
- /connection dropped.*/d
--/Service monitor not found.*/d
--/handle service check: Unsupported protocol*/d"])
-+/Service monitor not found.*/d"])
- AT_CLEANUP
+ AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=OFTABLE_MAC_LOOKUP | grep priority | \
+ awk '{print $[7], $[8]}' | sort], [0], [dnl
+ priority=100,arp,reg0=0xac10010a,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:01:10 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,arp,reg0=0xac100132,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,arp,reg0=0xac10013c,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,arp,reg0=0xac100146,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x50,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x60,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x70,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
++priority=100,arp,reg0=0xac100132,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50 actions=load:0x1->NXM_NX_REG10[[6]]
++priority=100,arp,reg0=0xac10013c,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60 actions=load:0x1->NXM_NX_REG10[[6]]
++priority=100,arp,reg0=0xac100146,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70 actions=load:0x1->NXM_NX_REG10[[6]]
++priority=100,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x50,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
++priority=100,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x60,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
++priority=100,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x70,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
+ ])
+
+ AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=OFTABLE_MAC_CACHE_USE | grep priority | \
+ awk '{print $[7], $[8]}' | sort], [0], [dnl
+ priority=100,arp,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:01:10,arp_spa=172.16.1.10,arp_op=2 actions=drop
++priority=100,arp,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50,arp_spa=172.16.1.50,arp_op=2 actions=drop
++priority=100,arp,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60,arp_spa=172.16.1.60,arp_op=2 actions=drop
++priority=100,arp,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70,arp_spa=172.16.1.70,arp_op=2 actions=drop
+ priority=100,ip,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:01:10,nw_src=172.16.1.10 actions=drop
++priority=100,ip,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50,nw_src=172.16.1.50 actions=drop
++priority=100,ip,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60,nw_src=172.16.1.60 actions=drop
++priority=100,ip,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70,nw_src=172.16.1.70 actions=drop
++priority=100,ipv6,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50,ipv6_src=172:16::50 actions=drop
++priority=100,ipv6,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60,ipv6_src=172:16::60 actions=drop
++priority=100,ipv6,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70,ipv6_src=172:16::70 actions=drop
+ ])
+
+ # Check that the recompute won't change the UUIDs and flows.
+@@ -18945,6 +18963,14 @@ check ip -6 neigh del dev $BR_NAME 172:16::70
+ OVS_WAIT_FOR_OUTPUT_UNQUOTED([ovn-appctl evpn/vtep-arp-list | cut -d',' -f2- | sort], [0], [dnl
])
-@@ -4816,8 +4815,7 @@ OVN_CLEANUP_NORTHD
- as
- OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
- /connection dropped.*/d
--/Service monitor not found.*/d
--/handle service check: Unsupported protocol*/d"])
-+/Service monitor not found.*/d"])
- AT_CLEANUP
++dnl Verify EVPN MAC_Binding rows are deleted from SB.
++wait_row_count MAC_Binding 0 ip=172.16.1.50
++wait_row_count MAC_Binding 0 ip=172.16.1.60
++wait_row_count MAC_Binding 0 ip=172.16.1.70
++wait_row_count MAC_Binding 0 ip="172\:16\:\:50"
++wait_row_count MAC_Binding 0 ip="172\:16\:\:60"
++wait_row_count MAC_Binding 0 ip="172\:16\:\:70"
++
+ AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=OFTABLE_MAC_BINDING | grep priority | \
+ awk '{print $[7], $[8]}' | sort], [0], [dnl
+ priority=100,reg0=0xac10010a,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:01:10,load:0x1->NXM_NX_REG10[[6]]
+@@ -18984,23 +19010,23 @@ OVS_WAIT_FOR_OUTPUT_UNQUOTED([ovn-appctl evpn/vtep-arp-list | cut -d',' -f2- | s
+ AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=OFTABLE_MAC_BINDING | grep priority | \
+ awk '{print $[7], $[8]}' | sort], [0], [dnl
+ priority=100,reg0=0xac10010a,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:01:10,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg0=0xac100132,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:50,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg0=0xac10013c,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:60,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg0=0xac100146,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:70,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg4=0x1720016,reg5=0,reg6=0,reg7=0x50,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:50,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg4=0x1720016,reg5=0,reg6=0,reg7=0x60,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:60,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg4=0x1720016,reg5=0,reg6=0,reg7=0x70,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:70,load:0x1->NXM_NX_REG10[[6]]
++priority=100,reg0=0xac100132,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:50,load:0x1->NXM_NX_REG10[[6]]
++priority=100,reg0=0xac10013c,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:60,load:0x1->NXM_NX_REG10[[6]]
++priority=100,reg0=0xac100146,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:70,load:0x1->NXM_NX_REG10[[6]]
++priority=100,reg4=0x1720016,reg5=0,reg6=0,reg7=0x50,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:50,load:0x1->NXM_NX_REG10[[6]]
++priority=100,reg4=0x1720016,reg5=0,reg6=0,reg7=0x60,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:60,load:0x1->NXM_NX_REG10[[6]]
++priority=100,reg4=0x1720016,reg5=0,reg6=0,reg7=0x70,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:70,load:0x1->NXM_NX_REG10[[6]]
])
-@@ -5027,9 +5025,12 @@ OVS_WAIT_UNTIL([
- test "${n_reset}" = "1"
+
+ AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=OFTABLE_MAC_LOOKUP | grep priority | \
+ awk '{print $[7], $[8]}' | sort], [0], [dnl
+ priority=100,arp,reg0=0xac10010a,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:01:10 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,arp,reg0=0xac100132,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,arp,reg0=0xac10013c,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,arp,reg0=0xac100146,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x50,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x60,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x70,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
++priority=100,arp,reg0=0xac100132,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50 actions=load:0x1->NXM_NX_REG10[[6]]
++priority=100,arp,reg0=0xac10013c,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60 actions=load:0x1->NXM_NX_REG10[[6]]
++priority=100,arp,reg0=0xac100146,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70 actions=load:0x1->NXM_NX_REG10[[6]]
++priority=100,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x50,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
++priority=100,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x60,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
++priority=100,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x70,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
])
-+ovn-appctl coverage/show > ovn_counters
-+AT_CHECK([test 50 -gt $(ovn-appctl -t ovn-controller coverage/read-counter pinctrl_notify_handler_thread)], [0])
-+
- # Check that we do not get too many immediate wake up.
- # Tolerate a few for any race conditions.
--AT_CHECK([test 5 -gt `cat ovn-controller.log | \
-+AT_CHECK([test 5 -gt `grep ovn_pinctrl0 ovn-controller.log | \
- grep -c "wakeup due to 0-ms timeout at controller/pinctrl.c:"`])
+ check ovn-nbctl --wait=hv lr-del lr
+@@ -19424,9 +19450,9 @@ rtr_dp_key=$(fetch_column Datapath tunnel_key external_ids:name=lr)
+ rtr_port_key=$(fetch_column Port_Binding tunnel_key logical_port=lr-ls-evpn)
- OVN_CLEANUP_CONTROLLER([hv1])
-@@ -13842,99 +13843,6 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d
- AT_CLEANUP
+ # Simulate remote workload ARPs (type-2 MAC+IP EVPN route).
+-# ovn-controller needs to add OF rules for ARP lookup but no rules for
+-# MAC_CACHE use. These entries do not age out automatically, their lifetime
+-# is controlled by the BGP-EVPN control plane.
++# EVPN-learned MAC bindings are written to the SB MAC_Binding table and
++# distributed to all chassis. These entries do not age out automatically,
++# their lifetime is controlled by the BGP-EVPN control plane.
+ check ip neigh add dev $BR_NAME 172.16.1.50 lladdr f0:00:0f:16:10:50 nud noarp extern_learn
+ check ip neigh add dev $BR_NAME 172.16.1.60 lladdr f0:00:0f:16:10:60 nud noarp extern_learn
+ check ip neigh add dev $BR_NAME 172.16.1.70 lladdr f0:00:0f:16:10:70 nud noarp extern_learn
+@@ -19444,66 +19470,51 @@ OVS_WAIT_FOR_OUTPUT_UNQUOTED([ovn-appctl evpn/vtep-arp-list | cut -d',' -f2- | s
+ VNI: 10, MAC: f0:00:0f:16:10:70, IP: 172:16::70, dp_key: $dp_key
])
--OVN_FOR_EACH_NORTHD([
--AT_SETUP([SB Disconnect - MAC_Binding buffer limit])
--ovn_start
--OVS_TRAFFIC_VSWITCHD_START()
--
--ADD_BR([br-int])
--ADD_BR([br-ext])
--
--SB_PATH="unix:$ovs_base/ovn-sb/ovn-sb.sock"
--ovs-ofctl add-flow br-ext action=normal
--# Set external-ids in br-int needed for ovn-controller
--ovs-vsctl \
-- -- set Open_vSwitch . external-ids:system-id=hv1 \
-- -- set Open_vSwitch . external-ids:ovn-remote=$SB_PATH \
-- -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
-- -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
-- -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
--
--# Start ovn-controller
--start_daemon ovn-controller
--
--ADD_NAMESPACES(sw01)
--ADD_VETH(sw01, sw01, br-int, "192.168.1.10/24", "f0:00:00:01:02:03", \
-- "192.168.1.1")
--ADD_NAMESPACES(server)
--ADD_VETH(s1, server, br-ext, "172.16.1.1/24", "f0:00:00:01:02:05", \
-- "172.16.1.254")
--
--check ovn-nbctl lr-add R1
--
--check ovn-nbctl ls-add sw0
--check ovn-nbctl ls-add sw1
--check ovn-nbctl ls-add sw-ext
--
--check ovn-nbctl lrp-add R1 rp-sw0 00:00:01:01:02:03 192.168.1.1/24
--check ovn-nbctl lrp-add R1 rp-ext 00:00:02:01:02:03 172.16.1.254/16
--
--check ovn-nbctl lrp-set-gateway-chassis rp-ext hv1
--
--check ovn-nbctl lsp-add sw0 sw0-rp -- set Logical_Switch_Port sw0-rp \
-- type=router options:router-port=rp-sw0 \
-- -- lsp-set-addresses sw0-rp router
--
--check ovn-nbctl set Logical_Switch sw0 other_config:dhcp_relay_port=sw0-rp
--
--check ovn-nbctl lsp-add sw-ext ext-rp -- set Logical_Switch_Port ext-rp \
-- type=router options:router-port=rp-ext \
-- -- lsp-set-addresses ext-rp router
--check ovn-nbctl lsp-add-localnet-port sw-ext lnet phynet
--
--check ovn-nbctl lsp-add sw0 sw01 \
-- -- lsp-set-addresses sw01 "f0:00:00:01:02:03 192.168.1.10"
--
--AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=phynet:br-ext])
--
--OVN_POPULATE_ARP
--
--check ovn-nbctl --wait=hv sync
--
--AS_BOX([Disconnect SB and send ping to generate multiple ARPs (>1000)])
--AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-remote=tcp:127.0.0.1:1234])
--AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-remote-probe-interval=100])
--
--sleep 1
--AT_CHECK([ovn-appctl connection-status], [0], [dnl
--not connected
+-AS_BOX([Check dynamic-routing-arp-prefer-local=true])
+-check ovn-nbctl --wait=hv set Logical_Switch ls-evpn other_config:dynamic-routing-arp-prefer-local=true
+-
+-AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=OFTABLE_MAC_BINDING | grep priority | \
+- awk '{print $[7], $[8]}' | sort], [0], [dnl
+-priority=100,reg0=0xac10010a,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:01:10,load:0x1->NXM_NX_REG10[[6]]
+-priority=20,reg0=0xac100132,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:50,load:0x1->NXM_NX_REG10[[6]]
+-priority=20,reg0=0xac10013c,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:60,load:0x1->NXM_NX_REG10[[6]]
+-priority=20,reg0=0xac100146,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:70,load:0x1->NXM_NX_REG10[[6]]
+-priority=20,reg4=0x1720016,reg5=0,reg6=0,reg7=0x50,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:50,load:0x1->NXM_NX_REG10[[6]]
+-priority=20,reg4=0x1720016,reg5=0,reg6=0,reg7=0x60,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:60,load:0x1->NXM_NX_REG10[[6]]
+-priority=20,reg4=0x1720016,reg5=0,reg6=0,reg7=0x70,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:70,load:0x1->NXM_NX_REG10[[6]]
-])
-
--for i in {1..20}; do
-- for j in {1..100}; do
-- NS_EXEC([sw01], [timeout 0.1 ping -q -c 1 -W 0.1 172.16.$i.$j >/dev/null 2>&1 &])
-- done
-- sleep 0.5
--done
--
--AS_BOX([Verify pinctrl_drop_buffered_packets_map counter])
--ovn-appctl coverage/show >ovn_counters
--AT_CAPTURE_FILE([ovn_counters])
--AT_CHECK([grep -q "pinctrl_drop_buffered_packets_map" ovn_counters], [0])
--
--AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-remote=$SB_PATH])
--
--OVN_CLEANUP_CONTROLLER([hv1])
--
--OVN_CLEANUP_NORTHD
--
--as
--OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d
--/failed to query port patch-.*/d
--/.*terminating with signal 15.*/d"])
--AT_CLEANUP
+-AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=OFTABLE_MAC_LOOKUP | grep priority | \
+- awk '{print $[7], $[8]}' | sort], [0], [dnl
+-priority=100,arp,reg0=0xac10010a,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:01:10 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=20,arp,reg0=0xac100132,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=20,arp,reg0=0xac10013c,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=20,arp,reg0=0xac100146,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=20,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x50,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=20,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x60,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=20,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x70,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
-])
-
- OVN_FOR_EACH_NORTHD([
- AT_SETUP([Routing protocol redirect - l3 gateway])
- AT_SKIP_IF([test $HAVE_NC = no])
-@@ -14958,6 +14866,8 @@ NS_CHECK_EXEC([vm1], [ping -q -c 3 -i 0.3 -w 2 172.18.1.12 | FORMAT_PING], \
+-AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=OFTABLE_MAC_CACHE_USE | grep priority | \
+- awk '{print $[7], $[8]}' | sort], [0], [dnl
+-priority=100,arp,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:01:10,arp_spa=172.16.1.10,arp_op=2 actions=drop
+-priority=100,ip,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:01:10,nw_src=172.16.1.10 actions=drop
+-])
++dnl Verify EVPN-learned MAC bindings appear in the SB MAC_Binding table.
++wait_row_count MAC_Binding 1 ip=172.16.1.50
++wait_row_count MAC_Binding 1 ip=172.16.1.60
++wait_row_count MAC_Binding 1 ip=172.16.1.70
++wait_row_count MAC_Binding 1 ip="172\:16\:\:50"
++wait_row_count MAC_Binding 1 ip="172\:16\:\:60"
++wait_row_count MAC_Binding 1 ip="172\:16\:\:70"
+
+-AS_BOX([Check dynamic-routing-arp-prefer-local=false])
+-check ovn-nbctl --wait=hv set Logical_Switch ls-evpn other_config:dynamic-routing-arp-prefer-local=false
++AS_BOX([Check EVPN MAC binding and lookup flows])
+
+ AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=OFTABLE_MAC_BINDING | grep priority | \
+ awk '{print $[7], $[8]}' | sort], [0], [dnl
+ priority=100,reg0=0xac10010a,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:01:10,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg0=0xac100132,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:50,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg0=0xac10013c,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:60,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg0=0xac100146,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:70,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg4=0x1720016,reg5=0,reg6=0,reg7=0x50,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:50,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg4=0x1720016,reg5=0,reg6=0,reg7=0x60,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:60,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg4=0x1720016,reg5=0,reg6=0,reg7=0x70,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:70,load:0x1->NXM_NX_REG10[[6]]
++priority=100,reg0=0xac100132,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:50,load:0x1->NXM_NX_REG10[[6]]
++priority=100,reg0=0xac10013c,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:60,load:0x1->NXM_NX_REG10[[6]]
++priority=100,reg0=0xac100146,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:70,load:0x1->NXM_NX_REG10[[6]]
++priority=100,reg4=0x1720016,reg5=0,reg6=0,reg7=0x50,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:50,load:0x1->NXM_NX_REG10[[6]]
++priority=100,reg4=0x1720016,reg5=0,reg6=0,reg7=0x60,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:60,load:0x1->NXM_NX_REG10[[6]]
++priority=100,reg4=0x1720016,reg5=0,reg6=0,reg7=0x70,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:70,load:0x1->NXM_NX_REG10[[6]]
])
- check ovn-nbctl --wait=hv set load_balancer lb0 options:add_route=false
-+# Wait for revalidation of stale megaflows which may lag behind.
-+check ovs-appctl revalidator/wait
- NS_CHECK_EXEC([vm1], [ping -q -c 3 -i 0.3 -w 2 172.18.1.12 | FORMAT_PING], \
- [0], [dnl
- 7 packets transmitted, 0 received, 100% packet loss, time 0ms
-@@ -21497,93 +21407,6 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
- AT_CLEANUP
+ AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=OFTABLE_MAC_LOOKUP | grep priority | \
+ awk '{print $[7], $[8]}' | sort], [0], [dnl
+ priority=100,arp,reg0=0xac10010a,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:01:10 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,arp,reg0=0xac100132,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,arp,reg0=0xac10013c,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,arp,reg0=0xac100146,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x50,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x60,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x70,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
++priority=100,arp,reg0=0xac100132,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50 actions=load:0x1->NXM_NX_REG10[[6]]
++priority=100,arp,reg0=0xac10013c,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60 actions=load:0x1->NXM_NX_REG10[[6]]
++priority=100,arp,reg0=0xac100146,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70 actions=load:0x1->NXM_NX_REG10[[6]]
++priority=100,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x50,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
++priority=100,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x60,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
++priority=100,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x70,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
+ ])
+
+ AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=OFTABLE_MAC_CACHE_USE | grep priority | \
+ awk '{print $[7], $[8]}' | sort], [0], [dnl
+ priority=100,arp,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:01:10,arp_spa=172.16.1.10,arp_op=2 actions=drop
++priority=100,arp,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50,arp_spa=172.16.1.50,arp_op=2 actions=drop
++priority=100,arp,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60,arp_spa=172.16.1.60,arp_op=2 actions=drop
++priority=100,arp,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70,arp_spa=172.16.1.70,arp_op=2 actions=drop
+ priority=100,ip,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:01:10,nw_src=172.16.1.10 actions=drop
++priority=100,ip,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50,nw_src=172.16.1.50 actions=drop
++priority=100,ip,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60,nw_src=172.16.1.60 actions=drop
++priority=100,ip,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70,nw_src=172.16.1.70 actions=drop
++priority=100,ipv6,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50,ipv6_src=172:16::50 actions=drop
++priority=100,ipv6,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60,ipv6_src=172:16::60 actions=drop
++priority=100,ipv6,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70,ipv6_src=172:16::70 actions=drop
+ ])
+
+ # Check that the recompute won't change the UUIDs and flows.
+@@ -19528,6 +19539,14 @@ check ip -6 neigh del dev $BR_NAME 172:16::70
+ OVS_WAIT_FOR_OUTPUT_UNQUOTED([ovn-appctl evpn/vtep-arp-list | cut -d',' -f2- | sort], [0], [dnl
+ ])
+
++dnl Verify EVPN MAC_Binding rows are deleted from SB.
++wait_row_count MAC_Binding 0 ip=172.16.1.50
++wait_row_count MAC_Binding 0 ip=172.16.1.60
++wait_row_count MAC_Binding 0 ip=172.16.1.70
++wait_row_count MAC_Binding 0 ip="172\:16\:\:50"
++wait_row_count MAC_Binding 0 ip="172\:16\:\:60"
++wait_row_count MAC_Binding 0 ip="172\:16\:\:70"
++
+ AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=OFTABLE_MAC_BINDING | grep priority | \
+ awk '{print $[7], $[8]}' | sort], [0], [dnl
+ priority=100,reg0=0xac10010a,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:01:10,load:0x1->NXM_NX_REG10[[6]]
+@@ -19567,23 +19586,23 @@ OVS_WAIT_FOR_OUTPUT_UNQUOTED([ovn-appctl evpn/vtep-arp-list | cut -d',' -f2- | s
+ AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=OFTABLE_MAC_BINDING | grep priority | \
+ awk '{print $[7], $[8]}' | sort], [0], [dnl
+ priority=100,reg0=0xac10010a,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:01:10,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg0=0xac100132,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:50,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg0=0xac10013c,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:60,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg0=0xac100146,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:70,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg4=0x1720016,reg5=0,reg6=0,reg7=0x50,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:50,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg4=0x1720016,reg5=0,reg6=0,reg7=0x60,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:60,load:0x1->NXM_NX_REG10[[6]]
+-priority=200,reg4=0x1720016,reg5=0,reg6=0,reg7=0x70,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:70,load:0x1->NXM_NX_REG10[[6]]
++priority=100,reg0=0xac100132,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:50,load:0x1->NXM_NX_REG10[[6]]
++priority=100,reg0=0xac10013c,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:60,load:0x1->NXM_NX_REG10[[6]]
++priority=100,reg0=0xac100146,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:70,load:0x1->NXM_NX_REG10[[6]]
++priority=100,reg4=0x1720016,reg5=0,reg6=0,reg7=0x50,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:50,load:0x1->NXM_NX_REG10[[6]]
++priority=100,reg4=0x1720016,reg5=0,reg6=0,reg7=0x60,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:60,load:0x1->NXM_NX_REG10[[6]]
++priority=100,reg4=0x1720016,reg5=0,reg6=0,reg7=0x70,reg15=0x$rtr_port_key,metadata=0x$rtr_dp_key actions=mod_dl_dst:f0:00:0f:16:10:70,load:0x1->NXM_NX_REG10[[6]]
])
--OVN_FOR_EACH_NORTHD([
--AT_SETUP([Unsupported protocol message])
+ AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=OFTABLE_MAC_LOOKUP | grep priority | \
+ awk '{print $[7], $[8]}' | sort], [0], [dnl
+ priority=100,arp,reg0=0xac10010a,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:01:10 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,arp,reg0=0xac100132,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,arp,reg0=0xac10013c,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,arp,reg0=0xac100146,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x50,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x60,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
+-priority=200,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x70,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
++priority=100,arp,reg0=0xac100132,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50 actions=load:0x1->NXM_NX_REG10[[6]]
++priority=100,arp,reg0=0xac10013c,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60 actions=load:0x1->NXM_NX_REG10[[6]]
++priority=100,arp,reg0=0xac100146,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70 actions=load:0x1->NXM_NX_REG10[[6]]
++priority=100,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x50,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:50,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
++priority=100,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x60,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:60,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
++priority=100,icmp6,reg0=0x1720016,reg1=0,reg2=0,reg3=0x70,reg14=0x$rtr_port_key,metadata=0x$rtr_dp_key,dl_src=f0:00:0f:16:10:70,icmp_code=0 actions=load:0x1->NXM_NX_REG10[[6]]
+ ])
+
+ check ovn-nbctl --wait=hv lr-del lr
+@@ -19675,7 +19694,7 @@ EVPN_SWITCH_TESTS([custom])
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([Router reroute policies - output port])
-AT_SKIP_IF([test $HAVE_NC = no])
++AT_SKIP_IF([test $HAVE_NCAT = no])
+
+ ovn_start
+ OVS_TRAFFIC_VSWITCHD_START()
+@@ -19742,8 +19761,8 @@ OVN_POPULATE_ARP
+ check ovn-nbctl --wait=hv sync
+ wait_for_ports_up
+
+-NETNS_DAEMONIZE([vm3], [nc -l -k 80], [vm3.pid])
+-NS_CHECK_EXEC([vm1], [nc 30.0.0.2 80 -z])
++NETNS_DAEMONIZE([vm3], [ncat -l -k 80], [vm3.pid])
++NS_CHECK_EXEC([vm1], [ncat 30.0.0.2 80 -z])
+
+ OVN_CLEANUP_CONTROLLER([hv1])
+
+@@ -20418,7 +20437,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([Distributed gw port CT zone allocation])
+-AT_SKIP_IF([test $HAVE_NC = no])
++AT_SKIP_IF([test $HAVE_NCAT = no])
+ CHECK_CONNTRACK()
+
+ ovn_start
+@@ -20520,8 +20539,8 @@ icmp,orig=(src=192.168.100.11,dst=172.16.0.100,id=<cleared>,type=8,code=0),reply
+ ])
+
+ # Test TCP traffic - should be blocked by ACLs.
+-NETNS_DAEMONIZE([external], [nc -l -k 172.16.0.100 80], [external.pid])
+-NS_CHECK_EXEC([vm1], [nc -z 172.16.0.100 80], [1])
++NETNS_DAEMONIZE([external], [ncat -l -k 172.16.0.100 80], [external.pid])
++NS_CHECK_EXEC([vm1], [ncat -z 172.16.0.100 80], [1])
+
+ # Remove enable_router_port_acl and verify CT zone release.
+ check ovn-nbctl lsp-set-options ext_lr router-port=lr_ext
+@@ -20997,14 +21016,14 @@ NETNS_START_TCPDUMP([server], [-U -i server -Q in -nn ip and '(ip[[6:2]] & 0x3ff
+ NETNS_START_TCPDUMP([client], [-U -i client -Q in -nn ip and '(ip[[6:2]] & 0x3fff != 0)'], [tcpdump-udp-client])
+
+ # Start UDP listeners on both sides.
+-NETNS_DAEMONIZE([server], [nc -l -u 172.16.1.2 5060 > udp_server.rcvd], [server.pid])
+-NETNS_DAEMONIZE([client], [nc -l -u 172.16.1.3 5061 > udp_client.rcvd], [client.pid])
++NETNS_DAEMONIZE([server], [ncat -l -u 172.16.1.2 5060 > udp_server.rcvd], [server.pid])
++NETNS_DAEMONIZE([client], [ncat -l -u 172.16.1.3 5061 > udp_client.rcvd], [client.pid])
+
+ # Client sends to server (will be fragmented due to MTU 900).
+-NS_CHECK_EXEC([client], [cat datafile | nc -w 1 -u 172.16.1.2 5060], [0], [ignore], [ignore])
++NS_CHECK_EXEC([client], [cat datafile | ncat -w 1 -u 172.16.1.2 5060], [0], [ignore], [ignore])
+
+ # Server sends to client (will be fragmented due to MTU 900).
+-NS_CHECK_EXEC([server], [cat datafile | nc -w 1 -u 172.16.1.3 5061], [0], [ignore], [ignore])
++NS_CHECK_EXEC([server], [cat datafile | ncat -w 1 -u 172.16.1.3 5061], [0], [ignore], [ignore])
+
+ OVS_WAIT_UNTIL([test -s udp_server.rcvd])
+ OVS_WAIT_UNTIL([test -s udp_client.rcvd])
+@@ -21093,8 +21112,8 @@ check ovn-nbctl --wait=hv sync
+ check ovs-appctl dpctl/flush-conntrack
+
+ # Test client -> server direction.
+-NETNS_DAEMONIZE([server], [nc -l 172.16.1.2 8080 > tcp_server.rcvd], [server.pid])
+-NS_CHECK_EXEC([client], [nc -w 1 172.16.1.2 8080 < datafile], [0], [ignore], [ignore])
++NETNS_DAEMONIZE([server], [ncat -l 172.16.1.2 8080 > tcp_server.rcvd], [server.pid])
++NS_CHECK_EXEC([client], [ncat -w 1 172.16.1.2 8080 < datafile], [0], [ignore], [ignore])
+
+ OVS_WAIT_UNTIL([test -s tcp_server.rcvd])
+ check cmp datafile tcp_server.rcvd
+@@ -21103,8 +21122,8 @@ check cmp datafile tcp_server.rcvd
+ kill $(cat server.pid) 2>/dev/null || true
+
+ # Test server -> client direction.
+-NETNS_DAEMONIZE([client], [nc -l 172.16.1.3 8081 > tcp_client.rcvd], [client.pid])
+-NS_CHECK_EXEC([server], [nc -w 1 172.16.1.3 8081 < datafile], [0], [ignore], [ignore])
++NETNS_DAEMONIZE([client], [ncat -l 172.16.1.3 8081 > tcp_client.rcvd], [client.pid])
++NS_CHECK_EXEC([server], [ncat -w 1 172.16.1.3 8081 < datafile], [0], [ignore], [ignore])
+
+ OVS_WAIT_UNTIL([test -s tcp_client.rcvd])
+ check cmp datafile tcp_client.rcvd
+@@ -21183,10 +21202,10 @@ check ovs-appctl dpctl/flush-conntrack
+ NETNS_START_TCPDUMP([server], [-U -i server -Q in -nn udp port 4000], [tcpdump-drop-server])
+
+ # Start UDP listener on server (on port 4000 which is NOT allowed by ACLs).
+-NETNS_DAEMONIZE([server], [nc -l -u 172.16.1.2 4000 > udp_drop.rcvd], [drop_server.pid])
++NETNS_DAEMONIZE([server], [ncat -l -u 172.16.1.2 4000 > udp_drop.rcvd], [drop_server.pid])
+
+ # Client sends to server on disallowed port.
+-NS_CHECK_EXEC([client], [echo "test" | nc -w 1 -u 172.16.1.2 4000], [0], [ignore], [ignore])
++NS_CHECK_EXEC([client], [echo "test" | ncat -w 1 -u 172.16.1.2 4000], [0], [ignore], [ignore])
+
+ # Wait a bit for any packets to arrive.
+ sleep 2
+@@ -21276,7 +21295,8 @@ check ovn-nbctl --wait=hv set NB_Global . options:acl_ct_translation=true
+ check ovn-nbctl --wait=hv sync
+
+ # Setup DHCP server configuration.
+-DHCP_TEST_DIR="$ovs_base/dhcp-test"
++DHCP_TEST_DIR="/tmp/dhcp-test"
++on_exit "rm -rf $DHCP_TEST_DIR"
+ mkdir -p $DHCP_TEST_DIR
+
+ cat > $DHCP_TEST_DIR/dhcpd.conf <<EOF
+@@ -21289,21 +21309,13 @@ subnet 192.168.1.0 netmask 255.255.255.0 {
+ }
+ EOF
+
+-touch $DHCP_TEST_DIR/dhcpd.leases
+-chown root:dhcpd $DHCP_TEST_DIR $DHCP_TEST_DIR/dhcpd.leases
+-chmod 775 $DHCP_TEST_DIR
+-chmod 664 $DHCP_TEST_DIR/dhcpd.leases
-
--ovn_start
--OVS_TRAFFIC_VSWITCHD_START()
--ADD_BR([br-int])
--
--# Set external-ids in br-int needed for ovn-controller.
--check ovs-vsctl \
-- -- set Open_vSwitch . external-ids:system-id=hv1 \
-- -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
-- -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
-- -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
-- -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
--
--# Start ovn-controller.
--start_daemon ovn-controller
--
--check ovn-nbctl ls-add ls1
--check ovn-nbctl lsp-add ls1 ls1p1
--check ovn-nbctl lsp-set-addresses ls1p1 "00:00:00:01:01:01 192.168.1.1"
--check ovn-nbctl lsp-add ls1 ls1p2
--check ovn-nbctl lsp-set-addresses ls1p2 "00:00:00:01:01:02 192.168.1.2"
--
--check ovn-nbctl lr-add lr1
--check ovn-nbctl lrp-add lr1 lr1-ls1 00:00:00:00:00:01 192.168.1.254/24
--check ovn-nbctl lsp-add ls1 ls1-lr1
--check ovn-nbctl lsp-set-addresses ls1-lr1 "00:00:00:00:00:01 192.168.1.254"
--check ovn-nbctl lsp-set-type ls1-lr1 router
--check ovn-nbctl lsp-set-options ls1-lr1 router-port=lr1-ls1
--
--check ovn-nbctl lrp-add lr1 lr1-ls2 00:00:00:00:00:02 192.168.2.254/24
--
--check ovn-nbctl ls-add ls2
--check ovn-nbctl lsp-add ls2 ls2-lr1
--check ovn-nbctl lsp-set-addresses ls2-lr1 "00:00:00:00:00:02 192.168.2.254"
--check ovn-nbctl lsp-set-type ls2-lr1 router
--check ovn-nbctl lsp-set-options ls2-lr1 router-port=lr1-ls2
--
--check ovn-nbctl lsp-add ls2 ls2p1
--check ovn-nbctl lsp-set-addresses ls2p1 "00:00:00:01:02:01 192.168.2.1"
--
--ADD_NAMESPACES(ls1p1)
--ADD_VETH(ls1p1, ls1p1, br-int, "192.168.1.1/24", "00:00:00:01:01:01",
-- "192.168.1.254")
--
--ADD_NAMESPACES(ls2p1)
--ADD_VETH(ls2p1, ls2p1, br-int, "192.168.2.1/24", "00:00:00:01:02:01",
-- "192.168.2.254")
--
--ADD_NAMESPACES(ls1p2)
--ADD_VETH(ls1p2, ls1p2, br-int, "192.168.1.2/24", "00:00:00:01:01:02",
-- "192.168.1.254")
--
--check ovn-nbctl lb-add lb0 192.168.5.1:12345 192.168.1.1:12345,192.168.1.2:12345
--check ovn-nbctl ls-lb-add ls1 lb0
--check ovn-nbctl lr-lb-add lr1 lb0
--lb_uuid=$(fetch_column nb:Load_Balancer _uuid name=lb0)
--check ovn-nbctl set Load_Balancer $lb_uuid protocol=udp
--check ovn-nbctl --wait=hv set Logical_Router lr1 options:chassis="hv1"
--
--wait_for_ports_up ls1p1 ls1p2 ls2p1
--
--NETNS_DAEMONIZE([ls1p1], [nc -l 12345 --udp -k --sh-exec ls], [nc1.pid])
--NETNS_DAEMONIZE([ls1p2], [nc -l 12345 --udp -k --sh-exec ls], [nc2.pid])
--
--hc_uuid=$(ovn-nbctl --id=@hc create Load_Balancer_Health_Check vip="192.168.5.1\:12345" -- \
-- add Load_Balancer $lb_uuid health_check @hc)
--check ovn-nbctl set Load_Balancer_Health_Check $hc_uuid options:timeout=20 options:success_count=3 options:failure_count=3
--check ovn-nbctl --wait=sb set load_balancer $lb_uuid ip_port_mappings:192.168.1.1=ls1p1:192.168.1.254
--
--NS_EXEC([ls2p1], [nc --udp 192.168.5.1 12345 <<< h])
--
--# It may not seem like we're actually testing anything in this test.
--# If there is a warning or error in the ovn-controller log about
--# an unsupported health check protocol, it will cause a test failure
--# when we stop ovn-controller.
--OVN_CLEANUP_CONTROLLER([hv1])
--OVN_CLEANUP_NORTHD
--
--as
--OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
--/connection dropped.*/d"])
--AT_CLEANUP
--])
+ # Start dhcpd as DHCP server in the server namespace.
+-NETNS_DAEMONIZE([server], [dhcpd -4 -f -cf $DHCP_TEST_DIR/dhcpd.conf server > $DHCP_TEST_DIR/dhcpd.log 2>&1], [dhcpd.pid])
-
+-# Give dhcpd time to start.
+-sleep 1
++NETNS_START_DHCPD([server], [server], [$DHCP_TEST_DIR/dhcpd.conf], [-4], [udp])
+
+ # Request IP via DHCP using dhclient.
+-NS_CHECK_EXEC([client], [dhclient -1 -v -lf $DHCP_TEST_DIR/dhclient.lease -pf $DHCP_TEST_DIR/dhclient.pid client], [0], [ignore], [ignore])
++NS_CHECK_EXEC([client], [dhclient -1 -lf $DHCP_TEST_DIR/dhclient.lease -pf $DHCP_TEST_DIR/dhclient.pid client])
+ # Register cleanup handler to kill dhclient when test exits.
+-on_exit 'kill $(cat $DHCP_TEST_DIR/dhclient.pid) 2>/dev/null || true'
++on_exit "test -e $DHCP_TEST_DIR/dhclient.pid && kill \$(cat $DHCP_TEST_DIR/dhclient.pid) 2>/dev/null || true"
+
+ # Verify client got an IP address from DHCP.
+ NS_CHECK_EXEC([client], [ip addr show client | grep -q "192.168.1.100"], [0])
+@@ -21321,7 +21333,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD_NO_HV([
+ AT_SETUP([Logical Switch Port Health Check])
+-AT_SKIP_IF([test $HAVE_NC = no])
++AT_SKIP_IF([test $HAVE_NCAT = no])
+
+ ovn_start
+ OVS_TRAFFIC_VSWITCHD_START()
+@@ -21363,7 +21375,7 @@ check ovn-nbctl --wait=hv sync
+ check_row_count sb:Service_Monitor 2
+
+ # Create one more health check on logical switch port.
+-NETNS_DAEMONIZE([lport], [nc -l -k 192.168.0.10 4041], [lport_tcp.pid])
++NETNS_DAEMONIZE([lport], [ncat -l -k 192.168.0.10 4041], [lport_tcp.pid])
+
+ # Wait until the services are set to online.
+ wait_row_count Service_Monitor 2 status=online
+@@ -21371,7 +21383,7 @@ wait_row_count nb:Logical_Switch_Port_Health_Check 1 status=online protocol=tcp
+
+ check ovn-nbctl lsp-hc-add lport udp 192.168.0.250 4042 192.168.0.10
+
+-NETNS_DAEMONIZE([lport], [nc -ulp 4042], [lport_udp.pid])
++NETNS_DAEMONIZE([lport], [ncat -ulp 4042], [lport_udp.pid])
+
+ # Wait until the services are set to online.
+ wait_row_count Service_Monitor 3 status=online
+@@ -21379,7 +21391,7 @@ wait_row_count nb:Logical_Switch_Port_Health_Check 1 status=online protocol=udp
+
+ check ovn-nbctl lsp-hc-del lport
+
+-# IPv6 ICMP health check (ping6).
++# IPv6 ICMP health check (ping -6).
+ check ovn-nbctl lsp-hc-add lport icmp 2001:db8::ff 2001:db8::10
+ check ovn-nbctl --wait=hv sync
+ check_row_count sb:Service_Monitor 1
+@@ -21394,7 +21406,7 @@ check ovn-nbctl --wait=hv sync
+ check_row_count sb:Service_Monitor 2
+
+ # Start IPv6 TCP server.
+-NETNS_DAEMONIZE([lport], [nc -6 -l -k 2001:db8::10 4043], [lport_ipv6_tcp.pid])
++NETNS_DAEMONIZE([lport], [ncat -6 -l -k 2001:db8::10 4043], [lport_ipv6_tcp.pid])
+
+ # Wait until the services are set to online.
+ wait_row_count Service_Monitor 2 status=online
+@@ -21406,7 +21418,7 @@ check ovn-nbctl --wait=hv sync
+ check_row_count sb:Service_Monitor 3
+
+ # Start IPv6 UDP server
+-NETNS_DAEMONIZE([lport], [nc -6 -u -l 2001:db8::10 4044], [lport_ipv6_udp.pid])
++NETNS_DAEMONIZE([lport], [ncat -6 -u -l 2001:db8::10 4044], [lport_ipv6_udp.pid])
+
+ # Wait until the services are set to online.
+ wait_row_count Service_Monitor 3 status=online
+@@ -21424,7 +21436,7 @@ AT_CLEANUP
+
OVN_FOR_EACH_NORTHD([
AT_SETUP([Load balancer health checks - service monitor source MAC matching])
- AT_SKIP_IF([test $HAVE_NC = no])
-@@ -21814,3 +21637,77 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
+-AT_SKIP_IF([test $HAVE_NC = no])
++AT_SKIP_IF([test $HAVE_NCAT = no])
+ AT_SKIP_IF([test $HAVE_TCPDUMP = no])
+
+ ovn_start
+@@ -21482,7 +21494,7 @@ NETNS_START_TCPDUMP([lsp], [-nne -i lsp -Q in 'udp port 42'], [lsp_in])
+ NETNS_START_TCPDUMP([lsp], [-nne -i lsp -Q out 'udp port 42 || icmp'], [lsp_out])
+
+ dnl Start a backend server, the monitor should change status to "online".
+-NETNS_DAEMONIZE([lsp], [nc -l -u 42], [nc.pid])
++NETNS_DAEMONIZE([lsp], [ncat -l -u 42], [nc.pid])
+ wait_row_count Service_Monitor 1 status=online
+
+ dnl Kill the backend server, the monitor should change status to "offline".
+@@ -21506,7 +21518,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([ACL - ICMP unreachable heap overread])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+
+ ovn_start
+
+@@ -21589,7 +21601,7 @@ AT_CLEANUP
+
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([DHCPv6 - Options heap overread])
+-AT_SKIP_IF([test $HAVE_SCAPY = no])
++CHECK_SCAPY
+
+ ovn_start
+
+@@ -21726,3 +21738,136 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
AT_CLEANUP
])
+
+OVN_FOR_EACH_NORTHD([
-+AT_SETUP([VIF port connected to localnet network])
++AT_SETUP([dynamic-routing - BGP learned routes with router filter name and multiple DGPs])
++
++# This test validates that BGP learned routes work correctly:
++# 1. Routes added to the VRF appear in Learned_Route table
++# 2. Remove routes learned in LRPs in same hypervisor when configure dynamic-routing-port-name
++# 3. Keep learned route just in LRP with dynamic-routing-port-name configured.
+#
+# Topology:
-+# (fabric) -- localnet-port -- LS --- DGP(chassis2) -- LR
-+# |
-+# |
-+# VM (chassis1)
-+#
-+# It is expected that ARP requests to this port are allowed on the chassis that hosts this port.
++# +---------+
++# | public |
++# +----+----+
++# |
++# +----+---+ +---------------+
++# | lr-frr |-----| lrp-dgp-dummy | (dynamic-routing-port-name NOT configured (hv1))
++# +----+---+ +---------------+
++# |
++# +------+-----------+
++# |lrp-local-bgp-port| (dynamic-routing-port-name configured (hv1))
++# +------------------+
+
+ovn_start
+OVS_TRAFFIC_VSWITCHD_START()
+ADD_BR([br-int])
-+ADD_BR([br-ext])
++ADD_BR([br-ex])
++
++check ovs-ofctl add-flow br-ex action=normal
+
-+ovs-ofctl add-flow br-ext action=normal
+# Set external-ids in br-int needed for ovn-controller
-+ovs-vsctl \
-+ -- set Open_vSwitch . external-ids:system-id=hv1 \
-+ -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
-+ -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
-+ -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
-+ -- set Open_vSwitch . external-ids:ovn-bridge-mappings=phynet:br-ext \
-+ -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
-+
-+# Start ovn-controller
++check ovs-vsctl \
++ -- set Open_vSwitch . external-ids:system-id=hv1 \
++ -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
++ -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
++ -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
++ -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
++
++# Configure bridge mappings for localnet.
++check ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=phys:br-ex
++
++vrf=10
++VRF_RESERVE([$vrf])
++
++# Start ovn-controller.
+start_daemon ovn-controller
+
-+check ovn-nbctl lr-add lr1
++check ip link add vrf-$vrf type vrf table $vrf
++on_exit "ip link del vrf-$vrf"
++check ip link set vrf-$vrf up
++
++# Create public logical switch with localnet port.
+check ovn-nbctl ls-add public
++check ovn-nbctl lsp-add-localnet-port public ln_port phys
++
++# Create lr-frr with dynamic routing in VRF $vrf.
++check ovn-nbctl lr-add lr-frr \
++ -- set Logical_Router lr-frr \
++ options:dynamic-routing=true \
++ options:dynamic-routing-vrf-id=$vrf \
++ options:dynamic-routing-redistribute=static
++
++check ovn-nbctl lrp-add lr-frr lrp-local-bgp-port 00:00:00:00:00:03 20.0.0.3/24 \
++ -- set Logical_Router_Port lrp-local-bgp-port options:dynamic-routing-maintain-vrf=false \
++ -- set Logical_Router_Port lrp-local-bgp-port options:routing-protocol-redirect=local-bgp-port
++
++check ovn-nbctl lrp-set-gateway-chassis lrp-local-bgp-port hv1
++check ovn-nbctl lsp-add-router-port public public-lr-frr lrp-local-bgp-port
++
++check ovn-nbctl lrp-add lr-frr lrp-dgp-dummy 00:00:00:00:00:04 20.0.1.3/24
++check ovn-nbctl lrp-set-gateway-chassis lrp-dgp-dummy hv1
++check ovn-nbctl ls-add ls-dummy
++check ovn-nbctl lsp-add-router-port ls-dummy lsp-dummy lrp-dgp-dummy
++
++# Create local-bgp-port in VRF 10.
++check ovs-vsctl add-port br-int local-bgp-port \
++ -- set Interface local-bgp-port type=internal \
++ -- set Interface local-bgp-port external_ids:iface-id=local-bgp-port
+
-+check ovn-nbctl lrp-add lr1 rp-public 00:00:02:01:02:03 172.31.1.1/24
-+check ovn-nbctl lsp-add-router-port public public-rp rp-public
-+check ovn-nbctl lsp-add-localnet-port public localnet phynet
-+check ovn-nbctl lrp-set-gateway-chassis rp-public hv2
-+
-+ADD_NAMESPACES(ext)
-+ADD_VETH(ext, ext, br-ext, "172.31.1.2/24", "f0:00:00:01:02:02", \
-+ "172.31.1.1")
-+ADD_NAMESPACES(lsp1)
-+ADD_VETH(lsp1, lsp1, br-int, "172.31.1.3/24", "f0:00:00:01:02:03", \
-+ "172.31.1.1")
-+ADD_NAMESPACES(lsp2)
-+ADD_VETH(lsp2, lsp2, br-int, "172.31.1.4/24", "f0:00:00:01:02:04", \
-+ "172.31.1.1")
-+
-+check ovn-nbctl lsp-add public lsp1
-+check ovn-nbctl lsp-set-addresses lsp1 "f0:00:00:01:02:03 172.31.1.3"
-+check ovn-nbctl lsp-add public lsp2
-+check ovn-nbctl lsp-set-addresses lsp2 "f0:00:00:01:02:04 172.31.1.4"
++check ovn-nbctl lsp-add public local-bgp-port \
++ -- lsp-set-addresses local-bgp-port unknown
+
++# Configure local-bgp-port interface and add to VRF.
++check ip link set local-bgp-port master vrf-$vrf
++check ip link set local-bgp-port address 00:00:00:00:00:03
++check ip addr add dev local-bgp-port 20.0.0.3/24
++check ip link set local-bgp-port up
++
++# Wait for everything to be ready.
++wait_for_ports_up
+check ovn-nbctl --wait=hv sync
+
-+NS_CHECK_EXEC([ext], [ping -q -c 3 -i 0.3 -w 2 172.31.1.3 | FORMAT_PING], \
-+[0], [dnl
-+3 packets transmitted, 3 received, 0% packet loss, time 0ms
-+])
++# Check lrp-local-bgp-port has dynamic-routing option set.
++check_row_count Port_Binding 1 logical_port=cr-lrp-local-bgp-port 'options:dynamic-routing=true'
++check_row_count Port_Binding 1 logical_port=cr-lrp-dgp-dummy 'options:dynamic-routing=true'
+
-+NS_CHECK_EXEC([lsp1], [ping -q -c 3 -i 0.3 -w 2 172.31.1.4 | FORMAT_PING], \
-+[0], [dnl
-+3 packets transmitted, 3 received, 0% packet loss, time 0ms
-+])
++# Add static routes.
++check ovn-nbctl lr-route-add lr-frr 10.10.2.1 20.0.0.42 lrp-local-bgp-port
++
++# Verify advertised routes exist.
++AS_BOX([Advertised_Route])
++wait_row_count Advertised_Route 1 ip_prefix=10.10.2.1
++
++# Add a route to the VRF (simulating BGP learning a route).
++check ip route add 10.10.3.1 via 20.0.0.25 vrf vrf-$vrf proto zebra
++
++# Verify learned route appears in SB database.
++wait_row_count Learned_Route 2 ip_prefix=10.10.3.1
++
++check ovn-nbctl --wait=hv set Logical_Router_Port lrp-local-bgp-port options:dynamic-routing-port-name=local-bgp-port
++lp=$(fetch_column port_binding _uuid logical_port=lrp-local-bgp-port)
+
++check_row_count Learned_Route 1 ip_prefix=10.10.3.1
++check_row_count Learned_Route 1 ip_prefix=10.10.3.1 logical_port=$lp
++
++check ovn-nbctl --wait=hv ls-del ls-dummy
++check ovn-nbctl --wait=hv ls-del public
++check ovn-nbctl --wait=hv lr-del lr-frr
++
++# Stop ovn-controller
+OVN_CLEANUP_CONTROLLER([hv1])
++check ovn-nbctl --wait=sb sync
++
++# Verify routes are removed in SB database.
++wait_row_count Learned_Route 0
++
+OVN_CLEANUP_NORTHD
+
+as
-+OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
-+/connection dropped.*/d"])
-+
++OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d
++/failed to query port patch-.*/d
++/.*terminating with signal 15.*/d"])
+AT_CLEANUP
+])
+diff --git a/tests/test-sparse-array.c b/tests/test-sparse-array.c
+index 3948ba884d..4258550c1e 100644
+--- a/tests/test-sparse-array.c
++++ b/tests/test-sparse-array.c
+@@ -188,14 +188,62 @@ test_remove_replace(struct ovs_cmdl_context *ctx OVS_UNUSED)
+ free(item_five);
+ }
+
++static void
++test_dynamic_bitmap_or(struct ovs_cmdl_context *ctx OVS_UNUSED)
++{
++ struct dynamic_bitmap a, b;
++
++ dynamic_bitmap_alloc(&a, 8);
++ dynamic_bitmap_alloc(&b, 8);
++
++ /* Set bits 1, 3 in 'a' through the tracked API. */
++ dynamic_bitmap_set1(&a, 1);
++ dynamic_bitmap_set1(&a, 3);
++ ovs_assert(a.n_elems == 2);
++
++ /* Set bits 3, 5, 7 in 'b'. */
++ dynamic_bitmap_set1(&b, 3);
++ dynamic_bitmap_set1(&b, 5);
++ dynamic_bitmap_set1(&b, 7);
++ ovs_assert(b.n_elems == 3);
++
++ /* OR 'b' into 'a'. Result should be {1, 3, 5, 7} = 4 elements.
++ * Before the fix, n_elems stayed at 2 because dynamic_bitmap_or
++ * did not recount. */
++ dynamic_bitmap_or(&a, b.map, b.capacity);
++ ovs_assert(a.n_elems == 4);
++ ovs_assert(dynamic_bitmap_is_set(&a, 1));
++ ovs_assert(dynamic_bitmap_is_set(&a, 3));
++ ovs_assert(dynamic_bitmap_is_set(&a, 5));
++ ovs_assert(dynamic_bitmap_is_set(&a, 7));
++ ovs_assert(!dynamic_bitmap_is_set(&a, 0));
++ ovs_assert(!dynamic_bitmap_is_set(&a, 2));
++
++ /* Clearing a bit that was added by the OR must not underflow
++ * n_elems. Before the fix, n_elems was 2 here so clearing two
++ * OR-added bits would wrap to SIZE_MAX. */
++ dynamic_bitmap_set0(&a, 5);
++ ovs_assert(a.n_elems == 3);
++ dynamic_bitmap_set0(&a, 7);
++ ovs_assert(a.n_elems == 2);
++ dynamic_bitmap_set0(&a, 1);
++ ovs_assert(a.n_elems == 1);
++ dynamic_bitmap_set0(&a, 3);
++ ovs_assert(a.n_elems == 0);
++
++ dynamic_bitmap_free(&a);
++ dynamic_bitmap_free(&b);
++}
++
+ static void
+ test_sparse_array_main(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+ {
+ ovn_set_program_name(argv[0]);
+ static const struct ovs_cmdl_command commands[] = {
+- {"add", NULL, 0, 0, test_add, OVS_RO},
+- {"remove-replace", NULL, 0, 0, test_remove_replace, OVS_RO},
+- {NULL, NULL, 0, 0, NULL, OVS_RO},
++ {"add", NULL, 0, 0, test_add, OVS_RO},
++ {"remove-replace", NULL, 0, 0, test_remove_replace, OVS_RO},
++ {"bitmap-or", NULL, 0, 0, test_dynamic_bitmap_or, OVS_RO},
++ {NULL, NULL, 0, 0, NULL, OVS_RO},
+ };
+ struct ovs_cmdl_context ctx;
+ ctx.argc = argc - 1;
+diff --git a/tests/test-spsc-ring.c b/tests/test-spsc-ring.c
+new file mode 100644
+index 0000000000..5fff7ee4ce
+--- /dev/null
++++ b/tests/test-spsc-ring.c
+@@ -0,0 +1,224 @@
++/* Copyright (c) 2026, Red Hat, Inc.
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at:
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++#include <config.h>
++#include <stdint.h>
++
++#include "tests/ovstest.h"
++#include "lib/ovn-util.h"
++#include "lib/spsc-ring.h"
++
++/* Basic push and pop with data integrity check. */
++static void
++test_basic(struct ovs_cmdl_context *ctx OVS_UNUSED)
++{
++ struct spsc_ring ring;
++ spsc_ring_init(&ring, 16, sizeof(uint32_t));
++
++ uint32_t val;
++
++ /* Pop on empty ring returns false. */
++ ovs_assert(!spsc_ring_pop(&ring, &val));
++
++ /* Push one element and pop it. */
++ val = 42;
++ ovs_assert(spsc_ring_push(&ring, &val));
++ val = 0;
++ ovs_assert(spsc_ring_pop(&ring, &val));
++ ovs_assert(val == 42);
++
++ /* Ring is empty again. */
++ ovs_assert(!spsc_ring_pop(&ring, &val));
++
++ spsc_ring_destroy(&ring);
++}
++
++/* FIFO ordering: push a sequence, pop and verify order. */
++static void
++test_fifo(struct ovs_cmdl_context *ctx OVS_UNUSED)
++{
++ struct spsc_ring ring;
++ spsc_ring_init(&ring, 64, sizeof(uint32_t));
++
++ for (uint32_t i = 0; i < 50; i++) {
++ ovs_assert(spsc_ring_push(&ring, &i));
++ }
++
++ for (uint32_t i = 0; i < 50; i++) {
++ uint32_t val;
++ ovs_assert(spsc_ring_pop(&ring, &val));
++ ovs_assert(val == i);
++ }
++
++ uint32_t val;
++ ovs_assert(!spsc_ring_pop(&ring, &val));
++
++ spsc_ring_destroy(&ring);
++}
++
++/* Fill ring to capacity, verify push fails, pop one, push succeeds. */
++static void
++test_full(struct ovs_cmdl_context *ctx OVS_UNUSED)
++{
++ struct spsc_ring ring;
++ spsc_ring_init(&ring, 8, sizeof(uint32_t));
++
++ /* Fill all 8 slots. */
++ for (uint32_t i = 0; i < 8; i++) {
++ ovs_assert(spsc_ring_push(&ring, &i));
++ }
++
++ /* 9th push must fail. */
++ uint32_t overflow = 99;
++ ovs_assert(!spsc_ring_push(&ring, &overflow));
++
++ /* Pop one element. */
++ uint32_t val;
++ ovs_assert(spsc_ring_pop(&ring, &val));
++ ovs_assert(val == 0);
++
++ /* Now push succeeds. */
++ ovs_assert(spsc_ring_push(&ring, &overflow));
++
++ /* Drain and verify: 1..7, then 99. */
++ for (uint32_t i = 1; i <= 7; i++) {
++ ovs_assert(spsc_ring_pop(&ring, &val));
++ ovs_assert(val == i);
++ }
++ ovs_assert(spsc_ring_pop(&ring, &val));
++ ovs_assert(val == 99);
++
++ ovs_assert(!spsc_ring_pop(&ring, &val));
++
++ spsc_ring_destroy(&ring);
++}
++
++/* Wraparound: push/pop many times to wrap head/tail past UINT32_MAX. */
++static void
++test_wraparound(struct ovs_cmdl_context *ctx OVS_UNUSED)
++{
++ struct spsc_ring ring;
++ spsc_ring_init(&ring, 4, sizeof(uint32_t));
++
++ /* Push and pop one-at-a-time many times to advance read/write.
++ * After 2^32 iterations the indices would wrap, but we can't run
++ * that many. Instead, manually set read/write near UINT32_MAX to
++ * test the wraparound arithmetic. */
++ atomic_store(&ring.read, UINT32_MAX - 2);
++ atomic_store(&ring.write, UINT32_MAX - 2);
++
++ /* Push 4 elements (fills the ring, wrapping tail past UINT32_MAX). */
++ for (uint32_t i = 0; i < 4; i++) {
++ ovs_assert(spsc_ring_push(&ring, &i));
++ }
++
++ /* Ring is full. */
++ uint32_t overflow = 99;
++ ovs_assert(!spsc_ring_push(&ring, &overflow));
++
++ /* Pop all 4, verify FIFO order. */
++ for (uint32_t i = 0; i < 4; i++) {
++ uint32_t val;
++ ovs_assert(spsc_ring_pop(&ring, &val));
++ ovs_assert(val == i);
++ }
++
++ /* Empty. */
++ uint32_t val;
++ ovs_assert(!spsc_ring_pop(&ring, &val));
++
++ spsc_ring_destroy(&ring);
++}
++
++/* Test SPSC_RING_FOR_EACH_POP macro. */
++static void
++test_for_each_pop(struct ovs_cmdl_context *ctx OVS_UNUSED)
++{
++ struct spsc_ring ring;
++ spsc_ring_init(&ring, 32, sizeof(uint32_t));
++
++ for (uint32_t i = 0; i < 20; i++) {
++ ovs_assert(spsc_ring_push(&ring, &i));
++ }
++
++ uint32_t count = 0;
++ uint32_t val;
++ SPSC_RING_FOR_EACH_POP (&ring, val) {
++ ovs_assert(val == count);
++ count++;
++ }
++ ovs_assert(count == 20);
++
++ /* Ring is empty after iteration. */
++ ovs_assert(!spsc_ring_pop(&ring, &val));
++
++ spsc_ring_destroy(&ring);
++}
++
++/* Test with a multi-field struct. */
++struct test_element {
++ uint64_t key;
++ uint32_t value;
++ uint8_t tag;
++};
++
++static void
++test_struct(struct ovs_cmdl_context *ctx OVS_UNUSED)
++{
++ struct spsc_ring ring;
++ spsc_ring_init(&ring, 16, sizeof(struct test_element));
++
++ for (uint64_t i = 0; i < 10; i++) {
++ struct test_element elem = {
++ .key = i * 1000,
++ .value = (uint32_t) i,
++ .tag = (uint8_t) (i & 0xff),
++ };
++ ovs_assert(spsc_ring_push(&ring, &elem));
++ }
++
++ struct test_element out;
++ uint64_t count = 0;
++ SPSC_RING_FOR_EACH_POP (&ring, out) {
++ ovs_assert(out.key == count * 1000);
++ ovs_assert(out.value == (uint32_t) count);
++ ovs_assert(out.tag == (uint8_t) (count & 0xff));
++ count++;
++ }
++ ovs_assert(count == 10);
++
++ spsc_ring_destroy(&ring);
++}
++
++static void
++test_spsc_ring_main(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
++{
++ ovn_set_program_name(argv[0]);
++ static const struct ovs_cmdl_command commands[] = {
++ {"basic", NULL, 0, 0, test_basic, OVS_RO},
++ {"fifo", NULL, 0, 0, test_fifo, OVS_RO},
++ {"full", NULL, 0, 0, test_full, OVS_RO},
++ {"wraparound", NULL, 0, 0, test_wraparound, OVS_RO},
++ {"for-each-pop", NULL, 0, 0, test_for_each_pop, OVS_RO},
++ {"struct", NULL, 0, 0, test_struct, OVS_RO},
++ {NULL, NULL, 0, 0, NULL, OVS_RO},
++ };
++ struct ovs_cmdl_context ctx;
++ ctx.argc = argc - 1;
++ ctx.argv = argv + 1;
++ ovs_cmdl_run_command(&ctx, commands);
++}
++
++OVSTEST_REGISTER("test-spsc-ring", test_spsc_ring_main);
+diff --git a/utilities/checkpatch.py b/utilities/checkpatch.py
+index c19e344428..032211261d 100755
+--- a/utilities/checkpatch.py
++++ b/utilities/checkpatch.py
+@@ -203,8 +203,8 @@ __regex_if_macros = re.compile(r'^ +(%s) \([\S]([\s\S]+[\S])*\) { +\\' %
+ __regex_nonascii_characters = re.compile("[^\u0000-\u007f]")
+ __regex_efgrep = re.compile(r'.*[ef]grep.*$')
+ __regex_hardcoded_table = re.compile(r'.*(table=[0-9]+)|.*(resubmit\(,[0-9]+\))')
+-__regex_ovn_nbctl = re.compile(r'^\s*ovn-nbctl ')
+-__regex_ovn_sbctl = re.compile(r'^\s*ovn-sbctl ')
++__regex_ovn_nbctl = re.compile(r'^\s*(?:ovn_as\s+\S+\s+)?ovn-nbctl\b')
++__regex_ovn_sbctl = re.compile(r'^\s*(?:ovn_as\s+\S+\s+)?ovn-sbctl\b')
+
+ skip_leading_whitespace_check = False
+ skip_trailing_whitespace_check = False
diff --git a/ovn.spec b/ovn.spec
index 529d4b6..6b59500 100644
--- a/ovn.spec
+++ b/ovn.spec
@@ -45,20 +45,20 @@
Name: ovn
Summary: Open Virtual Network support
URL: http://www.openvswitch.org/
-Version: 26.03.1
-Release: 52%{?commit0:.%{date}git%{shortcommit0}}%{?dist}
+Version: 26.03.2
+Release: 122%{?commit0:.%{date}git%{shortcommit0}}%{?dist}
Obsoletes: openvswitch-ovn-common < %{?epoch_ovs:%{epoch_ovs}:}2.11.0-8
Provides: openvswitch-ovn-common = %{?epoch:%{epoch}:}%{version}-%{release}
License: Apache-2.0 AND LGPL-2.1-only AND SISSL
-%define ovncommit 0cc1ea5bb71d29b91244f5368ecbbda8837bc542
+%define ovncommit 608aa1c3f6aa94ca5e5afc093452228f481d3b9b
# Always pull an upstream release, since this is what we rebase to.
Source: https://github.com/ovn-org/ovn/archive/%{ovncommit}.tar.gz#/ovn-%{version}.tar.gz
-%define ovscommit bdb95cc1920d4ab66fe062a9470eeb33a51d33e2
-%define ovsshortcommit bdb95cc
+%define ovscommit 45ee6f7d96dec264df2c044bc9afa61d4ef5af37
+%define ovsshortcommit 45ee6f7
Source10: https://github.com/openvswitch/ovs/archive/%{ovscommit}.tar.gz#/openvswitch-%{ovsshortcommit}.tar.gz
%define ovsdir ovs-%{ovscommit}
@@ -456,6 +456,16 @@ fi
%{_unitdir}/ovn-br-db.service
%changelog
+* Thu Sep 10 2026 Dumitru Ceara <dceara@redhat.com> - 26.03.2-122
+- Updated the OVN sources to upstream release v26.03.2 with the
+ commit 608aa1c3f6aa94ca5e5afc093452228f481d3b9b and picked up
+ the commits from v26.03.2 till the tip of branch-26.03
+ 572bc48cf71e9409d32674371a4c9a5ca603bed1 (99 commits) in
+ ovn.patch.
+- Updated the OVS sources to the branch-26.03 ovs submodule
+ commit 45ee6f7d96dec264df2c044bc9afa61d4ef5af37.
+
+
* Thu Aug 6 2026 Nicholas Hubbard <nhubbard@redhat.com> - 26.03.1-52
- Prevent RPM macro expansion in the version-suffix changelog entry.
diff --git a/sources b/sources
index d460d66..b2dd961 100644
--- a/sources
+++ b/sources
@@ -1,2 +1,2 @@
-SHA512 (openvswitch-bdb95cc.tar.gz) = 6d6ab25271bbe3e4ff8136bcaf63fe941dd70373c6035a7b50c881de4ff696a99256c935c0863e5ffd3a17857d61df9e901defef25dd8f74d9cbeaaccc7c604c
-SHA512 (ovn-26.03.1.tar.gz) = 7a9099d539fb80423854e3fa3f3e5aad86ba9e3e06ed30672b5c357ed392ff2556877c2fbef27b5e38380eb5d53cd1f413920221def203c3324042e4f20c89cd
+SHA512 (openvswitch-45ee6f7.tar.gz) = 01d9292ca1435a25d154506e0cc9a2066e12c94c822810f111431332aaee7fcd1d2d32f4d092c0c2167e3636817cf6373df7f6fcd70d24da8fac7177722a8b8c
+SHA512 (ovn-26.03.2.tar.gz) = 427155fa03065d2b02c863f59fb602f7b025af07df3fa0d91b834317164f4204fb8547c39e0f23758994b9ae30306c9bcbfff2411913c4680049255dbdc6cb1b
reply other threads:[~2026-09-10 10:17 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=178903547183.1.11980514382714072155.rpms-ovn-1d766d446a01@fedoraproject.org \
--to=dceara@redhat.com \
--cc=git-commits@fedoraproject.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox