diff --git a/.ovn.metadata b/.ovn.metadata
index 1e1f41f..b668ec7 100644
--- a/.ovn.metadata
+++ b/.ovn.metadata
@@ -1,5 +1,5 @@
002450621b33c5690060345b0aac25bc2426d675 SOURCES/docutils-0.12.tar.gz
-6d9f9a2a605a1310ee9b27a9374db308fc742814 SOURCES/openvswitch-fe55ce3.tar.gz
+05213fb15c10f668ae2f6b07be8afef5449e4c59 SOURCES/openvswitch-f19448b.tar.gz
2ddef5605ed74dd6c2331789129d27946508afee SOURCES/ovn-24.03.1.tar.gz
d34f96421a86004aa5d26ecf975edefd09f948b1 SOURCES/Pygments-1.4.tar.gz
6beb30f18ffac3de7689b7fd63e9a8a7d9c8df3a SOURCES/Sphinx-1.1.3.tar.gz
diff --git a/SOURCES/ovn24.03.patch b/SOURCES/ovn24.03.patch
index fbef749..90c0e40 100644
--- a/SOURCES/ovn24.03.patch
+++ b/SOURCES/ovn24.03.patch
@@ -17,6 +17,30 @@ index 0cb981775..456ab5c69 100644
- name: load image
run: |
sudo podman load -i /tmp/image.tar
+diff --git a/Documentation/internals/release-process.rst b/Documentation/internals/release-process.rst
+index 26d3f8d4d..988257975 100644
+--- a/Documentation/internals/release-process.rst
++++ b/Documentation/internals/release-process.rst
+@@ -203,5 +203,5 @@ Contact
+
+ Use dev@openvswitch.org to discuss the OVN development and release process.
+
+-__ https://www.ovn.org/en/releases/long_term_support/
++__ https://www.ovn.org/en/releases/#long-term-support
+ __ https://www.ovn.org
+diff --git a/Documentation/intro/install/general.rst b/Documentation/intro/install/general.rst
+index ab6209482..6efb3a701 100644
+--- a/Documentation/intro/install/general.rst
++++ b/Documentation/intro/install/general.rst
+@@ -428,7 +428,7 @@ the first time after you create the databases with ovsdb-tool, though running
+ it at any time is harmless::
+
+ $ ovn-nbctl --no-wait init
+- $ ovn-sbctl --no-wait init
++ $ ovn-sbctl init
+
+ Start ``ovn-northd``, telling it to connect to the OVN db servers same
+ Unix domain socket::
diff --git a/NEWS b/NEWS
index e91cbe43f..ca3562425 100644
--- a/NEWS
@@ -41,6 +65,786 @@ index 5f15422f2..962422bd2 100644
AC_CONFIG_MACRO_DIR([m4])
AC_CONFIG_AUX_DIR([build-aux])
AC_CONFIG_HEADERS([config.h])
+diff --git a/controller/binding.c b/controller/binding.c
+index 2afc5d48a..8ac2ce3e2 100644
+--- a/controller/binding.c
++++ b/controller/binding.c
+@@ -1709,7 +1709,10 @@ consider_container_lport(const struct sbrec_port_binding *pb,
+ }
+
+ ovs_assert(parent_b_lport && parent_b_lport->pb);
+- bool can_bind = lport_can_bind_on_this_chassis(b_ctx_in->chassis_rec, pb);
++ /* cannot bind to this chassis if the parent_port cannot be bounded. */
++ bool can_bind = lport_can_bind_on_this_chassis(b_ctx_in->chassis_rec,
++ parent_b_lport->pb) &&
++ lport_can_bind_on_this_chassis(b_ctx_in->chassis_rec, pb);
+
+ return consider_vif_lport_(pb, can_bind, b_ctx_in, b_ctx_out,
+ container_b_lport);
+diff --git a/controller/chassis.c b/controller/chassis.c
+index ad75df288..9bb2eba95 100644
+--- a/controller/chassis.c
++++ b/controller/chassis.c
+@@ -371,6 +371,7 @@ chassis_build_other_config(const struct ovs_chassis_cfg *ovs_cfg,
+ smap_replace(config, OVN_FEATURE_FDB_TIMESTAMP, "true");
+ smap_replace(config, OVN_FEATURE_LS_DPG_COLUMN, "true");
+ smap_replace(config, OVN_FEATURE_CT_COMMIT_NAT_V2, "true");
++ smap_replace(config, OVN_FEATURE_CT_COMMIT_TO_ZONE, "true");
+ }
+
+ /*
+@@ -516,6 +517,12 @@ chassis_other_config_changed(const struct ovs_chassis_cfg *ovs_cfg,
+ return true;
+ }
+
++ if (!smap_get_bool(&chassis_rec->other_config,
++ OVN_FEATURE_CT_COMMIT_TO_ZONE,
++ false)) {
++ return true;
++ }
++
+ return false;
+ }
+
+@@ -648,6 +655,7 @@ update_supported_sset(struct sset *supported)
+ sset_add(supported, OVN_FEATURE_FDB_TIMESTAMP);
+ sset_add(supported, OVN_FEATURE_LS_DPG_COLUMN);
+ sset_add(supported, OVN_FEATURE_CT_COMMIT_NAT_V2);
++ sset_add(supported, OVN_FEATURE_CT_COMMIT_TO_ZONE);
+ }
+
+ static void
+diff --git a/controller/lflow.c b/controller/lflow.c
+index 895d17d19..760ec0b41 100644
+--- a/controller/lflow.c
++++ b/controller/lflow.c
+@@ -874,6 +874,7 @@ add_matches_to_flow_table(const struct sbrec_logical_flow *lflow,
+ .collector_ids = l_ctx_in->collector_ids,
+ .lflow_uuid = lflow->header_.uuid,
+ .dp_key = ldp->datapath->tunnel_key,
++ .explicit_arp_ns_output = l_ctx_in->explicit_arp_ns_output,
+
+ .pipeline = ingress ? OVNACT_P_INGRESS : OVNACT_P_EGRESS,
+ .ingress_ptable = OFTABLE_LOG_INGRESS_PIPELINE,
+diff --git a/controller/lflow.h b/controller/lflow.h
+index 9b7ffa19c..295d004f4 100644
+--- a/controller/lflow.h
++++ b/controller/lflow.h
+@@ -130,6 +130,7 @@ struct lflow_ctx_in {
+ bool lb_hairpin_use_ct_mark;
+ bool localnet_learn_fdb;
+ bool localnet_learn_fdb_changed;
++ bool explicit_arp_ns_output;
+ };
+
+ struct lflow_ctx_out {
+diff --git a/controller/mac-learn.c b/controller/mac-learn.c
+index 071f01b4f..0c3b60c23 100644
+--- a/controller/mac-learn.c
++++ b/controller/mac-learn.c
+@@ -199,15 +199,24 @@ ovn_fdb_add(struct hmap *fdbs, uint32_t dp_key, struct eth_addr mac,
+ /* packet buffering functions */
+
+ struct packet_data *
+-ovn_packet_data_create(struct ofpbuf ofpacts,
+- const struct dp_packet *original_packet)
++ovn_packet_data_create(const struct ofputil_packet_in *pin,
++ const struct ofpbuf *continuation)
+ {
+ struct packet_data *pd = xmalloc(sizeof *pd);
+
+- pd->ofpacts = ofpacts;
+- /* clone the packet to send it later with correct L2 address */
+- pd->p = dp_packet_clone_data(dp_packet_data(original_packet),
+- dp_packet_size(original_packet));
++ pd->pin = (struct ofputil_packet_in) {
++ .packet = xmemdup(pin->packet, pin->packet_len),
++ .packet_len = pin->packet_len,
++ .flow_metadata = pin->flow_metadata,
++ .reason = pin->reason,
++ .table_id = pin->table_id,
++ .cookie = pin->cookie,
++ /* Userdata are empty on purpose,
++ * it is not needed for the continuation. */
++ .userdata = NULL,
++ .userdata_len = 0,
++ };
++ pd->continuation = ofpbuf_clone(continuation);
+
+ return pd;
+ }
+@@ -216,8 +225,8 @@ ovn_packet_data_create(struct ofpbuf ofpacts,
+ void
+ ovn_packet_data_destroy(struct packet_data *pd)
+ {
+- dp_packet_delete(pd->p);
+- ofpbuf_uninit(&pd->ofpacts);
++ free(pd->pin.packet);
++ ofpbuf_delete(pd->continuation);
+ free(pd);
+ }
+
+@@ -307,7 +316,10 @@ ovn_buffered_packets_ctx_run(struct buffered_packets_ctx *ctx,
+
+ struct packet_data *pd;
+ LIST_FOR_EACH_POP (pd, node, &bp->queue) {
+- struct eth_header *eth = dp_packet_data(pd->p);
++ struct dp_packet packet;
++ dp_packet_use_const(&packet, pd->pin.packet, pd->pin.packet_len);
++
++ struct eth_header *eth = dp_packet_data(&packet);
+ eth->eth_dst = mac;
+
+ ovs_list_push_back(&ctx->ready_packets_data, &pd->node);
+diff --git a/controller/mac-learn.h b/controller/mac-learn.h
+index e0fd6a8d1..20a015e1a 100644
+--- a/controller/mac-learn.h
++++ b/controller/mac-learn.h
+@@ -24,6 +24,7 @@
+ #include "openvswitch/hmap.h"
+ #include "openvswitch/list.h"
+ #include "openvswitch/ofpbuf.h"
++#include "openvswitch/ofp-packet.h"
+
+ struct ovsdb_idl_index;
+
+@@ -91,8 +92,8 @@ struct fdb_entry *ovn_fdb_add(struct hmap *fdbs,
+ struct packet_data {
+ struct ovs_list node;
+
+- struct ofpbuf ofpacts;
+- struct dp_packet *p;
++ struct ofpbuf *continuation;
++ struct ofputil_packet_in pin;
+ };
+
+ struct buffered_packets {
+@@ -120,8 +121,8 @@ struct buffered_packets_ctx {
+ };
+
+ struct packet_data *
+-ovn_packet_data_create(struct ofpbuf ofpacts,
+- const struct dp_packet *original_packet);
++ovn_packet_data_create(const struct ofputil_packet_in *pin,
++ const struct ofpbuf *continuation);
+ void ovn_packet_data_destroy(struct packet_data *pd);
+ struct buffered_packets *
+ ovn_buffered_packets_add(struct buffered_packets_ctx *ctx, uint64_t dp_key,
+diff --git a/controller/ofctrl.c b/controller/ofctrl.c
+index f14cd79a8..6a2564604 100644
+--- a/controller/ofctrl.c
++++ b/controller/ofctrl.c
+@@ -634,7 +634,6 @@ run_S_WAIT_BEFORE_CLEAR(void)
+ if (!wait_before_clear_time ||
+ (wait_before_clear_expire &&
+ time_msec() >= wait_before_clear_expire)) {
+- wait_before_clear_expire = 0;
+ state = S_CLEAR_FLOWS;
+ return;
+ }
+@@ -787,7 +786,7 @@ ofctrl_run(const struct ovsrec_bridge *br_int,
+
+ rconn_run(swconn);
+
+- if (!rconn_is_connected(swconn)) {
++ if (!rconn_is_connected(swconn) || !pending_ct_zones) {
+ return reconnected;
+ }
+
+@@ -1112,6 +1111,12 @@ sb_to_flow_size(const struct sb_to_flow *stf)
+ return sizeof *stf;
+ }
+
++static size_t
++sb_addrset_ref_size(const struct sb_addrset_ref *sar)
++{
++ return sizeof *sar + strlen(sar->name) + 1;
++}
++
+ static struct sb_to_flow *
+ sb_to_flow_find(struct hmap *uuid_flow_table, const struct uuid *sb_uuid)
+ {
+@@ -1181,8 +1186,8 @@ link_flow_to_sb(struct ovn_desired_flow_table *flow_table,
+ }
+ if (!found) {
+ sar = xmalloc(sizeof *sar);
+- mem_stats.sb_flow_ref_usage += sizeof *sar;
+ sar->name = xstrdup(as_info->name);
++ mem_stats.sb_flow_ref_usage += sb_addrset_ref_size(sar);
+ hmap_init(&sar->as_ip_to_flow_map);
+ ovs_list_insert(&stf->addrsets, &sar->list_node);
+ }
+@@ -1568,7 +1573,7 @@ remove_flows_from_sb_to_flow(struct ovn_desired_flow_table *flow_table,
+ free(itfn);
+ }
+ hmap_destroy(&sar->as_ip_to_flow_map);
+- mem_stats.sb_flow_ref_usage -= (sizeof *sar + strlen(sar->name) + 1);
++ mem_stats.sb_flow_ref_usage -= sb_addrset_ref_size(sar);
+ free(sar->name);
+ free(sar);
+ }
+diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
+index 1c9960c70..a40712e53 100644
+--- a/controller/ovn-controller.c
++++ b/controller/ovn-controller.c
+@@ -3667,6 +3667,7 @@ non_vif_data_ovs_iface_handler(struct engine_node *node, void *data OVS_UNUSED)
+
+ struct ed_type_northd_options {
+ bool lb_hairpin_use_ct_mark;
++ bool explicit_arp_ns_output;
+ };
+
+
+@@ -3697,6 +3698,13 @@ en_northd_options_run(struct engine_node *node, void *data)
+ ? smap_get_bool(&sb_global->options, "lb_hairpin_use_ct_mark",
+ DEFAULT_SB_GLOBAL_LB_HAIRPIN_USE_CT_MARK)
+ : DEFAULT_SB_GLOBAL_LB_HAIRPIN_USE_CT_MARK;
++
++ n_opts->explicit_arp_ns_output =
++ sb_global
++ ? smap_get_bool(&sb_global->options, "arp_ns_explicit_output",
++ false)
++ : false;
++
+ engine_set_node_state(node, EN_UPDATED);
+ }
+
+@@ -3719,6 +3727,18 @@ en_northd_options_sb_sb_global_handler(struct engine_node *node, void *data)
+ n_opts->lb_hairpin_use_ct_mark = lb_hairpin_use_ct_mark;
+ engine_set_node_state(node, EN_UPDATED);
+ }
++
++ bool explicit_arp_ns_output =
++ sb_global
++ ? smap_get_bool(&sb_global->options, "arp_ns_explicit_output",
++ false)
++ : false;
++
++ if (explicit_arp_ns_output != n_opts->explicit_arp_ns_output) {
++ n_opts->explicit_arp_ns_output = explicit_arp_ns_output;
++ engine_set_node_state(node, EN_UPDATED);
++ }
++
+ return true;
+ }
+
+@@ -3948,6 +3968,7 @@ init_lflow_ctx(struct engine_node *node,
+ l_ctx_in->localnet_learn_fdb_changed = rt_data->localnet_learn_fdb_changed;
+ l_ctx_in->chassis_tunnels = &non_vif_data->chassis_tunnels;
+ l_ctx_in->lb_hairpin_use_ct_mark = n_opts->lb_hairpin_use_ct_mark;
++ l_ctx_in->explicit_arp_ns_output = n_opts->explicit_arp_ns_output;
+ l_ctx_in->nd_ra_opts = &fo->nd_ra_opts;
+ l_ctx_in->dhcp_opts = &dhcp_opts->v4_opts;
+ l_ctx_in->dhcpv6_opts = &dhcp_opts->v6_opts;
+@@ -5736,10 +5757,11 @@ main(int argc, char *argv[])
+ }
+ }
+
+- if (br_int && ovs_feature_set_discovered()) {
++ if (br_int) {
+ ct_zones_data = engine_get_data(&en_ct_zones);
+- if (ct_zones_data && ofctrl_run(br_int, ovs_table,
+- &ct_zones_data->pending)) {
++ if (ofctrl_run(br_int, ovs_table,
++ ct_zones_data ? &ct_zones_data->pending
++ : NULL)) {
+ static struct vlog_rate_limit rl
+ = VLOG_RATE_LIMIT_INIT(1, 1);
+
+@@ -5748,7 +5770,7 @@ main(int argc, char *argv[])
+ engine_set_force_recompute(true);
+ }
+
+- if (chassis) {
++ if (chassis && ovs_feature_set_discovered()) {
+ encaps_run(ovs_idl_txn, br_int,
+ sbrec_chassis_table_get(ovnsb_idl_loop.idl),
+ chassis,
+diff --git a/controller/physical.c b/controller/physical.c
+index 7ef259da4..86d4b4578 100644
+--- a/controller/physical.c
++++ b/controller/physical.c
+@@ -1631,6 +1631,15 @@ consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name,
+ nested_container = true;
+ parent_port = lport_lookup_by_name(
+ sbrec_port_binding_by_name, binding->parent_port);
++
++ if (parent_port
++ && !lport_can_bind_on_this_chassis(chassis, parent_port)) {
++ /* Even though there is an ofport for this container
++ * parent port, it is requested on different chassis ignore
++ * this container port.
++ */
++ return;
++ }
+ }
+ } else if (!strcmp(binding->type, "localnet")
+ || !strcmp(binding->type, "l2gateway")) {
+diff --git a/controller/pinctrl.c b/controller/pinctrl.c
+index 98b29de9f..b2a380437 100644
+--- a/controller/pinctrl.c
++++ b/controller/pinctrl.c
+@@ -257,9 +257,9 @@ static void pinctrl_handle_put_nd_ra_opts(
+ struct ofpbuf *continuation);
+ static void pinctrl_handle_nd_ns(struct rconn *swconn,
+ const struct flow *ip_flow,
+- struct dp_packet *pkt_in,
+- const struct match *md,
+- struct ofpbuf *userdata);
++ const struct ofputil_packet_in *pin,
++ struct ofpbuf *userdata,
++ const struct ofpbuf *continuation);
+ static void pinctrl_handle_put_icmp_frag_mtu(struct rconn *swconn,
+ const struct flow *in_flow,
+ struct dp_packet *pkt_in,
+@@ -660,6 +660,8 @@ pinctrl_forward_pkt(struct rconn *swconn, int64_t dp_key,
+ put_load(dp_key, MFF_LOG_DATAPATH, 0, 64, &ofpacts);
+ put_load(in_port_key, MFF_LOG_INPORT, 0, 32, &ofpacts);
+ put_load(out_port_key, MFF_LOG_OUTPORT, 0, 32, &ofpacts);
++ /* Avoid re-injecting packet already consumed. */
++ put_load(1, MFF_LOG_FLAGS, MLF_IGMP_IGMP_SNOOP_INJECT_BIT, 1, &ofpacts);
+
+ struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&ofpacts);
+ resubmit->in_port = OFPP_CONTROLLER;
+@@ -701,9 +703,6 @@ struct ipv6_prefixd_state {
+ long long int next_announce;
+ long long int last_complete;
+ long long int last_used;
+- /* IPv6 PD server info */
+- struct in6_addr server_addr;
+- struct eth_addr sa;
+ /* server_id_info */
+ struct {
+ uint8_t data[DHCPV6_MAX_DUID_LEN];
+@@ -860,12 +859,13 @@ pinctrl_parse_dhcpv6_advt(struct rconn *swconn, const struct flow *ip_flow,
+ struct dp_packet packet;
+
+ dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
+- eth_compose(&packet, ip_flow->dl_src, ip_flow->dl_dst, ETH_TYPE_IPV6,
+- IPV6_HEADER_LEN);
++ eth_compose(&packet, (struct eth_addr) ETH_ADDR_C(33,33,00,01,00,02),
++ ip_flow->dl_dst, ETH_TYPE_IPV6, IPV6_HEADER_LEN);
+
+ struct udp_header *udp_h = compose_ipv6(&packet, IPPROTO_UDP,
+ &ip_flow->ipv6_dst,
+- &ip_flow->ipv6_src, 0, 0, 255,
++ &in6addr_all_dhcp_agents,
++ 0, 0, 255,
+ len + UDP_HEADER_LEN + 4);
+ udp_h->udp_len = htons(len + UDP_HEADER_LEN + 4);
+ udp_h->udp_csum = 0;
+@@ -919,7 +919,6 @@ out:
+ static void
+ pinctrl_prefixd_state_handler(const struct flow *ip_flow,
+ struct in6_addr addr, unsigned aid,
+- struct eth_addr sa, struct in6_addr server_addr,
+ char prefix_len, unsigned t1, unsigned t2,
+ unsigned plife_time, unsigned vlife_time,
+ const uint8_t *uuid, uint8_t uuid_len)
+@@ -929,8 +928,6 @@ pinctrl_prefixd_state_handler(const struct flow *ip_flow,
+ pfd = pinctrl_find_prefixd_state(ip_flow, aid);
+ if (pfd) {
+ pfd->state = PREFIX_PENDING;
+- pfd->server_addr = server_addr;
+- pfd->sa = sa;
+ memcpy(pfd->uuid.data, uuid, uuid_len);
+ pfd->uuid.len = uuid_len;
+ pfd->plife_time = plife_time * 1000;
+@@ -948,10 +945,6 @@ pinctrl_parse_dhcpv6_reply(struct dp_packet *pkt_in,
+ const struct flow *ip_flow)
+ OVS_REQUIRES(pinctrl_mutex)
+ {
+- struct eth_header *eth = dp_packet_eth(pkt_in);
+- struct ovs_16aligned_ip6_hdr *in_ip = dp_packet_l3(pkt_in);
+- struct in6_addr ip6_src;
+- memcpy(&ip6_src, &in_ip->ip6_src, sizeof ip6_src);
+ struct udp_header *udp_in = dp_packet_l4(pkt_in);
+ unsigned char *in_dhcpv6_data = (unsigned char *)(udp_in + 1);
+ size_t dlen = MIN(ntohs(udp_in->udp_len), dp_packet_l4_size(pkt_in));
+@@ -1034,8 +1027,7 @@ pinctrl_parse_dhcpv6_reply(struct dp_packet *pkt_in,
+ VLOG_DBG_RL(&rl, "Received DHCPv6 reply from %s with prefix %s/%d"
+ " aid %d", ip6_s, prefix, prefix_len, aid);
+ }
+- pinctrl_prefixd_state_handler(ip_flow, ipv6, aid, eth->eth_src,
+- ip6_src, prefix_len, t1, t2,
++ pinctrl_prefixd_state_handler(ip_flow, ipv6, aid, prefix_len, t1, t2,
+ plife_time, vlife_time, uuid, uuid_len);
+ }
+ }
+@@ -1068,28 +1060,22 @@ pinctrl_handle_dhcp6_server(struct rconn *swconn, const struct flow *ip_flow,
+ static void
+ compose_prefixd_packet(struct dp_packet *b, struct ipv6_prefixd_state *pfd)
+ {
+- struct in6_addr ipv6_dst;
+- struct eth_addr eth_dst;
+-
+ int payload = sizeof(struct dhcpv6_opt_server_id) +
+ sizeof(struct dhcpv6_opt_ia_na);
+ if (pfd->uuid.len) {
+ payload += pfd->uuid.len + sizeof(struct dhcpv6_opt_header);
+- ipv6_dst = pfd->server_addr;
+- eth_dst = pfd->sa;
+- } else {
+- eth_dst = (struct eth_addr) ETH_ADDR_C(33,33,00,01,00,02);
+- ipv6_parse("ff02::1:2", &ipv6_dst);
+ }
+ if (ipv6_addr_is_set(&pfd->prefix)) {
+ payload += sizeof(struct dhcpv6_opt_ia_prefix);
+ }
+
+- eth_compose(b, eth_dst, pfd->ea, ETH_TYPE_IPV6, IPV6_HEADER_LEN);
++ eth_compose(b, (struct eth_addr) ETH_ADDR_C(33,33,00,01,00,02), pfd->ea,
++ ETH_TYPE_IPV6, IPV6_HEADER_LEN);
+
+ int len = UDP_HEADER_LEN + 4 + payload;
+ struct udp_header *udp_h = compose_ipv6(b, IPPROTO_UDP, &pfd->ipv6_addr,
+- &ipv6_dst, 0, 0, 255, len);
++ &in6addr_all_dhcp_agents,
++ 0, 0, 255, len);
+ udp_h->udp_len = htons(len);
+ udp_h->udp_csum = 0;
+ packet_set_udp_port(b, htons(546), htons(547));
+@@ -1171,7 +1157,7 @@ ipv6_prefixd_send(struct rconn *swconn, struct ipv6_prefixd_state *pfd)
+ return pfd->next_announce;
+ }
+
+- if (pfd->state == PREFIX_DONE) {
++ if ((pfd->state == PREFIX_PENDING) || (pfd->state == PREFIX_DONE)) {
+ goto out;
+ }
+
+@@ -1222,33 +1208,58 @@ static bool ipv6_prefixd_should_inject(void)
+ struct ipv6_prefixd_state *pfd = iter->data;
+ long long int cur_time = time_msec();
+
+- if (pfd->state == PREFIX_SOLICIT) {
++ if (pfd->state == PREFIX_SOLICIT || pfd->state == PREFIX_REQUEST) {
+ return true;
+ }
+ if (pfd->state == PREFIX_DONE &&
+ cur_time > pfd->last_complete + pfd->t1) {
+- pfd->state = PREFIX_RENEW;
+ return true;
+ }
+ if (pfd->state == PREFIX_RENEW &&
+ cur_time > pfd->last_complete + pfd->t2) {
+- pfd->state = PREFIX_REBIND;
+ pfd->uuid.len = 0;
+ return true;
+ }
+ if (pfd->state == PREFIX_REBIND &&
+ cur_time > pfd->last_complete + pfd->vlife_time) {
+- pfd->state = PREFIX_SOLICIT;
+ return true;
+ }
+ }
+ return false;
+ }
+
++static void ipv6_prefixd_update_state(struct ipv6_prefixd_state *pfd)
++{
++ long long int cur_time = time_msec();
++
++ if (pfd->state == PREFIX_DONE &&
++ cur_time > pfd->last_complete + pfd->t1) {
++ pfd->state = PREFIX_RENEW;
++ return;
++ }
++ if (pfd->state == PREFIX_RENEW &&
++ cur_time > pfd->last_complete + pfd->t2) {
++ pfd->state = PREFIX_REBIND;
++ pfd->uuid.len = 0;
++ return;
++ }
++ if (pfd->state == PREFIX_REBIND &&
++ cur_time > pfd->last_complete + pfd->vlife_time) {
++ pfd->state = PREFIX_SOLICIT;
++ return;
++ }
++}
++
+ static void
+ ipv6_prefixd_wait(long long int timeout)
+ {
+- if (ipv6_prefixd_should_inject()) {
++ /* We need to wake up in all states :
++ * - In SOLICIT and REQUEST states we need to wakeup to handle
++ * next_announce timer.
++ * - In DONE, PENDING, RENEW and REBIND states, we need to wake up to
++ * handle T1, T2 timers.
++ */
++ if (!shash_is_empty(&ipv6_prefixd)) {
+ poll_timer_wait_until(timeout);
+ }
+ }
+@@ -1266,6 +1277,7 @@ send_ipv6_prefixd(struct rconn *swconn, long long int *send_prefixd_time)
+ if (*send_prefixd_time > next_msg) {
+ *send_prefixd_time = next_msg;
+ }
++ ipv6_prefixd_update_state(pfd);
+ }
+ }
+
+@@ -1464,11 +1476,13 @@ destroy_buffered_packets_ctx(void)
+
+ /* Called with in the pinctrl_handler thread context. */
+ static void
+-pinctrl_handle_buffered_packets(struct dp_packet *pkt_in,
+- const struct match *md, bool is_arp)
++pinctrl_handle_buffered_packets(const struct ofputil_packet_in *pin,
++ const struct ofpbuf *continuation,
++ bool is_arp)
+ OVS_REQUIRES(pinctrl_mutex)
+ {
+ struct in6_addr ip;
++ const struct match *md = &pin->flow_metadata;
+ uint64_t dp_key = ntohll(md->flow.metadata);
+ uint64_t oport_key = md->flow.regs[MFF_LOG_OUTPORT - MFF_REG0];
+
+@@ -1486,20 +1500,7 @@ OVS_REQUIRES(pinctrl_mutex)
+ return;
+ }
+
+- struct ofpbuf ofpacts;
+- ofpbuf_init(&ofpacts, 4096);
+- reload_metadata(&ofpacts, md);
+- /* reload pkt_mark field */
+- const struct mf_field *pkt_mark_field = mf_from_id(MFF_PKT_MARK);
+- union mf_value pkt_mark_value;
+- mf_get_value(pkt_mark_field, &md->flow, &pkt_mark_value);
+- ofpact_put_set_field(&ofpacts, pkt_mark_field, &pkt_mark_value, NULL);
+-
+- struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&ofpacts);
+- resubmit->in_port = OFPP_CONTROLLER;
+- resubmit->table_id = OFTABLE_OUTPUT_INIT;
+-
+- struct packet_data *pd = ovn_packet_data_create(ofpacts, pkt_in);
++ struct packet_data *pd = ovn_packet_data_create(pin, continuation);
+ ovn_buffered_packets_packet_data_enqueue(bp, pd);
+
+ /* There is a chance that the MAC binding was already created. */
+@@ -1509,8 +1510,8 @@ OVS_REQUIRES(pinctrl_mutex)
+ /* Called with in the pinctrl_handler thread context. */
+ static void
+ pinctrl_handle_arp(struct rconn *swconn, const struct flow *ip_flow,
+- struct dp_packet *pkt_in,
+- const struct match *md, struct ofpbuf *userdata)
++ const struct ofputil_packet_in *pin,
++ struct ofpbuf *userdata, const struct ofpbuf *continuation)
+ {
+ /* This action only works for IP packets, and the switch should only send
+ * us IP packets this way, but check here just to be sure. */
+@@ -1522,7 +1523,7 @@ pinctrl_handle_arp(struct rconn *swconn, const struct flow *ip_flow,
+ }
+
+ ovs_mutex_lock(&pinctrl_mutex);
+- pinctrl_handle_buffered_packets(pkt_in, md, true);
++ pinctrl_handle_buffered_packets(pin, continuation, true);
+ ovs_mutex_unlock(&pinctrl_mutex);
+
+ /* Compose an ARP packet. */
+@@ -1547,7 +1548,8 @@ pinctrl_handle_arp(struct rconn *swconn, const struct flow *ip_flow,
+ ip_flow->vlans[0].tci);
+ }
+
+- set_actions_and_enqueue_msg(swconn, &packet, md, userdata);
++ set_actions_and_enqueue_msg(swconn, &packet,
++ &pin->flow_metadata, userdata);
+ dp_packet_uninit(&packet);
+ }
+
+@@ -3200,8 +3202,7 @@ process_packet_in(struct rconn *swconn, const struct ofp_header *msg)
+
+ switch (ntohl(ah->opcode)) {
+ case ACTION_OPCODE_ARP:
+- pinctrl_handle_arp(swconn, &headers, &packet, &pin.flow_metadata,
+- &userdata);
++ pinctrl_handle_arp(swconn, &headers, &pin, &userdata, &continuation);
+ break;
+ case ACTION_OPCODE_IGMP:
+ pinctrl_ip_mcast_handle(swconn, &headers, &packet, &pin.flow_metadata,
+@@ -3267,8 +3268,7 @@ process_packet_in(struct rconn *swconn, const struct ofp_header *msg)
+ break;
+
+ case ACTION_OPCODE_ND_NS:
+- pinctrl_handle_nd_ns(swconn, &headers, &packet, &pin.flow_metadata,
+- &userdata);
++ pinctrl_handle_nd_ns(swconn, &headers, &pin, &userdata, &continuation);
+ break;
+
+ case ACTION_OPCODE_ICMP:
+@@ -3519,13 +3519,14 @@ pinctrl_handler(void *arg_)
+
+ rconn_run_wait(swconn);
+ rconn_recv_wait(swconn);
+- send_garp_rarp_wait(send_garp_rarp_time);
+- ipv6_ra_wait(send_ipv6_ra_time);
+- ip_mcast_querier_wait(send_mcast_query_time);
+- svc_monitors_wait(svc_monitors_next_run_time);
+- ipv6_prefixd_wait(send_prefixd_time);
+- bfd_monitor_wait(bfd_time);
+-
++ if (rconn_is_connected(swconn)) {
++ send_garp_rarp_wait(send_garp_rarp_time);
++ ipv6_ra_wait(send_ipv6_ra_time);
++ ip_mcast_querier_wait(send_mcast_query_time);
++ svc_monitors_wait(svc_monitors_next_run_time);
++ ipv6_prefixd_wait(send_prefixd_time);
++ bfd_monitor_wait(bfd_time);
++ }
+ seq_wait(pinctrl_handler_seq, new_seq);
+
+ latch_wait(&pctrl->pinctrl_thread_exit);
+@@ -4269,16 +4270,8 @@ send_mac_binding_buffered_pkts(struct rconn *swconn)
+
+ struct packet_data *pd;
+ LIST_FOR_EACH_POP (pd, node, &buffered_packets_ctx.ready_packets_data) {
+- struct ofputil_packet_out po = {
+- .packet = dp_packet_data(pd->p),
+- .packet_len = dp_packet_size(pd->p),
+- .buffer_id = UINT32_MAX,
+- .ofpacts = pd->ofpacts.data,
+- .ofpacts_len = pd->ofpacts.size,
+- };
+- match_set_in_port(&po.flow_metadata, OFPP_CONTROLLER);
+- queue_msg(swconn, ofputil_encode_packet_out(&po, proto));
+-
++ queue_msg(swconn, ofputil_encode_resume(&pd->pin, pd->continuation,
++ proto));
+ ovn_packet_data_destroy(pd);
+ }
+
+@@ -4592,7 +4585,7 @@ send_garp_rarp_update(struct ovsdb_idl_txn *ovnsb_idl_txn,
+ garp_rarp->announce_time = time_msec() + 1000;
+ garp_rarp->backoff = 1000; /* msec. */
+ }
+- } else {
++ } else if (ovnsb_idl_txn) {
+ add_garp_rarp(name, laddrs->ea,
+ laddrs->ipv4_addrs[i].addr,
+ binding_rec->datapath->tunnel_key,
+@@ -6212,8 +6205,9 @@ pinctrl_handle_nd_na(struct rconn *swconn, const struct flow *ip_flow,
+ /* Called with in the pinctrl_handler thread context. */
+ static void
+ pinctrl_handle_nd_ns(struct rconn *swconn, const struct flow *ip_flow,
+- struct dp_packet *pkt_in,
+- const struct match *md, struct ofpbuf *userdata)
++ const struct ofputil_packet_in *pin,
++ struct ofpbuf *userdata,
++ const struct ofpbuf *continuation)
+ {
+ /* This action only works for IPv6 packets. */
+ if (get_dl_type(ip_flow) != htons(ETH_TYPE_IPV6)) {
+@@ -6223,7 +6217,7 @@ pinctrl_handle_nd_ns(struct rconn *swconn, const struct flow *ip_flow,
+ }
+
+ ovs_mutex_lock(&pinctrl_mutex);
+- pinctrl_handle_buffered_packets(pkt_in, md, false);
++ pinctrl_handle_buffered_packets(pin, continuation, false);
+ ovs_mutex_unlock(&pinctrl_mutex);
+
+ uint64_t packet_stub[128 / 8];
+@@ -6236,7 +6230,8 @@ pinctrl_handle_nd_ns(struct rconn *swconn, const struct flow *ip_flow,
+ &ip_flow->ipv6_dst);
+
+ /* Reload previous packet metadata and set actions from userdata. */
+- set_actions_and_enqueue_msg(swconn, &packet, md, userdata);
++ set_actions_and_enqueue_msg(swconn, &packet,
++ &pin->flow_metadata, userdata);
+ dp_packet_uninit(&packet);
+ }
+
+@@ -6529,11 +6524,46 @@ struct put_vport_binding {
+ uint32_t vport_key;
+
+ uint32_t vport_parent_key;
++
++ /* This vport record Only relevant if "new_record" is true. */
++ bool new_record;
+ };
+
+ /* Contains "struct put_vport_binding"s. */
+ static struct hmap put_vport_bindings;
+
++/*
++ * Validate if the vport_binding record that was added
++ * by the pinctrl thread is still relevant and needs
++ * to be updated in the SBDB or not.
++ *
++ * vport_binding record is only relevant and needs to be updated in SB if:
++ * 2. The put_vport_binding:new_record is true:
++ * The new_record will be set to "true" when this vport record is created
++ * by function "pinctrl_handle_bind_vport".
++ *
++ * After the first attempt to bind this vport to the chassis and
++ * virtual_parent by function "run_put_vport_bindings" we will set the
++ * value of vpb:new_record to "false" and keep it in "put_vport_bindings"
++ *
++ * After the second attempt of binding the vpb it will be removed by
++ * this function.
++ *
++ * The above guarantees that we will try to bind the vport twice in
++ * a certain amount of time.
++ *
++*/
++static bool
++is_vport_binding_relevant(struct put_vport_binding *vpb)
++{
++
++ if (vpb->new_record) {
++ vpb->new_record = false;
++ return true;
++ }
++ return false;
++}
++
+ static void
+ init_put_vport_bindings(void)
+ {
+@@ -6541,18 +6571,21 @@ init_put_vport_bindings(void)
+ }
+
+ static void
+-flush_put_vport_bindings(void)
++flush_put_vport_bindings(bool force_flush)
+ {
+ struct put_vport_binding *vport_b;
+- HMAP_FOR_EACH_POP (vport_b, hmap_node, &put_vport_bindings) {
+- free(vport_b);
++ HMAP_FOR_EACH_SAFE (vport_b, hmap_node, &put_vport_bindings) {
++ if (!is_vport_binding_relevant(vport_b) || force_flush) {
++ hmap_remove(&put_vport_bindings, &vport_b->hmap_node);
++ free(vport_b);
++ }
+ }
+ }
+
+ static void
+ destroy_put_vport_bindings(void)
+ {
+- flush_put_vport_bindings();
++ flush_put_vport_bindings(true);
+ hmap_destroy(&put_vport_bindings);
+ }
+
+@@ -6630,7 +6663,7 @@ run_put_vport_bindings(struct ovsdb_idl_txn *ovnsb_idl_txn,
+ sbrec_port_binding_by_key, chassis, vpb);
+ }
+
+- flush_put_vport_bindings();
++ flush_put_vport_bindings(false);
+ }
+
+ /* Called with in the pinctrl_handler thread context. */
+@@ -6668,7 +6701,7 @@ pinctrl_handle_bind_vport(
+ vpb->dp_key = dp_key;
+ vpb->vport_key = vport_key;
+ vpb->vport_parent_key = vport_parent_key;
+-
++ vpb->new_record = true;
+ notify_pinctrl_main();
+ }
+
diff --git a/debian/changelog b/debian/changelog
index 2f20941be..acf278a15 100644
--- a/debian/changelog
@@ -55,3 +859,5264 @@ index 2f20941be..acf278a15 100644
OVN (24.03.1-1) unstable; urgency=low
[ OVN team ]
* New upstream version
+diff --git a/include/ovn/actions.h b/include/ovn/actions.h
+index 49fb96fc6..f0d39f147 100644
+--- a/include/ovn/actions.h
++++ b/include/ovn/actions.h
+@@ -67,6 +67,7 @@ struct collector_set_ids;
+ OVNACT(CT_NEXT, ovnact_ct_next) \
+ OVNACT(CT_COMMIT_V1, ovnact_ct_commit_v1) \
+ OVNACT(CT_COMMIT_V2, ovnact_nest) \
++ OVNACT(CT_COMMIT_TO_ZONE, ovnact_ct_commit_to_zone) \
+ OVNACT(CT_DNAT, ovnact_ct_nat) \
+ OVNACT(CT_SNAT, ovnact_ct_nat) \
+ OVNACT(CT_DNAT_IN_CZONE, ovnact_ct_nat) \
+@@ -75,7 +76,7 @@ struct collector_set_ids;
+ OVNACT(CT_LB_MARK, ovnact_ct_lb) \
+ OVNACT(SELECT, ovnact_select) \
+ OVNACT(CT_CLEAR, ovnact_null) \
+- OVNACT(CT_COMMIT_NAT, ovnact_ct_commit_nat) \
++ OVNACT(CT_COMMIT_NAT, ovnact_ct_commit_to_zone) \
+ OVNACT(CLONE, ovnact_nest) \
+ OVNACT(ARP, ovnact_nest) \
+ OVNACT(ICMP4, ovnact_nest) \
+@@ -296,11 +297,12 @@ struct ovnact_ct_nat {
+ uint8_t ltable; /* Logical table ID of next table. */
+ };
+
+-/* OVNACT_CT_COMMIT_NAT. */
+-struct ovnact_ct_commit_nat {
++/* OVNACT_CT_COMMIT_TO_ZONE, OVNACT_CT_COMMIT_NAT. */
++struct ovnact_ct_commit_to_zone {
+ struct ovnact ovnact;
+
+ bool dnat_zone;
++ bool do_nat;
+ uint8_t ltable;
+ };
+
+@@ -847,6 +849,9 @@ struct ovnact_encode_params {
+ /* The datapath key. */
+ uint32_t dp_key;
+
++ /* Indication if we should add explicit output after arp/nd_ns action. */
++ bool explicit_arp_ns_output;
++
+ /* OVN maps each logical flow table (ltable), one-to-one, onto a physical
+ * OpenFlow flow table (ptable). A number of parameters describe this
+ * mapping and data related to flow tables:
+diff --git a/include/ovn/features.h b/include/ovn/features.h
+index 08f1d8288..35a5d8ba0 100644
+--- a/include/ovn/features.h
++++ b/include/ovn/features.h
+@@ -28,6 +28,7 @@
+ #define OVN_FEATURE_FDB_TIMESTAMP "fdb-timestamp"
+ #define OVN_FEATURE_LS_DPG_COLUMN "ls-dpg-column"
+ #define OVN_FEATURE_CT_COMMIT_NAT_V2 "ct-commit-nat-v2"
++#define OVN_FEATURE_CT_COMMIT_TO_ZONE "ct-commit-to-zone"
+
+ /* OVS datapath supported features. Based on availability OVN might generate
+ * different types of openflows.
+diff --git a/include/ovn/logical-fields.h b/include/ovn/logical-fields.h
+index ce79b501c..8854dae7a 100644
+--- a/include/ovn/logical-fields.h
++++ b/include/ovn/logical-fields.h
+@@ -82,6 +82,7 @@ enum mff_log_flags_bits {
+ MLF_LOCALNET_BIT = 15,
+ MLF_RX_FROM_TUNNEL_BIT = 16,
+ MLF_ICMP_SNAT_BIT = 17,
++ MLF_IGMP_IGMP_SNOOP_INJECT_BIT = 18,
+ };
+
+ /* MFF_LOG_FLAGS_REG flag assignments */
+@@ -137,6 +138,8 @@ enum mff_log_flags {
+ MLF_RX_FROM_TUNNEL = (1 << MLF_RX_FROM_TUNNEL_BIT),
+
+ MLF_ICMP_SNAT = (1 << MLF_ICMP_SNAT_BIT),
++
++ MLF_IGMP_IGMP_SNOOP = (1 << MLF_IGMP_IGMP_SNOOP_INJECT_BIT),
+ };
+
+ /* OVN logical fields
+diff --git a/lib/acl-log.c b/lib/acl-log.c
+index 9530dd763..b3eb4bbd0 100644
+--- a/lib/acl-log.c
++++ b/lib/acl-log.c
+@@ -34,7 +34,9 @@ log_verdict_to_string(uint8_t verdict)
+ return "drop";
+ } else if (verdict == LOG_VERDICT_REJECT) {
+ return "reject";
+- } else {
++ } else if (verdict == LOG_VERDICT_PASS) {
++ return "pass";
++ } else {
+ return "";
+ }
+ }
+diff --git a/lib/acl-log.h b/lib/acl-log.h
+index da7fa2f02..3973a8e0b 100644
+--- a/lib/acl-log.h
++++ b/lib/acl-log.h
+@@ -33,6 +33,7 @@ enum log_verdict {
+ LOG_VERDICT_ALLOW,
+ LOG_VERDICT_DROP,
+ LOG_VERDICT_REJECT,
++ LOG_VERDICT_PASS,
+ LOG_VERDICT_UNKNOWN = UINT8_MAX
+ };
+
+diff --git a/lib/actions.c b/lib/actions.c
+index a45874dfb..2d9629209 100644
+--- a/lib/actions.c
++++ b/lib/actions.c
+@@ -985,6 +985,45 @@ parse_ct_nat(struct action_context *ctx, const char *name,
+ }
+ }
+
++static void
++parse_ct_commit_to_zone(struct action_context *ctx, const char *name,
++ bool do_nat, bool require_param,
++ struct ovnact_ct_commit_to_zone *cn)
++{
++ add_prerequisite(ctx, "ip");
++
++ if (ctx->pp->cur_ltable >= ctx->pp->n_tables) {
++ lexer_error(ctx->lexer,
++ "\"%s\" action not allowed in last table.", name);
++ return;
++ }
++
++ cn->ltable = ctx->pp->cur_ltable + 1;
++ cn->do_nat = do_nat;
++ cn->dnat_zone = true;
++
++ if (require_param) {
++ lexer_force_match(ctx->lexer, LEX_T_LPAREN);
++ } else {
++ if (!lexer_match(ctx->lexer, LEX_T_LPAREN)) {
++ return;
++ }
++ }
++
++ if (lexer_match_id(ctx->lexer, "dnat")) {
++ cn->dnat_zone = true;
++ } else if (lexer_match_id(ctx->lexer, "snat")) {
++ cn->dnat_zone = false;
++ } else {
++ lexer_error(ctx->lexer, "\"%s\" action accepts"
++ " only \"dnat\" or \"snat\" parameter.", name);
++ return;
++ }
++
++ lexer_force_match(ctx->lexer, LEX_T_RPAREN);
++}
++
++
+ static void
+ parse_CT_DNAT(struct action_context *ctx)
+ {
+@@ -1016,33 +1055,17 @@ parse_CT_SNAT_IN_CZONE(struct action_context *ctx)
+ static void
+ parse_CT_COMMIT_NAT(struct action_context *ctx)
+ {
+- add_prerequisite(ctx, "ip");
+-
+- if (ctx->pp->cur_ltable >= ctx->pp->n_tables) {
+- lexer_error(ctx->lexer,
+- "\"ct_commit_nat\" action not allowed in last table.");
+- return;
+- }
+-
+- struct ovnact_ct_commit_nat *cn = ovnact_put_CT_COMMIT_NAT(ctx->ovnacts);
+- cn->ltable = ctx->pp->cur_ltable + 1;
+- cn->dnat_zone = true;
+-
+- if (!lexer_match(ctx->lexer, LEX_T_LPAREN)) {
+- return;
+- }
+-
+- if (lexer_match_id(ctx->lexer, "dnat")) {
+- cn->dnat_zone = true;
+- } else if (lexer_match_id(ctx->lexer, "snat")) {
+- cn->dnat_zone = false;
+- } else {
+- lexer_error(ctx->lexer, "\"ct_commit_nat\" action accepts"
+- " only \"dnat\" or \"snat\" parameter.");
+- return;
+- }
++ parse_ct_commit_to_zone(ctx, "ct_commit_nat",
++ true, false,
++ ovnact_put_CT_COMMIT_NAT(ctx->ovnacts));
++}
+
+- lexer_force_match(ctx->lexer, LEX_T_RPAREN);
++static void
++parse_CT_COMMIT_TO_ZONE(struct action_context *ctx)
++{
++ parse_ct_commit_to_zone(ctx, "ct_commit_to_zone",
++ false, true,
++ ovnact_put_CT_COMMIT_TO_ZONE(ctx->ovnacts));
+ }
+
+ static void
+@@ -1095,12 +1118,20 @@ format_CT_SNAT_IN_CZONE(const struct ovnact_ct_nat *cn, struct ds *s)
+ }
+
+ static void
+-format_CT_COMMIT_NAT(const struct ovnact_ct_commit_nat *cn, struct ds *s)
++format_CT_COMMIT_NAT(const struct ovnact_ct_commit_to_zone *cn, struct ds *s)
+ {
+ ds_put_cstr(s, "ct_commit_nat");
+ ds_put_cstr(s, cn->dnat_zone ? "(dnat);" : "(snat);");
+ }
+
++static void
++format_CT_COMMIT_TO_ZONE(const struct ovnact_ct_commit_to_zone *cn,
++ struct ds *s)
++{
++ ds_put_cstr(s, "ct_commit_to_zone");
++ ds_put_cstr(s, cn->dnat_zone ? "(dnat);" : "(snat);");
++}
++
+ static void
+ encode_ct_nat(const struct ovnact_ct_nat *cn,
+ const struct ovnact_encode_params *ep,
+@@ -1170,6 +1201,39 @@ encode_ct_nat(const struct ovnact_ct_nat *cn,
+ ofpbuf_push_uninit(ofpacts, ct_offset);
+ }
+
++static void
++encode_ct_commit_to_zone(const struct ovnact_ct_commit_to_zone *cn,
++ const struct ovnact_encode_params *ep,
++ struct ofpbuf *ofpacts)
++{
++ const size_t ct_offset = ofpacts->size;
++
++ struct ofpact_conntrack *ct = ofpact_put_CT(ofpacts);
++ ct->recirc_table = cn->ltable + first_ptable(ep, ep->pipeline);
++ ct->zone_src.ofs = 0;
++ ct->zone_src.n_bits = 16;
++ ct->flags = NX_CT_F_COMMIT;
++ ct->alg = 0;
++
++ if (ep->is_switch) {
++ ct->zone_src.field = mf_from_id(MFF_LOG_CT_ZONE);
++ } else {
++ ct->zone_src.field = mf_from_id(cn->dnat_zone
++ ? MFF_LOG_DNAT_ZONE
++ : MFF_LOG_SNAT_ZONE);
++ }
++
++ if (cn->do_nat) {
++ struct ofpact_nat *nat = ofpact_put_NAT(ofpacts);
++ nat->range_af = AF_UNSPEC;
++ nat->flags = 0;
++ }
++
++ ct = ofpbuf_at_assert(ofpacts, ct_offset, sizeof *ct);
++ ofpacts->header = ct;
++ ofpact_finish_CT(ofpacts, &ct);
++}
++
+ static void
+ encode_CT_DNAT(const struct ovnact_ct_nat *cn,
+ const struct ovnact_encode_params *ep,
+@@ -1203,34 +1267,19 @@ encode_CT_SNAT_IN_CZONE(const struct ovnact_ct_nat *cn,
+ }
+
+ static void
+-encode_CT_COMMIT_NAT(const struct ovnact_ct_commit_nat *cn,
++encode_CT_COMMIT_NAT(const struct ovnact_ct_commit_to_zone *cn,
+ const struct ovnact_encode_params *ep,
+ struct ofpbuf *ofpacts)
+ {
+- const size_t ct_offset = ofpacts->size;
+-
+- struct ofpact_conntrack *ct = ofpact_put_CT(ofpacts);
+- ct->recirc_table = cn->ltable + first_ptable(ep, ep->pipeline);
+- ct->zone_src.ofs = 0;
+- ct->zone_src.n_bits = 16;
+- ct->flags = NX_CT_F_COMMIT;
+- ct->alg = 0;
+-
+- if (ep->is_switch) {
+- ct->zone_src.field = mf_from_id(MFF_LOG_CT_ZONE);
+- } else {
+- ct->zone_src.field = mf_from_id(cn->dnat_zone
+- ? MFF_LOG_DNAT_ZONE
+- : MFF_LOG_SNAT_ZONE);
+- }
+-
+- struct ofpact_nat *nat = ofpact_put_NAT(ofpacts);
+- nat->range_af = AF_UNSPEC;
+- nat->flags = 0;
++ encode_ct_commit_to_zone(cn, ep, ofpacts);
++}
+
+- ct = ofpbuf_at_assert(ofpacts, ct_offset, sizeof *ct);
+- ofpacts->header = ct;
+- ofpact_finish_CT(ofpacts, &ct);
++static void
++encode_CT_COMMIT_TO_ZONE(const struct ovnact_ct_commit_to_zone *cn,
++ const struct ovnact_encode_params *ep,
++ struct ofpbuf *ofpacts)
++{
++ encode_ct_commit_to_zone(cn, ep, ofpacts);
+ }
+
+ static void
+@@ -1239,7 +1288,7 @@ ovnact_ct_nat_free(struct ovnact_ct_nat *ct_nat OVS_UNUSED)
+ }
+
+ static void
+-ovnact_ct_commit_nat_free(struct ovnact_ct_commit_nat *cn OVS_UNUSED)
++ovnact_ct_commit_to_zone_free(struct ovnact_ct_commit_to_zone *cn OVS_UNUSED)
+ {
+ }
+
+@@ -1951,6 +2000,44 @@ format_REJECT(const struct ovnact_nest *nest, struct ds *s)
+ format_nested_action(nest, "reject", s);
+ }
+
++static bool
++is_paused_nested_action(enum action_opcode opcode)
++{
++ switch (opcode) {
++ case ACTION_OPCODE_ARP:
++ case ACTION_OPCODE_ND_NS:
++ return true;
++ case ACTION_OPCODE_IGMP:
++ case ACTION_OPCODE_PUT_ARP:
++ case ACTION_OPCODE_PUT_DHCP_OPTS:
++ case ACTION_OPCODE_ND_NA:
++ case ACTION_OPCODE_ND_NA_ROUTER:
++ case ACTION_OPCODE_PUT_ND:
++ case ACTION_OPCODE_PUT_FDB:
++ case ACTION_OPCODE_PUT_DHCPV6_OPTS:
++ case ACTION_OPCODE_DNS_LOOKUP:
++ case ACTION_OPCODE_LOG:
++ case ACTION_OPCODE_PUT_ND_RA_OPTS:
++ case ACTION_OPCODE_ICMP:
++ case ACTION_OPCODE_ICMP4_ERROR:
++ case ACTION_OPCODE_ICMP6_ERROR:
++ case ACTION_OPCODE_TCP_RESET:
++ case ACTION_OPCODE_SCTP_ABORT:
++ case ACTION_OPCODE_REJECT:
++ case ACTION_OPCODE_PUT_ICMP4_FRAG_MTU:
++ case ACTION_OPCODE_PUT_ICMP6_FRAG_MTU:
++ case ACTION_OPCODE_EVENT:
++ case ACTION_OPCODE_BIND_VPORT:
++ case ACTION_OPCODE_DHCP6_SERVER:
++ case ACTION_OPCODE_HANDLE_SVC_CHECK:
++ case ACTION_OPCODE_BFD_MSG:
++ case ACTION_OPCODE_ACTIVATION_STRATEGY_RARP:
++ case ACTION_OPCODE_MG_SPLIT_BUF:
++ default:
++ return false;
++ }
++}
++
+ static void
+ encode_nested_actions(const struct ovnact_nest *on,
+ const struct ovnact_encode_params *ep,
+@@ -1966,7 +2053,8 @@ encode_nested_actions(const struct ovnact_nest *on,
+ * converted to OpenFlow, as its userdata. ovn-controller will convert the
+ * packet to ARP or NA and then send the packet and actions back to the
+ * switch inside an OFPT_PACKET_OUT message. */
+- size_t oc_offset = encode_start_controller_op(opcode, false,
++ bool pause = is_paused_nested_action(opcode);
++ size_t oc_offset = encode_start_controller_op(opcode, pause,
+ ep->ctrl_meter_id, ofpacts);
+ ofpacts_put_openflow_actions(inner_ofpacts.data, inner_ofpacts.size,
+ ofpacts, OFP15_VERSION);
+@@ -1982,6 +2070,9 @@ encode_ARP(const struct ovnact_nest *on,
+ struct ofpbuf *ofpacts)
+ {
+ encode_nested_actions(on, ep, ACTION_OPCODE_ARP, ofpacts);
++ if (!ep->explicit_arp_ns_output) {
++ emit_resubmit(ofpacts, ep->output_ptable);
++ }
+ }
+
+ static void
+@@ -2070,6 +2161,9 @@ encode_ND_NS(const struct ovnact_nest *on,
+ struct ofpbuf *ofpacts)
+ {
+ encode_nested_actions(on, ep, ACTION_OPCODE_ND_NS, ofpacts);
++ if (!ep->explicit_arp_ns_output) {
++ emit_resubmit(ofpacts, ep->output_ptable);
++ }
+ }
+
+ static void
+@@ -3570,6 +3664,8 @@ parse_log_arg(struct action_context *ctx, struct ovnact_log *log)
+ log->verdict = LOG_VERDICT_REJECT;
+ } else if (lexer_match_id(ctx->lexer, "allow")) {
+ log->verdict = LOG_VERDICT_ALLOW;
++ } else if (lexer_match_id(ctx->lexer, "pass")) {
++ log->verdict = LOG_VERDICT_PASS;
+ } else {
+ lexer_syntax_error(ctx->lexer, "unknown verdict");
+ return;
+@@ -5423,6 +5519,8 @@ parse_action(struct action_context *ctx)
+ parse_CT_NEXT(ctx);
+ } else if (lexer_match_id(ctx->lexer, "ct_commit")) {
+ parse_CT_COMMIT(ctx);
++ } else if (lexer_match_id(ctx->lexer, "ct_commit_to_zone")) {
++ parse_CT_COMMIT_TO_ZONE(ctx);
+ } else if (lexer_match_id(ctx->lexer, "ct_dnat")) {
+ parse_CT_DNAT(ctx);
+ } else if (lexer_match_id(ctx->lexer, "ct_snat")) {
+diff --git a/lib/logical-fields.c b/lib/logical-fields.c
+index 20219a67a..68892dba5 100644
+--- a/lib/logical-fields.c
++++ b/lib/logical-fields.c
+@@ -139,6 +139,10 @@ ovn_init_symtab(struct shash *symtab)
+ flags_str);
+ snprintf(flags_str, sizeof flags_str, "flags[%d]", MLF_RX_FROM_TUNNEL_BIT);
+ expr_symtab_add_subfield(symtab, "flags.tunnel_rx", NULL, flags_str);
++ snprintf(flags_str, sizeof flags_str, "flags[%d]",
++ MLF_IGMP_IGMP_SNOOP_INJECT_BIT);
++ expr_symtab_add_subfield(symtab, "flags.igmp_loopback", NULL,
++ flags_str);
+
+ /* Connection tracking state. */
+ expr_symtab_add_field_scoped(symtab, "ct_mark", MFF_CT_MARK, NULL, false,
+diff --git a/lib/ovn-l7.c b/lib/ovn-l7.c
+index 3a5f3f3ec..2ddb68cb0 100644
+--- a/lib/ovn-l7.c
++++ b/lib/ovn-l7.c
+@@ -18,6 +18,8 @@
+
+ #include "ovn-l7.h"
+
++const struct in6_addr in6addr_all_dhcp_agents = IN6ADDR_ALL_DHCP_AGENTS_INIT;
++
+ bool
+ ipv6_addr_is_routable_multicast(const struct in6_addr *ip)
+ {
+diff --git a/lib/ovn-l7.h b/lib/ovn-l7.h
+index ad514a922..f4a30cc00 100644
+--- a/lib/ovn-l7.h
++++ b/lib/ovn-l7.h
+@@ -305,6 +305,12 @@ BUILD_ASSERT_DECL(DHCP_OPT_HEADER_LEN == sizeof(struct dhcp_opt_header));
+ #define DHCPV6_FQDN_FLAGS_O 1 << 1
+ #define DHCPV6_FQDN_FLAGS_N 1 << 2
+
++extern const struct in6_addr in6addr_all_dhcp_agents;
++#define IN6ADDR_ALL_DHCP_AGENTS_INIT { { { 0xff,0x02,0x00,0x00,0x00,0x00, \
++ 0x00,0x00,0x00,0x00,0x00,0x00, \
++ 0x00,0x01,0x00,0x02 } } }
++
++
+ #define DHCP6_OPT_HEADER_LEN 4
+ OVS_PACKED(
+ struct dhcpv6_opt_header {
+diff --git a/lib/ovn-util.c b/lib/ovn-util.c
+index ee5cbcdc3..9f97ae2ca 100644
+--- a/lib/ovn-util.c
++++ b/lib/ovn-util.c
+@@ -693,13 +693,17 @@ uint32_t
+ ovn_allocate_tnlid(struct hmap *set, const char *name, uint32_t min,
+ uint32_t max, uint32_t *hint)
+ {
+- for (uint32_t tnlid = next_tnlid(*hint, min, max); tnlid != *hint;
+- tnlid = next_tnlid(tnlid, min, max)) {
++ /* Normalize hint, because it can be outside of [min, max]. */
++ *hint = next_tnlid(*hint, min, max);
++
++ uint32_t tnlid = *hint;
++ do {
+ if (ovn_add_tnlid(set, tnlid)) {
+ *hint = tnlid;
+ return tnlid;
+ }
+- }
++ tnlid = next_tnlid(tnlid, min, max);
++ } while (tnlid != *hint);
+
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+ VLOG_WARN_RL(&rl, "all %s tunnel ids exhausted", name);
+diff --git a/northd/en-global-config.c b/northd/en-global-config.c
+index 34e393b33..28c78a12c 100644
+--- a/northd/en-global-config.c
++++ b/northd/en-global-config.c
+@@ -370,6 +370,7 @@ northd_enable_all_features(struct ed_type_global_config *data)
+ .fdb_timestamp = true,
+ .ls_dpg_column = true,
+ .ct_commit_nat_v2 = true,
++ .ct_commit_to_zone = true,
+ };
+ }
+
+@@ -439,6 +440,15 @@ build_chassis_features(const struct sbrec_chassis_table *sbrec_chassis_table,
+ chassis_features->ct_commit_nat_v2) {
+ chassis_features->ct_commit_nat_v2 = false;
+ }
++
++ bool ct_commit_to_zone =
++ smap_get_bool(&chassis->other_config,
++ OVN_FEATURE_CT_COMMIT_TO_ZONE,
++ false);
++ if (!ct_commit_to_zone &&
++ chassis_features->ct_commit_to_zone) {
++ chassis_features->ct_commit_to_zone = false;
++ }
+ }
+ }
+
+@@ -553,6 +563,10 @@ update_sb_config_options_to_sbrec(struct ed_type_global_config *config_data,
+ smap_replace(options, "sbctl_probe_interval", sip);
+ }
+
++ /* Adds indication that northd is handling explicit output after
++ * arp/nd_ns action. */
++ smap_add(options, "arp_ns_explicit_output", "true");
++
+ if (!smap_equal(&sb->options, options)) {
+ sbrec_sb_global_set_options(sb, options);
+ }
+diff --git a/northd/en-global-config.h b/northd/en-global-config.h
+index 38d732808..842bcee70 100644
+--- a/northd/en-global-config.h
++++ b/northd/en-global-config.h
+@@ -20,6 +20,7 @@ struct chassis_features {
+ bool fdb_timestamp;
+ bool ls_dpg_column;
+ bool ct_commit_nat_v2;
++ bool ct_commit_to_zone;
+ };
+
+ struct global_config_tracked_data {
+diff --git a/northd/northd.c b/northd/northd.c
+index 0f95578a3..8f20c4be3 100644
+--- a/northd/northd.c
++++ b/northd/northd.c
+@@ -4668,6 +4668,11 @@ fail:
+ static bool
+ lr_changes_can_be_handled(const struct nbrec_logical_router *lr)
+ {
++ /* We can't do I-P processing when the router is disabled. */
++ if (!lrouter_is_enabled(lr)) {
++ return false;
++ }
++
+ /* Check if the columns are changed in this row. */
+ enum nbrec_logical_router_column_id col;
+ for (col = 0; col < NBREC_LOGICAL_ROUTER_N_COLUMNS; col++) {
+@@ -5357,11 +5362,13 @@ ovn_igmp_group_get_ports(const struct sbrec_igmp_group *sb_igmp_group,
+ continue;
+ }
+
+- /* If this is already a port of a router on which relay is enabled,
+- * skip it for the group. Traffic is flooded there anyway.
++ /* If this is already a port of a router on which relay is enabled
++ * and it's not a transit switch to router port, skip it for the
++ * group. Traffic is flooded there anyway.
+ */
+ if (port->peer && port->peer->od &&
+- port->peer->od->mcast_info.rtr.relay) {
++ port->peer->od->mcast_info.rtr.relay &&
++ !ovn_datapath_is_transit_switch(port->od)) {
+ continue;
+ }
+
+@@ -6069,7 +6076,8 @@ build_interconn_mcast_snoop_flows(struct ovn_datapath *od,
+ continue;
+ }
+ /* Punt IGMP traffic to controller. */
+- char *match = xasprintf("inport == %s && igmp", op->json_key);
++ char *match = xasprintf("inport == %s && igmp && "
++ "flags.igmp_loopback == 0", op->json_key);
+ ovn_lflow_metered(lflows, od, S_SWITCH_OUT_PRE_LB, 120, match,
+ "clone { igmp; }; next;",
+ copp_meter_get(COPP_IGMP, od->nbs->copp,
+@@ -6078,7 +6086,8 @@ build_interconn_mcast_snoop_flows(struct ovn_datapath *od,
+ free(match);
+
+ /* Punt MLD traffic to controller. */
+- match = xasprintf("inport == %s && (mldv1 || mldv2)", op->json_key);
++ match = xasprintf("inport == %s && (mldv1 || mldv2) && "
++ "flags.igmp_loopback == 0", op->json_key);
+ ovn_lflow_metered(lflows, od, S_SWITCH_OUT_PRE_LB, 120, match,
+ "clone { igmp; }; next;",
+ copp_meter_get(COPP_IGMP, od->nbs->copp,
+@@ -9263,14 +9272,15 @@ build_lswitch_destination_lookup_bmcast(struct ovn_datapath *od,
+ ds_put_cstr(actions, "igmp;");
+ /* Punt IGMP traffic to controller. */
+ ovn_lflow_metered(lflows, od, S_SWITCH_IN_L2_LKUP, 100,
+- "igmp", ds_cstr(actions),
++ "flags.igmp_loopback == 0 && igmp", ds_cstr(actions),
+ copp_meter_get(COPP_IGMP, od->nbs->copp,
+ meter_groups),
+ lflow_ref);
+
+ /* Punt MLD traffic to controller. */
+ ovn_lflow_metered(lflows, od, S_SWITCH_IN_L2_LKUP, 100,
+- "mldv1 || mldv2", ds_cstr(actions),
++ "flags.igmp_loopback == 0 && (mldv1 || mldv2)",
++ ds_cstr(actions),
+ copp_meter_get(COPP_IGMP, od->nbs->copp,
+ meter_groups),
+ lflow_ref);
+@@ -9338,8 +9348,16 @@ build_lswitch_destination_lookup_bmcast(struct ovn_datapath *od,
+ }
+
+
+-/* Ingress table 25: Add IP multicast flows learnt from IGMP/MLD
+- * (priority 90). */
++/* Ingress table 27: Add IP multicast flows learnt from IGMP/MLD
++ * (priority 90).
++ *
++ * OR, for transit switches:
++ *
++ * Add IP multicast flows learnt from IGMP/MLD to forward traffic
++ * explicitly to the ports that are part of the IGMP/MLD group,
++ * and ignore MROUTER Ports.
++ * (priority 90).
++ */
+ static void
+ build_lswitch_ip_mcast_igmp_mld(struct ovn_igmp_group *igmp_group,
+ struct lflow_table *lflows,
+@@ -9353,6 +9371,9 @@ build_lswitch_ip_mcast_igmp_mld(struct ovn_igmp_group *igmp_group,
+ ds_clear(match);
+ ds_clear(actions);
+
++ bool transit_switch =
++ ovn_datapath_is_transit_switch(igmp_group->datapath);
++
+ struct mcast_switch_info *mcast_sw_info =
+ &igmp_group->datapath->mcast_info.sw;
+ uint64_t table_size = mcast_sw_info->table_size;
+@@ -9398,7 +9419,7 @@ build_lswitch_ip_mcast_igmp_mld(struct ovn_igmp_group *igmp_group,
+ }
+
+ /* Also flood traffic to all multicast routers with relay enabled. */
+- if (mcast_sw_info->flood_relay) {
++ if (mcast_sw_info->flood_relay && !transit_switch) {
+ ds_put_cstr(actions,
+ "clone { "
+ "outport = \""MC_MROUTER_FLOOD "\"; "
+@@ -10059,19 +10080,30 @@ build_ecmp_routing_policy_flows(struct lflow_table *lflows,
+ lflow_ref);
+ }
+
++ if (!n_valid_nexthops) {
++ goto cleanup;
++ }
++
+ ds_clear(&actions);
+- ds_put_format(&actions, "%s = %"PRIu16
+- "; %s = select(", REG_ECMP_GROUP_ID, ecmp_group_id,
+- REG_ECMP_MEMBER_ID);
++ if (n_valid_nexthops > 1) {
++ ds_put_format(&actions, "%s = %"PRIu16
++ "; %s = select(", REG_ECMP_GROUP_ID, ecmp_group_id,
++ REG_ECMP_MEMBER_ID);
++
++ for (size_t i = 0; i < n_valid_nexthops; i++) {
++ if (i > 0) {
++ ds_put_cstr(&actions, ", ");
++ }
+
+- for (size_t i = 0; i < n_valid_nexthops; i++) {
+- if (i > 0) {
+- ds_put_cstr(&actions, ", ");
++ ds_put_format(&actions, "%"PRIuSIZE, valid_nexthops[i]);
+ }
+-
+- ds_put_format(&actions, "%"PRIuSIZE, valid_nexthops[i]);
++ ds_put_cstr(&actions, ");");
++ } else {
++ ds_put_format(&actions, "%s = %"PRIu16
++ "; %s = %"PRIuSIZE"; next;", REG_ECMP_GROUP_ID,
++ ecmp_group_id, REG_ECMP_MEMBER_ID,
++ valid_nexthops[0]);
+ }
+- ds_put_cstr(&actions, ");");
+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_POLICY,
+ rule->priority, rule->match,
+ ds_cstr(&actions), &rule->header_,
+@@ -11349,10 +11381,11 @@ copy_ra_to_sb(struct ovn_port *op, const char *address_mode)
+ ds_put_format(&s, "%s/%u ", addrs->network_s, addrs->plen);
+ }
+
+- const char *ra_pd_list = smap_get(&op->sb->options, "ipv6_ra_pd_list");
+- if (ra_pd_list) {
+- ds_put_cstr(&s, ra_pd_list);
++ for (size_t i = 0; i < op->nbrp->n_ipv6_prefix; i++) {
++ ds_put_cstr(&s, op->nbrp->ipv6_prefix[i]);
++ ds_put_char(&s, ' ');
+ }
++
+ /* Remove trailing space */
+ ds_chomp(&s, ' ');
+ smap_add(&options, "ipv6_ra_prefixes", ds_cstr(&s));
+@@ -13381,7 +13414,7 @@ build_arp_request_flows_for_lrouter(
+ "ip6.dst = %s; "
+ "nd.target = %s; "
+ "output; "
+- "};", ETH_ADDR_ARGS(eth_dst), sn_addr_s,
++ "}; output;", ETH_ADDR_ARGS(eth_dst), sn_addr_s,
+ route->nexthop);
+
+ ovn_lflow_add_with_hint__(lflows, od, S_ROUTER_IN_ARP_REQUEST, 200,
+@@ -13401,7 +13434,7 @@ build_arp_request_flows_for_lrouter(
+ "arp.tpa = " REG_NEXT_HOP_IPV4 "; "
+ "arp.op = 1; " /* ARP request */
+ "output; "
+- "};",
++ "}; output;",
+ copp_meter_get(COPP_ARP_RESOLVE, od->nbr->copp,
+ meter_groups),
+ lflow_ref);
+@@ -13410,7 +13443,7 @@ build_arp_request_flows_for_lrouter(
+ "nd_ns { "
+ "nd.target = " REG_NEXT_HOP_IPV6 "; "
+ "output; "
+- "};",
++ "}; output;",
+ copp_meter_get(COPP_ND_NS_RESOLVE, od->nbr->copp,
+ meter_groups),
+ lflow_ref);
+@@ -14385,19 +14418,26 @@ build_lrouter_out_is_dnat_local(struct lflow_table *lflows,
+ static void
+ build_lrouter_out_snat_match(struct lflow_table *lflows,
+ const struct ovn_datapath *od,
+- const struct nbrec_nat *nat, struct ds *match,
+- bool distributed_nat, int cidr_bits, bool is_v6,
++ const struct nbrec_nat *nat,
++ struct ds *match,
++ bool distributed_nat, int cidr_bits,
++ bool is_v6,
+ struct ovn_port *l3dgw_port,
+- struct lflow_ref *lflow_ref)
++ struct lflow_ref *lflow_ref,
++ bool is_reverse)
+ {
+ ds_clear(match);
+
+- ds_put_format(match, "ip && ip%c.src == %s", is_v6 ? '6' : '4',
++ ds_put_format(match, "ip && ip%c.%s == %s",
++ is_v6 ? '6' : '4',
++ is_reverse ? "dst" : "src",
+ nat->logical_ip);
+
+ if (!od->is_gw_router) {
+ /* Distributed router. */
+- ds_put_format(match, " && outport == %s", l3dgw_port->json_key);
++ ds_put_format(match, " && %s == %s",
++ is_reverse ? "inport" : "outport",
++ l3dgw_port->json_key);
+ if (od->n_l3dgw_ports) {
+ ds_put_format(match, " && is_chassis_resident(\"%s\")",
+ distributed_nat
+@@ -14408,7 +14448,7 @@ build_lrouter_out_snat_match(struct lflow_table *lflows,
+
+ if (nat->allowed_ext_ips || nat->exempted_ext_ips) {
+ lrouter_nat_add_ext_ip_match(od, lflows, match, nat,
+- is_v6, false, cidr_bits,
++ is_v6, is_reverse, cidr_bits,
+ lflow_ref);
+ }
+ }
+@@ -14435,7 +14475,8 @@ build_lrouter_out_snat_stateless_flow(struct lflow_table *lflows,
+ uint16_t priority = cidr_bits + 1;
+
+ build_lrouter_out_snat_match(lflows, od, nat, match, distributed_nat,
+- cidr_bits, is_v6, l3dgw_port, lflow_ref);
++ cidr_bits, is_v6, l3dgw_port, lflow_ref,
++ false);
+
+ if (!od->is_gw_router) {
+ /* Distributed router. */
+@@ -14482,7 +14523,7 @@ build_lrouter_out_snat_in_czone_flow(struct lflow_table *lflows,
+
+ build_lrouter_out_snat_match(lflows, od, nat, match, distributed_nat,
+ cidr_bits, is_v6, l3dgw_port,
+- lflow_ref);
++ lflow_ref, false);
+
+ if (od->n_l3dgw_ports) {
+ priority += 128;
+@@ -14531,7 +14572,8 @@ build_lrouter_out_snat_flow(struct lflow_table *lflows,
+ struct ds *actions, bool distributed_nat,
+ struct eth_addr mac, int cidr_bits, bool is_v6,
+ struct ovn_port *l3dgw_port,
+- struct lflow_ref *lflow_ref)
++ struct lflow_ref *lflow_ref,
++ const struct chassis_features *features)
+ {
+ if (strcmp(nat->type, "snat") && strcmp(nat->type, "dnat_and_snat")) {
+ return;
+@@ -14545,7 +14587,9 @@ build_lrouter_out_snat_flow(struct lflow_table *lflows,
+ uint16_t priority = cidr_bits + 1;
+
+ build_lrouter_out_snat_match(lflows, od, nat, match, distributed_nat,
+- cidr_bits, is_v6, l3dgw_port, lflow_ref);
++ cidr_bits, is_v6, l3dgw_port, lflow_ref,
++ false);
++ size_t original_match_len = match->length;
+
+ if (!od->is_gw_router) {
+ /* Distributed router. */
+@@ -14570,6 +14614,35 @@ build_lrouter_out_snat_flow(struct lflow_table *lflows,
+ priority, ds_cstr(match),
+ ds_cstr(actions), &nat->header_,
+ lflow_ref);
++
++ /* For the SNAT networks, we need to make sure that connections are
++ * properly tracked so we can decide whether to perform SNAT on traffic
++ * exiting the network. */
++ if (features->ct_commit_to_zone && !strcmp(nat->type, "snat") &&
++ !od->is_gw_router) {
++ /* For traffic that comes from SNAT network, initiate CT state before
++ * entering S_ROUTER_OUT_SNAT to allow matching on various CT states.
++ */
++ ds_truncate(match, original_match_len);
++ ovn_lflow_add(lflows, od, S_ROUTER_OUT_POST_UNDNAT, 70,
++ ds_cstr(match), "ct_snat;",
++ lflow_ref);
++
++ build_lrouter_out_snat_match(lflows, od, nat, match,
++ distributed_nat, cidr_bits, is_v6,
++ l3dgw_port, lflow_ref, true);
++
++ /* New traffic that goes into SNAT network is committed to CT to avoid
++ * SNAT-ing replies.*/
++ ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, priority,
++ ds_cstr(match), "ct_snat;",
++ lflow_ref);
++
++ ds_put_cstr(match, " && ct.new");
++ ovn_lflow_add(lflows, od, S_ROUTER_OUT_POST_SNAT, priority,
++ ds_cstr(match), "ct_commit_to_zone(snat);",
++ lflow_ref);
++ }
+ }
+
+ static void
+@@ -15108,7 +15181,7 @@ build_lrouter_nat_defrag_and_lb(
+ } else {
+ build_lrouter_out_snat_flow(lflows, od, nat, match, actions,
+ distributed_nat, mac, cidr_bits, is_v6,
+- l3dgw_port, lflow_ref);
++ l3dgw_port, lflow_ref, features);
+ }
+
+ /* S_ROUTER_IN_ADMISSION - S_ROUTER_IN_IP_INPUT */
+@@ -15310,7 +15383,7 @@ build_routable_flows_for_router_port(
+ }
+
+ if (lrp->nbrp->ha_chassis_group ||
+- lrp->nbrp->n_gateway_chassis) {
++ lrp->nbrp->n_gateway_chassis || lrp->od->is_gw_router) {
+ for (size_t j = 0; j < ra.n_addrs; j++) {
+ struct lport_addresses *laddrs = &ra.laddrs[j];
+ for (size_t k = 0; k < laddrs->n_ipv4_addrs; k++) {
+diff --git a/northd/northd.h b/northd/northd.h
+index 3f1cd8341..5e9fa4745 100644
+--- a/northd/northd.h
++++ b/northd/northd.h
+@@ -362,6 +362,12 @@ ovn_datapath_is_stale(const struct ovn_datapath *od)
+ return !od->nbr && !od->nbs;
+ };
+
++static inline bool
++ovn_datapath_is_transit_switch(const struct ovn_datapath *od)
++{
++ return od->tunnel_key >= OVN_MIN_DP_KEY_GLOBAL;
++}
++
+ /* Pipeline stages. */
+
+ /* The two purposes for which ovn-northd uses OVN logical datapaths. */
+diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
+index 9583abeff..474f54017 100644
+--- a/northd/ovn-northd.8.xml
++++ b/northd/ovn-northd.8.xml
+@@ -1933,13 +1933,20 @@ output;
+
+
+
+- Priority-90 flows that forward registered IP multicast traffic to
+- their corresponding multicast group, which ovn-northd
+- creates based on learnt
+- entries. The flows also forward packets to the
+- MC_MROUTER_FLOOD
multicast group, which
+- ovn-nortdh
populates with all the logical ports that
+- are connected to logical routers with
++ Priority-90 flows for transit switches that forward registered
++ IP multicast traffic to their corresponding multicast group , which
++ ovn-northd
creates based on learnt
++ entries.
++
++
++
++ Priority-90 flows for non-transit switches that forward registered
++ IP multicast traffic to their corresponding multicast group, which
++ ovn-northd
creates based on learnt
++ entries. The flows
++ also forward packets to the MC_MROUTER_FLOOD
multicast
++ group, which ovn-nortdh
populates with all the logical
++ ports that are connected to logical routers with
+ :mcast_relay='true'.
+
+
+@@ -4865,6 +4872,13 @@ nd_ns {
+
+
+
+
+
++ ct_commit_to_zone(dnat);
++ ct_commit_to_zone(snat);
++
++
++ Commit the flow to the specific zone in the connection tracker.
++ The packet is then automatically sent to the next tables as if
++ followed by next;
action. The next tables will
++ see the changes in the packet caused by the connection tracker.
++
++
++
++ Note that this action is meaningful only in the Logical Router
++ Datapath as the Logical Switch Datapath does not use separate
++ connection tracking zones. Using this action in Logical Switch
++ Datapath falls back to committing the flow into the logical port's
++ conntrack zone.
++
++
+ ct_dnat;
+ ct_dnat(IP);
+
+diff --git a/tests/automake.mk b/tests/automake.mk
+index f6f0f0e33..1fdc89835 100644
+--- a/tests/automake.mk
++++ b/tests/automake.mk
+@@ -21,6 +21,7 @@ EXTRA_DIST += \
+ COMMON_MACROS_AT = \
+ tests/ovsdb-macros.at \
+ tests/ovs-macros.at \
++ tests/ovn-macros.at \
+ tests/ofproto-macros.at
+
+ TESTSUITE_AT = \
+diff --git a/tests/multinode.at b/tests/multinode.at
+index 0187382be..b959a2550 100644
+--- a/tests/multinode.at
++++ b/tests/multinode.at
+@@ -70,6 +70,14 @@ M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -q -c 3 -i 0.3 -w 2 20.0.0.3 | F
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+
++check multinode_nbctl lsp-set-addresses sw1-port1 unknown
++m_wait_for_ports_up sw1-port1
++
++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
++3 packets transmitted, 3 received, 0% packet loss, time 0ms
++])
++
+ AT_CLEANUP
+
+ AT_SETUP([ovn multinode pmtu - distributed router - geneve])
+diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at
+index 48349433b..5481a6317 100644
+--- a/tests/ovn-controller.at
++++ b/tests/ovn-controller.at
+@@ -2284,9 +2284,15 @@ lflow_run_2=$(ovn-appctl -t ovn-controller coverage/read-counter lflow_run)
+ AT_CHECK_UNQUOTED([echo $lflow_run_1], [0], [$lflow_run_2
+ ])
+
+-# Restart OVS this time, and wait until flows are reinstalled
++# Restart OVS this time. Flows should be reinstalled without waiting.
++# Set the wait-before-clear to a large value (60s) to make the test more reliable.
++check ovs-vsctl set open . external_ids:ovn-ofctrl-wait-before-clear=60000
++check ovn-nbctl --wait=hv sync
++
+ OVS_APP_EXIT_AND_WAIT([ovs-vswitchd])
+ start_daemon ovs-vswitchd --enable-dummy=system -vvconn -vofproto_dpif -vunixctl
++
++# Flow should be installed without waiting for another 60s.
+ OVS_WAIT_UNTIL([ovs-ofctl dump-flows br-int | grep -F 10.1.2.4 | grep -vF 2.2.2.2])
+
+ check ovn-nbctl --wait=hv lb-add lb3 3.3.3.3 10.1.2.5 \
+diff --git a/tests/ovn-ic.at b/tests/ovn-ic.at
+index 44dbf8ab1..ad24011f2 100644
+--- a/tests/ovn-ic.at
++++ b/tests/ovn-ic.at
+@@ -6,7 +6,7 @@ ovn_init_ic_db
+ ovn_start az1
+ ovn_start az2
+
+-wait_row_count ic-sb:Availability_Zone 2
++check ovn-ic-nbctl --wait=sb sync
+ AT_CHECK([ovn-ic-sbctl show], [0], [dnl
+ availability-zone az1
+ availability-zone az2
+@@ -31,7 +31,6 @@ OVN_CLEANUP_IC([az1], [az2])
+ AT_CLEANUP
+ ])
+
+-
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([ovn-ic -- AZ update in GW])
+ ovn_init_ic_db
+@@ -47,10 +46,12 @@ 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"
+-wait_column "$az_uuid" ic-sb:availability-zone _uuid name="az2"
++
++check ovn-ic-nbctl --wait=sb sync
++check_column "$az_uuid" ic-sb:availability-zone _uuid name="az2"
+
+ # make sure that gateway still point to the same AZ with new name
+-wait_column "$az_uuid" ic-sb:gateway availability_zone name="gw-az1"
++check_column "$az_uuid" ic-sb:gateway availability_zone name="gw-az1"
+
+ OVN_CLEANUP_IC([az1])
+ AT_CLEANUP
+@@ -66,11 +67,11 @@ ovn_start az1
+ ovn-sbctl chassis-add fakechassis vxlan 192.168.0.2
+
+ AT_CHECK([ovn-ic-nbctl ts-add ts1])
+-AT_CHECK([ovn-ic-nbctl ts-add ts2])
++AT_CHECK([ovn-ic-nbctl --wait=sb ts-add ts2])
+
+ # Check ISB
+-wait_row_count ic-sb:Datapath_Binding 1 transit_switch=ts1
+-wait_row_count ic-sb:Datapath_Binding 1 transit_switch=ts2
++check_row_count ic-sb:Datapath_Binding 1 transit_switch=ts1
++check_row_count ic-sb:Datapath_Binding 1 transit_switch=ts2
+ check_column "ts1 ts2" ic-sb:Datapath_Binding transit_switch
+ check_column "ts1 ts2" nb:Logical_Switch name
+
+@@ -81,8 +82,8 @@ ts1_key=$(fetch_column ic-sb:Datapath_Binding tunnel_key transit_switch=ts1)
+ check_column "$ts1_key" Datapath_Binding tunnel_key external_ids:interconn-ts=ts1
+
+ # Test delete
+-AT_CHECK([ovn-ic-nbctl ts-del ts1])
+-wait_row_count ic-sb:Datapath_Binding 0 transit_switch=ts1
++AT_CHECK([ovn-ic-nbctl --wait=sb ts-del ts1])
++check_row_count ic-sb:Datapath_Binding 0 transit_switch=ts1
+ check_column ts2 ic-sb:Datapath_Binding transit_switch
+ check_column ts2 nb:Logical_Switch name
+
+@@ -111,12 +112,11 @@ done
+ ovn_as az1
+
+ # create transit switch and connect to LR
+-check ovn-ic-nbctl ts-add ts1
++check ovn-ic-nbctl --wait=sb ts-add ts1
+ check ovn-nbctl lr-add lr1
+ check ovn-nbctl lrp-add lr1 lrp1 00:00:00:00:00:01 10.0.0.1/24
+ check ovn-nbctl lrp-set-gateway-chassis lrp1 gw-az1
+
+-OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep ts1])
+ check ovn-nbctl lsp-add ts1 lsp1 -- \
+ lsp-set-addresses lsp1 router -- \
+ lsp-set-type lsp1 router -- \
+@@ -124,8 +124,8 @@ check ovn-nbctl lsp-add ts1 lsp1 -- \
+
+ wait_row_count Datapath_Binding 1 external_ids:interconn-ts=ts1
+
+-# check port binding appeared
+-OVS_WAIT_UNTIL([ovn-ic-sbctl show | grep lsp1])
++# Sync ic-sb DB to see the TS changes.
++check ovn-ic-nbctl --wait=sb sync
+
+ AT_CHECK([ovn-ic-sbctl show | grep -A2 lsp1], [0], [dnl
+ port lsp1
+@@ -134,8 +134,8 @@ AT_CHECK([ovn-ic-sbctl show | grep -A2 lsp1], [0], [dnl
+ ])
+
+ # remove transit switch and check if port_binding is deleted
+-check ovn-ic-nbctl ts-del ts1
+-wait_row_count ic-sb:Port_Binding 0 logical_port=lsp1
++check ovn-ic-nbctl --wait=sb ts-del ts1
++check_row_count ic-sb:Port_Binding 0 logical_port=lsp1
+ for i in 1 2; do
+ az=az$i
+ ovn_as $az
+@@ -180,8 +180,7 @@ create_ic_infra() {
+
+ ovn_as $az
+
+- check ovn-ic-nbctl ts-add $ts
+- OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep $ts])
++ check ovn-ic-nbctl --wait=sb ts-add $ts
+ check ovn-nbctl lr-add $lr
+ check ovn-nbctl lrp-add $lr $lrp 00:00:00:00:00:0$az_id 10.0.$az_id.1/24
+ check ovn-nbctl lrp-set-gateway-chassis $lrp gw-$az
+@@ -197,18 +196,18 @@ create_ic_infra() {
+ create_ic_infra 1 1
+ create_ic_infra 1 2
+ create_ic_infra 2 1
++check ovn-ic-nbctl --wait=sb sync
+
+ ovn_as az1
+
+-wait_row_count ic-sb:Route 3 ip_prefix=192.168.0.0/16
++check_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
+-check ovn-ic-nbctl ts-del ts1-1
+-sleep 2
++check ovn-ic-nbctl --wait=sb ts-del ts1-1
+ ovn-ic-sbctl list route
+ ovn-ic-nbctl list transit_switch
+-wait_row_count ic-sb:route 2 ip_prefix=192.168.0.0/16
++checl_row_count ic-sb:route 2 ip_prefix=192.168.0.0/16
+ ovn-ic-sbctl list route
+
+ for i in 1 2; do
+@@ -246,7 +245,7 @@ done
+ ovn_as az1
+
+ # create transit switch and connect to LR
+-check ovn-ic-nbctl ts-add ts1
++check ovn-ic-nbctl --wait=sb ts-add ts1
+ for i in 1 2; do
+ ovn_as az$i
+
+@@ -254,7 +253,6 @@ for i in 1 2; do
+ check ovn-nbctl lrp-add lr1 lrp$i 00:00:00:00:0$i:01 10.0.$i.1/24
+ check ovn-nbctl lrp-set-gateway-chassis lrp$i gw-az$i
+
+- OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep ts1])
+ check ovn-nbctl lsp-add ts1 lsp$i -- \
+ lsp-set-addresses lsp$i router -- \
+ lsp-set-type lsp$i router -- \
+@@ -270,7 +268,8 @@ ovn-nbctl \
+ --id=@id create logical-router-static-route ip_prefix=1.1.1.1/32 nexthop=10.0.1.10 -- \
+ add logical-router lr1 static_routes @id
+
+-wait_row_count ic-sb:route 1 ip_prefix=1.1.1.1/32
++check ovn-ic-nbctl --wait=sb sync
++check_row_count ic-sb:route 1 ip_prefix=1.1.1.1/32
+
+ for i in 1 2; do
+ az=az$i
+@@ -348,6 +347,8 @@ ovn-ic-nbctl ts-add ts1
+ net_add n1
+ ovn_start az1
+ ovn_start az2
++# sync IC-NB to IC-SB and AZs after AZs creation.
++check ovn-ic-nbctl --wait=sb sync
+ sim_add gw1
+ as gw1
+ ovs-vsctl add-br br-phys
+@@ -355,8 +356,6 @@ ovn_az_attach az1 n1 br-phys 192.168.0.1
+ ovs-vsctl set open . external-ids:ovn-is-interconn=true
+
+ ovn_as az1
+-OVS_WAIT_UNTIL([ovn-sbctl list datapath_binding | grep interconn-ts | grep ts1])
+-
+ # Create LRP and connect to TS
+ ovn-nbctl lr-add lr1
+ ovn-nbctl lrp-add lr1 lrp-lr1-ts1 aa:aa:aa:aa:aa:01 169.254.100.1/24
+@@ -418,13 +417,12 @@ ovn-ic-nbctl ts-add ts1
+ for i in 1 2; do
+ ovn_start az$i
+ ovn_as az$i
+-
++ check ovn-ic-nbctl --wait=sb sync
+ # Enable route learning at AZ level
+ ovn-nbctl set nb_global . options:ic-route-learn=true
+ # Enable route advertising at AZ level
+ ovn-nbctl set nb_global . options:ic-route-adv=true
+
+- OVS_WAIT_UNTIL([ovn-nbctl show | grep ts1])
+ # Create LRP and connect to TS
+ ovn-nbctl lr-add lr$i
+ ovn-nbctl lrp-add lr$i lrp-lr$i-ts1 aa:aa:aa:aa:aa:0$i 169.254.100.$i/24
+@@ -454,15 +452,21 @@ Route Table :
+
+ # 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
+-OVS_WAIT_WHILE([ovn_as az2 ovn-nbctl lr-route-list lr2 | grep learned])
++ovn-ic-nbctl --wait=sb sync
++AT_CHECK([ovn_as az2 ovn-nbctl lr-route-list lr2 | grep -c learned], [1], [dnl
++0
++])
+
+ # Add the route back
+ ovn_as az1 ovn-nbctl 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])
++ovn-ic-nbctl --wait=sb sync
++AT_CHECK([ovn_as az2 ovn-nbctl lr-route-list lr2 | grep -c learned], [0], [dnl
++1
++])
+
+ # Disable route-learning for AZ1
+ 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])
++ovn-ic-nbctl --wait=sb sync
+ AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr1], [0], [dnl
+ IPv4 Routes
+ Route Table :
+@@ -478,7 +482,7 @@ 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.
+-OVS_WAIT_WHILE([ovn_as az2 ovn-nbctl lr-route-list lr2 | grep learned])
++check ovn-ic-nbctl --wait=sb sync
+ AT_CHECK([ovn_as az2 ovn-nbctl lr-route-list lr2], [0], [dnl
+ IPv4 Routes
+ Route Table :
+@@ -492,10 +496,7 @@ 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
+-
+-for i in 1 2; do
+- OVS_WAIT_UNTIL([ovn_as az$i ovn-nbctl lr-route-list lr$i | grep learned])
+-done
++check ovn-ic-nbctl --wait=sb sync
+
+ # Default route should NOT get advertised or learned, by default.
+ AT_CHECK([ovn_as az2 ovn-nbctl lr-route-list lr2], [0], [dnl
+@@ -558,8 +559,7 @@ for i in 1 2; do
+
+ for j in 1 2; do
+ ts=ts$j$j
+- ovn-ic-nbctl --may-exist ts-add $ts
+- OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep $ts])
++ ovn-ic-nbctl --wait=sb --may-exist ts-add $ts
+
+ # Create LRP and connect to TS
+ lr=lr$j$i
+@@ -582,9 +582,7 @@ echo az1
+ ovn_as az1 ovn-nbctl show
+ echo az2
+ ovn_as az2 ovn-nbctl show
+-
+-OVS_WAIT_UNTIL([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep learned | grep 192.168])
+-OVS_WAIT_UNTIL([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep learned | grep 10.10.10])
++check ovn-ic-nbctl --wait=sb sync
+
+ # Test routes from lr12 were learned to lr11
+ AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr11 |
+@@ -606,7 +604,7 @@ ovn_as az2 ovn-nbctl lr-del lr22
+ # check routes origin advertisement and learning
+
+ # setup topology with connected, static and source routes
+-ovn-ic-nbctl ts-add ts1
++ovn-ic-nbctl --wait=sb ts-add ts1
+ for i in 1 2; do
+ ovn_as az$i
+
+@@ -618,7 +616,6 @@ for i in 1 2; do
+ # Create LRP and connect to TS
+ ovn-nbctl lr-add lr$i
+ ovn-nbctl lrp-add lr$i lrp-lr$i-ts1 aa:aa:aa:aa:aa:0$i 169.254.100.$i/24
+- OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep ts1])
+ ovn-nbctl lsp-add ts1 lsp-ts1-lr$i \
+ -- lsp-set-addresses lsp-ts1-lr$i router \
+ -- lsp-set-type lsp-ts1-lr$i router \
+@@ -633,10 +630,7 @@ for i in 1 2; do
+ ovn-nbctl --policy=src-ip lr-route-add lr$i 10.22.$i.0/24 169.254.0.2
+ done
+
+-for i in 1 2; do
+- OVS_WAIT_UNTIL([ovn_as az$i ovn-nbctl lr-route-list lr$i | grep learned])
+-done
+-
++check ovn-ic-nbctl --wait=sb sync
+ # check that advertised routes in ic-sb have correct origin
+ ovn-ic-sbctl list route
+ wait_row_count ic-sb:Route 1 ip_prefix=10.11.1.0/24 origin=static
+@@ -674,8 +668,7 @@ for i in 1 2; do
+ ovn-nbctl set nb_global . options:ic-route-learn=true
+ # Enable route advertising at AZ level
+ ovn-nbctl set nb_global . options:ic-route-adv=true
+-
+- OVS_WAIT_UNTIL([ovn-nbctl show | grep ts1])
++ check ovn-ic-nbctl --wait=sb sync
+
+ # Create LRP and connect to TS
+ ovn-nbctl lr-add lr$i
+@@ -688,10 +681,7 @@ for i in 1 2; do
+ ovn-nbctl lrp-add lr$i lrp-lr$i-p$i 00:00:00:00:00:0$i 2002:db8:1::$i/64
+ done
+
+-for i in 1 2; do
+- OVS_WAIT_UNTIL([ovn_as az$i ovn-nbctl lr-route-list lr$i | grep learned])
+-done
+-
++check ovn-ic-nbctl --wait=sb sync
+ AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr1 | awk '/learned/{print $1, $2}'], [0], [dnl
+ 2002:db8:1::/64 2001:db8:1::2
+ ])
+@@ -720,14 +710,12 @@ ovn-ic-nbctl ts-add ts1
+ for i in 1 2; do
+ ovn_start az$i
+ ovn_as az$i
+-
++ check ovn-ic-nbctl --wait=sb sync
+ # Enable route learning at AZ level
+ ovn-nbctl set nb_global . options:ic-route-learn=true
+ # Enable route advertising at AZ level
+ ovn-nbctl set nb_global . options:ic-route-adv=true
+
+- OVS_WAIT_UNTIL([ovn-nbctl show | grep ts1])
+-
+ # Create LRP and connect to TS
+ ovn-nbctl lr-add lr$i
+ ovn-nbctl lrp-add lr$i lrp-lr$i-ts1 aa:aa:aa:aa:aa:0$i 169.254.100.$i/24
+@@ -743,10 +731,7 @@ for i in 1 2; do
+ ovn-nbctl --policy=src-ip --route-table=rtb1 lr-route-add lr$i 10.22.$i.0/24 169.254.0.2
+ done
+
+-for i in 1 2; do
+- OVS_WAIT_UNTIL([ovn_as az$i ovn-nbctl lr-route-list lr$i | grep learned])
+-done
+-
++check ovn-ic-nbctl --wait=sb sync
+ AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr1], [0], [dnl
+ IPv4 Routes
+ Route Table :
+@@ -763,10 +748,7 @@ for i in 1 2; do
+ ovn_as az$i ovn-nbctl --route-table=rtb1 lr-route-add lr$i 10.11.$i.0/24 169.254.0.1
+ done
+
+-for i in 1 2; do
+- OVS_WAIT_WHILE([ovn_as az$i ovn-nbctl lr-route-list lr$i | grep learned])
+-done
+-
++check ovn-ic-nbctl --wait=sb sync
+ # ensure route from rtb1 is not learned to any route table as route table is
+ # not set to TS port
+ AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr1], [0], [dnl
+@@ -778,7 +760,7 @@ Route Table rtb1:
+
+ # assign route table rtb1 to TS port on AZ2 and check routes are advertised to IC SB DB
+ check ovn_as az2 ovn-nbctl lrp-set-options lrp-lr2-ts1 route_table=rtb1
+-OVS_WAIT_UNTIL([ovn-ic-sbctl find route route_table=rtb1 | grep 10.11.2.0/24])
++check ovn-ic-nbctl --wait=sb sync
+
+ # ensure route was not learned as on AZ1 TS port's LRP was not set to route table rtb1
+ AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr1], [0], [dnl
+@@ -790,8 +772,8 @@ Route Table rtb1:
+
+ # set TS port's LRP to route table rtb1 to learn routes from AZ2 from rtb1
+ check ovn_as az1 ovn-nbctl lrp-set-options lrp-lr1-ts1 route_table=rtb1
++check ovn-ic-nbctl --wait=sb sync
+
+-OVS_WAIT_UNTIL([ovn_as az1 ovn-nbctl --route-table=rtb1 lr-route-list lr1 | grep learned])
+ AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr1], [0], [dnl
+ IPv4 Routes
+ Route Table rtb1:
+@@ -943,8 +925,7 @@ for i in 1 2; do
+
+ for j in 1 2 3; do
+ ts=ts1$j
+- ovn-ic-nbctl --may-exist ts-add $ts
+- OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep $ts])
++ ovn-ic-nbctl --wait=sb --may-exist ts-add $ts
+
+ lrp=lrp-$lr-$ts
+ lsp=lsp-$ts-$lr
+@@ -969,8 +950,7 @@ for i in 1 2; do
+
+ for j in 1 2; do
+ ts=ts2$j
+- ovn-ic-nbctl --may-exist ts-add $ts
+- OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep $ts])
++ ovn-ic-nbctl --wait=sb --may-exist ts-add $ts
+
+ lrp=lrp-$lr-$ts
+ lsp=lsp-$ts-$lr
+@@ -992,7 +972,7 @@ ovn_as az2 ovn-nbctl --route-table=rtb3 lr-route-add lr12 10.10.10.0/24 192.168.
+
+ # 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-ic-nbctl --wait=sb sync
+ # Test direct routes from lr12 were learned to lr11
+ OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep 192.168 |
+ grep learned | awk '{print $1, $2, $5}' | sort ], [0], [dnl
+@@ -1071,8 +1051,7 @@ for i in 1 2; do
+
+ for j in 1 2 3; do
+ ts=ts1$j
+- ovn-ic-nbctl --may-exist ts-add $ts
+- OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep $ts])
++ ovn-ic-nbctl --wait=sb --may-exist ts-add $ts
+
+ lrp=lrp-$lr-$ts
+ lsp=lsp-$ts-$lr
+@@ -1097,8 +1076,7 @@ for i in 1 2; do
+
+ for j in 1 2; do
+ ts=ts2$j
+- ovn-ic-nbctl --may-exist ts-add $ts
+- OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep $ts])
++ ovn-ic-nbctl --wait=sb --may-exist ts-add $ts
+
+ lrp=lrp-$lr-$ts
+ lsp=lsp-$ts-$lr
+@@ -1122,7 +1100,7 @@ ovn_as az2 ovn-nbctl --route-table=rtb3 lr-route-add lr12 2001:db8:aaaa::/64 200
+ 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
+-OVS_WAIT_UNTIL([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep 2001:db8:3::2])
++check ovn-ic-nbctl --wait=sb sync
+ AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep 2001:db8:200 |
+ grep learned | awk '{print $1, $2, $5}' | sort], [0], [dnl
+ 2001:db8:200::/64 2001:db8:1::2 ecmp
+@@ -1171,7 +1149,7 @@ ovn-ic-nbctl ts-add ts1
+ for i in 1 2; do
+ ovn_start az$i
+ ovn_as az$i
+- OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep ts1])
++ check ovn-ic-nbctl --wait=sb sync
+
+ # Enable route learning at AZ level
+ ovn-nbctl set nb_global . options:ic-route-learn=true
+@@ -1197,13 +1175,14 @@ for i in 1 2; do
+ check ovn-nbctl --wait=sb lr-route-add $lr 0.0.0.0/0 192.168.$i.11
+ done
+
+-OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep dst-ip | sort] , [0], [dnl
++check ovn-ic-nbctl --wait=sb sync
++AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep dst-ip | sort] , [0], [dnl
+ 0.0.0.0/0 192.168.1.11 dst-ip
+ 10.0.0.0/24 192.168.1.10 dst-ip
+ 192.168.2.0/24 169.254.100.2 dst-ip (learned)
+ ])
+
+-OVS_WAIT_FOR_OUTPUT([ovn_as az2 ovn-nbctl lr-route-list lr12 | grep dst-ip | sort], [0], [dnl
++AT_CHECK([ovn_as az2 ovn-nbctl lr-route-list lr12 | grep dst-ip | sort], [0], [dnl
+ 0.0.0.0/0 192.168.2.11 dst-ip
+ 10.0.0.0/24 192.168.2.10 dst-ip
+ 192.168.1.0/24 169.254.100.1 dst-ip (learned)
+@@ -1239,7 +1218,7 @@ done
+ # create lr11, lr21, lr22 and connect them
+
+ ovn_as az1
+-OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep ts1])
++check ovn-ic-nbctl --wait=sb sync
+
+ lr=lr11
+ ovn-nbctl lr-add $lr
+@@ -1254,7 +1233,6 @@ ovn-nbctl lsp-add ts1 $lsp \
+ -- lsp-set-options $lsp router-port=$lrp
+
+ ovn_as az2
+-OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep ts1])
+ for i in 1 2; do
+ lr=lr2$i
+ ovn-nbctl lr-add $lr
+@@ -1276,7 +1254,8 @@ 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 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
+-OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep 192.168 |
++check ovn-ic-nbctl --wait=sb sync
++AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep 192.168 |
+ grep learned | awk '{print $1, $2}' | sort ], [0], [dnl
+ 192.168.1.0/24 169.254.10.21
+ 192.168.2.0/24 169.254.10.22
+@@ -1363,8 +1342,7 @@ for i in 1 2; do
+ check ovn-nbctl set nb_global . options:ic-route-blacklist=" \
+ 2003:db8:1::/64,2004:aaaa::/32,2005:1234::/21"
+
+- OVS_WAIT_UNTIL([ovn-nbctl show | grep ts1])
+-
++ check ovn-ic-nbctl --wait=sb sync
+ # Create LRP and connect to TS
+ check ovn-nbctl lr-add lr$i
+ check ovn-nbctl lrp-add lr$i lrp-lr$i-ts1 aa:aa:aa:aa:aa:0$i \
+@@ -1393,10 +1371,7 @@ for i in 1 2; do
+ 44:44:44:44:44:4$i 2005:1834:5678::$i/50
+ done
+
+-for i in 1 2; do
+- OVS_WAIT_UNTIL([ovn_as az$i ovn-nbctl lr-route-list lr$i | grep learned])
+-done
+-
++check ovn-ic-nbctl --wait=sb sync
+ AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr1 |
+ awk '/learned/{print $1, $2}' ], [0], [dnl
+ 2002:db8:1::/64 2001:db8:1::2
+@@ -1410,7 +1385,8 @@ for i in 1 2; do
+ check ovn-nbctl remove nb_global . options ic-route-blacklist
+ done
+
+-OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr1 |
++check ovn-ic-nbctl --wait=sb sync
++AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr1 |
+ awk '/learned/{print $1, $2}' | sort ], [0], [dnl
+ 2002:db8:1::/64 2001:db8:1::2
+ 2003:db8:1::/64 2001:db8:1::2
+@@ -1430,7 +1406,8 @@ for i in 1 2; do
+ 55:55:55:55:55:5$i 2004:db8:1::$i/64
+ done
+
+-OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr1 |
++check ovn-ic-nbctl --wait=sb sync
++AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr1 |
+ awk '/learned/{print $1, $2}' | sort ], [0], [dnl
+ 2002:db8:1::/64 2001:db8:1::2
+ 2004:aaaa:bbb::/48 2001:db8:1::2
+@@ -1442,3 +1419,688 @@ OVN_CLEANUP_IC([az1], [az2])
+
+ AT_CLEANUP
+ ])
++
++OVN_FOR_EACH_NORTHD([
++AT_SETUP([interconnection])
++AT_KEYWORDS([slowtest])
++
++ovn_init_ic_db
++# The number needs to stay relatively low due to high memory consumption
++# with address sanitizers enabled.
++n_az=3
++n_ts=3
++for i in `seq 1 $n_az`; do
++ ovn_start az$i
++done
++
++net_add n1
++
++# 1 HV and 1 GW per AZ
++for az in `seq 1 $n_az`; do
++ sim_add hv$az
++ as hv$az
++ check ovs-vsctl add-br br-phys
++ ovn_az_attach az$az n1 br-phys 192.168.$az.1 16
++ for p in `seq 1 $n_ts`; do
++ check ovs-vsctl -- add-port br-int vif$p -- \
++ set interface vif$p external-ids:iface-id=lsp$az-$p \
++ options:tx_pcap=hv$az/vif$p-tx.pcap \
++ options:rxq_pcap=hv$az/vif$p-rx.pcap \
++ ofport-request=$p
++ done
++
++ sim_add gw$az
++ as gw$az
++ check ovs-vsctl add-br br-phys
++ ovn_az_attach az$az n1 br-phys 192.168.$az.2 16
++ check ovs-vsctl set open . external-ids:ovn-is-interconn=true
++done
++
++for ts in `seq 1 $n_ts`; do
++ AT_CHECK([ovn-ic-nbctl --wait=sb create Transit_Switch name=ts$ts], [0], [ignore])
++done
++
++for az in `seq 1 $n_az`; do
++ ovn_as az$az
++ check ovn-nbctl set nb_global . options:ic-route-learn=true
++ check ovn-nbctl set nb_global . options:ic-route-adv=true
++
++ # Each AZ has n_ts LSPi->LSi->LRi connecting to each TSi
++ echo
++ echo "az$az"
++ for i in `seq 1 $n_ts`; do
++ lsp_mac=00:00:00:0$az:0$i:00
++ lrp_ls_mac=00:00:00:0$az:0$i:01
++ lrp_ts_mac=00:00:00:0$az:0$i:02
++ lsp_ip=10.$az.$i.123
++ lrp_ls_ip=10.$az.$i.1
++ lrp_ts_ip=169.254.$i.$az
++
++ check ovn-nbctl ls-add ls$az-$i
++ check ovn-nbctl lsp-add ls$az-$i lsp$az-$i
++ check ovn-nbctl lsp-set-addresses lsp$az-$i "$lsp_mac $lsp_ip"
++
++ check ovn-nbctl lr-add lr$az-$i
++
++ check ovn-nbctl lrp-add lr$az-$i lrp-lr$az-$i-ls$az-$i $lrp_ls_mac $lrp_ls_ip/24
++ check ovn-nbctl lsp-add ls$az-$i lsp-ls$az-$i-lr$az-$i
++ check ovn-nbctl lsp-set-addresses lsp-ls$az-$i-lr$az-$i router
++ check ovn-nbctl lsp-set-type lsp-ls$az-$i-lr$az-$i router
++ check ovn-nbctl lsp-set-options lsp-ls$az-$i-lr$az-$i router-port=lrp-lr$az-$i-ls$az-$i
++
++ check ovn-nbctl lrp-add lr$az-$i lrp-lr$az-$i-ts$i $lrp_ts_mac $lrp_ts_ip/24
++ check ovn-nbctl lsp-add ts$i lsp-ts$i-lr$az-$i
++ check ovn-nbctl lsp-set-addresses lsp-ts$i-lr$az-$i router
++ check ovn-nbctl lsp-set-type lsp-ts$i-lr$az-$i router
++ check ovn-nbctl lsp-set-options lsp-ts$i-lr$az-$i router-port=lrp-lr$az-$i-ts$i
++ check ovn-nbctl lrp-set-gateway-chassis lrp-lr$az-$i-ts$i gw$az
++ done
++ check ovn-nbctl --wait=hv sync
++ ovn-sbctl list Port_Binding > az$az.ports
++ wait_for_ports_up
++done
++
++# 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
++
++for i in `seq 1 $n_az`; do
++ check ovn_as az$i ovn-nbctl --wait=hv sync
++ ovn_as az$i ovn-sbctl dump-flows > az$i/sbflows
++done
++
++# Allow some time for ovn-northd and ovn-controller to catch up.
++# XXX This should be more systematic.
++sleep 2
++
++# Populate requested-chassis options for remote lsps
++for az in $(seq 1 $n_az); do
++ ovn_as az${az}
++ for ts in $(seq 1 $n_ts); do
++ for i in $(seq 1 $n_ts); do
++ if [[ $i -eq ${az} ]]; then
++ continue
++ fi
++ check ovn-nbctl lsp-set-options lsp-ts${ts}-lr${i}-${ts} requested-chassis=gw$i
++ done
++ done
++done
++
++ovn-ic-nbctl show > ic-nbctl.dump
++AT_CAPTURE_FILE([ic-nbctl.dump])
++
++(echo "---------ISB dump-----"
++ ovn-ic-sbctl show
++ echo "---------------------"
++ ovn-ic-sbctl list gateway
++ echo "---------------------"
++ ovn-ic-sbctl list datapath_binding
++ echo "---------------------"
++ ovn-ic-sbctl list port_binding
++ echo "---------------------"
++ ovn-ic-sbctl list route
++ echo "---------------------") > ic-sbctl.dump
++AT_CAPTURE_FILE([ic-sbctl.dump])
++
++AT_CAPTURE_FILE([expected])
++AT_CAPTURE_FILE([received])
++check_packets() {
++ > expected
++ > received
++ for az in `seq 1 $n_az`; do
++ for i in `seq 1 $n_ts`; do
++ pcap=hv$az/vif$i-tx.pcap
++ echo "--- $pcap" | tee -a expected >> received
++ if test -e $az-$i.expected; then
++ sort $az-$i.expected >> expected
++ fi
++ if test -e $pcap; then
++ $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $pcap | sort >> received
++ fi
++ echo | tee -a expected >> received
++ done
++ done
++
++ $at_diff expected received >/dev/null
++}
++
++# Send packets between AZs on each TS
++for s_az in $(seq 1 $n_az); do
++ ovn_as az${s_az}
++ as hv${s_az}
++ for d_az in $(seq 1 $n_az); do
++ if test $s_az = $d_az; then
++ continue
++ fi
++
++ for i in $(seq 1 $n_ts); do
++ echo
++ AS_BOX([packet from az$s_az to az$d_az via ts$i])
++ lsp_smac=00:00:00:0${s_az}:0$i:00
++ lsp_dmac=00:00:00:0${d_az}:0$i:00
++ lrp_ls_smac=00:00:00:0${s_az}:0$i:01
++ lrp_ls_dmac=00:00:00:0${d_az}:0$i:01
++ lsp_sip=10.${s_az}.$i.123
++ lsp_dip=10.${d_az}.$i.123
++
++ ovn_inport=lsp${s_az}-$i
++ packet="inport==\"$ovn_inport\" && eth.src==$lsp_smac && eth.dst==$lrp_ls_smac &&
++ ip4 && ip.ttl==64 && ip4.src==$lsp_sip && ip4.dst==$lsp_dip &&
++ udp && udp.src==53 && udp.dst==4369"
++ echo "sending: $packet"
++ AT_CHECK([ovn_trace --ovs "$packet" > ${s_az}-${d_az}-$i.ovn-trace])
++ OVS_WAIT_UNTIL([ovs-appctl -t ovn-controller inject-pkt "$packet"])
++ ovs_inport=$(ovs-vsctl --bare --columns=ofport find Interface external-ids:iface-id="$ovn_inport")
++
++ ovs_packet=$(echo $packet | ovstest test-ovn expr-to-packets)
++ echo ovs_inport=$ovs_inport ovs_packet=$ovs_packet
++ AT_CHECK([ovs-appctl ofproto/trace br-int in_port="$ovs_inport" "$ovs_packet" > ${s_az}-${d_az}-$i.ovs-trace])
++
++ # Packet to Expect
++ # The TTL should be decremented by 2.
++ packet="eth.src==$lrp_ls_dmac && eth.dst==$lsp_dmac &&
++ ip4 && ip.ttl==62 && ip4.src==$lsp_sip && ip4.dst==$lsp_dip &&
++ udp && udp.src==53 && udp.dst==4369"
++ echo $packet | ovstest test-ovn expr-to-packets >> ${d_az}-$i.expected
++ done
++ done
++done
++OVS_WAIT_UNTIL([check_packets], [$at_diff -F'^---' expected received])
++
++for az in `seq 1 $n_az`; do
++ OVN_CLEANUP_SBOX([hv$az])
++ OVN_CLEANUP_SBOX([gw$az])
++ OVN_CLEANUP_AZ([az$az])
++done
++
++OVN_CLEANUP_IC
++
++AT_CLEANUP
++])
++
++OVN_FOR_EACH_NORTHD([
++AT_SETUP([interconnection - static multicast])
++
++# Logical network:
++#
++# AZ1 | AZ2
++# ---------------------------------------------------------------------
++# |
++# | +-- LR2 --- LS2 --- LSP2 (sender)
++# | | |
++# | | +----- LSP4 (receiver)
++# | /
++# LSP1 --- LS1 --- LR1 --- TS ---
++# (receiver) | \
++# | +-- LR3 --- LS3 --- LSP3 (receiver)
++#
++# LS1, LS2, LS3 configured to flood unregistered IP multicast.
++# LR1, LR2, LR3 configured to relay IP multicast.
++# LR1-LS1 configured to flood IP multicast traffic unconditionally.
++# LR3-LS3 configured to flood IP multicast traffic unconditionally.
++
++AT_CAPTURE_FILE([exp])
++AT_CAPTURE_FILE([rcv])
++check_packets() {
++ > exp
++ > rcv
++ if test "$1" = --uniq; then
++ sort="sort -u"; shift
++ else
++ sort=sort
++ fi
++ for tuple in "$@"; do
++ set $tuple; pcap=$1 type=$2
++ echo "--- $pcap" | tee -a exp >> rcv
++ $sort "$type" >> exp
++ $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $pcap | $sort >> rcv
++ echo | tee -a exp >> rcv
++ done
++
++ $at_diff exp rcv >/dev/null
++}
++
++ovn_init_ic_db
++ovn_start az1
++ovn_start az2
++
++net_add n1
++
++sim_add hv1
++as hv1
++check ovs-vsctl add-br br-phys
++ovn_az_attach az1 n1 br-phys 192.168.1.1 16
++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
++check ovs-vsctl set open . external-ids:ovn-is-interconn=true
++
++sim_add hv2
++as hv2
++check ovs-vsctl add-br br-phys
++ovn_az_attach az2 n1 br-phys 192.168.2.1 16
++check ovs-vsctl -- add-port br-int hv2-vif1 \
++ -- set interface hv2-vif1 external-ids:iface-id=lsp2 \
++ options:tx_pcap=hv2/vif1-tx.pcap \
++ options:rxq_pcap=hv2/vif1-rx.pcap
++check ovs-vsctl -- add-port br-int hv2-vif2 \
++ -- set interface hv2-vif2 external-ids:iface-id=lsp3 \
++ options:tx_pcap=hv2/vif2-tx.pcap \
++ options:rxq_pcap=hv2/vif2-rx.pcap
++check ovs-vsctl -- add-port br-int hv2-vif3 \
++ -- set interface hv2-vif3 external-ids:iface-id=lsp4 \
++ options:tx_pcap=hv2/vif3-tx.pcap \
++ options:rxq_pcap=hv2/vif3-rx.pcap
++check ovs-vsctl set open . external-ids:ovn-is-interconn=true
++
++AT_CHECK([ovn-ic-nbctl --wait=sb create Transit_Switch name=ts], [0], [ignore])
++
++ovn_as az1
++check ovn-nbctl lr-add lr1 \
++ -- lrp-add lr1 lr1-ts 00:00:00:01:00:01 42.42.42.1/24 \
++ -- lrp-add lr1 lr1-ls1 00:00:00:01:01:00 43.43.43.1/24 \
++ -- lrp-set-gateway-chassis lr1-ts hv1
++check ovn-nbctl ls-add ls1 \
++ -- lsp-add ls1 ls1-lr1 \
++ -- lsp-set-addresses ls1-lr1 router \
++ -- lsp-set-type ls1-lr1 router \
++ -- lsp-set-options ls1-lr1 router-port=lr1-ls1 \
++ -- lsp-add ls1 lsp1
++check ovn-nbctl lsp-add ts ts-lr1 \
++ -- lsp-set-addresses ts-lr1 router \
++ -- lsp-set-type ts-lr1 router \
++ -- lsp-set-options ts-lr1 router-port=lr1-ts
++wait_for_ports_up
++
++ovn_as az2
++check ovn-nbctl lr-add lr2 \
++ -- lrp-add lr2 lr2-ts 00:00:00:02:00:01 42.42.42.2/24 \
++ -- lrp-add lr2 lr2-ls2 00:00:00:02:01:00 44.44.44.1/24 \
++ -- lrp-set-gateway-chassis lr2-ts hv2
++check ovn-nbctl ls-add ls2 \
++ -- lsp-add ls2 ls2-lr2 \
++ -- lsp-set-addresses ls2-lr2 router \
++ -- lsp-set-type ls2-lr2 router \
++ -- lsp-set-options ls2-lr2 router-port=lr2-ls2 \
++ -- lsp-add ls2 lsp2 \
++ -- lsp-add ls2 lsp4
++check ovn-nbctl lsp-add ts ts-lr2 \
++ -- lsp-set-addresses ts-lr2 router \
++ -- lsp-set-type ts-lr2 router \
++ -- lsp-set-options ts-lr2 router-port=lr2-ts
++
++check ovn-nbctl lr-add lr3 \
++ -- lrp-add lr3 lr3-ts 00:00:00:02:00:02 42.42.42.3/24 \
++ -- lrp-add lr3 lr3-ls3 00:00:00:02:02:00 44.44.45.1/24 \
++ -- lrp-set-gateway-chassis lr3-ts hv2
++check ovn-nbctl ls-add ls3 \
++ -- lsp-add ls3 ls3-lr3 \
++ -- lsp-set-addresses ls3-lr3 router \
++ -- lsp-set-type ls3-lr3 router \
++ -- lsp-set-options ls3-lr3 router-port=lr3-ls3 \
++ -- lsp-add ls3 lsp3
++check ovn-nbctl lsp-add ts ts-lr3 \
++ -- lsp-set-addresses ts-lr3 router \
++ -- lsp-set-type ts-lr3 router \
++ -- lsp-set-options ts-lr3 router-port=lr3-ts
++
++wait_for_ports_up
++check ovn-ic-nbctl --wait=sb sync
++
++ovn_as az1
++check ovn-nbctl lsp-set-options ts-lr2 requested-chassis=hv2
++check ovn-nbctl lsp-set-options ts-lr3 requested-chassis=hv2
++
++ovn_as az2
++check ovn-nbctl lsp-set-options ts-lr1 requested-chassis=hv1
++
++dnl Enable unregistered IP multicast flooding and IP multicast relay.
++ovn_as az1
++check ovn-nbctl set logical_switch ls1 other_config:mcast_snoop="true" \
++ other_config:mcast_flood_unregistered="true"
++check ovn-nbctl set logical_router lr1 options:mcast_relay="true"
++check ovn-nbctl set logical_router_port lr1-ls1 options:mcast_flood="true"
++
++ovn_as az2
++check ovn-nbctl set logical_switch ls2 other_config:mcast_snoop="true" \
++ other_config:mcast_flood_unregistered="true"
++check ovn-nbctl set logical_router lr2 options:mcast_relay="true"
++check ovn-nbctl set logical_router_port lr2-ts options:mcast_flood="true"
++check ovn-nbctl set logical_switch ls3 other_config:mcast_snoop="true" \
++ other_config:mcast_flood_unregistered="true"
++check ovn-nbctl set logical_router lr3 options:mcast_relay="true"
++check ovn-nbctl set logical_router_port lr3-ls3 options:mcast_flood="true"
++
++check ovn_as az1 ovn-nbctl --wait=hv sync
++check ovn_as az2 ovn-nbctl --wait=hv sync
++
++# 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
++
++# Send an IP multicast packet from lsp2, it should be forwarded
++# statically to lsp1, lsp3 and lsp4.
++> expected_az1
++> expected_az2
++> expected_az2_switched
++send_ip_multicast_pkt hv2-vif1 hv2 \
++ 000000000001 01005e000144 \
++ $(ip_to_hex 44 44 44 2) $(ip_to_hex 239 0 1 68) 1e 20 7c6b 11 \
++ e518e518000aed350000
++store_ip_multicast_pkt \
++ 000000010100 01005e000144 \
++ $(ip_to_hex 44 44 44 2) $(ip_to_hex 239 0 1 68) 1e 1e 7e6b 11 \
++ e518e518000aed350000 expected_az1
++store_ip_multicast_pkt \
++ 000000020200 01005e000144 \
++ $(ip_to_hex 44 44 44 2) $(ip_to_hex 239 0 1 68) 1e 1e 7e6b 11 \
++ e518e518000aed350000 expected_az2
++store_ip_multicast_pkt \
++ 000000000001 01005e000144 \
++ $(ip_to_hex 44 44 44 2) $(ip_to_hex 239 0 1 68) 1e 20 7c6b 11 \
++ e518e518000aed350000 expected_az2_switched
++
++OVS_WAIT_UNTIL(
++ [check_packets 'hv1/vif1-tx.pcap expected_az1' \
++ 'hv2/vif2-tx.pcap expected_az2' \
++ 'hv2/vif3-tx.pcap expected_az2_switched'],
++ [$at_diff -F'^---' exp rcv])
++
++# Send an IP multicast packet from lsp2 towards 224.0.0.x, it should be
++# forwarded statically only to lsp3.
++as hv1 reset_pcap_file hv1-vif1 hv1/vif1
++as hv2 reset_pcap_file hv2-vif2 hv2/vif2
++as hv2 reset_pcap_file hv2-vif3 hv2/vif3
++> expected_az1
++> expected_az2
++> expected_az2_switched
++send_ip_multicast_pkt hv2-vif1 hv2 \
++ 000000000001 01005e000144 \
++ $(ip_to_hex 44 44 44 2) $(ip_to_hex 224 0 0 1) 1e 20 8cae 11 \
++ e518e518000aed350000
++store_ip_multicast_pkt \
++ 000000000001 01005e000144 \
++ $(ip_to_hex 44 44 44 2) $(ip_to_hex 224 0 0 1) 1e 20 8cae 11 \
++ e518e518000aed350000 expected_az2_switched
++
++OVS_WAIT_UNTIL(
++ [check_packets 'hv1/vif1-tx.pcap expected_az1' \
++ 'hv2/vif2-tx.pcap expected_az2' \
++ 'hv2/vif3-tx.pcap expected_az2_switched'],
++ [$at_diff -F'^---' exp rcv])
++
++OVN_CLEANUP_SBOX([hv1],["/IGMP Querier enabled without a valid IPv4 or IPv6 address/d
++/IGMP Querier enabled with invalid ETH src address/d"])
++
++OVN_CLEANUP_SBOX([hv2],["/IGMP Querier enabled without a valid IPv4 or IPv6 address/d
++/IGMP Querier enabled with invalid ETH src address/d"])
++
++OVN_CLEANUP_IC([az1],[az2])
++AT_CLEANUP
++])
++
++OVN_FOR_EACH_NORTHD([
++AT_SETUP([interconnection - IGMP/MLD multicast])
++AT_KEYWORDS([IP-multicast])
++
++# Logical network:
++#
++# AZ1 | AZ2
++# ---------------------------------------------------------------------
++# |
++# | +-- LR2 --- LS2 --- LSP2 (sender)
++# | /
++# LSP1 --- LS1 --- LR1 --- TS ---
++# (receiver) | \
++# | +-- LR3 --- LS3 --- LSP3 (receiver)
++#
++# LS1, LS2, LS3, TS configured to snoop IP multicast.
++# LR1, LR2, LR3 configured to relay IP multicast.
++# LR1-TS configured to flood IP multicast traffic unconditionally.
++# LR2-TS configured to flood IP multicast traffic unconditionally.
++# LR3-TS configured to flood IP multicast traffic unconditionally.
++
++AT_CAPTURE_FILE([exp])
++AT_CAPTURE_FILE([rcv])
++check_packets() {
++ > exp
++ > rcv
++ if test "$1" = --uniq; then
++ sort="sort -u"; shift
++ else
++ sort=sort
++ fi
++ for tuple in "$@"; do
++ set $tuple; pcap=$1 type=$2
++ echo "--- $pcap" | tee -a exp >> rcv
++ $sort "$type" >> exp
++ $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $pcap | $sort >> rcv
++ echo | tee -a exp >> rcv
++ done
++
++ $at_diff exp rcv >/dev/null
++}
++
++ovn_init_ic_db
++ovn_start az1
++ovn_start az2
++
++net_add n1
++
++sim_add hv1
++as hv1
++check ovs-vsctl add-br br-phys
++ovn_az_attach az1 n1 br-phys 192.168.1.1 16
++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
++check ovs-vsctl set open . external-ids:ovn-is-interconn=true
++
++sim_add hv2
++as hv2
++check ovs-vsctl add-br br-phys
++ovn_az_attach az2 n1 br-phys 192.168.2.1 16
++check ovs-vsctl -- add-port br-int hv2-vif1 \
++ -- set interface hv2-vif1 external-ids:iface-id=lsp2 \
++ options:tx_pcap=hv2/vif1-tx.pcap \
++ options:rxq_pcap=hv2/vif1-rx.pcap
++check ovs-vsctl -- add-port br-int hv2-vif2 \
++ -- set interface hv2-vif2 external-ids:iface-id=lsp3 \
++ options:tx_pcap=hv2/vif2-tx.pcap \
++ options:rxq_pcap=hv2/vif2-rx.pcap
++check ovs-vsctl set open . external-ids:ovn-is-interconn=true
++
++AT_CHECK([ovn-ic-nbctl --wait=sb create Transit_Switch name=ts], [0], [ignore])
++check ovn_as az1 ovn-nbctl wait-until logical_switch ts
++check ovn_as az2 ovn-nbctl wait-until logical_switch ts
++
++ovn_as az1
++check ovn-nbctl lr-add lr1 \
++ -- lrp-add lr1 lr1-ts 00:00:00:01:00:01 42.42.42.1/24 4242::1/64 \
++ -- lrp-add lr1 lr1-ls1 00:00:00:01:01:00 43.43.43.1/24 4343::1/64\
++ -- lrp-set-gateway-chassis lr1-ts hv1
++check ovn-nbctl ls-add ls1 \
++ -- lsp-add ls1 ls1-lr1 \
++ -- lsp-set-addresses ls1-lr1 router \
++ -- lsp-set-type ls1-lr1 router \
++ -- lsp-set-options ls1-lr1 router-port=lr1-ls1 \
++ -- lsp-add ls1 lsp1
++check ovn-nbctl lsp-add ts ts-lr1 \
++ -- lsp-set-addresses ts-lr1 router \
++ -- lsp-set-type ts-lr1 router \
++ -- lsp-set-options ts-lr1 router-port=lr1-ts
++wait_for_ports_up
++
++ovn_as az2
++check ovn-nbctl lr-add lr2 \
++ -- lrp-add lr2 lr2-ts 00:00:00:02:00:01 42.42.42.2/24 4242::2/64 \
++ -- lrp-add lr2 lr2-ls2 00:00:00:02:01:00 44.44.44.1/24 4444::1/64 \
++ -- lrp-set-gateway-chassis lr2-ts hv2
++check ovn-nbctl ls-add ls2 \
++ -- lsp-add ls2 ls2-lr2 \
++ -- lsp-set-addresses ls2-lr2 router \
++ -- lsp-set-type ls2-lr2 router \
++ -- lsp-set-options ls2-lr2 router-port=lr2-ls2 \
++ -- lsp-add ls2 lsp2
++check ovn-nbctl lsp-add ts ts-lr2 \
++ -- lsp-set-addresses ts-lr2 router \
++ -- lsp-set-type ts-lr2 router \
++ -- lsp-set-options ts-lr2 router-port=lr2-ts
++
++check ovn-nbctl lr-add lr3 \
++ -- lrp-add lr3 lr3-ts 00:00:00:02:00:02 42.42.42.3/24 4242::3/64 \
++ -- lrp-add lr3 lr3-ls3 00:00:00:02:02:00 44.44.45.1/24 4445::1/64 \
++ -- lrp-set-gateway-chassis lr3-ts hv2
++check ovn-nbctl ls-add ls3 \
++ -- lsp-add ls3 ls3-lr3 \
++ -- lsp-set-addresses ls3-lr3 router \
++ -- lsp-set-type ls3-lr3 router \
++ -- lsp-set-options ls3-lr3 router-port=lr3-ls3 \
++ -- lsp-add ls3 lsp3
++check ovn-nbctl lsp-add ts ts-lr3 \
++ -- lsp-set-addresses ts-lr3 router \
++ -- lsp-set-type ts-lr3 router \
++ -- lsp-set-options ts-lr3 router-port=lr3-ts
++
++wait_for_ports_up
++check ovn-ic-nbctl --wait=sb sync
++ovn_as az1
++check ovn-nbctl lsp-set-options ts-lr2 requested-chassis=hv2
++check ovn-nbctl lsp-set-options ts-lr3 requested-chassis=hv2
++
++ovn_as az2
++check ovn-nbctl lsp-set-options ts-lr1 requested-chassis=hv1
++
++dnl Enable IP multicast snooping and IP multicast relay. Reports are
++dnl forwarded statically.
++ovn_as az1
++check ovn-nbctl set logical_switch ls1 other_config:mcast_snoop="true"
++check ovn-nbctl set Logical_Switch_Port ls1-lr1 options:mcast_flood_reports="true"
++check ovn-nbctl set logical_router lr1 options:mcast_relay="true"
++check ovn-nbctl set logical_router_port lr1-ts options:mcast_flood="true"
++check ovn-nbctl set logical_switch ts other_config:mcast_snoop="true"
++check ovn-nbctl set logical_switch_port ts-lr1 options:mcast_flood_reports="true"
++check ovn-nbctl set logical_switch_port ts-lr2 options:mcast_flood_reports="true"
++check ovn-nbctl set logical_switch_port ts-lr3 options:mcast_flood_reports="true"
++
++ovn_as az2
++check ovn-nbctl set logical_switch ls2 other_config:mcast_snoop="true"
++check ovn-nbctl set Logical_Switch_Port ls2-lr2 options:mcast_flood_reports="true"
++check ovn-nbctl set logical_router lr2 options:mcast_relay="true"
++check ovn-nbctl set logical_router_port lr2-ts options:mcast_flood="true"
++check ovn-nbctl set logical_switch ls3 other_config:mcast_snoop="true"
++check ovn-nbctl set Logical_Switch_Port ls3-lr3 options:mcast_flood_reports="true"
++check ovn-nbctl set logical_router lr3 options:mcast_relay="true"
++check ovn-nbctl set logical_router_port lr3-ts options:mcast_flood="true"
++check ovn-nbctl set logical_switch ts other_config:mcast_snoop="true"
++check ovn-nbctl set logical_switch_port ts-lr1 options:mcast_flood_reports="true"
++check ovn-nbctl set logical_switch_port ts-lr2 options:mcast_flood_reports="true"
++check ovn-nbctl set logical_switch_port ts-lr3 options:mcast_flood_reports="true"
++
++check ovn_as az1 ovn-nbctl --wait=hv sync
++check ovn_as az2 ovn-nbctl --wait=hv sync
++
++# 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
++
++# Inject IGMP Join for 239.0.1.68 on LSP1.
++send_igmp_v3_report hv1-vif1 hv1 \
++ 000000000001 $(ip_to_hex 10 0 0 1) f9f8 \
++ $(ip_to_hex 239 0 1 68) 04 e9b9 \
++ /dev/null
++
++# Inject MLD Join for ff0a:dead:beef::1 on LSP1.
++send_mld_v2_report hv1-vif1 hv1 \
++ 000000000001 10000000000000000000000000000001 \
++ ff0adeadbeef00000000000000000001 04 c0e4 \
++ /dev/null
++
++# Inject IGMP Join for 239.0.1.68 on LSP3.
++send_igmp_v3_report hv2-vif2 hv2 \
++ 000000000001 $(ip_to_hex 10 0 0 1) f9f8 \
++ $(ip_to_hex 239 0 1 68) 04 e9b9 \
++ /dev/null
++
++# Inject MLD Join for ff0a:dead:beef::1 on LSP3.
++send_mld_v2_report hv2-vif2 hv2 \
++ 000000000001 10000000000000000000000000000001 \
++ ff0adeadbeef00000000000000000001 04 c0e4 \
++ /dev/null
++
++# Check that the IGMP and MLD groups are learned on both AZs (on the LS
++# and TS).
++ovn_as az1
++wait_row_count IGMP_Group 2 address=239.0.1.68
++wait_row_count IGMP_Group 2 address='"ff0a:dead:beef::1"'
++check ovn-nbctl --wait=hv sync
++
++#Validate that Multicast Group contains all registered ports for
++# specific igmp group.
++ts_dp=$(fetch_column datapath_binding _uuid external_ids:name=ts)
++ports=$(fetch_column multicast_group ports name="239.0.1.68" datapath=$ts_dp)
++check test X2 = X$(echo $ports | wc -w)
++
++
++ovn_as az2
++wait_row_count IGMP_Group 2 address=239.0.1.68
++wait_row_count IGMP_Group 2 address='"ff0a:dead:beef::1"'
++check ovn-nbctl --wait=hv sync
++ts_dp=$(fetch_column datapath_binding _uuid external_ids:name=ts)
++ports=$(fetch_column multicast_group ports name="239.0.1.68" datapath=$ts_dp)
++check test X2 = X$(echo $ports | wc -w)
++
++# Send an IP multicast packet from LSP2, it should be forwarded
++# to lsp1 and lsp3.
++> expected_az1
++> expected_az2
++send_ip_multicast_pkt hv2-vif1 hv2 \
++ 000000000001 01005e000144 \
++ $(ip_to_hex 44 44 44 2) $(ip_to_hex 239 0 1 68) 1e 20 7c6b 11 \
++ e518e518000aed350000
++store_ip_multicast_pkt \
++ 000000010100 01005e000144 \
++ $(ip_to_hex 44 44 44 2) $(ip_to_hex 239 0 1 68) 1e 1e 7e6b 11 \
++ e518e518000aed350000 expected_az1
++store_ip_multicast_pkt \
++ 000000020200 01005e000144 \
++ $(ip_to_hex 44 44 44 2) $(ip_to_hex 239 0 1 68) 1e 1e 7e6b 11 \
++ e518e518000aed350000 expected_az2
++
++send_ip6_multicast_pkt hv2-vif1 hv2 \
++ 000000000001 333300000001 \
++ 00100000000000000000000000000042 ff0adeadbeef00000000000000000001 \
++ 000e 40 11 \
++ 93407a69000e2b4e61736461640a
++store_ip6_multicast_pkt \
++ 000000010100 333300000001 \
++ 00100000000000000000000000000042 ff0adeadbeef00000000000000000001 \
++ 000e 3e 11 \
++ 93407a69000e2b4e61736461640a \
++ expected_az1
++store_ip6_multicast_pkt \
++ 000000020200 333300000001 \
++ 00100000000000000000000000000042 ff0adeadbeef00000000000000000001 \
++ 000e 3e 11 \
++ 93407a69000e2b4e61736461640a \
++ expected_az2
++
++OVS_WAIT_UNTIL(
++ [check_packets 'hv1/vif1-tx.pcap expected_az1' \
++ 'hv2/vif2-tx.pcap expected_az2'],
++ [$at_diff -F'^---' exp rcv])
++
++OVN_CLEANUP_SBOX([hv1], ["/IGMP Querier enabled without a valid IPv4/d
++/IGMP Querier enabled with invalid ETH src/d"])
++
++OVN_CLEANUP_SBOX([hv2], ["/IGMP Querier enabled without a valid IPv4/d
++/IGMP Querier enabled with invalid ETH src/d"])
++
++OVN_CLEANUP_IC([az1],[az2])
++AT_CLEANUP
++])
+diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at
+index d18c45e41..5b1e37d8a 100644
+--- a/tests/ovn-macros.at
++++ b/tests/ovn-macros.at
+@@ -48,12 +48,15 @@ m4_define([OVN_CLEANUP_SBOX],[
+ # "has no network name*" as localnet network_name is often set "after" creating localnet.
+ # "receive tunnel port not found*" happening when closing other hv.
+ # "Failed to locate tunnel to reach main chassis" happening when controller started e.g. before ovn-bridge-mappings is set
++ # "Transaction causes multiple rows in \"MAC_Binding\" table to have identical values"
++ # happening as ovn-controller has a race condition over MAC binding table with other controllers (GARP).
+ AT_CHECK([check_logs "
+ $error
+ /connection failed (No such file or directory)/d
+ /has no network name*/d
+ /receive tunnel port not found*/d
+ /Failed to locate tunnel to reach main chassis/d
++ /Transaction causes multiple rows.*MAC_Binding/d
+ " $sbox])
+ ])
+
+@@ -217,12 +220,14 @@ ovn_start_northd() {
+ # options are accepted to adjust that:
+ # --backup-northd Start a backup northd.
+ # --backup-northd=paused Start the backup northd in the paused state.
++# --use-tcp-to-sb Use tcp to connect to sb.
+ ovn_start () {
+ local backup_northd=false
+ local backup_northd_options=
+ case $1 in
+ --backup-northd) backup_northd=true; shift ;;
+ --backup-northd=paused) backup_northd=true; backup_northd_options=--paused; shift ;;
++ --use-tcp-to-sb) use_tcp=true; shift ;;
+ esac
+ local AZ=$1
+ local msg_prefix=${AZ:+$AZ: }
+@@ -243,7 +248,13 @@ ovn_start () {
+ ovn_start_northd $backup_northd_options backup $AZ
+ fi
+
+- if test X$HAVE_OPENSSL = Xyes; then
++ if test $use_tcp; then
++ # Create the SB DB ptcp connection.
++ ovn-sbctl \
++ -- --id=@c create connection \
++ target=\"ptcp:0:127.0.0.1\" \
++ -- add SB_Global . connections @c
++ elif test X$HAVE_OPENSSL = Xyes; then
+ # Create the SB DB pssl+RBAC connection.
+ ovn-sbctl \
+ -- --id=@c create connection \
+@@ -970,6 +981,22 @@ wake_up_ovsdb() {
+ AT_CHECK([kill -CONT $(cat $1/ovsdb-server.pid)])
+ }
+
++stop_ovsdb_controller_updates() {
++ TCP_PORT=$1
++ echo Stopping updates from ovn-controller to ovsdb using port $TCP_PORT
++ on_exit 'nft list tables | grep ovn-test && nft delete table ip ovn-test'
++ nft add table ip ovn-test
++ nft 'add chain ip ovn-test INPUT { type filter hook input priority 0; policy accept; }'
++ nft add rule ip ovn-test INPUT tcp dport $TCP_PORT counter drop
++}
++
++restart_ovsdb_controller_updates() {
++ TCP_PORT=$1
++ echo Restarting updates from ovn-controller to ovsdb
++ nft list ruleset | grep $TCP_PORT
++ nft delete table ip ovn-test
++}
++
+ trim_zeros() {
+ sed 's/\(00\)\{1,\}$//'
+ }
+diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
+index 9a5f3ae8a..3576594e3 100644
+--- a/tests/ovn-northd.at
++++ b/tests/ovn-northd.at
+@@ -1107,7 +1107,8 @@ ovn_start
+ #
+ # DR is connected to S1 and CR is connected to S2
+
+-check ovn-sbctl chassis-add gw1 geneve 127.0.0.1
++check ovn-sbctl chassis-add gw1 geneve 127.0.0.1 \
++ -- set chassis gw1 other_config:ct-commit-to-zone="true"
+
+ check ovn-nbctl lr-add DR
+ check ovn-nbctl lrp-add DR DR-S1 02:ac:10:01:00:01 172.16.1.1/24
+@@ -1155,15 +1156,24 @@ AT_CAPTURE_FILE([crflows])
+ AT_CHECK([grep -e "lr_out_snat" drflows | sed 's/table=../table=??/' | sort], [0], [dnl
+ table=??(lr_out_snat ), priority=0 , match=(1), action=(next;)
+ table=??(lr_out_snat ), priority=120 , match=(nd_ns), action=(next;)
++ table=??(lr_out_snat ), priority=161 , match=(ip && ip4.dst == 50.0.0.11 && inport == "DR-S1" && is_chassis_resident("cr-DR-S1") && ip4.src == $allowed_range), action=(ct_snat;)
+ table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && ip4.dst == $allowed_range && (!ct.trk || !ct.rpl)), action=(ct_snat(172.16.1.1);)
+ ])
+
++AT_CHECK([grep -e "lr_out_post_snat" drflows | sed 's/table=../table=??/' | sort], [0], [dnl
++ table=??(lr_out_post_snat ), priority=0 , match=(1), action=(next;)
++ table=??(lr_out_post_snat ), priority=161 , match=(ip && ip4.dst == 50.0.0.11 && inport == "DR-S1" && is_chassis_resident("cr-DR-S1") && ip4.src == $allowed_range && ct.new), action=(ct_commit_to_zone(snat);)
++])
++
+ AT_CHECK([grep -e "lr_out_snat" crflows | sed 's/table=../table=??/' | sort], [0], [dnl
+ table=??(lr_out_snat ), priority=0 , match=(1), action=(next;)
+ table=??(lr_out_snat ), priority=120 , match=(nd_ns), action=(next;)
+ table=??(lr_out_snat ), priority=33 , match=(ip && ip4.src == 50.0.0.11 && ip4.dst == $allowed_range && (!ct.trk || !ct.rpl)), action=(ct_snat(172.16.1.1);)
+ ])
+
++AT_CHECK([grep -e "lr_out_post_snat" crflows | sed 's/table=../table=??/' | sort], [0], [dnl
++ table=??(lr_out_post_snat ), priority=0 , match=(1), action=(next;)
++])
+
+ # SNAT with DISALLOWED_IPs
+ check ovn-nbctl lr-nat-del DR snat 50.0.0.11
+@@ -1185,10 +1195,16 @@ AT_CAPTURE_FILE([crflows2])
+ AT_CHECK([grep -e "lr_out_snat" drflows2 | sed 's/table=../table=??/' | sort], [0], [dnl
+ table=??(lr_out_snat ), priority=0 , match=(1), action=(next;)
+ table=??(lr_out_snat ), priority=120 , match=(nd_ns), action=(next;)
++ table=??(lr_out_snat ), priority=161 , match=(ip && ip4.dst == 50.0.0.11 && inport == "DR-S1" && is_chassis_resident("cr-DR-S1")), action=(ct_snat;)
+ table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.16.1.1);)
+ table=??(lr_out_snat ), priority=163 , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && ip4.dst == $disallowed_range), action=(next;)
+ ])
+
++AT_CHECK([grep -e "lr_out_post_snat" drflows2 | sed 's/table=../table=??/' | sort], [0], [dnl
++ table=??(lr_out_post_snat ), priority=0 , match=(1), action=(next;)
++ table=??(lr_out_post_snat ), priority=161 , match=(ip && ip4.dst == 50.0.0.11 && inport == "DR-S1" && is_chassis_resident("cr-DR-S1") && ct.new), action=(ct_commit_to_zone(snat);)
++])
++
+ AT_CHECK([grep -e "lr_out_snat" crflows2 | sed 's/table=../table=??/' | sort], [0], [dnl
+ table=??(lr_out_snat ), priority=0 , match=(1), action=(next;)
+ table=??(lr_out_snat ), priority=120 , match=(nd_ns), action=(next;)
+@@ -1196,6 +1212,10 @@ AT_CHECK([grep -e "lr_out_snat" crflows2 | sed 's/table=../table=??/' | sort], [
+ table=??(lr_out_snat ), priority=35 , match=(ip && ip4.src == 50.0.0.11 && ip4.dst == $disallowed_range), action=(next;)
+ ])
+
++AT_CHECK([grep -e "lr_out_post_snat" crflows2 | sed 's/table=../table=??/' | sort], [0], [dnl
++ table=??(lr_out_post_snat ), priority=0 , match=(1), action=(next;)
++])
++
+ # Stateful FIP with ALLOWED_IPs
+ check ovn-nbctl lr-nat-del DR snat 50.0.0.11
+ check ovn-nbctl lr-nat-del CR snat 50.0.0.11
+@@ -1217,12 +1237,20 @@ AT_CHECK([grep -e "lr_out_snat" drflows3 | sed 's/table=../table=??/' | sort], [
+ table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && ip4.dst == $allowed_range && (!ct.trk || !ct.rpl)), action=(ct_snat(172.16.1.2);)
+ ])
+
++AT_CHECK([grep -e "lr_out_post_snat" drflows3 | sed 's/table=../table=??/' | sort], [0], [dnl
++ table=??(lr_out_post_snat ), priority=0 , match=(1), action=(next;)
++])
++
+ AT_CHECK([grep -e "lr_out_snat" crflows3 | sed 's/table=../table=??/' | sort], [0], [dnl
+ table=??(lr_out_snat ), priority=0 , match=(1), action=(next;)
+ table=??(lr_out_snat ), priority=120 , match=(nd_ns), action=(next;)
+ table=??(lr_out_snat ), priority=33 , match=(ip && ip4.src == 50.0.0.11 && ip4.dst == $allowed_range && (!ct.trk || !ct.rpl)), action=(ct_snat(172.16.1.2);)
+ ])
+
++AT_CHECK([grep -e "lr_out_post_snat" crflows3 | sed 's/table=../table=??/' | sort], [0], [dnl
++ table=??(lr_out_post_snat ), priority=0 , match=(1), action=(next;)
++])
++
+ # Stateful FIP with DISALLOWED_IPs
+ ovn-nbctl lr-nat-del DR dnat_and_snat 172.16.1.2
+ ovn-nbctl lr-nat-del CR dnat_and_snat 172.16.1.2
+@@ -1279,7 +1307,7 @@ AT_CHECK([grep -e "lr_out_snat" crflows5 | sed 's/table=../table=??/' | sort], [
+ table=??(lr_out_snat ), priority=33 , match=(ip && ip4.src == 50.0.0.11 && ip4.dst == $allowed_range), action=(ip4.src=172.16.1.2; next;)
+ ])
+
+-# Stateful FIP with DISALLOWED_IPs
++# Stateless FIP with DISALLOWED_IPs
+ ovn-nbctl lr-nat-del DR dnat_and_snat 172.16.1.2
+ ovn-nbctl lr-nat-del CR dnat_and_snat 172.16.1.2
+
+@@ -2822,6 +2850,32 @@ AT_CHECK([test $lsp02 = 3 && test $ls1 = 123])
+ AT_CLEANUP
+ ])
+
++OVN_FOR_EACH_NORTHD_NO_HV([
++AT_SETUP([check tunnel ids exhaustion])
++ovn_start
++
++# Create a fake chassis with vxlan encap to lower MAX DP tunnel key to 2^12
++ovn-sbctl \
++ --id=@e create encap chassis_name=hv1 ip="192.168.0.1" type="vxlan" \
++ -- --id=@c create chassis name=hv1 encaps=@e
++
++cmd="ovn-nbctl --wait=sb"
++
++for i in {1..4097}; do
++ cmd="${cmd} -- ls-add lsw-${i}"
++done
++
++eval $cmd
++
++check_row_count nb:Logical_Switch 4097
++wait_row_count sb:Datapath_Binding 4095
++
++OVS_WAIT_UNTIL([grep "all datapath tunnel ids exhausted" northd/ovn-northd.log])
++
++AT_CLEANUP
++])
++
++
+ OVN_FOR_EACH_NORTHD_NO_HV([
+ AT_SETUP([Logical Flow Datapath Groups])
+ ovn_start
+@@ -5052,6 +5106,18 @@ check ovn-nbctl --wait=sb lrp-del-gateway-chassis ro2-sw hv2
+
+ check_lflows 0
+
++AS_BOX([Checking that NAT flows are installed for gw routers])
++
++check ovn-nbctl set logical_router ro1 options:chassis=hv1
++check ovn-nbctl --wait=sb set logical_router ro2 options:chassis=hv2
++
++check_lflows 1
++
++check ovn-nbctl clear logical_router ro1 options
++check ovn-nbctl --wait=sb clear logical_router ro2 options
++
++check_lflows 0
++
+ AS_BOX([Checking that NAT flows are installed for routers with HA_Chassis_Group])
+
+ check ovn-nbctl set logical_router_port ro1-sw ha_chassis_group="$grp1_uuid"
+@@ -5483,7 +5549,8 @@ AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [
+
+ check ovn-sbctl chassis-add gw1 geneve 127.0.0.1 \
+ -- set chassis gw1 other_config:ct-no-masked-label="true" \
+- -- set chassis gw1 other_config:ovn-ct-lb-related="true"
++ -- set chassis gw1 other_config:ovn-ct-lb-related="true" \
++ -- set chassis gw1 other_config:ct-commit-to-zone="true"
+
+ # Create a distributed gw port on lr0
+ check ovn-nbctl ls-add public
+@@ -5582,16 +5649,26 @@ AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0],
+ table=? (lr_out_undnat ), priority=100 , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_dnat;)
+ ])
+
+-AT_CHECK([grep "lr_out_post_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
+- table=? (lr_out_post_undnat ), priority=0 , match=(1), action=(next;)
++AT_CHECK([grep "lr_out_post_undnat" lr0flows | sed 's/table=../table=??/' | sort], [0], [dnl
++ table=??(lr_out_post_undnat ), priority=0 , match=(1), action=(next;)
++ table=??(lr_out_post_undnat ), priority=70 , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat;)
++ table=??(lr_out_post_undnat ), priority=70 , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat;)
+ ])
+
+-AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
+- table=? (lr_out_snat ), priority=0 , match=(1), action=(next;)
+- table=? (lr_out_snat ), priority=120 , match=(nd_ns), action=(next;)
+- table=? (lr_out_snat ), priority=153 , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.10);)
+- table=? (lr_out_snat ), priority=161 , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.30);)
+- table=? (lr_out_snat ), priority=161 , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.20);)
++AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=../table=??/' | sort], [0], [dnl
++ table=??(lr_out_snat ), priority=0 , match=(1), action=(next;)
++ table=??(lr_out_snat ), priority=120 , match=(nd_ns), action=(next;)
++ table=??(lr_out_snat ), priority=153 , match=(ip && ip4.dst == 10.0.0.0/24 && inport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat;)
++ table=??(lr_out_snat ), priority=153 , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.10);)
++ table=??(lr_out_snat ), priority=161 , match=(ip && ip4.dst == 10.0.0.10 && inport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat;)
++ table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.30);)
++ table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.20);)
++])
++
++AT_CHECK([grep "lr_out_post_snat" lr0flows | sed 's/table=../table=??/' | sort], [0], [dnl
++ table=??(lr_out_post_snat ), priority=0 , match=(1), action=(next;)
++ table=??(lr_out_post_snat ), priority=153 , match=(ip && ip4.dst == 10.0.0.0/24 && inport == "lr0-public" && is_chassis_resident("cr-lr0-public") && ct.new), action=(ct_commit_to_zone(snat);)
++ table=??(lr_out_post_snat ), priority=161 , match=(ip && ip4.dst == 10.0.0.10 && inport == "lr0-public" && is_chassis_resident("cr-lr0-public") && ct.new), action=(ct_commit_to_zone(snat);)
+ ])
+
+ # Associate load balancer to lr0
+@@ -5730,16 +5807,26 @@ AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0],
+ table=? (lr_out_undnat ), priority=120 , match=(ip4 && ((ip4.src == 10.0.0.80) || (ip4.src == 10.0.0.81)) && (inport == "lr0-public" || outport == "lr0-public") && is_chassis_resident("cr-lr0-public")), action=(ct_dnat;)
+ ])
+
+-AT_CHECK([grep "lr_out_post_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
+- table=? (lr_out_post_undnat ), priority=0 , match=(1), action=(next;)
++AT_CHECK([grep "lr_out_post_undnat" lr0flows | sed 's/table=../table=??/' | sort], [0], [dnl
++ table=??(lr_out_post_undnat ), priority=0 , match=(1), action=(next;)
++ table=??(lr_out_post_undnat ), priority=70 , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat;)
++ table=??(lr_out_post_undnat ), priority=70 , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat;)
+ ])
+
+-AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
+- table=? (lr_out_snat ), priority=0 , match=(1), action=(next;)
+- table=? (lr_out_snat ), priority=120 , match=(nd_ns), action=(next;)
+- table=? (lr_out_snat ), priority=153 , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.10);)
+- table=? (lr_out_snat ), priority=161 , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.30);)
+- table=? (lr_out_snat ), priority=161 , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.20);)
++AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=../table=??/' | sort], [0], [dnl
++ table=??(lr_out_snat ), priority=0 , match=(1), action=(next;)
++ table=??(lr_out_snat ), priority=120 , match=(nd_ns), action=(next;)
++ table=??(lr_out_snat ), priority=153 , match=(ip && ip4.dst == 10.0.0.0/24 && inport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat;)
++ table=??(lr_out_snat ), priority=153 , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.10);)
++ table=??(lr_out_snat ), priority=161 , match=(ip && ip4.dst == 10.0.0.10 && inport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat;)
++ table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.30);)
++ table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.20);)
++])
++
++AT_CHECK([grep "lr_out_post_snat" lr0flows | sed 's/table=../table=??/' | sort], [0], [dnl
++ table=??(lr_out_post_snat ), priority=0 , match=(1), action=(next;)
++ table=??(lr_out_post_snat ), priority=153 , match=(ip && ip4.dst == 10.0.0.0/24 && inport == "lr0-public" && is_chassis_resident("cr-lr0-public") && ct.new), action=(ct_commit_to_zone(snat);)
++ table=??(lr_out_post_snat ), priority=161 , match=(ip && ip4.dst == 10.0.0.10 && inport == "lr0-public" && is_chassis_resident("cr-lr0-public") && ct.new), action=(ct_commit_to_zone(snat);)
+ ])
+
+ # Make the logical router as Gateway router
+@@ -6893,6 +6980,9 @@ ct_dnat /* assuming no un-dnat entry, so no change */ {
+ arp.op = 1;
+ output("rtr-ls");
+ };
++ ct_dnat /* assuming no un-dnat entry, so no change */ {
++ output("rtr-ls");
++ };
+ };
+ };
+ ])
+@@ -7526,9 +7616,14 @@ ovn_start
+ # Validate SNAT, DNAT and DNAT_AND_SNAT behavior with multiple
+ # distributed gateway LRPs.
+
+-check ovn-sbctl chassis-add gw1 geneve 127.0.0.1
+-check ovn-sbctl chassis-add gw2 geneve 128.0.0.1
+-check ovn-sbctl chassis-add gw3 geneve 129.0.0.1
++check ovn-sbctl chassis-add gw1 geneve 127.0.0.1 \
++ -- set chassis gw1 other_config:ct-commit-to-zone="true"
++
++check ovn-sbctl chassis-add gw2 geneve 128.0.0.1 \
++ -- set chassis gw2 other_config:ct-commit-to-zone="true"
++
++check ovn-sbctl chassis-add gw3 geneve 129.0.0.1 \
++ -- set chassis gw3 other_config:ct-commit-to-zone="true"
+
+ check ovn-nbctl lr-add DR
+ check ovn-nbctl lrp-add DR DR-S1 02:ac:10:01:00:01 172.16.1.1/24
+@@ -7593,11 +7688,21 @@ AT_CHECK([grep lr_in_unsnat lrflows | grep ct_snat | sed 's/table=../table=??/'
+ ])
+
+ AT_CHECK([grep lr_out_snat lrflows | grep ct_snat | sed 's/table=../table=??/' | sort], [0], [dnl
++ table=??(lr_out_snat ), priority=161 , match=(ip && ip4.dst == 20.0.0.10 && inport == "DR-S1" && is_chassis_resident("cr-DR-S1")), action=(ct_snat;)
++ table=??(lr_out_snat ), priority=161 , match=(ip && ip4.dst == 20.0.0.10 && inport == "DR-S2" && is_chassis_resident("cr-DR-S2")), action=(ct_snat;)
++ table=??(lr_out_snat ), priority=161 , match=(ip && ip4.dst == 20.0.0.10 && inport == "DR-S3" && is_chassis_resident("cr-DR-S3")), action=(ct_snat;)
+ table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.16.1.10);)
+ table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S2" && is_chassis_resident("cr-DR-S2") && (!ct.trk || !ct.rpl)), action=(ct_snat(10.0.0.10);)
+ table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S3" && is_chassis_resident("cr-DR-S3") && (!ct.trk || !ct.rpl)), action=(ct_snat(192.168.0.10);)
+ ])
+
++AT_CHECK([grep lr_out_post_snat lrflows | sed 's/table=../table=??/' | sort], [0], [dnl
++ table=??(lr_out_post_snat ), priority=0 , match=(1), action=(next;)
++ table=??(lr_out_post_snat ), priority=161 , match=(ip && ip4.dst == 20.0.0.10 && inport == "DR-S1" && is_chassis_resident("cr-DR-S1") && ct.new), action=(ct_commit_to_zone(snat);)
++ table=??(lr_out_post_snat ), priority=161 , match=(ip && ip4.dst == 20.0.0.10 && inport == "DR-S2" && is_chassis_resident("cr-DR-S2") && ct.new), action=(ct_commit_to_zone(snat);)
++ table=??(lr_out_post_snat ), priority=161 , match=(ip && ip4.dst == 20.0.0.10 && inport == "DR-S3" && is_chassis_resident("cr-DR-S3") && ct.new), action=(ct_commit_to_zone(snat);)
++])
++
+ check ovn-nbctl --wait=sb lr-nat-del DR snat 20.0.0.10
+ AT_CHECK([ovn-sbctl dump-flows DR | grep -e lr_in_unsnat -e lr_out_snat | grep ct_snat | wc -l], [0], [0
+ ])
+@@ -11344,6 +11449,13 @@ check_engine_stats lflow recompute nocompute
+ check_engine_stats sync_to_sb_lb recompute compute
+ CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
++check ovn-nbctl --wait=sb lr-add lr2 -- set logical_router lr2 enabled=false
++check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
++check ovn-nbctl --wait=sb lb-add lb4 10.0.0.40:4040 10.0.40.40:4050 \
++ -- lr-lb-add lr2 lb4
++check_engine_stats northd recompute nocompute
++CHECK_NO_CHANGE_AFTER_RECOMPUTE
++
+ AT_CLEANUP
+ ])
+
+diff --git a/tests/ovn.at b/tests/ovn.at
+index 01df0a16d..c36fadb90 100644
+--- a/tests/ovn.at
++++ b/tests/ovn.at
+@@ -1305,6 +1305,22 @@ ct_commit { ct_label=0x181716151413121110090807060504030201; };
+ ct_commit { ip4.dst = 192.168.0.1; };
+ Field ip4.dst is not modifiable.
+
++# ct_commit_to_zone
++ct_commit_to_zone(dnat);
++ encodes as ct(commit,table=19,zone=NXM_NX_REG13[0..15])
++ has prereqs ip
++ct_commit_to_zone(snat);
++ encodes as ct(commit,table=19,zone=NXM_NX_REG13[0..15])
++ has prereqs ip
++ct_commit_to_zone;
++ Syntax error at `;' expecting `('.
++ct_commit_to_zone();
++ "ct_commit_to_zone" action accepts only "dnat" or "snat" parameter.
++ct_commit_to_zone(foo);
++ "ct_commit_to_zone" action accepts only "dnat" or "snat" parameter.
++ct_commit_to_zone(dnat;
++ Syntax error at `;' expecting `)'.
++
+ # Legact ct_commit_v1 action.
+ ct_commit();
+ formats as ct_commit;
+@@ -1529,11 +1545,11 @@ clone { ip4.dst = 255.255.255.255; output; }; next;
+
+ # arp
+ arp { eth.dst = ff:ff:ff:ff:ff:ff; output; }; output;
+- encodes as controller(userdata=00.00.00.00.00.00.00.00.00.19.00.10.80.00.06.06.ff.ff.ff.ff.ff.ff.00.00.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00),resubmit(,64)
++ encodes as controller(userdata=00.00.00.00.00.00.00.00.00.19.00.10.80.00.06.06.ff.ff.ff.ff.ff.ff.00.00.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00,pause),resubmit(,64)
+ has prereqs ip4
+ arp { };
+ formats as arp { drop; };
+- encodes as controller(userdata=00.00.00.00.00.00.00.00)
++ encodes as controller(userdata=00.00.00.00.00.00.00.00,pause)
+ has prereqs ip4
+
+ # get_arp
+@@ -1663,12 +1679,12 @@ reg1[0] = put_dhcp_opts(offerip=1.2.3.4, domain_search_list=1.2.3.4);
+
+ # nd_ns
+ nd_ns { nd.target = xxreg0; output; };
+- encodes as controller(userdata=00.00.00.09.00.00.00.00.00.1c.00.18.00.80.00.00.00.00.00.00.00.01.de.10.80.00.3e.10.00.00.00.00.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00)
++ encodes as controller(userdata=00.00.00.09.00.00.00.00.00.1c.00.18.00.80.00.00.00.00.00.00.00.01.de.10.80.00.3e.10.00.00.00.00.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00,pause)
+ has prereqs ip6
+
+ nd_ns { };
+ formats as nd_ns { drop; };
+- encodes as controller(userdata=00.00.00.09.00.00.00.00)
++ encodes as controller(userdata=00.00.00.09.00.00.00.00,pause)
+ has prereqs ip6
+
+ # nd_na
+@@ -1836,6 +1852,9 @@ log(name="test1", verdict=drop, severity=info, meter="meter1");
+ log(verdict=drop);
+ formats as log(verdict=drop, severity=info);
+ encodes as controller(userdata=00.00.00.07.00.00.00.00.01.06)
++log(verdict=pass);
++ formats as log(verdict=pass, severity=info);
++ encodes as controller(userdata=00.00.00.07.00.00.00.00.03.06)
+ log(verdict=bad_verdict, severity=info);
+ Syntax error at `bad_verdict' unknown verdict.
+ log(verdict=drop, severity=bad_severity);
+@@ -26272,692 +26291,6 @@ OVN_CLEANUP([hv1])
+ AT_CLEANUP
+ ])
+
+-OVN_FOR_EACH_NORTHD([
+-AT_SETUP([interconnection])
+-AT_KEYWORDS([slowtest])
+-
+-ovn_init_ic_db
+-# The number needs to stay relatively low due to high memory consumption
+-# with address sanitizers enabled.
+-n_az=3
+-n_ts=3
+-for i in `seq 1 $n_az`; do
+- ovn_start az$i
+-done
+-
+-net_add n1
+-
+-# 1 HV and 1 GW per AZ
+-for az in `seq 1 $n_az`; do
+- sim_add hv$az
+- as hv$az
+- check ovs-vsctl add-br br-phys
+- ovn_az_attach az$az n1 br-phys 192.168.$az.1 16
+- for p in `seq 1 $n_ts`; do
+- check ovs-vsctl -- add-port br-int vif$p -- \
+- set interface vif$p external-ids:iface-id=lsp$az-$p \
+- options:tx_pcap=hv$az/vif$p-tx.pcap \
+- options:rxq_pcap=hv$az/vif$p-rx.pcap \
+- ofport-request=$p
+- done
+-
+- sim_add gw$az
+- as gw$az
+- check ovs-vsctl add-br br-phys
+- ovn_az_attach az$az n1 br-phys 192.168.$az.2 16
+- check ovs-vsctl set open . external-ids:ovn-is-interconn=true
+-done
+-
+-for ts in `seq 1 $n_ts`; do
+- AT_CHECK([ovn-ic-nbctl create Transit_Switch name=ts$ts], [0], [ignore])
+- for az in `seq 1 $n_az`; do
+- echo "az$az: wait for ts$ts..."
+- check ovn_as az$az ovn-nbctl wait-until logical_switch ts$ts
+- done
+-done
+-
+-for az in `seq 1 $n_az`; do
+- ovn_as az$az
+- check ovn-nbctl set nb_global . options:ic-route-learn=true
+- check ovn-nbctl set nb_global . options:ic-route-adv=true
+-
+- # Each AZ has n_ts LSPi->LSi->LRi connecting to each TSi
+- echo
+- echo "az$az"
+- for i in `seq 1 $n_ts`; do
+- lsp_mac=00:00:00:0$az:0$i:00
+- lrp_ls_mac=00:00:00:0$az:0$i:01
+- lrp_ts_mac=00:00:00:0$az:0$i:02
+- lsp_ip=10.$az.$i.123
+- lrp_ls_ip=10.$az.$i.1
+- lrp_ts_ip=169.254.$i.$az
+-
+- check ovn-nbctl ls-add ls$az-$i
+- check ovn-nbctl lsp-add ls$az-$i lsp$az-$i
+- check ovn-nbctl lsp-set-addresses lsp$az-$i "$lsp_mac $lsp_ip"
+-
+- check ovn-nbctl lr-add lr$az-$i
+-
+- check ovn-nbctl lrp-add lr$az-$i lrp-lr$az-$i-ls$az-$i $lrp_ls_mac $lrp_ls_ip/24
+- check ovn-nbctl lsp-add ls$az-$i lsp-ls$az-$i-lr$az-$i
+- check ovn-nbctl lsp-set-addresses lsp-ls$az-$i-lr$az-$i router
+- check ovn-nbctl lsp-set-type lsp-ls$az-$i-lr$az-$i router
+- check ovn-nbctl lsp-set-options lsp-ls$az-$i-lr$az-$i router-port=lrp-lr$az-$i-ls$az-$i
+-
+- check ovn-nbctl lrp-add lr$az-$i lrp-lr$az-$i-ts$i $lrp_ts_mac $lrp_ts_ip/24
+- check ovn-nbctl lsp-add ts$i lsp-ts$i-lr$az-$i
+- check ovn-nbctl lsp-set-addresses lsp-ts$i-lr$az-$i router
+- check ovn-nbctl lsp-set-type lsp-ts$i-lr$az-$i router
+- check ovn-nbctl lsp-set-options lsp-ts$i-lr$az-$i router-port=lrp-lr$az-$i-ts$i
+- check ovn-nbctl lrp-set-gateway-chassis lrp-lr$az-$i-ts$i gw$az
+- done
+- check ovn-nbctl --wait=hv sync
+- ovn-sbctl list Port_Binding > az$az.ports
+- wait_for_ports_up
+-done
+-
+-# 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
+-
+-for i in `seq 1 $n_az`; do
+- check ovn_as az$i ovn-nbctl --wait=hv sync
+- ovn_as az$i ovn-sbctl dump-flows > az$i/sbflows
+-done
+-
+-# Allow some time for ovn-northd and ovn-controller to catch up.
+-# XXX This should be more systematic.
+-sleep 2
+-
+-# Populate requested-chassis options for remote lsps
+-for az in $(seq 1 $n_az); do
+- ovn_as az${az}
+- for ts in $(seq 1 $n_ts); do
+- for i in $(seq 1 $n_ts); do
+- if [[ $i -eq ${az} ]]; then
+- continue
+- fi
+- check ovn-nbctl lsp-set-options lsp-ts${ts}-lr${i}-${ts} requested-chassis=gw$i
+- done
+- done
+-done
+-
+-ovn-ic-nbctl show > ic-nbctl.dump
+-AT_CAPTURE_FILE([ic-nbctl.dump])
+-
+-(echo "---------ISB dump-----"
+- ovn-ic-sbctl show
+- echo "---------------------"
+- ovn-ic-sbctl list gateway
+- echo "---------------------"
+- ovn-ic-sbctl list datapath_binding
+- echo "---------------------"
+- ovn-ic-sbctl list port_binding
+- echo "---------------------"
+- ovn-ic-sbctl list route
+- echo "---------------------") > ic-sbctl.dump
+-AT_CAPTURE_FILE([ic-sbctl.dump])
+-
+-AT_CAPTURE_FILE([expected])
+-AT_CAPTURE_FILE([received])
+-check_packets() {
+- > expected
+- > received
+- for az in `seq 1 $n_az`; do
+- for i in `seq 1 $n_ts`; do
+- pcap=hv$az/vif$i-tx.pcap
+- echo "--- $pcap" | tee -a expected >> received
+- if test -e $az-$i.expected; then
+- sort $az-$i.expected >> expected
+- fi
+- if test -e $pcap; then
+- $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $pcap | sort >> received
+- fi
+- echo | tee -a expected >> received
+- done
+- done
+-
+- $at_diff expected received >/dev/null
+-}
+-
+-# Send packets between AZs on each TS
+-for s_az in $(seq 1 $n_az); do
+- ovn_as az${s_az}
+- as hv${s_az}
+- for d_az in $(seq 1 $n_az); do
+- if test $s_az = $d_az; then
+- continue
+- fi
+-
+- for i in $(seq 1 $n_ts); do
+- echo
+- AS_BOX([packet from az$s_az to az$d_az via ts$i])
+- lsp_smac=00:00:00:0${s_az}:0$i:00
+- lsp_dmac=00:00:00:0${d_az}:0$i:00
+- lrp_ls_smac=00:00:00:0${s_az}:0$i:01
+- lrp_ls_dmac=00:00:00:0${d_az}:0$i:01
+- lsp_sip=10.${s_az}.$i.123
+- lsp_dip=10.${d_az}.$i.123
+-
+- ovn_inport=lsp${s_az}-$i
+- packet="inport==\"$ovn_inport\" && eth.src==$lsp_smac && eth.dst==$lrp_ls_smac &&
+- ip4 && ip.ttl==64 && ip4.src==$lsp_sip && ip4.dst==$lsp_dip &&
+- udp && udp.src==53 && udp.dst==4369"
+- echo "sending: $packet"
+- AT_CHECK([ovn_trace --ovs "$packet" > ${s_az}-${d_az}-$i.ovn-trace])
+- OVS_WAIT_UNTIL([ovs-appctl -t ovn-controller inject-pkt "$packet"])
+- ovs_inport=$(ovs-vsctl --bare --columns=ofport find Interface external-ids:iface-id="$ovn_inport")
+-
+- ovs_packet=$(echo $packet | ovstest test-ovn expr-to-packets)
+- echo ovs_inport=$ovs_inport ovs_packet=$ovs_packet
+- AT_CHECK([ovs-appctl ofproto/trace br-int in_port="$ovs_inport" "$ovs_packet" > ${s_az}-${d_az}-$i.ovs-trace])
+-
+- # Packet to Expect
+- # The TTL should be decremented by 2.
+- packet="eth.src==$lrp_ls_dmac && eth.dst==$lsp_dmac &&
+- ip4 && ip.ttl==62 && ip4.src==$lsp_sip && ip4.dst==$lsp_dip &&
+- udp && udp.src==53 && udp.dst==4369"
+- echo $packet | ovstest test-ovn expr-to-packets >> ${d_az}-$i.expected
+- done
+- done
+-done
+-OVS_WAIT_UNTIL([check_packets], [$at_diff -F'^---' expected received])
+-
+-for az in `seq 1 $n_az`; do
+- OVN_CLEANUP_SBOX([hv$az])
+- OVN_CLEANUP_SBOX([gw$az])
+- OVN_CLEANUP_AZ([az$az])
+-done
+-
+-OVN_CLEANUP_IC
+-
+-AT_CLEANUP
+-])
+-
+-OVN_FOR_EACH_NORTHD([
+-AT_SETUP([interconnection - static multicast])
+-
+-# Logical network:
+-#
+-# AZ1 | AZ2
+-# ---------------------------------------------------------------------
+-# |
+-# | +-- LR2 --- LS2 --- LSP2 (sender)
+-# | | |
+-# | | +----- LSP4 (receiver)
+-# | /
+-# LSP1 --- LS1 --- LR1 --- TS ---
+-# (receiver) | \
+-# | +-- LR3 --- LS3 --- LSP3 (receiver)
+-#
+-# LS1, LS2, LS3 configured to flood unregistered IP multicast.
+-# LR1, LR2, LR3 configured to relay IP multicast.
+-# LR1-LS1 configured to flood IP multicast traffic unconditionally.
+-# LR3-LS3 configured to flood IP multicast traffic unconditionally.
+-
+-AT_CAPTURE_FILE([exp])
+-AT_CAPTURE_FILE([rcv])
+-check_packets() {
+- > exp
+- > rcv
+- if test "$1" = --uniq; then
+- sort="sort -u"; shift
+- else
+- sort=sort
+- fi
+- for tuple in "$@"; do
+- set $tuple; pcap=$1 type=$2
+- echo "--- $pcap" | tee -a exp >> rcv
+- $sort "$type" >> exp
+- $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $pcap | $sort >> rcv
+- echo | tee -a exp >> rcv
+- done
+-
+- $at_diff exp rcv >/dev/null
+-}
+-
+-ovn_init_ic_db
+-ovn_start az1
+-ovn_start az2
+-
+-net_add n1
+-
+-sim_add hv1
+-as hv1
+-check ovs-vsctl add-br br-phys
+-ovn_az_attach az1 n1 br-phys 192.168.1.1 16
+-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
+-check ovs-vsctl set open . external-ids:ovn-is-interconn=true
+-
+-sim_add hv2
+-as hv2
+-check ovs-vsctl add-br br-phys
+-ovn_az_attach az2 n1 br-phys 192.168.2.1 16
+-check ovs-vsctl -- add-port br-int hv2-vif1 \
+- -- set interface hv2-vif1 external-ids:iface-id=lsp2 \
+- options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap
+-check ovs-vsctl -- add-port br-int hv2-vif2 \
+- -- set interface hv2-vif2 external-ids:iface-id=lsp3 \
+- options:tx_pcap=hv2/vif2-tx.pcap \
+- options:rxq_pcap=hv2/vif2-rx.pcap
+-check ovs-vsctl -- add-port br-int hv2-vif3 \
+- -- set interface hv2-vif3 external-ids:iface-id=lsp4 \
+- options:tx_pcap=hv2/vif3-tx.pcap \
+- options:rxq_pcap=hv2/vif3-rx.pcap
+-check ovs-vsctl set open . external-ids:ovn-is-interconn=true
+-
+-AT_CHECK([ovn-ic-nbctl create Transit_Switch name=ts], [0], [ignore])
+-check ovn_as az1 ovn-nbctl wait-until logical_switch ts
+-check ovn_as az2 ovn-nbctl wait-until logical_switch ts
+-
+-ovn_as az1
+-check ovn-nbctl lr-add lr1 \
+- -- lrp-add lr1 lr1-ts 00:00:00:01:00:01 42.42.42.1/24 \
+- -- lrp-add lr1 lr1-ls1 00:00:00:01:01:00 43.43.43.1/24 \
+- -- lrp-set-gateway-chassis lr1-ts hv1
+-check ovn-nbctl ls-add ls1 \
+- -- lsp-add ls1 ls1-lr1 \
+- -- lsp-set-addresses ls1-lr1 router \
+- -- lsp-set-type ls1-lr1 router \
+- -- lsp-set-options ls1-lr1 router-port=lr1-ls1 \
+- -- lsp-add ls1 lsp1
+-check ovn-nbctl lsp-add ts ts-lr1 \
+- -- lsp-set-addresses ts-lr1 router \
+- -- lsp-set-type ts-lr1 router \
+- -- lsp-set-options ts-lr1 router-port=lr1-ts
+-wait_for_ports_up
+-
+-ovn_as az2
+-check ovn-nbctl lr-add lr2 \
+- -- lrp-add lr2 lr2-ts 00:00:00:02:00:01 42.42.42.2/24 \
+- -- lrp-add lr2 lr2-ls2 00:00:00:02:01:00 44.44.44.1/24 \
+- -- lrp-set-gateway-chassis lr2-ts hv2
+-check ovn-nbctl ls-add ls2 \
+- -- lsp-add ls2 ls2-lr2 \
+- -- lsp-set-addresses ls2-lr2 router \
+- -- lsp-set-type ls2-lr2 router \
+- -- lsp-set-options ls2-lr2 router-port=lr2-ls2 \
+- -- lsp-add ls2 lsp2 \
+- -- lsp-add ls2 lsp4
+-check ovn-nbctl lsp-add ts ts-lr2 \
+- -- lsp-set-addresses ts-lr2 router \
+- -- lsp-set-type ts-lr2 router \
+- -- lsp-set-options ts-lr2 router-port=lr2-ts
+-
+-check ovn-nbctl lr-add lr3 \
+- -- lrp-add lr3 lr3-ts 00:00:00:02:00:02 42.42.42.3/24 \
+- -- lrp-add lr3 lr3-ls3 00:00:00:02:02:00 44.44.45.1/24 \
+- -- lrp-set-gateway-chassis lr3-ts hv2
+-check ovn-nbctl ls-add ls3 \
+- -- lsp-add ls3 ls3-lr3 \
+- -- lsp-set-addresses ls3-lr3 router \
+- -- lsp-set-type ls3-lr3 router \
+- -- lsp-set-options ls3-lr3 router-port=lr3-ls3 \
+- -- lsp-add ls3 lsp3
+-check ovn-nbctl lsp-add ts ts-lr3 \
+- -- lsp-set-addresses ts-lr3 router \
+- -- lsp-set-type ts-lr3 router \
+- -- lsp-set-options ts-lr3 router-port=lr3-ts
+-
+-wait_for_ports_up
+-
+-ovn_as az1
+-OVS_WAIT_UNTIL([ovn-nbctl show | grep ts-lr2])
+-check ovn-nbctl lsp-set-options ts-lr2 requested-chassis=hv2
+-OVS_WAIT_UNTIL([ovn-nbctl show | grep ts-lr3])
+-check ovn-nbctl lsp-set-options ts-lr3 requested-chassis=hv2
+-
+-ovn_as az2
+-OVS_WAIT_UNTIL([ovn-nbctl show | grep ts-lr1])
+-check ovn-nbctl lsp-set-options ts-lr1 requested-chassis=hv1
+-
+-dnl Enable unregistered IP multicast flooding and IP multicast relay.
+-ovn_as az1
+-check ovn-nbctl set logical_switch ls1 other_config:mcast_snoop="true" \
+- other_config:mcast_flood_unregistered="true"
+-check ovn-nbctl set logical_router lr1 options:mcast_relay="true"
+-check ovn-nbctl set logical_router_port lr1-ls1 options:mcast_flood="true"
+-
+-ovn_as az2
+-check ovn-nbctl set logical_switch ls2 other_config:mcast_snoop="true" \
+- other_config:mcast_flood_unregistered="true"
+-check ovn-nbctl set logical_router lr2 options:mcast_relay="true"
+-check ovn-nbctl set logical_router_port lr2-ts options:mcast_flood="true"
+-check ovn-nbctl set logical_switch ls3 other_config:mcast_snoop="true" \
+- other_config:mcast_flood_unregistered="true"
+-check ovn-nbctl set logical_router lr3 options:mcast_relay="true"
+-check ovn-nbctl set logical_router_port lr3-ls3 options:mcast_flood="true"
+-
+-check ovn_as az1 ovn-nbctl --wait=hv sync
+-check ovn_as az2 ovn-nbctl --wait=hv sync
+-
+-# 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
+-
+-# Send an IP multicast packet from lsp2, it should be forwarded
+-# statically to lsp1, lsp3 and lsp4.
+-> expected_az1
+-> expected_az2
+-> expected_az2_switched
+-send_ip_multicast_pkt hv2-vif1 hv2 \
+- 000000000001 01005e000144 \
+- $(ip_to_hex 44 44 44 2) $(ip_to_hex 239 0 1 68) 1e 20 7c6b 11 \
+- e518e518000aed350000
+-store_ip_multicast_pkt \
+- 000000010100 01005e000144 \
+- $(ip_to_hex 44 44 44 2) $(ip_to_hex 239 0 1 68) 1e 1e 7e6b 11 \
+- e518e518000aed350000 expected_az1
+-store_ip_multicast_pkt \
+- 000000020200 01005e000144 \
+- $(ip_to_hex 44 44 44 2) $(ip_to_hex 239 0 1 68) 1e 1e 7e6b 11 \
+- e518e518000aed350000 expected_az2
+-store_ip_multicast_pkt \
+- 000000000001 01005e000144 \
+- $(ip_to_hex 44 44 44 2) $(ip_to_hex 239 0 1 68) 1e 20 7c6b 11 \
+- e518e518000aed350000 expected_az2_switched
+-
+-OVS_WAIT_UNTIL(
+- [check_packets 'hv1/vif1-tx.pcap expected_az1' \
+- 'hv2/vif2-tx.pcap expected_az2' \
+- 'hv2/vif3-tx.pcap expected_az2_switched'],
+- [$at_diff -F'^---' exp rcv])
+-
+-# Send an IP multicast packet from lsp2 towards 224.0.0.x, it should be
+-# forwarded statically only to lsp3.
+-as hv1 reset_pcap_file hv1-vif1 hv1/vif1
+-as hv2 reset_pcap_file hv2-vif2 hv2/vif2
+-as hv2 reset_pcap_file hv2-vif3 hv2/vif3
+-> expected_az1
+-> expected_az2
+-> expected_az2_switched
+-send_ip_multicast_pkt hv2-vif1 hv2 \
+- 000000000001 01005e000144 \
+- $(ip_to_hex 44 44 44 2) $(ip_to_hex 224 0 0 1) 1e 20 8cae 11 \
+- e518e518000aed350000
+-store_ip_multicast_pkt \
+- 000000000001 01005e000144 \
+- $(ip_to_hex 44 44 44 2) $(ip_to_hex 224 0 0 1) 1e 20 8cae 11 \
+- e518e518000aed350000 expected_az2_switched
+-
+-OVS_WAIT_UNTIL(
+- [check_packets 'hv1/vif1-tx.pcap expected_az1' \
+- 'hv2/vif2-tx.pcap expected_az2' \
+- 'hv2/vif3-tx.pcap expected_az2_switched'],
+- [$at_diff -F'^---' exp rcv])
+-
+-OVN_CLEANUP_SBOX([hv1],["/IGMP Querier enabled without a valid IPv4 or IPv6 address/d
+-/IGMP Querier enabled with invalid ETH src address/d"])
+-
+-OVN_CLEANUP_SBOX([hv2],["/IGMP Querier enabled without a valid IPv4 or IPv6 address/d
+-/IGMP Querier enabled with invalid ETH src address/d"])
+-
+-OVN_CLEANUP_IC([az1],[az2])
+-AT_CLEANUP
+-])
+-
+-OVN_FOR_EACH_NORTHD([
+-AT_SETUP([interconnection - IGMP/MLD multicast])
+-AT_KEYWORDS([IP-multicast])
+-
+-# Logical network:
+-#
+-# AZ1 | AZ2
+-# ---------------------------------------------------------------------
+-# |
+-# | +-- LR2 --- LS2 --- LSP2 (sender)
+-# | /
+-# LSP1 --- LS1 --- LR1 --- TS ---
+-# (receiver) | \
+-# | +-- LR3 --- LS3 --- LSP3 (receiver)
+-#
+-# LS1, LS2, LS3, TS configured to snoop IP multicast.
+-# LR1, LR2, LR3 configured to relay IP multicast.
+-# LR1-TS configured to flood IP multicast traffic unconditionally.
+-# LR2-TS configured to flood IP multicast traffic unconditionally.
+-# LR3-TS configured to flood IP multicast traffic unconditionally.
+-
+-AT_CAPTURE_FILE([exp])
+-AT_CAPTURE_FILE([rcv])
+-check_packets() {
+- > exp
+- > rcv
+- if test "$1" = --uniq; then
+- sort="sort -u"; shift
+- else
+- sort=sort
+- fi
+- for tuple in "$@"; do
+- set $tuple; pcap=$1 type=$2
+- echo "--- $pcap" | tee -a exp >> rcv
+- $sort "$type" >> exp
+- $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $pcap | $sort >> rcv
+- echo | tee -a exp >> rcv
+- done
+-
+- $at_diff exp rcv >/dev/null
+-}
+-
+-ovn_init_ic_db
+-ovn_start az1
+-ovn_start az2
+-
+-net_add n1
+-
+-sim_add hv1
+-as hv1
+-check ovs-vsctl add-br br-phys
+-ovn_az_attach az1 n1 br-phys 192.168.1.1 16
+-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
+-check ovs-vsctl set open . external-ids:ovn-is-interconn=true
+-
+-sim_add hv2
+-as hv2
+-check ovs-vsctl add-br br-phys
+-ovn_az_attach az2 n1 br-phys 192.168.2.1 16
+-check ovs-vsctl -- add-port br-int hv2-vif1 \
+- -- set interface hv2-vif1 external-ids:iface-id=lsp2 \
+- options:tx_pcap=hv2/vif1-tx.pcap \
+- options:rxq_pcap=hv2/vif1-rx.pcap
+-check ovs-vsctl -- add-port br-int hv2-vif2 \
+- -- set interface hv2-vif2 external-ids:iface-id=lsp3 \
+- options:tx_pcap=hv2/vif2-tx.pcap \
+- options:rxq_pcap=hv2/vif2-rx.pcap
+-check ovs-vsctl set open . external-ids:ovn-is-interconn=true
+-
+-AT_CHECK([ovn-ic-nbctl create Transit_Switch name=ts], [0], [ignore])
+-check ovn_as az1 ovn-nbctl wait-until logical_switch ts
+-check ovn_as az2 ovn-nbctl wait-until logical_switch ts
+-
+-ovn_as az1
+-check ovn-nbctl lr-add lr1 \
+- -- lrp-add lr1 lr1-ts 00:00:00:01:00:01 42.42.42.1/24 4242::1/64 \
+- -- lrp-add lr1 lr1-ls1 00:00:00:01:01:00 43.43.43.1/24 4343::1/64\
+- -- lrp-set-gateway-chassis lr1-ts hv1
+-check ovn-nbctl ls-add ls1 \
+- -- lsp-add ls1 ls1-lr1 \
+- -- lsp-set-addresses ls1-lr1 router \
+- -- lsp-set-type ls1-lr1 router \
+- -- lsp-set-options ls1-lr1 router-port=lr1-ls1 \
+- -- lsp-add ls1 lsp1
+-check ovn-nbctl lsp-add ts ts-lr1 \
+- -- lsp-set-addresses ts-lr1 router \
+- -- lsp-set-type ts-lr1 router \
+- -- lsp-set-options ts-lr1 router-port=lr1-ts
+-wait_for_ports_up
+-
+-ovn_as az2
+-check ovn-nbctl lr-add lr2 \
+- -- lrp-add lr2 lr2-ts 00:00:00:02:00:01 42.42.42.2/24 4242::2/64 \
+- -- lrp-add lr2 lr2-ls2 00:00:00:02:01:00 44.44.44.1/24 4444::1/64 \
+- -- lrp-set-gateway-chassis lr2-ts hv2
+-check ovn-nbctl ls-add ls2 \
+- -- lsp-add ls2 ls2-lr2 \
+- -- lsp-set-addresses ls2-lr2 router \
+- -- lsp-set-type ls2-lr2 router \
+- -- lsp-set-options ls2-lr2 router-port=lr2-ls2 \
+- -- lsp-add ls2 lsp2
+-check ovn-nbctl lsp-add ts ts-lr2 \
+- -- lsp-set-addresses ts-lr2 router \
+- -- lsp-set-type ts-lr2 router \
+- -- lsp-set-options ts-lr2 router-port=lr2-ts
+-
+-check ovn-nbctl lr-add lr3 \
+- -- lrp-add lr3 lr3-ts 00:00:00:02:00:02 42.42.42.3/24 4242::3/64 \
+- -- lrp-add lr3 lr3-ls3 00:00:00:02:02:00 44.44.45.1/24 4445::1/64 \
+- -- lrp-set-gateway-chassis lr3-ts hv2
+-check ovn-nbctl ls-add ls3 \
+- -- lsp-add ls3 ls3-lr3 \
+- -- lsp-set-addresses ls3-lr3 router \
+- -- lsp-set-type ls3-lr3 router \
+- -- lsp-set-options ls3-lr3 router-port=lr3-ls3 \
+- -- lsp-add ls3 lsp3
+-check ovn-nbctl lsp-add ts ts-lr3 \
+- -- lsp-set-addresses ts-lr3 router \
+- -- lsp-set-type ts-lr3 router \
+- -- lsp-set-options ts-lr3 router-port=lr3-ts
+-
+-wait_for_ports_up
+-
+-ovn_as az1
+-OVS_WAIT_UNTIL([ovn-nbctl show | grep ts-lr2])
+-check ovn-nbctl lsp-set-options ts-lr2 requested-chassis=hv2
+-OVS_WAIT_UNTIL([ovn-nbctl show | grep ts-lr3])
+-check ovn-nbctl lsp-set-options ts-lr3 requested-chassis=hv2
+-
+-ovn_as az2
+-OVS_WAIT_UNTIL([ovn-nbctl show | grep ts-lr1])
+-check ovn-nbctl lsp-set-options ts-lr1 requested-chassis=hv1
+-
+-dnl Enable IP multicast snooping and IP multicast relay. Reports are
+-dnl forwarded statically.
+-ovn_as az1
+-check ovn-nbctl set logical_switch ls1 other_config:mcast_snoop="true"
+-check ovn-nbctl set Logical_Switch_Port ls1-lr1 options:mcast_flood_reports="true"
+-check ovn-nbctl set logical_router lr1 options:mcast_relay="true"
+-check ovn-nbctl set logical_router_port lr1-ts options:mcast_flood="true"
+-check ovn-nbctl set logical_switch ts other_config:mcast_snoop="true"
+-check ovn-nbctl set logical_switch_port ts-lr1 options:mcast_flood_reports="true"
+-check ovn-nbctl set logical_switch_port ts-lr2 options:mcast_flood_reports="true"
+-check ovn-nbctl set logical_switch_port ts-lr3 options:mcast_flood_reports="true"
+-
+-ovn_as az2
+-check ovn-nbctl set logical_switch ls2 other_config:mcast_snoop="true"
+-check ovn-nbctl set Logical_Switch_Port ls2-lr2 options:mcast_flood_reports="true"
+-check ovn-nbctl set logical_router lr2 options:mcast_relay="true"
+-check ovn-nbctl set logical_router_port lr2-ts options:mcast_flood="true"
+-check ovn-nbctl set logical_switch ls3 other_config:mcast_snoop="true"
+-check ovn-nbctl set Logical_Switch_Port ls3-lr3 options:mcast_flood_reports="true"
+-check ovn-nbctl set logical_router lr3 options:mcast_relay="true"
+-check ovn-nbctl set logical_router_port lr3-ts options:mcast_flood="true"
+-check ovn-nbctl set logical_switch ts other_config:mcast_snoop="true"
+-check ovn-nbctl set logical_switch_port ts-lr1 options:mcast_flood_reports="true"
+-check ovn-nbctl set logical_switch_port ts-lr2 options:mcast_flood_reports="true"
+-check ovn-nbctl set logical_switch_port ts-lr3 options:mcast_flood_reports="true"
+-
+-check ovn_as az1 ovn-nbctl --wait=hv sync
+-check ovn_as az2 ovn-nbctl --wait=hv sync
+-
+-# 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
+-
+-# Inject IGMP Join for 239.0.1.68 on LSP1.
+-send_igmp_v3_report hv1-vif1 hv1 \
+- 000000000001 $(ip_to_hex 10 0 0 1) f9f8 \
+- $(ip_to_hex 239 0 1 68) 04 e9b9 \
+- /dev/null
+-
+-# Inject MLD Join for ff0a:dead:beef::1 on LSP1.
+-send_mld_v2_report hv1-vif1 hv1 \
+- 000000000001 10000000000000000000000000000001 \
+- ff0adeadbeef00000000000000000001 04 c0e4 \
+- /dev/null
+-
+-# Inject IGMP Join for 239.0.1.68 on LSP3.
+-send_igmp_v3_report hv2-vif2 hv2 \
+- 000000000001 $(ip_to_hex 10 0 0 1) f9f8 \
+- $(ip_to_hex 239 0 1 68) 04 e9b9 \
+- /dev/null
+-
+-# Inject MLD Join for ff0a:dead:beef::1 on LSP3.
+-send_mld_v2_report hv2-vif2 hv2 \
+- 000000000001 10000000000000000000000000000001 \
+- ff0adeadbeef00000000000000000001 04 c0e4 \
+- /dev/null
+-
+-# Check that the IGMP and MLD groups are learned on both AZs (on the LS
+-# and TS).
+-ovn_as az1
+-wait_row_count IGMP_Group 2 address=239.0.1.68
+-wait_row_count IGMP_Group 2 address='"ff0a:dead:beef::1"'
+-check ovn-nbctl --wait=hv sync
+-
+-ovn_as az2
+-wait_row_count IGMP_Group 2 address=239.0.1.68
+-wait_row_count IGMP_Group 2 address='"ff0a:dead:beef::1"'
+-check ovn-nbctl --wait=hv sync
+-
+-# Send an IP multicast packet from LSP2, it should be forwarded
+-# to lsp1 and lsp3.
+-> expected_az1
+-> expected_az2
+-send_ip_multicast_pkt hv2-vif1 hv2 \
+- 000000000001 01005e000144 \
+- $(ip_to_hex 44 44 44 2) $(ip_to_hex 239 0 1 68) 1e 20 7c6b 11 \
+- e518e518000aed350000
+-store_ip_multicast_pkt \
+- 000000010100 01005e000144 \
+- $(ip_to_hex 44 44 44 2) $(ip_to_hex 239 0 1 68) 1e 1e 7e6b 11 \
+- e518e518000aed350000 expected_az1
+-store_ip_multicast_pkt \
+- 000000020200 01005e000144 \
+- $(ip_to_hex 44 44 44 2) $(ip_to_hex 239 0 1 68) 1e 1e 7e6b 11 \
+- e518e518000aed350000 expected_az2
+-
+-send_ip6_multicast_pkt hv2-vif1 hv2 \
+- 000000000001 333300000001 \
+- 00100000000000000000000000000042 ff0adeadbeef00000000000000000001 \
+- 000e 40 11 \
+- 93407a69000e2b4e61736461640a
+-store_ip6_multicast_pkt \
+- 000000010100 333300000001 \
+- 00100000000000000000000000000042 ff0adeadbeef00000000000000000001 \
+- 000e 3e 11 \
+- 93407a69000e2b4e61736461640a \
+- expected_az1
+-store_ip6_multicast_pkt \
+- 000000020200 333300000001 \
+- 00100000000000000000000000000042 ff0adeadbeef00000000000000000001 \
+- 000e 3e 11 \
+- 93407a69000e2b4e61736461640a \
+- expected_az2
+-
+-OVS_WAIT_UNTIL(
+- [check_packets 'hv1/vif1-tx.pcap expected_az1' \
+- 'hv2/vif2-tx.pcap expected_az2'],
+- [$at_diff -F'^---' exp rcv])
+-
+-OVN_CLEANUP_SBOX([hv1], ["/IGMP Querier enabled without a valid IPv4/d
+-/IGMP Querier enabled with invalid ETH src/d"])
+-
+-OVN_CLEANUP_SBOX([hv2], ["/IGMP Querier enabled without a valid IPv4/d
+-/IGMP Querier enabled with invalid ETH src/d"])
+-
+-OVN_CLEANUP_IC([az1],[az2])
+-AT_CLEANUP
+-])
+-
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([ECMP static routes])
+ ovn_start
+@@ -33546,7 +32879,7 @@ for i in 1 2 3 4 5; do
+ as hv$i
+ ovs-vsctl add-br br-phys
+ ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+- ovn_attach n1 br-phys 192.168.0.$i 24 $encap
++ ovn_attach n1 br-phys 192.168.1.$i 24 $encap
+ done
+
+ # Add a vif on HV1
+@@ -38347,3 +37680,56 @@ OVS_WAIT_UNTIL([test 1 = $(as hv ovs-ofctl dump-flows br-int | grep -E "pkt_mark
+ OVN_CLEANUP([hv])
+ AT_CLEANUP
+ ])
++
++OVN_FOR_EACH_NORTHD([
++AT_SETUP([ovn-controller - cleanup VIF/CIF related flows/fields when updating requested-chassis])
++ovn_start
++
++net_add n1
++sim_add hv1
++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=lsp1 \
++ ofport-request=8
++
++check ovn-nbctl ls-add lsw0
++
++check ovn-nbctl lsp-add lsw0 lsp1
++check ovn-nbctl lsp-add lsw0 sw0-port1.1 lsp1 7
++
++# wait for the VIF to be claimed to this chassis
++wait_row_count Chassis 1 name=hv1
++hv1_uuid=$(fetch_column Chassis _uuid name=hv1)
++wait_for_ports_up lsp1
++wait_for_ports_up sw0-port1.1
++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=0 |grep priority=100 | grep -c in_port=8], [0],[dnl
++1
++])
++OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=0 |grep priority=150|grep dl_vlan=7| grep -c in_port=8], [0],[dnl
++1
++])
++
++# set lport requested-chassis to differant chassis
++check ovn-nbctl set Logical_Switch_Port lsp1 \
++ options:requested-chassis=foo
++
++OVS_WAIT_UNTIL([test `ovn-sbctl get Port_Binding lsp1 up` = 'false'])
++OVS_WAIT_UNTIL([test `ovn-sbctl get Port_Binding sw0-port1.1 up` = 'false'])
++wait_column "" Port_Binding chassis logical_port=lsp1
++wait_column "" Port_Binding chassis logical_port=sw0-port1.1
++
++OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=0 |grep priority=100 |grep -c in_port=8], [1],[dnl
++0
++])
++OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=0 |grep priority=150|grep dl_vlan=7| grep -c in_port=8], [1],[dnl
++0
++])
++
++OVN_CLEANUP([hv1])
++AT_CLEANUP
++])
+diff --git a/tests/system-common-macros.at b/tests/system-common-macros.at
+index 177178067..691c271a3 100644
+--- a/tests/system-common-macros.at
++++ b/tests/system-common-macros.at
+@@ -271,6 +271,18 @@ m4_define([OVS_START_L7],
+ ]
+ )
+
++# NETNS_START_TCPDUMP([namespace], [params], [name])
++#
++# Helper to properly start tcpdump and wait for the startup.
++# The tcpdump output is available in .tcpdump file.
++m4_define([NETNS_START_TCPDUMP],
++ [
++ NETNS_DAEMONIZE([$1], [tcpdump -l $2 >$3.tcpdump 2>$3.stderr], [$3.pid])
++ OVS_WAIT_UNTIL([grep -q "listening" $3.stderr])
++ ]
++)
++
++
+ # OVS_CHECK_VXLAN()
+ #
+ # Do basic check for vxlan functionality, skip the test if it's not there.
+@@ -352,7 +364,7 @@ OVS_TRAFFIC_VSWITCHD_START()
+ ADD_BR([br-int])
+ ADD_BR([br-ext])
+
+-ovs-ofctl add-flow br-ext action=normal
++ovs-vsctl set-fail-mode br-ext standalone
+ # Set external-ids in br-int needed for ovn-controller
+ ovs-vsctl \
+ -- set Open_vSwitch . external-ids:system-id=hv1 \
+@@ -418,16 +430,14 @@ ovn-nbctl lsp-add public public1 \
+ ovn-nbctl set logical_router_port rp-public options:prefix_delegation=true
+ ovn-nbctl set logical_router_port rp-public options:prefix=true
+ ovn-nbctl set logical_router_port rp-sw0 options:prefix=true
+-ovn-nbctl set logical_router_port rp-sw1 options:prefix=true
+
+ OVN_POPULATE_ARP
+
+ ovn-nbctl --wait=hv sync
+
+ cat > /etc/dhcp/dhcpd.conf < pkt.pcap &])
+-
++NETNS_START_TCPDUMP([server], [-nni s1], [server])
+ NETNS_DAEMONIZE([server], [dhcpd -6 -f s1 > dhcpd.log 2>&1], [dhcpd.pid])
+ ovn-nbctl --wait=hv sync
+
+ OVS_WAIT_WHILE([test "$(ovn-nbctl get logical_router_port rp-public ipv6_prefix | cut -c4-15)" = ""])
+ OVS_WAIT_WHILE([test "$(ovn-nbctl get logical_router_port rp-sw0 ipv6_prefix | cut -c4-15)" = ""])
+-OVS_WAIT_WHILE([test "$(ovn-nbctl get logical_router_port rp-sw1 ipv6_prefix | cut -c4-15)" = ""])
+
+ AT_CHECK([ovn-nbctl get logical_router_port rp-public ipv6_prefix | cut -c3-16], [0], [dnl
+ [2001:1db8:3333]
+@@ -453,30 +461,40 @@ AT_CHECK([ovn-nbctl get logical_router_port rp-public ipv6_prefix | cut -c3-16],
+ AT_CHECK([ovn-nbctl get logical_router_port rp-sw0 ipv6_prefix | cut -c3-16], [0], [dnl
+ [2001:1db8:3333]
+ ])
+-AT_CHECK([ovn-nbctl get logical_router_port rp-sw1 ipv6_prefix | cut -c3-16], [0], [dnl
+-[2001:1db8:3333]
+-])
+
+ prefix=$(ovn-nbctl list logical_router_port rp-public | awk -F/ '/ipv6_prefix/{print substr($ 1,25,9)}' | sed 's/://g')
+ ovn-nbctl list logical_router_port rp-public > /tmp/rp-public
+-ovn-nbctl set logical_router_port rp-sw0 options:prefix=false
+-ovn-nbctl set logical_router_port rp-sw1 options:prefix=false
+-# Renew message
+-NS_CHECK_EXEC([server], [tcpdump -c 1 -nni s1 ip6[[48:1]]=0x05 and ip6[[113:4]]=0x${prefix} > renew.pcap &])
++
++# Wait for 2 renew on each port.
++NETNS_START_TCPDUMP([server], [-c 4 -nni s1 ip6[[48:1]]=0x05 and ip6[[113:4]]=0x${prefix}], [renew])
+ # Reply message with Status OK
+-NS_CHECK_EXEC([server], [tcpdump -c 1 -nni s1 ip6[[48:1]]=0x07 and ip6[[81:4]]=0x${prefix} > reply.pcap &])
++NETNS_START_TCPDUMP([server], [-c 4 -nni s1 ip6[[48:1]]=0x07 and ip6[[81:4]]=0x${prefix}], [reply])
+
+ OVS_WAIT_UNTIL([
+- total_pkts=$(cat renew.pcap | wc -l)
+- test "${total_pkts}" = "1"
++ total_pkts=$(cat renew.tcpdump | wc -l)
++ test "${total_pkts}" = "4"
+ ])
+
+ OVS_WAIT_UNTIL([
+- total_pkts=$(cat reply.pcap | wc -l)
+- test "${total_pkts}" = "1"
++ total_pkts=$(cat reply.tcpdump | wc -l)
++ test "${total_pkts}" = "4"
+ ])
+
+-kill $(pidof tcpdump)
++ovn-nbctl set logical_router_port rp-public options:prefix=false
++ovn-nbctl set logical_router_port rp-sw0 options:prefix=false
++ovn-nbctl --wait=hv set logical_router_port rp-sw1 options:prefix=true
++sleep_sb
++NETNS_START_TCPDUMP([server], [-c 2 -nni s1 ip6[[48:1]]=0x05 and ip6[[113:4]]=0x${prefix}], [renew2])
++
++# Sleep enough to have solicit and renew being sent, then wait for 2 renew.
++# The reply to the request will usually be received as sb is sleeping.
++# Hence, the reply to the first renew will be received when sb is ro.
++sleep 10
++wake_up_sb
++OVS_WAIT_UNTIL([
++ total_pkts=$(cat renew2.tcpdump | wc -l)
++ test "${total_pkts}" = "2"
++])
+
+ ovn-nbctl set logical_router_port rp-sw0 options:prefix=false
+ ovn-nbctl clear logical_router_port rp-sw0 ipv6_prefix
+diff --git a/tests/system-ovn-kmod.at b/tests/system-ovn-kmod.at
+index 2341bb387..2246689f8 100644
+--- a/tests/system-ovn-kmod.at
++++ b/tests/system-ovn-kmod.at
+@@ -668,19 +668,15 @@ test_fragmented_traffic() {
+ NETNS_DAEMONIZE([server], [nc -l -u 172.16.1.2 4242 > /dev/null], [server.pid])
+
+ # Collect ICMP packets on client side
+- NETNS_DAEMONIZE([client], [tcpdump -l -U -i client -vnne \
+- udp > client.pcap 2>client_err], [tcpdump0.pid])
+- OVS_WAIT_UNTIL([grep "listening" client_err])
++ NETNS_START_TCPDUMP([client], [-U -i client -vnne udp], [tcpdump-client])
+
+ # Collect UDP packets on server side
+- NETNS_DAEMONIZE([server], [tcpdump -l -U -i server -vnne \
+- 'udp and ip[[6:2]] > 0 and not ip[[6]] = 64' > server.pcap 2>server_err], [tcpdump1.pid])
+- OVS_WAIT_UNTIL([grep "listening" server_err])
++ NETNS_START_TCPDUMP([server], [-U -i server -vnne 'udp and ip[[6:2]] > 0 and not ip[[6]] = 64'], [tcpdump-server])
+
+ NS_CHECK_EXEC([client], [$PYTHON3 ./client.py])
+- OVS_WAIT_UNTIL([test "$(cat server.pcap | wc -l)" = "4"])
++ OVS_WAIT_UNTIL([test "$(cat tcpdump-server.tcpdump | wc -l)" = "4"])
+
+- check kill $(cat tcpdump0.pid) $(cat tcpdump1.pid) $(cat server.pid)
++ kill $(cat tcpdump-client.pid) $(cat tcpdump-server.pid) $(cat server.pid)
+ }
+
+ AS_BOX([LB on router without port and protocol])
+@@ -817,8 +813,7 @@ wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+
+ # Create service that listens for TCP and UDP
+-NETNS_DAEMONIZE([vm2], [nc -l -u 1234], [nc0.pid])
+-NETNS_DAEMONIZE([vm2], [nc -l -k 1235], [nc1.pid])
++NETNS_DAEMONIZE([vm2], [nc -l -k 1235], [nc0.pid])
+
+ test_icmp() {
+ # Make sure that a ping works as expected
+@@ -842,7 +837,9 @@ icmp,orig=(src=173.0.1.2,dst=172.16.0.102,id=,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])
++ kill $(cat nc1.pid)
+
+ AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | \
+ sed -e 's/zone=[[0-9]]*/zone=/'], [0], [dnl
+@@ -1006,14 +1003,10 @@ while True:
+ NETNS_DAEMONIZE([server], [$PYTHON3 ./server.py > server.log], [server.pid])
+
+ dnl Collect packets on server side.
+-NETNS_DAEMONIZE([server], [tcpdump -l -U -i server -vnne \
+- 'ip and (icmp or udp)' > server.tcpdump 2>server_err], [tcpdump0.pid])
+-OVS_WAIT_UNTIL([grep "listening" server_err])
++NETNS_START_TCPDUMP([server], [-U -i server -vnne 'ip and (icmp or udp)'], [tcpdump-server])
+
+ dnl Collect packets on client side.
+-NETNS_DAEMONIZE([client], [tcpdump -l -U -i client -vnne \
+- 'ip and (icmp or udp)' > client.tcpdump 2>client_err], [tcpdump1.pid])
+-OVS_WAIT_UNTIL([grep "listening" client_err])
++NETNS_START_TCPDUMP([client], [-U -i client -vnne 'ip and (icmp or udp)'], [tcpdump-client])
+
+ dnl Send two packets to the server with a short interval.
+ dnl First packet should generate 'needs frag', the second should result in
+@@ -1031,8 +1024,8 @@ time.sleep(5)
+ NS_CHECK_EXEC([client], [$PYTHON3 ./client.py])
+
+ dnl Expecting 2 outgoing packets and 2 fragments back - 8 lines total.
+-OVS_WAIT_UNTIL([test "$(cat client.tcpdump | wc -l)" = "8"])
+-AT_CHECK([test $(grep -c "need to frag (mtu 800)" server.tcpdump) -eq 1])
++OVS_WAIT_UNTIL([test "$(cat tcpdump-client.tcpdump | wc -l)" = "8"])
++AT_CHECK([test $(grep -c "need to frag (mtu 800)" tcpdump-server.tcpdump) -eq 1])
+
+ ovn-appctl -t ovn-controller vlog/set info
+
+diff --git a/tests/system-ovn.at b/tests/system-ovn.at
+index a08e2fdc2..b5b68e32e 100644
+--- a/tests/system-ovn.at
++++ b/tests/system-ovn.at
+@@ -1602,12 +1602,11 @@ OVS_WAIT_UNTIL([
+ ovn-nbctl --reject lb-add lb3 30.0.0.10:80 ""
+ ovn-nbctl ls-lb-add foo lb3
+ # Filter reset segments
+-NS_CHECK_EXEC([foo1], [tcpdump -l -c 1 -neei foo1 ip[[33:1]]=0x14 > rst.pcap 2>tcpdump_err &])
+-OVS_WAIT_UNTIL([grep "listening" tcpdump_err])
++NETNS_START_TCPDUMP([foo1], [-c 1 -neei foo1 ip[[33:1]]=0x14], [rst])
+ NS_CHECK_EXEC([foo1], [wget -q 30.0.0.10],[4])
+
+ OVS_WAIT_UNTIL([
+- n_reset=$(cat rst.pcap | wc -l)
++ n_reset=$(cat rst.tcpdump | wc -l)
+ test "${n_reset}" = "1"
+ ])
+
+@@ -3564,16 +3563,52 @@ icmp,orig=(src=192.168.2.2,dst=172.16.1.2,id=,type=8,code=0),reply=(src
+ ])
+
+ # Try to ping external network
+-NS_CHECK_EXEC([ext-net], [tcpdump -l -n -c 3 -i ext-veth dst 172.16.1.3 and icmp > ext-net.pcap 2>tcpdump_err &])
+-OVS_WAIT_UNTIL([grep "listening" tcpdump_err])
++NETNS_START_TCPDUMP([ext-net], [-n -c 3 -i ext-veth dst 172.16.1.3 and icmp], [ext-net])
+ AT_CHECK([ovn-nbctl lr-nat-del R1 snat])
+ NS_CHECK_EXEC([foo1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.1 | FORMAT_PING], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+
++# test_connectivity_from_ext takes parameters 'vm' and 'ip'. It tests
++# icmp, udp and tcp connectivity from external network to the 'vm' on
++# the specified 'ip'.
++test_connectivity_from_ext() {
++ local vm=$1; shift
++ local ip=$1; shift
++
++ # Start listening daemon TCP connections.
++ NETNS_DAEMONIZE($vm, [nc -l -k 1235], [nc-$vm-$ip-tcp.pid])
++
++ # Ensure that vm can be pinged on the specified IP
++ NS_CHECK_EXEC([alice1], [ping -q -c 3 -i 0.3 -w 2 $ip | FORMAT_PING], \
++ [0], [dnl
++3 packets transmitted, 3 received, 0% packet loss, time 0ms
++])
++
++ # Perform two consecutive UDP connections to the specified IP
++ NETNS_DAEMONIZE($vm, [nc -l -u 1234], [nc-$vm-$ip-udp.pid])
++ NS_CHECK_EXEC([alice1], [nc -u $ip 1234 -p 2000 -z])
++ kill $(cat nc-$vm-$ip-udp.pid)
++
++ NETNS_DAEMONIZE($vm, [nc -l -u 1234], [nc-$vm-$ip-udp.pid])
++ NS_CHECK_EXEC([alice1], [nc -u $ip 1234 -p 2000 -z])
++ kill $(cat nc-$vm-$ip-udp.pid)
++
++ # Send data over TCP connection to the specified IP
++ NS_CHECK_EXEC([alice1], [echo "TCP test" | nc --send-only $ip 1235])
++}
++
++# Test access from external network to the internal IP of a VM that
++# has also configured DNAT
++test_connectivity_from_ext foo1 192.168.1.2
++
++# Test access from external network to the internal IP of a VM that
++# does not have DNAT
++test_connectivity_from_ext bar1 192.168.2.2
++
+ OVS_WAIT_UNTIL([
+- total_pkts=$(cat ext-net.pcap | wc -l)
++ total_pkts=$(cat ext-net.tcpdump | wc -l)
+ test "${total_pkts}" = "3"
+ ])
+
+@@ -3738,6 +3773,43 @@ sed -e 's/zone=[[0-9]]*/zone=/'], [0], [dnl
+ icmpv6,orig=(src=fd12::2,dst=fd20::2,id=,type=128,code=0),reply=(src=fd20::2,dst=fd20::1,id=,type=129,code=0),zone=
+ ])
+
++# test_connectivity_from_ext takes parameters 'vm' and 'ip'. It tests
++# icmp, udp and tcp connectivity from external network to the 'vm' on
++# the specified 'ip'.
++test_connectivity_from_ext() {
++ local vm=$1; shift
++ local ip=$1; shift
++
++ # Start listening daemon for TCP connections.
++ NETNS_DAEMONIZE($vm, [nc -6 -l -k 1235], [nc-$vm-$ip-tcp.pid])
++
++ # Ensure that vm can be pinged on the specified IP
++ NS_CHECK_EXEC([alice1], [ping -q -c 3 -i 0.3 -w 2 $ip | FORMAT_PING], \
++ [0], [dnl
++3 packets transmitted, 3 received, 0% packet loss, time 0ms
++])
++
++ # Perform two consecutive UDP connections to the specified IP
++ NETNS_DAEMONIZE($vm, [nc -6 -l -u 1234], [nc-$vm-$ip-udp.pid])
++ NS_CHECK_EXEC([alice1], [nc -u $ip 1234 -p 2000 -z])
++ kill $(cat nc-$vm-$ip-udp.pid)
++
++ NETNS_DAEMONIZE($vm, [nc -6 -l -u 1234], [nc-$vm-$ip-udp.pid])
++ NS_CHECK_EXEC([alice1], [nc -u $ip 1234 -p 2000 -z])
++ kill $(cat nc-$vm-$ip-udp.pid)
++
++ # Send data over TCP connection to the specified IP
++ NS_CHECK_EXEC([alice1], [echo "TCP test" | nc --send-only $ip 1235])
++}
++
++# Test access from external network to the internal IP of a VM that
++# has also configured DNAT
++test_connectivity_from_ext foo1 fd11::2
++
++# Test access from external network to the internal IP of a VM that
++# does not have DNAT
++test_connectivity_from_ext bar1 fd12::2
++
+ OVS_APP_EXIT_AND_WAIT([ovn-controller])
+
+ as ovn-sb
+@@ -3918,6 +3990,7 @@ NS_CHECK_EXEC([foo2], [ping -q -c 3 -i 0.3 -w 2 172.16.1.4 | FORMAT_PING], \
+ AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep icmp | FORMAT_CT(172.16.1.1) | \
+ sed -e 's/zone=[[0-9]]*/zone=/'], [0], [dnl
+ icmp,orig=(src=172.16.1.1,dst=172.16.1.4,id=,type=8,code=0),reply=(src=192.168.2.2,dst=172.16.1.1,id=,type=0,code=0),zone=
++icmp,orig=(src=172.16.1.1,dst=192.168.2.2,id=,type=8,code=0),reply=(src=192.168.2.2,dst=172.16.1.1,id=,type=0,code=0),zone=
+ icmp,orig=(src=192.168.1.3,dst=172.16.1.4,id=,type=8,code=0),reply=(src=172.16.1.4,dst=172.16.1.1,id=,type=0,code=0),zone=
+ ])
+
+@@ -4086,6 +4159,7 @@ NS_CHECK_EXEC([foo2], [ping -q -c 3 -i 0.3 -w 2 fd20::4 | FORMAT_PING], \
+ AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd20::1) | \
+ sed -e 's/zone=[[0-9]]*/zone=/'], [0], [dnl
+ icmpv6,orig=(src=fd11::3,dst=fd20::4,id=,type=128,code=0),reply=(src=fd20::4,dst=fd20::1,id=,type=129,code=0),zone=
++icmpv6,orig=(src=fd20::1,dst=fd12::2,id=,type=128,code=0),reply=(src=fd12::2,dst=fd20::1,id=,type=129,code=0),zone=
+ icmpv6,orig=(src=fd20::1,dst=fd20::4,id=,type=128,code=0),reply=(src=fd12::2,dst=fd20::1,id=,type=129,code=0),zone=
+ ])
+
+@@ -4316,19 +4390,19 @@ ovn-nbctl set Logical_Switch sw2 \
+ other_config:mcast_ip6_src="2000::fe"
+
+ # Check that v4 queries are generated.
+-NS_CHECK_EXEC([sw2-p1], [tcpdump -n -c 2 -i sw2-p1 igmp > sw2-p1-v4.pcap &])
++NETNS_START_TCPDUMP([sw2-p1], [-n -c 2 -i sw2-p1 igmp], [sw2-p1-v4])
+
+ OVS_WAIT_UNTIL([
+- total_queries=`grep "igmp query" -c sw2-p1-v4.pcap`
++ total_queries=`grep "igmp query" -c sw2-p1-v4.tcpdump`
+ test "${total_queries}" = "2"
+ ])
+
+ # Check that v6 queries are generated (ip6 next header == Hop-By-Hop and
+ # icmpv6 type == MLD Query).
+-NS_CHECK_EXEC([sw2-p1], [tcpdump -n -c 2 -i sw2-p1 ip6[[6]]==0 and ip6[[48]]==0x82 > sw2-p1-v6.pcap &])
++NETNS_START_TCPDUMP([sw2-p1], [-n -c 2 -i sw2-p1 ip6[[6]]==0 and ip6[[48]]==0x82], [sw2-p1-v6])
+
+ OVS_WAIT_UNTIL([
+- total_queries=`grep "multicast listener query" -c sw2-p1-v6.pcap`
++ total_queries=`grep "multicast listener query" -c sw2-p1-v6.tcpdump`
+ test "${total_queries}" = "2"
+ ])
+
+@@ -4546,13 +4620,13 @@ service_monitor protocol=udp | sed '/^$/d' | grep offline | wc -l`])
+ pid_file=$(cat l7_pid_file)
+ NS_CHECK_EXEC([sw1-p1], [kill $(cat $pid_file)])
+
+-NS_CHECK_EXEC([sw0-p2], [tcpdump -c 1 -neei sw0-p2 ip[[33:1]]=0x14 > rst.pcap &])
++NETNS_START_TCPDUMP([sw0-p2], [-c 1 -neei sw0-p2 ip[[33:1]]=0x14], [rst])
+ OVS_WAIT_UNTIL([test 2 = `ovn-sbctl --bare --columns status find \
+ service_monitor protocol=tcp | sed '/^$/d' | grep offline | wc -l`])
+ NS_CHECK_EXEC([sw0-p2], [wget 10.0.0.10 -v -o wget$i.log],[4])
+
+ OVS_WAIT_UNTIL([
+- n_reset=$(cat rst.pcap | wc -l)
++ n_reset=$(cat rst.tcpdump | wc -l)
+ test "${n_reset}" = "1"
+ ])
+
+@@ -4818,8 +4892,7 @@ NS_CHECK_EXEC([lsp], [nc 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"
+-NS_CHECK_EXEC([lsp], [tcpdump -l -nn -c 3 -i lsp ${filter} > lsp.pcap 2>tcpdump_err &])
+-OVS_WAIT_UNTIL([grep "listening" tcpdump_err])
++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])
+@@ -4828,7 +4901,7 @@ NS_CHECK_EXEC([lsp], [echo a | nc -u 88.88.88.90 2021], [ignore], [ignore], [ign
+
+ # Check hairpin traffic.
+ OVS_WAIT_UNTIL([
+- total_pkts=$(cat lsp.pcap | wc -l)
++ total_pkts=$(cat lsp.tcpdump | wc -l)
+ test "${total_pkts}" = "3"
+ ])
+
+@@ -4916,8 +4989,7 @@ NS_CHECK_EXEC([lsp], [nc 8800::0090 4041 -z], [0], [ignore], [ignore])
+
+ # Capture IPv6 UDP hairpinned packets.
+ filter="dst 4200::1 and dst port 2021 and udp"
+-NS_CHECK_EXEC([lsp], [tcpdump -l -nn -c 3 -i lsp $filter > lsp.pcap 2>tcpdump_err &])
+-OVS_WAIT_UNTIL([grep "listening" tcpdump_err])
++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])
+@@ -4926,7 +4998,7 @@ NS_CHECK_EXEC([lsp], [echo a | nc -u 8800::0090 2021], [ignore], [ignore], [igno
+
+ # Check hairpin traffic.
+ OVS_WAIT_UNTIL([
+- total_pkts=$(cat lsp.pcap | wc -l)
++ total_pkts=$(cat lsp.tcpdump | wc -l)
+ test "${total_pkts}" = "3"
+ ])
+
+@@ -5040,10 +5112,7 @@ ADD_VETH(sw1-p1-rej, sw1-p1-rej, br-int, "20.0.0.3/24", "40:54:00:00:00:03", \
+ "20.0.0.1")
+
+ # Capture packets in sw0-p1-rej.
+-NS_CHECK_EXEC([sw0-p1-rej], [tcpdump -l -nn -i sw0-p1-rej tcp > sw0-p1-rej-ip4.pcap 2> err &], [0])
+-
+-#Wait for tcpdump to get started before generating first packets
+-OVS_WAIT_UNTIL([test 1 = $(cat err | grep -c listening)])
++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'
+@@ -5061,17 +5130,12 @@ grep controller | grep tp_dst=84 -c)
+ ])
+
+ OVS_WAIT_UNTIL([
+- total=`cat sw0-p1-rej-ip4.pcap | grep "10\.0\.0\.3" | wc -l`
++ total=`cat sw0-p1-rej-ip4.tcpdump | grep "10\.0\.0\.3" | wc -l`
+ echo "total = $total"
+ test "${total}" = "4"
+ ])
+
+-kill $(pidof tcpdump)
+-
+-NS_CHECK_EXEC([sw0-p2-rej], [tcpdump -l -nn -i sw0-p2-rej tcp port 80 > sw0-p2-rej-ip6.pcap 2> err &], [0])
+-
+-#Wait for tcpdump to get started before generating first packets
+-OVS_WAIT_UNTIL([test 1 = $(cat err | grep -c listening)])
++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'
+@@ -5079,7 +5143,7 @@ OVS_WAIT_UNTIL([
+
+
+ OVS_WAIT_UNTIL([
+- total=`cat sw0-p2-rej-ip6.pcap | grep "aef0::3\.80" |wc -l`
++ total=`cat sw0-p2-rej-ip6.tcpdump | grep "aef0::3\.80" |wc -l`
+ echo "total = $total"
+ test "${total}" = "2"
+ ])
+@@ -5091,61 +5155,57 @@ OVS_WAIT_UNTIL([
+ ip netns exec sw1-p1-rej nc -vz 10.0.0.4 84 2>&1 | grep -i 'connection refused'
+ ])
+
+-kill $(pidof tcpdump)
+-
+
+ # Now test for IPv4 UDP.
+-NS_CHECK_EXEC([sw0-p1-rej], [tcpdump -l -nn -i sw0-p1-rej udp port 90 > sw0-p1-rej-udp.pcap 2> err &], [0])
+-NS_CHECK_EXEC([sw0-p1-rej], [tcpdump -l -nn -i sw0-p1-rej icmp > sw0-p1-rej-icmp.pcap 2> err &], [0])
++NETNS_START_TCPDUMP([sw0-p1-rej], [-nn -i sw0-p1-rej udp port 90], [sw0-p1-rej-udp])
++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
+- c=$(cat sw0-p1-rej-icmp.pcap | grep \
++ 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
+ ])
+
+ kill $(pidof tcpdump)
+-rm -f *.pcap
++rm -f *.tcpdump
+
+-NS_CHECK_EXEC([sw0-p1-rej], [tcpdump -l -nn -i sw0-p1-rej udp port 94 > sw0-p1-rej-udp.pcap 2> err &], [0])
+-NS_CHECK_EXEC([sw0-p1-rej], [tcpdump -l -nn -i sw0-p1-rej icmp > sw0-p1-rej-icmp.pcap 2> err &], [0])
++NETNS_START_TCPDUMP([sw0-p1-rej], [-nn -i sw0-p1-rej udp port 94], [sw0-p1-rej-udp])
++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
+- c=$(cat sw0-p1-rej-icmp.pcap | grep \
++ 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
+ ])
+-kill $(pidof tcpdump)
+
+ # Now test for IPv6 UDP.
+-NS_CHECK_EXEC([sw0-p2-rej], [tcpdump -l -nn -i sw0-p2-rej udp port 90 > sw0-p2-rej-ip6-udp.pcap 2> err &], [0])
+-NS_CHECK_EXEC([sw0-p2-rej], [tcpdump -l -nn -i sw0-p2-rej icmp6 > sw0-p2-rej-icmp6.pcap 2> err &], [0])
++NETNS_START_TCPDUMP([sw0-p2-rej], [-nn -i sw0-p2-rej udp port 90], [sw0-p2-rej-ip6-udp])
++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
+- c=$(cat sw0-p2-rej-icmp6.pcap | grep \
++ 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)
+ test $c -ge 1
+ ])
+
+ kill $(pidof tcpdump)
+-rm -f *.pcap
++rm -f *.tcpdump
+
+-NS_CHECK_EXEC([sw0-p2-rej], [tcpdump -l -nn -i sw0-p2-rej udp port 94 > sw0-p2-rej-ip6-udp.pcap 2> err &], [0])
+-NS_CHECK_EXEC([sw0-p2-rej], [tcpdump -l -nn -i sw0-p2-rej icmp6 > sw0-p2-rej-icmp6.pcap 2> err &], [0])
++NETNS_START_TCPDUMP([sw0-p2-rej], [-nn -i sw0-p2-rej udp port 94], [sw0-p2-rej-ip6-udp])
++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
+- c=$(cat sw0-p2-rej-icmp6.pcap | grep \
++ 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)
+ test $c -ge 1
+ ])
+-kill $(pidof tcpdump)
+
+ # Delete all the ACLs of pg0 and add the ACL with a generic match with reject action.
+ ovn-nbctl pg-del pg0
+@@ -5160,31 +5220,30 @@ OVS_WAIT_UNTIL([
+ ip netns exec sw0-p2-rej nc -vz6 aef0::3 80 2>&1 | grep -i 'connection refused'
+ ])
+
+-rm -f *.pcap
++rm -f *.tcpdump
+
+-NS_CHECK_EXEC([sw0-p1-rej], [tcpdump -l -nn -i sw0-p1-rej icmp > sw0-p1-rej-icmp.pcap 2> err &], [0])
++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
+- c=$(cat sw0-p1-rej-icmp.pcap | grep \
++ 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
+ ])
+
+ kill $(pidof tcpdump)
+-rm -f *.pcap
++rm -f *.tcpdump
+ # Now test for IPv6 UDP.
+-NS_CHECK_EXEC([sw0-p2-rej], [tcpdump -l -nn -i sw0-p2-rej icmp6 > sw0-p2-rej-icmp6.pcap 2> err &], [0])
++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
+- c=$(cat sw0-p2-rej-icmp6.pcap | grep \
++ 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)
+ test $c -ge 1
+ ])
+-kill $(pidof tcpdump)
+
+ OVS_APP_EXIT_AND_WAIT([ovn-controller])
+
+@@ -5287,10 +5346,7 @@ ADD_VETH(sw1-p1-rej, sw1-p1-rej, br-int, "20.0.0.3/24", "40:54:00:00:00:03", \
+ "20.0.0.1")
+
+ # Capture packets in sw0-p1-rej.
+-NS_CHECK_EXEC([sw0-p1-rej], [tcpdump -l -nn -i sw0-p1-rej tcp > sw0-p1-rej-ip4.pcap 2> err &], [0])
+-
+-#Wait for tcpdump to get started before generating first packets
+-OVS_WAIT_UNTIL([test 1 = $(cat err | grep -c listening)])
++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'
+@@ -5308,17 +5364,12 @@ grep controller | grep tp_dst=84 -c)
+ ])
+
+ OVS_WAIT_UNTIL([
+- total=`cat sw0-p1-rej-ip4.pcap | grep "10\.0\.0\.4" | wc -l`
++ total=`cat sw0-p1-rej-ip4.tcpdump | grep "10\.0\.0\.4" | wc -l`
+ echo "total = $total"
+ test "${total}" = "4"
+ ])
+
+-kill $(pidof tcpdump)
+-
+-NS_CHECK_EXEC([sw0-p2-rej], [tcpdump -l -nn -i sw0-p2-rej tcp port 80 > sw0-p2-rej-ip6.pcap 2> err &], [0])
+-
+-#Wait for tcpdump to get started before generating first packets
+-OVS_WAIT_UNTIL([test 1 = $(cat err | grep -c listening)])
++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'
+@@ -5326,7 +5377,7 @@ OVS_WAIT_UNTIL([
+
+
+ OVS_WAIT_UNTIL([
+- total=`cat sw0-p2-rej-ip6.pcap | grep "aef0::3\.80" | wc -l`
++ total=`cat sw0-p2-rej-ip6.tcpdump | grep "aef0::3\.80" | wc -l`
+ echo "total = $total"
+ test "${total}" = "2"
+ ])
+@@ -5342,53 +5393,51 @@ OVS_WAIT_UNTIL([
+ ])
+
+ # Now test for IPv4 UDP.
+-NS_CHECK_EXEC([sw0-p1-rej], [tcpdump -l -nn -i sw0-p1-rej udp port 90 > sw0-p1-rej-udp.pcap 2> err &], [0])
+-NS_CHECK_EXEC([sw0-p1-rej], [tcpdump -l -nn -i sw0-p1-rej icmp > sw0-p1-rej-icmp.pcap 2> err &], [0])
++NETNS_START_TCPDUMP([sw0-p1-rej], [-nn -i sw0-p1-rej udp port 90], [sw0-p1-rej-udp])
++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
+- c=$(cat sw0-p1-rej-icmp.pcap | grep \
++ 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
+ ])
+
+ kill $(pidof tcpdump)
+-rm -f *.pcap
++rm -f *.tcpdump
+
+-NS_CHECK_EXEC([sw0-p1-rej], [tcpdump -l -nn -i sw0-p1-rej udp port 94 > sw0-p1-rej-udp.pcap 2> err &], [0])
+-NS_CHECK_EXEC([sw0-p1-rej], [tcpdump -l -nn -i sw0-p1-rej icmp > sw0-p1-rej-icmp.pcap 2> err &], [0])
++NETNS_START_TCPDUMP([sw0-p1-rej], [-nn -i sw0-p1-rej udp port 94], [sw0-p1-rej-udp])
++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
+- c=$(cat sw0-p1-rej-icmp.pcap | grep \
++ 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
+ ])
+
+-kill $(pidof tcpdump)
+-
+ # Now test for IPv6 UDP.
+-NS_CHECK_EXEC([sw0-p2-rej], [tcpdump -l -nn -i sw0-p2-rej udp port 90 > sw0-p2-rej-ip6-udp.pcap 2> err &], [0])
+-NS_CHECK_EXEC([sw0-p2-rej], [tcpdump -l -nn -i sw0-p2-rej icmp6 > sw0-p2-rej-icmp6.pcap 2> err &], [0])
++NETNS_START_TCPDUMP([sw0-p2-rej], [-nn -i sw0-p2-rej udp port 90], [sw0-p2-rej-ip6-udp])
++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
+- c=$(cat sw0-p2-rej-icmp6.pcap | grep \
++ 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)
+ test $c -ge 1
+ ])
+
+ kill $(pidof tcpdump)
+-rm -f *.pcap
++rm -f *.tcpdump
+
+-NS_CHECK_EXEC([sw0-p2-rej], [tcpdump -l -nn -i sw0-p2-rej udp port 94 > sw0-p2-rej-ip6-udp.pcap 2> err &], [0])
+-NS_CHECK_EXEC([sw0-p2-rej], [tcpdump -l -nn -i sw0-p2-rej icmp6 > sw0-p2-rej-icmp6.pcap 2> err &], [0])
++NETNS_START_TCPDUMP([sw0-p2-rej], [-nn -i sw0-p2-rej udp port 94], [sw0-p2-rej-ip6-udp])
++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
+- c=$(cat sw0-p2-rej-icmp6.pcap | grep \
++ 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)
+ test $c -ge 1
+@@ -5408,33 +5457,31 @@ OVS_WAIT_UNTIL([
+ ])
+
+ kill $(pidof tcpdump)
+-rm -f *.pcap
++rm -f *.tcpdump
+
+-NS_CHECK_EXEC([sw0-p1-rej], [tcpdump -l -nn -i sw0-p1-rej icmp > sw0-p1-rej-icmp.pcap 2> err &], [0])
++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
+- c=$(cat sw0-p1-rej-icmp.pcap | grep \
++ 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
+ ])
+
+ kill $(pidof tcpdump)
+-rm -f *.pcap
++rm -f *.tcpdump
+ # Now test for IPv6 UDP.
+-NS_CHECK_EXEC([sw0-p2-rej], [tcpdump -l -nn -i sw0-p2-rej icmp6 > sw0-p2-rej-icmp6.pcap 2> err &], [0])
++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
+- c=$(cat sw0-p2-rej-icmp6.pcap | grep \
++ 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)
+ test $c -ge 1
+ ])
+
+-kill $(pidof tcpdump)
+-
+ OVS_APP_EXIT_AND_WAIT([ovn-controller])
+
+ as ovn-sb
+@@ -6859,18 +6906,23 @@ check ovn-nbctl clear logical_router_static_route $route_uuid bfd
+ wait_column "admin_down" nb:bfd status logical_port=rp-public
+ OVS_WAIT_UNTIL([ip netns exec server bfdd-control status | grep -qi state=Down])
+
+-check ovn-nbctl --bfd lr-policy-add R1 100 "ip4.src == 200.0.0.0/8" reroute 172.16.1.50
+-wait_column "up" nb:bfd status logical_port=rp-public
+-OVS_WAIT_UNTIL([ovn-sbctl dump-flows R1 |grep lr_in_policy |grep 'ip4.src == 200.0.0.0/8' |grep -q 172.16.1.50])
++check ovn-nbctl --bfd lr-policy-add R1 100 "ip4.src == 200.0.0.0/8" reroute 172.16.1.50,172.16.1.60
++wait_column "up" nb:bfd status dst_ip=172.16.1.50
++OVS_WAIT_UNTIL([ovn-sbctl dump-flows R1 | grep lr_in_policy_ecmp | grep -q 172.16.1.50])
+
+ check ovn-nbctl lr-policy-del R1
+-wait_column "admin_down" nb:bfd status logical_port=rp-public
++wait_column "admin_down" nb:bfd status dst_ip=172.16.1.50
++wait_column "admin_down" nb:bfd status dst_ip=172.16.1.60
+ OVS_WAIT_UNTIL([ip netns exec server bfdd-control status | grep -qi state=Down])
+
+-NS_CHECK_EXEC([server], [tcpdump -nni s1 udp port 3784 -Q in > bfd.pcap &])
++NETNS_START_TCPDUMP([server], [-nni s1 udp port 3784 -Q in], [bfd])
+ sleep 5
+ kill $(pidof tcpdump)
+-AT_CHECK([grep -qi bfd bfd.pcap],[1])
++AT_CHECK([grep -qi bfd bfd.tcpdump],[1])
++
++uuid=$(fetch_column nb:bfd _uuid dst_ip="172.16.1.60")
++check ovn-nbctl destroy bfd $uuid
++uuid=$(fetch_column nb:bfd _uuid logical_port="rp-public")
+
+ # restart the connection
+ check ovn-nbctl set logical_router_static_route $route_uuid bfd=$uuid
+@@ -6928,10 +6980,10 @@ stopping
+ # remove bfd entry
+ ovn-nbctl destroy bfd $uuid
+ check_row_count bfd 0
+-NS_CHECK_EXEC([server], [tcpdump -nni s1 udp port 3784 -Q in > bfd.pcap &])
++NETNS_START_TCPDUMP([server], [-nni s1 udp port 3784 -Q in], [bfd])
+ sleep 5
+ kill $(pidof tcpdump)
+-AT_CHECK([grep -qi bfd bfd.pcap],[1])
++AT_CHECK([grep -qi bfd bfd.tcpdump],[1])
+
+ uuid_v6=$(ovn-nbctl create bfd logical_port=rp-public dst_ip=\"1000::b\")
+ check ovn-nbctl lr-route-add R1 2000::/64 1000::b
+@@ -7392,7 +7444,7 @@ check ovn-nbctl lsp-add public public1 \
+ -- lsp-set-type public1 localnet \
+ -- lsp-set-options public1 network_name=phynet
+
+-NS_EXEC([sw01], [tcpdump -l -n -i sw01 icmp -Q in > reject.pcap &])
++NETNS_START_TCPDUMP([sw01], [-n -i sw01 icmp -Q in], [reject])
+ check ovn-nbctl meter-add acl-meter drop 1 pktps 0
+ check ovn-nbctl copp-add copp0 reject acl-meter
+ check ovn-nbctl ls-copp-add copp0 sw0
+@@ -7409,14 +7461,14 @@ EOF
+
+ # 1pps
+ OVS_WAIT_UNTIL([
+- n_reject=$(grep unreachable reject.pcap | wc -l)
++ n_reject=$(grep unreachable reject.tcpdump | wc -l)
+ test "${n_reject}" = "1"
+ ])
+ kill $(pidof tcpdump)
+-rm -f reject.pcap
++rm -f reject.tcpdump
+
+ # Let's update the meter
+-NS_EXEC([sw01], [tcpdump -l -n -i sw01 icmp -Q in > reject.pcap &])
++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
+@@ -7426,14 +7478,14 @@ EOF
+
+ # 10pps
+ OVS_WAIT_UNTIL([
+- n_reject=$(grep unreachable reject.pcap | wc -l)
++ n_reject=$(grep unreachable reject.tcpdump | wc -l)
+ test "${n_reject}" = "5"
+ ])
+
+ kill $(pidof tcpdump)
+-rm -f reject.pcap
++rm -f reject.tcpdump
+
+-NS_EXEC([sw01], [tcpdump -l -n -i sw01 icmp -Q in > reject.pcap &])
++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
+@@ -7442,12 +7494,11 @@ send (p, iface='sw01', loop = 0, verbose = 0, count = 20)
+ EOF
+
+ OVS_WAIT_UNTIL([
+- n_reject=$(grep unreachable reject.pcap | wc -l)
++ n_reject=$(grep unreachable reject.tcpdump | wc -l)
+ test "${n_reject}" = "20"
+ ])
+-kill $(pidof tcpdump)
+
+-NS_EXEC([server], [tcpdump -l -n -i s1 arp[[24:4]]=0xac100164 > arp.pcap &])
++NETNS_START_TCPDUMP([server], [-n -i s1 arp[[24:4]]=0xac100164], [arp])
+ check ovn-nbctl meter-add arp-meter drop 1 pktps 0
+ check ovn-nbctl copp-add copp1 arp-resolve arp-meter
+ check ovn-nbctl --wait=hv lr-copp-add copp1 R1
+@@ -7462,10 +7513,9 @@ EOF
+
+ # 1pps
+ OVS_WAIT_UNTIL([
+- n_arp=$(grep ARP arp.pcap | wc -l)
++ n_arp=$(grep ARP arp.tcpdump | wc -l)
+ test "${n_arp}" = "1"
+ ])
+-kill $(pidof tcpdump)
+
+ check ovn-nbctl meter-add icmp-meter drop 1 pktps 0
+ check ovn-nbctl copp-add copp2 icmp4-error icmp-meter
+@@ -7474,7 +7524,7 @@ AT_CHECK([ovn-nbctl copp-list copp2 |grep icmp4-error], [0], [dnl
+ icmp4-error: icmp-meter
+ ])
+
+-NS_EXEC([sw01], [tcpdump -l -n -i sw01 icmp > icmp.pcap &])
++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)
+@@ -7482,10 +7532,9 @@ EOF
+
+ # 1pps
+ OVS_WAIT_UNTIL([
+- n_icmp=$(grep ICMP icmp.pcap | wc -l)
++ n_icmp=$(grep ICMP icmp.tcpdump | wc -l)
+ test "${n_icmp}" = "1"
+ ])
+-kill $(pidof tcpdump)
+
+ check ovn-nbctl meter-add bfd-meter drop 1 pktps 0
+ check ovn-nbctl copp-add copp3 bfd bfd-meter
+@@ -7496,7 +7545,7 @@ bfd: bfd-meter
+
+ check ovn-nbctl --wait=hv --bfd lr-route-add R1 240.0.0.0/8 172.16.1.50 rp-public
+ printf "%08x" $(ovn-sbctl get bfd . disc) > /tmp/disc
+-NS_EXEC([server], [tcpdump -l -nn -i s1 udp port 3784 and ip[[29]]==0x90 -Q in > bfd.pcap &])
++NETNS_START_TCPDUMP([server], [-nn -i s1 udp port 3784 and ip[[29]]==0x90 -Q in], [bfd])
+ ip netns exec server scapy -H <<-EOF
+ import binascii
+ f = open("/tmp/disc", "r")
+@@ -7511,10 +7560,9 @@ rm /tmp/disc
+
+ # 1pps
+ OVS_WAIT_UNTIL([
+- n_bfd=$(grep 3784 bfd.pcap | wc -l)
++ n_bfd=$(grep 3784 bfd.tcpdump | wc -l)
+ test "${n_bfd}" = "1"
+ ])
+-kill $(pidof tcpdump)
+
+ check ovn-nbctl set nb_global . options:svc_monitor_mac="33:33:33:33:33:33"
+ check ovn-nbctl meter-add svc-meter drop 1 pktps 0
+@@ -8830,7 +8878,7 @@ ADD_NAMESPACES(vm2)
+ ADD_VETH(vm2, vm2, br-int, "42.42.42.2/24", "00:00:00:00:00:02")
+
+ ADD_NAMESPACES(vm3)
+-NETNS_DAEMONIZE([vm3], [tcpdump -n -i any -nnleX > vm3.pcap 2>/dev/null], [tcpdump3.pid])
++NETNS_START_TCPDUMP([vm3], [-n -i any -nnleX], [vm3])
+
+ ADD_VETH(vm3, vm3, br-int, "42.42.42.3/24", "00:00:00:00:00:03", \
+ "42.42.42.5")
+@@ -8845,11 +8893,11 @@ wait_igmp_flows_installed 228.0.0.1
+ NS_CHECK_EXEC([vm1], [ping -q -c 3 -i 0.3 -w 2 228.0.0.1], [ignore], [ignore])
+
+ OVS_WAIT_UNTIL([
+- requests=`grep "ICMP echo request" -c vm3.pcap`
++ requests=`grep "ICMP echo request" -c vm3.tcpdump`
+ test "${requests}" -ge "3"
+ ])
+
+-NETNS_DAEMONIZE([vm2], [tcpdump -n -i any -nnleX > vm2.pcap 2>/dev/null], [tcpdump2.pid])
++NETNS_START_TCPDUMP([vm2], [-n -i any -nnleX], [vm2])
+
+ for i in `seq 1 40`;do
+ NS_CHECK_EXEC([vm2], [ip addr add 228.1.$i.1 dev vm2 autojoin &], [0])
+@@ -8868,7 +8916,7 @@ ovn-sbctl list multicast_group
+ NS_CHECK_EXEC([vm1], [ping -q -c 3 -i 0.3 -w 2 228.1.1.1], [ignore], [ignore])
+
+ OVS_WAIT_UNTIL([
+- requests=`grep "ICMP echo request" -c vm2.pcap`
++ requests=`grep "ICMP echo request" -c vm2.tcpdump`
+ test "${requests}" -ge "3"
+ ])
+
+@@ -9047,16 +9095,13 @@ test_related_traffic() {
+
+ check ovs-appctl dpctl/flush-conntrack
+
+- NETNS_DAEMONIZE([client], [tcpdump -l -U -i client -w client.pcap 2>client_err], [tcpdump0.pid])
+- NETNS_DAEMONIZE([server], [tcpdump -l -U -i server -w server.pcap 2>server_err], [tcpdump1.pid])
++ NETNS_START_TCPDUMP([client], [-U -i client -w client.pcap], [tcpdump0])
++ 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])
+
+- OVS_WAIT_UNTIL([grep "listening" client_err])
+- OVS_WAIT_UNTIL([grep "listening" server_err])
+-
+ # Send UDP client -> server
+ check ovs-ofctl packet-out br-int "in_port=ovs-client,packet=$client_udp,actions=resubmit(,0)"
+
+@@ -9275,10 +9320,9 @@ 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],
+- [tcpdump -n -i vm1 -nnleX -c6 udp and dst 42.42.42.2 and dst port 4343 > vm1.pcap 2> vm1.pcap.stderr],
+- [tcpdump1.pid])
+-OVS_WAIT_UNTIL([grep "listening" vm1.pcap.stderr])
++NETNS_START_TCPDUMP([vm1],
++ [-n -i vm1 -nnleX -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])
+@@ -9298,7 +9342,7 @@ NS_CHECK_EXEC([vm2], [echo a | nc -u 66.66.66.66 999], [ignore], [ignore], [igno
+ NS_CHECK_EXEC([vm3], [echo a | nc -u 66.66.66.66 999], [ignore], [ignore], [ignore])
+
+ OVS_WAIT_UNTIL([
+- requests=`grep "UDP" -c vm1.pcap`
++ requests=`grep "UDP" -c vm1.tcpdump`
+ test "${requests}" -ge "6"
+ ])
+
+@@ -9427,10 +9471,9 @@ 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],
+- [tcpdump -n -i vm1 -nnleX -c6 udp and dst 4242::2 and dst port 4343 > vm1.pcap 2> vm1.pcap.stderr],
+- [tcpdump1.pid])
+-OVS_WAIT_UNTIL([grep "listening" vm1.pcap.stderr])
++NETNS_START_TCPDUMP([vm1],
++ [-n -i vm1 -nnleX -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])
+@@ -9450,7 +9493,7 @@ 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])
+
+ OVS_WAIT_UNTIL([
+- requests=`grep "UDP" -c vm1.pcap`
++ requests=`grep "UDP" -c vm1.tcpdump`
+ test "${requests}" -ge "6"
+ ])
+
+@@ -9508,10 +9551,10 @@ ADD_NAMESPACES(remote1)
+ ADD_VETH(remote1, remote1, br-ext, "172.16.1.4/24", "f0:00:00:01:02:06", \
+ "172.16.1.1")
+
+-NETNS_DAEMONIZE([remote], [tcpdump -c 3 -nneei remote -Q in src 192.168.1.2 and dst 172.16.1.2 and icmp > icmp.pcap], [tcpdump0.pid])
+-NETNS_DAEMONIZE([remote], [tcpdump -c 1 -nneei remote -Q in arp and arp[[24:4]]==0xac100102 > arp.pcap], [tcpdump1.pid])
+-NETNS_DAEMONIZE([remote1], [tcpdump -c 3 -nneei remote1 -Q in src 172.16.1.3 and dst 172.16.1.4 and icmp > icmp1.pcap 2>/dev/null], [tcpdump2.pid])
+-NETNS_DAEMONIZE([remote1], [tcpdump -c 1 -nneei remote1 -Q in arp and arp[[24:4]]==0xac100104 > arp1.pcap 2>/dev/null], [tcpdump3.pid])
++NETNS_START_TCPDUMP([remote], [-c 3 -nneei remote -Q in src 192.168.1.2 and dst 172.16.1.2 and icmp], [icmp])
++NETNS_START_TCPDUMP([remote], [-c 1 -nneei remote -Q in arp and arp[[24:4]]==0xac100102], [arp])
++NETNS_START_TCPDUMP([remote1], [-c 3 -nneei remote1 -Q in src 172.16.1.3 and dst 172.16.1.4 and icmp], [icmp1])
++NETNS_START_TCPDUMP([remote1], [-c 1 -nneei remote1 -Q in arp and arp[[24:4]]==0xac100104], [arp1])
+
+ check ovn-nbctl lr-add R1 -- set Logical_Router R1 options:chassis=hv1
+ check ovn-nbctl ls-add sw0
+@@ -9558,12 +9601,12 @@ NS_CHECK_EXEC([sw01], [ping -q -c 3 -i 0.3 -w 2 172.16.1.2 | FORMAT_PING], \
+ ])
+
+ OVS_WAIT_UNTIL([
+- total_arp_pkts=$(cat arp.pcap | wc -l)
++ total_arp_pkts=$(cat arp.tcpdump | wc -l)
+ test "${total_arp_pkts}" = "1"
+ ])
+
+ OVS_WAIT_UNTIL([
+- total_icmp_pkts=$(cat icmp.pcap | wc -l)
++ total_icmp_pkts=$(cat icmp.tcpdump | wc -l)
+ test "${total_icmp_pkts}" = "3"
+ ])
+
+@@ -9573,12 +9616,12 @@ NS_CHECK_EXEC([sw11], [ping -q -c 3 -i 0.3 -w 2 172.16.1.4 | FORMAT_PING], \
+ ])
+
+ OVS_WAIT_UNTIL([
+- total_arp1_pkts=$(cat arp1.pcap | wc -l)
++ total_arp1_pkts=$(cat arp1.tcpdump | wc -l)
+ test "${total_arp1_pkts}" = "1"
+ ])
+
+ OVS_WAIT_UNTIL([
+- total_icmp1_pkts=$(cat icmp1.pcap | wc -l)
++ total_icmp1_pkts=$(cat icmp1.tcpdump | wc -l)
+ test "${total_icmp1_pkts}" = "3"
+ ])
+
+@@ -10756,8 +10799,7 @@ check ovn-nbctl lsp-add bar bar1 \
+ # wait for ovn-controller to catch up.
+ check ovn-nbctl --wait=hv sync
+
+-NETNS_DAEMONIZE([foo1], [tcpdump -l -nn -e -i foo1 'ether dst 0a:58:a9:fe:01:01 and icmp' > foo1-icmp.pcap 2>foo1-tcpdump.stderr], [foo1-icmp-tcpdump.pid])
+-OVS_WAIT_UNTIL([grep "listening" foo1-tcpdump.stderr])
++NETNS_START_TCPDUMP([foo1], [-nn -e -i foo1 'ether dst 0a:58:a9:fe:01:01 and icmp'], [foo1-icmp])
+
+ # 'foo1' should be able to ping 'bar1'
+ NS_CHECK_EXEC([foo1], [ping -q -c 3 -i 0.3 -w 2 192.168.2.2 | FORMAT_PING], \
+@@ -10765,12 +10807,11 @@ NS_CHECK_EXEC([foo1], [ping -q -c 3 -i 0.3 -w 2 192.168.2.2 | FORMAT_PING], \
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+ OVS_WAIT_UNTIL([
+- total_pkts=$(cat foo1-icmp.pcap| wc -l)
++ total_pkts=$(cat foo1-icmp.tcpdump| wc -l)
+ test "${total_pkts}" = "3"
+ ])
+
+-NETNS_DAEMONIZE([foo2], [tcpdump -l -nn -e -i foo2 'ether dst 0a:58:a9:fe:01:01 and icmp' > foo2-icmp.pcap 2>foo2-tcpdump.stderr], [foo2-icmp-tcpdump.pid])
+-OVS_WAIT_UNTIL([grep "listening" foo2-tcpdump.stderr])
++NETNS_START_TCPDUMP([foo2], [-nn -e -i foo2 'ether dst 0a:58:a9:fe:01:01 and icmp'], [foo2-icmp])
+
+ # 'foo2' should be able to ping 'bar1'
+ NS_CHECK_EXEC([foo2], [ping -q -c 3 -i 0.3 -w 2 192.168.2.2 | FORMAT_PING], \
+@@ -10778,12 +10819,11 @@ NS_CHECK_EXEC([foo2], [ping -q -c 3 -i 0.3 -w 2 192.168.2.2 | FORMAT_PING], \
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+ OVS_WAIT_UNTIL([
+- total_pkts=$(cat foo2-icmp.pcap| wc -l)
++ total_pkts=$(cat foo2-icmp.tcpdump| wc -l)
+ test "${total_pkts}" = "3"
+ ])
+
+-NETNS_DAEMONIZE([foo3], [tcpdump -l -nn -e -i foo3 'ether dst 0a:58:a9:fe:01:01 and icmp' > foo3-icmp.pcap 2>foo3-tcpdump.stderr], [foo3-icmp-tcpdump.pid])
+-OVS_WAIT_UNTIL([grep "listening" foo3-tcpdump.stderr])
++NETNS_START_TCPDUMP([foo3], [-nn -e -i foo3 'ether dst 0a:58:a9:fe:01:01 and icmp'], [foo3-icmp])
+
+ # 'foo3' should be able to ping 'bar1'
+ NS_CHECK_EXEC([foo3], [ping -q -c 3 -i 0.3 -w 2 192.168.2.2 | FORMAT_PING], \
+@@ -10791,12 +10831,11 @@ NS_CHECK_EXEC([foo3], [ping -q -c 3 -i 0.3 -w 2 192.168.2.2 | FORMAT_PING], \
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+ OVS_WAIT_UNTIL([
+- total_pkts=$(cat foo3-icmp.pcap| wc -l)
++ total_pkts=$(cat foo3-icmp.tcpdump| wc -l)
+ test "${total_pkts}" = "3"
+ ])
+
+-NETNS_DAEMONIZE([ext1], [tcpdump -l -nn -e -i ext1 'ether dst 0a:58:a9:fe:01:01 and icmp' > ext1-icmp.pcap 2>ext1-tcpdump.stderr], [ext1-icmp-tcpdump.pid])
+-OVS_WAIT_UNTIL([grep "listening" ext1-tcpdump.stderr])
++NETNS_START_TCPDUMP([ext1], [-nn -e -i ext1 'ether dst 0a:58:a9:fe:01:01 and icmp'], [ext1-icmp])
+
+ # 'ext1' should be able to ping 'bar1'
+ NS_CHECK_EXEC([ext1], [ping -q -c 3 -i 0.3 -w 2 192.168.2.2 | FORMAT_PING], \
+@@ -10804,7 +10843,7 @@ NS_CHECK_EXEC([ext1], [ping -q -c 3 -i 0.3 -w 2 192.168.2.2 | FORMAT_PING], \
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+ OVS_WAIT_UNTIL([
+- total_pkts=$(cat ext1-icmp.pcap| wc -l)
++ total_pkts=$(cat ext1-icmp.tcpdump| wc -l)
+ test "${total_pkts}" = "3"
+ ])
+
+@@ -10900,8 +10939,7 @@ check ovn-nbctl --wait=hv sync
+ # Force ipv6 nd neighbour solicitation
+ NS_EXEC([foo1], [ping6 -c 1 fd12::2])
+
+-NETNS_DAEMONIZE([foo1], [tcpdump -vvvv -ttt -l -nn -e -i foo1 'ether dst 0a:58:a9:fe:01:01 and icmp6' > foo1-icmp6.pcap 2> foo1-tcpdump.stderr], [foo1-icmp6-tcpdump.pid])
+-OVS_WAIT_UNTIL([grep "listening" foo1-tcpdump.stderr])
++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], \
+@@ -10909,15 +10947,14 @@ NS_CHECK_EXEC([foo1], [ping6 -q -c 3 -i 0.3 -w 2 fd12::2 | FORMAT_PING], \
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+ OVS_WAIT_UNTIL([
+- total_pkts=$(cat foo1-icmp6.pcap| grep "echo request" | wc -l)
++ total_pkts=$(cat foo1-icmp6.tcpdump| grep "echo request" | wc -l)
+ test "${total_pkts}" = "3"
+ ])
+
+ # Force ipv6 nd neighbour solicitation
+ NS_EXEC([foo2], [ping6 -c 1 fd12::2])
+
+-NETNS_DAEMONIZE([foo2], [tcpdump -vvvv -ttt -l -nn -e -i foo2 'ether dst 0a:58:a9:fe:01:01 and icmp6' > foo2-icmp6.pcap 2> foo2-tcpdump.stderr], [foo2-icmp6-tcpdump.pid])
+-OVS_WAIT_UNTIL([grep "listening" foo2-tcpdump.stderr])
++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], \
+@@ -10925,15 +10962,14 @@ NS_CHECK_EXEC([foo2], [ping6 -q -c 3 -i 0.3 -w 2 fd12::2 | FORMAT_PING], \
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+ OVS_WAIT_UNTIL([
+- total_pkts=$(cat foo2-icmp6.pcap| grep "echo request" | wc -l)
++ total_pkts=$(cat foo2-icmp6.tcpdump| grep "echo request" | wc -l)
+ test "${total_pkts}" = "3"
+ ])
+
+ # Force ipv6 nd neighbour solicitation
+ NS_EXEC([foo3], [ping6 -c 1 fd12::2])
+
+-NETNS_DAEMONIZE([foo3], [tcpdump -vvvv -ttt -l -nn -e -i foo3 'ether dst 0a:58:a9:fe:01:01 and icmp6' > foo3-icmp6.pcap 2> foo3-tcpdump.stderr], [foo3-icmp6-tcpdump.pid])
+-OVS_WAIT_UNTIL([grep "listening" foo3-tcpdump.stderr])
++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], \
+@@ -10941,7 +10977,7 @@ NS_CHECK_EXEC([foo3], [ping6 -q -c 3 -i 0.3 -w 2 fd12::2 | FORMAT_PING], \
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+ OVS_WAIT_UNTIL([
+- total_pkts=$(cat foo3-icmp6.pcap| grep "echo request" | wc -l)
++ total_pkts=$(cat foo3-icmp6.tcpdump| grep "echo request" | wc -l)
+ test "${total_pkts}" = "3"
+ ])
+
+@@ -11361,27 +11397,23 @@ AT_CHECK([ip addr add 172.16.0.101/24 dev br-mirror])
+ AT_CHECK([ip addr add 2003::a/64 dev br-mirror nodad])
+ AT_CHECK([ip link set dev br-mirror up])
+
+-NS_CHECK_EXEC([mirror], [tcpdump -l -c 3 -neei mirror proto GRE > gre_mirror4.pcap 2>gre_mirror4_error &])
+-OVS_WAIT_UNTIL([grep "listening" gre_mirror4_error])
++NETNS_START_TCPDUMP([mirror], [-c 3 -neei mirror proto GRE], [gre_mirror4])
+
+ NS_CHECK_EXEC([foo1], [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
+ ])
+ OVS_WAIT_UNTIL([
+- n_packets=$(grep "GRE" -c gre_mirror4.pcap)
++ n_packets=$(grep "GRE" -c gre_mirror4.tcpdump)
+ test "${n_packets}" = "3"
+ ])
+
+-killall tcpdump
+-
+ ovn-nbctl mirror-del mirror0
+ ovn-nbctl mirror-add mirror1 gre 2 to-lport 2003::b
+
+ ovn-nbctl --wait=hv lsp-attach-mirror bar1 mirror1
+
+-NS_CHECK_EXEC([mirror], [tcpdump -l -c 3 -neei mirror proto GRE > gre_mirror6.pcap 2>gre_mirror6_error &])
+-OVS_WAIT_UNTIL([grep "listening" gre_mirror6_error])
++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], \
+ [0], [dnl
+@@ -11389,53 +11421,44 @@ NS_CHECK_EXEC([foo1], [ping6 -q -c 3 -i 0.3 -w 2 2002::2 | FORMAT_PING], \
+ ])
+
+ OVS_WAIT_UNTIL([
+- n_packets=$(grep "GRE" -c gre_mirror6.pcap)
++ n_packets=$(grep "GRE" -c gre_mirror6.tcpdump)
+ test "${n_packets}" = "3"
+ ])
+
+-killall tcpdump
+-
+ ovn-nbctl mirror-del mirror1
+ ovn-nbctl mirror-add mirror2 erspan 3 to-lport 172.16.0.100
+ ovn-nbctl --wait=hv lsp-attach-mirror bar1 mirror2
+
+-NS_CHECK_EXEC([mirror], [tcpdump -l -c 3 -neei mirror ip[[22:2]]=0x88be > erspan_mirror4.pcap 2>erspan_mirror4_error &])
+-OVS_WAIT_UNTIL([grep "listening" erspan_mirror4_error])
++NETNS_START_TCPDUMP([mirror], [-c 3 -neei mirror ip[[22:2]]=0x88be], [erspan_mirror4])
+
+ NS_CHECK_EXEC([foo1], [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
+ ])
+ OVS_WAIT_UNTIL([
+- n_packets=$(grep "gre-proto-0x88be" -c erspan_mirror4.pcap)
++ n_packets=$(grep "gre-proto-0x88be" -c erspan_mirror4.tcpdump)
+ test "${n_packets}" = "3"
+ ])
+
+-killall tcpdump
+-
+ ovn-nbctl mirror-del mirror2
+ ovn-nbctl mirror-add mirror3 erspan 4 to-lport 2003::b
+ ovn-nbctl --wait=hv lsp-attach-mirror bar1 mirror3
+
+-NS_CHECK_EXEC([mirror], [tcpdump -l -c 3 -neei mirror ip6[[42:2]]=0x88be > erspan_mirror6.pcap 2>erspan_mirror6_error &])
+-OVS_WAIT_UNTIL([grep "listening" erspan_mirror6_error])
++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], \
+ [0], [dnl
+ 3 packets transmitted, 3 received, 0% packet loss, time 0ms
+ ])
+ OVS_WAIT_UNTIL([
+- n_packets=$(grep "gre-proto-0x88be" -c erspan_mirror6.pcap)
++ n_packets=$(grep "gre-proto-0x88be" -c erspan_mirror6.tcpdump)
+ test "${n_packets}" = "3"
+ ])
+
+-killall tcpdump
+-
+ uuid=$(fetch_column nb:mirror _uuid name="mirror3")
+ ovn-nbctl --wait=hv set mirror $uuid type=gre
+
+-NS_CHECK_EXEC([mirror], [tcpdump -c 3 -l -neei mirror proto GRE > gre_mirror6.pcap 2>gre_mirror6_error &])
+-OVS_WAIT_UNTIL([grep "listening" gre_mirror6_error])
++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], \
+ [0], [dnl
+@@ -11443,12 +11466,10 @@ NS_CHECK_EXEC([foo1], [ping6 -q -c 3 -i 0.3 -w 2 2002::2 | FORMAT_PING], \
+ ])
+
+ OVS_WAIT_UNTIL([
+- n_packets=$(grep "GRE" -c gre_mirror6.pcap)
++ n_packets=$(grep "GRE" -c gre2_mirror6.tcpdump)
+ test "${n_packets}" = "3"
+ ])
+
+-killall tcpdump
+-
+ OVS_APP_EXIT_AND_WAIT([ovn-controller])
+
+ as ovn-sb
+@@ -12264,3 +12285,302 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
+ /connection dropped.*/d"])
+ AT_CLEANUP
+ ])
++
++OVN_FOR_EACH_NORTHD([
++AT_SETUP([IP NAT add-route])
++AT_KEYWORDS([ip-nat-add-route])
++
++ovn_start
++OVS_TRAFFIC_VSWITCHD_START()
++ADD_BR([br-int])
++
++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_daemon ovn-controller
++
++check ovn-nbctl lr-add lr0
++check ovn-nbctl set logical_router lr0 options:chassis=hv1
++
++check ovn-nbctl lr-add lr1
++check ovn-nbctl set logical_router lr1 options:chassis=hv1
++
++check ovn-nbctl ls-add sw0
++check ovn-nbctl ls-add sw1
++check ovn-nbctl ls-add join
++
++check ovn-nbctl lrp-add lr0 lr-sw0 00:00:01:01:02:03 192.168.0.1/24
++check ovn-nbctl lsp-add sw0 rp-sw0 -- set Logical_Switch_Port rp-sw0 \
++ type=router options:router-port=lr-sw0 \
++ -- lsp-set-addresses rp-sw0 router
++
++check ovn-nbctl lrp-add lr0 lr0-join 04:00:01:01:02:03 172.16.1.1/24
++check ovn-nbctl lsp-add join rp0-join -- set Logical_Switch_Port rp0-join \
++ type=router options:router-port=lr0-join \
++ -- lsp-set-addresses rp0-join router
++
++check ovn-nbctl lrp-add lr1 lr-sw1 00:00:02:01:02:03 192.168.1.1/24
++check ovn-nbctl lsp-add sw1 rp-sw1 -- set Logical_Switch_Port rp-sw1 \
++ type=router options:router-port=lr-sw1 \
++ -- lsp-set-addresses rp-sw1 router
++
++check ovn-nbctl lrp-add lr1 lr1-join 04:00:02:01:02:03 172.16.2.2/24
++check ovn-nbctl lsp-add join rp1-join -- set Logical_Switch_Port rp1-join \
++ type=router options:router-port=lr1-join \
++ -- lsp-set-addresses rp1-join router
++
++ADD_NAMESPACES(sw0-p0)
++ADD_VETH(sw0-p0, sw0-p0, br-int, "192.168.0.2/24", "f0:00:00:01:02:03", "192.168.0.1")
++check ovn-nbctl lsp-add sw0 sw0-p0 \
++ -- lsp-set-addresses sw0-p0 "f0:00:00:01:02:03 192.168.0.2"
++
++ADD_NAMESPACES(sw1-p0)
++ADD_VETH(sw1-p0, sw1-p0, br-int, "192.168.1.2/24", "f0:00:00:11:02:03", "192.168.1.1")
++check ovn-nbctl lsp-add sw1 sw1-p0 \
++ -- lsp-set-addresses sw1-p0 "f0:00:00:11:02:03 192.168.1.2"
++
++check ovn-nbctl --add-route lr-nat-add lr0 dnat_and_snat 172.16.1.100 192.168.0.2 sw0-p0 00:00:00:00:03:01
++check ovn-nbctl --add-route lr-nat-add lr1 dnat_and_snat 172.16.1.101 192.168.1.2 sw1-p0 00:00:00:00:04:01
++
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
++
++NS_CHECK_EXEC([sw0-p0], [ping -q -c 3 -i 0.3 -w 2 172.16.1.101 | FORMAT_PING], \
++[0], [dnl
++3 packets transmitted, 3 received, 0% packet loss, time 0ms
++])
++
++NS_CHECK_EXEC([sw1-p0], [ping -q -c 3 -i 0.3 -w 2 172.16.1.100 | FORMAT_PING], \
++[0], [dnl
++3 packets transmitted, 3 received, 0% packet loss, time 0ms
++])
++
++OVS_APP_EXIT_AND_WAIT([ovn-controller])
++
++as ovn-sb
++OVS_APP_EXIT_AND_WAIT([ovsdb-server])
++
++as ovn-nb
++OVS_APP_EXIT_AND_WAIT([ovsdb-server])
++
++as northd
++OVS_APP_EXIT_AND_WAIT([ovn-northd])
++
++as
++OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
++/connection dropped.*/d"])
++AT_CLEANUP
++])
++
++OVN_FOR_EACH_NORTHD([
++AT_SETUP([MAC_Bindings updates on read-only sb])
++ovn_start --use-tcp-to-sb
++OVS_TRAFFIC_VSWITCHD_START()
++ADD_BR([br-int])
++
++PARSE_LISTENING_PORT([$ovs_base/ovn-sb/ovsdb-server.log], [TCP_PORT])
++
++# Use tcp to connect to sb
++ovs-vsctl \
++ -- set Open_vSwitch . external-ids:system-id=hv1 \
++ -- set Open_vSwitch . external-ids:ovn-remote=tcp:127.0.0.1:$TCP_PORT \
++ -- 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
++
++# Logical network:
++# A public switch (pub) with a localnet port connected to two LRs (lr0 and lr1)
++# each with a distributed gateway port.
++# Two VMs: lp0 on sw0 connected to lr0
++# lp1 on sw1 connected to lr1
++#
++# This test adds a floating IP on one VM and checks the MAC_Binding entries to be updated properly.
++
++# Create logical switches
++check ovn-nbctl ls-add sw0
++check ovn-nbctl ls-add sw1
++check ovn-nbctl ls-add pub
++
++# Created localnet port on public switch
++check ovn-nbctl lsp-add pub ln-pub
++check ovn-nbctl lsp-set-type ln-pub localnet
++check ovn-nbctl lsp-set-addresses ln-pub unknown
++check ovn-nbctl lsp-set-options ln-pub network_name=phys
++
++# Create logical routers and connect them to public switch
++AT_CHECK([(ovn-nbctl create Logical_Router name=lr0;
++ ovn-nbctl create Logical_Router name=lr1) | uuidfilt], [0], [<0>
++<1>
++])
++check ovn-nbctl lrp-add lr0 lr0-pub f0:00:00:00:00:01 172.24.4.220/24
++check ovn-nbctl lsp-add pub pub-lr0 -- set Logical_Switch_Port pub-lr0 \
++ type=router options:router-port=lr0-pub options:nat-addresses="router" addresses="router"
++check ovn-nbctl lrp-add lr1 lr1-pub f0:00:00:00:01:01 172.24.4.221/24
++check ovn-nbctl lsp-add pub pub-lr1 -- set Logical_Switch_Port pub-lr1 \
++ type=router options:router-port=lr1-pub options:nat-addresses="router" addresses="router"
++
++check ovn-nbctl lrp-set-gateway-chassis lr0-pub hv1 10
++check ovn-nbctl lrp-set-gateway-chassis lr1-pub hv1 10
++
++# Connect sw0 and sw1 to lr0 and lr1
++check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.254/24
++check ovn-nbctl lsp-add sw0 sw0-lr0 -- set Logical_Switch_Port sw0-lr0 type=router \
++ options:router-port=lr0-sw0 addresses="router"
++check ovn-nbctl lrp-add lr1 lr1-sw1 00:00:00:00:ff:02 20.0.0.254/24
++check ovn-nbctl lsp-add sw1 sw1-lr1 -- set Logical_Switch_Port sw1-lr1 type=router \
++ options:router-port=lr1-sw1 addresses="router"
++
++ADD_BR([br-phys])
++check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
++
++check ovs-vsctl add-port br-int vif0 \
++ -- set Interface vif0 external-ids:iface-id=lp0 \
++ -- set Interface vif0 type=internal
++
++check ovn-nbctl lsp-add sw0 lp0
++check ovn-nbctl lsp-add sw1 lp1
++check ovn-nbctl lsp-set-addresses lp0 "50:54:00:00:00:01 10.0.0.10"
++check ovn-nbctl lsp-set-addresses lp1 "50:54:00:00:00:02 20.0.0.10"
++
++OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lp0` = xup])
++ovn-nbctl --wait=hv sync
++
++# Stopping updates to sb
++# By stopping temporarily updates from controller to sb, we are making sb read-only.
++# We can't just pause sb to make it read-only, as we expect sb to still handle northd changes.
++stop_ovsdb_controller_updates $TCP_PORT
++
++# Adding lp1 : this will make sb read-only
++check ovs-vsctl add-port br-int vif1 \
++ -- set Interface vif1 external-ids:iface-id=lp1 \
++ -- set Interface vif1 type=internal
++
++# Make sure ovn-controller handled vif1 addition. So, at this point ovn-controller did try to update sb
++OVS_WAIT_UNTIL([test x`ovs-vsctl get Interface vif1 external_ids:ovn-installed` = x'"true"'])
++
++# Create floating IP. SB should be read-only, so we should not be enable to update MAC_Bindings now.
++check ovn-nbctl --wait=sb lr-nat-add lr0 dnat_and_snat 172.24.4.100 10.0.0.10
++
++# Restarting updates.
++restart_ovsdb_controller_updates $TCP_PORT
++# Check that the MAC_Binding entries have been properly created as SB is now writable.
++wait_row_count MAC_Binding 1 logical_port=lr1-pub ip=172.24.4.100
++
++OVS_APP_EXIT_AND_WAIT([ovn-controller])
++
++as ovn-sb
++OVS_APP_EXIT_AND_WAIT([ovsdb-server])
++
++as ovn-nb
++OVS_APP_EXIT_AND_WAIT([ovsdb-server])
++
++as northd
++OVS_APP_EXIT_AND_WAIT([ovn-northd])
++
++as
++OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
++/connection dropped.*/d"])
++AT_CLEANUP
++])
++
++OVN_FOR_EACH_NORTHD([
++AT_SETUP([LB with first packet buffered])
++AT_KEYWORDS([ovnlb])
++
++CHECK_CONNTRACK()
++CHECK_CONNTRACK_NAT()
++
++ovn_start
++OVS_TRAFFIC_VSWITCHD_START()
++ADD_BR([br-int])
++ADD_BR([br-ext])
++
++check ovs-ofctl add-flow br-ext action=normal
++# 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 \
++ -- set Open_vSwitch . external-ids:ovn-bridge-mappings=phynet:br-ext
++
++# Start ovn-controller
++start_daemon ovn-controller
++
++check ovn-nbctl lr-add lr
++check ovn-nbctl ls-add internal
++check ovn-nbctl ls-add public
++
++check ovn-nbctl lrp-add lr lr-pub 00:00:01:01:02:03 192.168.100.1/24 1000::1/64
++check ovn-nbctl lsp-add public pub-lr -- set Logical_Switch_Port pub-lr \
++ type=router options:router-port=lr-pub addresses=\"00:00:01:01:02:03\"
++
++check ovn-nbctl lrp-add lr lr-internal 00:00:01:01:02:04 192.168.200.1/24 2000::1/64
++check ovn-nbctl lsp-add internal internal-lr -- set Logical_Switch_Port internal-lr \
++ type=router options:router-port=lr-internal addresses=\"00:00:01:01:02:04\"
++
++check ovn-nbctl lsp-add internal server -- lsp-set-addresses server "unknown"
++
++ovn-nbctl lsp-add public ln_port \
++ -- lsp-set-addresses ln_port unknown \
++ -- lsp-set-type ln_port localnet \
++ -- lsp-set-options ln_port network_name=phynet
++
++check ovn-nbctl set logical_router lr options:chassis=hv1
++
++check ovn-nbctl lb-add lb1 192.168.100.20 192.168.200.10
++check ovn-nbctl lb-add lb2 1000::20 2000::10
++check ovn-nbctl lr-lb-add lr lb1
++check ovn-nbctl lr-lb-add lr lb2
++check ovn-nbctl --wait=hv sync
++
++ADD_NAMESPACES(client)
++ADD_VETH(client, client, br-ext, "1000::10/64", "f0:00:00:01:02:03", \
++ "1000::1", "nodad", "192.168.100.10/24", "192.168.100.1")
++
++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")
++
++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_START_TCPDUMP([client], [-l -U -i client -vnne udp], [client])
++NETNS_START_TCPDUMP([server], [-l -U -i server -vnne udp], [server])
++
++check ovs-appctl dpctl/flush-conntrack
++
++NS_CHECK_EXEC([client], [nc -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])
++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)
++AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep -c "zone=$zone_id"], [0], [2
++])
++
++OVS_APP_EXIT_AND_WAIT([ovn-controller])
++
++as ovn-sb
++OVS_APP_EXIT_AND_WAIT([ovsdb-server])
++
++as ovn-nb
++OVS_APP_EXIT_AND_WAIT([ovsdb-server])
++
++as northd
++OVS_APP_EXIT_AND_WAIT([ovn-northd])
++
++as
++OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
++/connection dropped.*/d"])
++AT_CLEANUP
++])
+diff --git a/tests/test-ovn.c b/tests/test-ovn.c
+index 5326c6e69..2ea68f212 100644
+--- a/tests/test-ovn.c
++++ b/tests/test-ovn.c
+@@ -1359,6 +1359,7 @@ test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED)
+ .group_table = &group_table,
+ .meter_table = &meter_table,
+ .collector_ids = &collector_ids,
++ .explicit_arp_ns_output = true,
+
+ .pipeline = OVNACT_P_INGRESS,
+ .ingress_ptable = OFTABLE_LOG_INGRESS_PIPELINE,
+diff --git a/utilities/ovn-ctl b/utilities/ovn-ctl
+index 50d588358..6595e1ba5 100755
+--- a/utilities/ovn-ctl
++++ b/utilities/ovn-ctl
+@@ -265,8 +265,8 @@ $cluster_remote_port
+ # Set the owner of the ovn_dbdir (with -R option) to OVN_USER if set.
+ # This is required because the ovndbs are created with root permission
+ # if not present when create_cluster/upgrade_db is called.
+- INSTALL_USER="root"
+- INSTALL_GROUP="root"
++ INSTALL_USER="$(id -un)"
++ INSTALL_GROUP="$(id -gn)"
+ [ "$OVN_USER" != "" ] && INSTALL_USER="${OVN_USER%:*}"
+ [ "${OVN_USER##*:}" != "" ] && INSTALL_GROUP="${OVN_USER##*:}"
+
+@@ -1023,7 +1023,6 @@ Options:
+ --ovn-ic-sb-db-ssl-cert=CERT OVN IC Southbound DB SSL certificate file
+ --ovn-ic-sb-db-ssl-ca-cert=CERT OVN IC Southbound DB SSL CA certificate file
+ --ovn-user="user[:group]" pass the --user flag to the ovn daemons
+- --ovs-user="user[:group]" pass the --user flag to ovs daemons
+ --ovsdb-nb-wrapper=WRAPPER run with a wrapper like valgrind for debugging
+ --ovsdb-sb-wrapper=WRAPPER run with a wrapper like valgrind for debugging
+ --ovsdb-disable-file-column-diff=no|yes
+diff --git a/utilities/ovn-ctl.8.xml b/utilities/ovn-ctl.8.xml
+index 3bab055e4..41ce6002b 100644
+--- a/utilities/ovn-ctl.8.xml
++++ b/utilities/ovn-ctl.8.xml
+@@ -70,7 +70,6 @@
+ --ovsdb-nb-wrapper=WRAPPER
+ --ovsdb-sb-wrapper=WRAPPER
+ --ovn-user=USER:GROUP
+- --ovs-user=USER:GROUP
+ -h
| --help
+
+ File location options
+diff --git a/utilities/ovn-lib.in b/utilities/ovn-lib.in
+index 1e48ef28c..65cbfbcdc 100644
+--- a/utilities/ovn-lib.in
++++ b/utilities/ovn-lib.in
+@@ -48,8 +48,8 @@ LC_ALL=C; export LC_ALL
+ ovn_install_dir () {
+ DIR="$1"
+ INSTALL_MODE="${2:-755}"
+- INSTALL_USER="root"
+- INSTALL_GROUP="root"
++ INSTALL_USER="$(id -un)"
++ INSTALL_GROUP="$(id -gn)"
+ [ "$OVN_USER" != "" ] && INSTALL_USER="${OVN_USER%:*}"
+ [ "${OVN_USER##*:}" != "" ] && INSTALL_GROUP="${OVN_USER##*:}"
+
+diff --git a/utilities/ovn-nbctl.8.xml b/utilities/ovn-nbctl.8.xml
+index 39c352212..ea2b201a5 100644
+--- a/utilities/ovn-nbctl.8.xml
++++ b/utilities/ovn-nbctl.8.xml
+@@ -1175,7 +1175,7 @@
+ NAT Commands
+
+
+- - [
--may-exist
] [--stateless
] [--gateway-port
=GATEWAY_PORT] lr-nat-add
router type external_ip logical_ip [logical_port external_mac]
++ - [
--may-exist
] [--stateless
] [--gateway-port
=GATEWAY_PORT] [--portrange] lr-nat-add
router type external_ip logical_ip [logical_port external_mac] [external_port_range]
+ -
+
+ Adds the specified NAT to router.
+@@ -1212,6 +1212,18 @@
+ specify the GATEWAY_PORT.
+
+
++
++ If the --portrange
option is specified, then a range of
++ ports may be specified in the external_port_range
part
++ of the lr-nat-add
command. If this option is omitted,
++ then an external port range may not be specified. The format of the
++ port range is port_low-port_high
, where
++ port_low is a lower number than port_high. When
++ the packet is NATted, a random port from the range will be selected
++ as the source port. The range for the
++ external_port_range
is 1-65535
.
++
++
+
+ When type is dnat
, the externally
+ visible IP address external_ip is DNATted to the
+diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
+index 25eb86f7f..618f3a18b 100644
+--- a/utilities/ovn-nbctl.c
++++ b/utilities/ovn-nbctl.c
+@@ -392,6 +392,7 @@ Route commands:\n\
+ [--ecmp]\n\
+ [--ecmp-symmetric-reply]\n\
+ [--route-table=ROUTE_TABLE]\n\
++ [--bfd]\n\
+ lr-route-add ROUTER PREFIX NEXTHOP [PORT]\n\
+ add a route to ROUTER\n\
+ [--policy=POLICY]\n\
+@@ -402,6 +403,7 @@ Route commands:\n\
+ lr-route-list ROUTER print routes for ROUTER\n\
+ \n\
+ Policy commands:\n\
++ [--bfd]\n\
+ lr-policy-add ROUTER PRIORITY MATCH ACTION [NEXTHOP,[NEXTHOP,...]] \
+ [OPTIONS KEY=VALUE ...] \n\
+ add a policy to router\n\
+diff --git a/utilities/ovn-sbctl.8.xml b/utilities/ovn-sbctl.8.xml
+index 81ab4131d..32035d051 100644
+--- a/utilities/ovn-sbctl.8.xml
++++ b/utilities/ovn-sbctl.8.xml
+@@ -123,10 +123,10 @@
+
-
+ Instructs the daemon process to run one or more
ovn-sbctl
+ commands described above and reply with the results of running these
+- commands. Accepts the --no-wait
, --wait
,
+- --timeout
, --dry-run
, --oneline
,
+- and the options described under Table Formatting Options
+- in addition to the the command-specific options.
++ commands. Accepts the --timeout
, --dry-run
,
++ --oneline
, and the options described under
++ Table Formatting Options
in addition to the the
++ command-specific options.
+
+
+ exit
+diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c
+index e0f1c3ec9..bb5afc275 100644
+--- a/utilities/ovn-trace.c
++++ b/utilities/ovn-trace.c
+@@ -2463,24 +2463,20 @@ execute_ct_nat(const struct ovnact_ct_nat *ct_nat,
+ }
+
+ static void
+-execute_ct_commit_nat(const struct ovnact_ct_commit_nat *ct_nat,
+- const struct ovntrace_datapath *dp, struct flow *uflow,
+- enum ovnact_pipeline pipeline, struct ovs_list *super)
++ct_commit_to_zone__(const struct ovnact_ct_commit_to_zone *ct_nat,
++ const struct ovntrace_datapath *dp, struct flow *uflow,
++ enum ovnact_pipeline pipeline, struct ovs_list *super,
++ struct ds *action)
+ {
+ struct flow ct_flow = *uflow;
+- struct ds s = DS_EMPTY_INITIALIZER;
+-
+- ds_put_cstr(&s, "ct_commit_nat /* assuming no"
+- " un-nat entry, so no change */");
+
+ /* ct(nat) implies ct(). */
+ if (!(ct_flow.ct_state & CS_TRACKED)) {
+- ct_flow.ct_state |= next_ct_state(&s);
++ ct_flow.ct_state |= next_ct_state(action);
+ }
+
+ struct ovntrace_node *node = ovntrace_node_append(
+- super, OVNTRACE_NODE_TRANSFORMATION, "%s", ds_cstr(&s));
+- ds_destroy(&s);
++ super, OVNTRACE_NODE_TRANSFORMATION, "%s", ds_cstr(action));
+
+ /* Trace the actions in the next table. */
+ trace__(dp, &ct_flow, ct_nat->ltable, pipeline, &node->subs);
+@@ -2490,6 +2486,30 @@ execute_ct_commit_nat(const struct ovnact_ct_commit_nat *ct_nat,
+ * flow, not ct_flow. */
+ }
+
++static void
++execute_ct_commit_nat(const struct ovnact_ct_commit_to_zone *ct_nat,
++ const struct ovntrace_datapath *dp, struct flow *uflow,
++ enum ovnact_pipeline pipeline, struct ovs_list *super)
++{
++ struct ds s = DS_EMPTY_INITIALIZER;
++ ds_put_cstr(&s, "ct_commit_nat /* assuming no"
++ " un-nat entry, so no change */");
++ ct_commit_to_zone__(ct_nat, dp, uflow, pipeline, super, &s);
++ ds_destroy(&s);
++}
++
++static void
++execute_ct_commit_to_zone(const struct ovnact_ct_commit_to_zone *ct_commit,
++ const struct ovntrace_datapath *dp,
++ struct flow *uflow, enum ovnact_pipeline pipeline,
++ struct ovs_list *super)
++{
++ struct ds s = DS_EMPTY_INITIALIZER;
++ ds_put_format(&s, "ct_commit_to_zone(%s)",
++ ct_commit->dnat_zone ? "dnat" : "snat");
++ ct_commit_to_zone__(ct_commit, dp, uflow, pipeline, super, &s);
++ ds_destroy(&s);
++}
+
+ static void
+ execute_ct_lb(const struct ovnact_ct_lb *ct_lb,
+@@ -3148,6 +3168,11 @@ trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len,
+ flow_clear_conntrack(uflow);
+ break;
+
++ case OVNACT_CT_COMMIT_TO_ZONE:
++ execute_ct_commit_to_zone(ovnact_get_CT_COMMIT_TO_ZONE(a), dp,
++ uflow, pipeline, super);
++ break;
++
+ case OVNACT_CT_COMMIT_NAT:
+ execute_ct_commit_nat(ovnact_get_CT_COMMIT_NAT(a), dp, uflow,
+ pipeline, super);
+@@ -3540,7 +3565,7 @@ trace(const char *dp_s, const char *flow_s)
+ }
+ uint32_t in_key = uflow.regs[MFF_LOG_INPORT - MFF_REG0];
+ if (!in_key) {
+- VLOG_WARN("microflow does not specify ingress port");
++ return xstrdup("microflow does not specify ingress port");
+ }
+ const struct ovntrace_port *inport = ovntrace_port_find_by_key(dp, in_key);
+ const char *inport_name = inport ? inport->friendly_name : "(unnamed)";
+diff --git a/utilities/ovn_detrace.py.in b/utilities/ovn_detrace.py.in
+index 364f11a71..12df6e52a 100755
+--- a/utilities/ovn_detrace.py.in
++++ b/utilities/ovn_detrace.py.in
+@@ -37,6 +37,9 @@ except Exception:
+
+ argv0 = sys.argv[0]
+ version = "@VERSION@"
++DB_CONNECTION_ERR = ('The connection to {0} DB is not available,'
++ ' {0} information will be missing from the detrace.')
++
+
+ def usage():
+ print("""\
+@@ -77,6 +80,11 @@ def chassis_str(chassis):
+ ch = chassis[0]
+ return 'chassis-name "%s", chassis-str "%s"' % (ch.name, ch.hostname)
+
++
++class ConnectionException(Exception):
++ pass
++
++
+ class OVSDB(object):
+ STREAM_TIMEOUT_MS = 1000
+
+@@ -111,7 +119,7 @@ class OVSDB(object):
+ os.strerror(error)))
+ strm = None
+ if not strm:
+- raise Exception('Unable to connect to %s' % self.remote)
++ raise ConnectionException()
+
+ rpc = jsonrpc.Connection(strm)
+ req = jsonrpc.Message.create_request('get_schema', [schema_name])
+@@ -167,6 +175,8 @@ class CookieHandlerByUUUID(CookieHandler):
+ super(CookieHandlerByUUUID, self).__init__(db, table, printer)
+
+ def get_records(self, cookie):
++ if not self._db:
++ return []
+ # Adjust cookie to include leading zeroes if needed.
+ cookie = cookie.zfill(8)
+ return self._db.find_rows_by_partial_uuid(self._table, cookie)
+@@ -488,8 +498,19 @@ def main():
+ if ovs and not ovs_db:
+ ovs_db = 'unix:%s/db.sock' % ovs_rundir
+
+- ovsdb_ovnsb = OVSDB(ovnsb_db, 'OVN_Southbound', leader_only=leader_only)
+- ovsdb_ovnnb = OVSDB(ovnnb_db, 'OVN_Northbound', leader_only=leader_only)
++ try:
++ ovsdb_ovnsb = OVSDB(ovnsb_db, 'OVN_Southbound',
++ leader_only=leader_only)
++ except ConnectionException:
++ print(DB_CONNECTION_ERR.format('SB'), file=sys.stderr)
++ ovsdb_ovnsb = None
++
++ try:
++ ovsdb_ovnnb = OVSDB(ovnnb_db, 'OVN_Northbound',
++ leader_only=leader_only)
++ except ConnectionException:
++ print(DB_CONNECTION_ERR.format('NB'), file=sys.stderr)
++ ovsdb_ovnnb = None
+
+ printer = Printer()
+ cookie_handlers = get_cookie_handlers(ovsdb_ovnnb, ovsdb_ovnsb, printer)
diff --git a/SPECS/ovn24.03.spec b/SPECS/ovn24.03.spec
index eb40ea6..3f0352b 100644
--- a/SPECS/ovn24.03.spec
+++ b/SPECS/ovn24.03.spec
@@ -51,7 +51,7 @@ Summary: Open Virtual Network support
Group: System Environment/Daemons
URL: http://www.ovn.org/
Version: 24.03.1
-Release: 5%{?commit0:.%{date}git%{shortcommit0}}%{?dist}
+Release: 44%{?commit0:.%{date}git%{shortcommit0}}%{?dist}
Provides: openvswitch%{pkgver}-ovn-common = %{?epoch:%{epoch}:}%{version}-%{release}
Obsoletes: openvswitch%{pkgver}-ovn-common < 2.11.0-1
@@ -64,8 +64,8 @@ License: ASL 2.0 and LGPLv2+ and SISSL
# 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 fe55ce37a7b090d09dee5c01ae0797320ad678f6
-%define ovsshortcommit fe55ce3
+%define ovscommit f19448b8618967a108ec6f34713dd811ce1d1334
+%define ovsshortcommit f19448b
Source10: https://github.com/openvswitch/ovs/archive/%{ovscommit}.tar.gz#/openvswitch-%{ovsshortcommit}.tar.gz
%define ovsdir ovs-%{ovscommit}
@@ -530,6 +530,162 @@ fi
%{_unitdir}/ovn-controller-vtep.service
%changelog
+* Tue Apr 23 2024 Ales Musil - 24.03.1-44
+- tests: Fix netcat 7.94 issues.
+[Upstream: cfeeaa6e2fb3e0bed9d608259025473808d286c2]
+
+* Mon Apr 22 2024 Ales Musil - 24.03.1-43
+- northd, controller: Use paused controller action for packet buffering.
+[Upstream: 1e89d06e62caff88cab9c31f1b1b2ec8dfe64880]
+
+* Mon Apr 22 2024 Ales Musil - 24.03.1-42
+- northd: Do not incrementally proccess changes for disabled LR.
+[Upstream: ac0af22fb4c5fe2cea4aee38580fe39aec26e245]
+
+* Mon Apr 22 2024 Martin Kalcok - 24.03.1-41
+- northd: Fix direct access to SNAT network.
+[Upstream: ccaf22f1356a6a82514e893b204366621c04aad3]
+
+* Mon Apr 22 2024 Martin Kalcok - 24.03.1-40
+- actions: New action ct_commit_to_zone.
+[Upstream: 92f7a974cbe79fe9de6f4d92440eebe41e4ec015]
+
+* Mon Apr 22 2024 Mark Michelson - 24.03.1-39
+- ovn-nbctl: Document "--portrange" in the manpage.
+[Upstream: 76321c078749cdcaf2df52e44b37864a15c28b76]
+
+* Mon Apr 22 2024 Lorenzo Bianconi - 24.03.1-38
+- utilities: Add missing bfd option in ovn-nbctl manpage.
+[Upstream: d45c06312d859fb76297a44533ce80082d0067a9]
+
+* Fri Apr 19 2024 Ales Musil - 24.03.1-37
+- ovs: Bump the submodule to the tip of branch-3.3.
+[Upstream: 6d71cbfd1811b97a5aa7c5b9c9f957ef8579234f]
+
+* Fri Apr 12 2024 Ales Musil - 24.03.1-36
+- ovn-ctl: Use the current user for default file permissions.
+[Upstream: 7346953e2fe39a4e8a7871da4f2634050feb1659]
+
+* Fri Apr 12 2024 Ales Musil - 24.03.1-35
+- ovn-trace: Make sure we don't exit when the port is not specified.
+[Upstream: 419f8a836c42db2a35a550cf4a4e975e3a3eb2af]
+
+* Fri Apr 12 2024 Lorenzo Bianconi - 24.03.1-34
+- northd: Fix BFD for policy routing.
+[Upstream: a6095e1cb237016191519478fbef9f775a67bc89]
+
+* Thu Apr 04 2024 Mark Michelson - 24.03.1-33
+- acl-log: Properly log the "pass" verdict.
+[Upstream: 6eff687d5f42dd4cb4558b071aaa00d5a55667f6]
+
+* Thu Apr 04 2024 Xavier Simonart - 24.03.1-32
+- automake: Make system tests dependent of ovn-macro.
+[Upstream: 90c577ae544e0d16593d5a89c058276ff25d43e3]
+
+* Thu Apr 04 2024 Han Zhou - 24.03.1-31
+- ovn-controller.at: Fix flaky test "ofctrl wait before clearing flows".
+[Upstream: 2ab187e9bb6c9be6174529df9617534aef6a3a94]
+
+* Thu Apr 04 2024 Vladislav Odintsov - 24.03.1-30
+- northd: fix infinite loop in ovn_allocate_tnlid()
+[Upstream: 69e90f664a1130a5415904d41db6dba713eea8c2]
+
+* Wed Apr 03 2024 Xavier Simonart - 24.03.1-29
+- pinctrl: Fixed 100% cpu on ovs connection loss.
+[Upstream: 72390c4fea72cfbae5eb9409abb8a6dd7d5f3e69]
+
+* Wed Apr 03 2024 Xavier Simonart - 24.03.1-28
+- pinctrl: Fix missing MAC_Bindings.
+[Upstream: c76746653f8423d514d3374423a3e15b0ede86bd]
+
+* Wed Apr 03 2024 Xavier Simonart - 24.03.1-27
+- tests: Add macros to pause controller updates.
+[Upstream: 1187031d5ebf3fb0a79b16f48798d88829175702]
+
+* Tue Apr 02 2024 Han Zhou - 24.03.1-26
+- ofctrl: Wait at S_WAIT_BEFORE_CLEAR only once.
+[Upstream: 6b1618a96f178b7b7d6a0c1903291f4bc4cc7f1d]
+
+* Thu Mar 28 2024 Frode Nordahl - 24.03.1-25
+- northd: Fix population of ipv6_ra_prefixes from IPv6 PD.
+[Upstream: bad2e3042e9cdac38db058ab0ce1478f104fef03]
+
+* Thu Mar 28 2024 Frode Nordahl - 24.03.1-24
+- controller: Use multicast for IPv6 Prefix Delegation.
+[Upstream: 5ff0e2aee3553e52766eb1c34ccc203ad6022fde]
+
+* Thu Mar 28 2024 Lorenzo Bianconi - 24.03.1-23
+- ovn-ic: Avoid igmp/mld traffic flooding.
+[Upstream: 29af310ce16fcaf0a9ce18aa79bf44d5cf0cf1e4]
+
+* Thu Mar 28 2024 Mohammad Heib - 24.03.1-22
+- tests: Use sync command in ovn-ic tests.
+[Upstream: d1a7253333ace9db7b3cef68b74a7f190fcaca8b]
+
+* Thu Mar 28 2024 Mohammad Heib - 24.03.1-21
+- tests: Move ovn interconnection tests to ovn-ic.at.
+[Upstream: ef2e711819d517de7abf91d4c8edef9818a92346]
+
+* Thu Mar 28 2024 Mohammad Heib - 24.03.1-20
+- IC: Tansit switch don't flood mcast traffic to router ports if matches igmp group.
+[Upstream: 31c7d227dc2bef55f1f1d6f07abd1b21831c0ab2]
+
+* Thu Mar 28 2024 Mohammad Heib - 24.03.1-19
+- northd: Don't skip transit switch LSP when creating mcast groups.
+[Upstream: 6af50a99c6e4836cc29471cbcbe0a938c3aa1879]
+
+* Thu Mar 28 2024 Lorenzo Bianconi - 24.03.1-18
+- northd: Fix NAT configuration with --add-route option for gw-router.
+[Upstream: 1434d1bc55ed43994dba769b1070317476b9f1b0]
+
+* Mon Mar 25 2024 Ales Musil - 24.03.1-17
+- controller: Fix ofctrl memory usage underflow.
+[Upstream: 3e35d0daeac162437a5858d3ea6e3d508462184a]
+
+* Wed Mar 20 2024 Martin Kalcok - 24.03.1-16
+- docs: Remove ref. to "ovn-sbctl --no-wait".
+[Upstream: 5b3880242ba55f81f93ebccd0a91978e7d65ba06]
+
+* Wed Mar 20 2024 Igor Zhukov - 24.03.1-15
+- Fix broken link for LTS release.
+[Upstream: 5f7765b238be0aa323a3ded21f5bbe770cc75daf]
+
+* Wed Mar 20 2024 Han Zhou - 24.03.1-14
+- ovn-controller: Fix busy loop when ofctrl is disconnected.
+[Upstream: b213cb641a050f5294ba592196823dd7fe529ad2]
+
+* Tue Mar 19 2024 Ales Musil - 24.03.1-13
+- tests: Address netcat 7.94 changes.
+[Upstream: 653e010f1e2c7032e612c68ba34d968569c0e0c5]
+
+* Tue Mar 19 2024 Ales Musil - 24.03.1-12
+- tests: Add helper for tcpdump.
+[Upstream: f739c28e822332e28e46f083b9a85c4ccbf51691]
+
+* Tue Mar 19 2024 Xavier Simonart - 24.03.1-11
+- tests: Ignore transaction errors in MAC Binding.
+[Upstream: 9644436abc183a9ccd8434fdef4b5823b8855f9f]
+
+* Mon Mar 18 2024 Ales Musil - 24.03.1-10
+- utilities: Make database connection optional for ovn-detrace.
+[Upstream: f60d06f3ae1fd6b1527e0e9f61bcc85c8383cfbb]
+
+* Mon Mar 18 2024 Mohammad Heib - 24.03.1-9
+- ovn-controller: Stop dropping bind_vport requests immediately after handling. (#1954659)
+[Upstream: 0e85aa326847b8a4075753bab8ede7f991cb912b]
+
+* Fri Mar 15 2024 Xavier Simonart - 24.03.1-8
+- tests: Fix flaky "lr multiple gw ports" test.
+[Upstream: d72b3f90e3991cca1211146401c848d35f2b6012]
+
+* Fri Mar 15 2024 Xavier Simonart - 24.03.1-7
+- pinctrl: Fix prefix delegation.
+[Upstream: 735a81fec9b8f1fd769b5e3f4018702b8c4f03ca]
+
+* Wed Mar 13 2024 Mohammad Heib - 24.03.1-6
+- controller: Release container lport when releasing parent port. (#2220938)
+[Upstream: d5d4c3bf25f8d85523b22c3b3746f53ce1f6c767]
+
* Tue Mar 12 2024 Ilya Maximets - 24.03.1-5
- github: Reduce ASLR entropy to be compatible with asan in llvm 14.
[Upstream: 06d3a8fe48969aa7be4f8672ff77a386a1defab6]