-
-(cherry-picked from master commit 3357ab14076f0a7e91fe690538b4315c7219de60)
-Conflicts:
- northd/ovn-northd.c
----
- northd/ovn-northd.8.xml | 65 +++++++++++-----
- northd/ovn-northd.c | 160 +++++++++++++---------------------------
- tests/ovn-northd.at | 28 +++----
- tests/ovn.at | 52 ++++++-------
- 4 files changed, 141 insertions(+), 164 deletions(-)
-
-diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
-index 820698228..27d996944 100644
---- a/northd/ovn-northd.8.xml
-+++ b/northd/ovn-northd.8.xml
-@@ -718,24 +718,55 @@
- Ingress Table 12: Pre-Hairpin
-
- -
-- For all configured load balancer VIPs a priority-2 flow that
-- matches on traffic that needs to be hairpinned, i.e., after load
-- balancing the destination IP matches the source IP, which sets
--
reg0[6] = 1
and executes ct_snat(VIP)
-- to force replies to these packets to come back through OVN.
-+ If the logical switch has load balancer(s) configured, then a
-+ priorirty-100 flow is added with the match
-+ ip && ct.trk&& ct.dnat
to check if the
-+ packet needs to be hairpinned (if after load balancing the destination
-+ IP matches the source IP) or not by executing the action
-+ reg0[6] = chk_lb_hairpin();
and advances the packet to
-+ the next table.
-+
-+
-+ -
-+ If the logical switch has load balancer(s) configured, then a
-+ priorirty-90 flow is added with the match
ip
to check if
-+ the packet is a reply for a hairpinned connection or not by executing
-+ the action reg0[6] = chk_lb_hairpin_reply();
and advances
-+ the packet to the next table.
-
-+
- -
-- For all configured load balancer VIPs a priority-1 flow that
-- matches on replies to hairpinned traffic, i.e., destination IP is VIP,
-- source IP is the backend IP and source L4 port is backend port, which
-- sets
reg0[6] = 1
and executes ct_snat;
.
-+ A priority-0 flow that simply moves traffic to the next table.
-
-+
-+
-+ Ingress Table 13: Nat-Hairpin
-+
-+ -
-+ If the logical switch has load balancer(s) configured, then a
-+ priorirty-100 flow is added with the match
-+
ip && (ct.new || ct.est) && ct.trk &&
-+ ct.dnat && reg0[6] == 1
which hairpins the traffic by
-+ NATting source IP to the load balancer VIP by executing the action
-+ ct_snat_to_vip
and advances the packet to the next table.
-+
-+
-+ -
-+ If the logical switch has load balancer(s) configured, then a
-+ priorirty-90 flow is added with the match
-+
ip && reg0[6] == 1
which matches on the replies
-+ of hairpinned traffic (i.e., destination IP is VIP,
-+ source IP is the backend IP and source L4 port is backend port for L4
-+ load balancers) and executes ct_snat
and advances the
-+ packet to the next table.
-+
-+
- -
- A priority-0 flow that simply moves traffic to the next table.
-
-
-
-- Ingress Table 13: Hairpin
-+ Ingress Table 14: Hairpin
-
- -
- A priority-1 flow that hairpins traffic matched by non-default
-@@ -748,7 +779,7 @@
-
-
-
-- Ingress Table 14: ARP/ND responder
-+ Ingress Table 15: ARP/ND responder
-
-
- This table implements ARP/ND responder in a logical switch for known
-@@ -1038,7 +1069,7 @@ output;
-
-
-
--
Ingress Table 15: DHCP option processing
-+ Ingress Table 16: DHCP option processing
-
-
- This table adds the DHCPv4 options to a DHCPv4 packet from the
-@@ -1099,7 +1130,7 @@ next;
-
-
-
--
Ingress Table 16: DHCP responses
-+ Ingress Table 17: DHCP responses
-
-
- This table implements DHCP responder for the DHCP replies generated by
-@@ -1180,7 +1211,7 @@ output;
-
-
-
--
Ingress Table 17 DNS Lookup
-+ Ingress Table 18 DNS Lookup
-
-
- This table looks up and resolves the DNS names to the corresponding
-@@ -1209,7 +1240,7 @@ reg0[4] = dns_lookup(); next;
-
-
-
--
Ingress Table 18 DNS Responses
-+ Ingress Table 19 DNS Responses
-
-
- This table implements DNS responder for the DNS replies generated by
-@@ -1244,7 +1275,7 @@ output;
-
-
-
--
Ingress table 19 External ports
-+ Ingress table 20 External ports
-
-
- Traffic from the external
logical ports enter the ingress
-@@ -1287,7 +1318,7 @@ output;
-
-
-
--
Ingress Table 20 Destination Lookup
-+ Ingress Table 21 Destination Lookup
-
-
- This table implements switching behavior. It contains these logical
-diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
-index a7695bc63..bb31e04fa 100644
---- a/northd/ovn-northd.c
-+++ b/northd/ovn-northd.c
-@@ -150,14 +150,15 @@ enum ovn_stage {
- PIPELINE_STAGE(SWITCH, IN, LB, 10, "ls_in_lb") \
- PIPELINE_STAGE(SWITCH, IN, STATEFUL, 11, "ls_in_stateful") \
- PIPELINE_STAGE(SWITCH, IN, PRE_HAIRPIN, 12, "ls_in_pre_hairpin") \
-- PIPELINE_STAGE(SWITCH, IN, HAIRPIN, 13, "ls_in_hairpin") \
-- PIPELINE_STAGE(SWITCH, IN, ARP_ND_RSP, 14, "ls_in_arp_rsp") \
-- PIPELINE_STAGE(SWITCH, IN, DHCP_OPTIONS, 15, "ls_in_dhcp_options") \
-- PIPELINE_STAGE(SWITCH, IN, DHCP_RESPONSE, 16, "ls_in_dhcp_response") \
-- PIPELINE_STAGE(SWITCH, IN, DNS_LOOKUP, 17, "ls_in_dns_lookup") \
-- PIPELINE_STAGE(SWITCH, IN, DNS_RESPONSE, 18, "ls_in_dns_response") \
-- PIPELINE_STAGE(SWITCH, IN, EXTERNAL_PORT, 19, "ls_in_external_port") \
-- PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 20, "ls_in_l2_lkup") \
-+ PIPELINE_STAGE(SWITCH, IN, NAT_HAIRPIN, 13, "ls_in_nat_hairpin") \
-+ PIPELINE_STAGE(SWITCH, IN, HAIRPIN, 14, "ls_in_hairpin") \
-+ PIPELINE_STAGE(SWITCH, IN, ARP_ND_RSP, 15, "ls_in_arp_rsp") \
-+ PIPELINE_STAGE(SWITCH, IN, DHCP_OPTIONS, 16, "ls_in_dhcp_options") \
-+ PIPELINE_STAGE(SWITCH, IN, DHCP_RESPONSE, 17, "ls_in_dhcp_response") \
-+ PIPELINE_STAGE(SWITCH, IN, DNS_LOOKUP, 18, "ls_in_dns_lookup") \
-+ PIPELINE_STAGE(SWITCH, IN, DNS_RESPONSE, 19, "ls_in_dns_response") \
-+ PIPELINE_STAGE(SWITCH, IN, EXTERNAL_PORT, 20, "ls_in_external_port") \
-+ PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 21, "ls_in_l2_lkup") \
- \
- /* Logical switch egress stages. */ \
- PIPELINE_STAGE(SWITCH, OUT, PRE_LB, 0, "ls_out_pre_lb") \
-@@ -5811,85 +5812,6 @@ build_lb(struct ovn_datapath *od, struct hmap *lflows)
- }
- }
-
--static void
--build_lb_hairpin_rules(struct ovn_datapath *od, struct hmap *lflows,
-- struct ovn_northd_lb *lb,
-- struct ovn_lb_vip *lb_vip,
-- const char *ip_match, const char *proto)
--{
-- if (lb_vip->n_backends == 0) {
-- return;
-- }
--
-- struct ds action = DS_EMPTY_INITIALIZER;
-- struct ds match_initiator = DS_EMPTY_INITIALIZER;
-- struct ds match_reply = DS_EMPTY_INITIALIZER;
-- struct ds proto_match = DS_EMPTY_INITIALIZER;
--
-- /* Ingress Pre-Hairpin table.
-- * - Priority 2: SNAT load balanced traffic that needs to be hairpinned:
-- * - Both SRC and DST IP match backend->ip and destination port
-- * matches backend->port.
-- * - Priority 1: unSNAT replies to hairpinned load balanced traffic.
-- * - SRC IP matches backend->ip, DST IP matches LB VIP and source port
-- * matches backend->port.
-- */
-- ds_put_char(&match_reply, '(');
-- for (size_t i = 0; i < lb_vip->n_backends; i++) {
-- struct ovn_lb_backend *backend = &lb_vip->backends[i];
--
-- /* Packets that after load balancing have equal source and
-- * destination IPs should be hairpinned.
-- */
-- if (lb_vip->vip_port) {
-- ds_put_format(&proto_match, " && %s.dst == %"PRIu16,
-- proto, backend->port);
-- }
-- ds_put_format(&match_initiator, "(%s.src == %s && %s.dst == %s%s)",
-- ip_match, backend->ip_str, ip_match, backend->ip_str,
-- ds_cstr(&proto_match));
--
-- /* Replies to hairpinned traffic are originated by backend->ip:port. */
-- ds_clear(&proto_match);
-- if (lb_vip->vip_port) {
-- ds_put_format(&proto_match, " && %s.src == %"PRIu16, proto,
-- backend->port);
-- }
-- ds_put_format(&match_reply, "(%s.src == %s%s)",
-- ip_match, backend->ip_str, ds_cstr(&proto_match));
-- ds_clear(&proto_match);
--
-- if (i < lb_vip->n_backends - 1) {
-- ds_put_cstr(&match_initiator, " || ");
-- ds_put_cstr(&match_reply, " || ");
-- }
-- }
-- ds_put_char(&match_reply, ')');
--
-- /* SNAT hairpinned initiator traffic so that the reply traffic is
-- * also directed through OVN.
-- */
-- ds_put_format(&action, REGBIT_HAIRPIN " = 1; ct_snat(%s);",
-- lb_vip->vip_str);
-- ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_PRE_HAIRPIN, 2,
-- ds_cstr(&match_initiator), ds_cstr(&action),
-- &lb->nlb->header_);
--
-- /* Replies to hairpinned traffic are destined to the LB VIP. */
-- ds_put_format(&match_reply, " && %s.dst == %s", ip_match, lb_vip->vip_str);
--
-- /* UNSNAT replies for hairpinned traffic. */
-- ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_PRE_HAIRPIN, 1,
-- ds_cstr(&match_reply),
-- REGBIT_HAIRPIN " = 1; ct_snat;",
-- &lb->nlb->header_);
--
-- ds_destroy(&action);
-- ds_destroy(&match_initiator);
-- ds_destroy(&match_reply);
-- ds_destroy(&proto_match);
--}
--
- static void
- build_lb_rules(struct ovn_datapath *od, struct hmap *lflows,
- struct ovn_northd_lb *lb)
-@@ -5938,12 +5860,6 @@ build_lb_rules(struct ovn_datapath *od, struct hmap *lflows,
-
- ds_destroy(&match);
- ds_destroy(&action);
--
-- /* Also install flows that allow hairpinning of traffic (i.e., if
-- * a load balancer VIP is DNAT-ed to a backend that happens to be
-- * the source of the traffic).
-- */
-- build_lb_hairpin_rules(od, lflows, lb, lb_vip, ip_match, proto);
- }
- }
-
-@@ -5990,24 +5906,53 @@ build_stateful(struct ovn_datapath *od, struct hmap *lflows, struct hmap *lbs)
- ovs_assert(lb);
- build_lb_rules(od, lflows, lb);
- }
-+}
-
-- /* Ingress Pre-Hairpin table (Priority 0). Packets that don't need
-- * hairpinning should continue processing.
-+static void
-+build_lb_hairpin(struct ovn_datapath *od, struct hmap *lflows)
-+{
-+ /* Ingress Pre-Hairpin/Nat-Hairpin/Hairpin tabled (Priority 0).
-+ * Packets that don't need hairpinning should continue processing.
- */
- ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_HAIRPIN, 0, "1", "next;");
--
-- /* Ingress Hairpin table.
-- * - Priority 0: Packets that don't need hairpinning should continue
-- * processing.
-- * - Priority 1: Packets that were SNAT-ed for hairpinning should be
-- * looped back (i.e., swap ETH addresses and send back on inport).
-- */
-- ovn_lflow_add(lflows, od, S_SWITCH_IN_HAIRPIN, 1, REGBIT_HAIRPIN " == 1",
-- "eth.dst <-> eth.src;"
-- "outport = inport;"
-- "flags.loopback = 1;"
-- "output;");
-+ ovn_lflow_add(lflows, od, S_SWITCH_IN_NAT_HAIRPIN, 0, "1", "next;");
- ovn_lflow_add(lflows, od, S_SWITCH_IN_HAIRPIN, 0, "1", "next;");
-+
-+ if (has_lb_vip(od)) {
-+ /* Check if the packet needs to be hairpinned. */
-+ ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_PRE_HAIRPIN, 100,
-+ "ip && ct.trk && ct.dnat",
-+ REGBIT_HAIRPIN " = chk_lb_hairpin(); next;",
-+ &od->nbs->header_);
-+
-+ /* Check if the packet is a reply of hairpinned traffic. */
-+ ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_PRE_HAIRPIN, 90, "ip",
-+ REGBIT_HAIRPIN " = chk_lb_hairpin_reply(); "
-+ "next;", &od->nbs->header_);
-+
-+ /* If packet needs to be hairpinned, snat the src ip with the VIP. */
-+ ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_NAT_HAIRPIN, 100,
-+ "ip && (ct.new || ct.est) && ct.trk && ct.dnat"
-+ " && "REGBIT_HAIRPIN " == 1",
-+ "ct_snat_to_vip; next;",
-+ &od->nbs->header_);
-+
-+ /* For the reply of hairpinned traffic, snat the src ip to the VIP. */
-+ ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_NAT_HAIRPIN, 90,
-+ "ip && "REGBIT_HAIRPIN " == 1", "ct_snat;",
-+ &od->nbs->header_);
-+
-+ /* Ingress Hairpin table.
-+ * - Priority 1: Packets that were SNAT-ed for hairpinning should be
-+ * looped back (i.e., swap ETH addresses and send back on inport).
-+ */
-+ ovn_lflow_add(lflows, od, S_SWITCH_IN_HAIRPIN, 1,
-+ REGBIT_HAIRPIN " == 1",
-+ "eth.dst <-> eth.src;"
-+ "outport = inport;"
-+ "flags.loopback = 1;"
-+ "output;");
-+ }
- }
-
- static void
-@@ -6693,6 +6638,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
- build_qos(od, lflows);
- build_lb(od, lflows);
- build_stateful(od, lflows, lbs);
-+ build_lb_hairpin(od, lflows);
- }
-
- /* Build logical flows for the forwarding groups */
-diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
-index 961fb3712..bf3a99a6c 100644
---- a/tests/ovn-northd.at
-+++ b/tests/ovn-northd.at
-@@ -1775,13 +1775,13 @@ action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implici
- AT_CHECK([ovn-sbctl lflow-list sw0 | grep "ls_out_acl" | grep pg0 | sort], [0], [dnl
- table=5 (ls_out_acl ), priority=2003 , dnl
- match=(outport == @pg0 && ip6 && udp), dnl
--action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=20); };)
-+action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
- ])
-
- AT_CHECK([ovn-sbctl lflow-list sw1 | grep "ls_out_acl" | grep pg0 | sort], [0], [dnl
- table=5 (ls_out_acl ), priority=2003 , dnl
- match=(outport == @pg0 && ip6 && udp), dnl
--action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=20); };)
-+action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
- ])
-
- ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && udp" reject
-@@ -1789,19 +1789,19 @@ ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && udp" reject
- AT_CHECK([ovn-sbctl lflow-list sw0 | grep "ls_out_acl" | grep pg0 | sort], [0], [dnl
- table=5 (ls_out_acl ), priority=2002 , dnl
- match=(outport == @pg0 && ip4 && udp), dnl
--action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=20); };)
-+action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
- table=5 (ls_out_acl ), priority=2003 , dnl
- match=(outport == @pg0 && ip6 && udp), dnl
--action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=20); };)
-+action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
- ])
-
- AT_CHECK([ovn-sbctl lflow-list sw1 | grep "ls_out_acl" | grep pg0 | sort], [0], [dnl
- table=5 (ls_out_acl ), priority=2002 , dnl
- match=(outport == @pg0 && ip4 && udp), dnl
--action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=20); };)
-+action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
- table=5 (ls_out_acl ), priority=2003 , dnl
- match=(outport == @pg0 && ip6 && udp), dnl
--action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=20); };)
-+action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
- ])
-
- ovn-nbctl --wait=sb acl-add pg0 to-lport 1001 "outport == @pg0 && ip" allow-related
-@@ -1813,16 +1813,16 @@ match=(reg0[[7]] == 1 && (outport == @pg0 && ip)), action=(reg0[[1]] = 1; next;)
- match=(reg0[[8]] == 1 && (outport == @pg0 && ip)), action=(next;)
- table=5 (ls_out_acl ), priority=2002 , dnl
- match=((reg0[[10]] == 1) && outport == @pg0 && ip4 && udp), dnl
--action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=20); };)
-+action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
- table=5 (ls_out_acl ), priority=2002 , dnl
- match=((reg0[[9]] == 1) && outport == @pg0 && ip4 && udp), dnl
--action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=20); };)
-+action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
- table=5 (ls_out_acl ), priority=2003 , dnl
- match=((reg0[[10]] == 1) && outport == @pg0 && ip6 && udp), dnl
--action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=20); };)
-+action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
- table=5 (ls_out_acl ), priority=2003 , dnl
- match=((reg0[[9]] == 1) && outport == @pg0 && ip6 && udp), dnl
--action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=20); };)
-+action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
- ])
-
- AT_CHECK([ovn-sbctl lflow-list sw1 | grep "ls_out_acl" | grep pg0 | sort], [0], [dnl
-@@ -1832,16 +1832,16 @@ match=(reg0[[7]] == 1 && (outport == @pg0 && ip)), action=(reg0[[1]] = 1; next;)
- match=(reg0[[8]] == 1 && (outport == @pg0 && ip)), action=(next;)
- table=5 (ls_out_acl ), priority=2002 , dnl
- match=((reg0[[10]] == 1) && outport == @pg0 && ip4 && udp), dnl
--action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=20); };)
-+action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
- table=5 (ls_out_acl ), priority=2002 , dnl
- match=((reg0[[9]] == 1) && outport == @pg0 && ip4 && udp), dnl
--action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=20); };)
-+action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
- table=5 (ls_out_acl ), priority=2003 , dnl
- match=((reg0[[10]] == 1) && outport == @pg0 && ip6 && udp), dnl
--action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=20); };)
-+action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
- table=5 (ls_out_acl ), priority=2003 , dnl
- match=((reg0[[9]] == 1) && outport == @pg0 && ip6 && udp), dnl
--action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=20); };)
-+action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
- ])
-
- AT_CLEANUP
-diff --git a/tests/ovn.at b/tests/ovn.at
-index 8dbb13d3a..8f18ca9e5 100644
---- a/tests/ovn.at
-+++ b/tests/ovn.at
-@@ -14657,17 +14657,17 @@ ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
- AT_CHECK([ovn-sbctl dump-flows ls1 | grep "offerip = 10.0.0.6" | \
- wc -l], [0], [0
- ])
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=23 | \
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=24 | \
- grep controller | grep "0a.00.00.06" | wc -l], [0], [0
- ])
--AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=23 | \
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=24 | \
- grep controller | grep "0a.00.00.06" | wc -l], [0], [0
- ])
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=23 | \
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=24 | \
- grep controller | grep tp_src=546 | grep \
- "ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0
- ])
--AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=23 | \
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=24 | \
- grep controller | grep tp_src=546 | grep \
- "ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0
- ])
-@@ -14698,17 +14698,17 @@ port_binding logical_port=ls1-lp_ext1`
-
- # No DHCPv4/v6 flows for the external port - ls1-lp_ext1 - 10.0.0.6 in hv1 and hv2
- # as no localnet port added to ls1 yet.
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=23 | \
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=24 | \
- grep controller | grep "0a.00.00.06" | wc -l], [0], [0
- ])
--AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=23 | \
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=24 | \
- grep controller | grep "0a.00.00.06" | wc -l], [0], [0
- ])
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=23 | \
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=24 | \
- grep controller | grep tp_src=546 | grep \
- "ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0
- ])
--AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=23 | \
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=24 | \
- grep controller | grep tp_src=546 | grep \
- "ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0
- ])
-@@ -14730,38 +14730,38 @@ logical_port=ls1-lp_ext1`
- test "$chassis" = "$hv1_uuid"])
-
- # There should be DHCPv4/v6 OF flows for the ls1-lp_ext1 port in hv1
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=23 | \
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=24 | \
- grep controller | grep "0a.00.00.06" | grep reg14=0x$ln_public_key | \
- wc -l], [0], [3
- ])
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=23 | \
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=24 | \
- grep controller | grep tp_src=546 | grep \
- "ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | \
- grep reg14=0x$ln_public_key | wc -l], [0], [1
- ])
-
- # There should be no DHCPv4/v6 flows for ls1-lp_ext1 on hv2
--AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=23 | \
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=24 | \
- grep controller | grep "0a.00.00.06" | wc -l], [0], [0
- ])
--AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=23 | \
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=24 | \
- grep controller | grep tp_src=546 | grep \
- "ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0
- ])
-
- # No DHCPv4/v6 flows for the external port - ls1-lp_ext2 - 10.0.0.7 in hv1 and
- # hv2 as requested-chassis option is not set.
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=23 | \
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=24 | \
- grep controller | grep "0a.00.00.07" | wc -l], [0], [0
- ])
--AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=23 | \
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=24 | \
- grep controller | grep "0a.00.00.07" | wc -l], [0], [0
- ])
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=23 | \
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=24 | \
- grep controller | grep tp_src=546 | grep \
- "ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.07" | wc -l], [0], [0
- ])
--AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=23 | \
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=24 | \
- grep controller | grep tp_src=546 | grep \
- "ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.07" | wc -l], [0], [0
- ])
-@@ -15013,21 +15013,21 @@ logical_port=ls1-lp_ext1`
- test "$chassis" = "$hv2_uuid"])
-
- # There should be OF flows for DHCP4/v6 for the ls1-lp_ext1 port in hv2
--AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=23 | \
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=24 | \
- grep controller | grep "0a.00.00.06" | grep reg14=0x$ln_public_key | \
- wc -l], [0], [3
- ])
--AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=23 | \
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=24 | \
- grep controller | grep tp_src=546 | grep \
- "ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | \
- grep reg14=0x$ln_public_key | wc -l], [0], [1
- ])
-
- # There should be no DHCPv4/v6 flows for ls1-lp_ext1 on hv1
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=23 | \
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=24 | \
- grep controller | grep "0a.00.00.06" | wc -l], [0], [0
- ])
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=23 | \
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=24 | \
- grep controller | grep tp_src=546 | grep \
- "ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | \
- grep reg14=0x$ln_public_key | wc -l], [0], [0
-@@ -15293,7 +15293,7 @@ logical_port=ls1-lp_ext1`
- # There should be a flow in hv2 to drop traffic from ls1-lp_ext1 destined
- # to router mac.
- AT_CHECK([as hv2 ovs-ofctl dump-flows br-int \
--table=27,dl_src=f0:00:00:00:00:03,dl_dst=a0:10:00:00:00:01 | \
-+table=28,dl_src=f0:00:00:00:00:03,dl_dst=a0:10:00:00:00:01 | \
- grep -c "actions=drop"], [0], [1
- ])
-
-@@ -16564,9 +16564,9 @@ ovn-nbctl --wait=hv sync
- ovn-sbctl dump-flows sw0 | grep ls_in_arp_rsp | grep bind_vport > lflows.txt
-
- AT_CHECK([cat lflows.txt], [0], [dnl
-- table=14(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p1" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;)
-- table=14(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p2" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;)
-- table=14(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p3" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;)
-+ table=15(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p1" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;)
-+ table=15(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p2" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;)
-+ table=15(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p3" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;)
- ])
-
- ovn-sbctl dump-flows lr0 | grep lr_in_arp_resolve | grep "reg0 == 10.0.0.10" \
-@@ -16776,8 +16776,8 @@ ovn-nbctl --wait=hv set logical_switch_port sw0-vir options:virtual-ip=10.0.0.10
- ovn-sbctl dump-flows sw0 | grep ls_in_arp_rsp | grep bind_vport > lflows.txt
-
- AT_CHECK([cat lflows.txt], [0], [dnl
-- table=14(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p1" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;)
-- table=14(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p3" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;)
-+ table=15(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p1" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;)
-+ table=15(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p3" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;)
- ])
-
- ovn-nbctl --wait=hv remove logical_switch_port sw0-vir options virtual-parents
---
-2.28.0
-
diff --git a/SOURCES/0008-ovn-northd-split-build_lswitch_output_port_sec-into-.patch b/SOURCES/0008-ovn-northd-split-build_lswitch_output_port_sec-into-.patch
deleted file mode 100644
index 4982ad2..0000000
--- a/SOURCES/0008-ovn-northd-split-build_lswitch_output_port_sec-into-.patch
+++ /dev/null
@@ -1,181 +0,0 @@
-From a6b4b14ac1b6523f85fb13a7f259d9698a70444f Mon Sep 17 00:00:00 2001
-Message-Id:
-In-Reply-To:
-References:
-From: Anton Ivanov
-Date: Tue, 5 Jan 2021 17:49:35 +0000
-Subject: [PATCH 08/16] ovn-northd: split build_lswitch_output_port_sec into
- iterators.
-
-Split build_lswitch_output_port_sec into a per port and per
-datapath iterator. Migrate to the relevant per-port and
-per-datapath loops.
-
-Signed-off-by: Anton Ivanov
-Signed-off-by: Numan Siddique
-Signed-off-by: Lorenzo Bianconi
----
- northd/ovn-northd.c | 82 ++++++++++++++++++++-------------------------
- 1 file changed, 37 insertions(+), 45 deletions(-)
-
-diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
-index 27a788095..92300e017 100644
---- a/northd/ovn-northd.c
-+++ b/northd/ovn-northd.c
-@@ -4917,51 +4917,47 @@ build_lswitch_input_port_sec_od(
- }
- }
-
-+/* Egress table 8: Egress port security - IP (priorities 90 and 80)
-+ * if port security enabled.
-+ *
-+ * Egress table 9: Egress port security - L2 (priorities 50 and 150).
-+ *
-+ * Priority 50 rules implement port security for enabled logical port.
-+ *
-+ * Priority 150 rules drop packets to disabled logical ports, so that
-+ * they don't even receive multicast or broadcast packets.
-+ */
- static void
--build_lswitch_output_port_sec(struct hmap *ports, struct hmap *datapaths,
-- struct hmap *lflows)
-+build_lswitch_output_port_sec_op(struct ovn_port *op,
-+ struct hmap *lflows,
-+ struct ds *match,
-+ struct ds *actions)
- {
-- struct ds actions = DS_EMPTY_INITIALIZER;
-- struct ds match = DS_EMPTY_INITIALIZER;
-- struct ovn_port *op;
-
-- /* Egress table 8: Egress port security - IP (priorities 90 and 80)
-- * if port security enabled.
-- *
-- * Egress table 9: Egress port security - L2 (priorities 50 and 150).
-- *
-- * Priority 50 rules implement port security for enabled logical port.
-- *
-- * Priority 150 rules drop packets to disabled logical ports, so that
-- * they don't even receive multicast or broadcast packets.
-- */
-- HMAP_FOR_EACH (op, key_node, ports) {
-- if (!op->nbsp || lsp_is_external(op->nbsp)) {
-- continue;
-- }
-+ if (op->nbsp && (!lsp_is_external(op->nbsp))) {
-
-- ds_clear(&actions);
-- ds_clear(&match);
-+ ds_clear(actions);
-+ ds_clear(match);
-
-- ds_put_format(&match, "outport == %s", op->json_key);
-+ ds_put_format(match, "outport == %s", op->json_key);
- if (lsp_is_enabled(op->nbsp)) {
- build_port_security_l2("eth.dst", op->ps_addrs, op->n_ps_addrs,
-- &match);
-+ match);
-
- if (!strcmp(op->nbsp->type, "localnet")) {
- const char *queue_id = smap_get(&op->sb->options,
- "qdisc_queue_id");
- if (queue_id) {
-- ds_put_format(&actions, "set_queue(%s); ", queue_id);
-+ ds_put_format(actions, "set_queue(%s); ", queue_id);
- }
- }
-- ds_put_cstr(&actions, "output;");
-+ ds_put_cstr(actions, "output;");
- ovn_lflow_add_with_hint(lflows, op->od, S_SWITCH_OUT_PORT_SEC_L2,
-- 50, ds_cstr(&match), ds_cstr(&actions),
-+ 50, ds_cstr(match), ds_cstr(actions),
- &op->nbsp->header_);
- } else {
- ovn_lflow_add_with_hint(lflows, op->od, S_SWITCH_OUT_PORT_SEC_L2,
-- 150, ds_cstr(&match), "drop;",
-+ 150, ds_cstr(match), "drop;",
- &op->nbsp->header_);
- }
-
-@@ -4969,23 +4965,20 @@ build_lswitch_output_port_sec(struct hmap *ports, struct hmap *datapaths,
- build_port_security_ip(P_OUT, op, lflows, &op->nbsp->header_);
- }
- }
-+}
-
-- /* Egress tables 8: Egress port security - IP (priority 0)
-- * Egress table 9: Egress port security L2 - multicast/broadcast
-- * (priority 100). */
-- struct ovn_datapath *od;
-- HMAP_FOR_EACH (od, key_node, datapaths) {
-- if (!od->nbs) {
-- continue;
-- }
--
-+/* Egress tables 8: Egress port security - IP (priority 0)
-+ * Egress table 9: Egress port security L2 - multicast/broadcast
-+ * (priority 100). */
-+static void
-+build_lswitch_output_port_sec_od(struct ovn_datapath *od,
-+ struct hmap *lflows)
-+{
-+ if (od->nbs) {
- ovn_lflow_add(lflows, od, S_SWITCH_OUT_PORT_SEC_IP, 0, "1", "next;");
- ovn_lflow_add(lflows, od, S_SWITCH_OUT_PORT_SEC_L2, 100, "eth.mcast",
- "output;");
- }
--
-- ds_destroy(&match);
-- ds_destroy(&actions);
- }
-
- static void
-@@ -6768,8 +6761,7 @@ is_vlan_transparent(const struct ovn_datapath *od)
- }
-
- static void
--build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
-- struct hmap *lflows)
-+build_lswitch_flows(struct hmap *datapaths, struct hmap *lflows)
- {
- /* This flow table structure is documented in ovn-northd(8), so please
- * update ovn-northd.8.xml if you change anything. */
-@@ -6790,8 +6782,6 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
- }
- }
-
-- build_lswitch_output_port_sec(ports, datapaths, lflows);
--
- ds_destroy(&match);
- ds_destroy(&actions);
- }
-@@ -11351,6 +11341,7 @@ build_lswitch_and_lrouter_iterate_by_od(struct ovn_datapath *od,
- build_lswitch_dns_lookup_and_response(od, lsi->lflows);
- build_lswitch_dhcp_and_dns_defaults(od, lsi->lflows);
- build_lswitch_destination_lookup_bmcast(od, lsi->lflows, &lsi->actions);
-+ build_lswitch_output_port_sec_od(od, lsi->lflows);
-
- /* Build Logical Router Flows. */
- build_adm_ctrl_flows_for_lrouter(od, lsi->lflows);
-@@ -11391,6 +11382,8 @@ build_lswitch_and_lrouter_iterate_by_op(struct ovn_port *op,
- build_lswitch_external_port(op, lsi->lflows);
- build_lswitch_ip_unicast_lookup(op, lsi->lflows, lsi->mcgroups,
- &lsi->actions, &lsi->match);
-+ build_lswitch_output_port_sec_op(op, lsi->lflows,
-+ &lsi->actions, &lsi->match);
-
- /* Build Logical Router Flows. */
- build_adm_ctrl_flows_for_lrouter_port(op, lsi->lflows, &lsi->match,
-@@ -11462,8 +11455,7 @@ build_lswitch_and_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
- ds_destroy(&lsi.match);
- ds_destroy(&lsi.actions);
-
-- /* Legacy lswitch build - to be migrated. */
-- build_lswitch_flows(datapaths, ports, lflows);
-+ build_lswitch_flows(datapaths, lflows);
-
- /* Legacy lrouter build - to be migrated. */
- build_lrouter_flows(datapaths, ports, lflows, meter_groups, lbs);
---
-2.29.2
-
diff --git a/SOURCES/0008-pinctrl-Fix-leak-of-DNS-cache-records.patch b/SOURCES/0008-pinctrl-Fix-leak-of-DNS-cache-records.patch
deleted file mode 100644
index 7f23de7..0000000
--- a/SOURCES/0008-pinctrl-Fix-leak-of-DNS-cache-records.patch
+++ /dev/null
@@ -1,72 +0,0 @@
-From 87e4b1c8533f5b42175366706daff9c706dd1ecf Mon Sep 17 00:00:00 2001
-From: Ilya Maximets
-Date: Fri, 20 Nov 2020 01:17:16 +0100
-Subject: [PATCH 08/16] pinctrl: Fix leak of DNS cache records.
-
-'smap_clear()' doesn't free allocated memory, but 'smap_clone()'
-re-initializes hash map clearing internal pointers and leaking
-this memory. 'smap_destroy()' should be used instead.
-
-Also, all the records and array of datapaths should be freed on
-destruction of a cache entry.
-
- Direct leak of 16 byte(s) in 2 object(s) allocated from:
- #0 0x5211c7 in calloc (/controller/ovn-controller+0x5211c7)
- #1 0x752364 in xcalloc /lib/util.c:121:31
- #2 0x576e76 in sync_dns_cache /controller/pinctrl.c:2517:25
- #3 0x5758fb in pinctrl_run /controller/pinctrl.c:3158:5
- #4 0x59b06c in main /controller/ovn-controller.c:2642:25
- #5 0x7fb570fc11a2 in __libc_start_main (/lib64/libc.so.6+0x271a2)
-
- Indirect leak of 26 byte(s) in 2 object(s) allocated from:
- #0 0x52100f in malloc (/controller/ovn-controller+0x52100f)
- #1 0x7523d6 in xmalloc /lib/util.c:138:15
- #2 0x7524a8 in xmemdup0 /lib/util.c:168:15
- #3 0x73d8fc in smap_clone /lib/smap.c:314:45
- #4 0x576e2f in sync_dns_cache /controller/pinctrl.c:2513:13
- #5 0x5758fb in pinctrl_run /controller/pinctrl.c:3158:5
- #6 0x59b06c in main /controller/ovn-controller.c:2642:25
- #7 0x7fb570fc11a2 in __libc_start_main (/lib64/libc.so.6+0x271a2)
-
-Fixes: 6b72068202f1 ("ovn-controller: Add a new thread in pinctrl module to handle packet-ins.")
-Acked-by: Dumitru Ceara
-Signed-off-by: Ilya Maximets
-Signed-off-by: Numan Siddique
----
- controller/pinctrl.c | 6 +++++-
- 1 file changed, 5 insertions(+), 1 deletion(-)
-
-diff --git a/controller/pinctrl.c b/controller/pinctrl.c
-index 728fb3063..d445d235e 100644
---- a/controller/pinctrl.c
-+++ b/controller/pinctrl.c
-@@ -2508,7 +2508,7 @@ sync_dns_cache(const struct sbrec_dns_table *dns_table)
- dns_data->delete = false;
-
- if (!smap_equal(&dns_data->records, &sbrec_dns->records)) {
-- smap_clear(&dns_data->records);
-+ smap_destroy(&dns_data->records);
- smap_clone(&dns_data->records, &sbrec_dns->records);
- }
-
-@@ -2524,6 +2524,8 @@ sync_dns_cache(const struct sbrec_dns_table *dns_table)
- struct dns_data *d = iter->data;
- if (d->delete) {
- shash_delete(&dns_cache, iter);
-+ smap_destroy(&d->records);
-+ free(d->dps);
- free(d);
- }
- }
-@@ -2536,6 +2538,8 @@ destroy_dns_cache(void)
- SHASH_FOR_EACH_SAFE (iter, next, &dns_cache) {
- struct dns_data *d = iter->data;
- shash_delete(&dns_cache, iter);
-+ smap_destroy(&d->records);
-+ free(d->dps);
- free(d);
- }
- }
---
-2.28.0
-
diff --git a/SOURCES/0009-ovn-controller-Fix-leak-of-pending-ct-zones.patch b/SOURCES/0009-ovn-controller-Fix-leak-of-pending-ct-zones.patch
deleted file mode 100644
index 79bd340..0000000
--- a/SOURCES/0009-ovn-controller-Fix-leak-of-pending-ct-zones.patch
+++ /dev/null
@@ -1,42 +0,0 @@
-From deed21b21e9d9cb0a05c3af5024fd27fd34720c8 Mon Sep 17 00:00:00 2001
-From: Ilya Maximets
-Date: Fri, 20 Nov 2020 01:17:17 +0100
-Subject: [PATCH 09/16] ovn-controller: Fix leak of pending ct zones.
-
-shash contains pointers to the data that should be freed.
-
- Direct leak of 32 byte(s) in 2 object(s) allocated from:
- #0 0x52100f in malloc (/controller/ovn-controller+0x52100f)
- #1 0x752436 in xmalloc /lib/util.c:138:15
- #2 0x5a2f0b in add_pending_ct_zone_entry /controller/ovn-controller.c:548:45
- #3 0x5a2d1d in update_ct_zones /controller/ovn-controller.c:668:9
- #4 0x59d8c6 in en_ct_zones_run /controller/ovn-controller.c:1495:5
- #5 0x5dade4 in engine_run /lib/inc-proc-eng.c:377:9
- #6 0x59adf4 in main /controller/ovn-controller.c
- #7 0x7f0799ef41a2 in __libc_start_main (/lib64/libc.so.6+0x271a2)
-
-CC: xu rong
-Fixes: 252e1642fb59 ("ovn-controller: pending_ct_zones should be destroyed")
-Acked-by: Dumitru Ceara
-Signed-off-by: Ilya Maximets
-Signed-off-by: Numan Siddique
----
- controller/ovn-controller.c | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
-index 3b41cc390..e3cf855e7 100644
---- a/controller/ovn-controller.c
-+++ b/controller/ovn-controller.c
-@@ -1418,7 +1418,7 @@ en_ct_zones_cleanup(void *data)
- struct ed_type_ct_zones *ct_zones_data = data;
-
- simap_destroy(&ct_zones_data->current);
-- shash_destroy(&ct_zones_data->pending);
-+ shash_destroy_free_data(&ct_zones_data->pending);
- }
-
- static void
---
-2.28.0
-
diff --git a/SOURCES/0009-ovn-detrace-Add-SB-Load-Balancer-cookier-handler.patch b/SOURCES/0009-ovn-detrace-Add-SB-Load-Balancer-cookier-handler.patch
deleted file mode 100644
index 57112ed..0000000
--- a/SOURCES/0009-ovn-detrace-Add-SB-Load-Balancer-cookier-handler.patch
+++ /dev/null
@@ -1,46 +0,0 @@
-From 83a851eddce5ed189b6a145524c41ad817eb2ddf Mon Sep 17 00:00:00 2001
-From: Numan Siddique
-Date: Thu, 12 Nov 2020 17:27:59 +0530
-Subject: [PATCH 09/10] ovn-detrace: Add SB Load Balancer cookier handler.
-
-Acked-by: Dumitru Ceara
-Acked-by: Mark Michelson
-Signed-off-by: Numan Siddique
-
-(cherry-picked from master commit 287605267f64daed845828d5f11b473f0cc98f33)
----
- utilities/ovn-detrace.in | 11 ++++++++++-
- 1 file changed, 10 insertions(+), 1 deletion(-)
-
-diff --git a/utilities/ovn-detrace.in b/utilities/ovn-detrace.in
-index 1dd98df0a..af42b5fc4 100755
---- a/utilities/ovn-detrace.in
-+++ b/utilities/ovn-detrace.in
-@@ -328,6 +328,14 @@ class ChassisHandler(CookieHandlerByUUUID):
- def print_record(self, chassis):
- print_p('Chassis: %s' % (chassis_str([chassis])))
-
-+class SBLoadBalancerHandler(CookieHandlerByUUUID):
-+ def __init__(self, ovnsb_db):
-+ super(SBLoadBalancerHandler, self).__init__(ovnsb_db, 'Load_Balancer')
-+
-+ def print_record(self, lb):
-+ print_p('Load Balancer: %s protocol %s vips %s' % (
-+ lb.name, lb.protocol, lb.vips))
-+
- class OvsInterfaceHandler(CookieHandler):
- def __init__(self, ovs_db):
- super(OvsInterfaceHandler, self).__init__(ovs_db, 'Interface')
-@@ -452,7 +460,8 @@ def main():
- PortBindingHandler(ovsdb_ovnsb),
- MacBindingHandler(ovsdb_ovnsb),
- MulticastGroupHandler(ovsdb_ovnsb),
-- ChassisHandler(ovsdb_ovnsb)
-+ ChassisHandler(ovsdb_ovnsb),
-+ SBLoadBalancerHandler(ovsdb_ovnsb)
- ]
-
- regex_cookie = re.compile(r'^.*cookie 0x([0-9a-fA-F]+)')
---
-2.28.0
-
diff --git a/SOURCES/0009-ovn-northd-Move-lrouter-arp-and-nd-datapath-processi.patch b/SOURCES/0009-ovn-northd-Move-lrouter-arp-and-nd-datapath-processi.patch
deleted file mode 100644
index 2c011c7..0000000
--- a/SOURCES/0009-ovn-northd-Move-lrouter-arp-and-nd-datapath-processi.patch
+++ /dev/null
@@ -1,140 +0,0 @@
-From 34c2afc7d49f735e825e0d01bf1b2b64bb277f76 Mon Sep 17 00:00:00 2001
-Message-Id: <34c2afc7d49f735e825e0d01bf1b2b64bb277f76.1610458802.git.lorenzo.bianconi@redhat.com>
-In-Reply-To:
-References:
-From: Anton Ivanov
-Date: Tue, 5 Jan 2021 17:49:36 +0000
-Subject: [PATCH 09/16] ovn-northd: Move lrouter arp and nd datapath processing
- to a function.
-
-Signed-off-by: Anton Ivanov
-Signed-off-by: Numan Siddique
-Signed-off-by: Lorenzo Bianconi
----
- northd/ovn-northd.c | 96 +++++++++++++++++++++++----------------------
- 1 file changed, 50 insertions(+), 46 deletions(-)
-
-diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
-index 92300e017..7f7bb07be 100644
---- a/northd/ovn-northd.c
-+++ b/northd/ovn-northd.c
-@@ -8937,52 +8937,6 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
- struct ovn_datapath *od;
- struct ovn_port *op;
-
-- HMAP_FOR_EACH (od, key_node, datapaths) {
-- if (!od->nbr) {
-- continue;
-- }
--
-- /* Priority-90-92 flows handle ARP requests and ND packets. Most are
-- * per logical port but DNAT addresses can be handled per datapath
-- * for non gateway router ports.
-- *
-- * Priority 91 and 92 flows are added for each gateway router
-- * port to handle the special cases. In case we get the packet
-- * on a regular port, just reply with the port's ETH address.
-- */
-- for (int i = 0; i < od->nbr->n_nat; i++) {
-- struct ovn_nat *nat_entry = &od->nat_entries[i];
--
-- /* Skip entries we failed to parse. */
-- if (!nat_entry_is_valid(nat_entry)) {
-- continue;
-- }
--
-- /* Skip SNAT entries for now, we handle unique SNAT IPs separately
-- * below.
-- */
-- if (!strcmp(nat_entry->nb->type, "snat")) {
-- continue;
-- }
-- build_lrouter_nat_arp_nd_flow(od, nat_entry, lflows);
-- }
--
-- /* Now handle SNAT entries too, one per unique SNAT IP. */
-- struct shash_node *snat_snode;
-- SHASH_FOR_EACH (snat_snode, &od->snat_ips) {
-- struct ovn_snat_ip *snat_ip = snat_snode->data;
--
-- if (ovs_list_is_empty(&snat_ip->snat_entries)) {
-- continue;
-- }
--
-- struct ovn_nat *nat_entry =
-- CONTAINER_OF(ovs_list_front(&snat_ip->snat_entries),
-- struct ovn_nat, ext_addr_list_node);
-- build_lrouter_nat_arp_nd_flow(od, nat_entry, lflows);
-- }
-- }
--
- /* Logical router ingress table 3: IP Input for IPv4. */
- HMAP_FOR_EACH (op, key_node, ports) {
- if (!op->nbrp) {
-@@ -11308,6 +11262,55 @@ build_ipv6_input_flows_for_lrouter_port(
-
- }
-
-+static void
-+build_lrouter_arp_nd_for_datapath(struct ovn_datapath *od,
-+ struct hmap *lflows)
-+{
-+ if (od->nbr) {
-+
-+ /* Priority-90-92 flows handle ARP requests and ND packets. Most are
-+ * per logical port but DNAT addresses can be handled per datapath
-+ * for non gateway router ports.
-+ *
-+ * Priority 91 and 92 flows are added for each gateway router
-+ * port to handle the special cases. In case we get the packet
-+ * on a regular port, just reply with the port's ETH address.
-+ */
-+ for (int i = 0; i < od->nbr->n_nat; i++) {
-+ struct ovn_nat *nat_entry = &od->nat_entries[i];
-+
-+ /* Skip entries we failed to parse. */
-+ if (!nat_entry_is_valid(nat_entry)) {
-+ continue;
-+ }
-+
-+ /* Skip SNAT entries for now, we handle unique SNAT IPs separately
-+ * below.
-+ */
-+ if (!strcmp(nat_entry->nb->type, "snat")) {
-+ continue;
-+ }
-+ build_lrouter_nat_arp_nd_flow(od, nat_entry, lflows);
-+ }
-+
-+ /* Now handle SNAT entries too, one per unique SNAT IP. */
-+ struct shash_node *snat_snode;
-+ SHASH_FOR_EACH (snat_snode, &od->snat_ips) {
-+ struct ovn_snat_ip *snat_ip = snat_snode->data;
-+
-+ if (ovs_list_is_empty(&snat_ip->snat_entries)) {
-+ continue;
-+ }
-+
-+ struct ovn_nat *nat_entry =
-+ CONTAINER_OF(ovs_list_front(&snat_ip->snat_entries),
-+ struct ovn_nat, ext_addr_list_node);
-+ build_lrouter_nat_arp_nd_flow(od, nat_entry, lflows);
-+ }
-+ }
-+}
-+
-+
- struct lswitch_flow_build_info {
- struct hmap *datapaths;
- struct hmap *ports;
-@@ -11360,6 +11363,7 @@ build_lswitch_and_lrouter_iterate_by_od(struct ovn_datapath *od,
- build_arp_request_flows_for_lrouter(od, lsi->lflows, &lsi->match,
- &lsi->actions);
- build_misc_local_traffic_drop_flows_for_lrouter(od, lsi->lflows);
-+ build_lrouter_arp_nd_for_datapath(od, lsi->lflows);
- }
-
- /* Helper function to combine all lflow generation which is iterated by port.
---
-2.29.2
-
diff --git a/SOURCES/0010-ovn-nbctl-Fix-error-leak-on-duplicated-switch-port.patch b/SOURCES/0010-ovn-nbctl-Fix-error-leak-on-duplicated-switch-port.patch
deleted file mode 100644
index f33d9a2..0000000
--- a/SOURCES/0010-ovn-nbctl-Fix-error-leak-on-duplicated-switch-port.patch
+++ /dev/null
@@ -1,30 +0,0 @@
-From 90deedbeeb26efb1e042f6c0cd0d7caad4fb1b90 Mon Sep 17 00:00:00 2001
-From: Ilya Maximets
-Date: Fri, 20 Nov 2020 01:17:18 +0100
-Subject: [PATCH 10/16] ovn-nbctl: Fix error leak on duplicated switch port.
-
-Error is allocated from heap and should be freed.
-
-Fixes: 738295605b44 ("ovn: Detect and prevent duplicate address assignments.")
-Acked-by: Dumitru Ceara
-Signed-off-by: Ilya Maximets
-Signed-off-by: Numan Siddique
----
- utilities/ovn-nbctl.c | 1 +
- 1 file changed, 1 insertion(+)
-
-diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
-index dfcf67cfd..f4c4f9385 100644
---- a/utilities/ovn-nbctl.c
-+++ b/utilities/ovn-nbctl.c
-@@ -1683,6 +1683,7 @@ nbctl_lsp_set_addresses(struct ctl_context *ctx)
- error = lsp_contains_duplicates(ls, lsp, ctx->argv[i]);
- if (error) {
- ctl_error(ctx, "%s", error);
-+ free(error);
- return;
- }
- }
---
-2.28.0
-
diff --git a/SOURCES/0010-ovn-northd-Move-ipv4-input-to-a-function.patch b/SOURCES/0010-ovn-northd-Move-ipv4-input-to-a-function.patch
deleted file mode 100644
index d15e521..0000000
--- a/SOURCES/0010-ovn-northd-Move-ipv4-input-to-a-function.patch
+++ /dev/null
@@ -1,556 +0,0 @@
-From 761f760a42d97184c870e892d299587e657a2c52 Mon Sep 17 00:00:00 2001
-Message-Id: <761f760a42d97184c870e892d299587e657a2c52.1610458802.git.lorenzo.bianconi@redhat.com>
-In-Reply-To:
-References:
-From: Anton Ivanov
-Date: Tue, 5 Jan 2021 17:49:37 +0000
-Subject: [PATCH 10/16] ovn-northd: Move ipv4 input to a function.
-
-Signed-off-by: Anton Ivanov
-Signed-off-by: Numan Siddique
-Signed-off-by: Lorenzo Bianconi
----
- northd/ovn-northd.c | 499 ++++++++++++++++++++++----------------------
- 1 file changed, 249 insertions(+), 250 deletions(-)
-
-diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
-index 7f7bb07be..f9b8d588b 100644
---- a/northd/ovn-northd.c
-+++ b/northd/ovn-northd.c
-@@ -8924,7 +8924,7 @@ build_lrouter_force_snat_flows(struct hmap *lflows, struct ovn_datapath *od,
- }
-
- static void
--build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
-+build_lrouter_flows(struct hmap *datapaths,
- struct hmap *lflows, struct shash *meter_groups,
- struct hmap *lbs)
- {
-@@ -8935,254 +8935,6 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
- struct ds actions = DS_EMPTY_INITIALIZER;
-
- struct ovn_datapath *od;
-- struct ovn_port *op;
--
-- /* Logical router ingress table 3: IP Input for IPv4. */
-- HMAP_FOR_EACH (op, key_node, ports) {
-- if (!op->nbrp) {
-- continue;
-- }
--
-- if (op->derived) {
-- /* No ingress packets are accepted on a chassisredirect
-- * port, so no need to program flows for that port. */
-- continue;
-- }
--
-- if (op->lrp_networks.n_ipv4_addrs) {
-- /* L3 admission control: drop packets that originate from an
-- * IPv4 address owned by the router or a broadcast address
-- * known to the router (priority 100). */
-- ds_clear(&match);
-- ds_put_cstr(&match, "ip4.src == ");
-- op_put_v4_networks(&match, op, true);
-- ds_put_cstr(&match, " && "REGBIT_EGRESS_LOOPBACK" == 0");
-- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 100,
-- ds_cstr(&match), "drop;",
-- &op->nbrp->header_);
--
-- /* ICMP echo reply. These flows reply to ICMP echo requests
-- * received for the router's IP address. Since packets only
-- * get here as part of the logical router datapath, the inport
-- * (i.e. the incoming locally attached net) does not matter.
-- * The ip.ttl also does not matter (RFC1812 section 4.2.2.9) */
-- ds_clear(&match);
-- ds_put_cstr(&match, "ip4.dst == ");
-- op_put_v4_networks(&match, op, false);
-- ds_put_cstr(&match, " && icmp4.type == 8 && icmp4.code == 0");
--
-- const char * icmp_actions = "ip4.dst <-> ip4.src; "
-- "ip.ttl = 255; "
-- "icmp4.type = 0; "
-- "flags.loopback = 1; "
-- "next; ";
-- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90,
-- ds_cstr(&match), icmp_actions,
-- &op->nbrp->header_);
-- }
--
-- /* ICMP time exceeded */
-- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
-- ds_clear(&match);
-- ds_clear(&actions);
--
-- ds_put_format(&match,
-- "inport == %s && ip4 && "
-- "ip.ttl == {0, 1} && !ip.later_frag", op->json_key);
-- ds_put_format(&actions,
-- "icmp4 {"
-- "eth.dst <-> eth.src; "
-- "icmp4.type = 11; /* Time exceeded */ "
-- "icmp4.code = 0; /* TTL exceeded in transit */ "
-- "ip4.dst = ip4.src; "
-- "ip4.src = %s; "
-- "ip.ttl = 255; "
-- "next; };",
-- op->lrp_networks.ipv4_addrs[i].addr_s);
-- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 40,
-- ds_cstr(&match), ds_cstr(&actions),
-- &op->nbrp->header_);
-- }
--
-- /* ARP reply. These flows reply to ARP requests for the router's own
-- * IP address. */
-- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
-- ds_clear(&match);
-- ds_put_format(&match, "arp.spa == %s/%u",
-- op->lrp_networks.ipv4_addrs[i].network_s,
-- op->lrp_networks.ipv4_addrs[i].plen);
--
-- if (op->od->l3dgw_port && op->od->l3redirect_port && op->peer
-- && op->peer->od->n_localnet_ports) {
-- bool add_chassis_resident_check = false;
-- if (op == op->od->l3dgw_port) {
-- /* Traffic with eth.src = l3dgw_port->lrp_networks.ea_s
-- * should only be sent from the gateway chassis, so that
-- * upstream MAC learning points to the gateway chassis.
-- * Also need to avoid generation of multiple ARP responses
-- * from different chassis. */
-- add_chassis_resident_check = true;
-- } else {
-- /* Check if the option 'reside-on-redirect-chassis'
-- * is set to true on the router port. If set to true
-- * and if peer's logical switch has a localnet port, it
-- * means the router pipeline for the packets from
-- * peer's logical switch is be run on the chassis
-- * hosting the gateway port and it should reply to the
-- * ARP requests for the router port IPs.
-- */
-- add_chassis_resident_check = smap_get_bool(
-- &op->nbrp->options,
-- "reside-on-redirect-chassis", false);
-- }
--
-- if (add_chassis_resident_check) {
-- ds_put_format(&match, " && is_chassis_resident(%s)",
-- op->od->l3redirect_port->json_key);
-- }
-- }
--
-- build_lrouter_arp_flow(op->od, op,
-- op->lrp_networks.ipv4_addrs[i].addr_s,
-- REG_INPORT_ETH_ADDR, &match, false, 90,
-- &op->nbrp->header_, lflows);
-- }
--
-- /* A set to hold all load-balancer vips that need ARP responses. */
-- struct sset all_ips_v4 = SSET_INITIALIZER(&all_ips_v4);
-- struct sset all_ips_v6 = SSET_INITIALIZER(&all_ips_v6);
-- get_router_load_balancer_ips(op->od, &all_ips_v4, &all_ips_v6);
--
-- const char *ip_address;
-- SSET_FOR_EACH (ip_address, &all_ips_v4) {
-- ds_clear(&match);
-- if (op == op->od->l3dgw_port) {
-- ds_put_format(&match, "is_chassis_resident(%s)",
-- op->od->l3redirect_port->json_key);
-- }
--
-- build_lrouter_arp_flow(op->od, op,
-- ip_address, REG_INPORT_ETH_ADDR,
-- &match, false, 90, NULL, lflows);
-- }
--
-- SSET_FOR_EACH (ip_address, &all_ips_v6) {
-- ds_clear(&match);
-- if (op == op->od->l3dgw_port) {
-- ds_put_format(&match, "is_chassis_resident(%s)",
-- op->od->l3redirect_port->json_key);
-- }
--
-- build_lrouter_nd_flow(op->od, op, "nd_na",
-- ip_address, NULL, REG_INPORT_ETH_ADDR,
-- &match, false, 90, NULL, lflows);
-- }
--
-- sset_destroy(&all_ips_v4);
-- sset_destroy(&all_ips_v6);
--
-- if (!smap_get(&op->od->nbr->options, "chassis")
-- && !op->od->l3dgw_port) {
-- /* UDP/TCP port unreachable. */
-- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
-- ds_clear(&match);
-- ds_put_format(&match,
-- "ip4 && ip4.dst == %s && !ip.later_frag && udp",
-- op->lrp_networks.ipv4_addrs[i].addr_s);
-- const char *action = "icmp4 {"
-- "eth.dst <-> eth.src; "
-- "ip4.dst <-> ip4.src; "
-- "ip.ttl = 255; "
-- "icmp4.type = 3; "
-- "icmp4.code = 3; "
-- "next; };";
-- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-- 80, ds_cstr(&match), action,
-- &op->nbrp->header_);
--
-- ds_clear(&match);
-- ds_put_format(&match,
-- "ip4 && ip4.dst == %s && !ip.later_frag && tcp",
-- op->lrp_networks.ipv4_addrs[i].addr_s);
-- action = "tcp_reset {"
-- "eth.dst <-> eth.src; "
-- "ip4.dst <-> ip4.src; "
-- "next; };";
-- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-- 80, ds_cstr(&match), action,
-- &op->nbrp->header_);
--
-- ds_clear(&match);
-- ds_put_format(&match,
-- "ip4 && ip4.dst == %s && !ip.later_frag",
-- op->lrp_networks.ipv4_addrs[i].addr_s);
-- action = "icmp4 {"
-- "eth.dst <-> eth.src; "
-- "ip4.dst <-> ip4.src; "
-- "ip.ttl = 255; "
-- "icmp4.type = 3; "
-- "icmp4.code = 2; "
-- "next; };";
-- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-- 70, ds_cstr(&match), action,
-- &op->nbrp->header_);
-- }
-- }
--
-- /* Drop IP traffic destined to router owned IPs except if the IP is
-- * also a SNAT IP. Those are dropped later, in stage
-- * "lr_in_arp_resolve", if unSNAT was unsuccessful.
-- *
-- * Priority 60.
-- */
-- build_lrouter_drop_own_dest(op, S_ROUTER_IN_IP_INPUT, 60, false,
-- lflows);
--
-- /* ARP / ND handling for external IP addresses.
-- *
-- * DNAT and SNAT IP addresses are external IP addresses that need ARP
-- * handling.
-- *
-- * These are already taken care globally, per router. The only
-- * exception is on the l3dgw_port where we might need to use a
-- * different ETH address.
-- */
-- if (op != op->od->l3dgw_port) {
-- continue;
-- }
--
-- for (size_t i = 0; i < op->od->nbr->n_nat; i++) {
-- struct ovn_nat *nat_entry = &op->od->nat_entries[i];
--
-- /* Skip entries we failed to parse. */
-- if (!nat_entry_is_valid(nat_entry)) {
-- continue;
-- }
--
-- /* Skip SNAT entries for now, we handle unique SNAT IPs separately
-- * below.
-- */
-- if (!strcmp(nat_entry->nb->type, "snat")) {
-- continue;
-- }
-- build_lrouter_port_nat_arp_nd_flow(op, nat_entry, lflows);
-- }
--
-- /* Now handle SNAT entries too, one per unique SNAT IP. */
-- struct shash_node *snat_snode;
-- SHASH_FOR_EACH (snat_snode, &op->od->snat_ips) {
-- struct ovn_snat_ip *snat_ip = snat_snode->data;
--
-- if (ovs_list_is_empty(&snat_ip->snat_entries)) {
-- continue;
-- }
--
-- struct ovn_nat *nat_entry =
-- CONTAINER_OF(ovs_list_front(&snat_ip->snat_entries),
-- struct ovn_nat, ext_addr_list_node);
-- build_lrouter_port_nat_arp_nd_flow(op, nat_entry, lflows);
-- }
-- }
-
- /* NAT, Defrag and load balancing. */
- HMAP_FOR_EACH (od, key_node, datapaths) {
-@@ -11310,6 +11062,251 @@ build_lrouter_arp_nd_for_datapath(struct ovn_datapath *od,
- }
- }
-
-+/* Logical router ingress table 3: IP Input for IPv4. */
-+static void
-+build_lrouter_ipv4_ip_input(struct ovn_port *op,
-+ struct hmap *lflows,
-+ struct ds *match, struct ds *actions)
-+{
-+ /* No ingress packets are accepted on a chassisredirect
-+ * port, so no need to program flows for that port. */
-+ if (op->nbrp && (!op->derived)) {
-+ if (op->lrp_networks.n_ipv4_addrs) {
-+ /* L3 admission control: drop packets that originate from an
-+ * IPv4 address owned by the router or a broadcast address
-+ * known to the router (priority 100). */
-+ ds_clear(match);
-+ ds_put_cstr(match, "ip4.src == ");
-+ op_put_v4_networks(match, op, true);
-+ ds_put_cstr(match, " && "REGBIT_EGRESS_LOOPBACK" == 0");
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 100,
-+ ds_cstr(match), "drop;",
-+ &op->nbrp->header_);
-+
-+ /* ICMP echo reply. These flows reply to ICMP echo requests
-+ * received for the router's IP address. Since packets only
-+ * get here as part of the logical router datapath, the inport
-+ * (i.e. the incoming locally attached net) does not matter.
-+ * The ip.ttl also does not matter (RFC1812 section 4.2.2.9) */
-+ ds_clear(match);
-+ ds_put_cstr(match, "ip4.dst == ");
-+ op_put_v4_networks(match, op, false);
-+ ds_put_cstr(match, " && icmp4.type == 8 && icmp4.code == 0");
-+
-+ const char * icmp_actions = "ip4.dst <-> ip4.src; "
-+ "ip.ttl = 255; "
-+ "icmp4.type = 0; "
-+ "flags.loopback = 1; "
-+ "next; ";
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90,
-+ ds_cstr(match), icmp_actions,
-+ &op->nbrp->header_);
-+ }
-+
-+ /* ICMP time exceeded */
-+ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
-+ ds_clear(match);
-+ ds_clear(actions);
-+
-+ ds_put_format(match,
-+ "inport == %s && ip4 && "
-+ "ip.ttl == {0, 1} && !ip.later_frag", op->json_key);
-+ ds_put_format(actions,
-+ "icmp4 {"
-+ "eth.dst <-> eth.src; "
-+ "icmp4.type = 11; /* Time exceeded */ "
-+ "icmp4.code = 0; /* TTL exceeded in transit */ "
-+ "ip4.dst = ip4.src; "
-+ "ip4.src = %s; "
-+ "ip.ttl = 255; "
-+ "next; };",
-+ op->lrp_networks.ipv4_addrs[i].addr_s);
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 40,
-+ ds_cstr(match), ds_cstr(actions),
-+ &op->nbrp->header_);
-+ }
-+
-+ /* ARP reply. These flows reply to ARP requests for the router's own
-+ * IP address. */
-+ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
-+ ds_clear(match);
-+ ds_put_format(match, "arp.spa == %s/%u",
-+ op->lrp_networks.ipv4_addrs[i].network_s,
-+ op->lrp_networks.ipv4_addrs[i].plen);
-+
-+ if (op->od->l3dgw_port && op->od->l3redirect_port && op->peer
-+ && op->peer->od->n_localnet_ports) {
-+ bool add_chassis_resident_check = false;
-+ if (op == op->od->l3dgw_port) {
-+ /* Traffic with eth.src = l3dgw_port->lrp_networks.ea_s
-+ * should only be sent from the gateway chassis, so that
-+ * upstream MAC learning points to the gateway chassis.
-+ * Also need to avoid generation of multiple ARP responses
-+ * from different chassis. */
-+ add_chassis_resident_check = true;
-+ } else {
-+ /* Check if the option 'reside-on-redirect-chassis'
-+ * is set to true on the router port. If set to true
-+ * and if peer's logical switch has a localnet port, it
-+ * means the router pipeline for the packets from
-+ * peer's logical switch is be run on the chassis
-+ * hosting the gateway port and it should reply to the
-+ * ARP requests for the router port IPs.
-+ */
-+ add_chassis_resident_check = smap_get_bool(
-+ &op->nbrp->options,
-+ "reside-on-redirect-chassis", false);
-+ }
-+
-+ if (add_chassis_resident_check) {
-+ ds_put_format(match, " && is_chassis_resident(%s)",
-+ op->od->l3redirect_port->json_key);
-+ }
-+ }
-+
-+ build_lrouter_arp_flow(op->od, op,
-+ op->lrp_networks.ipv4_addrs[i].addr_s,
-+ REG_INPORT_ETH_ADDR, match, false, 90,
-+ &op->nbrp->header_, lflows);
-+ }
-+
-+ /* A set to hold all load-balancer vips that need ARP responses. */
-+ struct sset all_ips_v4 = SSET_INITIALIZER(&all_ips_v4);
-+ struct sset all_ips_v6 = SSET_INITIALIZER(&all_ips_v6);
-+ get_router_load_balancer_ips(op->od, &all_ips_v4, &all_ips_v6);
-+
-+ const char *ip_address;
-+ SSET_FOR_EACH (ip_address, &all_ips_v4) {
-+ ds_clear(match);
-+ if (op == op->od->l3dgw_port) {
-+ ds_put_format(match, "is_chassis_resident(%s)",
-+ op->od->l3redirect_port->json_key);
-+ }
-+
-+ build_lrouter_arp_flow(op->od, op,
-+ ip_address, REG_INPORT_ETH_ADDR,
-+ match, false, 90, NULL, lflows);
-+ }
-+
-+ SSET_FOR_EACH (ip_address, &all_ips_v6) {
-+ ds_clear(match);
-+ if (op == op->od->l3dgw_port) {
-+ ds_put_format(match, "is_chassis_resident(%s)",
-+ op->od->l3redirect_port->json_key);
-+ }
-+
-+ build_lrouter_nd_flow(op->od, op, "nd_na",
-+ ip_address, NULL, REG_INPORT_ETH_ADDR,
-+ match, false, 90, NULL, lflows);
-+ }
-+
-+ sset_destroy(&all_ips_v4);
-+ sset_destroy(&all_ips_v6);
-+
-+ if (!smap_get(&op->od->nbr->options, "chassis")
-+ && !op->od->l3dgw_port) {
-+ /* UDP/TCP port unreachable. */
-+ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
-+ ds_clear(match);
-+ ds_put_format(match,
-+ "ip4 && ip4.dst == %s && !ip.later_frag && udp",
-+ op->lrp_networks.ipv4_addrs[i].addr_s);
-+ const char *action = "icmp4 {"
-+ "eth.dst <-> eth.src; "
-+ "ip4.dst <-> ip4.src; "
-+ "ip.ttl = 255; "
-+ "icmp4.type = 3; "
-+ "icmp4.code = 3; "
-+ "next; };";
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-+ 80, ds_cstr(match), action,
-+ &op->nbrp->header_);
-+
-+ ds_clear(match);
-+ ds_put_format(match,
-+ "ip4 && ip4.dst == %s && !ip.later_frag && tcp",
-+ op->lrp_networks.ipv4_addrs[i].addr_s);
-+ action = "tcp_reset {"
-+ "eth.dst <-> eth.src; "
-+ "ip4.dst <-> ip4.src; "
-+ "next; };";
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-+ 80, ds_cstr(match), action,
-+ &op->nbrp->header_);
-+
-+ ds_clear(match);
-+ ds_put_format(match,
-+ "ip4 && ip4.dst == %s && !ip.later_frag",
-+ op->lrp_networks.ipv4_addrs[i].addr_s);
-+ action = "icmp4 {"
-+ "eth.dst <-> eth.src; "
-+ "ip4.dst <-> ip4.src; "
-+ "ip.ttl = 255; "
-+ "icmp4.type = 3; "
-+ "icmp4.code = 2; "
-+ "next; };";
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-+ 70, ds_cstr(match), action,
-+ &op->nbrp->header_);
-+ }
-+ }
-+
-+ /* Drop IP traffic destined to router owned IPs except if the IP is
-+ * also a SNAT IP. Those are dropped later, in stage
-+ * "lr_in_arp_resolve", if unSNAT was unsuccessful.
-+ *
-+ * Priority 60.
-+ */
-+ build_lrouter_drop_own_dest(op, S_ROUTER_IN_IP_INPUT, 60, false,
-+ lflows);
-+
-+ /* ARP / ND handling for external IP addresses.
-+ *
-+ * DNAT and SNAT IP addresses are external IP addresses that need ARP
-+ * handling.
-+ *
-+ * These are already taken care globally, per router. The only
-+ * exception is on the l3dgw_port where we might need to use a
-+ * different ETH address.
-+ */
-+ if (op != op->od->l3dgw_port) {
-+ return;
-+ }
-+
-+ for (size_t i = 0; i < op->od->nbr->n_nat; i++) {
-+ struct ovn_nat *nat_entry = &op->od->nat_entries[i];
-+
-+ /* Skip entries we failed to parse. */
-+ if (!nat_entry_is_valid(nat_entry)) {
-+ continue;
-+ }
-+
-+ /* Skip SNAT entries for now, we handle unique SNAT IPs separately
-+ * below.
-+ */
-+ if (!strcmp(nat_entry->nb->type, "snat")) {
-+ continue;
-+ }
-+ build_lrouter_port_nat_arp_nd_flow(op, nat_entry, lflows);
-+ }
-+
-+ /* Now handle SNAT entries too, one per unique SNAT IP. */
-+ struct shash_node *snat_snode;
-+ SHASH_FOR_EACH (snat_snode, &op->od->snat_ips) {
-+ struct ovn_snat_ip *snat_ip = snat_snode->data;
-+
-+ if (ovs_list_is_empty(&snat_ip->snat_entries)) {
-+ continue;
-+ }
-+
-+ struct ovn_nat *nat_entry =
-+ CONTAINER_OF(ovs_list_front(&snat_ip->snat_entries),
-+ struct ovn_nat, ext_addr_list_node);
-+ build_lrouter_port_nat_arp_nd_flow(op, nat_entry, lflows);
-+ }
-+ }
-+}
-+
-
- struct lswitch_flow_build_info {
- struct hmap *datapaths;
-@@ -11404,6 +11401,8 @@ build_lswitch_and_lrouter_iterate_by_op(struct ovn_port *op,
- build_dhcpv6_reply_flows_for_lrouter_port(op, lsi->lflows, &lsi->match);
- build_ipv6_input_flows_for_lrouter_port(op, lsi->lflows,
- &lsi->match, &lsi->actions);
-+ build_lrouter_ipv4_ip_input(op, lsi->lflows,
-+ &lsi->match, &lsi->actions);
- }
-
- static void
-@@ -11462,7 +11461,7 @@ build_lswitch_and_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
- build_lswitch_flows(datapaths, lflows);
-
- /* Legacy lrouter build - to be migrated. */
-- build_lrouter_flows(datapaths, ports, lflows, meter_groups, lbs);
-+ build_lrouter_flows(datapaths, lflows, meter_groups, lbs);
- }
-
- struct ovn_dp_group {
---
-2.29.2
-
diff --git a/SOURCES/0010-sbctl-Add-Load-Balancer-support-for-vflows-option.patch b/SOURCES/0010-sbctl-Add-Load-Balancer-support-for-vflows-option.patch
deleted file mode 100644
index ff7ae66..0000000
--- a/SOURCES/0010-sbctl-Add-Load-Balancer-support-for-vflows-option.patch
+++ /dev/null
@@ -1,96 +0,0 @@
-From a1cbb077f5907a3ad898e43478614d18ad7be294 Mon Sep 17 00:00:00 2001
-From: Numan Siddique
-Date: Thu, 12 Nov 2020 17:28:17 +0530
-Subject: [PATCH 10/10] sbctl: Add Load Balancer support for vflows option.
-
-Acked-by: Dumitru Ceara
-Acked-by: Mark Michelson
-Signed-off-by: Numan Siddique
----
- utilities/ovn-sbctl.c | 56 +++++++++++++++++++++++++++++++++++++++++++
- 1 file changed, 56 insertions(+)
-
-diff --git a/utilities/ovn-sbctl.c b/utilities/ovn-sbctl.c
-index 30236c9cc..a524175c4 100644
---- a/utilities/ovn-sbctl.c
-+++ b/utilities/ovn-sbctl.c
-@@ -542,6 +542,11 @@ pre_get_info(struct ctl_context *ctx)
- ovsdb_idl_add_column(ctx->idl, &sbrec_mac_binding_col_logical_port);
- ovsdb_idl_add_column(ctx->idl, &sbrec_mac_binding_col_ip);
- ovsdb_idl_add_column(ctx->idl, &sbrec_mac_binding_col_mac);
-+
-+ ovsdb_idl_add_column(ctx->idl, &sbrec_load_balancer_col_datapaths);
-+ ovsdb_idl_add_column(ctx->idl, &sbrec_load_balancer_col_vips);
-+ ovsdb_idl_add_column(ctx->idl, &sbrec_load_balancer_col_name);
-+ ovsdb_idl_add_column(ctx->idl, &sbrec_load_balancer_col_protocol);
- }
-
- static struct cmd_show_table cmd_show_tables[] = {
-@@ -1010,6 +1015,56 @@ cmd_lflow_list_chassis(struct ctl_context *ctx, struct vconn *vconn,
- }
- }
-
-+static void
-+cmd_lflow_list_load_balancers(struct ctl_context *ctx, struct vconn *vconn,
-+ const struct sbrec_datapath_binding *datapath,
-+ bool stats, bool print_uuid)
-+{
-+ const struct sbrec_load_balancer *lb;
-+ const struct sbrec_load_balancer *lb_prev = NULL;
-+ SBREC_LOAD_BALANCER_FOR_EACH (lb, ctx->idl) {
-+ bool dp_found = false;
-+ if (datapath) {
-+ size_t i;
-+ for (i = 0; i < lb->n_datapaths; i++) {
-+ if (datapath == lb->datapaths[i]) {
-+ dp_found = true;
-+ break;
-+ }
-+ }
-+ if (!dp_found) {
-+ continue;
-+ }
-+ }
-+
-+ if (!lb_prev) {
-+ printf("\nLoad Balancers:\n");
-+ }
-+
-+ printf(" ");
-+ print_uuid_part(&lb->header_.uuid, print_uuid);
-+ printf("name=\"%s\", protocol=\"%s\", ", lb->name, lb->protocol);
-+ if (!dp_found) {
-+ for (size_t i = 0; i < lb->n_datapaths; i++) {
-+ print_vflow_datapath_name(lb->datapaths[i], true);
-+ }
-+ }
-+
-+ printf("\n vips:\n");
-+ struct smap_node *node;
-+ SMAP_FOR_EACH (node, &lb->vips) {
-+ printf(" %s = %s\n", node->key, node->value);
-+ }
-+ printf("\n");
-+
-+ if (vconn) {
-+ sbctl_dump_openflow(vconn, &lb->header_.uuid, stats);
-+ }
-+
-+ lb_prev = lb;
-+ }
-+}
-+
- static void
- cmd_lflow_list(struct ctl_context *ctx)
- {
-@@ -1119,6 +1174,7 @@ cmd_lflow_list(struct ctl_context *ctx)
- cmd_lflow_list_mac_bindings(ctx, vconn, datapath, stats, print_uuid);
- cmd_lflow_list_mc_groups(ctx, vconn, datapath, stats, print_uuid);
- cmd_lflow_list_chassis(ctx, vconn, stats, print_uuid);
-+ cmd_lflow_list_load_balancers(ctx, vconn, datapath, stats, print_uuid);
- }
-
- vconn_close(vconn);
---
-2.28.0
-
diff --git a/SOURCES/0011-northd-Fix-leak-of-dynamic-string-for-fwd-group-port.patch b/SOURCES/0011-northd-Fix-leak-of-dynamic-string-for-fwd-group-port.patch
deleted file mode 100644
index 99655a3..0000000
--- a/SOURCES/0011-northd-Fix-leak-of-dynamic-string-for-fwd-group-port.patch
+++ /dev/null
@@ -1,50 +0,0 @@
-From 0006b3389d43d5f3d55b895a9f856106cd28085e Mon Sep 17 00:00:00 2001
-From: Ilya Maximets
-Date: Fri, 20 Nov 2020 01:17:19 +0100
-Subject: [PATCH 11/16] northd: Fix leak of dynamic string for fwd group ports.
-
-'group_ports' never destroyed and re-created on each iteration.
-
-CC: Manoj Sharma
-Fixes: edb240081518 ("Forwarding group to load balance l2 traffic with liveness detection")
-Acked-by: Dumitru Ceara
-Signed-off-by: Ilya Maximets
-Signed-off-by: Numan Siddique
-
-(cherry-picked from master commit 44f41669812c633bc180074e6d91e0d0f3a781a1)
----
- northd/ovn-northd.c | 4 +++-
- 1 file changed, 3 insertions(+), 1 deletion(-)
-
-diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
-index 0acff2322..23ad7ba7f 100644
---- a/northd/ovn-northd.c
-+++ b/northd/ovn-northd.c
-@@ -5960,6 +5960,7 @@ build_fwd_group_lflows(struct ovn_datapath *od, struct hmap *lflows)
- {
- struct ds match = DS_EMPTY_INITIALIZER;
- struct ds actions = DS_EMPTY_INITIALIZER;
-+ struct ds group_ports = DS_EMPTY_INITIALIZER;
-
- for (int i = 0; i < od->nbs->n_forwarding_groups; ++i) {
- const struct nbrec_forwarding_group *fwd_group = NULL;
-@@ -5993,7 +5994,7 @@ build_fwd_group_lflows(struct ovn_datapath *od, struct hmap *lflows)
- ds_put_format(&match, "eth.dst == %s", fwd_group->vmac);
-
- /* Create a comma separated string of child ports */
-- struct ds group_ports = DS_EMPTY_INITIALIZER;
-+ ds_clear(&group_ports);
- if (fwd_group->liveness) {
- ds_put_cstr(&group_ports, "liveness=\"true\",");
- }
-@@ -6013,6 +6014,7 @@ build_fwd_group_lflows(struct ovn_datapath *od, struct hmap *lflows)
-
- ds_destroy(&match);
- ds_destroy(&actions);
-+ ds_destroy(&group_ports);
- }
-
- static void
---
-2.28.0
-
diff --git a/SOURCES/0011-ovn-northd-move-NAT-Defrag-and-lb-to-a-function.patch b/SOURCES/0011-ovn-northd-move-NAT-Defrag-and-lb-to-a-function.patch
deleted file mode 100644
index a27b30b..0000000
--- a/SOURCES/0011-ovn-northd-move-NAT-Defrag-and-lb-to-a-function.patch
+++ /dev/null
@@ -1,4489 +0,0 @@
-From 7699c1043a3fec9eb215fc430202ca01846c505e Mon Sep 17 00:00:00 2001
-Message-Id: <7699c1043a3fec9eb215fc430202ca01846c505e.1610458802.git.lorenzo.bianconi@redhat.com>
-In-Reply-To:
-References:
-From: Anton Ivanov
-Date: Tue, 5 Jan 2021 17:49:38 +0000
-Subject: [PATCH 11/16] ovn-northd: move NAT, Defrag and lb to a function.
-
-Signed-off-by: Anton Ivanov
-Signed-off-by: Numan Siddique
-Signed-off-by: Lorenzo Bianconi
----
- northd/ovn-northd.c | 4128 +++++++++++++++++++++----------------------
- 1 file changed, 2058 insertions(+), 2070 deletions(-)
-
-diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
-index f9b8d588b..f588d8c32 100644
---- a/northd/ovn-northd.c
-+++ b/northd/ovn-northd.c
-@@ -8923,2391 +8923,2380 @@ build_lrouter_force_snat_flows(struct hmap *lflows, struct ovn_datapath *od,
- ds_destroy(&actions);
- }
-
-+/* Logical router ingress Table 0: L2 Admission Control
-+ * Generic admission control flows (without inport check).
-+ */
- static void
--build_lrouter_flows(struct hmap *datapaths,
-- struct hmap *lflows, struct shash *meter_groups,
-- struct hmap *lbs)
-+build_adm_ctrl_flows_for_lrouter(
-+ struct ovn_datapath *od, struct hmap *lflows)
- {
-- /* This flow table structure is documented in ovn-northd(8), so please
-- * update ovn-northd.8.xml if you change anything. */
--
-- struct ds match = DS_EMPTY_INITIALIZER;
-- struct ds actions = DS_EMPTY_INITIALIZER;
-+ if (od->nbr) {
-+ /* Logical VLANs not supported.
-+ * Broadcast/multicast source address is invalid. */
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ADMISSION, 100,
-+ "vlan.present || eth.src[40]", "drop;");
-+ }
-+}
-
-- struct ovn_datapath *od;
-+/* Logical router ingress Table 0: L2 Admission Control
-+ * This table drops packets that the router shouldn’t see at all based
-+ * on their Ethernet headers.
-+ */
-+static void
-+build_adm_ctrl_flows_for_lrouter_port(
-+ struct ovn_port *op, struct hmap *lflows,
-+ struct ds *match, struct ds *actions)
-+{
-+ if (op->nbrp) {
-+ if (!lrport_is_enabled(op->nbrp)) {
-+ /* Drop packets from disabled logical ports (since logical flow
-+ * tables are default-drop). */
-+ return;
-+ }
-
-- /* NAT, Defrag and load balancing. */
-- HMAP_FOR_EACH (od, key_node, datapaths) {
-- if (!od->nbr) {
-- continue;
-+ if (op->derived) {
-+ /* No ingress packets should be received on a chassisredirect
-+ * port. */
-+ return;
- }
-
-- /* Packets are allowed by default. */
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_DEFRAG, 0, "1", "next;");
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 0, "1", "next;");
-- ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 0, "1", "next;");
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 0, "1", "next;");
-- ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 0, "1", "next;");
-- ovn_lflow_add(lflows, od, S_ROUTER_OUT_EGR_LOOP, 0, "1", "next;");
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 0, "1", "next;");
-+ /* Store the ethernet address of the port receiving the packet.
-+ * This will save us from having to match on inport further down in
-+ * the pipeline.
-+ */
-+ ds_clear(actions);
-+ ds_put_format(actions, REG_INPORT_ETH_ADDR " = %s; next;",
-+ op->lrp_networks.ea_s);
-
-- /* Send the IPv6 NS packets to next table. When ovn-controller
-- * generates IPv6 NS (for the action - nd_ns{}), the injected
-- * packet would go through conntrack - which is not required. */
-- ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 120, "nd_ns", "next;");
-+ ds_clear(match);
-+ ds_put_format(match, "eth.mcast && inport == %s", op->json_key);
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_ADMISSION, 50,
-+ ds_cstr(match), ds_cstr(actions),
-+ &op->nbrp->header_);
-
-- /* NAT rules are only valid on Gateway routers and routers with
-- * l3dgw_port (router has a port with gateway chassis
-- * specified). */
-- if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) {
-- continue;
-+ ds_clear(match);
-+ ds_put_format(match, "eth.dst == %s && inport == %s",
-+ op->lrp_networks.ea_s, op->json_key);
-+ if (op->od->l3dgw_port && op == op->od->l3dgw_port
-+ && op->od->l3redirect_port) {
-+ /* Traffic with eth.dst = l3dgw_port->lrp_networks.ea_s
-+ * should only be received on the gateway chassis. */
-+ ds_put_format(match, " && is_chassis_resident(%s)",
-+ op->od->l3redirect_port->json_key);
- }
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_ADMISSION, 50,
-+ ds_cstr(match), ds_cstr(actions),
-+ &op->nbrp->header_);
-+ }
-+}
-
-- struct sset nat_entries = SSET_INITIALIZER(&nat_entries);
-
-- bool dnat_force_snat_ip =
-- !lport_addresses_is_empty(&od->dnat_force_snat_addrs);
-- bool lb_force_snat_ip =
-- !lport_addresses_is_empty(&od->lb_force_snat_addrs);
-+/* Logical router ingress Table 1 and 2: Neighbor lookup and learning
-+ * lflows for logical routers. */
-+static void
-+build_neigh_learning_flows_for_lrouter(
-+ struct ovn_datapath *od, struct hmap *lflows,
-+ struct ds *match, struct ds *actions)
-+{
-+ if (od->nbr) {
-
-- for (int i = 0; i < od->nbr->n_nat; i++) {
-- const struct nbrec_nat *nat;
-+ /* Learn MAC bindings from ARP/IPv6 ND.
-+ *
-+ * For ARP packets, table LOOKUP_NEIGHBOR does a lookup for the
-+ * (arp.spa, arp.sha) in the mac binding table using the 'lookup_arp'
-+ * action and stores the result in REGBIT_LOOKUP_NEIGHBOR_RESULT bit.
-+ * If "always_learn_from_arp_request" is set to false, it will also
-+ * lookup for the (arp.spa) in the mac binding table using the
-+ * "lookup_arp_ip" action for ARP request packets, and stores the
-+ * result in REGBIT_LOOKUP_NEIGHBOR_IP_RESULT bit; or set that bit
-+ * to "1" directly for ARP response packets.
-+ *
-+ * For IPv6 ND NA packets, table LOOKUP_NEIGHBOR does a lookup
-+ * for the (nd.target, nd.tll) in the mac binding table using the
-+ * 'lookup_nd' action and stores the result in
-+ * REGBIT_LOOKUP_NEIGHBOR_RESULT bit. If
-+ * "always_learn_from_arp_request" is set to false,
-+ * REGBIT_LOOKUP_NEIGHBOR_IP_RESULT bit is set.
-+ *
-+ * For IPv6 ND NS packets, table LOOKUP_NEIGHBOR does a lookup
-+ * for the (ip6.src, nd.sll) in the mac binding table using the
-+ * 'lookup_nd' action and stores the result in
-+ * REGBIT_LOOKUP_NEIGHBOR_RESULT bit. If
-+ * "always_learn_from_arp_request" is set to false, it will also lookup
-+ * for the (ip6.src) in the mac binding table using the "lookup_nd_ip"
-+ * action and stores the result in REGBIT_LOOKUP_NEIGHBOR_IP_RESULT
-+ * bit.
-+ *
-+ * Table LEARN_NEIGHBOR learns the mac-binding using the action
-+ * - 'put_arp/put_nd'. Learning mac-binding is skipped if
-+ * REGBIT_LOOKUP_NEIGHBOR_RESULT bit is set or
-+ * REGBIT_LOOKUP_NEIGHBOR_IP_RESULT is not set.
-+ *
-+ * */
-
-- nat = od->nbr->nat[i];
-+ /* Flows for LOOKUP_NEIGHBOR. */
-+ bool learn_from_arp_request = smap_get_bool(&od->nbr->options,
-+ "always_learn_from_arp_request", true);
-+ ds_clear(actions);
-+ ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT
-+ " = lookup_arp(inport, arp.spa, arp.sha); %snext;",
-+ learn_from_arp_request ? "" :
-+ REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" = 1; ");
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100,
-+ "arp.op == 2", ds_cstr(actions));
-
-- ovs_be32 ip, mask;
-- struct in6_addr ipv6, mask_v6, v6_exact = IN6ADDR_EXACT_INIT;
-- bool is_v6 = false;
-- bool stateless = lrouter_nat_is_stateless(nat);
-- struct nbrec_address_set *allowed_ext_ips =
-- nat->allowed_ext_ips;
-- struct nbrec_address_set *exempted_ext_ips =
-- nat->exempted_ext_ips;
-+ ds_clear(actions);
-+ ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT
-+ " = lookup_nd(inport, nd.target, nd.tll); %snext;",
-+ learn_from_arp_request ? "" :
-+ REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" = 1; ");
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100, "nd_na",
-+ ds_cstr(actions));
-
-- if (allowed_ext_ips && exempted_ext_ips) {
-- static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-- VLOG_WARN_RL(&rl, "NAT rule: "UUID_FMT" not applied, since "
-- "both allowed and exempt external ips set",
-- UUID_ARGS(&(nat->header_.uuid)));
-- continue;
-- }
-+ ds_clear(actions);
-+ ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT
-+ " = lookup_nd(inport, ip6.src, nd.sll); %snext;",
-+ learn_from_arp_request ? "" :
-+ REGBIT_LOOKUP_NEIGHBOR_IP_RESULT
-+ " = lookup_nd_ip(inport, ip6.src); ");
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100, "nd_ns",
-+ ds_cstr(actions));
-
-- char *error = ip_parse_masked(nat->external_ip, &ip, &mask);
-- if (error || mask != OVS_BE32_MAX) {
-- free(error);
-- error = ipv6_parse_masked(nat->external_ip, &ipv6, &mask_v6);
-- if (error || memcmp(&mask_v6, &v6_exact, sizeof(mask_v6))) {
-- /* Invalid for both IPv4 and IPv6 */
-- static struct vlog_rate_limit rl =
-- VLOG_RATE_LIMIT_INIT(5, 1);
-- VLOG_WARN_RL(&rl, "bad external ip %s for nat",
-- nat->external_ip);
-- free(error);
-- continue;
-- }
-- /* It was an invalid IPv4 address, but valid IPv6.
-- * Treat the rest of the handling of this NAT rule
-- * as IPv6. */
-- is_v6 = true;
-- }
-+ /* For other packet types, we can skip neighbor learning.
-+ * So set REGBIT_LOOKUP_NEIGHBOR_RESULT to 1. */
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 0, "1",
-+ REGBIT_LOOKUP_NEIGHBOR_RESULT" = 1; next;");
-
-- /* Check the validity of nat->logical_ip. 'logical_ip' can
-- * be a subnet when the type is "snat". */
-- int cidr_bits;
-- if (is_v6) {
-- error = ipv6_parse_masked(nat->logical_ip, &ipv6, &mask_v6);
-- cidr_bits = ipv6_count_cidr_bits(&mask_v6);
-- } else {
-- error = ip_parse_masked(nat->logical_ip, &ip, &mask);
-- cidr_bits = ip_count_cidr_bits(mask);
-- }
-- if (!strcmp(nat->type, "snat")) {
-- if (error) {
-- /* Invalid for both IPv4 and IPv6 */
-- static struct vlog_rate_limit rl =
-- VLOG_RATE_LIMIT_INIT(5, 1);
-- VLOG_WARN_RL(&rl, "bad ip network or ip %s for snat "
-- "in router "UUID_FMT"",
-- nat->logical_ip, UUID_ARGS(&od->key));
-- free(error);
-- continue;
-- }
-- } else {
-- if (error || (!is_v6 && mask != OVS_BE32_MAX)
-- || (is_v6 && memcmp(&mask_v6, &v6_exact,
-- sizeof mask_v6))) {
-- /* Invalid for both IPv4 and IPv6 */
-- static struct vlog_rate_limit rl =
-- VLOG_RATE_LIMIT_INIT(5, 1);
-- VLOG_WARN_RL(&rl, "bad ip %s for dnat in router "
-- ""UUID_FMT"", nat->logical_ip, UUID_ARGS(&od->key));
-- free(error);
-- continue;
-- }
-- }
-+ /* Flows for LEARN_NEIGHBOR. */
-+ /* Skip Neighbor learning if not required. */
-+ ds_clear(match);
-+ ds_put_format(match, REGBIT_LOOKUP_NEIGHBOR_RESULT" == 1%s",
-+ learn_from_arp_request ? "" :
-+ " || "REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" == 0");
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 100,
-+ ds_cstr(match), "next;");
-
-- /* For distributed router NAT, determine whether this NAT rule
-- * satisfies the conditions for distributed NAT processing. */
-- bool distributed = false;
-- struct eth_addr mac;
-- if (od->l3dgw_port && !strcmp(nat->type, "dnat_and_snat") &&
-- nat->logical_port && nat->external_mac) {
-- if (eth_addr_from_string(nat->external_mac, &mac)) {
-- distributed = true;
-- } else {
-- static struct vlog_rate_limit rl =
-- VLOG_RATE_LIMIT_INIT(5, 1);
-- VLOG_WARN_RL(&rl, "bad mac %s for dnat in router "
-- ""UUID_FMT"", nat->external_mac, UUID_ARGS(&od->key));
-- continue;
-- }
-- }
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90,
-+ "arp", "put_arp(inport, arp.spa, arp.sha); next;");
-
-- /* Ingress UNSNAT table: It is for already established connections'
-- * reverse traffic. i.e., SNAT has already been done in egress
-- * pipeline and now the packet has entered the ingress pipeline as
-- * part of a reply. We undo the SNAT here.
-- *
-- * Undoing SNAT has to happen before DNAT processing. This is
-- * because when the packet was DNATed in ingress pipeline, it did
-- * not know about the possibility of eventual additional SNAT in
-- * egress pipeline. */
-- if (!strcmp(nat->type, "snat")
-- || !strcmp(nat->type, "dnat_and_snat")) {
-- if (!od->l3dgw_port) {
-- /* Gateway router. */
-- ds_clear(&match);
-- ds_clear(&actions);
-- ds_put_format(&match, "ip && ip%s.dst == %s",
-- is_v6 ? "6" : "4",
-- nat->external_ip);
-- if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-- ds_put_format(&actions, "ip%s.dst=%s; next;",
-- is_v6 ? "6" : "4", nat->logical_ip);
-- } else {
-- ds_put_cstr(&actions, "ct_snat;");
-- }
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90,
-+ "nd_na", "put_nd(inport, nd.target, nd.tll); next;");
-
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT,
-- 90, ds_cstr(&match),
-- ds_cstr(&actions),
-- &nat->header_);
-- } else {
-- /* Distributed router. */
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90,
-+ "nd_ns", "put_nd(inport, ip6.src, nd.sll); next;");
-+ }
-
-- /* Traffic received on l3dgw_port is subject to NAT. */
-- ds_clear(&match);
-- ds_clear(&actions);
-- ds_put_format(&match, "ip && ip%s.dst == %s"
-- " && inport == %s",
-- is_v6 ? "6" : "4",
-- nat->external_ip,
-- od->l3dgw_port->json_key);
-- if (!distributed && od->l3redirect_port) {
-- /* Flows for NAT rules that are centralized are only
-- * programmed on the gateway chassis. */
-- ds_put_format(&match, " && is_chassis_resident(%s)",
-- od->l3redirect_port->json_key);
-- }
-+}
-
-- if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-- ds_put_format(&actions, "ip%s.dst=%s; next;",
-- is_v6 ? "6" : "4", nat->logical_ip);
-- } else {
-- ds_put_cstr(&actions, "ct_snat;");
-- }
-+/* Logical router ingress Table 1: Neighbor lookup lflows
-+ * for logical router ports. */
-+static void
-+build_neigh_learning_flows_for_lrouter_port(
-+ struct ovn_port *op, struct hmap *lflows,
-+ struct ds *match, struct ds *actions)
-+{
-+ if (op->nbrp) {
-
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT,
-- 100,
-- ds_cstr(&match), ds_cstr(&actions),
-- &nat->header_);
-+ bool learn_from_arp_request = smap_get_bool(&op->od->nbr->options,
-+ "always_learn_from_arp_request", true);
-+
-+ /* Check if we need to learn mac-binding from ARP requests. */
-+ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
-+ if (!learn_from_arp_request) {
-+ /* ARP request to this address should always get learned,
-+ * so add a priority-110 flow to set
-+ * REGBIT_LOOKUP_NEIGHBOR_IP_RESULT to 1. */
-+ ds_clear(match);
-+ ds_put_format(match,
-+ "inport == %s && arp.spa == %s/%u && "
-+ "arp.tpa == %s && arp.op == 1",
-+ op->json_key,
-+ op->lrp_networks.ipv4_addrs[i].network_s,
-+ op->lrp_networks.ipv4_addrs[i].plen,
-+ op->lrp_networks.ipv4_addrs[i].addr_s);
-+ if (op->od->l3dgw_port && op == op->od->l3dgw_port
-+ && op->od->l3redirect_port) {
-+ ds_put_format(match, " && is_chassis_resident(%s)",
-+ op->od->l3redirect_port->json_key);
- }
-+ const char *actions_s = REGBIT_LOOKUP_NEIGHBOR_RESULT
-+ " = lookup_arp(inport, arp.spa, arp.sha); "
-+ REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" = 1;"
-+ " next;";
-+ ovn_lflow_add_with_hint(lflows, op->od,
-+ S_ROUTER_IN_LOOKUP_NEIGHBOR, 110,
-+ ds_cstr(match), actions_s,
-+ &op->nbrp->header_);
-+ }
-+ ds_clear(match);
-+ ds_put_format(match,
-+ "inport == %s && arp.spa == %s/%u && arp.op == 1",
-+ op->json_key,
-+ op->lrp_networks.ipv4_addrs[i].network_s,
-+ op->lrp_networks.ipv4_addrs[i].plen);
-+ if (op->od->l3dgw_port && op == op->od->l3dgw_port
-+ && op->od->l3redirect_port) {
-+ ds_put_format(match, " && is_chassis_resident(%s)",
-+ op->od->l3redirect_port->json_key);
- }
-+ ds_clear(actions);
-+ ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT
-+ " = lookup_arp(inport, arp.spa, arp.sha); %snext;",
-+ learn_from_arp_request ? "" :
-+ REGBIT_LOOKUP_NEIGHBOR_IP_RESULT
-+ " = lookup_arp_ip(inport, arp.spa); ");
-+ ovn_lflow_add_with_hint(lflows, op->od,
-+ S_ROUTER_IN_LOOKUP_NEIGHBOR, 100,
-+ ds_cstr(match), ds_cstr(actions),
-+ &op->nbrp->header_);
-+ }
-+ }
-+}
-
-- /* Ingress DNAT table: Packets enter the pipeline with destination
-- * IP address that needs to be DNATted from a external IP address
-- * to a logical IP address. */
-- if (!strcmp(nat->type, "dnat")
-- || !strcmp(nat->type, "dnat_and_snat")) {
-- if (!od->l3dgw_port) {
-- /* Gateway router. */
-- /* Packet when it goes from the initiator to destination.
-- * We need to set flags.loopback because the router can
-- * send the packet back through the same interface. */
-- ds_clear(&match);
-- ds_put_format(&match, "ip && ip%s.dst == %s",
-- is_v6 ? "6" : "4",
-- nat->external_ip);
-- ds_clear(&actions);
-- if (allowed_ext_ips || exempted_ext_ips) {
-- lrouter_nat_add_ext_ip_match(od, lflows, &match, nat,
-- is_v6, true, mask);
-- }
-+/* Logical router ingress table ND_RA_OPTIONS & ND_RA_RESPONSE: IPv6 Router
-+ * Adv (RA) options and response. */
-+static void
-+build_ND_RA_flows_for_lrouter_port(
-+ struct ovn_port *op, struct hmap *lflows,
-+ struct ds *match, struct ds *actions)
-+{
-+ if (!op->nbrp || op->nbrp->peer || !op->peer) {
-+ return;
-+ }
-
-- if (dnat_force_snat_ip) {
-- /* Indicate to the future tables that a DNAT has taken
-- * place and a force SNAT needs to be done in the
-- * Egress SNAT table. */
-- ds_put_format(&actions,
-- "flags.force_snat_for_dnat = 1; ");
-- }
-+ if (!op->lrp_networks.n_ipv6_addrs) {
-+ return;
-+ }
-
-- if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-- ds_put_format(&actions, "flags.loopback = 1; "
-- "ip%s.dst=%s; next;",
-- is_v6 ? "6" : "4", nat->logical_ip);
-- } else {
-- ds_put_format(&actions, "flags.loopback = 1; "
-- "ct_dnat(%s", nat->logical_ip);
-+ struct smap options;
-+ smap_clone(&options, &op->sb->options);
-
-- if (nat->external_port_range[0]) {
-- ds_put_format(&actions, ",%s",
-- nat->external_port_range);
-- }
-- ds_put_format(&actions, ");");
-- }
-+ /* enable IPv6 prefix delegation */
-+ bool prefix_delegation = smap_get_bool(&op->nbrp->options,
-+ "prefix_delegation", false);
-+ if (!lrport_is_enabled(op->nbrp)) {
-+ prefix_delegation = false;
-+ }
-+ smap_add(&options, "ipv6_prefix_delegation",
-+ prefix_delegation ? "true" : "false");
-
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100,
-- ds_cstr(&match), ds_cstr(&actions),
-- &nat->header_);
-- } else {
-- /* Distributed router. */
-+ bool ipv6_prefix = smap_get_bool(&op->nbrp->options,
-+ "prefix", false);
-+ if (!lrport_is_enabled(op->nbrp)) {
-+ ipv6_prefix = false;
-+ }
-+ smap_add(&options, "ipv6_prefix",
-+ ipv6_prefix ? "true" : "false");
-+ sbrec_port_binding_set_options(op->sb, &options);
-
-- /* Traffic received on l3dgw_port is subject to NAT. */
-- ds_clear(&match);
-- ds_put_format(&match, "ip && ip%s.dst == %s"
-- " && inport == %s",
-- is_v6 ? "6" : "4",
-- nat->external_ip,
-- od->l3dgw_port->json_key);
-- if (!distributed && od->l3redirect_port) {
-- /* Flows for NAT rules that are centralized are only
-- * programmed on the gateway chassis. */
-- ds_put_format(&match, " && is_chassis_resident(%s)",
-- od->l3redirect_port->json_key);
-- }
-- ds_clear(&actions);
-- if (allowed_ext_ips || exempted_ext_ips) {
-- lrouter_nat_add_ext_ip_match(od, lflows, &match, nat,
-- is_v6, true, mask);
-- }
-+ smap_destroy(&options);
-
-- if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-- ds_put_format(&actions, "ip%s.dst=%s; next;",
-- is_v6 ? "6" : "4", nat->logical_ip);
-- } else {
-- ds_put_format(&actions, "ct_dnat(%s", nat->logical_ip);
-- if (nat->external_port_range[0]) {
-- ds_put_format(&actions, ",%s",
-- nat->external_port_range);
-- }
-- ds_put_format(&actions, ");");
-- }
-+ const char *address_mode = smap_get(
-+ &op->nbrp->ipv6_ra_configs, "address_mode");
-
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100,
-- ds_cstr(&match), ds_cstr(&actions),
-- &nat->header_);
-- }
-- }
-+ if (!address_mode) {
-+ return;
-+ }
-+ if (strcmp(address_mode, "slaac") &&
-+ strcmp(address_mode, "dhcpv6_stateful") &&
-+ strcmp(address_mode, "dhcpv6_stateless")) {
-+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-+ VLOG_WARN_RL(&rl, "Invalid address mode [%s] defined",
-+ address_mode);
-+ return;
-+ }
-
-- /* ARP resolve for NAT IPs. */
-- if (od->l3dgw_port) {
-- if (!strcmp(nat->type, "snat")) {
-- ds_clear(&match);
-- ds_put_format(
-- &match, "inport == %s && %s == %s",
-- od->l3dgw_port->json_key,
-- is_v6 ? "ip6.src" : "ip4.src", nat->external_ip);
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_IP_INPUT,
-- 120, ds_cstr(&match), "next;",
-- &nat->header_);
-- }
-+ if (smap_get_bool(&op->nbrp->ipv6_ra_configs, "send_periodic",
-+ false)) {
-+ copy_ra_to_sb(op, address_mode);
-+ }
-
-- if (!sset_contains(&nat_entries, nat->external_ip)) {
-- ds_clear(&match);
-- ds_put_format(
-- &match, "outport == %s && %s == %s",
-- od->l3dgw_port->json_key,
-- is_v6 ? REG_NEXT_HOP_IPV6 : REG_NEXT_HOP_IPV4,
-- nat->external_ip);
-- ds_clear(&actions);
-- ds_put_format(
-- &actions, "eth.dst = %s; next;",
-- distributed ? nat->external_mac :
-- od->l3dgw_port->lrp_networks.ea_s);
-- ovn_lflow_add_with_hint(lflows, od,
-- S_ROUTER_IN_ARP_RESOLVE,
-- 100, ds_cstr(&match),
-- ds_cstr(&actions),
-- &nat->header_);
-- sset_add(&nat_entries, nat->external_ip);
-- }
-- } else {
-- /* Add the NAT external_ip to the nat_entries even for
-- * gateway routers. This is required for adding load balancer
-- * flows.*/
-- sset_add(&nat_entries, nat->external_ip);
-- }
-+ ds_clear(match);
-+ ds_put_format(match, "inport == %s && ip6.dst == ff02::2 && nd_rs",
-+ op->json_key);
-+ ds_clear(actions);
-
-- /* Egress UNDNAT table: It is for already established connections'
-- * reverse traffic. i.e., DNAT has already been done in ingress
-- * pipeline and now the packet has entered the egress pipeline as
-- * part of a reply. We undo the DNAT here.
-- *
-- * Note that this only applies for NAT on a distributed router.
-- * Undo DNAT on a gateway router is done in the ingress DNAT
-- * pipeline stage. */
-- if (od->l3dgw_port && (!strcmp(nat->type, "dnat")
-- || !strcmp(nat->type, "dnat_and_snat"))) {
-- ds_clear(&match);
-- ds_put_format(&match, "ip && ip%s.src == %s"
-- " && outport == %s",
-- is_v6 ? "6" : "4",
-- nat->logical_ip,
-- od->l3dgw_port->json_key);
-- if (!distributed && od->l3redirect_port) {
-- /* Flows for NAT rules that are centralized are only
-- * programmed on the gateway chassis. */
-- ds_put_format(&match, " && is_chassis_resident(%s)",
-- od->l3redirect_port->json_key);
-- }
-- ds_clear(&actions);
-- if (distributed) {
-- ds_put_format(&actions, "eth.src = "ETH_ADDR_FMT"; ",
-- ETH_ADDR_ARGS(mac));
-- }
-+ const char *mtu_s = smap_get(
-+ &op->nbrp->ipv6_ra_configs, "mtu");
-
-- if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-- ds_put_format(&actions, "ip%s.src=%s; next;",
-- is_v6 ? "6" : "4", nat->external_ip);
-- } else {
-- ds_put_format(&actions, "ct_dnat;");
-- }
-+ /* As per RFC 2460, 1280 is minimum IPv6 MTU. */
-+ uint32_t mtu = (mtu_s && atoi(mtu_s) >= 1280) ? atoi(mtu_s) : 0;
-
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 100,
-- ds_cstr(&match), ds_cstr(&actions),
-- &nat->header_);
-- }
-+ ds_put_format(actions, REGBIT_ND_RA_OPTS_RESULT" = put_nd_ra_opts("
-+ "addr_mode = \"%s\", slla = %s",
-+ address_mode, op->lrp_networks.ea_s);
-+ if (mtu > 0) {
-+ ds_put_format(actions, ", mtu = %u", mtu);
-+ }
-
-- /* Egress SNAT table: Packets enter the egress pipeline with
-- * source ip address that needs to be SNATted to a external ip
-- * address. */
-- if (!strcmp(nat->type, "snat")
-- || !strcmp(nat->type, "dnat_and_snat")) {
-- if (!od->l3dgw_port) {
-- /* Gateway router. */
-- ds_clear(&match);
-- ds_put_format(&match, "ip && ip%s.src == %s",
-- is_v6 ? "6" : "4",
-- nat->logical_ip);
-- ds_clear(&actions);
-+ const char *prf = smap_get_def(
-+ &op->nbrp->ipv6_ra_configs, "router_preference", "MEDIUM");
-+ if (strcmp(prf, "MEDIUM")) {
-+ ds_put_format(actions, ", router_preference = \"%s\"", prf);
-+ }
-
-- if (allowed_ext_ips || exempted_ext_ips) {
-- lrouter_nat_add_ext_ip_match(od, lflows, &match, nat,
-- is_v6, false, mask);
-- }
-+ bool add_rs_response_flow = false;
-
-- if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-- ds_put_format(&actions, "ip%s.src=%s; next;",
-- is_v6 ? "6" : "4", nat->external_ip);
-- } else {
-- ds_put_format(&actions, "ct_snat(%s",
-- nat->external_ip);
-+ for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
-+ if (in6_is_lla(&op->lrp_networks.ipv6_addrs[i].network)) {
-+ continue;
-+ }
-
-- if (nat->external_port_range[0]) {
-- ds_put_format(&actions, ",%s",
-- nat->external_port_range);
-- }
-- ds_put_format(&actions, ");");
-- }
-+ ds_put_format(actions, ", prefix = %s/%u",
-+ op->lrp_networks.ipv6_addrs[i].network_s,
-+ op->lrp_networks.ipv6_addrs[i].plen);
-
-- /* The priority here is calculated such that the
-- * nat->logical_ip with the longest mask gets a higher
-- * priority. */
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT,
-- cidr_bits + 1,
-- ds_cstr(&match), ds_cstr(&actions),
-- &nat->header_);
-- } else {
-- uint16_t priority = cidr_bits + 1;
-+ add_rs_response_flow = true;
-+ }
-
-- /* Distributed router. */
-- ds_clear(&match);
-- ds_put_format(&match, "ip && ip%s.src == %s"
-- " && outport == %s",
-- is_v6 ? "6" : "4",
-- nat->logical_ip,
-- od->l3dgw_port->json_key);
-- if (!distributed && od->l3redirect_port) {
-- /* Flows for NAT rules that are centralized are only
-- * programmed on the gateway chassis. */
-- priority += 128;
-- ds_put_format(&match, " && is_chassis_resident(%s)",
-- od->l3redirect_port->json_key);
-- }
-- ds_clear(&actions);
-+ if (add_rs_response_flow) {
-+ ds_put_cstr(actions, "); next;");
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_ND_RA_OPTIONS,
-+ 50, ds_cstr(match), ds_cstr(actions),
-+ &op->nbrp->header_);
-+ ds_clear(actions);
-+ ds_clear(match);
-+ ds_put_format(match, "inport == %s && ip6.dst == ff02::2 && "
-+ "nd_ra && "REGBIT_ND_RA_OPTS_RESULT, op->json_key);
-
-- if (allowed_ext_ips || exempted_ext_ips) {
-- lrouter_nat_add_ext_ip_match(od, lflows, &match, nat,
-- is_v6, false, mask);
-- }
-+ char ip6_str[INET6_ADDRSTRLEN + 1];
-+ struct in6_addr lla;
-+ in6_generate_lla(op->lrp_networks.ea, &lla);
-+ memset(ip6_str, 0, sizeof(ip6_str));
-+ ipv6_string_mapped(ip6_str, &lla);
-+ ds_put_format(actions, "eth.dst = eth.src; eth.src = %s; "
-+ "ip6.dst = ip6.src; ip6.src = %s; "
-+ "outport = inport; flags.loopback = 1; "
-+ "output;",
-+ op->lrp_networks.ea_s, ip6_str);
-+ ovn_lflow_add_with_hint(lflows, op->od,
-+ S_ROUTER_IN_ND_RA_RESPONSE, 50,
-+ ds_cstr(match), ds_cstr(actions),
-+ &op->nbrp->header_);
-+ }
-+}
-
-- if (distributed) {
-- ds_put_format(&actions, "eth.src = "ETH_ADDR_FMT"; ",
-- ETH_ADDR_ARGS(mac));
-- }
-+/* Logical router ingress table ND_RA_OPTIONS & ND_RA_RESPONSE: RS
-+ * responder, by default goto next. (priority 0). */
-+static void
-+build_ND_RA_flows_for_lrouter(struct ovn_datapath *od, struct hmap *lflows)
-+{
-+ if (od->nbr) {
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ND_RA_OPTIONS, 0, "1", "next;");
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ND_RA_RESPONSE, 0, "1", "next;");
-+ }
-+}
-
-- if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-- ds_put_format(&actions, "ip%s.src=%s; next;",
-- is_v6 ? "6" : "4", nat->external_ip);
-- } else {
-- ds_put_format(&actions, "ct_snat(%s",
-- nat->external_ip);
-- if (nat->external_port_range[0]) {
-- ds_put_format(&actions, ",%s",
-- nat->external_port_range);
-- }
-- ds_put_format(&actions, ");");
-- }
-+/* Logical router ingress table IP_ROUTING : IP Routing.
-+ *
-+ * A packet that arrives at this table is an IP packet that should be
-+ * routed to the address in 'ip[46].dst'.
-+ *
-+ * For regular routes without ECMP, table IP_ROUTING sets outport to the
-+ * correct output port, eth.src to the output port's MAC address, and
-+ * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 to the next-hop IP address
-+ * (leaving 'ip[46].dst', the packet’s final destination, unchanged), and
-+ * advances to the next table.
-+ *
-+ * For ECMP routes, i.e. multiple routes with same policy and prefix, table
-+ * IP_ROUTING remembers ECMP group id and selects a member id, and advances
-+ * to table IP_ROUTING_ECMP, which sets outport, eth.src and
-+ * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 for the selected ECMP member.
-+ */
-+static void
-+build_ip_routing_flows_for_lrouter_port(
-+ struct ovn_port *op, struct hmap *lflows)
-+{
-+ if (op->nbrp) {
-
-- /* The priority here is calculated such that the
-- * nat->logical_ip with the longest mask gets a higher
-- * priority. */
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT,
-- priority, ds_cstr(&match),
-- ds_cstr(&actions),
-- &nat->header_);
-- }
-- }
-+ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
-+ add_route(lflows, op, op->lrp_networks.ipv4_addrs[i].addr_s,
-+ op->lrp_networks.ipv4_addrs[i].network_s,
-+ op->lrp_networks.ipv4_addrs[i].plen, NULL, false,
-+ &op->nbrp->header_);
-+ }
-
-- /* Logical router ingress table 0:
-- * For NAT on a distributed router, add rules allowing
-- * ingress traffic with eth.dst matching nat->external_mac
-- * on the l3dgw_port instance where nat->logical_port is
-- * resident. */
-- if (distributed) {
-- /* Store the ethernet address of the port receiving the packet.
-- * This will save us from having to match on inport further
-- * down in the pipeline.
-- */
-- ds_clear(&actions);
-- ds_put_format(&actions, REG_INPORT_ETH_ADDR " = %s; next;",
-- od->l3dgw_port->lrp_networks.ea_s);
-+ for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
-+ add_route(lflows, op, op->lrp_networks.ipv6_addrs[i].addr_s,
-+ op->lrp_networks.ipv6_addrs[i].network_s,
-+ op->lrp_networks.ipv6_addrs[i].plen, NULL, false,
-+ &op->nbrp->header_);
-+ }
-+ }
-+}
-
-- ds_clear(&match);
-- ds_put_format(&match,
-- "eth.dst == "ETH_ADDR_FMT" && inport == %s"
-- " && is_chassis_resident(\"%s\")",
-- ETH_ADDR_ARGS(mac),
-- od->l3dgw_port->json_key,
-- nat->logical_port);
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ADMISSION, 50,
-- ds_cstr(&match), ds_cstr(&actions),
-- &nat->header_);
-- }
-+static void
-+build_static_route_flows_for_lrouter(
-+ struct ovn_datapath *od, struct hmap *lflows,
-+ struct hmap *ports)
-+{
-+ if (od->nbr) {
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING_ECMP, 150,
-+ REG_ECMP_GROUP_ID" == 0", "next;");
-
-- /* Ingress Gateway Redirect Table: For NAT on a distributed
-- * router, add flows that are specific to a NAT rule. These
-- * flows indicate the presence of an applicable NAT rule that
-- * can be applied in a distributed manner.
-- * In particulr REG_SRC_IPV4/REG_SRC_IPV6 and eth.src are set to
-- * NAT external IP and NAT external mac so the ARP request
-- * generated in the following stage is sent out with proper IP/MAC
-- * src addresses.
-- */
-- if (distributed) {
-- ds_clear(&match);
-- ds_clear(&actions);
-- ds_put_format(&match,
-- "ip%s.src == %s && outport == %s && "
-- "is_chassis_resident(\"%s\")",
-- is_v6 ? "6" : "4", nat->logical_ip,
-- od->l3dgw_port->json_key, nat->logical_port);
-- ds_put_format(&actions, "eth.src = %s; %s = %s; next;",
-- nat->external_mac,
-- is_v6 ? REG_SRC_IPV6 : REG_SRC_IPV4,
-- nat->external_ip);
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_GW_REDIRECT,
-- 100, ds_cstr(&match),
-- ds_cstr(&actions), &nat->header_);
-+ struct hmap ecmp_groups = HMAP_INITIALIZER(&ecmp_groups);
-+ struct hmap unique_routes = HMAP_INITIALIZER(&unique_routes);
-+ struct ovs_list parsed_routes = OVS_LIST_INITIALIZER(&parsed_routes);
-+ struct ecmp_groups_node *group;
-+ for (int i = 0; i < od->nbr->n_static_routes; i++) {
-+ struct parsed_route *route =
-+ parsed_routes_add(&parsed_routes, od->nbr->static_routes[i]);
-+ if (!route) {
-+ continue;
- }
--
-- /* Egress Loopback table: For NAT on a distributed router.
-- * If packets in the egress pipeline on the distributed
-- * gateway port have ip.dst matching a NAT external IP, then
-- * loop a clone of the packet back to the beginning of the
-- * ingress pipeline with inport = outport. */
-- if (od->l3dgw_port) {
-- /* Distributed router. */
-- ds_clear(&match);
-- ds_put_format(&match, "ip%s.dst == %s && outport == %s",
-- is_v6 ? "6" : "4",
-- nat->external_ip,
-- od->l3dgw_port->json_key);
-- if (!distributed) {
-- ds_put_format(&match, " && is_chassis_resident(%s)",
-- od->l3redirect_port->json_key);
-- } else {
-- ds_put_format(&match, " && is_chassis_resident(\"%s\")",
-- nat->logical_port);
-- }
--
-- ds_clear(&actions);
-- ds_put_format(&actions,
-- "clone { ct_clear; "
-- "inport = outport; outport = \"\"; "
-- "flags = 0; flags.loopback = 1; ");
-- for (int j = 0; j < MFF_N_LOG_REGS; j++) {
-- ds_put_format(&actions, "reg%d = 0; ", j);
-+ group = ecmp_groups_find(&ecmp_groups, route);
-+ if (group) {
-+ ecmp_groups_add_route(group, route);
-+ } else {
-+ const struct parsed_route *existed_route =
-+ unique_routes_remove(&unique_routes, route);
-+ if (existed_route) {
-+ group = ecmp_groups_add(&ecmp_groups, existed_route);
-+ if (group) {
-+ ecmp_groups_add_route(group, route);
-+ }
-+ } else {
-+ unique_routes_add(&unique_routes, route);
- }
-- ds_put_format(&actions, REGBIT_EGRESS_LOOPBACK" = 1; "
-- "next(pipeline=ingress, table=%d); };",
-- ovn_stage_get_table(S_ROUTER_IN_ADMISSION));
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_EGR_LOOP, 100,
-- ds_cstr(&match), ds_cstr(&actions),
-- &nat->header_);
- }
- }
--
-- /* Handle force SNAT options set in the gateway router. */
-- if (!od->l3dgw_port) {
-- if (dnat_force_snat_ip) {
-- if (od->dnat_force_snat_addrs.n_ipv4_addrs) {
-- build_lrouter_force_snat_flows(lflows, od, "4",
-- od->dnat_force_snat_addrs.ipv4_addrs[0].addr_s,
-- "dnat");
-- }
-- if (od->dnat_force_snat_addrs.n_ipv6_addrs) {
-- build_lrouter_force_snat_flows(lflows, od, "6",
-- od->dnat_force_snat_addrs.ipv6_addrs[0].addr_s,
-- "dnat");
-- }
-- }
-- if (lb_force_snat_ip) {
-- if (od->lb_force_snat_addrs.n_ipv4_addrs) {
-- build_lrouter_force_snat_flows(lflows, od, "4",
-- od->lb_force_snat_addrs.ipv4_addrs[0].addr_s, "lb");
-- }
-- if (od->lb_force_snat_addrs.n_ipv6_addrs) {
-- build_lrouter_force_snat_flows(lflows, od, "6",
-- od->lb_force_snat_addrs.ipv6_addrs[0].addr_s, "lb");
-- }
-- }
--
-- /* For gateway router, re-circulate every packet through
-- * the DNAT zone. This helps with the following.
-- *
-- * Any packet that needs to be unDNATed in the reverse
-- * direction gets unDNATed. Ideally this could be done in
-- * the egress pipeline. But since the gateway router
-- * does not have any feature that depends on the source
-- * ip address being external IP address for IP routing,
-- * we can do it here, saving a future re-circulation. */
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 50,
-- "ip", "flags.loopback = 1; ct_dnat;");
-+ HMAP_FOR_EACH (group, hmap_node, &ecmp_groups) {
-+ /* add a flow in IP_ROUTING, and one flow for each member in
-+ * IP_ROUTING_ECMP. */
-+ build_ecmp_route_flow(lflows, od, ports, group);
- }
--
-- /* Load balancing and packet defrag are only valid on
-- * Gateway routers or router with gateway port. */
-- if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) {
-- sset_destroy(&nat_entries);
-- continue;
-+ const struct unique_routes_node *ur;
-+ HMAP_FOR_EACH (ur, hmap_node, &unique_routes) {
-+ build_static_route_flow(lflows, od, ports, ur->route);
- }
-+ ecmp_groups_destroy(&ecmp_groups);
-+ unique_routes_destroy(&unique_routes);
-+ parsed_routes_destroy(&parsed_routes);
-+ }
-+}
-
-- /* A set to hold all ips that need defragmentation and tracking. */
-- struct sset all_ips = SSET_INITIALIZER(&all_ips);
-+/* IP Multicast lookup. Here we set the output port, adjust TTL and
-+ * advance to next table (priority 500).
-+ */
-+static void
-+build_mcast_lookup_flows_for_lrouter(
-+ struct ovn_datapath *od, struct hmap *lflows,
-+ struct ds *match, struct ds *actions)
-+{
-+ if (od->nbr) {
-
-- for (int i = 0; i < od->nbr->n_load_balancer; i++) {
-- struct nbrec_load_balancer *nb_lb = od->nbr->load_balancer[i];
-- struct ovn_northd_lb *lb =
-- ovn_northd_lb_find(lbs, &nb_lb->header_.uuid);
-- ovs_assert(lb);
-+ /* Drop IPv6 multicast traffic that shouldn't be forwarded,
-+ * i.e., router solicitation and router advertisement.
-+ */
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 550,
-+ "nd_rs || nd_ra", "drop;");
-+ if (!od->mcast_info.rtr.relay) {
-+ return;
-+ }
-
-- for (size_t j = 0; j < lb->n_vips; j++) {
-- struct ovn_lb_vip *lb_vip = &lb->vips[j];
-- struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[j];
-- ds_clear(&actions);
-- build_lb_vip_actions(lb_vip, lb_vip_nb, &actions,
-- lb->selection_fields, false);
-+ struct ovn_igmp_group *igmp_group;
-
-- if (!sset_contains(&all_ips, lb_vip->vip_str)) {
-- sset_add(&all_ips, lb_vip->vip_str);
-- /* If there are any load balancing rules, we should send
-- * the packet to conntrack for defragmentation and
-- * tracking. This helps with two things.
-- *
-- * 1. With tracking, we can send only new connections to
-- * pick a DNAT ip address from a group.
-- * 2. If there are L4 ports in load balancing rules, we
-- * need the defragmentation to match on L4 ports. */
-- ds_clear(&match);
-- if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
-- ds_put_format(&match, "ip && ip4.dst == %s",
-- lb_vip->vip_str);
-- } else {
-- ds_put_format(&match, "ip && ip6.dst == %s",
-- lb_vip->vip_str);
-- }
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DEFRAG,
-- 100, ds_cstr(&match), "ct_next;",
-- &nb_lb->header_);
-- }
-+ LIST_FOR_EACH (igmp_group, list_node, &od->mcast_info.groups) {
-+ ds_clear(match);
-+ ds_clear(actions);
-+ if (IN6_IS_ADDR_V4MAPPED(&igmp_group->address)) {
-+ ds_put_format(match, "ip4 && ip4.dst == %s ",
-+ igmp_group->mcgroup.name);
-+ } else {
-+ ds_put_format(match, "ip6 && ip6.dst == %s ",
-+ igmp_group->mcgroup.name);
-+ }
-+ if (od->mcast_info.rtr.flood_static) {
-+ ds_put_cstr(actions,
-+ "clone { "
-+ "outport = \""MC_STATIC"\"; "
-+ "ip.ttl--; "
-+ "next; "
-+ "};");
-+ }
-+ ds_put_format(actions, "outport = \"%s\"; ip.ttl--; next;",
-+ igmp_group->mcgroup.name);
-+ ovn_lflow_add_unique(lflows, od, S_ROUTER_IN_IP_ROUTING, 500,
-+ ds_cstr(match), ds_cstr(actions));
-+ }
-
-- /* Higher priority rules are added for load-balancing in DNAT
-- * table. For every match (on a VIP[:port]), we add two flows
-- * via add_router_lb_flow(). One flow is for specific matching
-- * on ct.new with an action of "ct_lb($targets);". The other
-- * flow is for ct.est with an action of "ct_dnat;". */
-- ds_clear(&match);
-- if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
-- ds_put_format(&match, "ip && ip4.dst == %s",
-- lb_vip->vip_str);
-- } else {
-- ds_put_format(&match, "ip && ip6.dst == %s",
-- lb_vip->vip_str);
-- }
-+ /* If needed, flood unregistered multicast on statically configured
-+ * ports. Otherwise drop any multicast traffic.
-+ */
-+ if (od->mcast_info.rtr.flood_static) {
-+ ovn_lflow_add_unique(lflows, od, S_ROUTER_IN_IP_ROUTING, 450,
-+ "ip4.mcast || ip6.mcast",
-+ "clone { "
-+ "outport = \""MC_STATIC"\"; "
-+ "ip.ttl--; "
-+ "next; "
-+ "};");
-+ } else {
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 450,
-+ "ip4.mcast || ip6.mcast", "drop;");
-+ }
-+ }
-+}
-
-- int prio = 110;
-- bool is_udp = nullable_string_is_equal(nb_lb->protocol, "udp");
-- bool is_sctp = nullable_string_is_equal(nb_lb->protocol,
-- "sctp");
-- const char *proto = is_udp ? "udp" : is_sctp ? "sctp" : "tcp";
-+/* Logical router ingress table POLICY: Policy.
-+ *
-+ * A packet that arrives at this table is an IP packet that should be
-+ * permitted/denied/rerouted to the address in the rule's nexthop.
-+ * This table sets outport to the correct out_port,
-+ * eth.src to the output port's MAC address,
-+ * and REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 to the next-hop IP address
-+ * (leaving 'ip[46].dst', the packet’s final destination, unchanged), and
-+ * advances to the next table for ARP/ND resolution. */
-+static void
-+build_ingress_policy_flows_for_lrouter(
-+ struct ovn_datapath *od, struct hmap *lflows,
-+ struct hmap *ports)
-+{
-+ if (od->nbr) {
-+ /* This is a catch-all rule. It has the lowest priority (0)
-+ * does a match-all("1") and pass-through (next) */
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_POLICY, 0, "1",
-+ REG_ECMP_GROUP_ID" = 0; next;");
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_POLICY_ECMP, 150,
-+ REG_ECMP_GROUP_ID" == 0", "next;");
-
-- if (lb_vip->vip_port) {
-- ds_put_format(&match, " && %s && %s.dst == %d", proto,
-- proto, lb_vip->vip_port);
-- prio = 120;
-- }
-+ /* Convert routing policies to flows. */
-+ uint16_t ecmp_group_id = 1;
-+ for (int i = 0; i < od->nbr->n_policies; i++) {
-+ const struct nbrec_logical_router_policy *rule
-+ = od->nbr->policies[i];
-+ bool is_ecmp_reroute =
-+ (!strcmp(rule->action, "reroute") && rule->n_nexthops > 1);
-
-- if (od->l3redirect_port &&
-- (lb_vip->n_backends || !lb_vip->empty_backend_rej)) {
-- ds_put_format(&match, " && is_chassis_resident(%s)",
-- od->l3redirect_port->json_key);
-- }
-- add_router_lb_flow(lflows, od, &match, &actions, prio,
-- lb_force_snat_ip, lb_vip, proto,
-- nb_lb, meter_groups, &nat_entries);
-+ if (is_ecmp_reroute) {
-+ build_ecmp_routing_policy_flows(lflows, od, ports, rule,
-+ ecmp_group_id);
-+ ecmp_group_id++;
-+ } else {
-+ build_routing_policy_flow(lflows, od, ports, rule,
-+ &rule->header_);
- }
- }
-- sset_destroy(&all_ips);
-- sset_destroy(&nat_entries);
- }
--
-- ds_destroy(&match);
-- ds_destroy(&actions);
- }
-
--/* Logical router ingress Table 0: L2 Admission Control
-- * Generic admission control flows (without inport check).
-- */
-+/* Local router ingress table ARP_RESOLVE: ARP Resolution. */
- static void
--build_adm_ctrl_flows_for_lrouter(
-+build_arp_resolve_flows_for_lrouter(
- struct ovn_datapath *od, struct hmap *lflows)
- {
- if (od->nbr) {
-- /* Logical VLANs not supported.
-- * Broadcast/multicast source address is invalid. */
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_ADMISSION, 100,
-- "vlan.present || eth.src[40]", "drop;");
-+ /* Multicast packets already have the outport set so just advance to
-+ * next table (priority 500). */
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 500,
-+ "ip4.mcast || ip6.mcast", "next;");
-+
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 0, "ip4",
-+ "get_arp(outport, " REG_NEXT_HOP_IPV4 "); next;");
-+
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 0, "ip6",
-+ "get_nd(outport, " REG_NEXT_HOP_IPV6 "); next;");
- }
- }
-
--/* Logical router ingress Table 0: L2 Admission Control
-- * This table drops packets that the router shouldn’t see at all based
-- * on their Ethernet headers.
-- */
--static void
--build_adm_ctrl_flows_for_lrouter_port(
-+/* Local router ingress table ARP_RESOLVE: ARP Resolution.
-+ *
-+ * Any unicast packet that reaches this table is an IP packet whose
-+ * next-hop IP address is in REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6
-+ * (ip4.dst/ipv6.dst is the final destination).
-+ * This table resolves the IP address in
-+ * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 into an output port in outport and
-+ * an Ethernet address in eth.dst.
-+ */
-+static void
-+build_arp_resolve_flows_for_lrouter_port(
- struct ovn_port *op, struct hmap *lflows,
-+ struct hmap *ports,
- struct ds *match, struct ds *actions)
- {
-- if (op->nbrp) {
-- if (!lrport_is_enabled(op->nbrp)) {
-- /* Drop packets from disabled logical ports (since logical flow
-- * tables are default-drop). */
-- return;
-- }
-+ if (op->nbsp && !lsp_is_enabled(op->nbsp)) {
-+ return;
-+ }
-
-- if (op->derived) {
-- /* No ingress packets should be received on a chassisredirect
-- * port. */
-- return;
-- }
-+ if (op->nbrp) {
-+ /* This is a logical router port. If next-hop IP address in
-+ * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 matches IP address of this
-+ * router port, then the packet is intended to eventually be sent
-+ * to this logical port. Set the destination mac address using
-+ * this port's mac address.
-+ *
-+ * The packet is still in peer's logical pipeline. So the match
-+ * should be on peer's outport. */
-+ if (op->peer && op->nbrp->peer) {
-+ if (op->lrp_networks.n_ipv4_addrs) {
-+ ds_clear(match);
-+ ds_put_format(match, "outport == %s && "
-+ REG_NEXT_HOP_IPV4 "== ",
-+ op->peer->json_key);
-+ op_put_v4_networks(match, op, false);
-
-- /* Store the ethernet address of the port receiving the packet.
-- * This will save us from having to match on inport further down in
-- * the pipeline.
-- */
-- ds_clear(actions);
-- ds_put_format(actions, REG_INPORT_ETH_ADDR " = %s; next;",
-- op->lrp_networks.ea_s);
-+ ds_clear(actions);
-+ ds_put_format(actions, "eth.dst = %s; next;",
-+ op->lrp_networks.ea_s);
-+ ovn_lflow_add_with_hint(lflows, op->peer->od,
-+ S_ROUTER_IN_ARP_RESOLVE, 100,
-+ ds_cstr(match), ds_cstr(actions),
-+ &op->nbrp->header_);
-+ }
-
-- ds_clear(match);
-- ds_put_format(match, "eth.mcast && inport == %s", op->json_key);
-- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_ADMISSION, 50,
-- ds_cstr(match), ds_cstr(actions),
-- &op->nbrp->header_);
-+ if (op->lrp_networks.n_ipv6_addrs) {
-+ ds_clear(match);
-+ ds_put_format(match, "outport == %s && "
-+ REG_NEXT_HOP_IPV6 " == ",
-+ op->peer->json_key);
-+ op_put_v6_networks(match, op);
-
-- ds_clear(match);
-- ds_put_format(match, "eth.dst == %s && inport == %s",
-- op->lrp_networks.ea_s, op->json_key);
-- if (op->od->l3dgw_port && op == op->od->l3dgw_port
-- && op->od->l3redirect_port) {
-- /* Traffic with eth.dst = l3dgw_port->lrp_networks.ea_s
-- * should only be received on the gateway chassis. */
-- ds_put_format(match, " && is_chassis_resident(%s)",
-- op->od->l3redirect_port->json_key);
-+ ds_clear(actions);
-+ ds_put_format(actions, "eth.dst = %s; next;",
-+ op->lrp_networks.ea_s);
-+ ovn_lflow_add_with_hint(lflows, op->peer->od,
-+ S_ROUTER_IN_ARP_RESOLVE, 100,
-+ ds_cstr(match), ds_cstr(actions),
-+ &op->nbrp->header_);
-+ }
- }
-- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_ADMISSION, 50,
-- ds_cstr(match), ds_cstr(actions),
-- &op->nbrp->header_);
-- }
--}
-
-+ if (!op->derived && op->od->l3redirect_port) {
-+ const char *redirect_type = smap_get(&op->nbrp->options,
-+ "redirect-type");
-+ if (redirect_type && !strcasecmp(redirect_type, "bridged")) {
-+ /* Packet is on a non gateway chassis and
-+ * has an unresolved ARP on a network behind gateway
-+ * chassis attached router port. Since, redirect type
-+ * is "bridged", instead of calling "get_arp"
-+ * on this node, we will redirect the packet to gateway
-+ * chassis, by setting destination mac router port mac.*/
-+ ds_clear(match);
-+ ds_put_format(match, "outport == %s && "
-+ "!is_chassis_resident(%s)", op->json_key,
-+ op->od->l3redirect_port->json_key);
-+ ds_clear(actions);
-+ ds_put_format(actions, "eth.dst = %s; next;",
-+ op->lrp_networks.ea_s);
-
--/* Logical router ingress Table 1 and 2: Neighbor lookup and learning
-- * lflows for logical routers. */
--static void
--build_neigh_learning_flows_for_lrouter(
-- struct ovn_datapath *od, struct hmap *lflows,
-- struct ds *match, struct ds *actions)
--{
-- if (od->nbr) {
-+ ovn_lflow_add_with_hint(lflows, op->od,
-+ S_ROUTER_IN_ARP_RESOLVE, 50,
-+ ds_cstr(match), ds_cstr(actions),
-+ &op->nbrp->header_);
-+ }
-+ }
-
-- /* Learn MAC bindings from ARP/IPv6 ND.
-- *
-- * For ARP packets, table LOOKUP_NEIGHBOR does a lookup for the
-- * (arp.spa, arp.sha) in the mac binding table using the 'lookup_arp'
-- * action and stores the result in REGBIT_LOOKUP_NEIGHBOR_RESULT bit.
-- * If "always_learn_from_arp_request" is set to false, it will also
-- * lookup for the (arp.spa) in the mac binding table using the
-- * "lookup_arp_ip" action for ARP request packets, and stores the
-- * result in REGBIT_LOOKUP_NEIGHBOR_IP_RESULT bit; or set that bit
-- * to "1" directly for ARP response packets.
-- *
-- * For IPv6 ND NA packets, table LOOKUP_NEIGHBOR does a lookup
-- * for the (nd.target, nd.tll) in the mac binding table using the
-- * 'lookup_nd' action and stores the result in
-- * REGBIT_LOOKUP_NEIGHBOR_RESULT bit. If
-- * "always_learn_from_arp_request" is set to false,
-- * REGBIT_LOOKUP_NEIGHBOR_IP_RESULT bit is set.
-- *
-- * For IPv6 ND NS packets, table LOOKUP_NEIGHBOR does a lookup
-- * for the (ip6.src, nd.sll) in the mac binding table using the
-- * 'lookup_nd' action and stores the result in
-- * REGBIT_LOOKUP_NEIGHBOR_RESULT bit. If
-- * "always_learn_from_arp_request" is set to false, it will also lookup
-- * for the (ip6.src) in the mac binding table using the "lookup_nd_ip"
-- * action and stores the result in REGBIT_LOOKUP_NEIGHBOR_IP_RESULT
-- * bit.
-- *
-- * Table LEARN_NEIGHBOR learns the mac-binding using the action
-- * - 'put_arp/put_nd'. Learning mac-binding is skipped if
-- * REGBIT_LOOKUP_NEIGHBOR_RESULT bit is set or
-- * REGBIT_LOOKUP_NEIGHBOR_IP_RESULT is not set.
-+ /* Drop IP traffic destined to router owned IPs. Part of it is dropped
-+ * in stage "lr_in_ip_input" but traffic that could have been unSNATed
-+ * but didn't match any existing session might still end up here.
- *
-- * */
--
-- /* Flows for LOOKUP_NEIGHBOR. */
-- bool learn_from_arp_request = smap_get_bool(&od->nbr->options,
-- "always_learn_from_arp_request", true);
-- ds_clear(actions);
-- ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT
-- " = lookup_arp(inport, arp.spa, arp.sha); %snext;",
-- learn_from_arp_request ? "" :
-- REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" = 1; ");
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100,
-- "arp.op == 2", ds_cstr(actions));
-+ * Priority 1.
-+ */
-+ build_lrouter_drop_own_dest(op, S_ROUTER_IN_ARP_RESOLVE, 1, true,
-+ lflows);
-+ } else if (op->od->n_router_ports && !lsp_is_router(op->nbsp)
-+ && strcmp(op->nbsp->type, "virtual")) {
-+ /* This is a logical switch port that backs a VM or a container.
-+ * Extract its addresses. For each of the address, go through all
-+ * the router ports attached to the switch (to which this port
-+ * connects) and if the address in question is reachable from the
-+ * router port, add an ARP/ND entry in that router's pipeline. */
-
-- ds_clear(actions);
-- ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT
-- " = lookup_nd(inport, nd.target, nd.tll); %snext;",
-- learn_from_arp_request ? "" :
-- REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" = 1; ");
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100, "nd_na",
-- ds_cstr(actions));
-+ for (size_t i = 0; i < op->n_lsp_addrs; i++) {
-+ const char *ea_s = op->lsp_addrs[i].ea_s;
-+ for (size_t j = 0; j < op->lsp_addrs[i].n_ipv4_addrs; j++) {
-+ const char *ip_s = op->lsp_addrs[i].ipv4_addrs[j].addr_s;
-+ for (size_t k = 0; k < op->od->n_router_ports; k++) {
-+ /* Get the Logical_Router_Port that the
-+ * Logical_Switch_Port is connected to, as
-+ * 'peer'. */
-+ const char *peer_name = smap_get(
-+ &op->od->router_ports[k]->nbsp->options,
-+ "router-port");
-+ if (!peer_name) {
-+ continue;
-+ }
-
-- ds_clear(actions);
-- ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT
-- " = lookup_nd(inport, ip6.src, nd.sll); %snext;",
-- learn_from_arp_request ? "" :
-- REGBIT_LOOKUP_NEIGHBOR_IP_RESULT
-- " = lookup_nd_ip(inport, ip6.src); ");
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100, "nd_ns",
-- ds_cstr(actions));
-+ struct ovn_port *peer = ovn_port_find(ports, peer_name);
-+ if (!peer || !peer->nbrp) {
-+ continue;
-+ }
-
-- /* For other packet types, we can skip neighbor learning.
-- * So set REGBIT_LOOKUP_NEIGHBOR_RESULT to 1. */
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 0, "1",
-- REGBIT_LOOKUP_NEIGHBOR_RESULT" = 1; next;");
-+ if (!find_lrp_member_ip(peer, ip_s)) {
-+ continue;
-+ }
-
-- /* Flows for LEARN_NEIGHBOR. */
-- /* Skip Neighbor learning if not required. */
-- ds_clear(match);
-- ds_put_format(match, REGBIT_LOOKUP_NEIGHBOR_RESULT" == 1%s",
-- learn_from_arp_request ? "" :
-- " || "REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" == 0");
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 100,
-- ds_cstr(match), "next;");
-+ ds_clear(match);
-+ ds_put_format(match, "outport == %s && "
-+ REG_NEXT_HOP_IPV4 " == %s",
-+ peer->json_key, ip_s);
-
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90,
-- "arp", "put_arp(inport, arp.spa, arp.sha); next;");
-+ ds_clear(actions);
-+ ds_put_format(actions, "eth.dst = %s; next;", ea_s);
-+ ovn_lflow_add_with_hint(lflows, peer->od,
-+ S_ROUTER_IN_ARP_RESOLVE, 100,
-+ ds_cstr(match),
-+ ds_cstr(actions),
-+ &op->nbsp->header_);
-+ }
-+ }
-
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90,
-- "nd_na", "put_nd(inport, nd.target, nd.tll); next;");
-+ for (size_t j = 0; j < op->lsp_addrs[i].n_ipv6_addrs; j++) {
-+ const char *ip_s = op->lsp_addrs[i].ipv6_addrs[j].addr_s;
-+ for (size_t k = 0; k < op->od->n_router_ports; k++) {
-+ /* Get the Logical_Router_Port that the
-+ * Logical_Switch_Port is connected to, as
-+ * 'peer'. */
-+ const char *peer_name = smap_get(
-+ &op->od->router_ports[k]->nbsp->options,
-+ "router-port");
-+ if (!peer_name) {
-+ continue;
-+ }
-
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90,
-- "nd_ns", "put_nd(inport, ip6.src, nd.sll); next;");
-- }
--
--}
-+ struct ovn_port *peer = ovn_port_find(ports, peer_name);
-+ if (!peer || !peer->nbrp) {
-+ continue;
-+ }
-
--/* Logical router ingress Table 1: Neighbor lookup lflows
-- * for logical router ports. */
--static void
--build_neigh_learning_flows_for_lrouter_port(
-- struct ovn_port *op, struct hmap *lflows,
-- struct ds *match, struct ds *actions)
--{
-- if (op->nbrp) {
-+ if (!find_lrp_member_ip(peer, ip_s)) {
-+ continue;
-+ }
-
-- bool learn_from_arp_request = smap_get_bool(&op->od->nbr->options,
-- "always_learn_from_arp_request", true);
-+ ds_clear(match);
-+ ds_put_format(match, "outport == %s && "
-+ REG_NEXT_HOP_IPV6 " == %s",
-+ peer->json_key, ip_s);
-
-- /* Check if we need to learn mac-binding from ARP requests. */
-- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
-- if (!learn_from_arp_request) {
-- /* ARP request to this address should always get learned,
-- * so add a priority-110 flow to set
-- * REGBIT_LOOKUP_NEIGHBOR_IP_RESULT to 1. */
-- ds_clear(match);
-- ds_put_format(match,
-- "inport == %s && arp.spa == %s/%u && "
-- "arp.tpa == %s && arp.op == 1",
-- op->json_key,
-- op->lrp_networks.ipv4_addrs[i].network_s,
-- op->lrp_networks.ipv4_addrs[i].plen,
-- op->lrp_networks.ipv4_addrs[i].addr_s);
-- if (op->od->l3dgw_port && op == op->od->l3dgw_port
-- && op->od->l3redirect_port) {
-- ds_put_format(match, " && is_chassis_resident(%s)",
-- op->od->l3redirect_port->json_key);
-+ ds_clear(actions);
-+ ds_put_format(actions, "eth.dst = %s; next;", ea_s);
-+ ovn_lflow_add_with_hint(lflows, peer->od,
-+ S_ROUTER_IN_ARP_RESOLVE, 100,
-+ ds_cstr(match),
-+ ds_cstr(actions),
-+ &op->nbsp->header_);
- }
-- const char *actions_s = REGBIT_LOOKUP_NEIGHBOR_RESULT
-- " = lookup_arp(inport, arp.spa, arp.sha); "
-- REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" = 1;"
-- " next;";
-- ovn_lflow_add_with_hint(lflows, op->od,
-- S_ROUTER_IN_LOOKUP_NEIGHBOR, 110,
-- ds_cstr(match), actions_s,
-- &op->nbrp->header_);
-- }
-- ds_clear(match);
-- ds_put_format(match,
-- "inport == %s && arp.spa == %s/%u && arp.op == 1",
-- op->json_key,
-- op->lrp_networks.ipv4_addrs[i].network_s,
-- op->lrp_networks.ipv4_addrs[i].plen);
-- if (op->od->l3dgw_port && op == op->od->l3dgw_port
-- && op->od->l3redirect_port) {
-- ds_put_format(match, " && is_chassis_resident(%s)",
-- op->od->l3redirect_port->json_key);
- }
-- ds_clear(actions);
-- ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT
-- " = lookup_arp(inport, arp.spa, arp.sha); %snext;",
-- learn_from_arp_request ? "" :
-- REGBIT_LOOKUP_NEIGHBOR_IP_RESULT
-- " = lookup_arp_ip(inport, arp.spa); ");
-- ovn_lflow_add_with_hint(lflows, op->od,
-- S_ROUTER_IN_LOOKUP_NEIGHBOR, 100,
-- ds_cstr(match), ds_cstr(actions),
-- &op->nbrp->header_);
- }
-- }
--}
--
--/* Logical router ingress table ND_RA_OPTIONS & ND_RA_RESPONSE: IPv6 Router
-- * Adv (RA) options and response. */
--static void
--build_ND_RA_flows_for_lrouter_port(
-- struct ovn_port *op, struct hmap *lflows,
-- struct ds *match, struct ds *actions)
--{
-- if (!op->nbrp || op->nbrp->peer || !op->peer) {
-- return;
-- }
-+ } else if (op->od->n_router_ports && !lsp_is_router(op->nbsp)
-+ && !strcmp(op->nbsp->type, "virtual")) {
-+ /* This is a virtual port. Add ARP replies for the virtual ip with
-+ * the mac of the present active virtual parent.
-+ * If the logical port doesn't have virtual parent set in
-+ * Port_Binding table, then add the flow to set eth.dst to
-+ * 00:00:00:00:00:00 and advance to next table so that ARP is
-+ * resolved by router pipeline using the arp{} action.
-+ * The MAC_Binding entry for the virtual ip might be invalid. */
-+ ovs_be32 ip;
-
-- if (!op->lrp_networks.n_ipv6_addrs) {
-- return;
-- }
-+ const char *vip = smap_get(&op->nbsp->options,
-+ "virtual-ip");
-+ const char *virtual_parents = smap_get(&op->nbsp->options,
-+ "virtual-parents");
-+ if (!vip || !virtual_parents ||
-+ !ip_parse(vip, &ip) || !op->sb) {
-+ return;
-+ }
-
-- struct smap options;
-- smap_clone(&options, &op->sb->options);
-+ if (!op->sb->virtual_parent || !op->sb->virtual_parent[0] ||
-+ !op->sb->chassis) {
-+ /* The virtual port is not claimed yet. */
-+ for (size_t i = 0; i < op->od->n_router_ports; i++) {
-+ const char *peer_name = smap_get(
-+ &op->od->router_ports[i]->nbsp->options,
-+ "router-port");
-+ if (!peer_name) {
-+ continue;
-+ }
-
-- /* enable IPv6 prefix delegation */
-- bool prefix_delegation = smap_get_bool(&op->nbrp->options,
-- "prefix_delegation", false);
-- if (!lrport_is_enabled(op->nbrp)) {
-- prefix_delegation = false;
-- }
-- smap_add(&options, "ipv6_prefix_delegation",
-- prefix_delegation ? "true" : "false");
-+ struct ovn_port *peer = ovn_port_find(ports, peer_name);
-+ if (!peer || !peer->nbrp) {
-+ continue;
-+ }
-
-- bool ipv6_prefix = smap_get_bool(&op->nbrp->options,
-- "prefix", false);
-- if (!lrport_is_enabled(op->nbrp)) {
-- ipv6_prefix = false;
-- }
-- smap_add(&options, "ipv6_prefix",
-- ipv6_prefix ? "true" : "false");
-- sbrec_port_binding_set_options(op->sb, &options);
-+ if (find_lrp_member_ip(peer, vip)) {
-+ ds_clear(match);
-+ ds_put_format(match, "outport == %s && "
-+ REG_NEXT_HOP_IPV4 " == %s",
-+ peer->json_key, vip);
-
-- smap_destroy(&options);
-+ const char *arp_actions =
-+ "eth.dst = 00:00:00:00:00:00; next;";
-+ ovn_lflow_add_with_hint(lflows, peer->od,
-+ S_ROUTER_IN_ARP_RESOLVE, 100,
-+ ds_cstr(match),
-+ arp_actions,
-+ &op->nbsp->header_);
-+ break;
-+ }
-+ }
-+ } else {
-+ struct ovn_port *vp =
-+ ovn_port_find(ports, op->sb->virtual_parent);
-+ if (!vp || !vp->nbsp) {
-+ return;
-+ }
-
-- const char *address_mode = smap_get(
-- &op->nbrp->ipv6_ra_configs, "address_mode");
-+ for (size_t i = 0; i < vp->n_lsp_addrs; i++) {
-+ bool found_vip_network = false;
-+ const char *ea_s = vp->lsp_addrs[i].ea_s;
-+ for (size_t j = 0; j < vp->od->n_router_ports; j++) {
-+ /* Get the Logical_Router_Port that the
-+ * Logical_Switch_Port is connected to, as
-+ * 'peer'. */
-+ const char *peer_name = smap_get(
-+ &vp->od->router_ports[j]->nbsp->options,
-+ "router-port");
-+ if (!peer_name) {
-+ continue;
-+ }
-
-- if (!address_mode) {
-- return;
-- }
-- if (strcmp(address_mode, "slaac") &&
-- strcmp(address_mode, "dhcpv6_stateful") &&
-- strcmp(address_mode, "dhcpv6_stateless")) {
-- static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-- VLOG_WARN_RL(&rl, "Invalid address mode [%s] defined",
-- address_mode);
-- return;
-- }
-+ struct ovn_port *peer =
-+ ovn_port_find(ports, peer_name);
-+ if (!peer || !peer->nbrp) {
-+ continue;
-+ }
-
-- if (smap_get_bool(&op->nbrp->ipv6_ra_configs, "send_periodic",
-- false)) {
-- copy_ra_to_sb(op, address_mode);
-- }
-+ if (!find_lrp_member_ip(peer, vip)) {
-+ continue;
-+ }
-
-- ds_clear(match);
-- ds_put_format(match, "inport == %s && ip6.dst == ff02::2 && nd_rs",
-- op->json_key);
-- ds_clear(actions);
-+ ds_clear(match);
-+ ds_put_format(match, "outport == %s && "
-+ REG_NEXT_HOP_IPV4 " == %s",
-+ peer->json_key, vip);
-
-- const char *mtu_s = smap_get(
-- &op->nbrp->ipv6_ra_configs, "mtu");
-+ ds_clear(actions);
-+ ds_put_format(actions, "eth.dst = %s; next;", ea_s);
-+ ovn_lflow_add_with_hint(lflows, peer->od,
-+ S_ROUTER_IN_ARP_RESOLVE, 100,
-+ ds_cstr(match),
-+ ds_cstr(actions),
-+ &op->nbsp->header_);
-+ found_vip_network = true;
-+ break;
-+ }
-
-- /* As per RFC 2460, 1280 is minimum IPv6 MTU. */
-- uint32_t mtu = (mtu_s && atoi(mtu_s) >= 1280) ? atoi(mtu_s) : 0;
-+ if (found_vip_network) {
-+ break;
-+ }
-+ }
-+ }
-+ } else if (lsp_is_router(op->nbsp)) {
-+ /* This is a logical switch port that connects to a router. */
-
-- ds_put_format(actions, REGBIT_ND_RA_OPTS_RESULT" = put_nd_ra_opts("
-- "addr_mode = \"%s\", slla = %s",
-- address_mode, op->lrp_networks.ea_s);
-- if (mtu > 0) {
-- ds_put_format(actions, ", mtu = %u", mtu);
-- }
-+ /* The peer of this switch port is the router port for which
-+ * we need to add logical flows such that it can resolve
-+ * ARP entries for all the other router ports connected to
-+ * the switch in question. */
-
-- const char *prf = smap_get_def(
-- &op->nbrp->ipv6_ra_configs, "router_preference", "MEDIUM");
-- if (strcmp(prf, "MEDIUM")) {
-- ds_put_format(actions, ", router_preference = \"%s\"", prf);
-- }
-+ const char *peer_name = smap_get(&op->nbsp->options,
-+ "router-port");
-+ if (!peer_name) {
-+ return;
-+ }
-
-- bool add_rs_response_flow = false;
-+ struct ovn_port *peer = ovn_port_find(ports, peer_name);
-+ if (!peer || !peer->nbrp) {
-+ return;
-+ }
-
-- for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
-- if (in6_is_lla(&op->lrp_networks.ipv6_addrs[i].network)) {
-- continue;
-+ if (peer->od->nbr &&
-+ smap_get_bool(&peer->od->nbr->options,
-+ "dynamic_neigh_routers", false)) {
-+ return;
- }
-
-- ds_put_format(actions, ", prefix = %s/%u",
-- op->lrp_networks.ipv6_addrs[i].network_s,
-- op->lrp_networks.ipv6_addrs[i].plen);
-+ for (size_t i = 0; i < op->od->n_router_ports; i++) {
-+ const char *router_port_name = smap_get(
-+ &op->od->router_ports[i]->nbsp->options,
-+ "router-port");
-+ struct ovn_port *router_port = ovn_port_find(ports,
-+ router_port_name);
-+ if (!router_port || !router_port->nbrp) {
-+ continue;
-+ }
-
-- add_rs_response_flow = true;
-- }
-+ /* Skip the router port under consideration. */
-+ if (router_port == peer) {
-+ continue;
-+ }
-
-- if (add_rs_response_flow) {
-- ds_put_cstr(actions, "); next;");
-- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_ND_RA_OPTIONS,
-- 50, ds_cstr(match), ds_cstr(actions),
-- &op->nbrp->header_);
-- ds_clear(actions);
-- ds_clear(match);
-- ds_put_format(match, "inport == %s && ip6.dst == ff02::2 && "
-- "nd_ra && "REGBIT_ND_RA_OPTS_RESULT, op->json_key);
-+ if (router_port->lrp_networks.n_ipv4_addrs) {
-+ ds_clear(match);
-+ ds_put_format(match, "outport == %s && "
-+ REG_NEXT_HOP_IPV4 " == ",
-+ peer->json_key);
-+ op_put_v4_networks(match, router_port, false);
-
-- char ip6_str[INET6_ADDRSTRLEN + 1];
-- struct in6_addr lla;
-- in6_generate_lla(op->lrp_networks.ea, &lla);
-- memset(ip6_str, 0, sizeof(ip6_str));
-- ipv6_string_mapped(ip6_str, &lla);
-- ds_put_format(actions, "eth.dst = eth.src; eth.src = %s; "
-- "ip6.dst = ip6.src; ip6.src = %s; "
-- "outport = inport; flags.loopback = 1; "
-- "output;",
-- op->lrp_networks.ea_s, ip6_str);
-- ovn_lflow_add_with_hint(lflows, op->od,
-- S_ROUTER_IN_ND_RA_RESPONSE, 50,
-- ds_cstr(match), ds_cstr(actions),
-- &op->nbrp->header_);
-- }
--}
-+ ds_clear(actions);
-+ ds_put_format(actions, "eth.dst = %s; next;",
-+ router_port->lrp_networks.ea_s);
-+ ovn_lflow_add_with_hint(lflows, peer->od,
-+ S_ROUTER_IN_ARP_RESOLVE, 100,
-+ ds_cstr(match), ds_cstr(actions),
-+ &op->nbsp->header_);
-+ }
-
--/* Logical router ingress table ND_RA_OPTIONS & ND_RA_RESPONSE: RS
-- * responder, by default goto next. (priority 0). */
--static void
--build_ND_RA_flows_for_lrouter(struct ovn_datapath *od, struct hmap *lflows)
--{
-- if (od->nbr) {
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_ND_RA_OPTIONS, 0, "1", "next;");
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_ND_RA_RESPONSE, 0, "1", "next;");
-+ if (router_port->lrp_networks.n_ipv6_addrs) {
-+ ds_clear(match);
-+ ds_put_format(match, "outport == %s && "
-+ REG_NEXT_HOP_IPV6 " == ",
-+ peer->json_key);
-+ op_put_v6_networks(match, router_port);
-+
-+ ds_clear(actions);
-+ ds_put_format(actions, "eth.dst = %s; next;",
-+ router_port->lrp_networks.ea_s);
-+ ovn_lflow_add_with_hint(lflows, peer->od,
-+ S_ROUTER_IN_ARP_RESOLVE, 100,
-+ ds_cstr(match), ds_cstr(actions),
-+ &op->nbsp->header_);
-+ }
-+ }
- }
-+
- }
-
--/* Logical router ingress table IP_ROUTING : IP Routing.
-+/* Local router ingress table CHK_PKT_LEN: Check packet length.
- *
-- * A packet that arrives at this table is an IP packet that should be
-- * routed to the address in 'ip[46].dst'.
-+ * Any IPv4 packet with outport set to the distributed gateway
-+ * router port, check the packet length and store the result in the
-+ * 'REGBIT_PKT_LARGER' register bit.
- *
-- * For regular routes without ECMP, table IP_ROUTING sets outport to the
-- * correct output port, eth.src to the output port's MAC address, and
-- * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 to the next-hop IP address
-- * (leaving 'ip[46].dst', the packet’s final destination, unchanged), and
-- * advances to the next table.
-+ * Local router ingress table LARGER_PKTS: Handle larger packets.
- *
-- * For ECMP routes, i.e. multiple routes with same policy and prefix, table
-- * IP_ROUTING remembers ECMP group id and selects a member id, and advances
-- * to table IP_ROUTING_ECMP, which sets outport, eth.src and
-- * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 for the selected ECMP member.
-- */
-+ * Any IPv4 packet with outport set to the distributed gateway
-+ * router port and the 'REGBIT_PKT_LARGER' register bit is set,
-+ * generate ICMPv4 packet with type 3 (Destination Unreachable) and
-+ * code 4 (Fragmentation needed).
-+ * */
- static void
--build_ip_routing_flows_for_lrouter_port(
-- struct ovn_port *op, struct hmap *lflows)
-+build_check_pkt_len_flows_for_lrouter(
-+ struct ovn_datapath *od, struct hmap *lflows,
-+ struct hmap *ports,
-+ struct ds *match, struct ds *actions)
- {
-- if (op->nbrp) {
-+ if (od->nbr) {
-
-- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
-- add_route(lflows, op, op->lrp_networks.ipv4_addrs[i].addr_s,
-- op->lrp_networks.ipv4_addrs[i].network_s,
-- op->lrp_networks.ipv4_addrs[i].plen, NULL, false,
-- &op->nbrp->header_);
-- }
-+ /* Packets are allowed by default. */
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_CHK_PKT_LEN, 0, "1",
-+ "next;");
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_LARGER_PKTS, 0, "1",
-+ "next;");
-
-- for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
-- add_route(lflows, op, op->lrp_networks.ipv6_addrs[i].addr_s,
-- op->lrp_networks.ipv6_addrs[i].network_s,
-- op->lrp_networks.ipv6_addrs[i].plen, NULL, false,
-- &op->nbrp->header_);
-+ if (od->l3dgw_port && od->l3redirect_port) {
-+ int gw_mtu = 0;
-+ if (od->l3dgw_port->nbrp) {
-+ gw_mtu = smap_get_int(&od->l3dgw_port->nbrp->options,
-+ "gateway_mtu", 0);
-+ }
-+ /* Add the flows only if gateway_mtu is configured. */
-+ if (gw_mtu <= 0) {
-+ return;
-+ }
-+
-+ ds_clear(match);
-+ ds_put_format(match, "outport == %s", od->l3dgw_port->json_key);
-+
-+ ds_clear(actions);
-+ ds_put_format(actions,
-+ REGBIT_PKT_LARGER" = check_pkt_larger(%d);"
-+ " next;", gw_mtu + VLAN_ETH_HEADER_LEN);
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_CHK_PKT_LEN, 50,
-+ ds_cstr(match), ds_cstr(actions),
-+ &od->l3dgw_port->nbrp->header_);
-+
-+ for (size_t i = 0; i < od->nbr->n_ports; i++) {
-+ struct ovn_port *rp = ovn_port_find(ports,
-+ od->nbr->ports[i]->name);
-+ if (!rp || rp == od->l3dgw_port) {
-+ continue;
-+ }
-+
-+ if (rp->lrp_networks.ipv4_addrs) {
-+ ds_clear(match);
-+ ds_put_format(match, "inport == %s && outport == %s"
-+ " && ip4 && "REGBIT_PKT_LARGER,
-+ rp->json_key, od->l3dgw_port->json_key);
-+
-+ ds_clear(actions);
-+ /* Set icmp4.frag_mtu to gw_mtu */
-+ ds_put_format(actions,
-+ "icmp4_error {"
-+ REGBIT_EGRESS_LOOPBACK" = 1; "
-+ "eth.dst = %s; "
-+ "ip4.dst = ip4.src; "
-+ "ip4.src = %s; "
-+ "ip.ttl = 255; "
-+ "icmp4.type = 3; /* Destination Unreachable. */ "
-+ "icmp4.code = 4; /* Frag Needed and DF was Set. */ "
-+ "icmp4.frag_mtu = %d; "
-+ "next(pipeline=ingress, table=%d); };",
-+ rp->lrp_networks.ea_s,
-+ rp->lrp_networks.ipv4_addrs[0].addr_s,
-+ gw_mtu,
-+ ovn_stage_get_table(S_ROUTER_IN_ADMISSION));
-+ ovn_lflow_add_with_hint(lflows, od,
-+ S_ROUTER_IN_LARGER_PKTS, 50,
-+ ds_cstr(match), ds_cstr(actions),
-+ &rp->nbrp->header_);
-+ }
-+
-+ if (rp->lrp_networks.ipv6_addrs) {
-+ ds_clear(match);
-+ ds_put_format(match, "inport == %s && outport == %s"
-+ " && ip6 && "REGBIT_PKT_LARGER,
-+ rp->json_key, od->l3dgw_port->json_key);
-+
-+ ds_clear(actions);
-+ /* Set icmp6.frag_mtu to gw_mtu */
-+ ds_put_format(actions,
-+ "icmp6_error {"
-+ REGBIT_EGRESS_LOOPBACK" = 1; "
-+ "eth.dst = %s; "
-+ "ip6.dst = ip6.src; "
-+ "ip6.src = %s; "
-+ "ip.ttl = 255; "
-+ "icmp6.type = 2; /* Packet Too Big. */ "
-+ "icmp6.code = 0; "
-+ "icmp6.frag_mtu = %d; "
-+ "next(pipeline=ingress, table=%d); };",
-+ rp->lrp_networks.ea_s,
-+ rp->lrp_networks.ipv6_addrs[0].addr_s,
-+ gw_mtu,
-+ ovn_stage_get_table(S_ROUTER_IN_ADMISSION));
-+ ovn_lflow_add_with_hint(lflows, od,
-+ S_ROUTER_IN_LARGER_PKTS, 50,
-+ ds_cstr(match), ds_cstr(actions),
-+ &rp->nbrp->header_);
-+ }
-+ }
- }
- }
- }
-
-+/* Logical router ingress table GW_REDIRECT: Gateway redirect.
-+ *
-+ * For traffic with outport equal to the l3dgw_port
-+ * on a distributed router, this table redirects a subset
-+ * of the traffic to the l3redirect_port which represents
-+ * the central instance of the l3dgw_port.
-+ */
- static void
--build_static_route_flows_for_lrouter(
-+build_gateway_redirect_flows_for_lrouter(
- struct ovn_datapath *od, struct hmap *lflows,
-- struct hmap *ports)
-+ struct ds *match, struct ds *actions)
- {
- if (od->nbr) {
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING_ECMP, 150,
-- REG_ECMP_GROUP_ID" == 0", "next;");
-+ if (od->l3dgw_port && od->l3redirect_port) {
-+ const struct ovsdb_idl_row *stage_hint = NULL;
-
-- struct hmap ecmp_groups = HMAP_INITIALIZER(&ecmp_groups);
-- struct hmap unique_routes = HMAP_INITIALIZER(&unique_routes);
-- struct ovs_list parsed_routes = OVS_LIST_INITIALIZER(&parsed_routes);
-- struct ecmp_groups_node *group;
-- for (int i = 0; i < od->nbr->n_static_routes; i++) {
-- struct parsed_route *route =
-- parsed_routes_add(&parsed_routes, od->nbr->static_routes[i]);
-- if (!route) {
-- continue;
-+ if (od->l3dgw_port->nbrp) {
-+ stage_hint = &od->l3dgw_port->nbrp->header_;
- }
-- group = ecmp_groups_find(&ecmp_groups, route);
-- if (group) {
-- ecmp_groups_add_route(group, route);
-- } else {
-- const struct parsed_route *existed_route =
-- unique_routes_remove(&unique_routes, route);
-- if (existed_route) {
-- group = ecmp_groups_add(&ecmp_groups, existed_route);
-- if (group) {
-- ecmp_groups_add_route(group, route);
-- }
-- } else {
-- unique_routes_add(&unique_routes, route);
-- }
-- }
-- }
-- HMAP_FOR_EACH (group, hmap_node, &ecmp_groups) {
-- /* add a flow in IP_ROUTING, and one flow for each member in
-- * IP_ROUTING_ECMP. */
-- build_ecmp_route_flow(lflows, od, ports, group);
-- }
-- const struct unique_routes_node *ur;
-- HMAP_FOR_EACH (ur, hmap_node, &unique_routes) {
-- build_static_route_flow(lflows, od, ports, ur->route);
-+
-+ /* For traffic with outport == l3dgw_port, if the
-+ * packet did not match any higher priority redirect
-+ * rule, then the traffic is redirected to the central
-+ * instance of the l3dgw_port. */
-+ ds_clear(match);
-+ ds_put_format(match, "outport == %s",
-+ od->l3dgw_port->json_key);
-+ ds_clear(actions);
-+ ds_put_format(actions, "outport = %s; next;",
-+ od->l3redirect_port->json_key);
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_GW_REDIRECT, 50,
-+ ds_cstr(match), ds_cstr(actions),
-+ stage_hint);
- }
-- ecmp_groups_destroy(&ecmp_groups);
-- unique_routes_destroy(&unique_routes);
-- parsed_routes_destroy(&parsed_routes);
-+
-+ /* Packets are allowed by default. */
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_GW_REDIRECT, 0, "1", "next;");
- }
- }
-
--/* IP Multicast lookup. Here we set the output port, adjust TTL and
-- * advance to next table (priority 500).
-- */
-+/* Local router ingress table ARP_REQUEST: ARP request.
-+ *
-+ * In the common case where the Ethernet destination has been resolved,
-+ * this table outputs the packet (priority 0). Otherwise, it composes
-+ * and sends an ARP/IPv6 NA request (priority 100). */
- static void
--build_mcast_lookup_flows_for_lrouter(
-+build_arp_request_flows_for_lrouter(
- struct ovn_datapath *od, struct hmap *lflows,
- struct ds *match, struct ds *actions)
- {
- if (od->nbr) {
-+ for (int i = 0; i < od->nbr->n_static_routes; i++) {
-+ const struct nbrec_logical_router_static_route *route;
-
-- /* Drop IPv6 multicast traffic that shouldn't be forwarded,
-- * i.e., router solicitation and router advertisement.
-- */
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 550,
-- "nd_rs || nd_ra", "drop;");
-- if (!od->mcast_info.rtr.relay) {
-- return;
-- }
--
-- struct ovn_igmp_group *igmp_group;
-+ route = od->nbr->static_routes[i];
-+ struct in6_addr gw_ip6;
-+ unsigned int plen;
-+ char *error = ipv6_parse_cidr(route->nexthop, &gw_ip6, &plen);
-+ if (error || plen != 128) {
-+ free(error);
-+ continue;
-+ }
-
-- LIST_FOR_EACH (igmp_group, list_node, &od->mcast_info.groups) {
- ds_clear(match);
-+ ds_put_format(match, "eth.dst == 00:00:00:00:00:00 && "
-+ "ip6 && " REG_NEXT_HOP_IPV6 " == %s",
-+ route->nexthop);
-+ struct in6_addr sn_addr;
-+ struct eth_addr eth_dst;
-+ in6_addr_solicited_node(&sn_addr, &gw_ip6);
-+ ipv6_multicast_to_ethernet(ð_dst, &sn_addr);
-+
-+ char sn_addr_s[INET6_ADDRSTRLEN + 1];
-+ ipv6_string_mapped(sn_addr_s, &sn_addr);
-+
- ds_clear(actions);
-- if (IN6_IS_ADDR_V4MAPPED(&igmp_group->address)) {
-- ds_put_format(match, "ip4 && ip4.dst == %s ",
-- igmp_group->mcgroup.name);
-- } else {
-- ds_put_format(match, "ip6 && ip6.dst == %s ",
-- igmp_group->mcgroup.name);
-- }
-- if (od->mcast_info.rtr.flood_static) {
-- ds_put_cstr(actions,
-- "clone { "
-- "outport = \""MC_STATIC"\"; "
-- "ip.ttl--; "
-- "next; "
-- "};");
-- }
-- ds_put_format(actions, "outport = \"%s\"; ip.ttl--; next;",
-- igmp_group->mcgroup.name);
-- ovn_lflow_add_unique(lflows, od, S_ROUTER_IN_IP_ROUTING, 500,
-- ds_cstr(match), ds_cstr(actions));
-- }
-+ ds_put_format(actions,
-+ "nd_ns { "
-+ "eth.dst = "ETH_ADDR_FMT"; "
-+ "ip6.dst = %s; "
-+ "nd.target = %s; "
-+ "output; "
-+ "};", ETH_ADDR_ARGS(eth_dst), sn_addr_s,
-+ route->nexthop);
-
-- /* If needed, flood unregistered multicast on statically configured
-- * ports. Otherwise drop any multicast traffic.
-- */
-- if (od->mcast_info.rtr.flood_static) {
-- ovn_lflow_add_unique(lflows, od, S_ROUTER_IN_IP_ROUTING, 450,
-- "ip4.mcast || ip6.mcast",
-- "clone { "
-- "outport = \""MC_STATIC"\"; "
-- "ip.ttl--; "
-- "next; "
-- "};");
-- } else {
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 450,
-- "ip4.mcast || ip6.mcast", "drop;");
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ARP_REQUEST, 200,
-+ ds_cstr(match), ds_cstr(actions),
-+ &route->header_);
- }
-+
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 100,
-+ "eth.dst == 00:00:00:00:00:00 && ip4",
-+ "arp { "
-+ "eth.dst = ff:ff:ff:ff:ff:ff; "
-+ "arp.spa = " REG_SRC_IPV4 "; "
-+ "arp.tpa = " REG_NEXT_HOP_IPV4 "; "
-+ "arp.op = 1; " /* ARP request */
-+ "output; "
-+ "};");
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 100,
-+ "eth.dst == 00:00:00:00:00:00 && ip6",
-+ "nd_ns { "
-+ "nd.target = " REG_NEXT_HOP_IPV6 "; "
-+ "output; "
-+ "};");
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 0, "1", "output;");
- }
- }
-
--/* Logical router ingress table POLICY: Policy.
-+/* Logical router egress table DELIVERY: Delivery (priority 100-110).
- *
-- * A packet that arrives at this table is an IP packet that should be
-- * permitted/denied/rerouted to the address in the rule's nexthop.
-- * This table sets outport to the correct out_port,
-- * eth.src to the output port's MAC address,
-- * and REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 to the next-hop IP address
-- * (leaving 'ip[46].dst', the packet’s final destination, unchanged), and
-- * advances to the next table for ARP/ND resolution. */
-+ * Priority 100 rules deliver packets to enabled logical ports.
-+ * Priority 110 rules match multicast packets and update the source
-+ * mac before delivering to enabled logical ports. IP multicast traffic
-+ * bypasses S_ROUTER_IN_IP_ROUTING route lookups.
-+ */
- static void
--build_ingress_policy_flows_for_lrouter(
-- struct ovn_datapath *od, struct hmap *lflows,
-- struct hmap *ports)
-+build_egress_delivery_flows_for_lrouter_port(
-+ struct ovn_port *op, struct hmap *lflows,
-+ struct ds *match, struct ds *actions)
- {
-- if (od->nbr) {
-- /* This is a catch-all rule. It has the lowest priority (0)
-- * does a match-all("1") and pass-through (next) */
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_POLICY, 0, "1",
-- REG_ECMP_GROUP_ID" = 0; next;");
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_POLICY_ECMP, 150,
-- REG_ECMP_GROUP_ID" == 0", "next;");
-+ if (op->nbrp) {
-+ if (!lrport_is_enabled(op->nbrp)) {
-+ /* Drop packets to disabled logical ports (since logical flow
-+ * tables are default-drop). */
-+ return;
-+ }
-
-- /* Convert routing policies to flows. */
-- uint16_t ecmp_group_id = 1;
-- for (int i = 0; i < od->nbr->n_policies; i++) {
-- const struct nbrec_logical_router_policy *rule
-- = od->nbr->policies[i];
-- bool is_ecmp_reroute =
-- (!strcmp(rule->action, "reroute") && rule->n_nexthops > 1);
-+ if (op->derived) {
-+ /* No egress packets should be processed in the context of
-+ * a chassisredirect port. The chassisredirect port should
-+ * be replaced by the l3dgw port in the local output
-+ * pipeline stage before egress processing. */
-+ return;
-+ }
-
-- if (is_ecmp_reroute) {
-- build_ecmp_routing_policy_flows(lflows, od, ports, rule,
-- ecmp_group_id);
-- ecmp_group_id++;
-- } else {
-- build_routing_policy_flow(lflows, od, ports, rule,
-- &rule->header_);
-- }
-+ /* If multicast relay is enabled then also adjust source mac for IP
-+ * multicast traffic.
-+ */
-+ if (op->od->mcast_info.rtr.relay) {
-+ ds_clear(match);
-+ ds_clear(actions);
-+ ds_put_format(match, "(ip4.mcast || ip6.mcast) && outport == %s",
-+ op->json_key);
-+ ds_put_format(actions, "eth.src = %s; output;",
-+ op->lrp_networks.ea_s);
-+ ovn_lflow_add(lflows, op->od, S_ROUTER_OUT_DELIVERY, 110,
-+ ds_cstr(match), ds_cstr(actions));
- }
-+
-+ ds_clear(match);
-+ ds_put_format(match, "outport == %s", op->json_key);
-+ ovn_lflow_add(lflows, op->od, S_ROUTER_OUT_DELIVERY, 100,
-+ ds_cstr(match), "output;");
- }
-+
- }
-
--/* Local router ingress table ARP_RESOLVE: ARP Resolution. */
- static void
--build_arp_resolve_flows_for_lrouter(
-+build_misc_local_traffic_drop_flows_for_lrouter(
- struct ovn_datapath *od, struct hmap *lflows)
- {
- if (od->nbr) {
-- /* Multicast packets already have the outport set so just advance to
-- * next table (priority 500). */
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 500,
-- "ip4.mcast || ip6.mcast", "next;");
-+ /* L3 admission control: drop multicast and broadcast source, localhost
-+ * source or destination, and zero network source or destination
-+ * (priority 100). */
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 100,
-+ "ip4.src_mcast ||"
-+ "ip4.src == 255.255.255.255 || "
-+ "ip4.src == 127.0.0.0/8 || "
-+ "ip4.dst == 127.0.0.0/8 || "
-+ "ip4.src == 0.0.0.0/8 || "
-+ "ip4.dst == 0.0.0.0/8",
-+ "drop;");
-
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 0, "ip4",
-- "get_arp(outport, " REG_NEXT_HOP_IPV4 "); next;");
-+ /* Drop ARP packets (priority 85). ARP request packets for router's own
-+ * IPs are handled with priority-90 flows.
-+ * Drop IPv6 ND packets (priority 85). ND NA packets for router's own
-+ * IPs are handled with priority-90 flows.
-+ */
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 85,
-+ "arp || nd", "drop;");
-
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 0, "ip6",
-- "get_nd(outport, " REG_NEXT_HOP_IPV6 "); next;");
-- }
--}
-+ /* Allow IPv6 multicast traffic that's supposed to reach the
-+ * router pipeline (e.g., router solicitations).
-+ */
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 84, "nd_rs || nd_ra",
-+ "next;");
-+
-+ /* Drop other reserved multicast. */
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 83,
-+ "ip6.mcast_rsvd", "drop;");
-+
-+ /* Allow other multicast if relay enabled (priority 82). */
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 82,
-+ "ip4.mcast || ip6.mcast",
-+ od->mcast_info.rtr.relay ? "next;" : "drop;");
-+
-+ /* Drop Ethernet local broadcast. By definition this traffic should
-+ * not be forwarded.*/
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 50,
-+ "eth.bcast", "drop;");
-+
-+ /* TTL discard */
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 30,
-+ "ip4 && ip.ttl == {0, 1}", "drop;");
-+
-+ /* Pass other traffic not already handled to the next table for
-+ * routing. */
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 0, "1", "next;");
-+ }
-+}
-
--/* Local router ingress table ARP_RESOLVE: ARP Resolution.
-- *
-- * Any unicast packet that reaches this table is an IP packet whose
-- * next-hop IP address is in REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6
-- * (ip4.dst/ipv6.dst is the final destination).
-- * This table resolves the IP address in
-- * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 into an output port in outport and
-- * an Ethernet address in eth.dst.
-- */
- static void
--build_arp_resolve_flows_for_lrouter_port(
-+build_dhcpv6_reply_flows_for_lrouter_port(
- struct ovn_port *op, struct hmap *lflows,
-- struct hmap *ports,
-- struct ds *match, struct ds *actions)
-+ struct ds *match)
- {
-- if (op->nbsp && !lsp_is_enabled(op->nbsp)) {
-- return;
-+ if (op->nbrp && (!op->derived)) {
-+ for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
-+ ds_clear(match);
-+ ds_put_format(match, "ip6.dst == %s && udp.src == 547 &&"
-+ " udp.dst == 546",
-+ op->lrp_networks.ipv6_addrs[i].addr_s);
-+ ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 100,
-+ ds_cstr(match),
-+ "reg0 = 0; handle_dhcpv6_reply;");
-+ }
- }
-
-- if (op->nbrp) {
-- /* This is a logical router port. If next-hop IP address in
-- * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 matches IP address of this
-- * router port, then the packet is intended to eventually be sent
-- * to this logical port. Set the destination mac address using
-- * this port's mac address.
-- *
-- * The packet is still in peer's logical pipeline. So the match
-- * should be on peer's outport. */
-- if (op->peer && op->nbrp->peer) {
-- if (op->lrp_networks.n_ipv4_addrs) {
-- ds_clear(match);
-- ds_put_format(match, "outport == %s && "
-- REG_NEXT_HOP_IPV4 "== ",
-- op->peer->json_key);
-- op_put_v4_networks(match, op, false);
-+}
-
-- ds_clear(actions);
-- ds_put_format(actions, "eth.dst = %s; next;",
-- op->lrp_networks.ea_s);
-- ovn_lflow_add_with_hint(lflows, op->peer->od,
-- S_ROUTER_IN_ARP_RESOLVE, 100,
-- ds_cstr(match), ds_cstr(actions),
-- &op->nbrp->header_);
-- }
-+static void
-+build_ipv6_input_flows_for_lrouter_port(
-+ struct ovn_port *op, struct hmap *lflows,
-+ struct ds *match, struct ds *actions)
-+{
-+ if (op->nbrp && (!op->derived)) {
-+ /* No ingress packets are accepted on a chassisredirect
-+ * port, so no need to program flows for that port. */
-+ if (op->lrp_networks.n_ipv6_addrs) {
-+ /* ICMPv6 echo reply. These flows reply to echo requests
-+ * received for the router's IP address. */
-+ ds_clear(match);
-+ ds_put_cstr(match, "ip6.dst == ");
-+ op_put_v6_networks(match, op);
-+ ds_put_cstr(match, " && icmp6.type == 128 && icmp6.code == 0");
-
-- if (op->lrp_networks.n_ipv6_addrs) {
-- ds_clear(match);
-- ds_put_format(match, "outport == %s && "
-- REG_NEXT_HOP_IPV6 " == ",
-- op->peer->json_key);
-- op_put_v6_networks(match, op);
-+ const char *lrp_actions =
-+ "ip6.dst <-> ip6.src; "
-+ "ip.ttl = 255; "
-+ "icmp6.type = 129; "
-+ "flags.loopback = 1; "
-+ "next; ";
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90,
-+ ds_cstr(match), lrp_actions,
-+ &op->nbrp->header_);
-+ }
-
-- ds_clear(actions);
-- ds_put_format(actions, "eth.dst = %s; next;",
-- op->lrp_networks.ea_s);
-- ovn_lflow_add_with_hint(lflows, op->peer->od,
-- S_ROUTER_IN_ARP_RESOLVE, 100,
-- ds_cstr(match), ds_cstr(actions),
-- &op->nbrp->header_);
-+ /* ND reply. These flows reply to ND solicitations for the
-+ * router's own IP address. */
-+ for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
-+ ds_clear(match);
-+ if (op->od->l3dgw_port && op == op->od->l3dgw_port
-+ && op->od->l3redirect_port) {
-+ /* Traffic with eth.src = l3dgw_port->lrp_networks.ea_s
-+ * should only be sent from the gateway chassi, so that
-+ * upstream MAC learning points to the gateway chassis.
-+ * Also need to avoid generation of multiple ND replies
-+ * from different chassis. */
-+ ds_put_format(match, "is_chassis_resident(%s)",
-+ op->od->l3redirect_port->json_key);
- }
-+
-+ build_lrouter_nd_flow(op->od, op, "nd_na_router",
-+ op->lrp_networks.ipv6_addrs[i].addr_s,
-+ op->lrp_networks.ipv6_addrs[i].sn_addr_s,
-+ REG_INPORT_ETH_ADDR, match, false, 90,
-+ &op->nbrp->header_, lflows);
- }
-
-- if (!op->derived && op->od->l3redirect_port) {
-- const char *redirect_type = smap_get(&op->nbrp->options,
-- "redirect-type");
-- if (redirect_type && !strcasecmp(redirect_type, "bridged")) {
-- /* Packet is on a non gateway chassis and
-- * has an unresolved ARP on a network behind gateway
-- * chassis attached router port. Since, redirect type
-- * is "bridged", instead of calling "get_arp"
-- * on this node, we will redirect the packet to gateway
-- * chassis, by setting destination mac router port mac.*/
-+ /* UDP/TCP port unreachable */
-+ if (!smap_get(&op->od->nbr->options, "chassis")
-+ && !op->od->l3dgw_port) {
-+ for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
- ds_clear(match);
-- ds_put_format(match, "outport == %s && "
-- "!is_chassis_resident(%s)", op->json_key,
-- op->od->l3redirect_port->json_key);
-- ds_clear(actions);
-- ds_put_format(actions, "eth.dst = %s; next;",
-- op->lrp_networks.ea_s);
-+ ds_put_format(match,
-+ "ip6 && ip6.dst == %s && !ip.later_frag && tcp",
-+ op->lrp_networks.ipv6_addrs[i].addr_s);
-+ const char *action = "tcp_reset {"
-+ "eth.dst <-> eth.src; "
-+ "ip6.dst <-> ip6.src; "
-+ "next; };";
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-+ 80, ds_cstr(match), action,
-+ &op->nbrp->header_);
-
-- ovn_lflow_add_with_hint(lflows, op->od,
-- S_ROUTER_IN_ARP_RESOLVE, 50,
-- ds_cstr(match), ds_cstr(actions),
-+ ds_clear(match);
-+ ds_put_format(match,
-+ "ip6 && ip6.dst == %s && !ip.later_frag && udp",
-+ op->lrp_networks.ipv6_addrs[i].addr_s);
-+ action = "icmp6 {"
-+ "eth.dst <-> eth.src; "
-+ "ip6.dst <-> ip6.src; "
-+ "ip.ttl = 255; "
-+ "icmp6.type = 1; "
-+ "icmp6.code = 4; "
-+ "next; };";
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-+ 80, ds_cstr(match), action,
-+ &op->nbrp->header_);
-+
-+ ds_clear(match);
-+ ds_put_format(match,
-+ "ip6 && ip6.dst == %s && !ip.later_frag",
-+ op->lrp_networks.ipv6_addrs[i].addr_s);
-+ action = "icmp6 {"
-+ "eth.dst <-> eth.src; "
-+ "ip6.dst <-> ip6.src; "
-+ "ip.ttl = 255; "
-+ "icmp6.type = 1; "
-+ "icmp6.code = 3; "
-+ "next; };";
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-+ 70, ds_cstr(match), action,
- &op->nbrp->header_);
- }
- }
-
-- /* Drop IP traffic destined to router owned IPs. Part of it is dropped
-- * in stage "lr_in_ip_input" but traffic that could have been unSNATed
-- * but didn't match any existing session might still end up here.
-- *
-- * Priority 1.
-- */
-- build_lrouter_drop_own_dest(op, S_ROUTER_IN_ARP_RESOLVE, 1, true,
-- lflows);
-- } else if (op->od->n_router_ports && !lsp_is_router(op->nbsp)
-- && strcmp(op->nbsp->type, "virtual")) {
-- /* This is a logical switch port that backs a VM or a container.
-- * Extract its addresses. For each of the address, go through all
-- * the router ports attached to the switch (to which this port
-- * connects) and if the address in question is reachable from the
-- * router port, add an ARP/ND entry in that router's pipeline. */
-+ /* ICMPv6 time exceeded */
-+ for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
-+ /* skip link-local address */
-+ if (in6_is_lla(&op->lrp_networks.ipv6_addrs[i].network)) {
-+ continue;
-+ }
-
-- for (size_t i = 0; i < op->n_lsp_addrs; i++) {
-- const char *ea_s = op->lsp_addrs[i].ea_s;
-- for (size_t j = 0; j < op->lsp_addrs[i].n_ipv4_addrs; j++) {
-- const char *ip_s = op->lsp_addrs[i].ipv4_addrs[j].addr_s;
-- for (size_t k = 0; k < op->od->n_router_ports; k++) {
-- /* Get the Logical_Router_Port that the
-- * Logical_Switch_Port is connected to, as
-- * 'peer'. */
-- const char *peer_name = smap_get(
-- &op->od->router_ports[k]->nbsp->options,
-- "router-port");
-- if (!peer_name) {
-- continue;
-- }
-+ ds_clear(match);
-+ ds_clear(actions);
-
-- struct ovn_port *peer = ovn_port_find(ports, peer_name);
-- if (!peer || !peer->nbrp) {
-- continue;
-- }
-+ ds_put_format(match,
-+ "inport == %s && ip6 && "
-+ "ip6.src == %s/%d && "
-+ "ip.ttl == {0, 1} && !ip.later_frag",
-+ op->json_key,
-+ op->lrp_networks.ipv6_addrs[i].network_s,
-+ op->lrp_networks.ipv6_addrs[i].plen);
-+ ds_put_format(actions,
-+ "icmp6 {"
-+ "eth.dst <-> eth.src; "
-+ "ip6.dst = ip6.src; "
-+ "ip6.src = %s; "
-+ "ip.ttl = 255; "
-+ "icmp6.type = 3; /* Time exceeded */ "
-+ "icmp6.code = 0; /* TTL exceeded in transit */ "
-+ "next; };",
-+ op->lrp_networks.ipv6_addrs[i].addr_s);
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 40,
-+ ds_cstr(match), ds_cstr(actions),
-+ &op->nbrp->header_);
-+ }
-+ }
-
-- if (!find_lrp_member_ip(peer, ip_s)) {
-- continue;
-- }
-+}
-
-- ds_clear(match);
-- ds_put_format(match, "outport == %s && "
-- REG_NEXT_HOP_IPV4 " == %s",
-- peer->json_key, ip_s);
-+static void
-+build_lrouter_arp_nd_for_datapath(struct ovn_datapath *od,
-+ struct hmap *lflows)
-+{
-+ if (od->nbr) {
-
-- ds_clear(actions);
-- ds_put_format(actions, "eth.dst = %s; next;", ea_s);
-- ovn_lflow_add_with_hint(lflows, peer->od,
-- S_ROUTER_IN_ARP_RESOLVE, 100,
-- ds_cstr(match),
-- ds_cstr(actions),
-- &op->nbsp->header_);
-- }
-+ /* Priority-90-92 flows handle ARP requests and ND packets. Most are
-+ * per logical port but DNAT addresses can be handled per datapath
-+ * for non gateway router ports.
-+ *
-+ * Priority 91 and 92 flows are added for each gateway router
-+ * port to handle the special cases. In case we get the packet
-+ * on a regular port, just reply with the port's ETH address.
-+ */
-+ for (int i = 0; i < od->nbr->n_nat; i++) {
-+ struct ovn_nat *nat_entry = &od->nat_entries[i];
-+
-+ /* Skip entries we failed to parse. */
-+ if (!nat_entry_is_valid(nat_entry)) {
-+ continue;
- }
-
-- for (size_t j = 0; j < op->lsp_addrs[i].n_ipv6_addrs; j++) {
-- const char *ip_s = op->lsp_addrs[i].ipv6_addrs[j].addr_s;
-- for (size_t k = 0; k < op->od->n_router_ports; k++) {
-- /* Get the Logical_Router_Port that the
-- * Logical_Switch_Port is connected to, as
-- * 'peer'. */
-- const char *peer_name = smap_get(
-- &op->od->router_ports[k]->nbsp->options,
-- "router-port");
-- if (!peer_name) {
-- continue;
-- }
-+ /* Skip SNAT entries for now, we handle unique SNAT IPs separately
-+ * below.
-+ */
-+ if (!strcmp(nat_entry->nb->type, "snat")) {
-+ continue;
-+ }
-+ build_lrouter_nat_arp_nd_flow(od, nat_entry, lflows);
-+ }
-
-- struct ovn_port *peer = ovn_port_find(ports, peer_name);
-- if (!peer || !peer->nbrp) {
-- continue;
-- }
-+ /* Now handle SNAT entries too, one per unique SNAT IP. */
-+ struct shash_node *snat_snode;
-+ SHASH_FOR_EACH (snat_snode, &od->snat_ips) {
-+ struct ovn_snat_ip *snat_ip = snat_snode->data;
-
-- if (!find_lrp_member_ip(peer, ip_s)) {
-- continue;
-- }
-+ if (ovs_list_is_empty(&snat_ip->snat_entries)) {
-+ continue;
-+ }
-
-- ds_clear(match);
-- ds_put_format(match, "outport == %s && "
-- REG_NEXT_HOP_IPV6 " == %s",
-- peer->json_key, ip_s);
-+ struct ovn_nat *nat_entry =
-+ CONTAINER_OF(ovs_list_front(&snat_ip->snat_entries),
-+ struct ovn_nat, ext_addr_list_node);
-+ build_lrouter_nat_arp_nd_flow(od, nat_entry, lflows);
-+ }
-+ }
-+}
-
-- ds_clear(actions);
-- ds_put_format(actions, "eth.dst = %s; next;", ea_s);
-- ovn_lflow_add_with_hint(lflows, peer->od,
-- S_ROUTER_IN_ARP_RESOLVE, 100,
-- ds_cstr(match),
-- ds_cstr(actions),
-- &op->nbsp->header_);
-- }
-- }
-+/* Logical router ingress table 3: IP Input for IPv4. */
-+static void
-+build_lrouter_ipv4_ip_input(struct ovn_port *op,
-+ struct hmap *lflows,
-+ struct ds *match, struct ds *actions)
-+{
-+ /* No ingress packets are accepted on a chassisredirect
-+ * port, so no need to program flows for that port. */
-+ if (op->nbrp && (!op->derived)) {
-+ if (op->lrp_networks.n_ipv4_addrs) {
-+ /* L3 admission control: drop packets that originate from an
-+ * IPv4 address owned by the router or a broadcast address
-+ * known to the router (priority 100). */
-+ ds_clear(match);
-+ ds_put_cstr(match, "ip4.src == ");
-+ op_put_v4_networks(match, op, true);
-+ ds_put_cstr(match, " && "REGBIT_EGRESS_LOOPBACK" == 0");
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 100,
-+ ds_cstr(match), "drop;",
-+ &op->nbrp->header_);
-+
-+ /* ICMP echo reply. These flows reply to ICMP echo requests
-+ * received for the router's IP address. Since packets only
-+ * get here as part of the logical router datapath, the inport
-+ * (i.e. the incoming locally attached net) does not matter.
-+ * The ip.ttl also does not matter (RFC1812 section 4.2.2.9) */
-+ ds_clear(match);
-+ ds_put_cstr(match, "ip4.dst == ");
-+ op_put_v4_networks(match, op, false);
-+ ds_put_cstr(match, " && icmp4.type == 8 && icmp4.code == 0");
-+
-+ const char * icmp_actions = "ip4.dst <-> ip4.src; "
-+ "ip.ttl = 255; "
-+ "icmp4.type = 0; "
-+ "flags.loopback = 1; "
-+ "next; ";
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90,
-+ ds_cstr(match), icmp_actions,
-+ &op->nbrp->header_);
- }
-- } else if (op->od->n_router_ports && !lsp_is_router(op->nbsp)
-- && !strcmp(op->nbsp->type, "virtual")) {
-- /* This is a virtual port. Add ARP replies for the virtual ip with
-- * the mac of the present active virtual parent.
-- * If the logical port doesn't have virtual parent set in
-- * Port_Binding table, then add the flow to set eth.dst to
-- * 00:00:00:00:00:00 and advance to next table so that ARP is
-- * resolved by router pipeline using the arp{} action.
-- * The MAC_Binding entry for the virtual ip might be invalid. */
-- ovs_be32 ip;
-
-- const char *vip = smap_get(&op->nbsp->options,
-- "virtual-ip");
-- const char *virtual_parents = smap_get(&op->nbsp->options,
-- "virtual-parents");
-- if (!vip || !virtual_parents ||
-- !ip_parse(vip, &ip) || !op->sb) {
-- return;
-+ /* ICMP time exceeded */
-+ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
-+ ds_clear(match);
-+ ds_clear(actions);
-+
-+ ds_put_format(match,
-+ "inport == %s && ip4 && "
-+ "ip.ttl == {0, 1} && !ip.later_frag", op->json_key);
-+ ds_put_format(actions,
-+ "icmp4 {"
-+ "eth.dst <-> eth.src; "
-+ "icmp4.type = 11; /* Time exceeded */ "
-+ "icmp4.code = 0; /* TTL exceeded in transit */ "
-+ "ip4.dst = ip4.src; "
-+ "ip4.src = %s; "
-+ "ip.ttl = 255; "
-+ "next; };",
-+ op->lrp_networks.ipv4_addrs[i].addr_s);
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 40,
-+ ds_cstr(match), ds_cstr(actions),
-+ &op->nbrp->header_);
- }
-
-- if (!op->sb->virtual_parent || !op->sb->virtual_parent[0] ||
-- !op->sb->chassis) {
-- /* The virtual port is not claimed yet. */
-- for (size_t i = 0; i < op->od->n_router_ports; i++) {
-- const char *peer_name = smap_get(
-- &op->od->router_ports[i]->nbsp->options,
-- "router-port");
-- if (!peer_name) {
-- continue;
-- }
-+ /* ARP reply. These flows reply to ARP requests for the router's own
-+ * IP address. */
-+ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
-+ ds_clear(match);
-+ ds_put_format(match, "arp.spa == %s/%u",
-+ op->lrp_networks.ipv4_addrs[i].network_s,
-+ op->lrp_networks.ipv4_addrs[i].plen);
-
-- struct ovn_port *peer = ovn_port_find(ports, peer_name);
-- if (!peer || !peer->nbrp) {
-- continue;
-+ if (op->od->l3dgw_port && op->od->l3redirect_port && op->peer
-+ && op->peer->od->n_localnet_ports) {
-+ bool add_chassis_resident_check = false;
-+ if (op == op->od->l3dgw_port) {
-+ /* Traffic with eth.src = l3dgw_port->lrp_networks.ea_s
-+ * should only be sent from the gateway chassis, so that
-+ * upstream MAC learning points to the gateway chassis.
-+ * Also need to avoid generation of multiple ARP responses
-+ * from different chassis. */
-+ add_chassis_resident_check = true;
-+ } else {
-+ /* Check if the option 'reside-on-redirect-chassis'
-+ * is set to true on the router port. If set to true
-+ * and if peer's logical switch has a localnet port, it
-+ * means the router pipeline for the packets from
-+ * peer's logical switch is be run on the chassis
-+ * hosting the gateway port and it should reply to the
-+ * ARP requests for the router port IPs.
-+ */
-+ add_chassis_resident_check = smap_get_bool(
-+ &op->nbrp->options,
-+ "reside-on-redirect-chassis", false);
- }
-
-- if (find_lrp_member_ip(peer, vip)) {
-- ds_clear(match);
-- ds_put_format(match, "outport == %s && "
-- REG_NEXT_HOP_IPV4 " == %s",
-- peer->json_key, vip);
--
-- const char *arp_actions =
-- "eth.dst = 00:00:00:00:00:00; next;";
-- ovn_lflow_add_with_hint(lflows, peer->od,
-- S_ROUTER_IN_ARP_RESOLVE, 100,
-- ds_cstr(match),
-- arp_actions,
-- &op->nbsp->header_);
-- break;
-+ if (add_chassis_resident_check) {
-+ ds_put_format(match, " && is_chassis_resident(%s)",
-+ op->od->l3redirect_port->json_key);
- }
- }
-- } else {
-- struct ovn_port *vp =
-- ovn_port_find(ports, op->sb->virtual_parent);
-- if (!vp || !vp->nbsp) {
-- return;
-- }
-
-- for (size_t i = 0; i < vp->n_lsp_addrs; i++) {
-- bool found_vip_network = false;
-- const char *ea_s = vp->lsp_addrs[i].ea_s;
-- for (size_t j = 0; j < vp->od->n_router_ports; j++) {
-- /* Get the Logical_Router_Port that the
-- * Logical_Switch_Port is connected to, as
-- * 'peer'. */
-- const char *peer_name = smap_get(
-- &vp->od->router_ports[j]->nbsp->options,
-- "router-port");
-- if (!peer_name) {
-- continue;
-- }
-+ build_lrouter_arp_flow(op->od, op,
-+ op->lrp_networks.ipv4_addrs[i].addr_s,
-+ REG_INPORT_ETH_ADDR, match, false, 90,
-+ &op->nbrp->header_, lflows);
-+ }
-
-- struct ovn_port *peer =
-- ovn_port_find(ports, peer_name);
-- if (!peer || !peer->nbrp) {
-- continue;
-- }
-+ /* A set to hold all load-balancer vips that need ARP responses. */
-+ struct sset all_ips_v4 = SSET_INITIALIZER(&all_ips_v4);
-+ struct sset all_ips_v6 = SSET_INITIALIZER(&all_ips_v6);
-+ get_router_load_balancer_ips(op->od, &all_ips_v4, &all_ips_v6);
-
-- if (!find_lrp_member_ip(peer, vip)) {
-- continue;
-- }
--
-- ds_clear(match);
-- ds_put_format(match, "outport == %s && "
-- REG_NEXT_HOP_IPV4 " == %s",
-- peer->json_key, vip);
-+ const char *ip_address;
-+ SSET_FOR_EACH (ip_address, &all_ips_v4) {
-+ ds_clear(match);
-+ if (op == op->od->l3dgw_port) {
-+ ds_put_format(match, "is_chassis_resident(%s)",
-+ op->od->l3redirect_port->json_key);
-+ }
-
-- ds_clear(actions);
-- ds_put_format(actions, "eth.dst = %s; next;", ea_s);
-- ovn_lflow_add_with_hint(lflows, peer->od,
-- S_ROUTER_IN_ARP_RESOLVE, 100,
-- ds_cstr(match),
-- ds_cstr(actions),
-- &op->nbsp->header_);
-- found_vip_network = true;
-- break;
-- }
-+ build_lrouter_arp_flow(op->od, op,
-+ ip_address, REG_INPORT_ETH_ADDR,
-+ match, false, 90, NULL, lflows);
-+ }
-
-- if (found_vip_network) {
-- break;
-- }
-+ SSET_FOR_EACH (ip_address, &all_ips_v6) {
-+ ds_clear(match);
-+ if (op == op->od->l3dgw_port) {
-+ ds_put_format(match, "is_chassis_resident(%s)",
-+ op->od->l3redirect_port->json_key);
- }
-+
-+ build_lrouter_nd_flow(op->od, op, "nd_na",
-+ ip_address, NULL, REG_INPORT_ETH_ADDR,
-+ match, false, 90, NULL, lflows);
- }
-- } else if (lsp_is_router(op->nbsp)) {
-- /* This is a logical switch port that connects to a router. */
-
-- /* The peer of this switch port is the router port for which
-- * we need to add logical flows such that it can resolve
-- * ARP entries for all the other router ports connected to
-- * the switch in question. */
-+ sset_destroy(&all_ips_v4);
-+ sset_destroy(&all_ips_v6);
-
-- const char *peer_name = smap_get(&op->nbsp->options,
-- "router-port");
-- if (!peer_name) {
-- return;
-- }
-+ if (!smap_get(&op->od->nbr->options, "chassis")
-+ && !op->od->l3dgw_port) {
-+ /* UDP/TCP port unreachable. */
-+ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
-+ ds_clear(match);
-+ ds_put_format(match,
-+ "ip4 && ip4.dst == %s && !ip.later_frag && udp",
-+ op->lrp_networks.ipv4_addrs[i].addr_s);
-+ const char *action = "icmp4 {"
-+ "eth.dst <-> eth.src; "
-+ "ip4.dst <-> ip4.src; "
-+ "ip.ttl = 255; "
-+ "icmp4.type = 3; "
-+ "icmp4.code = 3; "
-+ "next; };";
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-+ 80, ds_cstr(match), action,
-+ &op->nbrp->header_);
-
-- struct ovn_port *peer = ovn_port_find(ports, peer_name);
-- if (!peer || !peer->nbrp) {
-- return;
-+ ds_clear(match);
-+ ds_put_format(match,
-+ "ip4 && ip4.dst == %s && !ip.later_frag && tcp",
-+ op->lrp_networks.ipv4_addrs[i].addr_s);
-+ action = "tcp_reset {"
-+ "eth.dst <-> eth.src; "
-+ "ip4.dst <-> ip4.src; "
-+ "next; };";
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-+ 80, ds_cstr(match), action,
-+ &op->nbrp->header_);
-+
-+ ds_clear(match);
-+ ds_put_format(match,
-+ "ip4 && ip4.dst == %s && !ip.later_frag",
-+ op->lrp_networks.ipv4_addrs[i].addr_s);
-+ action = "icmp4 {"
-+ "eth.dst <-> eth.src; "
-+ "ip4.dst <-> ip4.src; "
-+ "ip.ttl = 255; "
-+ "icmp4.type = 3; "
-+ "icmp4.code = 2; "
-+ "next; };";
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-+ 70, ds_cstr(match), action,
-+ &op->nbrp->header_);
-+ }
- }
-
-- if (peer->od->nbr &&
-- smap_get_bool(&peer->od->nbr->options,
-- "dynamic_neigh_routers", false)) {
-+ /* Drop IP traffic destined to router owned IPs except if the IP is
-+ * also a SNAT IP. Those are dropped later, in stage
-+ * "lr_in_arp_resolve", if unSNAT was unsuccessful.
-+ *
-+ * Priority 60.
-+ */
-+ build_lrouter_drop_own_dest(op, S_ROUTER_IN_IP_INPUT, 60, false,
-+ lflows);
-+
-+ /* ARP / ND handling for external IP addresses.
-+ *
-+ * DNAT and SNAT IP addresses are external IP addresses that need ARP
-+ * handling.
-+ *
-+ * These are already taken care globally, per router. The only
-+ * exception is on the l3dgw_port where we might need to use a
-+ * different ETH address.
-+ */
-+ if (op != op->od->l3dgw_port) {
- return;
- }
-
-- for (size_t i = 0; i < op->od->n_router_ports; i++) {
-- const char *router_port_name = smap_get(
-- &op->od->router_ports[i]->nbsp->options,
-- "router-port");
-- struct ovn_port *router_port = ovn_port_find(ports,
-- router_port_name);
-- if (!router_port || !router_port->nbrp) {
-+ for (size_t i = 0; i < op->od->nbr->n_nat; i++) {
-+ struct ovn_nat *nat_entry = &op->od->nat_entries[i];
-+
-+ /* Skip entries we failed to parse. */
-+ if (!nat_entry_is_valid(nat_entry)) {
- continue;
- }
-
-- /* Skip the router port under consideration. */
-- if (router_port == peer) {
-- continue;
-+ /* Skip SNAT entries for now, we handle unique SNAT IPs separately
-+ * below.
-+ */
-+ if (!strcmp(nat_entry->nb->type, "snat")) {
-+ continue;
- }
-+ build_lrouter_port_nat_arp_nd_flow(op, nat_entry, lflows);
-+ }
-
-- if (router_port->lrp_networks.n_ipv4_addrs) {
-- ds_clear(match);
-- ds_put_format(match, "outport == %s && "
-- REG_NEXT_HOP_IPV4 " == ",
-- peer->json_key);
-- op_put_v4_networks(match, router_port, false);
-+ /* Now handle SNAT entries too, one per unique SNAT IP. */
-+ struct shash_node *snat_snode;
-+ SHASH_FOR_EACH (snat_snode, &op->od->snat_ips) {
-+ struct ovn_snat_ip *snat_ip = snat_snode->data;
-
-- ds_clear(actions);
-- ds_put_format(actions, "eth.dst = %s; next;",
-- router_port->lrp_networks.ea_s);
-- ovn_lflow_add_with_hint(lflows, peer->od,
-- S_ROUTER_IN_ARP_RESOLVE, 100,
-- ds_cstr(match), ds_cstr(actions),
-- &op->nbsp->header_);
-+ if (ovs_list_is_empty(&snat_ip->snat_entries)) {
-+ continue;
- }
-
-- if (router_port->lrp_networks.n_ipv6_addrs) {
-- ds_clear(match);
-- ds_put_format(match, "outport == %s && "
-- REG_NEXT_HOP_IPV6 " == ",
-- peer->json_key);
-- op_put_v6_networks(match, router_port);
--
-- ds_clear(actions);
-- ds_put_format(actions, "eth.dst = %s; next;",
-- router_port->lrp_networks.ea_s);
-- ovn_lflow_add_with_hint(lflows, peer->od,
-- S_ROUTER_IN_ARP_RESOLVE, 100,
-- ds_cstr(match), ds_cstr(actions),
-- &op->nbsp->header_);
-- }
-+ struct ovn_nat *nat_entry =
-+ CONTAINER_OF(ovs_list_front(&snat_ip->snat_entries),
-+ struct ovn_nat, ext_addr_list_node);
-+ build_lrouter_port_nat_arp_nd_flow(op, nat_entry, lflows);
- }
- }
--
- }
-
--/* Local router ingress table CHK_PKT_LEN: Check packet length.
-- *
-- * Any IPv4 packet with outport set to the distributed gateway
-- * router port, check the packet length and store the result in the
-- * 'REGBIT_PKT_LARGER' register bit.
-- *
-- * Local router ingress table LARGER_PKTS: Handle larger packets.
-- *
-- * Any IPv4 packet with outport set to the distributed gateway
-- * router port and the 'REGBIT_PKT_LARGER' register bit is set,
-- * generate ICMPv4 packet with type 3 (Destination Unreachable) and
-- * code 4 (Fragmentation needed).
-- * */
-+/* NAT, Defrag and load balancing. */
- static void
--build_check_pkt_len_flows_for_lrouter(
-- struct ovn_datapath *od, struct hmap *lflows,
-- struct hmap *ports,
-- struct ds *match, struct ds *actions)
-+build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od,
-+ struct hmap *lflows,
-+ struct shash *meter_groups,
-+ struct hmap *lbs,
-+ struct ds *match, struct ds *actions)
- {
- if (od->nbr) {
-
- /* Packets are allowed by default. */
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_CHK_PKT_LEN, 0, "1",
-- "next;");
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_LARGER_PKTS, 0, "1",
-- "next;");
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_DEFRAG, 0, "1", "next;");
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 0, "1", "next;");
-+ ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 0, "1", "next;");
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 0, "1", "next;");
-+ ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 0, "1", "next;");
-+ ovn_lflow_add(lflows, od, S_ROUTER_OUT_EGR_LOOP, 0, "1", "next;");
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 0, "1", "next;");
-
-- if (od->l3dgw_port && od->l3redirect_port) {
-- int gw_mtu = 0;
-- if (od->l3dgw_port->nbrp) {
-- gw_mtu = smap_get_int(&od->l3dgw_port->nbrp->options,
-- "gateway_mtu", 0);
-- }
-- /* Add the flows only if gateway_mtu is configured. */
-- if (gw_mtu <= 0) {
-- return;
-- }
-+ /* Send the IPv6 NS packets to next table. When ovn-controller
-+ * generates IPv6 NS (for the action - nd_ns{}), the injected
-+ * packet would go through conntrack - which is not required. */
-+ ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 120, "nd_ns", "next;");
-
-- ds_clear(match);
-- ds_put_format(match, "outport == %s", od->l3dgw_port->json_key);
-+ /* NAT rules are only valid on Gateway routers and routers with
-+ * l3dgw_port (router has a port with gateway chassis
-+ * specified). */
-+ if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) {
-+ return;
-+ }
-
-- ds_clear(actions);
-- ds_put_format(actions,
-- REGBIT_PKT_LARGER" = check_pkt_larger(%d);"
-- " next;", gw_mtu + VLAN_ETH_HEADER_LEN);
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_CHK_PKT_LEN, 50,
-- ds_cstr(match), ds_cstr(actions),
-- &od->l3dgw_port->nbrp->header_);
-+ struct sset nat_entries = SSET_INITIALIZER(&nat_entries);
-
-- for (size_t i = 0; i < od->nbr->n_ports; i++) {
-- struct ovn_port *rp = ovn_port_find(ports,
-- od->nbr->ports[i]->name);
-- if (!rp || rp == od->l3dgw_port) {
-- continue;
-- }
-+ bool dnat_force_snat_ip =
-+ !lport_addresses_is_empty(&od->dnat_force_snat_addrs);
-+ bool lb_force_snat_ip =
-+ !lport_addresses_is_empty(&od->lb_force_snat_addrs);
-
-- if (rp->lrp_networks.ipv4_addrs) {
-- ds_clear(match);
-- ds_put_format(match, "inport == %s && outport == %s"
-- " && ip4 && "REGBIT_PKT_LARGER,
-- rp->json_key, od->l3dgw_port->json_key);
-+ for (int i = 0; i < od->nbr->n_nat; i++) {
-+ const struct nbrec_nat *nat;
-
-- ds_clear(actions);
-- /* Set icmp4.frag_mtu to gw_mtu */
-- ds_put_format(actions,
-- "icmp4_error {"
-- REGBIT_EGRESS_LOOPBACK" = 1; "
-- "eth.dst = %s; "
-- "ip4.dst = ip4.src; "
-- "ip4.src = %s; "
-- "ip.ttl = 255; "
-- "icmp4.type = 3; /* Destination Unreachable. */ "
-- "icmp4.code = 4; /* Frag Needed and DF was Set. */ "
-- "icmp4.frag_mtu = %d; "
-- "next(pipeline=ingress, table=%d); };",
-- rp->lrp_networks.ea_s,
-- rp->lrp_networks.ipv4_addrs[0].addr_s,
-- gw_mtu,
-- ovn_stage_get_table(S_ROUTER_IN_ADMISSION));
-- ovn_lflow_add_with_hint(lflows, od,
-- S_ROUTER_IN_LARGER_PKTS, 50,
-- ds_cstr(match), ds_cstr(actions),
-- &rp->nbrp->header_);
-- }
-+ nat = od->nbr->nat[i];
-
-- if (rp->lrp_networks.ipv6_addrs) {
-- ds_clear(match);
-- ds_put_format(match, "inport == %s && outport == %s"
-- " && ip6 && "REGBIT_PKT_LARGER,
-- rp->json_key, od->l3dgw_port->json_key);
-+ ovs_be32 ip, mask;
-+ struct in6_addr ipv6, mask_v6, v6_exact = IN6ADDR_EXACT_INIT;
-+ bool is_v6 = false;
-+ bool stateless = lrouter_nat_is_stateless(nat);
-+ struct nbrec_address_set *allowed_ext_ips =
-+ nat->allowed_ext_ips;
-+ struct nbrec_address_set *exempted_ext_ips =
-+ nat->exempted_ext_ips;
-
-- ds_clear(actions);
-- /* Set icmp6.frag_mtu to gw_mtu */
-- ds_put_format(actions,
-- "icmp6_error {"
-- REGBIT_EGRESS_LOOPBACK" = 1; "
-- "eth.dst = %s; "
-- "ip6.dst = ip6.src; "
-- "ip6.src = %s; "
-- "ip.ttl = 255; "
-- "icmp6.type = 2; /* Packet Too Big. */ "
-- "icmp6.code = 0; "
-- "icmp6.frag_mtu = %d; "
-- "next(pipeline=ingress, table=%d); };",
-- rp->lrp_networks.ea_s,
-- rp->lrp_networks.ipv6_addrs[0].addr_s,
-- gw_mtu,
-- ovn_stage_get_table(S_ROUTER_IN_ADMISSION));
-- ovn_lflow_add_with_hint(lflows, od,
-- S_ROUTER_IN_LARGER_PKTS, 50,
-- ds_cstr(match), ds_cstr(actions),
-- &rp->nbrp->header_);
-- }
-+ if (allowed_ext_ips && exempted_ext_ips) {
-+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-+ VLOG_WARN_RL(&rl, "NAT rule: "UUID_FMT" not applied, since "
-+ "both allowed and exempt external ips set",
-+ UUID_ARGS(&(nat->header_.uuid)));
-+ continue;
- }
-- }
-- }
--}
-
--/* Logical router ingress table GW_REDIRECT: Gateway redirect.
-- *
-- * For traffic with outport equal to the l3dgw_port
-- * on a distributed router, this table redirects a subset
-- * of the traffic to the l3redirect_port which represents
-- * the central instance of the l3dgw_port.
-- */
--static void
--build_gateway_redirect_flows_for_lrouter(
-- struct ovn_datapath *od, struct hmap *lflows,
-- struct ds *match, struct ds *actions)
--{
-- if (od->nbr) {
-- if (od->l3dgw_port && od->l3redirect_port) {
-- const struct ovsdb_idl_row *stage_hint = NULL;
-+ char *error = ip_parse_masked(nat->external_ip, &ip, &mask);
-+ if (error || mask != OVS_BE32_MAX) {
-+ free(error);
-+ error = ipv6_parse_masked(nat->external_ip, &ipv6, &mask_v6);
-+ if (error || memcmp(&mask_v6, &v6_exact, sizeof(mask_v6))) {
-+ /* Invalid for both IPv4 and IPv6 */
-+ static struct vlog_rate_limit rl =
-+ VLOG_RATE_LIMIT_INIT(5, 1);
-+ VLOG_WARN_RL(&rl, "bad external ip %s for nat",
-+ nat->external_ip);
-+ free(error);
-+ continue;
-+ }
-+ /* It was an invalid IPv4 address, but valid IPv6.
-+ * Treat the rest of the handling of this NAT rule
-+ * as IPv6. */
-+ is_v6 = true;
-+ }
-
-- if (od->l3dgw_port->nbrp) {
-- stage_hint = &od->l3dgw_port->nbrp->header_;
-+ /* Check the validity of nat->logical_ip. 'logical_ip' can
-+ * be a subnet when the type is "snat". */
-+ int cidr_bits;
-+ if (is_v6) {
-+ error = ipv6_parse_masked(nat->logical_ip, &ipv6, &mask_v6);
-+ cidr_bits = ipv6_count_cidr_bits(&mask_v6);
-+ } else {
-+ error = ip_parse_masked(nat->logical_ip, &ip, &mask);
-+ cidr_bits = ip_count_cidr_bits(mask);
-+ }
-+ if (!strcmp(nat->type, "snat")) {
-+ if (error) {
-+ /* Invalid for both IPv4 and IPv6 */
-+ static struct vlog_rate_limit rl =
-+ VLOG_RATE_LIMIT_INIT(5, 1);
-+ VLOG_WARN_RL(&rl, "bad ip network or ip %s for snat "
-+ "in router "UUID_FMT"",
-+ nat->logical_ip, UUID_ARGS(&od->key));
-+ free(error);
-+ continue;
-+ }
-+ } else {
-+ if (error || (!is_v6 && mask != OVS_BE32_MAX)
-+ || (is_v6 && memcmp(&mask_v6, &v6_exact,
-+ sizeof mask_v6))) {
-+ /* Invalid for both IPv4 and IPv6 */
-+ static struct vlog_rate_limit rl =
-+ VLOG_RATE_LIMIT_INIT(5, 1);
-+ VLOG_WARN_RL(&rl, "bad ip %s for dnat in router "
-+ ""UUID_FMT"", nat->logical_ip, UUID_ARGS(&od->key));
-+ free(error);
-+ continue;
-+ }
- }
-
-- /* For traffic with outport == l3dgw_port, if the
-- * packet did not match any higher priority redirect
-- * rule, then the traffic is redirected to the central
-- * instance of the l3dgw_port. */
-- ds_clear(match);
-- ds_put_format(match, "outport == %s",
-- od->l3dgw_port->json_key);
-- ds_clear(actions);
-- ds_put_format(actions, "outport = %s; next;",
-- od->l3redirect_port->json_key);
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_GW_REDIRECT, 50,
-- ds_cstr(match), ds_cstr(actions),
-- stage_hint);
-- }
-+ /* For distributed router NAT, determine whether this NAT rule
-+ * satisfies the conditions for distributed NAT processing. */
-+ bool distributed = false;
-+ struct eth_addr mac;
-+ if (od->l3dgw_port && !strcmp(nat->type, "dnat_and_snat") &&
-+ nat->logical_port && nat->external_mac) {
-+ if (eth_addr_from_string(nat->external_mac, &mac)) {
-+ distributed = true;
-+ } else {
-+ static struct vlog_rate_limit rl =
-+ VLOG_RATE_LIMIT_INIT(5, 1);
-+ VLOG_WARN_RL(&rl, "bad mac %s for dnat in router "
-+ ""UUID_FMT"", nat->external_mac, UUID_ARGS(&od->key));
-+ continue;
-+ }
-+ }
-
-- /* Packets are allowed by default. */
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_GW_REDIRECT, 0, "1", "next;");
-- }
--}
-+ /* Ingress UNSNAT table: It is for already established connections'
-+ * reverse traffic. i.e., SNAT has already been done in egress
-+ * pipeline and now the packet has entered the ingress pipeline as
-+ * part of a reply. We undo the SNAT here.
-+ *
-+ * Undoing SNAT has to happen before DNAT processing. This is
-+ * because when the packet was DNATed in ingress pipeline, it did
-+ * not know about the possibility of eventual additional SNAT in
-+ * egress pipeline. */
-+ if (!strcmp(nat->type, "snat")
-+ || !strcmp(nat->type, "dnat_and_snat")) {
-+ if (!od->l3dgw_port) {
-+ /* Gateway router. */
-+ ds_clear(match);
-+ ds_clear(actions);
-+ ds_put_format(match, "ip && ip%s.dst == %s",
-+ is_v6 ? "6" : "4",
-+ nat->external_ip);
-+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-+ ds_put_format(actions, "ip%s.dst=%s; next;",
-+ is_v6 ? "6" : "4", nat->logical_ip);
-+ } else {
-+ ds_put_cstr(actions, "ct_snat;");
-+ }
-
--/* Local router ingress table ARP_REQUEST: ARP request.
-- *
-- * In the common case where the Ethernet destination has been resolved,
-- * this table outputs the packet (priority 0). Otherwise, it composes
-- * and sends an ARP/IPv6 NA request (priority 100). */
--static void
--build_arp_request_flows_for_lrouter(
-- struct ovn_datapath *od, struct hmap *lflows,
-- struct ds *match, struct ds *actions)
--{
-- if (od->nbr) {
-- for (int i = 0; i < od->nbr->n_static_routes; i++) {
-- const struct nbrec_logical_router_static_route *route;
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT,
-+ 90, ds_cstr(match),
-+ ds_cstr(actions),
-+ &nat->header_);
-+ } else {
-+ /* Distributed router. */
-
-- route = od->nbr->static_routes[i];
-- struct in6_addr gw_ip6;
-- unsigned int plen;
-- char *error = ipv6_parse_cidr(route->nexthop, &gw_ip6, &plen);
-- if (error || plen != 128) {
-- free(error);
-- continue;
-- }
-+ /* Traffic received on l3dgw_port is subject to NAT. */
-+ ds_clear(match);
-+ ds_clear(actions);
-+ ds_put_format(match, "ip && ip%s.dst == %s"
-+ " && inport == %s",
-+ is_v6 ? "6" : "4",
-+ nat->external_ip,
-+ od->l3dgw_port->json_key);
-+ if (!distributed && od->l3redirect_port) {
-+ /* Flows for NAT rules that are centralized are only
-+ * programmed on the gateway chassis. */
-+ ds_put_format(match, " && is_chassis_resident(%s)",
-+ od->l3redirect_port->json_key);
-+ }
-
-- ds_clear(match);
-- ds_put_format(match, "eth.dst == 00:00:00:00:00:00 && "
-- "ip6 && " REG_NEXT_HOP_IPV6 " == %s",
-- route->nexthop);
-- struct in6_addr sn_addr;
-- struct eth_addr eth_dst;
-- in6_addr_solicited_node(&sn_addr, &gw_ip6);
-- ipv6_multicast_to_ethernet(ð_dst, &sn_addr);
-+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-+ ds_put_format(actions, "ip%s.dst=%s; next;",
-+ is_v6 ? "6" : "4", nat->logical_ip);
-+ } else {
-+ ds_put_cstr(actions, "ct_snat;");
-+ }
-
-- char sn_addr_s[INET6_ADDRSTRLEN + 1];
-- ipv6_string_mapped(sn_addr_s, &sn_addr);
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT,
-+ 100,
-+ ds_cstr(match), ds_cstr(actions),
-+ &nat->header_);
-+ }
-+ }
-
-- ds_clear(actions);
-- ds_put_format(actions,
-- "nd_ns { "
-- "eth.dst = "ETH_ADDR_FMT"; "
-- "ip6.dst = %s; "
-- "nd.target = %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,
-- ds_cstr(match), ds_cstr(actions),
-- &route->header_);
-- }
--
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 100,
-- "eth.dst == 00:00:00:00:00:00 && ip4",
-- "arp { "
-- "eth.dst = ff:ff:ff:ff:ff:ff; "
-- "arp.spa = " REG_SRC_IPV4 "; "
-- "arp.tpa = " REG_NEXT_HOP_IPV4 "; "
-- "arp.op = 1; " /* ARP request */
-- "output; "
-- "};");
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 100,
-- "eth.dst == 00:00:00:00:00:00 && ip6",
-- "nd_ns { "
-- "nd.target = " REG_NEXT_HOP_IPV6 "; "
-- "output; "
-- "};");
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 0, "1", "output;");
-- }
--}
--
--/* Logical router egress table DELIVERY: Delivery (priority 100-110).
-- *
-- * Priority 100 rules deliver packets to enabled logical ports.
-- * Priority 110 rules match multicast packets and update the source
-- * mac before delivering to enabled logical ports. IP multicast traffic
-- * bypasses S_ROUTER_IN_IP_ROUTING route lookups.
-- */
--static void
--build_egress_delivery_flows_for_lrouter_port(
-- struct ovn_port *op, struct hmap *lflows,
-- struct ds *match, struct ds *actions)
--{
-- if (op->nbrp) {
-- if (!lrport_is_enabled(op->nbrp)) {
-- /* Drop packets to disabled logical ports (since logical flow
-- * tables are default-drop). */
-- return;
-- }
--
-- if (op->derived) {
-- /* No egress packets should be processed in the context of
-- * a chassisredirect port. The chassisredirect port should
-- * be replaced by the l3dgw port in the local output
-- * pipeline stage before egress processing. */
-- return;
-- }
--
-- /* If multicast relay is enabled then also adjust source mac for IP
-- * multicast traffic.
-- */
-- if (op->od->mcast_info.rtr.relay) {
-- ds_clear(match);
-- ds_clear(actions);
-- ds_put_format(match, "(ip4.mcast || ip6.mcast) && outport == %s",
-- op->json_key);
-- ds_put_format(actions, "eth.src = %s; output;",
-- op->lrp_networks.ea_s);
-- ovn_lflow_add(lflows, op->od, S_ROUTER_OUT_DELIVERY, 110,
-- ds_cstr(match), ds_cstr(actions));
-- }
--
-- ds_clear(match);
-- ds_put_format(match, "outport == %s", op->json_key);
-- ovn_lflow_add(lflows, op->od, S_ROUTER_OUT_DELIVERY, 100,
-- ds_cstr(match), "output;");
-- }
--
--}
--
--static void
--build_misc_local_traffic_drop_flows_for_lrouter(
-- struct ovn_datapath *od, struct hmap *lflows)
--{
-- if (od->nbr) {
-- /* L3 admission control: drop multicast and broadcast source, localhost
-- * source or destination, and zero network source or destination
-- * (priority 100). */
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 100,
-- "ip4.src_mcast ||"
-- "ip4.src == 255.255.255.255 || "
-- "ip4.src == 127.0.0.0/8 || "
-- "ip4.dst == 127.0.0.0/8 || "
-- "ip4.src == 0.0.0.0/8 || "
-- "ip4.dst == 0.0.0.0/8",
-- "drop;");
--
-- /* Drop ARP packets (priority 85). ARP request packets for router's own
-- * IPs are handled with priority-90 flows.
-- * Drop IPv6 ND packets (priority 85). ND NA packets for router's own
-- * IPs are handled with priority-90 flows.
-- */
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 85,
-- "arp || nd", "drop;");
--
-- /* Allow IPv6 multicast traffic that's supposed to reach the
-- * router pipeline (e.g., router solicitations).
-- */
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 84, "nd_rs || nd_ra",
-- "next;");
--
-- /* Drop other reserved multicast. */
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 83,
-- "ip6.mcast_rsvd", "drop;");
--
-- /* Allow other multicast if relay enabled (priority 82). */
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 82,
-- "ip4.mcast || ip6.mcast",
-- od->mcast_info.rtr.relay ? "next;" : "drop;");
--
-- /* Drop Ethernet local broadcast. By definition this traffic should
-- * not be forwarded.*/
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 50,
-- "eth.bcast", "drop;");
-+ /* Ingress DNAT table: Packets enter the pipeline with destination
-+ * IP address that needs to be DNATted from a external IP address
-+ * to a logical IP address. */
-+ if (!strcmp(nat->type, "dnat")
-+ || !strcmp(nat->type, "dnat_and_snat")) {
-+ if (!od->l3dgw_port) {
-+ /* Gateway router. */
-+ /* Packet when it goes from the initiator to destination.
-+ * We need to set flags.loopback because the router can
-+ * send the packet back through the same interface. */
-+ ds_clear(match);
-+ ds_put_format(match, "ip && ip%s.dst == %s",
-+ is_v6 ? "6" : "4",
-+ nat->external_ip);
-+ ds_clear(actions);
-+ if (allowed_ext_ips || exempted_ext_ips) {
-+ lrouter_nat_add_ext_ip_match(od, lflows, match, nat,
-+ is_v6, true, mask);
-+ }
-
-- /* TTL discard */
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 30,
-- "ip4 && ip.ttl == {0, 1}", "drop;");
-+ if (dnat_force_snat_ip) {
-+ /* Indicate to the future tables that a DNAT has taken
-+ * place and a force SNAT needs to be done in the
-+ * Egress SNAT table. */
-+ ds_put_format(actions,
-+ "flags.force_snat_for_dnat = 1; ");
-+ }
-
-- /* Pass other traffic not already handled to the next table for
-- * routing. */
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 0, "1", "next;");
-- }
--}
-+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-+ ds_put_format(actions, "flags.loopback = 1; "
-+ "ip%s.dst=%s; next;",
-+ is_v6 ? "6" : "4", nat->logical_ip);
-+ } else {
-+ ds_put_format(actions, "flags.loopback = 1; "
-+ "ct_dnat(%s", nat->logical_ip);
-
--static void
--build_dhcpv6_reply_flows_for_lrouter_port(
-- struct ovn_port *op, struct hmap *lflows,
-- struct ds *match)
--{
-- if (op->nbrp && (!op->derived)) {
-- for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
-- ds_clear(match);
-- ds_put_format(match, "ip6.dst == %s && udp.src == 547 &&"
-- " udp.dst == 546",
-- op->lrp_networks.ipv6_addrs[i].addr_s);
-- ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 100,
-- ds_cstr(match),
-- "reg0 = 0; handle_dhcpv6_reply;");
-- }
-- }
-+ if (nat->external_port_range[0]) {
-+ ds_put_format(actions, ",%s",
-+ nat->external_port_range);
-+ }
-+ ds_put_format(actions, ");");
-+ }
-
--}
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100,
-+ ds_cstr(match), ds_cstr(actions),
-+ &nat->header_);
-+ } else {
-+ /* Distributed router. */
-
--static void
--build_ipv6_input_flows_for_lrouter_port(
-- struct ovn_port *op, struct hmap *lflows,
-- struct ds *match, struct ds *actions)
--{
-- if (op->nbrp && (!op->derived)) {
-- /* No ingress packets are accepted on a chassisredirect
-- * port, so no need to program flows for that port. */
-- if (op->lrp_networks.n_ipv6_addrs) {
-- /* ICMPv6 echo reply. These flows reply to echo requests
-- * received for the router's IP address. */
-- ds_clear(match);
-- ds_put_cstr(match, "ip6.dst == ");
-- op_put_v6_networks(match, op);
-- ds_put_cstr(match, " && icmp6.type == 128 && icmp6.code == 0");
-+ /* Traffic received on l3dgw_port is subject to NAT. */
-+ ds_clear(match);
-+ ds_put_format(match, "ip && ip%s.dst == %s"
-+ " && inport == %s",
-+ is_v6 ? "6" : "4",
-+ nat->external_ip,
-+ od->l3dgw_port->json_key);
-+ if (!distributed && od->l3redirect_port) {
-+ /* Flows for NAT rules that are centralized are only
-+ * programmed on the gateway chassis. */
-+ ds_put_format(match, " && is_chassis_resident(%s)",
-+ od->l3redirect_port->json_key);
-+ }
-+ ds_clear(actions);
-+ if (allowed_ext_ips || exempted_ext_ips) {
-+ lrouter_nat_add_ext_ip_match(od, lflows, match, nat,
-+ is_v6, true, mask);
-+ }
-
-- const char *lrp_actions =
-- "ip6.dst <-> ip6.src; "
-- "ip.ttl = 255; "
-- "icmp6.type = 129; "
-- "flags.loopback = 1; "
-- "next; ";
-- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90,
-- ds_cstr(match), lrp_actions,
-- &op->nbrp->header_);
-- }
-+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-+ ds_put_format(actions, "ip%s.dst=%s; next;",
-+ is_v6 ? "6" : "4", nat->logical_ip);
-+ } else {
-+ ds_put_format(actions, "ct_dnat(%s", nat->logical_ip);
-+ if (nat->external_port_range[0]) {
-+ ds_put_format(actions, ",%s",
-+ nat->external_port_range);
-+ }
-+ ds_put_format(actions, ");");
-+ }
-
-- /* ND reply. These flows reply to ND solicitations for the
-- * router's own IP address. */
-- for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
-- ds_clear(match);
-- if (op->od->l3dgw_port && op == op->od->l3dgw_port
-- && op->od->l3redirect_port) {
-- /* Traffic with eth.src = l3dgw_port->lrp_networks.ea_s
-- * should only be sent from the gateway chassi, so that
-- * upstream MAC learning points to the gateway chassis.
-- * Also need to avoid generation of multiple ND replies
-- * from different chassis. */
-- ds_put_format(match, "is_chassis_resident(%s)",
-- op->od->l3redirect_port->json_key);
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100,
-+ ds_cstr(match), ds_cstr(actions),
-+ &nat->header_);
-+ }
- }
-
-- build_lrouter_nd_flow(op->od, op, "nd_na_router",
-- op->lrp_networks.ipv6_addrs[i].addr_s,
-- op->lrp_networks.ipv6_addrs[i].sn_addr_s,
-- REG_INPORT_ETH_ADDR, match, false, 90,
-- &op->nbrp->header_, lflows);
-- }
--
-- /* UDP/TCP port unreachable */
-- if (!smap_get(&op->od->nbr->options, "chassis")
-- && !op->od->l3dgw_port) {
-- for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
-- ds_clear(match);
-- ds_put_format(match,
-- "ip6 && ip6.dst == %s && !ip.later_frag && tcp",
-- op->lrp_networks.ipv6_addrs[i].addr_s);
-- const char *action = "tcp_reset {"
-- "eth.dst <-> eth.src; "
-- "ip6.dst <-> ip6.src; "
-- "next; };";
-- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-- 80, ds_cstr(match), action,
-- &op->nbrp->header_);
--
-- ds_clear(match);
-- ds_put_format(match,
-- "ip6 && ip6.dst == %s && !ip.later_frag && udp",
-- op->lrp_networks.ipv6_addrs[i].addr_s);
-- action = "icmp6 {"
-- "eth.dst <-> eth.src; "
-- "ip6.dst <-> ip6.src; "
-- "ip.ttl = 255; "
-- "icmp6.type = 1; "
-- "icmp6.code = 4; "
-- "next; };";
-- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-- 80, ds_cstr(match), action,
-- &op->nbrp->header_);
--
-- ds_clear(match);
-- ds_put_format(match,
-- "ip6 && ip6.dst == %s && !ip.later_frag",
-- op->lrp_networks.ipv6_addrs[i].addr_s);
-- action = "icmp6 {"
-- "eth.dst <-> eth.src; "
-- "ip6.dst <-> ip6.src; "
-- "ip.ttl = 255; "
-- "icmp6.type = 1; "
-- "icmp6.code = 3; "
-- "next; };";
-- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-- 70, ds_cstr(match), action,
-- &op->nbrp->header_);
-- }
-- }
-+ /* ARP resolve for NAT IPs. */
-+ if (od->l3dgw_port) {
-+ if (!strcmp(nat->type, "snat")) {
-+ ds_clear(match);
-+ ds_put_format(
-+ match, "inport == %s && %s == %s",
-+ od->l3dgw_port->json_key,
-+ is_v6 ? "ip6.src" : "ip4.src", nat->external_ip);
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_IP_INPUT,
-+ 120, ds_cstr(match), "next;",
-+ &nat->header_);
-+ }
-
-- /* ICMPv6 time exceeded */
-- for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
-- /* skip link-local address */
-- if (in6_is_lla(&op->lrp_networks.ipv6_addrs[i].network)) {
-- continue;
-+ if (!sset_contains(&nat_entries, nat->external_ip)) {
-+ ds_clear(match);
-+ ds_put_format(
-+ match, "outport == %s && %s == %s",
-+ od->l3dgw_port->json_key,
-+ is_v6 ? REG_NEXT_HOP_IPV6 : REG_NEXT_HOP_IPV4,
-+ nat->external_ip);
-+ ds_clear(actions);
-+ ds_put_format(
-+ actions, "eth.dst = %s; next;",
-+ distributed ? nat->external_mac :
-+ od->l3dgw_port->lrp_networks.ea_s);
-+ ovn_lflow_add_with_hint(lflows, od,
-+ S_ROUTER_IN_ARP_RESOLVE,
-+ 100, ds_cstr(match),
-+ ds_cstr(actions),
-+ &nat->header_);
-+ sset_add(&nat_entries, nat->external_ip);
-+ }
-+ } else {
-+ /* Add the NAT external_ip to the nat_entries even for
-+ * gateway routers. This is required for adding load balancer
-+ * flows.*/
-+ sset_add(&nat_entries, nat->external_ip);
- }
-
-- ds_clear(match);
-- ds_clear(actions);
--
-- ds_put_format(match,
-- "inport == %s && ip6 && "
-- "ip6.src == %s/%d && "
-- "ip.ttl == {0, 1} && !ip.later_frag",
-- op->json_key,
-- op->lrp_networks.ipv6_addrs[i].network_s,
-- op->lrp_networks.ipv6_addrs[i].plen);
-- ds_put_format(actions,
-- "icmp6 {"
-- "eth.dst <-> eth.src; "
-- "ip6.dst = ip6.src; "
-- "ip6.src = %s; "
-- "ip.ttl = 255; "
-- "icmp6.type = 3; /* Time exceeded */ "
-- "icmp6.code = 0; /* TTL exceeded in transit */ "
-- "next; };",
-- op->lrp_networks.ipv6_addrs[i].addr_s);
-- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 40,
-- ds_cstr(match), ds_cstr(actions),
-- &op->nbrp->header_);
-- }
-- }
-+ /* Egress UNDNAT table: It is for already established connections'
-+ * reverse traffic. i.e., DNAT has already been done in ingress
-+ * pipeline and now the packet has entered the egress pipeline as
-+ * part of a reply. We undo the DNAT here.
-+ *
-+ * Note that this only applies for NAT on a distributed router.
-+ * Undo DNAT on a gateway router is done in the ingress DNAT
-+ * pipeline stage. */
-+ if (od->l3dgw_port && (!strcmp(nat->type, "dnat")
-+ || !strcmp(nat->type, "dnat_and_snat"))) {
-+ ds_clear(match);
-+ ds_put_format(match, "ip && ip%s.src == %s"
-+ " && outport == %s",
-+ is_v6 ? "6" : "4",
-+ nat->logical_ip,
-+ od->l3dgw_port->json_key);
-+ if (!distributed && od->l3redirect_port) {
-+ /* Flows for NAT rules that are centralized are only
-+ * programmed on the gateway chassis. */
-+ ds_put_format(match, " && is_chassis_resident(%s)",
-+ od->l3redirect_port->json_key);
-+ }
-+ ds_clear(actions);
-+ if (distributed) {
-+ ds_put_format(actions, "eth.src = "ETH_ADDR_FMT"; ",
-+ ETH_ADDR_ARGS(mac));
-+ }
-
--}
-+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-+ ds_put_format(actions, "ip%s.src=%s; next;",
-+ is_v6 ? "6" : "4", nat->external_ip);
-+ } else {
-+ ds_put_format(actions, "ct_dnat;");
-+ }
-
--static void
--build_lrouter_arp_nd_for_datapath(struct ovn_datapath *od,
-- struct hmap *lflows)
--{
-- if (od->nbr) {
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 100,
-+ ds_cstr(match), ds_cstr(actions),
-+ &nat->header_);
-+ }
-
-- /* Priority-90-92 flows handle ARP requests and ND packets. Most are
-- * per logical port but DNAT addresses can be handled per datapath
-- * for non gateway router ports.
-- *
-- * Priority 91 and 92 flows are added for each gateway router
-- * port to handle the special cases. In case we get the packet
-- * on a regular port, just reply with the port's ETH address.
-- */
-- for (int i = 0; i < od->nbr->n_nat; i++) {
-- struct ovn_nat *nat_entry = &od->nat_entries[i];
-+ /* Egress SNAT table: Packets enter the egress pipeline with
-+ * source ip address that needs to be SNATted to a external ip
-+ * address. */
-+ if (!strcmp(nat->type, "snat")
-+ || !strcmp(nat->type, "dnat_and_snat")) {
-+ if (!od->l3dgw_port) {
-+ /* Gateway router. */
-+ ds_clear(match);
-+ ds_put_format(match, "ip && ip%s.src == %s",
-+ is_v6 ? "6" : "4",
-+ nat->logical_ip);
-+ ds_clear(actions);
-
-- /* Skip entries we failed to parse. */
-- if (!nat_entry_is_valid(nat_entry)) {
-- continue;
-- }
-+ if (allowed_ext_ips || exempted_ext_ips) {
-+ lrouter_nat_add_ext_ip_match(od, lflows, match, nat,
-+ is_v6, false, mask);
-+ }
-
-- /* Skip SNAT entries for now, we handle unique SNAT IPs separately
-- * below.
-- */
-- if (!strcmp(nat_entry->nb->type, "snat")) {
-- continue;
-- }
-- build_lrouter_nat_arp_nd_flow(od, nat_entry, lflows);
-- }
-+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-+ ds_put_format(actions, "ip%s.src=%s; next;",
-+ is_v6 ? "6" : "4", nat->external_ip);
-+ } else {
-+ ds_put_format(actions, "ct_snat(%s",
-+ nat->external_ip);
-
-- /* Now handle SNAT entries too, one per unique SNAT IP. */
-- struct shash_node *snat_snode;
-- SHASH_FOR_EACH (snat_snode, &od->snat_ips) {
-- struct ovn_snat_ip *snat_ip = snat_snode->data;
-+ if (nat->external_port_range[0]) {
-+ ds_put_format(actions, ",%s",
-+ nat->external_port_range);
-+ }
-+ ds_put_format(actions, ");");
-+ }
-
-- if (ovs_list_is_empty(&snat_ip->snat_entries)) {
-- continue;
-- }
-+ /* The priority here is calculated such that the
-+ * nat->logical_ip with the longest mask gets a higher
-+ * priority. */
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT,
-+ cidr_bits + 1,
-+ ds_cstr(match), ds_cstr(actions),
-+ &nat->header_);
-+ } else {
-+ uint16_t priority = cidr_bits + 1;
-
-- struct ovn_nat *nat_entry =
-- CONTAINER_OF(ovs_list_front(&snat_ip->snat_entries),
-- struct ovn_nat, ext_addr_list_node);
-- build_lrouter_nat_arp_nd_flow(od, nat_entry, lflows);
-- }
-- }
--}
-+ /* Distributed router. */
-+ ds_clear(match);
-+ ds_put_format(match, "ip && ip%s.src == %s"
-+ " && outport == %s",
-+ is_v6 ? "6" : "4",
-+ nat->logical_ip,
-+ od->l3dgw_port->json_key);
-+ if (!distributed && od->l3redirect_port) {
-+ /* Flows for NAT rules that are centralized are only
-+ * programmed on the gateway chassis. */
-+ priority += 128;
-+ ds_put_format(match, " && is_chassis_resident(%s)",
-+ od->l3redirect_port->json_key);
-+ }
-+ ds_clear(actions);
-
--/* Logical router ingress table 3: IP Input for IPv4. */
--static void
--build_lrouter_ipv4_ip_input(struct ovn_port *op,
-- struct hmap *lflows,
-- struct ds *match, struct ds *actions)
--{
-- /* No ingress packets are accepted on a chassisredirect
-- * port, so no need to program flows for that port. */
-- if (op->nbrp && (!op->derived)) {
-- if (op->lrp_networks.n_ipv4_addrs) {
-- /* L3 admission control: drop packets that originate from an
-- * IPv4 address owned by the router or a broadcast address
-- * known to the router (priority 100). */
-- ds_clear(match);
-- ds_put_cstr(match, "ip4.src == ");
-- op_put_v4_networks(match, op, true);
-- ds_put_cstr(match, " && "REGBIT_EGRESS_LOOPBACK" == 0");
-- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 100,
-- ds_cstr(match), "drop;",
-- &op->nbrp->header_);
-+ if (allowed_ext_ips || exempted_ext_ips) {
-+ lrouter_nat_add_ext_ip_match(od, lflows, match, nat,
-+ is_v6, false, mask);
-+ }
-
-- /* ICMP echo reply. These flows reply to ICMP echo requests
-- * received for the router's IP address. Since packets only
-- * get here as part of the logical router datapath, the inport
-- * (i.e. the incoming locally attached net) does not matter.
-- * The ip.ttl also does not matter (RFC1812 section 4.2.2.9) */
-- ds_clear(match);
-- ds_put_cstr(match, "ip4.dst == ");
-- op_put_v4_networks(match, op, false);
-- ds_put_cstr(match, " && icmp4.type == 8 && icmp4.code == 0");
-+ if (distributed) {
-+ ds_put_format(actions, "eth.src = "ETH_ADDR_FMT"; ",
-+ ETH_ADDR_ARGS(mac));
-+ }
-
-- const char * icmp_actions = "ip4.dst <-> ip4.src; "
-- "ip.ttl = 255; "
-- "icmp4.type = 0; "
-- "flags.loopback = 1; "
-- "next; ";
-- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90,
-- ds_cstr(match), icmp_actions,
-- &op->nbrp->header_);
-- }
-+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-+ ds_put_format(actions, "ip%s.src=%s; next;",
-+ is_v6 ? "6" : "4", nat->external_ip);
-+ } else {
-+ ds_put_format(actions, "ct_snat(%s",
-+ nat->external_ip);
-+ if (nat->external_port_range[0]) {
-+ ds_put_format(actions, ",%s",
-+ nat->external_port_range);
-+ }
-+ ds_put_format(actions, ");");
-+ }
-
-- /* ICMP time exceeded */
-- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
-- ds_clear(match);
-- ds_clear(actions);
-+ /* The priority here is calculated such that the
-+ * nat->logical_ip with the longest mask gets a higher
-+ * priority. */
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT,
-+ priority, ds_cstr(match),
-+ ds_cstr(actions),
-+ &nat->header_);
-+ }
-+ }
-
-- ds_put_format(match,
-- "inport == %s && ip4 && "
-- "ip.ttl == {0, 1} && !ip.later_frag", op->json_key);
-- ds_put_format(actions,
-- "icmp4 {"
-- "eth.dst <-> eth.src; "
-- "icmp4.type = 11; /* Time exceeded */ "
-- "icmp4.code = 0; /* TTL exceeded in transit */ "
-- "ip4.dst = ip4.src; "
-- "ip4.src = %s; "
-- "ip.ttl = 255; "
-- "next; };",
-- op->lrp_networks.ipv4_addrs[i].addr_s);
-- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 40,
-- ds_cstr(match), ds_cstr(actions),
-- &op->nbrp->header_);
-- }
-+ /* Logical router ingress table 0:
-+ * For NAT on a distributed router, add rules allowing
-+ * ingress traffic with eth.dst matching nat->external_mac
-+ * on the l3dgw_port instance where nat->logical_port is
-+ * resident. */
-+ if (distributed) {
-+ /* Store the ethernet address of the port receiving the packet.
-+ * This will save us from having to match on inport further
-+ * down in the pipeline.
-+ */
-+ ds_clear(actions);
-+ ds_put_format(actions, REG_INPORT_ETH_ADDR " = %s; next;",
-+ od->l3dgw_port->lrp_networks.ea_s);
-
-- /* ARP reply. These flows reply to ARP requests for the router's own
-- * IP address. */
-- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
-- ds_clear(match);
-- ds_put_format(match, "arp.spa == %s/%u",
-- op->lrp_networks.ipv4_addrs[i].network_s,
-- op->lrp_networks.ipv4_addrs[i].plen);
-+ ds_clear(match);
-+ ds_put_format(match,
-+ "eth.dst == "ETH_ADDR_FMT" && inport == %s"
-+ " && is_chassis_resident(\"%s\")",
-+ ETH_ADDR_ARGS(mac),
-+ od->l3dgw_port->json_key,
-+ nat->logical_port);
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ADMISSION, 50,
-+ ds_cstr(match), ds_cstr(actions),
-+ &nat->header_);
-+ }
-
-- if (op->od->l3dgw_port && op->od->l3redirect_port && op->peer
-- && op->peer->od->n_localnet_ports) {
-- bool add_chassis_resident_check = false;
-- if (op == op->od->l3dgw_port) {
-- /* Traffic with eth.src = l3dgw_port->lrp_networks.ea_s
-- * should only be sent from the gateway chassis, so that
-- * upstream MAC learning points to the gateway chassis.
-- * Also need to avoid generation of multiple ARP responses
-- * from different chassis. */
-- add_chassis_resident_check = true;
-+ /* Ingress Gateway Redirect Table: For NAT on a distributed
-+ * router, add flows that are specific to a NAT rule. These
-+ * flows indicate the presence of an applicable NAT rule that
-+ * can be applied in a distributed manner.
-+ * In particulr REG_SRC_IPV4/REG_SRC_IPV6 and eth.src are set to
-+ * NAT external IP and NAT external mac so the ARP request
-+ * generated in the following stage is sent out with proper IP/MAC
-+ * src addresses.
-+ */
-+ if (distributed) {
-+ ds_clear(match);
-+ ds_clear(actions);
-+ ds_put_format(match,
-+ "ip%s.src == %s && outport == %s && "
-+ "is_chassis_resident(\"%s\")",
-+ is_v6 ? "6" : "4", nat->logical_ip,
-+ od->l3dgw_port->json_key, nat->logical_port);
-+ ds_put_format(actions, "eth.src = %s; %s = %s; next;",
-+ nat->external_mac,
-+ is_v6 ? REG_SRC_IPV6 : REG_SRC_IPV4,
-+ nat->external_ip);
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_GW_REDIRECT,
-+ 100, ds_cstr(match),
-+ ds_cstr(actions), &nat->header_);
-+ }
-+
-+ /* Egress Loopback table: For NAT on a distributed router.
-+ * If packets in the egress pipeline on the distributed
-+ * gateway port have ip.dst matching a NAT external IP, then
-+ * loop a clone of the packet back to the beginning of the
-+ * ingress pipeline with inport = outport. */
-+ if (od->l3dgw_port) {
-+ /* Distributed router. */
-+ ds_clear(match);
-+ ds_put_format(match, "ip%s.dst == %s && outport == %s",
-+ is_v6 ? "6" : "4",
-+ nat->external_ip,
-+ od->l3dgw_port->json_key);
-+ if (!distributed) {
-+ ds_put_format(match, " && is_chassis_resident(%s)",
-+ od->l3redirect_port->json_key);
- } else {
-- /* Check if the option 'reside-on-redirect-chassis'
-- * is set to true on the router port. If set to true
-- * and if peer's logical switch has a localnet port, it
-- * means the router pipeline for the packets from
-- * peer's logical switch is be run on the chassis
-- * hosting the gateway port and it should reply to the
-- * ARP requests for the router port IPs.
-- */
-- add_chassis_resident_check = smap_get_bool(
-- &op->nbrp->options,
-- "reside-on-redirect-chassis", false);
-+ ds_put_format(match, " && is_chassis_resident(\"%s\")",
-+ nat->logical_port);
- }
-
-- if (add_chassis_resident_check) {
-- ds_put_format(match, " && is_chassis_resident(%s)",
-- op->od->l3redirect_port->json_key);
-+ ds_clear(actions);
-+ ds_put_format(actions,
-+ "clone { ct_clear; "
-+ "inport = outport; outport = \"\"; "
-+ "flags = 0; flags.loopback = 1; ");
-+ for (int j = 0; j < MFF_N_LOG_REGS; j++) {
-+ ds_put_format(actions, "reg%d = 0; ", j);
- }
-+ ds_put_format(actions, REGBIT_EGRESS_LOOPBACK" = 1; "
-+ "next(pipeline=ingress, table=%d); };",
-+ ovn_stage_get_table(S_ROUTER_IN_ADMISSION));
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_EGR_LOOP, 100,
-+ ds_cstr(match), ds_cstr(actions),
-+ &nat->header_);
- }
--
-- build_lrouter_arp_flow(op->od, op,
-- op->lrp_networks.ipv4_addrs[i].addr_s,
-- REG_INPORT_ETH_ADDR, match, false, 90,
-- &op->nbrp->header_, lflows);
- }
-
-- /* A set to hold all load-balancer vips that need ARP responses. */
-- struct sset all_ips_v4 = SSET_INITIALIZER(&all_ips_v4);
-- struct sset all_ips_v6 = SSET_INITIALIZER(&all_ips_v6);
-- get_router_load_balancer_ips(op->od, &all_ips_v4, &all_ips_v6);
--
-- const char *ip_address;
-- SSET_FOR_EACH (ip_address, &all_ips_v4) {
-- ds_clear(match);
-- if (op == op->od->l3dgw_port) {
-- ds_put_format(match, "is_chassis_resident(%s)",
-- op->od->l3redirect_port->json_key);
-+ /* Handle force SNAT options set in the gateway router. */
-+ if (!od->l3dgw_port) {
-+ if (dnat_force_snat_ip) {
-+ if (od->dnat_force_snat_addrs.n_ipv4_addrs) {
-+ build_lrouter_force_snat_flows(lflows, od, "4",
-+ od->dnat_force_snat_addrs.ipv4_addrs[0].addr_s,
-+ "dnat");
-+ }
-+ if (od->dnat_force_snat_addrs.n_ipv6_addrs) {
-+ build_lrouter_force_snat_flows(lflows, od, "6",
-+ od->dnat_force_snat_addrs.ipv6_addrs[0].addr_s,
-+ "dnat");
-+ }
- }
--
-- build_lrouter_arp_flow(op->od, op,
-- ip_address, REG_INPORT_ETH_ADDR,
-- match, false, 90, NULL, lflows);
-- }
--
-- SSET_FOR_EACH (ip_address, &all_ips_v6) {
-- ds_clear(match);
-- if (op == op->od->l3dgw_port) {
-- ds_put_format(match, "is_chassis_resident(%s)",
-- op->od->l3redirect_port->json_key);
-+ if (lb_force_snat_ip) {
-+ if (od->lb_force_snat_addrs.n_ipv4_addrs) {
-+ build_lrouter_force_snat_flows(lflows, od, "4",
-+ od->lb_force_snat_addrs.ipv4_addrs[0].addr_s, "lb");
-+ }
-+ if (od->lb_force_snat_addrs.n_ipv6_addrs) {
-+ build_lrouter_force_snat_flows(lflows, od, "6",
-+ od->lb_force_snat_addrs.ipv6_addrs[0].addr_s, "lb");
-+ }
- }
-
-- build_lrouter_nd_flow(op->od, op, "nd_na",
-- ip_address, NULL, REG_INPORT_ETH_ADDR,
-- match, false, 90, NULL, lflows);
-+ /* For gateway router, re-circulate every packet through
-+ * the DNAT zone. This helps with the following.
-+ *
-+ * Any packet that needs to be unDNATed in the reverse
-+ * direction gets unDNATed. Ideally this could be done in
-+ * the egress pipeline. But since the gateway router
-+ * does not have any feature that depends on the source
-+ * ip address being external IP address for IP routing,
-+ * we can do it here, saving a future re-circulation. */
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 50,
-+ "ip", "flags.loopback = 1; ct_dnat;");
- }
-
-- sset_destroy(&all_ips_v4);
-- sset_destroy(&all_ips_v6);
--
-- if (!smap_get(&op->od->nbr->options, "chassis")
-- && !op->od->l3dgw_port) {
-- /* UDP/TCP port unreachable. */
-- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
-- ds_clear(match);
-- ds_put_format(match,
-- "ip4 && ip4.dst == %s && !ip.later_frag && udp",
-- op->lrp_networks.ipv4_addrs[i].addr_s);
-- const char *action = "icmp4 {"
-- "eth.dst <-> eth.src; "
-- "ip4.dst <-> ip4.src; "
-- "ip.ttl = 255; "
-- "icmp4.type = 3; "
-- "icmp4.code = 3; "
-- "next; };";
-- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-- 80, ds_cstr(match), action,
-- &op->nbrp->header_);
--
-- ds_clear(match);
-- ds_put_format(match,
-- "ip4 && ip4.dst == %s && !ip.later_frag && tcp",
-- op->lrp_networks.ipv4_addrs[i].addr_s);
-- action = "tcp_reset {"
-- "eth.dst <-> eth.src; "
-- "ip4.dst <-> ip4.src; "
-- "next; };";
-- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-- 80, ds_cstr(match), action,
-- &op->nbrp->header_);
--
-- ds_clear(match);
-- ds_put_format(match,
-- "ip4 && ip4.dst == %s && !ip.later_frag",
-- op->lrp_networks.ipv4_addrs[i].addr_s);
-- action = "icmp4 {"
-- "eth.dst <-> eth.src; "
-- "ip4.dst <-> ip4.src; "
-- "ip.ttl = 255; "
-- "icmp4.type = 3; "
-- "icmp4.code = 2; "
-- "next; };";
-- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-- 70, ds_cstr(match), action,
-- &op->nbrp->header_);
-- }
-+ /* Load balancing and packet defrag are only valid on
-+ * Gateway routers or router with gateway port. */
-+ if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) {
-+ sset_destroy(&nat_entries);
-+ return;
- }
-
-- /* Drop IP traffic destined to router owned IPs except if the IP is
-- * also a SNAT IP. Those are dropped later, in stage
-- * "lr_in_arp_resolve", if unSNAT was unsuccessful.
-- *
-- * Priority 60.
-- */
-- build_lrouter_drop_own_dest(op, S_ROUTER_IN_IP_INPUT, 60, false,
-- lflows);
-+ /* A set to hold all ips that need defragmentation and tracking. */
-+ struct sset all_ips = SSET_INITIALIZER(&all_ips);
-
-- /* ARP / ND handling for external IP addresses.
-- *
-- * DNAT and SNAT IP addresses are external IP addresses that need ARP
-- * handling.
-- *
-- * These are already taken care globally, per router. The only
-- * exception is on the l3dgw_port where we might need to use a
-- * different ETH address.
-- */
-- if (op != op->od->l3dgw_port) {
-- return;
-- }
-+ for (int i = 0; i < od->nbr->n_load_balancer; i++) {
-+ struct nbrec_load_balancer *nb_lb = od->nbr->load_balancer[i];
-+ struct ovn_northd_lb *lb =
-+ ovn_northd_lb_find(lbs, &nb_lb->header_.uuid);
-+ ovs_assert(lb);
-
-- for (size_t i = 0; i < op->od->nbr->n_nat; i++) {
-- struct ovn_nat *nat_entry = &op->od->nat_entries[i];
-+ for (size_t j = 0; j < lb->n_vips; j++) {
-+ struct ovn_lb_vip *lb_vip = &lb->vips[j];
-+ struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[j];
-+ ds_clear(actions);
-+ build_lb_vip_actions(lb_vip, lb_vip_nb, actions,
-+ lb->selection_fields, false);
-
-- /* Skip entries we failed to parse. */
-- if (!nat_entry_is_valid(nat_entry)) {
-- continue;
-- }
-+ if (!sset_contains(&all_ips, lb_vip->vip_str)) {
-+ sset_add(&all_ips, lb_vip->vip_str);
-+ /* If there are any load balancing rules, we should send
-+ * the packet to conntrack for defragmentation and
-+ * tracking. This helps with two things.
-+ *
-+ * 1. With tracking, we can send only new connections to
-+ * pick a DNAT ip address from a group.
-+ * 2. If there are L4 ports in load balancing rules, we
-+ * need the defragmentation to match on L4 ports. */
-+ ds_clear(match);
-+ if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
-+ ds_put_format(match, "ip && ip4.dst == %s",
-+ lb_vip->vip_str);
-+ } else {
-+ ds_put_format(match, "ip && ip6.dst == %s",
-+ lb_vip->vip_str);
-+ }
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DEFRAG,
-+ 100, ds_cstr(match), "ct_next;",
-+ &nb_lb->header_);
-+ }
-
-- /* Skip SNAT entries for now, we handle unique SNAT IPs separately
-- * below.
-- */
-- if (!strcmp(nat_entry->nb->type, "snat")) {
-- continue;
-- }
-- build_lrouter_port_nat_arp_nd_flow(op, nat_entry, lflows);
-- }
-+ /* Higher priority rules are added for load-balancing in DNAT
-+ * table. For every match (on a VIP[:port]), we add two flows
-+ * via add_router_lb_flow(). One flow is for specific matching
-+ * on ct.new with an action of "ct_lb($targets);". The other
-+ * flow is for ct.est with an action of "ct_dnat;". */
-+ ds_clear(match);
-+ if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
-+ ds_put_format(match, "ip && ip4.dst == %s",
-+ lb_vip->vip_str);
-+ } else {
-+ ds_put_format(match, "ip && ip6.dst == %s",
-+ lb_vip->vip_str);
-+ }
-
-- /* Now handle SNAT entries too, one per unique SNAT IP. */
-- struct shash_node *snat_snode;
-- SHASH_FOR_EACH (snat_snode, &op->od->snat_ips) {
-- struct ovn_snat_ip *snat_ip = snat_snode->data;
-+ int prio = 110;
-+ bool is_udp = nullable_string_is_equal(nb_lb->protocol, "udp");
-+ bool is_sctp = nullable_string_is_equal(nb_lb->protocol,
-+ "sctp");
-+ const char *proto = is_udp ? "udp" : is_sctp ? "sctp" : "tcp";
-
-- if (ovs_list_is_empty(&snat_ip->snat_entries)) {
-- continue;
-- }
-+ if (lb_vip->vip_port) {
-+ ds_put_format(match, " && %s && %s.dst == %d", proto,
-+ proto, lb_vip->vip_port);
-+ prio = 120;
-+ }
-
-- struct ovn_nat *nat_entry =
-- CONTAINER_OF(ovs_list_front(&snat_ip->snat_entries),
-- struct ovn_nat, ext_addr_list_node);
-- build_lrouter_port_nat_arp_nd_flow(op, nat_entry, lflows);
-+ if (od->l3redirect_port &&
-+ (lb_vip->n_backends || !lb_vip->empty_backend_rej)) {
-+ ds_put_format(match, " && is_chassis_resident(%s)",
-+ od->l3redirect_port->json_key);
-+ }
-+ add_router_lb_flow(lflows, od, match, actions, prio,
-+ lb_force_snat_ip, lb_vip, proto,
-+ nb_lb, meter_groups, &nat_entries);
-+ }
- }
-+ sset_destroy(&all_ips);
-+ sset_destroy(&nat_entries);
- }
- }
-
-
-+
- struct lswitch_flow_build_info {
- struct hmap *datapaths;
- struct hmap *ports;
-@@ -11361,6 +11350,8 @@ build_lswitch_and_lrouter_iterate_by_od(struct ovn_datapath *od,
- &lsi->actions);
- build_misc_local_traffic_drop_flows_for_lrouter(od, lsi->lflows);
- build_lrouter_arp_nd_for_datapath(od, lsi->lflows);
-+ build_lrouter_nat_defrag_and_lb(od, lsi->lflows, lsi->meter_groups,
-+ lsi->lbs, &lsi->match, &lsi->actions);
- }
-
- /* Helper function to combine all lflow generation which is iterated by port.
-@@ -11459,9 +11450,6 @@ build_lswitch_and_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
- ds_destroy(&lsi.actions);
-
- build_lswitch_flows(datapaths, lflows);
--
-- /* Legacy lrouter build - to be migrated. */
-- build_lrouter_flows(datapaths, lflows, meter_groups, lbs);
- }
-
- struct ovn_dp_group {
---
-2.29.2
-
diff --git a/SOURCES/0012-actions-Fix-leak-of-dynamic-string-on-fwd-group-enco.patch b/SOURCES/0012-actions-Fix-leak-of-dynamic-string-on-fwd-group-enco.patch
deleted file mode 100644
index f87080e..0000000
--- a/SOURCES/0012-actions-Fix-leak-of-dynamic-string-on-fwd-group-enco.patch
+++ /dev/null
@@ -1,40 +0,0 @@
-From 3211033ae3ed6a055d4bc296ee8ff847f571640c Mon Sep 17 00:00:00 2001
-From: Ilya Maximets
-Date: Fri, 20 Nov 2020 01:17:20 +0100
-Subject: [PATCH 12/16] actions: Fix leak of dynamic string on fwd group
- encoding failure.
-
-CC: Manoj Sharma
-Fixes: edb240081518 ("Forwarding group to load balance l2 traffic with liveness detection")
-Acked-by: Dumitru Ceara
-Signed-off-by: Ilya Maximets
-Signed-off-by: Numan Siddique
-
-(cherry-picked from master commit 61d209bbf2abacb71cdb63555fe1fe6b0020daf3)
----
- lib/actions.c | 2 ++
- 1 file changed, 2 insertions(+)
-
-diff --git a/lib/actions.c b/lib/actions.c
-index 156ebb2fe..3219ab3be 100644
---- a/lib/actions.c
-+++ b/lib/actions.c
-@@ -3448,6 +3448,7 @@ encode_FWD_GROUP(const struct ovnact_fwd_group *fwd_group,
-
- /* Find the tunnel key of the logical port */
- if (!ep->lookup_port(ep->aux, port_name, &port_tunnel_key)) {
-+ ds_destroy(&ds);
- return;
- }
- ds_put_format(&ds, ",bucket=");
-@@ -3455,6 +3456,7 @@ encode_FWD_GROUP(const struct ovnact_fwd_group *fwd_group,
- if (fwd_group->liveness) {
- /* Find the openflow port number of the tunnel port */
- if (!ep->tunnel_ofport(ep->aux, port_name, &ofport)) {
-+ ds_destroy(&ds);
- return;
- }
-
---
-2.28.0
-
diff --git a/SOURCES/0012-controller-introduce-BFD-tx-path-in-ovn-controller.patch b/SOURCES/0012-controller-introduce-BFD-tx-path-in-ovn-controller.patch
deleted file mode 100644
index 6a48950..0000000
--- a/SOURCES/0012-controller-introduce-BFD-tx-path-in-ovn-controller.patch
+++ /dev/null
@@ -1,967 +0,0 @@
-From 2473b80f778654f0204d1cf4671e543cb6467d5f Mon Sep 17 00:00:00 2001
-Message-Id: <2473b80f778654f0204d1cf4671e543cb6467d5f.1610458802.git.lorenzo.bianconi@redhat.com>
-In-Reply-To:
-References:
-From: Lorenzo Bianconi
-Date: Fri, 8 Jan 2021 17:36:20 +0100
-Subject: [PATCH 12/16] controller: introduce BFD tx path in ovn-controller.
-
-Introduce the capability to transmit BFD packets in ovn-controller.
-Introduce BFD tables in nb/sb dbs in order to configure BFD parameters
-(e.g. min_tx, min_rx, ..) for ovn-controller.
-
-Acked-by: Mark Michelson
-Signed-off-by: Lorenzo Bianconi
-Signed-off-by: Numan Siddique
----
- controller/ovn-controller.c | 1 +
- controller/pinctrl.c | 298 +++++++++++++++++++++++++++++++++++-
- controller/pinctrl.h | 2 +
- lib/ovn-l7.h | 19 +++
- northd/ovn-northd.c | 202 ++++++++++++++++++++++++
- ovn-nb.ovsschema | 29 +++-
- ovn-nb.xml | 67 ++++++++
- ovn-sb.ovsschema | 27 +++-
- ovn-sb.xml | 78 ++++++++++
- 9 files changed, 718 insertions(+), 5 deletions(-)
-
-diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
-index 366fc9c06..75512871b 100644
---- a/controller/ovn-controller.c
-+++ b/controller/ovn-controller.c
-@@ -2837,6 +2837,7 @@ main(int argc, char *argv[])
- ovnsb_idl_loop.idl),
- sbrec_service_monitor_table_get(
- ovnsb_idl_loop.idl),
-+ sbrec_bfd_table_get(ovnsb_idl_loop.idl),
- br_int, chassis,
- &runtime_data->local_datapaths,
- &runtime_data->active_tunnels);
-diff --git a/controller/pinctrl.c b/controller/pinctrl.c
-index 7e3abf0a4..9df6533a1 100644
---- a/controller/pinctrl.c
-+++ b/controller/pinctrl.c
-@@ -323,6 +323,18 @@ put_load(uint64_t value, enum mf_field_id dst, int ofs, int n_bits,
- static void notify_pinctrl_main(void);
- static void notify_pinctrl_handler(void);
-
-+static bool bfd_monitor_should_inject(void);
-+static void bfd_monitor_wait(long long int timeout);
-+static void bfd_monitor_init(void);
-+static void bfd_monitor_destroy(void);
-+static void bfd_monitor_send_msg(struct rconn *swconn, long long int *bfd_time)
-+ OVS_REQUIRES(pinctrl_mutex);
-+static void bfd_monitor_run(const struct sbrec_bfd_table *bfd_table,
-+ struct ovsdb_idl_index *sbrec_port_binding_by_name,
-+ const struct sbrec_chassis *chassis,
-+ const struct sset *active_tunnels)
-+ OVS_REQUIRES(pinctrl_mutex);
-+
- COVERAGE_DEFINE(pinctrl_drop_put_mac_binding);
- COVERAGE_DEFINE(pinctrl_drop_buffered_packets_map);
- COVERAGE_DEFINE(pinctrl_drop_controller_event);
-@@ -487,6 +499,7 @@ pinctrl_init(void)
- ip_mcast_snoop_init();
- init_put_vport_bindings();
- init_svc_monitors();
-+ bfd_monitor_init();
- pinctrl.br_int_name = NULL;
- pinctrl_handler_seq = seq_create();
- pinctrl_main_seq = seq_create();
-@@ -3053,6 +3066,8 @@ pinctrl_handler(void *arg_)
- swconn = rconn_create(5, 0, DSCP_DEFAULT, 1 << OFP15_VERSION);
-
- while (!latch_is_set(&pctrl->pinctrl_thread_exit)) {
-+ long long int bfd_time = LLONG_MAX;
-+
- ovs_mutex_lock(&pinctrl_mutex);
- pinctrl_rconn_setup(swconn, pctrl->br_int_name);
- ip_mcast_snoop_run();
-@@ -3085,6 +3100,7 @@ pinctrl_handler(void *arg_)
- send_ipv6_ras(swconn, &send_ipv6_ra_time);
- send_ipv6_prefixd(swconn, &send_prefixd_time);
- send_mac_binding_buffered_pkts(swconn);
-+ bfd_monitor_send_msg(swconn, &bfd_time);
- ovs_mutex_unlock(&pinctrl_mutex);
-
- ip_mcast_querier_run(swconn, &send_mcast_query_time);
-@@ -3102,6 +3118,7 @@ pinctrl_handler(void *arg_)
- 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);
-
- new_seq = seq_read(pinctrl_handler_seq);
- seq_wait(pinctrl_handler_seq, new_seq);
-@@ -3149,6 +3166,7 @@ pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
- const struct sbrec_dns_table *dns_table,
- const struct sbrec_controller_event_table *ce_table,
- const struct sbrec_service_monitor_table *svc_mon_table,
-+ const struct sbrec_bfd_table *bfd_table,
- const struct ovsrec_bridge *br_int,
- const struct sbrec_chassis *chassis,
- const struct hmap *local_datapaths,
-@@ -3179,6 +3197,10 @@ pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
- local_datapaths);
- sync_svc_monitors(ovnsb_idl_txn, svc_mon_table, sbrec_port_binding_by_name,
- chassis);
-+ if (ovnsb_idl_txn) {
-+ bfd_monitor_run(bfd_table, sbrec_port_binding_by_name, chassis,
-+ active_tunnels);
-+ }
- ovs_mutex_unlock(&pinctrl_mutex);
- }
-
-@@ -3722,6 +3744,7 @@ pinctrl_destroy(void)
- destroy_dns_cache();
- ip_mcast_snoop_destroy();
- destroy_svc_monitors();
-+ bfd_monitor_destroy();
- seq_destroy(pinctrl_main_seq);
- seq_destroy(pinctrl_handler_seq);
- }
-@@ -5525,7 +5548,8 @@ may_inject_pkts(void)
- !shash_is_empty(&send_garp_rarp_data) ||
- ipv6_prefixd_should_inject() ||
- !ovs_list_is_empty(&mcast_query_list) ||
-- !ovs_list_is_empty(&buffered_mac_bindings));
-+ !ovs_list_is_empty(&buffered_mac_bindings) ||
-+ bfd_monitor_should_inject());
- }
-
- static void
-@@ -6312,6 +6336,278 @@ sync_svc_monitors(struct ovsdb_idl_txn *ovnsb_idl_txn,
-
- }
-
-+static struct hmap bfd_monitor_map;
-+
-+struct bfd_entry {
-+ struct hmap_node node;
-+ bool erase;
-+
-+ /* L2 source address */
-+ struct eth_addr src_mac;
-+ /* IPv4 source address */
-+ ovs_be32 ip_src;
-+ /* IPv4 destination address */
-+ ovs_be32 ip_dst;
-+ /* RFC 5881 section 4
-+ * The source port MUST be in the range 49152 through 65535.
-+ * The same UDP source port number MUST be used for all BFD
-+ * Control packets associated with a particular session.
-+ * The source port number SHOULD be unique among all BFD
-+ * sessions on the system
-+ */
-+ uint16_t udp_src;
-+ ovs_be32 disc;
-+
-+ int64_t port_key;
-+ int64_t metadata;
-+
-+ long long int next_tx;
-+};
-+
-+static void
-+bfd_monitor_init(void)
-+{
-+ hmap_init(&bfd_monitor_map);
-+}
-+
-+static void
-+bfd_monitor_destroy(void)
-+{
-+ struct bfd_entry *entry;
-+ HMAP_FOR_EACH_POP (entry, node, &bfd_monitor_map) {
-+ free(entry);
-+ }
-+ hmap_destroy(&bfd_monitor_map);
-+}
-+
-+static struct bfd_entry *
-+pinctrl_find_bfd_monitor_entry_by_port(char *ip, uint16_t port)
-+{
-+ struct bfd_entry *entry;
-+ HMAP_FOR_EACH_WITH_HASH (entry, node, hash_string(ip, 0),
-+ &bfd_monitor_map) {
-+ if (entry->udp_src == port) {
-+ return entry;
-+ }
-+ }
-+ return NULL;
-+}
-+
-+static bool
-+bfd_monitor_should_inject(void)
-+{
-+ long long int cur_time = time_msec();
-+ struct bfd_entry *entry;
-+
-+ HMAP_FOR_EACH (entry, node, &bfd_monitor_map) {
-+ if (entry->next_tx < cur_time) {
-+ return true;
-+ }
-+ }
-+ return false;
-+}
-+
-+static void
-+bfd_monitor_wait(long long int timeout)
-+{
-+ if (!hmap_is_empty(&bfd_monitor_map)) {
-+ poll_timer_wait_until(timeout);
-+ }
-+}
-+
-+static void
-+bfd_monitor_put_bfd_msg(struct bfd_entry *entry, struct dp_packet *packet)
-+{
-+ struct udp_header *udp;
-+ struct bfd_msg *msg;
-+
-+ /* Properly align after the ethernet header */
-+ dp_packet_reserve(packet, 2);
-+ struct eth_header *eth = dp_packet_put_uninit(packet, sizeof *eth);
-+ eth->eth_dst = eth_addr_broadcast;
-+ eth->eth_src = entry->src_mac;
-+ eth->eth_type = htons(ETH_TYPE_IP);
-+
-+ struct ip_header *ip = dp_packet_put_zeros(packet, sizeof *ip);
-+ ip->ip_ihl_ver = IP_IHL_VER(5, 4);
-+ ip->ip_tot_len = htons(sizeof *ip + sizeof *udp + sizeof *msg);
-+ ip->ip_ttl = MAXTTL;
-+ ip->ip_tos = IPTOS_PREC_INTERNETCONTROL;
-+ ip->ip_proto = IPPROTO_UDP;
-+ put_16aligned_be32(&ip->ip_src, entry->ip_src);
-+ put_16aligned_be32(&ip->ip_dst, entry->ip_dst);
-+ /* Checksum has already been zeroed by put_zeros call. */
-+ ip->ip_csum = csum(ip, sizeof *ip);
-+
-+ udp = dp_packet_put_zeros(packet, sizeof *udp);
-+ udp->udp_src = htons(entry->udp_src);
-+ udp->udp_dst = htons(BFD_DEST_PORT);
-+ udp->udp_len = htons(sizeof *udp + sizeof *msg);
-+
-+ msg = dp_packet_put_uninit(packet, sizeof *msg);
-+ msg->vers_diag = (BFD_VERSION << 5);
-+ msg->length = BFD_PACKET_LEN;
-+}
-+
-+static void
-+bfd_monitor_send_msg(struct rconn *swconn, long long int *bfd_time)
-+ OVS_REQUIRES(pinctrl_mutex)
-+{
-+ long long int cur_time = time_msec();
-+ struct bfd_entry *entry;
-+
-+ HMAP_FOR_EACH (entry, node, &bfd_monitor_map) {
-+ if (cur_time < entry->next_tx) {
-+ goto next;
-+ }
-+
-+ uint64_t packet_stub[256 / 8];
-+ struct dp_packet packet;
-+ dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
-+ bfd_monitor_put_bfd_msg(entry, &packet);
-+
-+ uint64_t ofpacts_stub[4096 / 8];
-+ struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub);
-+
-+ /* Set MFF_LOG_DATAPATH and MFF_LOG_INPORT. */
-+ uint32_t dp_key = entry->metadata;
-+ uint32_t port_key = entry->port_key;
-+ put_load(dp_key, MFF_LOG_DATAPATH, 0, 64, &ofpacts);
-+ put_load(port_key, MFF_LOG_INPORT, 0, 32, &ofpacts);
-+ put_load(1, MFF_LOG_FLAGS, MLF_LOCAL_ONLY_BIT, 1, &ofpacts);
-+ struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&ofpacts);
-+ resubmit->in_port = OFPP_CONTROLLER;
-+ resubmit->table_id = OFTABLE_LOG_INGRESS_PIPELINE;
-+
-+ struct ofputil_packet_out po = {
-+ .packet = dp_packet_data(&packet),
-+ .packet_len = dp_packet_size(&packet),
-+ .buffer_id = UINT32_MAX,
-+ .ofpacts = ofpacts.data,
-+ .ofpacts_len = ofpacts.size,
-+ };
-+
-+ match_set_in_port(&po.flow_metadata, OFPP_CONTROLLER);
-+ enum ofp_version version = rconn_get_version(swconn);
-+ enum ofputil_protocol proto =
-+ ofputil_protocol_from_ofp_version(version);
-+ queue_msg(swconn, ofputil_encode_packet_out(&po, proto));
-+ dp_packet_uninit(&packet);
-+ ofpbuf_uninit(&ofpacts);
-+
-+ entry->next_tx = cur_time + 5000;
-+next:
-+ if (*bfd_time > entry->next_tx) {
-+ *bfd_time = entry->next_tx;
-+ }
-+ }
-+}
-+
-+static void
-+bfd_monitor_run(const struct sbrec_bfd_table *bfd_table,
-+ struct ovsdb_idl_index *sbrec_port_binding_by_name,
-+ const struct sbrec_chassis *chassis,
-+ const struct sset *active_tunnels)
-+ OVS_REQUIRES(pinctrl_mutex)
-+{
-+ struct bfd_entry *entry, *next_entry;
-+ long long int cur_time = time_msec();
-+ bool changed = false;
-+
-+ HMAP_FOR_EACH (entry, node, &bfd_monitor_map) {
-+ entry->erase = true;
-+ }
-+
-+ const struct sbrec_bfd *bt;
-+ SBREC_BFD_TABLE_FOR_EACH (bt, bfd_table) {
-+ const struct sbrec_port_binding *pb
-+ = lport_lookup_by_name(sbrec_port_binding_by_name,
-+ bt->logical_port);
-+ if (!pb) {
-+ continue;
-+ }
-+
-+ const char *peer_s = smap_get(&pb->options, "peer");
-+ if (!peer_s) {
-+ continue;
-+ }
-+
-+ const struct sbrec_port_binding *peer
-+ = lport_lookup_by_name(sbrec_port_binding_by_name, peer_s);
-+ if (!peer) {
-+ continue;
-+ }
-+
-+ char *redirect_name = xasprintf("cr-%s", pb->logical_port);
-+ bool resident = lport_is_chassis_resident(
-+ sbrec_port_binding_by_name, chassis, active_tunnels,
-+ redirect_name);
-+ free(redirect_name);
-+ if ((strcmp(pb->type, "l3gateway") || pb->chassis != chassis) &&
-+ !resident) {
-+ continue;
-+ }
-+
-+ entry = pinctrl_find_bfd_monitor_entry_by_port(
-+ bt->dst_ip, bt->src_port);
-+ if (!entry) {
-+ ovs_be32 ip_dst, ip_src = htonl(BFD_DEFAULT_SRC_IP);
-+ struct eth_addr ea = eth_addr_zero;
-+ int i;
-+
-+ if (!ip_parse(bt->dst_ip, &ip_dst)) {
-+ continue;
-+ }
-+
-+ for (i = 0; i < pb->n_mac; i++) {
-+ struct lport_addresses laddrs;
-+
-+ if (!extract_lsp_addresses(pb->mac[i], &laddrs)) {
-+ continue;
-+ }
-+
-+ ea = laddrs.ea;
-+ if (laddrs.n_ipv4_addrs > 0) {
-+ ip_src = laddrs.ipv4_addrs[0].addr;
-+ destroy_lport_addresses(&laddrs);
-+ break;
-+ }
-+ destroy_lport_addresses(&laddrs);
-+ }
-+
-+ if (eth_addr_is_zero(ea)) {
-+ continue;
-+ }
-+
-+ entry = xzalloc(sizeof *entry);
-+ entry->src_mac = ea;
-+ entry->ip_src = ip_src;
-+ entry->ip_dst = ip_dst;
-+ entry->udp_src = bt->src_port;
-+ entry->disc = htonl(bt->disc);
-+ entry->next_tx = cur_time;
-+ entry->metadata = pb->datapath->tunnel_key;
-+ entry->port_key = pb->tunnel_key;
-+
-+ uint32_t hash = hash_string(bt->dst_ip, 0);
-+ hmap_insert(&bfd_monitor_map, &entry->node, hash);
-+ changed = true;
-+ }
-+ entry->erase = false;
-+ }
-+
-+ HMAP_FOR_EACH_SAFE (entry, next_entry, node, &bfd_monitor_map) {
-+ if (entry->erase) {
-+ hmap_remove(&bfd_monitor_map, &entry->node);
-+ free(entry);
-+ }
-+ }
-+
-+ if (changed) {
-+ notify_pinctrl_handler();
-+ }
-+}
-+
- static uint16_t
- get_random_src_port(void)
- {
-diff --git a/controller/pinctrl.h b/controller/pinctrl.h
-index 4b101ec92..8555d983d 100644
---- a/controller/pinctrl.h
-+++ b/controller/pinctrl.h
-@@ -31,6 +31,7 @@ struct sbrec_chassis;
- struct sbrec_dns_table;
- struct sbrec_controller_event_table;
- struct sbrec_service_monitor_table;
-+struct sbrec_bfd_table;
-
- void pinctrl_init(void);
- void pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
-@@ -44,6 +45,7 @@ void pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
- const struct sbrec_dns_table *,
- const struct sbrec_controller_event_table *,
- const struct sbrec_service_monitor_table *,
-+ const struct sbrec_bfd_table *,
- const struct ovsrec_bridge *, const struct sbrec_chassis *,
- const struct hmap *local_datapaths,
- const struct sset *active_tunnels);
-diff --git a/lib/ovn-l7.h b/lib/ovn-l7.h
-index c84a0e7a9..d00982449 100644
---- a/lib/ovn-l7.h
-+++ b/lib/ovn-l7.h
-@@ -26,6 +26,25 @@
- #include "hash.h"
- #include "ovn/logical-fields.h"
-
-+#define BFD_PACKET_LEN 24
-+#define BFD_DEST_PORT 3784
-+#define BFD_VERSION 1
-+#define BFD_DEFAULT_SRC_IP 0xA9FE0101 /* 169.254.1.1 */
-+#define BFD_DEFAULT_DST_IP 0xA9FE0100 /* 169.254.1.0 */
-+
-+struct bfd_msg {
-+ uint8_t vers_diag;
-+ uint8_t flags;
-+ uint8_t mult;
-+ uint8_t length;
-+ ovs_be32 my_disc;
-+ ovs_be32 your_disc;
-+ ovs_be32 min_tx;
-+ ovs_be32 min_rx;
-+ ovs_be32 min_rx_echo;
-+};
-+BUILD_ASSERT_DECL(BFD_PACKET_LEN == sizeof(struct bfd_msg));
-+
- /* Generic options map which is used to store dhcpv4 opts and dhcpv6 opts. */
- struct gen_opts_map {
- struct hmap_node hmap_node;
-diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
-index f588d8c32..77ea2181c 100644
---- a/northd/ovn-northd.c
-+++ b/northd/ovn-northd.c
-@@ -7487,6 +7487,191 @@ build_lswitch_ip_unicast_lookup(struct ovn_port *op,
- }
- }
-
-+struct bfd_entry {
-+ struct hmap_node hmap_node;
-+
-+ const struct sbrec_bfd *sb_bt;
-+
-+ bool ref;
-+};
-+
-+static struct bfd_entry *
-+bfd_port_lookup(struct hmap *bfd_map, const char *logical_port,
-+ const char *dst_ip)
-+{
-+ struct bfd_entry *bfd_e;
-+ uint32_t hash;
-+
-+ hash = hash_string(dst_ip, 0);
-+ hash = hash_string(logical_port, hash);
-+ HMAP_FOR_EACH_WITH_HASH (bfd_e, hmap_node, hash, bfd_map) {
-+ if (!strcmp(bfd_e->sb_bt->logical_port, logical_port) &&
-+ !strcmp(bfd_e->sb_bt->dst_ip, dst_ip)) {
-+ return bfd_e;
-+ }
-+ }
-+ return NULL;
-+}
-+
-+static void
-+bfd_cleanup_connections(struct northd_context *ctx, struct hmap *bfd_map)
-+{
-+ const struct nbrec_bfd *nb_bt;
-+ struct bfd_entry *bfd_e;
-+
-+ NBREC_BFD_FOR_EACH (nb_bt, ctx->ovnnb_idl) {
-+ bfd_e = bfd_port_lookup(bfd_map, nb_bt->logical_port, nb_bt->dst_ip);
-+ if (!bfd_e) {
-+ continue;
-+ }
-+
-+ if (!bfd_e->ref && strcmp(nb_bt->status, "admin_down")) {
-+ /* no user for this bfd connection */
-+ nbrec_bfd_set_status(nb_bt, "admin_down");
-+ }
-+ }
-+
-+ HMAP_FOR_EACH_POP (bfd_e, hmap_node, bfd_map) {
-+ free(bfd_e);
-+ }
-+}
-+
-+#define BFD_DEF_MINTX 1000 /* 1s */
-+#define BFD_DEF_MINRX 1000 /* 1s */
-+#define BFD_DEF_DETECT_MULT 5
-+
-+static void
-+build_bfd_update_sb_conf(const struct nbrec_bfd *nb_bt,
-+ const struct sbrec_bfd *sb_bt)
-+{
-+ if (strcmp(nb_bt->dst_ip, sb_bt->dst_ip)) {
-+ sbrec_bfd_set_dst_ip(sb_bt, nb_bt->dst_ip);
-+ }
-+
-+ if (strcmp(nb_bt->logical_port, sb_bt->logical_port)) {
-+ sbrec_bfd_set_logical_port(sb_bt, nb_bt->logical_port);
-+ }
-+
-+ if (strcmp(nb_bt->status, sb_bt->status)) {
-+ sbrec_bfd_set_status(sb_bt, nb_bt->status);
-+ }
-+
-+ int detect_mult = nb_bt->n_detect_mult ? nb_bt->detect_mult[0]
-+ : BFD_DEF_DETECT_MULT;
-+ if (detect_mult != sb_bt->detect_mult) {
-+ sbrec_bfd_set_detect_mult(sb_bt, detect_mult);
-+ }
-+
-+ int min_tx = nb_bt->n_min_tx ? nb_bt->min_tx[0] : BFD_DEF_MINTX;
-+ if (min_tx != sb_bt->min_tx) {
-+ sbrec_bfd_set_min_tx(sb_bt, min_tx);
-+ }
-+
-+ int min_rx = nb_bt->n_min_rx ? nb_bt->min_rx[0] : BFD_DEF_MINRX;
-+ if (min_rx != sb_bt->min_rx) {
-+ sbrec_bfd_set_min_rx(sb_bt, min_rx);
-+ }
-+}
-+
-+/* RFC 5881 section 4
-+ * The source port MUST be in the range 49152 through 65535.
-+ * The same UDP source port number MUST be used for all BFD
-+ * Control packets associated with a particular session.
-+ * The source port number SHOULD be unique among all BFD
-+ * sessions on the system
-+ */
-+#define BFD_UDP_SRC_PORT_START 49152
-+#define BFD_UDP_SRC_PORT_LEN (65535 - BFD_UDP_SRC_PORT_START)
-+
-+static int bfd_get_unused_port(unsigned long *bfd_src_ports)
-+{
-+ int port;
-+
-+ port = bitmap_scan(bfd_src_ports, 0, 0, BFD_UDP_SRC_PORT_LEN);
-+ if (port == BFD_UDP_SRC_PORT_LEN) {
-+ return -ENOSPC;
-+ }
-+ bitmap_set1(bfd_src_ports, port);
-+
-+ return port + BFD_UDP_SRC_PORT_START;
-+}
-+
-+static void
-+build_bfd_table(struct northd_context *ctx, struct hmap *bfd_connections)
-+{
-+ struct hmap sb_only = HMAP_INITIALIZER(&sb_only);
-+ const struct sbrec_bfd *sb_bt;
-+ unsigned long *bfd_src_ports;
-+ struct bfd_entry *bfd_e;
-+ uint32_t hash;
-+
-+ bfd_src_ports = bitmap_allocate(BFD_UDP_SRC_PORT_LEN);
-+
-+ SBREC_BFD_FOR_EACH (sb_bt, ctx->ovnsb_idl) {
-+ bfd_e = xmalloc(sizeof *bfd_e);
-+ bfd_e->sb_bt = sb_bt;
-+ hash = hash_string(sb_bt->dst_ip, 0);
-+ hash = hash_string(sb_bt->logical_port, hash);
-+ hmap_insert(&sb_only, &bfd_e->hmap_node, hash);
-+ bitmap_set1(bfd_src_ports, sb_bt->src_port - BFD_UDP_SRC_PORT_START);
-+ }
-+
-+ const struct nbrec_bfd *nb_bt;
-+ NBREC_BFD_FOR_EACH (nb_bt, ctx->ovnnb_idl) {
-+ if (!nb_bt->status) {
-+ /* default state is admin_down */
-+ nbrec_bfd_set_status(nb_bt, "admin_down");
-+ }
-+
-+ bfd_e = bfd_port_lookup(&sb_only, nb_bt->logical_port, nb_bt->dst_ip);
-+ if (!bfd_e) {
-+ int udp_src = bfd_get_unused_port(bfd_src_ports);
-+ if (udp_src < 0) {
-+ continue;
-+ }
-+
-+ sb_bt = sbrec_bfd_insert(ctx->ovnsb_txn);
-+ sbrec_bfd_set_logical_port(sb_bt, nb_bt->logical_port);
-+ sbrec_bfd_set_dst_ip(sb_bt, nb_bt->dst_ip);
-+ sbrec_bfd_set_disc(sb_bt, 1 + random_uint32());
-+ sbrec_bfd_set_src_port(sb_bt, udp_src);
-+ sbrec_bfd_set_status(sb_bt, nb_bt->status);
-+
-+ int min_tx = nb_bt->n_min_tx ? nb_bt->min_tx[0] : BFD_DEF_MINTX;
-+ sbrec_bfd_set_min_tx(sb_bt, min_tx);
-+ int min_rx = nb_bt->n_min_rx ? nb_bt->min_rx[0] : BFD_DEF_MINRX;
-+ sbrec_bfd_set_min_rx(sb_bt, min_rx);
-+ int d_mult = nb_bt->n_detect_mult ? nb_bt->detect_mult[0]
-+ : BFD_DEF_DETECT_MULT;
-+ sbrec_bfd_set_detect_mult(sb_bt, d_mult);
-+ } else if (strcmp(bfd_e->sb_bt->status, nb_bt->status)) {
-+ if (!strcmp(nb_bt->status, "admin_down") ||
-+ !strcmp(bfd_e->sb_bt->status, "admin_down")) {
-+ sbrec_bfd_set_status(bfd_e->sb_bt, nb_bt->status);
-+ } else {
-+ nbrec_bfd_set_status(nb_bt, bfd_e->sb_bt->status);
-+ }
-+ }
-+ if (bfd_e) {
-+ build_bfd_update_sb_conf(nb_bt, bfd_e->sb_bt);
-+
-+ hmap_remove(&sb_only, &bfd_e->hmap_node);
-+ bfd_e->ref = false;
-+ hash = hash_string(bfd_e->sb_bt->dst_ip, 0);
-+ hash = hash_string(bfd_e->sb_bt->logical_port, hash);
-+ hmap_insert(bfd_connections, &bfd_e->hmap_node, hash);
-+ }
-+ }
-+
-+ HMAP_FOR_EACH_POP (bfd_e, hmap_node, &sb_only) {
-+ sbrec_bfd_delete(bfd_e->sb_bt);
-+ free(bfd_e);
-+ }
-+ hmap_destroy(&sb_only);
-+
-+ bitmap_free(bfd_src_ports);
-+}
-+
- /* Returns a string of the IP address of the router port 'op' that
- * overlaps with 'ip_s". If one is not found, returns NULL.
- *
-@@ -12444,6 +12629,7 @@ ovnnb_db_run(struct northd_context *ctx,
- struct hmap igmp_groups;
- struct shash meter_groups = SHASH_INITIALIZER(&meter_groups);
- struct hmap lbs;
-+ struct hmap bfd_connections = HMAP_INITIALIZER(&bfd_connections);
-
- /* Sync ipsec configuration.
- * Copy nb_cfg from northbound to southbound database.
-@@ -12538,6 +12724,7 @@ ovnnb_db_run(struct northd_context *ctx,
- build_ip_mcast(ctx, datapaths);
- build_mcast_groups(ctx, datapaths, ports, &mcast_groups, &igmp_groups);
- build_meter_groups(ctx, &meter_groups);
-+ build_bfd_table(ctx, &bfd_connections);
- build_lflows(ctx, datapaths, ports, &port_groups, &mcast_groups,
- &igmp_groups, &meter_groups, &lbs);
- ovn_update_ipv6_prefix(ports);
-@@ -12563,9 +12750,13 @@ ovnnb_db_run(struct northd_context *ctx,
- HMAP_FOR_EACH_SAFE (pg, next_pg, key_node, &port_groups) {
- ovn_port_group_destroy(&port_groups, pg);
- }
-+
-+ bfd_cleanup_connections(ctx, &bfd_connections);
-+
- hmap_destroy(&igmp_groups);
- hmap_destroy(&mcast_groups);
- hmap_destroy(&port_groups);
-+ hmap_destroy(&bfd_connections);
-
- struct shash_node *node, *next;
- SHASH_FOR_EACH_SAFE (node, next, &meter_groups) {
-@@ -13497,6 +13688,16 @@ main(int argc, char *argv[])
- add_column_noalert(ovnsb_idl_loop.idl,
- &sbrec_load_balancer_col_external_ids);
-
-+ ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_bfd);
-+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_bfd_col_logical_port);
-+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_bfd_col_dst_ip);
-+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_bfd_col_status);
-+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_bfd_col_min_tx);
-+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_bfd_col_min_rx);
-+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_bfd_col_detect_mult);
-+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_bfd_col_disc);
-+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_bfd_col_src_port);
-+
- struct ovsdb_idl_index *sbrec_chassis_by_name
- = chassis_index_create(ovnsb_idl_loop.idl);
-
-@@ -13619,6 +13820,7 @@ main(int argc, char *argv[])
- }
- }
-
-+
- free(ovn_internal_version);
- unixctl_server_destroy(unixctl);
- ovsdb_idl_loop_destroy(&ovnnb_idl_loop);
-diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
-index b77a2308c..aea932f55 100644
---- a/ovn-nb.ovsschema
-+++ b/ovn-nb.ovsschema
-@@ -1,7 +1,7 @@
- {
- "name": "OVN_Northbound",
-- "version": "5.30.0",
-- "cksum": "3273824429 27172",
-+ "version": "5.31.0",
-+ "cksum": "1511492848 28473",
- "tables": {
- "NB_Global": {
- "columns": {
-@@ -526,5 +526,30 @@
- "type": {"key": "string", "value": "string",
- "min": 0, "max": "unlimited"}}},
- "indexes": [["name"]],
-+ "isRoot": true},
-+ "BFD": {
-+ "columns": {
-+ "logical_port": {"type": "string"},
-+ "dst_ip": {"type": "string"},
-+ "min_tx": {"type": {"key": {"type": "integer",
-+ "minInteger": 1},
-+ "min": 0, "max": 1}},
-+ "min_rx": {"type": {"key": {"type": "integer"},
-+ "min": 0, "max": 1}},
-+ "detect_mult": {"type": {"key": {"type": "integer",
-+ "minInteger": 1},
-+ "min": 0, "max": 1}},
-+ "status": {
-+ "type": {"key": {"type": "string",
-+ "enum": ["set", ["down", "init", "up",
-+ "admin_down"]]},
-+ "min": 0, "max": 1}},
-+ "external_ids": {
-+ "type": {"key": "string", "value": "string",
-+ "min": 0, "max": "unlimited"}},
-+ "options": {
-+ "type": {"key": "string", "value": "string",
-+ "min": 0, "max": "unlimited"}}},
-+ "indexes": [["logical_port", "dst_ip"]],
- "isRoot": true}}
- }
-diff --git a/ovn-nb.xml b/ovn-nb.xml
-index 0cf043790..cdc5e0f3a 100644
---- a/ovn-nb.xml
-+++ b/ovn-nb.xml
-@@ -3728,4 +3728,71 @@
-
-
-
-+
-+
-+
-+ Contains BFD parameter for ovn-controller bfd configuration.
-+
-+
-+
-+
-+ OVN logical port when BFD engine is running.
-+
-+
-+
-+ BFD peer IP address.
-+
-+
-+
-+ This is the minimum interval, in milliseconds, that the local
-+ system would like to use when transmitting BFD Control packets,
-+ less any jitter applied. The value zero is reserved. Default
-+ value is 1000 ms.
-+
-+
-+
-+ This is the minimum interval, in milliseconds, between received
-+ BFD Control packets that this system is capable of supporting,
-+ less any jitter applied by the sender. If this value is zero,
-+ the transmitting system does not want the remote system to send
-+ any periodic BFD Control packets.
-+
-+
-+
-+ Detection time multiplier. The negotiated transmit interval,
-+ multiplied by this value, provides the Detection Time for the
-+ receiving system in Asynchronous mode. Default value is 5.
-+
-+
-+
-+ Reserved for future use.
-+
-+
-+
-+ See External IDs at the beginning of this document.
-+
-+
-+
-+
-+
-+
-+ BFD port logical states. Possible values are:
-+
-+ -
-+
admin_down
-+
-+ -
-+
down
-+
-+ -
-+
init
-+
-+ -
-+
up
-+
-+
-+
-+
-+
-+
-
-diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
-index 5228839b8..97db6de39 100644
---- a/ovn-sb.ovsschema
-+++ b/ovn-sb.ovsschema
-@@ -1,7 +1,7 @@
- {
- "name": "OVN_Southbound",
-- "version": "20.12.0",
-- "cksum": "3969471120 24441",
-+ "version": "20.13.0",
-+ "cksum": "3035725595 25676",
- "tables": {
- "SB_Global": {
- "columns": {
-@@ -484,6 +484,29 @@
- "external_ids": {
- "type": {"key": "string", "value": "string",
- "min": 0, "max": "unlimited"}}},
-+ "isRoot": true},
-+ "BFD": {
-+ "columns": {
-+ "src_port": {"type": {"key": {"type": "integer",
-+ "minInteger": 49152,
-+ "maxInteger": 65535}}},
-+ "disc": {"type": {"key": {"type": "integer"}}},
-+ "logical_port": {"type": "string"},
-+ "dst_ip": {"type": "string"},
-+ "min_tx": {"type": {"key": {"type": "integer"}}},
-+ "min_rx": {"type": {"key": {"type": "integer"}}},
-+ "detect_mult": {"type": {"key": {"type": "integer"}}},
-+ "status": {
-+ "type": {"key": {"type": "string",
-+ "enum": ["set", ["down", "init", "up",
-+ "admin_down"]]}}},
-+ "external_ids": {
-+ "type": {"key": "string", "value": "string",
-+ "min": 0, "max": "unlimited"}},
-+ "options": {
-+ "type": {"key": "string", "value": "string",
-+ "min": 0, "max": "unlimited"}}},
-+ "indexes": [["logical_port", "dst_ip", "src_port", "disc"]],
- "isRoot": true}
- }
- }
-diff --git a/ovn-sb.xml b/ovn-sb.xml
-index c13994848..eb440e492 100644
---- a/ovn-sb.xml
-+++ b/ovn-sb.xml
-@@ -4231,4 +4231,82 @@ tcp.flags = RST;
-
-
-
-+
-+
-+
-+ Contains BFD parameter for ovn-controller bfd configuration.
-+
-+
-+
-+
-+ udp source port used in bfd control packets.
-+ The source port MUST be in the range 49152 through 65535
-+ (RFC5881 section 4).
-+
-+
-+
-+ A unique, nonzero discriminator value generated by the transmitting
-+ system, used to demultiplex multiple BFD sessions between the same pair
-+ of systems.
-+
-+
-+
-+ OVN logical port when BFD engine is running.
-+
-+
-+
-+ BFD peer IP address.
-+
-+
-+
-+ This is the minimum interval, in milliseconds, that the local
-+ system would like to use when transmitting BFD Control packets,
-+ less any jitter applied. The value zero is reserved.
-+
-+
-+
-+ This is the minimum interval, in milliseconds, between received
-+ BFD Control packets that this system is capable of supporting,
-+ less any jitter applied by the sender. If this value is zero,
-+ the transmitting system does not want the remote system to send
-+ any periodic BFD Control packets.
-+
-+
-+
-+ Detection time multiplier. The negotiated transmit interval,
-+ multiplied by this value, provides the Detection Time for the
-+ receiving system in Asynchronous mode.
-+
-+
-+
-+ Reserved for future use.
-+
-+
-+
-+ See External IDs at the beginning of this document.
-+
-+
-+
-+
-+
-+
-+ BFD port logical states. Possible values are:
-+
-+ -
-+
admin_down
-+
-+ -
-+
down
-+
-+ -
-+
init
-+
-+ -
-+
up
-+
-+
-+
-+
-+
-+
-
---
-2.29.2
-
diff --git a/SOURCES/0013-action-introduce-handle_bfd_msg-action.patch b/SOURCES/0013-action-introduce-handle_bfd_msg-action.patch
deleted file mode 100644
index bf54a19..0000000
--- a/SOURCES/0013-action-introduce-handle_bfd_msg-action.patch
+++ /dev/null
@@ -1,164 +0,0 @@
-From 2d71cf47fdb194287719a97ee81dbb0dd9fab9d8 Mon Sep 17 00:00:00 2001
-Message-Id: <2d71cf47fdb194287719a97ee81dbb0dd9fab9d8.1610458802.git.lorenzo.bianconi@redhat.com>
-In-Reply-To:
-References:
-From: Lorenzo Bianconi
-Date: Fri, 8 Jan 2021 17:36:21 +0100
-Subject: [PATCH 13/16] action: introduce handle_bfd_msg() action.
-
-Add handle_bfd_msg() action to parse BFD packets received by the
-controller. handle_bfd_msg() logic is currently empty and it will be
-implemented adding BFD state machine in the following patches.
-
-Acked-by: Mark Michelson
-Signed-off-by: Lorenzo Bianconi
-Signed-off-by: Numan Siddique
----
- controller/pinctrl.c | 15 +++++++++++++++
- include/ovn/actions.h | 7 +++++++
- lib/actions.c | 27 +++++++++++++++++++++++++++
- tests/ovn.at | 4 ++++
- utilities/ovn-trace.c | 2 ++
- 5 files changed, 55 insertions(+)
-
-diff --git a/controller/pinctrl.c b/controller/pinctrl.c
-index 9df6533a1..deeae7479 100644
---- a/controller/pinctrl.c
-+++ b/controller/pinctrl.c
-@@ -329,6 +329,9 @@ static void bfd_monitor_init(void);
- static void bfd_monitor_destroy(void);
- static void bfd_monitor_send_msg(struct rconn *swconn, long long int *bfd_time)
- OVS_REQUIRES(pinctrl_mutex);
-+static void
-+pinctrl_handle_bfd_msg(void)
-+ OVS_REQUIRES(pinctrl_mutex);
- static void bfd_monitor_run(const struct sbrec_bfd_table *bfd_table,
- struct ovsdb_idl_index *sbrec_port_binding_by_name,
- const struct sbrec_chassis *chassis,
-@@ -2975,6 +2978,12 @@ process_packet_in(struct rconn *swconn, const struct ofp_header *msg)
- ovs_mutex_unlock(&pinctrl_mutex);
- break;
-
-+ case ACTION_OPCODE_BFD_MSG:
-+ ovs_mutex_lock(&pinctrl_mutex);
-+ pinctrl_handle_bfd_msg();
-+ ovs_mutex_unlock(&pinctrl_mutex);
-+ break;
-+
- default:
- VLOG_WARN_RL(&rl, "unrecognized packet-in opcode %"PRIu32,
- ntohl(ah->opcode));
-@@ -6503,6 +6512,12 @@ next:
- }
- }
-
-+static void
-+pinctrl_handle_bfd_msg(void)
-+ OVS_REQUIRES(pinctrl_mutex)
-+{
-+}
-+
- static void
- bfd_monitor_run(const struct sbrec_bfd_table *bfd_table,
- struct ovsdb_idl_index *sbrec_port_binding_by_name,
-diff --git a/include/ovn/actions.h b/include/ovn/actions.h
-index 9c1ebf4aa..d104d4d64 100644
---- a/include/ovn/actions.h
-+++ b/include/ovn/actions.h
-@@ -105,6 +105,7 @@ struct ovn_extend_table;
- OVNACT(CHK_LB_HAIRPIN, ovnact_result) \
- OVNACT(CHK_LB_HAIRPIN_REPLY, ovnact_result) \
- OVNACT(CT_SNAT_TO_VIP, ovnact_null) \
-+ OVNACT(BFD_MSG, ovnact_null) \
-
- /* enum ovnact_type, with a member OVNACT_ for each action. */
- enum OVS_PACKED_ENUM ovnact_type {
-@@ -627,6 +628,12 @@ enum action_opcode {
- * The actions, in OpenFlow 1.3 format, follow the action_header.
- */
- ACTION_OPCODE_REJECT,
-+
-+ /* handle_bfd_msg { ...actions ...}."
-+ *
-+ * The actions, in OpenFlow 1.3 format, follow the action_header.
-+ */
-+ ACTION_OPCODE_BFD_MSG,
- };
-
- /* Header. */
-diff --git a/lib/actions.c b/lib/actions.c
-index fbaeb34bc..86be97f44 100644
---- a/lib/actions.c
-+++ b/lib/actions.c
-@@ -2742,6 +2742,31 @@ encode_DHCP6_REPLY(const struct ovnact_null *a OVS_UNUSED,
- encode_controller_op(ACTION_OPCODE_DHCP6_SERVER, ofpacts);
- }
-
-+static void
-+format_BFD_MSG(const struct ovnact_null *a OVS_UNUSED, struct ds *s)
-+{
-+ ds_put_cstr(s, "handle_bfd_msg();");
-+}
-+
-+static void
-+encode_BFD_MSG(const struct ovnact_null *a OVS_UNUSED,
-+ const struct ovnact_encode_params *ep OVS_UNUSED,
-+ struct ofpbuf *ofpacts)
-+{
-+ encode_controller_op(ACTION_OPCODE_BFD_MSG, ofpacts);
-+}
-+
-+static void
-+parse_handle_bfd_msg(struct action_context *ctx OVS_UNUSED)
-+{
-+ if (!lexer_force_match(ctx->lexer, LEX_T_LPAREN)) {
-+ return;
-+ }
-+
-+ ovnact_put_BFD_MSG(ctx->ovnacts);
-+ lexer_force_match(ctx->lexer, LEX_T_RPAREN);
-+}
-+
- static void
- parse_SET_QUEUE(struct action_context *ctx)
- {
-@@ -3842,6 +3867,8 @@ parse_action(struct action_context *ctx)
- parse_fwd_group_action(ctx);
- } else if (lexer_match_id(ctx->lexer, "handle_dhcpv6_reply")) {
- ovnact_put_DHCP6_REPLY(ctx->ovnacts);
-+ } else if (lexer_match_id(ctx->lexer, "handle_bfd_msg")) {
-+ parse_handle_bfd_msg(ctx);
- } else if (lexer_match_id(ctx->lexer, "reject")) {
- parse_REJECT(ctx);
- } else if (lexer_match_id(ctx->lexer, "ct_snat_to_vip")) {
-diff --git a/tests/ovn.at b/tests/ovn.at
-index ce6db8677..27cb2e410 100644
---- a/tests/ovn.at
-+++ b/tests/ovn.at
-@@ -1807,6 +1807,10 @@ ct_snat_to_vip;
- ct_snat_to_vip(foo);
- Syntax error at `(' expecting `;'.
-
-+# bfd packets
-+handle_bfd_msg();
-+ encodes as controller(userdata=00.00.00.17.00.00.00.00)
-+
- # Miscellaneous negative tests.
- ;
- Syntax error at `;'.
-diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c
-index 465049d34..e3aa73fb7 100644
---- a/utilities/ovn-trace.c
-+++ b/utilities/ovn-trace.c
-@@ -2544,6 +2544,8 @@ trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len,
- break;
- case OVNACT_DHCP6_REPLY:
- break;
-+ case OVNACT_BFD_MSG:
-+ break;
- }
- }
- ds_destroy(&s);
---
-2.29.2
-
diff --git a/SOURCES/0013-ovn-nbctl-Fix-leak-of-IPs-while-configuring-NAT.patch b/SOURCES/0013-ovn-nbctl-Fix-leak-of-IPs-while-configuring-NAT.patch
deleted file mode 100644
index 17d12df..0000000
--- a/SOURCES/0013-ovn-nbctl-Fix-leak-of-IPs-while-configuring-NAT.patch
+++ /dev/null
@@ -1,36 +0,0 @@
-From 35864ac03e30fb69bafce90b49ada2f9da6eec86 Mon Sep 17 00:00:00 2001
-From: Ilya Maximets
-Date: Fri, 20 Nov 2020 01:17:21 +0100
-Subject: [PATCH 13/16] ovn-nbctl: Fix leak of IPs while configuring NAT.
-
-CC: Ankur Sharma
-Fixes: 20bc58a67f39 ("External IP based NAT: Add Columns and CLI")
-Signed-off-by: Ilya Maximets
-Acked-by: Dumitru Ceara
-Acked-by: Ankur Sharma
-Signed-off-by: Numan Siddique
-
-(cherry-picked from master commit f9e449fce78b2e0682cef53ba09cade492b4d260)
----
- utilities/ovn-nbctl.c | 3 +++
- 1 file changed, 3 insertions(+)
-
-diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
-index f4c4f9385..6f5117876 100644
---- a/utilities/ovn-nbctl.c
-+++ b/utilities/ovn-nbctl.c
-@@ -4601,8 +4601,11 @@ nbctl_lr_nat_set_ext_ips(struct ctl_context *ctx)
- } else {
- nbrec_nat_set_allowed_ext_ips(nat, addr_set);
- }
-+ free(nat_ip);
-+ free(old_ip);
- return;
- }
-+ free(old_ip);
- }
-
- if (!nat_found) {
---
-2.28.0
-
diff --git a/SOURCES/0014-controller-bfd-introduce-BFD-state-machine.patch b/SOURCES/0014-controller-bfd-introduce-BFD-state-machine.patch
deleted file mode 100644
index 3a932e7..0000000
--- a/SOURCES/0014-controller-bfd-introduce-BFD-state-machine.patch
+++ /dev/null
@@ -1,751 +0,0 @@
-From e75d53c69261a0b104c75d8f6f7dc7175a690833 Mon Sep 17 00:00:00 2001
-Message-Id:
-In-Reply-To:
-References:
-From: Lorenzo Bianconi
-Date: Fri, 8 Jan 2021 17:36:22 +0100
-Subject: [PATCH 14/16] controller: bfd: introduce BFD state machine.
-
-Introduce BFD state machine for BFD packet parsing
-according to RFC880 https://tools.ietf.org/html/rfc5880.
-Introduce BFD logical flows in ovn-northd.
-
-Change-Id: I1ea057ad45393360fa917eb6e3a576dd37cfbc0d
-Acked-by: Mark Michelson
-Signed-off-by: Lorenzo Bianconi
-Signed-off-by: Numan Siddique
----
- NEWS | 6 +
- controller/pinctrl.c | 342 ++++++++++++++++++++++++++++++++++++++--
- northd/ovn-northd.8.xml | 21 +++
- northd/ovn-northd.c | 85 +++++++++-
- tests/ovn-northd.at | 55 +++++++
- 5 files changed, 488 insertions(+), 21 deletions(-)
-
-diff --git a/NEWS b/NEWS
-index f71ec329c..85f63503e 100644
---- a/NEWS
-+++ b/NEWS
-@@ -1,3 +1,9 @@
-+Post-v20.12.0
-+-------------------------
-+ - Support ECMP multiple nexthops for reroute router policies.
-+ - BFD protocol support according to RFC880 [0]. IPv6 is not suported yet.
-+ [0] https://tools.ietf.org/html/rfc5880)
-+
- OVN v20.12.0 - 18 Dec 2020
- --------------------------
- - The "datapath" argument to ovn-trace is now optional, since the
-diff --git a/controller/pinctrl.c b/controller/pinctrl.c
-index deeae7479..6e363a0f9 100644
---- a/controller/pinctrl.c
-+++ b/controller/pinctrl.c
-@@ -330,9 +330,10 @@ static void bfd_monitor_destroy(void);
- static void bfd_monitor_send_msg(struct rconn *swconn, long long int *bfd_time)
- OVS_REQUIRES(pinctrl_mutex);
- static void
--pinctrl_handle_bfd_msg(void)
-+pinctrl_handle_bfd_msg(const struct flow *ip_flow, struct dp_packet *pkt_in)
- OVS_REQUIRES(pinctrl_mutex);
--static void bfd_monitor_run(const struct sbrec_bfd_table *bfd_table,
-+static void bfd_monitor_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
-+ const struct sbrec_bfd_table *bfd_table,
- struct ovsdb_idl_index *sbrec_port_binding_by_name,
- const struct sbrec_chassis *chassis,
- const struct sset *active_tunnels)
-@@ -2980,7 +2981,7 @@ process_packet_in(struct rconn *swconn, const struct ofp_header *msg)
-
- case ACTION_OPCODE_BFD_MSG:
- ovs_mutex_lock(&pinctrl_mutex);
-- pinctrl_handle_bfd_msg();
-+ pinctrl_handle_bfd_msg(&headers, &packet);
- ovs_mutex_unlock(&pinctrl_mutex);
- break;
-
-@@ -3206,10 +3207,8 @@ pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
- local_datapaths);
- sync_svc_monitors(ovnsb_idl_txn, svc_mon_table, sbrec_port_binding_by_name,
- chassis);
-- if (ovnsb_idl_txn) {
-- bfd_monitor_run(bfd_table, sbrec_port_binding_by_name, chassis,
-- active_tunnels);
-- }
-+ bfd_monitor_run(ovnsb_idl_txn, bfd_table, sbrec_port_binding_by_name,
-+ chassis, active_tunnels);
- ovs_mutex_unlock(&pinctrl_mutex);
- }
-
-@@ -6345,8 +6344,48 @@ sync_svc_monitors(struct ovsdb_idl_txn *ovnsb_idl_txn,
-
- }
-
-+enum bfd_state {
-+ BFD_STATE_ADMIN_DOWN,
-+ BFD_STATE_DOWN,
-+ BFD_STATE_INIT,
-+ BFD_STATE_UP,
-+};
-+
-+enum bfd_flags {
-+ BFD_FLAG_MULTIPOINT = 1 << 0,
-+ BFD_FLAG_DEMAND = 1 << 1,
-+ BFD_FLAG_AUTH = 1 << 2,
-+ BFD_FLAG_CTL = 1 << 3,
-+ BFD_FLAG_FINAL = 1 << 4,
-+ BFD_FLAG_POLL = 1 << 5
-+};
-+
-+#define BFD_FLAGS_MASK 0x3f
-+
-+static char *
-+bfd_get_status(enum bfd_state state)
-+{
-+ switch (state) {
-+ case BFD_STATE_ADMIN_DOWN:
-+ return "admin_down";
-+ case BFD_STATE_DOWN:
-+ return "down";
-+ case BFD_STATE_INIT:
-+ return "init";
-+ case BFD_STATE_UP:
-+ return "up";
-+ default:
-+ return "";
-+ }
-+}
-+
- static struct hmap bfd_monitor_map;
-
-+#define BFD_UPDATE_BATCH_TH 10
-+static uint16_t bfd_pending_update;
-+#define BFD_UPDATE_TIMEOUT 5000LL
-+static long long bfd_last_update;
-+
- struct bfd_entry {
- struct hmap_node node;
- bool erase;
-@@ -6365,11 +6404,23 @@ struct bfd_entry {
- * sessions on the system
- */
- uint16_t udp_src;
-- ovs_be32 disc;
-+ ovs_be32 local_disc;
-+ ovs_be32 remote_disc;
-+
-+ uint32_t local_min_tx;
-+ uint32_t local_min_rx;
-+ uint32_t remote_min_rx;
-+
-+ uint8_t local_mult;
-
- int64_t port_key;
- int64_t metadata;
-
-+ enum bfd_state state;
-+ bool change_state;
-+
-+ uint32_t detection_timeout;
-+ long long int last_rx;
- long long int next_tx;
- };
-
-@@ -6377,6 +6428,7 @@ static void
- bfd_monitor_init(void)
- {
- hmap_init(&bfd_monitor_map);
-+ bfd_last_update = time_msec();
- }
-
- static void
-@@ -6402,6 +6454,24 @@ pinctrl_find_bfd_monitor_entry_by_port(char *ip, uint16_t port)
- return NULL;
- }
-
-+static struct bfd_entry *
-+pinctrl_find_bfd_monitor_entry_by_disc(ovs_be32 ip, ovs_be32 disc)
-+{
-+ char *ip_src = xasprintf(IP_FMT, IP_ARGS(ip));
-+ struct bfd_entry *ret = NULL, *entry;
-+
-+ HMAP_FOR_EACH_WITH_HASH (entry, node, hash_string(ip_src, 0),
-+ &bfd_monitor_map) {
-+ if (entry->local_disc == disc) {
-+ ret = entry;
-+ break;
-+ }
-+ }
-+
-+ free(ip_src);
-+ return ret;
-+}
-+
- static bool
- bfd_monitor_should_inject(void)
- {
-@@ -6453,9 +6523,60 @@ bfd_monitor_put_bfd_msg(struct bfd_entry *entry, struct dp_packet *packet)
- udp->udp_dst = htons(BFD_DEST_PORT);
- udp->udp_len = htons(sizeof *udp + sizeof *msg);
-
-- msg = dp_packet_put_uninit(packet, sizeof *msg);
-+ msg = dp_packet_put_zeros(packet, sizeof *msg);
- msg->vers_diag = (BFD_VERSION << 5);
-+ msg->mult = entry->local_mult;
- msg->length = BFD_PACKET_LEN;
-+ msg->flags = entry->state << 6;
-+ msg->my_disc = entry->local_disc;
-+ msg->your_disc = entry->remote_disc;
-+ /* min_tx and min_rx are in us - RFC 5880 page 9 */
-+ msg->min_tx = htonl(entry->local_min_tx * 1000);
-+ msg->min_rx = htonl(entry->local_min_rx * 1000);
-+}
-+
-+static bool
-+bfd_monitor_need_update(void)
-+{
-+ long long int cur_time = time_msec();
-+
-+ if (bfd_pending_update == BFD_UPDATE_BATCH_TH) {
-+ goto update;
-+ }
-+
-+ if (bfd_pending_update &&
-+ bfd_last_update + BFD_UPDATE_TIMEOUT < cur_time) {
-+ goto update;
-+ }
-+ return false;
-+
-+update:
-+ bfd_last_update = cur_time;
-+ bfd_pending_update = 0;
-+ return true;
-+}
-+
-+static void
-+bfd_check_detection_timeout(struct bfd_entry *entry)
-+{
-+ if (entry->state == BFD_STATE_ADMIN_DOWN) {
-+ return;
-+ }
-+
-+ if (!entry->detection_timeout) {
-+ return;
-+ }
-+
-+ long long int cur_time = time_msec();
-+ if (cur_time < entry->last_rx + entry->detection_timeout) {
-+ return;
-+ }
-+
-+ entry->state = BFD_STATE_DOWN;
-+ entry->change_state = true;
-+ bfd_last_update = cur_time;
-+ bfd_pending_update = 0;
-+ notify_pinctrl_main();
- }
-
- static void
-@@ -6465,11 +6586,27 @@ bfd_monitor_send_msg(struct rconn *swconn, long long int *bfd_time)
- long long int cur_time = time_msec();
- struct bfd_entry *entry;
-
-+ if (bfd_monitor_need_update()) {
-+ notify_pinctrl_main();
-+ }
-+
- HMAP_FOR_EACH (entry, node, &bfd_monitor_map) {
-+ unsigned long tx_timeout;
-+
-+ bfd_check_detection_timeout(entry);
-+
- if (cur_time < entry->next_tx) {
- goto next;
- }
-
-+ if (!entry->remote_min_rx) {
-+ continue;
-+ }
-+
-+ if (entry->state == BFD_STATE_ADMIN_DOWN) {
-+ continue;
-+ }
-+
- uint64_t packet_stub[256 / 8];
- struct dp_packet packet;
- dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
-@@ -6504,7 +6641,9 @@ bfd_monitor_send_msg(struct rconn *swconn, long long int *bfd_time)
- dp_packet_uninit(&packet);
- ofpbuf_uninit(&ofpacts);
-
-- entry->next_tx = cur_time + 5000;
-+ tx_timeout = MAX(entry->local_min_tx, entry->remote_min_rx);
-+ tx_timeout -= random_range((tx_timeout * 25) / 100);
-+ entry->next_tx = cur_time + tx_timeout;
- next:
- if (*bfd_time > entry->next_tx) {
- *bfd_time = entry->next_tx;
-@@ -6512,14 +6651,167 @@ next:
- }
- }
-
-+static bool
-+pinctrl_check_bfd_msg(const struct flow *ip_flow, struct dp_packet *pkt_in)
-+{
-+ if (ip_flow->dl_type != htons(ETH_TYPE_IP) &&
-+ ip_flow->dl_type != htons(ETH_TYPE_IPV6)) {
-+ return false;
-+ }
-+
-+ if (ip_flow->nw_proto != IPPROTO_UDP) {
-+ return false;
-+ }
-+
-+ struct udp_header *udp_hdr = dp_packet_l4(pkt_in);
-+ if (udp_hdr->udp_dst != htons(BFD_DEST_PORT)) {
-+ return false;
-+ }
-+
-+ const struct bfd_msg *msg = dp_packet_get_udp_payload(pkt_in);
-+ uint8_t version = msg->vers_diag >> 5;
-+ if (version != BFD_VERSION) {
-+ return false;
-+ }
-+
-+ enum bfd_flags flags = msg->flags & BFD_FLAGS_MASK;
-+ if (flags & BFD_FLAG_AUTH) {
-+ /* AUTH not supported yet */
-+ return false;
-+ }
-+
-+ if (msg->length < BFD_PACKET_LEN) {
-+ return false;
-+ }
-+
-+ if (!msg->mult) {
-+ return false;
-+ }
-+
-+ if (flags & BFD_FLAG_MULTIPOINT) {
-+ return false;
-+ }
-+
-+ if (!msg->my_disc) {
-+ return false;
-+ }
-+
-+ enum bfd_state peer_state = msg->flags >> 6;
-+ if (peer_state >= BFD_STATE_INIT && !msg->your_disc) {
-+ return false;
-+ }
-+
-+ return true;
-+}
-+
- static void
--pinctrl_handle_bfd_msg(void)
-+pinctrl_handle_bfd_msg(const struct flow *ip_flow, struct dp_packet *pkt_in)
- OVS_REQUIRES(pinctrl_mutex)
- {
-+ if (!pinctrl_check_bfd_msg(ip_flow, pkt_in)) {
-+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-+ VLOG_WARN_RL(&rl, "BFD packet discarded");
-+ return;
-+ }
-+
-+ const struct bfd_msg *msg = dp_packet_get_udp_payload(pkt_in);
-+ struct bfd_entry *entry = pinctrl_find_bfd_monitor_entry_by_disc(
-+ ip_flow->nw_src, msg->your_disc);
-+ if (!entry) {
-+ return;
-+ }
-+
-+ bool change_state = false;
-+ entry->remote_disc = msg->my_disc;
-+ uint32_t remote_min_tx = ntohl(msg->min_tx) / 1000;
-+ entry->remote_min_rx = ntohl(msg->min_rx) / 1000;
-+ entry->detection_timeout = msg->mult * MAX(remote_min_tx,
-+ entry->local_min_rx);
-+
-+ enum bfd_state peer_state = msg->flags >> 6;
-+ if (peer_state == BFD_STATE_ADMIN_DOWN &&
-+ entry->state >= BFD_STATE_INIT) {
-+ entry->state = BFD_STATE_DOWN;
-+ entry->last_rx = time_msec();
-+ change_state = true;
-+ goto out;
-+ }
-+
-+ /* bfd state machine */
-+ switch (entry->state) {
-+ case BFD_STATE_DOWN:
-+ if (peer_state == BFD_STATE_DOWN) {
-+ entry->state = BFD_STATE_INIT;
-+ change_state = true;
-+ }
-+ if (peer_state == BFD_STATE_INIT) {
-+ entry->state = BFD_STATE_UP;
-+ change_state = true;
-+ }
-+ entry->last_rx = time_msec();
-+ break;
-+ case BFD_STATE_INIT:
-+ if (peer_state == BFD_STATE_INIT ||
-+ peer_state == BFD_STATE_UP) {
-+ entry->state = BFD_STATE_UP;
-+ change_state = true;
-+ }
-+ if (peer_state == BFD_STATE_ADMIN_DOWN) {
-+ entry->state = BFD_STATE_DOWN;
-+ change_state = true;
-+ }
-+ entry->last_rx = time_msec();
-+ break;
-+ case BFD_STATE_UP:
-+ if (peer_state == BFD_STATE_ADMIN_DOWN ||
-+ peer_state == BFD_STATE_DOWN) {
-+ entry->state = BFD_STATE_DOWN;
-+ change_state = true;
-+ }
-+ entry->last_rx = time_msec();
-+ break;
-+ case BFD_STATE_ADMIN_DOWN:
-+ default:
-+ break;
-+ }
-+
-+out:
-+ /* let's try to bacth db updates */
-+ if (change_state) {
-+ entry->change_state = true;
-+ bfd_pending_update++;
-+ }
-+ if (bfd_monitor_need_update()) {
-+ notify_pinctrl_main();
-+ }
-+}
-+
-+static void
-+bfd_monitor_check_sb_conf(const struct sbrec_bfd *sb_bt,
-+ struct bfd_entry *entry)
-+{
-+ ovs_be32 ip_dst;
-+
-+ if (ip_parse(sb_bt->dst_ip, &ip_dst) && ip_dst != entry->ip_dst) {
-+ entry->ip_dst = ip_dst;
-+ }
-+
-+ if (sb_bt->min_tx != entry->local_min_tx) {
-+ entry->local_min_tx = sb_bt->min_tx;
-+ }
-+
-+ if (sb_bt->min_rx != entry->local_min_rx) {
-+ entry->local_min_rx = sb_bt->min_rx;
-+ }
-+
-+ if (sb_bt->detect_mult != entry->local_mult) {
-+ entry->local_mult = sb_bt->detect_mult;
-+ }
- }
-
- static void
--bfd_monitor_run(const struct sbrec_bfd_table *bfd_table,
-+bfd_monitor_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
-+ const struct sbrec_bfd_table *bfd_table,
- struct ovsdb_idl_index *sbrec_port_binding_by_name,
- const struct sbrec_chassis *chassis,
- const struct sset *active_tunnels)
-@@ -6599,15 +6891,39 @@ bfd_monitor_run(const struct sbrec_bfd_table *bfd_table,
- entry->ip_src = ip_src;
- entry->ip_dst = ip_dst;
- entry->udp_src = bt->src_port;
-- entry->disc = htonl(bt->disc);
-+ entry->local_disc = htonl(bt->disc);
- entry->next_tx = cur_time;
-+ entry->last_rx = cur_time;
-+ entry->detection_timeout = 30000;
- entry->metadata = pb->datapath->tunnel_key;
- entry->port_key = pb->tunnel_key;
-+ entry->state = BFD_STATE_ADMIN_DOWN;
-+ entry->local_min_tx = bt->min_tx;
-+ entry->local_min_rx = bt->min_rx;
-+ entry->remote_min_rx = 1; /* RFC5880 page 29 */
-+ entry->local_mult = bt->detect_mult;
-
- uint32_t hash = hash_string(bt->dst_ip, 0);
- hmap_insert(&bfd_monitor_map, &entry->node, hash);
-+ } else if (!strcmp(bt->status, "admin_down") &&
-+ entry->state != BFD_STATE_ADMIN_DOWN) {
-+ entry->state = BFD_STATE_ADMIN_DOWN;
-+ entry->change_state = false;
-+ entry->remote_disc = 0;
-+ } else if (strcmp(bt->status, "admin_down") &&
-+ entry->state == BFD_STATE_ADMIN_DOWN) {
-+ entry->state = BFD_STATE_DOWN;
-+ entry->change_state = false;
-+ entry->remote_disc = 0;
- changed = true;
-+ } else if (entry->change_state && ovnsb_idl_txn) {
-+ if (entry->state == BFD_STATE_DOWN) {
-+ entry->remote_disc = 0;
-+ }
-+ sbrec_bfd_set_status(bt, bfd_get_status(entry->state));
-+ entry->change_state = false;
- }
-+ bfd_monitor_check_sb_conf(bt, entry);
- entry->erase = false;
- }
-
-diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
-index 1f0f71f34..48c52a56a 100644
---- a/northd/ovn-northd.8.xml
-+++ b/northd/ovn-northd.8.xml
-@@ -1936,6 +1936,27 @@ next;
-
-
-
-+
-+
-+ For each BFD port the two following priority-110 flows are added
-+ to manage BFD traffic:
-+
-+
-+ -
-+ if
ip4.src
or ip6.src
is any IP
-+ address owned by the router port and udp.dst == 3784
-+
, the packet is advanced to the next pipeline stage.
-+
-+
-+ -
-+ if
ip4.dst
or ip6.dst
is any IP
-+ address owned by the router port and udp.dst == 3784
-+
, the handle_bfd_msg
action is executed.
-+
-+
-+
-+
-+
-
-
- L3 admission control: A priority-100 flow drops packets that match
-diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
-index 77ea2181c..363bb0895 100644
---- a/northd/ovn-northd.c
-+++ b/northd/ovn-northd.c
-@@ -1473,6 +1473,8 @@ struct ovn_port {
-
- bool has_unknown; /* If the addresses have 'unknown' defined. */
-
-+ bool has_bfd;
-+
- /* The port's peer:
- *
- * - A switch port S of type "router" has a router port R as a peer,
-@@ -7597,7 +7599,8 @@ static int bfd_get_unused_port(unsigned long *bfd_src_ports)
- }
-
- static void
--build_bfd_table(struct northd_context *ctx, struct hmap *bfd_connections)
-+build_bfd_table(struct northd_context *ctx, struct hmap *bfd_connections,
-+ struct hmap *ports)
- {
- struct hmap sb_only = HMAP_INITIALIZER(&sb_only);
- const struct sbrec_bfd *sb_bt;
-@@ -7661,9 +7664,18 @@ build_bfd_table(struct northd_context *ctx, struct hmap *bfd_connections)
- hash = hash_string(bfd_e->sb_bt->logical_port, hash);
- hmap_insert(bfd_connections, &bfd_e->hmap_node, hash);
- }
-+
-+ struct ovn_port *op = ovn_port_find(ports, nb_bt->logical_port);
-+ if (op) {
-+ op->has_bfd = true;
-+ }
- }
-
- HMAP_FOR_EACH_POP (bfd_e, hmap_node, &sb_only) {
-+ struct ovn_port *op = ovn_port_find(ports, bfd_e->sb_bt->logical_port);
-+ if (op) {
-+ op->has_bfd = false;
-+ }
- sbrec_bfd_delete(bfd_e->sb_bt);
- free(bfd_e);
- }
-@@ -8423,16 +8435,15 @@ add_route(struct hmap *lflows, const struct ovn_port *op,
- build_route_match(op_inport, network_s, plen, is_src_route, is_ipv4,
- &match, &priority);
-
-- struct ds actions = DS_EMPTY_INITIALIZER;
-- ds_put_format(&actions, "ip.ttl--; "REG_ECMP_GROUP_ID" = 0; %s = ",
-+ struct ds common_actions = DS_EMPTY_INITIALIZER;
-+ ds_put_format(&common_actions, REG_ECMP_GROUP_ID" = 0; %s = ",
- is_ipv4 ? REG_NEXT_HOP_IPV4 : REG_NEXT_HOP_IPV6);
--
- if (gateway) {
-- ds_put_cstr(&actions, gateway);
-+ ds_put_cstr(&common_actions, gateway);
- } else {
-- ds_put_format(&actions, "ip%s.dst", is_ipv4 ? "4" : "6");
-+ ds_put_format(&common_actions, "ip%s.dst", is_ipv4 ? "4" : "6");
- }
-- ds_put_format(&actions, "; "
-+ ds_put_format(&common_actions, "; "
- "%s = %s; "
- "eth.src = %s; "
- "outport = %s; "
-@@ -8442,11 +8453,20 @@ add_route(struct hmap *lflows, const struct ovn_port *op,
- lrp_addr_s,
- op->lrp_networks.ea_s,
- op->json_key);
-+ struct ds actions = DS_EMPTY_INITIALIZER;
-+ ds_put_format(&actions, "ip.ttl--; %s", ds_cstr(&common_actions));
-
- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_ROUTING, priority,
- ds_cstr(&match), ds_cstr(&actions),
- stage_hint);
-+ if (op->has_bfd) {
-+ ds_put_format(&match, " && udp.dst == 3784");
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_ROUTING,
-+ priority + 1, ds_cstr(&match),
-+ ds_cstr(&common_actions), stage_hint);
-+ }
- ds_destroy(&match);
-+ ds_destroy(&common_actions);
- ds_destroy(&actions);
- }
-
-@@ -9108,6 +9128,52 @@ build_lrouter_force_snat_flows(struct hmap *lflows, struct ovn_datapath *od,
- ds_destroy(&actions);
- }
-
-+static void
-+build_lrouter_bfd_flows(struct hmap *lflows, struct ovn_port *op)
-+{
-+ if (!op->has_bfd) {
-+ return;
-+ }
-+
-+ struct ds ip_list = DS_EMPTY_INITIALIZER;
-+ struct ds match = DS_EMPTY_INITIALIZER;
-+
-+ if (op->lrp_networks.n_ipv4_addrs) {
-+ op_put_v4_networks(&ip_list, op, false);
-+ ds_put_format(&match, "ip4.src == %s && udp.dst == 3784",
-+ ds_cstr(&ip_list));
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 110,
-+ ds_cstr(&match), "next; ",
-+ &op->nbrp->header_);
-+ ds_clear(&match);
-+ ds_put_format(&match, "ip4.dst == %s && udp.dst == 3784",
-+ ds_cstr(&ip_list));
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 110,
-+ ds_cstr(&match), "handle_bfd_msg(); ",
-+ &op->nbrp->header_);
-+ }
-+ if (op->lrp_networks.n_ipv6_addrs) {
-+ ds_clear(&ip_list);
-+ ds_clear(&match);
-+
-+ op_put_v6_networks(&ip_list, op);
-+ ds_put_format(&match, "ip6.src == %s && udp.dst == 3784",
-+ ds_cstr(&ip_list));
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 110,
-+ ds_cstr(&match), "next; ",
-+ &op->nbrp->header_);
-+ ds_clear(&match);
-+ ds_put_format(&match, "ip6.dst == %s && udp.dst == 3784",
-+ ds_cstr(&ip_list));
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 110,
-+ ds_cstr(&match), "handle_bfd_msg(); ",
-+ &op->nbrp->header_);
-+ }
-+
-+ ds_destroy(&ip_list);
-+ ds_destroy(&match);
-+}
-+
- /* Logical router ingress Table 0: L2 Admission Control
- * Generic admission control flows (without inport check).
- */
-@@ -10614,6 +10680,9 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
- &op->nbrp->header_);
- }
-
-+ /* BFD msg handling */
-+ build_lrouter_bfd_flows(lflows, op);
-+
- /* ICMP time exceeded */
- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
- ds_clear(match);
-@@ -12724,7 +12793,7 @@ ovnnb_db_run(struct northd_context *ctx,
- build_ip_mcast(ctx, datapaths);
- build_mcast_groups(ctx, datapaths, ports, &mcast_groups, &igmp_groups);
- build_meter_groups(ctx, &meter_groups);
-- build_bfd_table(ctx, &bfd_connections);
-+ build_bfd_table(ctx, &bfd_connections, ports);
- build_lflows(ctx, datapaths, ports, &port_groups, &mcast_groups,
- &igmp_groups, &meter_groups, &lbs);
- ovn_update_ipv6_prefix(ports);
-diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
-index ce6c44db4..eee004328 100644
---- a/tests/ovn-northd.at
-+++ b/tests/ovn-northd.at
-@@ -2322,3 +2322,58 @@ sed 's/reg8\[[0..15\]] == [[0-9]]*/reg8\[[0..15\]] == /' | sort], [0],
- ])
-
- AT_CLEANUP
-+
-+AT_SETUP([ovn -- check BFD config propagation to SBDB])
-+AT_KEYWORDS([northd-bfd])
-+ovn_start
-+
-+check ovn-nbctl --wait=sb lr-add r0
-+for i in $(seq 1 4); do
-+ check ovn-nbctl --wait=sb lrp-add r0 r0-sw$i 00:00:00:00:00:0$i 192.168.$i.1/24
-+ check ovn-nbctl --wait=sb ls-add sw$i
-+ check ovn-nbctl --wait=sb lsp-add sw$i sw$i-r0
-+ check ovn-nbctl --wait=sb lsp-set-type sw$i-r0 router
-+ check ovn-nbctl --wait=sb lsp-set-options sw$i-r0 router-port=r0-sw$i
-+ check ovn-nbctl --wait=sb lsp-set-addresses sw$i-r0 00:00:00:00:00:0$i
-+done
-+
-+uuid=$(ovn-nbctl create bfd logical_port=r0-sw1 dst_ip=192.168.10.2 status=down min_tx=250 min_rx=250 detect_mult=10)
-+ovn-nbctl create bfd logical_port=r0-sw2 dst_ip=192.168.20.2 status=down min_tx=500 min_rx=500 detect_mult=20
-+ovn-nbctl create bfd logical_port=r0-sw3 dst_ip=192.168.30.2 status=down
-+ovn-nbctl create bfd logical_port=r0-sw4 dst_ip=192.168.40.2 status=down min_tx=0 detect_mult=0
-+
-+check_column 10 bfd detect_mult logical_port=r0-sw1
-+check_column "192.168.10.2" bfd dst_ip logical_port=r0-sw1
-+check_column 250 bfd min_rx logical_port=r0-sw1
-+check_column 250 bfd min_tx logical_port=r0-sw1
-+check_column admin_down bfd status logical_port=r0-sw1
-+
-+check_column 20 bfd detect_mult logical_port=r0-sw2
-+check_column "192.168.20.2" bfd dst_ip logical_port=r0-sw2
-+check_column 500 bfd min_rx logical_port=r0-sw2
-+check_column 500 bfd min_tx logical_port=r0-sw2
-+check_column admin_down bfd status logical_port=r0-sw2
-+
-+check_column 5 bfd detect_mult logical_port=r0-sw3
-+check_column "192.168.30.2" bfd dst_ip logical_port=r0-sw3
-+check_column 1000 bfd min_rx logical_port=r0-sw3
-+check_column 1000 bfd min_tx logical_port=r0-sw3
-+check_column admin_down bfd status logical_port=r0-sw3
-+
-+uuid=$(fetch_column nb:bfd _uuid logical_port=r0-sw1)
-+check ovn-nbctl set bfd $uuid min_tx=1000
-+check ovn-nbctl set bfd $uuid min_rx=1000
-+check ovn-nbctl set bfd $uuid detect_mult=100
-+
-+uuid_2=$(fetch_column nb:bfd _uuid logical_port=r0-sw2)
-+check ovn-nbctl clear bfd $uuid_2 min_rx
-+check_column 1000 bfd min_rx logical_port=r0-sw2
-+
-+check_column 1000 bfd min_tx logical_port=r0-sw1
-+check_column 1000 bfd min_rx logical_port=r0-sw1
-+check_column 100 bfd detect_mult logical_port=r0-sw1
-+
-+ovn-nbctl destroy bfd $uuid
-+check_row_count bfd 2
-+
-+AT_CLEANUP
---
-2.29.2
-
diff --git a/SOURCES/0014-ovn-nbctl-Fix-IP-leak-on-router-NAT-addition-failure.patch b/SOURCES/0014-ovn-nbctl-Fix-IP-leak-on-router-NAT-addition-failure.patch
deleted file mode 100644
index 009ab96..0000000
--- a/SOURCES/0014-ovn-nbctl-Fix-IP-leak-on-router-NAT-addition-failure.patch
+++ /dev/null
@@ -1,33 +0,0 @@
-From dff5ff1175d2c1f2a2619276aa287ecdeac04702 Mon Sep 17 00:00:00 2001
-From: Ilya Maximets
-Date: Fri, 20 Nov 2020 01:17:22 +0100
-Subject: [PATCH 14/16] ovn-nbctl: Fix IP leak on router NAT addition failure.
-
-Cleanup needed instead of direct return.
-
-Fixes: 43f42ecb3a5a ("Use normalized IP addreses in `ovn-nbctl lr-nat-add`")
-Acked-by: Dumitru Ceara
-Signed-off-by: Ilya Maximets
-Signed-off-by: Numan Siddique
-
-(cherry-picked from master commit 360b5bf20f23eb103edf86f3b13ab0a5fe0490db)
----
- utilities/ovn-nbctl.c | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
-index 6f5117876..af9b396c3 100644
---- a/utilities/ovn-nbctl.c
-+++ b/utilities/ovn-nbctl.c
-@@ -4311,7 +4311,7 @@ nbctl_lr_nat_add(struct ctl_context *ctx)
-
- if (strcmp(nat_type, "dnat_and_snat") && stateless) {
- ctl_error(ctx, "stateless is not applicable to dnat or snat types");
-- return;
-+ goto cleanup;
- }
-
- int is_snat = !strcmp("snat", nat_type);
---
-2.28.0
-
diff --git a/SOURCES/0015-bfd-support-demand-mode-on-rx-side.patch b/SOURCES/0015-bfd-support-demand-mode-on-rx-side.patch
deleted file mode 100644
index 9765cbb..0000000
--- a/SOURCES/0015-bfd-support-demand-mode-on-rx-side.patch
+++ /dev/null
@@ -1,202 +0,0 @@
-From a3a3062985cadc2f2193b10ccb3404d587028c61 Mon Sep 17 00:00:00 2001
-Message-Id:
-In-Reply-To:
-References:
-From: Lorenzo Bianconi
-Date: Fri, 8 Jan 2021 17:36:23 +0100
-Subject: [PATCH 15/16] bfd: support demand mode on rx side.
-
-Introduce rx demand mode support according to RFC5880 [0].
-Demand mode on tx side is not supported yet.
-
-https://tools.ietf.org/html/rfc5880
-Acked-by: Mark Michelson
-Signed-off-by: Lorenzo Bianconi
-Signed-off-by: Numan Siddique
----
- controller/pinctrl.c | 105 ++++++++++++++++++++++++++++---------------
- 1 file changed, 68 insertions(+), 37 deletions(-)
-
-diff --git a/controller/pinctrl.c b/controller/pinctrl.c
-index 6e363a0f9..5820ab659 100644
---- a/controller/pinctrl.c
-+++ b/controller/pinctrl.c
-@@ -330,7 +330,8 @@ static void bfd_monitor_destroy(void);
- static void bfd_monitor_send_msg(struct rconn *swconn, long long int *bfd_time)
- OVS_REQUIRES(pinctrl_mutex);
- static void
--pinctrl_handle_bfd_msg(const struct flow *ip_flow, struct dp_packet *pkt_in)
-+pinctrl_handle_bfd_msg(struct rconn *swconn, const struct flow *ip_flow,
-+ struct dp_packet *pkt_in)
- OVS_REQUIRES(pinctrl_mutex);
- static void bfd_monitor_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
- const struct sbrec_bfd_table *bfd_table,
-@@ -2981,7 +2982,7 @@ process_packet_in(struct rconn *swconn, const struct ofp_header *msg)
-
- case ACTION_OPCODE_BFD_MSG:
- ovs_mutex_lock(&pinctrl_mutex);
-- pinctrl_handle_bfd_msg(&headers, &packet);
-+ pinctrl_handle_bfd_msg(swconn, &headers, &packet);
- ovs_mutex_unlock(&pinctrl_mutex);
- break;
-
-@@ -6411,6 +6412,8 @@ struct bfd_entry {
- uint32_t local_min_rx;
- uint32_t remote_min_rx;
-
-+ bool remote_demand_mode;
-+
- uint8_t local_mult;
-
- int64_t port_key;
-@@ -6495,7 +6498,8 @@ bfd_monitor_wait(long long int timeout)
- }
-
- static void
--bfd_monitor_put_bfd_msg(struct bfd_entry *entry, struct dp_packet *packet)
-+bfd_monitor_put_bfd_msg(struct bfd_entry *entry, struct dp_packet *packet,
-+ bool final)
- {
- struct udp_header *udp;
- struct bfd_msg *msg;
-@@ -6527,7 +6531,8 @@ bfd_monitor_put_bfd_msg(struct bfd_entry *entry, struct dp_packet *packet)
- msg->vers_diag = (BFD_VERSION << 5);
- msg->mult = entry->local_mult;
- msg->length = BFD_PACKET_LEN;
-- msg->flags = entry->state << 6;
-+ msg->flags = final ? BFD_FLAG_FINAL : 0;
-+ msg->flags |= entry->state << 6;
- msg->my_disc = entry->local_disc;
- msg->your_disc = entry->remote_disc;
- /* min_tx and min_rx are in us - RFC 5880 page 9 */
-@@ -6535,6 +6540,46 @@ bfd_monitor_put_bfd_msg(struct bfd_entry *entry, struct dp_packet *packet)
- msg->min_rx = htonl(entry->local_min_rx * 1000);
- }
-
-+static void
-+pinctrl_send_bfd_tx_msg(struct rconn *swconn, struct bfd_entry *entry,
-+ bool final)
-+{
-+ uint64_t packet_stub[256 / 8];
-+ struct dp_packet packet;
-+ dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
-+ bfd_monitor_put_bfd_msg(entry, &packet, final);
-+
-+ uint64_t ofpacts_stub[4096 / 8];
-+ struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub);
-+
-+ /* Set MFF_LOG_DATAPATH and MFF_LOG_INPORT. */
-+ uint32_t dp_key = entry->metadata;
-+ uint32_t port_key = entry->port_key;
-+ put_load(dp_key, MFF_LOG_DATAPATH, 0, 64, &ofpacts);
-+ put_load(port_key, MFF_LOG_INPORT, 0, 32, &ofpacts);
-+ put_load(1, MFF_LOG_FLAGS, MLF_LOCAL_ONLY_BIT, 1, &ofpacts);
-+ struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&ofpacts);
-+ resubmit->in_port = OFPP_CONTROLLER;
-+ resubmit->table_id = OFTABLE_LOG_INGRESS_PIPELINE;
-+
-+ struct ofputil_packet_out po = {
-+ .packet = dp_packet_data(&packet),
-+ .packet_len = dp_packet_size(&packet),
-+ .buffer_id = UINT32_MAX,
-+ .ofpacts = ofpacts.data,
-+ .ofpacts_len = ofpacts.size,
-+ };
-+
-+ match_set_in_port(&po.flow_metadata, OFPP_CONTROLLER);
-+ enum ofp_version version = rconn_get_version(swconn);
-+ enum ofputil_protocol proto =
-+ ofputil_protocol_from_ofp_version(version);
-+ queue_msg(swconn, ofputil_encode_packet_out(&po, proto));
-+ dp_packet_uninit(&packet);
-+ ofpbuf_uninit(&ofpacts);
-+}
-+
-+
- static bool
- bfd_monitor_need_update(void)
- {
-@@ -6607,39 +6652,11 @@ bfd_monitor_send_msg(struct rconn *swconn, long long int *bfd_time)
- continue;
- }
-
-- uint64_t packet_stub[256 / 8];
-- struct dp_packet packet;
-- dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
-- bfd_monitor_put_bfd_msg(entry, &packet);
--
-- uint64_t ofpacts_stub[4096 / 8];
-- struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub);
--
-- /* Set MFF_LOG_DATAPATH and MFF_LOG_INPORT. */
-- uint32_t dp_key = entry->metadata;
-- uint32_t port_key = entry->port_key;
-- put_load(dp_key, MFF_LOG_DATAPATH, 0, 64, &ofpacts);
-- put_load(port_key, MFF_LOG_INPORT, 0, 32, &ofpacts);
-- put_load(1, MFF_LOG_FLAGS, MLF_LOCAL_ONLY_BIT, 1, &ofpacts);
-- struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&ofpacts);
-- resubmit->in_port = OFPP_CONTROLLER;
-- resubmit->table_id = OFTABLE_LOG_INGRESS_PIPELINE;
--
-- struct ofputil_packet_out po = {
-- .packet = dp_packet_data(&packet),
-- .packet_len = dp_packet_size(&packet),
-- .buffer_id = UINT32_MAX,
-- .ofpacts = ofpacts.data,
-- .ofpacts_len = ofpacts.size,
-- };
-+ if (entry->remote_demand_mode) {
-+ continue;
-+ }
-
-- match_set_in_port(&po.flow_metadata, OFPP_CONTROLLER);
-- enum ofp_version version = rconn_get_version(swconn);
-- enum ofputil_protocol proto =
-- ofputil_protocol_from_ofp_version(version);
-- queue_msg(swconn, ofputil_encode_packet_out(&po, proto));
-- dp_packet_uninit(&packet);
-- ofpbuf_uninit(&ofpacts);
-+ pinctrl_send_bfd_tx_msg(swconn, entry, false);
-
- tx_timeout = MAX(entry->local_min_tx, entry->remote_min_rx);
- tx_timeout -= random_range((tx_timeout * 25) / 100);
-@@ -6696,6 +6713,10 @@ pinctrl_check_bfd_msg(const struct flow *ip_flow, struct dp_packet *pkt_in)
- return false;
- }
-
-+ if ((flags & BFD_FLAG_FINAL) && (flags & BFD_FLAG_POLL)) {
-+ return false;
-+ }
-+
- enum bfd_state peer_state = msg->flags >> 6;
- if (peer_state >= BFD_STATE_INIT && !msg->your_disc) {
- return false;
-@@ -6705,7 +6726,8 @@ pinctrl_check_bfd_msg(const struct flow *ip_flow, struct dp_packet *pkt_in)
- }
-
- static void
--pinctrl_handle_bfd_msg(const struct flow *ip_flow, struct dp_packet *pkt_in)
-+pinctrl_handle_bfd_msg(struct rconn *swconn, const struct flow *ip_flow,
-+ struct dp_packet *pkt_in)
- OVS_REQUIRES(pinctrl_mutex)
- {
- if (!pinctrl_check_bfd_msg(ip_flow, pkt_in)) {
-@@ -6775,6 +6797,15 @@ pinctrl_handle_bfd_msg(const struct flow *ip_flow, struct dp_packet *pkt_in)
- break;
- }
-
-+ if (entry->state == BFD_STATE_UP &&
-+ (msg->flags & BFD_FLAG_DEMAND)) {
-+ entry->remote_demand_mode = true;
-+ }
-+
-+ if (msg->flags & BFD_FLAG_POLL) {
-+ pinctrl_send_bfd_tx_msg(swconn, entry, true);
-+ }
-+
- out:
- /* let's try to bacth db updates */
- if (change_state) {
---
-2.29.2
-
diff --git a/SOURCES/0015-ovn-nbctl-Fix-IP-leak-on-failure-of-lr-policy-additi.patch b/SOURCES/0015-ovn-nbctl-Fix-IP-leak-on-failure-of-lr-policy-additi.patch
deleted file mode 100644
index bf9294a..0000000
--- a/SOURCES/0015-ovn-nbctl-Fix-IP-leak-on-failure-of-lr-policy-additi.patch
+++ /dev/null
@@ -1,31 +0,0 @@
-From dcce158a3b80d3143b0b148753b28cc2cf26d36d Mon Sep 17 00:00:00 2001
-From: Ilya Maximets
-Date: Fri, 20 Nov 2020 01:17:23 +0100
-Subject: [PATCH 15/16] ovn-nbctl: Fix IP leak on failure of lr policy
- addition.
-
-Fixes: 742474bad730 ("ovn-nbctl: Enhance lr-policy-add to set the options.")
-Acked-by: Dumitru Ceara
-Signed-off-by: Ilya Maximets
-Signed-off-by: Numan Siddique
-
-(cherry-picked from master commit 47385c83f865306b5c85a61d530e2a9383640ceb)
----
- utilities/ovn-nbctl.c | 1 +
- 1 file changed, 1 insertion(+)
-
-diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
-index af9b396c3..9d04a85a9 100644
---- a/utilities/ovn-nbctl.c
-+++ b/utilities/ovn-nbctl.c
-@@ -3689,6 +3689,7 @@ nbctl_lr_policy_add(struct ctl_context *ctx)
- } else {
- ctl_error(ctx, "No value specified for the option : %s", key);
- free(key);
-+ free(next_hop);
- return;
- }
- free(key);
---
-2.28.0
-
diff --git a/SOURCES/0016-ovn-integrate-bfd-for-static-routes.patch b/SOURCES/0016-ovn-integrate-bfd-for-static-routes.patch
deleted file mode 100644
index 7fae1d5..0000000
--- a/SOURCES/0016-ovn-integrate-bfd-for-static-routes.patch
+++ /dev/null
@@ -1,407 +0,0 @@
-From 986137dc1d4dc6905a7c5ab5e279856260966e12 Mon Sep 17 00:00:00 2001
-Message-Id: <986137dc1d4dc6905a7c5ab5e279856260966e12.1610458802.git.lorenzo.bianconi@redhat.com>
-In-Reply-To:
-References:
-From: Lorenzo Bianconi
-Date: Fri, 8 Jan 2021 17:36:24 +0100
-Subject: [PATCH 16/16] ovn: integrate bfd for static routes.
-
-Introduce the bfd reference in logical_router_static_router table
-in order to check if the next-hop is properly running using the BFD
-protocol. The CMS is supposed to populate bfd column with the proper
-reference otherwise the BFD status is set to admin_down.
-Add BFD tests in system-ovn.at.
-
-Acked-by: Mark Michelson
-Signed-off-by: Lorenzo Bianconi
-Signed-off-by: Numan Siddique
----
- NEWS | 3 +-
- northd/ovn-northd.c | 45 +++++++++++----
- ovn-nb.ovsschema | 6 +-
- ovn-nb.xml | 7 +++
- tests/atlocal.in | 3 +
- tests/ovn-nbctl.at | 8 ++-
- tests/ovn-northd.at | 8 +++
- tests/system-ovn.at | 136 ++++++++++++++++++++++++++++++++++++++++++++
- 8 files changed, 203 insertions(+), 13 deletions(-)
-
-diff --git a/NEWS b/NEWS
-index 85f63503e..0b4b8f4d3 100644
---- a/NEWS
-+++ b/NEWS
-@@ -1,7 +1,8 @@
- Post-v20.12.0
- -------------------------
- - Support ECMP multiple nexthops for reroute router policies.
-- - BFD protocol support according to RFC880 [0]. IPv6 is not suported yet.
-+ - BFD protocol support according to RFC880 [0]. Introduce next-hop BFD
-+ availability check for OVN static routes. IPv6 is not suported yet.
- [0] https://tools.ietf.org/html/rfc5880)
-
- OVN v20.12.0 - 18 Dec 2020
-diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
-index 363bb0895..fa2bd73c3 100644
---- a/northd/ovn-northd.c
-+++ b/northd/ovn-northd.c
-@@ -7952,7 +7952,8 @@ route_hash(struct parsed_route *route)
- * Otherwise return NULL. */
- static struct parsed_route *
- parsed_routes_add(struct ovs_list *routes,
-- const struct nbrec_logical_router_static_route *route)
-+ const struct nbrec_logical_router_static_route *route,
-+ struct hmap *bfd_connections)
- {
- /* Verify that the next hop is an IP address with an all-ones mask. */
- struct in6_addr nexthop;
-@@ -7993,6 +7994,25 @@ parsed_routes_add(struct ovs_list *routes,
- return NULL;
- }
-
-+ const struct nbrec_bfd *nb_bt = route->bfd;
-+ if (nb_bt && !strcmp(nb_bt->dst_ip, route->nexthop)) {
-+ struct bfd_entry *bfd_e;
-+
-+ bfd_e = bfd_port_lookup(bfd_connections, nb_bt->logical_port,
-+ nb_bt->dst_ip);
-+ if (bfd_e) {
-+ bfd_e->ref = true;
-+ }
-+
-+ if (!strcmp(nb_bt->status, "admin_down")) {
-+ nbrec_bfd_set_status(nb_bt, "down");
-+ }
-+
-+ if (!strcmp(nb_bt->status, "down")) {
-+ return NULL;
-+ }
-+ }
-+
- struct parsed_route *pr = xzalloc(sizeof *pr);
- pr->prefix = prefix;
- pr->plen = plen;
-@@ -9579,7 +9599,7 @@ build_ip_routing_flows_for_lrouter_port(
- static void
- build_static_route_flows_for_lrouter(
- struct ovn_datapath *od, struct hmap *lflows,
-- struct hmap *ports)
-+ struct hmap *ports, struct hmap *bfd_connections)
- {
- if (od->nbr) {
- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING_ECMP, 150,
-@@ -9591,7 +9611,8 @@ build_static_route_flows_for_lrouter(
- struct ecmp_groups_node *group;
- for (int i = 0; i < od->nbr->n_static_routes; i++) {
- struct parsed_route *route =
-- parsed_routes_add(&parsed_routes, od->nbr->static_routes[i]);
-+ parsed_routes_add(&parsed_routes, od->nbr->static_routes[i],
-+ bfd_connections);
- if (!route) {
- continue;
- }
-@@ -11571,7 +11592,8 @@ struct lswitch_flow_build_info {
-
- static void
- build_lswitch_and_lrouter_iterate_by_od(struct ovn_datapath *od,
-- struct lswitch_flow_build_info *lsi)
-+ struct lswitch_flow_build_info *lsi,
-+ struct hmap *bfd_connections)
- {
- /* Build Logical Switch Flows. */
- build_lswitch_lflows_pre_acl_and_acl(od, lsi->port_groups, lsi->lflows,
-@@ -11591,7 +11613,8 @@ build_lswitch_and_lrouter_iterate_by_od(struct ovn_datapath *od,
- build_neigh_learning_flows_for_lrouter(od, lsi->lflows, &lsi->match,
- &lsi->actions);
- build_ND_RA_flows_for_lrouter(od, lsi->lflows);
-- build_static_route_flows_for_lrouter(od, lsi->lflows, lsi->ports);
-+ build_static_route_flows_for_lrouter(od, lsi->lflows, lsi->ports,
-+ bfd_connections);
- build_mcast_lookup_flows_for_lrouter(od, lsi->lflows, &lsi->match,
- &lsi->actions);
- build_ingress_policy_flows_for_lrouter(od, lsi->lflows, lsi->ports);
-@@ -11655,7 +11678,8 @@ build_lswitch_and_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
- struct hmap *port_groups, struct hmap *lflows,
- struct hmap *mcgroups,
- struct hmap *igmp_groups,
-- struct shash *meter_groups, struct hmap *lbs)
-+ struct shash *meter_groups, struct hmap *lbs,
-+ struct hmap *bfd_connections)
- {
- struct ovn_datapath *od;
- struct ovn_port *op;
-@@ -11682,7 +11706,7 @@ build_lswitch_and_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
- * will move here and will be reogranized by iterator type.
- */
- HMAP_FOR_EACH (od, key_node, datapaths) {
-- build_lswitch_and_lrouter_iterate_by_od(od, &lsi);
-+ build_lswitch_and_lrouter_iterate_by_od(od, &lsi, bfd_connections);
- }
- HMAP_FOR_EACH (op, key_node, ports) {
- build_lswitch_and_lrouter_iterate_by_op(op, &lsi);
-@@ -11780,13 +11804,14 @@ build_lflows(struct northd_context *ctx, struct hmap *datapaths,
- struct hmap *ports, struct hmap *port_groups,
- struct hmap *mcgroups, struct hmap *igmp_groups,
- struct shash *meter_groups,
-- struct hmap *lbs)
-+ struct hmap *lbs, struct hmap *bfd_connections)
- {
- struct hmap lflows = HMAP_INITIALIZER(&lflows);
-
- build_lswitch_and_lrouter_flows(datapaths, ports,
- port_groups, &lflows, mcgroups,
-- igmp_groups, meter_groups, lbs);
-+ igmp_groups, meter_groups, lbs,
-+ bfd_connections);
-
- /* Collecting all unique datapath groups. */
- struct hmap dp_groups = HMAP_INITIALIZER(&dp_groups);
-@@ -12795,7 +12820,7 @@ ovnnb_db_run(struct northd_context *ctx,
- build_meter_groups(ctx, &meter_groups);
- build_bfd_table(ctx, &bfd_connections, ports);
- build_lflows(ctx, datapaths, ports, &port_groups, &mcast_groups,
-- &igmp_groups, &meter_groups, &lbs);
-+ &igmp_groups, &meter_groups, &lbs, &bfd_connections);
- ovn_update_ipv6_prefix(ports);
-
- sync_address_sets(ctx);
-diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
-index aea932f55..29019809c 100644
---- a/ovn-nb.ovsschema
-+++ b/ovn-nb.ovsschema
-@@ -1,7 +1,7 @@
- {
- "name": "OVN_Northbound",
- "version": "5.31.0",
-- "cksum": "1511492848 28473",
-+ "cksum": "2352750632 28701",
- "tables": {
- "NB_Global": {
- "columns": {
-@@ -374,6 +374,10 @@
- "min": 0, "max": 1}},
- "nexthop": {"type": "string"},
- "output_port": {"type": {"key": "string", "min": 0, "max": 1}},
-+ "bfd": {"type": {"key": {"type": "uuid", "refTable": "BFD",
-+ "refType": "weak"},
-+ "min": 0,
-+ "max": 1}},
- "options": {
- "type": {"key": "string", "value": "string",
- "min": 0, "max": "unlimited"}},
-diff --git a/ovn-nb.xml b/ovn-nb.xml
-index cdc5e0f3a..105d8697e 100644
---- a/ovn-nb.xml
-+++ b/ovn-nb.xml
-@@ -2644,6 +2644,13 @@
-
-
-
-+
-+
-+ Reference to row if the route has associated a
-+ BFD session
-+
-+
-+
-
- ovn-ic
populates this key if the route is learned from the
- global database. In this case the value
-diff --git a/tests/atlocal.in b/tests/atlocal.in
-index d9a4c91d4..5ebc8e117 100644
---- a/tests/atlocal.in
-+++ b/tests/atlocal.in
-@@ -181,6 +181,9 @@ fi
- # Set HAVE_DIBBLER-SERVER
- find_command dibbler-server
-
-+# Set HAVE_BFDD_BEACON
-+find_command bfdd-beacon
-+
- # Turn off proxies.
- unset http_proxy
- unset https_proxy
-diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
-index 01edfcbc1..2827b223c 100644
---- a/tests/ovn-nbctl.at
-+++ b/tests/ovn-nbctl.at
-@@ -1617,7 +1617,13 @@ IPv6 Routes
- 2001:db8::/64 2001:db8:0:f102::1 dst-ip lp0
- 2001:db8:1::/64 2001:db8:0:f103::1 dst-ip
- ::/0 2001:db8:0:f101::1 dst-ip
--])])
-+])
-+
-+AT_CHECK([ovn-nbctl lrp-add lr0 lr0-p0 00:00:01:01:02:03 192.168.10.1/24])
-+bfd_uuid=$(ovn-nbctl create bfd logical_port=lr0-p0 dst_ip=100.0.0.50 status=down min_tx=250 min_rx=250 detect_mult=10)
-+AT_CHECK([ovn-nbctl lr-route-add lr0 100.0.0.0/24 192.168.0.1])
-+route_uuid=$(fetch_column nb:logical_router_static_route _uuid ip_prefix="100.0.0.0/24")
-+AT_CHECK([ovn-nbctl set logical_router_static_route $route_uuid bfd=$bfd_uuid])])
-
- dnl ---------------------------------------------------------------------
-
-diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
-index eee004328..91eb9a3d1 100644
---- a/tests/ovn-northd.at
-+++ b/tests/ovn-northd.at
-@@ -2373,6 +2373,14 @@ check_column 1000 bfd min_tx logical_port=r0-sw1
- check_column 1000 bfd min_rx logical_port=r0-sw1
- check_column 100 bfd detect_mult logical_port=r0-sw1
-
-+check ovn-nbctl lr-route-add r0 100.0.0.0/8 192.168.10.2
-+route_uuid=$(fetch_column nb:logical_router_static_route _uuid ip_prefix="100.0.0.0/8")
-+check ovn-nbctl set logical_router_static_route $route_uuid bfd=$uuid
-+check_column down bfd status logical_port=r0-sw1
-+
-+check ovn-nbctl clear logical_router_static_route $route_uuid bfd
-+check_column admin_down bfd status logical_port=r0-sw1
-+
- ovn-nbctl destroy bfd $uuid
- check_row_count bfd 2
-
-diff --git a/tests/system-ovn.at b/tests/system-ovn.at
-index 1e73001ab..06d606166 100644
---- a/tests/system-ovn.at
-+++ b/tests/system-ovn.at
-@@ -5531,3 +5531,139 @@ as
- OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d
- /.*terminating with signal 15.*/d"])
- AT_CLEANUP
-+
-+AT_SETUP([ovn -- BFD])
-+AT_SKIP_IF([test $HAVE_BFDD_BEACON = no])
-+AT_SKIP_IF([test $HAVE_TCPDUMP = no])
-+AT_KEYWORDS([ovn-bfd])
-+
-+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
-+
-+# Start ovn-controller
-+start_daemon ovn-controller
-+
-+check ovn-nbctl lr-add R1
-+
-+check ovn-nbctl ls-add sw0
-+check ovn-nbctl ls-add sw1
-+check ovn-nbctl ls-add public
-+
-+check ovn-nbctl lrp-add R1 rp-sw0 00:00:01:01:02:03 192.168.1.1/24
-+check ovn-nbctl lrp-add R1 rp-sw1 00:00:03:01:02:03 192.168.2.1/24
-+check ovn-nbctl lrp-add R1 rp-public 00:00:02:01:02:03 172.16.1.1/24 \
-+ -- lrp-set-gateway-chassis rp-public hv1
-+
-+check ovn-nbctl lsp-add sw0 sw0-rp -- set Logical_Switch_Port sw0-rp \
-+ type=router options:router-port=rp-sw0 \
-+ -- lsp-set-addresses sw0-rp router
-+check ovn-nbctl lsp-add sw1 sw1-rp -- set Logical_Switch_Port sw1-rp \
-+ type=router options:router-port=rp-sw1 \
-+ -- lsp-set-addresses sw1-rp router
-+
-+check ovn-nbctl lsp-add public public-rp -- set Logical_Switch_Port public-rp \
-+ type=router options:router-port=rp-public \
-+ -- lsp-set-addresses public-rp router
-+
-+ADD_NAMESPACES(sw01)
-+ADD_VETH(sw01, sw01, br-int, "192.168.1.2/24", "f0:00:00:01:02:03", \
-+ "192.168.1.1")
-+check ovn-nbctl lsp-add sw0 sw01 \
-+ -- lsp-set-addresses sw01 "f0:00:00:01:02:03 192.168.1.2"
-+
-+ADD_NAMESPACES(sw11)
-+ADD_VETH(sw11, sw11, br-int, "192.168.2.2/24", "f0:00:00:02:02:03", \
-+ "192.168.2.1")
-+check ovn-nbctl lsp-add sw1 sw11 \
-+ -- lsp-set-addresses sw11 "f0:00:00:02:02:03 192.168.2.2"
-+
-+ADD_NAMESPACES(server)
-+NS_CHECK_EXEC([server], [ip link set dev lo up])
-+ADD_VETH(s1, server, br-ext, "172.16.1.50/24", "f0:00:00:01:02:05", \
-+ "172.16.1.1")
-+
-+AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=phynet:br-ext])
-+check ovn-nbctl lsp-add public public1 \
-+ -- lsp-set-addresses public1 unknown \
-+ -- lsp-set-type public1 localnet \
-+ -- lsp-set-options public1 network_name=phynet
-+
-+NS_CHECK_EXEC([server], [bfdd-beacon --listen=172.16.1.50], [0])
-+NS_CHECK_EXEC([server], [bfdd-control allow 172.16.1.1], [0], [dnl
-+Allowing connections from 172.16.1.1
-+])
-+
-+uuid=$(ovn-nbctl create bfd logical_port=rp-public dst_ip=172.16.1.50 min_tx=250 min_rx=250 detect_mult=10)
-+check ovn-nbctl lr-route-add R1 100.0.0.0/8 172.16.1.50
-+route_uuid=$(fetch_column nb:logical_router_static_route _uuid ip_prefix="100.0.0.0/8")
-+check ovn-nbctl set logical_router_static_route $route_uuid bfd=$uuid
-+check ovn-nbctl --wait=hv sync
-+
-+wait_column "up" nb:bfd status logical_port=rp-public
-+OVS_WAIT_UNTIL([ovn-sbctl dump-flows R1 | grep 'match=(ip4.dst == 100.0.0.0/8)' | grep -q 172.16.1.50])
-+
-+# un-associate the bfd connection and the static route
-+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])
-+NS_CHECK_EXEC([server], [tcpdump -nni s1 udp port 3784 -Q in > bfd.pcap &])
-+sleep 5
-+kill $(pidof tcpdump)
-+AT_CHECK([grep -qi bfd bfd.pcap],[1])
-+
-+# restart the connection
-+check ovn-nbctl set logical_router_static_route $route_uuid bfd=$uuid
-+wait_column "up" nb:bfd status logical_port=rp-public
-+
-+# switch to gw router configuration
-+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 clear logical_router_port rp-public gateway_chassis
-+check ovn-nbctl set logical_router R1 options:chassis=hv1
-+check ovn-nbctl set logical_router_static_route $route_uuid bfd=$uuid
-+wait_column "up" nb:bfd status logical_port=rp-public
-+
-+# stop bfd endpoint
-+NS_CHECK_EXEC([server], [bfdd-control stop], [0], [dnl
-+stopping
-+])
-+
-+wait_column "down" nb:bfd status logical_port=rp-public
-+OVS_WAIT_UNTIL([test "$(ovn-sbctl dump-flows R1 | grep 'match=(ip4.dst == 100.0.0.0/8)' | grep 172.16.1.50)" = ""])
-+
-+# 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 &])
-+sleep 5
-+kill $(pidof tcpdump)
-+AT_CHECK([grep -qi bfd bfd.pcap],[1])
-+
-+kill $(pidof 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(["/.*error receiving.*/d
-+/.*terminating with signal 15.*/d"])
-+AT_CLEANUP
---
-2.29.2
-
diff --git a/SOURCES/0016-ovn-nbctl-Fix-leak-of-array-of-new-policies.patch b/SOURCES/0016-ovn-nbctl-Fix-leak-of-array-of-new-policies.patch
deleted file mode 100644
index a54515a..0000000
--- a/SOURCES/0016-ovn-nbctl-Fix-leak-of-array-of-new-policies.patch
+++ /dev/null
@@ -1,29 +0,0 @@
-From 7ea067754e208071f97e5ed89264094698fd7363 Mon Sep 17 00:00:00 2001
-From: Ilya Maximets
-Date: Fri, 20 Nov 2020 01:17:24 +0100
-Subject: [PATCH 16/16] ovn-nbctl: Fix leak of array of new policies.
-
-CC: Tao YunXiang
-Fixes: 5820502a5507 ("ovn-nbctl.c: Fix lr-policy-del command")
-Acked-by: Dumitru Ceara
-Signed-off-by: Ilya Maximets
-Signed-off-by: Numan Siddique
----
- utilities/ovn-nbctl.c | 1 +
- 1 file changed, 1 insertion(+)
-
-diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
-index 9d04a85a9..24faaf20f 100644
---- a/utilities/ovn-nbctl.c
-+++ b/utilities/ovn-nbctl.c
-@@ -3758,6 +3758,7 @@ nbctl_lr_policy_del(struct ctl_context *ctx)
- if (!shash_find(&ctx->options, "--if-exists")) {
- ctl_error(ctx, "Logical router policy uuid is not found.");
- }
-+ free(new_policies);
- return;
- }
-
---
-2.28.0
-
diff --git a/SOURCES/ovn-20.12.0.patch b/SOURCES/ovn-20.12.0.patch
deleted file mode 100644
index b8ea6d5..0000000
--- a/SOURCES/ovn-20.12.0.patch
+++ /dev/null
@@ -1,22089 +0,0 @@
-diff --git a/.ci/linux-build.sh b/.ci/linux-build.sh
-index 0e9f87fa8..731dcacb9 100755
---- a/.ci/linux-build.sh
-+++ b/.ci/linux-build.sh
-@@ -9,8 +9,7 @@ EXTRA_OPTS="--enable-Werror"
-
- function configure_ovs()
- {
-- git clone https://github.com/openvswitch/ovs.git ovs_src
-- pushd ovs_src
-+ pushd ovs
- ./boot.sh && ./configure $* || { cat config.log; exit 1; }
- make -j4 || { cat config.log; exit 1; }
- popd
-@@ -19,7 +18,7 @@ function configure_ovs()
- function configure_ovn()
- {
- configure_ovs $*
-- ./boot.sh && ./configure --with-ovs-source=$PWD/ovs_src $* || \
-+ ./boot.sh && ./configure $* || \
- { cat config.log; exit 1; }
- }
-
-@@ -43,7 +42,7 @@ if [ "$TESTSUITE" ]; then
- # Now we only need to prepare the Makefile without sparse-wrapped CC.
- configure_ovn
-
-- export DISTCHECK_CONFIGURE_FLAGS="$OPTS --with-ovs-source=$PWD/ovs_src"
-+ export DISTCHECK_CONFIGURE_FLAGS="$OPTS"
- if ! make distcheck -j4 TESTSUITEFLAGS="-j4" RECHECK=yes; then
- # testsuite.log is necessary for debugging.
- cat */_build/sub/tests/testsuite.log
-diff --git a/.ci/osx-build.sh b/.ci/osx-build.sh
-index 6617f0b9d..4b78b66dd 100755
---- a/.ci/osx-build.sh
-+++ b/.ci/osx-build.sh
-@@ -7,8 +7,7 @@ EXTRA_OPTS=""
-
- function configure_ovs()
- {
-- git clone https://github.com/openvswitch/ovs.git ovs_src
-- pushd ovs_src
-+ pushd ovs
- ./boot.sh && ./configure $*
- make -j4 || { cat config.log; exit 1; }
- popd
-@@ -17,7 +16,7 @@ function configure_ovs()
- function configure_ovn()
- {
- configure_ovs $*
-- ./boot.sh && ./configure $* --with-ovs-source=$PWD/ovs_src
-+ ./boot.sh && ./configure $*
- }
-
- configure_ovn $EXTRA_OPTS $*
-@@ -32,7 +31,7 @@ if ! "$@"; then
- exit 1
- fi
- if [ "$TESTSUITE" ] && [ "$CC" != "clang" ]; then
-- export DISTCHECK_CONFIGURE_FLAGS="$EXTRA_OPTS --with-ovs-source=$PWD/ovs_src"
-+ export DISTCHECK_CONFIGURE_FLAGS="$EXTRA_OPTS"
- if ! make distcheck RECHECK=yes; then
- # testsuite.log is necessary for debugging.
- cat */_build/sub/tests/testsuite.log
-diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
-index 7be75ca36..d825e257c 100644
---- a/.github/workflows/test.yml
-+++ b/.github/workflows/test.yml
-@@ -48,6 +48,8 @@ jobs:
- steps:
- - name: checkout
- uses: actions/checkout@v2
-+ with:
-+ submodules: recursive
-
- - name: install required dependencies
- run: sudo apt install -y ${{ env.dependencies }}
-@@ -99,6 +101,8 @@ jobs:
- steps:
- - name: checkout
- uses: actions/checkout@v2
-+ with:
-+ submodules: recursive
- - name: install dependencies
- run: brew install automake libtool
- - name: prepare
-diff --git a/.gitignore b/.gitignore
-index 7ca9b3859..68333384e 100644
---- a/.gitignore
-+++ b/.gitignore
-@@ -94,3 +94,5 @@ testsuite.tmp.orig
- /.venv
- /cxx-check
- /*.ovsschema.stamp
-+/compile_ovn.sh
-+
-diff --git a/.gitmodules b/.gitmodules
-new file mode 100644
-index 000000000..e083f6bde
---- /dev/null
-+++ b/.gitmodules
-@@ -0,0 +1,3 @@
-+[submodule "ovs"]
-+ path = ovs
-+ url = https://github.com/openvswitch/ovs
-diff --git a/.gitreview b/.gitreview
-new file mode 100644
-index 000000000..27e8042ac
---- /dev/null
-+++ b/.gitreview
-@@ -0,0 +1,6 @@
-+[gerrit]
-+host=code.engineering.redhat.com
-+port=22
-+project=ovn.git
-+defaultbranch=ovn2.13
-+
-diff --git a/AUTHORS.rst b/AUTHORS.rst
-index 5d926c11f..29c2c011c 100644
---- a/AUTHORS.rst
-+++ b/AUTHORS.rst
-@@ -155,6 +155,7 @@ Geoffrey Wossum gwossum@acm.org
- Gianluca Merlo gianluca.merlo@gmail.com
- Giuseppe Lettieri g.lettieri@iet.unipi.it
- Glen Gibb grg@stanford.edu
-+Gongming Chen gmingchen@tencent.com
- Guoshuai Li ligs@dtdream.com
- Guolin Yang gyang@vmware.com
- Guru Chaitanya Perakam gperakam@Brocade.com
-diff --git a/Documentation/intro/install/general.rst b/Documentation/intro/install/general.rst
-index 65b1f4a40..cee99c63d 100644
---- a/Documentation/intro/install/general.rst
-+++ b/Documentation/intro/install/general.rst
-@@ -66,6 +66,10 @@ To compile the userspace programs in the OVN distribution, you will
- need the following software:
-
- - Open vSwitch (https://docs.openvswitch.org/en/latest/intro/install/).
-+ Open vSwitch is included as a submodule in the OVN source code. It is
-+ kept at the minimum recommended version for OVN to operate optimally.
-+ See below for instructions about how to use a different OVS source
-+ location.
-
- - GNU make
-
-@@ -140,27 +144,44 @@ Bootstrapping
- -------------
-
- This step is not needed if you have downloaded a released tarball. If
--you pulled the sources directly from an Open vSwitch Git tree or got a
--Git tree snapshot, then run boot.sh in the top source directory to build
-+you pulled the sources directly from an OVN Git tree or got a Git tree
-+snapshot, then run boot.sh in the top source directory to build
- the "configure" script::
-
- $ ./boot.sh
-
--Before configuring OVN, clone, configure and build Open vSwitch.
-+Before configuring OVN, build Open vSwitch. The easiest way to do this
-+is to use the included OVS submodule in the OVN source::
-+
-+ $ git submodule update --init
-+ $ cd ovs
-+ $ ./boot.sh
-+ $ ./configure
-+ $ make
-+ $ cd ..
-+
-+It is not required to use the included OVS submodule; however the OVS
-+submodule is guaranteed to be the minimum recommended version of OVS
-+to ensure OVN's optimal operation. If you wish to use OVS source code
-+from a different location on the file system, then be sure to configure
-+and build OVS before building OVN.
-
- .. _general-configuring:
-
- Configuring
- -----------
-
--Configure the package by running the configure script. You need to
--invoke configure with atleast the argument --with-ovs-source.
--For example::
-+Then configure the package by running the configure script::
-+
-+ $ ./configure
-+
-+If your OVS source directory is not the included OVS submodule, specify the
-+location of the OVS source code using --with-ovs-source::
-
- $ ./configure --with-ovs-source=/path/to/ovs/source
-
--If you have built Open vSwitch in a separate directory, then you
--need to provide that path in the option - --with-ovs-build.
-+If you have built Open vSwitch in a separate directory from its source
-+code, then you need to provide that path in the option - --with-ovs-build.
-
- By default all files are installed under ``/usr/local``. OVN expects to find
- its database in ``/usr/local/etc/ovn`` by default.
-diff --git a/Makefile.am b/Makefile.am
-index 7ce3d27e4..04a6d7c63 100644
---- a/Makefile.am
-+++ b/Makefile.am
-@@ -48,6 +48,8 @@ AM_CFLAGS = -Wstrict-prototypes
- AM_CFLAGS += $(WARNING_FLAGS)
- AM_CFLAGS += $(OVS_CFLAGS)
-
-+AM_DISTCHECK_CONFIGURE_FLAGS = --with-ovs-source=$(PWD)/ovs
-+
- if NDEBUG
- AM_CPPFLAGS += -DNDEBUG
- AM_CFLAGS += -fomit-frame-pointer
-@@ -105,7 +107,9 @@ EXTRA_DIST = \
- ovn-ic-nb.ovsschema \
- ovn-ic-nb.xml \
- ovn-ic-sb.ovsschema \
-- ovn-ic-sb.xml
-+ ovn-ic-sb.xml \
-+ .gitreview \
-+ compile_ovn.sh
- bin_PROGRAMS =
- sbin_PROGRAMS =
- bin_SCRIPTS =
-@@ -157,6 +161,7 @@ noinst_HEADERS += $(EXTRA_DIST)
-
- ro_c = echo '/* -*- mode: c; buffer-read-only: t -*- */'
- ro_shell = printf '\043 Generated automatically -- do not modify! -*- buffer-read-only: t -*-\n'
-+submodules = $(shell grep 'path =' $(srcdir)/.gitmodules | sed -E 's/[\t ]*path =\s*(.*)/\1/g' | xargs)
-
- SUFFIXES += .in
- .in:
-@@ -216,6 +221,8 @@ dist-hook-git: distfiles
- @if test -e $(srcdir)/.git && (git --version) >/dev/null 2>&1; then \
- (cd $(srcdir) && git ls-files) | grep -v '\.gitignore$$' | \
- grep -v '\.gitattributes$$' | \
-+ grep -v '\.gitmodules$$' | \
-+ grep -v "$(submodules)" | \
- LC_ALL=C sort -u > all-gitfiles; \
- LC_ALL=C comm -1 -3 distfiles all-gitfiles > missing-distfiles; \
- if test -s missing-distfiles; then \
-@@ -247,8 +254,8 @@ ALL_LOCAL += config-h-check
- config-h-check:
- @cd $(srcdir); \
- if test -e .git && (git --version) >/dev/null 2>&1 && \
-- git --no-pager grep -L '#include ' `git ls-files | grep '\.c$$' | \
-- grep -vE '^ovs/datapath|^ovs/lib/sflow|^ovs/datapath-windows|^python|^ovs/python'`; \
-+ git --no-pager grep -L '#include ' `git ls-files | grep -v $(submodules) | grep '\.c$$' | \
-+ grep -vE '^python'`; \
- then \
- echo "See above for list of violations of the rule that"; \
- echo "every C source file must #include ."; \
-@@ -261,8 +268,7 @@ ALL_LOCAL += printf-check
- printf-check:
- @cd $(srcdir); \
- if test -e .git && (git --version) >/dev/null 2>&1 && \
-- git --no-pager grep -n -E -e '%[-+ #0-9.*]*([ztj]|hh)' --and --not -e 'ovs_scan' `git ls-files | grep '\.[ch]$$' | \
-- grep -vE '^ovs/datapath|^ovs/lib/sflow'`; \
-+ git --no-pager grep -n -E -e '%[-+ #0-9.*]*([ztj]|hh)' --and --not -e 'ovs_scan' `git ls-files | grep -v $(submodules) | grep '\.[ch]$$'`; \
- then \
- echo "See above for list of violations of the rule that"; \
- echo "'z', 't', 'j', 'hh' printf() type modifiers are"; \
-@@ -288,7 +294,7 @@ ALL_LOCAL += check-assert-h-usage
- check-assert-h-usage:
- @if test -e $(srcdir)/.git && (git --version) >/dev/null 2>&1 && \
- (cd $(srcdir) && git --no-pager grep -l -E '[<]assert.h[>]') | \
-- $(EGREP) -v '^ovs/lib/(sflow_receiver|vlog).c$$|^ovs/tests/|^tests/'; \
-+ $(EGREP) -v '^tests/'; \
- then \
- echo "Files listed above unexpectedly #include <""assert.h"">."; \
- echo "Please use ovs_assert (from util.h) instead of assert."; \
-@@ -304,8 +310,7 @@ ALL_LOCAL += check-endian
- check-endian:
- @if test -e $(srcdir)/.git && (git --version) >/dev/null 2>&1 && \
- (cd $(srcdir) && git --no-pager grep -l -E \
-- -e 'BIG_ENDIAN|LITTLE_ENDIAN' --and --not -e 'BYTE_ORDER' | \
-- $(EGREP) -v '^ovs/datapath/|^ovs/include/sparse/rte_'); \
-+ -e 'BIG_ENDIAN|LITTLE_ENDIAN' --and --not -e 'BYTE_ORDER'); \
- then \
- echo "See above for list of files that misuse LITTLE""_ENDIAN"; \
- echo "or BIG""_ENDIAN. Please use WORDS_BIGENDIAN instead."; \
-@@ -329,9 +334,9 @@ check-tabs:
- @cd $(srcdir); \
- if test -e .git && (git --version) >/dev/null 2>&1 && \
- grep -ln "^ " \
-- `git ls-files \
-+ `git ls-files | grep -v $(submodules) \
- | grep -v -f build-aux/initial-tab-whitelist` /dev/null \
-- | $(EGREP) -v ':[ ]*/?\*'; \
-+ | $(EGREP) -v ':[ ]*/?\*'; \
- then \
- echo "See above for files that use tabs for indentation."; \
- echo "Please use spaces instead."; \
-@@ -344,8 +349,7 @@ thread-safety-check:
- @cd $(srcdir); \
- if test -e .git && (git --version) >/dev/null 2>&1 && \
- grep -n -f build-aux/thread-safety-blacklist \
-- `git ls-files | grep '\.[ch]$$' \
-- | $(EGREP) -v '^ovs/datapath|^ovs/lib/sflow'` /dev/null \
-+ `git ls-files | grep -v $(submodules) | grep '\.[ch]$$'` /dev/null \
- | $(EGREP) -v ':[ ]*/?\*'; \
- then \
- echo "See above for list of calls to functions that are"; \
-@@ -361,7 +365,7 @@ ALL_LOCAL += check-ifconfig
- check-ifconfig:
- @if test -e $(srcdir)/.git && (git --version) >/dev/null 2>&1 && \
- (cd $(srcdir) && git --no-pager grep -l -E -e 'ifconfig' | \
-- $(EGREP) -v 'Makefile.am|ovs-vsctl-bashcomp|openvswitch-custom\.te'); \
-+ $(EGREP) -v 'Makefile.am|openvswitch-custom\.te'); \
- then \
- echo "See above for list of files that use or reference"; \
- echo "'ifconfig'. Please use 'ip' instead."; \
-diff --git a/NEWS b/NEWS
-index f71ec329c..57a9ba939 100644
---- a/NEWS
-+++ b/NEWS
-@@ -1,3 +1,19 @@
-+Post-v20.12.0
-+-------------------------
-+ - Support ECMP multiple nexthops for reroute router policies.
-+ - BFD protocol support according to RFC880 [0]. Introduce next-hop BFD
-+ availability check for OVN static routes.
-+ [0] https://tools.ietf.org/html/rfc5880)
-+ - Change the semantic of the "Logical_Switch_Port.up" field such that it is
-+ set to "true" only when all corresponding OVS openflow operations have
-+ been processed. This also introduces a new "OVS.Interface.external-id",
-+ "ovn-installed". This external-id is set by ovn-controller only after all
-+ openflow operations corresponding to the OVS interface being added have
-+ been processed.
-+ - Add a new option to Load_Balancer.options, "hairpin_snat_ip", to allow
-+ users to explicitly select which source IP should be used for load
-+ balancer hairpin traffic.
-+
- OVN v20.12.0 - 18 Dec 2020
- --------------------------
- - The "datapath" argument to ovn-trace is now optional, since the
-diff --git a/acinclude.m4 b/acinclude.m4
-index a797adc82..2ca15cb33 100644
---- a/acinclude.m4
-+++ b/acinclude.m4
-@@ -338,7 +338,7 @@ AC_DEFUN([OVN_CHECK_OVS], [
- AC_ERROR([$OVSDIR is not an OVS source directory])
- fi
- else
-- AC_ERROR([OVS source dir path needs to be specified (use --with-ovs-source)])
-+ OVSDIR=$srcdir/ovs
- fi
-
- AC_MSG_RESULT([$OVSDIR])
-diff --git a/build-aux/initial-tab-whitelist b/build-aux/initial-tab-whitelist
-index 216cd2ed3..b2f5a0791 100644
---- a/build-aux/initial-tab-whitelist
-+++ b/build-aux/initial-tab-whitelist
-@@ -8,3 +8,4 @@
- ^xenserver/
- ^debian/rules.modules$
- ^debian/rules$
-+^\.gitmodules$
-diff --git a/compile_ovn.sh b/compile_ovn.sh
-new file mode 100755
-index 000000000..1b980df4f
---- /dev/null
-+++ b/compile_ovn.sh
-@@ -0,0 +1,14 @@
-+#!/bin/bash
-+
-+git submodule update --init
-+
-+pushd ovs
-+./boot.sh
-+./configure --enable-Werror --enable-sparse
-+make -j5
-+popd
-+
-+./boot.sh
-+./configure --enable-Werror --enable-sparse
-+make -j5
-+
-diff --git a/controller-vtep/binding.c b/controller-vtep/binding.c
-index 83377157e..01d5a16d2 100644
---- a/controller-vtep/binding.c
-+++ b/controller-vtep/binding.c
-@@ -109,7 +109,12 @@ update_pb_chassis(const struct sbrec_port_binding *port_binding_rec,
- port_binding_rec->chassis->name,
- chassis_rec->name);
- }
-+
- sbrec_port_binding_set_chassis(port_binding_rec, chassis_rec);
-+ if (port_binding_rec->n_up) {
-+ bool up = true;
-+ sbrec_port_binding_set_up(port_binding_rec, &up, 1);
-+ }
- }
- }
-
-diff --git a/controller/automake.mk b/controller/automake.mk
-index 45e1bdd36..9b8debd2f 100644
---- a/controller/automake.mk
-+++ b/controller/automake.mk
-@@ -18,6 +18,8 @@ controller_ovn_controller_SOURCES = \
- controller/lport.h \
- controller/ofctrl.c \
- controller/ofctrl.h \
-+ controller/ofctrl-seqno.c \
-+ controller/ofctrl-seqno.h \
- controller/pinctrl.c \
- controller/pinctrl.h \
- controller/patch.c \
-@@ -25,7 +27,10 @@ controller_ovn_controller_SOURCES = \
- controller/ovn-controller.c \
- controller/ovn-controller.h \
- controller/physical.c \
-- controller/physical.h
-+ controller/physical.h \
-+ controller/mac-learn.c \
-+ controller/mac-learn.h
-+
- controller_ovn_controller_LDADD = lib/libovn.la $(OVS_LIBDIR)/libopenvswitch.la
- man_MANS += controller/ovn-controller.8
- EXTRA_DIST += controller/ovn-controller.8.xml
-diff --git a/controller/binding.c b/controller/binding.c
-index cb60c5d67..4e6c75696 100644
---- a/controller/binding.c
-+++ b/controller/binding.c
-@@ -18,6 +18,7 @@
- #include "ha-chassis.h"
- #include "lflow.h"
- #include "lport.h"
-+#include "ofctrl-seqno.h"
- #include "patch.h"
-
- #include "lib/bitmap.h"
-@@ -34,6 +35,38 @@
-
- VLOG_DEFINE_THIS_MODULE(binding);
-
-+/* External ID to be set in the OVS.Interface record when the OVS interface
-+ * is ready for use, i.e., is bound to an OVN port and its corresponding
-+ * flows have been installed.
-+ */
-+#define OVN_INSTALLED_EXT_ID "ovn-installed"
-+
-+/* Set of OVS interface IDs that have been released in the most recent
-+ * processing iterations. This gets updated in release_lport() and is
-+ * periodically emptied in binding_seqno_run().
-+ */
-+static struct sset binding_iface_released_set =
-+ SSET_INITIALIZER(&binding_iface_released_set);
-+
-+/* Set of OVS interface IDs that have been bound in the most recent
-+ * processing iterations. This gets updated in release_lport() and is
-+ * periodically emptied in binding_seqno_run().
-+ */
-+static struct sset binding_iface_bound_set =
-+ SSET_INITIALIZER(&binding_iface_bound_set);
-+
-+static void
-+binding_iface_released_add(const char *iface_id)
-+{
-+ sset_add(&binding_iface_released_set, iface_id);
-+}
-+
-+static void
-+binding_iface_bound_add(const char *iface_id)
-+{
-+ sset_add(&binding_iface_bound_set, iface_id);
-+}
-+
- #define OVN_QOS_TYPE "linux-htb"
-
- struct qos_queue {
-@@ -688,6 +721,7 @@ local_binding_add_child(struct local_binding *lbinding,
- struct local_binding *child)
- {
- local_binding_add(&lbinding->children, child);
-+ child->parent = lbinding;
- }
-
- static struct local_binding *
-@@ -697,6 +731,13 @@ local_binding_find_child(struct local_binding *lbinding,
- return local_binding_find(&lbinding->children, child_name);
- }
-
-+static void
-+local_binding_delete_child(struct local_binding *lbinding,
-+ struct local_binding *child)
-+{
-+ shash_find_and_delete(&lbinding->children, child->name);
-+}
-+
- static bool
- is_lport_vif(const struct sbrec_port_binding *pb)
- {
-@@ -823,15 +864,52 @@ get_lport_type(const struct sbrec_port_binding *pb)
- return LP_UNKNOWN;
- }
-
-+/* For newly claimed ports, if 'notify_up' is 'false':
-+ * - set the 'pb.up' field to true if 'pb' has no 'parent_pb'.
-+ * - set the 'pb.up' field to true if 'parent_pb.up' is 'true' (e.g., for
-+ * container and virtual ports).
-+ * Otherwise request a notification to be sent when the OVS flows
-+ * corresponding to 'pb' have been installed.
-+ *
-+ * Note:
-+ * Updates (directly or through a notification) the 'pb->up' field only if
-+ * it's explicitly set to 'false'.
-+ * This is to ensure compatibility with older versions of ovn-northd.
-+ */
-+static void
-+claimed_lport_set_up(const struct sbrec_port_binding *pb,
-+ const struct sbrec_port_binding *parent_pb,
-+ const struct sbrec_chassis *chassis_rec,
-+ bool notify_up)
-+{
-+ if (!notify_up) {
-+ bool up = true;
-+ if (!parent_pb || (parent_pb->n_up && parent_pb->up[0])) {
-+ sbrec_port_binding_set_up(pb, &up, 1);
-+ }
-+ return;
-+ }
-+
-+ if (pb->chassis != chassis_rec || (pb->n_up && !pb->up[0])) {
-+ binding_iface_bound_add(pb->logical_port);
-+ }
-+}
-+
- /* Returns false if lport is not claimed due to 'sb_readonly'.
- * Returns true otherwise.
- */
- static bool
- claim_lport(const struct sbrec_port_binding *pb,
-+ const struct sbrec_port_binding *parent_pb,
- const struct sbrec_chassis *chassis_rec,
- const struct ovsrec_interface *iface_rec,
-- bool sb_readonly, struct hmap *tracked_datapaths)
-+ bool sb_readonly, bool notify_up,
-+ struct hmap *tracked_datapaths)
- {
-+ if (!sb_readonly) {
-+ claimed_lport_set_up(pb, parent_pb, chassis_rec, notify_up);
-+ }
-+
- if (pb->chassis != chassis_rec) {
- if (sb_readonly) {
- return false;
-@@ -900,7 +978,12 @@ release_lport(const struct sbrec_port_binding *pb, bool sb_readonly,
- sbrec_port_binding_set_virtual_parent(pb, NULL);
- }
-
-+ if (pb->n_up) {
-+ bool up = false;
-+ sbrec_port_binding_set_up(pb, &up, 1);
-+ }
- update_lport_tracking(pb, tracked_datapaths);
-+ binding_iface_released_add(pb->logical_port);
- VLOG_INFO("Releasing lport %s from this chassis.", pb->logical_port);
- return true;
- }
-@@ -958,8 +1041,7 @@ release_local_binding_children(const struct sbrec_chassis *chassis_rec,
- }
- }
-
-- /* Clear the local bindings' 'pb' and 'iface'. */
-- l->pb = NULL;
-+ /* Clear the local bindings' 'iface'. */
- l->iface = NULL;
- }
-
-@@ -998,8 +1080,12 @@ consider_vif_lport_(const struct sbrec_port_binding *pb,
- if (lbinding_set) {
- if (can_bind) {
- /* We can claim the lport. */
-- if (!claim_lport(pb, b_ctx_in->chassis_rec, lbinding->iface,
-- !b_ctx_in->ovnsb_idl_txn,
-+ const struct sbrec_port_binding *parent_pb =
-+ lbinding->parent ? lbinding->parent->pb : NULL;
-+
-+ if (!claim_lport(pb, parent_pb, b_ctx_in->chassis_rec,
-+ lbinding->iface, !b_ctx_in->ovnsb_idl_txn,
-+ !lbinding->parent,
- b_ctx_out->tracked_dp_bindings)){
- return false;
- }
-@@ -1203,8 +1289,8 @@ consider_nonvif_lport_(const struct sbrec_port_binding *pb,
- b_ctx_out->tracked_dp_bindings);
-
- update_local_lport_ids(pb, b_ctx_out);
-- return claim_lport(pb, b_ctx_in->chassis_rec, NULL,
-- !b_ctx_in->ovnsb_idl_txn,
-+ return claim_lport(pb, NULL, b_ctx_in->chassis_rec, NULL,
-+ !b_ctx_in->ovnsb_idl_txn, false,
- b_ctx_out->tracked_dp_bindings);
- } else if (pb->chassis == b_ctx_in->chassis_rec) {
- return release_lport(pb, !b_ctx_in->ovnsb_idl_txn,
-@@ -2063,6 +2149,16 @@ handle_deleted_vif_lport(const struct sbrec_port_binding *pb,
- * when the interface change happens. */
- if (is_lport_container(pb)) {
- remove_local_lports(pb->logical_port, b_ctx_out);
-+
-+ /* If the container port is removed we should also remove it from
-+ * its parent's children set.
-+ */
-+ if (lbinding) {
-+ if (lbinding->parent) {
-+ local_binding_delete_child(lbinding->parent, lbinding);
-+ }
-+ local_binding_destroy(lbinding);
-+ }
- }
-
- handle_deleted_lport(pb, b_ctx_in, b_ctx_out);
-@@ -2132,13 +2228,26 @@ bool
- binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in,
- struct binding_ctx_out *b_ctx_out)
- {
-- bool handled = true;
-+ /* Run the tracked port binding loop twice to ensure correctness:
-+ * 1. First to handle deleted changes. This is split in four sub-parts
-+ * because child local bindings must be cleaned up first:
-+ * a. Container ports first.
-+ * b. Then virtual ports.
-+ * c. Then regular VIFs.
-+ * d. Last other ports.
-+ * 2. Second to handle add/update changes.
-+ */
-+ struct shash deleted_container_pbs =
-+ SHASH_INITIALIZER(&deleted_container_pbs);
-+ struct shash deleted_virtual_pbs =
-+ SHASH_INITIALIZER(&deleted_virtual_pbs);
-+ struct shash deleted_vif_pbs =
-+ SHASH_INITIALIZER(&deleted_vif_pbs);
-+ struct shash deleted_other_pbs =
-+ SHASH_INITIALIZER(&deleted_other_pbs);
- const struct sbrec_port_binding *pb;
-+ bool handled = true;
-
-- /* Run the tracked port binding loop twice. One to handle deleted
-- * changes. And another to handle add/update changes.
-- * This will ensure correctness.
-- */
- SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
- b_ctx_in->port_binding_table) {
- if (!sbrec_port_binding_is_deleted(pb)) {
-@@ -2146,18 +2255,60 @@ binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in,
- }
-
- enum en_lport_type lport_type = get_lport_type(pb);
-- if (lport_type == LP_VIF || lport_type == LP_VIRTUAL) {
-- handled = handle_deleted_vif_lport(pb, lport_type, b_ctx_in,
-- b_ctx_out);
-+
-+ if (lport_type == LP_VIF) {
-+ if (is_lport_container(pb)) {
-+ shash_add(&deleted_container_pbs, pb->logical_port, pb);
-+ } else {
-+ shash_add(&deleted_vif_pbs, pb->logical_port, pb);
-+ }
-+ } else if (lport_type == LP_VIRTUAL) {
-+ shash_add(&deleted_virtual_pbs, pb->logical_port, pb);
- } else {
-- handle_deleted_lport(pb, b_ctx_in, b_ctx_out);
-+ shash_add(&deleted_other_pbs, pb->logical_port, pb);
- }
-+ }
-
-+ struct shash_node *node;
-+ struct shash_node *node_next;
-+ SHASH_FOR_EACH_SAFE (node, node_next, &deleted_container_pbs) {
-+ handled = handle_deleted_vif_lport(node->data, LP_VIF, b_ctx_in,
-+ b_ctx_out);
-+ shash_delete(&deleted_container_pbs, node);
- if (!handled) {
-- break;
-+ goto delete_done;
-+ }
-+ }
-+
-+ SHASH_FOR_EACH_SAFE (node, node_next, &deleted_virtual_pbs) {
-+ handled = handle_deleted_vif_lport(node->data, LP_VIRTUAL, b_ctx_in,
-+ b_ctx_out);
-+ shash_delete(&deleted_virtual_pbs, node);
-+ if (!handled) {
-+ goto delete_done;
- }
- }
-
-+ SHASH_FOR_EACH_SAFE (node, node_next, &deleted_vif_pbs) {
-+ handled = handle_deleted_vif_lport(node->data, LP_VIF, b_ctx_in,
-+ b_ctx_out);
-+ shash_delete(&deleted_vif_pbs, node);
-+ if (!handled) {
-+ goto delete_done;
-+ }
-+ }
-+
-+ SHASH_FOR_EACH_SAFE (node, node_next, &deleted_other_pbs) {
-+ handle_deleted_lport(node->data, b_ctx_in, b_ctx_out);
-+ shash_delete(&deleted_other_pbs, node);
-+ }
-+
-+delete_done:
-+ shash_destroy(&deleted_container_pbs);
-+ shash_destroy(&deleted_virtual_pbs);
-+ shash_destroy(&deleted_vif_pbs);
-+ shash_destroy(&deleted_other_pbs);
-+
- if (!handled) {
- return false;
- }
-@@ -2288,3 +2439,155 @@ binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in,
- destroy_qos_map(&qos_map);
- return handled;
- }
-+
-+/* Registered ofctrl seqno type for port_binding flow installation. */
-+static size_t binding_seq_type_pb_cfg;
-+
-+/* Binding specific seqno to be acked by ofctrl when flows for new interfaces
-+ * have been installed.
-+ */
-+static uint32_t binding_iface_seqno = 0;
-+
-+/* Map indexed by iface-id containing the sequence numbers that when acked
-+ * indicate that the OVS flows for the iface-id have been installed.
-+ */
-+static struct simap binding_iface_seqno_map =
-+ SIMAP_INITIALIZER(&binding_iface_seqno_map);
-+
-+void
-+binding_init(void)
-+{
-+ binding_seq_type_pb_cfg = ofctrl_seqno_add_type();
-+}
-+
-+/* Processes new release/bind operations OVN ports. For newly bound ports
-+ * it creates ofctrl seqno update requests that will be acked when
-+ * corresponding OVS flows have been installed.
-+ *
-+ * NOTE: Should be called only when valid SB and OVS transactions are
-+ * available.
-+ */
-+void
-+binding_seqno_run(struct shash *local_bindings)
-+{
-+ const char *iface_id;
-+ const char *iface_id_next;
-+
-+ SSET_FOR_EACH_SAFE (iface_id, iface_id_next, &binding_iface_released_set) {
-+ struct shash_node *lb_node = shash_find(local_bindings, iface_id);
-+
-+ /* If the local binding still exists (i.e., the OVS interface is
-+ * still configured locally) then remove the external id and remove
-+ * it from the in-flight seqno map.
-+ */
-+ if (lb_node) {
-+ struct local_binding *lb = lb_node->data;
-+
-+ if (lb->iface && smap_get(&lb->iface->external_ids,
-+ OVN_INSTALLED_EXT_ID)) {
-+ ovsrec_interface_update_external_ids_delkey(
-+ lb->iface, OVN_INSTALLED_EXT_ID);
-+ }
-+ }
-+ simap_find_and_delete(&binding_iface_seqno_map, iface_id);
-+ sset_delete(&binding_iface_released_set,
-+ SSET_NODE_FROM_NAME(iface_id));
-+ }
-+
-+ bool new_ifaces = false;
-+ uint32_t new_seqno = binding_iface_seqno + 1;
-+
-+ SSET_FOR_EACH_SAFE (iface_id, iface_id_next, &binding_iface_bound_set) {
-+ struct shash_node *lb_node = shash_find(local_bindings, iface_id);
-+
-+ struct local_binding *lb = lb_node ? lb_node->data : NULL;
-+
-+ /* Make sure the binding is still complete, i.e., both SB port_binding
-+ * and OVS interface still exist.
-+ *
-+ * If so, then this is a newly bound interface, make sure we reset the
-+ * Port_Binding 'up' field and the OVS Interface 'external-id'.
-+ */
-+ if (lb && lb->pb && lb->iface) {
-+ new_ifaces = true;
-+
-+ if (smap_get(&lb->iface->external_ids, OVN_INSTALLED_EXT_ID)) {
-+ ovsrec_interface_update_external_ids_delkey(
-+ lb->iface, OVN_INSTALLED_EXT_ID);
-+ }
-+ if (lb->pb->n_up) {
-+ bool up = false;
-+ sbrec_port_binding_set_up(lb->pb, &up, 1);
-+ }
-+ simap_put(&binding_iface_seqno_map, lb->name, new_seqno);
-+ }
-+ sset_delete(&binding_iface_bound_set, SSET_NODE_FROM_NAME(iface_id));
-+ }
-+
-+ /* Request a seqno update when the flows for new interfaces have been
-+ * installed in OVS.
-+ */
-+ if (new_ifaces) {
-+ binding_iface_seqno = new_seqno;
-+ ofctrl_seqno_update_create(binding_seq_type_pb_cfg, new_seqno);
-+ }
-+}
-+
-+/* Processes ofctrl seqno ACKs for new bindings. Sets the
-+ * 'OVN_INSTALLED_EXT_ID' external-id in the OVS interface and the
-+ * Port_Binding.up field for all ports for which OVS flows have been
-+ * installed.
-+ *
-+ * NOTE: Should be called only when valid SB and OVS transactions are
-+ * available.
-+ */
-+void
-+binding_seqno_install(struct shash *local_bindings)
-+{
-+ struct ofctrl_acked_seqnos *acked_seqnos =
-+ ofctrl_acked_seqnos_get(binding_seq_type_pb_cfg);
-+ struct simap_node *node;
-+ struct simap_node *node_next;
-+
-+ SIMAP_FOR_EACH_SAFE (node, node_next, &binding_iface_seqno_map) {
-+ struct shash_node *lb_node = shash_find(local_bindings, node->name);
-+
-+ if (!lb_node) {
-+ goto del_seqno;
-+ }
-+
-+ struct local_binding *lb = lb_node->data;
-+ if (!lb->pb || !lb->iface) {
-+ goto del_seqno;
-+ }
-+
-+ if (!ofctrl_acked_seqnos_contains(acked_seqnos, node->data)) {
-+ continue;
-+ }
-+
-+ ovsrec_interface_update_external_ids_setkey(lb->iface,
-+ OVN_INSTALLED_EXT_ID,
-+ "true");
-+ if (lb->pb->n_up) {
-+ bool up = true;
-+
-+ sbrec_port_binding_set_up(lb->pb, &up, 1);
-+ struct shash_node *child_node;
-+ SHASH_FOR_EACH (child_node, &lb->children) {
-+ struct local_binding *lb_child = child_node->data;
-+ sbrec_port_binding_set_up(lb_child->pb, &up, 1);
-+ }
-+ }
-+
-+del_seqno:
-+ simap_delete(&binding_iface_seqno_map, node);
-+ }
-+
-+ ofctrl_acked_seqnos_destroy(acked_seqnos);
-+}
-+
-+void
-+binding_seqno_flush(void)
-+{
-+ simap_clear(&binding_iface_seqno_map);
-+}
-diff --git a/controller/binding.h b/controller/binding.h
-index c9740560f..c9ebef4b1 100644
---- a/controller/binding.h
-+++ b/controller/binding.h
-@@ -100,6 +100,7 @@ struct local_binding {
-
- /* shash of 'struct local_binding' representing children. */
- struct shash children;
-+ struct local_binding *parent;
- };
-
- static inline struct local_binding *
-@@ -134,4 +135,9 @@ bool binding_handle_ovs_interface_changes(struct binding_ctx_in *,
- bool binding_handle_port_binding_changes(struct binding_ctx_in *,
- struct binding_ctx_out *);
- void binding_tracked_dp_destroy(struct hmap *tracked_datapaths);
-+
-+void binding_init(void);
-+void binding_seqno_run(struct shash *local_bindings);
-+void binding_seqno_install(struct shash *local_bindings);
-+void binding_seqno_flush(void);
- #endif /* controller/binding.h */
-diff --git a/controller/chassis.c b/controller/chassis.c
-index b4d4b0e37..0937e33e6 100644
---- a/controller/chassis.c
-+++ b/controller/chassis.c
-@@ -28,6 +28,7 @@
- #include "lib/ovn-sb-idl.h"
- #include "ovn-controller.h"
- #include "lib/util.h"
-+#include "ovn/features.h"
-
- VLOG_DEFINE_THIS_MODULE(chassis);
-
-@@ -293,6 +294,7 @@ chassis_build_other_config(struct smap *config, const char *bridge_mappings,
- smap_replace(config, "iface-types", iface_types);
- smap_replace(config, "ovn-chassis-mac-mappings", chassis_macs);
- smap_replace(config, "is-interconn", is_interconn ? "true" : "false");
-+ smap_replace(config, OVN_FEATURE_PORT_UP_NOTIF, "true");
- }
-
- /*
-@@ -363,6 +365,11 @@ chassis_other_config_changed(const char *bridge_mappings,
- return true;
- }
-
-+ if (!smap_get_bool(&chassis_rec->other_config, OVN_FEATURE_PORT_UP_NOTIF,
-+ false)) {
-+ return true;
-+ }
-+
- return false;
- }
-
-diff --git a/controller/lflow.c b/controller/lflow.c
-index c02585b1e..76a4deaa0 100644
---- a/controller/lflow.c
-+++ b/controller/lflow.c
-@@ -88,6 +88,11 @@ static void lflow_resource_destroy_lflow(struct lflow_resource_ref *,
- static bool
- lookup_port_cb(const void *aux_, const char *port_name, unsigned int *portp)
- {
-+ if (!strcmp(port_name, "none")) {
-+ *portp = 0;
-+ return true;
-+ }
-+
- const struct lookup_port_aux *aux = aux_;
-
- const struct sbrec_port_binding *pb
-@@ -480,22 +485,19 @@ lflow_handle_changed_flows(struct lflow_ctx_in *l_ctx_in,
- struct controller_event_options controller_event_opts;
- controller_event_opts_init(&controller_event_opts);
-
-- /* Handle flow removing first (for deleted or updated lflows), and then
-- * handle reprocessing or adding flows, so that when the flows being
-- * removed and added with same match conditions can be processed in the
-- * proper order */
--
-+ /* Flood remove the flows for all the tracked lflows. Its possible that
-+ * lflow_add_flows_for_datapath() may have been called before calling
-+ * this function. */
- struct hmap flood_remove_nodes = HMAP_INITIALIZER(&flood_remove_nodes);
- struct ofctrl_flood_remove_node *ofrn, *next;
- SBREC_LOGICAL_FLOW_TABLE_FOR_EACH_TRACKED (lflow,
- l_ctx_in->logical_flow_table) {
-+ VLOG_DBG("delete lflow "UUID_FMT, UUID_ARGS(&lflow->header_.uuid));
-+ ofrn = xmalloc(sizeof *ofrn);
-+ ofrn->sb_uuid = lflow->header_.uuid;
-+ hmap_insert(&flood_remove_nodes, &ofrn->hmap_node,
-+ uuid_hash(&ofrn->sb_uuid));
- if (!sbrec_logical_flow_is_new(lflow)) {
-- VLOG_DBG("delete lflow "UUID_FMT,
-- UUID_ARGS(&lflow->header_.uuid));
-- ofrn = xmalloc(sizeof *ofrn);
-- ofrn->sb_uuid = lflow->header_.uuid;
-- hmap_insert(&flood_remove_nodes, &ofrn->hmap_node,
-- uuid_hash(&ofrn->sb_uuid));
- if (l_ctx_out->lflow_cache_map) {
- lflow_cache_delete(l_ctx_out->lflow_cache_map, lflow);
- }
-@@ -525,21 +527,6 @@ lflow_handle_changed_flows(struct lflow_ctx_in *l_ctx_in,
- }
- hmap_destroy(&flood_remove_nodes);
-
-- /* Now handle new lflows only. */
-- SBREC_LOGICAL_FLOW_TABLE_FOR_EACH_TRACKED (lflow,
-- l_ctx_in->logical_flow_table) {
-- if (sbrec_logical_flow_is_new(lflow)) {
-- VLOG_DBG("add lflow "UUID_FMT,
-- UUID_ARGS(&lflow->header_.uuid));
-- if (!consider_logical_flow(lflow, &dhcp_opts, &dhcpv6_opts,
-- &nd_ra_opts, &controller_event_opts,
-- l_ctx_in, l_ctx_out)) {
-- ret = false;
-- l_ctx_out->conj_id_overflow = true;
-- break;
-- }
-- }
-- }
- dhcp_opts_destroy(&dhcp_opts);
- dhcp_opts_destroy(&dhcpv6_opts);
- nd_ra_opts_destroy(&nd_ra_opts);
-@@ -668,9 +655,8 @@ update_conj_id_ofs(uint32_t *conj_id_ofs, uint32_t n_conjs)
- static void
- add_matches_to_flow_table(const struct sbrec_logical_flow *lflow,
- const struct sbrec_datapath_binding *dp,
-- struct hmap *matches, size_t conj_id_ofs,
-- uint8_t ptable, uint8_t output_ptable,
-- struct ofpbuf *ovnacts,
-+ struct hmap *matches, uint8_t ptable,
-+ uint8_t output_ptable, struct ofpbuf *ovnacts,
- bool ingress, struct lflow_ctx_in *l_ctx_in,
- struct lflow_ctx_out *l_ctx_out)
- {
-@@ -702,15 +688,14 @@ add_matches_to_flow_table(const struct sbrec_logical_flow *lflow,
- .lb_hairpin_ptable = OFTABLE_CHK_LB_HAIRPIN,
- .lb_hairpin_reply_ptable = OFTABLE_CHK_LB_HAIRPIN_REPLY,
- .ct_snat_vip_ptable = OFTABLE_CT_SNAT_FOR_VIP,
-+ .fdb_ptable = OFTABLE_GET_FDB,
-+ .fdb_lookup_ptable = OFTABLE_LOOKUP_FDB,
- };
- ovnacts_encode(ovnacts->data, ovnacts->size, &ep, &ofpacts);
-
- struct expr_match *m;
- HMAP_FOR_EACH (m, hmap_node, matches) {
- match_set_metadata(&m->match, htonll(dp->tunnel_key));
-- if (m->match.wc.masks.conj_id) {
-- m->match.flow.conj_id += conj_id_ofs;
-- }
- if (datapath_is_switch(dp)) {
- unsigned int reg_index
- = (ingress ? MFF_LOG_INPORT : MFF_LOG_OUTPORT) - MFF_REG0;
-@@ -744,7 +729,7 @@ add_matches_to_flow_table(const struct sbrec_logical_flow *lflow,
- struct ofpact_conjunction *dst;
-
- dst = ofpact_put_CONJUNCTION(&conj);
-- dst->id = src->id + conj_id_ofs;
-+ dst->id = src->id;
- dst->clause = src->clause;
- dst->n_clauses = src->n_clauses;
- }
-@@ -915,9 +900,9 @@ consider_logical_flow__(const struct sbrec_logical_flow *lflow,
- return true;
- }
-
-- add_matches_to_flow_table(lflow, dp, &matches, *l_ctx_out->conj_id_ofs,
-- ptable, output_ptable, &ovnacts, ingress,
-- l_ctx_in, l_ctx_out);
-+ expr_matches_prepare(&matches, *l_ctx_out->conj_id_ofs);
-+ add_matches_to_flow_table(lflow, dp, &matches, ptable, output_ptable,
-+ &ovnacts, ingress, l_ctx_in, l_ctx_out);
-
- ovnacts_free(ovnacts.data, ovnacts.size);
- ofpbuf_uninit(&ovnacts);
-@@ -930,10 +915,11 @@ consider_logical_flow__(const struct sbrec_logical_flow *lflow,
- lflow_cache_get(l_ctx_out->lflow_cache_map, lflow);
-
- if (lc && lc->type == LCACHE_T_MATCHES) {
-- /* 'matches' is cached. No need to do expr parsing.
-+ /* 'matches' is cached. No need to do expr parsing and no need
-+ * to call expr_matches_prepare() to update the conj ids.
- * Add matches to flow table and return. */
-- add_matches_to_flow_table(lflow, dp, lc->expr_matches, lc->conj_id_ofs,
-- ptable, output_ptable, &ovnacts, ingress,
-+ add_matches_to_flow_table(lflow, dp, lc->expr_matches, ptable,
-+ output_ptable, &ovnacts, ingress,
- l_ctx_in, l_ctx_out);
- ovnacts_free(ovnacts.data, ovnacts.size);
- ofpbuf_uninit(&ovnacts);
-@@ -1009,10 +995,11 @@ consider_logical_flow__(const struct sbrec_logical_flow *lflow,
- }
- }
-
-+ expr_matches_prepare(matches, lc->conj_id_ofs);
-+
- /* Encode OVN logical actions into OpenFlow. */
-- add_matches_to_flow_table(lflow, dp, matches, lc->conj_id_ofs,
-- ptable, output_ptable, &ovnacts, ingress,
-- l_ctx_in, l_ctx_out);
-+ add_matches_to_flow_table(lflow, dp, matches, ptable, output_ptable,
-+ &ovnacts, ingress, l_ctx_in, l_ctx_out);
- ovnacts_free(ovnacts.data, ovnacts.size);
- ofpbuf_uninit(&ovnacts);
-
-@@ -1080,6 +1067,18 @@ put_load(const uint8_t *data, size_t len,
- bitwise_one(ofpact_set_field_mask(sf), sf->field->n_bytes, ofs, n_bits);
- }
-
-+static void
-+put_load64(uint64_t value, enum mf_field_id dst, int ofs, int n_bits,
-+ struct ofpbuf *ofpacts)
-+{
-+ struct ofpact_set_field *sf = ofpact_put_set_field(ofpacts,
-+ mf_from_id(dst), NULL,
-+ NULL);
-+ ovs_be64 n_value = htonll(value);
-+ bitwise_copy(&n_value, 8, 0, sf->value, sf->field->n_bytes, ofs, n_bits);
-+ bitwise_one(ofpact_set_field_mask(sf), sf->field->n_bytes, ofs, n_bits);
-+}
-+
- static void
- consider_neighbor_flow(struct ovsdb_idl_index *sbrec_port_binding_by_name,
- const struct hmap *local_datapaths,
-@@ -1173,6 +1172,184 @@ add_neighbor_flows(struct ovsdb_idl_index *sbrec_port_binding_by_name,
- }
- }
-
-+/* Builds the "learn()" action to be triggered by packets initiating a
-+ * hairpin session.
-+ *
-+ * This will generate flows in table OFTABLE_CHK_LB_HAIRPIN_REPLY of the form:
-+ * - match:
-+ * metadata=,ip/ipv6,ip.src=,ip.dst=
-+ * nw_proto='lb_proto',tp_src_port=
-+ * - action:
-+ * set MLF_LOOKUP_LB_HAIRPIN_BIT=1
-+ */
-+static void
-+add_lb_vip_hairpin_reply_action(struct in6_addr *vip6, ovs_be32 vip,
-+ uint8_t lb_proto, bool has_l4_port,
-+ uint64_t cookie, struct ofpbuf *ofpacts)
-+{
-+ struct match match = MATCH_CATCHALL_INITIALIZER;
-+ struct ofpact_learn *ol = ofpact_put_LEARN(ofpacts);
-+ struct ofpact_learn_spec *ol_spec;
-+ unsigned int imm_bytes;
-+ uint8_t *src_imm;
-+
-+ /* Once learned, hairpin reply flows are permanent until the VIP/backend
-+ * is removed.
-+ */
-+ ol->flags = NX_LEARN_F_DELETE_LEARNED;
-+ ol->idle_timeout = OFP_FLOW_PERMANENT;
-+ ol->hard_timeout = OFP_FLOW_PERMANENT;
-+ ol->priority = OFP_DEFAULT_PRIORITY;
-+ ol->table_id = OFTABLE_CHK_LB_HAIRPIN_REPLY;
-+ ol->cookie = htonll(cookie);
-+
-+ /* Match on metadata of the packet that created the hairpin session. */
-+ ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec);
-+
-+ ol_spec->dst.field = mf_from_id(MFF_METADATA);
-+ ol_spec->dst.ofs = 0;
-+ ol_spec->dst.n_bits = ol_spec->dst.field->n_bits;
-+ ol_spec->n_bits = ol_spec->dst.n_bits;
-+ ol_spec->dst_type = NX_LEARN_DST_MATCH;
-+ ol_spec->src_type = NX_LEARN_SRC_FIELD;
-+ ol_spec->src.field = mf_from_id(MFF_METADATA);
-+
-+ /* Match on the same ETH type as the packet that created the hairpin
-+ * session.
-+ */
-+ ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec);
-+ ol_spec->dst.field = mf_from_id(MFF_ETH_TYPE);
-+ ol_spec->dst.ofs = 0;
-+ ol_spec->dst.n_bits = ol_spec->dst.field->n_bits;
-+ ol_spec->n_bits = ol_spec->dst.n_bits;
-+ ol_spec->dst_type = NX_LEARN_DST_MATCH;
-+ ol_spec->src_type = NX_LEARN_SRC_IMMEDIATE;
-+ union mf_value imm_eth_type = {
-+ .be16 = !vip6 ? htons(ETH_TYPE_IP) : htons(ETH_TYPE_IPV6)
-+ };
-+ mf_write_subfield_value(&ol_spec->dst, &imm_eth_type, &match);
-+
-+ /* Push value last, as this may reallocate 'ol_spec'. */
-+ imm_bytes = DIV_ROUND_UP(ol_spec->dst.n_bits, 8);
-+ src_imm = ofpbuf_put_zeros(ofpacts, OFPACT_ALIGN(imm_bytes));
-+ memcpy(src_imm, &imm_eth_type, imm_bytes);
-+
-+ /* Hairpin replies have ip.src == . */
-+ ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec);
-+ if (!vip6) {
-+ ol_spec->dst.field = mf_from_id(MFF_IPV4_SRC);
-+ ol_spec->src.field = mf_from_id(MFF_IPV4_SRC);
-+ } else {
-+ ol_spec->dst.field = mf_from_id(MFF_IPV6_SRC);
-+ ol_spec->src.field = mf_from_id(MFF_IPV6_SRC);
-+ }
-+ ol_spec->dst.ofs = 0;
-+ ol_spec->dst.n_bits = ol_spec->dst.field->n_bits;
-+ ol_spec->n_bits = ol_spec->dst.n_bits;
-+ ol_spec->dst_type = NX_LEARN_DST_MATCH;
-+ ol_spec->src_type = NX_LEARN_SRC_FIELD;
-+
-+ /* Hairpin replies have ip.dst == . */
-+ union mf_value imm_ip;
-+ ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec);
-+ if (!vip6) {
-+ ol_spec->dst.field = mf_from_id(MFF_IPV4_DST);
-+ imm_ip = (union mf_value) {
-+ .be32 = vip
-+ };
-+ } else {
-+ ol_spec->dst.field = mf_from_id(MFF_IPV6_DST);
-+ imm_ip = (union mf_value) {
-+ .ipv6 = *vip6
-+ };
-+ }
-+ ol_spec->dst.ofs = 0;
-+ ol_spec->dst.n_bits = ol_spec->dst.field->n_bits;
-+ ol_spec->n_bits = ol_spec->dst.n_bits;
-+ ol_spec->dst_type = NX_LEARN_DST_MATCH;
-+ ol_spec->src_type = NX_LEARN_SRC_IMMEDIATE;
-+ mf_write_subfield_value(&ol_spec->dst, &imm_ip, &match);
-+
-+ /* Push value last, as this may reallocate 'ol_spec' */
-+ imm_bytes = DIV_ROUND_UP(ol_spec->dst.n_bits, 8);
-+ src_imm = ofpbuf_put_zeros(ofpacts, OFPACT_ALIGN(imm_bytes));
-+ memcpy(src_imm, &imm_ip, imm_bytes);
-+
-+ /* Hairpin replies have the same nw_proto as packets that created the
-+ * session.
-+ */
-+ union mf_value imm_proto = {
-+ .u8 = lb_proto,
-+ };
-+ ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec);
-+ ol_spec->dst.field = mf_from_id(MFF_IP_PROTO);
-+ ol_spec->src.field = mf_from_id(MFF_IP_PROTO);
-+ ol_spec->dst.ofs = 0;
-+ ol_spec->dst.n_bits = ol_spec->dst.field->n_bits;
-+ ol_spec->n_bits = ol_spec->dst.n_bits;
-+ ol_spec->dst_type = NX_LEARN_DST_MATCH;
-+ ol_spec->src_type = NX_LEARN_SRC_IMMEDIATE;
-+ mf_write_subfield_value(&ol_spec->dst, &imm_proto, &match);
-+
-+ /* Push value last, as this may reallocate 'ol_spec' */
-+ imm_bytes = DIV_ROUND_UP(ol_spec->dst.n_bits, 8);
-+ src_imm = ofpbuf_put_zeros(ofpacts, OFPACT_ALIGN(imm_bytes));
-+ memcpy(src_imm, &imm_proto, imm_bytes);
-+
-+ /* Hairpin replies have source port == . */
-+ if (has_l4_port) {
-+ ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec);
-+ switch (lb_proto) {
-+ case IPPROTO_TCP:
-+ ol_spec->dst.field = mf_from_id(MFF_TCP_SRC);
-+ ol_spec->src.field = mf_from_id(MFF_TCP_DST);
-+ break;
-+ case IPPROTO_UDP:
-+ ol_spec->dst.field = mf_from_id(MFF_UDP_SRC);
-+ ol_spec->src.field = mf_from_id(MFF_UDP_DST);
-+ break;
-+ case IPPROTO_SCTP:
-+ ol_spec->dst.field = mf_from_id(MFF_SCTP_SRC);
-+ ol_spec->src.field = mf_from_id(MFF_SCTP_DST);
-+ break;
-+ default:
-+ OVS_NOT_REACHED();
-+ break;
-+ }
-+ ol_spec->dst.ofs = 0;
-+ ol_spec->dst.n_bits = ol_spec->dst.field->n_bits;
-+ ol_spec->n_bits = ol_spec->dst.n_bits;
-+ ol_spec->dst_type = NX_LEARN_DST_MATCH;
-+ ol_spec->src_type = NX_LEARN_SRC_FIELD;
-+ }
-+
-+ /* Set MLF_LOOKUP_LB_HAIRPIN_BIT for hairpin replies. */
-+ ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec);
-+ ol_spec->dst.field = mf_from_id(MFF_LOG_FLAGS);
-+ ol_spec->dst.ofs = MLF_LOOKUP_LB_HAIRPIN_BIT;
-+ ol_spec->dst.n_bits = 1;
-+ ol_spec->n_bits = ol_spec->dst.n_bits;
-+ ol_spec->dst_type = NX_LEARN_DST_LOAD;
-+ ol_spec->src_type = NX_LEARN_SRC_IMMEDIATE;
-+ union mf_value imm_reg_value = {
-+ .u8 = 1
-+ };
-+ mf_write_subfield_value(&ol_spec->dst, &imm_reg_value, &match);
-+
-+ /* Push value last, as this may reallocate 'ol_spec' */
-+ imm_bytes = DIV_ROUND_UP(ol_spec->dst.n_bits, 8);
-+ src_imm = ofpbuf_put_zeros(ofpacts, OFPACT_ALIGN(imm_bytes));
-+ memcpy(src_imm, &imm_reg_value, imm_bytes);
-+
-+ ofpact_finish_LEARN(ofpacts, &ol);
-+}
-+
-+/* Adds flows to detect hairpin sessions.
-+ *
-+ * For backwards compatibilty with older ovn-northd versions, uses
-+ * ct_nw_dst(), ct_ipv6_dst(), ct_tp_dst(), otherwise uses the
-+ * original destination tuple stored by ovn-northd.
-+ */
- static void
- add_lb_vip_hairpin_flows(struct ovn_controller_lb *lb,
- struct ovn_lb_vip *lb_vip,
-@@ -1182,43 +1359,81 @@ add_lb_vip_hairpin_flows(struct ovn_controller_lb *lb,
- {
- uint64_t stub[1024 / 8];
- struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(stub);
-+ struct match hairpin_match = MATCH_CATCHALL_INITIALIZER;
-
- uint8_t value = 1;
- put_load(&value, sizeof value, MFF_LOG_FLAGS,
- MLF_LOOKUP_LB_HAIRPIN_BIT, 1, &ofpacts);
-
-- struct match hairpin_match = MATCH_CATCHALL_INITIALIZER;
-- struct match hairpin_reply_match = MATCH_CATCHALL_INITIALIZER;
-+ /* Matching on ct_nw_dst()/ct_ipv6_dst()/ct_tp_dst() requires matching
-+ * on ct_state first.
-+ */
-+ if (!lb->hairpin_orig_tuple) {
-+ uint32_t ct_state = OVS_CS_F_TRACKED | OVS_CS_F_DST_NAT;
-+ match_set_ct_state_masked(&hairpin_match, ct_state, ct_state);
-+ }
-
- if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
-- ovs_be32 ip4 = in6_addr_get_mapped_ipv4(&lb_backend->ip);
-+ ovs_be32 bip4 = in6_addr_get_mapped_ipv4(&lb_backend->ip);
-+ ovs_be32 vip4 = in6_addr_get_mapped_ipv4(&lb_vip->vip);
-+ ovs_be32 snat_vip4 = lb->hairpin_snat_ips.n_ipv4_addrs
-+ ? lb->hairpin_snat_ips.ipv4_addrs[0].addr
-+ : vip4;
-
- match_set_dl_type(&hairpin_match, htons(ETH_TYPE_IP));
-- match_set_nw_src(&hairpin_match, ip4);
-- match_set_nw_dst(&hairpin_match, ip4);
--
-- match_set_dl_type(&hairpin_reply_match,
-- htons(ETH_TYPE_IP));
-- match_set_nw_src(&hairpin_reply_match, ip4);
-- match_set_nw_dst(&hairpin_reply_match,
-- in6_addr_get_mapped_ipv4(&lb_vip->vip));
-+ match_set_nw_src(&hairpin_match, bip4);
-+ match_set_nw_dst(&hairpin_match, bip4);
-+
-+ if (!lb->hairpin_orig_tuple) {
-+ match_set_ct_nw_dst(&hairpin_match, vip4);
-+ } else {
-+ match_set_reg(&hairpin_match,
-+ MFF_LOG_LB_ORIG_DIP_IPV4 - MFF_LOG_REG0,
-+ ntohl(vip4));
-+ }
-+
-+ add_lb_vip_hairpin_reply_action(NULL, snat_vip4, lb_proto,
-+ lb_backend->port,
-+ lb->slb->header_.uuid.parts[0],
-+ &ofpacts);
- } else {
-+ struct in6_addr *bip6 = &lb_backend->ip;
-+ struct in6_addr *snat_vip6 =
-+ lb->hairpin_snat_ips.n_ipv6_addrs
-+ ? &lb->hairpin_snat_ips.ipv6_addrs[0].addr
-+ : &lb_vip->vip;
- match_set_dl_type(&hairpin_match, htons(ETH_TYPE_IPV6));
-- match_set_ipv6_src(&hairpin_match, &lb_backend->ip);
-- match_set_ipv6_dst(&hairpin_match, &lb_backend->ip);
-+ match_set_ipv6_src(&hairpin_match, bip6);
-+ match_set_ipv6_dst(&hairpin_match, bip6);
-
-- match_set_dl_type(&hairpin_reply_match,
-- htons(ETH_TYPE_IPV6));
-- match_set_ipv6_src(&hairpin_reply_match, &lb_backend->ip);
-- match_set_ipv6_dst(&hairpin_reply_match, &lb_vip->vip);
-+ if (!lb->hairpin_orig_tuple) {
-+ match_set_ct_ipv6_dst(&hairpin_match, &lb_vip->vip);
-+ } else {
-+ ovs_be128 vip6_value;
-+
-+ memcpy(&vip6_value, &lb_vip->vip, sizeof vip6_value);
-+ match_set_xxreg(&hairpin_match,
-+ MFF_LOG_LB_ORIG_DIP_IPV6 - MFF_LOG_XXREG0,
-+ ntoh128(vip6_value));
-+ }
-+
-+ add_lb_vip_hairpin_reply_action(snat_vip6, 0, lb_proto,
-+ lb_backend->port,
-+ lb->slb->header_.uuid.parts[0],
-+ &ofpacts);
- }
-
- if (lb_backend->port) {
- match_set_nw_proto(&hairpin_match, lb_proto);
- match_set_tp_dst(&hairpin_match, htons(lb_backend->port));
--
-- match_set_nw_proto(&hairpin_reply_match, lb_proto);
-- match_set_tp_src(&hairpin_reply_match, htons(lb_backend->port));
-+ if (!lb->hairpin_orig_tuple) {
-+ match_set_ct_nw_proto(&hairpin_match, lb_proto);
-+ match_set_ct_tp_dst(&hairpin_match, htons(lb_vip->vip_port));
-+ } else {
-+ match_set_reg_masked(&hairpin_match,
-+ MFF_LOG_LB_ORIG_TP_DPORT - MFF_REG0,
-+ lb_vip->vip_port, UINT16_MAX);
-+ }
- }
-
- /* In the original direction, only match on traffic that was already
-@@ -1239,23 +1454,19 @@ add_lb_vip_hairpin_flows(struct ovn_controller_lb *lb,
- ofctrl_add_flow(flow_table, OFTABLE_CHK_LB_HAIRPIN, 100,
- lb->slb->header_.uuid.parts[0], &hairpin_match,
- &ofpacts, &lb->slb->header_.uuid);
--
-- for (size_t i = 0; i < lb->slb->n_datapaths; i++) {
-- match_set_metadata(&hairpin_reply_match,
-- htonll(lb->slb->datapaths[i]->tunnel_key));
--
-- ofctrl_add_flow(flow_table, OFTABLE_CHK_LB_HAIRPIN_REPLY, 100,
-- lb->slb->header_.uuid.parts[0],
-- &hairpin_reply_match,
-- &ofpacts, &lb->slb->header_.uuid);
-- }
--
- ofpbuf_uninit(&ofpacts);
- }
-
-+/* Adds flows to perform SNAT for hairpin sessions.
-+ *
-+ * For backwards compatibilty with older ovn-northd versions, uses
-+ * ct_nw_dst(), ct_ipv6_dst(), ct_tp_dst(), otherwise uses the
-+ * original destination tuple stored by ovn-northd.
-+ */
- static void
- add_lb_ct_snat_vip_flows(struct ovn_controller_lb *lb,
- struct ovn_lb_vip *lb_vip,
-+ uint8_t lb_proto,
- struct ovn_desired_flow_table *flow_table)
- {
- uint64_t stub[1024 / 8];
-@@ -1279,25 +1490,65 @@ add_lb_ct_snat_vip_flows(struct ovn_controller_lb *lb,
-
- if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
- nat->range_af = AF_INET;
-- nat->range.addr.ipv4.min = in6_addr_get_mapped_ipv4(&lb_vip->vip);
-+ nat->range.addr.ipv4.min =
-+ lb->hairpin_snat_ips.n_ipv4_addrs
-+ ? lb->hairpin_snat_ips.ipv4_addrs[0].addr
-+ : in6_addr_get_mapped_ipv4(&lb_vip->vip);
- } else {
- nat->range_af = AF_INET6;
-- nat->range.addr.ipv6.min = lb_vip->vip;
-+ nat->range.addr.ipv6.min
-+ = lb->hairpin_snat_ips.n_ipv6_addrs
-+ ? lb->hairpin_snat_ips.ipv6_addrs[0].addr
-+ : lb_vip->vip;
- }
- ofpacts.header = ofpbuf_push_uninit(&ofpacts, nat_offset);
- ofpact_finish(&ofpacts, &ct->ofpact);
-
- struct match match = MATCH_CATCHALL_INITIALIZER;
-+
-+ /* Matching on ct_nw_dst()/ct_ipv6_dst()/ct_tp_dst() requires matching
-+ * on ct_state first.
-+ */
-+ if (!lb->hairpin_orig_tuple) {
-+ uint32_t ct_state = OVS_CS_F_TRACKED | OVS_CS_F_DST_NAT;
-+ match_set_ct_state_masked(&match, ct_state, ct_state);
-+ }
-+
- if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
-+ ovs_be32 vip4 = in6_addr_get_mapped_ipv4(&lb_vip->vip);
-+
- match_set_dl_type(&match, htons(ETH_TYPE_IP));
-- match_set_ct_nw_dst(&match, nat->range.addr.ipv4.min);
-+
-+ if (!lb->hairpin_orig_tuple) {
-+ match_set_ct_nw_dst(&match, vip4);
-+ } else {
-+ match_set_reg(&match, MFF_LOG_LB_ORIG_DIP_IPV4 - MFF_LOG_REG0,
-+ ntohl(vip4));
-+ }
- } else {
- match_set_dl_type(&match, htons(ETH_TYPE_IPV6));
-- match_set_ct_ipv6_dst(&match, &lb_vip->vip);
-+
-+ if (!lb->hairpin_orig_tuple) {
-+ match_set_ct_ipv6_dst(&match, &lb_vip->vip);
-+ } else {
-+ ovs_be128 vip6_value;
-+
-+ memcpy(&vip6_value, &lb_vip->vip, sizeof vip6_value);
-+ match_set_xxreg(&match, MFF_LOG_LB_ORIG_DIP_IPV6 - MFF_LOG_XXREG0,
-+ ntoh128(vip6_value));
-+ }
- }
-
-- uint32_t ct_state = OVS_CS_F_TRACKED | OVS_CS_F_DST_NAT;
-- match_set_ct_state_masked(&match, ct_state, ct_state);
-+ match_set_nw_proto(&match, lb_proto);
-+ if (lb_vip->vip_port) {
-+ if (!lb->hairpin_orig_tuple) {
-+ match_set_ct_nw_proto(&match, lb_proto);
-+ match_set_ct_tp_dst(&match, htons(lb_vip->vip_port));
-+ } else {
-+ match_set_reg_masked(&match, MFF_LOG_LB_ORIG_TP_DPORT - MFF_REG0,
-+ lb_vip->vip_port, UINT16_MAX);
-+ }
-+ }
-
- for (size_t i = 0; i < lb->slb->n_datapaths; i++) {
- match_set_metadata(&match,
-@@ -1351,7 +1602,7 @@ consider_lb_hairpin_flows(const struct sbrec_load_balancer *sbrec_lb,
- flow_table);
- }
-
-- add_lb_ct_snat_vip_flows(lb, lb_vip, flow_table);
-+ add_lb_ct_snat_vip_flows(lb, lb_vip, lb_proto, flow_table);
- }
-
- ovn_controller_lb_destroy(lb);
-@@ -1404,6 +1655,61 @@ lflow_handle_changed_neighbors(
- }
- }
-
-+static void
-+consider_fdb_flows(const struct sbrec_fdb *fdb,
-+ const struct hmap *local_datapaths,
-+ struct ovn_desired_flow_table *flow_table)
-+{
-+ if (!get_local_datapath(local_datapaths, fdb->dp_key)) {
-+ return;
-+ }
-+
-+ struct eth_addr mac;
-+ if (!eth_addr_from_string(fdb->mac, &mac)) {
-+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-+ VLOG_WARN_RL(&rl, "bad 'mac' %s", fdb->mac);
-+ return;
-+ }
-+
-+ struct match match = MATCH_CATCHALL_INITIALIZER;
-+ match_set_metadata(&match, htonll(fdb->dp_key));
-+ match_set_dl_dst(&match, mac);
-+
-+ uint64_t stub[1024 / 8];
-+ struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(stub);
-+ put_load64(fdb->port_key, MFF_LOG_OUTPORT, 0, 32, &ofpacts);
-+ ofctrl_add_flow(flow_table, OFTABLE_GET_FDB, 100,
-+ fdb->header_.uuid.parts[0], &match, &ofpacts,
-+ &fdb->header_.uuid);
-+ ofpbuf_clear(&ofpacts);
-+
-+ uint8_t value = 1;
-+ put_load(&value, sizeof value, MFF_LOG_FLAGS,
-+ MLF_LOOKUP_FDB_BIT, 1, &ofpacts);
-+
-+ struct match lookup_match = MATCH_CATCHALL_INITIALIZER;
-+ match_set_metadata(&lookup_match, htonll(fdb->dp_key));
-+ match_set_dl_src(&lookup_match, mac);
-+ match_set_reg(&lookup_match, MFF_LOG_INPORT - MFF_REG0, fdb->port_key);
-+ ofctrl_add_flow(flow_table, OFTABLE_LOOKUP_FDB, 100,
-+ fdb->header_.uuid.parts[0], &lookup_match, &ofpacts,
-+ &fdb->header_.uuid);
-+ ofpbuf_uninit(&ofpacts);
-+}
-+
-+/* Adds an OpenFlow flow to flow tables for each MAC binding in the OVN
-+ * southbound database. */
-+static void
-+add_fdb_flows(const struct sbrec_fdb_table *fdb_table,
-+ const struct hmap *local_datapaths,
-+ struct ovn_desired_flow_table *flow_table)
-+{
-+ const struct sbrec_fdb *fdb;
-+ SBREC_FDB_TABLE_FOR_EACH (fdb, fdb_table) {
-+ consider_fdb_flows(fdb, local_datapaths, flow_table);
-+ }
-+}
-+
-
- /* Translates logical flows in the Logical_Flow table in the OVN_SB database
- * into OpenFlow flows. See ovn-architecture(7) for more information. */
-@@ -1431,6 +1737,8 @@ lflow_run(struct lflow_ctx_in *l_ctx_in, struct lflow_ctx_out *l_ctx_out)
- l_ctx_out->flow_table);
- add_lb_hairpin_flows(l_ctx_in->lb_table, l_ctx_in->local_datapaths,
- l_ctx_out->flow_table);
-+ add_fdb_flows(l_ctx_in->fdb_table, l_ctx_in->local_datapaths,
-+ l_ctx_out->flow_table);
- }
-
- void
-@@ -1582,3 +1890,37 @@ lflow_handle_changed_lbs(struct lflow_ctx_in *l_ctx_in,
-
- return true;
- }
-+
-+bool
-+lflow_handle_changed_fdbs(struct lflow_ctx_in *l_ctx_in,
-+ struct lflow_ctx_out *l_ctx_out)
-+{
-+ const struct sbrec_fdb *fdb;
-+
-+ SBREC_FDB_TABLE_FOR_EACH_TRACKED (fdb, l_ctx_in->fdb_table) {
-+ if (sbrec_fdb_is_deleted(fdb)) {
-+ VLOG_DBG("Remove fdb flows for deleted fdb "UUID_FMT,
-+ UUID_ARGS(&fdb->header_.uuid));
-+ ofctrl_remove_flows(l_ctx_out->flow_table, &fdb->header_.uuid);
-+ }
-+ }
-+
-+ SBREC_FDB_TABLE_FOR_EACH_TRACKED (fdb, l_ctx_in->fdb_table) {
-+ if (sbrec_fdb_is_deleted(fdb)) {
-+ continue;
-+ }
-+
-+ if (!sbrec_fdb_is_new(fdb)) {
-+ VLOG_DBG("Remove fdb flows for updated fdb "UUID_FMT,
-+ UUID_ARGS(&fdb->header_.uuid));
-+ ofctrl_remove_flows(l_ctx_out->flow_table, &fdb->header_.uuid);
-+ }
-+
-+ VLOG_DBG("Add fdb flows for fdb "UUID_FMT,
-+ UUID_ARGS(&fdb->header_.uuid));
-+ consider_fdb_flows(fdb, l_ctx_in->local_datapaths,
-+ l_ctx_out->flow_table);
-+ }
-+
-+ return true;
-+}
-diff --git a/controller/lflow.h b/controller/lflow.h
-index ba79cc374..2eb2cb112 100644
---- a/controller/lflow.h
-+++ b/controller/lflow.h
-@@ -60,9 +60,9 @@ struct uuid;
- * you make any changes. */
- #define OFTABLE_PHY_TO_LOG 0
- #define OFTABLE_LOG_INGRESS_PIPELINE 8 /* First of LOG_PIPELINE_LEN tables. */
--#define OFTABLE_REMOTE_OUTPUT 32
--#define OFTABLE_LOCAL_OUTPUT 33
--#define OFTABLE_CHECK_LOOPBACK 34
-+#define OFTABLE_REMOTE_OUTPUT 37
-+#define OFTABLE_LOCAL_OUTPUT 38
-+#define OFTABLE_CHECK_LOOPBACK 39
- #define OFTABLE_LOG_EGRESS_PIPELINE 40 /* First of LOG_PIPELINE_LEN tables. */
- #define OFTABLE_SAVE_INPORT 64
- #define OFTABLE_LOG_TO_PHY 65
-@@ -71,9 +71,8 @@ struct uuid;
- #define OFTABLE_CHK_LB_HAIRPIN 68
- #define OFTABLE_CHK_LB_HAIRPIN_REPLY 69
- #define OFTABLE_CT_SNAT_FOR_VIP 70
--
--/* The number of tables for the ingress and egress pipelines. */
--#define LOG_PIPELINE_LEN 24
-+#define OFTABLE_GET_FDB 71
-+#define OFTABLE_LOOKUP_FDB 72
-
- enum ref_type {
- REF_TYPE_ADDRSET,
-@@ -136,6 +135,7 @@ struct lflow_ctx_in {
- const struct sbrec_logical_flow_table *logical_flow_table;
- const struct sbrec_logical_dp_group_table *logical_dp_group_table;
- const struct sbrec_multicast_group_table *mc_group_table;
-+ const struct sbrec_fdb_table *fdb_table;
- const struct sbrec_chassis *chassis;
- const struct sbrec_load_balancer_table *lb_table;
- const struct hmap *local_datapaths;
-@@ -167,6 +167,7 @@ void lflow_handle_changed_neighbors(
- const struct hmap *local_datapaths,
- struct ovn_desired_flow_table *);
- bool lflow_handle_changed_lbs(struct lflow_ctx_in *, struct lflow_ctx_out *);
-+bool lflow_handle_changed_fdbs(struct lflow_ctx_in *, struct lflow_ctx_out *);
- void lflow_destroy(void);
-
- void lflow_cache_init(struct hmap *);
-diff --git a/controller/mac-learn.c b/controller/mac-learn.c
-new file mode 100644
-index 000000000..27634dca8
---- /dev/null
-+++ b/controller/mac-learn.c
-@@ -0,0 +1,180 @@
-+/* Copyright (c) 2020, Red Hat, Inc.
-+ *
-+ * Licensed under the Apache License, Version 2.0 (the "License");
-+ * you may not use this file except in compliance with the License.
-+ * You may obtain a copy of the License at:
-+ *
-+ * http://www.apache.org/licenses/LICENSE-2.0
-+ *
-+ * Unless required by applicable law or agreed to in writing, software
-+ * distributed under the License is distributed on an "AS IS" BASIS,
-+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-+ * See the License for the specific language governing permissions and
-+ * limitations under the License.
-+ */
-+
-+#include
-+
-+#include "mac-learn.h"
-+
-+/* OpenvSwitch lib includes. */
-+#include "openvswitch/vlog.h"
-+#include "lib/packets.h"
-+#include "lib/smap.h"
-+
-+VLOG_DEFINE_THIS_MODULE(mac_learn);
-+
-+#define MAX_MAC_BINDINGS 1000
-+#define MAX_FDB_ENTRIES 1000
-+
-+static size_t mac_binding_hash(uint32_t dp_key, uint32_t port_key,
-+ struct in6_addr *);
-+static struct mac_binding *mac_binding_find(struct hmap *mac_bindings,
-+ uint32_t dp_key,
-+ uint32_t port_key,
-+ struct in6_addr *ip, size_t hash);
-+static size_t fdb_entry_hash(uint32_t dp_key, struct eth_addr *);
-+
-+static struct fdb_entry *fdb_entry_find(struct hmap *fdbs, uint32_t dp_key,
-+ struct eth_addr *mac, size_t hash);
-+
-+/* mac_binding functions. */
-+void
-+ovn_mac_bindings_init(struct hmap *mac_bindings)
-+{
-+ hmap_init(mac_bindings);
-+}
-+
-+void
-+ovn_mac_bindings_flush(struct hmap *mac_bindings)
-+{
-+ struct mac_binding *mb;
-+ HMAP_FOR_EACH_POP (mb, hmap_node, mac_bindings) {
-+ free(mb);
-+ }
-+}
-+
-+void
-+ovn_mac_bindings_destroy(struct hmap *mac_bindings)
-+{
-+ ovn_mac_bindings_flush(mac_bindings);
-+ hmap_destroy(mac_bindings);
-+}
-+
-+struct mac_binding *
-+ovn_mac_binding_add(struct hmap *mac_bindings, uint32_t dp_key,
-+ uint32_t port_key, struct in6_addr *ip,
-+ struct eth_addr mac)
-+{
-+ uint32_t hash = mac_binding_hash(dp_key, port_key, ip);
-+
-+ struct mac_binding *mb =
-+ mac_binding_find(mac_bindings, dp_key, port_key, ip, hash);
-+ if (!mb) {
-+ if (hmap_count(mac_bindings) >= MAX_MAC_BINDINGS) {
-+ return NULL;
-+ }
-+
-+ mb = xmalloc(sizeof *mb);
-+ mb->dp_key = dp_key;
-+ mb->port_key = port_key;
-+ mb->ip = *ip;
-+ hmap_insert(mac_bindings, &mb->hmap_node, hash);
-+ }
-+ mb->mac = mac;
-+
-+ return mb;
-+}
-+
-+/* fdb functions. */
-+void
-+ovn_fdb_init(struct hmap *fdbs)
-+{
-+ hmap_init(fdbs);
-+}
-+
-+void
-+ovn_fdbs_flush(struct hmap *fdbs)
-+{
-+ struct fdb_entry *fdb_e;
-+ HMAP_FOR_EACH_POP (fdb_e, hmap_node, fdbs) {
-+ free(fdb_e);
-+ }
-+}
-+
-+void
-+ovn_fdbs_destroy(struct hmap *fdbs)
-+{
-+ ovn_fdbs_flush(fdbs);
-+ hmap_destroy(fdbs);
-+}
-+
-+struct fdb_entry *
-+ovn_fdb_add(struct hmap *fdbs, uint32_t dp_key, struct eth_addr mac,
-+ uint32_t port_key)
-+{
-+ uint32_t hash = fdb_entry_hash(dp_key, &mac);
-+
-+ struct fdb_entry *fdb_e =
-+ fdb_entry_find(fdbs, dp_key, &mac, hash);
-+ if (!fdb_e) {
-+ if (hmap_count(fdbs) >= MAX_FDB_ENTRIES) {
-+ return NULL;
-+ }
-+
-+ fdb_e = xzalloc(sizeof *fdb_e);
-+ fdb_e->dp_key = dp_key;
-+ fdb_e->mac = mac;
-+ hmap_insert(fdbs, &fdb_e->hmap_node, hash);
-+ }
-+ fdb_e->port_key = port_key;
-+
-+ return fdb_e;
-+
-+}
-+
-+/* mac_binding related static functions. */
-+
-+static size_t
-+mac_binding_hash(uint32_t dp_key, uint32_t port_key, struct in6_addr *ip)
-+{
-+ return hash_bytes(ip, sizeof *ip, hash_2words(dp_key, port_key));
-+}
-+
-+static struct mac_binding *
-+mac_binding_find(struct hmap *mac_bindings, uint32_t dp_key,
-+ uint32_t port_key, struct in6_addr *ip, size_t hash)
-+{
-+ struct mac_binding *mb;
-+ HMAP_FOR_EACH_WITH_HASH (mb, hmap_node, hash, mac_bindings) {
-+ if (mb->dp_key == dp_key && mb->port_key == port_key &&
-+ IN6_ARE_ADDR_EQUAL(&mb->ip, ip)) {
-+ return mb;
-+ }
-+ }
-+
-+ return NULL;
-+}
-+
-+/* fdb related static functions. */
-+
-+static size_t
-+fdb_entry_hash(uint32_t dp_key, struct eth_addr *mac)
-+{
-+ uint64_t mac64 = eth_addr_to_uint64(*mac);
-+ return hash_2words(dp_key, hash_uint64(mac64));
-+}
-+
-+static struct fdb_entry *
-+fdb_entry_find(struct hmap *fdbs, uint32_t dp_key,
-+ struct eth_addr *mac, size_t hash)
-+{
-+ struct fdb_entry *fdb_e;
-+ HMAP_FOR_EACH_WITH_HASH (fdb_e, hmap_node, hash, fdbs) {
-+ if (fdb_e->dp_key == dp_key && eth_addr_equals(fdb_e->mac, *mac)) {
-+ return fdb_e;
-+ }
-+ }
-+
-+ return NULL;
-+}
-diff --git a/controller/mac-learn.h b/controller/mac-learn.h
-new file mode 100644
-index 000000000..e7e8ba2d3
---- /dev/null
-+++ b/controller/mac-learn.h
-@@ -0,0 +1,66 @@
-+/*
-+ * Copyright (c) 2020 Red Hat, Inc.
-+ *
-+ * Licensed under the Apache License, Version 2.0 (the "License");
-+ * you may not use this file except in compliance with the License.
-+ * You may obtain a copy of the License at:
-+ *
-+ * http://www.apache.org/licenses/LICENSE-2.0
-+ *
-+ * Unless required by applicable law or agreed to in writing, software
-+ * distributed under the License is distributed on an "AS IS" BASIS,
-+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-+ * See the License for the specific language governing permissions and
-+ * limitations under the License.
-+ */
-+
-+#ifndef OVN_MAC_LEARN_H
-+#define OVN_MAC_LEARN_H 1
-+
-+#include
-+#include
-+#include "openvswitch/hmap.h"
-+
-+struct mac_binding {
-+ struct hmap_node hmap_node; /* In a hmap. */
-+
-+ /* Key. */
-+ uint32_t dp_key;
-+ uint32_t port_key; /* Port from where this mac_binding is learnt. */
-+ struct in6_addr ip;
-+
-+ /* Value. */
-+ struct eth_addr mac;
-+};
-+
-+void ovn_mac_bindings_init(struct hmap *mac_bindings);
-+void ovn_mac_bindings_flush(struct hmap *mac_bindings);
-+void ovn_mac_bindings_destroy(struct hmap *mac_bindings);
-+
-+struct mac_binding *ovn_mac_binding_add(struct hmap *mac_bindings,
-+ uint32_t dp_key, uint32_t port_key,
-+ struct in6_addr *ip,
-+ struct eth_addr mac);
-+
-+
-+
-+struct fdb_entry {
-+ struct hmap_node hmap_node; /* In a hmap. */
-+
-+ /* Key. */
-+ uint32_t dp_key;
-+ struct eth_addr mac;
-+
-+ /* value. */
-+ uint32_t port_key;
-+};
-+
-+void ovn_fdb_init(struct hmap *fdbs);
-+void ovn_fdbs_flush(struct hmap *fdbs);
-+void ovn_fdbs_destroy(struct hmap *fdbs);
-+
-+struct fdb_entry *ovn_fdb_add(struct hmap *fdbs,
-+ uint32_t dp_key, struct eth_addr mac,
-+ uint32_t port_key);
-+
-+#endif /* OVN_MAC_LEARN_H */
-diff --git a/controller/ofctrl-seqno.c b/controller/ofctrl-seqno.c
-new file mode 100644
-index 000000000..c9334b078
---- /dev/null
-+++ b/controller/ofctrl-seqno.c
-@@ -0,0 +1,254 @@
-+/* Copyright (c) 2021, Red Hat, Inc.
-+ *
-+ * Licensed under the Apache License, Version 2.0 (the "License");
-+ * you may not use this file except in compliance with the License.
-+ * You may obtain a copy of the License at:
-+ *
-+ * http://www.apache.org/licenses/LICENSE-2.0
-+ *
-+ * Unless required by applicable law or agreed to in writing, software
-+ * distributed under the License is distributed on an "AS IS" BASIS,
-+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-+ * See the License for the specific language governing permissions and
-+ * limitations under the License.
-+ */
-+
-+#include
-+
-+#include "hash.h"
-+#include "ofctrl-seqno.h"
-+#include "openvswitch/list.h"
-+#include "util.h"
-+
-+/* A sequence number update request, i.e., when the barrier corresponding to
-+ * the 'flow_cfg' sequence number is replied to by OVS then it is safe
-+ * to inform the application that the 'req_cfg' seqno has been processed.
-+ */
-+struct ofctrl_seqno_update {
-+ struct ovs_list list_node; /* In 'ofctrl_seqno_updates'. */
-+ size_t seqno_type; /* Application specific seqno type.
-+ * Relevant only for 'req_cfg'.
-+ */
-+ uint64_t flow_cfg; /* The seqno that needs to be acked by OVS
-+ * before 'req_cfg' can be acked for the
-+ * application.
-+ */
-+ uint64_t req_cfg; /* Application specific seqno. */
-+};
-+
-+/* List of in flight sequence number updates. */
-+static struct ovs_list ofctrl_seqno_updates;
-+
-+/* Last sequence number request sent to OVS. */
-+static uint64_t ofctrl_req_seqno;
-+
-+/* State of seqno requests for a given application seqno type. */
-+struct ofctrl_seqno_state {
-+ struct ovs_list acked_cfgs; /* Acked requests since the last time the
-+ * application consumed acked requests.
-+ */
-+ uint64_t cur_cfg; /* Last acked application seqno. */
-+ uint64_t req_cfg; /* Last requested application seqno. */
-+};
-+
-+/* Per application seqno type states. */
-+static size_t n_ofctrl_seqno_states;
-+static struct ofctrl_seqno_state *ofctrl_seqno_states;
-+
-+/* ofctrl_acked_seqnos related static function prototypes. */
-+static void ofctrl_acked_seqnos_init(struct ofctrl_acked_seqnos *seqnos,
-+ uint64_t last_acked);
-+static void ofctrl_acked_seqnos_add(struct ofctrl_acked_seqnos *seqnos,
-+ uint32_t val);
-+
-+/* ofctrl_seqno_update related static function prototypes. */
-+static void ofctrl_seqno_update_create__(size_t seqno_type, uint64_t req_cfg);
-+static void ofctrl_seqno_update_list_destroy(struct ovs_list *seqno_list);
-+static void ofctrl_seqno_cfg_run(size_t seqno_type,
-+ struct ofctrl_seqno_update *update);
-+
-+/* Returns the collection of acked ofctrl_seqno_update requests of type
-+ * 'seqno_type'. It's the responsibility of the caller to free memory by
-+ * calling ofctrl_acked_seqnos_destroy().
-+ */
-+struct ofctrl_acked_seqnos *
-+ofctrl_acked_seqnos_get(size_t seqno_type)
-+{
-+ struct ofctrl_acked_seqnos *acked_seqnos = xmalloc(sizeof *acked_seqnos);
-+ struct ofctrl_seqno_state *state = &ofctrl_seqno_states[seqno_type];
-+ struct ofctrl_seqno_update *update;
-+
-+ ofctrl_acked_seqnos_init(acked_seqnos, state->cur_cfg);
-+
-+ ovs_assert(seqno_type < n_ofctrl_seqno_states);
-+ LIST_FOR_EACH_POP (update, list_node, &state->acked_cfgs) {
-+ ofctrl_acked_seqnos_add(acked_seqnos, update->req_cfg);
-+ free(update);
-+ }
-+ return acked_seqnos;
-+}
-+
-+void
-+ofctrl_acked_seqnos_destroy(struct ofctrl_acked_seqnos *seqnos)
-+{
-+ if (!seqnos) {
-+ return;
-+ }
-+
-+ struct ofctrl_ack_seqno *seqno_node;
-+ HMAP_FOR_EACH_POP (seqno_node, node, &seqnos->acked) {
-+ free(seqno_node);
-+ }
-+ hmap_destroy(&seqnos->acked);
-+ free(seqnos);
-+}
-+
-+/* Returns true if 'val' is one of the acked sequence numbers in 'seqnos'. */
-+bool
-+ofctrl_acked_seqnos_contains(const struct ofctrl_acked_seqnos *seqnos,
-+ uint32_t val)
-+{
-+ struct ofctrl_ack_seqno *sn;
-+
-+ HMAP_FOR_EACH_WITH_HASH (sn, node, hash_int(val, 0), &seqnos->acked) {
-+ if (sn->seqno == val) {
-+ return true;
-+ }
-+ }
-+ return false;
-+}
-+
-+void
-+ofctrl_seqno_init(void)
-+{
-+ ovs_list_init(&ofctrl_seqno_updates);
-+}
-+
-+/* Adds a new type of application specific seqno updates. */
-+size_t
-+ofctrl_seqno_add_type(void)
-+{
-+ size_t new_type = n_ofctrl_seqno_states;
-+ n_ofctrl_seqno_states++;
-+
-+ struct ofctrl_seqno_state *new_states =
-+ xzalloc(n_ofctrl_seqno_states * sizeof *new_states);
-+
-+ for (size_t i = 0; i < n_ofctrl_seqno_states - 1; i++) {
-+ ovs_list_move(&new_states[i].acked_cfgs,
-+ &ofctrl_seqno_states[i].acked_cfgs);
-+ }
-+ ovs_list_init(&new_states[new_type].acked_cfgs);
-+
-+ free(ofctrl_seqno_states);
-+ ofctrl_seqno_states = new_states;
-+ return new_type;
-+}
-+
-+/* Creates a new seqno update request for an application specific
-+ * 'seqno_type'.
-+ */
-+void
-+ofctrl_seqno_update_create(size_t seqno_type, uint64_t new_cfg)
-+{
-+ ovs_assert(seqno_type < n_ofctrl_seqno_states);
-+
-+ struct ofctrl_seqno_state *state = &ofctrl_seqno_states[seqno_type];
-+
-+ /* If new_cfg didn't change since the last request there should already
-+ * be an update pending.
-+ */
-+ if (new_cfg == state->req_cfg) {
-+ return;
-+ }
-+
-+ state->req_cfg = new_cfg;
-+ ofctrl_seqno_update_create__(seqno_type, new_cfg);
-+}
-+
-+/* Should be called when the application is certain that all OVS flow updates
-+ * corresponding to 'flow_cfg' were processed. Populates the application
-+ * specific lists of acked requests in 'ofctrl_seqno_states'.
-+ */
-+void
-+ofctrl_seqno_run(uint64_t flow_cfg)
-+{
-+ struct ofctrl_seqno_update *update, *prev;
-+ LIST_FOR_EACH_SAFE (update, prev, list_node, &ofctrl_seqno_updates) {
-+ if (flow_cfg < update->flow_cfg) {
-+ break;
-+ }
-+
-+ ovs_list_remove(&update->list_node);
-+ ofctrl_seqno_cfg_run(update->seqno_type, update);
-+ }
-+}
-+
-+/* Returns the seqno to be used when sending a barrier request to OVS. */
-+uint64_t
-+ofctrl_seqno_get_req_cfg(void)
-+{
-+ return ofctrl_req_seqno;
-+}
-+
-+/* Should be called whenever the openflow connection to OVS is lost. Flushes
-+ * all pending 'ofctrl_seqno_updates'.
-+ */
-+void
-+ofctrl_seqno_flush(void)
-+{
-+ for (size_t i = 0; i < n_ofctrl_seqno_states; i++) {
-+ ofctrl_seqno_update_list_destroy(&ofctrl_seqno_states[i].acked_cfgs);
-+ }
-+ ofctrl_seqno_update_list_destroy(&ofctrl_seqno_updates);
-+ ofctrl_req_seqno = 0;
-+}
-+
-+static void
-+ofctrl_acked_seqnos_init(struct ofctrl_acked_seqnos *seqnos,
-+ uint64_t last_acked)
-+{
-+ hmap_init(&seqnos->acked);
-+ seqnos->last_acked = last_acked;
-+}
-+
-+static void
-+ofctrl_acked_seqnos_add(struct ofctrl_acked_seqnos *seqnos, uint32_t val)
-+{
-+ seqnos->last_acked = val;
-+
-+ struct ofctrl_ack_seqno *sn = xmalloc(sizeof *sn);
-+ hmap_insert(&seqnos->acked, &sn->node, hash_int(val, 0));
-+ sn->seqno = val;
-+}
-+
-+static void
-+ofctrl_seqno_update_create__(size_t seqno_type, uint64_t req_cfg)
-+{
-+ struct ofctrl_seqno_update *update = xmalloc(sizeof *update);
-+
-+ ofctrl_req_seqno++;
-+ ovs_list_push_back(&ofctrl_seqno_updates, &update->list_node);
-+ update->seqno_type = seqno_type;
-+ update->flow_cfg = ofctrl_req_seqno;
-+ update->req_cfg = req_cfg;
-+}
-+
-+static void
-+ofctrl_seqno_update_list_destroy(struct ovs_list *seqno_list)
-+{
-+ struct ofctrl_seqno_update *update;
-+
-+ LIST_FOR_EACH_POP (update, list_node, seqno_list) {
-+ free(update);
-+ }
-+}
-+
-+static void
-+ofctrl_seqno_cfg_run(size_t seqno_type, struct ofctrl_seqno_update *update)
-+{
-+ ovs_assert(seqno_type < n_ofctrl_seqno_states);
-+ ovs_list_push_back(&ofctrl_seqno_states[seqno_type].acked_cfgs,
-+ &update->list_node);
-+ ofctrl_seqno_states[seqno_type].cur_cfg = update->req_cfg;
-+}
-diff --git a/controller/ofctrl-seqno.h b/controller/ofctrl-seqno.h
-new file mode 100644
-index 000000000..876947c26
---- /dev/null
-+++ b/controller/ofctrl-seqno.h
-@@ -0,0 +1,49 @@
-+/* Copyright (c) 2021, Red Hat, Inc.
-+ *
-+ * Licensed under the Apache License, Version 2.0 (the "License");
-+ * you may not use this file except in compliance with the License.
-+ * You may obtain a copy of the License at:
-+ *
-+ * http://www.apache.org/licenses/LICENSE-2.0
-+ *
-+ * Unless required by applicable law or agreed to in writing, software
-+ * distributed under the License is distributed on an "AS IS" BASIS,
-+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-+ * See the License for the specific language governing permissions and
-+ * limitations under the License.
-+ */
-+
-+#ifndef OFCTRL_SEQNO_H
-+#define OFCTRL_SEQNO_H 1
-+
-+#include
-+
-+#include
-+
-+/* Collection of acked ofctrl_seqno_update requests and the most recent
-+ * 'last_acked' value.
-+ */
-+struct ofctrl_acked_seqnos {
-+ struct hmap acked;
-+ uint64_t last_acked;
-+};
-+
-+/* Acked application specific seqno. Stored in ofctrl_acked_seqnos.acked. */
-+struct ofctrl_ack_seqno {
-+ struct hmap_node node;
-+ uint64_t seqno;
-+};
-+
-+struct ofctrl_acked_seqnos *ofctrl_acked_seqnos_get(size_t seqno_type);
-+void ofctrl_acked_seqnos_destroy(struct ofctrl_acked_seqnos *seqnos);
-+bool ofctrl_acked_seqnos_contains(const struct ofctrl_acked_seqnos *seqnos,
-+ uint32_t val);
-+
-+void ofctrl_seqno_init(void);
-+size_t ofctrl_seqno_add_type(void);
-+void ofctrl_seqno_update_create(size_t seqno_type, uint64_t new_cfg);
-+void ofctrl_seqno_run(uint64_t flow_cfg);
-+uint64_t ofctrl_seqno_get_req_cfg(void);
-+void ofctrl_seqno_flush(void);
-+
-+#endif /* controller/ofctrl-seqno.h */
-diff --git a/controller/ofctrl.c b/controller/ofctrl.c
-index a1ac69531..415d9b7e1 100644
---- a/controller/ofctrl.c
-+++ b/controller/ofctrl.c
-@@ -268,13 +268,14 @@ enum ofctrl_state {
- /* An in-flight update to the switch's flow table.
- *
- * When we receive a barrier reply from the switch with the given 'xid', we
-- * know that the switch is caught up to northbound database sequence number
-- * 'nb_cfg' (and make that available to the client via ofctrl_get_cur_cfg(), so
-- * that it can store it into our Chassis record's nb_cfg column). */
-+ * know that the switch is caught up to the requested sequence number
-+ * 'req_cfg' (and make that available to the client via ofctrl_get_cur_cfg(),
-+ * so that it can store it into external state, e.g., our Chassis record's
-+ * nb_cfg column). */
- struct ofctrl_flow_update {
- struct ovs_list list_node; /* In 'flow_updates'. */
- ovs_be32 xid; /* OpenFlow transaction ID for barrier. */
-- int64_t nb_cfg; /* Northbound database sequence number. */
-+ uint64_t req_cfg; /* Requested sequence number. */
- };
-
- static struct ofctrl_flow_update *
-@@ -286,8 +287,8 @@ ofctrl_flow_update_from_list_node(const struct ovs_list *list_node)
- /* Currently in-flight updates. */
- static struct ovs_list flow_updates;
-
--/* nb_cfg of latest committed flow update. */
--static int64_t cur_cfg;
-+/* req_cfg of latest committed flow update. */
-+static uint64_t cur_cfg;
-
- /* Current state. */
- static enum ofctrl_state state;
-@@ -632,8 +633,8 @@ recv_S_UPDATE_FLOWS(const struct ofp_header *oh, enum ofptype type,
- struct ofctrl_flow_update *fup = ofctrl_flow_update_from_list_node(
- ovs_list_front(&flow_updates));
- if (fup->xid == oh->xid) {
-- if (fup->nb_cfg >= cur_cfg) {
-- cur_cfg = fup->nb_cfg;
-+ if (fup->req_cfg >= cur_cfg) {
-+ cur_cfg = fup->req_cfg;
- }
- ovs_list_remove(&fup->list_node);
- free(fup);
-@@ -763,7 +764,7 @@ ofctrl_destroy(void)
- shash_destroy(&symtab);
- }
-
--int64_t
-+uint64_t
- ofctrl_get_cur_cfg(void)
- {
- return cur_cfg;
-@@ -1246,10 +1247,23 @@ ofctrl_flood_remove_flows(struct ovn_desired_flow_table *flow_table,
- struct hmap *flood_remove_nodes)
- {
- struct ofctrl_flood_remove_node *ofrn;
-+ int i, n = 0;
-+
-+ /* flood_remove_flows_for_sb_uuid() will modify the 'flood_remove_nodes'
-+ * hash map by inserting new items, so we can't use it for iteration.
-+ * Copying the sb_uuids into an array. */
-+ struct uuid *sb_uuids;
-+ sb_uuids = xmalloc(hmap_count(flood_remove_nodes) * sizeof *sb_uuids);
-+ struct hmap flood_remove_uuids = HMAP_INITIALIZER(&flood_remove_uuids);
- HMAP_FOR_EACH (ofrn, hmap_node, flood_remove_nodes) {
-- flood_remove_flows_for_sb_uuid(flow_table, &ofrn->sb_uuid,
-+ sb_uuids[n++] = ofrn->sb_uuid;
-+ }
-+
-+ for (i = 0; i < n; i++) {
-+ flood_remove_flows_for_sb_uuid(flow_table, &sb_uuids[i],
- flood_remove_nodes);
- }
-+ free(sb_uuids);
-
- /* remove any related group and meter info */
- HMAP_FOR_EACH (ofrn, hmap_node, flood_remove_nodes) {
-@@ -1975,7 +1989,7 @@ update_installed_flows_by_track(struct ovn_desired_flow_table *flow_table,
- * tracked, so it must have been modified. */
- installed_flow_mod(&i->flow, &f->flow, msgs);
- ovn_flow_log(&i->flow, "updating installed (tracked)");
-- } else {
-+ } else if (!f->installed_flow) {
- /* Adding a new flow that conflicts with an existing installed
- * flow, so add it to the link. If this flow becomes active,
- * e.g., it is less restrictive than the previous active flow
-@@ -2024,28 +2038,28 @@ void
- ofctrl_put(struct ovn_desired_flow_table *flow_table,
- struct shash *pending_ct_zones,
- const struct sbrec_meter_table *meter_table,
-- int64_t nb_cfg,
-+ uint64_t req_cfg,
- bool flow_changed)
- {
- static bool skipped_last_time = false;
-- static int64_t old_nb_cfg = 0;
-+ static uint64_t old_req_cfg = 0;
- bool need_put = false;
- if (flow_changed || skipped_last_time || need_reinstall_flows) {
- need_put = true;
-- old_nb_cfg = nb_cfg;
-- } else if (nb_cfg != old_nb_cfg) {
-- /* nb_cfg changed since last ofctrl_put() call */
-- if (cur_cfg == old_nb_cfg) {
-+ old_req_cfg = req_cfg;
-+ } else if (req_cfg != old_req_cfg) {
-+ /* req_cfg changed since last ofctrl_put() call */
-+ if (cur_cfg == old_req_cfg) {
- /* If there are no updates pending, we were up-to-date already,
-- * update with the new nb_cfg.
-+ * update with the new req_cfg.
- */
- if (ovs_list_is_empty(&flow_updates)) {
-- cur_cfg = nb_cfg;
-- old_nb_cfg = nb_cfg;
-+ cur_cfg = req_cfg;
-+ old_req_cfg = req_cfg;
- }
- } else {
- need_put = true;
-- old_nb_cfg = nb_cfg;
-+ old_req_cfg = req_cfg;
- }
- }
-
-@@ -2187,24 +2201,23 @@ ofctrl_put(struct ovn_desired_flow_table *flow_table,
- /* Track the flow update. */
- struct ofctrl_flow_update *fup, *prev;
- LIST_FOR_EACH_REVERSE_SAFE (fup, prev, list_node, &flow_updates) {
-- if (nb_cfg < fup->nb_cfg) {
-+ if (req_cfg < fup->req_cfg) {
- /* This ofctrl_flow_update is for a configuration later than
-- * 'nb_cfg'. This should not normally happen, because it means
-- * that 'nb_cfg' in the SB_Global table of the southbound
-- * database decreased, and it should normally be monotonically
-- * increasing. */
-- VLOG_WARN("nb_cfg regressed from %"PRId64" to %"PRId64,
-- fup->nb_cfg, nb_cfg);
-+ * 'req_cfg'. This should not normally happen, because it
-+ * means that the local seqno decreased and it should normally
-+ * be monotonically increasing. */
-+ VLOG_WARN("req_cfg regressed from %"PRId64" to %"PRId64,
-+ fup->req_cfg, req_cfg);
- ovs_list_remove(&fup->list_node);
- free(fup);
-- } else if (nb_cfg == fup->nb_cfg) {
-+ } else if (req_cfg == fup->req_cfg) {
- /* This ofctrl_flow_update is for the same configuration as
-- * 'nb_cfg'. Probably, some change to the physical topology
-+ * 'req_cfg'. Probably, some change to the physical topology
- * means that we had to revise the OpenFlow flow table even
- * though the logical topology did not change. Update fp->xid,
- * so that we don't send a notification that we're up-to-date
- * until we're really caught up. */
-- VLOG_DBG("advanced xid target for nb_cfg=%"PRId64, nb_cfg);
-+ VLOG_DBG("advanced xid target for req_cfg=%"PRId64, req_cfg);
- fup->xid = xid_;
- goto done;
- } else {
-@@ -2216,18 +2229,18 @@ ofctrl_put(struct ovn_desired_flow_table *flow_table,
- fup = xmalloc(sizeof *fup);
- ovs_list_push_back(&flow_updates, &fup->list_node);
- fup->xid = xid_;
-- fup->nb_cfg = nb_cfg;
-+ fup->req_cfg = req_cfg;
- done:;
- } else if (!ovs_list_is_empty(&flow_updates)) {
-- /* Getting up-to-date with 'nb_cfg' didn't require any extra flow table
-- * changes, so whenever we get up-to-date with the most recent flow
-- * table update, we're also up-to-date with 'nb_cfg'. */
-+ /* Getting up-to-date with 'req_cfg' didn't require any extra flow
-+ * table changes, so whenever we get up-to-date with the most recent
-+ * flow table update, we're also up-to-date with 'req_cfg'. */
- struct ofctrl_flow_update *fup = ofctrl_flow_update_from_list_node(
- ovs_list_back(&flow_updates));
-- fup->nb_cfg = nb_cfg;
-+ fup->req_cfg = req_cfg;
- } else {
- /* We were completely up-to-date before and still are. */
-- cur_cfg = nb_cfg;
-+ cur_cfg = req_cfg;
- }
-
- flow_table->change_tracked = true;
-diff --git a/controller/ofctrl.h b/controller/ofctrl.h
-index 64b0ea5dd..88769566a 100644
---- a/controller/ofctrl.h
-+++ b/controller/ofctrl.h
-@@ -55,12 +55,12 @@ enum mf_field_id ofctrl_get_mf_field_id(void);
- void ofctrl_put(struct ovn_desired_flow_table *,
- struct shash *pending_ct_zones,
- const struct sbrec_meter_table *,
-- int64_t nb_cfg,
-+ uint64_t nb_cfg,
- bool flow_changed);
- bool ofctrl_can_put(void);
- void ofctrl_wait(void);
- void ofctrl_destroy(void);
--int64_t ofctrl_get_cur_cfg(void);
-+uint64_t ofctrl_get_cur_cfg(void);
-
- void ofctrl_ct_flush_zone(uint16_t zone_id);
-
-diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
-index 366fc9c06..288e2e12d 100644
---- a/controller/ovn-controller.c
-+++ b/controller/ovn-controller.c
-@@ -39,6 +39,7 @@
- #include "lib/vswitch-idl.h"
- #include "lport.h"
- #include "ofctrl.h"
-+#include "ofctrl-seqno.h"
- #include "openvswitch/vconn.h"
- #include "openvswitch/vlog.h"
- #include "ovn/actions.h"
-@@ -98,6 +99,9 @@ struct pending_pkt {
- char *flow_s;
- };
-
-+/* Registered ofctrl seqno type for nb_cfg propagation. */
-+static size_t ofctrl_seq_type_nb_cfg;
-+
- struct local_datapath *
- get_local_datapath(const struct hmap *local_datapaths, uint32_t tunnel_key)
- {
-@@ -583,7 +587,18 @@ add_pending_ct_zone_entry(struct shash *pending_ct_zones,
- pending->state = state; /* Skip flushing zone. */
- pending->zone = zone;
- pending->add = add;
-- shash_add(pending_ct_zones, name, pending);
-+
-+ /* Its important that we add only one entry for the key 'name'.
-+ * Replace 'pending' with 'existing' and free up 'existing'.
-+ * Otherwise, we may end up in a continuous loop of adding
-+ * and deleting the zone entry in the 'external_ids' of
-+ * integration bridge.
-+ */
-+ struct ct_zone_pending_entry *existing =
-+ shash_replace(pending_ct_zones, name, pending);
-+ if (existing) {
-+ free(existing);
-+ }
- }
-
- static void
-@@ -798,11 +813,11 @@ restore_ct_zones(const struct ovsrec_bridge_table *bridge_table,
- }
- }
-
--static int64_t
-+static uint64_t
- get_nb_cfg(const struct sbrec_sb_global_table *sb_global_table,
- unsigned int cond_seqno, unsigned int expected_cond_seqno)
- {
-- static int64_t nb_cfg = 0;
-+ static uint64_t nb_cfg = 0;
-
- /* Delay getting nb_cfg if there are monitor condition changes
- * in flight. It might be that those changes would instruct the
-@@ -825,11 +840,14 @@ static void
- store_nb_cfg(struct ovsdb_idl_txn *sb_txn, struct ovsdb_idl_txn *ovs_txn,
- const struct sbrec_chassis_private *chassis,
- const struct ovsrec_bridge *br_int,
-- unsigned int delay_nb_cfg_report,
-- int64_t cur_cfg)
-+ unsigned int delay_nb_cfg_report)
- {
-+ struct ofctrl_acked_seqnos *acked_nb_cfg_seqnos =
-+ ofctrl_acked_seqnos_get(ofctrl_seq_type_nb_cfg);
-+ uint64_t cur_cfg = acked_nb_cfg_seqnos->last_acked;
-+
- if (!cur_cfg) {
-- return;
-+ goto done;
- }
-
- if (sb_txn && chassis && cur_cfg != chassis->nb_cfg) {
-@@ -850,6 +868,9 @@ store_nb_cfg(struct ovsdb_idl_txn *sb_txn, struct ovsdb_idl_txn *ovs_txn,
- cur_cfg_str);
- free(cur_cfg_str);
- }
-+
-+done:
-+ ofctrl_acked_seqnos_destroy(acked_nb_cfg_seqnos);
- }
-
- static const char *
-@@ -911,7 +932,8 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
- SB_NODE(dhcp_options, "dhcp_options") \
- SB_NODE(dhcpv6_options, "dhcpv6_options") \
- SB_NODE(dns, "dns") \
-- SB_NODE(load_balancer, "load_balancer")
-+ SB_NODE(load_balancer, "load_balancer") \
-+ SB_NODE(fdb, "fdb")
-
- enum sb_engine_node {
- #define SB_NODE(NAME, NAME_STR) SB_##NAME,
-@@ -967,6 +989,12 @@ en_ofctrl_is_connected_run(struct engine_node *node, void *data)
- struct ed_type_ofctrl_is_connected *of_data = data;
- if (of_data->connected != ofctrl_is_connected()) {
- of_data->connected = !of_data->connected;
-+
-+ /* Flush ofctrl seqno requests when the ofctrl connection goes down. */
-+ if (!of_data->connected) {
-+ ofctrl_seqno_flush();
-+ binding_seqno_flush();
-+ }
- engine_set_node_state(node, EN_UPDATED);
- return;
- }
-@@ -1836,6 +1864,10 @@ static void init_lflow_ctx(struct engine_node *node,
- (struct sbrec_load_balancer_table *)EN_OVSDB_GET(
- engine_get_input("SB_load_balancer", node));
-
-+ struct sbrec_fdb_table *fdb_table =
-+ (struct sbrec_fdb_table *)EN_OVSDB_GET(
-+ engine_get_input("SB_fdb", node));
-+
- struct ovsrec_open_vswitch_table *ovs_table =
- (struct ovsrec_open_vswitch_table *)EN_OVSDB_GET(
- engine_get_input("OVS_open_vswitch", node));
-@@ -1873,6 +1905,7 @@ static void init_lflow_ctx(struct engine_node *node,
- l_ctx_in->logical_flow_table = logical_flow_table;
- l_ctx_in->logical_dp_group_table = logical_dp_group_table;
- l_ctx_in->mc_group_table = multicast_group_table;
-+ l_ctx_in->fdb_table = fdb_table,
- l_ctx_in->chassis = chassis;
- l_ctx_in->lb_table = lb_table;
- l_ctx_in->local_datapaths = &rt_data->local_datapaths;
-@@ -2313,6 +2346,23 @@ flow_output_sb_load_balancer_handler(struct engine_node *node, void *data)
- return handled;
- }
-
-+static bool
-+flow_output_sb_fdb_handler(struct engine_node *node, void *data)
-+{
-+ struct ed_type_runtime_data *rt_data =
-+ engine_get_input_data("runtime_data", node);
-+
-+ struct ed_type_flow_output *fo = data;
-+ struct lflow_ctx_in l_ctx_in;
-+ struct lflow_ctx_out l_ctx_out;
-+ init_lflow_ctx(node, rt_data, fo, &l_ctx_in, &l_ctx_out);
-+
-+ bool handled = lflow_handle_changed_fdbs(&l_ctx_in, &l_ctx_out);
-+
-+ engine_set_node_state(node, EN_UPDATED);
-+ return handled;
-+}
-+
- struct ovn_controller_exit_args {
- bool *exiting;
- bool *restart;
-@@ -2389,6 +2439,10 @@ main(int argc, char *argv[])
-
- daemonize_complete();
-
-+ /* Register ofctrl seqno types. */
-+ ofctrl_seq_type_nb_cfg = ofctrl_seqno_add_type();
-+
-+ binding_init();
- patch_init();
- pinctrl_init();
- lflow_init();
-@@ -2440,6 +2494,10 @@ main(int argc, char *argv[])
- = ip_mcast_index_create(ovnsb_idl_loop.idl);
- struct ovsdb_idl_index *sbrec_igmp_group
- = igmp_group_index_create(ovnsb_idl_loop.idl);
-+ struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac
-+ = ovsdb_idl_index_create2(ovnsb_idl_loop.idl,
-+ &sbrec_fdb_col_mac,
-+ &sbrec_fdb_col_dp_key);
-
- ovsdb_idl_track_add_all(ovnsb_idl_loop.idl);
- ovsdb_idl_omit_alert(ovnsb_idl_loop.idl,
-@@ -2566,6 +2624,8 @@ main(int argc, char *argv[])
- engine_add_input(&en_flow_output, &en_sb_dns, NULL);
- engine_add_input(&en_flow_output, &en_sb_load_balancer,
- flow_output_sb_load_balancer_handler);
-+ engine_add_input(&en_flow_output, &en_sb_fdb,
-+ flow_output_sb_fdb_handler);
-
- engine_add_input(&en_ct_zones, &en_ovs_open_vswitch, NULL);
- engine_add_input(&en_ct_zones, &en_ovs_bridge, NULL);
-@@ -2624,6 +2684,7 @@ main(int argc, char *argv[])
- ofctrl_init(&flow_output_data->group_table,
- &flow_output_data->meter_table,
- get_ofctrl_probe_interval(ovs_idl_loop.idl));
-+ ofctrl_seqno_init();
-
- unixctl_command_register("group-table-list", "", 0, 0,
- extend_table_list,
-@@ -2832,11 +2893,13 @@ main(int argc, char *argv[])
- sbrec_mac_binding_by_lport_ip,
- sbrec_igmp_group,
- sbrec_ip_multicast,
-+ sbrec_fdb_by_dp_key_mac,
- sbrec_dns_table_get(ovnsb_idl_loop.idl),
- sbrec_controller_event_table_get(
- ovnsb_idl_loop.idl),
- sbrec_service_monitor_table_get(
- ovnsb_idl_loop.idl),
-+ sbrec_bfd_table_get(ovnsb_idl_loop.idl),
- br_int, chassis,
- &runtime_data->local_datapaths,
- &runtime_data->active_tunnels);
-@@ -2852,17 +2915,29 @@ main(int argc, char *argv[])
- sb_monitor_all);
- }
- }
-+
-+ ofctrl_seqno_update_create(
-+ ofctrl_seq_type_nb_cfg,
-+ get_nb_cfg(sbrec_sb_global_table_get(
-+ ovnsb_idl_loop.idl),
-+ ovnsb_cond_seqno,
-+ ovnsb_expected_cond_seqno));
-+ if (runtime_data && ovs_idl_txn && ovnsb_idl_txn) {
-+ binding_seqno_run(&runtime_data->local_bindings);
-+ }
-+
- flow_output_data = engine_get_data(&en_flow_output);
- if (flow_output_data && ct_zones_data) {
- ofctrl_put(&flow_output_data->flow_table,
- &ct_zones_data->pending,
- sbrec_meter_table_get(ovnsb_idl_loop.idl),
-- get_nb_cfg(sbrec_sb_global_table_get(
-- ovnsb_idl_loop.idl),
-- ovnsb_cond_seqno,
-- ovnsb_expected_cond_seqno),
-+ ofctrl_seqno_get_req_cfg(),
- engine_node_changed(&en_flow_output));
- }
-+ ofctrl_seqno_run(ofctrl_get_cur_cfg());
-+ if (runtime_data && ovs_idl_txn && ovnsb_idl_txn) {
-+ binding_seqno_install(&runtime_data->local_bindings);
-+ }
- }
-
- }
-@@ -2888,7 +2963,7 @@ main(int argc, char *argv[])
- }
-
- store_nb_cfg(ovnsb_idl_txn, ovs_idl_txn, chassis_private,
-- br_int, delay_nb_cfg_report, ofctrl_get_cur_cfg());
-+ br_int, delay_nb_cfg_report);
-
- if (pending_pkt.conn) {
- struct ed_type_addr_sets *as_data =
-diff --git a/controller/pinctrl.c b/controller/pinctrl.c
-index 7e3abf0a4..3dc10389d 100644
---- a/controller/pinctrl.c
-+++ b/controller/pinctrl.c
-@@ -26,6 +26,7 @@
- #include "flow.h"
- #include "ha-chassis.h"
- #include "lport.h"
-+#include "mac-learn.h"
- #include "nx-match.h"
- #include "latch.h"
- #include "lib/packets.h"
-@@ -38,6 +39,7 @@
- #include "openvswitch/ofp-util.h"
- #include "openvswitch/vlog.h"
- #include "lib/random.h"
-+#include "lib/crc32c.h"
-
- #include "lib/dhcp.h"
- #include "ovn-controller.h"
-@@ -192,7 +194,6 @@ static void run_put_mac_bindings(
- struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip)
- OVS_REQUIRES(pinctrl_mutex);
- static void wait_put_mac_bindings(struct ovsdb_idl_txn *ovnsb_idl_txn);
--static void flush_put_mac_bindings(void);
- static void send_mac_binding_buffered_pkts(struct rconn *swconn)
- OVS_REQUIRES(pinctrl_mutex);
-
-@@ -323,6 +324,39 @@ put_load(uint64_t value, enum mf_field_id dst, int ofs, int n_bits,
- static void notify_pinctrl_main(void);
- static void notify_pinctrl_handler(void);
-
-+static bool bfd_monitor_should_inject(void);
-+static void bfd_monitor_wait(long long int timeout);
-+static void bfd_monitor_init(void);
-+static void bfd_monitor_destroy(void);
-+static void bfd_monitor_send_msg(struct rconn *swconn, long long int *bfd_time)
-+ OVS_REQUIRES(pinctrl_mutex);
-+static void
-+pinctrl_handle_bfd_msg(struct rconn *swconn, const struct flow *ip_flow,
-+ struct dp_packet *pkt_in)
-+ OVS_REQUIRES(pinctrl_mutex);
-+static void bfd_monitor_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
-+ const struct sbrec_bfd_table *bfd_table,
-+ struct ovsdb_idl_index *sbrec_port_binding_by_name,
-+ const struct sbrec_chassis *chassis,
-+ const struct sset *active_tunnels)
-+ OVS_REQUIRES(pinctrl_mutex);
-+static void init_fdb_entries(void);
-+static void destroy_fdb_entries(void);
-+static const struct sbrec_fdb *fdb_lookup(
-+ struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac,
-+ uint32_t dp_key, const char *mac);
-+static void run_put_fdb(struct ovsdb_idl_txn *ovnsb_idl_txn,
-+ struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac,
-+ const struct fdb_entry *fdb_e)
-+ OVS_REQUIRES(pinctrl_mutex);
-+static void run_put_fdbs(struct ovsdb_idl_txn *ovnsb_idl_txn,
-+ struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac)
-+ OVS_REQUIRES(pinctrl_mutex);
-+static void wait_put_fdbs(struct ovsdb_idl_txn *ovnsb_idl_txn);
-+static void pinctrl_handle_put_fdb(const struct flow *md,
-+ const struct flow *headers)
-+ OVS_REQUIRES(pinctrl_mutex);
-+
- COVERAGE_DEFINE(pinctrl_drop_put_mac_binding);
- COVERAGE_DEFINE(pinctrl_drop_buffered_packets_map);
- COVERAGE_DEFINE(pinctrl_drop_controller_event);
-@@ -487,6 +521,8 @@ pinctrl_init(void)
- ip_mcast_snoop_init();
- init_put_vport_bindings();
- init_svc_monitors();
-+ bfd_monitor_init();
-+ init_fdb_entries();
- pinctrl.br_int_name = NULL;
- pinctrl_handler_seq = seq_create();
- pinctrl_main_seq = seq_create();
-@@ -1380,6 +1416,11 @@ buffered_push_packet(struct buffered_packets *bp,
- ofpbuf_init(&bi->ofpacts, 4096);
-
- reload_metadata(&bi->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(&bi->ofpacts, pkt_mark_field, &pkt_mark_value, NULL);
- bi->ofp_port = md->flow.in_port.ofp_port;
-
- struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&bi->ofpacts);
-@@ -1763,6 +1804,116 @@ pinctrl_handle_tcp_reset(struct rconn *swconn, const struct flow *ip_flow,
- dp_packet_uninit(&packet);
- }
-
-+static void dp_packet_put_sctp_abort(struct dp_packet *packet,
-+ bool reflect_tag)
-+{
-+ struct sctp_chunk_header abort = {
-+ .sctp_chunk_type = SCTP_CHUNK_TYPE_ABORT,
-+ .sctp_chunk_flags = reflect_tag ? SCTP_ABORT_CHUNK_FLAG_T : 0,
-+ .sctp_chunk_len = htons(SCTP_CHUNK_HEADER_LEN),
-+ };
-+
-+ dp_packet_put(packet, &abort, sizeof abort);
-+}
-+
-+static void
-+pinctrl_handle_sctp_abort(struct rconn *swconn, const struct flow *ip_flow,
-+ struct dp_packet *pkt_in,
-+ const struct match *md, struct ofpbuf *userdata,
-+ bool loopback)
-+{
-+ if (ip_flow->nw_proto != IPPROTO_SCTP) {
-+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-+ VLOG_WARN_RL(&rl, "SCTP_ABORT action on non-SCTP packet");
-+ return;
-+ }
-+
-+ struct sctp_header *sh_in = dp_packet_l4(pkt_in);
-+ if (!sh_in) {
-+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-+ VLOG_WARN_RL(&rl, "SCTP_ABORT action on malformed SCTP packet");
-+ return;
-+ }
-+
-+ const struct sctp_chunk_header *sh_in_chunk =
-+ dp_packet_get_sctp_payload(pkt_in);
-+ if (!sh_in_chunk) {
-+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-+ VLOG_WARN_RL(&rl, "SCTP_ABORT action on SCTP packet with no chunks");
-+ return;
-+ }
-+
-+ if (sh_in_chunk->sctp_chunk_type == SCTP_CHUNK_TYPE_ABORT) {
-+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-+ VLOG_WARN_RL(&rl, "sctp_abort action on incoming SCTP ABORT.");
-+ return;
-+ }
-+
-+ const struct sctp_init_chunk *sh_in_init = NULL;
-+ if (sh_in_chunk->sctp_chunk_type == SCTP_CHUNK_TYPE_INIT) {
-+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-+ sh_in_init = dp_packet_at(pkt_in, pkt_in->l4_ofs +
-+ SCTP_HEADER_LEN +
-+ SCTP_CHUNK_HEADER_LEN,
-+ SCTP_INIT_CHUNK_LEN);
-+ if (!sh_in_init) {
-+ VLOG_WARN_RL(&rl, "Incomplete SCTP INIT chunk. Ignoring packet.");
-+ return;
-+ }
-+ }
-+
-+ uint64_t packet_stub[128 / 8];
-+ struct dp_packet packet;
-+
-+ dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
-+
-+ struct eth_addr eth_src = loopback ? ip_flow->dl_dst : ip_flow->dl_src;
-+ struct eth_addr eth_dst = loopback ? ip_flow->dl_src : ip_flow->dl_dst;
-+
-+ if (get_dl_type(ip_flow) == htons(ETH_TYPE_IPV6)) {
-+ const struct in6_addr *ip6_src =
-+ loopback ? &ip_flow->ipv6_dst : &ip_flow->ipv6_src;
-+ const struct in6_addr *ip6_dst =
-+ loopback ? &ip_flow->ipv6_src : &ip_flow->ipv6_dst;
-+ pinctrl_compose_ipv6(&packet, eth_src, eth_dst,
-+ (struct in6_addr *) ip6_src,
-+ (struct in6_addr *) ip6_dst,
-+ IPPROTO_SCTP, 63, SCTP_HEADER_LEN +
-+ SCTP_CHUNK_HEADER_LEN);
-+ } else {
-+ ovs_be32 nw_src = loopback ? ip_flow->nw_dst : ip_flow->nw_src;
-+ ovs_be32 nw_dst = loopback ? ip_flow->nw_src : ip_flow->nw_dst;
-+ pinctrl_compose_ipv4(&packet, eth_src, eth_dst, nw_src, nw_dst,
-+ IPPROTO_SCTP, 63, SCTP_HEADER_LEN +
-+ SCTP_CHUNK_HEADER_LEN);
-+ }
-+
-+ struct sctp_header *sh = dp_packet_put_zeros(&packet, sizeof *sh);
-+ dp_packet_set_l4(&packet, sh);
-+ sh->sctp_dst = ip_flow->tp_src;
-+ sh->sctp_src = ip_flow->tp_dst;
-+ put_16aligned_be32(&sh->sctp_csum, 0);
-+
-+ bool tag_reflected;
-+ if (get_16aligned_be32(&sh_in->sctp_vtag) == 0 && sh_in_init) {
-+ /* See RFC 4960 Section 8.4, item 3. */
-+ put_16aligned_be32(&sh->sctp_vtag, sh_in_init->initiate_tag);
-+ tag_reflected = false;
-+ } else {
-+ /* See RFC 4960 Section 8.4, item 8. */
-+ sh->sctp_vtag = sh_in->sctp_vtag;
-+ tag_reflected = true;
-+ }
-+
-+ dp_packet_put_sctp_abort(&packet, tag_reflected);
-+
-+ put_16aligned_be32(&sh->sctp_csum, crc32c((void *) sh,
-+ dp_packet_l4_size(&packet)));
-+
-+ set_actions_and_enqueue_msg(swconn, &packet, md, userdata);
-+ dp_packet_uninit(&packet);
-+}
-+
- static void
- pinctrl_handle_reject(struct rconn *swconn, const struct flow *ip_flow,
- struct dp_packet *pkt_in,
-@@ -1770,6 +1921,8 @@ pinctrl_handle_reject(struct rconn *swconn, const struct flow *ip_flow,
- {
- if (ip_flow->nw_proto == IPPROTO_TCP) {
- pinctrl_handle_tcp_reset(swconn, ip_flow, pkt_in, md, userdata, true);
-+ } else if (ip_flow->nw_proto == IPPROTO_SCTP) {
-+ pinctrl_handle_sctp_abort(swconn, ip_flow, pkt_in, md, userdata, true);
- } else {
- pinctrl_handle_icmp(swconn, ip_flow, pkt_in, md, userdata, true, true);
- }
-@@ -2884,6 +3037,12 @@ process_packet_in(struct rconn *swconn, const struct ofp_header *msg)
- ovs_mutex_unlock(&pinctrl_mutex);
- break;
-
-+ case ACTION_OPCODE_PUT_FDB:
-+ ovs_mutex_lock(&pinctrl_mutex);
-+ pinctrl_handle_put_fdb(&pin.flow_metadata.flow, &headers);
-+ ovs_mutex_unlock(&pinctrl_mutex);
-+ break;
-+
- case ACTION_OPCODE_PUT_DHCPV6_OPTS:
- pinctrl_handle_put_dhcpv6_opts(swconn, &packet, &pin, &userdata,
- &continuation);
-@@ -2926,6 +3085,11 @@ process_packet_in(struct rconn *swconn, const struct ofp_header *msg)
- &userdata, false);
- break;
-
-+ case ACTION_OPCODE_SCTP_ABORT:
-+ pinctrl_handle_sctp_abort(swconn, &headers, &packet,
-+ &pin.flow_metadata, &userdata, false);
-+ break;
-+
- case ACTION_OPCODE_REJECT:
- pinctrl_handle_reject(swconn, &headers, &packet, &pin.flow_metadata,
- &userdata);
-@@ -2962,6 +3126,12 @@ process_packet_in(struct rconn *swconn, const struct ofp_header *msg)
- ovs_mutex_unlock(&pinctrl_mutex);
- break;
-
-+ case ACTION_OPCODE_BFD_MSG:
-+ ovs_mutex_lock(&pinctrl_mutex);
-+ pinctrl_handle_bfd_msg(swconn, &headers, &packet);
-+ ovs_mutex_unlock(&pinctrl_mutex);
-+ break;
-+
- default:
- VLOG_WARN_RL(&rl, "unrecognized packet-in opcode %"PRIu32,
- ntohl(ah->opcode));
-@@ -3053,6 +3223,8 @@ pinctrl_handler(void *arg_)
- swconn = rconn_create(5, 0, DSCP_DEFAULT, 1 << OFP15_VERSION);
-
- while (!latch_is_set(&pctrl->pinctrl_thread_exit)) {
-+ long long int bfd_time = LLONG_MAX;
-+
- ovs_mutex_lock(&pinctrl_mutex);
- pinctrl_rconn_setup(swconn, pctrl->br_int_name);
- ip_mcast_snoop_run();
-@@ -3085,6 +3257,7 @@ pinctrl_handler(void *arg_)
- send_ipv6_ras(swconn, &send_ipv6_ra_time);
- send_ipv6_prefixd(swconn, &send_prefixd_time);
- send_mac_binding_buffered_pkts(swconn);
-+ bfd_monitor_send_msg(swconn, &bfd_time);
- ovs_mutex_unlock(&pinctrl_mutex);
-
- ip_mcast_querier_run(swconn, &send_mcast_query_time);
-@@ -3102,6 +3275,7 @@ pinctrl_handler(void *arg_)
- 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);
-
- new_seq = seq_read(pinctrl_handler_seq);
- seq_wait(pinctrl_handler_seq, new_seq);
-@@ -3146,9 +3320,11 @@ pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
- struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
- struct ovsdb_idl_index *sbrec_igmp_groups,
- struct ovsdb_idl_index *sbrec_ip_multicast_opts,
-+ struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac,
- const struct sbrec_dns_table *dns_table,
- const struct sbrec_controller_event_table *ce_table,
- const struct sbrec_service_monitor_table *svc_mon_table,
-+ const struct sbrec_bfd_table *bfd_table,
- const struct ovsrec_bridge *br_int,
- const struct sbrec_chassis *chassis,
- const struct hmap *local_datapaths,
-@@ -3179,6 +3355,9 @@ pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
- local_datapaths);
- sync_svc_monitors(ovnsb_idl_txn, svc_mon_table, sbrec_port_binding_by_name,
- chassis);
-+ bfd_monitor_run(ovnsb_idl_txn, bfd_table, sbrec_port_binding_by_name,
-+ chassis, active_tunnels);
-+ run_put_fdbs(ovnsb_idl_txn, sbrec_fdb_by_dp_key_mac);
- ovs_mutex_unlock(&pinctrl_mutex);
- }
-
-@@ -3702,6 +3881,7 @@ pinctrl_wait(struct ovsdb_idl_txn *ovnsb_idl_txn)
- wait_put_vport_bindings(ovnsb_idl_txn);
- int64_t new_seq = seq_read(pinctrl_main_seq);
- seq_wait(pinctrl_main_seq, new_seq);
-+ wait_put_fdbs(ovnsb_idl_txn);
- }
-
- /* Called by ovn-controller. */
-@@ -3722,6 +3902,8 @@ pinctrl_destroy(void)
- destroy_dns_cache();
- ip_mcast_snoop_destroy();
- destroy_svc_monitors();
-+ bfd_monitor_destroy();
-+ destroy_fdb_entries();
- seq_destroy(pinctrl_main_seq);
- seq_destroy(pinctrl_handler_seq);
- }
-@@ -3738,47 +3920,20 @@ pinctrl_destroy(void)
- * available. */
-
- /* Buffered "put_mac_binding" operation. */
--struct put_mac_binding {
-- struct hmap_node hmap_node; /* In 'put_mac_bindings'. */
--
-- /* Key. */
-- uint32_t dp_key;
-- uint32_t port_key;
-- struct in6_addr ip_key;
-
-- /* Value. */
-- struct eth_addr mac;
--};
--
--/* Contains "struct put_mac_binding"s. */
-+/* Contains "struct mac_binding"s. */
- static struct hmap put_mac_bindings;
-
- static void
- init_put_mac_bindings(void)
- {
-- hmap_init(&put_mac_bindings);
-+ ovn_mac_bindings_init(&put_mac_bindings);
- }
-
- static void
- destroy_put_mac_bindings(void)
- {
-- flush_put_mac_bindings();
-- hmap_destroy(&put_mac_bindings);
--}
--
--static struct put_mac_binding *
--pinctrl_find_put_mac_binding(uint32_t dp_key, uint32_t port_key,
-- const struct in6_addr *ip_key, uint32_t hash)
--{
-- struct put_mac_binding *pa;
-- HMAP_FOR_EACH_WITH_HASH (pa, hmap_node, hash, &put_mac_bindings) {
-- if (pa->dp_key == dp_key
-- && pa->port_key == port_key
-- && IN6_ARE_ADDR_EQUAL(&pa->ip_key, ip_key)) {
-- return pa;
-- }
-- }
-- return NULL;
-+ ovn_mac_bindings_destroy(&put_mac_bindings);
- }
-
- /* Called with in the pinctrl_handler thread context. */
-@@ -3798,23 +3953,14 @@ pinctrl_handle_put_mac_binding(const struct flow *md,
- ovs_be128 ip6 = hton128(flow_get_xxreg(md, 0));
- memcpy(&ip_key, &ip6, sizeof ip_key);
- }
-- uint32_t hash = hash_bytes(&ip_key, sizeof ip_key,
-- hash_2words(dp_key, port_key));
-- struct put_mac_binding *pmb
-- = pinctrl_find_put_mac_binding(dp_key, port_key, &ip_key, hash);
-- if (!pmb) {
-- if (hmap_count(&put_mac_bindings) >= 1000) {
-- COVERAGE_INC(pinctrl_drop_put_mac_binding);
-- return;
-- }
-
-- pmb = xmalloc(sizeof *pmb);
-- hmap_insert(&put_mac_bindings, &pmb->hmap_node, hash);
-- pmb->dp_key = dp_key;
-- pmb->port_key = port_key;
-- pmb->ip_key = ip_key;
-+ struct mac_binding *mb = ovn_mac_binding_add(&put_mac_bindings, dp_key,
-+ port_key, &ip_key,
-+ headers->dl_src);
-+ if (!mb) {
-+ COVERAGE_INC(pinctrl_drop_put_mac_binding);
-+ return;
- }
-- pmb->mac = headers->dl_src;
-
- /* We can send the buffered packet once the main ovn-controller
- * thread calls pinctrl_run() and it writes the mac_bindings stored
-@@ -3857,12 +4003,12 @@ mac_binding_lookup(struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
- /* Update or add an IP-MAC binding for 'logical_port'.
- * Caller should make sure that 'ovnsb_idl_txn' is valid. */
- static void
--mac_binding_add(struct ovsdb_idl_txn *ovnsb_idl_txn,
-- struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
-- const char *logical_port,
-- const struct sbrec_datapath_binding *dp,
-- struct eth_addr ea, const char *ip,
-- bool update_only)
-+mac_binding_add_to_sb(struct ovsdb_idl_txn *ovnsb_idl_txn,
-+ struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
-+ const char *logical_port,
-+ const struct sbrec_datapath_binding *dp,
-+ struct eth_addr ea, const char *ip,
-+ bool update_only)
- {
- /* Convert ethernet argument to string form for database. */
- char mac_string[ETH_ADDR_STRLEN + 1];
-@@ -3918,9 +4064,9 @@ send_garp_locally(struct ovsdb_idl_txn *ovnsb_idl_txn,
- struct ds ip_s = DS_EMPTY_INITIALIZER;
-
- ip_format_masked(ip, OVS_BE32_MAX, &ip_s);
-- mac_binding_add(ovnsb_idl_txn, sbrec_mac_binding_by_lport_ip,
-- remote->logical_port, remote->datapath,
-- ea, ds_cstr(&ip_s), update_only);
-+ mac_binding_add_to_sb(ovnsb_idl_txn, sbrec_mac_binding_by_lport_ip,
-+ remote->logical_port, remote->datapath,
-+ ea, ds_cstr(&ip_s), update_only);
- ds_destroy(&ip_s);
- }
- }
-@@ -3930,30 +4076,30 @@ run_put_mac_binding(struct ovsdb_idl_txn *ovnsb_idl_txn,
- struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
- struct ovsdb_idl_index *sbrec_port_binding_by_key,
- struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
-- const struct put_mac_binding *pmb)
-+ const struct mac_binding *mb)
- {
- /* Convert logical datapath and logical port key into lport. */
- const struct sbrec_port_binding *pb = lport_lookup_by_key(
- sbrec_datapath_binding_by_key, sbrec_port_binding_by_key,
-- pmb->dp_key, pmb->port_key);
-+ mb->dp_key, mb->port_key);
- if (!pb) {
- static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-
- VLOG_WARN_RL(&rl, "unknown logical port with datapath %"PRIu32" "
-- "and port %"PRIu32, pmb->dp_key, pmb->port_key);
-+ "and port %"PRIu32, mb->dp_key, mb->port_key);
- return;
- }
-
- /* Convert ethernet argument to string form for database. */
- char mac_string[ETH_ADDR_STRLEN + 1];
- snprintf(mac_string, sizeof mac_string,
-- ETH_ADDR_FMT, ETH_ADDR_ARGS(pmb->mac));
-+ ETH_ADDR_FMT, ETH_ADDR_ARGS(mb->mac));
-
- struct ds ip_s = DS_EMPTY_INITIALIZER;
-- ipv6_format_mapped(&pmb->ip_key, &ip_s);
-- mac_binding_add(ovnsb_idl_txn, sbrec_mac_binding_by_lport_ip,
-- pb->logical_port, pb->datapath, pmb->mac, ds_cstr(&ip_s),
-- false);
-+ ipv6_format_mapped(&mb->ip, &ip_s);
-+ mac_binding_add_to_sb(ovnsb_idl_txn, sbrec_mac_binding_by_lport_ip,
-+ pb->logical_port, pb->datapath, mb->mac,
-+ ds_cstr(&ip_s), false);
- ds_destroy(&ip_s);
- }
-
-@@ -3970,14 +4116,14 @@ run_put_mac_bindings(struct ovsdb_idl_txn *ovnsb_idl_txn,
- return;
- }
-
-- const struct put_mac_binding *pmb;
-- HMAP_FOR_EACH (pmb, hmap_node, &put_mac_bindings) {
-+ const struct mac_binding *mb;
-+ HMAP_FOR_EACH (mb, hmap_node, &put_mac_bindings) {
- run_put_mac_binding(ovnsb_idl_txn, sbrec_datapath_binding_by_key,
- sbrec_port_binding_by_key,
- sbrec_mac_binding_by_lport_ip,
-- pmb);
-+ mb);
- }
-- flush_put_mac_bindings();
-+ ovn_mac_bindings_flush(&put_mac_bindings);
- }
-
- static void
-@@ -4033,14 +4179,6 @@ wait_put_mac_bindings(struct ovsdb_idl_txn *ovnsb_idl_txn)
- }
- }
-
--static void
--flush_put_mac_bindings(void)
--{
-- struct put_mac_binding *pmb;
-- HMAP_FOR_EACH_POP (pmb, hmap_node, &put_mac_bindings) {
-- free(pmb);
-- }
--}
-
- /*
- * Send gratuitous/reverse ARP for vif on localnet.
-@@ -5525,7 +5663,8 @@ may_inject_pkts(void)
- !shash_is_empty(&send_garp_rarp_data) ||
- ipv6_prefixd_should_inject() ||
- !ovs_list_is_empty(&mcast_query_list) ||
-- !ovs_list_is_empty(&buffered_mac_bindings));
-+ !ovs_list_is_empty(&buffered_mac_bindings) ||
-+ bfd_monitor_should_inject());
- }
-
- static void
-@@ -6312,6 +6451,665 @@ sync_svc_monitors(struct ovsdb_idl_txn *ovnsb_idl_txn,
-
- }
-
-+enum bfd_state {
-+ BFD_STATE_ADMIN_DOWN,
-+ BFD_STATE_DOWN,
-+ BFD_STATE_INIT,
-+ BFD_STATE_UP,
-+};
-+
-+enum bfd_flags {
-+ BFD_FLAG_MULTIPOINT = 1 << 0,
-+ BFD_FLAG_DEMAND = 1 << 1,
-+ BFD_FLAG_AUTH = 1 << 2,
-+ BFD_FLAG_CTL = 1 << 3,
-+ BFD_FLAG_FINAL = 1 << 4,
-+ BFD_FLAG_POLL = 1 << 5
-+};
-+
-+#define BFD_FLAGS_MASK 0x3f
-+
-+static char *
-+bfd_get_status(enum bfd_state state)
-+{
-+ switch (state) {
-+ case BFD_STATE_ADMIN_DOWN:
-+ return "admin_down";
-+ case BFD_STATE_DOWN:
-+ return "down";
-+ case BFD_STATE_INIT:
-+ return "init";
-+ case BFD_STATE_UP:
-+ return "up";
-+ default:
-+ return "";
-+ }
-+}
-+
-+static struct hmap bfd_monitor_map;
-+
-+#define BFD_UPDATE_BATCH_TH 10
-+static uint16_t bfd_pending_update;
-+#define BFD_UPDATE_TIMEOUT 5000LL
-+static long long bfd_last_update;
-+
-+struct bfd_entry {
-+ struct hmap_node node;
-+ bool erase;
-+
-+ /* L2 source address */
-+ struct eth_addr src_mac;
-+ /* IP source address */
-+ struct in6_addr ip_src;
-+ /* IP destination address */
-+ struct in6_addr ip_dst;
-+ /* RFC 5881 section 4
-+ * The source port MUST be in the range 49152 through 65535.
-+ * The same UDP source port number MUST be used for all BFD
-+ * Control packets associated with a particular session.
-+ * The source port number SHOULD be unique among all BFD
-+ * sessions on the system
-+ */
-+ uint16_t udp_src;
-+ ovs_be32 local_disc;
-+ ovs_be32 remote_disc;
-+
-+ uint32_t local_min_tx;
-+ uint32_t local_min_rx;
-+ uint32_t remote_min_rx;
-+
-+ bool remote_demand_mode;
-+
-+ uint8_t local_mult;
-+
-+ int64_t port_key;
-+ int64_t metadata;
-+
-+ enum bfd_state state;
-+ bool change_state;
-+
-+ uint32_t detection_timeout;
-+ long long int last_rx;
-+ long long int next_tx;
-+};
-+
-+static void
-+bfd_monitor_init(void)
-+{
-+ hmap_init(&bfd_monitor_map);
-+ bfd_last_update = time_msec();
-+}
-+
-+static void
-+bfd_monitor_destroy(void)
-+{
-+ struct bfd_entry *entry;
-+ HMAP_FOR_EACH_POP (entry, node, &bfd_monitor_map) {
-+ free(entry);
-+ }
-+ hmap_destroy(&bfd_monitor_map);
-+}
-+
-+static struct bfd_entry *
-+pinctrl_find_bfd_monitor_entry_by_port(char *ip, uint16_t port)
-+{
-+ struct bfd_entry *entry;
-+ HMAP_FOR_EACH_WITH_HASH (entry, node, hash_string(ip, 0),
-+ &bfd_monitor_map) {
-+ if (entry->udp_src == port) {
-+ return entry;
-+ }
-+ }
-+ return NULL;
-+}
-+
-+static struct bfd_entry *
-+pinctrl_find_bfd_monitor_entry_by_disc(char *ip, ovs_be32 disc)
-+{
-+ struct bfd_entry *ret = NULL, *entry;
-+
-+ HMAP_FOR_EACH_WITH_HASH (entry, node, hash_string(ip, 0),
-+ &bfd_monitor_map) {
-+ if (entry->local_disc == disc) {
-+ ret = entry;
-+ break;
-+ }
-+ }
-+ return ret;
-+}
-+
-+static bool
-+bfd_monitor_should_inject(void)
-+{
-+ long long int cur_time = time_msec();
-+ struct bfd_entry *entry;
-+
-+ HMAP_FOR_EACH (entry, node, &bfd_monitor_map) {
-+ if (entry->next_tx < cur_time) {
-+ return true;
-+ }
-+ }
-+ return false;
-+}
-+
-+static void
-+bfd_monitor_wait(long long int timeout)
-+{
-+ if (!hmap_is_empty(&bfd_monitor_map)) {
-+ poll_timer_wait_until(timeout);
-+ }
-+}
-+
-+static void
-+bfd_monitor_put_bfd_msg(struct bfd_entry *entry, struct dp_packet *packet,
-+ bool final)
-+{
-+ int payload_len = sizeof(struct udp_header) + sizeof(struct bfd_msg);
-+
-+ /* Properly align after the ethernet header */
-+ dp_packet_reserve(packet, 2);
-+ if (IN6_IS_ADDR_V4MAPPED(&entry->ip_src)) {
-+ ovs_be32 ip_src = in6_addr_get_mapped_ipv4(&entry->ip_src);
-+ ovs_be32 ip_dst = in6_addr_get_mapped_ipv4(&entry->ip_dst);
-+ pinctrl_compose_ipv4(packet, entry->src_mac, eth_addr_broadcast,
-+ ip_src, ip_dst, IPPROTO_UDP, MAXTTL, payload_len);
-+ } else {
-+ pinctrl_compose_ipv6(packet, entry->src_mac, eth_addr_broadcast,
-+ &entry->ip_src, &entry->ip_dst, IPPROTO_UDP,
-+ MAXTTL, payload_len);
-+ }
-+
-+ struct udp_header *udp = dp_packet_put_zeros(packet, sizeof *udp);
-+ udp->udp_len = htons(payload_len);
-+ udp->udp_csum = 0;
-+ udp->udp_src = htons(entry->udp_src);
-+ udp->udp_dst = htons(BFD_DEST_PORT);
-+
-+ struct bfd_msg *msg = dp_packet_put_zeros(packet, sizeof *msg);
-+ msg->vers_diag = (BFD_VERSION << 5);
-+ msg->mult = entry->local_mult;
-+ msg->length = BFD_PACKET_LEN;
-+ msg->flags = final ? BFD_FLAG_FINAL : 0;
-+ msg->flags |= entry->state << 6;
-+ msg->my_disc = entry->local_disc;
-+ msg->your_disc = entry->remote_disc;
-+ /* min_tx and min_rx are in us - RFC 5880 page 9 */
-+ msg->min_tx = htonl(entry->local_min_tx * 1000);
-+ msg->min_rx = htonl(entry->local_min_rx * 1000);
-+
-+ if (!IN6_IS_ADDR_V4MAPPED(&entry->ip_src)) {
-+ /* IPv6 needs UDP checksum calculated */
-+ uint32_t csum = packet_csum_pseudoheader6(dp_packet_l3(packet));
-+ int len = (uint8_t *)udp - (uint8_t *)dp_packet_eth(packet);
-+ csum = csum_continue(csum, udp, dp_packet_size(packet) - len);
-+ udp->udp_csum = csum_finish(csum);
-+ if (!udp->udp_csum) {
-+ udp->udp_csum = htons(0xffff);
-+ }
-+ }
-+}
-+
-+static void
-+pinctrl_send_bfd_tx_msg(struct rconn *swconn, struct bfd_entry *entry,
-+ bool final)
-+{
-+ uint64_t packet_stub[256 / 8];
-+ struct dp_packet packet;
-+ dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
-+ bfd_monitor_put_bfd_msg(entry, &packet, final);
-+
-+ uint64_t ofpacts_stub[4096 / 8];
-+ struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub);
-+
-+ /* Set MFF_LOG_DATAPATH and MFF_LOG_INPORT. */
-+ uint32_t dp_key = entry->metadata;
-+ uint32_t port_key = entry->port_key;
-+ put_load(dp_key, MFF_LOG_DATAPATH, 0, 64, &ofpacts);
-+ put_load(port_key, MFF_LOG_INPORT, 0, 32, &ofpacts);
-+ put_load(1, MFF_LOG_FLAGS, MLF_LOCAL_ONLY_BIT, 1, &ofpacts);
-+ struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&ofpacts);
-+ resubmit->in_port = OFPP_CONTROLLER;
-+ resubmit->table_id = OFTABLE_LOG_INGRESS_PIPELINE;
-+
-+ struct ofputil_packet_out po = {
-+ .packet = dp_packet_data(&packet),
-+ .packet_len = dp_packet_size(&packet),
-+ .buffer_id = UINT32_MAX,
-+ .ofpacts = ofpacts.data,
-+ .ofpacts_len = ofpacts.size,
-+ };
-+
-+ match_set_in_port(&po.flow_metadata, OFPP_CONTROLLER);
-+ enum ofp_version version = rconn_get_version(swconn);
-+ enum ofputil_protocol proto =
-+ ofputil_protocol_from_ofp_version(version);
-+ queue_msg(swconn, ofputil_encode_packet_out(&po, proto));
-+ dp_packet_uninit(&packet);
-+ ofpbuf_uninit(&ofpacts);
-+}
-+
-+
-+static bool
-+bfd_monitor_need_update(void)
-+{
-+ long long int cur_time = time_msec();
-+
-+ if (bfd_pending_update == BFD_UPDATE_BATCH_TH) {
-+ goto update;
-+ }
-+
-+ if (bfd_pending_update &&
-+ bfd_last_update + BFD_UPDATE_TIMEOUT < cur_time) {
-+ goto update;
-+ }
-+ return false;
-+
-+update:
-+ bfd_last_update = cur_time;
-+ bfd_pending_update = 0;
-+ return true;
-+}
-+
-+static void
-+bfd_check_detection_timeout(struct bfd_entry *entry)
-+{
-+ if (entry->state == BFD_STATE_ADMIN_DOWN) {
-+ return;
-+ }
-+
-+ if (!entry->detection_timeout) {
-+ return;
-+ }
-+
-+ long long int cur_time = time_msec();
-+ if (cur_time < entry->last_rx + entry->detection_timeout) {
-+ return;
-+ }
-+
-+ entry->state = BFD_STATE_DOWN;
-+ entry->change_state = true;
-+ bfd_last_update = cur_time;
-+ bfd_pending_update = 0;
-+ notify_pinctrl_main();
-+}
-+
-+static void
-+bfd_monitor_send_msg(struct rconn *swconn, long long int *bfd_time)
-+ OVS_REQUIRES(pinctrl_mutex)
-+{
-+ long long int cur_time = time_msec();
-+ struct bfd_entry *entry;
-+
-+ if (bfd_monitor_need_update()) {
-+ notify_pinctrl_main();
-+ }
-+
-+ HMAP_FOR_EACH (entry, node, &bfd_monitor_map) {
-+ unsigned long tx_timeout;
-+
-+ bfd_check_detection_timeout(entry);
-+
-+ if (cur_time < entry->next_tx) {
-+ goto next;
-+ }
-+
-+ if (!entry->remote_min_rx) {
-+ continue;
-+ }
-+
-+ if (entry->state == BFD_STATE_ADMIN_DOWN) {
-+ continue;
-+ }
-+
-+ if (entry->remote_demand_mode) {
-+ continue;
-+ }
-+
-+ pinctrl_send_bfd_tx_msg(swconn, entry, false);
-+
-+ tx_timeout = MAX(entry->local_min_tx, entry->remote_min_rx);
-+ tx_timeout -= random_range((tx_timeout * 25) / 100);
-+ entry->next_tx = cur_time + tx_timeout;
-+next:
-+ if (*bfd_time > entry->next_tx) {
-+ *bfd_time = entry->next_tx;
-+ }
-+ }
-+}
-+
-+static bool
-+pinctrl_check_bfd_msg(const struct flow *ip_flow, struct dp_packet *pkt_in)
-+{
-+ if (ip_flow->dl_type != htons(ETH_TYPE_IP) &&
-+ ip_flow->dl_type != htons(ETH_TYPE_IPV6)) {
-+ return false;
-+ }
-+
-+ if (ip_flow->nw_proto != IPPROTO_UDP) {
-+ return false;
-+ }
-+
-+ struct udp_header *udp_hdr = dp_packet_l4(pkt_in);
-+ if (udp_hdr->udp_dst != htons(BFD_DEST_PORT)) {
-+ return false;
-+ }
-+
-+ const struct bfd_msg *msg = dp_packet_get_udp_payload(pkt_in);
-+ uint8_t version = msg->vers_diag >> 5;
-+ if (version != BFD_VERSION) {
-+ return false;
-+ }
-+
-+ enum bfd_flags flags = msg->flags & BFD_FLAGS_MASK;
-+ if (flags & BFD_FLAG_AUTH) {
-+ /* AUTH not supported yet */
-+ return false;
-+ }
-+
-+ if (msg->length < BFD_PACKET_LEN) {
-+ return false;
-+ }
-+
-+ if (!msg->mult) {
-+ return false;
-+ }
-+
-+ if (flags & BFD_FLAG_MULTIPOINT) {
-+ return false;
-+ }
-+
-+ if (!msg->my_disc) {
-+ return false;
-+ }
-+
-+ if ((flags & BFD_FLAG_FINAL) && (flags & BFD_FLAG_POLL)) {
-+ return false;
-+ }
-+
-+ enum bfd_state peer_state = msg->flags >> 6;
-+ if (peer_state >= BFD_STATE_INIT && !msg->your_disc) {
-+ return false;
-+ }
-+
-+ return true;
-+}
-+
-+static void
-+pinctrl_handle_bfd_msg(struct rconn *swconn, const struct flow *ip_flow,
-+ struct dp_packet *pkt_in)
-+ OVS_REQUIRES(pinctrl_mutex)
-+{
-+ if (!pinctrl_check_bfd_msg(ip_flow, pkt_in)) {
-+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-+ VLOG_WARN_RL(&rl, "BFD packet discarded");
-+ return;
-+ }
-+
-+ char *ip_src;
-+ if (ip_flow->dl_type == htons(ETH_TYPE_IP)) {
-+ ip_src = normalize_ipv4_prefix(ip_flow->nw_src, 32);
-+ } else {
-+ ip_src = normalize_ipv6_prefix(&ip_flow->ipv6_src, 128);
-+ }
-+
-+ const struct bfd_msg *msg = dp_packet_get_udp_payload(pkt_in);
-+ struct bfd_entry *entry =
-+ pinctrl_find_bfd_monitor_entry_by_disc(ip_src, msg->your_disc);
-+ free(ip_src);
-+
-+ if (!entry) {
-+ return;
-+ }
-+
-+ bool change_state = false;
-+ entry->remote_disc = msg->my_disc;
-+ uint32_t remote_min_tx = ntohl(msg->min_tx) / 1000;
-+ entry->remote_min_rx = ntohl(msg->min_rx) / 1000;
-+ entry->detection_timeout = msg->mult * MAX(remote_min_tx,
-+ entry->local_min_rx);
-+
-+ enum bfd_state peer_state = msg->flags >> 6;
-+ if (peer_state == BFD_STATE_ADMIN_DOWN &&
-+ entry->state >= BFD_STATE_INIT) {
-+ entry->state = BFD_STATE_DOWN;
-+ entry->last_rx = time_msec();
-+ change_state = true;
-+ goto out;
-+ }
-+
-+ /* bfd state machine */
-+ switch (entry->state) {
-+ case BFD_STATE_DOWN:
-+ if (peer_state == BFD_STATE_DOWN) {
-+ entry->state = BFD_STATE_INIT;
-+ change_state = true;
-+ }
-+ if (peer_state == BFD_STATE_INIT) {
-+ entry->state = BFD_STATE_UP;
-+ change_state = true;
-+ }
-+ entry->last_rx = time_msec();
-+ break;
-+ case BFD_STATE_INIT:
-+ if (peer_state == BFD_STATE_INIT ||
-+ peer_state == BFD_STATE_UP) {
-+ entry->state = BFD_STATE_UP;
-+ change_state = true;
-+ }
-+ if (peer_state == BFD_STATE_ADMIN_DOWN) {
-+ entry->state = BFD_STATE_DOWN;
-+ change_state = true;
-+ }
-+ entry->last_rx = time_msec();
-+ break;
-+ case BFD_STATE_UP:
-+ if (peer_state == BFD_STATE_ADMIN_DOWN ||
-+ peer_state == BFD_STATE_DOWN) {
-+ entry->state = BFD_STATE_DOWN;
-+ change_state = true;
-+ }
-+ entry->last_rx = time_msec();
-+ break;
-+ case BFD_STATE_ADMIN_DOWN:
-+ default:
-+ break;
-+ }
-+
-+ if (entry->state == BFD_STATE_UP &&
-+ (msg->flags & BFD_FLAG_DEMAND)) {
-+ entry->remote_demand_mode = true;
-+ }
-+
-+ if (msg->flags & BFD_FLAG_POLL) {
-+ pinctrl_send_bfd_tx_msg(swconn, entry, true);
-+ }
-+
-+out:
-+ /* let's try to bacth db updates */
-+ if (change_state) {
-+ entry->change_state = true;
-+ bfd_pending_update++;
-+ }
-+ if (bfd_monitor_need_update()) {
-+ notify_pinctrl_main();
-+ }
-+}
-+
-+static void
-+bfd_monitor_check_sb_conf(const struct sbrec_bfd *sb_bt,
-+ struct bfd_entry *entry)
-+{
-+ struct lport_addresses dst_addr;
-+
-+ if (extract_ip_addresses(sb_bt->dst_ip, &dst_addr)) {
-+ struct in6_addr addr;
-+
-+ if (dst_addr.n_ipv6_addrs > 0) {
-+ addr = dst_addr.ipv6_addrs[0].addr;
-+ } else {
-+ addr = in6_addr_mapped_ipv4(dst_addr.ipv4_addrs[0].addr);
-+ }
-+
-+ if (!ipv6_addr_equals(&addr, &entry->ip_dst)) {
-+ entry->ip_dst = addr;
-+ }
-+ destroy_lport_addresses(&dst_addr);
-+ }
-+
-+ if (sb_bt->min_tx != entry->local_min_tx) {
-+ entry->local_min_tx = sb_bt->min_tx;
-+ }
-+
-+ if (sb_bt->min_rx != entry->local_min_rx) {
-+ entry->local_min_rx = sb_bt->min_rx;
-+ }
-+
-+ if (sb_bt->detect_mult != entry->local_mult) {
-+ entry->local_mult = sb_bt->detect_mult;
-+ }
-+}
-+
-+static void
-+bfd_monitor_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
-+ const struct sbrec_bfd_table *bfd_table,
-+ struct ovsdb_idl_index *sbrec_port_binding_by_name,
-+ const struct sbrec_chassis *chassis,
-+ const struct sset *active_tunnels)
-+ OVS_REQUIRES(pinctrl_mutex)
-+{
-+ struct bfd_entry *entry, *next_entry;
-+ long long int cur_time = time_msec();
-+ bool changed = false;
-+
-+ HMAP_FOR_EACH (entry, node, &bfd_monitor_map) {
-+ entry->erase = true;
-+ }
-+
-+ const struct sbrec_bfd *bt;
-+ SBREC_BFD_TABLE_FOR_EACH (bt, bfd_table) {
-+ const struct sbrec_port_binding *pb
-+ = lport_lookup_by_name(sbrec_port_binding_by_name,
-+ bt->logical_port);
-+ if (!pb) {
-+ continue;
-+ }
-+
-+ const char *peer_s = smap_get(&pb->options, "peer");
-+ if (!peer_s) {
-+ continue;
-+ }
-+
-+ const struct sbrec_port_binding *peer
-+ = lport_lookup_by_name(sbrec_port_binding_by_name, peer_s);
-+ if (!peer) {
-+ continue;
-+ }
-+
-+ char *redirect_name = xasprintf("cr-%s", pb->logical_port);
-+ bool resident = lport_is_chassis_resident(
-+ sbrec_port_binding_by_name, chassis, active_tunnels,
-+ redirect_name);
-+ free(redirect_name);
-+ if ((strcmp(pb->type, "l3gateway") || pb->chassis != chassis) &&
-+ !resident) {
-+ continue;
-+ }
-+
-+ entry = pinctrl_find_bfd_monitor_entry_by_port(
-+ bt->dst_ip, bt->src_port);
-+ if (!entry) {
-+ struct eth_addr ea = eth_addr_zero;
-+ struct lport_addresses dst_addr;
-+ struct in6_addr ip_src, ip_dst;
-+ int i;
-+
-+ ip_dst = in6_addr_mapped_ipv4(htonl(BFD_DEFAULT_DST_IP));
-+ ip_src = in6_addr_mapped_ipv4(htonl(BFD_DEFAULT_SRC_IP));
-+
-+ if (!extract_ip_addresses(bt->dst_ip, &dst_addr)) {
-+ continue;
-+ }
-+
-+ for (i = 0; i < pb->n_mac; i++) {
-+ struct lport_addresses laddrs;
-+
-+ if (!extract_lsp_addresses(pb->mac[i], &laddrs)) {
-+ continue;
-+ }
-+
-+ ea = laddrs.ea;
-+ if (dst_addr.n_ipv6_addrs > 0 && laddrs.n_ipv6_addrs > 0) {
-+ ip_dst = dst_addr.ipv6_addrs[0].addr;
-+ ip_src = laddrs.ipv6_addrs[0].addr;
-+ destroy_lport_addresses(&laddrs);
-+ break;
-+ } else if (laddrs.n_ipv4_addrs > 0) {
-+ ip_dst = in6_addr_mapped_ipv4(dst_addr.ipv4_addrs[0].addr);
-+ ip_src = in6_addr_mapped_ipv4(laddrs.ipv4_addrs[0].addr);
-+ destroy_lport_addresses(&laddrs);
-+ break;
-+ }
-+ destroy_lport_addresses(&laddrs);
-+ }
-+ destroy_lport_addresses(&dst_addr);
-+
-+ if (eth_addr_is_zero(ea)) {
-+ continue;
-+ }
-+
-+ entry = xzalloc(sizeof *entry);
-+ entry->src_mac = ea;
-+ entry->ip_src = ip_src;
-+ entry->ip_dst = ip_dst;
-+ entry->udp_src = bt->src_port;
-+ entry->local_disc = htonl(bt->disc);
-+ entry->next_tx = cur_time;
-+ entry->last_rx = cur_time;
-+ entry->detection_timeout = 30000;
-+ entry->metadata = pb->datapath->tunnel_key;
-+ entry->port_key = pb->tunnel_key;
-+ entry->state = BFD_STATE_ADMIN_DOWN;
-+ entry->local_min_tx = bt->min_tx;
-+ entry->local_min_rx = bt->min_rx;
-+ entry->remote_min_rx = 1; /* RFC5880 page 29 */
-+ entry->local_mult = bt->detect_mult;
-+
-+ uint32_t hash = hash_string(bt->dst_ip, 0);
-+ hmap_insert(&bfd_monitor_map, &entry->node, hash);
-+ } else if (!strcmp(bt->status, "admin_down") &&
-+ entry->state != BFD_STATE_ADMIN_DOWN) {
-+ entry->state = BFD_STATE_ADMIN_DOWN;
-+ entry->change_state = false;
-+ entry->remote_disc = 0;
-+ } else if (strcmp(bt->status, "admin_down") &&
-+ entry->state == BFD_STATE_ADMIN_DOWN) {
-+ entry->state = BFD_STATE_DOWN;
-+ entry->change_state = false;
-+ entry->remote_disc = 0;
-+ changed = true;
-+ } else if (entry->change_state && ovnsb_idl_txn) {
-+ if (entry->state == BFD_STATE_DOWN) {
-+ entry->remote_disc = 0;
-+ }
-+ sbrec_bfd_set_status(bt, bfd_get_status(entry->state));
-+ entry->change_state = false;
-+ }
-+ bfd_monitor_check_sb_conf(bt, entry);
-+ entry->erase = false;
-+ }
-+
-+ HMAP_FOR_EACH_SAFE (entry, next_entry, node, &bfd_monitor_map) {
-+ if (entry->erase) {
-+ hmap_remove(&bfd_monitor_map, &entry->node);
-+ free(entry);
-+ }
-+ }
-+
-+ if (changed) {
-+ notify_pinctrl_handler();
-+ }
-+}
-+
- static uint16_t
- get_random_src_port(void)
- {
-@@ -6724,3 +7522,94 @@ pinctrl_handle_svc_check(struct rconn *swconn, const struct flow *ip_flow,
- svc_mon->next_send_time = time_msec() + svc_mon->interval;
- }
- }
-+
-+static struct hmap put_fdbs;
-+
-+/* MAC learning (fdb) related functions. Runs within the main
-+ * ovn-controller thread context. */
-+
-+static void
-+init_fdb_entries(void)
-+{
-+ ovn_fdb_init(&put_fdbs);
-+}
-+
-+static void
-+destroy_fdb_entries(void)
-+{
-+ ovn_fdbs_destroy(&put_fdbs);
-+}
-+
-+static const struct sbrec_fdb *
-+fdb_lookup(struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac, uint32_t dp_key,
-+ const char *mac)
-+{
-+ struct sbrec_fdb *fdb = sbrec_fdb_index_init_row(sbrec_fdb_by_dp_key_mac);
-+ sbrec_fdb_index_set_dp_key(fdb, dp_key);
-+ sbrec_fdb_index_set_mac(fdb, mac);
-+
-+ const struct sbrec_fdb *retval
-+ = sbrec_fdb_index_find(sbrec_fdb_by_dp_key_mac, fdb);
-+
-+ sbrec_fdb_index_destroy_row(fdb);
-+
-+ return retval;
-+}
-+
-+static void
-+run_put_fdb(struct ovsdb_idl_txn *ovnsb_idl_txn,
-+ struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac,
-+ const struct fdb_entry *fdb_e)
-+{
-+ /* Convert ethernet argument to string form for database. */
-+ char mac_string[ETH_ADDR_STRLEN + 1];
-+ snprintf(mac_string, sizeof mac_string,
-+ ETH_ADDR_FMT, ETH_ADDR_ARGS(fdb_e->mac));
-+
-+ /* Update or add an FDB entry. */
-+ const struct sbrec_fdb *sb_fdb =
-+ fdb_lookup(sbrec_fdb_by_dp_key_mac, fdb_e->dp_key, mac_string);
-+ if (!sb_fdb) {
-+ sb_fdb = sbrec_fdb_insert(ovnsb_idl_txn);
-+ sbrec_fdb_set_dp_key(sb_fdb, fdb_e->dp_key);
-+ sbrec_fdb_set_mac(sb_fdb, mac_string);
-+ }
-+ sbrec_fdb_set_port_key(sb_fdb, fdb_e->port_key);
-+}
-+
-+static void
-+run_put_fdbs(struct ovsdb_idl_txn *ovnsb_idl_txn,
-+ struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac)
-+ OVS_REQUIRES(pinctrl_mutex)
-+{
-+ if (!ovnsb_idl_txn) {
-+ return;
-+ }
-+
-+ const struct fdb_entry *fdb_e;
-+ HMAP_FOR_EACH (fdb_e, hmap_node, &put_fdbs) {
-+ run_put_fdb(ovnsb_idl_txn, sbrec_fdb_by_dp_key_mac, fdb_e);
-+ }
-+ ovn_fdbs_flush(&put_fdbs);
-+}
-+
-+
-+static void
-+wait_put_fdbs(struct ovsdb_idl_txn *ovnsb_idl_txn)
-+{
-+ if (ovnsb_idl_txn && !hmap_is_empty(&put_fdbs)) {
-+ poll_immediate_wake();
-+ }
-+}
-+
-+/* Called with in the pinctrl_handler thread context. */
-+static void
-+pinctrl_handle_put_fdb(const struct flow *md, const struct flow *headers)
-+ OVS_REQUIRES(pinctrl_mutex)
-+{
-+ uint32_t dp_key = ntohll(md->metadata);
-+ uint32_t port_key = md->regs[MFF_LOG_INPORT - MFF_REG0];
-+
-+ ovn_fdb_add(&put_fdbs, dp_key, headers->dl_src, port_key);
-+ notify_pinctrl_main();
-+}
-diff --git a/controller/pinctrl.h b/controller/pinctrl.h
-index 4b101ec92..cc0a51984 100644
---- a/controller/pinctrl.h
-+++ b/controller/pinctrl.h
-@@ -31,6 +31,7 @@ struct sbrec_chassis;
- struct sbrec_dns_table;
- struct sbrec_controller_event_table;
- struct sbrec_service_monitor_table;
-+struct sbrec_bfd_table;
-
- void pinctrl_init(void);
- void pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
-@@ -41,9 +42,11 @@ void pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
- struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
- struct ovsdb_idl_index *sbrec_igmp_groups,
- struct ovsdb_idl_index *sbrec_ip_multicast_opts,
-+ struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac,
- const struct sbrec_dns_table *,
- const struct sbrec_controller_event_table *,
- const struct sbrec_service_monitor_table *,
-+ const struct sbrec_bfd_table *,
- const struct ovsrec_bridge *, const struct sbrec_chassis *,
- const struct hmap *local_datapaths,
- const struct sset *active_tunnels);
-diff --git a/controller/test-ofctrl-seqno.c b/controller/test-ofctrl-seqno.c
-new file mode 100644
-index 000000000..fce88d4bd
---- /dev/null
-+++ b/controller/test-ofctrl-seqno.c
-@@ -0,0 +1,194 @@
-+/* Copyright (c) 2021, Red Hat, Inc.
-+ *
-+ * Licensed under the Apache License, Version 2.0 (the "License");
-+ * you may not use this file except in compliance with the License.
-+ * You may obtain a copy of the License at:
-+ *
-+ * http://www.apache.org/licenses/LICENSE-2.0
-+ *
-+ * Unless required by applicable law or agreed to in writing, software
-+ * distributed under the License is distributed on an "AS IS" BASIS,
-+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-+ * See the License for the specific language governing permissions and
-+ * limitations under the License.
-+ */
-+
-+#include
-+
-+#include "tests/ovstest.h"
-+#include "sort.h"
-+#include "util.h"
-+
-+#include "ofctrl-seqno.h"
-+
-+static void
-+test_init(void)
-+{
-+ ofctrl_seqno_init();
-+}
-+
-+static bool
-+test_read_uint_value(struct ovs_cmdl_context *ctx, unsigned int index,
-+ const char *descr, unsigned int *result)
-+{
-+ if (index >= ctx->argc) {
-+ fprintf(stderr, "Missing %s argument\n", descr);
-+ return false;
-+ }
-+
-+ const char *arg = ctx->argv[index];
-+ if (!str_to_uint(arg, 10, result)) {
-+ fprintf(stderr, "Invalid %s: %s\n", descr, arg);
-+ return false;
-+ }
-+ return true;
-+}
-+
-+static int
-+test_seqno_compare(size_t a, size_t b, void *values_)
-+{
-+ uint64_t *values = values_;
-+
-+ return values[a] == values[b] ? 0 : (values[a] < values[b] ? -1 : 1);
-+}
-+
-+static void
-+test_seqno_swap(size_t a, size_t b, void *values_)
-+{
-+ uint64_t *values = values_;
-+ uint64_t tmp = values[a];
-+
-+ values[a] = values[b];
-+ values[b] = tmp;
-+}
-+
-+static void
-+test_dump_acked_seqnos(size_t seqno_type)
-+{
-+ struct ofctrl_acked_seqnos * acked_seqnos =
-+ ofctrl_acked_seqnos_get(seqno_type);
-+
-+ printf("ofctrl-seqno-type: %"PRIuSIZE"\n", seqno_type);
-+ printf(" last-acked %"PRIu64"\n", acked_seqnos->last_acked);
-+
-+ size_t n_acked = hmap_count(&acked_seqnos->acked);
-+ uint64_t *acked = xmalloc(n_acked * sizeof *acked);
-+ struct ofctrl_ack_seqno *ack_seqno;
-+ size_t i = 0;
-+
-+ /* A bit hacky but ignoring overflows the "total of all seqno + 1" should
-+ * be a number that is not part of the acked seqnos.
-+ */
-+ uint64_t total_seqno = 1;
-+ HMAP_FOR_EACH (ack_seqno, node, &acked_seqnos->acked) {
-+ ovs_assert(ofctrl_acked_seqnos_contains(acked_seqnos,
-+ ack_seqno->seqno));
-+ total_seqno += ack_seqno->seqno;
-+ acked[i++] = ack_seqno->seqno;
-+ }
-+ ovs_assert(!ofctrl_acked_seqnos_contains(acked_seqnos, total_seqno));
-+
-+ sort(n_acked, test_seqno_compare, test_seqno_swap, acked);
-+
-+ for (i = 0; i < n_acked; i++) {
-+ printf(" %"PRIu64"\n", acked[i]);
-+ }
-+
-+ free(acked);
-+ ofctrl_acked_seqnos_destroy(acked_seqnos);
-+}
-+
-+static void
-+test_ofctrl_seqno_add_type(struct ovs_cmdl_context *ctx)
-+{
-+ unsigned int n_types;
-+
-+ test_init();
-+
-+ if (!test_read_uint_value(ctx, 1, "n_types", &n_types)) {
-+ return;
-+ }
-+ for (unsigned int i = 0; i < n_types; i++) {
-+ printf("%"PRIuSIZE"\n", ofctrl_seqno_add_type());
-+ }
-+}
-+
-+static void
-+test_ofctrl_seqno_ack_seqnos(struct ovs_cmdl_context *ctx)
-+{
-+ unsigned int n_reqs = 0;
-+ unsigned int shift = 2;
-+ unsigned int n_types;
-+ unsigned int n_acks;
-+
-+ test_init();
-+ bool batch_acks = !strcmp(ctx->argv[1], "true");
-+
-+ if (!test_read_uint_value(ctx, shift++, "n_types", &n_types)) {
-+ return;
-+ }
-+
-+ for (unsigned int i = 0; i < n_types; i++) {
-+ ovs_assert(ofctrl_seqno_add_type() == i);
-+
-+ /* Read number of app specific seqnos. */
-+ unsigned int n_app_seqnos;
-+
-+ if (!test_read_uint_value(ctx, shift++, "n_app_seqnos",
-+ &n_app_seqnos)) {
-+ return;
-+ }
-+
-+ for (unsigned int j = 0; j < n_app_seqnos; j++, n_reqs++) {
-+ unsigned int app_seqno;
-+
-+ if (!test_read_uint_value(ctx, shift++, "app_seqno", &app_seqno)) {
-+ return;
-+ }
-+ ofctrl_seqno_update_create(i, app_seqno);
-+ }
-+ }
-+ printf("ofctrl-seqno-req-cfg: %u\n", n_reqs);
-+
-+ if (!test_read_uint_value(ctx, shift++, "n_acks", &n_acks)) {
-+ return;
-+ }
-+ for (unsigned int i = 0; i < n_acks; i++) {
-+ unsigned int ack_seqno;
-+
-+ if (!test_read_uint_value(ctx, shift++, "ack_seqno", &ack_seqno)) {
-+ return;
-+ }
-+ ofctrl_seqno_run(ack_seqno);
-+
-+ if (!batch_acks) {
-+ for (unsigned int st = 0; st < n_types; st++) {
-+ test_dump_acked_seqnos(st);
-+ }
-+ }
-+ }
-+ if (batch_acks) {
-+ for (unsigned int st = 0; st < n_types; st++) {
-+ test_dump_acked_seqnos(st);
-+ }
-+ }
-+}
-+
-+static void
-+test_ofctrl_seqno_main(int argc, char *argv[])
-+{
-+ set_program_name(argv[0]);
-+ static const struct ovs_cmdl_command commands[] = {
-+ {"ofctrl_seqno_add_type", NULL, 1, 1,
-+ test_ofctrl_seqno_add_type, OVS_RO},
-+ {"ofctrl_seqno_ack_seqnos", NULL, 2, INT_MAX,
-+ test_ofctrl_seqno_ack_seqnos, OVS_RO},
-+ {NULL, NULL, 0, 0, NULL, OVS_RO},
-+ };
-+ struct ovs_cmdl_context ctx;
-+ ctx.argc = argc - 1;
-+ ctx.argv = argv + 1;
-+ ovs_cmdl_run_command(&ctx, commands);
-+}
-+
-+OVSTEST_REGISTER("test-ofctrl-seqno", test_ofctrl_seqno_main);
-diff --git a/include/ovn/actions.h b/include/ovn/actions.h
-index 9c1ebf4aa..040213177 100644
---- a/include/ovn/actions.h
-+++ b/include/ovn/actions.h
-@@ -105,6 +105,11 @@ struct ovn_extend_table;
- OVNACT(CHK_LB_HAIRPIN, ovnact_result) \
- OVNACT(CHK_LB_HAIRPIN_REPLY, ovnact_result) \
- OVNACT(CT_SNAT_TO_VIP, ovnact_null) \
-+ OVNACT(BFD_MSG, ovnact_null) \
-+ OVNACT(SCTP_ABORT, ovnact_nest) \
-+ OVNACT(PUT_FDB, ovnact_put_fdb) \
-+ OVNACT(GET_FDB, ovnact_get_fdb) \
-+ OVNACT(LOOKUP_FDB, ovnact_lookup_fdb) \
-
- /* enum ovnact_type, with a member OVNACT_ for each action. */
- enum OVS_PACKED_ENUM ovnact_type {
-@@ -413,6 +418,28 @@ struct ovnact_fwd_group {
- uint8_t ltable; /* Logical table ID of next table. */
- };
-
-+/* OVNACT_PUT_FDB. */
-+struct ovnact_put_fdb {
-+ struct ovnact ovnact;
-+ struct expr_field port; /* Logical port name. */
-+ struct expr_field mac; /* 48-bit Ethernet address. */
-+};
-+
-+/* OVNACT_GET_FDB. */
-+struct ovnact_get_fdb {
-+ struct ovnact ovnact;
-+ struct expr_field mac; /* 48-bit Ethernet address. */
-+ struct expr_field dst; /* 32-bit destination field. */
-+};
-+
-+/* OVNACT_LOOKUP_FDB. */
-+struct ovnact_lookup_fdb {
-+ struct ovnact ovnact;
-+ struct expr_field mac; /* 48-bit Ethernet address. */
-+ struct expr_field port; /* Logical port name. */
-+ struct expr_field dst; /* 1-bit destination field. */
-+};
-+
- /* Internal use by the helpers below. */
- void ovnact_init(struct ovnact *, enum ovnact_type, size_t len);
- void *ovnact_put(struct ofpbuf *, enum ovnact_type, size_t len);
-@@ -627,6 +654,22 @@ enum action_opcode {
- * The actions, in OpenFlow 1.3 format, follow the action_header.
- */
- ACTION_OPCODE_REJECT,
-+
-+ /* handle_bfd_msg { ...actions ...}."
-+ *
-+ * The actions, in OpenFlow 1.3 format, follow the action_header.
-+ */
-+ ACTION_OPCODE_BFD_MSG,
-+
-+ /* "sctp_abort { ...actions... }".
-+ *
-+ * The actions, in OpenFlow 1.3 format, follow the action_header.
-+ */
-+ ACTION_OPCODE_SCTP_ABORT,
-+
-+ /* put_fdb(inport, eth.src).
-+ */
-+ ACTION_OPCODE_PUT_FDB,
- };
-
- /* Header. */
-@@ -748,6 +791,10 @@ struct ovnact_encode_params {
- * 'chk_lb_hairpin_reply' to resubmit. */
- uint8_t ct_snat_vip_ptable; /* OpenFlow table for
- * 'ct_snat_to_vip' to resubmit. */
-+ uint8_t fdb_ptable; /* OpenFlow table for
-+ * 'get_fdb' to resubmit. */
-+ uint8_t fdb_lookup_ptable; /* OpenFlow table for
-+ * 'lookup_fdb' to resubmit. */
- };
-
- void ovnacts_encode(const struct ovnact[], size_t ovnacts_len,
-diff --git a/include/ovn/automake.mk b/include/ovn/automake.mk
-index 54b0e2c0e..582241a57 100644
---- a/include/ovn/automake.mk
-+++ b/include/ovn/automake.mk
-@@ -2,5 +2,6 @@ ovnincludedir = $(includedir)/ovn
- ovninclude_HEADERS = \
- include/ovn/actions.h \
- include/ovn/expr.h \
-+ include/ovn/features.h \
- include/ovn/lex.h \
- include/ovn/logical-fields.h
-diff --git a/include/ovn/expr.h b/include/ovn/expr.h
-index 0a83ec7a8..c2c821818 100644
---- a/include/ovn/expr.h
-+++ b/include/ovn/expr.h
-@@ -477,6 +477,7 @@ uint32_t expr_to_matches(const struct expr *,
- const void *aux,
- struct hmap *matches);
- void expr_matches_destroy(struct hmap *matches);
-+void expr_matches_prepare(struct hmap *matches, uint32_t conj_id_ofs);
- void expr_matches_print(const struct hmap *matches, FILE *);
-
- /* Action parsing helper. */
-diff --git a/include/ovn/features.h b/include/ovn/features.h
-new file mode 100644
-index 000000000..10ee46fcd
---- /dev/null
-+++ b/include/ovn/features.h
-@@ -0,0 +1,22 @@
-+/* Copyright (c) 2021, Red Hat, Inc.
-+ *
-+ * Licensed under the Apache License, Version 2.0 (the "License");
-+ * you may not use this file except in compliance with the License.
-+ * You may obtain a copy of the License at:
-+ *
-+ * http://www.apache.org/licenses/LICENSE-2.0
-+ *
-+ * Unless required by applicable law or agreed to in writing, software
-+ * distributed under the License is distributed on an "AS IS" BASIS,
-+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-+ * See the License for the specific language governing permissions and
-+ * limitations under the License.
-+ */
-+
-+#ifndef OVN_FEATURES_H
-+#define OVN_FEATURES_H 1
-+
-+/* ovn-controller supported feature names. */
-+#define OVN_FEATURE_PORT_UP_NOTIF "port-up-notif"
-+
-+#endif
-diff --git a/include/ovn/logical-fields.h b/include/ovn/logical-fields.h
-index aee474856..017176f98 100644
---- a/include/ovn/logical-fields.h
-+++ b/include/ovn/logical-fields.h
-@@ -44,7 +44,13 @@ enum ovn_controller_event {
- /* Logical registers.
- *
- * Make sure these don't overlap with the logical fields! */
--#define MFF_LOG_REG0 MFF_REG0
-+#define MFF_LOG_REG0 MFF_REG0
-+#define MFF_LOG_LB_ORIG_DIP_IPV4 MFF_REG1
-+#define MFF_LOG_LB_ORIG_TP_DPORT MFF_REG2
-+
-+#define MFF_LOG_XXREG0 MFF_XXREG0
-+#define MFF_LOG_LB_ORIG_DIP_IPV6 MFF_XXREG1
-+
- #define MFF_N_LOG_REGS 10
-
- void ovn_init_symtab(struct shash *symtab);
-@@ -59,6 +65,7 @@ enum mff_log_flags_bits {
- MLF_NESTED_CONTAINER_BIT = 5,
- MLF_LOOKUP_MAC_BIT = 6,
- MLF_LOOKUP_LB_HAIRPIN_BIT = 7,
-+ MLF_LOOKUP_FDB_BIT = 8,
- };
-
- /* MFF_LOG_FLAGS_REG flag assignments */
-@@ -92,6 +99,9 @@ enum mff_log_flags {
- MLF_LOOKUP_MAC = (1 << MLF_LOOKUP_MAC_BIT),
-
- MLF_LOOKUP_LB_HAIRPIN = (1 << MLF_LOOKUP_LB_HAIRPIN_BIT),
-+
-+ /* Indicate that the lookup in the fdb table was successful. */
-+ MLF_LOOKUP_FDB = (1 << MLF_LOOKUP_FDB_BIT),
- };
-
- /* OVN logical fields
-diff --git a/lib/actions.c b/lib/actions.c
-index fbaeb34bc..b3433f49e 100644
---- a/lib/actions.c
-+++ b/lib/actions.c
-@@ -1490,6 +1490,12 @@ parse_TCP_RESET(struct action_context *ctx)
- parse_nested_action(ctx, OVNACT_TCP_RESET, "tcp", ctx->scope);
- }
-
-+static void
-+parse_SCTP_ABORT(struct action_context *ctx)
-+{
-+ parse_nested_action(ctx, OVNACT_SCTP_ABORT, "sctp", ctx->scope);
-+}
-+
- static void
- parse_ND_NA(struct action_context *ctx)
- {
-@@ -1571,6 +1577,12 @@ format_TCP_RESET(const struct ovnact_nest *nest, struct ds *s)
- format_nested_action(nest, "tcp_reset", s);
- }
-
-+static void
-+format_SCTP_ABORT(const struct ovnact_nest *nest, struct ds *s)
-+{
-+ format_nested_action(nest, "sctp_abort", s);
-+}
-+
- static void
- format_ND_NA(const struct ovnact_nest *nest, struct ds *s)
- {
-@@ -1700,6 +1712,14 @@ encode_TCP_RESET(const struct ovnact_nest *on,
- encode_nested_actions(on, ep, ACTION_OPCODE_TCP_RESET, ofpacts);
- }
-
-+static void
-+encode_SCTP_ABORT(const struct ovnact_nest *on,
-+ const struct ovnact_encode_params *ep,
-+ struct ofpbuf *ofpacts)
-+{
-+ encode_nested_actions(on, ep, ACTION_OPCODE_SCTP_ABORT, ofpacts);
-+}
-+
- static void
- encode_REJECT(const struct ovnact_nest *on,
- const struct ovnact_encode_params *ep,
-@@ -2742,6 +2762,31 @@ encode_DHCP6_REPLY(const struct ovnact_null *a OVS_UNUSED,
- encode_controller_op(ACTION_OPCODE_DHCP6_SERVER, ofpacts);
- }
-
-+static void
-+format_BFD_MSG(const struct ovnact_null *a OVS_UNUSED, struct ds *s)
-+{
-+ ds_put_cstr(s, "handle_bfd_msg();");
-+}
-+
-+static void
-+encode_BFD_MSG(const struct ovnact_null *a OVS_UNUSED,
-+ const struct ovnact_encode_params *ep OVS_UNUSED,
-+ struct ofpbuf *ofpacts)
-+{
-+ encode_controller_op(ACTION_OPCODE_BFD_MSG, ofpacts);
-+}
-+
-+static void
-+parse_handle_bfd_msg(struct action_context *ctx OVS_UNUSED)
-+{
-+ if (!lexer_force_match(ctx->lexer, LEX_T_LPAREN)) {
-+ return;
-+ }
-+
-+ ovnact_put_BFD_MSG(ctx->ovnacts);
-+ lexer_force_match(ctx->lexer, LEX_T_RPAREN);
-+}
-+
- static void
- parse_SET_QUEUE(struct action_context *ctx)
- {
-@@ -3698,6 +3743,172 @@ encode_CT_SNAT_TO_VIP(const struct ovnact_null *null OVS_UNUSED,
- emit_resubmit(ofpacts, ep->ct_snat_vip_ptable);
- }
-
-+static void
-+format_PUT_FDB(const struct ovnact_put_fdb *put_fdb, struct ds *s)
-+{
-+ ds_put_cstr(s, "put_fdb(");
-+ expr_field_format(&put_fdb->port, s);
-+ ds_put_cstr(s, ", ");
-+ expr_field_format(&put_fdb->mac, s);
-+ ds_put_cstr(s, ");");
-+}
-+
-+static void
-+encode_PUT_FDB(const struct ovnact_put_fdb *put_fdb,
-+ const struct ovnact_encode_params *ep OVS_UNUSED,
-+ struct ofpbuf *ofpacts)
-+{
-+ const struct arg args[] = {
-+ { expr_resolve_field(&put_fdb->port), MFF_LOG_INPORT },
-+ { expr_resolve_field(&put_fdb->mac), MFF_ETH_SRC }
-+ };
-+ encode_setup_args(args, ARRAY_SIZE(args), ofpacts);
-+ encode_controller_op(ACTION_OPCODE_PUT_FDB, ofpacts);
-+ encode_restore_args(args, ARRAY_SIZE(args), ofpacts);
-+}
-+
-+static void
-+parse_put_fdb(struct action_context *ctx, struct ovnact_put_fdb *put_fdb)
-+{
-+ lexer_force_match(ctx->lexer, LEX_T_LPAREN);
-+ action_parse_field(ctx, 0, false, &put_fdb->port);
-+ lexer_force_match(ctx->lexer, LEX_T_COMMA);
-+ action_parse_field(ctx, 48, false, &put_fdb->mac);
-+ lexer_force_match(ctx->lexer, LEX_T_RPAREN);
-+}
-+
-+static void
-+ovnact_put_fdb_free(struct ovnact_put_fdb *put_fdb OVS_UNUSED)
-+{
-+}
-+
-+static void
-+format_GET_FDB(const struct ovnact_get_fdb *get_fdb, struct ds *s)
-+{
-+ expr_field_format(&get_fdb->dst, s);
-+ ds_put_cstr(s, " = get_fdb(");
-+ expr_field_format(&get_fdb->mac, s);
-+ ds_put_cstr(s, ");");
-+}
-+
-+static void
-+encode_GET_FDB(const struct ovnact_get_fdb *get_fdb,
-+ const struct ovnact_encode_params *ep,
-+ struct ofpbuf *ofpacts)
-+{
-+ struct mf_subfield dst = expr_resolve_field(&get_fdb->dst);
-+ ovs_assert(dst.field);
-+
-+ const struct arg args[] = {
-+ { expr_resolve_field(&get_fdb->mac), MFF_ETH_DST },
-+ };
-+ encode_setup_args(args, ARRAY_SIZE(args), ofpacts);
-+ put_load(0, MFF_LOG_OUTPORT, 0, 32, ofpacts);
-+ emit_resubmit(ofpacts, ep->fdb_ptable);
-+ encode_restore_args(args, ARRAY_SIZE(args), ofpacts);
-+
-+ if (dst.field->id != MFF_LOG_OUTPORT) {
-+ struct ofpact_reg_move *orm = ofpact_put_REG_MOVE(ofpacts);
-+ orm->dst = dst;
-+ orm->src.field = mf_from_id(MFF_LOG_OUTPORT);
-+ orm->src.ofs = 0;
-+ orm->src.n_bits = 32;
-+ }
-+}
-+
-+static void
-+parse_get_fdb(struct action_context *ctx,
-+ struct expr_field *dst,
-+ struct ovnact_get_fdb *get_fdb)
-+{
-+ lexer_get(ctx->lexer); /* Skip get_bfd. */
-+ lexer_get(ctx->lexer); /* Skip '('. */
-+
-+ /* Validate that the destination is a 32-bit, modifiable field if it
-+ is not a string field (i.e 'inport' or 'outport'). */
-+ if (dst->n_bits) {
-+ char *error = expr_type_check(dst, 32, true, ctx->scope);
-+ if (error) {
-+ lexer_error(ctx->lexer, "%s", error);
-+ free(error);
-+ return;
-+ }
-+ }
-+ get_fdb->dst = *dst;
-+
-+ action_parse_field(ctx, 48, false, &get_fdb->mac);
-+ lexer_force_match(ctx->lexer, LEX_T_RPAREN);
-+}
-+
-+static void
-+ovnact_get_fdb_free(struct ovnact_get_fdb *get_fdb OVS_UNUSED)
-+{
-+}
-+
-+static void
-+format_LOOKUP_FDB(const struct ovnact_lookup_fdb *lookup_fdb, struct ds *s)
-+{
-+ expr_field_format(&lookup_fdb->dst, s);
-+ ds_put_cstr(s, " = lookup_fdb(");
-+ expr_field_format(&lookup_fdb->port, s);
-+ ds_put_cstr(s, ", ");
-+ expr_field_format(&lookup_fdb->mac, s);
-+ ds_put_cstr(s, ");");
-+}
-+
-+static void
-+encode_LOOKUP_FDB(const struct ovnact_lookup_fdb *lookup_fdb,
-+ const struct ovnact_encode_params *ep,
-+ struct ofpbuf *ofpacts)
-+{
-+ const struct arg args[] = {
-+ { expr_resolve_field(&lookup_fdb->port), MFF_LOG_INPORT },
-+ { expr_resolve_field(&lookup_fdb->mac), MFF_ETH_SRC },
-+ };
-+ encode_setup_args(args, ARRAY_SIZE(args), ofpacts);
-+
-+ struct mf_subfield dst = expr_resolve_field(&lookup_fdb->dst);
-+ ovs_assert(dst.field);
-+
-+ put_load(0, MFF_LOG_FLAGS, MLF_LOOKUP_FDB_BIT, 1, ofpacts);
-+ emit_resubmit(ofpacts, ep->fdb_lookup_ptable);
-+ encode_restore_args(args, ARRAY_SIZE(args), ofpacts);
-+
-+ struct ofpact_reg_move *orm = ofpact_put_REG_MOVE(ofpacts);
-+ orm->dst = dst;
-+ orm->src.field = mf_from_id(MFF_LOG_FLAGS);
-+ orm->src.ofs = MLF_LOOKUP_FDB_BIT;
-+ orm->src.n_bits = 1;
-+}
-+
-+static void
-+parse_lookup_fdb(struct action_context *ctx,
-+ struct expr_field *dst,
-+ struct ovnact_lookup_fdb *lookup_fdb)
-+{
-+ lexer_get(ctx->lexer); /* Skip lookup_bfd. */
-+ lexer_get(ctx->lexer); /* Skip '('. */
-+
-+ /* Validate that the destination is a 1-bit, modifiable field. */
-+ char *error = expr_type_check(dst, 1, true, ctx->scope);
-+ if (error) {
-+ lexer_error(ctx->lexer, "%s", error);
-+ free(error);
-+ return;
-+ }
-+ lookup_fdb->dst = *dst;
-+
-+ action_parse_field(ctx, 0, false, &lookup_fdb->port);
-+ lexer_force_match(ctx->lexer, LEX_T_COMMA);
-+ action_parse_field(ctx, 48, false, &lookup_fdb->mac);
-+ lexer_force_match(ctx->lexer, LEX_T_RPAREN);
-+}
-+
-+static void
-+ovnact_lookup_fdb_free(struct ovnact_lookup_fdb *get_fdb OVS_UNUSED)
-+{
-+}
-+
- /* Parses an assignment or exchange or put_dhcp_opts action. */
- static void
- parse_set_action(struct action_context *ctx)
-@@ -3758,6 +3969,14 @@ parse_set_action(struct action_context *ctx)
- && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
- parse_chk_lb_hairpin_reply(
- ctx, &lhs, ovnact_put_CHK_LB_HAIRPIN_REPLY(ctx->ovnacts));
-+ } else if (!strcmp(ctx->lexer->token.s, "get_fdb")
-+ && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
-+ parse_get_fdb(
-+ ctx, &lhs, ovnact_put_GET_FDB(ctx->ovnacts));
-+ } else if (!strcmp(ctx->lexer->token.s, "lookup_fdb")
-+ && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
-+ parse_lookup_fdb(
-+ ctx, &lhs, ovnact_put_LOOKUP_FDB(ctx->ovnacts));
- } else {
- parse_assignment_action(ctx, false, &lhs);
- }
-@@ -3812,6 +4031,8 @@ parse_action(struct action_context *ctx)
- ovnact_put_IGMP(ctx->ovnacts);
- } else if (lexer_match_id(ctx->lexer, "tcp_reset")) {
- parse_TCP_RESET(ctx);
-+ } else if (lexer_match_id(ctx->lexer, "sctp_abort")) {
-+ parse_SCTP_ABORT(ctx);
- } else if (lexer_match_id(ctx->lexer, "nd_na")) {
- parse_ND_NA(ctx);
- } else if (lexer_match_id(ctx->lexer, "nd_na_router")) {
-@@ -3842,10 +4063,14 @@ parse_action(struct action_context *ctx)
- parse_fwd_group_action(ctx);
- } else if (lexer_match_id(ctx->lexer, "handle_dhcpv6_reply")) {
- ovnact_put_DHCP6_REPLY(ctx->ovnacts);
-+ } else if (lexer_match_id(ctx->lexer, "handle_bfd_msg")) {
-+ parse_handle_bfd_msg(ctx);
- } else if (lexer_match_id(ctx->lexer, "reject")) {
- parse_REJECT(ctx);
- } else if (lexer_match_id(ctx->lexer, "ct_snat_to_vip")) {
- ovnact_put_CT_SNAT_TO_VIP(ctx->ovnacts);
-+ } else if (lexer_match_id(ctx->lexer, "put_fdb")) {
-+ parse_put_fdb(ctx, ovnact_put_PUT_FDB(ctx->ovnacts));
- } else {
- lexer_syntax_error(ctx->lexer, "expecting action");
- }
-diff --git a/lib/expr.c b/lib/expr.c
-index 4566d9110..796e88ac7 100644
---- a/lib/expr.c
-+++ b/lib/expr.c
-@@ -3125,6 +3125,25 @@ expr_to_matches(const struct expr *expr,
- return n_conjs;
- }
-
-+/* Prepares the expr matches in the hmap 'matches' by updating the
-+ * conj id offsets specified in 'conj_id_ofs'.
-+ */
-+void
-+expr_matches_prepare(struct hmap *matches, uint32_t conj_id_ofs)
-+{
-+ struct expr_match *m;
-+ HMAP_FOR_EACH (m, hmap_node, matches) {
-+ if (m->match.wc.masks.conj_id) {
-+ m->match.flow.conj_id += conj_id_ofs;
-+ }
-+
-+ for (size_t i = 0; i < m->n; i++) {
-+ struct cls_conjunction *src = &m->conjunctions[i];
-+ src->id += conj_id_ofs;
-+ }
-+ }
-+}
-+
- /* Destroys all of the 'struct expr_match'es in 'matches', as well as the
- * 'matches' hmap itself. */
- void
-diff --git a/lib/lb.c b/lib/lb.c
-index a90042e58..f305e9a87 100644
---- a/lib/lb.c
-+++ b/lib/lb.c
-@@ -170,6 +170,24 @@ void ovn_northd_lb_vip_destroy(struct ovn_northd_lb_vip *vip)
- free(vip->backends_nb);
- }
-
-+static void
-+ovn_lb_get_hairpin_snat_ip(const struct uuid *lb_uuid,
-+ const struct smap *lb_options,
-+ struct lport_addresses *hairpin_addrs)
-+{
-+ const char *addresses = smap_get(lb_options, "hairpin_snat_ip");
-+
-+ if (!addresses) {
-+ return;
-+ }
-+
-+ if (!extract_ip_address(addresses, hairpin_addrs)) {
-+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-+ VLOG_WARN_RL(&rl, "bad hairpin_snat_ip %s in load balancer "UUID_FMT,
-+ addresses, UUID_ARGS(lb_uuid));
-+ }
-+}
-+
- struct ovn_northd_lb *
- ovn_northd_lb_create(const struct nbrec_load_balancer *nbrec_lb,
- struct hmap *ports,
-@@ -189,6 +207,8 @@ ovn_northd_lb_create(const struct nbrec_load_balancer *nbrec_lb,
- struct ovn_lb_vip *lb_vip = &lb->vips[n_vips];
- struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[n_vips];
-
-+ lb_vip->empty_backend_rej = smap_get_bool(&nbrec_lb->options,
-+ "reject", false);
- if (!ovn_lb_vip_init(lb_vip, node->key, node->value)) {
- continue;
- }
-@@ -222,6 +242,9 @@ ovn_northd_lb_create(const struct nbrec_load_balancer *nbrec_lb,
- ds_chomp(&sel_fields, ',');
- lb->selection_fields = ds_steal_cstr(&sel_fields);
- }
-+
-+ ovn_lb_get_hairpin_snat_ip(&nbrec_lb->header_.uuid, &nbrec_lb->options,
-+ &lb->hairpin_snat_ips);
- return lb;
- }
-
-@@ -258,6 +281,7 @@ ovn_northd_lb_destroy(struct ovn_northd_lb *lb)
- free(lb->vips);
- free(lb->vips_nb);
- free(lb->selection_fields);
-+ destroy_lport_addresses(&lb->hairpin_snat_ips);
- free(lb->dps);
- free(lb);
- }
-@@ -287,6 +311,12 @@ ovn_controller_lb_create(const struct sbrec_load_balancer *sbrec_lb)
- * correct value.
- */
- lb->n_vips = n_vips;
-+
-+ lb->hairpin_orig_tuple = smap_get_bool(&sbrec_lb->options,
-+ "hairpin_orig_tuple",
-+ false);
-+ ovn_lb_get_hairpin_snat_ip(&sbrec_lb->header_.uuid, &sbrec_lb->options,
-+ &lb->hairpin_snat_ips);
- return lb;
- }
-
-@@ -297,5 +327,6 @@ ovn_controller_lb_destroy(struct ovn_controller_lb *lb)
- ovn_lb_vip_destroy(&lb->vips[i]);
- }
- free(lb->vips);
-+ destroy_lport_addresses(&lb->hairpin_snat_ips);
- free(lb);
- }
-diff --git a/lib/lb.h b/lib/lb.h
-index 6644ad0d8..9a78c72f3 100644
---- a/lib/lb.h
-+++ b/lib/lb.h
-@@ -20,6 +20,7 @@
- #include
- #include
- #include "openvswitch/hmap.h"
-+#include "ovn-util.h"
-
- struct nbrec_load_balancer;
- struct sbrec_load_balancer;
-@@ -37,6 +38,11 @@ struct ovn_northd_lb {
- struct ovn_northd_lb_vip *vips_nb;
- size_t n_vips;
-
-+ struct lport_addresses hairpin_snat_ips; /* IP (v4 and/or v6) to be used
-+ * as source for hairpinned
-+ * traffic.
-+ */
-+
- size_t n_dps;
- size_t n_allocated_dps;
- const struct sbrec_datapath_binding **dps;
-@@ -49,6 +55,7 @@ struct ovn_lb_vip {
-
- struct ovn_lb_backend *backends;
- size_t n_backends;
-+ bool empty_backend_rej;
- };
-
- struct ovn_lb_backend {
-@@ -88,6 +95,14 @@ struct ovn_controller_lb {
-
- struct ovn_lb_vip *vips;
- size_t n_vips;
-+ bool hairpin_orig_tuple; /* True if ovn-northd stores the original
-+ * destination tuple in registers.
-+ */
-+
-+ struct lport_addresses hairpin_snat_ips; /* IP (v4 and/or v6) to be used
-+ * as source for hairpinned
-+ * traffic.
-+ */
- };
-
- struct ovn_controller_lb *ovn_controller_lb_create(
-diff --git a/lib/ovn-l7.h b/lib/ovn-l7.h
-index c84a0e7a9..d00982449 100644
---- a/lib/ovn-l7.h
-+++ b/lib/ovn-l7.h
-@@ -26,6 +26,25 @@
- #include "hash.h"
- #include "ovn/logical-fields.h"
-
-+#define BFD_PACKET_LEN 24
-+#define BFD_DEST_PORT 3784
-+#define BFD_VERSION 1
-+#define BFD_DEFAULT_SRC_IP 0xA9FE0101 /* 169.254.1.1 */
-+#define BFD_DEFAULT_DST_IP 0xA9FE0100 /* 169.254.1.0 */
-+
-+struct bfd_msg {
-+ uint8_t vers_diag;
-+ uint8_t flags;
-+ uint8_t mult;
-+ uint8_t length;
-+ ovs_be32 my_disc;
-+ ovs_be32 your_disc;
-+ ovs_be32 min_tx;
-+ ovs_be32 min_rx;
-+ ovs_be32 min_rx_echo;
-+};
-+BUILD_ASSERT_DECL(BFD_PACKET_LEN == sizeof(struct bfd_msg));
-+
- /* Generic options map which is used to store dhcpv4 opts and dhcpv6 opts. */
- struct gen_opts_map {
- struct hmap_node hmap_node;
-diff --git a/lib/ovn-util.c b/lib/ovn-util.c
-index 2136f90fe..8f6719471 100644
---- a/lib/ovn-util.c
-+++ b/lib/ovn-util.c
-@@ -232,6 +232,27 @@ extract_ip_addresses(const char *address, struct lport_addresses *laddrs)
- return false;
- }
-
-+/* Extracts at most one IPv4 and at most one IPv6 address from 'address'
-+ * which should be of the format 'IP1 [IP2]'.
-+ *
-+ * Return true if at most one IPv4 address and at most one IPv6 address
-+ * is found in 'address'. IPs must be host IPs, i.e., no unmasked bits.
-+ *
-+ * The caller must call destroy_lport_addresses().
-+ */
-+bool extract_ip_address(const char *address, struct lport_addresses *laddrs)
-+{
-+ if (!extract_ip_addresses(address, laddrs) ||
-+ laddrs->n_ipv4_addrs > 1 ||
-+ laddrs->n_ipv6_addrs > 1 ||
-+ (laddrs->n_ipv4_addrs && laddrs->ipv4_addrs[0].plen != 32) ||
-+ (laddrs->n_ipv6_addrs && laddrs->ipv6_addrs[0].plen != 128)) {
-+ destroy_lport_addresses(laddrs);
-+ return false;
-+ }
-+ return true;
-+}
-+
- /* Extracts the mac, IPv4 and IPv6 addresses from the
- * "nbrec_logical_router_port" parameter 'lrp'. Stores the IPv4 and
- * IPv6 addresses in the 'ipv4_addrs' and 'ipv6_addrs' fields of
-@@ -559,18 +580,30 @@ ovn_destroy_tnlids(struct hmap *tnlids)
- hmap_destroy(tnlids);
- }
-
-+/* Returns true if 'tnlid' is present in the hmap 'tnlids'. */
- bool
--ovn_add_tnlid(struct hmap *set, uint32_t tnlid)
-+ovn_tnlid_present(struct hmap *tnlids, uint32_t tnlid)
- {
- uint32_t hash = hash_int(tnlid, 0);
- struct tnlid_node *node;
-- HMAP_FOR_EACH_IN_BUCKET (node, hmap_node, hash, set) {
-+ HMAP_FOR_EACH_IN_BUCKET (node, hmap_node, hash, tnlids) {
- if (node->tnlid == tnlid) {
-- return false;
-+ return true;
- }
- }
-
-- node = xmalloc(sizeof *node);
-+ return false;
-+}
-+
-+bool
-+ovn_add_tnlid(struct hmap *set, uint32_t tnlid)
-+{
-+ if (ovn_tnlid_present(set, tnlid)) {
-+ return false;
-+ }
-+
-+ uint32_t hash = hash_int(tnlid, 0);
-+ struct tnlid_node *node = xmalloc(sizeof *node);
- hmap_insert(set, &node->hmap_node, hash);
- node->tnlid = tnlid;
- return true;
-diff --git a/lib/ovn-util.h b/lib/ovn-util.h
-index 679f47a97..40ecafe57 100644
---- a/lib/ovn-util.h
-+++ b/lib/ovn-util.h
-@@ -72,6 +72,7 @@ bool extract_addresses(const char *address, struct lport_addresses *,
- int *ofs);
- bool extract_lsp_addresses(const char *address, struct lport_addresses *);
- bool extract_ip_addresses(const char *address, struct lport_addresses *);
-+bool extract_ip_address(const char *address, struct lport_addresses *);
- bool extract_lrp_networks(const struct nbrec_logical_router_port *,
- struct lport_addresses *);
- bool extract_sbrec_binding_first_mac(const struct sbrec_port_binding *binding,
-@@ -125,6 +126,7 @@ void ovn_conn_show(struct unixctl_conn *conn, int argc OVS_UNUSED,
- struct hmap;
- void ovn_destroy_tnlids(struct hmap *tnlids);
- bool ovn_add_tnlid(struct hmap *set, uint32_t tnlid);
-+bool ovn_tnlid_present(struct hmap *tnlids, uint32_t tnlid);
- uint32_t ovn_allocate_tnlid(struct hmap *set, const char *name, uint32_t min,
- uint32_t max, uint32_t *hint);
-
-@@ -227,4 +229,40 @@ bool ip_address_and_port_from_lb_key(const char *key, char **ip_address,
- * value. */
- char *ovn_get_internal_version(void);
-
-+
-+/* OVN Packet definitions. These may eventually find a home in OVS's
-+ * packets.h file. For the time being, they live here because OVN uses them
-+ * and OVS does not.
-+ */
-+#define SCTP_CHUNK_HEADER_LEN 4
-+struct sctp_chunk_header {
-+ uint8_t sctp_chunk_type;
-+ uint8_t sctp_chunk_flags;
-+ ovs_be16 sctp_chunk_len;
-+};
-+BUILD_ASSERT_DECL(SCTP_CHUNK_HEADER_LEN == sizeof(struct sctp_chunk_header));
-+
-+#define SCTP_INIT_CHUNK_LEN 16
-+struct sctp_init_chunk {
-+ ovs_be32 initiate_tag;
-+ ovs_be32 a_rwnd;
-+ ovs_be16 num_outbound_streams;
-+ ovs_be16 num_inbound_streams;
-+ ovs_be32 initial_tsn;
-+};
-+BUILD_ASSERT_DECL(SCTP_INIT_CHUNK_LEN == sizeof(struct sctp_init_chunk));
-+
-+/* These are the only SCTP chunk types that OVN cares about.
-+ * There is no need to define the other chunk types until they are
-+ * needed.
-+ */
-+#define SCTP_CHUNK_TYPE_INIT 1
-+#define SCTP_CHUNK_TYPE_ABORT 6
-+
-+/* See RFC 4960 Sections 3.3.7 and 8.5.1 for information on this flag. */
-+#define SCTP_ABORT_CHUNK_FLAG_T (1 << 0)
-+
-+/* The number of tables for the ingress and egress pipelines. */
-+#define LOG_PIPELINE_LEN 29
-+
- #endif
-diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
-index a9a3a9f4f..55b1c9655 100644
---- a/northd/ovn-northd.8.xml
-+++ b/northd/ovn-northd.8.xml
-@@ -307,7 +307,73 @@
-
-
-
-- Ingress Table 3: from-lport
Pre-ACLs
-+ Ingress Table 3: Lookup MAC address learning table
-+
-+
-+ This table looks up the MAC learning table of the logical switch
-+ datapath to check if the port-mac
pair is present
-+ or not. MAC is learnt only for logical switch VIF ports whose
-+ port security is disabled and 'unknown' address set.
-+
-+
-+
-+
-+ Ingress Table 4: Learn MAC of 'unknown' ports.
-+
-+
-+ This table learns the MAC addresses seen on the logical ports
-+ whose port security is disabled and 'unknown' address set
-+ if the lookup_fdb
action returned false in the
-+ previous table.
-+
-+
-+
-+
-+ Ingress Table 5: from-lport
Pre-ACLs
-
-
- This table prepares flows for possible stateful ACL processing in
-@@ -332,7 +398,7 @@
- db="OVN_Northbound"/> table.
-
-
-- Ingress Table 4: Pre-LB
-+ Ingress Table 6: Pre-LB
-
-
- This table prepares flows for possible stateful load balancing processing
-@@ -399,7 +465,7 @@
- logical router datapath to logical switch datapath.
-
-
-- Ingress Table 5: Pre-stateful
-+ Ingress Table 7: Pre-stateful
-
-
- This table prepares flows for all possible stateful processing
-@@ -410,12 +476,13 @@
- ct_next;
action.
-
-
-- Ingress Table 6: from-lport
ACL hints
-+ Ingress Table 8: from-lport
ACL hints
-
-
- This table consists of logical flows that set hints
- (reg0
bits) to be used in the next stage, in the ACL
-- processing table. Multiple hints can be set for the same packet.
-+ processing table, if stateful ACLs or load balancers are configured.
-+ Multiple hints can be set for the same packet.
- The possible hints are:
-
-
-
-- Ingress table 7: from-lport
ACLs
-+ Ingress table 9: from-lport
ACLs
-
-
- Logical flows in this table closely reproduce those in the
-@@ -518,8 +585,9 @@
- flows with the
- tcp_reset { output <-> inport;
- next(pipeline=egress,table=5);}
-- action for TCP connections and icmp4/icmp6
action
-- for UDP connections.
-+ action for TCP connections,icmp4/icmp6
action
-+ for UDP connections, and sctp_abort {output <-%gt; inport;
-+ next(pipeline=egress,table=5);}
action for SCTP associations.
-
-
- Other ACLs translate to drop;
for new or untracked
-@@ -597,7 +665,7 @@
-
-
-
-- Ingress Table 8: from-lport
QoS Marking
-+ Ingress Table 10: from-lport
QoS Marking
-
-
- Logical flows in this table closely reproduce those in the
-@@ -619,7 +687,7 @@
-
-
-
--
Ingress Table 9: from-lport
QoS Meter
-+ Ingress Table 11: from-lport
QoS Meter
-
-
- Logical flows in this table closely reproduce those in the
-@@ -641,7 +709,7 @@
-
-
-
--
Ingress Table 10: LB
-+ Ingress Table 12: LB
-
-
- It contains a priority-0 flow that simply moves traffic to the next
-@@ -667,7 +735,7 @@
- connection.)
-
-
-- Ingress Table 11: Stateful
-+ Ingress Table 13: Stateful
-
-
- -
-@@ -687,7 +755,11 @@
- of VIP. If health check is enabled, then args
- will only contain those endpoints whose service monitor status entry
- in
OVN_Southbound
db is either online
or
-- empty.
-+ empty. For IPv4 traffic the flow also loads the original destination
-+ IP and transport port in registers reg1
and
-+ reg2
. For IPv6 traffic the flow also loads the original
-+ destination IP and transport port in registers xxreg1
and
-+ reg2
.
-
- -
- For all the configured load balancing rules for a switch in
-@@ -699,40 +771,54 @@
- VIP. The action on this flow is
- ct_lb(args)
, where args contains comma
- separated IP addresses of the same address family as VIP.
-+ For IPv4 traffic the flow also loads the original destination
-+ IP and transport port in registers reg1
and
-+ reg2
. For IPv6 traffic the flow also loads the original
-+ destination IP and transport port in registers xxreg1
and
-+ reg2
.
-+
-+
-+ -
-+ If the load balancer is created with
--reject
option and
-+ it has no active backends, a TCP reset segment (for tcp) or an ICMP
-+ port unreachable packet (for all other kind of traffic) will be sent
-+ whenever an incoming packet is received for this load-balancer.
-+ Please note using --reject
option will disable
-+ empty_lb SB controller event for this load balancer.
-
-+
- -
- A priority-100 flow commits packets to connection tracker using
-
ct_commit; next;
action based on a hint provided by
- the previous tables (with a match for reg0[1] == 1
).
-
- -
-- A priority-100 flow sends the packets to connection tracker using
-+ Priority-100 flows that send the packets to connection tracker using
-
ct_lb;
as the action based on a hint provided by the
-- previous tables (with a match for reg0[2] == 1
).
-+ previous tables (with a match for reg0[2] == 1
and
-+ on supported load balancer protocols and address families).
-+ For IPv4 traffic the flows also load the original destination
-+ IP and transport port in registers reg1
and
-+ reg2
. For IPv6 traffic the flows also load the original
-+ destination IP and transport port in registers xxreg1
and
-+ reg2
.
-
- -
- A priority-0 flow that simply moves traffic to the next table.
-
-
-
-- Ingress Table 12: Pre-Hairpin
-+ Ingress Table 14: Pre-Hairpin
-
- -
- If the logical switch has load balancer(s) configured, then a
-- priorirty-100 flow is added with the match
--
ip && ct.trk&& ct.dnat
to check if the
-+ priority-100 flow is added with the match
-+ ip && ct.trk
to check if the
- packet needs to be hairpinned (if after load balancing the destination
-- IP matches the source IP) or not by executing the action
-- reg0[6] = chk_lb_hairpin();
and advances the packet to
-- the next table.
--
--
-- -
-- If the logical switch has load balancer(s) configured, then a
-- priorirty-90 flow is added with the match
ip
to check if
-- the packet is a reply for a hairpinned connection or not by executing
-- the action reg0[6] = chk_lb_hairpin_reply();
and advances
-- the packet to the next table.
-+ IP matches the source IP) or not by executing the actions
-+ reg0[6] = chk_lb_hairpin();
and
-+ reg0[12] = chk_lb_hairpin_reply();
and advances the packet
-+ to the next table.
-
-
- -
-@@ -740,21 +826,30 @@
-
-
-
-- Ingress Table 13: Nat-Hairpin
-+ Ingress Table 15: Nat-Hairpin
-
- -
- If the logical switch has load balancer(s) configured, then a
-- priorirty-100 flow is added with the match
--
ip && (ct.new || ct.est) && ct.trk &&
-- ct.dnat && reg0[6] == 1
which hairpins the traffic by
-+ priority-100 flow is added with the match
-+ ip && ct.new && ct.trk &&
-+ reg0[6] == 1
which hairpins the traffic by
- NATting source IP to the load balancer VIP by executing the action
- ct_snat_to_vip
and advances the packet to the next table.
-
-
- -
- If the logical switch has load balancer(s) configured, then a
-- priorirty-90 flow is added with the match
--
ip && reg0[6] == 1
which matches on the replies
-+ priority-100 flow is added with the match
-+ ip && ct.est && ct.trk &&
-+ reg0[6] == 1
which hairpins the traffic by
-+ NATting source IP to the load balancer VIP by executing the action
-+ ct_snat
and advances the packet to the next table.
-+
-+
-+ -
-+ If the logical switch has load balancer(s) configured, then a
-+ priority-90 flow is added with the match
-+
ip && reg0[12] == 1
which matches on the replies
- of hairpinned traffic (i.e., destination IP is VIP,
- source IP is the backend IP and source L4 port is backend port for L4
- load balancers) and executes ct_snat
and advances the
-@@ -766,7 +861,7 @@
-
-
-
-- Ingress Table 14: Hairpin
-+ Ingress Table 16: Hairpin
-
- -
- A priority-1 flow that hairpins traffic matched by non-default
-@@ -779,7 +874,7 @@
-
-
-
-- Ingress Table 15: ARP/ND responder
-+ Ingress Table 17: ARP/ND responder
-
-
- This table implements ARP/ND responder in a logical switch for known
-@@ -1069,7 +1164,7 @@ output;
-
-
-
--
Ingress Table 16: DHCP option processing
-+ Ingress Table 18: DHCP option processing
-
-
- This table adds the DHCPv4 options to a DHCPv4 packet from the
-@@ -1130,7 +1225,7 @@ next;
-
-
-
--
Ingress Table 17: DHCP responses
-+ Ingress Table 19: DHCP responses
-
-
- This table implements DHCP responder for the DHCP replies generated by
-@@ -1211,7 +1306,7 @@ output;
-
-
-
--
Ingress Table 18 DNS Lookup
-+ Ingress Table 20 DNS Lookup
-
-
- This table looks up and resolves the DNS names to the corresponding
-@@ -1240,7 +1335,7 @@ reg0[4] = dns_lookup(); next;
-
-
-
--
Ingress Table 19 DNS Responses
-+ Ingress Table 21 DNS Responses
-
-
- This table implements DNS responder for the DNS replies generated by
-@@ -1275,7 +1370,7 @@ output;
-
-
-
--
Ingress table 20 External ports
-+ Ingress table 22 External ports
-
-
- Traffic from the external
logical ports enter the ingress
-@@ -1318,7 +1413,7 @@ output;
-
-
-
--
Ingress Table 21 Destination Lookup
-+ Ingress Table 23 Destination Lookup
-
-
- This table implements switching behavior. It contains these logical
-@@ -1481,12 +1576,58 @@ output;
-
-
-
-- One priority-0 fallback flow that matches all packets and outputs them
-- to the MC_UNKNOWN
multicast group, which
-- ovn-northd
populates with all enabled logical ports that
-- accept unknown destination packets. As a small optimization, if no
-- logical ports accept unknown destination packets,
-- ovn-northd
omits this multicast group and logical flow.
-+ One priority-0 fallback flow that matches all packets with the
-+ action outport = get_fdb(eth.dst); next;
. The action
-+ get_fdb
gets the port for the eth.dst
-+ in the MAC learning table of the logical switch datapath. If there
-+ is no entry for eth.dst
in the MAC learning table,
-+ then it stores none
in the outport
.
-+
-+
-+
-+ Ingress Table 23 Destination unknown
-+
-+
-+ This table handles the packets whose destination was not found or
-+ and looked up in the MAC learning table of the logical switch
-+ datapath. It contains the following flows.
-+
-+
-+
-+ -
-+
-+ If the logical switch has logical ports with 'unknown' addresses set,
-+ then the below logical flow is added
-+
-+
-+
-+ -
-+ Priority 50 flow with the match
outport == none
then
-+ outputs them to the MC_UNKNOWN
multicast group, which
-+ ovn-northd
populates with all enabled logical ports
-+ that accept unknown destination packets. As a small optimization,
-+ if no logical ports accept unknown destination packets,
-+ ovn-northd
omits this multicast group and logical
-+ flow.
-+
-+
-+
-+
-+ If the logical switch has no logical ports with 'unknown' address
-+ set, then the below logical flow is added
-+
-+
-+
-+ -
-+ Priority 50 flow with the match
outport == none
-+ and drops the packets.
-+
-+
-+
-+
-+ -
-+ One priority-0 fallback flow that outputs the packet to the egress
-+ stage with the outport learnt from
get_fdb
action.
-
-
-
-@@ -1926,6 +2067,27 @@ next;
-
-
-
-+
-+
-+ For each BFD port the two following priority-110 flows are added
-+ to manage BFD traffic:
-+
-+
-+ -
-+ if
ip4.src
or ip6.src
is any IP
-+ address owned by the router port and udp.dst == 3784
-+
, the packet is advanced to the next pipeline stage.
-+
-+
-+ -
-+ if
ip4.dst
or ip6.dst
is any IP
-+ address owned by the router port and udp.dst == 3784
-+
, the handle_bfd_msg
action is executed.
-+
-+
-+
-+
-+
-
-
- L3 admission control: A priority-100 flow drops packets that match
-@@ -2449,6 +2611,16 @@ icmp6 {
- with an action ct_snat;
.
-
-
-+
-+ If the Gateway router is configured with
-+ lb_force_snat_ip=router_ip
then for every logical router
-+ port P attached to the Gateway router with the router ip
-+ B, a priority-110 flow is added with the match
-+ inport == P && ip4.dst == B
or
-+ inport == P && ip6.dst == B
-+ with an action ct_snat;
.
-+
-+
-
- If the Gateway router has been configured to force SNAT any
- previously load-balanced packets to B, a priority-100 flow
-@@ -2592,6 +2764,15 @@ icmp6 {
- packets, the above action will be replaced by
- flags.force_snat_for_lb = 1; ct_dnat;
.
-
-+
-+
-+ If the load balancer is created with --reject
option and
-+ it has no active backends, a TCP reset segment (for tcp) or an ICMP
-+ port unreachable packet (for all other kind of traffic) will be sent
-+ whenever an incoming packet is received for this load-balancer.
-+ Please note using --reject
option will disable
-+ empty_lb SB controller event for this load balancer.
-+
-
-
- Ingress Table 6: DNAT on Gateway Routers
-@@ -3022,14 +3203,36 @@ outport = P;
-
-
-
-- If the policy action is reroute
, then the logical
-- flow is added with the following actions:
-+ If the policy action is reroute
with 2 or more nexthops
-+ defined, then the logical flow is added with the following actions:
-+
-+
-+
-+reg8[0..15] = GID;
-+reg8[16..31] = select(1,..n);
-+
-+
-+
-+ where GID is the ECMP group id generated by
-+ ovn-northd
for this policy and n
-+ is the number of nexthops. select
action
-+ selects one of the nexthop member id, stores it in the register
-+ reg8[16..31]
and advances the packet to the
-+ next stage.
-+
-+
-+
-+
-+
-+ If the policy action is reroute
with just one nexhop,
-+ then the logical flow is added with the following actions:
-
-
-
- [xx]reg0 = H;
- eth.src = E;
- outport = P;
-+reg8[0..15] = 0;
- flags.loopback = 1;
- next;
-
-@@ -3053,7 +3256,51 @@ next;
-
-
-
-- Ingress Table 13: ARP/ND Resolution
-+ Ingress Table 13: ECMP handling for router policies
-+
-+ This table handles the ECMP for the router policies configured
-+ with multiple nexthops.
-+
-+
-+
-+ -
-+
-+ A priority-150 flow is added to advance the packet to the next stage
-+ if the ECMP group id register reg8[0..15]
is 0.
-+
-+
-+
-+ -
-+
-+ For each ECMP reroute router policy with multiple nexthops,
-+ a priority-100 flow is added for each nexthop H
-+ with the match reg8[0..15] == GID &&
-+ reg8[16..31] == M
where GID
-+ is the router policy group id generated by ovn-northd
-+ and M is the member id of the nexthop H
-+ generated by ovn-northd
. The following actions are added
-+ to the flow:
-+
-+
-+
-+[xx]reg0 = H;
-+eth.src = E;
-+outport = P
-+"flags.loopback = 1; "
-+"next;"
-+
-+
-+
-+ where H is the nexthop
defined in the
-+ router policy, E is the ethernet address of the
-+ logical router port from which the nexthop
is
-+ reachable and P is the logical router port from
-+ which the nexthop
is reachable.
-+
-+
-+
-+
-+ Ingress Table 14: ARP/ND Resolution
-
-
- Any packet that reaches this table is an IP packet whose next-hop
-@@ -3239,7 +3486,7 @@ next;
-
-
-
--
Ingress Table 14: Check packet length
-+ Ingress Table 15: Check packet length
-
-
- For distributed logical routers with distributed gateway port configured
-@@ -3269,7 +3516,7 @@ REGBIT_PKT_LARGER = check_pkt_larger(L); next;
- and advances to the next table.
-
-
-- Ingress Table 15: Handle larger packets
-+ Ingress Table 16: Handle larger packets
-
-
- For distributed logical routers with distributed gateway port configured
-@@ -3330,7 +3577,7 @@ icmp6 {
- and advances to the next table.
-
-
-- Ingress Table 16: Gateway Redirect
-+ Ingress Table 17: Gateway Redirect
-
-
- For distributed logical routers where one of the logical router
-@@ -3370,7 +3617,7 @@ icmp6 {
-
-
-
--
Ingress Table 17: ARP Request
-+ Ingress Table 18: ARP Request
-
-
- In the common case where the Ethernet destination has been resolved, this
-@@ -3546,6 +3793,32 @@ nd_ns {
- flags.force_snat_for_dnat == 1 && ip
with an
- action ct_snat(B);
.
-
-+
-+
-+
-+
-+ If the Gateway router in the OVN Northbound database has been
-+ configured to force SNAT a packet (that has been previously
-+ load-balanced) using router IP (i.e :lb_force_snat_ip=router_ip), then for
-+ each logical router port P attached to the Gateway
-+ router, a priority-110 flow matches
-+ flags.force_snat_for_lb == 1 && outport == P
-+
with an action ct_snat(R);
-+ where R is the IP configured on the router port.
-+ If R
is an IPv4 address then the match will also
-+ include ip4
and if it is an IPv6 address, then the
-+ match will also include ip6
.
-+
-+
-+
-+ If the logical router port P is configured with multiple
-+ IPv4 and multiple IPv6 addresses, only the first IPv4 and first IPv6
-+ address is considered.
-+
-+
-+
-+
-
- If the Gateway router in the OVN Northbound database has been
- configured to force SNAT a packet (that has been previously
-@@ -3553,6 +3826,9 @@ nd_ns {
- flags.force_snat_for_lb == 1 && ip
with an
- action ct_snat(B);
.
-
-+
-+
-+
-
- For each configuration in the OVN Northbound database, that asks
- to change the source IP address of a packet from an IP address of
-@@ -3566,14 +3842,18 @@ nd_ns {
- options, then the action would be ip4/6.src=
- (B)
.
-
-+
-
-+
-
- If the NAT rule has allowed_ext_ips
configured, then
- there is an additional match ip4.dst == allowed_ext_ips
-
. Similarly, for IPV6, match would be ip6.dst ==
- allowed_ext_ips
.
-
-+
-
-+
-
- If the NAT rule has exempted_ext_ips
set, then
- there is an additional flow configured at the priority + 1 of
-@@ -3582,7 +3862,9 @@ nd_ns {
- . This flow is used to bypass the ct_snat action for a packet
- which is destinted to exempted_ext_ips
.
-
-+
-
-+
-
- A priority-0 logical flow with match 1
has actions
- next;
.
-diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
-index 5a3227568..c81e3220c 100644
---- a/northd/ovn-northd.c
-+++ b/northd/ovn-northd.c
-@@ -38,6 +38,7 @@
- #include "lib/ovn-util.h"
- #include "lib/lb.h"
- #include "ovn/actions.h"
-+#include "ovn/features.h"
- #include "ovn/logical-fields.h"
- #include "packets.h"
- #include "openvswitch/poll-loop.h"
-@@ -141,25 +142,28 @@ enum ovn_stage {
- PIPELINE_STAGE(SWITCH, IN, PORT_SEC_L2, 0, "ls_in_port_sec_l2") \
- PIPELINE_STAGE(SWITCH, IN, PORT_SEC_IP, 1, "ls_in_port_sec_ip") \
- PIPELINE_STAGE(SWITCH, IN, PORT_SEC_ND, 2, "ls_in_port_sec_nd") \
-- PIPELINE_STAGE(SWITCH, IN, PRE_ACL, 3, "ls_in_pre_acl") \
-- PIPELINE_STAGE(SWITCH, IN, PRE_LB, 4, "ls_in_pre_lb") \
-- PIPELINE_STAGE(SWITCH, IN, PRE_STATEFUL, 5, "ls_in_pre_stateful") \
-- PIPELINE_STAGE(SWITCH, IN, ACL_HINT, 6, "ls_in_acl_hint") \
-- PIPELINE_STAGE(SWITCH, IN, ACL, 7, "ls_in_acl") \
-- PIPELINE_STAGE(SWITCH, IN, QOS_MARK, 8, "ls_in_qos_mark") \
-- PIPELINE_STAGE(SWITCH, IN, QOS_METER, 9, "ls_in_qos_meter") \
-- PIPELINE_STAGE(SWITCH, IN, LB, 10, "ls_in_lb") \
-- PIPELINE_STAGE(SWITCH, IN, STATEFUL, 11, "ls_in_stateful") \
-- PIPELINE_STAGE(SWITCH, IN, PRE_HAIRPIN, 12, "ls_in_pre_hairpin") \
-- PIPELINE_STAGE(SWITCH, IN, NAT_HAIRPIN, 13, "ls_in_nat_hairpin") \
-- PIPELINE_STAGE(SWITCH, IN, HAIRPIN, 14, "ls_in_hairpin") \
-- PIPELINE_STAGE(SWITCH, IN, ARP_ND_RSP, 15, "ls_in_arp_rsp") \
-- PIPELINE_STAGE(SWITCH, IN, DHCP_OPTIONS, 16, "ls_in_dhcp_options") \
-- PIPELINE_STAGE(SWITCH, IN, DHCP_RESPONSE, 17, "ls_in_dhcp_response") \
-- PIPELINE_STAGE(SWITCH, IN, DNS_LOOKUP, 18, "ls_in_dns_lookup") \
-- PIPELINE_STAGE(SWITCH, IN, DNS_RESPONSE, 19, "ls_in_dns_response") \
-- PIPELINE_STAGE(SWITCH, IN, EXTERNAL_PORT, 20, "ls_in_external_port") \
-- PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 21, "ls_in_l2_lkup") \
-+ PIPELINE_STAGE(SWITCH, IN, LOOKUP_FDB , 3, "ls_in_lookup_fdb") \
-+ PIPELINE_STAGE(SWITCH, IN, PUT_FDB, 4, "ls_in_put_fdb") \
-+ PIPELINE_STAGE(SWITCH, IN, PRE_ACL, 5, "ls_in_pre_acl") \
-+ PIPELINE_STAGE(SWITCH, IN, PRE_LB, 6, "ls_in_pre_lb") \
-+ PIPELINE_STAGE(SWITCH, IN, PRE_STATEFUL, 7, "ls_in_pre_stateful") \
-+ PIPELINE_STAGE(SWITCH, IN, ACL_HINT, 8, "ls_in_acl_hint") \
-+ PIPELINE_STAGE(SWITCH, IN, ACL, 9, "ls_in_acl") \
-+ PIPELINE_STAGE(SWITCH, IN, QOS_MARK, 10, "ls_in_qos_mark") \
-+ PIPELINE_STAGE(SWITCH, IN, QOS_METER, 11, "ls_in_qos_meter") \
-+ PIPELINE_STAGE(SWITCH, IN, LB, 12, "ls_in_lb") \
-+ PIPELINE_STAGE(SWITCH, IN, STATEFUL, 13, "ls_in_stateful") \
-+ PIPELINE_STAGE(SWITCH, IN, PRE_HAIRPIN, 14, "ls_in_pre_hairpin") \
-+ PIPELINE_STAGE(SWITCH, IN, NAT_HAIRPIN, 15, "ls_in_nat_hairpin") \
-+ PIPELINE_STAGE(SWITCH, IN, HAIRPIN, 16, "ls_in_hairpin") \
-+ PIPELINE_STAGE(SWITCH, IN, ARP_ND_RSP, 17, "ls_in_arp_rsp") \
-+ PIPELINE_STAGE(SWITCH, IN, DHCP_OPTIONS, 18, "ls_in_dhcp_options") \
-+ PIPELINE_STAGE(SWITCH, IN, DHCP_RESPONSE, 19, "ls_in_dhcp_response") \
-+ PIPELINE_STAGE(SWITCH, IN, DNS_LOOKUP, 20, "ls_in_dns_lookup") \
-+ PIPELINE_STAGE(SWITCH, IN, DNS_RESPONSE, 21, "ls_in_dns_response") \
-+ PIPELINE_STAGE(SWITCH, IN, EXTERNAL_PORT, 22, "ls_in_external_port") \
-+ PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 23, "ls_in_l2_lkup") \
-+ PIPELINE_STAGE(SWITCH, IN, L2_UNKNOWN, 24, "ls_in_l2_unknown") \
- \
- /* Logical switch egress stages. */ \
- PIPELINE_STAGE(SWITCH, OUT, PRE_LB, 0, "ls_out_pre_lb") \
-@@ -188,11 +192,12 @@ enum ovn_stage {
- PIPELINE_STAGE(ROUTER, IN, IP_ROUTING, 10, "lr_in_ip_routing") \
- PIPELINE_STAGE(ROUTER, IN, IP_ROUTING_ECMP, 11, "lr_in_ip_routing_ecmp") \
- PIPELINE_STAGE(ROUTER, IN, POLICY, 12, "lr_in_policy") \
-- PIPELINE_STAGE(ROUTER, IN, ARP_RESOLVE, 13, "lr_in_arp_resolve") \
-- PIPELINE_STAGE(ROUTER, IN, CHK_PKT_LEN , 14, "lr_in_chk_pkt_len") \
-- PIPELINE_STAGE(ROUTER, IN, LARGER_PKTS, 15,"lr_in_larger_pkts") \
-- PIPELINE_STAGE(ROUTER, IN, GW_REDIRECT, 16, "lr_in_gw_redirect") \
-- PIPELINE_STAGE(ROUTER, IN, ARP_REQUEST, 17, "lr_in_arp_request") \
-+ PIPELINE_STAGE(ROUTER, IN, POLICY_ECMP, 13, "lr_in_policy_ecmp") \
-+ PIPELINE_STAGE(ROUTER, IN, ARP_RESOLVE, 14, "lr_in_arp_resolve") \
-+ PIPELINE_STAGE(ROUTER, IN, CHK_PKT_LEN , 15, "lr_in_chk_pkt_len") \
-+ PIPELINE_STAGE(ROUTER, IN, LARGER_PKTS, 16, "lr_in_larger_pkts") \
-+ PIPELINE_STAGE(ROUTER, IN, GW_REDIRECT, 17, "lr_in_gw_redirect") \
-+ PIPELINE_STAGE(ROUTER, IN, ARP_REQUEST, 18, "lr_in_arp_request") \
- \
- /* Logical router egress stages. */ \
- PIPELINE_STAGE(ROUTER, OUT, UNDNAT, 0, "lr_out_undnat") \
-@@ -225,6 +230,12 @@ enum ovn_stage {
- #define REGBIT_ACL_HINT_ALLOW "reg0[8]"
- #define REGBIT_ACL_HINT_DROP "reg0[9]"
- #define REGBIT_ACL_HINT_BLOCK "reg0[10]"
-+#define REGBIT_LKUP_FDB "reg0[11]"
-+#define REGBIT_HAIRPIN_REPLY "reg0[12]"
-+
-+#define REG_ORIG_DIP_IPV4 "reg1"
-+#define REG_ORIG_DIP_IPV6 "xxreg1"
-+#define REG_ORIG_TP_DPORT "reg2[0..15]"
-
- /* Register definitions for switches and routers. */
-
-@@ -259,12 +270,29 @@ enum ovn_stage {
- * OVS register usage:
- *
- * Logical Switch pipeline:
-- * +---------+----------------------------------------------+
-- * | R0 | REGBIT_{CONNTRACK/DHCP/DNS/HAIRPIN} |
-- * | | REGBIT_ACL_HINT_{ALLOW_NEW/ALLOW/DROP/BLOCK} |
-- * +---------+----------------------------------------------+
-- * | R1 - R9 | UNUSED |
-- * +---------+----------------------------------------------+
-+ * +----+----------------------------------------------+---+------------------+
-+ * | R0 | REGBIT_{CONNTRACK/DHCP/DNS} | | |
-+ * | | REGBIT_{HAIRPIN/HAIRPIN_REPLY} | X | |
-+ * | | REGBIT_ACL_HINT_{ALLOW_NEW/ALLOW/DROP/BLOCK} | X | |
-+ * +----+----------------------------------------------+ X | |
-+ * | R1 | ORIG_DIP_IPV4 (>= IN_STATEFUL) | R | |
-+ * +----+----------------------------------------------+ E | |
-+ * | R2 | ORIG_TP_DPORT (>= IN_STATEFUL) | G | |
-+ * +----+----------------------------------------------+ 0 | |
-+ * | R3 | UNUSED | | |
-+ * +----+----------------------------------------------+---+------------------+
-+ * | R4 | UNUSED | | |
-+ * +----+----------------------------------------------+ X | ORIG_DIP_IPV6 |
-+ * | R5 | UNUSED | X | (>= IN_STATEFUL) |
-+ * +----+----------------------------------------------+ R | |
-+ * | R6 | UNUSED | E | |
-+ * +----+----------------------------------------------+ G | |
-+ * | R7 | UNUSED | 1 | |
-+ * +----+----------------------------------------------+---+------------------+
-+ * | R8 | UNUSED |
-+ * +----+----------------------------------------------+
-+ * | R9 | UNUSED |
-+ * +----+----------------------------------------------+
- *
- * Logical Router pipeline:
- * +-----+--------------------------+---+-----------------+---+---------------+
-@@ -608,6 +636,8 @@ struct ovn_datapath {
- struct hmap port_tnlids;
- uint32_t port_key_hint;
-
-+ bool has_stateful_acl;
-+ bool has_lb_vip;
- bool has_unknown;
-
- /* IPAM data. */
-@@ -633,6 +663,7 @@ struct ovn_datapath {
-
- struct lport_addresses dnat_force_snat_addrs;
- struct lport_addresses lb_force_snat_addrs;
-+ bool lb_force_snat_router_ip;
-
- struct ovn_port **localnet_ports;
- size_t n_localnet_ports;
-@@ -646,6 +677,9 @@ struct ovn_datapath {
- struct hmap nb_pgs;
- };
-
-+static bool ls_has_stateful_acl(struct ovn_datapath *od);
-+static bool ls_has_lb_vip(struct ovn_datapath *od);
-+
- /* Contains a NAT entry with the external addresses pre-parsed. */
- struct ovn_nat {
- const struct nbrec_nat *nb;
-@@ -723,14 +757,28 @@ init_nat_entries(struct ovn_datapath *od)
- }
- }
-
-- if (get_force_snat_ip(od, "lb", &od->lb_force_snat_addrs)) {
-- if (od->lb_force_snat_addrs.n_ipv4_addrs) {
-- snat_ip_add(od, od->lb_force_snat_addrs.ipv4_addrs[0].addr_s,
-- NULL);
-- }
-- if (od->lb_force_snat_addrs.n_ipv6_addrs) {
-- snat_ip_add(od, od->lb_force_snat_addrs.ipv6_addrs[0].addr_s,
-- NULL);
-+ /* Check if 'lb_force_snat_ip' is configured with 'router_ip'. */
-+ const char *lb_force_snat =
-+ smap_get(&od->nbr->options, "lb_force_snat_ip");
-+ if (lb_force_snat && !strcmp(lb_force_snat, "router_ip")
-+ && smap_get(&od->nbr->options, "chassis")) {
-+ /* Set it to true only if its gateway router and
-+ * options:lb_force_snat_ip=router_ip. */
-+ od->lb_force_snat_router_ip = true;
-+ } else {
-+ od->lb_force_snat_router_ip = false;
-+
-+ /* Check if 'lb_force_snat_ip' is configured with a set of
-+ * IP address(es). */
-+ if (get_force_snat_ip(od, "lb", &od->lb_force_snat_addrs)) {
-+ if (od->lb_force_snat_addrs.n_ipv4_addrs) {
-+ snat_ip_add(od, od->lb_force_snat_addrs.ipv4_addrs[0].addr_s,
-+ NULL);
-+ }
-+ if (od->lb_force_snat_addrs.n_ipv6_addrs) {
-+ snat_ip_add(od, od->lb_force_snat_addrs.ipv6_addrs[0].addr_s,
-+ NULL);
-+ }
- }
- }
-
-@@ -872,6 +920,20 @@ ovn_datapath_find(struct hmap *datapaths, const struct uuid *uuid)
- return NULL;
- }
-
-+static struct ovn_datapath *
-+ovn_datapath_find_by_key(struct hmap *datapaths, uint32_t dp_key)
-+{
-+ struct ovn_datapath *od;
-+
-+ HMAP_FOR_EACH (od, key_node, datapaths) {
-+ if (od->tunnel_key == dp_key) {
-+ return od;
-+ }
-+ }
-+
-+ return NULL;
-+}
-+
- static bool
- ovn_datapath_is_stale(const struct ovn_datapath *od)
- {
-@@ -1472,6 +1534,8 @@ struct ovn_port {
-
- bool has_unknown; /* If the addresses have 'unknown' defined. */
-
-+ bool has_bfd;
-+
- /* The port's peer:
- *
- * - A switch port S of type "router" has a router port R as a peer,
-@@ -1543,17 +1607,38 @@ ovn_port_destroy(struct hmap *ports, struct ovn_port *port)
- }
- }
-
-+/* Returns the ovn_port that matches 'name'. If 'prefer_bound' is true and
-+ * multiple ports share the same name, gives precendence to ports bound to
-+ * an ovn_datapath.
-+ */
- static struct ovn_port *
--ovn_port_find(const struct hmap *ports, const char *name)
-+ovn_port_find__(const struct hmap *ports, const char *name,
-+ bool prefer_bound)
- {
-+ struct ovn_port *matched_op = NULL;
- struct ovn_port *op;
-
- HMAP_FOR_EACH_WITH_HASH (op, key_node, hash_string(name, 0), ports) {
- if (!strcmp(op->key, name)) {
-- return op;
-+ matched_op = op;
-+ if (!prefer_bound || op->od) {
-+ return op;
-+ }
- }
- }
-- return NULL;
-+ return matched_op;
-+}
-+
-+static struct ovn_port *
-+ovn_port_find(const struct hmap *ports, const char *name)
-+{
-+ return ovn_port_find__(ports, name, false);
-+}
-+
-+static struct ovn_port *
-+ovn_port_find_bound(const struct hmap *ports, const char *name)
-+{
-+ return ovn_port_find__(ports, name, true);
- }
-
- /* Returns true if the logical switch port 'enabled' column is empty or
-@@ -2336,15 +2421,13 @@ join_logical_ports(struct northd_context *ctx,
- for (size_t i = 0; i < od->nbs->n_ports; i++) {
- const struct nbrec_logical_switch_port *nbsp
- = od->nbs->ports[i];
-- struct ovn_port *op = ovn_port_find(ports, nbsp->name);
-- if (op && op->sb->datapath == od->sb) {
-- if (op->nbsp || op->nbrp) {
-- static struct vlog_rate_limit rl
-- = VLOG_RATE_LIMIT_INIT(5, 1);
-- VLOG_WARN_RL(&rl, "duplicate logical port %s",
-- nbsp->name);
-- continue;
-- }
-+ struct ovn_port *op = ovn_port_find_bound(ports, nbsp->name);
-+ if (op && (op->od || op->nbsp || op->nbrp)) {
-+ static struct vlog_rate_limit rl
-+ = VLOG_RATE_LIMIT_INIT(5, 1);
-+ VLOG_WARN_RL(&rl, "duplicate logical port %s", nbsp->name);
-+ continue;
-+ } else if (op && (!op->sb || op->sb->datapath == od->sb)) {
- ovn_port_set_nb(op, nbsp, NULL);
- ovs_list_remove(&op->list);
-
-@@ -2435,16 +2518,15 @@ join_logical_ports(struct northd_context *ctx,
- continue;
- }
-
-- struct ovn_port *op = ovn_port_find(ports, nbrp->name);
-- if (op && op->sb->datapath == od->sb) {
-- if (op->nbsp || op->nbrp) {
-- static struct vlog_rate_limit rl
-- = VLOG_RATE_LIMIT_INIT(5, 1);
-- VLOG_WARN_RL(&rl, "duplicate logical router port %s",
-- nbrp->name);
-- destroy_lport_addresses(&lrp_networks);
-- continue;
-- }
-+ struct ovn_port *op = ovn_port_find_bound(ports, nbrp->name);
-+ if (op && (op->od || op->nbsp || op->nbrp)) {
-+ static struct vlog_rate_limit rl
-+ = VLOG_RATE_LIMIT_INIT(5, 1);
-+ VLOG_WARN_RL(&rl, "duplicate logical router port %s",
-+ nbrp->name);
-+ destroy_lport_addresses(&lrp_networks);
-+ continue;
-+ } else if (op && (!op->sb || op->sb->datapath == od->sb)) {
- ovn_port_set_nb(op, NULL, nbrp);
- ovs_list_remove(&op->list);
- ovs_list_push_back(both, &op->list);
-@@ -2487,7 +2569,7 @@ join_logical_ports(struct northd_context *ctx,
- char *redirect_name =
- ovn_chassis_redirect_name(nbrp->name);
- struct ovn_port *crp = ovn_port_find(ports, redirect_name);
-- if (crp && crp->sb->datapath == od->sb) {
-+ if (crp && crp->sb && crp->sb->datapath == od->sb) {
- crp->derived = true;
- ovn_port_set_nb(crp, NULL, nbrp);
- ovs_list_remove(&crp->list);
-@@ -3179,6 +3261,12 @@ ovn_port_update_sbrec(struct northd_context *ctx,
- } else {
- sbrec_port_binding_set_ha_chassis_group(op->sb, NULL);
- }
-+ } else if (op->sb->ha_chassis_group) {
-+ /* Clear the port bindings ha_chassis_group if the type is
-+ * not external and if this column is set. This can happen
-+ * when an external port is reset to type normal and
-+ * ha_chassis_group cleared in the same transaction. */
-+ sbrec_port_binding_set_ha_chassis_group(op->sb, NULL);
- }
- } else {
- const char *chassis = NULL;
-@@ -3308,6 +3396,14 @@ ovn_port_update_sbrec(struct northd_context *ctx,
- if (op->tunnel_key != op->sb->tunnel_key) {
- sbrec_port_binding_set_tunnel_key(op->sb, op->tunnel_key);
- }
-+
-+ /* ovn-controller will update 'Port_Binding.up' only if it was explicitly
-+ * set to 'false'.
-+ */
-+ if (!op->sb->n_up) {
-+ bool up = false;
-+ sbrec_port_binding_set_up(op->sb, &up, 1);
-+ }
- }
-
- /* Remove mac_binding entries that refer to logical_ports which are
-@@ -3340,6 +3436,26 @@ cleanup_sb_ha_chassis_groups(struct northd_context *ctx,
- }
- }
-
-+static void
-+cleanup_stale_fdp_entries(struct northd_context *ctx, struct hmap *datapaths)
-+{
-+ const struct sbrec_fdb *fdb_e, *next;
-+ SBREC_FDB_FOR_EACH_SAFE (fdb_e, next, ctx->ovnsb_idl) {
-+ bool delete = true;
-+ struct ovn_datapath *od
-+ = ovn_datapath_find_by_key(datapaths, fdb_e->dp_key);
-+ if (od) {
-+ if (ovn_tnlid_present(&od->port_tnlids, fdb_e->port_key)) {
-+ delete = false;
-+ }
-+ }
-+
-+ if (delete) {
-+ sbrec_fdb_delete(fdb_e);
-+ }
-+ }
-+}
-+
- struct service_monitor_info {
- struct hmap_node hmap_node;
- const struct sbrec_service_monitor *sbrec_mon;
-@@ -3436,12 +3552,12 @@ ovn_lb_svc_create(struct northd_context *ctx, struct ovn_northd_lb *lb,
- }
-
- static
--void build_lb_vip_ct_lb_actions(struct ovn_lb_vip *lb_vip,
-- struct ovn_northd_lb_vip *lb_vip_nb,
-- struct ds *action,
-- char *selection_fields)
-+void build_lb_vip_actions(struct ovn_lb_vip *lb_vip,
-+ struct ovn_northd_lb_vip *lb_vip_nb,
-+ struct ds *action, char *selection_fields,
-+ bool ls_dp)
- {
-- bool skip_hash_fields = false;
-+ bool skip_hash_fields = false, reject = false;
-
- if (lb_vip_nb->lb_health_check) {
- ds_put_cstr(action, "ct_lb(backends=");
-@@ -3463,18 +3579,30 @@ void build_lb_vip_ct_lb_actions(struct ovn_lb_vip *lb_vip,
- }
-
- if (!n_active_backends) {
-- skip_hash_fields = true;
-- ds_clear(action);
-- ds_put_cstr(action, "drop;");
-+ if (!lb_vip->empty_backend_rej) {
-+ ds_clear(action);
-+ ds_put_cstr(action, "drop;");
-+ skip_hash_fields = true;
-+ } else {
-+ reject = true;
-+ }
- } else {
- ds_chomp(action, ',');
- ds_put_cstr(action, ");");
- }
-+ } else if (lb_vip->empty_backend_rej && !lb_vip->n_backends) {
-+ reject = true;
- } else {
- ds_put_format(action, "ct_lb(backends=%s);", lb_vip_nb->backend_ips);
- }
-
-- if (!skip_hash_fields && selection_fields && selection_fields[0]) {
-+ if (reject) {
-+ int stage = ls_dp ? ovn_stage_get_table(S_SWITCH_OUT_QOS_MARK)
-+ : ovn_stage_get_table(S_ROUTER_OUT_SNAT);
-+ ds_clear(action);
-+ ds_put_format(action, "reg0 = 0; reject { outport <-> inport; "
-+ "next(pipeline=egress,table=%d);};", stage);
-+ } else if (!skip_hash_fields && selection_fields && selection_fields[0]) {
- ds_chomp(action, ';');
- ds_chomp(action, ')');
- ds_put_format(action, "; hash_fields=\"%s\");", selection_fields);
-@@ -3547,10 +3675,18 @@ build_ovn_lbs(struct northd_context *ctx, struct hmap *datapaths,
- /* Create SB Load balancer records if not present and sync
- * the SB load balancer columns. */
- HMAP_FOR_EACH (lb, hmap_node, lbs) {
-+
- if (!lb->n_dps) {
- continue;
- }
-
-+ /* Store the fact that northd provides the original (destination IP +
-+ * transport port) tuple.
-+ */
-+ struct smap options;
-+ smap_clone(&options, &lb->nlb->options);
-+ smap_replace(&options, "hairpin_orig_tuple", "true");
-+
- if (!lb->slb) {
- sbrec_lb = sbrec_load_balancer_insert(ctx->ovnsb_txn);
- lb->slb = sbrec_lb;
-@@ -3564,9 +3700,11 @@ build_ovn_lbs(struct northd_context *ctx, struct hmap *datapaths,
- sbrec_load_balancer_set_name(lb->slb, lb->nlb->name);
- sbrec_load_balancer_set_vips(lb->slb, &lb->nlb->vips);
- sbrec_load_balancer_set_protocol(lb->slb, lb->nlb->protocol);
-+ sbrec_load_balancer_set_options(lb->slb, &options);
- sbrec_load_balancer_set_datapaths(
- lb->slb, (struct sbrec_datapath_binding **)lb->dps,
- lb->n_dps);
-+ smap_destroy(&options);
- }
-
- /* Set the list of associated load balanacers to a logical switch
-@@ -4822,7 +4960,7 @@ ovn_ls_port_group_destroy(struct hmap *nb_pgs)
- }
-
- static bool
--has_stateful_acl(struct ovn_datapath *od)
-+ls_has_stateful_acl(struct ovn_datapath *od)
- {
- for (size_t i = 0; i < od->nbs->n_acls; i++) {
- struct nbrec_acl *acl = od->nbs->acls[i];
-@@ -4905,50 +5043,82 @@ build_lswitch_input_port_sec_od(
- }
-
- static void
--build_lswitch_output_port_sec(struct hmap *ports, struct hmap *datapaths,
-- struct hmap *lflows)
-+build_lswitch_learn_fdb_op(
-+ struct ovn_port *op, struct hmap *lflows,
-+ struct ds *actions, struct ds *match)
- {
-- struct ds actions = DS_EMPTY_INITIALIZER;
-- struct ds match = DS_EMPTY_INITIALIZER;
-- struct ovn_port *op;
-+ if (op->nbsp && !op->n_ps_addrs && !strcmp(op->nbsp->type, "") &&
-+ op->has_unknown) {
-+ ds_clear(match);
-+ ds_clear(actions);
-+ ds_put_format(match, "inport == %s", op->json_key);
-+ ds_put_format(actions, REGBIT_LKUP_FDB
-+ " = lookup_fdb(inport, eth.src); next;");
-+ ovn_lflow_add_with_hint(lflows, op->od, S_SWITCH_IN_LOOKUP_FDB, 100,
-+ ds_cstr(match), ds_cstr(actions),
-+ &op->nbsp->header_);
-
-- /* Egress table 8: Egress port security - IP (priorities 90 and 80)
-- * if port security enabled.
-- *
-- * Egress table 9: Egress port security - L2 (priorities 50 and 150).
-- *
-- * Priority 50 rules implement port security for enabled logical port.
-- *
-- * Priority 150 rules drop packets to disabled logical ports, so that
-- * they don't even receive multicast or broadcast packets.
-- */
-- HMAP_FOR_EACH (op, key_node, ports) {
-- if (!op->nbsp || lsp_is_external(op->nbsp)) {
-- continue;
-- }
-+ ds_put_cstr(match, " && "REGBIT_LKUP_FDB" == 0");
-+ ds_clear(actions);
-+ ds_put_cstr(actions, "put_fdb(inport, eth.src); next;");
-+ ovn_lflow_add_with_hint(lflows, op->od, S_SWITCH_IN_PUT_FDB, 100,
-+ ds_cstr(match), ds_cstr(actions),
-+ &op->nbsp->header_);
-+ }
-+}
-
-- ds_clear(&actions);
-- ds_clear(&match);
-+static void
-+build_lswitch_learn_fdb_od(
-+ struct ovn_datapath *od, struct hmap *lflows)
-+{
-+
-+ if (od->nbs) {
-+ ovn_lflow_add(lflows, od, S_SWITCH_IN_LOOKUP_FDB, 0, "1", "next;");
-+ ovn_lflow_add(lflows, od, S_SWITCH_IN_PUT_FDB, 0, "1", "next;");
-+ }
-+}
-+
-+/* Egress table 8: Egress port security - IP (priorities 90 and 80)
-+ * if port security enabled.
-+ *
-+ * Egress table 9: Egress port security - L2 (priorities 50 and 150).
-+ *
-+ * Priority 50 rules implement port security for enabled logical port.
-+ *
-+ * Priority 150 rules drop packets to disabled logical ports, so that
-+ * they don't even receive multicast or broadcast packets.
-+ */
-+static void
-+build_lswitch_output_port_sec_op(struct ovn_port *op,
-+ struct hmap *lflows,
-+ struct ds *match,
-+ struct ds *actions)
-+{
-+
-+ if (op->nbsp && (!lsp_is_external(op->nbsp))) {
-+
-+ ds_clear(actions);
-+ ds_clear(match);
-
-- ds_put_format(&match, "outport == %s", op->json_key);
-+ ds_put_format(match, "outport == %s", op->json_key);
- if (lsp_is_enabled(op->nbsp)) {
- build_port_security_l2("eth.dst", op->ps_addrs, op->n_ps_addrs,
-- &match);
-+ match);
-
- if (!strcmp(op->nbsp->type, "localnet")) {
- const char *queue_id = smap_get(&op->sb->options,
- "qdisc_queue_id");
- if (queue_id) {
-- ds_put_format(&actions, "set_queue(%s); ", queue_id);
-+ ds_put_format(actions, "set_queue(%s); ", queue_id);
- }
- }
-- ds_put_cstr(&actions, "output;");
-+ ds_put_cstr(actions, "output;");
- ovn_lflow_add_with_hint(lflows, op->od, S_SWITCH_OUT_PORT_SEC_L2,
-- 50, ds_cstr(&match), ds_cstr(&actions),
-+ 50, ds_cstr(match), ds_cstr(actions),
- &op->nbsp->header_);
- } else {
- ovn_lflow_add_with_hint(lflows, op->od, S_SWITCH_OUT_PORT_SEC_L2,
-- 150, ds_cstr(&match), "drop;",
-+ 150, ds_cstr(match), "drop;",
- &op->nbsp->header_);
- }
-
-@@ -4956,23 +5126,20 @@ build_lswitch_output_port_sec(struct hmap *ports, struct hmap *datapaths,
- build_port_security_ip(P_OUT, op, lflows, &op->nbsp->header_);
- }
- }
-+}
-
-- /* Egress tables 8: Egress port security - IP (priority 0)
-- * Egress table 9: Egress port security L2 - multicast/broadcast
-- * (priority 100). */
-- struct ovn_datapath *od;
-- HMAP_FOR_EACH (od, key_node, datapaths) {
-- if (!od->nbs) {
-- continue;
-- }
--
-+/* Egress tables 8: Egress port security - IP (priority 0)
-+ * Egress table 9: Egress port security L2 - multicast/broadcast
-+ * (priority 100). */
-+static void
-+build_lswitch_output_port_sec_od(struct ovn_datapath *od,
-+ struct hmap *lflows)
-+{
-+ if (od->nbs) {
- ovn_lflow_add(lflows, od, S_SWITCH_OUT_PORT_SEC_IP, 0, "1", "next;");
- ovn_lflow_add(lflows, od, S_SWITCH_OUT_PORT_SEC_L2, 100, "eth.mcast",
- "output;");
- }
--
-- ds_destroy(&match);
-- ds_destroy(&actions);
- }
-
- static void
-@@ -5008,8 +5175,6 @@ skip_port_from_conntrack(struct ovn_datapath *od, struct ovn_port *op,
- static void
- build_pre_acls(struct ovn_datapath *od, struct hmap *lflows)
- {
-- bool has_stateful = has_stateful_acl(od);
--
- /* Ingress and Egress Pre-ACL Table (Priority 0): Packets are
- * allowed by default. */
- ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 0, "1", "next;");
-@@ -5024,7 +5189,7 @@ build_pre_acls(struct ovn_datapath *od, struct hmap *lflows)
- /* If there are any stateful ACL rules in this datapath, we must
- * send all IP packets through the conntrack action, which handles
- * defragmentation, in order to match L4 headers. */
-- if (has_stateful) {
-+ if (od->has_stateful_acl) {
- for (size_t i = 0; i < od->n_router_ports; i++) {
- skip_port_from_conntrack(od, od->router_ports[i],
- S_SWITCH_IN_PRE_ACL, S_SWITCH_OUT_PRE_ACL,
-@@ -5084,7 +5249,10 @@ build_empty_lb_event_flow(struct ovn_datapath *od, struct hmap *lflows,
- struct nbrec_load_balancer *lb,
- int pl, struct shash *meter_groups)
- {
-- if (!controller_event_en || lb_vip->n_backends) {
-+ bool controller_event = smap_get_bool(&lb->options, "event", false) ||
-+ controller_event_en; /* deprecated */
-+ if (!controller_event || lb_vip->n_backends ||
-+ lb_vip->empty_backend_rej) {
- return;
- }
-
-@@ -5124,7 +5292,7 @@ build_empty_lb_event_flow(struct ovn_datapath *od, struct hmap *lflows,
- }
-
- static bool
--has_lb_vip(struct ovn_datapath *od)
-+ls_has_lb_vip(struct ovn_datapath *od)
- {
- for (int i = 0; i < od->nbs->n_load_balancer; i++) {
- struct nbrec_load_balancer *nb_lb = od->nbs->load_balancer[i];
-@@ -5267,6 +5435,13 @@ build_acl_hints(struct ovn_datapath *od, struct hmap *lflows)
- for (size_t i = 0; i < ARRAY_SIZE(stages); i++) {
- enum ovn_stage stage = stages[i];
-
-+ /* In any case, advance to the next stage. */
-+ ovn_lflow_add(lflows, od, stage, 0, "1", "next;");
-+
-+ if (!od->has_stateful_acl && !od->has_lb_vip) {
-+ continue;
-+ }
-+
- /* New, not already established connections, may hit either allow
- * or drop ACLs. For allow ACLs, the connection must also be committed
- * to conntrack so we set REGBIT_ACL_HINT_ALLOW_NEW.
-@@ -5327,9 +5502,6 @@ build_acl_hints(struct ovn_datapath *od, struct hmap *lflows)
- ovn_lflow_add(lflows, od, stage, 1, "ct.est && ct_label.blocked == 0",
- REGBIT_ACL_HINT_BLOCK " = 1; "
- "next;");
--
-- /* In any case, advance to the next stage. */
-- ovn_lflow_add(lflows, od, stage, 0, "1", "next;");
- }
- }
-
-@@ -5661,7 +5833,7 @@ static void
- build_acls(struct ovn_datapath *od, struct hmap *lflows,
- struct hmap *port_groups, const struct shash *meter_groups)
- {
-- bool has_stateful = (has_stateful_acl(od) || has_lb_vip(od));
-+ bool has_stateful = od->has_stateful_acl || od->has_lb_vip;
-
- /* Ingress and Egress ACL Table (Priority 0): Packets are allowed by
- * default. A related rule at priority 1 is added below if there
-@@ -5930,7 +6102,7 @@ build_lb(struct ovn_datapath *od, struct hmap *lflows)
- }
- }
-
-- if (has_lb_vip(od)) {
-+ if (od->has_lb_vip) {
- /* Ingress and Egress LB Table (Priority 65534).
- *
- * Send established traffic through conntrack for just NAT. */
-@@ -5953,11 +6125,20 @@ build_lb_rules(struct ovn_datapath *od, struct hmap *lflows,
- struct ovn_lb_vip *lb_vip = &lb->vips[i];
- struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[i];
-
-+ struct ds action = DS_EMPTY_INITIALIZER;
- const char *ip_match = NULL;
-+
-+ /* Store the original destination IP to be used when generating
-+ * hairpin flows.
-+ */
- if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
- ip_match = "ip4";
-+ ds_put_format(&action, REG_ORIG_DIP_IPV4 " = %s; ",
-+ lb_vip->vip_str);
- } else {
- ip_match = "ip6";
-+ ds_put_format(&action, REG_ORIG_DIP_IPV6 " = %s; ",
-+ lb_vip->vip_str);
- }
-
- const char *proto = NULL;
-@@ -5970,12 +6151,17 @@ build_lb_rules(struct ovn_datapath *od, struct hmap *lflows,
- proto = "sctp";
- }
- }
-+
-+ /* Store the original destination port to be used when generating
-+ * hairpin flows.
-+ */
-+ ds_put_format(&action, REG_ORIG_TP_DPORT " = %"PRIu16"; ",
-+ lb_vip->vip_port);
- }
-
- /* New connections in Ingress table. */
-- struct ds action = DS_EMPTY_INITIALIZER;
-- build_lb_vip_ct_lb_actions(lb_vip, lb_vip_nb, &action,
-- lb->selection_fields);
-+ build_lb_vip_actions(lb_vip, lb_vip_nb, &action,
-+ lb->selection_fields, true);
-
- struct ds match = DS_EMPTY_INITIALIZER;
- ds_put_format(&match, "ct.new && %s.dst == %s", ip_match,
-@@ -6021,9 +6207,39 @@ build_stateful(struct ovn_datapath *od, struct hmap *lflows, struct hmap *lbs)
- * REGBIT_CONNTRACK_COMMIT is set for new connections and
- * REGBIT_CONNTRACK_NAT is set for established connections. So they
- * don't overlap.
-+ *
-+ * In the ingress pipeline, also store the original destination IP and
-+ * transport port to be used when detecting hairpin packets.
- */
-- ovn_lflow_add(lflows, od, S_SWITCH_IN_STATEFUL, 100,
-- REGBIT_CONNTRACK_NAT" == 1", "ct_lb;");
-+ const char *lb_protocols[] = {"tcp", "udp", "sctp"};
-+ struct ds actions = DS_EMPTY_INITIALIZER;
-+ struct ds match = DS_EMPTY_INITIALIZER;
-+
-+ for (size_t i = 0; i < ARRAY_SIZE(lb_protocols); i++) {
-+ ds_clear(&match);
-+ ds_clear(&actions);
-+ ds_put_format(&match, REGBIT_CONNTRACK_NAT" == 1 && ip4 && %s",
-+ lb_protocols[i]);
-+ ds_put_format(&actions, REG_ORIG_DIP_IPV4 " = ip4.dst; "
-+ REG_ORIG_TP_DPORT " = %s.dst; ct_lb;",
-+ lb_protocols[i]);
-+ ovn_lflow_add(lflows, od, S_SWITCH_IN_STATEFUL, 100,
-+ ds_cstr(&match), ds_cstr(&actions));
-+
-+ ds_clear(&match);
-+ ds_clear(&actions);
-+ ds_put_format(&match, REGBIT_CONNTRACK_NAT" == 1 && ip6 && %s",
-+ lb_protocols[i]);
-+ ds_put_format(&actions, REG_ORIG_DIP_IPV6 " = ip6.dst; "
-+ REG_ORIG_TP_DPORT " = %s.dst; ct_lb;",
-+ lb_protocols[i]);
-+ ovn_lflow_add(lflows, od, S_SWITCH_IN_STATEFUL, 100,
-+ ds_cstr(&match), ds_cstr(&actions));
-+ }
-+
-+ ds_destroy(&actions);
-+ ds_destroy(&match);
-+
- ovn_lflow_add(lflows, od, S_SWITCH_OUT_STATEFUL, 100,
- REGBIT_CONNTRACK_NAT" == 1", "ct_lb;");
-
-@@ -6051,40 +6267,50 @@ build_lb_hairpin(struct ovn_datapath *od, struct hmap *lflows)
- ovn_lflow_add(lflows, od, S_SWITCH_IN_NAT_HAIRPIN, 0, "1", "next;");
- ovn_lflow_add(lflows, od, S_SWITCH_IN_HAIRPIN, 0, "1", "next;");
-
-- if (has_lb_vip(od)) {
-- /* Check if the packet needs to be hairpinned. */
-- ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_PRE_HAIRPIN, 100,
-- "ip && ct.trk && ct.dnat",
-- REGBIT_HAIRPIN " = chk_lb_hairpin(); next;",
-+ if (od->has_lb_vip) {
-+ /* Check if the packet needs to be hairpinned.
-+ * Set REGBIT_HAIRPIN in the original direction and
-+ * REGBIT_HAIRPIN_REPLY in the reply direction.
-+ */
-+ ovn_lflow_add_with_hint(
-+ lflows, od, S_SWITCH_IN_PRE_HAIRPIN, 100, "ip && ct.trk",
-+ REGBIT_HAIRPIN " = chk_lb_hairpin(); "
-+ REGBIT_HAIRPIN_REPLY " = chk_lb_hairpin_reply(); "
-+ "next;",
-+ &od->nbs->header_);
-+
-+ /* If packet needs to be hairpinned, snat the src ip with the VIP
-+ * for new sessions. */
-+ ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_NAT_HAIRPIN, 100,
-+ "ip && ct.new && ct.trk"
-+ " && "REGBIT_HAIRPIN " == 1",
-+ "ct_snat_to_vip; next;",
- &od->nbs->header_);
-
-- /* Check if the packet is a reply of hairpinned traffic. */
-- ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_PRE_HAIRPIN, 90, "ip",
-- REGBIT_HAIRPIN " = chk_lb_hairpin_reply(); "
-- "next;", &od->nbs->header_);
--
-- /* If packet needs to be hairpinned, snat the src ip with the VIP. */
-+ /* If packet needs to be hairpinned, for established sessions there
-+ * should already be an SNAT conntrack entry.
-+ */
- ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_NAT_HAIRPIN, 100,
-- "ip && (ct.new || ct.est) && ct.trk && ct.dnat"
-+ "ip && ct.est && ct.trk"
- " && "REGBIT_HAIRPIN " == 1",
-- "ct_snat_to_vip; next;",
-+ "ct_snat;",
- &od->nbs->header_);
-
- /* For the reply of hairpinned traffic, snat the src ip to the VIP. */
- ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_NAT_HAIRPIN, 90,
-- "ip && "REGBIT_HAIRPIN " == 1", "ct_snat;",
-+ "ip && "REGBIT_HAIRPIN_REPLY " == 1",
-+ "ct_snat;",
- &od->nbs->header_);
-
- /* Ingress Hairpin table.
- * - Priority 1: Packets that were SNAT-ed for hairpinning should be
- * looped back (i.e., swap ETH addresses and send back on inport).
- */
-- ovn_lflow_add(lflows, od, S_SWITCH_IN_HAIRPIN, 1,
-- REGBIT_HAIRPIN " == 1",
-- "eth.dst <-> eth.src;"
-- "outport = inport;"
-- "flags.loopback = 1;"
-- "output;");
-+ ovn_lflow_add(
-+ lflows, od, S_SWITCH_IN_HAIRPIN, 1,
-+ "("REGBIT_HAIRPIN " == 1 || " REGBIT_HAIRPIN_REPLY " == 1)",
-+ "eth.dst <-> eth.src; outport = inport; flags.loopback = 1; "
-+ "output;");
- }
- }
-
-@@ -6754,9 +6980,7 @@ is_vlan_transparent(const struct ovn_datapath *od)
- }
-
- static void
--build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
-- struct hmap *lflows, struct hmap *mcgroups,
-- struct hmap *igmp_groups, struct hmap *lbs)
-+build_lswitch_flows(struct hmap *datapaths, struct hmap *lflows)
- {
- /* This flow table structure is documented in ovn-northd(8), so please
- * update ovn-northd.8.xml if you change anything. */
-@@ -6765,32 +6989,111 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
- struct ds actions = DS_EMPTY_INITIALIZER;
- struct ovn_datapath *od;
-
-- /* Ingress table 13: ARP/ND responder, skip requests coming from localnet
-- * and vtep ports. (priority 100); see ovn-northd.8.xml for the
-- * rationale. */
-- struct ovn_port *op;
-- HMAP_FOR_EACH (op, key_node, ports) {
-- if (!op->nbsp) {
-+ /* Ingress table 24: Destination lookup for unknown MACs (priority 0). */
-+ HMAP_FOR_EACH (od, key_node, datapaths) {
-+ if (!od->nbs) {
- continue;
- }
-
-+ ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 0, "1",
-+ "outport = get_fdb(eth.dst); next;");
-+
-+ if (od->has_unknown) {
-+ ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_UNKNOWN, 50,
-+ "outport == \"none\"",
-+ "outport = \""MC_UNKNOWN"\"; output;");
-+ } else {
-+ ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_UNKNOWN, 50,
-+ "outport == \"none\"", "drop;");
-+ }
-+ ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_UNKNOWN, 0, "1",
-+ "output;");
-+ }
-+
-+ ds_destroy(&match);
-+ ds_destroy(&actions);
-+}
-+
-+/* Build pre-ACL and ACL tables for both ingress and egress.
-+ * Ingress tables 3 through 10. Egress tables 0 through 7. */
-+static void
-+build_lswitch_lflows_pre_acl_and_acl(struct ovn_datapath *od,
-+ struct hmap *port_groups,
-+ struct hmap *lflows,
-+ struct shash *meter_groups,
-+ struct hmap *lbs)
-+{
-+ if (od->nbs) {
-+ od->has_stateful_acl = ls_has_stateful_acl(od);
-+ od->has_lb_vip = ls_has_lb_vip(od);
-+
-+ build_pre_acls(od, lflows);
-+ build_pre_lb(od, lflows, meter_groups, lbs);
-+ build_pre_stateful(od, lflows);
-+ build_acl_hints(od, lflows);
-+ build_acls(od, lflows, port_groups, meter_groups);
-+ build_qos(od, lflows);
-+ build_lb(od, lflows);
-+ build_stateful(od, lflows, lbs);
-+ build_lb_hairpin(od, lflows);
-+ }
-+}
-+
-+/* Logical switch ingress table 0: Admission control framework (priority
-+ * 100). */
-+static void
-+build_lswitch_lflows_admission_control(struct ovn_datapath *od,
-+ struct hmap *lflows)
-+{
-+ if (od->nbs) {
-+ /* Logical VLANs not supported. */
-+ if (!is_vlan_transparent(od)) {
-+ /* Block logical VLANs. */
-+ ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_L2, 100,
-+ "vlan.present", "drop;");
-+ }
-+
-+ /* Broadcast/multicast source address is invalid. */
-+ ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_L2, 100, "eth.src[40]",
-+ "drop;");
-+
-+ /* Port security flows have priority 50
-+ * (see build_lswitch_input_port_sec()) and will continue
-+ * to the next table if packet source is acceptable. */
-+ }
-+}
-+
-+/* Ingress table 13: ARP/ND responder, skip requests coming from localnet
-+ * and vtep ports. (priority 100); see ovn-northd.8.xml for the
-+ * rationale. */
-+
-+static void
-+build_lswitch_arp_nd_responder_skip_local(struct ovn_port *op,
-+ struct hmap *lflows,
-+ struct ds *match)
-+{
-+ if (op->nbsp) {
- if ((!strcmp(op->nbsp->type, "localnet")) ||
- (!strcmp(op->nbsp->type, "vtep"))) {
-- ds_clear(&match);
-- ds_put_format(&match, "inport == %s", op->json_key);
-+ ds_clear(match);
-+ ds_put_format(match, "inport == %s", op->json_key);
- ovn_lflow_add_with_hint(lflows, op->od, S_SWITCH_IN_ARP_ND_RSP,
-- 100, ds_cstr(&match), "next;",
-+ 100, ds_cstr(match), "next;",
- &op->nbsp->header_);
- }
- }
-+}
-
-- /* Ingress table 13: ARP/ND responder, reply for known IPs.
-- * (priority 50). */
-- HMAP_FOR_EACH (op, key_node, ports) {
-- if (!op->nbsp) {
-- continue;
-- }
--
-+/* Ingress table 13: ARP/ND responder, reply for known IPs.
-+ * (priority 50). */
-+static void
-+build_lswitch_arp_nd_responder_known_ips(struct ovn_port *op,
-+ struct hmap *lflows,
-+ struct hmap *ports,
-+ struct ds *actions,
-+ struct ds *match)
-+{
-+ if (op->nbsp) {
- if (!strcmp(op->nbsp->type, "virtual")) {
- /* Handle
- * - GARPs for virtual ip which belongs to a logical port
-@@ -6806,7 +7109,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
- "virtual-parents");
- if (!virtual_ip || !virtual_parents ||
- !ip_parse(virtual_ip, &ip)) {
-- continue;
-+ return;
- }
-
- char *tokstr = xstrdup(virtual_parents);
-@@ -6821,21 +7124,21 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
- continue;
- }
-
-- ds_clear(&match);
-- ds_put_format(&match, "inport == \"%s\" && "
-+ ds_clear(match);
-+ ds_put_format(match, "inport == \"%s\" && "
- "((arp.op == 1 && arp.spa == %s && "
- "arp.tpa == %s) || (arp.op == 2 && "
- "arp.spa == %s))",
- vparent, virtual_ip, virtual_ip,
- virtual_ip);
-- ds_clear(&actions);
-- ds_put_format(&actions,
-+ ds_clear(actions);
-+ ds_put_format(actions,
- "bind_vport(%s, inport); "
- "next;",
- op->json_key);
- ovn_lflow_add_with_hint(lflows, op->od,
- S_SWITCH_IN_ARP_ND_RSP, 100,
-- ds_cstr(&match), ds_cstr(&actions),
-+ ds_cstr(match), ds_cstr(actions),
- &vp->nbsp->header_);
- }
-
-@@ -6850,20 +7153,20 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
- if (check_lsp_is_up &&
- !lsp_is_up(op->nbsp) && !lsp_is_router(op->nbsp) &&
- strcmp(op->nbsp->type, "localport")) {
-- continue;
-+ return;
- }
-
- if (lsp_is_external(op->nbsp) || op->has_unknown) {
-- continue;
-+ return;
- }
-
- for (size_t i = 0; i < op->n_lsp_addrs; i++) {
- for (size_t j = 0; j < op->lsp_addrs[i].n_ipv4_addrs; j++) {
-- ds_clear(&match);
-- ds_put_format(&match, "arp.tpa == %s && arp.op == 1",
-+ ds_clear(match);
-+ ds_put_format(match, "arp.tpa == %s && arp.op == 1",
- op->lsp_addrs[i].ipv4_addrs[j].addr_s);
-- ds_clear(&actions);
-- ds_put_format(&actions,
-+ ds_clear(actions);
-+ ds_put_format(actions,
- "eth.dst = eth.src; "
- "eth.src = %s; "
- "arp.op = 2; /* ARP reply */ "
-@@ -6878,8 +7181,8 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
- op->lsp_addrs[i].ipv4_addrs[j].addr_s);
- ovn_lflow_add_with_hint(lflows, op->od,
- S_SWITCH_IN_ARP_ND_RSP, 50,
-- ds_cstr(&match),
-- ds_cstr(&actions),
-+ ds_cstr(match),
-+ ds_cstr(actions),
- &op->nbsp->header_);
-
- /* Do not reply to an ARP request from the port that owns
-@@ -6894,10 +7197,10 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
- * address is intended to detect situations where the
- * network is not working as configured, so dropping the
- * request would frustrate that intent.) */
-- ds_put_format(&match, " && inport == %s", op->json_key);
-+ ds_put_format(match, " && inport == %s", op->json_key);
- ovn_lflow_add_with_hint(lflows, op->od,
- S_SWITCH_IN_ARP_ND_RSP, 100,
-- ds_cstr(&match), "next;",
-+ ds_cstr(match), "next;",
- &op->nbsp->header_);
- }
-
-@@ -6905,15 +7208,15 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
- * unicast IPv6 address and its all-nodes multicast address,
- * but always respond with the unicast IPv6 address. */
- for (size_t j = 0; j < op->lsp_addrs[i].n_ipv6_addrs; j++) {
-- ds_clear(&match);
-- ds_put_format(&match,
-+ ds_clear(match);
-+ ds_put_format(match,
- "nd_ns && ip6.dst == {%s, %s} && nd.target == %s",
- op->lsp_addrs[i].ipv6_addrs[j].addr_s,
- op->lsp_addrs[i].ipv6_addrs[j].sn_addr_s,
- op->lsp_addrs[i].ipv6_addrs[j].addr_s);
-
-- ds_clear(&actions);
-- ds_put_format(&actions,
-+ ds_clear(actions);
-+ ds_put_format(actions,
- "%s { "
- "eth.src = %s; "
- "ip6.src = %s; "
-@@ -6930,93 +7233,99 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
- op->lsp_addrs[i].ea_s);
- ovn_lflow_add_with_hint(lflows, op->od,
- S_SWITCH_IN_ARP_ND_RSP, 50,
-- ds_cstr(&match),
-- ds_cstr(&actions),
-+ ds_cstr(match),
-+ ds_cstr(actions),
- &op->nbsp->header_);
-
- /* Do not reply to a solicitation from the port that owns
- * the address (otherwise DAD detection will fail). */
-- ds_put_format(&match, " && inport == %s", op->json_key);
-+ ds_put_format(match, " && inport == %s", op->json_key);
- ovn_lflow_add_with_hint(lflows, op->od,
- S_SWITCH_IN_ARP_ND_RSP, 100,
-- ds_cstr(&match), "next;",
-+ ds_cstr(match), "next;",
- &op->nbsp->header_);
- }
- }
- }
- }
-+}
-
-- /* Ingress table 13: ARP/ND responder, by default goto next.
-- * (priority 0)*/
-- HMAP_FOR_EACH (od, key_node, datapaths) {
-- if (!od->nbs) {
-- continue;
-- }
--
-+/* Ingress table 13: ARP/ND responder, by default goto next.
-+ * (priority 0)*/
-+static void
-+build_lswitch_arp_nd_responder_default(struct ovn_datapath *od,
-+ struct hmap *lflows)
-+{
-+ if (od->nbs) {
- ovn_lflow_add(lflows, od, S_SWITCH_IN_ARP_ND_RSP, 0, "1", "next;");
- }
-+}
-
-- /* Ingress table 13: ARP/ND responder for service monitor source ip.
-- * (priority 110)*/
-- struct ovn_northd_lb *lb;
-- HMAP_FOR_EACH (lb, hmap_node, lbs) {
-- for (size_t i = 0; i < lb->n_vips; i++) {
-- struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[i];
-- if (!lb_vip_nb->lb_health_check) {
-+/* Ingress table 13: ARP/ND responder for service monitor source ip.
-+ * (priority 110)*/
-+static void
-+build_lswitch_arp_nd_service_monitor(struct ovn_northd_lb *lb,
-+ struct hmap *lflows,
-+ struct ds *actions,
-+ struct ds *match)
-+{
-+ for (size_t i = 0; i < lb->n_vips; i++) {
-+ struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[i];
-+ if (!lb_vip_nb->lb_health_check) {
-+ continue;
-+ }
-+
-+ for (size_t j = 0; j < lb_vip_nb->n_backends; j++) {
-+ struct ovn_northd_lb_backend *backend_nb =
-+ &lb_vip_nb->backends_nb[j];
-+ if (!backend_nb->op || !backend_nb->svc_mon_src_ip) {
- continue;
- }
-
-- for (size_t j = 0; j < lb_vip_nb->n_backends; j++) {
-- struct ovn_northd_lb_backend *backend_nb =
-- &lb_vip_nb->backends_nb[j];
-- if (!backend_nb->op || !backend_nb->svc_mon_src_ip) {
-- continue;
-- }
--
-- ds_clear(&match);
-- ds_put_format(&match, "arp.tpa == %s && arp.op == 1",
-- backend_nb->svc_mon_src_ip);
-- ds_clear(&actions);
-- ds_put_format(&actions,
-- "eth.dst = eth.src; "
-- "eth.src = %s; "
-- "arp.op = 2; /* ARP reply */ "
-- "arp.tha = arp.sha; "
-- "arp.sha = %s; "
-- "arp.tpa = arp.spa; "
-- "arp.spa = %s; "
-- "outport = inport; "
-- "flags.loopback = 1; "
-- "output;",
-- svc_monitor_mac, svc_monitor_mac,
-- backend_nb->svc_mon_src_ip);
-- ovn_lflow_add_with_hint(lflows,
-- backend_nb->op->od,
-- S_SWITCH_IN_ARP_ND_RSP, 110,
-- ds_cstr(&match), ds_cstr(&actions),
-- &lb->nlb->header_);
-- }
-+ ds_clear(match);
-+ ds_put_format(match, "arp.tpa == %s && arp.op == 1",
-+ backend_nb->svc_mon_src_ip);
-+ ds_clear(actions);
-+ ds_put_format(actions,
-+ "eth.dst = eth.src; "
-+ "eth.src = %s; "
-+ "arp.op = 2; /* ARP reply */ "
-+ "arp.tha = arp.sha; "
-+ "arp.sha = %s; "
-+ "arp.tpa = arp.spa; "
-+ "arp.spa = %s; "
-+ "outport = inport; "
-+ "flags.loopback = 1; "
-+ "output;",
-+ svc_monitor_mac, svc_monitor_mac,
-+ backend_nb->svc_mon_src_ip);
-+ ovn_lflow_add_with_hint(lflows,
-+ backend_nb->op->od,
-+ S_SWITCH_IN_ARP_ND_RSP, 110,
-+ ds_cstr(match), ds_cstr(actions),
-+ &lb->nlb->header_);
- }
- }
-+}
-
-
-- /* Logical switch ingress table 14 and 15: DHCP options and response
-- * priority 100 flows. */
-- HMAP_FOR_EACH (op, key_node, ports) {
-- if (!op->nbsp) {
-- continue;
-- }
--
-+/* Logical switch ingress table 14 and 15: DHCP options and response
-+ * priority 100 flows. */
-+static void
-+build_lswitch_dhcp_options_and_response(struct ovn_port *op,
-+ struct hmap *lflows)
-+{
-+ if (op->nbsp) {
- if (!lsp_is_enabled(op->nbsp) || lsp_is_router(op->nbsp)) {
- /* Don't add the DHCP flows if the port is not enabled or if the
- * port is a router port. */
-- continue;
-+ return;
- }
-
- if (!op->nbsp->dhcpv4_options && !op->nbsp->dhcpv6_options) {
- /* CMS has disabled both native DHCPv4 and DHCPv6 for this lport.
- */
-- continue;
-+ return;
- }
-
- bool is_external = lsp_is_external(op->nbsp);
-@@ -7024,7 +7333,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
- !op->nbsp->ha_chassis_group)) {
- /* If it's an external port and there are no localnet ports
- * and if it doesn't belong to an HA chassis group ignore it. */
-- continue;
-+ return;
- }
-
- for (size_t i = 0; i < op->n_lsp_addrs; i++) {
-@@ -7047,14 +7356,35 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
- }
- }
- }
-+}
-
-- /* Logical switch ingress table 17 and 18: DNS lookup and response
-- * priority 100 flows.
-- */
-- HMAP_FOR_EACH (od, key_node, datapaths) {
-- if (!od->nbs || !ls_has_dns_records(od->nbs)) {
-- continue;
-- }
-+/* Ingress table 14 and 15: DHCP options and response, by default goto
-+ * next. (priority 0).
-+ * Ingress table 16 and 17: DNS lookup and response, by default goto next.
-+ * (priority 0).
-+ * Ingress table 18 - External port handling, by default goto next.
-+ * (priority 0). */
-+static void
-+build_lswitch_dhcp_and_dns_defaults(struct ovn_datapath *od,
-+ struct hmap *lflows)
-+{
-+ if (od->nbs) {
-+ ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP_OPTIONS, 0, "1", "next;");
-+ ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP_RESPONSE, 0, "1", "next;");
-+ ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_LOOKUP, 0, "1", "next;");
-+ ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 0, "1", "next;");
-+ ovn_lflow_add(lflows, od, S_SWITCH_IN_EXTERNAL_PORT, 0, "1", "next;");
-+ }
-+}
-+
-+/* Logical switch ingress table 17 and 18: DNS lookup and response
-+* priority 100 flows.
-+*/
-+static void
-+build_lswitch_dns_lookup_and_response(struct ovn_datapath *od,
-+ struct hmap *lflows)
-+{
-+ if (od->nbs && ls_has_dns_records(od->nbs)) {
-
- ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_LOOKUP, 100,
- "udp.dst == 53",
-@@ -7071,47 +7401,33 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
- ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 100,
- dns_match, dns_action);
- }
-+}
-
-- /* Ingress table 14 and 15: DHCP options and response, by default goto
-- * next. (priority 0).
-- * Ingress table 16 and 17: DNS lookup and response, by default goto next.
-- * (priority 0).
-- * Ingress table 18 - External port handling, by default goto next.
-- * (priority 0). */
--
-- HMAP_FOR_EACH (od, key_node, datapaths) {
-- if (!od->nbs) {
-- continue;
-- }
--
-- ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP_OPTIONS, 0, "1", "next;");
-- ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP_RESPONSE, 0, "1", "next;");
-- ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_LOOKUP, 0, "1", "next;");
-- ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 0, "1", "next;");
-- ovn_lflow_add(lflows, od, S_SWITCH_IN_EXTERNAL_PORT, 0, "1", "next;");
-- }
--
-- HMAP_FOR_EACH (op, key_node, ports) {
-- if (!op->nbsp || !lsp_is_external(op->nbsp)) {
-- continue;
-- }
-+/* Table 18: External port. Drop ARP request for router ips from
-+ * external ports on chassis not binding those ports.
-+ * This makes the router pipeline to be run only on the chassis
-+ * binding the external ports. */
-+static void
-+build_lswitch_external_port(struct ovn_port *op,
-+ struct hmap *lflows)
-+{
-+ if (op->nbsp && lsp_is_external(op->nbsp)) {
-
-- /* Table 18: External port. Drop ARP request for router ips from
-- * external ports on chassis not binding those ports.
-- * This makes the router pipeline to be run only on the chassis
-- * binding the external ports. */
- for (size_t i = 0; i < op->od->n_localnet_ports; i++) {
- build_drop_arp_nd_flows_for_unbound_router_ports(
- op, op->od->localnet_ports[i], lflows);
- }
- }
-+}
-
-- /* Ingress table 19: Destination lookup, broadcast and multicast handling
-- * (priority 70 - 100). */
-- HMAP_FOR_EACH (od, key_node, datapaths) {
-- if (!od->nbs) {
-- continue;
-- }
-+/* Ingress table 19: Destination lookup, broadcast and multicast handling
-+ * (priority 70 - 100). */
-+static void
-+build_lswitch_destination_lookup_bmcast(struct ovn_datapath *od,
-+ struct hmap *lflows,
-+ struct ds *actions)
-+{
-+ if (od->nbs) {
-
- ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 110,
- "eth.dst == $svc_monitor_mac",
-@@ -7120,22 +7436,22 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
- struct mcast_switch_info *mcast_sw_info = &od->mcast_info.sw;
-
- if (mcast_sw_info->enabled) {
-- ds_clear(&actions);
-+ ds_clear(actions);
- if (mcast_sw_info->flood_reports) {
-- ds_put_cstr(&actions,
-+ ds_put_cstr(actions,
- "clone { "
- "outport = \""MC_MROUTER_STATIC"\"; "
- "output; "
- "};");
- }
-- ds_put_cstr(&actions, "igmp;");
-+ ds_put_cstr(actions, "igmp;");
- /* Punt IGMP traffic to controller. */
- ovn_lflow_add_unique(lflows, od, S_SWITCH_IN_L2_LKUP, 100,
-- "ip4 && ip.proto == 2", ds_cstr(&actions));
-+ "ip4 && ip.proto == 2", ds_cstr(actions));
-
- /* Punt MLD traffic to controller. */
- ovn_lflow_add_unique(lflows, od, S_SWITCH_IN_L2_LKUP, 100,
-- "mldv1 || mldv2", ds_cstr(&actions));
-+ "mldv1 || mldv2", ds_cstr(actions));
-
- /* Flood all IP multicast traffic destined to 224.0.0.X to all
- * ports - RFC 4541, section 2.1.2, item 2.
-@@ -7157,10 +7473,10 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
- * handled by the L2 multicast flow.
- */
- if (!mcast_sw_info->flood_unregistered) {
-- ds_clear(&actions);
-+ ds_clear(actions);
-
- if (mcast_sw_info->flood_relay) {
-- ds_put_cstr(&actions,
-+ ds_put_cstr(actions,
- "clone { "
- "outport = \""MC_MROUTER_FLOOD"\"; "
- "output; "
-@@ -7168,7 +7484,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
- }
-
- if (mcast_sw_info->flood_static) {
-- ds_put_cstr(&actions, "outport =\""MC_STATIC"\"; output;");
-+ ds_put_cstr(actions, "outport =\""MC_STATIC"\"; output;");
- }
-
- /* Explicitly drop the traffic if relay or static flooding
-@@ -7176,30 +7492,33 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
- */
- if (!mcast_sw_info->flood_relay &&
- !mcast_sw_info->flood_static) {
-- ds_put_cstr(&actions, "drop;");
-+ ds_put_cstr(actions, "drop;");
- }
-
- ovn_lflow_add_unique(lflows, od, S_SWITCH_IN_L2_LKUP, 80,
- "ip4.mcast || ip6.mcast",
-- ds_cstr(&actions));
-+ ds_cstr(actions));
- }
- }
-
- ovn_lflow_add_unique(lflows, od, S_SWITCH_IN_L2_LKUP, 70, "eth.mcast",
- "outport = \""MC_FLOOD"\"; output;");
- }
-+}
-
-- /* Ingress table 19: Add IP multicast flows learnt from IGMP/MLD
-- * (priority 90). */
-- struct ovn_igmp_group *igmp_group;
-
-- HMAP_FOR_EACH (igmp_group, hmap_node, igmp_groups) {
-- if (!igmp_group->datapath) {
-- continue;
-- }
-+/* Ingress table 19: Add IP multicast flows learnt from IGMP/MLD
-+ * (priority 90). */
-+static void
-+build_lswitch_ip_mcast_igmp_mld(struct ovn_igmp_group *igmp_group,
-+ struct hmap *lflows,
-+ struct ds *actions,
-+ struct ds *match)
-+{
-+ if (igmp_group->datapath) {
-
-- ds_clear(&match);
-- ds_clear(&actions);
-+ ds_clear(match);
-+ ds_clear(actions);
-
- struct mcast_switch_info *mcast_sw_info =
- &igmp_group->datapath->mcast_info.sw;
-@@ -7211,57 +7530,62 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
- ovs_be32 group_address =
- in6_addr_get_mapped_ipv4(&igmp_group->address);
- if (ip_is_local_multicast(group_address)) {
-- continue;
-+ return;
- }
-
- if (mcast_sw_info->active_v4_flows >= mcast_sw_info->table_size) {
-- continue;
-+ return;
- }
- mcast_sw_info->active_v4_flows++;
-- ds_put_format(&match, "eth.mcast && ip4 && ip4.dst == %s ",
-+ ds_put_format(match, "eth.mcast && ip4 && ip4.dst == %s ",
- igmp_group->mcgroup.name);
- } else {
- /* RFC 4291, section 2.7.1: Skip groups that correspond to all
- * hosts.
- */
- if (ipv6_is_all_hosts(&igmp_group->address)) {
-- continue;
-+ return;
- }
- if (mcast_sw_info->active_v6_flows >= mcast_sw_info->table_size) {
-- continue;
-+ return;
- }
- mcast_sw_info->active_v6_flows++;
-- ds_put_format(&match, "eth.mcast && ip6 && ip6.dst == %s ",
-+ ds_put_format(match, "eth.mcast && ip6 && ip6.dst == %s ",
- igmp_group->mcgroup.name);
- }
-
- /* Also flood traffic to all multicast routers with relay enabled. */
- if (mcast_sw_info->flood_relay) {
-- ds_put_cstr(&actions,
-+ ds_put_cstr(actions,
- "clone { "
- "outport = \""MC_MROUTER_FLOOD "\"; "
- "output; "
- "};");
- }
- if (mcast_sw_info->flood_static) {
-- ds_put_cstr(&actions,
-+ ds_put_cstr(actions,
- "clone { "
- "outport =\""MC_STATIC"\"; "
- "output; "
- "};");
- }
-- ds_put_format(&actions, "outport = \"%s\"; output; ",
-+ ds_put_format(actions, "outport = \"%s\"; output; ",
- igmp_group->mcgroup.name);
-
- ovn_lflow_add_unique(lflows, igmp_group->datapath, S_SWITCH_IN_L2_LKUP,
-- 90, ds_cstr(&match), ds_cstr(&actions));
-+ 90, ds_cstr(match), ds_cstr(actions));
- }
-+}
-
-- /* Ingress table 19: Destination lookup, unicast handling (priority 50), */
-- HMAP_FOR_EACH (op, key_node, ports) {
-- if (!op->nbsp || lsp_is_external(op->nbsp)) {
-- continue;
-- }
-+/* Ingress table 19: Destination lookup, unicast handling (priority 50), */
-+static void
-+build_lswitch_ip_unicast_lookup(struct ovn_port *op,
-+ struct hmap *lflows,
-+ struct hmap *mcgroups,
-+ struct ds *actions,
-+ struct ds *match)
-+{
-+ if (op->nbsp && (!lsp_is_external(op->nbsp))) {
-
- /* For ports connected to logical routers add flows to bypass the
- * broadcast flooding of ARP/ND requests in table 19. We direct the
-@@ -7279,15 +7603,15 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
- struct eth_addr mac;
- if (ovs_scan(op->nbsp->addresses[i],
- ETH_ADDR_SCAN_FMT, ETH_ADDR_SCAN_ARGS(mac))) {
-- ds_clear(&match);
-- ds_put_format(&match, "eth.dst == "ETH_ADDR_FMT,
-+ ds_clear(match);
-+ ds_put_format(match, "eth.dst == "ETH_ADDR_FMT,
- ETH_ADDR_ARGS(mac));
-
-- ds_clear(&actions);
-- ds_put_format(&actions, "outport = %s; output;", op->json_key);
-+ ds_clear(actions);
-+ ds_put_format(actions, "outport = %s; output;", op->json_key);
- ovn_lflow_add_with_hint(lflows, op->od, S_SWITCH_IN_L2_LKUP,
-- 50, ds_cstr(&match),
-- ds_cstr(&actions),
-+ 50, ds_cstr(match),
-+ ds_cstr(actions),
- &op->nbsp->header_);
- } else if (!strcmp(op->nbsp->addresses[i], "unknown")) {
- if (lsp_is_enabled(op->nbsp)) {
-@@ -7300,15 +7624,15 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
- ETH_ADDR_SCAN_FMT, ETH_ADDR_SCAN_ARGS(mac))) {
- continue;
- }
-- ds_clear(&match);
-- ds_put_format(&match, "eth.dst == "ETH_ADDR_FMT,
-+ ds_clear(match);
-+ ds_put_format(match, "eth.dst == "ETH_ADDR_FMT,
- ETH_ADDR_ARGS(mac));
-
-- ds_clear(&actions);
-- ds_put_format(&actions, "outport = %s; output;", op->json_key);
-+ ds_clear(actions);
-+ ds_put_format(actions, "outport = %s; output;", op->json_key);
- ovn_lflow_add_with_hint(lflows, op->od, S_SWITCH_IN_L2_LKUP,
-- 50, ds_cstr(&match),
-- ds_cstr(&actions),
-+ 50, ds_cstr(match),
-+ ds_cstr(actions),
- &op->nbsp->header_);
- } else if (!strcmp(op->nbsp->addresses[i], "router")) {
- if (!op->peer || !op->peer->nbrp
-@@ -7316,8 +7640,8 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
- ETH_ADDR_SCAN_FMT, ETH_ADDR_SCAN_ARGS(mac))) {
- continue;
- }
-- ds_clear(&match);
-- ds_put_format(&match, "eth.dst == "ETH_ADDR_FMT,
-+ ds_clear(match);
-+ ds_put_format(match, "eth.dst == "ETH_ADDR_FMT,
- ETH_ADDR_ARGS(mac));
- if (op->peer->od->l3dgw_port
- && op->peer->od->l3redirect_port
-@@ -7343,16 +7667,16 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
- }
-
- if (add_chassis_resident_check) {
-- ds_put_format(&match, " && is_chassis_resident(%s)",
-+ ds_put_format(match, " && is_chassis_resident(%s)",
- op->peer->od->l3redirect_port->json_key);
- }
- }
-
-- ds_clear(&actions);
-- ds_put_format(&actions, "outport = %s; output;", op->json_key);
-+ ds_clear(actions);
-+ ds_put_format(actions, "outport = %s; output;", op->json_key);
- ovn_lflow_add_with_hint(lflows, op->od,
- S_SWITCH_IN_L2_LKUP, 50,
-- ds_cstr(&match), ds_cstr(&actions),
-+ ds_cstr(match), ds_cstr(actions),
- &op->nbsp->header_);
-
- /* Add ethernet addresses specified in NAT rules on
-@@ -7366,19 +7690,19 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
- && nat->logical_port && nat->external_mac
- && eth_addr_from_string(nat->external_mac, &mac)) {
-
-- ds_clear(&match);
-- ds_put_format(&match, "eth.dst == "ETH_ADDR_FMT
-+ ds_clear(match);
-+ ds_put_format(match, "eth.dst == "ETH_ADDR_FMT
- " && is_chassis_resident(\"%s\")",
- ETH_ADDR_ARGS(mac),
- nat->logical_port);
-
-- ds_clear(&actions);
-- ds_put_format(&actions, "outport = %s; output;",
-+ ds_clear(actions);
-+ ds_put_format(actions, "outport = %s; output;",
- op->json_key);
- ovn_lflow_add_with_hint(lflows, op->od,
- S_SWITCH_IN_L2_LKUP, 50,
-- ds_cstr(&match),
-- ds_cstr(&actions),
-+ ds_cstr(match),
-+ ds_cstr(actions),
- &op->nbsp->header_);
- }
- }
-@@ -7392,71 +7716,202 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
- }
- }
- }
-+}
-
-- /* Ingress table 19: Destination lookup for unknown MACs (priority 0). */
-- HMAP_FOR_EACH (od, key_node, datapaths) {
-- if (!od->nbs) {
-+struct bfd_entry {
-+ struct hmap_node hmap_node;
-+
-+ const struct sbrec_bfd *sb_bt;
-+
-+ bool ref;
-+};
-+
-+static struct bfd_entry *
-+bfd_port_lookup(struct hmap *bfd_map, const char *logical_port,
-+ const char *dst_ip)
-+{
-+ struct bfd_entry *bfd_e;
-+ uint32_t hash;
-+
-+ hash = hash_string(dst_ip, 0);
-+ hash = hash_string(logical_port, hash);
-+ HMAP_FOR_EACH_WITH_HASH (bfd_e, hmap_node, hash, bfd_map) {
-+ if (!strcmp(bfd_e->sb_bt->logical_port, logical_port) &&
-+ !strcmp(bfd_e->sb_bt->dst_ip, dst_ip)) {
-+ return bfd_e;
-+ }
-+ }
-+ return NULL;
-+}
-+
-+static void
-+bfd_cleanup_connections(struct northd_context *ctx, struct hmap *bfd_map)
-+{
-+ const struct nbrec_bfd *nb_bt;
-+ struct bfd_entry *bfd_e;
-+
-+ NBREC_BFD_FOR_EACH (nb_bt, ctx->ovnnb_idl) {
-+ bfd_e = bfd_port_lookup(bfd_map, nb_bt->logical_port, nb_bt->dst_ip);
-+ if (!bfd_e) {
- continue;
- }
-
-- if (od->has_unknown) {
-- ovn_lflow_add_unique(lflows, od, S_SWITCH_IN_L2_LKUP, 0, "1",
-- "outport = \""MC_UNKNOWN"\"; output;");
-+ if (!bfd_e->ref && strcmp(nb_bt->status, "admin_down")) {
-+ /* no user for this bfd connection */
-+ nbrec_bfd_set_status(nb_bt, "admin_down");
- }
- }
-
-- build_lswitch_output_port_sec(ports, datapaths, lflows);
--
-- ds_destroy(&match);
-- ds_destroy(&actions);
-+ HMAP_FOR_EACH_POP (bfd_e, hmap_node, bfd_map) {
-+ free(bfd_e);
-+ }
- }
-
--/* Build pre-ACL and ACL tables for both ingress and egress.
-- * Ingress tables 3 through 10. Egress tables 0 through 7. */
-+#define BFD_DEF_MINTX 1000 /* 1s */
-+#define BFD_DEF_MINRX 1000 /* 1s */
-+#define BFD_DEF_DETECT_MULT 5
-+
- static void
--build_lswitch_lflows_pre_acl_and_acl(struct ovn_datapath *od,
-- struct hmap *port_groups,
-- struct hmap *lflows,
-- struct shash *meter_groups,
-- struct hmap *lbs)
-+build_bfd_update_sb_conf(const struct nbrec_bfd *nb_bt,
-+ const struct sbrec_bfd *sb_bt)
- {
-- if (od->nbs) {
-- build_pre_acls(od, lflows);
-- build_pre_lb(od, lflows, meter_groups, lbs);
-- build_pre_stateful(od, lflows);
-- build_acl_hints(od, lflows);
-- build_acls(od, lflows, port_groups, meter_groups);
-- build_qos(od, lflows);
-- build_lb(od, lflows);
-- build_stateful(od, lflows, lbs);
-- build_lb_hairpin(od, lflows);
-+ if (strcmp(nb_bt->dst_ip, sb_bt->dst_ip)) {
-+ sbrec_bfd_set_dst_ip(sb_bt, nb_bt->dst_ip);
-+ }
-+
-+ if (strcmp(nb_bt->logical_port, sb_bt->logical_port)) {
-+ sbrec_bfd_set_logical_port(sb_bt, nb_bt->logical_port);
-+ }
-+
-+ if (strcmp(nb_bt->status, sb_bt->status)) {
-+ sbrec_bfd_set_status(sb_bt, nb_bt->status);
-+ }
-+
-+ int detect_mult = nb_bt->n_detect_mult ? nb_bt->detect_mult[0]
-+ : BFD_DEF_DETECT_MULT;
-+ if (detect_mult != sb_bt->detect_mult) {
-+ sbrec_bfd_set_detect_mult(sb_bt, detect_mult);
-+ }
-+
-+ int min_tx = nb_bt->n_min_tx ? nb_bt->min_tx[0] : BFD_DEF_MINTX;
-+ if (min_tx != sb_bt->min_tx) {
-+ sbrec_bfd_set_min_tx(sb_bt, min_tx);
-+ }
-+
-+ int min_rx = nb_bt->n_min_rx ? nb_bt->min_rx[0] : BFD_DEF_MINRX;
-+ if (min_rx != sb_bt->min_rx) {
-+ sbrec_bfd_set_min_rx(sb_bt, min_rx);
- }
- }
-
--/* Logical switch ingress table 0: Admission control framework (priority
-- * 100). */
-+/* RFC 5881 section 4
-+ * The source port MUST be in the range 49152 through 65535.
-+ * The same UDP source port number MUST be used for all BFD
-+ * Control packets associated with a particular session.
-+ * The source port number SHOULD be unique among all BFD
-+ * sessions on the system
-+ */
-+#define BFD_UDP_SRC_PORT_START 49152
-+#define BFD_UDP_SRC_PORT_LEN (65535 - BFD_UDP_SRC_PORT_START)
-+
-+static int bfd_get_unused_port(unsigned long *bfd_src_ports)
-+{
-+ int port;
-+
-+ port = bitmap_scan(bfd_src_ports, 0, 0, BFD_UDP_SRC_PORT_LEN);
-+ if (port == BFD_UDP_SRC_PORT_LEN) {
-+ return -ENOSPC;
-+ }
-+ bitmap_set1(bfd_src_ports, port);
-+
-+ return port + BFD_UDP_SRC_PORT_START;
-+}
-+
- static void
--build_lswitch_lflows_admission_control(struct ovn_datapath *od,
-- struct hmap *lflows)
-+build_bfd_table(struct northd_context *ctx, struct hmap *bfd_connections,
-+ struct hmap *ports)
- {
-- if (od->nbs) {
-- /* Logical VLANs not supported. */
-- if (!is_vlan_transparent(od)) {
-- /* Block logical VLANs. */
-- ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_L2, 100,
-- "vlan.present", "drop;");
-+ struct hmap sb_only = HMAP_INITIALIZER(&sb_only);
-+ const struct sbrec_bfd *sb_bt;
-+ unsigned long *bfd_src_ports;
-+ struct bfd_entry *bfd_e;
-+ uint32_t hash;
-+
-+ bfd_src_ports = bitmap_allocate(BFD_UDP_SRC_PORT_LEN);
-+
-+ SBREC_BFD_FOR_EACH (sb_bt, ctx->ovnsb_idl) {
-+ bfd_e = xmalloc(sizeof *bfd_e);
-+ bfd_e->sb_bt = sb_bt;
-+ hash = hash_string(sb_bt->dst_ip, 0);
-+ hash = hash_string(sb_bt->logical_port, hash);
-+ hmap_insert(&sb_only, &bfd_e->hmap_node, hash);
-+ bitmap_set1(bfd_src_ports, sb_bt->src_port - BFD_UDP_SRC_PORT_START);
-+ }
-+
-+ const struct nbrec_bfd *nb_bt;
-+ NBREC_BFD_FOR_EACH (nb_bt, ctx->ovnnb_idl) {
-+ if (!nb_bt->status) {
-+ /* default state is admin_down */
-+ nbrec_bfd_set_status(nb_bt, "admin_down");
- }
-
-- /* Broadcast/multicast source address is invalid. */
-- ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_L2, 100, "eth.src[40]",
-- "drop;");
-+ bfd_e = bfd_port_lookup(&sb_only, nb_bt->logical_port, nb_bt->dst_ip);
-+ if (!bfd_e) {
-+ int udp_src = bfd_get_unused_port(bfd_src_ports);
-+ if (udp_src < 0) {
-+ continue;
-+ }
-
-- /* Port security flows have priority 50
-- * (see build_lswitch_input_port_sec()) and will continue
-- * to the next table if packet source is acceptable. */
-+ sb_bt = sbrec_bfd_insert(ctx->ovnsb_txn);
-+ sbrec_bfd_set_logical_port(sb_bt, nb_bt->logical_port);
-+ sbrec_bfd_set_dst_ip(sb_bt, nb_bt->dst_ip);
-+ sbrec_bfd_set_disc(sb_bt, 1 + random_uint32());
-+ sbrec_bfd_set_src_port(sb_bt, udp_src);
-+ sbrec_bfd_set_status(sb_bt, nb_bt->status);
-+
-+ int min_tx = nb_bt->n_min_tx ? nb_bt->min_tx[0] : BFD_DEF_MINTX;
-+ sbrec_bfd_set_min_tx(sb_bt, min_tx);
-+ int min_rx = nb_bt->n_min_rx ? nb_bt->min_rx[0] : BFD_DEF_MINRX;
-+ sbrec_bfd_set_min_rx(sb_bt, min_rx);
-+ int d_mult = nb_bt->n_detect_mult ? nb_bt->detect_mult[0]
-+ : BFD_DEF_DETECT_MULT;
-+ sbrec_bfd_set_detect_mult(sb_bt, d_mult);
-+ } else if (strcmp(bfd_e->sb_bt->status, nb_bt->status)) {
-+ if (!strcmp(nb_bt->status, "admin_down") ||
-+ !strcmp(bfd_e->sb_bt->status, "admin_down")) {
-+ sbrec_bfd_set_status(bfd_e->sb_bt, nb_bt->status);
-+ } else {
-+ nbrec_bfd_set_status(nb_bt, bfd_e->sb_bt->status);
-+ }
-+ }
-+ if (bfd_e) {
-+ build_bfd_update_sb_conf(nb_bt, bfd_e->sb_bt);
-+
-+ hmap_remove(&sb_only, &bfd_e->hmap_node);
-+ bfd_e->ref = false;
-+ hash = hash_string(bfd_e->sb_bt->dst_ip, 0);
-+ hash = hash_string(bfd_e->sb_bt->logical_port, hash);
-+ hmap_insert(bfd_connections, &bfd_e->hmap_node, hash);
-+ }
-+
-+ struct ovn_port *op = ovn_port_find(ports, nb_bt->logical_port);
-+ if (op) {
-+ op->has_bfd = true;
-+ }
- }
--}
-
-+ HMAP_FOR_EACH_POP (bfd_e, hmap_node, &sb_only) {
-+ struct ovn_port *op = ovn_port_find(ports, bfd_e->sb_bt->logical_port);
-+ if (op) {
-+ op->has_bfd = false;
-+ }
-+ sbrec_bfd_delete(bfd_e->sb_bt);
-+ free(bfd_e);
-+ }
-+ hmap_destroy(&sb_only);
-+
-+ bitmap_free(bfd_src_ports);
-+}
-
- /* Returns a string of the IP address of the router port 'op' that
- * overlaps with 'ip_s". If one is not found, returns NULL.
-@@ -7549,33 +8004,39 @@ build_routing_policy_flow(struct hmap *lflows, struct ovn_datapath *od,
- struct ds actions = DS_EMPTY_INITIALIZER;
-
- if (!strcmp(rule->action, "reroute")) {
-+ ovs_assert(rule->n_nexthops <= 1);
-+
-+ char *nexthop =
-+ (rule->n_nexthops == 1 ? rule->nexthops[0] : rule->nexthop);
- struct ovn_port *out_port = get_outport_for_routing_policy_nexthop(
-- od, ports, rule->priority, rule->nexthop);
-+ od, ports, rule->priority, nexthop);
- if (!out_port) {
- return;
- }
-
-- const char *lrp_addr_s = find_lrp_member_ip(out_port, rule->nexthop);
-+ const char *lrp_addr_s = find_lrp_member_ip(out_port, nexthop);
- if (!lrp_addr_s) {
- static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
- VLOG_WARN_RL(&rl, "lrp_addr not found for routing policy "
- " priority %"PRId64" nexthop %s",
-- rule->priority, rule->nexthop);
-+ rule->priority, nexthop);
- return;
- }
- uint32_t pkt_mark = ovn_smap_get_uint(&rule->options, "pkt_mark", 0);
- if (pkt_mark) {
- ds_put_format(&actions, "pkt.mark = %u; ", pkt_mark);
- }
-- bool is_ipv4 = strchr(rule->nexthop, '.') ? true : false;
-+
-+ bool is_ipv4 = strchr(nexthop, '.') ? true : false;
- ds_put_format(&actions, "%s = %s; "
- "%s = %s; "
- "eth.src = %s; "
- "outport = %s; "
- "flags.loopback = 1; "
-+ REG_ECMP_GROUP_ID" = 0; "
- "next;",
- is_ipv4 ? REG_NEXT_HOP_IPV4 : REG_NEXT_HOP_IPV6,
-- rule->nexthop,
-+ nexthop,
- is_ipv4 ? REG_SRC_IPV4 : REG_SRC_IPV6,
- lrp_addr_s,
- out_port->lrp_networks.ea_s,
-@@ -7588,7 +8049,7 @@ build_routing_policy_flow(struct hmap *lflows, struct ovn_datapath *od,
- if (pkt_mark) {
- ds_put_format(&actions, "pkt.mark = %u; ", pkt_mark);
- }
-- ds_put_cstr(&actions, "next;");
-+ ds_put_cstr(&actions, REG_ECMP_GROUP_ID" = 0; next;");
- }
- ds_put_format(&match, "%s", rule->match);
-
-@@ -7598,15 +8059,116 @@ build_routing_policy_flow(struct hmap *lflows, struct ovn_datapath *od,
- ds_destroy(&actions);
- }
-
--struct parsed_route {
-- struct ovs_list list_node;
-- struct in6_addr prefix;
-- unsigned int plen;
-- bool is_src_route;
-- uint32_t hash;
-- const struct nbrec_logical_router_static_route *route;
-- bool ecmp_symmetric_reply;
--};
-+static void
-+build_ecmp_routing_policy_flows(struct hmap *lflows, struct ovn_datapath *od,
-+ struct hmap *ports,
-+ const struct nbrec_logical_router_policy *rule,
-+ uint16_t ecmp_group_id)
-+{
-+ ovs_assert(rule->n_nexthops > 1);
-+
-+ bool nexthops_is_ipv4 = true;
-+
-+ /* Check that all the nexthops belong to the same addr family before
-+ * adding logical flows. */
-+ for (uint16_t i = 0; i < rule->n_nexthops; i++) {
-+ bool is_ipv4 = strchr(rule->nexthops[i], '.') ? true : false;
-+
-+ if (i == 0) {
-+ nexthops_is_ipv4 = is_ipv4;
-+ }
-+
-+ if (is_ipv4 != nexthops_is_ipv4) {
-+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-+ VLOG_WARN_RL(&rl, "nexthop [%s] of the router policy with "
-+ "the match [%s] do not belong to the same address "
-+ "family as other next hops",
-+ rule->nexthops[i], rule->match);
-+ return;
-+ }
-+ }
-+
-+ struct ds match = DS_EMPTY_INITIALIZER;
-+ struct ds actions = DS_EMPTY_INITIALIZER;
-+
-+ for (size_t i = 0; i < rule->n_nexthops; i++) {
-+ struct ovn_port *out_port = get_outport_for_routing_policy_nexthop(
-+ od, ports, rule->priority, rule->nexthops[i]);
-+ if (!out_port) {
-+ goto cleanup;
-+ }
-+
-+ const char *lrp_addr_s =
-+ find_lrp_member_ip(out_port, rule->nexthops[i]);
-+ if (!lrp_addr_s) {
-+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-+ VLOG_WARN_RL(&rl, "lrp_addr not found for routing policy "
-+ " priority %"PRId64" nexthop %s",
-+ rule->priority, rule->nexthops[i]);
-+ goto cleanup;
-+ }
-+
-+ ds_clear(&actions);
-+ uint32_t pkt_mark = ovn_smap_get_uint(&rule->options, "pkt_mark", 0);
-+ if (pkt_mark) {
-+ ds_put_format(&actions, "pkt.mark = %u; ", pkt_mark);
-+ }
-+
-+ bool is_ipv4 = strchr(rule->nexthops[i], '.') ? true : false;
-+
-+ ds_put_format(&actions, "%s = %s; "
-+ "%s = %s; "
-+ "eth.src = %s; "
-+ "outport = %s; "
-+ "flags.loopback = 1; "
-+ "next;",
-+ is_ipv4 ? REG_NEXT_HOP_IPV4 : REG_NEXT_HOP_IPV6,
-+ rule->nexthops[i],
-+ is_ipv4 ? REG_SRC_IPV4 : REG_SRC_IPV6,
-+ lrp_addr_s,
-+ out_port->lrp_networks.ea_s,
-+ out_port->json_key);
-+
-+ ds_clear(&match);
-+ ds_put_format(&match, REG_ECMP_GROUP_ID" == %"PRIu16" && "
-+ REG_ECMP_MEMBER_ID" == %"PRIuSIZE,
-+ ecmp_group_id, i + 1);
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_POLICY_ECMP,
-+ 100, ds_cstr(&match),
-+ ds_cstr(&actions), &rule->header_);
-+ }
-+
-+ ds_clear(&actions);
-+ 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 < rule->n_nexthops; i++) {
-+ if (i > 0) {
-+ ds_put_cstr(&actions, ", ");
-+ }
-+
-+ ds_put_format(&actions, "%"PRIuSIZE, i + 1);
-+ }
-+ ds_put_cstr(&actions, ");");
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_POLICY,
-+ rule->priority, rule->match,
-+ ds_cstr(&actions), &rule->header_);
-+
-+cleanup:
-+ ds_destroy(&match);
-+ ds_destroy(&actions);
-+}
-+
-+struct parsed_route {
-+ struct ovs_list list_node;
-+ struct in6_addr prefix;
-+ unsigned int plen;
-+ bool is_src_route;
-+ uint32_t hash;
-+ const struct nbrec_logical_router_static_route *route;
-+ bool ecmp_symmetric_reply;
-+};
-
- static uint32_t
- route_hash(struct parsed_route *route)
-@@ -7619,7 +8181,8 @@ route_hash(struct parsed_route *route)
- * Otherwise return NULL. */
- static struct parsed_route *
- parsed_routes_add(struct ovs_list *routes,
-- const struct nbrec_logical_router_static_route *route)
-+ const struct nbrec_logical_router_static_route *route,
-+ struct hmap *bfd_connections)
- {
- /* Verify that the next hop is an IP address with an all-ones mask. */
- struct in6_addr nexthop;
-@@ -7660,6 +8223,25 @@ parsed_routes_add(struct ovs_list *routes,
- return NULL;
- }
-
-+ const struct nbrec_bfd *nb_bt = route->bfd;
-+ if (nb_bt && !strcmp(nb_bt->dst_ip, route->nexthop)) {
-+ struct bfd_entry *bfd_e;
-+
-+ bfd_e = bfd_port_lookup(bfd_connections, nb_bt->logical_port,
-+ nb_bt->dst_ip);
-+ if (bfd_e) {
-+ bfd_e->ref = true;
-+ }
-+
-+ if (!strcmp(nb_bt->status, "admin_down")) {
-+ nbrec_bfd_set_status(nb_bt, "down");
-+ }
-+
-+ if (!strcmp(nb_bt->status, "down")) {
-+ return NULL;
-+ }
-+ }
-+
- struct parsed_route *pr = xzalloc(sizeof *pr);
- pr->prefix = prefix;
- pr->plen = plen;
-@@ -8102,16 +8684,15 @@ add_route(struct hmap *lflows, const struct ovn_port *op,
- build_route_match(op_inport, network_s, plen, is_src_route, is_ipv4,
- &match, &priority);
-
-- struct ds actions = DS_EMPTY_INITIALIZER;
-- ds_put_format(&actions, "ip.ttl--; "REG_ECMP_GROUP_ID" = 0; %s = ",
-+ struct ds common_actions = DS_EMPTY_INITIALIZER;
-+ ds_put_format(&common_actions, REG_ECMP_GROUP_ID" = 0; %s = ",
- is_ipv4 ? REG_NEXT_HOP_IPV4 : REG_NEXT_HOP_IPV6);
--
- if (gateway) {
-- ds_put_cstr(&actions, gateway);
-+ ds_put_cstr(&common_actions, gateway);
- } else {
-- ds_put_format(&actions, "ip%s.dst", is_ipv4 ? "4" : "6");
-+ ds_put_format(&common_actions, "ip%s.dst", is_ipv4 ? "4" : "6");
- }
-- ds_put_format(&actions, "; "
-+ ds_put_format(&common_actions, "; "
- "%s = %s; "
- "eth.src = %s; "
- "outport = %s; "
-@@ -8121,11 +8702,20 @@ add_route(struct hmap *lflows, const struct ovn_port *op,
- lrp_addr_s,
- op->lrp_networks.ea_s,
- op->json_key);
-+ struct ds actions = DS_EMPTY_INITIALIZER;
-+ ds_put_format(&actions, "ip.ttl--; %s", ds_cstr(&common_actions));
-
- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_ROUTING, priority,
- ds_cstr(&match), ds_cstr(&actions),
- stage_hint);
-+ if (op->has_bfd) {
-+ ds_put_format(&match, " && udp.dst == 3784");
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_ROUTING,
-+ priority + 1, ds_cstr(&match),
-+ ds_cstr(&common_actions), stage_hint);
-+ }
- ds_destroy(&match);
-+ ds_destroy(&common_actions);
- ds_destroy(&actions);
- }
-
-@@ -8203,15 +8793,10 @@ get_force_snat_ip(struct ovn_datapath *od, const char *key_type,
- return false;
- }
-
-- if (!extract_ip_addresses(addresses, laddrs) ||
-- laddrs->n_ipv4_addrs > 1 ||
-- laddrs->n_ipv6_addrs > 1 ||
-- (laddrs->n_ipv4_addrs && laddrs->ipv4_addrs[0].plen != 32) ||
-- (laddrs->n_ipv6_addrs && laddrs->ipv6_addrs[0].plen != 128)) {
-+ if (!extract_ip_address(addresses, laddrs)) {
- static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
- VLOG_WARN_RL(&rl, "bad ip %s in options of router "UUID_FMT"",
- addresses, UUID_ARGS(&od->key));
-- destroy_lport_addresses(laddrs);
- return false;
- }
-
-@@ -8221,7 +8806,7 @@ get_force_snat_ip(struct ovn_datapath *od, const char *key_type,
- static void
- add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od,
- struct ds *match, struct ds *actions, int priority,
-- bool lb_force_snat_ip, struct ovn_lb_vip *lb_vip,
-+ bool force_snat_for_lb, struct ovn_lb_vip *lb_vip,
- const char *proto, struct nbrec_load_balancer *lb,
- struct shash *meter_groups, struct sset *nat_entries)
- {
-@@ -8230,7 +8815,7 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od,
-
- /* A match and actions for new connections. */
- char *new_match = xasprintf("ct.new && %s", ds_cstr(match));
-- if (lb_force_snat_ip) {
-+ if (force_snat_for_lb) {
- char *new_actions = xasprintf("flags.force_snat_for_lb = 1; %s",
- ds_cstr(actions));
- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, priority,
-@@ -8243,7 +8828,7 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od,
-
- /* A match and actions for established connections. */
- char *est_match = xasprintf("ct.est && %s", ds_cstr(match));
-- if (lb_force_snat_ip) {
-+ if (force_snat_for_lb) {
- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, priority,
- est_match,
- "flags.force_snat_for_lb = 1; ct_dnat;",
-@@ -8320,7 +8905,7 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od,
- ds_put_format(&undnat_match, ") && outport == %s && "
- "is_chassis_resident(%s)", od->l3dgw_port->json_key,
- od->l3redirect_port->json_key);
-- if (lb_force_snat_ip) {
-+ if (force_snat_for_lb) {
- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 120,
- ds_cstr(&undnat_match),
- "flags.force_snat_for_lb = 1; ct_dnat;",
-@@ -8788,2375 +9373,2531 @@ build_lrouter_force_snat_flows(struct hmap *lflows, struct ovn_datapath *od,
- }
-
- static void
--build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
-- struct hmap *lflows, struct shash *meter_groups,
-- struct hmap *lbs)
-+build_lrouter_force_snat_flows_op(struct ovn_port *op,
-+ struct hmap *lflows,
-+ struct ds *match, struct ds *actions)
- {
-- /* This flow table structure is documented in ovn-northd(8), so please
-- * update ovn-northd.8.xml if you change anything. */
-+ if (!op->nbrp || !op->peer || !op->od->lb_force_snat_router_ip) {
-+ return;
-+ }
-
-- struct ds match = DS_EMPTY_INITIALIZER;
-- struct ds actions = DS_EMPTY_INITIALIZER;
-+ if (op->lrp_networks.n_ipv4_addrs) {
-+ ds_clear(match);
-+ ds_clear(actions);
-
-- struct ovn_datapath *od;
-- struct ovn_port *op;
-+ ds_put_format(match, "inport == %s && ip4.dst == %s",
-+ op->json_key, op->lrp_networks.ipv4_addrs[0].addr_s);
-+ ovn_lflow_add(lflows, op->od, S_ROUTER_IN_UNSNAT, 110,
-+ ds_cstr(match), "ct_snat;");
-
-- HMAP_FOR_EACH (od, key_node, datapaths) {
-- if (!od->nbr) {
-- continue;
-- }
-+ ds_clear(match);
-
-- /* Priority-90-92 flows handle ARP requests and ND packets. Most are
-- * per logical port but DNAT addresses can be handled per datapath
-- * for non gateway router ports.
-- *
-- * Priority 91 and 92 flows are added for each gateway router
-- * port to handle the special cases. In case we get the packet
-- * on a regular port, just reply with the port's ETH address.
-- */
-- for (int i = 0; i < od->nbr->n_nat; i++) {
-- struct ovn_nat *nat_entry = &od->nat_entries[i];
-+ /* Higher priority rules to force SNAT with the router port ip.
-+ * This only takes effect when the packet has already been
-+ * load balanced once. */
-+ ds_put_format(match, "flags.force_snat_for_lb == 1 && ip4 && "
-+ "outport == %s", op->json_key);
-+ ds_put_format(actions, "ct_snat(%s);",
-+ op->lrp_networks.ipv4_addrs[0].addr_s);
-+ ovn_lflow_add(lflows, op->od, S_ROUTER_OUT_SNAT, 110,
-+ ds_cstr(match), ds_cstr(actions));
-+ if (op->lrp_networks.n_ipv4_addrs > 2) {
-+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-+ VLOG_WARN_RL(&rl, "Logical router port %s is configured with "
-+ "multiple IPv4 addresses. Only the first "
-+ "IP [%s] is considered as SNAT for load "
-+ "balancer", op->json_key,
-+ op->lrp_networks.ipv4_addrs[0].addr_s);
-+ }
-+ }
-+
-+ /* op->lrp_networks.ipv6_addrs will always have LLA and that will be
-+ * last in the list. So add the flows only if n_ipv6_addrs > 1. */
-+ if (op->lrp_networks.n_ipv6_addrs > 1) {
-+ ds_clear(match);
-+ ds_clear(actions);
-
-- /* Skip entries we failed to parse. */
-- if (!nat_entry_is_valid(nat_entry)) {
-- continue;
-- }
-+ ds_put_format(match, "inport == %s && ip6.dst == %s",
-+ op->json_key, op->lrp_networks.ipv6_addrs[0].addr_s);
-+ ovn_lflow_add(lflows, op->od, S_ROUTER_IN_UNSNAT, 110,
-+ ds_cstr(match), "ct_snat;");
-
-- /* Skip SNAT entries for now, we handle unique SNAT IPs separately
-- * below.
-- */
-- if (!strcmp(nat_entry->nb->type, "snat")) {
-- continue;
-- }
-- build_lrouter_nat_arp_nd_flow(od, nat_entry, lflows);
-+ ds_clear(match);
-+
-+ /* Higher priority rules to force SNAT with the router port ip.
-+ * This only takes effect when the packet has already been
-+ * load balanced once. */
-+ ds_put_format(match, "flags.force_snat_for_lb == 1 && ip6 && "
-+ "outport == %s", op->json_key);
-+ ds_put_format(actions, "ct_snat(%s);",
-+ op->lrp_networks.ipv6_addrs[0].addr_s);
-+ ovn_lflow_add(lflows, op->od, S_ROUTER_OUT_SNAT, 110,
-+ ds_cstr(match), ds_cstr(actions));
-+ if (op->lrp_networks.n_ipv6_addrs > 2) {
-+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-+ VLOG_WARN_RL(&rl, "Logical router port %s is configured with "
-+ "multiple IPv6 addresses. Only the first "
-+ "IP [%s] is considered as SNAT for load "
-+ "balancer", op->json_key,
-+ op->lrp_networks.ipv6_addrs[0].addr_s);
- }
-+ }
-+}
-
-- /* Now handle SNAT entries too, one per unique SNAT IP. */
-- struct shash_node *snat_snode;
-- SHASH_FOR_EACH (snat_snode, &od->snat_ips) {
-- struct ovn_snat_ip *snat_ip = snat_snode->data;
-+static void
-+build_lrouter_bfd_flows(struct hmap *lflows, struct ovn_port *op)
-+{
-+ if (!op->has_bfd) {
-+ return;
-+ }
-
-- if (ovs_list_is_empty(&snat_ip->snat_entries)) {
-- continue;
-- }
-+ struct ds ip_list = DS_EMPTY_INITIALIZER;
-+ struct ds match = DS_EMPTY_INITIALIZER;
-
-- struct ovn_nat *nat_entry =
-- CONTAINER_OF(ovs_list_front(&snat_ip->snat_entries),
-- struct ovn_nat, ext_addr_list_node);
-- build_lrouter_nat_arp_nd_flow(od, nat_entry, lflows);
-- }
-+ if (op->lrp_networks.n_ipv4_addrs) {
-+ op_put_v4_networks(&ip_list, op, false);
-+ ds_put_format(&match, "ip4.src == %s && udp.dst == 3784",
-+ ds_cstr(&ip_list));
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 110,
-+ ds_cstr(&match), "next; ",
-+ &op->nbrp->header_);
-+ ds_clear(&match);
-+ ds_put_format(&match, "ip4.dst == %s && udp.dst == 3784",
-+ ds_cstr(&ip_list));
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 110,
-+ ds_cstr(&match), "handle_bfd_msg(); ",
-+ &op->nbrp->header_);
- }
-+ if (op->lrp_networks.n_ipv6_addrs) {
-+ ds_clear(&ip_list);
-+ ds_clear(&match);
-
-- /* Logical router ingress table 3: IP Input for IPv4. */
-- HMAP_FOR_EACH (op, key_node, ports) {
-- if (!op->nbrp) {
-- continue;
-+ op_put_v6_networks(&ip_list, op);
-+ ds_put_format(&match, "ip6.src == %s && udp.dst == 3784",
-+ ds_cstr(&ip_list));
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 110,
-+ ds_cstr(&match), "next; ",
-+ &op->nbrp->header_);
-+ ds_clear(&match);
-+ ds_put_format(&match, "ip6.dst == %s && udp.dst == 3784",
-+ ds_cstr(&ip_list));
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 110,
-+ ds_cstr(&match), "handle_bfd_msg(); ",
-+ &op->nbrp->header_);
-+ }
-+
-+ ds_destroy(&ip_list);
-+ ds_destroy(&match);
-+}
-+
-+/* Logical router ingress Table 0: L2 Admission Control
-+ * Generic admission control flows (without inport check).
-+ */
-+static void
-+build_adm_ctrl_flows_for_lrouter(
-+ struct ovn_datapath *od, struct hmap *lflows)
-+{
-+ if (od->nbr) {
-+ /* Logical VLANs not supported.
-+ * Broadcast/multicast source address is invalid. */
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ADMISSION, 100,
-+ "vlan.present || eth.src[40]", "drop;");
-+ }
-+}
-+
-+/* Logical router ingress Table 0: L2 Admission Control
-+ * This table drops packets that the router shouldn’t see at all based
-+ * on their Ethernet headers.
-+ */
-+static void
-+build_adm_ctrl_flows_for_lrouter_port(
-+ struct ovn_port *op, struct hmap *lflows,
-+ struct ds *match, struct ds *actions)
-+{
-+ if (op->nbrp) {
-+ if (!lrport_is_enabled(op->nbrp)) {
-+ /* Drop packets from disabled logical ports (since logical flow
-+ * tables are default-drop). */
-+ return;
- }
-
- if (op->derived) {
-- /* No ingress packets are accepted on a chassisredirect
-- * port, so no need to program flows for that port. */
-- continue;
-+ /* No ingress packets should be received on a chassisredirect
-+ * port. */
-+ return;
- }
-
-- if (op->lrp_networks.n_ipv4_addrs) {
-- /* L3 admission control: drop packets that originate from an
-- * IPv4 address owned by the router or a broadcast address
-- * known to the router (priority 100). */
-- ds_clear(&match);
-- ds_put_cstr(&match, "ip4.src == ");
-- op_put_v4_networks(&match, op, true);
-- ds_put_cstr(&match, " && "REGBIT_EGRESS_LOOPBACK" == 0");
-- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 100,
-- ds_cstr(&match), "drop;",
-- &op->nbrp->header_);
-+ /* Store the ethernet address of the port receiving the packet.
-+ * This will save us from having to match on inport further down in
-+ * the pipeline.
-+ */
-+ ds_clear(actions);
-+ ds_put_format(actions, REG_INPORT_ETH_ADDR " = %s; next;",
-+ op->lrp_networks.ea_s);
-
-- /* ICMP echo reply. These flows reply to ICMP echo requests
-- * received for the router's IP address. Since packets only
-- * get here as part of the logical router datapath, the inport
-- * (i.e. the incoming locally attached net) does not matter.
-- * The ip.ttl also does not matter (RFC1812 section 4.2.2.9) */
-- ds_clear(&match);
-- ds_put_cstr(&match, "ip4.dst == ");
-- op_put_v4_networks(&match, op, false);
-- ds_put_cstr(&match, " && icmp4.type == 8 && icmp4.code == 0");
-+ ds_clear(match);
-+ ds_put_format(match, "eth.mcast && inport == %s", op->json_key);
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_ADMISSION, 50,
-+ ds_cstr(match), ds_cstr(actions),
-+ &op->nbrp->header_);
-
-- const char * icmp_actions = "ip4.dst <-> ip4.src; "
-- "ip.ttl = 255; "
-- "icmp4.type = 0; "
-- "flags.loopback = 1; "
-- "next; ";
-- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90,
-- ds_cstr(&match), icmp_actions,
-- &op->nbrp->header_);
-+ ds_clear(match);
-+ ds_put_format(match, "eth.dst == %s && inport == %s",
-+ op->lrp_networks.ea_s, op->json_key);
-+ if (op->od->l3dgw_port && op == op->od->l3dgw_port
-+ && op->od->l3redirect_port) {
-+ /* Traffic with eth.dst = l3dgw_port->lrp_networks.ea_s
-+ * should only be received on the gateway chassis. */
-+ ds_put_format(match, " && is_chassis_resident(%s)",
-+ op->od->l3redirect_port->json_key);
- }
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_ADMISSION, 50,
-+ ds_cstr(match), ds_cstr(actions),
-+ &op->nbrp->header_);
-+ }
-+}
-
-- /* ICMP time exceeded */
-- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
-- ds_clear(&match);
-- ds_clear(&actions);
-
-- ds_put_format(&match,
-- "inport == %s && ip4 && "
-- "ip.ttl == {0, 1} && !ip.later_frag", op->json_key);
-- ds_put_format(&actions,
-- "icmp4 {"
-- "eth.dst <-> eth.src; "
-- "icmp4.type = 11; /* Time exceeded */ "
-- "icmp4.code = 0; /* TTL exceeded in transit */ "
-- "ip4.dst = ip4.src; "
-- "ip4.src = %s; "
-- "ip.ttl = 255; "
-- "next; };",
-- op->lrp_networks.ipv4_addrs[i].addr_s);
-- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 40,
-- ds_cstr(&match), ds_cstr(&actions),
-- &op->nbrp->header_);
-- }
-+/* Logical router ingress Table 1 and 2: Neighbor lookup and learning
-+ * lflows for logical routers. */
-+static void
-+build_neigh_learning_flows_for_lrouter(
-+ struct ovn_datapath *od, struct hmap *lflows,
-+ struct ds *match, struct ds *actions)
-+{
-+ if (od->nbr) {
-
-- /* ARP reply. These flows reply to ARP requests for the router's own
-- * IP address. */
-- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
-- ds_clear(&match);
-- ds_put_format(&match, "arp.spa == %s/%u",
-- op->lrp_networks.ipv4_addrs[i].network_s,
-- op->lrp_networks.ipv4_addrs[i].plen);
-+ /* Learn MAC bindings from ARP/IPv6 ND.
-+ *
-+ * For ARP packets, table LOOKUP_NEIGHBOR does a lookup for the
-+ * (arp.spa, arp.sha) in the mac binding table using the 'lookup_arp'
-+ * action and stores the result in REGBIT_LOOKUP_NEIGHBOR_RESULT bit.
-+ * If "always_learn_from_arp_request" is set to false, it will also
-+ * lookup for the (arp.spa) in the mac binding table using the
-+ * "lookup_arp_ip" action for ARP request packets, and stores the
-+ * result in REGBIT_LOOKUP_NEIGHBOR_IP_RESULT bit; or set that bit
-+ * to "1" directly for ARP response packets.
-+ *
-+ * For IPv6 ND NA packets, table LOOKUP_NEIGHBOR does a lookup
-+ * for the (nd.target, nd.tll) in the mac binding table using the
-+ * 'lookup_nd' action and stores the result in
-+ * REGBIT_LOOKUP_NEIGHBOR_RESULT bit. If
-+ * "always_learn_from_arp_request" is set to false,
-+ * REGBIT_LOOKUP_NEIGHBOR_IP_RESULT bit is set.
-+ *
-+ * For IPv6 ND NS packets, table LOOKUP_NEIGHBOR does a lookup
-+ * for the (ip6.src, nd.sll) in the mac binding table using the
-+ * 'lookup_nd' action and stores the result in
-+ * REGBIT_LOOKUP_NEIGHBOR_RESULT bit. If
-+ * "always_learn_from_arp_request" is set to false, it will also lookup
-+ * for the (ip6.src) in the mac binding table using the "lookup_nd_ip"
-+ * action and stores the result in REGBIT_LOOKUP_NEIGHBOR_IP_RESULT
-+ * bit.
-+ *
-+ * Table LEARN_NEIGHBOR learns the mac-binding using the action
-+ * - 'put_arp/put_nd'. Learning mac-binding is skipped if
-+ * REGBIT_LOOKUP_NEIGHBOR_RESULT bit is set or
-+ * REGBIT_LOOKUP_NEIGHBOR_IP_RESULT is not set.
-+ *
-+ * */
-
-- if (op->od->l3dgw_port && op->od->l3redirect_port && op->peer
-- && op->peer->od->n_localnet_ports) {
-- bool add_chassis_resident_check = false;
-- if (op == op->od->l3dgw_port) {
-- /* Traffic with eth.src = l3dgw_port->lrp_networks.ea_s
-- * should only be sent from the gateway chassis, so that
-- * upstream MAC learning points to the gateway chassis.
-- * Also need to avoid generation of multiple ARP responses
-- * from different chassis. */
-- add_chassis_resident_check = true;
-- } else {
-- /* Check if the option 'reside-on-redirect-chassis'
-- * is set to true on the router port. If set to true
-- * and if peer's logical switch has a localnet port, it
-- * means the router pipeline for the packets from
-- * peer's logical switch is be run on the chassis
-- * hosting the gateway port and it should reply to the
-- * ARP requests for the router port IPs.
-- */
-- add_chassis_resident_check = smap_get_bool(
-- &op->nbrp->options,
-- "reside-on-redirect-chassis", false);
-- }
-+ /* Flows for LOOKUP_NEIGHBOR. */
-+ bool learn_from_arp_request = smap_get_bool(&od->nbr->options,
-+ "always_learn_from_arp_request", true);
-+ ds_clear(actions);
-+ ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT
-+ " = lookup_arp(inport, arp.spa, arp.sha); %snext;",
-+ learn_from_arp_request ? "" :
-+ REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" = 1; ");
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100,
-+ "arp.op == 2", ds_cstr(actions));
-
-- if (add_chassis_resident_check) {
-- ds_put_format(&match, " && is_chassis_resident(%s)",
-- op->od->l3redirect_port->json_key);
-- }
-- }
-+ ds_clear(actions);
-+ ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT
-+ " = lookup_nd(inport, nd.target, nd.tll); %snext;",
-+ learn_from_arp_request ? "" :
-+ REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" = 1; ");
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100, "nd_na",
-+ ds_cstr(actions));
-
-- build_lrouter_arp_flow(op->od, op,
-- op->lrp_networks.ipv4_addrs[i].addr_s,
-- REG_INPORT_ETH_ADDR, &match, false, 90,
-- &op->nbrp->header_, lflows);
-- }
-+ ds_clear(actions);
-+ ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT
-+ " = lookup_nd(inport, ip6.src, nd.sll); %snext;",
-+ learn_from_arp_request ? "" :
-+ REGBIT_LOOKUP_NEIGHBOR_IP_RESULT
-+ " = lookup_nd_ip(inport, ip6.src); ");
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100, "nd_ns",
-+ ds_cstr(actions));
-
-- /* A set to hold all load-balancer vips that need ARP responses. */
-- struct sset all_ips_v4 = SSET_INITIALIZER(&all_ips_v4);
-- struct sset all_ips_v6 = SSET_INITIALIZER(&all_ips_v6);
-- get_router_load_balancer_ips(op->od, &all_ips_v4, &all_ips_v6);
-+ /* For other packet types, we can skip neighbor learning.
-+ * So set REGBIT_LOOKUP_NEIGHBOR_RESULT to 1. */
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 0, "1",
-+ REGBIT_LOOKUP_NEIGHBOR_RESULT" = 1; next;");
-
-- const char *ip_address;
-- SSET_FOR_EACH (ip_address, &all_ips_v4) {
-- ds_clear(&match);
-- if (op == op->od->l3dgw_port) {
-- ds_put_format(&match, "is_chassis_resident(%s)",
-- op->od->l3redirect_port->json_key);
-- }
-+ /* Flows for LEARN_NEIGHBOR. */
-+ /* Skip Neighbor learning if not required. */
-+ ds_clear(match);
-+ ds_put_format(match, REGBIT_LOOKUP_NEIGHBOR_RESULT" == 1%s",
-+ learn_from_arp_request ? "" :
-+ " || "REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" == 0");
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 100,
-+ ds_cstr(match), "next;");
-
-- build_lrouter_arp_flow(op->od, op,
-- ip_address, REG_INPORT_ETH_ADDR,
-- &match, false, 90, NULL, lflows);
-- }
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90,
-+ "arp", "put_arp(inport, arp.spa, arp.sha); next;");
-
-- SSET_FOR_EACH (ip_address, &all_ips_v6) {
-- ds_clear(&match);
-- if (op == op->od->l3dgw_port) {
-- ds_put_format(&match, "is_chassis_resident(%s)",
-- op->od->l3redirect_port->json_key);
-- }
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90,
-+ "nd_na", "put_nd(inport, nd.target, nd.tll); next;");
-
-- build_lrouter_nd_flow(op->od, op, "nd_na",
-- ip_address, NULL, REG_INPORT_ETH_ADDR,
-- &match, false, 90, NULL, lflows);
-- }
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90,
-+ "nd_ns", "put_nd(inport, ip6.src, nd.sll); next;");
-+ }
-
-- sset_destroy(&all_ips_v4);
-- sset_destroy(&all_ips_v6);
-+}
-
-- if (!smap_get(&op->od->nbr->options, "chassis")
-- && !op->od->l3dgw_port) {
-- /* UDP/TCP port unreachable. */
-- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
-- ds_clear(&match);
-- ds_put_format(&match,
-- "ip4 && ip4.dst == %s && !ip.later_frag && udp",
-- op->lrp_networks.ipv4_addrs[i].addr_s);
-- const char *action = "icmp4 {"
-- "eth.dst <-> eth.src; "
-- "ip4.dst <-> ip4.src; "
-- "ip.ttl = 255; "
-- "icmp4.type = 3; "
-- "icmp4.code = 3; "
-- "next; };";
-- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-- 80, ds_cstr(&match), action,
-- &op->nbrp->header_);
-+/* Logical router ingress Table 1: Neighbor lookup lflows
-+ * for logical router ports. */
-+static void
-+build_neigh_learning_flows_for_lrouter_port(
-+ struct ovn_port *op, struct hmap *lflows,
-+ struct ds *match, struct ds *actions)
-+{
-+ if (op->nbrp) {
-
-- ds_clear(&match);
-- ds_put_format(&match,
-- "ip4 && ip4.dst == %s && !ip.later_frag && tcp",
-- op->lrp_networks.ipv4_addrs[i].addr_s);
-- action = "tcp_reset {"
-- "eth.dst <-> eth.src; "
-- "ip4.dst <-> ip4.src; "
-- "next; };";
-- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-- 80, ds_cstr(&match), action,
-- &op->nbrp->header_);
-+ bool learn_from_arp_request = smap_get_bool(&op->od->nbr->options,
-+ "always_learn_from_arp_request", true);
-
-- ds_clear(&match);
-- ds_put_format(&match,
-- "ip4 && ip4.dst == %s && !ip.later_frag",
-+ /* Check if we need to learn mac-binding from ARP requests. */
-+ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
-+ if (!learn_from_arp_request) {
-+ /* ARP request to this address should always get learned,
-+ * so add a priority-110 flow to set
-+ * REGBIT_LOOKUP_NEIGHBOR_IP_RESULT to 1. */
-+ ds_clear(match);
-+ ds_put_format(match,
-+ "inport == %s && arp.spa == %s/%u && "
-+ "arp.tpa == %s && arp.op == 1",
-+ op->json_key,
-+ op->lrp_networks.ipv4_addrs[i].network_s,
-+ op->lrp_networks.ipv4_addrs[i].plen,
- op->lrp_networks.ipv4_addrs[i].addr_s);
-- action = "icmp4 {"
-- "eth.dst <-> eth.src; "
-- "ip4.dst <-> ip4.src; "
-- "ip.ttl = 255; "
-- "icmp4.type = 3; "
-- "icmp4.code = 2; "
-- "next; };";
-- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-- 70, ds_cstr(&match), action,
-+ if (op->od->l3dgw_port && op == op->od->l3dgw_port
-+ && op->od->l3redirect_port) {
-+ ds_put_format(match, " && is_chassis_resident(%s)",
-+ op->od->l3redirect_port->json_key);
-+ }
-+ const char *actions_s = REGBIT_LOOKUP_NEIGHBOR_RESULT
-+ " = lookup_arp(inport, arp.spa, arp.sha); "
-+ REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" = 1;"
-+ " next;";
-+ ovn_lflow_add_with_hint(lflows, op->od,
-+ S_ROUTER_IN_LOOKUP_NEIGHBOR, 110,
-+ ds_cstr(match), actions_s,
- &op->nbrp->header_);
- }
-+ ds_clear(match);
-+ ds_put_format(match,
-+ "inport == %s && arp.spa == %s/%u && arp.op == 1",
-+ op->json_key,
-+ op->lrp_networks.ipv4_addrs[i].network_s,
-+ op->lrp_networks.ipv4_addrs[i].plen);
-+ if (op->od->l3dgw_port && op == op->od->l3dgw_port
-+ && op->od->l3redirect_port) {
-+ ds_put_format(match, " && is_chassis_resident(%s)",
-+ op->od->l3redirect_port->json_key);
-+ }
-+ ds_clear(actions);
-+ ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT
-+ " = lookup_arp(inport, arp.spa, arp.sha); %snext;",
-+ learn_from_arp_request ? "" :
-+ REGBIT_LOOKUP_NEIGHBOR_IP_RESULT
-+ " = lookup_arp_ip(inport, arp.spa); ");
-+ ovn_lflow_add_with_hint(lflows, op->od,
-+ S_ROUTER_IN_LOOKUP_NEIGHBOR, 100,
-+ ds_cstr(match), ds_cstr(actions),
-+ &op->nbrp->header_);
- }
-+ }
-+}
-
-- /* Drop IP traffic destined to router owned IPs except if the IP is
-- * also a SNAT IP. Those are dropped later, in stage
-- * "lr_in_arp_resolve", if unSNAT was unsuccessful.
-- *
-- * Priority 60.
-- */
-- build_lrouter_drop_own_dest(op, S_ROUTER_IN_IP_INPUT, 60, false,
-- lflows);
--
-- /* ARP / ND handling for external IP addresses.
-- *
-- * DNAT and SNAT IP addresses are external IP addresses that need ARP
-- * handling.
-- *
-- * These are already taken care globally, per router. The only
-- * exception is on the l3dgw_port where we might need to use a
-- * different ETH address.
-- */
-- if (op != op->od->l3dgw_port) {
-- continue;
-- }
-+/* Logical router ingress table ND_RA_OPTIONS & ND_RA_RESPONSE: IPv6 Router
-+ * Adv (RA) options and response. */
-+static void
-+build_ND_RA_flows_for_lrouter_port(
-+ struct ovn_port *op, struct hmap *lflows,
-+ struct ds *match, struct ds *actions)
-+{
-+ if (!op->nbrp || op->nbrp->peer || !op->peer) {
-+ return;
-+ }
-
-- for (size_t i = 0; i < op->od->nbr->n_nat; i++) {
-- struct ovn_nat *nat_entry = &op->od->nat_entries[i];
-+ if (!op->lrp_networks.n_ipv6_addrs) {
-+ return;
-+ }
-
-- /* Skip entries we failed to parse. */
-- if (!nat_entry_is_valid(nat_entry)) {
-- continue;
-- }
-+ struct smap options;
-+ smap_clone(&options, &op->sb->options);
-
-- /* Skip SNAT entries for now, we handle unique SNAT IPs separately
-- * below.
-- */
-- if (!strcmp(nat_entry->nb->type, "snat")) {
-- continue;
-- }
-- build_lrouter_port_nat_arp_nd_flow(op, nat_entry, lflows);
-- }
-+ /* enable IPv6 prefix delegation */
-+ bool prefix_delegation = smap_get_bool(&op->nbrp->options,
-+ "prefix_delegation", false);
-+ if (!lrport_is_enabled(op->nbrp)) {
-+ prefix_delegation = false;
-+ }
-+ smap_add(&options, "ipv6_prefix_delegation",
-+ prefix_delegation ? "true" : "false");
-
-- /* Now handle SNAT entries too, one per unique SNAT IP. */
-- struct shash_node *snat_snode;
-- SHASH_FOR_EACH (snat_snode, &op->od->snat_ips) {
-- struct ovn_snat_ip *snat_ip = snat_snode->data;
-+ bool ipv6_prefix = smap_get_bool(&op->nbrp->options,
-+ "prefix", false);
-+ if (!lrport_is_enabled(op->nbrp)) {
-+ ipv6_prefix = false;
-+ }
-+ smap_add(&options, "ipv6_prefix",
-+ ipv6_prefix ? "true" : "false");
-+ sbrec_port_binding_set_options(op->sb, &options);
-
-- if (ovs_list_is_empty(&snat_ip->snat_entries)) {
-- continue;
-- }
-+ smap_destroy(&options);
-
-- struct ovn_nat *nat_entry =
-- CONTAINER_OF(ovs_list_front(&snat_ip->snat_entries),
-- struct ovn_nat, ext_addr_list_node);
-- build_lrouter_port_nat_arp_nd_flow(op, nat_entry, lflows);
-- }
-+ const char *address_mode = smap_get(
-+ &op->nbrp->ipv6_ra_configs, "address_mode");
-+
-+ if (!address_mode) {
-+ return;
-+ }
-+ if (strcmp(address_mode, "slaac") &&
-+ strcmp(address_mode, "dhcpv6_stateful") &&
-+ strcmp(address_mode, "dhcpv6_stateless")) {
-+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-+ VLOG_WARN_RL(&rl, "Invalid address mode [%s] defined",
-+ address_mode);
-+ return;
- }
-
-- /* NAT, Defrag and load balancing. */
-- HMAP_FOR_EACH (od, key_node, datapaths) {
-- if (!od->nbr) {
-- continue;
-- }
-+ if (smap_get_bool(&op->nbrp->ipv6_ra_configs, "send_periodic",
-+ false)) {
-+ copy_ra_to_sb(op, address_mode);
-+ }
-
-- /* Packets are allowed by default. */
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_DEFRAG, 0, "1", "next;");
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 0, "1", "next;");
-- ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 0, "1", "next;");
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 0, "1", "next;");
-- ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 0, "1", "next;");
-- ovn_lflow_add(lflows, od, S_ROUTER_OUT_EGR_LOOP, 0, "1", "next;");
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 0, "1", "next;");
-+ ds_clear(match);
-+ ds_put_format(match, "inport == %s && ip6.dst == ff02::2 && nd_rs",
-+ op->json_key);
-+ ds_clear(actions);
-
-- /* Send the IPv6 NS packets to next table. When ovn-controller
-- * generates IPv6 NS (for the action - nd_ns{}), the injected
-- * packet would go through conntrack - which is not required. */
-- ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 120, "nd_ns", "next;");
-+ const char *mtu_s = smap_get(
-+ &op->nbrp->ipv6_ra_configs, "mtu");
-
-- /* NAT rules are only valid on Gateway routers and routers with
-- * l3dgw_port (router has a port with gateway chassis
-- * specified). */
-- if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) {
-- continue;
-- }
-+ /* As per RFC 2460, 1280 is minimum IPv6 MTU. */
-+ uint32_t mtu = (mtu_s && atoi(mtu_s) >= 1280) ? atoi(mtu_s) : 0;
-
-- struct sset nat_entries = SSET_INITIALIZER(&nat_entries);
-+ ds_put_format(actions, REGBIT_ND_RA_OPTS_RESULT" = put_nd_ra_opts("
-+ "addr_mode = \"%s\", slla = %s",
-+ address_mode, op->lrp_networks.ea_s);
-+ if (mtu > 0) {
-+ ds_put_format(actions, ", mtu = %u", mtu);
-+ }
-
-- bool dnat_force_snat_ip =
-- !lport_addresses_is_empty(&od->dnat_force_snat_addrs);
-- bool lb_force_snat_ip =
-- !lport_addresses_is_empty(&od->lb_force_snat_addrs);
-+ const char *prf = smap_get_def(
-+ &op->nbrp->ipv6_ra_configs, "router_preference", "MEDIUM");
-+ if (strcmp(prf, "MEDIUM")) {
-+ ds_put_format(actions, ", router_preference = \"%s\"", prf);
-+ }
-
-- for (int i = 0; i < od->nbr->n_nat; i++) {
-- const struct nbrec_nat *nat;
-+ bool add_rs_response_flow = false;
-
-- nat = od->nbr->nat[i];
-+ for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
-+ if (in6_is_lla(&op->lrp_networks.ipv6_addrs[i].network)) {
-+ continue;
-+ }
-
-- ovs_be32 ip, mask;
-- struct in6_addr ipv6, mask_v6, v6_exact = IN6ADDR_EXACT_INIT;
-- bool is_v6 = false;
-- bool stateless = lrouter_nat_is_stateless(nat);
-- struct nbrec_address_set *allowed_ext_ips =
-- nat->allowed_ext_ips;
-- struct nbrec_address_set *exempted_ext_ips =
-- nat->exempted_ext_ips;
-+ ds_put_format(actions, ", prefix = %s/%u",
-+ op->lrp_networks.ipv6_addrs[i].network_s,
-+ op->lrp_networks.ipv6_addrs[i].plen);
-
-- if (allowed_ext_ips && exempted_ext_ips) {
-- static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-- VLOG_WARN_RL(&rl, "NAT rule: "UUID_FMT" not applied, since "
-- "both allowed and exempt external ips set",
-- UUID_ARGS(&(nat->header_.uuid)));
-- continue;
-- }
-+ add_rs_response_flow = true;
-+ }
-
-- char *error = ip_parse_masked(nat->external_ip, &ip, &mask);
-- if (error || mask != OVS_BE32_MAX) {
-- free(error);
-- error = ipv6_parse_masked(nat->external_ip, &ipv6, &mask_v6);
-- if (error || memcmp(&mask_v6, &v6_exact, sizeof(mask_v6))) {
-- /* Invalid for both IPv4 and IPv6 */
-- static struct vlog_rate_limit rl =
-- VLOG_RATE_LIMIT_INIT(5, 1);
-- VLOG_WARN_RL(&rl, "bad external ip %s for nat",
-- nat->external_ip);
-- free(error);
-- continue;
-- }
-- /* It was an invalid IPv4 address, but valid IPv6.
-- * Treat the rest of the handling of this NAT rule
-- * as IPv6. */
-- is_v6 = true;
-- }
-+ if (add_rs_response_flow) {
-+ ds_put_cstr(actions, "); next;");
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_ND_RA_OPTIONS,
-+ 50, ds_cstr(match), ds_cstr(actions),
-+ &op->nbrp->header_);
-+ ds_clear(actions);
-+ ds_clear(match);
-+ ds_put_format(match, "inport == %s && ip6.dst == ff02::2 && "
-+ "nd_ra && "REGBIT_ND_RA_OPTS_RESULT, op->json_key);
-
-- /* Check the validity of nat->logical_ip. 'logical_ip' can
-- * be a subnet when the type is "snat". */
-- int cidr_bits;
-- if (is_v6) {
-- error = ipv6_parse_masked(nat->logical_ip, &ipv6, &mask_v6);
-- cidr_bits = ipv6_count_cidr_bits(&mask_v6);
-- } else {
-- error = ip_parse_masked(nat->logical_ip, &ip, &mask);
-- cidr_bits = ip_count_cidr_bits(mask);
-- }
-- if (!strcmp(nat->type, "snat")) {
-- if (error) {
-- /* Invalid for both IPv4 and IPv6 */
-- static struct vlog_rate_limit rl =
-- VLOG_RATE_LIMIT_INIT(5, 1);
-- VLOG_WARN_RL(&rl, "bad ip network or ip %s for snat "
-- "in router "UUID_FMT"",
-- nat->logical_ip, UUID_ARGS(&od->key));
-- free(error);
-- continue;
-- }
-- } else {
-- if (error || (!is_v6 && mask != OVS_BE32_MAX)
-- || (is_v6 && memcmp(&mask_v6, &v6_exact,
-- sizeof mask_v6))) {
-- /* Invalid for both IPv4 and IPv6 */
-- static struct vlog_rate_limit rl =
-- VLOG_RATE_LIMIT_INIT(5, 1);
-- VLOG_WARN_RL(&rl, "bad ip %s for dnat in router "
-- ""UUID_FMT"", nat->logical_ip, UUID_ARGS(&od->key));
-- free(error);
-- continue;
-- }
-- }
-+ char ip6_str[INET6_ADDRSTRLEN + 1];
-+ struct in6_addr lla;
-+ in6_generate_lla(op->lrp_networks.ea, &lla);
-+ memset(ip6_str, 0, sizeof(ip6_str));
-+ ipv6_string_mapped(ip6_str, &lla);
-+ ds_put_format(actions, "eth.dst = eth.src; eth.src = %s; "
-+ "ip6.dst = ip6.src; ip6.src = %s; "
-+ "outport = inport; flags.loopback = 1; "
-+ "output;",
-+ op->lrp_networks.ea_s, ip6_str);
-+ ovn_lflow_add_with_hint(lflows, op->od,
-+ S_ROUTER_IN_ND_RA_RESPONSE, 50,
-+ ds_cstr(match), ds_cstr(actions),
-+ &op->nbrp->header_);
-+ }
-+}
-
-- /* For distributed router NAT, determine whether this NAT rule
-- * satisfies the conditions for distributed NAT processing. */
-- bool distributed = false;
-- struct eth_addr mac;
-- if (od->l3dgw_port && !strcmp(nat->type, "dnat_and_snat") &&
-- nat->logical_port && nat->external_mac) {
-- if (eth_addr_from_string(nat->external_mac, &mac)) {
-- distributed = true;
-- } else {
-- static struct vlog_rate_limit rl =
-- VLOG_RATE_LIMIT_INIT(5, 1);
-- VLOG_WARN_RL(&rl, "bad mac %s for dnat in router "
-- ""UUID_FMT"", nat->external_mac, UUID_ARGS(&od->key));
-- continue;
-- }
-- }
-+/* Logical router ingress table ND_RA_OPTIONS & ND_RA_RESPONSE: RS
-+ * responder, by default goto next. (priority 0). */
-+static void
-+build_ND_RA_flows_for_lrouter(struct ovn_datapath *od, struct hmap *lflows)
-+{
-+ if (od->nbr) {
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ND_RA_OPTIONS, 0, "1", "next;");
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ND_RA_RESPONSE, 0, "1", "next;");
-+ }
-+}
-
-- /* Ingress UNSNAT table: It is for already established connections'
-- * reverse traffic. i.e., SNAT has already been done in egress
-- * pipeline and now the packet has entered the ingress pipeline as
-- * part of a reply. We undo the SNAT here.
-- *
-- * Undoing SNAT has to happen before DNAT processing. This is
-- * because when the packet was DNATed in ingress pipeline, it did
-- * not know about the possibility of eventual additional SNAT in
-- * egress pipeline. */
-- if (!strcmp(nat->type, "snat")
-- || !strcmp(nat->type, "dnat_and_snat")) {
-- if (!od->l3dgw_port) {
-- /* Gateway router. */
-- ds_clear(&match);
-- ds_clear(&actions);
-- ds_put_format(&match, "ip && ip%s.dst == %s",
-- is_v6 ? "6" : "4",
-- nat->external_ip);
-- if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-- ds_put_format(&actions, "ip%s.dst=%s; next;",
-- is_v6 ? "6" : "4", nat->logical_ip);
-- } else {
-- ds_put_cstr(&actions, "ct_snat;");
-- }
-+/* Logical router ingress table IP_ROUTING : IP Routing.
-+ *
-+ * A packet that arrives at this table is an IP packet that should be
-+ * routed to the address in 'ip[46].dst'.
-+ *
-+ * For regular routes without ECMP, table IP_ROUTING sets outport to the
-+ * correct output port, eth.src to the output port's MAC address, and
-+ * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 to the next-hop IP address
-+ * (leaving 'ip[46].dst', the packet’s final destination, unchanged), and
-+ * advances to the next table.
-+ *
-+ * For ECMP routes, i.e. multiple routes with same policy and prefix, table
-+ * IP_ROUTING remembers ECMP group id and selects a member id, and advances
-+ * to table IP_ROUTING_ECMP, which sets outport, eth.src and
-+ * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 for the selected ECMP member.
-+ */
-+static void
-+build_ip_routing_flows_for_lrouter_port(
-+ struct ovn_port *op, struct hmap *lflows)
-+{
-+ if (op->nbrp) {
-
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT,
-- 90, ds_cstr(&match),
-- ds_cstr(&actions),
-- &nat->header_);
-- } else {
-- /* Distributed router. */
-+ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
-+ add_route(lflows, op, op->lrp_networks.ipv4_addrs[i].addr_s,
-+ op->lrp_networks.ipv4_addrs[i].network_s,
-+ op->lrp_networks.ipv4_addrs[i].plen, NULL, false,
-+ &op->nbrp->header_);
-+ }
-
-- /* Traffic received on l3dgw_port is subject to NAT. */
-- ds_clear(&match);
-- ds_clear(&actions);
-- ds_put_format(&match, "ip && ip%s.dst == %s"
-- " && inport == %s",
-- is_v6 ? "6" : "4",
-- nat->external_ip,
-- od->l3dgw_port->json_key);
-- if (!distributed && od->l3redirect_port) {
-- /* Flows for NAT rules that are centralized are only
-- * programmed on the gateway chassis. */
-- ds_put_format(&match, " && is_chassis_resident(%s)",
-- od->l3redirect_port->json_key);
-- }
-+ for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
-+ add_route(lflows, op, op->lrp_networks.ipv6_addrs[i].addr_s,
-+ op->lrp_networks.ipv6_addrs[i].network_s,
-+ op->lrp_networks.ipv6_addrs[i].plen, NULL, false,
-+ &op->nbrp->header_);
-+ }
-+ }
-+}
-
-- if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-- ds_put_format(&actions, "ip%s.dst=%s; next;",
-- is_v6 ? "6" : "4", nat->logical_ip);
-- } else {
-- ds_put_cstr(&actions, "ct_snat;");
-- }
-+static void
-+build_static_route_flows_for_lrouter(
-+ struct ovn_datapath *od, struct hmap *lflows,
-+ struct hmap *ports, struct hmap *bfd_connections)
-+{
-+ if (od->nbr) {
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING_ECMP, 150,
-+ REG_ECMP_GROUP_ID" == 0", "next;");
-
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT,
-- 100,
-- ds_cstr(&match), ds_cstr(&actions),
-- &nat->header_);
-+ struct hmap ecmp_groups = HMAP_INITIALIZER(&ecmp_groups);
-+ struct hmap unique_routes = HMAP_INITIALIZER(&unique_routes);
-+ struct ovs_list parsed_routes = OVS_LIST_INITIALIZER(&parsed_routes);
-+ struct ecmp_groups_node *group;
-+ for (int i = 0; i < od->nbr->n_static_routes; i++) {
-+ struct parsed_route *route =
-+ parsed_routes_add(&parsed_routes, od->nbr->static_routes[i],
-+ bfd_connections);
-+ if (!route) {
-+ continue;
-+ }
-+ group = ecmp_groups_find(&ecmp_groups, route);
-+ if (group) {
-+ ecmp_groups_add_route(group, route);
-+ } else {
-+ const struct parsed_route *existed_route =
-+ unique_routes_remove(&unique_routes, route);
-+ if (existed_route) {
-+ group = ecmp_groups_add(&ecmp_groups, existed_route);
-+ if (group) {
-+ ecmp_groups_add_route(group, route);
-+ }
-+ } else {
-+ unique_routes_add(&unique_routes, route);
- }
- }
-+ }
-+ HMAP_FOR_EACH (group, hmap_node, &ecmp_groups) {
-+ /* add a flow in IP_ROUTING, and one flow for each member in
-+ * IP_ROUTING_ECMP. */
-+ build_ecmp_route_flow(lflows, od, ports, group);
-+ }
-+ const struct unique_routes_node *ur;
-+ HMAP_FOR_EACH (ur, hmap_node, &unique_routes) {
-+ build_static_route_flow(lflows, od, ports, ur->route);
-+ }
-+ ecmp_groups_destroy(&ecmp_groups);
-+ unique_routes_destroy(&unique_routes);
-+ parsed_routes_destroy(&parsed_routes);
-+ }
-+}
-
-- /* Ingress DNAT table: Packets enter the pipeline with destination
-- * IP address that needs to be DNATted from a external IP address
-- * to a logical IP address. */
-- if (!strcmp(nat->type, "dnat")
-- || !strcmp(nat->type, "dnat_and_snat")) {
-- if (!od->l3dgw_port) {
-- /* Gateway router. */
-- /* Packet when it goes from the initiator to destination.
-- * We need to set flags.loopback because the router can
-- * send the packet back through the same interface. */
-- ds_clear(&match);
-- ds_put_format(&match, "ip && ip%s.dst == %s",
-- is_v6 ? "6" : "4",
-- nat->external_ip);
-- ds_clear(&actions);
-- if (allowed_ext_ips || exempted_ext_ips) {
-- lrouter_nat_add_ext_ip_match(od, lflows, &match, nat,
-- is_v6, true, mask);
-- }
-+/* IP Multicast lookup. Here we set the output port, adjust TTL and
-+ * advance to next table (priority 500).
-+ */
-+static void
-+build_mcast_lookup_flows_for_lrouter(
-+ struct ovn_datapath *od, struct hmap *lflows,
-+ struct ds *match, struct ds *actions)
-+{
-+ if (od->nbr) {
-
-- if (dnat_force_snat_ip) {
-- /* Indicate to the future tables that a DNAT has taken
-- * place and a force SNAT needs to be done in the
-- * Egress SNAT table. */
-- ds_put_format(&actions,
-- "flags.force_snat_for_dnat = 1; ");
-- }
-+ /* Drop IPv6 multicast traffic that shouldn't be forwarded,
-+ * i.e., router solicitation and router advertisement.
-+ */
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 550,
-+ "nd_rs || nd_ra", "drop;");
-+ if (!od->mcast_info.rtr.relay) {
-+ return;
-+ }
-
-- if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-- ds_put_format(&actions, "flags.loopback = 1; "
-- "ip%s.dst=%s; next;",
-- is_v6 ? "6" : "4", nat->logical_ip);
-- } else {
-- ds_put_format(&actions, "flags.loopback = 1; "
-- "ct_dnat(%s", nat->logical_ip);
-+ struct ovn_igmp_group *igmp_group;
-
-- if (nat->external_port_range[0]) {
-- ds_put_format(&actions, ",%s",
-- nat->external_port_range);
-- }
-- ds_put_format(&actions, ");");
-- }
-+ LIST_FOR_EACH (igmp_group, list_node, &od->mcast_info.groups) {
-+ ds_clear(match);
-+ ds_clear(actions);
-+ if (IN6_IS_ADDR_V4MAPPED(&igmp_group->address)) {
-+ ds_put_format(match, "ip4 && ip4.dst == %s ",
-+ igmp_group->mcgroup.name);
-+ } else {
-+ ds_put_format(match, "ip6 && ip6.dst == %s ",
-+ igmp_group->mcgroup.name);
-+ }
-+ if (od->mcast_info.rtr.flood_static) {
-+ ds_put_cstr(actions,
-+ "clone { "
-+ "outport = \""MC_STATIC"\"; "
-+ "ip.ttl--; "
-+ "next; "
-+ "};");
-+ }
-+ ds_put_format(actions, "outport = \"%s\"; ip.ttl--; next;",
-+ igmp_group->mcgroup.name);
-+ ovn_lflow_add_unique(lflows, od, S_ROUTER_IN_IP_ROUTING, 500,
-+ ds_cstr(match), ds_cstr(actions));
-+ }
-
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100,
-- ds_cstr(&match), ds_cstr(&actions),
-- &nat->header_);
-- } else {
-- /* Distributed router. */
-+ /* If needed, flood unregistered multicast on statically configured
-+ * ports. Otherwise drop any multicast traffic.
-+ */
-+ if (od->mcast_info.rtr.flood_static) {
-+ ovn_lflow_add_unique(lflows, od, S_ROUTER_IN_IP_ROUTING, 450,
-+ "ip4.mcast || ip6.mcast",
-+ "clone { "
-+ "outport = \""MC_STATIC"\"; "
-+ "ip.ttl--; "
-+ "next; "
-+ "};");
-+ } else {
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 450,
-+ "ip4.mcast || ip6.mcast", "drop;");
-+ }
-+ }
-+}
-
-- /* Traffic received on l3dgw_port is subject to NAT. */
-- ds_clear(&match);
-- ds_put_format(&match, "ip && ip%s.dst == %s"
-- " && inport == %s",
-- is_v6 ? "6" : "4",
-- nat->external_ip,
-- od->l3dgw_port->json_key);
-- if (!distributed && od->l3redirect_port) {
-- /* Flows for NAT rules that are centralized are only
-- * programmed on the gateway chassis. */
-- ds_put_format(&match, " && is_chassis_resident(%s)",
-- od->l3redirect_port->json_key);
-- }
-- ds_clear(&actions);
-- if (allowed_ext_ips || exempted_ext_ips) {
-- lrouter_nat_add_ext_ip_match(od, lflows, &match, nat,
-- is_v6, true, mask);
-- }
-+/* Logical router ingress table POLICY: Policy.
-+ *
-+ * A packet that arrives at this table is an IP packet that should be
-+ * permitted/denied/rerouted to the address in the rule's nexthop.
-+ * This table sets outport to the correct out_port,
-+ * eth.src to the output port's MAC address,
-+ * and REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 to the next-hop IP address
-+ * (leaving 'ip[46].dst', the packet’s final destination, unchanged), and
-+ * advances to the next table for ARP/ND resolution. */
-+static void
-+build_ingress_policy_flows_for_lrouter(
-+ struct ovn_datapath *od, struct hmap *lflows,
-+ struct hmap *ports)
-+{
-+ if (od->nbr) {
-+ /* This is a catch-all rule. It has the lowest priority (0)
-+ * does a match-all("1") and pass-through (next) */
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_POLICY, 0, "1",
-+ REG_ECMP_GROUP_ID" = 0; next;");
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_POLICY_ECMP, 150,
-+ REG_ECMP_GROUP_ID" == 0", "next;");
-
-- if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-- ds_put_format(&actions, "ip%s.dst=%s; next;",
-- is_v6 ? "6" : "4", nat->logical_ip);
-- } else {
-- ds_put_format(&actions, "ct_dnat(%s", nat->logical_ip);
-- if (nat->external_port_range[0]) {
-- ds_put_format(&actions, ",%s",
-- nat->external_port_range);
-- }
-- ds_put_format(&actions, ");");
-- }
-+ /* Convert routing policies to flows. */
-+ uint16_t ecmp_group_id = 1;
-+ for (int i = 0; i < od->nbr->n_policies; i++) {
-+ const struct nbrec_logical_router_policy *rule
-+ = od->nbr->policies[i];
-+ bool is_ecmp_reroute =
-+ (!strcmp(rule->action, "reroute") && rule->n_nexthops > 1);
-
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100,
-- ds_cstr(&match), ds_cstr(&actions),
-- &nat->header_);
-- }
-+ if (is_ecmp_reroute) {
-+ build_ecmp_routing_policy_flows(lflows, od, ports, rule,
-+ ecmp_group_id);
-+ ecmp_group_id++;
-+ } else {
-+ build_routing_policy_flow(lflows, od, ports, rule,
-+ &rule->header_);
- }
-+ }
-+ }
-+}
-
-- /* ARP resolve for NAT IPs. */
-- if (od->l3dgw_port) {
-- if (!strcmp(nat->type, "snat")) {
-- ds_clear(&match);
-- ds_put_format(
-- &match, "inport == %s && %s == %s",
-- od->l3dgw_port->json_key,
-- is_v6 ? "ip6.src" : "ip4.src", nat->external_ip);
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_IP_INPUT,
-- 120, ds_cstr(&match), "next;",
-- &nat->header_);
-- }
-+/* Local router ingress table ARP_RESOLVE: ARP Resolution. */
-+static void
-+build_arp_resolve_flows_for_lrouter(
-+ struct ovn_datapath *od, struct hmap *lflows)
-+{
-+ if (od->nbr) {
-+ /* Multicast packets already have the outport set so just advance to
-+ * next table (priority 500). */
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 500,
-+ "ip4.mcast || ip6.mcast", "next;");
-
-- if (!sset_contains(&nat_entries, nat->external_ip)) {
-- ds_clear(&match);
-- ds_put_format(
-- &match, "outport == %s && %s == %s",
-- od->l3dgw_port->json_key,
-- is_v6 ? REG_NEXT_HOP_IPV6 : REG_NEXT_HOP_IPV4,
-- nat->external_ip);
-- ds_clear(&actions);
-- ds_put_format(
-- &actions, "eth.dst = %s; next;",
-- distributed ? nat->external_mac :
-- od->l3dgw_port->lrp_networks.ea_s);
-- ovn_lflow_add_with_hint(lflows, od,
-- S_ROUTER_IN_ARP_RESOLVE,
-- 100, ds_cstr(&match),
-- ds_cstr(&actions),
-- &nat->header_);
-- sset_add(&nat_entries, nat->external_ip);
-- }
-- } else {
-- /* Add the NAT external_ip to the nat_entries even for
-- * gateway routers. This is required for adding load balancer
-- * flows.*/
-- sset_add(&nat_entries, nat->external_ip);
-- }
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 0, "ip4",
-+ "get_arp(outport, " REG_NEXT_HOP_IPV4 "); next;");
-
-- /* Egress UNDNAT table: It is for already established connections'
-- * reverse traffic. i.e., DNAT has already been done in ingress
-- * pipeline and now the packet has entered the egress pipeline as
-- * part of a reply. We undo the DNAT here.
-- *
-- * Note that this only applies for NAT on a distributed router.
-- * Undo DNAT on a gateway router is done in the ingress DNAT
-- * pipeline stage. */
-- if (od->l3dgw_port && (!strcmp(nat->type, "dnat")
-- || !strcmp(nat->type, "dnat_and_snat"))) {
-- ds_clear(&match);
-- ds_put_format(&match, "ip && ip%s.src == %s"
-- " && outport == %s",
-- is_v6 ? "6" : "4",
-- nat->logical_ip,
-- od->l3dgw_port->json_key);
-- if (!distributed && od->l3redirect_port) {
-- /* Flows for NAT rules that are centralized are only
-- * programmed on the gateway chassis. */
-- ds_put_format(&match, " && is_chassis_resident(%s)",
-- od->l3redirect_port->json_key);
-- }
-- ds_clear(&actions);
-- if (distributed) {
-- ds_put_format(&actions, "eth.src = "ETH_ADDR_FMT"; ",
-- ETH_ADDR_ARGS(mac));
-- }
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 0, "ip6",
-+ "get_nd(outport, " REG_NEXT_HOP_IPV6 "); next;");
-+ }
-+}
-
-- if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-- ds_put_format(&actions, "ip%s.src=%s; next;",
-- is_v6 ? "6" : "4", nat->external_ip);
-- } else {
-- ds_put_format(&actions, "ct_dnat;");
-- }
-+/* Local router ingress table ARP_RESOLVE: ARP Resolution.
-+ *
-+ * Any unicast packet that reaches this table is an IP packet whose
-+ * next-hop IP address is in REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6
-+ * (ip4.dst/ipv6.dst is the final destination).
-+ * This table resolves the IP address in
-+ * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 into an output port in outport and
-+ * an Ethernet address in eth.dst.
-+ */
-+static void
-+build_arp_resolve_flows_for_lrouter_port(
-+ struct ovn_port *op, struct hmap *lflows,
-+ struct hmap *ports,
-+ struct ds *match, struct ds *actions)
-+{
-+ if (op->nbsp && !lsp_is_enabled(op->nbsp)) {
-+ return;
-+ }
-
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 100,
-- ds_cstr(&match), ds_cstr(&actions),
-- &nat->header_);
-- }
-+ if (op->nbrp) {
-+ /* This is a logical router port. If next-hop IP address in
-+ * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 matches IP address of this
-+ * router port, then the packet is intended to eventually be sent
-+ * to this logical port. Set the destination mac address using
-+ * this port's mac address.
-+ *
-+ * The packet is still in peer's logical pipeline. So the match
-+ * should be on peer's outport. */
-+ if (op->peer && op->nbrp->peer) {
-+ if (op->lrp_networks.n_ipv4_addrs) {
-+ ds_clear(match);
-+ ds_put_format(match, "outport == %s && "
-+ REG_NEXT_HOP_IPV4 "== ",
-+ op->peer->json_key);
-+ op_put_v4_networks(match, op, false);
-
-- /* Egress SNAT table: Packets enter the egress pipeline with
-- * source ip address that needs to be SNATted to a external ip
-- * address. */
-- if (!strcmp(nat->type, "snat")
-- || !strcmp(nat->type, "dnat_and_snat")) {
-- if (!od->l3dgw_port) {
-- /* Gateway router. */
-- ds_clear(&match);
-- ds_put_format(&match, "ip && ip%s.src == %s",
-- is_v6 ? "6" : "4",
-- nat->logical_ip);
-- ds_clear(&actions);
-+ ds_clear(actions);
-+ ds_put_format(actions, "eth.dst = %s; next;",
-+ op->lrp_networks.ea_s);
-+ ovn_lflow_add_with_hint(lflows, op->peer->od,
-+ S_ROUTER_IN_ARP_RESOLVE, 100,
-+ ds_cstr(match), ds_cstr(actions),
-+ &op->nbrp->header_);
-+ }
-
-- if (allowed_ext_ips || exempted_ext_ips) {
-- lrouter_nat_add_ext_ip_match(od, lflows, &match, nat,
-- is_v6, false, mask);
-- }
-+ if (op->lrp_networks.n_ipv6_addrs) {
-+ ds_clear(match);
-+ ds_put_format(match, "outport == %s && "
-+ REG_NEXT_HOP_IPV6 " == ",
-+ op->peer->json_key);
-+ op_put_v6_networks(match, op);
-
-- if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-- ds_put_format(&actions, "ip%s.src=%s; next;",
-- is_v6 ? "6" : "4", nat->external_ip);
-- } else {
-- ds_put_format(&actions, "ct_snat(%s",
-- nat->external_ip);
-+ ds_clear(actions);
-+ ds_put_format(actions, "eth.dst = %s; next;",
-+ op->lrp_networks.ea_s);
-+ ovn_lflow_add_with_hint(lflows, op->peer->od,
-+ S_ROUTER_IN_ARP_RESOLVE, 100,
-+ ds_cstr(match), ds_cstr(actions),
-+ &op->nbrp->header_);
-+ }
-+ }
-
-- if (nat->external_port_range[0]) {
-- ds_put_format(&actions, ",%s",
-- nat->external_port_range);
-- }
-- ds_put_format(&actions, ");");
-- }
-+ if (!op->derived && op->od->l3redirect_port) {
-+ const char *redirect_type = smap_get(&op->nbrp->options,
-+ "redirect-type");
-+ if (redirect_type && !strcasecmp(redirect_type, "bridged")) {
-+ /* Packet is on a non gateway chassis and
-+ * has an unresolved ARP on a network behind gateway
-+ * chassis attached router port. Since, redirect type
-+ * is "bridged", instead of calling "get_arp"
-+ * on this node, we will redirect the packet to gateway
-+ * chassis, by setting destination mac router port mac.*/
-+ ds_clear(match);
-+ ds_put_format(match, "outport == %s && "
-+ "!is_chassis_resident(%s)", op->json_key,
-+ op->od->l3redirect_port->json_key);
-+ ds_clear(actions);
-+ ds_put_format(actions, "eth.dst = %s; next;",
-+ op->lrp_networks.ea_s);
-
-- /* The priority here is calculated such that the
-- * nat->logical_ip with the longest mask gets a higher
-- * priority. */
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT,
-- cidr_bits + 1,
-- ds_cstr(&match), ds_cstr(&actions),
-- &nat->header_);
-- } else {
-- uint16_t priority = cidr_bits + 1;
-+ ovn_lflow_add_with_hint(lflows, op->od,
-+ S_ROUTER_IN_ARP_RESOLVE, 50,
-+ ds_cstr(match), ds_cstr(actions),
-+ &op->nbrp->header_);
-+ }
-+ }
-
-- /* Distributed router. */
-- ds_clear(&match);
-- ds_put_format(&match, "ip && ip%s.src == %s"
-- " && outport == %s",
-- is_v6 ? "6" : "4",
-- nat->logical_ip,
-- od->l3dgw_port->json_key);
-- if (!distributed && od->l3redirect_port) {
-- /* Flows for NAT rules that are centralized are only
-- * programmed on the gateway chassis. */
-- priority += 128;
-- ds_put_format(&match, " && is_chassis_resident(%s)",
-- od->l3redirect_port->json_key);
-- }
-- ds_clear(&actions);
-+ /* Drop IP traffic destined to router owned IPs. Part of it is dropped
-+ * in stage "lr_in_ip_input" but traffic that could have been unSNATed
-+ * but didn't match any existing session might still end up here.
-+ *
-+ * Priority 1.
-+ */
-+ build_lrouter_drop_own_dest(op, S_ROUTER_IN_ARP_RESOLVE, 1, true,
-+ lflows);
-+ } else if (op->od->n_router_ports && !lsp_is_router(op->nbsp)
-+ && strcmp(op->nbsp->type, "virtual")) {
-+ /* This is a logical switch port that backs a VM or a container.
-+ * Extract its addresses. For each of the address, go through all
-+ * the router ports attached to the switch (to which this port
-+ * connects) and if the address in question is reachable from the
-+ * router port, add an ARP/ND entry in that router's pipeline. */
-
-- if (allowed_ext_ips || exempted_ext_ips) {
-- lrouter_nat_add_ext_ip_match(od, lflows, &match, nat,
-- is_v6, false, mask);
-+ for (size_t i = 0; i < op->n_lsp_addrs; i++) {
-+ const char *ea_s = op->lsp_addrs[i].ea_s;
-+ for (size_t j = 0; j < op->lsp_addrs[i].n_ipv4_addrs; j++) {
-+ const char *ip_s = op->lsp_addrs[i].ipv4_addrs[j].addr_s;
-+ for (size_t k = 0; k < op->od->n_router_ports; k++) {
-+ /* Get the Logical_Router_Port that the
-+ * Logical_Switch_Port is connected to, as
-+ * 'peer'. */
-+ const char *peer_name = smap_get(
-+ &op->od->router_ports[k]->nbsp->options,
-+ "router-port");
-+ if (!peer_name) {
-+ continue;
- }
-
-- if (distributed) {
-- ds_put_format(&actions, "eth.src = "ETH_ADDR_FMT"; ",
-- ETH_ADDR_ARGS(mac));
-+ struct ovn_port *peer = ovn_port_find(ports, peer_name);
-+ if (!peer || !peer->nbrp) {
-+ continue;
- }
-
-- if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-- ds_put_format(&actions, "ip%s.src=%s; next;",
-- is_v6 ? "6" : "4", nat->external_ip);
-- } else {
-- ds_put_format(&actions, "ct_snat(%s",
-- nat->external_ip);
-- if (nat->external_port_range[0]) {
-- ds_put_format(&actions, ",%s",
-- nat->external_port_range);
-- }
-- ds_put_format(&actions, ");");
-+ if (!find_lrp_member_ip(peer, ip_s)) {
-+ continue;
- }
-
-- /* The priority here is calculated such that the
-- * nat->logical_ip with the longest mask gets a higher
-- * priority. */
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT,
-- priority, ds_cstr(&match),
-- ds_cstr(&actions),
-- &nat->header_);
-+ ds_clear(match);
-+ ds_put_format(match, "outport == %s && "
-+ REG_NEXT_HOP_IPV4 " == %s",
-+ peer->json_key, ip_s);
-+
-+ ds_clear(actions);
-+ ds_put_format(actions, "eth.dst = %s; next;", ea_s);
-+ ovn_lflow_add_with_hint(lflows, peer->od,
-+ S_ROUTER_IN_ARP_RESOLVE, 100,
-+ ds_cstr(match),
-+ ds_cstr(actions),
-+ &op->nbsp->header_);
- }
- }
-
-- /* Logical router ingress table 0:
-- * For NAT on a distributed router, add rules allowing
-- * ingress traffic with eth.dst matching nat->external_mac
-- * on the l3dgw_port instance where nat->logical_port is
-- * resident. */
-- if (distributed) {
-- /* Store the ethernet address of the port receiving the packet.
-- * This will save us from having to match on inport further
-- * down in the pipeline.
-- */
-- ds_clear(&actions);
-- ds_put_format(&actions, REG_INPORT_ETH_ADDR " = %s; next;",
-- od->l3dgw_port->lrp_networks.ea_s);
-+ for (size_t j = 0; j < op->lsp_addrs[i].n_ipv6_addrs; j++) {
-+ const char *ip_s = op->lsp_addrs[i].ipv6_addrs[j].addr_s;
-+ for (size_t k = 0; k < op->od->n_router_ports; k++) {
-+ /* Get the Logical_Router_Port that the
-+ * Logical_Switch_Port is connected to, as
-+ * 'peer'. */
-+ const char *peer_name = smap_get(
-+ &op->od->router_ports[k]->nbsp->options,
-+ "router-port");
-+ if (!peer_name) {
-+ continue;
-+ }
-
-- ds_clear(&match);
-- ds_put_format(&match,
-- "eth.dst == "ETH_ADDR_FMT" && inport == %s"
-- " && is_chassis_resident(\"%s\")",
-- ETH_ADDR_ARGS(mac),
-- od->l3dgw_port->json_key,
-- nat->logical_port);
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ADMISSION, 50,
-- ds_cstr(&match), ds_cstr(&actions),
-- &nat->header_);
-- }
-+ struct ovn_port *peer = ovn_port_find(ports, peer_name);
-+ if (!peer || !peer->nbrp) {
-+ continue;
-+ }
-
-- /* Ingress Gateway Redirect Table: For NAT on a distributed
-- * router, add flows that are specific to a NAT rule. These
-- * flows indicate the presence of an applicable NAT rule that
-- * can be applied in a distributed manner.
-- * In particulr REG_SRC_IPV4/REG_SRC_IPV6 and eth.src are set to
-- * NAT external IP and NAT external mac so the ARP request
-- * generated in the following stage is sent out with proper IP/MAC
-- * src addresses.
-- */
-- if (distributed) {
-- ds_clear(&match);
-- ds_clear(&actions);
-- ds_put_format(&match,
-- "ip%s.src == %s && outport == %s && "
-- "is_chassis_resident(\"%s\")",
-- is_v6 ? "6" : "4", nat->logical_ip,
-- od->l3dgw_port->json_key, nat->logical_port);
-- ds_put_format(&actions, "eth.src = %s; %s = %s; next;",
-- nat->external_mac,
-- is_v6 ? REG_SRC_IPV6 : REG_SRC_IPV4,
-- nat->external_ip);
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_GW_REDIRECT,
-- 100, ds_cstr(&match),
-- ds_cstr(&actions), &nat->header_);
-- }
-+ if (!find_lrp_member_ip(peer, ip_s)) {
-+ continue;
-+ }
-
-- /* Egress Loopback table: For NAT on a distributed router.
-- * If packets in the egress pipeline on the distributed
-- * gateway port have ip.dst matching a NAT external IP, then
-- * loop a clone of the packet back to the beginning of the
-- * ingress pipeline with inport = outport. */
-- if (od->l3dgw_port) {
-- /* Distributed router. */
-- ds_clear(&match);
-- ds_put_format(&match, "ip%s.dst == %s && outport == %s",
-- is_v6 ? "6" : "4",
-- nat->external_ip,
-- od->l3dgw_port->json_key);
-- if (!distributed) {
-- ds_put_format(&match, " && is_chassis_resident(%s)",
-- od->l3redirect_port->json_key);
-- } else {
-- ds_put_format(&match, " && is_chassis_resident(\"%s\")",
-- nat->logical_port);
-- }
-+ ds_clear(match);
-+ ds_put_format(match, "outport == %s && "
-+ REG_NEXT_HOP_IPV6 " == %s",
-+ peer->json_key, ip_s);
-
-- ds_clear(&actions);
-- ds_put_format(&actions,
-- "clone { ct_clear; "
-- "inport = outport; outport = \"\"; "
-- "flags = 0; flags.loopback = 1; ");
-- for (int j = 0; j < MFF_N_LOG_REGS; j++) {
-- ds_put_format(&actions, "reg%d = 0; ", j);
-+ ds_clear(actions);
-+ ds_put_format(actions, "eth.dst = %s; next;", ea_s);
-+ ovn_lflow_add_with_hint(lflows, peer->od,
-+ S_ROUTER_IN_ARP_RESOLVE, 100,
-+ ds_cstr(match),
-+ ds_cstr(actions),
-+ &op->nbsp->header_);
- }
-- ds_put_format(&actions, REGBIT_EGRESS_LOOPBACK" = 1; "
-- "next(pipeline=ingress, table=%d); };",
-- ovn_stage_get_table(S_ROUTER_IN_ADMISSION));
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_EGR_LOOP, 100,
-- ds_cstr(&match), ds_cstr(&actions),
-- &nat->header_);
- }
- }
-+ } else if (op->od->n_router_ports && !lsp_is_router(op->nbsp)
-+ && !strcmp(op->nbsp->type, "virtual")) {
-+ /* This is a virtual port. Add ARP replies for the virtual ip with
-+ * the mac of the present active virtual parent.
-+ * If the logical port doesn't have virtual parent set in
-+ * Port_Binding table, then add the flow to set eth.dst to
-+ * 00:00:00:00:00:00 and advance to next table so that ARP is
-+ * resolved by router pipeline using the arp{} action.
-+ * The MAC_Binding entry for the virtual ip might be invalid. */
-+ ovs_be32 ip;
-
-- /* Handle force SNAT options set in the gateway router. */
-- if (!od->l3dgw_port) {
-- if (dnat_force_snat_ip) {
-- if (od->dnat_force_snat_addrs.n_ipv4_addrs) {
-- build_lrouter_force_snat_flows(lflows, od, "4",
-- od->dnat_force_snat_addrs.ipv4_addrs[0].addr_s,
-- "dnat");
-- }
-- if (od->dnat_force_snat_addrs.n_ipv6_addrs) {
-- build_lrouter_force_snat_flows(lflows, od, "6",
-- od->dnat_force_snat_addrs.ipv6_addrs[0].addr_s,
-- "dnat");
-- }
-- }
-- if (lb_force_snat_ip) {
-- if (od->lb_force_snat_addrs.n_ipv4_addrs) {
-- build_lrouter_force_snat_flows(lflows, od, "4",
-- od->lb_force_snat_addrs.ipv4_addrs[0].addr_s, "lb");
-- }
-- if (od->lb_force_snat_addrs.n_ipv6_addrs) {
-- build_lrouter_force_snat_flows(lflows, od, "6",
-- od->lb_force_snat_addrs.ipv6_addrs[0].addr_s, "lb");
-- }
-- }
--
-- /* For gateway router, re-circulate every packet through
-- * the DNAT zone. This helps with the following.
-- *
-- * Any packet that needs to be unDNATed in the reverse
-- * direction gets unDNATed. Ideally this could be done in
-- * the egress pipeline. But since the gateway router
-- * does not have any feature that depends on the source
-- * ip address being external IP address for IP routing,
-- * we can do it here, saving a future re-circulation. */
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 50,
-- "ip", "flags.loopback = 1; ct_dnat;");
-+ const char *vip = smap_get(&op->nbsp->options,
-+ "virtual-ip");
-+ const char *virtual_parents = smap_get(&op->nbsp->options,
-+ "virtual-parents");
-+ if (!vip || !virtual_parents ||
-+ !ip_parse(vip, &ip) || !op->sb) {
-+ return;
- }
-
-- /* Load balancing and packet defrag are only valid on
-- * Gateway routers or router with gateway port. */
-- if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) {
-- sset_destroy(&nat_entries);
-- continue;
-- }
-+ if (!op->sb->virtual_parent || !op->sb->virtual_parent[0] ||
-+ !op->sb->chassis) {
-+ /* The virtual port is not claimed yet. */
-+ for (size_t i = 0; i < op->od->n_router_ports; i++) {
-+ const char *peer_name = smap_get(
-+ &op->od->router_ports[i]->nbsp->options,
-+ "router-port");
-+ if (!peer_name) {
-+ continue;
-+ }
-
-- /* A set to hold all ips that need defragmentation and tracking. */
-- struct sset all_ips = SSET_INITIALIZER(&all_ips);
-+ struct ovn_port *peer = ovn_port_find(ports, peer_name);
-+ if (!peer || !peer->nbrp) {
-+ continue;
-+ }
-
-- for (int i = 0; i < od->nbr->n_load_balancer; i++) {
-- struct nbrec_load_balancer *nb_lb = od->nbr->load_balancer[i];
-- struct ovn_northd_lb *lb =
-- ovn_northd_lb_find(lbs, &nb_lb->header_.uuid);
-- ovs_assert(lb);
-+ if (find_lrp_member_ip(peer, vip)) {
-+ ds_clear(match);
-+ ds_put_format(match, "outport == %s && "
-+ REG_NEXT_HOP_IPV4 " == %s",
-+ peer->json_key, vip);
-
-- for (size_t j = 0; j < lb->n_vips; j++) {
-- struct ovn_lb_vip *lb_vip = &lb->vips[j];
-- struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[j];
-- ds_clear(&actions);
-- build_lb_vip_ct_lb_actions(lb_vip, lb_vip_nb, &actions,
-- lb->selection_fields);
-+ const char *arp_actions =
-+ "eth.dst = 00:00:00:00:00:00; next;";
-+ ovn_lflow_add_with_hint(lflows, peer->od,
-+ S_ROUTER_IN_ARP_RESOLVE, 100,
-+ ds_cstr(match),
-+ arp_actions,
-+ &op->nbsp->header_);
-+ break;
-+ }
-+ }
-+ } else {
-+ struct ovn_port *vp =
-+ ovn_port_find(ports, op->sb->virtual_parent);
-+ if (!vp || !vp->nbsp) {
-+ return;
-+ }
-
-- if (!sset_contains(&all_ips, lb_vip->vip_str)) {
-- sset_add(&all_ips, lb_vip->vip_str);
-- /* If there are any load balancing rules, we should send
-- * the packet to conntrack for defragmentation and
-- * tracking. This helps with two things.
-- *
-- * 1. With tracking, we can send only new connections to
-- * pick a DNAT ip address from a group.
-- * 2. If there are L4 ports in load balancing rules, we
-- * need the defragmentation to match on L4 ports. */
-- ds_clear(&match);
-- if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
-- ds_put_format(&match, "ip && ip4.dst == %s",
-- lb_vip->vip_str);
-- } else {
-- ds_put_format(&match, "ip && ip6.dst == %s",
-- lb_vip->vip_str);
-+ for (size_t i = 0; i < vp->n_lsp_addrs; i++) {
-+ bool found_vip_network = false;
-+ const char *ea_s = vp->lsp_addrs[i].ea_s;
-+ for (size_t j = 0; j < vp->od->n_router_ports; j++) {
-+ /* Get the Logical_Router_Port that the
-+ * Logical_Switch_Port is connected to, as
-+ * 'peer'. */
-+ const char *peer_name = smap_get(
-+ &vp->od->router_ports[j]->nbsp->options,
-+ "router-port");
-+ if (!peer_name) {
-+ continue;
- }
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DEFRAG,
-- 100, ds_cstr(&match), "ct_next;",
-- &nb_lb->header_);
-- }
-
-- /* Higher priority rules are added for load-balancing in DNAT
-- * table. For every match (on a VIP[:port]), we add two flows
-- * via add_router_lb_flow(). One flow is for specific matching
-- * on ct.new with an action of "ct_lb($targets);". The other
-- * flow is for ct.est with an action of "ct_dnat;". */
-- ds_clear(&match);
-- if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
-- ds_put_format(&match, "ip && ip4.dst == %s",
-- lb_vip->vip_str);
-- } else {
-- ds_put_format(&match, "ip && ip6.dst == %s",
-- lb_vip->vip_str);
-- }
-+ struct ovn_port *peer =
-+ ovn_port_find(ports, peer_name);
-+ if (!peer || !peer->nbrp) {
-+ continue;
-+ }
-
-- int prio = 110;
-- bool is_udp = nullable_string_is_equal(nb_lb->protocol, "udp");
-- bool is_sctp = nullable_string_is_equal(nb_lb->protocol,
-- "sctp");
-- const char *proto = is_udp ? "udp" : is_sctp ? "sctp" : "tcp";
-+ if (!find_lrp_member_ip(peer, vip)) {
-+ continue;
-+ }
-
-- if (lb_vip->vip_port) {
-- ds_put_format(&match, " && %s && %s.dst == %d", proto,
-- proto, lb_vip->vip_port);
-- prio = 120;
-+ ds_clear(match);
-+ ds_put_format(match, "outport == %s && "
-+ REG_NEXT_HOP_IPV4 " == %s",
-+ peer->json_key, vip);
-+
-+ ds_clear(actions);
-+ ds_put_format(actions, "eth.dst = %s; next;", ea_s);
-+ ovn_lflow_add_with_hint(lflows, peer->od,
-+ S_ROUTER_IN_ARP_RESOLVE, 100,
-+ ds_cstr(match),
-+ ds_cstr(actions),
-+ &op->nbsp->header_);
-+ found_vip_network = true;
-+ break;
- }
-
-- if (od->l3redirect_port) {
-- ds_put_format(&match, " && is_chassis_resident(%s)",
-- od->l3redirect_port->json_key);
-+ if (found_vip_network) {
-+ break;
- }
-- add_router_lb_flow(lflows, od, &match, &actions, prio,
-- lb_force_snat_ip, lb_vip, proto,
-- nb_lb, meter_groups, &nat_entries);
- }
- }
-- sset_destroy(&all_ips);
-- sset_destroy(&nat_entries);
-- }
--
-- ds_destroy(&match);
-- ds_destroy(&actions);
--}
-+ } else if (lsp_is_router(op->nbsp)) {
-+ /* This is a logical switch port that connects to a router. */
-
--/* Logical router ingress Table 0: L2 Admission Control
-- * Generic admission control flows (without inport check).
-- */
--static void
--build_adm_ctrl_flows_for_lrouter(
-- struct ovn_datapath *od, struct hmap *lflows)
--{
-- if (od->nbr) {
-- /* Logical VLANs not supported.
-- * Broadcast/multicast source address is invalid. */
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_ADMISSION, 100,
-- "vlan.present || eth.src[40]", "drop;");
-- }
--}
-+ /* The peer of this switch port is the router port for which
-+ * we need to add logical flows such that it can resolve
-+ * ARP entries for all the other router ports connected to
-+ * the switch in question. */
-
--/* Logical router ingress Table 0: L2 Admission Control
-- * This table drops packets that the router shouldn’t see at all based
-- * on their Ethernet headers.
-- */
--static void
--build_adm_ctrl_flows_for_lrouter_port(
-- struct ovn_port *op, struct hmap *lflows,
-- struct ds *match, struct ds *actions)
--{
-- if (op->nbrp) {
-- if (!lrport_is_enabled(op->nbrp)) {
-- /* Drop packets from disabled logical ports (since logical flow
-- * tables are default-drop). */
-+ const char *peer_name = smap_get(&op->nbsp->options,
-+ "router-port");
-+ if (!peer_name) {
- return;
- }
-
-- if (op->derived) {
-- /* No ingress packets should be received on a chassisredirect
-- * port. */
-+ struct ovn_port *peer = ovn_port_find(ports, peer_name);
-+ if (!peer || !peer->nbrp) {
- return;
- }
-
-- /* Store the ethernet address of the port receiving the packet.
-- * This will save us from having to match on inport further down in
-- * the pipeline.
-- */
-- ds_clear(actions);
-- ds_put_format(actions, REG_INPORT_ETH_ADDR " = %s; next;",
-- op->lrp_networks.ea_s);
--
-- ds_clear(match);
-- ds_put_format(match, "eth.mcast && inport == %s", op->json_key);
-- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_ADMISSION, 50,
-- ds_cstr(match), ds_cstr(actions),
-- &op->nbrp->header_);
--
-- ds_clear(match);
-- ds_put_format(match, "eth.dst == %s && inport == %s",
-- op->lrp_networks.ea_s, op->json_key);
-- if (op->od->l3dgw_port && op == op->od->l3dgw_port
-- && op->od->l3redirect_port) {
-- /* Traffic with eth.dst = l3dgw_port->lrp_networks.ea_s
-- * should only be received on the gateway chassis. */
-- ds_put_format(match, " && is_chassis_resident(%s)",
-- op->od->l3redirect_port->json_key);
-+ if (peer->od->nbr &&
-+ smap_get_bool(&peer->od->nbr->options,
-+ "dynamic_neigh_routers", false)) {
-+ return;
- }
-- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_ADMISSION, 50,
-- ds_cstr(match), ds_cstr(actions),
-- &op->nbrp->header_);
-- }
--}
--
--
--/* Logical router ingress Table 1 and 2: Neighbor lookup and learning
-- * lflows for logical routers. */
--static void
--build_neigh_learning_flows_for_lrouter(
-- struct ovn_datapath *od, struct hmap *lflows,
-- struct ds *match, struct ds *actions)
--{
-- if (od->nbr) {
--
-- /* Learn MAC bindings from ARP/IPv6 ND.
-- *
-- * For ARP packets, table LOOKUP_NEIGHBOR does a lookup for the
-- * (arp.spa, arp.sha) in the mac binding table using the 'lookup_arp'
-- * action and stores the result in REGBIT_LOOKUP_NEIGHBOR_RESULT bit.
-- * If "always_learn_from_arp_request" is set to false, it will also
-- * lookup for the (arp.spa) in the mac binding table using the
-- * "lookup_arp_ip" action for ARP request packets, and stores the
-- * result in REGBIT_LOOKUP_NEIGHBOR_IP_RESULT bit; or set that bit
-- * to "1" directly for ARP response packets.
-- *
-- * For IPv6 ND NA packets, table LOOKUP_NEIGHBOR does a lookup
-- * for the (nd.target, nd.tll) in the mac binding table using the
-- * 'lookup_nd' action and stores the result in
-- * REGBIT_LOOKUP_NEIGHBOR_RESULT bit. If
-- * "always_learn_from_arp_request" is set to false,
-- * REGBIT_LOOKUP_NEIGHBOR_IP_RESULT bit is set.
-- *
-- * For IPv6 ND NS packets, table LOOKUP_NEIGHBOR does a lookup
-- * for the (ip6.src, nd.sll) in the mac binding table using the
-- * 'lookup_nd' action and stores the result in
-- * REGBIT_LOOKUP_NEIGHBOR_RESULT bit. If
-- * "always_learn_from_arp_request" is set to false, it will also lookup
-- * for the (ip6.src) in the mac binding table using the "lookup_nd_ip"
-- * action and stores the result in REGBIT_LOOKUP_NEIGHBOR_IP_RESULT
-- * bit.
-- *
-- * Table LEARN_NEIGHBOR learns the mac-binding using the action
-- * - 'put_arp/put_nd'. Learning mac-binding is skipped if
-- * REGBIT_LOOKUP_NEIGHBOR_RESULT bit is set or
-- * REGBIT_LOOKUP_NEIGHBOR_IP_RESULT is not set.
-- *
-- * */
--
-- /* Flows for LOOKUP_NEIGHBOR. */
-- bool learn_from_arp_request = smap_get_bool(&od->nbr->options,
-- "always_learn_from_arp_request", true);
-- ds_clear(actions);
-- ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT
-- " = lookup_arp(inport, arp.spa, arp.sha); %snext;",
-- learn_from_arp_request ? "" :
-- REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" = 1; ");
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100,
-- "arp.op == 2", ds_cstr(actions));
--
-- ds_clear(actions);
-- ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT
-- " = lookup_nd(inport, nd.target, nd.tll); %snext;",
-- learn_from_arp_request ? "" :
-- REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" = 1; ");
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100, "nd_na",
-- ds_cstr(actions));
--
-- ds_clear(actions);
-- ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT
-- " = lookup_nd(inport, ip6.src, nd.sll); %snext;",
-- learn_from_arp_request ? "" :
-- REGBIT_LOOKUP_NEIGHBOR_IP_RESULT
-- " = lookup_nd_ip(inport, ip6.src); ");
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100, "nd_ns",
-- ds_cstr(actions));
--
-- /* For other packet types, we can skip neighbor learning.
-- * So set REGBIT_LOOKUP_NEIGHBOR_RESULT to 1. */
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 0, "1",
-- REGBIT_LOOKUP_NEIGHBOR_RESULT" = 1; next;");
--
-- /* Flows for LEARN_NEIGHBOR. */
-- /* Skip Neighbor learning if not required. */
-- ds_clear(match);
-- ds_put_format(match, REGBIT_LOOKUP_NEIGHBOR_RESULT" == 1%s",
-- learn_from_arp_request ? "" :
-- " || "REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" == 0");
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 100,
-- ds_cstr(match), "next;");
--
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90,
-- "arp", "put_arp(inport, arp.spa, arp.sha); next;");
-
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90,
-- "nd_na", "put_nd(inport, nd.target, nd.tll); next;");
--
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90,
-- "nd_ns", "put_nd(inport, ip6.src, nd.sll); next;");
-- }
-+ for (size_t i = 0; i < op->od->n_router_ports; i++) {
-+ const char *router_port_name = smap_get(
-+ &op->od->router_ports[i]->nbsp->options,
-+ "router-port");
-+ struct ovn_port *router_port = ovn_port_find(ports,
-+ router_port_name);
-+ if (!router_port || !router_port->nbrp) {
-+ continue;
-+ }
-
--}
-+ /* Skip the router port under consideration. */
-+ if (router_port == peer) {
-+ continue;
-+ }
-
--/* Logical router ingress Table 1: Neighbor lookup lflows
-- * for logical router ports. */
--static void
--build_neigh_learning_flows_for_lrouter_port(
-- struct ovn_port *op, struct hmap *lflows,
-- struct ds *match, struct ds *actions)
--{
-- if (op->nbrp) {
-+ if (router_port->lrp_networks.n_ipv4_addrs) {
-+ ds_clear(match);
-+ ds_put_format(match, "outport == %s && "
-+ REG_NEXT_HOP_IPV4 " == ",
-+ peer->json_key);
-+ op_put_v4_networks(match, router_port, false);
-
-- bool learn_from_arp_request = smap_get_bool(&op->od->nbr->options,
-- "always_learn_from_arp_request", true);
-+ ds_clear(actions);
-+ ds_put_format(actions, "eth.dst = %s; next;",
-+ router_port->lrp_networks.ea_s);
-+ ovn_lflow_add_with_hint(lflows, peer->od,
-+ S_ROUTER_IN_ARP_RESOLVE, 100,
-+ ds_cstr(match), ds_cstr(actions),
-+ &op->nbsp->header_);
-+ }
-
-- /* Check if we need to learn mac-binding from ARP requests. */
-- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
-- if (!learn_from_arp_request) {
-- /* ARP request to this address should always get learned,
-- * so add a priority-110 flow to set
-- * REGBIT_LOOKUP_NEIGHBOR_IP_RESULT to 1. */
-+ if (router_port->lrp_networks.n_ipv6_addrs) {
- ds_clear(match);
-- ds_put_format(match,
-- "inport == %s && arp.spa == %s/%u && "
-- "arp.tpa == %s && arp.op == 1",
-- op->json_key,
-- op->lrp_networks.ipv4_addrs[i].network_s,
-- op->lrp_networks.ipv4_addrs[i].plen,
-- op->lrp_networks.ipv4_addrs[i].addr_s);
-- if (op->od->l3dgw_port && op == op->od->l3dgw_port
-- && op->od->l3redirect_port) {
-- ds_put_format(match, " && is_chassis_resident(%s)",
-- op->od->l3redirect_port->json_key);
-- }
-- const char *actions_s = REGBIT_LOOKUP_NEIGHBOR_RESULT
-- " = lookup_arp(inport, arp.spa, arp.sha); "
-- REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" = 1;"
-- " next;";
-- ovn_lflow_add_with_hint(lflows, op->od,
-- S_ROUTER_IN_LOOKUP_NEIGHBOR, 110,
-- ds_cstr(match), actions_s,
-- &op->nbrp->header_);
-- }
-- ds_clear(match);
-- ds_put_format(match,
-- "inport == %s && arp.spa == %s/%u && arp.op == 1",
-- op->json_key,
-- op->lrp_networks.ipv4_addrs[i].network_s,
-- op->lrp_networks.ipv4_addrs[i].plen);
-- if (op->od->l3dgw_port && op == op->od->l3dgw_port
-- && op->od->l3redirect_port) {
-- ds_put_format(match, " && is_chassis_resident(%s)",
-- op->od->l3redirect_port->json_key);
-+ ds_put_format(match, "outport == %s && "
-+ REG_NEXT_HOP_IPV6 " == ",
-+ peer->json_key);
-+ op_put_v6_networks(match, router_port);
-+
-+ ds_clear(actions);
-+ ds_put_format(actions, "eth.dst = %s; next;",
-+ router_port->lrp_networks.ea_s);
-+ ovn_lflow_add_with_hint(lflows, peer->od,
-+ S_ROUTER_IN_ARP_RESOLVE, 100,
-+ ds_cstr(match), ds_cstr(actions),
-+ &op->nbsp->header_);
- }
-- ds_clear(actions);
-- ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT
-- " = lookup_arp(inport, arp.spa, arp.sha); %snext;",
-- learn_from_arp_request ? "" :
-- REGBIT_LOOKUP_NEIGHBOR_IP_RESULT
-- " = lookup_arp_ip(inport, arp.spa); ");
-- ovn_lflow_add_with_hint(lflows, op->od,
-- S_ROUTER_IN_LOOKUP_NEIGHBOR, 100,
-- ds_cstr(match), ds_cstr(actions),
-- &op->nbrp->header_);
- }
- }
-+
- }
-
--/* Logical router ingress table ND_RA_OPTIONS & ND_RA_RESPONSE: IPv6 Router
-- * Adv (RA) options and response. */
-+/* Local router ingress table CHK_PKT_LEN: Check packet length.
-+ *
-+ * Any IPv4 packet with outport set to the distributed gateway
-+ * router port, check the packet length and store the result in the
-+ * 'REGBIT_PKT_LARGER' register bit.
-+ *
-+ * Local router ingress table LARGER_PKTS: Handle larger packets.
-+ *
-+ * Any IPv4 packet with outport set to the distributed gateway
-+ * router port and the 'REGBIT_PKT_LARGER' register bit is set,
-+ * generate ICMPv4 packet with type 3 (Destination Unreachable) and
-+ * code 4 (Fragmentation needed).
-+ * */
- static void
--build_ND_RA_flows_for_lrouter_port(
-- struct ovn_port *op, struct hmap *lflows,
-+build_check_pkt_len_flows_for_lrouter(
-+ struct ovn_datapath *od, struct hmap *lflows,
-+ struct hmap *ports,
- struct ds *match, struct ds *actions)
- {
-- if (!op->nbrp || op->nbrp->peer || !op->peer) {
-- return;
-- }
--
-- if (!op->lrp_networks.n_ipv6_addrs) {
-- return;
-- }
--
-- struct smap options;
-- smap_clone(&options, &op->sb->options);
--
-- /* enable IPv6 prefix delegation */
-- bool prefix_delegation = smap_get_bool(&op->nbrp->options,
-- "prefix_delegation", false);
-- if (!lrport_is_enabled(op->nbrp)) {
-- prefix_delegation = false;
-- }
-- smap_add(&options, "ipv6_prefix_delegation",
-- prefix_delegation ? "true" : "false");
-+ if (od->nbr) {
-
-- bool ipv6_prefix = smap_get_bool(&op->nbrp->options,
-- "prefix", false);
-- if (!lrport_is_enabled(op->nbrp)) {
-- ipv6_prefix = false;
-- }
-- smap_add(&options, "ipv6_prefix",
-- ipv6_prefix ? "true" : "false");
-- sbrec_port_binding_set_options(op->sb, &options);
-+ /* Packets are allowed by default. */
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_CHK_PKT_LEN, 0, "1",
-+ "next;");
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_LARGER_PKTS, 0, "1",
-+ "next;");
-
-- smap_destroy(&options);
-+ if (od->l3dgw_port && od->l3redirect_port) {
-+ int gw_mtu = 0;
-+ if (od->l3dgw_port->nbrp) {
-+ gw_mtu = smap_get_int(&od->l3dgw_port->nbrp->options,
-+ "gateway_mtu", 0);
-+ }
-+ /* Add the flows only if gateway_mtu is configured. */
-+ if (gw_mtu <= 0) {
-+ return;
-+ }
-
-- const char *address_mode = smap_get(
-- &op->nbrp->ipv6_ra_configs, "address_mode");
-+ ds_clear(match);
-+ ds_put_format(match, "outport == %s", od->l3dgw_port->json_key);
-
-- if (!address_mode) {
-- return;
-- }
-- if (strcmp(address_mode, "slaac") &&
-- strcmp(address_mode, "dhcpv6_stateful") &&
-- strcmp(address_mode, "dhcpv6_stateless")) {
-- static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-- VLOG_WARN_RL(&rl, "Invalid address mode [%s] defined",
-- address_mode);
-- return;
-- }
-+ ds_clear(actions);
-+ ds_put_format(actions,
-+ REGBIT_PKT_LARGER" = check_pkt_larger(%d);"
-+ " next;", gw_mtu + VLAN_ETH_HEADER_LEN);
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_CHK_PKT_LEN, 50,
-+ ds_cstr(match), ds_cstr(actions),
-+ &od->l3dgw_port->nbrp->header_);
-
-- if (smap_get_bool(&op->nbrp->ipv6_ra_configs, "send_periodic",
-- false)) {
-- copy_ra_to_sb(op, address_mode);
-- }
-+ for (size_t i = 0; i < od->nbr->n_ports; i++) {
-+ struct ovn_port *rp = ovn_port_find(ports,
-+ od->nbr->ports[i]->name);
-+ if (!rp || rp == od->l3dgw_port) {
-+ continue;
-+ }
-
-- ds_clear(match);
-- ds_put_format(match, "inport == %s && ip6.dst == ff02::2 && nd_rs",
-- op->json_key);
-- ds_clear(actions);
-+ if (rp->lrp_networks.ipv4_addrs) {
-+ ds_clear(match);
-+ ds_put_format(match, "inport == %s && outport == %s"
-+ " && ip4 && "REGBIT_PKT_LARGER,
-+ rp->json_key, od->l3dgw_port->json_key);
-
-- const char *mtu_s = smap_get(
-- &op->nbrp->ipv6_ra_configs, "mtu");
-+ ds_clear(actions);
-+ /* Set icmp4.frag_mtu to gw_mtu */
-+ ds_put_format(actions,
-+ "icmp4_error {"
-+ REGBIT_EGRESS_LOOPBACK" = 1; "
-+ "eth.dst = %s; "
-+ "ip4.dst = ip4.src; "
-+ "ip4.src = %s; "
-+ "ip.ttl = 255; "
-+ "icmp4.type = 3; /* Destination Unreachable. */ "
-+ "icmp4.code = 4; /* Frag Needed and DF was Set. */ "
-+ "icmp4.frag_mtu = %d; "
-+ "next(pipeline=ingress, table=%d); };",
-+ rp->lrp_networks.ea_s,
-+ rp->lrp_networks.ipv4_addrs[0].addr_s,
-+ gw_mtu,
-+ ovn_stage_get_table(S_ROUTER_IN_ADMISSION));
-+ ovn_lflow_add_with_hint(lflows, od,
-+ S_ROUTER_IN_LARGER_PKTS, 50,
-+ ds_cstr(match), ds_cstr(actions),
-+ &rp->nbrp->header_);
-+ }
-
-- /* As per RFC 2460, 1280 is minimum IPv6 MTU. */
-- uint32_t mtu = (mtu_s && atoi(mtu_s) >= 1280) ? atoi(mtu_s) : 0;
-+ if (rp->lrp_networks.ipv6_addrs) {
-+ ds_clear(match);
-+ ds_put_format(match, "inport == %s && outport == %s"
-+ " && ip6 && "REGBIT_PKT_LARGER,
-+ rp->json_key, od->l3dgw_port->json_key);
-
-- ds_put_format(actions, REGBIT_ND_RA_OPTS_RESULT" = put_nd_ra_opts("
-- "addr_mode = \"%s\", slla = %s",
-- address_mode, op->lrp_networks.ea_s);
-- if (mtu > 0) {
-- ds_put_format(actions, ", mtu = %u", mtu);
-+ ds_clear(actions);
-+ /* Set icmp6.frag_mtu to gw_mtu */
-+ ds_put_format(actions,
-+ "icmp6_error {"
-+ REGBIT_EGRESS_LOOPBACK" = 1; "
-+ "eth.dst = %s; "
-+ "ip6.dst = ip6.src; "
-+ "ip6.src = %s; "
-+ "ip.ttl = 255; "
-+ "icmp6.type = 2; /* Packet Too Big. */ "
-+ "icmp6.code = 0; "
-+ "icmp6.frag_mtu = %d; "
-+ "next(pipeline=ingress, table=%d); };",
-+ rp->lrp_networks.ea_s,
-+ rp->lrp_networks.ipv6_addrs[0].addr_s,
-+ gw_mtu,
-+ ovn_stage_get_table(S_ROUTER_IN_ADMISSION));
-+ ovn_lflow_add_with_hint(lflows, od,
-+ S_ROUTER_IN_LARGER_PKTS, 50,
-+ ds_cstr(match), ds_cstr(actions),
-+ &rp->nbrp->header_);
-+ }
-+ }
-+ }
- }
-+}
-
-- const char *prf = smap_get_def(
-- &op->nbrp->ipv6_ra_configs, "router_preference", "MEDIUM");
-- if (strcmp(prf, "MEDIUM")) {
-- ds_put_format(actions, ", router_preference = \"%s\"", prf);
-- }
-+/* Logical router ingress table GW_REDIRECT: Gateway redirect.
-+ *
-+ * For traffic with outport equal to the l3dgw_port
-+ * on a distributed router, this table redirects a subset
-+ * of the traffic to the l3redirect_port which represents
-+ * the central instance of the l3dgw_port.
-+ */
-+static void
-+build_gateway_redirect_flows_for_lrouter(
-+ struct ovn_datapath *od, struct hmap *lflows,
-+ struct ds *match, struct ds *actions)
-+{
-+ if (od->nbr) {
-+ if (od->l3dgw_port && od->l3redirect_port) {
-+ const struct ovsdb_idl_row *stage_hint = NULL;
-
-- bool add_rs_response_flow = false;
-+ if (od->l3dgw_port->nbrp) {
-+ stage_hint = &od->l3dgw_port->nbrp->header_;
-+ }
-
-- for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
-- if (in6_is_lla(&op->lrp_networks.ipv6_addrs[i].network)) {
-- continue;
-+ /* For traffic with outport == l3dgw_port, if the
-+ * packet did not match any higher priority redirect
-+ * rule, then the traffic is redirected to the central
-+ * instance of the l3dgw_port. */
-+ ds_clear(match);
-+ ds_put_format(match, "outport == %s",
-+ od->l3dgw_port->json_key);
-+ ds_clear(actions);
-+ ds_put_format(actions, "outport = %s; next;",
-+ od->l3redirect_port->json_key);
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_GW_REDIRECT, 50,
-+ ds_cstr(match), ds_cstr(actions),
-+ stage_hint);
- }
-
-- ds_put_format(actions, ", prefix = %s/%u",
-- op->lrp_networks.ipv6_addrs[i].network_s,
-- op->lrp_networks.ipv6_addrs[i].plen);
--
-- add_rs_response_flow = true;
-- }
--
-- if (add_rs_response_flow) {
-- ds_put_cstr(actions, "); next;");
-- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_ND_RA_OPTIONS,
-- 50, ds_cstr(match), ds_cstr(actions),
-- &op->nbrp->header_);
-- ds_clear(actions);
-- ds_clear(match);
-- ds_put_format(match, "inport == %s && ip6.dst == ff02::2 && "
-- "nd_ra && "REGBIT_ND_RA_OPTS_RESULT, op->json_key);
--
-- char ip6_str[INET6_ADDRSTRLEN + 1];
-- struct in6_addr lla;
-- in6_generate_lla(op->lrp_networks.ea, &lla);
-- memset(ip6_str, 0, sizeof(ip6_str));
-- ipv6_string_mapped(ip6_str, &lla);
-- ds_put_format(actions, "eth.dst = eth.src; eth.src = %s; "
-- "ip6.dst = ip6.src; ip6.src = %s; "
-- "outport = inport; flags.loopback = 1; "
-- "output;",
-- op->lrp_networks.ea_s, ip6_str);
-- ovn_lflow_add_with_hint(lflows, op->od,
-- S_ROUTER_IN_ND_RA_RESPONSE, 50,
-- ds_cstr(match), ds_cstr(actions),
-- &op->nbrp->header_);
-+ /* Packets are allowed by default. */
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_GW_REDIRECT, 0, "1", "next;");
- }
- }
-
--/* Logical router ingress table ND_RA_OPTIONS & ND_RA_RESPONSE: RS
-- * responder, by default goto next. (priority 0). */
-+/* Local router ingress table ARP_REQUEST: ARP request.
-+ *
-+ * In the common case where the Ethernet destination has been resolved,
-+ * this table outputs the packet (priority 0). Otherwise, it composes
-+ * and sends an ARP/IPv6 NA request (priority 100). */
- static void
--build_ND_RA_flows_for_lrouter(struct ovn_datapath *od, struct hmap *lflows)
-+build_arp_request_flows_for_lrouter(
-+ struct ovn_datapath *od, struct hmap *lflows,
-+ struct ds *match, struct ds *actions)
- {
- if (od->nbr) {
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_ND_RA_OPTIONS, 0, "1", "next;");
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_ND_RA_RESPONSE, 0, "1", "next;");
-+ for (int i = 0; i < od->nbr->n_static_routes; i++) {
-+ const struct nbrec_logical_router_static_route *route;
-+
-+ route = od->nbr->static_routes[i];
-+ struct in6_addr gw_ip6;
-+ unsigned int plen;
-+ char *error = ipv6_parse_cidr(route->nexthop, &gw_ip6, &plen);
-+ if (error || plen != 128) {
-+ free(error);
-+ continue;
-+ }
-+
-+ ds_clear(match);
-+ ds_put_format(match, "eth.dst == 00:00:00:00:00:00 && "
-+ "ip6 && " REG_NEXT_HOP_IPV6 " == %s",
-+ route->nexthop);
-+ struct in6_addr sn_addr;
-+ struct eth_addr eth_dst;
-+ in6_addr_solicited_node(&sn_addr, &gw_ip6);
-+ ipv6_multicast_to_ethernet(ð_dst, &sn_addr);
-+
-+ char sn_addr_s[INET6_ADDRSTRLEN + 1];
-+ ipv6_string_mapped(sn_addr_s, &sn_addr);
-+
-+ ds_clear(actions);
-+ ds_put_format(actions,
-+ "nd_ns { "
-+ "eth.dst = "ETH_ADDR_FMT"; "
-+ "ip6.dst = %s; "
-+ "nd.target = %s; "
-+ "output; "
-+ "};", ETH_ADDR_ARGS(eth_dst), sn_addr_s,
-+ route->nexthop);
-+
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ARP_REQUEST, 200,
-+ ds_cstr(match), ds_cstr(actions),
-+ &route->header_);
-+ }
-+
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 100,
-+ "eth.dst == 00:00:00:00:00:00 && ip4",
-+ "arp { "
-+ "eth.dst = ff:ff:ff:ff:ff:ff; "
-+ "arp.spa = " REG_SRC_IPV4 "; "
-+ "arp.tpa = " REG_NEXT_HOP_IPV4 "; "
-+ "arp.op = 1; " /* ARP request */
-+ "output; "
-+ "};");
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 100,
-+ "eth.dst == 00:00:00:00:00:00 && ip6",
-+ "nd_ns { "
-+ "nd.target = " REG_NEXT_HOP_IPV6 "; "
-+ "output; "
-+ "};");
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 0, "1", "output;");
- }
- }
-
--/* Logical router ingress table IP_ROUTING : IP Routing.
-- *
-- * A packet that arrives at this table is an IP packet that should be
-- * routed to the address in 'ip[46].dst'.
-- *
-- * For regular routes without ECMP, table IP_ROUTING sets outport to the
-- * correct output port, eth.src to the output port's MAC address, and
-- * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 to the next-hop IP address
-- * (leaving 'ip[46].dst', the packet’s final destination, unchanged), and
-- * advances to the next table.
-+/* Logical router egress table DELIVERY: Delivery (priority 100-110).
- *
-- * For ECMP routes, i.e. multiple routes with same policy and prefix, table
-- * IP_ROUTING remembers ECMP group id and selects a member id, and advances
-- * to table IP_ROUTING_ECMP, which sets outport, eth.src and
-- * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 for the selected ECMP member.
-+ * Priority 100 rules deliver packets to enabled logical ports.
-+ * Priority 110 rules match multicast packets and update the source
-+ * mac before delivering to enabled logical ports. IP multicast traffic
-+ * bypasses S_ROUTER_IN_IP_ROUTING route lookups.
- */
- static void
--build_ip_routing_flows_for_lrouter_port(
-- struct ovn_port *op, struct hmap *lflows)
-+build_egress_delivery_flows_for_lrouter_port(
-+ struct ovn_port *op, struct hmap *lflows,
-+ struct ds *match, struct ds *actions)
- {
- if (op->nbrp) {
-+ if (!lrport_is_enabled(op->nbrp)) {
-+ /* Drop packets to disabled logical ports (since logical flow
-+ * tables are default-drop). */
-+ return;
-+ }
-
-- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
-- add_route(lflows, op, op->lrp_networks.ipv4_addrs[i].addr_s,
-- op->lrp_networks.ipv4_addrs[i].network_s,
-- op->lrp_networks.ipv4_addrs[i].plen, NULL, false,
-- &op->nbrp->header_);
-+ if (op->derived) {
-+ /* No egress packets should be processed in the context of
-+ * a chassisredirect port. The chassisredirect port should
-+ * be replaced by the l3dgw port in the local output
-+ * pipeline stage before egress processing. */
-+ return;
- }
-
-- for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
-- add_route(lflows, op, op->lrp_networks.ipv6_addrs[i].addr_s,
-- op->lrp_networks.ipv6_addrs[i].network_s,
-- op->lrp_networks.ipv6_addrs[i].plen, NULL, false,
-- &op->nbrp->header_);
-+ /* If multicast relay is enabled then also adjust source mac for IP
-+ * multicast traffic.
-+ */
-+ if (op->od->mcast_info.rtr.relay) {
-+ ds_clear(match);
-+ ds_clear(actions);
-+ ds_put_format(match, "(ip4.mcast || ip6.mcast) && outport == %s",
-+ op->json_key);
-+ ds_put_format(actions, "eth.src = %s; output;",
-+ op->lrp_networks.ea_s);
-+ ovn_lflow_add(lflows, op->od, S_ROUTER_OUT_DELIVERY, 110,
-+ ds_cstr(match), ds_cstr(actions));
- }
-+
-+ ds_clear(match);
-+ ds_put_format(match, "outport == %s", op->json_key);
-+ ovn_lflow_add(lflows, op->od, S_ROUTER_OUT_DELIVERY, 100,
-+ ds_cstr(match), "output;");
-+ }
-+
-+}
-+
-+static void
-+build_misc_local_traffic_drop_flows_for_lrouter(
-+ struct ovn_datapath *od, struct hmap *lflows)
-+{
-+ if (od->nbr) {
-+ /* L3 admission control: drop multicast and broadcast source, localhost
-+ * source or destination, and zero network source or destination
-+ * (priority 100). */
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 100,
-+ "ip4.src_mcast ||"
-+ "ip4.src == 255.255.255.255 || "
-+ "ip4.src == 127.0.0.0/8 || "
-+ "ip4.dst == 127.0.0.0/8 || "
-+ "ip4.src == 0.0.0.0/8 || "
-+ "ip4.dst == 0.0.0.0/8",
-+ "drop;");
-+
-+ /* Drop ARP packets (priority 85). ARP request packets for router's own
-+ * IPs are handled with priority-90 flows.
-+ * Drop IPv6 ND packets (priority 85). ND NA packets for router's own
-+ * IPs are handled with priority-90 flows.
-+ */
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 85,
-+ "arp || nd", "drop;");
-+
-+ /* Allow IPv6 multicast traffic that's supposed to reach the
-+ * router pipeline (e.g., router solicitations).
-+ */
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 84, "nd_rs || nd_ra",
-+ "next;");
-+
-+ /* Drop other reserved multicast. */
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 83,
-+ "ip6.mcast_rsvd", "drop;");
-+
-+ /* Allow other multicast if relay enabled (priority 82). */
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 82,
-+ "ip4.mcast || ip6.mcast",
-+ od->mcast_info.rtr.relay ? "next;" : "drop;");
-+
-+ /* Drop Ethernet local broadcast. By definition this traffic should
-+ * not be forwarded.*/
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 50,
-+ "eth.bcast", "drop;");
-+
-+ /* TTL discard */
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 30,
-+ "ip4 && ip.ttl == {0, 1}", "drop;");
-+
-+ /* Pass other traffic not already handled to the next table for
-+ * routing. */
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 0, "1", "next;");
- }
- }
-
- static void
--build_static_route_flows_for_lrouter(
-- struct ovn_datapath *od, struct hmap *lflows,
-- struct hmap *ports)
-+build_dhcpv6_reply_flows_for_lrouter_port(
-+ struct ovn_port *op, struct hmap *lflows,
-+ struct ds *match)
- {
-- if (od->nbr) {
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING_ECMP, 150,
-- REG_ECMP_GROUP_ID" == 0", "next;");
--
-- struct hmap ecmp_groups = HMAP_INITIALIZER(&ecmp_groups);
-- struct hmap unique_routes = HMAP_INITIALIZER(&unique_routes);
-- struct ovs_list parsed_routes = OVS_LIST_INITIALIZER(&parsed_routes);
-- struct ecmp_groups_node *group;
-- for (int i = 0; i < od->nbr->n_static_routes; i++) {
-- struct parsed_route *route =
-- parsed_routes_add(&parsed_routes, od->nbr->static_routes[i]);
-- if (!route) {
-- continue;
-- }
-- group = ecmp_groups_find(&ecmp_groups, route);
-- if (group) {
-- ecmp_groups_add_route(group, route);
-- } else {
-- const struct parsed_route *existed_route =
-- unique_routes_remove(&unique_routes, route);
-- if (existed_route) {
-- group = ecmp_groups_add(&ecmp_groups, existed_route);
-- if (group) {
-- ecmp_groups_add_route(group, route);
-- }
-- } else {
-- unique_routes_add(&unique_routes, route);
-- }
-- }
-- }
-- HMAP_FOR_EACH (group, hmap_node, &ecmp_groups) {
-- /* add a flow in IP_ROUTING, and one flow for each member in
-- * IP_ROUTING_ECMP. */
-- build_ecmp_route_flow(lflows, od, ports, group);
-- }
-- const struct unique_routes_node *ur;
-- HMAP_FOR_EACH (ur, hmap_node, &unique_routes) {
-- build_static_route_flow(lflows, od, ports, ur->route);
-+ if (op->nbrp && (!op->derived)) {
-+ for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
-+ ds_clear(match);
-+ ds_put_format(match, "ip6.dst == %s && udp.src == 547 &&"
-+ " udp.dst == 546",
-+ op->lrp_networks.ipv6_addrs[i].addr_s);
-+ ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 100,
-+ ds_cstr(match),
-+ "reg0 = 0; handle_dhcpv6_reply;");
- }
-- ecmp_groups_destroy(&ecmp_groups);
-- unique_routes_destroy(&unique_routes);
-- parsed_routes_destroy(&parsed_routes);
- }
-+
- }
-
--/* IP Multicast lookup. Here we set the output port, adjust TTL and
-- * advance to next table (priority 500).
-- */
- static void
--build_mcast_lookup_flows_for_lrouter(
-- struct ovn_datapath *od, struct hmap *lflows,
-+build_ipv6_input_flows_for_lrouter_port(
-+ struct ovn_port *op, struct hmap *lflows,
- struct ds *match, struct ds *actions)
- {
-- if (od->nbr) {
-+ if (op->nbrp && (!op->derived)) {
-+ /* No ingress packets are accepted on a chassisredirect
-+ * port, so no need to program flows for that port. */
-+ if (op->lrp_networks.n_ipv6_addrs) {
-+ /* ICMPv6 echo reply. These flows reply to echo requests
-+ * received for the router's IP address. */
-+ ds_clear(match);
-+ ds_put_cstr(match, "ip6.dst == ");
-+ op_put_v6_networks(match, op);
-+ ds_put_cstr(match, " && icmp6.type == 128 && icmp6.code == 0");
-
-- /* Drop IPv6 multicast traffic that shouldn't be forwarded,
-- * i.e., router solicitation and router advertisement.
-- */
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 550,
-- "nd_rs || nd_ra", "drop;");
-- if (!od->mcast_info.rtr.relay) {
-- return;
-+ const char *lrp_actions =
-+ "ip6.dst <-> ip6.src; "
-+ "ip.ttl = 255; "
-+ "icmp6.type = 129; "
-+ "flags.loopback = 1; "
-+ "next; ";
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90,
-+ ds_cstr(match), lrp_actions,
-+ &op->nbrp->header_);
- }
-
-- struct ovn_igmp_group *igmp_group;
--
-- LIST_FOR_EACH (igmp_group, list_node, &od->mcast_info.groups) {
-+ /* ND reply. These flows reply to ND solicitations for the
-+ * router's own IP address. */
-+ for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
- ds_clear(match);
-- ds_clear(actions);
-- if (IN6_IS_ADDR_V4MAPPED(&igmp_group->address)) {
-- ds_put_format(match, "ip4 && ip4.dst == %s ",
-- igmp_group->mcgroup.name);
-- } else {
-- ds_put_format(match, "ip6 && ip6.dst == %s ",
-- igmp_group->mcgroup.name);
-- }
-- if (od->mcast_info.rtr.flood_static) {
-- ds_put_cstr(actions,
-- "clone { "
-- "outport = \""MC_STATIC"\"; "
-- "ip.ttl--; "
-- "next; "
-- "};");
-+ if (op->od->l3dgw_port && op == op->od->l3dgw_port
-+ && op->od->l3redirect_port) {
-+ /* Traffic with eth.src = l3dgw_port->lrp_networks.ea_s
-+ * should only be sent from the gateway chassi, so that
-+ * upstream MAC learning points to the gateway chassis.
-+ * Also need to avoid generation of multiple ND replies
-+ * from different chassis. */
-+ ds_put_format(match, "is_chassis_resident(%s)",
-+ op->od->l3redirect_port->json_key);
- }
-- ds_put_format(actions, "outport = \"%s\"; ip.ttl--; next;",
-- igmp_group->mcgroup.name);
-- ovn_lflow_add_unique(lflows, od, S_ROUTER_IN_IP_ROUTING, 500,
-- ds_cstr(match), ds_cstr(actions));
-+
-+ build_lrouter_nd_flow(op->od, op, "nd_na_router",
-+ op->lrp_networks.ipv6_addrs[i].addr_s,
-+ op->lrp_networks.ipv6_addrs[i].sn_addr_s,
-+ REG_INPORT_ETH_ADDR, match, false, 90,
-+ &op->nbrp->header_, lflows);
- }
-
-- /* If needed, flood unregistered multicast on statically configured
-- * ports. Otherwise drop any multicast traffic.
-- */
-- if (od->mcast_info.rtr.flood_static) {
-- ovn_lflow_add_unique(lflows, od, S_ROUTER_IN_IP_ROUTING, 450,
-- "ip4.mcast || ip6.mcast",
-- "clone { "
-- "outport = \""MC_STATIC"\"; "
-- "ip.ttl--; "
-- "next; "
-- "};");
-- } else {
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 450,
-- "ip4.mcast || ip6.mcast", "drop;");
-+ /* UDP/TCP/SCTP port unreachable */
-+ if (!smap_get(&op->od->nbr->options, "chassis")
-+ && !op->od->l3dgw_port) {
-+ for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
-+ ds_clear(match);
-+ ds_put_format(match,
-+ "ip6 && ip6.dst == %s && !ip.later_frag && tcp",
-+ op->lrp_networks.ipv6_addrs[i].addr_s);
-+ const char *action = "tcp_reset {"
-+ "eth.dst <-> eth.src; "
-+ "ip6.dst <-> ip6.src; "
-+ "next; };";
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-+ 80, ds_cstr(match), action,
-+ &op->nbrp->header_);
-+
-+ ds_clear(match);
-+ ds_put_format(match,
-+ "ip6 && ip6.dst == %s && !ip.later_frag && sctp",
-+ op->lrp_networks.ipv6_addrs[i].addr_s);
-+ action = "sctp_abort {"
-+ "eth.dst <-> eth.src; "
-+ "ip6.dst <-> ip6.src; "
-+ "next; };";
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-+ 80, ds_cstr(match), action,
-+ &op->nbrp->header_);
-+
-+ ds_clear(match);
-+ ds_put_format(match,
-+ "ip6 && ip6.dst == %s && !ip.later_frag && udp",
-+ op->lrp_networks.ipv6_addrs[i].addr_s);
-+ action = "icmp6 {"
-+ "eth.dst <-> eth.src; "
-+ "ip6.dst <-> ip6.src; "
-+ "ip.ttl = 255; "
-+ "icmp6.type = 1; "
-+ "icmp6.code = 4; "
-+ "next; };";
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-+ 80, ds_cstr(match), action,
-+ &op->nbrp->header_);
-+
-+ ds_clear(match);
-+ ds_put_format(match,
-+ "ip6 && ip6.dst == %s && !ip.later_frag",
-+ op->lrp_networks.ipv6_addrs[i].addr_s);
-+ action = "icmp6 {"
-+ "eth.dst <-> eth.src; "
-+ "ip6.dst <-> ip6.src; "
-+ "ip.ttl = 255; "
-+ "icmp6.type = 1; "
-+ "icmp6.code = 3; "
-+ "next; };";
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-+ 70, ds_cstr(match), action,
-+ &op->nbrp->header_);
-+ }
- }
-- }
--}
-
--/* Logical router ingress table POLICY: Policy.
-- *
-- * A packet that arrives at this table is an IP packet that should be
-- * permitted/denied/rerouted to the address in the rule's nexthop.
-- * This table sets outport to the correct out_port,
-- * eth.src to the output port's MAC address,
-- * and REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 to the next-hop IP address
-- * (leaving 'ip[46].dst', the packet’s final destination, unchanged), and
-- * advances to the next table for ARP/ND resolution. */
--static void
--build_ingress_policy_flows_for_lrouter(
-- struct ovn_datapath *od, struct hmap *lflows,
-- struct hmap *ports)
--{
-- if (od->nbr) {
-- /* This is a catch-all rule. It has the lowest priority (0)
-- * does a match-all("1") and pass-through (next) */
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_POLICY, 0, "1", "next;");
-+ /* ICMPv6 time exceeded */
-+ for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
-+ /* skip link-local address */
-+ if (in6_is_lla(&op->lrp_networks.ipv6_addrs[i].network)) {
-+ continue;
-+ }
-
-- /* Convert routing policies to flows. */
-- for (int i = 0; i < od->nbr->n_policies; i++) {
-- const struct nbrec_logical_router_policy *rule
-- = od->nbr->policies[i];
-- build_routing_policy_flow(lflows, od, ports, rule, &rule->header_);
-+ ds_clear(match);
-+ ds_clear(actions);
-+
-+ ds_put_format(match,
-+ "inport == %s && ip6 && "
-+ "ip6.src == %s/%d && "
-+ "ip.ttl == {0, 1} && !ip.later_frag",
-+ op->json_key,
-+ op->lrp_networks.ipv6_addrs[i].network_s,
-+ op->lrp_networks.ipv6_addrs[i].plen);
-+ ds_put_format(actions,
-+ "icmp6 {"
-+ "eth.dst <-> eth.src; "
-+ "ip6.dst = ip6.src; "
-+ "ip6.src = %s; "
-+ "ip.ttl = 255; "
-+ "icmp6.type = 3; /* Time exceeded */ "
-+ "icmp6.code = 0; /* TTL exceeded in transit */ "
-+ "next; };",
-+ op->lrp_networks.ipv6_addrs[i].addr_s);
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 40,
-+ ds_cstr(match), ds_cstr(actions),
-+ &op->nbrp->header_);
- }
- }
-+
- }
-
--/* Local router ingress table ARP_RESOLVE: ARP Resolution. */
- static void
--build_arp_resolve_flows_for_lrouter(
-- struct ovn_datapath *od, struct hmap *lflows)
-+build_lrouter_arp_nd_for_datapath(struct ovn_datapath *od,
-+ struct hmap *lflows)
- {
- if (od->nbr) {
-- /* Multicast packets already have the outport set so just advance to
-- * next table (priority 500). */
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 500,
-- "ip4.mcast || ip6.mcast", "next;");
-
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 0, "ip4",
-- "get_arp(outport, " REG_NEXT_HOP_IPV4 "); next;");
-+ /* Priority-90-92 flows handle ARP requests and ND packets. Most are
-+ * per logical port but DNAT addresses can be handled per datapath
-+ * for non gateway router ports.
-+ *
-+ * Priority 91 and 92 flows are added for each gateway router
-+ * port to handle the special cases. In case we get the packet
-+ * on a regular port, just reply with the port's ETH address.
-+ */
-+ for (int i = 0; i < od->nbr->n_nat; i++) {
-+ struct ovn_nat *nat_entry = &od->nat_entries[i];
-
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 0, "ip6",
-- "get_nd(outport, " REG_NEXT_HOP_IPV6 "); next;");
-+ /* Skip entries we failed to parse. */
-+ if (!nat_entry_is_valid(nat_entry)) {
-+ continue;
-+ }
-+
-+ /* Skip SNAT entries for now, we handle unique SNAT IPs separately
-+ * below.
-+ */
-+ if (!strcmp(nat_entry->nb->type, "snat")) {
-+ continue;
-+ }
-+ build_lrouter_nat_arp_nd_flow(od, nat_entry, lflows);
-+ }
-+
-+ /* Now handle SNAT entries too, one per unique SNAT IP. */
-+ struct shash_node *snat_snode;
-+ SHASH_FOR_EACH (snat_snode, &od->snat_ips) {
-+ struct ovn_snat_ip *snat_ip = snat_snode->data;
-+
-+ if (ovs_list_is_empty(&snat_ip->snat_entries)) {
-+ continue;
-+ }
-+
-+ struct ovn_nat *nat_entry =
-+ CONTAINER_OF(ovs_list_front(&snat_ip->snat_entries),
-+ struct ovn_nat, ext_addr_list_node);
-+ build_lrouter_nat_arp_nd_flow(od, nat_entry, lflows);
-+ }
- }
- }
-
--/* Local router ingress table ARP_RESOLVE: ARP Resolution.
-- *
-- * Any unicast packet that reaches this table is an IP packet whose
-- * next-hop IP address is in REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6
-- * (ip4.dst/ipv6.dst is the final destination).
-- * This table resolves the IP address in
-- * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 into an output port in outport and
-- * an Ethernet address in eth.dst.
-- */
-+/* Logical router ingress table 3: IP Input for IPv4. */
- static void
--build_arp_resolve_flows_for_lrouter_port(
-- struct ovn_port *op, struct hmap *lflows,
-- struct hmap *ports,
-- struct ds *match, struct ds *actions)
-+build_lrouter_ipv4_ip_input(struct ovn_port *op,
-+ struct hmap *lflows,
-+ struct ds *match, struct ds *actions)
- {
-- if (op->nbsp && !lsp_is_enabled(op->nbsp)) {
-- return;
-- }
-+ /* No ingress packets are accepted on a chassisredirect
-+ * port, so no need to program flows for that port. */
-+ if (op->nbrp && (!op->derived)) {
-+ if (op->lrp_networks.n_ipv4_addrs) {
-+ /* L3 admission control: drop packets that originate from an
-+ * IPv4 address owned by the router or a broadcast address
-+ * known to the router (priority 100). */
-+ ds_clear(match);
-+ ds_put_cstr(match, "ip4.src == ");
-+ op_put_v4_networks(match, op, true);
-+ ds_put_cstr(match, " && "REGBIT_EGRESS_LOOPBACK" == 0");
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 100,
-+ ds_cstr(match), "drop;",
-+ &op->nbrp->header_);
-
-- if (op->nbrp) {
-- /* This is a logical router port. If next-hop IP address in
-- * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 matches IP address of this
-- * router port, then the packet is intended to eventually be sent
-- * to this logical port. Set the destination mac address using
-- * this port's mac address.
-- *
-- * The packet is still in peer's logical pipeline. So the match
-- * should be on peer's outport. */
-- if (op->peer && op->nbrp->peer) {
-- if (op->lrp_networks.n_ipv4_addrs) {
-- ds_clear(match);
-- ds_put_format(match, "outport == %s && "
-- REG_NEXT_HOP_IPV4 "== ",
-- op->peer->json_key);
-- op_put_v4_networks(match, op, false);
-+ /* ICMP echo reply. These flows reply to ICMP echo requests
-+ * received for the router's IP address. Since packets only
-+ * get here as part of the logical router datapath, the inport
-+ * (i.e. the incoming locally attached net) does not matter.
-+ * The ip.ttl also does not matter (RFC1812 section 4.2.2.9) */
-+ ds_clear(match);
-+ ds_put_cstr(match, "ip4.dst == ");
-+ op_put_v4_networks(match, op, false);
-+ ds_put_cstr(match, " && icmp4.type == 8 && icmp4.code == 0");
-
-- ds_clear(actions);
-- ds_put_format(actions, "eth.dst = %s; next;",
-- op->lrp_networks.ea_s);
-- ovn_lflow_add_with_hint(lflows, op->peer->od,
-- S_ROUTER_IN_ARP_RESOLVE, 100,
-- ds_cstr(match), ds_cstr(actions),
-- &op->nbrp->header_);
-- }
-+ const char * icmp_actions = "ip4.dst <-> ip4.src; "
-+ "ip.ttl = 255; "
-+ "icmp4.type = 0; "
-+ "flags.loopback = 1; "
-+ "next; ";
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90,
-+ ds_cstr(match), icmp_actions,
-+ &op->nbrp->header_);
-+ }
-
-- if (op->lrp_networks.n_ipv6_addrs) {
-- ds_clear(match);
-- ds_put_format(match, "outport == %s && "
-- REG_NEXT_HOP_IPV6 " == ",
-- op->peer->json_key);
-- op_put_v6_networks(match, op);
-+ /* BFD msg handling */
-+ build_lrouter_bfd_flows(lflows, op);
-
-- ds_clear(actions);
-- ds_put_format(actions, "eth.dst = %s; next;",
-- op->lrp_networks.ea_s);
-- ovn_lflow_add_with_hint(lflows, op->peer->od,
-- S_ROUTER_IN_ARP_RESOLVE, 100,
-- ds_cstr(match), ds_cstr(actions),
-- &op->nbrp->header_);
-- }
-+ /* ICMP time exceeded */
-+ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
-+ ds_clear(match);
-+ ds_clear(actions);
-+
-+ ds_put_format(match,
-+ "inport == %s && ip4 && "
-+ "ip.ttl == {0, 1} && !ip.later_frag", op->json_key);
-+ ds_put_format(actions,
-+ "icmp4 {"
-+ "eth.dst <-> eth.src; "
-+ "icmp4.type = 11; /* Time exceeded */ "
-+ "icmp4.code = 0; /* TTL exceeded in transit */ "
-+ "ip4.dst = ip4.src; "
-+ "ip4.src = %s; "
-+ "ip.ttl = 255; "
-+ "next; };",
-+ op->lrp_networks.ipv4_addrs[i].addr_s);
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 40,
-+ ds_cstr(match), ds_cstr(actions),
-+ &op->nbrp->header_);
- }
-
-- if (!op->derived && op->od->l3redirect_port) {
-- const char *redirect_type = smap_get(&op->nbrp->options,
-- "redirect-type");
-- if (redirect_type && !strcasecmp(redirect_type, "bridged")) {
-- /* Packet is on a non gateway chassis and
-- * has an unresolved ARP on a network behind gateway
-- * chassis attached router port. Since, redirect type
-- * is "bridged", instead of calling "get_arp"
-- * on this node, we will redirect the packet to gateway
-- * chassis, by setting destination mac router port mac.*/
-- ds_clear(match);
-- ds_put_format(match, "outport == %s && "
-- "!is_chassis_resident(%s)", op->json_key,
-- op->od->l3redirect_port->json_key);
-- ds_clear(actions);
-- ds_put_format(actions, "eth.dst = %s; next;",
-- op->lrp_networks.ea_s);
-+ /* ARP reply. These flows reply to ARP requests for the router's own
-+ * IP address. */
-+ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
-+ ds_clear(match);
-+ ds_put_format(match, "arp.spa == %s/%u",
-+ op->lrp_networks.ipv4_addrs[i].network_s,
-+ op->lrp_networks.ipv4_addrs[i].plen);
-
-- ovn_lflow_add_with_hint(lflows, op->od,
-- S_ROUTER_IN_ARP_RESOLVE, 50,
-- ds_cstr(match), ds_cstr(actions),
-- &op->nbrp->header_);
-- }
-- }
-+ if (op->od->l3dgw_port && op->od->l3redirect_port && op->peer
-+ && op->peer->od->n_localnet_ports) {
-+ bool add_chassis_resident_check = false;
-+ if (op == op->od->l3dgw_port) {
-+ /* Traffic with eth.src = l3dgw_port->lrp_networks.ea_s
-+ * should only be sent from the gateway chassis, so that
-+ * upstream MAC learning points to the gateway chassis.
-+ * Also need to avoid generation of multiple ARP responses
-+ * from different chassis. */
-+ add_chassis_resident_check = true;
-+ } else {
-+ /* Check if the option 'reside-on-redirect-chassis'
-+ * is set to true on the router port. If set to true
-+ * and if peer's logical switch has a localnet port, it
-+ * means the router pipeline for the packets from
-+ * peer's logical switch is be run on the chassis
-+ * hosting the gateway port and it should reply to the
-+ * ARP requests for the router port IPs.
-+ */
-+ add_chassis_resident_check = smap_get_bool(
-+ &op->nbrp->options,
-+ "reside-on-redirect-chassis", false);
-+ }
-
-- /* Drop IP traffic destined to router owned IPs. Part of it is dropped
-- * in stage "lr_in_ip_input" but traffic that could have been unSNATed
-- * but didn't match any existing session might still end up here.
-- *
-- * Priority 1.
-- */
-- build_lrouter_drop_own_dest(op, S_ROUTER_IN_ARP_RESOLVE, 1, true,
-- lflows);
-- } else if (op->od->n_router_ports && !lsp_is_router(op->nbsp)
-- && strcmp(op->nbsp->type, "virtual")) {
-- /* This is a logical switch port that backs a VM or a container.
-- * Extract its addresses. For each of the address, go through all
-- * the router ports attached to the switch (to which this port
-- * connects) and if the address in question is reachable from the
-- * router port, add an ARP/ND entry in that router's pipeline. */
-+ if (add_chassis_resident_check) {
-+ ds_put_format(match, " && is_chassis_resident(%s)",
-+ op->od->l3redirect_port->json_key);
-+ }
-+ }
-
-- for (size_t i = 0; i < op->n_lsp_addrs; i++) {
-- const char *ea_s = op->lsp_addrs[i].ea_s;
-- for (size_t j = 0; j < op->lsp_addrs[i].n_ipv4_addrs; j++) {
-- const char *ip_s = op->lsp_addrs[i].ipv4_addrs[j].addr_s;
-- for (size_t k = 0; k < op->od->n_router_ports; k++) {
-- /* Get the Logical_Router_Port that the
-- * Logical_Switch_Port is connected to, as
-- * 'peer'. */
-- const char *peer_name = smap_get(
-- &op->od->router_ports[k]->nbsp->options,
-- "router-port");
-- if (!peer_name) {
-- continue;
-- }
-+ build_lrouter_arp_flow(op->od, op,
-+ op->lrp_networks.ipv4_addrs[i].addr_s,
-+ REG_INPORT_ETH_ADDR, match, false, 90,
-+ &op->nbrp->header_, lflows);
-+ }
-
-- struct ovn_port *peer = ovn_port_find(ports, peer_name);
-- if (!peer || !peer->nbrp) {
-- continue;
-- }
-+ /* A set to hold all load-balancer vips that need ARP responses. */
-+ struct sset all_ips_v4 = SSET_INITIALIZER(&all_ips_v4);
-+ struct sset all_ips_v6 = SSET_INITIALIZER(&all_ips_v6);
-+ get_router_load_balancer_ips(op->od, &all_ips_v4, &all_ips_v6);
-
-- if (!find_lrp_member_ip(peer, ip_s)) {
-- continue;
-- }
-+ const char *ip_address;
-+ SSET_FOR_EACH (ip_address, &all_ips_v4) {
-+ ds_clear(match);
-+ if (op == op->od->l3dgw_port) {
-+ ds_put_format(match, "is_chassis_resident(%s)",
-+ op->od->l3redirect_port->json_key);
-+ }
-
-- ds_clear(match);
-- ds_put_format(match, "outport == %s && "
-- REG_NEXT_HOP_IPV4 " == %s",
-- peer->json_key, ip_s);
-+ build_lrouter_arp_flow(op->od, op,
-+ ip_address, REG_INPORT_ETH_ADDR,
-+ match, false, 90, NULL, lflows);
-+ }
-
-- ds_clear(actions);
-- ds_put_format(actions, "eth.dst = %s; next;", ea_s);
-- ovn_lflow_add_with_hint(lflows, peer->od,
-- S_ROUTER_IN_ARP_RESOLVE, 100,
-- ds_cstr(match),
-- ds_cstr(actions),
-- &op->nbsp->header_);
-- }
-+ SSET_FOR_EACH (ip_address, &all_ips_v6) {
-+ ds_clear(match);
-+ if (op == op->od->l3dgw_port) {
-+ ds_put_format(match, "is_chassis_resident(%s)",
-+ op->od->l3redirect_port->json_key);
- }
-
-- for (size_t j = 0; j < op->lsp_addrs[i].n_ipv6_addrs; j++) {
-- const char *ip_s = op->lsp_addrs[i].ipv6_addrs[j].addr_s;
-- for (size_t k = 0; k < op->od->n_router_ports; k++) {
-- /* Get the Logical_Router_Port that the
-- * Logical_Switch_Port is connected to, as
-- * 'peer'. */
-- const char *peer_name = smap_get(
-- &op->od->router_ports[k]->nbsp->options,
-- "router-port");
-- if (!peer_name) {
-- continue;
-- }
-+ build_lrouter_nd_flow(op->od, op, "nd_na",
-+ ip_address, NULL, REG_INPORT_ETH_ADDR,
-+ match, false, 90, NULL, lflows);
-+ }
-
-- struct ovn_port *peer = ovn_port_find(ports, peer_name);
-- if (!peer || !peer->nbrp) {
-- continue;
-- }
-+ sset_destroy(&all_ips_v4);
-+ sset_destroy(&all_ips_v6);
-
-- if (!find_lrp_member_ip(peer, ip_s)) {
-- continue;
-- }
-+ if (!smap_get(&op->od->nbr->options, "chassis")
-+ && !op->od->l3dgw_port) {
-+ /* UDP/TCP/SCTP port unreachable. */
-+ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
-+ ds_clear(match);
-+ ds_put_format(match,
-+ "ip4 && ip4.dst == %s && !ip.later_frag && udp",
-+ op->lrp_networks.ipv4_addrs[i].addr_s);
-+ const char *action = "icmp4 {"
-+ "eth.dst <-> eth.src; "
-+ "ip4.dst <-> ip4.src; "
-+ "ip.ttl = 255; "
-+ "icmp4.type = 3; "
-+ "icmp4.code = 3; "
-+ "next; };";
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-+ 80, ds_cstr(match), action,
-+ &op->nbrp->header_);
-
-- ds_clear(match);
-- ds_put_format(match, "outport == %s && "
-- REG_NEXT_HOP_IPV6 " == %s",
-- peer->json_key, ip_s);
-+ ds_clear(match);
-+ ds_put_format(match,
-+ "ip4 && ip4.dst == %s && !ip.later_frag && tcp",
-+ op->lrp_networks.ipv4_addrs[i].addr_s);
-+ action = "tcp_reset {"
-+ "eth.dst <-> eth.src; "
-+ "ip4.dst <-> ip4.src; "
-+ "next; };";
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-+ 80, ds_cstr(match), action,
-+ &op->nbrp->header_);
-
-- ds_clear(actions);
-- ds_put_format(actions, "eth.dst = %s; next;", ea_s);
-- ovn_lflow_add_with_hint(lflows, peer->od,
-- S_ROUTER_IN_ARP_RESOLVE, 100,
-- ds_cstr(match),
-- ds_cstr(actions),
-- &op->nbsp->header_);
-- }
-+ ds_clear(match);
-+ ds_put_format(match,
-+ "ip4 && ip4.dst == %s && !ip.later_frag && sctp",
-+ op->lrp_networks.ipv4_addrs[i].addr_s);
-+ action = "sctp_abort {"
-+ "eth.dst <-> eth.src; "
-+ "ip4.dst <-> ip4.src; "
-+ "next; };";
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-+ 80, ds_cstr(match), action,
-+ &op->nbrp->header_);
-+
-+ ds_clear(match);
-+ ds_put_format(match,
-+ "ip4 && ip4.dst == %s && !ip.later_frag",
-+ op->lrp_networks.ipv4_addrs[i].addr_s);
-+ action = "icmp4 {"
-+ "eth.dst <-> eth.src; "
-+ "ip4.dst <-> ip4.src; "
-+ "ip.ttl = 255; "
-+ "icmp4.type = 3; "
-+ "icmp4.code = 2; "
-+ "next; };";
-+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-+ 70, ds_cstr(match), action,
-+ &op->nbrp->header_);
- }
- }
-- } else if (op->od->n_router_ports && !lsp_is_router(op->nbsp)
-- && !strcmp(op->nbsp->type, "virtual")) {
-- /* This is a virtual port. Add ARP replies for the virtual ip with
-- * the mac of the present active virtual parent.
-- * If the logical port doesn't have virtual parent set in
-- * Port_Binding table, then add the flow to set eth.dst to
-- * 00:00:00:00:00:00 and advance to next table so that ARP is
-- * resolved by router pipeline using the arp{} action.
-- * The MAC_Binding entry for the virtual ip might be invalid. */
-- ovs_be32 ip;
-
-- const char *vip = smap_get(&op->nbsp->options,
-- "virtual-ip");
-- const char *virtual_parents = smap_get(&op->nbsp->options,
-- "virtual-parents");
-- if (!vip || !virtual_parents ||
-- !ip_parse(vip, &ip) || !op->sb) {
-+ /* Drop IP traffic destined to router owned IPs except if the IP is
-+ * also a SNAT IP. Those are dropped later, in stage
-+ * "lr_in_arp_resolve", if unSNAT was unsuccessful.
-+ *
-+ * If op->pd->lb_force_snat_router_ip is true, it means the IP of the
-+ * router port is also SNAT IP.
-+ *
-+ * Priority 60.
-+ */
-+ if (!op->od->lb_force_snat_router_ip) {
-+ build_lrouter_drop_own_dest(op, S_ROUTER_IN_IP_INPUT, 60, false,
-+ lflows);
-+ }
-+ /* ARP / ND handling for external IP addresses.
-+ *
-+ * DNAT and SNAT IP addresses are external IP addresses that need ARP
-+ * handling.
-+ *
-+ * These are already taken care globally, per router. The only
-+ * exception is on the l3dgw_port where we might need to use a
-+ * different ETH address.
-+ */
-+ if (op != op->od->l3dgw_port) {
- return;
- }
-
-- if (!op->sb->virtual_parent || !op->sb->virtual_parent[0] ||
-- !op->sb->chassis) {
-- /* The virtual port is not claimed yet. */
-- for (size_t i = 0; i < op->od->n_router_ports; i++) {
-- const char *peer_name = smap_get(
-- &op->od->router_ports[i]->nbsp->options,
-- "router-port");
-- if (!peer_name) {
-- continue;
-- }
--
-- struct ovn_port *peer = ovn_port_find(ports, peer_name);
-- if (!peer || !peer->nbrp) {
-- continue;
-- }
--
-- if (find_lrp_member_ip(peer, vip)) {
-- ds_clear(match);
-- ds_put_format(match, "outport == %s && "
-- REG_NEXT_HOP_IPV4 " == %s",
-- peer->json_key, vip);
-+ for (size_t i = 0; i < op->od->nbr->n_nat; i++) {
-+ struct ovn_nat *nat_entry = &op->od->nat_entries[i];
-
-- const char *arp_actions =
-- "eth.dst = 00:00:00:00:00:00; next;";
-- ovn_lflow_add_with_hint(lflows, peer->od,
-- S_ROUTER_IN_ARP_RESOLVE, 100,
-- ds_cstr(match),
-- arp_actions,
-- &op->nbsp->header_);
-- break;
-- }
-- }
-- } else {
-- struct ovn_port *vp =
-- ovn_port_find(ports, op->sb->virtual_parent);
-- if (!vp || !vp->nbsp) {
-- return;
-+ /* Skip entries we failed to parse. */
-+ if (!nat_entry_is_valid(nat_entry)) {
-+ continue;
- }
-
-- for (size_t i = 0; i < vp->n_lsp_addrs; i++) {
-- bool found_vip_network = false;
-- const char *ea_s = vp->lsp_addrs[i].ea_s;
-- for (size_t j = 0; j < vp->od->n_router_ports; j++) {
-- /* Get the Logical_Router_Port that the
-- * Logical_Switch_Port is connected to, as
-- * 'peer'. */
-- const char *peer_name = smap_get(
-- &vp->od->router_ports[j]->nbsp->options,
-- "router-port");
-- if (!peer_name) {
-- continue;
-- }
-+ /* Skip SNAT entries for now, we handle unique SNAT IPs separately
-+ * below.
-+ */
-+ if (!strcmp(nat_entry->nb->type, "snat")) {
-+ continue;
-+ }
-+ build_lrouter_port_nat_arp_nd_flow(op, nat_entry, lflows);
-+ }
-
-- struct ovn_port *peer =
-- ovn_port_find(ports, peer_name);
-- if (!peer || !peer->nbrp) {
-- continue;
-- }
-+ /* Now handle SNAT entries too, one per unique SNAT IP. */
-+ struct shash_node *snat_snode;
-+ SHASH_FOR_EACH (snat_snode, &op->od->snat_ips) {
-+ struct ovn_snat_ip *snat_ip = snat_snode->data;
-
-- if (!find_lrp_member_ip(peer, vip)) {
-- continue;
-- }
-+ if (ovs_list_is_empty(&snat_ip->snat_entries)) {
-+ continue;
-+ }
-
-- ds_clear(match);
-- ds_put_format(match, "outport == %s && "
-- REG_NEXT_HOP_IPV4 " == %s",
-- peer->json_key, vip);
-+ struct ovn_nat *nat_entry =
-+ CONTAINER_OF(ovs_list_front(&snat_ip->snat_entries),
-+ struct ovn_nat, ext_addr_list_node);
-+ build_lrouter_port_nat_arp_nd_flow(op, nat_entry, lflows);
-+ }
-+ }
-+}
-
-- ds_clear(actions);
-- ds_put_format(actions, "eth.dst = %s; next;", ea_s);
-- ovn_lflow_add_with_hint(lflows, peer->od,
-- S_ROUTER_IN_ARP_RESOLVE, 100,
-- ds_cstr(match),
-- ds_cstr(actions),
-- &op->nbsp->header_);
-- found_vip_network = true;
-- break;
-- }
-+/* NAT, Defrag and load balancing. */
-+static void
-+build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od,
-+ struct hmap *lflows,
-+ struct shash *meter_groups,
-+ struct hmap *lbs,
-+ struct ds *match, struct ds *actions)
-+{
-+ if (od->nbr) {
-
-- if (found_vip_network) {
-- break;
-- }
-- }
-- }
-- } else if (lsp_is_router(op->nbsp)) {
-- /* This is a logical switch port that connects to a router. */
-+ /* Packets are allowed by default. */
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_DEFRAG, 0, "1", "next;");
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 0, "1", "next;");
-+ ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 0, "1", "next;");
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 0, "1", "next;");
-+ ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 0, "1", "next;");
-+ ovn_lflow_add(lflows, od, S_ROUTER_OUT_EGR_LOOP, 0, "1", "next;");
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 0, "1", "next;");
-
-- /* The peer of this switch port is the router port for which
-- * we need to add logical flows such that it can resolve
-- * ARP entries for all the other router ports connected to
-- * the switch in question. */
-+ /* Send the IPv6 NS packets to next table. When ovn-controller
-+ * generates IPv6 NS (for the action - nd_ns{}), the injected
-+ * packet would go through conntrack - which is not required. */
-+ ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 120, "nd_ns", "next;");
-
-- const char *peer_name = smap_get(&op->nbsp->options,
-- "router-port");
-- if (!peer_name) {
-+ /* NAT rules are only valid on Gateway routers and routers with
-+ * l3dgw_port (router has a port with gateway chassis
-+ * specified). */
-+ if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) {
- return;
- }
-
-- struct ovn_port *peer = ovn_port_find(ports, peer_name);
-- if (!peer || !peer->nbrp) {
-- return;
-- }
-+ struct sset nat_entries = SSET_INITIALIZER(&nat_entries);
-
-- if (peer->od->nbr &&
-- smap_get_bool(&peer->od->nbr->options,
-- "dynamic_neigh_routers", false)) {
-- return;
-- }
-+ bool dnat_force_snat_ip =
-+ !lport_addresses_is_empty(&od->dnat_force_snat_addrs);
-+ bool lb_force_snat_ip =
-+ !lport_addresses_is_empty(&od->lb_force_snat_addrs);
-
-- for (size_t i = 0; i < op->od->n_router_ports; i++) {
-- const char *router_port_name = smap_get(
-- &op->od->router_ports[i]->nbsp->options,
-- "router-port");
-- struct ovn_port *router_port = ovn_port_find(ports,
-- router_port_name);
-- if (!router_port || !router_port->nbrp) {
-+ for (int i = 0; i < od->nbr->n_nat; i++) {
-+ const struct nbrec_nat *nat;
-+
-+ nat = od->nbr->nat[i];
-+
-+ ovs_be32 ip, mask;
-+ struct in6_addr ipv6, mask_v6, v6_exact = IN6ADDR_EXACT_INIT;
-+ bool is_v6 = false;
-+ bool stateless = lrouter_nat_is_stateless(nat);
-+ struct nbrec_address_set *allowed_ext_ips =
-+ nat->allowed_ext_ips;
-+ struct nbrec_address_set *exempted_ext_ips =
-+ nat->exempted_ext_ips;
-+
-+ if (allowed_ext_ips && exempted_ext_ips) {
-+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-+ VLOG_WARN_RL(&rl, "NAT rule: "UUID_FMT" not applied, since "
-+ "both allowed and exempt external ips set",
-+ UUID_ARGS(&(nat->header_.uuid)));
- continue;
- }
-
-- /* Skip the router port under consideration. */
-- if (router_port == peer) {
-- continue;
-+ char *error = ip_parse_masked(nat->external_ip, &ip, &mask);
-+ if (error || mask != OVS_BE32_MAX) {
-+ free(error);
-+ error = ipv6_parse_masked(nat->external_ip, &ipv6, &mask_v6);
-+ if (error || memcmp(&mask_v6, &v6_exact, sizeof(mask_v6))) {
-+ /* Invalid for both IPv4 and IPv6 */
-+ static struct vlog_rate_limit rl =
-+ VLOG_RATE_LIMIT_INIT(5, 1);
-+ VLOG_WARN_RL(&rl, "bad external ip %s for nat",
-+ nat->external_ip);
-+ free(error);
-+ continue;
-+ }
-+ /* It was an invalid IPv4 address, but valid IPv6.
-+ * Treat the rest of the handling of this NAT rule
-+ * as IPv6. */
-+ is_v6 = true;
- }
-
-- if (router_port->lrp_networks.n_ipv4_addrs) {
-- ds_clear(match);
-- ds_put_format(match, "outport == %s && "
-- REG_NEXT_HOP_IPV4 " == ",
-- peer->json_key);
-- op_put_v4_networks(match, router_port, false);
--
-- ds_clear(actions);
-- ds_put_format(actions, "eth.dst = %s; next;",
-- router_port->lrp_networks.ea_s);
-- ovn_lflow_add_with_hint(lflows, peer->od,
-- S_ROUTER_IN_ARP_RESOLVE, 100,
-- ds_cstr(match), ds_cstr(actions),
-- &op->nbsp->header_);
-+ /* Check the validity of nat->logical_ip. 'logical_ip' can
-+ * be a subnet when the type is "snat". */
-+ int cidr_bits;
-+ if (is_v6) {
-+ error = ipv6_parse_masked(nat->logical_ip, &ipv6, &mask_v6);
-+ cidr_bits = ipv6_count_cidr_bits(&mask_v6);
-+ } else {
-+ error = ip_parse_masked(nat->logical_ip, &ip, &mask);
-+ cidr_bits = ip_count_cidr_bits(mask);
-+ }
-+ if (!strcmp(nat->type, "snat")) {
-+ if (error) {
-+ /* Invalid for both IPv4 and IPv6 */
-+ static struct vlog_rate_limit rl =
-+ VLOG_RATE_LIMIT_INIT(5, 1);
-+ VLOG_WARN_RL(&rl, "bad ip network or ip %s for snat "
-+ "in router "UUID_FMT"",
-+ nat->logical_ip, UUID_ARGS(&od->key));
-+ free(error);
-+ continue;
-+ }
-+ } else {
-+ if (error || (!is_v6 && mask != OVS_BE32_MAX)
-+ || (is_v6 && memcmp(&mask_v6, &v6_exact,
-+ sizeof mask_v6))) {
-+ /* Invalid for both IPv4 and IPv6 */
-+ static struct vlog_rate_limit rl =
-+ VLOG_RATE_LIMIT_INIT(5, 1);
-+ VLOG_WARN_RL(&rl, "bad ip %s for dnat in router "
-+ ""UUID_FMT"", nat->logical_ip, UUID_ARGS(&od->key));
-+ free(error);
-+ continue;
-+ }
- }
-
-- if (router_port->lrp_networks.n_ipv6_addrs) {
-- ds_clear(match);
-- ds_put_format(match, "outport == %s && "
-- REG_NEXT_HOP_IPV6 " == ",
-- peer->json_key);
-- op_put_v6_networks(match, router_port);
--
-- ds_clear(actions);
-- ds_put_format(actions, "eth.dst = %s; next;",
-- router_port->lrp_networks.ea_s);
-- ovn_lflow_add_with_hint(lflows, peer->od,
-- S_ROUTER_IN_ARP_RESOLVE, 100,
-- ds_cstr(match), ds_cstr(actions),
-- &op->nbsp->header_);
-+ /* For distributed router NAT, determine whether this NAT rule
-+ * satisfies the conditions for distributed NAT processing. */
-+ bool distributed = false;
-+ struct eth_addr mac;
-+ if (od->l3dgw_port && !strcmp(nat->type, "dnat_and_snat") &&
-+ nat->logical_port && nat->external_mac) {
-+ if (eth_addr_from_string(nat->external_mac, &mac)) {
-+ distributed = true;
-+ } else {
-+ static struct vlog_rate_limit rl =
-+ VLOG_RATE_LIMIT_INIT(5, 1);
-+ VLOG_WARN_RL(&rl, "bad mac %s for dnat in router "
-+ ""UUID_FMT"", nat->external_mac, UUID_ARGS(&od->key));
-+ continue;
-+ }
- }
-- }
-- }
-
--}
-+ /* Ingress UNSNAT table: It is for already established connections'
-+ * reverse traffic. i.e., SNAT has already been done in egress
-+ * pipeline and now the packet has entered the ingress pipeline as
-+ * part of a reply. We undo the SNAT here.
-+ *
-+ * Undoing SNAT has to happen before DNAT processing. This is
-+ * because when the packet was DNATed in ingress pipeline, it did
-+ * not know about the possibility of eventual additional SNAT in
-+ * egress pipeline. */
-+ if (!strcmp(nat->type, "snat")
-+ || !strcmp(nat->type, "dnat_and_snat")) {
-+ if (!od->l3dgw_port) {
-+ /* Gateway router. */
-+ ds_clear(match);
-+ ds_clear(actions);
-+ ds_put_format(match, "ip && ip%s.dst == %s",
-+ is_v6 ? "6" : "4",
-+ nat->external_ip);
-+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-+ ds_put_format(actions, "ip%s.dst=%s; next;",
-+ is_v6 ? "6" : "4", nat->logical_ip);
-+ } else {
-+ ds_put_cstr(actions, "ct_snat;");
-+ }
-
--/* Local router ingress table CHK_PKT_LEN: Check packet length.
-- *
-- * Any IPv4 packet with outport set to the distributed gateway
-- * router port, check the packet length and store the result in the
-- * 'REGBIT_PKT_LARGER' register bit.
-- *
-- * Local router ingress table LARGER_PKTS: Handle larger packets.
-- *
-- * Any IPv4 packet with outport set to the distributed gateway
-- * router port and the 'REGBIT_PKT_LARGER' register bit is set,
-- * generate ICMPv4 packet with type 3 (Destination Unreachable) and
-- * code 4 (Fragmentation needed).
-- * */
--static void
--build_check_pkt_len_flows_for_lrouter(
-- struct ovn_datapath *od, struct hmap *lflows,
-- struct hmap *ports,
-- struct ds *match, struct ds *actions)
--{
-- if (od->nbr) {
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT,
-+ 90, ds_cstr(match),
-+ ds_cstr(actions),
-+ &nat->header_);
-+ } else {
-+ /* Distributed router. */
-
-- /* Packets are allowed by default. */
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_CHK_PKT_LEN, 0, "1",
-- "next;");
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_LARGER_PKTS, 0, "1",
-- "next;");
-+ /* Traffic received on l3dgw_port is subject to NAT. */
-+ ds_clear(match);
-+ ds_clear(actions);
-+ ds_put_format(match, "ip && ip%s.dst == %s"
-+ " && inport == %s",
-+ is_v6 ? "6" : "4",
-+ nat->external_ip,
-+ od->l3dgw_port->json_key);
-+ if (!distributed && od->l3redirect_port) {
-+ /* Flows for NAT rules that are centralized are only
-+ * programmed on the gateway chassis. */
-+ ds_put_format(match, " && is_chassis_resident(%s)",
-+ od->l3redirect_port->json_key);
-+ }
-
-- if (od->l3dgw_port && od->l3redirect_port) {
-- int gw_mtu = 0;
-- if (od->l3dgw_port->nbrp) {
-- gw_mtu = smap_get_int(&od->l3dgw_port->nbrp->options,
-- "gateway_mtu", 0);
-- }
-- /* Add the flows only if gateway_mtu is configured. */
-- if (gw_mtu <= 0) {
-- return;
-+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-+ ds_put_format(actions, "ip%s.dst=%s; next;",
-+ is_v6 ? "6" : "4", nat->logical_ip);
-+ } else {
-+ ds_put_cstr(actions, "ct_snat;");
-+ }
-+
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT,
-+ 100,
-+ ds_cstr(match), ds_cstr(actions),
-+ &nat->header_);
-+ }
- }
-
-- ds_clear(match);
-- ds_put_format(match, "outport == %s", od->l3dgw_port->json_key);
-+ /* Ingress DNAT table: Packets enter the pipeline with destination
-+ * IP address that needs to be DNATted from a external IP address
-+ * to a logical IP address. */
-+ if (!strcmp(nat->type, "dnat")
-+ || !strcmp(nat->type, "dnat_and_snat")) {
-+ if (!od->l3dgw_port) {
-+ /* Gateway router. */
-+ /* Packet when it goes from the initiator to destination.
-+ * We need to set flags.loopback because the router can
-+ * send the packet back through the same interface. */
-+ ds_clear(match);
-+ ds_put_format(match, "ip && ip%s.dst == %s",
-+ is_v6 ? "6" : "4",
-+ nat->external_ip);
-+ ds_clear(actions);
-+ if (allowed_ext_ips || exempted_ext_ips) {
-+ lrouter_nat_add_ext_ip_match(od, lflows, match, nat,
-+ is_v6, true, mask);
-+ }
-+
-+ if (dnat_force_snat_ip) {
-+ /* Indicate to the future tables that a DNAT has taken
-+ * place and a force SNAT needs to be done in the
-+ * Egress SNAT table. */
-+ ds_put_format(actions,
-+ "flags.force_snat_for_dnat = 1; ");
-+ }
-
-- ds_clear(actions);
-- ds_put_format(actions,
-- REGBIT_PKT_LARGER" = check_pkt_larger(%d);"
-- " next;", gw_mtu + VLAN_ETH_HEADER_LEN);
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_CHK_PKT_LEN, 50,
-- ds_cstr(match), ds_cstr(actions),
-- &od->l3dgw_port->nbrp->header_);
-+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-+ ds_put_format(actions, "flags.loopback = 1; "
-+ "ip%s.dst=%s; next;",
-+ is_v6 ? "6" : "4", nat->logical_ip);
-+ } else {
-+ ds_put_format(actions, "flags.loopback = 1; "
-+ "ct_dnat(%s", nat->logical_ip);
-
-- for (size_t i = 0; i < od->nbr->n_ports; i++) {
-- struct ovn_port *rp = ovn_port_find(ports,
-- od->nbr->ports[i]->name);
-- if (!rp || rp == od->l3dgw_port) {
-- continue;
-- }
-+ if (nat->external_port_range[0]) {
-+ ds_put_format(actions, ",%s",
-+ nat->external_port_range);
-+ }
-+ ds_put_format(actions, ");");
-+ }
-
-- if (rp->lrp_networks.ipv4_addrs) {
-- ds_clear(match);
-- ds_put_format(match, "inport == %s && outport == %s"
-- " && ip4 && "REGBIT_PKT_LARGER,
-- rp->json_key, od->l3dgw_port->json_key);
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100,
-+ ds_cstr(match), ds_cstr(actions),
-+ &nat->header_);
-+ } else {
-+ /* Distributed router. */
-
-+ /* Traffic received on l3dgw_port is subject to NAT. */
-+ ds_clear(match);
-+ ds_put_format(match, "ip && ip%s.dst == %s"
-+ " && inport == %s",
-+ is_v6 ? "6" : "4",
-+ nat->external_ip,
-+ od->l3dgw_port->json_key);
-+ if (!distributed && od->l3redirect_port) {
-+ /* Flows for NAT rules that are centralized are only
-+ * programmed on the gateway chassis. */
-+ ds_put_format(match, " && is_chassis_resident(%s)",
-+ od->l3redirect_port->json_key);
-+ }
- ds_clear(actions);
-- /* Set icmp4.frag_mtu to gw_mtu */
-- ds_put_format(actions,
-- "icmp4_error {"
-- REGBIT_EGRESS_LOOPBACK" = 1; "
-- "eth.dst = %s; "
-- "ip4.dst = ip4.src; "
-- "ip4.src = %s; "
-- "ip.ttl = 255; "
-- "icmp4.type = 3; /* Destination Unreachable. */ "
-- "icmp4.code = 4; /* Frag Needed and DF was Set. */ "
-- "icmp4.frag_mtu = %d; "
-- "next(pipeline=ingress, table=%d); };",
-- rp->lrp_networks.ea_s,
-- rp->lrp_networks.ipv4_addrs[0].addr_s,
-- gw_mtu,
-- ovn_stage_get_table(S_ROUTER_IN_ADMISSION));
-- ovn_lflow_add_with_hint(lflows, od,
-- S_ROUTER_IN_LARGER_PKTS, 50,
-+ if (allowed_ext_ips || exempted_ext_ips) {
-+ lrouter_nat_add_ext_ip_match(od, lflows, match, nat,
-+ is_v6, true, mask);
-+ }
-+
-+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-+ ds_put_format(actions, "ip%s.dst=%s; next;",
-+ is_v6 ? "6" : "4", nat->logical_ip);
-+ } else {
-+ ds_put_format(actions, "ct_dnat(%s", nat->logical_ip);
-+ if (nat->external_port_range[0]) {
-+ ds_put_format(actions, ",%s",
-+ nat->external_port_range);
-+ }
-+ ds_put_format(actions, ");");
-+ }
-+
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100,
- ds_cstr(match), ds_cstr(actions),
-- &rp->nbrp->header_);
-+ &nat->header_);
- }
-+ }
-
-- if (rp->lrp_networks.ipv6_addrs) {
-+ /* ARP resolve for NAT IPs. */
-+ if (od->l3dgw_port) {
-+ if (!strcmp(nat->type, "snat")) {
- ds_clear(match);
-- ds_put_format(match, "inport == %s && outport == %s"
-- " && ip6 && "REGBIT_PKT_LARGER,
-- rp->json_key, od->l3dgw_port->json_key);
-+ ds_put_format(
-+ match, "inport == %s && %s == %s",
-+ od->l3dgw_port->json_key,
-+ is_v6 ? "ip6.src" : "ip4.src", nat->external_ip);
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_IP_INPUT,
-+ 120, ds_cstr(match), "next;",
-+ &nat->header_);
-+ }
-
-+ if (!sset_contains(&nat_entries, nat->external_ip)) {
-+ ds_clear(match);
-+ ds_put_format(
-+ match, "outport == %s && %s == %s",
-+ od->l3dgw_port->json_key,
-+ is_v6 ? REG_NEXT_HOP_IPV6 : REG_NEXT_HOP_IPV4,
-+ nat->external_ip);
- ds_clear(actions);
-- /* Set icmp6.frag_mtu to gw_mtu */
-- ds_put_format(actions,
-- "icmp6_error {"
-- REGBIT_EGRESS_LOOPBACK" = 1; "
-- "eth.dst = %s; "
-- "ip6.dst = ip6.src; "
-- "ip6.src = %s; "
-- "ip.ttl = 255; "
-- "icmp6.type = 2; /* Packet Too Big. */ "
-- "icmp6.code = 0; "
-- "icmp6.frag_mtu = %d; "
-- "next(pipeline=ingress, table=%d); };",
-- rp->lrp_networks.ea_s,
-- rp->lrp_networks.ipv6_addrs[0].addr_s,
-- gw_mtu,
-- ovn_stage_get_table(S_ROUTER_IN_ADMISSION));
-+ ds_put_format(
-+ actions, "eth.dst = %s; next;",
-+ distributed ? nat->external_mac :
-+ od->l3dgw_port->lrp_networks.ea_s);
- ovn_lflow_add_with_hint(lflows, od,
-- S_ROUTER_IN_LARGER_PKTS, 50,
-- ds_cstr(match), ds_cstr(actions),
-- &rp->nbrp->header_);
-+ S_ROUTER_IN_ARP_RESOLVE,
-+ 100, ds_cstr(match),
-+ ds_cstr(actions),
-+ &nat->header_);
-+ sset_add(&nat_entries, nat->external_ip);
- }
-+ } else {
-+ /* Add the NAT external_ip to the nat_entries even for
-+ * gateway routers. This is required for adding load balancer
-+ * flows.*/
-+ sset_add(&nat_entries, nat->external_ip);
- }
-- }
-- }
--}
--
--/* Logical router ingress table GW_REDIRECT: Gateway redirect.
-- *
-- * For traffic with outport equal to the l3dgw_port
-- * on a distributed router, this table redirects a subset
-- * of the traffic to the l3redirect_port which represents
-- * the central instance of the l3dgw_port.
-- */
--static void
--build_gateway_redirect_flows_for_lrouter(
-- struct ovn_datapath *od, struct hmap *lflows,
-- struct ds *match, struct ds *actions)
--{
-- if (od->nbr) {
-- if (od->l3dgw_port && od->l3redirect_port) {
-- const struct ovsdb_idl_row *stage_hint = NULL;
--
-- if (od->l3dgw_port->nbrp) {
-- stage_hint = &od->l3dgw_port->nbrp->header_;
-- }
--
-- /* For traffic with outport == l3dgw_port, if the
-- * packet did not match any higher priority redirect
-- * rule, then the traffic is redirected to the central
-- * instance of the l3dgw_port. */
-- ds_clear(match);
-- ds_put_format(match, "outport == %s",
-- od->l3dgw_port->json_key);
-- ds_clear(actions);
-- ds_put_format(actions, "outport = %s; next;",
-- od->l3redirect_port->json_key);
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_GW_REDIRECT, 50,
-- ds_cstr(match), ds_cstr(actions),
-- stage_hint);
-- }
-
-- /* Packets are allowed by default. */
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_GW_REDIRECT, 0, "1", "next;");
-- }
--}
-+ /* Egress UNDNAT table: It is for already established connections'
-+ * reverse traffic. i.e., DNAT has already been done in ingress
-+ * pipeline and now the packet has entered the egress pipeline as
-+ * part of a reply. We undo the DNAT here.
-+ *
-+ * Note that this only applies for NAT on a distributed router.
-+ * Undo DNAT on a gateway router is done in the ingress DNAT
-+ * pipeline stage. */
-+ if (od->l3dgw_port && (!strcmp(nat->type, "dnat")
-+ || !strcmp(nat->type, "dnat_and_snat"))) {
-+ ds_clear(match);
-+ ds_put_format(match, "ip && ip%s.src == %s"
-+ " && outport == %s",
-+ is_v6 ? "6" : "4",
-+ nat->logical_ip,
-+ od->l3dgw_port->json_key);
-+ if (!distributed && od->l3redirect_port) {
-+ /* Flows for NAT rules that are centralized are only
-+ * programmed on the gateway chassis. */
-+ ds_put_format(match, " && is_chassis_resident(%s)",
-+ od->l3redirect_port->json_key);
-+ }
-+ ds_clear(actions);
-+ if (distributed) {
-+ ds_put_format(actions, "eth.src = "ETH_ADDR_FMT"; ",
-+ ETH_ADDR_ARGS(mac));
-+ }
-
--/* Local router ingress table ARP_REQUEST: ARP request.
-- *
-- * In the common case where the Ethernet destination has been resolved,
-- * this table outputs the packet (priority 0). Otherwise, it composes
-- * and sends an ARP/IPv6 NA request (priority 100). */
--static void
--build_arp_request_flows_for_lrouter(
-- struct ovn_datapath *od, struct hmap *lflows,
-- struct ds *match, struct ds *actions)
--{
-- if (od->nbr) {
-- for (int i = 0; i < od->nbr->n_static_routes; i++) {
-- const struct nbrec_logical_router_static_route *route;
-+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-+ ds_put_format(actions, "ip%s.src=%s; next;",
-+ is_v6 ? "6" : "4", nat->external_ip);
-+ } else {
-+ ds_put_format(actions, "ct_dnat;");
-+ }
-
-- route = od->nbr->static_routes[i];
-- struct in6_addr gw_ip6;
-- unsigned int plen;
-- char *error = ipv6_parse_cidr(route->nexthop, &gw_ip6, &plen);
-- if (error || plen != 128) {
-- free(error);
-- continue;
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 100,
-+ ds_cstr(match), ds_cstr(actions),
-+ &nat->header_);
- }
-
-- ds_clear(match);
-- ds_put_format(match, "eth.dst == 00:00:00:00:00:00 && "
-- "ip6 && " REG_NEXT_HOP_IPV6 " == %s",
-- route->nexthop);
-- struct in6_addr sn_addr;
-- struct eth_addr eth_dst;
-- in6_addr_solicited_node(&sn_addr, &gw_ip6);
-- ipv6_multicast_to_ethernet(ð_dst, &sn_addr);
--
-- char sn_addr_s[INET6_ADDRSTRLEN + 1];
-- ipv6_string_mapped(sn_addr_s, &sn_addr);
--
-- ds_clear(actions);
-- ds_put_format(actions,
-- "nd_ns { "
-- "eth.dst = "ETH_ADDR_FMT"; "
-- "ip6.dst = %s; "
-- "nd.target = %s; "
-- "output; "
-- "};", ETH_ADDR_ARGS(eth_dst), sn_addr_s,
-- route->nexthop);
--
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ARP_REQUEST, 200,
-- ds_cstr(match), ds_cstr(actions),
-- &route->header_);
-- }
--
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 100,
-- "eth.dst == 00:00:00:00:00:00 && ip4",
-- "arp { "
-- "eth.dst = ff:ff:ff:ff:ff:ff; "
-- "arp.spa = " REG_SRC_IPV4 "; "
-- "arp.tpa = " REG_NEXT_HOP_IPV4 "; "
-- "arp.op = 1; " /* ARP request */
-- "output; "
-- "};");
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 100,
-- "eth.dst == 00:00:00:00:00:00 && ip6",
-- "nd_ns { "
-- "nd.target = " REG_NEXT_HOP_IPV6 "; "
-- "output; "
-- "};");
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 0, "1", "output;");
-- }
--}
-+ /* Egress SNAT table: Packets enter the egress pipeline with
-+ * source ip address that needs to be SNATted to a external ip
-+ * address. */
-+ if (!strcmp(nat->type, "snat")
-+ || !strcmp(nat->type, "dnat_and_snat")) {
-+ if (!od->l3dgw_port) {
-+ /* Gateway router. */
-+ ds_clear(match);
-+ ds_put_format(match, "ip && ip%s.src == %s",
-+ is_v6 ? "6" : "4",
-+ nat->logical_ip);
-+ ds_clear(actions);
-
--/* Logical router egress table DELIVERY: Delivery (priority 100-110).
-- *
-- * Priority 100 rules deliver packets to enabled logical ports.
-- * Priority 110 rules match multicast packets and update the source
-- * mac before delivering to enabled logical ports. IP multicast traffic
-- * bypasses S_ROUTER_IN_IP_ROUTING route lookups.
-- */
--static void
--build_egress_delivery_flows_for_lrouter_port(
-- struct ovn_port *op, struct hmap *lflows,
-- struct ds *match, struct ds *actions)
--{
-- if (op->nbrp) {
-- if (!lrport_is_enabled(op->nbrp)) {
-- /* Drop packets to disabled logical ports (since logical flow
-- * tables are default-drop). */
-- return;
-- }
-+ if (allowed_ext_ips || exempted_ext_ips) {
-+ lrouter_nat_add_ext_ip_match(od, lflows, match, nat,
-+ is_v6, false, mask);
-+ }
-
-- if (op->derived) {
-- /* No egress packets should be processed in the context of
-- * a chassisredirect port. The chassisredirect port should
-- * be replaced by the l3dgw port in the local output
-- * pipeline stage before egress processing. */
-- return;
-- }
-+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-+ ds_put_format(actions, "ip%s.src=%s; next;",
-+ is_v6 ? "6" : "4", nat->external_ip);
-+ } else {
-+ ds_put_format(actions, "ct_snat(%s",
-+ nat->external_ip);
-
-- /* If multicast relay is enabled then also adjust source mac for IP
-- * multicast traffic.
-- */
-- if (op->od->mcast_info.rtr.relay) {
-- ds_clear(match);
-- ds_clear(actions);
-- ds_put_format(match, "(ip4.mcast || ip6.mcast) && outport == %s",
-- op->json_key);
-- ds_put_format(actions, "eth.src = %s; output;",
-- op->lrp_networks.ea_s);
-- ovn_lflow_add(lflows, op->od, S_ROUTER_OUT_DELIVERY, 110,
-- ds_cstr(match), ds_cstr(actions));
-- }
-+ if (nat->external_port_range[0]) {
-+ ds_put_format(actions, ",%s",
-+ nat->external_port_range);
-+ }
-+ ds_put_format(actions, ");");
-+ }
-
-- ds_clear(match);
-- ds_put_format(match, "outport == %s", op->json_key);
-- ovn_lflow_add(lflows, op->od, S_ROUTER_OUT_DELIVERY, 100,
-- ds_cstr(match), "output;");
-- }
-+ /* The priority here is calculated such that the
-+ * nat->logical_ip with the longest mask gets a higher
-+ * priority. */
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT,
-+ cidr_bits + 1,
-+ ds_cstr(match), ds_cstr(actions),
-+ &nat->header_);
-+ } else {
-+ uint16_t priority = cidr_bits + 1;
-
--}
-+ /* Distributed router. */
-+ ds_clear(match);
-+ ds_put_format(match, "ip && ip%s.src == %s"
-+ " && outport == %s",
-+ is_v6 ? "6" : "4",
-+ nat->logical_ip,
-+ od->l3dgw_port->json_key);
-+ if (!distributed && od->l3redirect_port) {
-+ /* Flows for NAT rules that are centralized are only
-+ * programmed on the gateway chassis. */
-+ priority += 128;
-+ ds_put_format(match, " && is_chassis_resident(%s)",
-+ od->l3redirect_port->json_key);
-+ }
-+ ds_clear(actions);
-
--static void
--build_misc_local_traffic_drop_flows_for_lrouter(
-- struct ovn_datapath *od, struct hmap *lflows)
--{
-- if (od->nbr) {
-- /* L3 admission control: drop multicast and broadcast source, localhost
-- * source or destination, and zero network source or destination
-- * (priority 100). */
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 100,
-- "ip4.src_mcast ||"
-- "ip4.src == 255.255.255.255 || "
-- "ip4.src == 127.0.0.0/8 || "
-- "ip4.dst == 127.0.0.0/8 || "
-- "ip4.src == 0.0.0.0/8 || "
-- "ip4.dst == 0.0.0.0/8",
-- "drop;");
-+ if (allowed_ext_ips || exempted_ext_ips) {
-+ lrouter_nat_add_ext_ip_match(od, lflows, match, nat,
-+ is_v6, false, mask);
-+ }
-
-- /* Drop ARP packets (priority 85). ARP request packets for router's own
-- * IPs are handled with priority-90 flows.
-- * Drop IPv6 ND packets (priority 85). ND NA packets for router's own
-- * IPs are handled with priority-90 flows.
-- */
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 85,
-- "arp || nd", "drop;");
-+ if (distributed) {
-+ ds_put_format(actions, "eth.src = "ETH_ADDR_FMT"; ",
-+ ETH_ADDR_ARGS(mac));
-+ }
-
-- /* Allow IPv6 multicast traffic that's supposed to reach the
-- * router pipeline (e.g., router solicitations).
-- */
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 84, "nd_rs || nd_ra",
-- "next;");
-+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-+ ds_put_format(actions, "ip%s.src=%s; next;",
-+ is_v6 ? "6" : "4", nat->external_ip);
-+ } else {
-+ ds_put_format(actions, "ct_snat(%s",
-+ nat->external_ip);
-+ if (nat->external_port_range[0]) {
-+ ds_put_format(actions, ",%s",
-+ nat->external_port_range);
-+ }
-+ ds_put_format(actions, ");");
-+ }
-
-- /* Drop other reserved multicast. */
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 83,
-- "ip6.mcast_rsvd", "drop;");
-+ /* The priority here is calculated such that the
-+ * nat->logical_ip with the longest mask gets a higher
-+ * priority. */
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT,
-+ priority, ds_cstr(match),
-+ ds_cstr(actions),
-+ &nat->header_);
-+ }
-+ }
-
-- /* Allow other multicast if relay enabled (priority 82). */
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 82,
-- "ip4.mcast || ip6.mcast",
-- od->mcast_info.rtr.relay ? "next;" : "drop;");
-+ /* Logical router ingress table 0:
-+ * For NAT on a distributed router, add rules allowing
-+ * ingress traffic with eth.dst matching nat->external_mac
-+ * on the l3dgw_port instance where nat->logical_port is
-+ * resident. */
-+ if (distributed) {
-+ /* Store the ethernet address of the port receiving the packet.
-+ * This will save us from having to match on inport further
-+ * down in the pipeline.
-+ */
-+ ds_clear(actions);
-+ ds_put_format(actions, REG_INPORT_ETH_ADDR " = %s; next;",
-+ od->l3dgw_port->lrp_networks.ea_s);
-
-- /* Drop Ethernet local broadcast. By definition this traffic should
-- * not be forwarded.*/
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 50,
-- "eth.bcast", "drop;");
-+ ds_clear(match);
-+ ds_put_format(match,
-+ "eth.dst == "ETH_ADDR_FMT" && inport == %s"
-+ " && is_chassis_resident(\"%s\")",
-+ ETH_ADDR_ARGS(mac),
-+ od->l3dgw_port->json_key,
-+ nat->logical_port);
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ADMISSION, 50,
-+ ds_cstr(match), ds_cstr(actions),
-+ &nat->header_);
-+ }
-
-- /* TTL discard */
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 30,
-- "ip4 && ip.ttl == {0, 1}", "drop;");
-+ /* Ingress Gateway Redirect Table: For NAT on a distributed
-+ * router, add flows that are specific to a NAT rule. These
-+ * flows indicate the presence of an applicable NAT rule that
-+ * can be applied in a distributed manner.
-+ * In particulr REG_SRC_IPV4/REG_SRC_IPV6 and eth.src are set to
-+ * NAT external IP and NAT external mac so the ARP request
-+ * generated in the following stage is sent out with proper IP/MAC
-+ * src addresses.
-+ */
-+ if (distributed) {
-+ ds_clear(match);
-+ ds_clear(actions);
-+ ds_put_format(match,
-+ "ip%s.src == %s && outport == %s && "
-+ "is_chassis_resident(\"%s\")",
-+ is_v6 ? "6" : "4", nat->logical_ip,
-+ od->l3dgw_port->json_key, nat->logical_port);
-+ ds_put_format(actions, "eth.src = %s; %s = %s; next;",
-+ nat->external_mac,
-+ is_v6 ? REG_SRC_IPV6 : REG_SRC_IPV4,
-+ nat->external_ip);
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_GW_REDIRECT,
-+ 100, ds_cstr(match),
-+ ds_cstr(actions), &nat->header_);
-+ }
-
-- /* Pass other traffic not already handled to the next table for
-- * routing. */
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 0, "1", "next;");
-- }
--}
-+ /* Egress Loopback table: For NAT on a distributed router.
-+ * If packets in the egress pipeline on the distributed
-+ * gateway port have ip.dst matching a NAT external IP, then
-+ * loop a clone of the packet back to the beginning of the
-+ * ingress pipeline with inport = outport. */
-+ if (od->l3dgw_port) {
-+ /* Distributed router. */
-+ ds_clear(match);
-+ ds_put_format(match, "ip%s.dst == %s && outport == %s",
-+ is_v6 ? "6" : "4",
-+ nat->external_ip,
-+ od->l3dgw_port->json_key);
-+ if (!distributed) {
-+ ds_put_format(match, " && is_chassis_resident(%s)",
-+ od->l3redirect_port->json_key);
-+ } else {
-+ ds_put_format(match, " && is_chassis_resident(\"%s\")",
-+ nat->logical_port);
-+ }
-
--static void
--build_dhcpv6_reply_flows_for_lrouter_port(
-- struct ovn_port *op, struct hmap *lflows,
-- struct ds *match)
--{
-- if (op->nbrp && (!op->derived)) {
-- for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
-- ds_clear(match);
-- ds_put_format(match, "ip6.dst == %s && udp.src == 547 &&"
-- " udp.dst == 546",
-- op->lrp_networks.ipv6_addrs[i].addr_s);
-- ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 100,
-- ds_cstr(match),
-- "reg0 = 0; handle_dhcpv6_reply;");
-+ ds_clear(actions);
-+ ds_put_format(actions,
-+ "clone { ct_clear; "
-+ "inport = outport; outport = \"\"; "
-+ "flags = 0; flags.loopback = 1; ");
-+ for (int j = 0; j < MFF_N_LOG_REGS; j++) {
-+ ds_put_format(actions, "reg%d = 0; ", j);
-+ }
-+ ds_put_format(actions, REGBIT_EGRESS_LOOPBACK" = 1; "
-+ "next(pipeline=ingress, table=%d); };",
-+ ovn_stage_get_table(S_ROUTER_IN_ADMISSION));
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_EGR_LOOP, 100,
-+ ds_cstr(match), ds_cstr(actions),
-+ &nat->header_);
-+ }
- }
-- }
-
--}
-+ /* Handle force SNAT options set in the gateway router. */
-+ if (!od->l3dgw_port) {
-+ if (dnat_force_snat_ip) {
-+ if (od->dnat_force_snat_addrs.n_ipv4_addrs) {
-+ build_lrouter_force_snat_flows(lflows, od, "4",
-+ od->dnat_force_snat_addrs.ipv4_addrs[0].addr_s,
-+ "dnat");
-+ }
-+ if (od->dnat_force_snat_addrs.n_ipv6_addrs) {
-+ build_lrouter_force_snat_flows(lflows, od, "6",
-+ od->dnat_force_snat_addrs.ipv6_addrs[0].addr_s,
-+ "dnat");
-+ }
-+ }
-+ if (lb_force_snat_ip) {
-+ if (od->lb_force_snat_addrs.n_ipv4_addrs) {
-+ build_lrouter_force_snat_flows(lflows, od, "4",
-+ od->lb_force_snat_addrs.ipv4_addrs[0].addr_s, "lb");
-+ }
-+ if (od->lb_force_snat_addrs.n_ipv6_addrs) {
-+ build_lrouter_force_snat_flows(lflows, od, "6",
-+ od->lb_force_snat_addrs.ipv6_addrs[0].addr_s, "lb");
-+ }
-+ }
-
--static void
--build_ipv6_input_flows_for_lrouter_port(
-- struct ovn_port *op, struct hmap *lflows,
-- struct ds *match, struct ds *actions)
--{
-- if (op->nbrp && (!op->derived)) {
-- /* No ingress packets are accepted on a chassisredirect
-- * port, so no need to program flows for that port. */
-- if (op->lrp_networks.n_ipv6_addrs) {
-- /* ICMPv6 echo reply. These flows reply to echo requests
-- * received for the router's IP address. */
-- ds_clear(match);
-- ds_put_cstr(match, "ip6.dst == ");
-- op_put_v6_networks(match, op);
-- ds_put_cstr(match, " && icmp6.type == 128 && icmp6.code == 0");
-+ /* For gateway router, re-circulate every packet through
-+ * the DNAT zone. This helps with the following.
-+ *
-+ * Any packet that needs to be unDNATed in the reverse
-+ * direction gets unDNATed. Ideally this could be done in
-+ * the egress pipeline. But since the gateway router
-+ * does not have any feature that depends on the source
-+ * ip address being external IP address for IP routing,
-+ * we can do it here, saving a future re-circulation. */
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 50,
-+ "ip", "flags.loopback = 1; ct_dnat;");
-+ }
-
-- const char *lrp_actions =
-- "ip6.dst <-> ip6.src; "
-- "ip.ttl = 255; "
-- "icmp6.type = 129; "
-- "flags.loopback = 1; "
-- "next; ";
-- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90,
-- ds_cstr(match), lrp_actions,
-- &op->nbrp->header_);
-+ /* Load balancing and packet defrag are only valid on
-+ * Gateway routers or router with gateway port. */
-+ if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) {
-+ sset_destroy(&nat_entries);
-+ return;
- }
-
-- /* ND reply. These flows reply to ND solicitations for the
-- * router's own IP address. */
-- for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
-- ds_clear(match);
-- if (op->od->l3dgw_port && op == op->od->l3dgw_port
-- && op->od->l3redirect_port) {
-- /* Traffic with eth.src = l3dgw_port->lrp_networks.ea_s
-- * should only be sent from the gateway chassi, so that
-- * upstream MAC learning points to the gateway chassis.
-- * Also need to avoid generation of multiple ND replies
-- * from different chassis. */
-- ds_put_format(match, "is_chassis_resident(%s)",
-- op->od->l3redirect_port->json_key);
-- }
-+ /* A set to hold all ips that need defragmentation and tracking. */
-+ struct sset all_ips = SSET_INITIALIZER(&all_ips);
-
-- build_lrouter_nd_flow(op->od, op, "nd_na_router",
-- op->lrp_networks.ipv6_addrs[i].addr_s,
-- op->lrp_networks.ipv6_addrs[i].sn_addr_s,
-- REG_INPORT_ETH_ADDR, match, false, 90,
-- &op->nbrp->header_, lflows);
-- }
-+ for (int i = 0; i < od->nbr->n_load_balancer; i++) {
-+ struct nbrec_load_balancer *nb_lb = od->nbr->load_balancer[i];
-+ struct ovn_northd_lb *lb =
-+ ovn_northd_lb_find(lbs, &nb_lb->header_.uuid);
-+ ovs_assert(lb);
-
-- /* UDP/TCP port unreachable */
-- if (!smap_get(&op->od->nbr->options, "chassis")
-- && !op->od->l3dgw_port) {
-- for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
-- ds_clear(match);
-- ds_put_format(match,
-- "ip6 && ip6.dst == %s && !ip.later_frag && tcp",
-- op->lrp_networks.ipv6_addrs[i].addr_s);
-- const char *action = "tcp_reset {"
-- "eth.dst <-> eth.src; "
-- "ip6.dst <-> ip6.src; "
-- "next; };";
-- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-- 80, ds_cstr(match), action,
-- &op->nbrp->header_);
-+ for (size_t j = 0; j < lb->n_vips; j++) {
-+ struct ovn_lb_vip *lb_vip = &lb->vips[j];
-+ struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[j];
-+ ds_clear(actions);
-+ build_lb_vip_actions(lb_vip, lb_vip_nb, actions,
-+ lb->selection_fields, false);
-
-- ds_clear(match);
-- ds_put_format(match,
-- "ip6 && ip6.dst == %s && !ip.later_frag && udp",
-- op->lrp_networks.ipv6_addrs[i].addr_s);
-- action = "icmp6 {"
-- "eth.dst <-> eth.src; "
-- "ip6.dst <-> ip6.src; "
-- "ip.ttl = 255; "
-- "icmp6.type = 1; "
-- "icmp6.code = 4; "
-- "next; };";
-- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-- 80, ds_cstr(match), action,
-- &op->nbrp->header_);
-+ if (!sset_contains(&all_ips, lb_vip->vip_str)) {
-+ sset_add(&all_ips, lb_vip->vip_str);
-+ /* If there are any load balancing rules, we should send
-+ * the packet to conntrack for defragmentation and
-+ * tracking. This helps with two things.
-+ *
-+ * 1. With tracking, we can send only new connections to
-+ * pick a DNAT ip address from a group.
-+ * 2. If there are L4 ports in load balancing rules, we
-+ * need the defragmentation to match on L4 ports. */
-+ ds_clear(match);
-+ if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
-+ ds_put_format(match, "ip && ip4.dst == %s",
-+ lb_vip->vip_str);
-+ } else {
-+ ds_put_format(match, "ip && ip6.dst == %s",
-+ lb_vip->vip_str);
-+ }
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DEFRAG,
-+ 100, ds_cstr(match), "ct_next;",
-+ &nb_lb->header_);
-+ }
-
-+ /* Higher priority rules are added for load-balancing in DNAT
-+ * table. For every match (on a VIP[:port]), we add two flows
-+ * via add_router_lb_flow(). One flow is for specific matching
-+ * on ct.new with an action of "ct_lb($targets);". The other
-+ * flow is for ct.est with an action of "ct_dnat;". */
- ds_clear(match);
-- ds_put_format(match,
-- "ip6 && ip6.dst == %s && !ip.later_frag",
-- op->lrp_networks.ipv6_addrs[i].addr_s);
-- action = "icmp6 {"
-- "eth.dst <-> eth.src; "
-- "ip6.dst <-> ip6.src; "
-- "ip.ttl = 255; "
-- "icmp6.type = 1; "
-- "icmp6.code = 3; "
-- "next; };";
-- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
-- 70, ds_cstr(match), action,
-- &op->nbrp->header_);
-- }
-- }
-+ if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
-+ ds_put_format(match, "ip && ip4.dst == %s",
-+ lb_vip->vip_str);
-+ } else {
-+ ds_put_format(match, "ip && ip6.dst == %s",
-+ lb_vip->vip_str);
-+ }
-
-- /* ICMPv6 time exceeded */
-- for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
-- /* skip link-local address */
-- if (in6_is_lla(&op->lrp_networks.ipv6_addrs[i].network)) {
-- continue;
-- }
-+ int prio = 110;
-+ bool is_udp = nullable_string_is_equal(nb_lb->protocol, "udp");
-+ bool is_sctp = nullable_string_is_equal(nb_lb->protocol,
-+ "sctp");
-+ const char *proto = is_udp ? "udp" : is_sctp ? "sctp" : "tcp";
-
-- ds_clear(match);
-- ds_clear(actions);
-+ if (lb_vip->vip_port) {
-+ ds_put_format(match, " && %s && %s.dst == %d", proto,
-+ proto, lb_vip->vip_port);
-+ prio = 120;
-+ }
-
-- ds_put_format(match,
-- "inport == %s && ip6 && "
-- "ip6.src == %s/%d && "
-- "ip.ttl == {0, 1} && !ip.later_frag",
-- op->json_key,
-- op->lrp_networks.ipv6_addrs[i].network_s,
-- op->lrp_networks.ipv6_addrs[i].plen);
-- ds_put_format(actions,
-- "icmp6 {"
-- "eth.dst <-> eth.src; "
-- "ip6.dst = ip6.src; "
-- "ip6.src = %s; "
-- "ip.ttl = 255; "
-- "icmp6.type = 3; /* Time exceeded */ "
-- "icmp6.code = 0; /* TTL exceeded in transit */ "
-- "next; };",
-- op->lrp_networks.ipv6_addrs[i].addr_s);
-- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 40,
-- ds_cstr(match), ds_cstr(actions),
-- &op->nbrp->header_);
-+ if (od->l3redirect_port &&
-+ (lb_vip->n_backends || !lb_vip->empty_backend_rej)) {
-+ ds_put_format(match, " && is_chassis_resident(%s)",
-+ od->l3redirect_port->json_key);
-+ }
-+ bool force_snat_for_lb =
-+ lb_force_snat_ip || od->lb_force_snat_router_ip;
-+ add_router_lb_flow(lflows, od, match, actions, prio,
-+ force_snat_for_lb, lb_vip, proto,
-+ nb_lb, meter_groups, &nat_entries);
-+ }
- }
-+ sset_destroy(&all_ips);
-+ sset_destroy(&nat_entries);
- }
--
- }
-
-+
-+
- struct lswitch_flow_build_info {
- struct hmap *datapaths;
- struct hmap *ports;
-@@ -11177,7 +11918,8 @@ struct lswitch_flow_build_info {
-
- static void
- build_lswitch_and_lrouter_iterate_by_od(struct ovn_datapath *od,
-- struct lswitch_flow_build_info *lsi)
-+ struct lswitch_flow_build_info *lsi,
-+ struct hmap *bfd_connections)
- {
- /* Build Logical Switch Flows. */
- build_lswitch_lflows_pre_acl_and_acl(od, lsi->port_groups, lsi->lflows,
-@@ -11186,13 +11928,20 @@ build_lswitch_and_lrouter_iterate_by_od(struct ovn_datapath *od,
- build_fwd_group_lflows(od, lsi->lflows);
- build_lswitch_lflows_admission_control(od, lsi->lflows);
- build_lswitch_input_port_sec_od(od, lsi->lflows);
-+ build_lswitch_learn_fdb_od(od, lsi->lflows);
-+ build_lswitch_arp_nd_responder_default(od, lsi->lflows);
-+ build_lswitch_dns_lookup_and_response(od, lsi->lflows);
-+ build_lswitch_dhcp_and_dns_defaults(od, lsi->lflows);
-+ build_lswitch_destination_lookup_bmcast(od, lsi->lflows, &lsi->actions);
-+ build_lswitch_output_port_sec_od(od, lsi->lflows);
-
- /* Build Logical Router Flows. */
- build_adm_ctrl_flows_for_lrouter(od, lsi->lflows);
- build_neigh_learning_flows_for_lrouter(od, lsi->lflows, &lsi->match,
- &lsi->actions);
- build_ND_RA_flows_for_lrouter(od, lsi->lflows);
-- build_static_route_flows_for_lrouter(od, lsi->lflows, lsi->ports);
-+ build_static_route_flows_for_lrouter(od, lsi->lflows, lsi->ports,
-+ bfd_connections);
- build_mcast_lookup_flows_for_lrouter(od, lsi->lflows, &lsi->match,
- &lsi->actions);
- build_ingress_policy_flows_for_lrouter(od, lsi->lflows, lsi->ports);
-@@ -11204,6 +11953,9 @@ build_lswitch_and_lrouter_iterate_by_od(struct ovn_datapath *od,
- build_arp_request_flows_for_lrouter(od, lsi->lflows, &lsi->match,
- &lsi->actions);
- build_misc_local_traffic_drop_flows_for_lrouter(od, lsi->lflows);
-+ build_lrouter_arp_nd_for_datapath(od, lsi->lflows);
-+ build_lrouter_nat_defrag_and_lb(od, lsi->lflows, lsi->meter_groups,
-+ lsi->lbs, &lsi->match, &lsi->actions);
- }
-
- /* Helper function to combine all lflow generation which is iterated by port.
-@@ -11216,6 +11968,20 @@ build_lswitch_and_lrouter_iterate_by_op(struct ovn_port *op,
- /* Build Logical Switch Flows. */
- build_lswitch_input_port_sec_op(op, lsi->lflows, &lsi->actions,
- &lsi->match);
-+ build_lswitch_learn_fdb_op(op, lsi->lflows, &lsi->actions,
-+ &lsi->match);
-+ build_lswitch_arp_nd_responder_skip_local(op, lsi->lflows,
-+ &lsi->match);
-+ build_lswitch_arp_nd_responder_known_ips(op, lsi->lflows,
-+ lsi->ports,
-+ &lsi->actions,
-+ &lsi->match);
-+ build_lswitch_dhcp_options_and_response(op,lsi->lflows);
-+ build_lswitch_external_port(op, lsi->lflows);
-+ build_lswitch_ip_unicast_lookup(op, lsi->lflows, lsi->mcgroups,
-+ &lsi->actions, &lsi->match);
-+ build_lswitch_output_port_sec_op(op, lsi->lflows,
-+ &lsi->actions, &lsi->match);
-
- /* Build Logical Router Flows. */
- build_adm_ctrl_flows_for_lrouter_port(op, lsi->lflows, &lsi->match,
-@@ -11232,6 +11998,10 @@ build_lswitch_and_lrouter_iterate_by_op(struct ovn_port *op,
- build_dhcpv6_reply_flows_for_lrouter_port(op, lsi->lflows, &lsi->match);
- build_ipv6_input_flows_for_lrouter_port(op, lsi->lflows,
- &lsi->match, &lsi->actions);
-+ build_lrouter_ipv4_ip_input(op, lsi->lflows,
-+ &lsi->match, &lsi->actions);
-+ build_lrouter_force_snat_flows_op(op, lsi->lflows, &lsi->match,
-+ &lsi->actions);
- }
-
- static void
-@@ -11239,10 +12009,13 @@ build_lswitch_and_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
- struct hmap *port_groups, struct hmap *lflows,
- struct hmap *mcgroups,
- struct hmap *igmp_groups,
-- struct shash *meter_groups, struct hmap *lbs)
-+ struct shash *meter_groups, struct hmap *lbs,
-+ struct hmap *bfd_connections)
- {
- struct ovn_datapath *od;
- struct ovn_port *op;
-+ struct ovn_northd_lb *lb;
-+ struct ovn_igmp_group *igmp_group;
-
- char *svc_check_match = xasprintf("eth.dst == %s", svc_monitor_mac);
-
-@@ -11264,22 +12037,28 @@ build_lswitch_and_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
- * will move here and will be reogranized by iterator type.
- */
- HMAP_FOR_EACH (od, key_node, datapaths) {
-- build_lswitch_and_lrouter_iterate_by_od(od, &lsi);
-+ build_lswitch_and_lrouter_iterate_by_od(od, &lsi, bfd_connections);
- }
- HMAP_FOR_EACH (op, key_node, ports) {
- build_lswitch_and_lrouter_iterate_by_op(op, &lsi);
- }
-+ HMAP_FOR_EACH (lb, hmap_node, lbs) {
-+ build_lswitch_arp_nd_service_monitor(lb, lsi.lflows,
-+ &lsi.actions,
-+ &lsi.match);
-+ }
-+ HMAP_FOR_EACH (igmp_group, hmap_node, igmp_groups) {
-+ build_lswitch_ip_mcast_igmp_mld(igmp_group,
-+ lsi.lflows,
-+ &lsi.actions,
-+ &lsi.match);
-+ }
- free(svc_check_match);
-
- ds_destroy(&lsi.match);
- ds_destroy(&lsi.actions);
-
-- /* Legacy lswitch build - to be migrated. */
-- build_lswitch_flows(datapaths, ports, lflows, mcgroups,
-- igmp_groups, lbs);
--
-- /* Legacy lrouter build - to be migrated. */
-- build_lrouter_flows(datapaths, ports, lflows, meter_groups, lbs);
-+ build_lswitch_flows(datapaths, lflows);
- }
-
- struct ovn_dp_group {
-@@ -11356,13 +12135,14 @@ build_lflows(struct northd_context *ctx, struct hmap *datapaths,
- struct hmap *ports, struct hmap *port_groups,
- struct hmap *mcgroups, struct hmap *igmp_groups,
- struct shash *meter_groups,
-- struct hmap *lbs)
-+ struct hmap *lbs, struct hmap *bfd_connections)
- {
- struct hmap lflows = HMAP_INITIALIZER(&lflows);
-
- build_lswitch_and_lrouter_flows(datapaths, ports,
- port_groups, &lflows, mcgroups,
-- igmp_groups, meter_groups, lbs);
-+ igmp_groups, meter_groups, lbs,
-+ bfd_connections);
-
- /* Collecting all unique datapath groups. */
- struct hmap dp_groups = HMAP_INITIALIZER(&dp_groups);
-@@ -11801,17 +12581,20 @@ static void
- sync_meters_iterate_nb_meter(struct northd_context *ctx,
- const char *meter_name,
- const struct nbrec_meter *nb_meter,
-- struct shash *sb_meters)
-+ struct shash *sb_meters,
-+ struct sset *used_sb_meters)
- {
-+ const struct sbrec_meter *sb_meter;
- bool new_sb_meter = false;
-
-- const struct sbrec_meter *sb_meter = shash_find_and_delete(sb_meters,
-- meter_name);
-+ sb_meter = shash_find_data(sb_meters, meter_name);
- if (!sb_meter) {
- sb_meter = sbrec_meter_insert(ctx->ovnsb_txn);
- sbrec_meter_set_name(sb_meter, meter_name);
-+ shash_add(sb_meters, sb_meter->name, sb_meter);
- new_sb_meter = true;
- }
-+ sset_add(used_sb_meters, meter_name);
-
- if (new_sb_meter || bands_need_update(nb_meter, sb_meter)) {
- struct sbrec_meter_band **sb_bands;
-@@ -11833,6 +12616,24 @@ sync_meters_iterate_nb_meter(struct northd_context *ctx,
- sbrec_meter_set_unit(sb_meter, nb_meter->unit);
- }
-
-+static void
-+sync_acl_fair_meter(struct northd_context *ctx, struct shash *meter_groups,
-+ const struct nbrec_acl *acl, struct shash *sb_meters,
-+ struct sset *used_sb_meters)
-+{
-+ const struct nbrec_meter *nb_meter =
-+ fair_meter_lookup_by_name(meter_groups, acl->meter);
-+
-+ if (!nb_meter) {
-+ return;
-+ }
-+
-+ char *meter_name = alloc_acl_log_unique_meter_name(acl);
-+ sync_meters_iterate_nb_meter(ctx, meter_name, nb_meter, sb_meters,
-+ used_sb_meters);
-+ free(meter_name);
-+}
-+
- /* Each entry in the Meter and Meter_Band tables in OVN_Northbound have
- * a corresponding entries in the Meter and Meter_Band tables in
- * OVN_Southbound. Additionally, ACL logs that use fair meters have
-@@ -11840,9 +12641,10 @@ sync_meters_iterate_nb_meter(struct northd_context *ctx,
- */
- static void
- sync_meters(struct northd_context *ctx, struct hmap *datapaths,
-- struct shash *meter_groups)
-+ struct shash *meter_groups, struct hmap *port_groups)
- {
- struct shash sb_meters = SHASH_INITIALIZER(&sb_meters);
-+ struct sset used_sb_meters = SSET_INITIALIZER(&used_sb_meters);
-
- const struct sbrec_meter *sb_meter;
- SBREC_METER_FOR_EACH (sb_meter, ctx->ovnsb_idl) {
-@@ -11852,7 +12654,7 @@ sync_meters(struct northd_context *ctx, struct hmap *datapaths,
- const struct nbrec_meter *nb_meter;
- NBREC_METER_FOR_EACH (nb_meter, ctx->ovnnb_idl) {
- sync_meters_iterate_nb_meter(ctx, nb_meter->name, nb_meter,
-- &sb_meters);
-+ &sb_meters, &used_sb_meters);
- }
-
- /*
-@@ -11866,19 +12668,28 @@ sync_meters(struct northd_context *ctx, struct hmap *datapaths,
- continue;
- }
- for (size_t i = 0; i < od->nbs->n_acls; i++) {
-- struct nbrec_acl *acl = od->nbs->acls[i];
-- nb_meter = fair_meter_lookup_by_name(meter_groups, acl->meter);
-- if (!nb_meter) {
-- continue;
-+ sync_acl_fair_meter(ctx, meter_groups, od->nbs->acls[i],
-+ &sb_meters, &used_sb_meters);
-+ }
-+ struct ovn_port_group *pg;
-+ HMAP_FOR_EACH (pg, key_node, port_groups) {
-+ if (ovn_port_group_ls_find(pg, &od->nbs->header_.uuid)) {
-+ for (size_t i = 0; i < pg->nb_pg->n_acls; i++) {
-+ sync_acl_fair_meter(ctx, meter_groups, pg->nb_pg->acls[i],
-+ &sb_meters, &used_sb_meters);
-+ }
- }
--
-- char *meter_name = alloc_acl_log_unique_meter_name(acl);
-- sync_meters_iterate_nb_meter(ctx, meter_name, nb_meter,
-- &sb_meters);
-- free(meter_name);
- }
- }
-
-+ const char *used_meter;
-+ const char *used_meter_next;
-+ SSET_FOR_EACH_SAFE (used_meter, used_meter_next, &used_sb_meters) {
-+ shash_find_and_delete(&sb_meters, used_meter);
-+ sset_delete(&used_sb_meters, SSET_NODE_FROM_NAME(used_meter));
-+ }
-+ sset_destroy(&used_sb_meters);
-+
- struct shash_node *node, *next;
- SHASH_FOR_EACH_SAFE (node, next, &sb_meters) {
- sbrec_meter_delete(node->data);
-@@ -12274,6 +13085,7 @@ ovnnb_db_run(struct northd_context *ctx,
- struct hmap igmp_groups;
- struct shash meter_groups = SHASH_INITIALIZER(&meter_groups);
- struct hmap lbs;
-+ struct hmap bfd_connections = HMAP_INITIALIZER(&bfd_connections);
-
- /* Sync ipsec configuration.
- * Copy nb_cfg from northbound to southbound database.
-@@ -12354,6 +13166,7 @@ ovnnb_db_run(struct northd_context *ctx,
-
- use_logical_dp_groups = smap_get_bool(&nb->options,
- "use_logical_dp_groups", false);
-+ /* deprecated, use --event instead */
- controller_event_en = smap_get_bool(&nb->options,
- "controller_event", false);
- check_lsp_is_up = !smap_get_bool(&nb->options,
-@@ -12368,14 +13181,16 @@ ovnnb_db_run(struct northd_context *ctx,
- build_ip_mcast(ctx, datapaths);
- build_mcast_groups(ctx, datapaths, ports, &mcast_groups, &igmp_groups);
- build_meter_groups(ctx, &meter_groups);
-+ build_bfd_table(ctx, &bfd_connections, ports);
- build_lflows(ctx, datapaths, ports, &port_groups, &mcast_groups,
-- &igmp_groups, &meter_groups, &lbs);
-+ &igmp_groups, &meter_groups, &lbs, &bfd_connections);
- ovn_update_ipv6_prefix(ports);
-
- sync_address_sets(ctx);
- sync_port_groups(ctx, &port_groups);
-- sync_meters(ctx, datapaths, &meter_groups);
-+ sync_meters(ctx, datapaths, &meter_groups, &port_groups);
- sync_dns_entries(ctx, datapaths);
-+ cleanup_stale_fdp_entries(ctx, datapaths);
-
- struct ovn_northd_lb *lb;
- HMAP_FOR_EACH_POP (lb, hmap_node, &lbs) {
-@@ -12393,9 +13208,13 @@ ovnnb_db_run(struct northd_context *ctx,
- HMAP_FOR_EACH_SAFE (pg, next_pg, key_node, &port_groups) {
- ovn_port_group_destroy(&port_groups, pg);
- }
-+
-+ bfd_cleanup_connections(ctx, &bfd_connections);
-+
- hmap_destroy(&igmp_groups);
- hmap_destroy(&mcast_groups);
- hmap_destroy(&port_groups);
-+ hmap_destroy(&bfd_connections);
-
- struct shash_node *node, *next;
- SHASH_FOR_EACH_SAFE (node, next, &meter_groups) {
-@@ -12542,7 +13361,17 @@ handle_port_binding_changes(struct northd_context *ctx, struct hmap *ports,
- continue;
- }
-
-- bool up = (sb->chassis || lsp_is_router(op->nbsp));
-+ bool up = false;
-+
-+ if (lsp_is_router(op->nbsp)) {
-+ up = true;
-+ } else if (sb->chassis) {
-+ up = smap_get_bool(&sb->chassis->other_config,
-+ OVN_FEATURE_PORT_UP_NOTIF, false)
-+ ? sb->n_up && sb->up[0]
-+ : true;
-+ }
-+
- if (!op->nbsp->up || *op->nbsp->up != up) {
- nbrec_logical_switch_port_set_up(op->nbsp, &up, 1);
- }
-@@ -12690,7 +13519,7 @@ static const char *rbac_encap_update[] =
- static const char *rbac_port_binding_auth[] =
- {""};
- static const char *rbac_port_binding_update[] =
-- {"chassis"};
-+ {"chassis", "up"};
-
- static const char *rbac_mac_binding_auth[] =
- {""};
-@@ -13176,6 +14005,8 @@ main(int argc, char *argv[])
- &sbrec_port_binding_col_ha_chassis_group);
- ovsdb_idl_add_column(ovnsb_idl_loop.idl,
- &sbrec_port_binding_col_virtual_parent);
-+ ovsdb_idl_add_column(ovnsb_idl_loop.idl,
-+ &sbrec_port_binding_col_up);
- ovsdb_idl_add_column(ovnsb_idl_loop.idl,
- &sbrec_gateway_chassis_col_chassis);
- ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_gateway_chassis_col_name);
-@@ -13324,9 +14155,25 @@ main(int argc, char *argv[])
- add_column_noalert(ovnsb_idl_loop.idl, &sbrec_load_balancer_col_name);
- add_column_noalert(ovnsb_idl_loop.idl, &sbrec_load_balancer_col_vips);
- add_column_noalert(ovnsb_idl_loop.idl, &sbrec_load_balancer_col_protocol);
-+ add_column_noalert(ovnsb_idl_loop.idl, &sbrec_load_balancer_col_options);
- add_column_noalert(ovnsb_idl_loop.idl,
- &sbrec_load_balancer_col_external_ids);
-
-+ ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_bfd);
-+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_bfd_col_logical_port);
-+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_bfd_col_dst_ip);
-+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_bfd_col_status);
-+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_bfd_col_min_tx);
-+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_bfd_col_min_rx);
-+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_bfd_col_detect_mult);
-+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_bfd_col_disc);
-+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_bfd_col_src_port);
-+
-+ ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_fdb);
-+ add_column_noalert(ovnsb_idl_loop.idl, &sbrec_fdb_col_mac);
-+ add_column_noalert(ovnsb_idl_loop.idl, &sbrec_fdb_col_dp_key);
-+ add_column_noalert(ovnsb_idl_loop.idl, &sbrec_fdb_col_port_key);
-+
- struct ovsdb_idl_index *sbrec_chassis_by_name
- = chassis_index_create(ovnsb_idl_loop.idl);
-
-@@ -13449,6 +14296,7 @@ main(int argc, char *argv[])
- }
- }
-
-+
- free(ovn_internal_version);
- unixctl_server_destroy(unixctl);
- ovsdb_idl_loop_destroy(&ovnnb_idl_loop);
-diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
-index 269e3a888..29019809c 100644
---- a/ovn-nb.ovsschema
-+++ b/ovn-nb.ovsschema
-@@ -1,7 +1,7 @@
- {
- "name": "OVN_Northbound",
-- "version": "5.28.0",
-- "cksum": "610359755 26847",
-+ "version": "5.31.0",
-+ "cksum": "2352750632 28701",
- "tables": {
- "NB_Global": {
- "columns": {
-@@ -188,6 +188,11 @@
- ["eth_src", "eth_dst", "ip_src", "ip_dst",
- "tp_src", "tp_dst"]]},
- "min": 0, "max": "unlimited"}},
-+ "options": {
-+ "type": {"key": "string",
-+ "value": "string",
-+ "min": 0,
-+ "max": "unlimited"}},
- "external_ids": {
- "type": {"key": "string", "value": "string",
- "min": 0, "max": "unlimited"}}},
-@@ -369,6 +374,10 @@
- "min": 0, "max": 1}},
- "nexthop": {"type": "string"},
- "output_port": {"type": {"key": "string", "min": 0, "max": 1}},
-+ "bfd": {"type": {"key": {"type": "uuid", "refTable": "BFD",
-+ "refType": "weak"},
-+ "min": 0,
-+ "max": 1}},
- "options": {
- "type": {"key": "string", "value": "string",
- "min": 0, "max": "unlimited"}},
-@@ -386,6 +395,8 @@
- "key": {"type": "string",
- "enum": ["set", ["allow", "drop", "reroute"]]}}},
- "nexthop": {"type": {"key": "string", "min": 0, "max": 1}},
-+ "nexthops": {"type": {
-+ "key": "string", "min": 0, "max": "unlimited"}},
- "options": {
- "type": {"key": "string", "value": "string",
- "min": 0, "max": "unlimited"}},
-@@ -519,5 +530,30 @@
- "type": {"key": "string", "value": "string",
- "min": 0, "max": "unlimited"}}},
- "indexes": [["name"]],
-+ "isRoot": true},
-+ "BFD": {
-+ "columns": {
-+ "logical_port": {"type": "string"},
-+ "dst_ip": {"type": "string"},
-+ "min_tx": {"type": {"key": {"type": "integer",
-+ "minInteger": 1},
-+ "min": 0, "max": 1}},
-+ "min_rx": {"type": {"key": {"type": "integer"},
-+ "min": 0, "max": 1}},
-+ "detect_mult": {"type": {"key": {"type": "integer",
-+ "minInteger": 1},
-+ "min": 0, "max": 1}},
-+ "status": {
-+ "type": {"key": {"type": "string",
-+ "enum": ["set", ["down", "init", "up",
-+ "admin_down"]]},
-+ "min": 0, "max": 1}},
-+ "external_ids": {
-+ "type": {"key": "string", "value": "string",
-+ "min": 0, "max": "unlimited"}},
-+ "options": {
-+ "type": {"key": "string", "value": "string",
-+ "min": 0, "max": "unlimited"}}},
-+ "indexes": [["logical_port", "dst_ip"]],
- "isRoot": true}}
- }
-diff --git a/ovn-nb.xml b/ovn-nb.xml
-index c9ab25ceb..09b755f1a 100644
---- a/ovn-nb.xml
-+++ b/ovn-nb.xml
-@@ -1635,6 +1635,24 @@
- See External IDs at the beginning of this document.
-
-
-+
-+
-+ If the load balancer is created with --reject
option and
-+ it has no active backends, a TCP reset segment (for tcp) or an ICMP
-+ port unreachable packet (for all other kind of traffic) will be sent
-+ whenever an incoming packet is received for this load-balancer.
-+ Please note using --reject
option will disable empty_lb
-+ SB controller event for this load balancer.
-+
-+
-+
-+ IP to be used as source IP for packets that have been hair-pinned
-+ after load balancing. The default behavior when the option is not set
-+ is to use the load balancer VIP as source IP. This option may have
-+ exactly one IPv4 and/or one IPv6 address on it, separated by a space
-+ character.
-+
-+
-
-
-
-@@ -1917,16 +1935,29 @@
-
-
-
-- If set, indicates a set of IP addresses to use to force SNAT a packet
-- that has already been load-balanced in the gateway router. When
-- multiple gateway routers are configured, a packet can potentially
-- enter any of the gateway routers, get DNATted as part of the load-
-- balancing and eventually reach the logical switch port.
-- For the return traffic to go back to the same gateway router (for
-- unDNATing), the packet needs a SNAT in the first place. This can be
-- achieved by setting the above option with a gateway specific set of
-- IP addresses. This option may have exactly one IPv4 and/or one IPv6
-- address on it, separated by a space character.
-+ If set, this option can take two possible type of values. Either
-+ a set of IP addresses or the string value - router_ip
.
-+
-+
-+
-+ If a set of IP addresses are configured, it indicates to use to
-+ force SNAT a packet that has already been load-balanced in the
-+ gateway router. When multiple gateway routers are configured, a
-+ packet can potentially enter any of the gateway routers, get
-+ DNATted as part of the load-balancing and eventually reach the
-+ logical switch port. For the return traffic to go back to the
-+ same gateway router (for unDNATing), the packet needs a SNAT in the
-+ first place. This can be achieved by setting the above option with
-+ a gateway specific set of IP addresses. This option may have exactly
-+ one IPv4 and/or one IPv6 address on it, separated by a space
-+ character.
-+
-+
-+
-+ If it is configured with the value router_ip
, then
-+ the load balanced packet is SNATed with the IP of router port
-+ (attached to the gateway router) selected as the destination after
-+ taking the routing decision.
-
-
-
-@@ -2634,6 +2665,13 @@
-
-
-
-+
-+
-+ Reference to row if the route has associated a
-+ BFD session
-+
-+
-+
-
- ovn-ic
populates this key if the route is learned from the
- global database. In this case the value
-@@ -2713,18 +2751,34 @@
-
-
-
-- reroute
: Reroute packet to .
-+ reroute
: Reroute packet to or
-+ .
-
-
-
-
-
-+
-+ Note: This column is deprecated in favor of .
-+
-
- Next-hop IP address for this route, which should be the IP
- address of a connected router port or the IP address of a logical port.
-
-
-
-+
-+
-+ Next-hop ECMP IP addresses for this route. Each IP in the list should
-+ be the IP address of a connected router port or the IP address of a
-+ logical port.
-+
-+
-+
-+ One IP from the list is selected as next hop.
-+
-+
-+
-
-
- Marks the packet with the value specified when the router policy
-@@ -3702,4 +3756,71 @@
-
-
-
-+
-+
-+
-+ Contains BFD parameter for ovn-controller bfd configuration.
-+
-+
-+
-+
-+ OVN logical port when BFD engine is running.
-+
-+
-+
-+ BFD peer IP address.
-+
-+
-+
-+ This is the minimum interval, in milliseconds, that the local
-+ system would like to use when transmitting BFD Control packets,
-+ less any jitter applied. The value zero is reserved. Default
-+ value is 1000 ms.
-+
-+
-+
-+ This is the minimum interval, in milliseconds, between received
-+ BFD Control packets that this system is capable of supporting,
-+ less any jitter applied by the sender. If this value is zero,
-+ the transmitting system does not want the remote system to send
-+ any periodic BFD Control packets.
-+
-+
-+
-+ Detection time multiplier. The negotiated transmit interval,
-+ multiplied by this value, provides the Detection Time for the
-+ receiving system in Asynchronous mode. Default value is 5.
-+
-+
-+
-+ Reserved for future use.
-+
-+
-+
-+ See External IDs at the beginning of this document.
-+
-+
-+
-+
-+
-+
-+ BFD port logical states. Possible values are:
-+
-+ -
-+
admin_down
-+
-+ -
-+
down
-+
-+ -
-+
init
-+
-+ -
-+
up
-+
-+
-+
-+
-+
-+
-
-diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
-index 5228839b8..b5d3338f4 100644
---- a/ovn-sb.ovsschema
-+++ b/ovn-sb.ovsschema
-@@ -1,7 +1,7 @@
- {
- "name": "OVN_Southbound",
-- "version": "20.12.0",
-- "cksum": "3969471120 24441",
-+ "version": "20.16.1",
-+ "cksum": "4243908307 26536",
- "tables": {
- "SB_Global": {
- "columns": {
-@@ -103,7 +103,7 @@
- "egress"]]}}},
- "table_id": {"type": {"key": {"type": "integer",
- "minInteger": 0,
-- "maxInteger": 23}}},
-+ "maxInteger": 32}}},
- "priority": {"type": {"key": {"type": "integer",
- "minInteger": 0,
- "maxInteger": 65535}}},
-@@ -225,6 +225,7 @@
- "nat_addresses": {"type": {"key": "string",
- "min": 0,
- "max": "unlimited"}},
-+ "up": {"type": {"key": "boolean", "min": 0, "max": 1}},
- "external_ids": {"type": {"key": "string",
- "value": "string",
- "min": 0,
-@@ -481,9 +482,50 @@
- "type": {"key": {"type": "uuid",
- "refTable": "Datapath_Binding"},
- "min": 0, "max": "unlimited"}},
-+ "options": {
-+ "type": {"key": "string",
-+ "value": "string",
-+ "min": 0,
-+ "max": "unlimited"}},
-+ "external_ids": {
-+ "type": {"key": "string", "value": "string",
-+ "min": 0, "max": "unlimited"}}},
-+ "isRoot": true},
-+ "BFD": {
-+ "columns": {
-+ "src_port": {"type": {"key": {"type": "integer",
-+ "minInteger": 49152,
-+ "maxInteger": 65535}}},
-+ "disc": {"type": {"key": {"type": "integer"}}},
-+ "logical_port": {"type": "string"},
-+ "dst_ip": {"type": "string"},
-+ "min_tx": {"type": {"key": {"type": "integer"}}},
-+ "min_rx": {"type": {"key": {"type": "integer"}}},
-+ "detect_mult": {"type": {"key": {"type": "integer"}}},
-+ "status": {
-+ "type": {"key": {"type": "string",
-+ "enum": ["set", ["down", "init", "up",
-+ "admin_down"]]}}},
- "external_ids": {
-+ "type": {"key": "string", "value": "string",
-+ "min": 0, "max": "unlimited"}},
-+ "options": {
- "type": {"key": "string", "value": "string",
- "min": 0, "max": "unlimited"}}},
-+ "indexes": [["logical_port", "dst_ip", "src_port", "disc"]],
-+ "isRoot": true},
-+ "FDB": {
-+ "columns": {
-+ "mac": {"type": "string"},
-+ "dp_key": {
-+ "type": {"key": {"type": "integer",
-+ "minInteger": 1,
-+ "maxInteger": 16777215}}},
-+ "port_key": {
-+ "type": {"key": {"type": "integer",
-+ "minInteger": 1,
-+ "maxInteger": 16777215}}}},
-+ "indexes": [["mac", "dp_key"]],
- "isRoot": true}
- }
- }
-diff --git a/ovn-sb.xml b/ovn-sb.xml
-index c13994848..258a12b4e 100644
---- a/ovn-sb.xml
-+++ b/ovn-sb.xml
-@@ -322,6 +322,11 @@
- table. See ovn-controller
(8) for more information.
-
-
-+
-+ ovn-controller
populates this key with true
-+ when it supports Port_Binding.up
.
-+
-+
-
- The overall purpose of these columns is described under Common
- Columns
at the beginning of this document.
-@@ -1521,6 +1526,68 @@
-
-
-
-+ P = get_fdb(A);
-+
-+
-+
-+ Parameters:48-bit MAC address field A.
-+
-+
-+
-+ Looks up A in fdb table. If an entry is found, stores
-+ the logical port key to the out parameter P
.
-+
-+
-+ Example: outport = get_fdb(eth.src);
-+
-+
-+
-+ put_fdb(P, A);
-+
-+
-+
-+
-+ Parameters: logical port string field P, 48-bit
-+ MAC address field A.
-+
-+
-+
-+ Adds or updates the entry for Ethernet address A in
-+ fdb table, setting its logical port key to P.
-+
-+
-+ Example: put_fdb(inport, arp.spa);
-+
-+
-+
-+ R = lookup_fdb(P, A);
-+
-+
-+
-+
-+ Parameters: 48-bit MAC address field M,
-+ logical port string field P.
-+
-+
-+
-+ Result: stored to a 1-bit subfield R.
-+
-+
-+
-+ Looks up A in fdb table. If an entry is found
-+ and the the logical port key is P, P
,
-+ stores 1
in the 1-bit subfield
-+ R, else 0.
-+
-+
-+
-+ Example:
-+
-+ reg0[0] = lookup_fdb(inport, eth.src);
-+
-+
-+
-+
- nd_ns { action;
... };
-
-
-@@ -2771,6 +2838,14 @@ tcp.flags = RST;
-
-
-
-+
-+
-+ This is set to true
whenever all OVS flows
-+ required by this Port_Binding have been installed. This is
-+ populated by ovn-controller
.
-+
-+
-+
-
-
- A number that represents the logical port in the key (e.g. STT key or
-@@ -4225,10 +4300,126 @@ tcp.flags = RST;
- Datapaths to which this load balancer applies to.
-
-
-+
-+
-+ IP to be used as source IP for packets that have been hair-pinned after
-+ load balancing. This value is automatically populated by
-+ ovn-northd
.
-+
-+
-+ This value is automatically set to true
by
-+ ovn-northd
when original destination IP and transport port
-+ of the load balanced packets are stored in registers
-+ reg1, reg2, xxreg1
.
-+
-+
-+
-
-
- See External IDs at the beginning of this document.
-
-
-
-+
-+
-+
-+ Contains BFD parameter for ovn-controller bfd configuration.
-+
-+
-+
-+
-+ udp source port used in bfd control packets.
-+ The source port MUST be in the range 49152 through 65535
-+ (RFC5881 section 4).
-+
-+
-+
-+ A unique, nonzero discriminator value generated by the transmitting
-+ system, used to demultiplex multiple BFD sessions between the same pair
-+ of systems.
-+
-+
-+
-+ OVN logical port when BFD engine is running.
-+
-+
-+
-+ BFD peer IP address.
-+
-+
-+
-+ This is the minimum interval, in milliseconds, that the local
-+ system would like to use when transmitting BFD Control packets,
-+ less any jitter applied. The value zero is reserved.
-+
-+
-+
-+ This is the minimum interval, in milliseconds, between received
-+ BFD Control packets that this system is capable of supporting,
-+ less any jitter applied by the sender. If this value is zero,
-+ the transmitting system does not want the remote system to send
-+ any periodic BFD Control packets.
-+
-+
-+
-+ Detection time multiplier. The negotiated transmit interval,
-+ multiplied by this value, provides the Detection Time for the
-+ receiving system in Asynchronous mode.
-+
-+
-+
-+ Reserved for future use.
-+
-+
-+
-+ See External IDs at the beginning of this document.
-+
-+
-+
-+
-+
-+
-+ BFD port logical states. Possible values are:
-+
-+ -
-+
admin_down
-+
-+ -
-+
down
-+
-+ -
-+
init
-+
-+ -
-+
up
-+
-+
-+
-+
-+
-+
-+
-+
-+
-+ This table is primarily used to learn the MACs observed on a VIF
-+ which belongs to a Logical_Switch_Port
record in
-+ OVN_Northbound
whose port security is disabled
-+ and 'unknown' address set. If port security is disabled on a
-+ Logical_Switch_Port
record, OVN should allow traffic
-+ with any source mac from the VIF. This table will be used to deliver
-+ a packet to the VIF, If a packet's eth.dst
is learnt.
-+
-+
-+
-+ The learnt mac address.
-+
-+
-+
-+ The key of the datapath on which this FDB was learnt.
-+
-+
-+
-+ The key of the port binding on which this FDB was learnt.
-+
-+
-
-diff --git a/ovs b/ovs
-new file mode 160000
-index 000000000..ac09cbfcb
---- /dev/null
-+++ b/ovs
-@@ -0,0 +1 @@
-+Subproject commit ac09cbfcb70ac6f443f039d5934448bd80f74493
-diff --git a/tests/atlocal.in b/tests/atlocal.in
-index d9a4c91d4..5ebc8e117 100644
---- a/tests/atlocal.in
-+++ b/tests/atlocal.in
-@@ -181,6 +181,9 @@ fi
- # Set HAVE_DIBBLER-SERVER
- find_command dibbler-server
-
-+# Set HAVE_BFDD_BEACON
-+find_command bfdd-beacon
-+
- # Turn off proxies.
- unset http_proxy
- unset https_proxy
-diff --git a/tests/automake.mk b/tests/automake.mk
-index c5c286eae..c09f615d5 100644
---- a/tests/automake.mk
-+++ b/tests/automake.mk
-@@ -31,7 +31,8 @@ TESTSUITE_AT = \
- tests/ovn-controller-vtep.at \
- tests/ovn-ic.at \
- tests/ovn-macros.at \
-- tests/ovn-performance.at
-+ tests/ovn-performance.at \
-+ tests/ovn-ofctrl-seqno.at
-
- SYSTEM_KMOD_TESTSUITE_AT = \
- tests/system-common-macros.at \
-@@ -202,7 +203,10 @@ noinst_PROGRAMS += tests/ovstest
- tests_ovstest_SOURCES = \
- tests/ovstest.c \
- tests/ovstest.h \
-- tests/test-ovn.c
-+ tests/test-ovn.c \
-+ controller/test-ofctrl-seqno.c \
-+ controller/ofctrl-seqno.c \
-+ controller/ofctrl-seqno.h
-
- tests_ovstest_LDADD = $(OVS_LIBDIR)/daemon.lo \
- $(OVS_LIBDIR)/libopenvswitch.la lib/libovn.la
-diff --git a/tests/ofproto-macros.at b/tests/ofproto-macros.at
-index dd5d3848d..3d7ac08b3 100644
---- a/tests/ofproto-macros.at
-+++ b/tests/ofproto-macros.at
-@@ -12,7 +12,10 @@ strip_n_bytes () {
-
- # Strips 'cookie=...' from ovs-ofctl output.
- strip_cookie () {
-- sed 's/ cookie=0x[0-9a-fA-F]*,//'
-+ sed '
-+s/ cookie=0x[0-9a-fA-F]*,//
-+s/cookie=0x[0-9a-fA-F]*,//
-+'
- }
-
- # Strips out uninteresting parts of ovs-ofctl output, as well as parts
-@@ -37,7 +40,7 @@ s/dir\/[0-9]*\/br0.mgmt/dir\/XXXX\/br0.mgmt/
- # Strips out uninteresting parts of ovs-ofctl output, including n_packets=..
- # n_bytes=..
- ofctl_strip_all () {
-- ofctl_strip | strip_n_packets | strip_n_bytes | strip_cookie
-+ ofctl_strip | strip_n_packets | strip_n_bytes | strip_cookie | sort
- }
-
- # Filter (multiline) vconn debug messages from ovs-vswitchd.log.
-diff --git a/tests/ovn-controller-vtep.at b/tests/ovn-controller-vtep.at
-index cb582811f..b2261d285 100644
---- a/tests/ovn-controller-vtep.at
-+++ b/tests/ovn-controller-vtep.at
-@@ -177,22 +177,22 @@ AT_CLEANUP
- AT_SETUP([ovn-controller-vtep - binding 1])
- OVN_CONTROLLER_VTEP_START
-
--# adds logical switch 'lswitch0' and vlan_bindings.
-+AS_BOX([add logical switch 'lswitch0' and vlan_bindings])
- AT_CHECK([vtep-ctl add-ls lswitch0 -- bind-ls br-vtep p0 100 lswitch0 -- bind-ls br-vtep p1 300 lswitch0])
- # adds logical switch port in ovn-nb database, and sets the type and options.
- OVN_NB_ADD_VTEP_PORT([br-test], [br-vtep_lswitch0], [br-vtep], [lswitch0])
--check ovn-sbctl wait-until Port_Binding br-vtep_lswitch0 chassis!='[[]]'
-+wait_row_count Port_Binding 1 logical_port=br-vtep_lswitch0 chassis!='[[]]'
- # should see one binding, associated to chassis of 'br-vtep'.
- chassis_uuid=$(ovn-sbctl --columns=_uuid list Chassis br-vtep | cut -d ':' -f2 | tr -d ' ')
- AT_CHECK_UNQUOTED([ovn-sbctl --columns=chassis list Port_Binding br-vtep_lswitch0 | cut -d ':' -f2 | tr -d ' '], [0], [dnl
- ${chassis_uuid}
- ])
-
--# adds another logical switch 'lswitch1' and vlan_bindings.
-+AS_BOX([add another logical switch 'lswitch1' and vlan_bindings])
- AT_CHECK([vtep-ctl add-ls lswitch1 -- bind-ls br-vtep p0 200 lswitch1])
- # adds logical switch port in ovn-nb database for lswitch1.
- OVN_NB_ADD_VTEP_PORT([br-test], [br-vtep_lswitch1], [br-vtep], [lswitch1])
--check ovn-sbctl wait-until Port_Binding br-vtep_lswitch1 chassis!='[[]]'
-+wait_row_count Port_Binding 1 logical_port=br-vtep_lswitch1 chassis!='[[]]'
- # This is allowed, but not recommended, to have two vlan_bindings (to different vtep logical switches)
- # from one vtep gateway physical port in one ovn-nb logical swithch.
- AT_CHECK_UNQUOTED([ovn-sbctl --columns=chassis list Port_Binding | cut -d ':' -f2 | tr -d ' ' | sort], [0], [dnl
-@@ -201,7 +201,7 @@ ${chassis_uuid}
- ${chassis_uuid}
- ])
-
--# adds another logical switch port in ovn-nb database for lswitch0.
-+AS_BOX([add another logical switch port in ovn-nb database for lswitch0])
- OVN_NB_ADD_VTEP_PORT([br-test], [br-vtep_lswitch0_dup], [br-vtep], [lswitch0])
-
- # confirms the warning log.
-@@ -228,7 +228,7 @@ ${chassis_uuid}
- ${chassis_uuid}
- ])
-
--# deletes physical ports from vtep.
-+AS_BOX([delete physical ports from vtep])
- AT_CHECK([ovs-vsctl del-port p0 -- del-port p1])
- OVS_WAIT_UNTIL([test -z "`ovn-sbctl list Chassis | grep -- br-vtep_lswitch`"])
- OVS_WAIT_UNTIL([test -z "`vtep-ctl list physical_port p0`"])
-diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at
-index 1b4679963..f818f9cea 100644
---- a/tests/ovn-controller.at
-+++ b/tests/ovn-controller.at
-@@ -414,3 +414,20 @@ OVS_WAIT_UNTIL([ovs-vsctl get Bridge br-int external_ids:ovn-nb-cfg], [0], [1])
-
- OVN_CLEANUP([hv1])
- AT_CLEANUP
-+
-+AT_SETUP([ovn -- features])
-+AT_KEYWORDS([features])
-+ovn_start
-+
-+net_add n1
-+sim_add hv1
-+ovs-vsctl add-br br-phys
-+ovn_attach n1 br-phys 192.168.0.1
-+
-+# Wait for ovn-controller to register in the SB.
-+OVS_WAIT_UNTIL([
-+ test "$(ovn-sbctl get chassis hv1 other_config:port-up-notif)" = '"true"'
-+])
-+
-+OVN_CLEANUP([hv1])
-+AT_CLEANUP
-diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at
-index 59e500c57..2ba29a960 100644
---- a/tests/ovn-macros.at
-+++ b/tests/ovn-macros.at
-@@ -417,6 +417,22 @@ wait_column() {
- echo "$column in $db table $table has value $found, from the following rows:"
- ovn-${db}ctl list $table])
- }
-+
-+# wait_for_ports_up [PORT...]
-+#
-+# With arguments, waits for specified Logical_Switch_Ports to come up.
-+# Without arguments, waits for all "plain" and router
-+# Logical_Switch_Ports to come up.
-+wait_for_ports_up() {
-+ if test $# = 0; then
-+ wait_row_count nb:Logical_Switch_Port 0 up!=true type='""'
-+ wait_row_count nb:Logical_Switch_Port 0 up!=true type=router
-+ else
-+ for port; do
-+ wait_row_count nb:Logical_Switch_Port 1 up=true name=$port
-+ done
-+ fi
-+}
- OVS_END_SHELL_HELPERS
-
- m4_define([OVN_POPULATE_ARP], [AT_CHECK(ovn_populate_arp__, [0], [ignore])])
-diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
-index 01edfcbc1..6d91aa4c5 100644
---- a/tests/ovn-nbctl.at
-+++ b/tests/ovn-nbctl.at
-@@ -1539,34 +1539,34 @@ AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl
- dnl Add ecmp routes
- AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.0.0/24 11.0.0.1])
- AT_CHECK([ovn-nbctl --ecmp lr-route-add lr0 10.0.0.0/24 11.0.0.2])
--AT_CHECK([ovn-nbctl --ecmp lr-route-add lr0 10.0.0.0/24 11.0.0.2])
- AT_CHECK([ovn-nbctl --ecmp lr-route-add lr0 10.0.0.0/24 11.0.0.3])
--AT_CHECK([ovn-nbctl --ecmp lr-route-add lr0 10.0.0.0/24 11.0.0.3 lp0])
-+AT_CHECK([ovn-nbctl --ecmp lr-route-add lr0 10.0.0.0/24 11.0.0.4 lp0])
- AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl
- IPv4 Routes
-- 10.0.0.0/24 11.0.0.1 dst-ip
-- 10.0.0.0/24 11.0.0.2 dst-ip
-- 10.0.0.0/24 11.0.0.2 dst-ip
-- 10.0.0.0/24 11.0.0.3 dst-ip
-- 10.0.0.0/24 11.0.0.3 dst-ip lp0
-+ 10.0.0.0/24 11.0.0.1 dst-ip ecmp
-+ 10.0.0.0/24 11.0.0.2 dst-ip ecmp
-+ 10.0.0.0/24 11.0.0.3 dst-ip ecmp
-+ 10.0.0.0/24 11.0.0.4 dst-ip lp0 ecmp
-+])
-+AT_CHECK([ovn-nbctl --ecmp lr-route-add lr0 10.0.0.0/24 11.0.0.2], [1], [],
-+ [ovn-nbctl: duplicate nexthop for the same ECMP route
- ])
-
- dnl Delete ecmp routes
- AT_CHECK([ovn-nbctl lr-route-del lr0 10.0.0.0/24 11.0.0.1])
- AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl
- IPv4 Routes
-- 10.0.0.0/24 11.0.0.2 dst-ip
-- 10.0.0.0/24 11.0.0.2 dst-ip
-- 10.0.0.0/24 11.0.0.3 dst-ip
-- 10.0.0.0/24 11.0.0.3 dst-ip lp0
-+ 10.0.0.0/24 11.0.0.2 dst-ip ecmp
-+ 10.0.0.0/24 11.0.0.3 dst-ip ecmp
-+ 10.0.0.0/24 11.0.0.4 dst-ip lp0 ecmp
- ])
- AT_CHECK([ovn-nbctl lr-route-del lr0 10.0.0.0/24 11.0.0.2])
- AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl
- IPv4 Routes
-- 10.0.0.0/24 11.0.0.3 dst-ip
-- 10.0.0.0/24 11.0.0.3 dst-ip lp0
-+ 10.0.0.0/24 11.0.0.3 dst-ip ecmp
-+ 10.0.0.0/24 11.0.0.4 dst-ip lp0 ecmp
- ])
--AT_CHECK([ovn-nbctl lr-route-del lr0 10.0.0.0/24 11.0.0.3 lp0])
-+AT_CHECK([ovn-nbctl lr-route-del lr0 10.0.0.0/24 11.0.0.4 lp0])
- AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl
- IPv4 Routes
- 10.0.0.0/24 11.0.0.3 dst-ip
-@@ -1605,7 +1605,15 @@ AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.1.1/24 11.0.1.1 lp0])
- AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.0.1/24 11.0.0.1])
- AT_CHECK([ovn-nbctl lr-route-add lr0 0:0:0:0:0:0:0:0/0 2001:0db8:0:f101::1])
- AT_CHECK([ovn-nbctl lr-route-add lr0 2001:0db8:0::/64 2001:0db8:0:f102::1 lp0])
--AT_CHECK([ovn-nbctl lr-route-add lr0 2001:0db8:1::/64 2001:0db8:0:f103::1])
-+AT_CHECK([ovn-nbctl --ecmp lr-route-add lr0 2001:0db8:1::/64 2001:0db8:0:f103::1])
-+AT_CHECK([ovn-nbctl --ecmp lr-route-add lr0 2001:0db8:1::/64 2001:0db8:0:f103::2])
-+AT_CHECK([ovn-nbctl --ecmp lr-route-add lr0 2001:0db8:1::/64 2001:0db8:0:f103::3])
-+AT_CHECK([ovn-nbctl --ecmp lr-route-add lr0 2001:0db8:1::/64 2001:0db8:0:f103::4])
-+AT_CHECK([ovn-nbctl lr-route-add lr0 2002:0db8:1::/64 2001:0db8:0:f103::5])
-+AT_CHECK([ovn-nbctl --ecmp-symmetric-reply lr-route-add lr0 2003:0db8:1::/64 2001:0db8:0:f103::6])
-+AT_CHECK([ovn-nbctl --ecmp-symmetric-reply lr-route-add lr0 2003:0db8:1::/64 2001:0db8:0:f103::6], [1], [],
-+ [ovn-nbctl: duplicate nexthop for the same ECMP route
-+])
-
- AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl
- IPv4 Routes
-@@ -1615,9 +1623,20 @@ IPv4 Routes
-
- IPv6 Routes
- 2001:db8::/64 2001:db8:0:f102::1 dst-ip lp0
-- 2001:db8:1::/64 2001:db8:0:f103::1 dst-ip
-+ 2001:db8:1::/64 2001:db8:0:f103::1 dst-ip ecmp
-+ 2001:db8:1::/64 2001:db8:0:f103::2 dst-ip ecmp
-+ 2001:db8:1::/64 2001:db8:0:f103::3 dst-ip ecmp
-+ 2001:db8:1::/64 2001:db8:0:f103::4 dst-ip ecmp
-+ 2002:db8:1::/64 2001:db8:0:f103::5 dst-ip
-+ 2003:db8:1::/64 2001:db8:0:f103::6 dst-ip ecmp-symmetric-reply
- ::/0 2001:db8:0:f101::1 dst-ip
--])])
-+])
-+
-+AT_CHECK([ovn-nbctl lrp-add lr0 lr0-p0 00:00:01:01:02:03 192.168.10.1/24])
-+bfd_uuid=$(ovn-nbctl create bfd logical_port=lr0-p0 dst_ip=100.0.0.50 status=down min_tx=250 min_rx=250 detect_mult=10)
-+AT_CHECK([ovn-nbctl lr-route-add lr0 100.0.0.0/24 192.168.0.1])
-+route_uuid=$(fetch_column nb:logical_router_static_route _uuid ip_prefix="100.0.0.0/24")
-+AT_CHECK([ovn-nbctl set logical_router_static_route $route_uuid bfd=$bfd_uuid])])
-
- dnl ---------------------------------------------------------------------
-
-diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
-index 90ca0a4db..11d4a9c86 100644
---- a/tests/ovn-northd.at
-+++ b/tests/ovn-northd.at
-@@ -605,11 +605,12 @@ wait_row_count Port_Binding 0 logical_port=sw0-pext1 'chassis!=[[]]'
- wait_row_count HA_Chassis_Group 1 name=hagrp1
- wait_row_count HA_Chassis 3
-
--# Clear ha_chassis_group for sw0-pext2
--ovn-nbctl --wait=sb clear logical_switch_port sw0-pext2 ha_chassis_group
-+AS_BOX([Clear ha_chassis_group for sw0-pext2 and reset port type to normal in the same txn])
-
--wait_row_count Port_Binding 0 logical_port=sw0-pext2 'chassis!=[[]]'
-+check ovn-nbctl --wait=sb clear logical_switch_port sw0-pext2 \
-+ha_chassis_group -- set logical_switch_port sw0-pext2 'type=""'
- wait_row_count HA_Chassis_Group 0
-+wait_row_count Port_Binding 0 logical_port=sw0-pext2 'chassis!=[[]]'
- check_row_count HA_Chassis 0
-
- as ovn-sb
-@@ -624,61 +625,66 @@ AT_CLEANUP
- AT_SETUP([ovn -- ovn-northd pause and resume])
- ovn_start
-
--AT_CHECK([test xfalse = x`as northd ovn-appctl -t ovn-northd is-paused`])
--AT_CHECK([as northd ovn-appctl -t ovn-northd status], [0], [Status: active
--])
--AT_CHECK([test xfalse = x`as northd-backup ovn-appctl -t ovn-northd \
--is-paused`])
--AT_CHECK([as northd-backup ovn-appctl -t ovn-northd status], [0],
--[Status: standby
--])
--
--ovn-nbctl ls-add sw0
--
--OVS_WAIT_UNTIL([
-- ovn-sbctl lflow-list sw0
-- test 0 = $?])
-+get_northd_status() {
-+ as northd ovn-appctl -t ovn-northd is-paused
-+ as northd ovn-appctl -t ovn-northd status
-+ as northd-backup ovn-appctl -t ovn-northd is-paused
-+ as northd-backup ovn-appctl -t ovn-northd status
-+}
-
--ovn-nbctl ls-del sw0
--OVS_WAIT_UNTIL([
-- ovn-sbctl lflow-list sw0
-- test 1 = $?])
-+AS_BOX([Pause the backup])
-+# This forces the main northd to become active (otherwise there's no
-+# guarantee, ovn_start is racy).
-+check as northd-backup ovs-appctl -t ovn-northd pause
-+OVS_WAIT_FOR_OUTPUT([get_northd_status], [0], [false
-+Status: active
-+true
-+Status: paused
-+])
-+
-+AS_BOX([Resume the backup])
-+check as northd-backup ovs-appctl -t ovn-northd resume
-+OVS_WAIT_FOR_OUTPUT([get_northd_status], [0], [false
-+Status: active
-+false
-+Status: standby
-+])
-+
-+AS_BOX([Check that ovn-northd is active])
-+# Check that ovn-northd is active, by verifying that it creates and
-+# destroys southbound datapaths as one would expect.
-+check_row_count Datapath_Binding 0
-+check ovn-nbctl --wait=sb ls-add sw0
-+check_row_count Datapath_Binding 1
-+check ovn-nbctl --wait=sb ls-del sw0
-+check_row_count Datapath_Binding 0
-
--# Now pause the ovn-northd
--as northd ovs-appctl -t ovn-northd pause
--as northd-backup ovs-appctl -t ovn-northd pause
--AT_CHECK([test xtrue = x`as northd ovn-appctl -t ovn-northd is-paused`])
--AT_CHECK([as northd ovn-appctl -t ovn-northd status], [0], [Status: paused
--])
--AT_CHECK([test xtrue = x`as northd-backup ovn-appctl -t ovn-northd is-paused`])
--AT_CHECK([as northd-backup ovn-appctl -t ovn-northd status], [0],
--[Status: paused
-+AS_BOX([Pause the main northd])
-+check as northd ovs-appctl -t ovn-northd pause
-+check as northd-backup ovs-appctl -t ovn-northd pause
-+AT_CHECK([get_northd_status], [0], [true
-+Status: paused
-+true
-+Status: paused
- ])
-
--ovn-nbctl ls-add sw0
--
--# There should be no logical flows for sw0 datapath.
--OVS_WAIT_UNTIL([
-- ovn-sbctl lflow-list sw0
-- test 1 = $?])
--
--# Now resume ovn-northd
--as northd ovs-appctl -t ovn-northd resume
--AT_CHECK([test xfalse = x`as northd ovn-appctl -t ovn-northd is-paused`])
--OVS_WAIT_UNTIL([as northd ovn-appctl -t ovn-northd status], [0],
--[Status: active
--])
-+AS_BOX([Verify that ovn-northd is paused])
-+# Now ovn-northd won't respond by adding a datapath, because it's paused.
-+check ovn-nbctl ls-add sw0
-+check sleep 5
-+check_row_count Datapath_Binding 0
-
--as northd-backup ovs-appctl -t ovn-northd resume
--AT_CHECK([test xfalse = x`as northd-backup ovn-appctl -t ovn-northd \
--is-paused`])
--AT_CHECK([as northd-backup ovn-appctl -t ovn-northd status], [0],
--[Status: standby
-+AS_BOX([Resume the main northd])
-+check as northd ovs-appctl -t ovn-northd resume
-+check as northd-backup ovs-appctl -t ovn-northd resume
-+OVS_WAIT_FOR_OUTPUT([get_northd_status], [0], [false
-+Status: active
-+false
-+Status: standby
- ])
-
--OVS_WAIT_UNTIL([
-- ovn-sbctl lflow-list sw0
-- test 0 = $?])
-+check ovn-nbctl --wait=sb sync
-+check_row_count Datapath_Binding 1
-
- AT_CLEANUP
-
-@@ -849,7 +855,7 @@ uuid=$(fetch_column Port_Binding _uuid logical_port=cr-DR-S1)
- echo "CR-LRP UUID is: " $uuid
-
- check ovn-nbctl set Logical_Router $cr_uuid options:chassis=gw1
--check ovn-nbctl --wait=hv sync
-+check ovn-nbctl --wait=sb sync
-
- ovn-nbctl create Address_Set name=allowed_range addresses=\"1.1.1.1\"
- ovn-nbctl create Address_Set name=disallowed_range addresses=\"2.2.2.2\"
-@@ -1048,7 +1054,7 @@ health_check @hc | uuidfilt], [0], [<0>
-
- wait_row_count Service_Monitor 0
-
--# create logical switches and ports
-+AS_BOX([create logical switches and ports])
- ovn-nbctl ls-add sw0
- ovn-nbctl --wait=sb lsp-add sw0 sw0-p1 -- lsp-set-addresses sw0-p1 \
- "00:00:00:00:00:03 10.0.0.3"
-@@ -1072,54 +1078,57 @@ check ovn-nbctl --wait=sb ls-lb-add sw0 lb1
- AT_CAPTURE_FILE([sbflows])
- OVS_WAIT_FOR_OUTPUT(
- [ovn-sbctl dump-flows sw0 | tee sbflows | grep 'priority=120.*ct_lb' | sed 's/table=..//'], 0, [dnl
-- (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
-+ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
- ])
-
--# Delete the Load_Balancer_Health_Check
-+AS_BOX([Delete the Load_Balancer_Health_Check])
- ovn-nbctl --wait=sb clear load_balancer . health_check
- wait_row_count Service_Monitor 0
-
- AT_CAPTURE_FILE([sbflows2])
- OVS_WAIT_FOR_OUTPUT(
- [ovn-sbctl dump-flows sw0 | tee sbflows2 | grep 'priority=120.*ct_lb' | sed 's/table=..//'], [0],
--[ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
-+[ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
- ])
-
--# Create the Load_Balancer_Health_Check again.
-+AS_BOX([Create the Load_Balancer_Health_Check again.])
- ovn-nbctl --wait=sb -- --id=@hc create \
- Load_Balancer_Health_Check vip="10.0.0.10\:80" -- add Load_Balancer . \
- health_check @hc
- wait_row_count Service_Monitor 2
-+check ovn-nbctl --wait=sb sync
-
- ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
- AT_CHECK([cat lflows.txt | sed 's/table=..//'], [0], [dnl
-- (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
-+ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
- ])
-
--# Get the uuid of both the service_monitor
-+AS_BOX([Get the uuid of both the service_monitor])
- sm_sw0_p1=$(fetch_column Service_Monitor _uuid logical_port=sw0-p1)
- sm_sw1_p1=$(fetch_column Service_Monitor _uuid logical_port=sw1-p1)
-
- AT_CAPTURE_FILE([sbflows3])
- OVS_WAIT_FOR_OUTPUT(
- [ovn-sbctl dump-flows sw0 | tee sbflows 3 | grep 'priority=120.*ct_lb' | sed 's/table=..//'], [0],
--[ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
-+[ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
- ])
-
--# Set the service monitor for sw1-p1 to offline
-+AS_BOX([Set the service monitor for sw1-p1 to offline])
- check ovn-sbctl set service_monitor sw1-p1 status=offline
- wait_row_count Service_Monitor 1 logical_port=sw1-p1 status=offline
-+check ovn-nbctl --wait=sb sync
-
- AT_CAPTURE_FILE([sbflows4])
- OVS_WAIT_FOR_OUTPUT(
- [ovn-sbctl dump-flows sw0 | tee sbflows4 | grep 'priority=120.*ct_lb' | sed 's/table=..//'], [0],
--[ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80);)
-+[ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80);)
- ])
-
--# Set the service monitor for sw0-p1 to offline
-+AS_BOX([Set the service monitor for sw0-p1 to offline])
- ovn-sbctl set service_monitor $sm_sw0_p1 status=offline
-
- wait_row_count Service_Monitor 1 logical_port=sw0-p1 status=offline
-+check ovn-nbctl --wait=sb sync
-
- AT_CAPTURE_FILE([sbflows5])
- OVS_WAIT_FOR_OUTPUT(
-@@ -1131,32 +1140,34 @@ OVS_WAIT_FOR_OUTPUT(
- (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(drop;)
- ])
-
--# Set the service monitor for sw0-p1 and sw1-p1 to online
-+AS_BOX([Set the service monitor for sw0-p1 and sw1-p1 to online])
- ovn-sbctl set service_monitor $sm_sw0_p1 status=online
- ovn-sbctl set service_monitor $sm_sw1_p1 status=online
-
- wait_row_count Service_Monitor 1 logical_port=sw1-p1 status=online
-+check ovn-nbctl --wait=sb sync
-
- AT_CAPTURE_FILE([sbflows7])
- OVS_WAIT_FOR_OUTPUT(
- [ovn-sbctl dump-flows sw0 | tee sbflows7 | grep ct_lb | grep priority=120 | sed 's/table=..//'], 0,
--[ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
-+[ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
- ])
-
--# Set the service monitor for sw1-p1 to error
-+AS_BOX([Set the service monitor for sw1-p1 to error])
- ovn-sbctl set service_monitor $sm_sw1_p1 status=error
- wait_row_count Service_Monitor 1 logical_port=sw1-p1 status=error
-+check ovn-nbctl --wait=sb sync
-
- ovn-sbctl dump-flows sw0 | grep "ip4.dst == 10.0.0.10 && tcp.dst == 80" \
- | grep priority=120 > lflows.txt
- AT_CHECK([cat lflows.txt | sed 's/table=..//'], [0], [dnl
-- (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80);)
-+ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80);)
- ])
-
--# Add one more vip to lb1
-+AS_BOX([Add one more vip to lb1])
- check ovn-nbctl set load_balancer . vip:10.0.0.40\\:1000=10.0.0.3:1000,20.0.0.3:80
-
--# create health_check for new vip - 10.0.0.40
-+AS_BOX([create health_check for new vip - 10.0.0.40])
- AT_CHECK(
- [ovn-nbctl --wait=sb \
- -- --id=@hc create Load_Balancer_Health_Check vip=10.0.0.40\\:1000 \
-@@ -1176,34 +1187,35 @@ AT_CAPTURE_FILE([sbflows9])
- OVS_WAIT_FOR_OUTPUT(
- [ovn-sbctl dump-flows sw0 | tee sbflows9 | grep ct_lb | grep priority=120 | sed 's/table=..//' | sort],
- 0,
--[ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80);)
-- (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(backends=10.0.0.3:1000);)
-+[ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80);)
-+ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(reg1 = 10.0.0.40; reg2[[0..15]] = 1000; ct_lb(backends=10.0.0.3:1000);)
- ])
-
--# Set the service monitor for sw1-p1 to online
-+AS_BOX([Set the service monitor for sw1-p1 to online])
- check ovn-sbctl set service_monitor sw1-p1 status=online
-
- wait_row_count Service_Monitor 1 logical_port=sw1-p1 status=online
-+check ovn-nbctl --wait=sb sync
-
- AT_CAPTURE_FILE([sbflows10])
- OVS_WAIT_FOR_OUTPUT(
- [ovn-sbctl dump-flows sw0 | tee sbflows10 | grep ct_lb | grep priority=120 | sed 's/table=..//' | sort],
- 0,
--[ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
-- (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(backends=10.0.0.3:1000,20.0.0.3:80);)
-+[ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
-+ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(reg1 = 10.0.0.40; reg2[[0..15]] = 1000; ct_lb(backends=10.0.0.3:1000,20.0.0.3:80);)
- ])
-
--# Associate lb1 to sw1
-+AS_BOX([Associate lb1 to sw1])
- check ovn-nbctl --wait=sb ls-lb-add sw1 lb1
- AT_CAPTURE_FILE([sbflows11])
- OVS_WAIT_FOR_OUTPUT(
- [ovn-sbctl dump-flows sw1 | tee sbflows11 | grep ct_lb | grep priority=120 | sed 's/table=..//' | sort],
- 0, [dnl
-- (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
-- (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(ct_lb(backends=10.0.0.3:1000,20.0.0.3:80);)
-+ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
-+ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(reg1 = 10.0.0.40; reg2[[0..15]] = 1000; ct_lb(backends=10.0.0.3:1000,20.0.0.3:80);)
- ])
-
--# Now create lb2 same as lb1 but udp protocol.
-+AS_BOX([Now create lb2 same as lb1 but udp protocol.])
- check ovn-nbctl lb-add lb2 10.0.0.10:80 10.0.0.3:80,20.0.0.3:80 udp
- check ovn-nbctl --wait=sb set load_balancer lb2 ip_port_mappings:10.0.0.3=sw0-p1:10.0.0.2
- check ovn-nbctl --wait=sb set load_balancer lb2 ip_port_mappings:20.0.0.3=sw1-p1:20.0.0.2
-@@ -1214,15 +1226,17 @@ AT_CHECK([ovn-nbctl -- --id=@hc create Load_Balancer_Health_Check vip="10.0.0.10
-
- check ovn-nbctl ls-lb-add sw0 lb2
- check ovn-nbctl ls-lb-add sw1 lb2
-+check ovn-nbctl --wait=sb sync
-
- wait_row_count Service_Monitor 5
-
--# Change the svc_monitor_mac. This should get reflected in service_monitor table rows.
-+AS_BOX([Change the svc_monitor_mac.])
-+# This should get reflected in service_monitor table rows.
- check ovn-nbctl set NB_Global . options:svc_monitor_mac="fe:a0:65:a2:01:03"
-
- wait_row_count Service_Monitor 5 src_mac='"fe:a0:65:a2:01:03"'
-
--# Change the source ip for 10.0.0.3 backend ip in lb2
-+AS_BOX([Change the source ip for 10.0.0.3 backend ip in lb2])
- check ovn-nbctl --wait=sb set load_balancer lb2 ip_port_mappings:10.0.0.3=sw0-p1:10.0.0.100
-
- wait_row_count Service_Monitor 1 logical_port=sw0-p1 src_ip=10.0.0.100
-@@ -1233,6 +1247,31 @@ wait_row_count Service_Monitor 2
- ovn-nbctl --wait=sb lb-del lb2
- wait_row_count Service_Monitor 0
-
-+check ovn-nbctl --reject lb-add lb3 10.0.0.10:80 10.0.0.3:80,20.0.0.3:80
-+check ovn-nbctl --wait=sb set load_balancer lb3 ip_port_mappings:10.0.0.3=sw0-p1:10.0.0.2
-+check ovn-nbctl --wait=sb set load_balancer lb3 ip_port_mappings:20.0.0.3=sw1-p1:20.0.0.2
-+wait_row_count Service_Monitor 0
-+
-+check ovn-nbctl --wait=sb ls-lb-add sw0 lb3
-+AT_CHECK([ovn-nbctl --wait=sb -- --id=@hc create \
-+Load_Balancer_Health_Check vip="10.0.0.10\:80" -- add Load_Balancer lb3 \
-+health_check @hc | uuidfilt], [0], [<0>
-+])
-+wait_row_count Service_Monitor 2
-+
-+# Set the service monitor for sw0-p1 and sw1-p1 to online
-+sm_sw0_p1=$(fetch_column Service_Monitor _uuid logical_port=sw0-p1)
-+sm_sw1_p1=$(fetch_column Service_Monitor _uuid logical_port=sw1-p1)
-+
-+ovn-sbctl set service_monitor $sm_sw0_p1 status=offline
-+ovn-sbctl set service_monitor $sm_sw1_p1 status=offline
-+
-+AT_CAPTURE_FILE([sbflows12])
-+OVS_WAIT_FOR_OUTPUT(
-+ [ovn-sbctl dump-flows sw0 | tee sbflows12 | grep "ip4.dst == 10.0.0.10 && tcp.dst == 80" | grep priority=120 | sed 's/table=..//'], [0], [dnl
-+ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg0 = 0; reject { outport <-> inport; next(pipeline=egress,table=6);};)
-+])
-+
- AT_CLEANUP
-
- AT_SETUP([ovn -- Load balancer VIP in NAT entries])
-@@ -1704,7 +1743,7 @@ check ovn-nbctl pg-add pg0 sw0-p1 sw1-p1
- check ovn-nbctl acl-add pg0 from-lport 1002 "inport == @pg0 && ip4 && tcp && tcp.dst == 80" reject
- check ovn-nbctl acl-add pg0 to-lport 1003 "outport == @pg0 && ip6 && udp" reject
-
--check ovn-nbctl --wait=hv sync
-+check ovn-nbctl --wait=sb sync
-
- AS_BOX([1])
-
-@@ -1713,28 +1752,12 @@ AT_CAPTURE_FILE([sw0flows])
- ovn-sbctl dump-flows sw1 > sw1flows
- AT_CAPTURE_FILE([sw1flows])
-
--AT_CHECK([grep "ls_in_acl" sw0flows | grep pg0 | sort], [0], [dnl
-- table=7 (ls_in_acl ), priority=2002 , dnl
--match=(inport == @pg0 && ip4 && tcp && tcp.dst == 80), dnl
--action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=egress,table=6); };)
--])
--
--AT_CHECK([grep "ls_in_acl" sw1flows | grep pg0 | sort], [0], [dnl
-- table=7 (ls_in_acl ), priority=2002 , dnl
--match=(inport == @pg0 && ip4 && tcp && tcp.dst == 80), dnl
--action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=egress,table=6); };)
--])
--
--AT_CHECK([grep "ls_out_acl" sw0flows | grep pg0 | sort], [0], [dnl
-- table=5 (ls_out_acl ), priority=2003 , dnl
--match=(outport == @pg0 && ip6 && udp), dnl
--action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
--])
--
--AT_CHECK([grep "ls_out_acl" sw1flows | grep pg0 | sort], [0], [dnl
-- table=5 (ls_out_acl ), priority=2003 , dnl
--match=(outport == @pg0 && ip6 && udp), dnl
--action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
-+AT_CHECK(
-+ [grep -E 'ls_(in|out)_acl' sw0flows sw1flows | grep pg0 | sort], [0], [dnl
-+sw0flows: table=5 (ls_out_acl ), priority=2003 , match=(outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
-+sw0flows: table=9 (ls_in_acl ), priority=2002 , match=(inport == @pg0 && ip4 && tcp && tcp.dst == 80), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=egress,table=6); };)
-+sw1flows: table=5 (ls_out_acl ), priority=2003 , match=(outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
-+sw1flows: table=9 (ls_in_acl ), priority=2002 , match=(inport == @pg0 && ip4 && tcp && tcp.dst == 80), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=egress,table=6); };)
- ])
-
- AS_BOX([2])
-@@ -1746,22 +1769,11 @@ AT_CAPTURE_FILE([sw0flows2])
- ovn-sbctl dump-flows sw1 > sw1flows2
- AT_CAPTURE_FILE([sw1flows2])
-
--AT_CHECK([grep "ls_out_acl" sw0flows2 | grep pg0 | sort], [0], [dnl
-- table=5 (ls_out_acl ), priority=2002 , dnl
--match=(outport == @pg0 && ip4 && udp), dnl
--action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
-- table=5 (ls_out_acl ), priority=2003 , dnl
--match=(outport == @pg0 && ip6 && udp), dnl
--action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
--])
--
--AT_CHECK([grep "ls_out_acl" sw1flows2 | grep pg0 | sort], [0], [dnl
-- table=5 (ls_out_acl ), priority=2002 , dnl
--match=(outport == @pg0 && ip4 && udp), dnl
--action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
-- table=5 (ls_out_acl ), priority=2003 , dnl
--match=(outport == @pg0 && ip6 && udp), dnl
--action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
-+AT_CHECK([grep "ls_out_acl" sw0flows2 sw1flows2 | grep pg0 | sort], [0], [dnl
-+sw0flows2: table=5 (ls_out_acl ), priority=2002 , match=(outport == @pg0 && ip4 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
-+sw0flows2: table=5 (ls_out_acl ), priority=2003 , match=(outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
-+sw1flows2: table=5 (ls_out_acl ), priority=2002 , match=(outport == @pg0 && ip4 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
-+sw1flows2: table=5 (ls_out_acl ), priority=2003 , match=(outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
- ])
-
- AS_BOX([3])
-@@ -1773,42 +1785,19 @@ AT_CAPTURE_FILE([sw0flows3])
- ovn-sbctl dump-flows sw1 > sw1flows3
- AT_CAPTURE_FILE([sw1flows3])
-
--AT_CHECK([grep "ls_out_acl" sw0flows3 | grep pg0 | sort], [0], [dnl
-- table=5 (ls_out_acl ), priority=2001 , dnl
--match=(reg0[[7]] == 1 && (outport == @pg0 && ip)), action=(reg0[[1]] = 1; next;)
-- table=5 (ls_out_acl ), priority=2001 , dnl
--match=(reg0[[8]] == 1 && (outport == @pg0 && ip)), action=(next;)
-- table=5 (ls_out_acl ), priority=2002 , dnl
--match=((reg0[[10]] == 1) && outport == @pg0 && ip4 && udp), dnl
--action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
-- table=5 (ls_out_acl ), priority=2002 , dnl
--match=((reg0[[9]] == 1) && outport == @pg0 && ip4 && udp), dnl
--action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
-- table=5 (ls_out_acl ), priority=2003 , dnl
--match=((reg0[[10]] == 1) && outport == @pg0 && ip6 && udp), dnl
--action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
-- table=5 (ls_out_acl ), priority=2003 , dnl
--match=((reg0[[9]] == 1) && outport == @pg0 && ip6 && udp), dnl
--action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
--])
--
--AT_CHECK([grep "ls_out_acl" sw1flows3 | grep pg0 | sort], [0], [dnl
-- table=5 (ls_out_acl ), priority=2001 , dnl
--match=(reg0[[7]] == 1 && (outport == @pg0 && ip)), action=(reg0[[1]] = 1; next;)
-- table=5 (ls_out_acl ), priority=2001 , dnl
--match=(reg0[[8]] == 1 && (outport == @pg0 && ip)), action=(next;)
-- table=5 (ls_out_acl ), priority=2002 , dnl
--match=((reg0[[10]] == 1) && outport == @pg0 && ip4 && udp), dnl
--action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
-- table=5 (ls_out_acl ), priority=2002 , dnl
--match=((reg0[[9]] == 1) && outport == @pg0 && ip4 && udp), dnl
--action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
-- table=5 (ls_out_acl ), priority=2003 , dnl
--match=((reg0[[10]] == 1) && outport == @pg0 && ip6 && udp), dnl
--action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
-- table=5 (ls_out_acl ), priority=2003 , dnl
--match=((reg0[[9]] == 1) && outport == @pg0 && ip6 && udp), dnl
--action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
-+AT_CHECK([grep "ls_out_acl" sw0flows3 sw1flows3 | grep pg0 | sort], [0], [dnl
-+sw0flows3: table=5 (ls_out_acl ), priority=2001 , match=(reg0[[7]] == 1 && (outport == @pg0 && ip)), action=(reg0[[1]] = 1; next;)
-+sw0flows3: table=5 (ls_out_acl ), priority=2001 , match=(reg0[[8]] == 1 && (outport == @pg0 && ip)), action=(next;)
-+sw0flows3: table=5 (ls_out_acl ), priority=2002 , match=((reg0[[10]] == 1) && outport == @pg0 && ip4 && udp), action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
-+sw0flows3: table=5 (ls_out_acl ), priority=2002 , match=((reg0[[9]] == 1) && outport == @pg0 && ip4 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
-+sw0flows3: table=5 (ls_out_acl ), priority=2003 , match=((reg0[[10]] == 1) && outport == @pg0 && ip6 && udp), action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
-+sw0flows3: table=5 (ls_out_acl ), priority=2003 , match=((reg0[[9]] == 1) && outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
-+sw1flows3: table=5 (ls_out_acl ), priority=2001 , match=(reg0[[7]] == 1 && (outport == @pg0 && ip)), action=(reg0[[1]] = 1; next;)
-+sw1flows3: table=5 (ls_out_acl ), priority=2001 , match=(reg0[[8]] == 1 && (outport == @pg0 && ip)), action=(next;)
-+sw1flows3: table=5 (ls_out_acl ), priority=2002 , match=((reg0[[10]] == 1) && outport == @pg0 && ip4 && udp), action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
-+sw1flows3: table=5 (ls_out_acl ), priority=2002 , match=((reg0[[9]] == 1) && outport == @pg0 && ip4 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
-+sw1flows3: table=5 (ls_out_acl ), priority=2003 , match=((reg0[[10]] == 1) && outport == @pg0 && ip6 && udp), action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
-+sw1flows3: table=5 (ls_out_acl ), priority=2003 , match=((reg0[[9]] == 1) && outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
- ])
-
- AT_CLEANUP
-@@ -1818,20 +1807,25 @@ AT_KEYWORDS([acl log meter fair])
- ovn_start
-
- check ovn-nbctl ls-add sw0
-+check ovn-nbctl ls-add sw1
- check ovn-nbctl lsp-add sw0 sw0-p1 -- lsp-set-addresses sw0-p1 "50:54:00:00:00:01 10.0.0.11"
- check ovn-nbctl lsp-add sw0 sw0-p2 -- lsp-set-addresses sw0-p2 "50:54:00:00:00:02 10.0.0.12"
--check ovn-nbctl lsp-add sw0 sw0-p3 -- lsp-set-addresses sw0-p3 "50:54:00:00:00:03 10.0.0.13"
-+check ovn-nbctl lsp-add sw1 sw1-p3 -- lsp-set-addresses sw1-p3 "50:54:00:00:00:03 10.0.0.13"
-+check ovn-nbctl pg-add pg0 sw0-p1 sw0-p2 sw1-p3
-
- check ovn-nbctl meter-add meter_me drop 1 pktps
- nb_meter_uuid=$(fetch_column nb:Meter _uuid name=meter_me)
-
- check ovn-nbctl acl-add sw0 to-lport 1002 'outport == "sw0-p1" && ip4.src == 10.0.0.12' allow
- check ovn-nbctl acl-add sw0 to-lport 1002 'outport == "sw0-p1" && ip4.src == 10.0.0.13' allow
-+check ovn-nbctl acl-add pg0 to-lport 1002 'outport == "pg0" && ip4.src == 10.0.0.11' drop
-
- acl1=$(ovn-nbctl --bare --column _uuid,match find acl | grep -B1 '10.0.0.12' | head -1)
- acl2=$(ovn-nbctl --bare --column _uuid,match find acl | grep -B1 '10.0.0.13' | head -1)
-+acl3=$(ovn-nbctl --bare --column _uuid,match find acl | grep -B1 '10.0.0.11' | head -1)
- check ovn-nbctl set acl $acl1 log=true severity=alert meter=meter_me name=acl_one
- check ovn-nbctl set acl $acl2 log=true severity=info meter=meter_me name=acl_two
-+check ovn-nbctl set acl $acl3 log=true severity=info meter=meter_me name=acl_three
- check ovn-nbctl --wait=sb sync
-
- check_row_count nb:meter 1
-@@ -1840,8 +1834,9 @@ check_column meter_me nb:meter name
- check_acl_lflow() {
- acl_log_name=$1
- meter_name=$2
-+ ls=$3
- # echo checking that logical flow for acl log $acl_log_name has $meter_name
-- AT_CHECK([ovn-sbctl lflow-list | grep ls_out_acl | \
-+ AT_CHECK([ovn-sbctl lflow-list $ls | grep ls_out_acl | \
- grep "\"${acl_log_name}\"" | \
- grep -c "meter=\"${meter_name}\""], [0], [1
- ])
-@@ -1857,59 +1852,144 @@ check_meter_by_name() {
-
- # Make sure 'fair' value properly affects the Meters in SB
- check_meter_by_name meter_me
--check_meter_by_name NOT meter_me__${acl1} meter_me__${acl2}
-+check_meter_by_name NOT meter_me__${acl1} meter_me__${acl2} meter_me__${acl3}
-
- check ovn-nbctl --wait=sb set Meter $nb_meter_uuid fair=true
--check_meter_by_name meter_me meter_me__${acl1} meter_me__${acl2}
-+check_meter_by_name meter_me meter_me__${acl1} meter_me__${acl2} meter_me__${acl3}
-
- check ovn-nbctl --wait=sb set Meter $nb_meter_uuid fair=false
- check_meter_by_name meter_me
--check_meter_by_name NOT meter_me__${acl1} meter_me__${acl2}
-+check_meter_by_name NOT meter_me__${acl1} meter_me__${acl2} meter_me__${acl3}
-
- check ovn-nbctl --wait=sb set Meter $nb_meter_uuid fair=true
--check_meter_by_name meter_me meter_me__${acl1} meter_me__${acl2}
-+check_meter_by_name meter_me meter_me__${acl1} meter_me__${acl2} meter_me__${acl3}
-
- # Change template meter and make sure that is reflected on acl meters as well
- template_band=$(fetch_column nb:meter bands name=meter_me)
- check ovn-nbctl --wait=sb set meter_band $template_band rate=123
- # Make sure that every Meter_Band has the right rate. (ovn-northd
--# creates 3 identical Meter_Band rows, all identical; ovn-northd-ddlog
-+# creates 4 identical Meter_Band rows, all identical; ovn-northd-ddlog
- # creates just 1. It doesn't matter, they work just as well.)
- n_meter_bands=$(count_rows meter_band)
--AT_FAIL_IF([test "$n_meter_bands" != 1 && test "$n_meter_bands" != 3])
-+AT_FAIL_IF([test "$n_meter_bands" != 1 && test "$n_meter_bands" != 4])
- check_row_count meter_band $n_meter_bands rate=123
-
- # Check meter in logical flows for acl logs
--check_acl_lflow acl_one meter_me__${acl1}
--check_acl_lflow acl_two meter_me__${acl2}
-+check_acl_lflow acl_one meter_me__${acl1} sw0
-+check_acl_lflow acl_two meter_me__${acl2} sw0
-+check_acl_lflow acl_three meter_me__${acl3} sw0
-+check_acl_lflow acl_three meter_me__${acl3} sw1
-
- # Stop using meter for acl1
- check ovn-nbctl --wait=sb clear acl $acl1 meter
- check_meter_by_name meter_me meter_me__${acl2}
- check_meter_by_name NOT meter_me__${acl1}
--check_acl_lflow acl_two meter_me__${acl2}
-+check_acl_lflow acl_two meter_me__${acl2} sw0
-+check_acl_lflow acl_three meter_me__${acl3} sw0
-+check_acl_lflow acl_three meter_me__${acl3} sw1
-
- # Remove template Meter should remove all others as well
- check ovn-nbctl --wait=sb meter-del meter_me
- check_row_count meter 0
- # Check that logical flow remains but uses non-unique meter since fair
- # attribute is lost by the removal of the Meter row.
--check_acl_lflow acl_two meter_me
-+check_acl_lflow acl_two meter_me sw0
-+check_acl_lflow acl_three meter_me sw0
-+check_acl_lflow acl_three meter_me sw1
-
- # Re-add template meter and make sure acl2's meter is back in sb
- check ovn-nbctl --wait=sb --fair meter-add meter_me drop 1 pktps
- check_meter_by_name meter_me meter_me__${acl2}
- check_meter_by_name NOT meter_me__${acl1}
--check_acl_lflow acl_two meter_me__${acl2}
-+check_acl_lflow acl_two meter_me__${acl2} sw0
-+check_acl_lflow acl_three meter_me__${acl3} sw0
-+check_acl_lflow acl_three meter_me__${acl3} sw1
-
- # Remove acl2
- sw0=$(fetch_column nb:logical_switch _uuid name=sw0)
- check ovn-nbctl --wait=sb remove logical_switch $sw0 acls $acl2
--check_meter_by_name meter_me
-+check_meter_by_name meter_me meter_me__${acl3}
- check_meter_by_name NOT meter_me__${acl1} meter_me__${acl2}
-
- AT_CLEANUP
-
-+AT_SETUP([ovn -- ACL skip hints for stateless config])
-+AT_KEYWORDS([acl])
-+ovn_start
-+
-+check ovn-nbctl --wait=sb \
-+ -- ls-add ls \
-+ -- lsp-add ls lsp \
-+ -- acl-add ls from-lport 1 "ip" allow \
-+ -- acl-add ls to-lport 1 "ip" allow
-+
-+AS_BOX([Check no match on ct_state with stateless ACLs])
-+AT_CHECK([ovn-sbctl lflow-list ls | grep -e ls_in_acl_hint -e ls_out_acl_hint -e ls_in_acl -e ls_out_acl | grep 'ct\.' | sort], [0], [dnl
-+])
-+
-+AS_BOX([Check match ct_state with stateful ACLs])
-+check ovn-nbctl --wait=sb \
-+ -- acl-add ls from-lport 2 "udp" allow-related \
-+ -- acl-add ls to-lport 2 "udp" allow-related
-+AT_CHECK([ovn-sbctl lflow-list ls | grep -e ls_in_acl_hint -e ls_out_acl_hint -e ls_in_acl -e ls_out_acl | grep 'ct\.' | sort], [0], [dnl
-+ table=4 (ls_out_acl_hint ), priority=1 , match=(ct.est && ct_label.blocked == 0), action=(reg0[[10]] = 1; next;)
-+ table=4 (ls_out_acl_hint ), priority=2 , match=(ct.est && ct_label.blocked == 1), action=(reg0[[9]] = 1; next;)
-+ table=4 (ls_out_acl_hint ), priority=3 , match=(!ct.est), action=(reg0[[9]] = 1; next;)
-+ table=4 (ls_out_acl_hint ), priority=4 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 0), action=(reg0[[8]] = 1; reg0[[10]] = 1; next;)
-+ table=4 (ls_out_acl_hint ), priority=5 , match=(!ct.trk), action=(reg0[[8]] = 1; reg0[[9]] = 1; next;)
-+ table=4 (ls_out_acl_hint ), priority=6 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 1), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;)
-+ table=4 (ls_out_acl_hint ), priority=7 , match=(ct.new && !ct.est), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;)
-+ table=5 (ls_out_acl ), priority=1 , match=(ip && (!ct.est || (ct.est && ct_label.blocked == 1))), action=(reg0[[1]] = 1; next;)
-+ table=5 (ls_out_acl ), priority=65535, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;)
-+ table=5 (ls_out_acl ), priority=65535, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;)
-+ table=5 (ls_out_acl ), priority=65535, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;)
-+ table=8 (ls_in_acl_hint ), priority=1 , match=(ct.est && ct_label.blocked == 0), action=(reg0[[10]] = 1; next;)
-+ table=8 (ls_in_acl_hint ), priority=2 , match=(ct.est && ct_label.blocked == 1), action=(reg0[[9]] = 1; next;)
-+ table=8 (ls_in_acl_hint ), priority=3 , match=(!ct.est), action=(reg0[[9]] = 1; next;)
-+ table=8 (ls_in_acl_hint ), priority=4 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 0), action=(reg0[[8]] = 1; reg0[[10]] = 1; next;)
-+ table=8 (ls_in_acl_hint ), priority=5 , match=(!ct.trk), action=(reg0[[8]] = 1; reg0[[9]] = 1; next;)
-+ table=8 (ls_in_acl_hint ), priority=6 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 1), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;)
-+ table=8 (ls_in_acl_hint ), priority=7 , match=(ct.new && !ct.est), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;)
-+ table=9 (ls_in_acl ), priority=1 , match=(ip && (!ct.est || (ct.est && ct_label.blocked == 1))), action=(reg0[[1]] = 1; next;)
-+ table=9 (ls_in_acl ), priority=65535, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;)
-+ table=9 (ls_in_acl ), priority=65535, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;)
-+ table=9 (ls_in_acl ), priority=65535, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;)
-+])
-+
-+AS_BOX([Check match ct_state with load balancer])
-+check ovn-nbctl --wait=sb \
-+ -- acl-del ls from-lport 2 "udp" \
-+ -- acl-del ls to-lport 2 "udp" \
-+ -- lb-add lb "10.0.0.1" "10.0.0.2" \
-+ -- ls-lb-add ls lb
-+
-+AT_CHECK([ovn-sbctl lflow-list ls | grep -e ls_in_acl_hint -e ls_out_acl_hint -e ls_in_acl -e ls_out_acl | grep 'ct\.' | sort], [0], [dnl
-+ table=4 (ls_out_acl_hint ), priority=1 , match=(ct.est && ct_label.blocked == 0), action=(reg0[[10]] = 1; next;)
-+ table=4 (ls_out_acl_hint ), priority=2 , match=(ct.est && ct_label.blocked == 1), action=(reg0[[9]] = 1; next;)
-+ table=4 (ls_out_acl_hint ), priority=3 , match=(!ct.est), action=(reg0[[9]] = 1; next;)
-+ table=4 (ls_out_acl_hint ), priority=4 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 0), action=(reg0[[8]] = 1; reg0[[10]] = 1; next;)
-+ table=4 (ls_out_acl_hint ), priority=5 , match=(!ct.trk), action=(reg0[[8]] = 1; reg0[[9]] = 1; next;)
-+ table=4 (ls_out_acl_hint ), priority=6 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 1), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;)
-+ table=4 (ls_out_acl_hint ), priority=7 , match=(ct.new && !ct.est), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;)
-+ table=5 (ls_out_acl ), priority=1 , match=(ip && (!ct.est || (ct.est && ct_label.blocked == 1))), action=(reg0[[1]] = 1; next;)
-+ table=5 (ls_out_acl ), priority=65535, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;)
-+ table=5 (ls_out_acl ), priority=65535, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;)
-+ table=5 (ls_out_acl ), priority=65535, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;)
-+ table=8 (ls_in_acl_hint ), priority=1 , match=(ct.est && ct_label.blocked == 0), action=(reg0[[10]] = 1; next;)
-+ table=8 (ls_in_acl_hint ), priority=2 , match=(ct.est && ct_label.blocked == 1), action=(reg0[[9]] = 1; next;)
-+ table=8 (ls_in_acl_hint ), priority=3 , match=(!ct.est), action=(reg0[[9]] = 1; next;)
-+ table=8 (ls_in_acl_hint ), priority=4 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 0), action=(reg0[[8]] = 1; reg0[[10]] = 1; next;)
-+ table=8 (ls_in_acl_hint ), priority=5 , match=(!ct.trk), action=(reg0[[8]] = 1; reg0[[9]] = 1; next;)
-+ table=8 (ls_in_acl_hint ), priority=6 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 1), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;)
-+ table=8 (ls_in_acl_hint ), priority=7 , match=(ct.new && !ct.est), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;)
-+ table=9 (ls_in_acl ), priority=1 , match=(ip && (!ct.est || (ct.est && ct_label.blocked == 1))), action=(reg0[[1]] = 1; next;)
-+ table=9 (ls_in_acl ), priority=65535, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;)
-+ table=9 (ls_in_acl ), priority=65535, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;)
-+ table=9 (ls_in_acl ), priority=65535, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;)
-+])
-+
-+AT_CLEANUP
-+
- AT_SETUP([datapath requested-tnl-key])
- AT_KEYWORDS([requested tnl tunnel key keys])
- ovn_start
-@@ -2092,6 +2172,12 @@ echo
- echo "__file__:__line__: check that datapath sw1 has lb0 and lb1 set in the load_balancers column."
- check_column "$lb0_uuid $lb1_uuid" sb:datapath_binding load_balancers external_ids:name=sw1
-
-+
-+echo
-+echo "__file__:__line__: Set hairpin_snat_ip on lb1 and check that SB DB is updated."
-+check ovn-nbctl --wait=sb set Load_Balancer lb1 options:hairpin_snat_ip="42.42.42.42 4242::4242"
-+check_column "$lb1_uuid" sb:load_balancer _uuid name=lb1 options='{hairpin_orig_tuple="true", hairpin_snat_ip="42.42.42.42 4242::4242"}'
-+
- echo
- echo "__file__:__line__: Delete load balancer lb1 an check that datapath sw1's load_balancers are updated accordingly."
-
-@@ -2100,6 +2186,35 @@ check_column "$lb0_uuid" sb:datapath_binding load_balancers external_ids:name=sw
-
- AT_CLEANUP
-
-+AT_SETUP([ovn -- LS load balancer hairpin logical flows])
-+ovn_start
-+
-+check ovn-nbctl \
-+ -- ls-add sw0 \
-+ -- lb-add lb0 10.0.0.10:80 10.0.0.4:8080 \
-+ -- ls-lb-add sw0 lb0
-+
-+check ovn-nbctl --wait=sb sync
-+
-+AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_pre_hairpin | sort], [0], [dnl
-+ table=14(ls_in_pre_hairpin ), priority=0 , match=(1), action=(next;)
-+ table=14(ls_in_pre_hairpin ), priority=100 , match=(ip && ct.trk), action=(reg0[[6]] = chk_lb_hairpin(); reg0[[12]] = chk_lb_hairpin_reply(); next;)
-+])
-+
-+AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_nat_hairpin | sort], [0], [dnl
-+ table=15(ls_in_nat_hairpin ), priority=0 , match=(1), action=(next;)
-+ table=15(ls_in_nat_hairpin ), priority=100 , match=(ip && ct.est && ct.trk && reg0[[6]] == 1), action=(ct_snat;)
-+ table=15(ls_in_nat_hairpin ), priority=100 , match=(ip && ct.new && ct.trk && reg0[[6]] == 1), action=(ct_snat_to_vip; next;)
-+ table=15(ls_in_nat_hairpin ), priority=90 , match=(ip && reg0[[12]] == 1), action=(ct_snat;)
-+])
-+
-+AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_hairpin | sort], [0], [dnl
-+ table=16(ls_in_hairpin ), priority=0 , match=(1), action=(next;)
-+ table=16(ls_in_hairpin ), priority=1 , match=((reg0[[6]] == 1 || reg0[[12]] == 1)), action=(eth.dst <-> eth.src; outport = inport; flags.loopback = 1; output;)
-+])
-+
-+AT_CLEANUP
-+
- AT_SETUP([ovn -- logical gatapath groups])
- AT_KEYWORDS([use_logical_dp_groups])
- ovn_start
-@@ -2173,3 +2288,498 @@ dnl Number of common flows should be the same.
- check_row_count Logical_Flow ${n_flows_common} logical_dp_group=${dp_group_uuid}
-
- AT_CLEANUP
-+
-+AT_SETUP([ovn -- Router policies - ECMP reroute])
-+AT_KEYWORDS([router policies ecmp reroute])
-+ovn_start
-+
-+check ovn-nbctl ls-add sw0
-+check ovn-nbctl lsp-add sw0 sw0-port1
-+check ovn-nbctl lsp-set-addresses sw0-port1 "50:54:00:00:00:03 10.0.0.3"
-+
-+check ovn-nbctl ls-add sw1
-+check ovn-nbctl lsp-add sw1 sw1-port1
-+check ovn-nbctl lsp-set-addresses sw1-port1 "40:54:00:00:00:03 20.0.0.3"
-+
-+# Create a logical router and attach both logical switches
-+check ovn-nbctl lr-add lr0
-+check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24 1000::a/64
-+check ovn-nbctl lsp-add sw0 sw0-lr0
-+check ovn-nbctl lsp-set-type sw0-lr0 router
-+check ovn-nbctl lsp-set-addresses sw0-lr0 00:00:00:00:ff:01
-+check ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
-+
-+check ovn-nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 20.0.0.1/24 2000::a/64
-+check ovn-nbctl lsp-add sw1 sw1-lr0
-+check ovn-nbctl lsp-set-type sw1-lr0 router
-+check ovn-nbctl lsp-set-addresses sw1-lr0 00:00:00:00:ff:02
-+check ovn-nbctl lsp-set-options sw1-lr0 router-port=lr-sw1
-+
-+check ovn-nbctl ls-add public
-+check ovn-nbctl lrp-add lr0 lr0-public 00:00:20:20:12:13 172.168.0.100/24
-+check ovn-nbctl lsp-add public public-lr0
-+check ovn-nbctl lsp-set-type public-lr0 router
-+check ovn-nbctl lsp-set-addresses public-lr0 router
-+check ovn-nbctl lsp-set-options public-lr0 router-port=lr0-public
-+
-+check ovn-nbctl --wait=sb lr-policy-add lr0 10 "ip4.src == 10.0.0.3" reroute 172.168.0.101,172.168.0.102
-+
-+ovn-sbctl dump-flows lr0 > lr0flows3
-+AT_CAPTURE_FILE([lr0flows3])
-+
-+AT_CHECK([grep "lr_in_policy" lr0flows3 | sort], [0], [dnl
-+ table=12(lr_in_policy ), priority=0 , match=(1), action=(reg8[[0..15]] = 0; next;)
-+ table=12(lr_in_policy ), priority=10 , match=(ip4.src == 10.0.0.3), action=(reg8[[0..15]] = 1; reg8[[16..31]] = select(1, 2);)
-+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == 1 && reg8[[16..31]] == 1), action=(reg0 = 172.168.0.101; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;)
-+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == 1 && reg8[[16..31]] == 2), action=(reg0 = 172.168.0.102; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;)
-+ table=13(lr_in_policy_ecmp ), priority=150 , match=(reg8[[0..15]] == 0), action=(next;)
-+])
-+
-+check ovn-nbctl --wait=sb lr-policy-add lr0 10 "ip4.src == 10.0.0.4" reroute 172.168.0.101,172.168.0.102,172.168.0.103
-+ovn-sbctl dump-flows lr0 > lr0flows3
-+AT_CAPTURE_FILE([lr0flows3])
-+
-+AT_CHECK([grep "lr_in_policy" lr0flows3 | \
-+sed 's/reg8\[[0..15\]] = [[0-9]]*/reg8\[[0..15\]] = /' | \
-+sed 's/reg8\[[0..15\]] == [[0-9]]*/reg8\[[0..15\]] == /' | sort], [0], [dnl
-+ table=12(lr_in_policy ), priority=0 , match=(1), action=(reg8[[0..15]] = ; next;)
-+ table=12(lr_in_policy ), priority=10 , match=(ip4.src == 10.0.0.3), action=(reg8[[0..15]] = ; reg8[[16..31]] = select(1, 2);)
-+ table=12(lr_in_policy ), priority=10 , match=(ip4.src == 10.0.0.4), action=(reg8[[0..15]] = ; reg8[[16..31]] = select(1, 2, 3);)
-+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 1), action=(reg0 = 172.168.0.101; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;)
-+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 1), action=(reg0 = 172.168.0.101; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;)
-+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 2), action=(reg0 = 172.168.0.102; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;)
-+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 2), action=(reg0 = 172.168.0.102; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;)
-+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 3), action=(reg0 = 172.168.0.103; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;)
-+ table=13(lr_in_policy_ecmp ), priority=150 , match=(reg8[[0..15]] == ), action=(next;)
-+])
-+
-+check ovn-nbctl --wait=sb lr-policy-add lr0 10 "ip4.src == 10.0.0.5" reroute 172.168.0.110
-+ovn-sbctl dump-flows lr0 > lr0flows3
-+AT_CAPTURE_FILE([lr0flows3])
-+
-+AT_CHECK([grep "lr_in_policy" lr0flows3 | \
-+sed 's/reg8\[[0..15\]] = [[0-9]]*/reg8\[[0..15\]] = /' | \
-+sed 's/reg8\[[0..15\]] == [[0-9]]*/reg8\[[0..15\]] == /' | sort], [0], [dnl
-+ table=12(lr_in_policy ), priority=0 , match=(1), action=(reg8[[0..15]] = ; next;)
-+ table=12(lr_in_policy ), priority=10 , match=(ip4.src == 10.0.0.3), action=(reg8[[0..15]] = ; reg8[[16..31]] = select(1, 2);)
-+ table=12(lr_in_policy ), priority=10 , match=(ip4.src == 10.0.0.4), action=(reg8[[0..15]] = ; reg8[[16..31]] = select(1, 2, 3);)
-+ table=12(lr_in_policy ), priority=10 , match=(ip4.src == 10.0.0.5), action=(reg0 = 172.168.0.110; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; reg8[[0..15]] = ; next;)
-+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 1), action=(reg0 = 172.168.0.101; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;)
-+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 1), action=(reg0 = 172.168.0.101; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;)
-+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 2), action=(reg0 = 172.168.0.102; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;)
-+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 2), action=(reg0 = 172.168.0.102; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;)
-+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 3), action=(reg0 = 172.168.0.103; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;)
-+ table=13(lr_in_policy_ecmp ), priority=150 , match=(reg8[[0..15]] == ), action=(next;)
-+])
-+
-+check ovn-nbctl --wait=sb lr-policy-del lr0 10 "ip4.src == 10.0.0.3"
-+ovn-sbctl dump-flows lr0 > lr0flows3
-+AT_CAPTURE_FILE([lr0flows3])
-+
-+AT_CHECK([grep "lr_in_policy" lr0flows3 | \
-+sed 's/reg8\[[0..15\]] = [[0-9]]*/reg8\[[0..15\]] = /' | \
-+sed 's/reg8\[[0..15\]] == [[0-9]]*/reg8\[[0..15\]] == /' | sort], [0], [dnl
-+ table=12(lr_in_policy ), priority=0 , match=(1), action=(reg8[[0..15]] = ; next;)
-+ table=12(lr_in_policy ), priority=10 , match=(ip4.src == 10.0.0.4), action=(reg8[[0..15]] = ; reg8[[16..31]] = select(1, 2, 3);)
-+ table=12(lr_in_policy ), priority=10 , match=(ip4.src == 10.0.0.5), action=(reg0 = 172.168.0.110; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; reg8[[0..15]] = ; next;)
-+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 1), action=(reg0 = 172.168.0.101; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;)
-+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 2), action=(reg0 = 172.168.0.102; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;)
-+ table=13(lr_in_policy_ecmp ), priority=100 , match=(reg8[[0..15]] == && reg8[[16..31]] == 3), action=(reg0 = 172.168.0.103; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;)
-+ table=13(lr_in_policy_ecmp ), priority=150 , match=(reg8[[0..15]] == ), action=(next;)
-+])
-+
-+check ovn-nbctl --wait=sb lr-policy-del lr0 10 "ip4.src == 10.0.0.4"
-+ovn-sbctl dump-flows lr0 > lr0flows3
-+AT_CAPTURE_FILE([lr0flows3])
-+
-+AT_CHECK([grep "lr_in_policy" lr0flows3 | \
-+sed 's/reg8\[[0..15\]] = [[0-9]]*/reg8\[[0..15\]] = /' | \
-+sed 's/reg8\[[0..15\]] == [[0-9]]*/reg8\[[0..15\]] == /' | sort], [0], [dnl
-+ table=12(lr_in_policy ), priority=0 , match=(1), action=(reg8[[0..15]] = ; next;)
-+ table=12(lr_in_policy ), priority=10 , match=(ip4.src == 10.0.0.5), action=(reg0 = 172.168.0.110; reg1 = 172.168.0.100; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; reg8[[0..15]] = ; next;)
-+ table=13(lr_in_policy_ecmp ), priority=150 , match=(reg8[[0..15]] == ), action=(next;)
-+])
-+
-+check ovn-nbctl --wait=sb add logical_router_policy . nexthops "2000\:\:b"
-+ovn-sbctl dump-flows lr0 > lr0flows3
-+AT_CAPTURE_FILE([lr0flows3])
-+
-+AT_CHECK([grep "lr_in_policy" lr0flows3 | \
-+sed 's/reg8\[[0..15\]] = [[0-9]]*/reg8\[[0..15\]] = /' | \
-+sed 's/reg8\[[0..15\]] == [[0-9]]*/reg8\[[0..15\]] == /' | sort], [0], [dnl
-+ table=12(lr_in_policy ), priority=0 , match=(1), action=(reg8[[0..15]] = ; next;)
-+ table=13(lr_in_policy_ecmp ), priority=150 , match=(reg8[[0..15]] == ), action=(next;)
-+])
-+
-+AT_CLEANUP
-+
-+AT_SETUP([ovn -- check BFD config propagation to SBDB])
-+AT_KEYWORDS([northd-bfd])
-+ovn_start
-+
-+check ovn-nbctl --wait=sb lr-add r0
-+for i in $(seq 1 5); do
-+ check ovn-nbctl --wait=sb lrp-add r0 r0-sw$i 00:00:00:00:00:0$i 192.168.$i.1/24
-+ check ovn-nbctl --wait=sb ls-add sw$i
-+ check ovn-nbctl --wait=sb lsp-add sw$i sw$i-r0
-+ check ovn-nbctl --wait=sb lsp-set-type sw$i-r0 router
-+ check ovn-nbctl --wait=sb lsp-set-options sw$i-r0 router-port=r0-sw$i
-+ check ovn-nbctl --wait=sb lsp-set-addresses sw$i-r0 00:00:00:00:00:0$i
-+done
-+
-+uuid=$(ovn-nbctl create bfd logical_port=r0-sw1 dst_ip=192.168.10.2 status=down min_tx=250 min_rx=250 detect_mult=10)
-+ovn-nbctl create bfd logical_port=r0-sw2 dst_ip=192.168.20.2 status=down min_tx=500 min_rx=500 detect_mult=20
-+ovn-nbctl create bfd logical_port=r0-sw3 dst_ip=192.168.30.2 status=down
-+ovn-nbctl create bfd logical_port=r0-sw4 dst_ip=192.168.40.2 status=down min_tx=0 detect_mult=0
-+
-+check_column 10 bfd detect_mult logical_port=r0-sw1
-+check_column "192.168.10.2" bfd dst_ip logical_port=r0-sw1
-+check_column 250 bfd min_rx logical_port=r0-sw1
-+check_column 250 bfd min_tx logical_port=r0-sw1
-+check_column admin_down bfd status logical_port=r0-sw1
-+
-+check_column 20 bfd detect_mult logical_port=r0-sw2
-+check_column "192.168.20.2" bfd dst_ip logical_port=r0-sw2
-+check_column 500 bfd min_rx logical_port=r0-sw2
-+check_column 500 bfd min_tx logical_port=r0-sw2
-+check_column admin_down bfd status logical_port=r0-sw2
-+
-+check_column 5 bfd detect_mult logical_port=r0-sw3
-+check_column "192.168.30.2" bfd dst_ip logical_port=r0-sw3
-+check_column 1000 bfd min_rx logical_port=r0-sw3
-+check_column 1000 bfd min_tx logical_port=r0-sw3
-+check_column admin_down bfd status logical_port=r0-sw3
-+
-+uuid=$(fetch_column nb:bfd _uuid logical_port=r0-sw1)
-+check ovn-nbctl set bfd $uuid min_tx=1000
-+check ovn-nbctl set bfd $uuid min_rx=1000
-+check ovn-nbctl set bfd $uuid detect_mult=100
-+
-+uuid_2=$(fetch_column nb:bfd _uuid logical_port=r0-sw2)
-+check ovn-nbctl clear bfd $uuid_2 min_rx
-+check_column 1000 bfd min_rx logical_port=r0-sw2
-+
-+check_column 1000 bfd min_tx logical_port=r0-sw1
-+check_column 1000 bfd min_rx logical_port=r0-sw1
-+check_column 100 bfd detect_mult logical_port=r0-sw1
-+
-+check ovn-nbctl --bfd=$uuid lr-route-add r0 100.0.0.0/8 192.168.10.2
-+check_column down bfd status logical_port=r0-sw1
-+AT_CHECK([ovn-nbctl lr-route-list r0 | grep 192.168.10.2 | grep -q bfd],[0])
-+
-+check ovn-nbctl --bfd lr-route-add r0 200.0.0.0/8 192.168.20.2
-+check_column down bfd status logical_port=r0-sw2
-+AT_CHECK([ovn-nbctl lr-route-list r0 | grep 192.168.20.2 | grep -q bfd],[0])
-+
-+check ovn-nbctl --bfd lr-route-add r0 240.0.0.0/8 192.168.50.2 r0-sw5
-+check_column down bfd status logical_port=r0-sw5
-+AT_CHECK([ovn-nbctl lr-route-list r0 | grep 192.168.50.2 | grep -q bfd],[0])
-+
-+route_uuid=$(fetch_column nb:logical_router_static_route _uuid ip_prefix="100.0.0.0/8")
-+check ovn-nbctl clear logical_router_static_route $route_uuid bfd
-+check_column admin_down bfd status logical_port=r0-sw1
-+
-+ovn-nbctl destroy bfd $uuid
-+check_row_count bfd 3
-+
-+AT_CLEANUP
-+
-+AT_SETUP([ovn -- check LSP attached to multiple LS])
-+ovn_start
-+
-+check ovn-nbctl ls-add ls1 \
-+ -- ls-add ls2 \
-+ -- lsp-add ls1 p1
-+check ovn-nbctl --wait=sb sync
-+
-+uuid=$(fetch_column nb:Logical_Switch_Port _uuid name=p1)
-+check ovn-nbctl set Logical_Switch ls2 ports=$uuid
-+check ovn-nbctl --wait=sb sync
-+
-+AT_CHECK([grep -qE 'duplicate logical port p1' northd/ovn-northd.log], [0])
-+
-+AT_CLEANUP
-+
-+AT_SETUP([ovn -- check LRP attached to multiple LR])
-+ovn_start
-+
-+check ovn-nbctl lr-add lr1 \
-+ -- lr-add lr2 \
-+ -- lrp-add lr1 p1 00:00:00:00:00:01 10.0.0.1/24
-+check ovn-nbctl --wait=sb sync
-+
-+uuid=$(fetch_column nb:Logical_Router_Port _uuid name=p1)
-+check ovn-nbctl set Logical_Router lr2 ports=$uuid
-+check ovn-nbctl --wait=sb sync
-+
-+AT_CHECK([grep -qE 'duplicate logical router port p1' northd/ovn-northd.log], [0])
-+
-+AT_CLEANUP
-+
-+AT_SETUP([ovn -- check duplicate LSP/LRP])
-+ovn_start
-+
-+check ovn-nbctl ls-add ls \
-+ -- lsp-add ls p1 \
-+ -- lr-add lr \
-+ -- lrp-add lr p1 00:00:00:00:00:01 10.0.0.1/24
-+check ovn-nbctl --wait=sb sync
-+
-+AT_CHECK([grep -qE 'duplicate logical.*port p1' northd/ovn-northd.log], [0])
-+
-+AT_CLEANUP
-+
-+AT_SETUP([ovn -- Port_Binding.up backwards compatibility])
-+ovn_start
-+
-+ovn-nbctl ls-add ls1
-+ovn-nbctl --wait=sb lsp-add ls1 lsp1
-+
-+# Simulate the fact that lsp1 had been previously bound on hv1 by an
-+# ovn-controller running an older version.
-+ovn-sbctl \
-+ --id=@e create encap chassis_name=hv1 ip="192.168.0.1" type="geneve" \
-+ -- --id=@c create chassis name=hv1 encaps=@e \
-+ -- set Port_Binding lsp1 chassis=@c
-+
-+wait_for_ports_up lsp1
-+
-+# Simulate the fact that hv1 is aware of Port_Binding.up, ovn-northd
-+# should transition the port state to down.
-+check ovn-sbctl set chassis hv1 other_config:port-up-notif=true
-+wait_row_count nb:Logical_Switch_Port 1 up=false name=lsp1
-+
-+AT_CLEANUP
-+
-+AT_SETUP([ovn -- lb_force_snat_ip for Gateway Routers])
-+ovn_start
-+
-+check ovn-nbctl ls-add sw0
-+check ovn-nbctl ls-add sw1
-+
-+# Create a logical router and attach both logical switches
-+check ovn-nbctl lr-add lr0
-+check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24
-+check ovn-nbctl lsp-add sw0 sw0-lr0
-+check ovn-nbctl lsp-set-type sw0-lr0 router
-+check ovn-nbctl lsp-set-addresses sw0-lr0 00:00:00:00:ff:01
-+check ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
-+
-+check ovn-nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 20.0.0.1/24
-+check ovn-nbctl lsp-add sw1 sw1-lr0
-+check ovn-nbctl lsp-set-type sw1-lr0 router
-+check ovn-nbctl lsp-set-addresses sw1-lr0 00:00:00:00:ff:02
-+check ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1
-+
-+check ovn-nbctl ls-add public
-+check ovn-nbctl lrp-add lr0 lr0-public 00:00:20:20:12:13 172.168.0.100/24
-+check ovn-nbctl lsp-add public public-lr0
-+check ovn-nbctl lsp-set-type public-lr0 router
-+check ovn-nbctl lsp-set-addresses public-lr0 router
-+check ovn-nbctl lsp-set-options public-lr0 router-port=lr0-public
-+
-+check ovn-nbctl lb-add lb1 10.0.0.10:80 10.0.0.4:8080
-+check ovn-nbctl lr-lb-add lr0 lb1
-+check ovn-nbctl set logical_router lr0 options:chassis=ch1
-+
-+ovn-sbctl dump-flows lr0 > lr0flows
-+AT_CAPTURE_FILE([lr0flows])
-+
-+AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
-+ table=5 (lr_in_unsnat ), priority=0 , match=(1), action=(next;)
-+])
-+
-+AT_CHECK([grep "lr_in_dnat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl
-+])
-+
-+
-+AT_CHECK([grep "lr_out_snat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl
-+])
-+
-+check ovn-nbctl --wait=sb set logical_router lr0 options:lb_force_snat_ip="20.0.0.4 aef0::4"
-+
-+ovn-sbctl dump-flows lr0 > lr0flows
-+AT_CAPTURE_FILE([lr0flows])
-+
-+
-+AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
-+ table=5 (lr_in_unsnat ), priority=0 , match=(1), action=(next;)
-+ table=5 (lr_in_unsnat ), priority=110 , match=(ip4 && ip4.dst == 20.0.0.4), action=(ct_snat;)
-+ table=5 (lr_in_unsnat ), priority=110 , match=(ip6 && ip6.dst == aef0::4), action=(ct_snat;)
-+])
-+
-+AT_CHECK([grep "lr_in_dnat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl
-+ table=6 (lr_in_dnat ), priority=120 , match=(ct.est && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_dnat;)
-+ table=6 (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_lb(backends=10.0.0.4:8080);)
-+])
-+
-+AT_CHECK([grep "lr_out_snat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl
-+ table=1 (lr_out_snat ), priority=100 , match=(flags.force_snat_for_lb == 1 && ip4), action=(ct_snat(20.0.0.4);)
-+ table=1 (lr_out_snat ), priority=100 , match=(flags.force_snat_for_lb == 1 && ip6), action=(ct_snat(aef0::4);)
-+])
-+
-+check ovn-nbctl --wait=sb set logical_router lr0 options:lb_force_snat_ip="router_ip"
-+
-+ovn-sbctl dump-flows lr0 > lr0flows
-+AT_CAPTURE_FILE([lr0flows])
-+
-+AT_CHECK([grep "lr_in_ip_input" lr0flows | grep "priority=60" | sort], [0], [dnl
-+])
-+
-+AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
-+ table=5 (lr_in_unsnat ), priority=0 , match=(1), action=(next;)
-+ table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-public" && ip4.dst == 172.168.0.100), action=(ct_snat;)
-+ table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-sw0" && ip4.dst == 10.0.0.1), action=(ct_snat;)
-+ table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-sw1" && ip4.dst == 20.0.0.1), action=(ct_snat;)
-+])
-+
-+AT_CHECK([grep "lr_in_dnat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl
-+ table=6 (lr_in_dnat ), priority=120 , match=(ct.est && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_dnat;)
-+ table=6 (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_lb(backends=10.0.0.4:8080);)
-+])
-+
-+AT_CHECK([grep "lr_out_snat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl
-+ table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-public"), action=(ct_snat(172.168.0.100);)
-+ table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw0"), action=(ct_snat(10.0.0.1);)
-+ table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw1"), action=(ct_snat(20.0.0.1);)
-+])
-+
-+check ovn-nbctl --wait=sb remove logical_router lr0 options chassis
-+
-+ovn-sbctl dump-flows lr0 > lr0flows
-+AT_CAPTURE_FILE([lr0flows])
-+
-+AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
-+ table=5 (lr_in_unsnat ), priority=0 , match=(1), action=(next;)
-+])
-+
-+AT_CHECK([grep "lr_out_snat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl
-+])
-+
-+check ovn-nbctl set logical_router lr0 options:chassis=ch1
-+check ovn-nbctl --wait=sb add logical_router_port lr0-sw1 networks "bef0\:\:1/64"
-+
-+ovn-sbctl dump-flows lr0 > lr0flows
-+AT_CAPTURE_FILE([lr0flows])
-+
-+AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
-+ table=5 (lr_in_unsnat ), priority=0 , match=(1), action=(next;)
-+ table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-public" && ip4.dst == 172.168.0.100), action=(ct_snat;)
-+ table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-sw0" && ip4.dst == 10.0.0.1), action=(ct_snat;)
-+ table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-sw1" && ip4.dst == 20.0.0.1), action=(ct_snat;)
-+ table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-sw1" && ip6.dst == bef0::1), action=(ct_snat;)
-+])
-+
-+AT_CHECK([grep "lr_in_dnat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl
-+ table=6 (lr_in_dnat ), priority=120 , match=(ct.est && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_dnat;)
-+ table=6 (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_lb(backends=10.0.0.4:8080);)
-+])
-+
-+AT_CHECK([grep "lr_out_snat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl
-+ table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-public"), action=(ct_snat(172.168.0.100);)
-+ table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw0"), action=(ct_snat(10.0.0.1);)
-+ table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw1"), action=(ct_snat(20.0.0.1);)
-+ table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip6 && outport == "lr0-sw1"), action=(ct_snat(bef0::1);)
-+])
-+
-+AT_CLEANUP
-+
-+AT_SETUP([ovn -- FDB cleanup])
-+
-+ovn_start
-+
-+ovn-nbctl ls-add sw0
-+ovn-nbctl lsp-add sw0 sw0-p1
-+ovn-nbctl lsp-add sw0 sw0-p2
-+ovn-nbctl lsp-add sw0 sw0-p3
-+
-+ovn-nbctl ls-add sw1
-+ovn-nbctl lsp-add sw1 sw1-p1
-+ovn-nbctl lsp-add sw1 sw1-p2
-+ovn-nbctl --wait=sb lsp-add sw1 sw1-p3
-+
-+sw0_key=$(fetch_column datapath_binding tunnel_key external_ids:name=sw0)
-+sw1_key=$(fetch_column datapath_binding tunnel_key external_ids:name=sw1)
-+sw0p1_key=$(fetch_column port_binding tunnel_key logical_port=sw0-p1)
-+sw0p2_key=$(fetch_column port_binding tunnel_key logical_port=sw0-p2)
-+sw1p1_key=$(fetch_column port_binding tunnel_key logical_port=sw1-p1)
-+
-+ovn-sbctl create FDB mac="00\:00\:00\:00\:00\:01" dp_key=$sw0_key port_key=$sw0p1_key
-+ovn-sbctl create FDB mac="00\:00\:00\:00\:00\:02" dp_key=$sw0_key port_key=$sw0p1_key
-+ovn-sbctl create FDB mac="00\:00\:00\:00\:00\:03" dp_key=$sw0_key port_key=$sw0p2_key
-+ovn-sbctl create FDB mac="00\:00\:00\:00\:01\:01" dp_key=$sw1_key port_key=$sw1p1_key
-+ovn-sbctl create FDB mac="00\:00\:00\:00\:01\:02" dp_key=$sw1_key port_key=$sw1p1_key
-+ovn-sbctl create FDB mac="00\:00\:00\:00\:01\:03" dp_key=$sw1_key port_key=$sw1p1_key
-+
-+wait_row_count FDB 6
-+
-+ovn-sbctl create fdb mac="00\:00\:00\:00\:01\:03" dp_key=$sw1_key port_key=10
-+wait_row_count FDB 6
-+ovn-sbctl create fdb mac="00\:00\:00\:00\:01\:03" dp_key=4 port_key=10
-+wait_row_count FDB 6
-+
-+ovn-nbctl --wait=sb ls-del sw1
-+wait_row_count FDB 3
-+
-+ovn-nbctl lsp-del sw0-p3
-+wait_row_count FDB 3
-+
-+ovn-nbctl lsp-del sw0-p1
-+wait_row_count FDB 1
-+
-+check_column '00:00:00:00:00:03' FDB mac
-+ovn-sbctl list fdb
-+
-+check_column $sw0_key FDB dp_key
-+check_column $sw0p2_key FDB port_key
-+
-+ovn-nbctl --wait=sb lsp-add sw0-p1
-+wait_row_count FDB 1
-+
-+ovn-nbctl lsp-del sw0-p2
-+ovn-nbctl lsp-add sw0-p2
-+wait_row_count FDB 0
-+
-+ovn-sbctl list FDB
-+
-+AT_CLEANUP
-+
-+AT_SETUP([ovn -- HA chassis group cleanup for external port ])
-+ovn_start
-+
-+check ovn-nbctl ls-add sw0
-+check ovn-nbctl lsp-add sw0 sw0-p1
-+check ovn-nbctl lsp-set-type sw0-p1 external
-+
-+check ovn-sbctl chassis-add ch1 geneve 127.0.0.1
-+check ovn-sbctl chassis-add ch2 geneve 127.0.0.2
-+
-+check ovn-nbctl ha-chassis-group-add hagrp1
-+check ovn-nbctl ha-chassis-group-add-chassis hagrp1 ch1 20
-+check ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch2 10
-+
-+ha_grp1_uuid=$(fetch_column nb:ha_chassis_group _uuid)
-+echo "ha grp1 uuid = $ha_grp1_uuid"
-+ovn-nbctl list ha_chassis_group
-+check ovn-nbctl set logical_switch_port sw0-p1 ha_chassis_group=$ha_grp1_uuid
-+
-+wait_row_count ha_chassis_group 1
-+check ovn-nbctl clear logical_switch_port sw0-p1 ha_chassis_group
-+wait_row_count ha_chassis_group 0
-+
-+check ovn-nbctl set logical_switch_port sw0-p1 ha_chassis_group=$ha_grp1_uuid
-+wait_row_count ha_chassis_group 1
-+sb_ha_grp1_uuid=$(fetch_column ha_chassis_group _uuid)
-+
-+echo
-+echo "__file__:__line__:Check that port_binding sw0-p1 has ha_chassis_group set"
-+
-+check_column "$sb_ha_grp1_uuid" Port_Binding ha_chassis_group logical_port=sw0-p1
-+
-+AS_BOX([Clear ha_chassis_group for sw0-p1 and reset port type to normal port in the same txn])
-+
-+check ovn-nbctl clear logical_switch_port sw0-p1 ha_chassis_group -- set logical_switch_port sw0-p1 'type=""'
-+wait_row_count ha_chassis_group 0
-+check_column "" Port_Binding chassis logical_port=sw0-p1
-+
-+AT_CLEANUP
-diff --git a/tests/ovn-ofctrl-seqno.at b/tests/ovn-ofctrl-seqno.at
-new file mode 100644
-index 000000000..59dfea947
---- /dev/null
-+++ b/tests/ovn-ofctrl-seqno.at
-@@ -0,0 +1,226 @@
-+#
-+# Unit tests for the controller/ofctrl-seqno.c module.
-+#
-+AT_BANNER([OVN unit tests - ofctrl-seqno])
-+
-+AT_SETUP([ovn -- unit test -- ofctrl-seqno add-type])
-+
-+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_add_type 1], [0], [dnl
-+0
-+])
-+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_add_type 2], [0], [dnl
-+0
-+1
-+])
-+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_add_type 3], [0], [dnl
-+0
-+1
-+2
-+])
-+AT_CLEANUP
-+
-+AT_SETUP([ovn -- unit test -- ofctrl-seqno ack-seqnos])
-+
-+AS_BOX([No Ack Batching, 1 seqno type])
-+n_types=1
-+n_app_seqnos=3
-+app_seqnos="40 41 42"
-+
-+n_acks=1
-+acks="1"
-+echo "ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos false ${n_types} ${n_app_seqnos} ${app_seqnos} ${n_acks} ${acks}"
-+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos false ${n_types} \
-+ ${n_app_seqnos} ${app_seqnos} ${n_acks} ${acks}], [0], [dnl
-+ofctrl-seqno-req-cfg: 3
-+ofctrl-seqno-type: 0
-+ last-acked 40
-+ 40
-+])
-+
-+n_acks=2
-+acks="1 2"
-+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos false ${n_types} \
-+ ${n_app_seqnos} ${app_seqnos} ${n_acks} ${acks}], [0], [dnl
-+ofctrl-seqno-req-cfg: 3
-+ofctrl-seqno-type: 0
-+ last-acked 40
-+ 40
-+ofctrl-seqno-type: 0
-+ last-acked 41
-+ 41
-+])
-+
-+n_acks=3
-+acks="1 2 3"
-+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos false ${n_types} \
-+ ${n_app_seqnos} ${app_seqnos} ${n_acks} ${acks}], [0], [dnl
-+ofctrl-seqno-req-cfg: 3
-+ofctrl-seqno-type: 0
-+ last-acked 40
-+ 40
-+ofctrl-seqno-type: 0
-+ last-acked 41
-+ 41
-+ofctrl-seqno-type: 0
-+ last-acked 42
-+ 42
-+])
-+
-+AS_BOX([Ack Batching, 1 seqno type])
-+n_types=1
-+n_app_seqnos=3
-+app_seqnos="40 41 42"
-+
-+n_acks=1
-+acks="1"
-+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos true ${n_types} \
-+ ${n_app_seqnos} ${app_seqnos} ${n_acks} ${acks}], [0], [dnl
-+ofctrl-seqno-req-cfg: 3
-+ofctrl-seqno-type: 0
-+ last-acked 40
-+ 40
-+])
-+
-+n_acks=2
-+acks="1 2"
-+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos true ${n_types} \
-+ ${n_app_seqnos} ${app_seqnos} ${n_acks} ${acks}], [0], [dnl
-+ofctrl-seqno-req-cfg: 3
-+ofctrl-seqno-type: 0
-+ last-acked 41
-+ 40
-+ 41
-+])
-+
-+n_acks=3
-+acks="1 2 3"
-+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos true ${n_types} \
-+ ${n_app_seqnos} ${app_seqnos} ${n_acks} ${acks}], [0], [dnl
-+ofctrl-seqno-req-cfg: 3
-+ofctrl-seqno-type: 0
-+ last-acked 42
-+ 40
-+ 41
-+ 42
-+])
-+
-+AS_BOX([No Ack Batching, 2 seqno types])
-+n_types=2
-+n_app_seqnos=3
-+app_seqnos1="40 41 42"
-+app_seqnos2="50 51 52"
-+
-+n_acks=1
-+acks="1"
-+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos false ${n_types} \
-+ ${n_app_seqnos} ${app_seqnos1} ${n_app_seqnos} ${app_seqnos2} \
-+ ${n_acks} ${acks}], [0], [dnl
-+ofctrl-seqno-req-cfg: 6
-+ofctrl-seqno-type: 0
-+ last-acked 40
-+ 40
-+ofctrl-seqno-type: 1
-+ last-acked 0
-+])
-+
-+n_acks=3
-+acks="1 2 3"
-+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos false ${n_types} \
-+ ${n_app_seqnos} ${app_seqnos1} ${n_app_seqnos} ${app_seqnos2} \
-+ ${n_acks} ${acks}], [0], [dnl
-+ofctrl-seqno-req-cfg: 6
-+ofctrl-seqno-type: 0
-+ last-acked 40
-+ 40
-+ofctrl-seqno-type: 1
-+ last-acked 0
-+ofctrl-seqno-type: 0
-+ last-acked 41
-+ 41
-+ofctrl-seqno-type: 1
-+ last-acked 0
-+ofctrl-seqno-type: 0
-+ last-acked 42
-+ 42
-+ofctrl-seqno-type: 1
-+ last-acked 0
-+])
-+
-+n_acks=3
-+acks="4 5 6"
-+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos false ${n_types} \
-+ ${n_app_seqnos} ${app_seqnos1} ${n_app_seqnos} ${app_seqnos2} \
-+ ${n_acks} ${acks}], [0], [dnl
-+ofctrl-seqno-req-cfg: 6
-+ofctrl-seqno-type: 0
-+ last-acked 42
-+ 40
-+ 41
-+ 42
-+ofctrl-seqno-type: 1
-+ last-acked 50
-+ 50
-+ofctrl-seqno-type: 0
-+ last-acked 42
-+ofctrl-seqno-type: 1
-+ last-acked 51
-+ 51
-+ofctrl-seqno-type: 0
-+ last-acked 42
-+ofctrl-seqno-type: 1
-+ last-acked 52
-+ 52
-+])
-+
-+AS_BOX([Ack Batching, 2 seqno types])
-+n_types=2
-+n_app_seqnos=3
-+app_seqnos1="40 41 42"
-+app_seqnos2="50 51 52"
-+
-+n_acks=1
-+acks="1"
-+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos true ${n_types} \
-+ ${n_app_seqnos} ${app_seqnos1} ${n_app_seqnos} ${app_seqnos2} \
-+ ${n_acks} ${acks}], [0], [dnl
-+ofctrl-seqno-req-cfg: 6
-+ofctrl-seqno-type: 0
-+ last-acked 40
-+ 40
-+ofctrl-seqno-type: 1
-+ last-acked 0
-+])
-+
-+n_acks=3
-+acks="1 2 3"
-+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos true ${n_types} \
-+ ${n_app_seqnos} ${app_seqnos1} ${n_app_seqnos} ${app_seqnos2} \
-+ ${n_acks} ${acks}], [0], [dnl
-+ofctrl-seqno-req-cfg: 6
-+ofctrl-seqno-type: 0
-+ last-acked 42
-+ 40
-+ 41
-+ 42
-+ofctrl-seqno-type: 1
-+ last-acked 0
-+])
-+
-+n_acks=3
-+acks="4 5 6"
-+AT_CHECK([ovstest test-ofctrl-seqno ofctrl_seqno_ack_seqnos true ${n_types} \
-+ ${n_app_seqnos} ${app_seqnos1} ${n_app_seqnos} ${app_seqnos2} \
-+ ${n_acks} ${acks}], [0], [dnl
-+ofctrl-seqno-req-cfg: 6
-+ofctrl-seqno-type: 0
-+ last-acked 42
-+ 40
-+ 41
-+ 42
-+ofctrl-seqno-type: 1
-+ last-acked 52
-+ 50
-+ 51
-+ 52
-+])
-+AT_CLEANUP
-diff --git a/tests/ovn-performance.at b/tests/ovn-performance.at
-index 6cc5b2174..e510c6cef 100644
---- a/tests/ovn-performance.at
-+++ b/tests/ovn-performance.at
-@@ -232,37 +232,32 @@ AT_SETUP([ovn -- ovn-controller incremental processing])
-
- ovn_start
- net_add n1
--for i in 1 2; do
-+for i in `seq 1 5`; do
- sim_add hv$i
- as hv$i
- ovs-vsctl add-br br-phys
- ovn_attach n1 br-phys 192.168.0.$i
--done
--
--for i in 1 2 3; do
-- sim_add gw$i
-- as gw$i
-- ovs-vsctl add-br br-phys
-- ovs-vsctl add-br br-ex
-- ovs-vsctl set open . external_ids:ovn-bridge-mappings="public:br-ex"
-- j=$((i + 2))
-- ovn_attach n1 br-phys 192.168.0.$j
-- ip link add vgw$i type dummy
-- ovs-vsctl add-port br-ex vgw$i
-+ if [[ $i -ge 3 ]] ; then
-+ ovs-vsctl add-br br-ex
-+ ovs-vsctl set open . external_ids:ovn-bridge-mappings="public:br-ex"
-+ ip link add vgw$i type dummy
-+ ovs-vsctl add-port br-ex vgw$i
-+ fi
- done
-
- # Wait for the tunnel ports to be created and up.
- # Otherwise this may affect the lflow_run count.
-+for i in `seq 1 5`; do
-+ for j in `seq 1 5`; do
-+ if [[ $i -ne $j ]] ; then
-+ OVS_WAIT_UNTIL([
-+ test $(as hv$i ovs-vsctl list interface ovn-hv$j-0 | \
-+ grep -c tunnel_egress_iface_carrier=up) -eq 1
-+ ])
-+ fi
-+ done
-+done
-
--OVS_WAIT_UNTIL([
-- test $(as hv1 ovs-vsctl list interface ovn-hv2-0 | \
--grep tunnel_egress_iface_carrier=up | wc -l) -eq 1
--])
--
--OVS_WAIT_UNTIL([
-- test $(as hv2 ovs-vsctl list interface ovn-hv1-0 | \
--grep tunnel_egress_iface_carrier=up | wc -l) -eq 1
--])
-
- # Add router lr1
- OVN_CONTROLLER_EXPECT_NO_HIT(
-@@ -463,63 +458,63 @@ OVN_CONTROLLER_EXPECT_NO_HIT(
- )
-
- OVN_CONTROLLER_EXPECT_HIT_COND(
-- [hv1 hv2 gw1 gw2 gw3], [lflow_run], [=0 =0 >0 =0 =0],
-- [ovn-nbctl --wait=hv lrp-set-gateway-chassis lr1-public gw1 30 && ovn-nbctl --wait=hv sync]
-+ [hv1 hv2 hv3 hv4 hv5], [lflow_run], [=0 =0 >0 =0 =0],
-+ [ovn-nbctl --wait=hv lrp-set-gateway-chassis lr1-public hv3 30 && ovn-nbctl --wait=hv sync]
- )
-
--# After this, BFD should be enabled from hv1 and hv2 to gw1.
--# So there should be lflow_run hits in hv1, hv2, gw1 and gw2
-+# After this, BFD should be enabled from hv1 and hv2 to hv3.
-+# So there should be lflow_run hits in hv1, hv2, hv3 and hv4
- OVN_CONTROLLER_EXPECT_HIT_COND(
-- [hv1 hv2 gw1 gw2 gw3], [lflow_run], [>0 >0 >0 >0 =0],
-- [ovn-nbctl --wait=hv lrp-set-gateway-chassis lr1-public gw2 20 && ovn-nbctl --wait=hv sync]
--)
--
--OVN_CONTROLLER_EXPECT_HIT(
-- [hv1 hv2 gw1 gw2 gw3], [lflow_run],
-- [ovn-nbctl --wait=hv lrp-set-gateway-chassis lr1-public gw3 10 && ovn-nbctl --wait=hv sync]
--)
--
--# create QoS rule
--OVN_CONTROLLER_EXPECT_NO_HIT(
-- [hv1 hv2 gw1 gw2 gw3], [lflow_run],
-- [ovn-nbctl --wait=hv set Logical_Switch_Port ln-public options:qos_burst=1000]
-+ [hv1 hv2 hv3 hv4 hv5], [lflow_run], [>0 >0 >0 >0 =0],
-+ [ovn-nbctl --wait=hv lrp-set-gateway-chassis lr1-public hv4 20 && ovn-nbctl --wait=hv sync]
- )
-
- OVN_CONTROLLER_EXPECT_HIT(
-- [gw1], [lflow_run],
-- [as gw1 ovs-vsctl set interface vgw1 external-ids:ovn-egress-iface=true]
-+ [hv1 hv2 hv3 hv4 hv5], [lflow_run],
-+ [ovn-nbctl --wait=hv lrp-set-gateway-chassis lr1-public hv5 10 && ovn-nbctl --wait=hv sync]
- )
-
--# Make gw2 master. There is remote possibility that full recompute
--# triggers for gw2 after it becomes master. Most of the time
--# there will be no recompute.
--ovn-nbctl --wait=hv lrp-set-gateway-chassis lr1-public gw2 40
--gw2_ch=$(ovn-sbctl --bare --columns _uuid list chassis gw2)
--OVS_WAIT_UNTIL([ovn-sbctl find port_binding logical_port=cr-lr1-public chassis=$gw2_ch])
-+# Make hv4 master. There is remote possibility that full recompute
-+# triggers for hv1-hv5 after hv4 becomes master because of updates to the
-+# ovn-hv$i-0 interfaces. Most of the time there will be no recompute.
-+ovn-nbctl --wait=hv lrp-set-gateway-chassis lr1-public hv4 40
-+hv4_ch=$(ovn-sbctl --bare --columns _uuid list chassis hv4)
-+OVS_WAIT_UNTIL([ovn-sbctl find port_binding logical_port=cr-lr1-public chassis=$hv4_ch])
-
- OVN_CONTROLLER_EXPECT_HIT_COND(
-- [hv1 hv2 gw1 gw2 gw3], [lflow_run], [=0 =0 =0 >=0 =0],
-+ [hv1 hv2 hv3 hv4 hv5], [lflow_run], [>=0 >=0 >=0 >=0 >=0],
- [ovn-nbctl --wait=hv sync]
- )
-
--# Delete gw2 from gateway chassis
-+# Delete hv4 from gateway chassis
- OVN_CONTROLLER_EXPECT_HIT(
-- [hv1 hv2 gw1 gw2 gw3], [lflow_run],
-- [ovn-nbctl --wait=hv lrp-del-gateway-chassis lr1-public gw2 && ovn-nbctl --wait=hv sync]
-+ [hv1 hv2 hv3 hv4 hv5], [lflow_run],
-+ [ovn-nbctl --wait=hv lrp-del-gateway-chassis lr1-public hv4 && ovn-nbctl --wait=hv sync]
- )
-
--# Delete gw1 from gateway chassis
--# After this, the BFD should be disabled entirely as gw3 is the
-+# Delete hv3 from gateway chassis
-+# After this, the BFD should be disabled entirely as hv5 is the
- # only gateway chassis.
- OVN_CONTROLLER_EXPECT_HIT_COND(
-- [hv1 hv2 gw1 gw2 gw3], [lflow_run], [>0 >0 >0 =0 >0],
-- [ovn-nbctl --wait=hv lrp-del-gateway-chassis lr1-public gw1]
-+ [hv1 hv2 hv3 hv4 hv5], [lflow_run], [>0 >0 >0 =0 >0],
-+ [ovn-nbctl --wait=hv lrp-del-gateway-chassis lr1-public hv3]
- )
-
--# Delete gw3 from gateway chassis. There should be no lflow_run.
-+# Delete hv5 from gateway chassis. There should be no lflow_run.
- OVN_CONTROLLER_EXPECT_NO_HIT(
-- [hv1 hv2 gw1 gw2 gw3], [lflow_run],
-- [ovn-nbctl --wait=hv lrp-del-gateway-chassis lr1-public gw3]
-+ [hv1 hv2 hv3 hv4 hv5], [lflow_run],
-+ [ovn-nbctl --wait=hv lrp-del-gateway-chassis lr1-public hv5]
-+)
-+
-+# create QoS rule
-+OVN_CONTROLLER_EXPECT_NO_HIT(
-+ [hv1 hv2 hv3 hv4 hv5], [lflow_run],
-+ [ovn-nbctl --wait=hv set Logical_Switch_Port ln-public options:qos_burst=1000]
-+)
-+
-+OVN_CONTROLLER_EXPECT_HIT(
-+ [hv3], [lflow_run],
-+ [as hv3 ovs-vsctl set interface vgw3 external-ids:ovn-egress-iface=true]
- )
-
- for i in 1 2; do
-diff --git a/tests/ovn.at b/tests/ovn.at
-index 2e0bc9c53..bd59c0a77 100644
---- a/tests/ovn.at
-+++ b/tests/ovn.at
-@@ -1637,6 +1637,17 @@ tcp_reset { };
- encodes as controller(userdata=00.00.00.0b.00.00.00.00)
- has prereqs tcp
-
-+# sctp_abort
-+sctp_abort {eth.dst = ff:ff:ff:ff:ff:ff; output; }; output;
-+ formats as sctp_abort { eth.dst = ff:ff:ff:ff:ff:ff; output; }; output;
-+ encodes as controller(userdata=00.00.00.18.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)
-+ has prereqs sctp
-+
-+sctp_abort { };
-+ formats as sctp_abort { drop; };
-+ encodes as controller(userdata=00.00.00.18.00.00.00.00)
-+ has prereqs sctp
-+
- # reject
- reject { eth.dst = ff:ff:ff:ff:ff:ff; output; }; output;
- encodes as controller(userdata=00.00.00.16.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)
-@@ -1807,6 +1818,68 @@ ct_snat_to_vip;
- ct_snat_to_vip(foo);
- Syntax error at `(' expecting `;'.
-
-+# bfd packets
-+handle_bfd_msg();
-+ encodes as controller(userdata=00.00.00.17.00.00.00.00)
-+
-+# put_fdb
-+put_fdb(inport, arp.sha);
-+ encodes as push:NXM_OF_ETH_SRC[],push:NXM_NX_ARP_SHA[],pop:NXM_OF_ETH_SRC[],controller(userdata=00.00.00.19.00.00.00.00),pop:NXM_OF_ETH_SRC[]
-+ has prereqs eth.type == 0x806
-+
-+put_fdb(inport, eth.src);
-+ encodes as controller(userdata=00.00.00.19.00.00.00.00)
-+
-+put_fdb(inport, ip4.src);
-+ Cannot use 32-bit field ip4.src[0..31] where 48-bit field is required.
-+
-+# get_fdb
-+outport = get_fdb(eth.dst);
-+ encodes as set_field:0->reg15,resubmit(,71)
-+
-+outport = get_fdb(eth.src);
-+ encodes as push:NXM_OF_ETH_DST[],push:NXM_OF_ETH_SRC[],pop:NXM_OF_ETH_DST[],set_field:0->reg15,resubmit(,71),pop:NXM_OF_ETH_DST[]
-+
-+inport = get_fdb(arp.sha);
-+ encodes as push:NXM_OF_ETH_DST[],push:NXM_NX_ARP_SHA[],pop:NXM_OF_ETH_DST[],set_field:0->reg15,resubmit(,71),pop:NXM_OF_ETH_DST[],move:NXM_NX_REG15[]->NXM_NX_REG14[]
-+ has prereqs eth.type == 0x806
-+
-+reg0 = get_fdb(arp.tha);
-+ encodes as push:NXM_OF_ETH_DST[],push:NXM_NX_ARP_THA[],pop:NXM_OF_ETH_DST[],set_field:0->reg15,resubmit(,71),pop:NXM_OF_ETH_DST[],move:NXM_NX_REG15[]->NXM_NX_XXREG0[96..127]
-+ has prereqs eth.type == 0x806
-+
-+reg0[1..3] = get_fdb(eth.src);
-+ Cannot use 3-bit field reg0[1..3] where 32-bit field is required.
-+
-+reg15 = get_fdb(eth.dst);
-+ Syntax error at `reg15' expecting field name.
-+
-+outport = get_fdb(ip4.dst);
-+ Cannot use 32-bit field ip4.dst[0..31] where 48-bit field is required.
-+
-+# lookup_fdb
-+reg0[0] = lookup_fdb(inport, eth.src);
-+ encodes as set_field:0/0x100->reg10,resubmit(,72),move:NXM_NX_REG10[8]->NXM_NX_XXREG0[96]
-+
-+reg1[4] = lookup_fdb(outport, eth.dst);
-+ encodes as push:NXM_NX_REG14[],push:NXM_OF_ETH_SRC[],push:NXM_OF_ETH_DST[],push:NXM_NX_REG15[],pop:NXM_NX_REG14[],pop:NXM_OF_ETH_SRC[],set_field:0/0x100->reg10,resubmit(,72),pop:NXM_OF_ETH_SRC[],pop:NXM_NX_REG14[],move:NXM_NX_REG10[8]->NXM_NX_XXREG0[68]
-+
-+reg0[0] = lookup_fdb(outport, arp.sha);
-+ encodes as push:NXM_NX_REG14[],push:NXM_OF_ETH_SRC[],push:NXM_NX_ARP_SHA[],push:NXM_NX_REG15[],pop:NXM_NX_REG14[],pop:NXM_OF_ETH_SRC[],set_field:0/0x100->reg10,resubmit(,72),pop:NXM_OF_ETH_SRC[],pop:NXM_NX_REG14[],move:NXM_NX_REG10[8]->NXM_NX_XXREG0[96]
-+ has prereqs eth.type == 0x806
-+
-+reg0 = lookup_fdb(outport, arp.sha);
-+ Cannot use 32-bit field reg0[0..31] where 1-bit field is required.
-+
-+outport = lookup_fdb(outport, arp.sha);
-+ Cannot use string field outport where numeric field is required.
-+
-+reg1[1] = lookup_fdb(outport, ip4.src);
-+ Cannot use 32-bit field ip4.src[0..31] where 48-bit field is required.
-+
-+reg1[1] = lookup_fdb(ip4.src, eth.src);
-+ Cannot use numeric field ip4.src where string field is required.
-+
- # Miscellaneous negative tests.
- ;
- Syntax error at `;'.
-@@ -1837,53 +1910,46 @@ ovn_start
- # Turn on port security on all the vifs except vif[123]1.
- # Make vif13, vif2[23], vif3[123] destinations for unknown MACs.
- # Add some ACLs for Ethertypes 1234, 1235, 1236.
--ovn-nbctl ls-add lsw0
-+check ovn-nbctl ls-add lsw0
- net_add n1
- for i in 1 2 3; do
- sim_add hv$i
- as hv$i
-- ovs-vsctl add-br br-phys
-+ check ovs-vsctl add-br br-phys
- ovn_attach n1 br-phys 192.168.0.$i
-
- for j in 1 2 3; do
-- ovs-vsctl add-port br-int vif$i$j -- set Interface vif$i$j external-ids:iface-id=lp$i$j options:tx_pcap=hv$i/vif$i$j-tx.pcap options:rxq_pcap=hv$i/vif$i$j-rx.pcap ofport-request=$i$j
-- ovn-nbctl lsp-add lsw0 lp$i$j
-+ check ovs-vsctl add-port br-int vif$i$j -- set Interface vif$i$j external-ids:iface-id=lp$i$j options:tx_pcap=hv$i/vif$i$j-tx.pcap options:rxq_pcap=hv$i/vif$i$j-rx.pcap ofport-request=$i$j
-+ check ovn-nbctl lsp-add lsw0 lp$i$j
- if test $j = 1; then
-- ovn-nbctl lsp-set-addresses lp$i$j "f0:00:00:00:00:$i$j 192.168.0.$i$j" unknown
-+ check ovn-nbctl lsp-set-addresses lp$i$j "f0:00:00:00:00:$i$j 192.168.0.$i$j" unknown
- else
- if test $j = 3; then
- ip_addrs="192.168.0.$i$j fe80::ea2a:eaff:fe28:$i$j/64 192.169.0.$i$j"
- else
- ip_addrs="192.168.0.$i$j"
- fi
-- ovn-nbctl lsp-set-addresses lp$i$j "f0:00:00:00:00:$i$j $ip_addrs"
-- ovn-nbctl lsp-set-port-security lp$i$j f0:00:00:00:00:$i$j
-+ check ovn-nbctl lsp-set-addresses lp$i$j "f0:00:00:00:00:$i$j $ip_addrs"
-+ check ovn-nbctl lsp-set-port-security lp$i$j f0:00:00:00:00:$i$j
- fi
- done
- done
--ovn-nbctl acl-add lsw0 from-lport 1000 'eth.type == 0x1234' drop
--ovn-nbctl acl-add lsw0 from-lport 1000 'eth.type == 0x1235 && inport == "lp11"' drop
--ovn-nbctl acl-add lsw0 to-lport 1000 'eth.type == 0x1236 && outport == "lp33"' drop
-+check ovn-nbctl acl-add lsw0 from-lport 1000 'eth.type == 0x1234' drop
-+check ovn-nbctl acl-add lsw0 from-lport 1000 'eth.type == 0x1235 && inport == "lp11"' drop
-+check ovn-nbctl acl-add lsw0 to-lport 1000 'eth.type == 0x1236 && outport == "lp33"' drop
- ovn-nbctl create Address_Set name=set1 addresses=\"f0:00:00:00:00:11\",\"f0:00:00:00:00:21\",\"f0:00:00:00:00:31\"
--ovn-nbctl acl-add lsw0 to-lport 1000 'eth.type == 0x1237 && eth.src == $set1 && outport == "lp33"' drop
--
--get_lsp_uuid () {
-- ovn-nbctl lsp-list lsw0 | grep $1 | awk '{ print $1 }'
--}
-+check ovn-nbctl acl-add lsw0 to-lport 1000 'eth.type == 0x1237 && eth.src == $set1 && outport == "lp33"' drop
-
--ovn-nbctl create Port_Group name=pg1 ports=`get_lsp_uuid lp22`,`get_lsp_uuid lp33`
--ovn-nbctl acl-add lsw0 to-lport 1000 'eth.type == 0x1238 && outport == @pg1' drop
-+check ovn-nbctl pg-add pg1 lp22 lp33
-+check ovn-nbctl acl-add lsw0 to-lport 1000 'eth.type == 0x1238 && outport == @pg1' drop
- check ovn-nbctl --wait=hv sync
-+wait_for_ports_up
-
- # 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
-
--# Allow some time for ovn-northd and ovn-controller to catch up.
--# XXX This should be more systematic.
--sleep 1
--
- # Make sure there is no attempt to adding duplicated flows by ovn-controller
- AT_FAIL_IF([test -n "`grep duplicate hv1/ovn-controller.log`"])
- AT_FAIL_IF([test -n "`grep duplicate hv2/ovn-controller.log`"])
-@@ -2078,11 +2144,7 @@ done
-
- # set address for lp13 with invalid characters.
- # lp13 should be configured with only 192.168.0.13.
--ovn-nbctl lsp-set-addresses lp13 "f0:00:00:00:00:13 192.168.0.13 invalid 192.169.0.13"
--
--# Allow some time for ovn-northd and ovn-controller to catch up.
--# XXX This should be more systematic.
--sleep 1
-+check ovn-nbctl --wait=hv lsp-set-addresses lp13 "f0:00:00:00:00:13 192.168.0.13 invalid 192.169.0.13"
-
- sip=`ip_to_hex 192 168 0 11`
- tip=`ip_to_hex 192 168 0 13`
-@@ -2155,7 +2217,11 @@ for i in 1 2; do
- done
- done
-
--sleep 1
-+# Wait for bindings to take effect.
-+wait_row_count Port_Binding 1 logical_port=lp11 'encap!=[[]]'
-+wait_row_count Port_Binding 1 logical_port=lp12 'encap!=[[]]'
-+wait_row_count Port_Binding 1 logical_port=lp21 'encap!=[[]]'
-+wait_row_count Port_Binding 1 logical_port=lp22 'encap!=[[]]'
-
- # dump port bindings; since we have vxlan and geneve tunnels, we expect the
- # ports to be bound to geneve tunnels.
-@@ -2175,9 +2241,8 @@ check_row_count Port_Binding 1 logical_port=lp22 encap=$encap_rec
- # for ARP resolution).
- OVN_POPULATE_ARP
-
--# Allow some time for ovn-northd and ovn-controller to catch up.
--# XXX This should be more systematic.
--sleep 1
-+wait_for_ports_up
-+check ovn-nbctl --wait=hv sync
-
- # Make sure there is no attempt to adding duplicated flows by ovn-controller
- AT_FAIL_IF([test -n "`grep duplicate hv1/ovn-controller.log`"])
-@@ -2567,6 +2632,7 @@ for i in 1 2; do
- OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up $lsp_name` = xup])
- done
- done
-+wait_for_ports_up
- ovn-nbctl --wait=sb sync
- ovn-sbctl dump-flows > sbflows
- AT_CAPTURE_FILE([sbflows])
-@@ -2733,6 +2799,7 @@ for hv in 1 2; do
- done
-
-
-+wait_for_ports_up
- ovn-nbctl --wait=sb sync
-
- ovn-sbctl dump-flows > sbflows
-@@ -2866,6 +2933,7 @@ for hv in 1 2; do
- done
-
-
-+wait_for_ports_up
- ovn-nbctl --wait=sb sync
- ovn-nbctl show
- ovn-sbctl dump-flows > sbflows
-@@ -3003,6 +3071,7 @@ for i in 1 2; do
- OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up $lsp_name` = xup])
- done
-
-+wait_for_ports_up
- ovn-nbctl --wait=sb sync
- ovn-nbctl show
- ovn-sbctl dump-flows > sbflows
-@@ -3213,6 +3282,7 @@ for tag in 10 20; do
- OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up $lsp_name` = xup])
- done
- done
-+wait_for_ports_up
- ovn-nbctl --wait=sb sync
- ovn-sbctl dump-flows
-
-@@ -3371,9 +3441,8 @@ ovs-vsctl add-port br-phys vif3 -- set Interface vif3 options:tx_pcap=hv3/vif3-t
- # for ARP resolution).
- OVN_POPULATE_ARP
-
--# Allow some time for ovn-northd and ovn-controller to catch up.
--# XXX This should be more systematic.
--sleep 1
-+wait_for_ports_up
-+check ovn-nbctl --wait=hv sync
-
- # test_packet INPORT DST SRC ETHTYPE OUTPORT...
- #
-@@ -3537,9 +3606,8 @@ ovs-vsctl add-port br-phys vif3 -- set Interface vif3 options:tx_pcap=hv3/vif3-t
- # for ARP resolution).
- OVN_POPULATE_ARP
-
--# Allow some time for ovn-northd and ovn-controller to catch up.
--# XXX This should be more systematic.
--sleep 1
-+wait_for_ports_up
-+check ovn-nbctl --wait=hv sync
-
- # test_packet INPORT DST SRC ETHTYPE OUTPORT...
- #
-@@ -3728,6 +3796,7 @@ for i in 1 2 3; do
- done
- done
-
-+wait_for_ports_up
- check ovn-nbctl --wait=hv sync
-
- # Pre-populate the hypervisors' ARP tables so that we don't lose any
-@@ -3735,9 +3804,6 @@ check ovn-nbctl --wait=hv sync
- # for ARP resolution).
- OVN_POPULATE_ARP
-
--# Allow some time for ovn-northd and ovn-controller to catch up.
--# XXX This should be more systematic.
--
- # test_ip INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT...
- #
- # This shell function causes a packet to be received on INPORT. The packet's
-@@ -4134,8 +4200,8 @@ done
- OVN_POPULATE_ARP
-
- # Allow some time for ovn-northd and ovn-controller to catch up.
--# XXX This should be more systematic.
--sleep 1
-+wait_for_ports_up
-+check ovn-nbctl --wait=hv sync
-
- # test_ip INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT...
- #
-@@ -4307,8 +4373,8 @@ done
- OVN_POPULATE_ARP
-
- # Allow some time for ovn-northd and ovn-controller to catch up.
--# XXX This should be more systematic.
--sleep 1
-+wait_for_ports_up
-+check ovn-nbctl --wait=hv sync
-
- # Given the name of a logical port, prints the name of the hypervisor
- # on which it is located.
-@@ -4740,8 +4806,8 @@ ovs-vsctl -- add-port br-int hv2-vif1 -- \
- OVN_POPULATE_ARP
-
- # Allow some time for ovn-northd and ovn-controller to catch up.
--# XXX This should be more systematic.
--sleep 1
-+wait_for_ports_up
-+check ovn-nbctl --wait=hv sync
-
- # Packet to send.
- packet="inport==\"ls1-lp1\" && eth.src==$ls1_lp1_mac && eth.dst==$rp_ls1_mac &&
-@@ -4851,9 +4917,8 @@ ovs-vsctl -- add-port br-int vif2 -- \
- ofport-request=1
-
-
--# Allow some time for ovn-northd and ovn-controller to catch up.
--# XXX This should be more systematic.
--sleep 1
-+wait_for_ports_up
-+check ovn-nbctl --wait=hv sync
-
- # Send ip packets between the two ports.
-
-@@ -4885,11 +4950,7 @@ as hv1 ovs-ofctl dump-flows br-int
-
-
- #Disable router R1
--ovn-nbctl set Logical_Router R1 enabled=false
--
--# Allow some time for ovn-northd and ovn-controller to catch up.
--# XXX This should be more systematic.
--sleep 1
-+ovn-nbctl --wait=hv set Logical_Router R1 enabled=false
-
- echo "---------SB dump-----"
- ovn-sbctl list datapath_binding
-@@ -4964,10 +5025,11 @@ ovs-vsctl -- add-port br-int vif2 -- \
- options:rxq_pcap=hv1/vif2-rx.pcap \
- ofport-request=1
-
-+wait_for_ports_up
-+check ovn-nbctl --wait=hv sync
-
--# Allow some time for ovn-northd and ovn-controller to catch up.
--# XXX This should be more systematic.
--sleep 1
-+ovs-sbctl dump-flows > sbflows
-+AT_CAPTURE_FILE([sbflows])
-
- # Send ip packets between the two ports.
-
-@@ -4979,39 +5041,11 @@ dst_ip=`ip_to_hex 172 16 1 2`
- packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
- as hv1 ovs-appctl netdev-dummy/receive vif1 $packet
-
--
--echo "---------NB dump-----"
--ovn-nbctl show
--echo "---------------------"
--ovn-nbctl list logical_router
--echo "---------------------"
--ovn-nbctl list logical_router_port
--echo "---------------------"
--
--echo "---------SB dump-----"
--ovn-sbctl list datapath_binding
--echo "---------------------"
--ovn-sbctl list logical_flow
--echo "---------------------"
--
--echo "------ hv1 dump ----------"
--as hv1 ovs-ofctl dump-flows br-int
--
- #Disable router R1
--ovn-nbctl set Logical_Router R1 enabled=false
--
--echo "---------SB dump-----"
--ovn-sbctl list datapath_binding
--echo "---------------------"
--ovn-sbctl list logical_flow
--echo "---------------------"
--
--echo "------ hv1 dump ----------"
--as hv1 ovs-ofctl dump-flows br-int
-+ovn-nbctl --wait=hv set Logical_Router R1 enabled=false
-
--# Allow some time for the disabling of logical router R1 to propagate.
--# XXX This should be more systematic.
--sleep 1
-+ovs-sbctl dump-flows > sbflows2
-+AT_CAPTURE_FILE([sbflows2])
-
- as hv1 ovs-appctl netdev-dummy/receive vif1 $packet
-
-@@ -5114,8 +5148,11 @@ ovs-vsctl -- add-port br-int hv2-vif1 -- \
- OVN_POPULATE_ARP
-
- # Allow some time for ovn-northd and ovn-controller to catch up.
--# XXX This should be more systematic.
--sleep 1
-+wait_for_ports_up
-+check ovn-nbctl --wait=hv sync
-+
-+ovn-sbctl dump-flows > sbflows
-+AT_CAPTURE_FILE([sbflows])
-
- # Send ip packets between foo1 and alice1
- src_mac="f00000010203"
-@@ -5133,25 +5170,6 @@ dst_ip=`ip_to_hex 172 16 2 2`
- packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
- as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
-
--echo "---------NB dump-----"
--ovn-nbctl show
--echo "---------------------"
--ovn-nbctl list logical_router
--echo "---------------------"
--ovn-nbctl list logical_router_port
--echo "---------------------"
--
--echo "---------SB dump-----"
--ovn-sbctl list datapath_binding
--echo "---------------------"
--ovn-sbctl list port_binding
--echo "---------------------"
--
--echo "------ hv1 dump ----------"
--as hv1 ovs-ofctl dump-flows br-int
--echo "------ hv2 dump ----------"
--as hv2 ovs-ofctl dump-flows br-int
--
- # Packet to Expect at bob1
- src_mac="000000010205"
- dst_mac="f00000010205"
-@@ -5333,8 +5351,8 @@ ovs-vsctl -- add-port br-int hv2-vif1 -- \
- OVN_POPULATE_ARP
-
- # Allow some time for ovn-northd and ovn-controller to catch up.
--# XXX This should be more systematic.
--sleep 1
-+wait_for_ports_up
-+check ovn-nbctl --wait=hv sync
-
- # Send ip packets between foo1 and alice1
- src_mac="f00000010203"
-@@ -5469,7 +5487,8 @@ as hv1 ovs-appctl vlog/set dbg
-
- OVN_POPULATE_ARP
-
--sleep 2
-+wait_for_ports_up
-+check ovn-nbctl --wait=hv sync
-
- as hv1 ovs-vsctl show
-
-@@ -6189,7 +6208,8 @@ ovs-vsctl -- add-port br-int hv1-vif5 -- \
-
- OVN_POPULATE_ARP
-
--sleep 2
-+wait_for_ports_up
-+check ovn-nbctl --wait=hv sync
-
- trim_zeros() {
- sed 's/\(00\)\{1,\}$//'
-@@ -6469,10 +6489,8 @@ ovn-nbctl lsp-add foo foo1 \
- ovn-nbctl lsp-add alice alice1 \
- -- lsp-set-addresses alice1 "f0:00:00:01:02:04 172.16.1.2"
-
--
--# Allow some time for ovn-northd and ovn-controller to catch up.
--# XXX This should be more systematic.
--sleep 2
-+wait_for_ports_up
-+check ovn-nbctl --wait=hv sync
-
- # Send ip packets between foo1 and alice1
- src_mac="f00000010203"
-@@ -6535,7 +6553,8 @@ ip_prefix=192.168.1.0/24 nexthop=20.0.0.1 -- add Logical_Router \
- R2 static_routes @lrt
-
- # Wait for ovn-controller to catch up.
--sleep 1
-+wait_for_ports_up
-+check ovn-nbctl --wait=hv sync
-
- # Send the packet again.
- as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
-@@ -6605,11 +6624,11 @@ ovs-vsctl -- add-port br-int vif2 -- \
- options:rxq_pcap=hv1/vif2-rx.pcap \
- ofport-request=1
-
--
- # Allow some time for ovn-northd and ovn-controller to catch up.
--# XXX This should be more systematic.
--sleep 1
--
-+wait_for_ports_up
-+check ovn-nbctl --wait=hv sync
-+ovn-nbctl dump-flows > sbflows
-+AT_CAPTURE_FILE([sbflows])
-
- for i in 1 2; do
- : > vif$i.expected
-@@ -6764,10 +6783,6 @@ ovs-vsctl -- add-port br-int vif3 -- \
- options:rxq_pcap=pbr-hv/vif3-rx.pcap \
- ofport-request=1
-
--# Allow some time for ovn-northd and ovn-controller to catch up.
--# XXX This should be more systematic.
--sleep 1
--
- ls1_ro_mac=00:00:00:01:02:f1
- ls1_ro_ip=192.168.1.1
-
-@@ -6952,10 +6967,6 @@ ovs-vsctl -- add-port br-int vif3 -- \
- options:rxq_pcap=pbr-hv/vif3-rx.pcap \
- ofport-request=1
-
--# Allow some time for ovn-northd and ovn-controller to catch up.
--# XXX This should be more systematic.
--sleep 1
--
- ls1_ro_mac=00:00:00:01:02:f1
- ls1_ro_ip=2001::1
-
-@@ -7158,6 +7169,7 @@ ovn-nbctl lsp-del lp1
- ovn-nbctl ls-del ls1
-
- # wait for earlier changes to take effect
-+wait_for_ports_up
- check ovn-nbctl --wait=sb sync
-
- # ensure OF rules are no longer present. There used to be a bug here.
-@@ -7204,14 +7216,15 @@ ovn-nbctl acl-add lsw0 to-lport 1002 'outport == "lp1" && ip6 && icmp6' allow-r
- ovn-nbctl acl-add lsw0 to-lport 1002 'outport == "lp2" && ip6 && icmp6' allow-related
-
- # Allow some time for ovn-northd and ovn-controller to catch up.
--# XXX This should be more systematic.
-+# XXX The "sleep" here seems to be essential for ovn-northd-ddlog,
-+# which may indicate that it needs improvement.
-+wait_for_ports_up
-+check ovn-nbctl --wait=hv sync
- sleep 1
-
--# Given the name of a logical port, prints the name of the hypervisor
--# on which it is located.
--vif_to_hv() {
-- echo hv1${1%?}
--}
-+ovn-nbctl dump-flows > sbflows
-+AT_CAPTURE_FILE([sbflows])
-+
- for i in 1 2; do
- : > $i.expected
- done
-@@ -7225,11 +7238,6 @@ na_packet=fa163e940598fa163ea1f9ae86dd6000000000203afffd81ce49a9480000f8163efffe
- as hv1 ovs-appctl netdev-dummy/receive vif1 $ns_packet
- echo $na_packet >> 1.expected
-
--echo "------ hv1 dump ------"
--as hv1 ovs-vsctl show
--as hv1 ovs-ofctl -O OpenFlow13 show br-int
--as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-int
--
- for i in 1 2; do
- OVN_CHECK_PACKETS([hv1/vif$i-tx.pcap], [$i.expected])
- done
-@@ -7250,9 +7258,7 @@ ovn_attach n1 br-phys 192.168.0.1
-
- row=`ovn-nbctl create Address_Set name=set1 addresses=\"1.1.1.1\"`
- ovn-nbctl set Address_Set $row name=set1 addresses=\"1.1.1.1,1.1.1.2\"
--ovn-nbctl destroy Address_Set $row
--
--sleep 1
-+ovn-nbctl --wait=hv destroy Address_Set $row
-
- # A bug previously existed in the address set support code
- # that caused ovn-controller to crash after an address set
-@@ -7640,8 +7646,8 @@ ovs-vsctl -- add-port br-int hv1-vif3 -- \
- ofport-request=3
-
- # Allow some time for ovn-northd and ovn-controller to catch up.
--# XXX This should be more systematic.
--sleep 1
-+wait_for_ports_up
-+check ovn-nbctl --wait=hv sync
-
- # Send ip packets between foo1 and foo2
- src_mac="0a0000a80103"
-@@ -7848,32 +7854,11 @@ ovs-vsctl -- add-port br-int hv1-ls2lp2 -- \
- ofport-request=2
-
- # Allow some time for ovn-northd and ovn-controller to catch up.
--# XXX This should be more systematic.
--sleep 1
--
--echo "---------NB dump-----"
--ovn-nbctl show
--echo "---------------------"
--ovn-nbctl list logical_router
--echo "---------------------"
--ovn-nbctl list logical_router_port
--echo "---------------------"
--
--echo "---------SB dump-----"
--ovn-sbctl list datapath_binding
--echo "---------------------"
--ovn-sbctl list port_binding
--echo "---------------------"
--ovn-sbctl dump-flows
--echo "---------------------"
--ovn-sbctl list chassis
--ovn-sbctl list encap
--echo "---------------------"
-+wait_for_ports_up
-+check ovn-nbctl --wait=hv sync
-
--echo "------Flows dump-----"
--as hv1
--ovs-ofctl dump-flows
--echo "---------------------"
-+ovn-sbctl dump-flows > sbflows
-+AT_CAPTURE_FILE([sbflows])
-
- src_mac="f00000000003"
- dst_mac="f00000000001"
-@@ -8256,18 +8241,18 @@ as hv1
- AT_CHECK([ovs-vsctl add-port br-int localvif1 -- set Interface localvif1 external_ids:iface-id=localvif1])
-
- # On hv1, check that there are no flows outputting bcast to tunnel
--OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br-int table=32 | ofctl_strip | grep output | wc -l` -eq 0])
-+OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br-int table=37 | ofctl_strip | grep output | wc -l` -eq 0])
-
- # On hv2, check that no flow outputs bcast to tunnel to hv1.
- as hv2
--OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br-int table=32 | ofctl_strip | grep output | wc -l` -eq 0])
-+OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br-int table=37 | ofctl_strip | grep output | wc -l` -eq 0])
-
- # Now bind vif2 on hv2.
- AT_CHECK([ovs-vsctl add-port br-int localvif2 -- set Interface localvif2 external_ids:iface-id=localvif2])
-
- # At this point, the broadcast flow on vif2 should be deleted.
--# because, there is now a localnet vif bound (table=32 programming logic)
--OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br-int table=32 | ofctl_strip | grep output | wc -l` -eq 0])
-+# because, there is now a localnet vif bound (table=37 programming logic)
-+OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br-int table=37 | ofctl_strip | grep output | wc -l` -eq 0])
-
- # Verify that the local net patch port exists on hv2.
- OVS_WAIT_UNTIL([test `ovs-vsctl show | grep "Port patch-br-int-to-ln_port" | wc -l` -eq 1])
-@@ -8319,6 +8304,7 @@ ovn-nbctl --wait=sb lsp-add lsw0 lp2
- ovn-nbctl lsp-set-addresses lp1 $lp1_mac
- ovn-nbctl lsp-set-addresses lp2 $lp2_mac
- ovn-nbctl --wait=sb sync
-+wait_for_ports_up
-
- ovn-nbctl acl-add lsw0 to-lport 1000 'tcp.dst==80' drop
- ovn-nbctl --log --severity=alert --name=drop-flow acl-add lsw0 to-lport 1000 'tcp.dst==81' drop
-@@ -8425,6 +8411,7 @@ ovn-nbctl --wait=sb lsp-add lsw0 lp2
- ovn-nbctl lsp-set-addresses lp1 $lp1_mac
- ovn-nbctl lsp-set-addresses lp2 $lp2_mac
- ovn-nbctl --wait=sb sync
-+wait_for_ports_up
-
-
- # Add an ACL that rate-limits logs at 10 per second.
-@@ -8515,6 +8502,7 @@ ovn-nbctl --wait=sb lsp-add lsw0 lp2
- ovn-nbctl lsp-set-addresses lp1 $lp1_mac
- ovn-nbctl lsp-set-addresses lp2 $lp2_mac
- ovn-nbctl --wait=sb sync
-+wait_for_ports_up
-
- ovn-appctl -t ovn-controller vlog/set file:dbg
-
-@@ -8562,6 +8550,7 @@ check ovs-vsctl add-br br-phys
- ovn_attach n1 br-phys 192.168.0.1
- check ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lp1 options:tx_pcap=vif1-tx.pcap options:rxq_pcap=vif1-rx.pcap ofport-request=1
- check ovs-vsctl add-port br-int vif2 -- set Interface vif2 external-ids:iface-id=lp2 options:tx_pcap=vif2-tx.pcap options:rxq_pcap=vif2-rx.pcap ofport-request=2
-+wait_for_ports_up lp1 lp2
-
- AT_CAPTURE_FILE([trace])
- ovn_trace () {
-@@ -8960,8 +8949,8 @@ ovs-vsctl -- add-port br-int vm2 -- \
- OVN_POPULATE_ARP
-
- # Allow some time for ovn-northd and ovn-controller to catch up.
--# XXX This should be more systematic.
--sleep 1
-+wait_for_ports_up
-+check ovn-nbctl --wait=hv sync
-
- # Test that ovn-controllers create ct-zone entry for container ports.
- foo1_zoneid=$(as hv1 ovs-vsctl get bridge br-int external_ids:ct-zone-foo1)
-@@ -8986,8 +8975,10 @@ bar2_zoneid=$(as hv2 ovs-vsctl get bridge br-int external_ids:ct-zone-bar2)
- AT_CHECK([test -z $bar2_zoneid])
-
- # Add back bar2
-+wait_for_ports_up
- ovn-nbctl lsp-add bar bar2 vm2 1 \
- -- lsp-set-addresses bar2 "f0:00:00:01:02:08 192.168.2.3"
-+wait_for_ports_up
- ovn-nbctl --wait=hv sync
-
- bar2_zoneid=$(as hv2 ovs-vsctl get bridge br-int external_ids:ct-zone-bar2)
-@@ -9126,6 +9117,13 @@ OVS_WAIT_UNTIL([test xup = x$(ovn-nbctl lsp-get-up vm1)])
- OVS_WAIT_UNTIL([test xup = x$(ovn-nbctl lsp-get-up foo1)])
- OVS_WAIT_UNTIL([test xup = x$(ovn-nbctl lsp-get-up bar1)])
-
-+# Move VM1 to a new logical switch.
-+ovn-nbctl ls-add mgmt2
-+ovn-nbctl lsp-del vm1 -- lsp-add mgmt2 vm1
-+OVS_WAIT_UNTIL([test xup = x$(ovn-nbctl lsp-get-up vm1)])
-+OVS_WAIT_UNTIL([test xup = x$(ovn-nbctl lsp-get-up foo1)])
-+OVS_WAIT_UNTIL([test xup = x$(ovn-nbctl lsp-get-up bar1)])
-+
- as hv1 ovs-vsctl del-port vm1
- OVS_WAIT_UNTIL([test xdown = x$(ovn-nbctl lsp-get-up vm1)])
- OVS_WAIT_UNTIL([test xdown = x$(ovn-nbctl lsp-get-up foo1)])
-@@ -9267,8 +9265,8 @@ ovn-nbctl --wait=hv lsp-add bob bob1 \
- OVN_POPULATE_ARP
-
- # Allow some time for ovn-northd and ovn-controller to catch up.
--# XXX This should be more systematic.
--sleep 1
-+wait_for_ports_up
-+check ovn-nbctl --wait=hv sync
-
- trim_zeros() {
- sed 's/\(00\)\{1,\}$//'
-@@ -9375,7 +9373,8 @@ ovs-vsctl -- add-port br-int hv1-vif2 -- \
- ofport-request=2
-
- OVN_POPULATE_ARP
--sleep 2
-+wait_for_ports_up
-+check ovn-nbctl --wait=hv sync
- as hv1 ovs-vsctl show
-
- echo "*************************"
-@@ -9868,6 +9867,7 @@ check as gw1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
- check as gw2 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
- check as ext1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
-
-+wait_for_ports_up
- AT_CHECK([ovn-nbctl --wait=sb sync], [0], [ignore])
-
- ovn-sbctl dump-flows > sbflows
-@@ -9935,13 +9935,9 @@ test_ip_packet()
- fi
- as ext1 reset_pcap_file ext1-vif1 ext1/vif1
-
-- sleep 1
--
- # Resend packet from foo1 to outside1
- check as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
-
-- sleep 1
--
- AT_CAPTURE_FILE([exp])
- AT_CAPTURE_FILE([rcv])
- check_packets() {
-@@ -10131,6 +10127,7 @@ as gw1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
- as gw2 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
- as ext1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
-
-+wait_for_ports_up
- check ovn-nbctl --wait=sb sync
-
- ovn-sbctl dump-flows > sbflows
-@@ -10143,8 +10140,7 @@ hv1_ch_uuid=$(fetch_column Chassis _uuid name=hv1)
- wait_column "$hv1_ch_uuid" HA_Chassis_Group ref_chassis
-
- # Allow some time for ovn-northd and ovn-controller to catch up.
--# XXX This should be more systematic.
--sleep 2
-+check ovn-nbctl --wait=hv sync
-
- reset_pcap_file() {
- local iface=$1
-@@ -10342,6 +10338,7 @@ check as hv3 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
-
-
- dnl Allow some time for ovn-northd and ovn-controller to catch up.
-+wait_for_ports_up
- ovn-nbctl --wait=hv sync
-
- (echo "---------NB dump-----"
-@@ -10386,12 +10383,12 @@ AT_CAPTURE_FILE([hv2flows])
-
- AT_CHECK(
- [# Check that redirect mapping is programmed only on hv2
-- grep table=33 hv1flows | grep =0x3,metadata=0x1 | wc -l
-- grep table=33 hv2flows | grep =0x3,metadata=0x1 | grep load:0x2- | wc -l
-+ grep table=38 hv1flows | grep =0x3,metadata=0x1 | wc -l
-+ grep table=38 hv2flows | grep =0x3,metadata=0x1 | grep load:0x2- | wc -l
-
- # Check that hv1 sends chassisredirect port traffic to hv2
-- grep table=32 hv1flows | grep =0x3,metadata=0x1 | grep output | wc -l
-- grep table=32 hv2flows | grep =0x3,metadata=0x1 | wc -l
-+ grep table=37 hv1flows | grep =0x3,metadata=0x1 | grep output | wc -l
-+ grep table=37 hv2flows | grep =0x3,metadata=0x1 | wc -l
-
- # Check that arp reply on distributed gateway port is only programmed on hv2
- grep arp hv1flows | grep load:0x2- | grep =0x2,metadata=0x1 | wc -l
-@@ -10461,6 +10458,7 @@ OVS_WAIT_UNTIL([test 1 = `as hv2 ovs-vsctl show | \
- grep "Port patch-br-int-to-ln-alice" | wc -l`])
-
- dnl Allow some time for ovn-controller to catch up.
-+wait_for_ports_up
- ovn-nbctl --wait=hv sync
-
- # ARP for router IP address from outside1
-@@ -10534,8 +10532,8 @@ ovn-nbctl lsp-add foo foo2 \
- -- lsp-set-addresses foo2 "f0:00:00:01:02:06 192.168.1.3"
-
- # Allow some time for ovn-northd and ovn-controller to catch up.
--# XXX This should be more systematic.
--sleep 1
-+wait_for_ports_up
-+check ovn-nbctl --wait=hv sync
-
- : > hv1-vif2.expected
-
-@@ -10624,10 +10622,6 @@ AT_CHECK([ovn-nbctl lsp-set-addresses ln_port unknown])
- AT_CHECK([ovn-nbctl lsp-set-type ln_port localnet])
- AT_CHECK([ovn-nbctl --wait=hv lsp-set-options ln_port network_name=physnet1])
-
--# Allow some time for ovn-northd and ovn-controller to catch up.
--# XXX This should be more systematic.
--sleep 2
--
- # Expect no packets when hv2 bridge-mapping is not present
- : > packets
- OVN_CHECK_PACKETS([hv1/snoopvif-tx.pcap], [packets])
-@@ -10847,50 +10841,54 @@ ovn-nbctl lsp-set-addresses ln-outside unknown
- ovn-nbctl lsp-set-type ln-outside localnet
- ovn-nbctl lsp-set-options ln-outside network_name=phys
-
--# Allow some time for ovn-northd and ovn-controller to catch up.
--check ovn-nbctl --wait=hv sync
--
- # Check that there is a logical flow in logical switch foo's pipeline
- # to set the outport to rp-foo (which is expected).
- OVS_WAIT_UNTIL([test 1 = `ovn-sbctl dump-flows foo | grep ls_in_l2_lkup | \
- grep rp-foo | grep -v is_chassis_resident | grep priority=50 -c`])
-
- # Set the option 'reside-on-redirect-chassis' for foo
--check ovn-nbctl --wait=hv set logical_router_port foo options:reside-on-redirect-chassis=true
-+check ovn-nbctl set logical_router_port foo options:reside-on-redirect-chassis=true
-+wait_for_ports_up
-+check ovn-nbctl --wait=hv sync
-+
- # Check that there is a logical flow in logical switch foo's pipeline
- # to set the outport to rp-foo with the condition is_chassis_redirect.
--ovn-sbctl dump-flows foo
--OVS_WAIT_UNTIL([test 1 = `ovn-sbctl dump-flows foo | grep ls_in_l2_lkup | \
-+ovn-sbctl dump-flows foo > sbflows
-+AT_CAPTURE_FILE([sbflows])
-+OVS_WAIT_UNTIL([test 1 = `grep ls_in_l2_lkup sbflows | \
- grep rp-foo | grep is_chassis_resident | grep priority=50 -c`])
-
--echo "---------NB dump-----"
--ovn-nbctl show
--echo "---------------------"
--ovn-nbctl list logical_router
--echo "---------------------"
--ovn-nbctl list nat
--echo "---------------------"
--ovn-nbctl list logical_router_port
--echo "---------------------"
--
--echo "---------SB dump-----"
--ovn-sbctl list datapath_binding
--echo "---------------------"
--ovn-sbctl list port_binding
--echo "---------------------"
--ovn-sbctl dump-flows
--echo "---------------------"
--ovn-sbctl list chassis
--echo "---------------------"
-+(echo "---------NB dump-----"
-+ ovn-nbctl show
-+ echo "---------------------"
-+ ovn-nbctl list logical_router
-+ echo "---------------------"
-+ ovn-nbctl list nat
-+ echo "---------------------"
-+ ovn-nbctl list logical_router_port
-+ echo "---------------------") > nbdump
-+AT_CAPTURE_FILE([nbdump])
-+
-+(echo "---------SB dump-----"
-+ ovn-sbctl list datapath_binding
-+ echo "---------------------"
-+ ovn-sbctl list port_binding
-+ echo "---------------------"
-+ ovn-sbctl list chassis
-+ echo "---------------------") > sbdump
-+AT_CAPTURE_FILE([sbdump])
-
- for chassis in hv1 hv2 hv3; do
-- as $chassis
-- echo "------ $chassis dump ----------"
-- ovs-vsctl show br-int
-- ovs-ofctl show br-int
-- ovs-ofctl dump-flows br-int
-- echo "--------------------------"
-+ (as $chassis
-+ echo "------ $chassis dump ----------"
-+ ovs-vsctl show
-+ ovs-ofctl show br-int
-+ ovs-ofctl dump-flows br-int
-+ echo "--------------------------") > ${chassis}dump
- done
-+AT_CAPTURE_FILE([hv1dump])
-+AT_CAPTURE_FILE([hv2dump])
-+AT_CAPTURE_FILE([hv3dump])
-
- foo1_ip=$(ip_to_hex 192 168 1 2)
- gw_ip=$(ip_to_hex 172 16 1 6)
-@@ -10940,8 +10938,8 @@ as hv3 reset_pcap_file hv3-vif1 hv3/vif1
- as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
- sleep 2
-
--# On hv1, table 32 check that no packet goes via the tunnel port
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=32 \
-+# On hv1, table 37 check that no packet goes via the tunnel port
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=37 \
- | grep "NXM_NX_TUN_ID" | grep -v n_packets=0 | wc -l], [0], [[0
- ]])
-
-@@ -11083,8 +11081,8 @@ ovs-vsctl -- add-port br-int hv1-vif3 -- \
- ofport-request=3
-
- # Allow some time for ovn-northd and ovn-controller to catch up.
--# XXX This should be more systematic.
--sleep 1
-+wait_for_ports_up
-+check ovn-nbctl --wait=hv sync
-
- reset_pcap_file() {
- local iface=$1
-@@ -11337,8 +11335,11 @@ ovs-vsctl -- add-port br-int hv2-vif1 -- \
- OVN_POPULATE_ARP
-
- # Allow some time for ovn-northd and ovn-controller to catch up.
--# XXX This should be more systematic.
--sleep 1
-+wait_for_ports_up
-+check ovn-nbctl --wait=hv sync
-+
-+ovn-sbctl dump-flows > sbflows
-+AT_CAPTURE_FILE([sbflows])
-
- # Send ip packets between foo1 and alice1
- src_mac="f00000010203"
-@@ -11402,6 +11403,7 @@ for i in 1 2; do
- OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lp${i}1` = xup])
- done
-
-+wait_for_ports_up
- ovn-nbctl --wait=sb sync
- ovn-sbctl dump-flows
-
-@@ -11553,6 +11555,7 @@ ovn-nbctl lsp-set-type ln-outside localnet
- ovn-nbctl lsp-set-options ln-outside network_name=phys
-
- # Allow some time for ovn-northd and ovn-controller to catch up.
-+wait_for_ports_up
- check ovn-nbctl --wait=hv sync
-
- echo "---------NB dump-----"
-@@ -11626,20 +11629,20 @@ echo $hv2_gw1_ofport
- echo $hv2_gw2_ofport
-
- echo "--- hv1 ---"
--as hv1 ovs-ofctl dump-flows br-int table=32
-+as hv1 ovs-ofctl dump-flows br-int table=37
-
- echo "--- hv2 ---"
--as hv2 ovs-ofctl dump-flows br-int table=32
-+as hv2 ovs-ofctl dump-flows br-int table=37
-
- gw1_chassis=$(fetch_column Chassis _uuid name=gw1)
- gw2_chassis=$(fetch_column Chassis _uuid name=gw2)
-
--OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=32 | \
-+OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=37 | \
- grep active_backup | grep slaves:$hv1_gw1_ofport,$hv1_gw2_ofport \
- | wc -l], [0], [1
- ])
-
--OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=32 | \
-+OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=37 | \
- grep active_backup | grep slaves:$hv2_gw1_ofport,$hv2_gw2_ofport \
- | wc -l], [0], [1
- ])
-@@ -11676,15 +11679,16 @@ ovn-nbctl --id=@gc0 create Gateway_Chassis \
- set Logical_Router_Port outside 'gateway_chassis=[@gc0,@gc1]'
-
-
-+wait_for_ports_up
- check ovn-nbctl --wait=hv sync
-
- # we make sure that the hypervisors noticed, and inverted the slave ports
--OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=32 | \
-+OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=37 | \
- grep active_backup | grep slaves:$hv1_gw2_ofport,$hv1_gw1_ofport \
- | wc -l], [0], [1
- ])
-
--OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=32 | \
-+OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=37 | \
- grep active_backup | grep slaves:$hv2_gw2_ofport,$hv2_gw1_ofport \
- | wc -l], [0], [1
- ])
-@@ -11837,12 +11841,12 @@ ovn-nbctl set Logical_Router_Port outside ha_chassis_group=$hagrp1_uuid
- wait_row_count HA_Chassis_Group 1
- wait_row_count HA_Chassis 2
-
--OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=32 | \
-+OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=37 | \
- grep active_backup | grep slaves:$hv1_gw1_ofport,$hv1_gw2_ofport \
- | wc -l], [0], [1
- ])
-
--OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=32 | \
-+OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=37 | \
- grep active_backup | grep slaves:$hv2_gw1_ofport,$hv2_gw2_ofport \
- | wc -l], [0], [1
- ])
-@@ -11894,12 +11898,12 @@ wait_column "$exp_ref_ch_list" HA_Chassis_Group ref_chassis
- # Increase the priority of gw2
- ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 gw2 40
-
--OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=32 | \
-+OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=37 | \
- grep active_backup | grep slaves:$hv1_gw2_ofport,$hv1_gw1_ofport \
- | wc -l], [0], [1
- ])
-
--OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=32 | \
-+OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=37 | \
- grep active_backup | grep slaves:$hv2_gw2_ofport,$hv2_gw1_ofport \
- | wc -l], [0], [1
- ])
-@@ -12041,6 +12045,7 @@ AT_CHECK([ovn-nbctl lsp-set-type ln_port localnet])
- AT_CHECK([ovn-nbctl lsp-set-options ln_port network_name=physnet1])
-
- # wait for earlier changes to take effect
-+wait_for_ports_up
- check ovn-nbctl --wait=hv sync
-
- reset_pcap_file() {
-@@ -12241,6 +12246,7 @@ ovn-nbctl lsp-set-type ln-outside localnet
- ovn-nbctl lsp-set-options ln-outside network_name=phys
-
- # Allow some time for ovn-northd and ovn-controller to catch up.
-+wait_for_ports_up
- check ovn-nbctl --wait=hv sync
-
- # currently when ovn-controller is restarted, the old entry is deleted
-@@ -12878,6 +12884,45 @@ test_tcp_syn_packet() {
- check as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet
- }
-
-+# test_sctp_init_packet INPORT HV ETH_SRC ETH_DST IPV4_SRC IPV4_DST IP_CHKSUM SCTP_SPORT SCTP_DPORT SCTP_INIT_TAG SCTP_CHKSUM EXP_IP_CHKSUM EXP_SCTP_ABORT_CHKSUM
-+#
-+# Causes a packet to be received on INPORT of the hypervisor HV. The packet is an SCTP INIT chunk with
-+# ETH_SRC, ETH_DST, IPV4_SRC, IPV4_DST, IP_CHKSUM, SCTP_SPORT, SCTP_DPORT, and SCTP_CHKSUM as specified.
-+# The INIT "initiate_tag" will be set to SCTP_INIT_TAG.
-+# EXP_IP_CHKSUM and EXP_SCTP_CHKSUM are the ip and sctp checksums of the SCTP ABORT chunk generated from the ACL rule hit
-+#
-+# INPORT is an lport number, e.g. 11 for vif11.
-+# HV is a hypervisor number.
-+# ETH_SRC and ETH_DST are each 12 hex digits.
-+# IPV4_SRC and IPV4_DST are each 8 hex digits.
-+# SCTP_SPORT and SCTP_DPORT are 4 hex digits.
-+# IP_CHKSUM and EXP_IP_CHKSUM are 4 hex digits.
-+# SCTP_CHKSUM and EXP_SCTP_CHKSUM are 8 hex digits.
-+test_sctp_init_packet() {
-+ local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv4_src=$5 ipv4_dst=$6 ip_chksum=$7
-+ local sctp_sport=$8 sctp_dport=$9 sctp_init_tag=${10} sctp_chksum=${11}
-+ local exp_ip_chksum=${12} exp_sctp_abort_chksum=${13}
-+
-+ local ip_ttl=ff
-+ local eth_hdr=${eth_dst}${eth_src}0800
-+ local ip_hdr=4500002500004000${ip_ttl}84${ip_chksum}${ipv4_src}${ipv4_dst}
-+ local sctp_hdr=${sctp_sport}${sctp_dport}00000000${sctp_chksum}
-+ local sctp_init=01000014${sctp_init_tag}0000000000010001${sctp_init_tag}
-+
-+ local packet=${eth_hdr}${ip_hdr}${sctp_hdr}${sctp_init}
-+
-+ local sctp_abort_ttl=3f
-+ local reply_eth_hdr=${eth_src}${eth_dst}0800
-+ local reply_ip_hdr=4500002400004000${sctp_abort_ttl}84${exp_ip_chksum}${ipv4_dst}${ipv4_src}
-+ local reply_sctp_hdr=${sctp_dport}${sctp_sport}${sctp_init_tag}${exp_sctp_abort_chksum}
-+ local reply_sctp_abort=06000004
-+
-+ local reply=${reply_eth_hdr}${reply_ip_hdr}${reply_sctp_hdr}${reply_sctp_abort}
-+ echo $reply >> vif$inport.expected
-+
-+ check as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet
-+}
-+
- # Create hypervisors hv[123].
- # Add vif1[123] to hv1, vif2[123] to hv2, vif3[123] to hv3.
- # Add all of the vifs to a single logical switch sw0.
-@@ -12904,8 +12949,6 @@ for i in 1 2 3; do
- done
-
- OVN_POPULATE_ARP
--# allow some time for ovn-northd and ovn-controller to catch up.
--sleep 1
-
- for i in 1 2 3; do
- : > vif${i}1.expected
-@@ -12916,6 +12959,7 @@ check ovn-nbctl --log acl-add sw0 from-lport 1000 "inport == \"sw0-p11\"" reject
- check ovn-nbctl --log acl-add sw0 from-lport 1000 "inport == \"sw0-p21\"" reject
-
- # Allow some time for ovn-northd and ovn-controller to catch up.
-+wait_for_ports_up
- check ovn-nbctl --wait=hv sync
-
- ovn-sbctl dump-flows > sbflows
-@@ -12931,6 +12975,10 @@ test_tcp_syn_packet 11 1 000000000011 000000000021 $(ip_to_hex 192 168 1 11) $(i
- test_tcp_syn_packet 21 2 000000000021 000000000011 $(ip_to_hex 192 168 1 21) $(ip_to_hex 192 168 1 11) 0000 8b40 3039 0000 b85f 70e4
- test_tcp_syn_packet 31 3 000000000031 000000000012 $(ip_to_hex 192 168 1 31) $(ip_to_hex 192 168 1 12) 0000 8b40 3039 0000 b854 70d9
-
-+test_sctp_init_packet 11 1 000000000011 000000000021 $(ip_to_hex 192 168 1 11) $(ip_to_hex 192 168 1 21) 0000 8b40 3039 00000001 82112601 b7e5 10fe95b6
-+test_sctp_init_packet 21 2 000000000021 000000000011 $(ip_to_hex 192 168 1 21) $(ip_to_hex 192 168 1 11) 0000 8b40 3039 00000002 C0379D5A b7e5 39f23aaf
-+test_sctp_init_packet 31 3 000000000031 000000000012 $(ip_to_hex 192 168 1 31) $(ip_to_hex 192 168 1 12) 0000 8b40 3039 00000003 028E263C b7da 7124045b
-+
- for i in 1 2 3; do
- OVN_CHECK_PACKETS([hv$i/vif${i}1-tx.pcap], [vif${i}1.expected])
- done
-@@ -13062,8 +13110,8 @@ done
- OVN_POPULATE_ARP
-
- # Allow some time for ovn-northd and ovn-controller to catch up.
--# XXX This should be more systematic.
--sleep 1
-+wait_for_ports_up
-+check ovn-nbctl --wait=hv sync
-
- # test_ip INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT...
- #
-@@ -13142,10 +13190,6 @@ for is in 1 2 3; do
- done
- done
-
--# Allow some time for packet forwarding.
--# XXX This can be improved.
--sleep 1
--
- # Now check the packets actually received against the ones expected.
- for i in 1 2 3; do
- for j in 1 2 3; do
-@@ -13284,8 +13328,8 @@ done
- OVN_POPULATE_ARP
-
- # Allow some time for ovn-northd and ovn-controller to catch up.
--# XXX This should be more systematic.
--sleep 1
-+wait_for_ports_up
-+check ovn-nbctl --wait=hv sync
-
- lsp_to_mac() {
- echo f0:00:00:00:0${1:0:1}:${1:1:2}
-@@ -13391,10 +13435,6 @@ for is in 1 2 3; do
- done
- done
-
--# Allow some time for packet forwarding.
--# XXX This can be improved.
--sleep 1
--
- # Now check the packets actually received against the ones expected.
- for i in 1 2 3; do
- for j in 1 2 3; do
-@@ -13704,6 +13744,7 @@ grep conjunction.*conjunction.*conjunction | wc -l`])
- ovn-nbctl acl-del ls1 to-lport 1001 \
- 'ip4 && ip4.src == $set1 && ip4.dst == $set1'
-
-+wait_for_ports_up
- ovn-nbctl --wait=hv sync
- # priority=2001,ip,metadata=0x1,nw_dst=10.0.0.10 actions=conjunction(10,1/2)
- # priority=2001,ip,metadata=0x1,nw_dst=10.0.0.8 actions=conjunction(11,1/2)
-@@ -13725,27 +13766,30 @@ AT_CLEANUP
- AT_SETUP([ovn -- Superseding ACLs with conjunction])
- ovn_start
-
--ovn-nbctl ls-add ls1
-+check ovn-nbctl set nb_global . options:svc_monitor_mac=66:66:66:66:66:66
-+check ovn-nbctl ls-add ls1
-
--ovn-nbctl lsp-add ls1 ls1-lp1 \
---- lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01"
-+check ovn-nbctl lsp-add ls1 ls1-lp1 \
-+-- lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01" \
-+-- set logical_switch_port ls1-lp1 options:requested-tnl-key=1
-
--ovn-nbctl lsp-add ls1 ls1-lp2 \
---- lsp-set-addresses ls1-lp2 "f0:00:00:00:00:02"
-+check ovn-nbctl lsp-add ls1 ls1-lp2 \
-+-- lsp-set-addresses ls1-lp2 "f0:00:00:00:00:02" \
-+-- set logical_switch_port ls1-lp1 options:requested-tnl-key=2
-
- net_add n1
- sim_add hv1
-
- as hv1
--ovs-vsctl add-br br-phys
-+check ovs-vsctl add-br br-phys
- ovn_attach n1 br-phys 192.168.0.1
--ovs-vsctl -- add-port br-int hv1-vif1 -- \
-+check ovs-vsctl -- add-port br-int hv1-vif1 -- \
- set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \
- options:tx_pcap=hv1/vif1-tx.pcap \
- options:rxq_pcap=hv1/vif1-rx.pcap \
- ofport-request=1
-
--ovs-vsctl -- add-port br-int hv1-vif2 -- \
-+check ovs-vsctl -- add-port br-int hv1-vif2 -- \
- set interface hv1-vif2 external-ids:iface-id=ls1-lp2 \
- options:tx_pcap=hv1/vif2-tx.pcap \
- options:rxq_pcap=hv1/vif2-rx.pcap \
-@@ -13765,7 +13809,8 @@ test_ip() {
- local packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}\
- ${dst_ip}0035111100080000
- shift; shift; shift; shift; shift
-- as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
-+ check as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
-+ ovs-appctl ofproto/trace br-int in_port=hv1-vif1 "$packet" > trace
- for outport; do
- echo $packet >> $outport.expected
- done
-@@ -13774,19 +13819,51 @@ ${dst_ip}0035111100080000
- reset_pcap_file() {
- local iface=$1
- local pcap_file=$2
-- ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
-+ check ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
- options:rxq_pcap=dummy-rx.pcap
- rm -f ${pcap_file}*.pcap
-- ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \
-+ check ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \
- options:rxq_pcap=${pcap_file}-rx.pcap
- }
-
- # Add a default deny ACL and an allow ACL for specific IP traffic.
--ovn-nbctl acl-add ls1 to-lport 2 'arp' allow
--ovn-nbctl acl-add ls1 to-lport 1 'ip4' drop
--ovn-nbctl acl-add ls1 to-lport 3 '(ip4.src==10.0.0.1 || ip4.src==10.0.0.2) && (ip4.dst == 10.0.0.3 || ip4.dst == 10.0.0.4)' allow
--ovn-nbctl acl-add ls1 to-lport 3 '(ip4.src==10.0.0.1 || ip4.src==10.0.0.42) && (ip4.dst == 10.0.0.3 || ip4.dst == 10.0.0.4)' allow
--ovn-nbctl --wait=hv sync
-+check ovn-nbctl acl-add ls1 to-lport 2 'arp' allow
-+check ovn-nbctl acl-add ls1 to-lport 1 'ip4' drop
-+check ovn-nbctl acl-add ls1 to-lport 3 '(ip4.src==10.0.0.1 || ip4.src==10.0.0.2) && (ip4.dst == 10.0.0.3 || ip4.dst == 10.0.0.4)' allow
-+check ovn-nbctl acl-add ls1 to-lport 3 '(ip4.src==10.0.0.1 || ip4.src==10.0.0.42) && (ip4.dst == 10.0.0.3 || ip4.dst == 10.0.0.4)' allow
-+wait_for_ports_up
-+check ovn-nbctl --wait=hv sync
-+
-+ovn-sbctl dump-flows > sbflows
-+AT_CAPTURE_FILE([sbflows])
-+
-+# Traffic 10.0.0.1, 10.0.0.2 -> 10.0.0.3, 10.0.0.4 should be allowed.
-+for src in `seq 1 2`; do
-+ for dst in `seq 3 4`; do
-+ sip=`ip_to_hex 10 0 0 $src`
-+ dip=`ip_to_hex 10 0 0 $dst`
-+
-+ test_ip 1 f00000000001 f00000000002 $sip $dip 2
-+ done
-+done
-+
-+# Traffic 10.0.0.1, 10.0.0.2 -> 10.0.0.5 should be dropped.
-+dip=`ip_to_hex 10 0 0 5`
-+for src in `seq 1 2`; do
-+ sip=`ip_to_hex 10 0 0 $src`
-+
-+ test_ip 1 f00000000001 f00000000002 $sip $dip
-+done
-+
-+cat 2.expected > expout
-+$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
-+AT_CHECK([cat 2.packets], [0], [expout])
-+reset_pcap_file hv1-vif2 hv1/vif2
-+rm -f 2.packets
-+> 2.expected
-+
-+# Trigger recompute and make sure that the traffic still works as expected.
-+as hv1 ovn-appctl -t ovn-controller recompute
-
- # Traffic 10.0.0.1, 10.0.0.2 -> 10.0.0.3, 10.0.0.4 should be allowed.
- for src in `seq 1 2`; do
-@@ -13814,9 +13891,9 @@ rm -f 2.packets
- > 2.expected
-
- # Add two less restrictive allow ACLs for src IP 10.0.0.1.
--ovn-nbctl acl-add ls1 to-lport 3 'ip4.src==10.0.0.1 || ip4.src==10.0.0.1' allow
--ovn-nbctl acl-add ls1 to-lport 3 'ip4.src==10.0.0.1' allow
--ovn-nbctl --wait=hv sync
-+check ovn-nbctl acl-add ls1 to-lport 3 'ip4.src==10.0.0.1 || ip4.src==10.0.0.1' allow
-+check ovn-nbctl acl-add ls1 to-lport 3 'ip4.src==10.0.0.1' allow
-+check ovn-nbctl --wait=hv sync
-
- # Check OVS flows, the less restrictive flows should have been installed.
- AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \
-@@ -13858,11 +13935,9 @@ reset_pcap_file hv1-vif2 hv1/vif2
- rm -f 2.packets
- > 2.expected
-
--#sleep infinity
--
- # Remove the first less restrictive allow ACL.
--ovn-nbctl acl-del ls1 to-lport 3 'ip4.src==10.0.0.1 || ip4.src==10.0.0.1'
--ovn-nbctl --wait=hv sync
-+check ovn-nbctl acl-del ls1 to-lport 3 'ip4.src==10.0.0.1 || ip4.src==10.0.0.1'
-+check ovn-nbctl --wait=hv sync
-
- # Check OVS flows, the second less restrictive allow ACL should have been installed.
- AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \
-@@ -13878,8 +13953,8 @@ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \
- ])
-
- # Remove the less restrictive allow ACL.
--ovn-nbctl acl-del ls1 to-lport 3 'ip4.src==10.0.0.1'
--ovn-nbctl --wait=hv sync
-+check ovn-nbctl acl-del ls1 to-lport 3 'ip4.src==10.0.0.1'
-+check ovn-nbctl --wait=hv sync
-
- # Check OVS flows, the 10.0.0.1 conjunction should have been reinstalled.
- AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \
-@@ -13917,8 +13992,8 @@ $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
- AT_CHECK([cat 2.packets], [0], [expout])
-
- # Re-add the less restrictive allow ACL for src IP 10.0.0.1
--ovn-nbctl acl-add ls1 to-lport 3 'ip4.src==10.0.0.1' allow
--ovn-nbctl --wait=hv sync
-+check ovn-nbctl acl-add ls1 to-lport 3 'ip4.src==10.0.0.1' allow
-+check ovn-nbctl --wait=hv sync
-
- # Check OVS flows, the less restrictive flows should have been installed.
- AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \
-@@ -13933,6 +14008,29 @@ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \
- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction()
- ])
-
-+# Add another ACL that overlaps with the existing less restrictive ones.
-+check ovn-nbctl acl-add ls1 to-lport 3 'udp || ((ip4.src==10.0.0.1 || ip4.src==10.0.0.2) && (ip4.dst == 10.0.0.3 || ip4.dst == 10.0.0.4))' allow
-+check ovn-nbctl --wait=hv sync
-+
-+# Check OVS flows, the same conjunctive flows as above should still be there,
-+# with an additional conjunction action.
-+#
-+# New non-conjunctive flows should be added to match on 'udp'.
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \
-+ grep "priority=1003" | \
-+ sed 's/conjunction([[^)]]*)/conjunction()/g' | sort], [0], [dnl
-+ table=45, priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,46)
-+ table=45, priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,46)
-+ table=45, priority=1003,conj_id=4,ip,metadata=0x1 actions=resubmit(,46)
-+ table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction(),conjunction()
-+ table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction(),conjunction()
-+ table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,46)
-+ table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction(),conjunction()
-+ table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction()
-+ table=45, priority=1003,udp,metadata=0x1 actions=resubmit(,46)
-+ table=45, priority=1003,udp6,metadata=0x1 actions=resubmit(,46)
-+])
-+
- OVN_CLEANUP([hv1])
- AT_CLEANUP
-
-@@ -13983,8 +14081,8 @@ ovn-nbctl create Address_Set name=set1 addresses=\"f0:00:00:00:00:11\",\"f0:00:0
- OVN_POPULATE_ARP
-
- # Allow some time for ovn-northd and ovn-controller to catch up.
--# XXX This should be more systematic.
--sleep 1
-+wait_for_ports_up
-+check ovn-nbctl --wait=hv sync
-
- # Make sure there is no attempt to adding duplicated flows by ovn-controller
- AT_FAIL_IF([test -n "`grep duplicate hv1/ovn-controller.log`"])
-@@ -14224,6 +14322,7 @@ done
-
- OVN_POPULATE_ARP
- # allow some time for ovn-northd and ovn-controller to catch up.
-+wait_for_ports_up
- ovn-nbctl --wait=hv sync
-
- test_ip_packet 1 1 000000000001 00000000ff01 $(ip_to_hex 192 168 1 1) $(ip_to_hex 192 168 2 1) $(ip_to_hex 192 168 1 254) 0000 f87c ea96
-@@ -14294,6 +14393,45 @@ test_tcp_syn_packet() {
- as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet
- }
-
-+# test_sctp_init_packet INPORT HV ETH_SRC ETH_DST IPV4_SRC IPV4_DST IP_CHKSUM SCTP_SPORT SCTP_DPORT SCTP_INIT_TAG SCTP_CHKSUM EXP_IP_CHKSUM EXP_SCTP_ABORT_CHKSUM
-+#
-+# Causes a packet to be received on INPORT of the hypervisor HV. The packet is an SCTP INIT chunk with
-+# ETH_SRC, ETH_DST, IPV4_SRC, IPV4_DST, IP_CHKSUM, SCTP_SPORT, SCTP_DPORT, and SCTP_CHKSUM as specified.
-+# The INIT "initiate_tag" will be set to SCTP_INIT_TAG.
-+# EXP_IP_CHKSUM and EXP_SCTP_CHKSUM are the ip and sctp checksums of the SCTP ABORT chunk generated by OVN logical router
-+#
-+# INPORT is an lport number, e.g. 1 for vif1.
-+# HV is a hypervisor number.
-+# ETH_SRC and ETH_DST are each 12 hex digits.
-+# IPV4_SRC and IPV4_DST are each 8 hex digits.
-+# SCTP_SPORT and SCTP_DPORT are 4 hex digits.
-+# IP_CHKSUM and EXP_IP_CHKSUM are 4 hex digits.
-+# SCTP_CHKSUM and EXP_SCTP_CHKSUM are 8 hex digits.
-+test_sctp_init_packet() {
-+ local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv4_src=$5 ipv4_dst=$6 ip_chksum=$7
-+ local sctp_sport=$8 sctp_dport=$9 sctp_init_tag=${10} sctp_chksum=${11}
-+ local exp_ip_chksum=${12} exp_sctp_abort_chksum=${13}
-+
-+ local ip_ttl=ff
-+ local eth_hdr=${eth_dst}${eth_src}0800
-+ local ip_hdr=4500002500004000${ip_ttl}84${ip_chksum}${ipv4_src}${ipv4_dst}
-+ local sctp_hdr=${sctp_sport}${sctp_dport}00000000${sctp_chksum}
-+ local sctp_init=01000014${sctp_init_tag}0000000000010001${sctp_init_tag}
-+
-+ local packet=${eth_hdr}${ip_hdr}${sctp_hdr}${sctp_init}
-+
-+ local sctp_abort_ttl=3e
-+ local reply_eth_hdr=${eth_src}${eth_dst}0800
-+ local reply_ip_hdr=4500002400004000${sctp_abort_ttl}84${exp_ip_chksum}${ipv4_dst}${ipv4_src}
-+ local reply_sctp_hdr=${sctp_dport}${sctp_sport}${sctp_init_tag}${exp_sctp_abort_chksum}
-+ local reply_sctp_abort=06000004
-+
-+ local reply=${reply_eth_hdr}${reply_ip_hdr}${reply_sctp_hdr}${reply_sctp_abort}
-+ echo $reply >> vif$inport.expected
-+
-+ check as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet
-+}
-+
- # test_tcp6_packet INPORT HV ETH_SRC ETH_DST IPV6_SRC IPV6_ROUTER TCP_SPORT TCP_DPORT TCP_CHKSUM EXP_TCP_RST_CHKSUM
- #
- # Causes a packet to be received on INPORT of the hypervisor HV. The packet is a TCP syn segment with
-@@ -14314,6 +14452,36 @@ test_tcp6_packet() {
- as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet
- }
-
-+# test_tcp6_packet INPORT HV ETH_SRC ETH_DST IPV6_SRC IPV6_ROUTER SCTP_SPORT SCTP_DPORT SCTP_INIT_TAG SCTP_CHKSUM EXP_SCTP_ABORT_CHKSUM
-+#
-+# Causes a packet to be received on INPORT of the hypervisor HV. The packet is an SCTP INIT chunk with
-+# ETH_SRC, ETH_DST, IPV6_SRC, IPV6_ROUTER, SCTP_SPORT, SCTP_DPORT and SCTP_CHKSUM as specified.
-+# The INIT "initiate_tag" will be set to SCTP_INIT_TAG.
-+# EXP_SCTP_CHKSUM is the sctp checksum of the SCTP ABORT chunk generated by OVN logical router
-+test_sctp6_packet() {
-+ local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv6_src=$5 ipv6_router=$6
-+ local sctp_sport=$7 sctp_dport=$8 sctp_init_tag=$9 sctp_chksum=${10}
-+ local exp_sctp_abort_chksum=${11}
-+ shift 11
-+
-+ local eth_hdr=${eth_dst}${eth_src}86dd
-+ local ip_hdr=60000000002084ff${ipv6_src}${ipv6_router}
-+ local sctp_hdr=${sctp_sport}${sctp_dport}00000000${sctp_chksum}
-+ local sctp_init=01000014${sctp_init_tag}0000000000010001${sctp_init_tag}
-+
-+ local packet=${eth_hdr}${ip_hdr}${sctp_hdr}${sctp_init}
-+
-+ local reply_eth_hdr=${eth_src}${eth_dst}86dd
-+ local reply_ip_hdr=600000000010843e${ipv6_router}${ipv6_src}
-+ local reply_sctp_hdr=${sctp_dport}${sctp_sport}${sctp_init_tag}${exp_sctp_abort_chksum}
-+ local reply_sctp_abort=06000004
-+
-+ local reply=${reply_eth_hdr}${reply_ip_hdr}${reply_sctp_hdr}${reply_sctp_abort}
-+ echo $reply >> vif$inport.expected
-+
-+ check as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet
-+}
-+
- # test_ip6_packet INPORT HV ETH_SRC ETH_DST IPV6_SRC IPV6_DST IPV6_PROTO IPV6_LEN DATA EXP_ICMP_CODE EXP_ICMP_CHKSUM
- #
- # Causes a packet to be received on INPORT of the hypervisor HV. The packet is an IPv6
-@@ -14365,16 +14533,17 @@ done
-
- OVN_POPULATE_ARP
- # allow some time for ovn-northd and ovn-controller to catch up.
-+wait_for_ports_up
- ovn-nbctl --wait=hv sync
-
- test_ip_packet 1 1 000000000001 00000000ff01 $(ip_to_hex 192 168 1 1) $(ip_to_hex 192 168 1 254) 11 0000 f87c f485 0303
--test_ip_packet 1 1 000000000001 00000000ff01 $(ip_to_hex 192 168 1 1) $(ip_to_hex 192 168 1 254) 84 0000 f87c f413 0302
- test_ip6_packet 1 1 000000000001 00000000ff01 20010db8000100000000000000000011 20010db8000100000000000000000001 11 0015 dbb8303900155bac6b646f65206676676e6d66720a 0104 1d31
- OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
-
- test_tcp_syn_packet 2 2 000000000002 00000000ff02 $(ip_to_hex 192 168 2 1) $(ip_to_hex 192 168 2 254) 0000 8b40 3039 0000 b680 6e05
--test_ip6_packet 2 2 000000000002 00000000ff02 20010db8000200000000000000000011 20010db8000200000000000000000001 84 0004 01020304 0103 5e74
-+test_sctp_init_packet 2 2 000000000002 00000000ff02 $(ip_to_hex 192 168 2 1) $(ip_to_hex 192 168 2 254) 0000 8b40 3039 00000001 82112601 b606 10fe95b6
- test_tcp6_packet 2 2 000000000002 00000000ff02 20010db8000200000000000000000011 20010db8000200000000000000000001 8b40 3039 0000 98cd
-+test_sctp6_packet 2 2 000000000002 00000000ff02 20010db8000200000000000000000011 20010db8000200000000000000000001 8b40 3039 00000002 C0379D5A 39f23aaf
- OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [vif2.expected])
-
- OVN_CLEANUP([hv1], [hv2])
-@@ -14439,7 +14608,8 @@ ovs-vsctl -- add-port br-int hv2-vif1 -- \
-
- OVN_POPULATE_ARP
-
--sleep 1
-+wait_for_ports_up
-+check ovn-nbctl --wait=hv sync
-
- packet="inport==\"sw1-p1\" && eth.src==$sw1_p1_mac && eth.dst==$sw1_ro_mac &&
- ip4 && ip.ttl==64 && ip4.src==$sw1_p1_ip && ip4.dst==$sw2_p1_ip &&
-@@ -14632,6 +14802,8 @@ OVS_WAIT_UNTIL(
- logical_port=ls1-lp_ext1`
- test "$chassis" = "$hv1_uuid"])
-
-+wait_for_ports_up ls1-lp_ext1
-+
- # There should be DHCPv4/v6 OF flows for the ls1-lp_ext1 port in hv1
- (ovn-sbctl dump-flows lr0; ovn-sbctl dump-flows ls1) > sbflows
- as hv1 ovs-ofctl dump-flows br-int > brintflows
-@@ -14912,6 +15084,7 @@ OVS_WAIT_UNTIL(
- [chassis=`ovn-sbctl --bare --columns chassis find port_binding \
- logical_port=ls1-lp_ext1`
- test "$chassis" = "$hv2_uuid"])
-+wait_for_ports_up ls1-lp_ext1
-
- # There should be OF flows for DHCP4/v6 for the ls1-lp_ext1 port in hv2
- AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | \
-@@ -15026,6 +15199,7 @@ OVS_WAIT_UNTIL(
- [chassis=`ovn-sbctl --bare --columns chassis find port_binding \
- logical_port=ls1-lp_ext1`
- test "$chassis" = "$hv1_uuid"])
-+wait_for_ports_up ls1-lp_ext1
-
- as hv1
- ovs-vsctl show
-@@ -15106,6 +15280,7 @@ OVS_WAIT_UNTIL(
- [chassis=`ovn-sbctl --bare --columns chassis find port_binding \
- logical_port=ls1-lp_ext1`
- test "$chassis" = "$hv3_uuid"])
-+wait_for_ports_up ls1-lp_ext1
-
- as hv1
- ovs-vsctl show
-@@ -15190,11 +15365,12 @@ OVS_WAIT_UNTIL(
- [chassis=`ovn-sbctl --bare --columns chassis find port_binding \
- logical_port=ls1-lp_ext1`
- test "$chassis" = "$hv1_uuid"])
-+wait_for_ports_up ls1-lp_ext1
-
- # There should be a flow in hv2 to drop traffic from ls1-lp_ext1 destined
- # to router mac.
- AT_CHECK([as hv2 ovs-ofctl dump-flows br-int \
--table=28,dl_src=f0:00:00:00:00:03,dl_dst=a0:10:00:00:00:01 | \
-+table=30,dl_src=f0:00:00:00:00:03,dl_dst=a0:10:00:00:00:01 | \
- grep -c "actions=drop"], [0], [1
- ])
-
-@@ -15207,6 +15383,7 @@ OVS_WAIT_UNTIL(
- [chassis=`ovn-sbctl --bare --columns chassis find port_binding \
- logical_port=ls1-lp_ext1`
- test "$chassis" = "$hv2_uuid"])
-+wait_for_ports_up ls1-lp_ext1
-
- as hv1
- OVS_APP_EXIT_AND_WAIT([ovs-vswitchd])
-@@ -15357,7 +15534,8 @@ ovs-vsctl -- add-port br-int hv2-vif1 -- \
-
- OVN_POPULATE_ARP
-
--sleep 1
-+wait_for_ports_up
-+check ovn-nbctl --wait=hv sync
-
- packet="inport==\"sw1-p1\" && eth.src==$sw1_p1_mac && eth.dst==$sw1_ro_mac &&
- ip4 && ip.ttl==64 && ip4.src==$sw1_p1_ip && ip4.dst==$sw2_p1_ip &&
-@@ -15640,6 +15818,7 @@ test_ip6_packet_larger() {
- fi
- }
-
-+wait_for_ports_up
- ovn-nbctl --wait=hv sync
-
- ovn-nbctl show > nbdump
-@@ -15789,6 +15968,7 @@ ovn-nbctl lsp-add sw1 rp-sw1 -- set Logical_Switch_Port rp-sw1 \
-
- ovn-nbctl lsp-add sw0 sw0-p0 \
- -- lsp-set-addresses sw0-p0 "f0:00:00:01:02:03 192.168.1.2 2001::2"
-+
- ovn-nbctl lsp-add sw0 sw0-p1 \
- -- lsp-set-addresses sw0-p1 "f0:00:00:11:02:03 192.168.1.3 2001::3"
-
-@@ -15799,6 +15979,7 @@ ovn-nbctl lr-nat-add lr0 snat 172.16.1.1 192.168.1.0/24
- ovn-nbctl lr-nat-add lr0 snat 2002::1 2001::/64
-
- OVN_POPULATE_ARP
-+wait_for_ports_up
- ovn-nbctl --wait=hv sync
-
- ovn-sbctl dump-flows > sbflows
-@@ -15847,6 +16028,14 @@ ovn-nbctl --wait=hv sync
- ovn-sbctl dump-flows > sbflows2
- AT_CAPTURE_FILE([sbflows2])
-
-+# create a route policy for pkt marking
-+check ovn-nbctl lr-policy-add lr0 2000 "ip4.src == 192.168.1.3" allow
-+policy=$(fetch_column nb:Logical_Router_Policy _uuid priority=2000)
-+check ovn-nbctl set logical_router_policy $policy options:pkt_mark=100
-+as hv2
-+# add a flow in egress pipeline to check pkt marking
-+ovs-ofctl --protocols=OpenFlow13 add-flow br-int "table=37,priority=200,ip,nw_src=172.16.1.2,pkt_mark=0x64 actions=resubmit(,38)"
-+
- dst_ip=$(ip_to_hex 172 16 2 10)
- fip_ip=$(ip_to_hex 172 16 1 2)
- src_ip=$(ip_to_hex 192 168 1 3)
-@@ -15857,6 +16046,8 @@ echo $(get_arp_req f00000010204 $fip_ip $gw_router_ip) >> expected
- send_arp_reply 2 1 $gw_router_mac f00000010204 $gw_router_ip $fip_ip
- echo "${gw_router_mac}f0000001020408004500001c00004000fe0121b4${fip_ip}${dst_ip}${data}" >> expected
-
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=37 | grep pkt_mark=0x64 | grep -q n_packets=1],[0])
-+
- OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
-
- OVN_CLEANUP([hv1],[hv2])
-@@ -16045,6 +16236,7 @@ for i in 1 2 3 4 5; do
- done
-
- dnl Wait for the changes to be propagated
-+wait_for_ports_up
- check ovn-nbctl --wait=hv sync
-
- dnl Assert that each Chassis has a tunnel formed to every other Chassis
-@@ -16324,6 +16516,7 @@ ovn-nbctl lrp-add router router-to-ls2 00:00:01:01:02:05 192.168.2.3/24
- ovn-nbctl lsp-add ls1 ls1-to-router -- set Logical_Switch_Port ls1-to-router type=router options:router-port=router-to-ls1 -- lsp-set-addresses ls1-to-router router
- ovn-nbctl lsp-add ls2 ls2-to-router -- set Logical_Switch_Port ls2-to-router type=router options:router-port=router-to-ls2 -- lsp-set-addresses ls2-to-router router
-
-+wait_for_ports_up
- ovn-nbctl --wait=sb sync
- #ovn-sbctl dump-flows
-
-@@ -16500,6 +16693,7 @@ ovn-nbctl lsp-set-type sw0-vir virtual
- ovn-nbctl set logical_switch_port sw0-vir options:virtual-ip=10.0.0.10
- ovn-nbctl set logical_switch_port sw0-vir options:virtual-parents=sw0-p1,sw0-p2,sw0-p3
-
-+wait_for_ports_up
- ovn-nbctl --wait=hv sync
-
- # Check that logical flows are added for sw0-vir in lsp_in_arp_rsp pipeline
-@@ -16555,12 +16749,10 @@ spa=$(ip_to_hex 10 0 0 10)
- tpa=$(ip_to_hex 10 0 0 10)
- send_garp 1 1 $eth_src $eth_dst $spa $tpa
-
--OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \
--logical_port=sw0-vir) = x$hv1_ch_uuid], [0], [])
--
--AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \
--logical_port=sw0-vir) = xsw0-p1])
--
-+wait_row_count Port_Binding 1 logical_port=sw0-vir chassis=$hv1_ch_uuid
-+check_row_count Port_Binding 1 logical_port=sw0-vir virtual_parent=sw0-p1
-+wait_for_ports_up sw0-vir
-+check ovn-nbctl --wait=hv sync
-
- # There should be an arp resolve flow to resolve the virtual_ip with the
- # sw0-p1's MAC.
-@@ -16578,6 +16770,8 @@ ovn-sbctl clear port_binding $pb_uuid virtual_parent
- OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \
- logical_port=sw0-vir) = x])
-
-+wait_row_count nb:Logical_Switch_Port 1 up=false name=sw0-vir
-+
- # From sw0-p0 resend GARP for 10.0.0.10. hv1 should reclaim sw0-vir
- # and sw0-p1 should be its virtual_parent.
- send_garp 1 1 $eth_src $eth_dst $spa $tpa
-@@ -16588,6 +16782,8 @@ logical_port=sw0-vir) = x$hv1_ch_uuid], [0], [])
- AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \
- logical_port=sw0-vir) = xsw0-p1])
-
-+wait_for_ports_up sw0-vir
-+
- # From sw0-p3 send GARP for 10.0.0.10. hv1 should claim sw0-vir
- # and sw0-p3 should be its virtual_parent.
- eth_src=505400000005
-@@ -16602,6 +16798,7 @@ logical_port=sw0-vir) = x$hv1_ch_uuid], [0], [])
- OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \
- logical_port=sw0-vir) = xsw0-p3])
-
-+wait_for_ports_up sw0-vir
-
- # There should be an arp resolve flow to resolve the virtual_ip with the
- # sw0-p2's MAC.
-@@ -16627,6 +16824,7 @@ logical_port=sw0-vir) = x$hv2_ch_uuid], [0], [])
- AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \
- logical_port=sw0-vir) = xsw0-p2])
-
-+wait_for_ports_up sw0-vir
-
- # There should be an arp resolve flow to resolve the virtual_ip with the
- # sw0-p3's MAC.
-@@ -16652,6 +16850,8 @@ sleep 1
- AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \
- logical_port=sw0-vir) = xsw0-p1])
-
-+wait_for_ports_up sw0-vir
-+
- ovn-sbctl dump-flows lr0 > lr0-flows5
- AT_CAPTURE_FILE([lr0-flows5])
- AT_CHECK([grep lr_in_arp_resolve lr0-flows5 | grep "reg0 == 10.0.0.10" | sed 's/table=../table=??/'], [0], [dnl
-@@ -16668,6 +16868,8 @@ sleep 1
- AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \
- logical_port=sw0-vir) = x])
-
-+wait_row_count nb:Logical_Switch_Port 1 up=false name=sw0-vir
-+
- # Since the sw0-vir is not claimed by any chassis, eth.dst should be set to
- # zero if the ip4.dst is the virtual ip.
- ovn-sbctl dump-flows lr0 > lr0-flows6
-@@ -16691,6 +16893,8 @@ sleep 1
- AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \
- logical_port=sw0-vir) = xsw0-p2])
-
-+wait_for_ports_up sw0-vir
-+
- ovn-sbctl dump-flows lr0 > lr0-flows7
- AT_CAPTURE_FILE([lr0-flows7])
- AT_CHECK([grep lr_in_arp_resolve lr0-flows7 | grep "reg0 == 10.0.0.10" | sed 's/table=../table=??/'], [0], [dnl
-@@ -16705,6 +16909,8 @@ logical_port=sw0-vir) = x], [0], [])
- AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \
- logical_port=sw0-vir) = x])
-
-+wait_row_count nb:Logical_Switch_Port 1 up=false name=sw0-vir
-+
- # Clear virtual_ip column of sw0-vir. There should be no bind_vport flows.
- ovn-nbctl --wait=hv remove logical_switch_port sw0-vir options virtual-ip
-
-@@ -16807,22 +17013,22 @@ ovs-vsctl -- add-port br-int vif33 -- \
- options:rxq_pcap=hv$i/vif33-rx.pcap \
- ofport-request=33
-
--ovn-nbctl --wait=hv set NB_Global . options:controller_event=true
--ovn-nbctl lb-add lb0 192.168.1.100:80 ""
-+ovn-nbctl --event lb-add lb0 192.168.1.100:80 ""
- ovn-nbctl ls-lb-add sw0 lb0
- uuid_lb0=$(ovn-nbctl --bare --columns=_uuid find load_balancer name=lb0)
-
--ovn-nbctl lb-add lb1 192.168.2.100:80 ""
-+ovn-nbctl --event lb-add lb1 192.168.2.100:80 ""
- ovn-nbctl lr-lb-add lr0 lb1
- uuid_lb1=$(ovn-nbctl --bare --columns=_uuid find load_balancer name=lb1)
-
--ovn-nbctl lb-add lb2 [[2001::10]]:50051 ""
-+ovn-nbctl --event lb-add lb2 [[2001::10]]:50051 ""
- ovn-nbctl ls-lb-add sw0 lb2
- uuid_lb2=$(ovn-nbctl --bare --columns=_uuid find load_balancer name=lb2)
-
- ovn-nbctl --wait=hv meter-add event-elb drop 100 pktps 10
-
- OVN_POPULATE_ARP
-+wait_for_ports_up
- check ovn-nbctl --wait=hv sync
- ovn-sbctl lflow-list > sbflows
- AT_CAPTURE_FILE([sbflows])
-@@ -16889,6 +17095,8 @@ AT_CHECK_UNQUOTED([ovn-sbctl get controller_event $uuid event_info:load_balancer
- "$uuid_lb2"
- ])
-
-+AT_CHECK_UNQUOTED([ovn-trace sw0 'inport == "sw0-p11" && eth.src == 00:00:00:00:00:11 && ip4.dst == 192.168.1.100 && tcp && tcp.dst == 80' | grep -q 'event = "empty_lb_backends"'], [0])
-+
- OVN_CLEANUP([hv1], [hv2])
- AT_CLEANUP
-
-@@ -17159,6 +17367,7 @@ AT_CAPTURE_FILE([sbflows3])
- cp ovn-sb/ovn-sb.db ovn-sb3.db
- ovn-sbctl dump-flows > sbflows3
-
-+AS_BOX([IGMP traffic test 1])
- # Send traffic and make sure it gets forwarded only on the two ports that
- # joined.
- > expected
-@@ -17207,6 +17416,7 @@ send_igmp_v3_report hv1-vif1 hv1 \
- wait_row_count IGMP_Group 1 address=239.0.1.68
- check ovn-nbctl --wait=hv sync
-
-+AS_BOX([IGMP traffic test 2])
- # Send traffic and make sure it gets forwarded only on the port that joined.
- as hv1 reset_pcap_file hv1-vif1 hv1/vif1
- as hv2 reset_pcap_file hv2-vif1 hv2/vif1
-@@ -17246,6 +17456,7 @@ send_igmp_v3_report hv1-vif1 hv1 \
- # Check that the IGMP Group is learned.
- wait_row_count IGMP_Group 1 address=224.0.0.42
-
-+AS_BOX([IGMP traffic test 3])
- # Send traffic and make sure it gets flooded to all ports.
- as hv1 reset_pcap_file hv1-vif1 hv1/vif1
- as hv1 reset_pcap_file hv1-vif2 hv1/vif2
-@@ -17275,6 +17486,7 @@ check ovn-nbctl set Logical_Switch sw2 \
- other_config:mcast_eth_src="00:00:00:00:02:fe" \
- other_config:mcast_ip4_src="20.0.0.254"
-
-+AS_BOX([IGMP traffic test 4])
- # Wait for 1 query interval (1 sec) and check that two queries are generated.
- > expected
- store_igmp_v3_query 0000000002fe $(ip_to_hex 20 0 0 254) 84dd expected
-@@ -17296,6 +17508,7 @@ check ovn-nbctl set Logical_Switch sw3 \
-
- check ovn-nbctl --wait=hv sync
-
-+AS_BOX([IGMP traffic test 5])
- # Send traffic from sw3 and make sure rtr doesn't relay it.
- > expected_empty
-
-@@ -17345,6 +17558,7 @@ send_igmp_v3_report hv2-vif3 hv2 \
- wait_row_count IGMP_Group 2 address=239.0.1.68
- check ovn-nbctl --wait=hv sync
-
-+AS_BOX([IGMP traffic test 6])
- # Send traffic from sw3 and make sure it is relayed by rtr.
- # to ports that joined.
- > expected_routed_sw1
-@@ -17394,6 +17608,7 @@ send_igmp_v3_report hv1-vif4 hv1 \
- wait_row_count IGMP_Group 3 address=239.0.1.68
- check ovn-nbctl --wait=hv sync
-
-+AS_BOX([IGMP traffic test 7])
- # Send traffic from sw3 and make sure it is relayed by rtr
- # to ports that joined.
- > expected_routed_sw1
-@@ -17493,6 +17708,7 @@ send_igmp_v3_report hv1-vif2 hv1 \
- wait_row_count IGMP_Group 1 address=239.0.1.68
- check ovn-nbctl --wait=hv sync
-
-+AS_BOX([IGMP traffic test 8])
- # Send traffic from sw1-p21
- send_ip_multicast_pkt hv2-vif1 hv2 \
- 000000000001 01005e000144 \
-@@ -17790,6 +18006,7 @@ check ovs-vsctl -- add-port br-int hv2-vif4 -- \
- ofport-request=1
- check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
-
-+wait_for_ports_up
- check ovn-nbctl --wait=hv sync
-
- AT_CAPTURE_FILE([sbflows])
-@@ -18470,6 +18687,7 @@ m4_define([DVR_N_S_ARP_HANDLING],
-
- # Set a hypervisor as gateway chassis, for router port 172.31.0.1
- ovn-nbctl lrp-set-gateway-chassis router-to-underlay hv3
-+ wait_for_ports_up
- ovn-nbctl --wait=sb sync
-
- wait_row_count Port_Binding 1 logical_port=cr-router-to-underlay
-@@ -18689,6 +18907,7 @@ m4_define([DVR_N_S_PING],
- ovn-nbctl lrp-set-gateway-chassis router-to-underlay hv3
- ovn-nbctl lrp-set-redirect-type router-to-underlay bridged
-
-+ wait_for_ports_up
- ovn-nbctl --wait=sb sync
-
-
-@@ -18816,7 +19035,7 @@ m4_define([DVR_N_S_PING],
- OVN_CHECK_PACKETS_REMOVE_BROADCAST([hv4/vif-north-tx.pcap], [vif-north.expected])
-
- # Confirm that packets did not go out via tunnel port.
-- AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=33 | grep NXM_NX_TUN_METADATA0 | grep n_packets=0 | wc -l], [0], [[0
-+ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=38 | grep NXM_NX_TUN_METADATA0 | grep n_packets=0 | wc -l], [0], [[0
- ]])
-
- # Confirm that packet went out via localnet port
-@@ -18919,6 +19138,7 @@ ovn-nbctl lsp-set-addresses sw1-lr0 00:00:00:00:ff:02
- ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1
-
- OVN_POPULATE_ARP
-+wait_for_ports_up
- ovn-nbctl --wait=hv sync
-
- as hv1 ovs-appctl -t ovn-controller vlog/set dbg
-@@ -18945,7 +19165,8 @@ list mac_binding], [0], [lr0-sw0
- 50:54:00:00:00:03
- ])
-
--AT_CHECK([test 1 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
-+AT_CHECK([test 1 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | \
-+grep table_id=10 | wc -l`])
- AT_CHECK([test 1 = `as hv1 ovs-ofctl dump-flows br-int table=10 | grep arp | \
- grep controller | grep -v n_packets=0 | wc -l`])
-
-@@ -18962,7 +19183,8 @@ OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int table=67 | grep n_p
-
- # The packet should not be sent to ovn-controller. The packet
- # count should be 1 only.
--AT_CHECK([test 1 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
-+AT_CHECK([test 1 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | \
-+grep table_id=10 | wc -l`])
- AT_CHECK([test 1 = `as hv1 ovs-ofctl dump-flows br-int table=10 | grep arp | \
- grep controller | grep -v n_packets=0 | wc -l`])
-
-@@ -18975,7 +19197,8 @@ send_garp 1 1 $eth_src $eth_dst $spa $tpa
-
- # The garp packet should be sent to ovn-controller and the mac_binding entry
- # should be updated.
--OVS_WAIT_UNTIL([test 2 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
-+OVS_WAIT_UNTIL([test 2 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | \
-+grep table_id=10 | wc -l`])
-
- check_row_count MAC_Binding 1
-
-@@ -19000,7 +19223,8 @@ send_garp 1 1 $eth_src $eth_dst $spa $tpa
-
- # The garp packet should be sent to ovn-controller and the mac_binding entry
- # should be updated.
--OVS_WAIT_UNTIL([test 3 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
-+OVS_WAIT_UNTIL([test 3 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | \
-+grep table_id=10 | wc -l`])
-
- OVS_WAIT_UNTIL(
- [test 1 = `as hv1 ovs-ofctl dump-flows br-int table=67 | grep dl_src=50:54:00:00:00:33 \
-@@ -19021,7 +19245,8 @@ OVS_WAIT_UNTIL(
- | grep n_packets=1 | wc -l`]
- )
-
--AT_CHECK([test 3 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
-+AT_CHECK([test 3 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | \
-+grep table_id=10 | wc -l`])
-
- # Now send ARP reply packet with IP - 10.0.0.40 and mac 505400000023
- eth_src=505400000023
-@@ -19038,7 +19263,8 @@ send_arp_reply 1 1 $eth_src $eth_dst $spa $tpa
-
- # The garp packet should be sent to ovn-controller and the mac_binding entry
- # should be updated.
--OVS_WAIT_UNTIL([test 4 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
-+OVS_WAIT_UNTIL([test 4 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | \
-+grep table_id=10 | wc -l`])
-
- # Wait for an entry in table=67 for the learnt mac_binding entry.
-
-@@ -19054,7 +19280,8 @@ OVS_WAIT_UNTIL(
- | grep n_packets=1 | wc -l`]
- )
-
--AT_CHECK([test 4 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
-+AT_CHECK([test 4 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | \
-+grep table_id=10 | wc -l`])
-
- send_arp_reply 1 1 $eth_src $eth_dst $spa $tpa
- OVS_WAIT_UNTIL(
-@@ -19062,7 +19289,8 @@ OVS_WAIT_UNTIL(
- | grep n_packets=2 | wc -l`]
- )
-
--AT_CHECK([test 4 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
-+AT_CHECK([test 4 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | \
-+grep table_id=10 | wc -l`])
-
- OVN_CLEANUP([hv1], [hv2])
- AT_CLEANUP
-@@ -19100,8 +19328,7 @@ ovn-nbctl lsp-add ls1 lp11
- ovn-nbctl lsp-set-addresses lp11 "f0:00:00:00:00:11"
- ovn-nbctl lsp-set-port-security lp11 f0:00:00:00:00:11
-
--OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lp11` = xup])
--
-+wait_for_ports_up
- ovn-nbctl --wait=sb sync
-
- ovn-nbctl show
-@@ -19270,6 +19497,7 @@ ovn-nbctl lrp-set-gateway-chassis router-to-underlay hv3
- ovn-nbctl --stateless lr-nat-add router dnat_and_snat 172.31.0.100 192.168.1.1
- ovn-nbctl lrp-set-redirect-type router-to-underlay bridged
-
-+wait_for_ports_up
- ovn-nbctl --wait=sb sync
-
-
-@@ -19534,6 +19762,7 @@ check ovn-nbctl lsp-set-options ln-public network_name=public
- check ovn-nbctl --wait=hv lrp-set-gateway-chassis lr0-public hv1 20
-
- OVN_POPULATE_ARP
-+wait_for_ports_up
- check ovn-nbctl --wait=hv sync
-
- wait_row_count Service_Monitor 2
-@@ -19542,7 +19771,7 @@ AT_CAPTURE_FILE([sbflows])
- OVS_WAIT_FOR_OUTPUT(
- [ovn-sbctl dump-flows > sbflows
- ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 | sed 's/table=..//'], 0,
-- [ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80; hash_fields="ip_dst,ip_src,tcp_dst,tcp_src");)
-+ [ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80; hash_fields="ip_dst,ip_src,tcp_dst,tcp_src");)
- ])
-
- AT_CAPTURE_FILE([sbflows2])
-@@ -19722,6 +19951,7 @@ ovn-nbctl lsp-set-options ln-public network_name=public
- ovn-nbctl --wait=hv lrp-set-gateway-chassis lr0-public hv1 20
-
- OVN_POPULATE_ARP
-+wait_for_ports_up
- ovn-nbctl --wait=hv sync
-
- # And now for the anticlimax. We need to ensure that there is no
-@@ -19861,6 +20091,7 @@ check ovs-vsctl -- add-port br-int hv1-vif2 -- \
- ofport-request=3
-
- OVN_POPULATE_ARP
-+wait_for_ports_up
- check ovn-nbctl --wait=hv sync
-
- ovn-sbctl dump-flows > sbflows
-@@ -20216,6 +20447,7 @@ ovn-nbctl lsp-add lsw0 lp1
- ovn-nbctl lsp-set-addresses lp1 "f0:00:00:00:00:01 10.0.0.1"
- ovn-nbctl acl-add lsw0 from-lport 1000 'eth.type == 0x1234' drop
-
-+wait_for_ports_up
- check ovn-nbctl --wait=hv sync
-
- # Trace with --ovs should see ovs flow related to the ACL
-@@ -20310,6 +20542,7 @@ for az in `seq 1 $n_az`; do
- 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
-@@ -20485,6 +20718,7 @@ ovs-vsctl -- add-port br-int hv1-vif3 -- \
-
- # wait for earlier changes to take effect
- check ovn-nbctl --wait=hv sync
-+wait_for_ports_up
-
- ovn-sbctl dump-flows > sbflows
- AT_CAPTURE_FILE([sbflows])
-@@ -20672,8 +20906,9 @@ build_tcp_syn() {
-
- send_ipv4_pkt() {
- local hv=$1 inport=$2 eth_src=$3 eth_dst=$4
-- local ip_src=$5 ip_dst=$6 ip_proto=$7 ip_len=$8 ip_chksum=$9
-- local l4_payload=${10}
-+ local ip_src=$5 ip_dst=$6 ip_proto=$7 ip_len=$8
-+ local l4_payload=$9
-+ local hp_ip_src=${10}
- local hp_l4_payload=${11}
- local outfile=${12}
-
-@@ -20681,8 +20916,10 @@ send_ipv4_pkt() {
-
- local eth=${eth_dst}${eth_src}0800
- local hp_eth=${eth_src}${eth_dst}0800
-- local ip=4500${ip_len}00004000${ip_ttl}${ip_proto}${ip_chksum}${ip_src}${ip_dst}
-- local hp_ip=4500${ip_len}00004000${ip_ttl}${ip_proto}${ip_chksum}${ip_dst}${ip_src}
-+ local ip=4500${ip_len}00004000${ip_ttl}${ip_proto}0000${ip_src}${ip_dst}
-+ ip=$(ip4_csum_inplace $ip)
-+ local hp_ip=4500${ip_len}00004000${ip_ttl}${ip_proto}0000${hp_ip_src}${ip_src}
-+ hp_ip=$(ip4_csum_inplace ${hp_ip})
- local packet=${eth}${ip}${l4_payload}
- local hp_packet=${hp_eth}${hp_ip}${hp_l4_payload}
-
-@@ -20694,15 +20931,16 @@ send_ipv6_pkt() {
- local hv=$1 inport=$2 eth_src=$3 eth_dst=$4
- local ip_src=$5 ip_dst=$6 ip_proto=$7 ip_len=$8
- local l4_payload=$9
-- local hp_l4_payload=${10}
-- local outfile=${11}
-+ local hp_ip_src=${10}
-+ local hp_l4_payload=${11}
-+ local outfile=${12}
-
- local ip_ttl=40
-
- local eth=${eth_dst}${eth_src}86dd
- local hp_eth=${eth_src}${eth_dst}86dd
- local ip=60000000${ip_len}${ip_proto}${ip_ttl}${ip_src}${ip_dst}
-- local hp_ip=60000000${ip_len}${ip_proto}${ip_ttl}${ip_dst}${ip_src}
-+ local hp_ip=60000000${ip_len}${ip_proto}${ip_ttl}${hp_ip_src}${ip_src}
- local packet=${eth}${ip}${l4_payload}
- local hp_packet=${hp_eth}${hp_ip}${hp_l4_payload}
-
-@@ -20724,16 +20962,26 @@ ovs-vsctl -- add-port br-int hv1-vif1 -- \
-
- # One logical switch with IPv4 and IPv6 load balancers that hairpin the
- # traffic.
-+# Also create "duplicate" load balancers, i.e., different VIPs using the same
-+# backends.
- ovn-nbctl ls-add sw
- ovn-nbctl lsp-add sw lsp -- lsp-set-addresses lsp 00:00:00:00:00:01
--ovn-nbctl lb-add lb-ipv4-tcp 88.88.88.88:8080 42.42.42.1:4041 tcp
--ovn-nbctl lb-add lb-ipv4-udp 88.88.88.88:4040 42.42.42.1:2021 udp
--ovn-nbctl lb-add lb-ipv6-tcp [[8800::0088]]:8080 [[4200::1]]:4041 tcp
--ovn-nbctl lb-add lb-ipv6-udp [[8800::0088]]:4040 [[4200::1]]:2021 udp
-+ovn-nbctl lb-add lb-ipv4-tcp 88.88.88.88:8080 42.42.42.1:4041 tcp
-+ovn-nbctl lb-add lb-ipv4-tcp-dup 88.88.88.89:8080 42.42.42.1:4041 tcp
-+ovn-nbctl lb-add lb-ipv4-udp 88.88.88.88:4040 42.42.42.1:2021 udp
-+ovn-nbctl lb-add lb-ipv4-udp-dup 88.88.88.89:4040 42.42.42.1:2021 udp
-+ovn-nbctl lb-add lb-ipv6-tcp [[8800::0088]]:8080 [[4200::1]]:4041 tcp
-+ovn-nbctl lb-add lb-ipv6-tcp-dup [[8800::0089]]:8080 [[4200::1]]:4041 tcp
-+ovn-nbctl lb-add lb-ipv6-udp [[8800::0088]]:4040 [[4200::1]]:2021 udp
-+ovn-nbctl lb-add lb-ipv6-udp-dup [[8800::0089]]:4040 [[4200::1]]:2021 udp
- ovn-nbctl ls-lb-add sw lb-ipv4-tcp
-+ovn-nbctl ls-lb-add sw lb-ipv4-tcp-dup
- ovn-nbctl ls-lb-add sw lb-ipv4-udp
-+ovn-nbctl ls-lb-add sw lb-ipv4-udp-dup
- ovn-nbctl ls-lb-add sw lb-ipv6-tcp
-+ovn-nbctl ls-lb-add sw lb-ipv6-tcp-dup
- ovn-nbctl ls-lb-add sw lb-ipv6-udp
-+ovn-nbctl ls-lb-add sw lb-ipv6-udp-dup
-
- ovn-nbctl lr-add rtr
- ovn-nbctl lrp-add rtr rtr-sw 00:00:00:00:01:00 42.42.42.254/24 4200::00ff/64
-@@ -20743,67 +20991,332 @@ ovn-nbctl lsp-add sw sw-rtr \
- -- lsp-set-options sw-rtr router-port=rtr-sw
-
- ovn-nbctl --wait=hv sync
-+wait_for_ports_up
-
--# Inject IPv4 TCP packet from lsp.
-+ovn-sbctl dump-flows > sbflows
-+AT_CAPTURE_FILE([sbflows])
- > expected
-+
-+AS_BOX([IPv4 TCP Hairpin])
-+
-+# Inject IPv4 TCP packets from lsp.
- tcp_payload=$(build_tcp_syn 84d0 1f90 05a7)
- hp_tcp_payload=$(build_tcp_syn 84d0 0fc9 156e)
- send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \
- $(ip_to_hex 42 42 42 1) $(ip_to_hex 88 88 88 88) \
-- 06 0028 35f5 \
-- ${tcp_payload} ${hp_tcp_payload} \
-+ 06 0028 \
-+ ${tcp_payload} \
-+ $(ip_to_hex 88 88 88 88) ${hp_tcp_payload} \
- expected
-
--ovn-sbctl dump-flows > sbflows
--AT_CAPTURE_FILE([sbflows])
-+tcp_payload=$(build_tcp_syn 84d1 1f90 05a5)
-+hp_tcp_payload=$(build_tcp_syn 84d1 0fc9 156c)
-+send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \
-+ $(ip_to_hex 42 42 42 1) $(ip_to_hex 88 88 88 89) \
-+ 06 0028 \
-+ ${tcp_payload} \
-+ $(ip_to_hex 88 88 88 89) ${hp_tcp_payload} \
-+ expected
-+
-+# Check that traffic is hairpinned.
-+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
-+
-+# Check learned hairpin reply flows.
-+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
-+])
-+
-+# Change LB Hairpin SNAT IP.
-+# Also flush conntrack to avoid reusing an existing entry.
-+as hv1 ovs-appctl dpctl/flush-conntrack
-+
-+ovn-nbctl --wait=hv set load_balancer lb-ipv4-tcp options:hairpin_snat_ip="88.88.88.87"
-+# Inject IPv4 TCP packets from lsp.
-+tcp_payload=$(build_tcp_syn 84d0 1f90 05a7)
-+hp_tcp_payload=$(build_tcp_syn 84d0 0fc9 156f)
-+send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \
-+ $(ip_to_hex 42 42 42 1) $(ip_to_hex 88 88 88 88) \
-+ 06 0028 \
-+ ${tcp_payload} \
-+ $(ip_to_hex 88 88 88 87) ${hp_tcp_payload} \
-+ expected
-+
-+tcp_payload=$(build_tcp_syn 84d1 1f90 05a5)
-+hp_tcp_payload=$(build_tcp_syn 84d1 0fc9 156c)
-+send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \
-+ $(ip_to_hex 42 42 42 1) $(ip_to_hex 88 88 88 89) \
-+ 06 0028 \
-+ ${tcp_payload} \
-+ $(ip_to_hex 88 88 88 89) ${hp_tcp_payload} \
-+ expected
-
- # Check that traffic is hairpinned.
- OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
-
--# Inject IPv4 UDP packet from lsp.
-+# Check learned hairpin reply flows.
-+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
-+])
-+
-+AS_BOX([IPv4 UDP Hairpin])
-+
-+# Inject IPv4 UDP packets from lsp.
- udp_payload=$(build_udp 84d0 0fc8 6666)
- hp_udp_payload=$(build_udp 84d0 07e5 6e49)
- send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \
- $(ip_to_hex 42 42 42 1) $(ip_to_hex 88 88 88 88) \
-- 11 001e 35f4 \
-- ${udp_payload} ${hp_udp_payload} \
-+ 11 001e \
-+ ${udp_payload} \
-+ $(ip_to_hex 88 88 88 88) ${hp_udp_payload} \
-+ expected
-+
-+udp_payload=$(build_udp 84d1 0fc8 6664)
-+hp_udp_payload=$(build_udp 84d1 07e5 6e47)
-+send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \
-+ $(ip_to_hex 42 42 42 1) $(ip_to_hex 88 88 88 89) \
-+ 11 001e \
-+ ${udp_payload} \
-+ $(ip_to_hex 88 88 88 89) ${hp_udp_payload} \
-+ expected
-+
-+# Check that traffic is hairpinned.
-+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
-+
-+# Check learned hairpin reply flows.
-+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+])
-+
-+# Change LB Hairpin SNAT IP.
-+# Also flush conntrack to avoid reusing an existing entry.
-+as hv1 ovs-appctl dpctl/flush-conntrack
-+ovn-nbctl --wait=hv set load_balancer lb-ipv4-udp options:hairpin_snat_ip="88.88.88.87"
-+# Inject IPv4 UDP packets from lsp.
-+udp_payload=$(build_udp 84d0 0fc8 6666)
-+hp_udp_payload=$(build_udp 84d0 07e5 6e4a)
-+send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \
-+ $(ip_to_hex 42 42 42 1) $(ip_to_hex 88 88 88 88) \
-+ 11 001e \
-+ ${udp_payload} \
-+ $(ip_to_hex 88 88 88 87) ${hp_udp_payload} \
-+ expected
-+
-+udp_payload=$(build_udp 84d1 0fc8 6664)
-+hp_udp_payload=$(build_udp 84d1 07e5 6e47)
-+send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \
-+ $(ip_to_hex 42 42 42 1) $(ip_to_hex 88 88 88 89) \
-+ 11 001e \
-+ ${udp_payload} \
-+ $(ip_to_hex 88 88 88 89) ${hp_udp_payload} \
- expected
-
- # Check that traffic is hairpinned.
- OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
-
--# Inject IPv6 TCP packet from lsp.
-+# Check learned hairpin reply flows.
-+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+])
-+
-+AS_BOX([IPv6 TCP Hairpin])
-+
-+# Inject IPv6 TCP packets from lsp.
- tcp_payload=$(build_tcp_syn 84d0 1f90 3ff9)
- hp_tcp_payload=$(build_tcp_syn 84d0 0fc9 4fc0)
- send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \
- 42000000000000000000000000000001 88000000000000000000000000000088 \
- 06 0014 \
-- ${tcp_payload} ${hp_tcp_payload} \
-+ ${tcp_payload} \
-+ 88000000000000000000000000000088 ${hp_tcp_payload} \
- expected
-
--# Check that traffic is hairpinned.
--OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
--
--# Inject IPv6 UDP packet from lsp.
--udp_payload=$(build_udp 84d0 0fc8 a0b8)
--hp_udp_payload=$(build_udp 84d0 07e5 a89b)
-+tcp_payload=$(build_tcp_syn 84d1 1f90 3ff7)
-+hp_tcp_payload=$(build_tcp_syn 84d1 0fc9 4fbe)
- send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \
-- 42000000000000000000000000000001 88000000000000000000000000000088 \
-- 11 000a \
-- ${udp_payload} ${hp_udp_payload} \
-+ 42000000000000000000000000000001 88000000000000000000000000000089 \
-+ 06 0014 \
-+ ${tcp_payload} \
-+ 88000000000000000000000000000089 ${hp_tcp_payload} \
- expected
-
- # Check that traffic is hairpinned.
- OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
-
--OVN_CLEANUP([hv1])
--AT_CLEANUP
-+# Check learned hairpin reply flows.
-+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::89,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+])
-
--AT_SETUP([ovn -- Big Load Balancer])
--ovn_start
-+# Change LB Hairpin SNAT IP.
-+# Also flush conntrack to avoid reusing an existing entry.
-+as hv1 ovs-appctl dpctl/flush-conntrack
-+ovn-nbctl --wait=hv set load_balancer lb-ipv6-tcp options:hairpin_snat_ip="8800::0087"
-
--ovn-nbctl ls-add ls1
--ovn-nbctl lsp-add ls1 lsp1
-+# Inject IPv6 TCP packets from lsp.
-+tcp_payload=$(build_tcp_syn 84d0 1f90 3ff9)
-+hp_tcp_payload=$(build_tcp_syn 84d0 0fc9 4fc1)
-+send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \
-+ 42000000000000000000000000000001 88000000000000000000000000000088 \
-+ 06 0014 \
-+ ${tcp_payload} \
-+ 88000000000000000000000000000087 ${hp_tcp_payload} \
-+ expected
-+
-+tcp_payload=$(build_tcp_syn 84d1 1f90 3ff7)
-+hp_tcp_payload=$(build_tcp_syn 84d1 0fc9 4fbe)
-+send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \
-+ 42000000000000000000000000000001 88000000000000000000000000000089 \
-+ 06 0014 \
-+ ${tcp_payload} \
-+ 88000000000000000000000000000089 ${hp_tcp_payload} \
-+ expected
-+
-+# Check that traffic is hairpinned.
-+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
-+
-+# Check learned hairpin reply flows.
-+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::89,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+])
-+
-+AS_BOX([IPv6 UDP Hairpin])
-+
-+# Inject IPv6 UDP packets from lsp.
-+udp_payload=$(build_udp 84d0 0fc8 a0b8)
-+hp_udp_payload=$(build_udp 84d0 07e5 a89b)
-+send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \
-+ 42000000000000000000000000000001 88000000000000000000000000000088 \
-+ 11 000a \
-+ ${udp_payload} \
-+ 88000000000000000000000000000088 ${hp_udp_payload} \
-+ expected
-+
-+udp_payload=$(build_udp 84d1 0fc8 a0b6)
-+hp_udp_payload=$(build_udp 84d1 07e5 a899)
-+send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \
-+ 42000000000000000000000000000001 88000000000000000000000000000089 \
-+ 11 000a \
-+ ${udp_payload} \
-+ 88000000000000000000000000000089 ${hp_udp_payload} \
-+ expected
-+
-+# Check that traffic is hairpinned.
-+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
-+
-+# Check learned hairpin reply flows.
-+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::89,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::89,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+])
-+
-+# Change LB Hairpin SNAT IP.
-+# Also flush conntrack to avoid reusing an existing entry.
-+as hv1 ovs-appctl dpctl/flush-conntrack
-+ovn-nbctl --wait=hv set load_balancer lb-ipv6-udp options:hairpin_snat_ip="8800::0087"
-+
-+# Inject IPv6 UDP packets from lsp.
-+udp_payload=$(build_udp 84d0 0fc8 a0b8)
-+hp_udp_payload=$(build_udp 84d0 07e5 a89b)
-+send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \
-+ 42000000000000000000000000000001 88000000000000000000000000000088 \
-+ 11 000a \
-+ ${udp_payload} \
-+ 88000000000000000000000000000087 ${hp_udp_payload} \
-+ expected
-+
-+udp_payload=$(build_udp 84d1 0fc8 a0b6)
-+hp_udp_payload=$(build_udp 84d1 07e5 a899)
-+send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \
-+ 42000000000000000000000000000001 88000000000000000000000000000089 \
-+ 11 000a \
-+ ${udp_payload} \
-+ 88000000000000000000000000000089 ${hp_udp_payload} \
-+ expected
-+
-+# Check learned hairpin reply flows.
-+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::89,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::87,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::89,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+])
-+
-+AS_BOX([Delete VIP])
-+check ovn-nbctl --wait=hv set Load_Balancer lb-ipv4-tcp vips='"88.88.88.88:8080"=""'
-+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::89,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::87,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::89,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+])
-+
-+AS_BOX([Delete LB])
-+check ovn-nbctl --wait=hv \
-+ -- lb-del lb-ipv4-tcp \
-+ -- lb-del lb-ipv4-tcp-dup \
-+ -- lb-del lb-ipv4-udp \
-+ -- lb-del lb-ipv4-udp-dup
-+
-+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::89,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::87,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::89,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+])
-+
-+check ovn-nbctl --wait=hv \
-+ -- lb-del lb-ipv6-tcp \
-+ -- lb-del lb-ipv6-tcp-dup
-+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=69, udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::87,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+ table=69, udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::89,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+])
-+
-+check ovn-nbctl --wait=hv \
-+ -- lb-del lb-ipv6-udp \
-+ -- lb-del lb-ipv6-udp-dup
-+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [1], [dnl
-+])
-+
-+OVN_CLEANUP([hv1])
-+AT_CLEANUP
-+
-+AT_SETUP([ovn -- Big Load Balancer])
-+ovn_start
-+
-+ovn-nbctl ls-add ls1
-+ovn-nbctl lsp-add ls1 lsp1
-
- net_add n1
- sim_add hv1
-@@ -20936,6 +21449,7 @@ check ovn-nbctl lsp-set-options ln-public network_name=public
- check ovn-nbctl lrp-set-gateway-chassis lr0-public hv1 20
- check ovn-nbctl lr-nat-add lr0 snat 172.168.0.100 10.0.0.0/24
- check ovn-nbctl --wait=hv sync
-+wait_for_ports_up
-
- wait_row_count datapath_binding 1 external-ids:name=lr0
- lr0_dp_uuid=$(ovn-sbctl --bare --columns _uuid list datapath_binding lr0)
-@@ -21156,31 +21670,31 @@ AT_CHECK([
-
- AT_CHECK([ovn-sbctl lflow-list | grep -E "lr_in_policy.*priority=1001" | sort], [0], [dnl
- table=12(lr_in_policy ), priority=1001 , dnl
--match=(ip6), action=(pkt.mark = 4294967295; next;)
-+match=(ip6), action=(pkt.mark = 4294967295; reg8[[0..15]] = 0; next;)
- ])
-
- ovn-nbctl --wait=hv set logical_router_policy $pol5 options:pkt_mark=-1
- AT_CHECK([ovn-sbctl lflow-list | grep -E "lr_in_policy.*priority=1001" | sort], [0], [dnl
- table=12(lr_in_policy ), priority=1001 , dnl
--match=(ip6), action=(next;)
-+match=(ip6), action=(reg8[[0..15]] = 0; next;)
- ])
-
- ovn-nbctl --wait=hv set logical_router_policy $pol5 options:pkt_mark=2147483648
- AT_CHECK([ovn-sbctl lflow-list | grep -E "lr_in_policy.*priority=1001" | sort], [0], [dnl
- table=12(lr_in_policy ), priority=1001 , dnl
--match=(ip6), action=(pkt.mark = 2147483648; next;)
-+match=(ip6), action=(pkt.mark = 2147483648; reg8[[0..15]] = 0; next;)
- ])
-
- ovn-nbctl --wait=hv set logical_router_policy $pol5 options:pkt_mark=foo
- AT_CHECK([ovn-sbctl lflow-list | grep -E "lr_in_policy.*priority=1001" | sort], [0], [dnl
- table=12(lr_in_policy ), priority=1001 , dnl
--match=(ip6), action=(next;)
-+match=(ip6), action=(reg8[[0..15]] = 0; next;)
- ])
-
- ovn-nbctl --wait=hv set logical_router_policy $pol5 options:pkt_mark=4294967296
- AT_CHECK([ovn-sbctl lflow-list | grep -E "lr_in_policy.*priority=1001" | sort], [0], [dnl
- table=12(lr_in_policy ), priority=1001 , dnl
--match=(ip6), action=(next;)
-+match=(ip6), action=(reg8[[0..15]] = 0; next;)
- ])
-
- OVN_CLEANUP([hv1])
-@@ -21602,22 +22116,22 @@ AT_CHECK([test ! -z $p1_zoneid])
- p2_zoneid=$(as hv1 ovs-vsctl get bridge br-int external_ids:ct-zone-sw0-p2 | sed 's/"//g')
- AT_CHECK([test ! -z $p2_zoneid])
-
--AT_CHECK([test $(ovs-ofctl dump-flows br-int table=33,metadata=${sw0_dpkey},\
-+AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw0_dpkey},\
- reg15=0x${p1_dpkey} | grep REG13 | wc -l) -eq 1])
-
--AT_CHECK([test $(ovs-ofctl dump-flows br-int table=33,metadata=${sw0_dpkey},\
-+AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw0_dpkey},\
- reg15=0x${p1_dpkey} | grep "load:0x${p1_zoneid}->NXM_NX_REG13" | wc -l) -eq 1])
-
--AT_CHECK([test $(ovs-ofctl dump-flows br-int table=33,metadata=${sw1_dpkey},\
-+AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw1_dpkey},\
- reg15=0x${p2_dpkey} | grep REG13 | wc -l) -eq 1])
-
--AT_CHECK([test $(ovs-ofctl dump-flows br-int table=33,metadata=${sw1_dpkey},\
-+AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw1_dpkey},\
- reg15=0x${p2_dpkey} | grep "load:0x${p2_zoneid}->NXM_NX_REG13" | wc -l) -eq 1])
-
- ovs-vsctl set interface hv1-vif1 external_ids:iface-id=foo
- OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p1) = xdown])
-
--AT_CHECK([test $(ovs-ofctl dump-flows br-int table=33,metadata=${sw0_dpkey},\
-+AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw0_dpkey},\
- reg15=0x${p1_dpkey} | grep REG13 | wc -l) -eq 0])
-
- p1_zoneid=$(as hv1 ovs-vsctl get bridge br-int external_ids:ct-zone-sw0-p1 | sed 's/"//g')
-@@ -21629,16 +22143,16 @@ OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p1) = xup])
- p1_zoneid=$(as hv1 ovs-vsctl get bridge br-int external_ids:ct-zone-sw0-p1 | sed 's/"//g')
- AT_CHECK([test ! -z $p1_zoneid])
-
--AT_CHECK([test $(ovs-ofctl dump-flows br-int table=33,metadata=${sw0_dpkey},\
-+AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw0_dpkey},\
- reg15=0x${p1_dpkey} | grep REG13 | wc -l) -eq 1])
-
--AT_CHECK([test $(ovs-ofctl dump-flows br-int table=33,metadata=${sw0_dpkey},\
-+AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw0_dpkey},\
- reg15=0x${p1_dpkey} | grep "load:0x${p1_zoneid}->NXM_NX_REG13" | wc -l) -eq 1])
-
- ovs-vsctl del-port hv1-vif2
- OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p2) = xdown])
-
--AT_CHECK([test $(ovs-ofctl dump-flows br-int table=33,metadata=${sw0_dpkey},\
-+AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw0_dpkey},\
- reg15=0x${p2_dpkey} | grep REG13 | wc -l) -eq 0])
-
- p2_zoneid=$(as hv1 ovs-vsctl get bridge br-int external_ids:ct-zone-sw0-p2 | sed 's/"//g')
-@@ -21646,7 +22160,7 @@ AT_CHECK([test -z $p2_zoneid])
-
- ovn-nbctl lsp-del sw0-p1
-
--OVS_WAIT_UNTIL([test $(ovs-ofctl dump-flows br-int table=33,metadata=${sw0_dpkey},\
-+OVS_WAIT_UNTIL([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw0_dpkey},\
- reg15=0x${p1_dpkey} | grep REG13 | wc -l) -eq 0])
-
- p1_zoneid=$(as hv1 ovs-vsctl get bridge br-int external_ids:ct-zone-sw0-p1 | sed 's/"//g')
-@@ -21723,6 +22237,7 @@ check ovn-nbctl --policy="src-ip" lr-route-add DR 10.0.0.0/24 20.0.0.2
- check ovn-nbctl --ecmp-symmetric-reply --policy="src-ip" lr-route-add GW 10.0.0.0/24 172.16.0.2
- check ovn-nbctl --ecmp-symmetric-reply --policy="src-ip" lr-route-add GW 10.0.0.0/24 172.16.0.3
-
-+wait_for_ports_up
- check ovn-nbctl --wait=hv sync
-
- # Ensure ECMP symmetric reply flows are not present on any hypervisor.
-@@ -21753,26 +22268,25 @@ ovn-nbctl set Logical_Router $gw_uuid options:chassis=hv1
- ovn-nbctl --wait=hv sync
-
- # And ensure that ECMP symmetric reply flows are present only on hv1
--AT_CHECK([
-- test 1 -eq $(as hv1 ovs-ofctl dump-flows br-int table=15 | \
-- grep "priority=100" | \
-- grep "ct(commit,zone=NXM_NX_REG11\\[[0..15\\]],exec(move:NXM_OF_ETH_SRC\\[[\\]]->NXM_NX_CT_LABEL\\[[32..79\\]],load:0x[[0-9]]->NXM_NX_CT_LABEL\\[[80..95\\]]))" -c)
--])
--AT_CHECK([
-- test 1 -eq $(as hv1 ovs-ofctl dump-flows br-int table=21 | \
-- grep "priority=200" | \
-- grep "actions=move:NXM_NX_CT_LABEL\\[[32..79\\]]->NXM_OF_ETH_DST\\[[\\]]" -c)
--])
-+as hv1 ovs-ofctl dump-flows br-int > hv1flows
-+AT_CAPTURE_FILE([hv1flows])
-+as hv2 ovs-ofctl dump-flows br-int > hv2flows
-+AT_CAPTURE_FILE([hv2flows])
-
- AT_CHECK([
-- test 0 -eq $(as hv2 ovs-ofctl dump-flows br-int table=15 | \
-- grep "priority=100" | \
-- grep "ct(commit,zone=NXM_NX_REG11\\[[0..15\\]],exec(move:NXM_OF_ETH_SRC\\[[\\]]->NXM_NX_CT_LABEL\\[[32..79\\]],load:0x[[0-9]]->NXM_NX_CT_LABEL\\[[80..95\\]]))" -c)
--])
--AT_CHECK([
-- test 0 -eq $(as hv2 ovs-ofctl dump-flows br-int table=21 | \
-- grep "priority=200" | \
-- grep "actions=move:NXM_NX_CT_LABEL\\[[32..79\\]]->NXM_OF_ETH_DST\\[[\\]]" -c)
-+ for hv in 1 2; do
-+ grep table=15 hv${hv}flows | \
-+ grep "priority=100" | \
-+ grep -c "ct(commit,zone=NXM_NX_REG11\\[[0..15\\]],exec(move:NXM_OF_ETH_SRC\\[[\\]]->NXM_NX_CT_LABEL\\[[32..79\\]],load:0x[[0-9]]->NXM_NX_CT_LABEL\\[[80..95\\]]))"
-+
-+ grep table=22 hv${hv}flows | \
-+ grep "priority=200" | \
-+ grep -c "actions=move:NXM_NX_CT_LABEL\\[[32..79\\]]->NXM_OF_ETH_DST\\[[\\]]"
-+ done; :], [0], [dnl
-+1
-+1
-+0
-+0
- ])
-
- OVN_CLEANUP([hv1], [hv2])
-@@ -21856,6 +22370,7 @@ ovs-vsctl -- add-port br-int hv2-vif1 -- \
- # for ARP resolution).
- OVN_POPULATE_ARP
-
-+wait_for_ports_up
- ovn-nbctl --wait=hv sync
-
- AT_CHECK([ovn-sbctl lflow-list | grep lr_in_arp_resolve | grep 10.0.0.1], [1], [])
-@@ -21895,22 +22410,22 @@ as hv1
- ovs-vsctl add-br br-phys
- ovn_attach n1 br-phys 192.168.0.1
-
--ovn-nbctl ls-add sw0
--ovn-nbctl lsp-add sw0 sw0-p1
--ovn-nbctl lsp-set-addresses sw0-p1 "10:14:00:00:00:03 10.0.0.3"
--ovn-nbctl lsp-set-port-security sw0-p1 "10:14:00:00:00:03 10.0.0.3"
-+check ovn-nbctl ls-add sw0
-+check ovn-nbctl lsp-add sw0 sw0-p1
-+check ovn-nbctl lsp-set-addresses sw0-p1 "10:14:00:00:00:03 10.0.0.3"
-+check ovn-nbctl lsp-set-port-security sw0-p1 "10:14:00:00:00:03 10.0.0.3"
-
--ovn-nbctl lsp-add sw0 sw0-p2
--ovn-nbctl lsp-set-addresses sw0-p2 "10:14:00:00:00:04 10.0.0.4"
--ovn-nbctl lsp-set-port-security sw0-p2 "10:14:00:00:00:04 10.0.0.4"
-+check ovn-nbctl lsp-add sw0 sw0-p2
-+check ovn-nbctl lsp-set-addresses sw0-p2 "10:14:00:00:00:04 10.0.0.4"
-+check ovn-nbctl lsp-set-port-security sw0-p2 "10:14:00:00:00:04 10.0.0.4"
-
--ovn-nbctl lsp-add sw0 sw0-p3
--ovn-nbctl lsp-set-addresses sw0-p3 "10:14:00:00:00:05 10.0.0.5"
--ovn-nbctl lsp-set-port-security sw0-p3 "10:14:00:00:00:05 10.0.0.5"
-+check ovn-nbctl lsp-add sw0 sw0-p3
-+check ovn-nbctl lsp-set-addresses sw0-p3 "10:14:00:00:00:05 10.0.0.5"
-+check ovn-nbctl lsp-set-port-security sw0-p3 "10:14:00:00:00:05 10.0.0.5"
-
--ovn-nbctl lsp-add sw0 sw0-p4
--ovn-nbctl lsp-set-addresses sw0-p4 "10:14:00:00:00:06 10.0.0.6"
--ovn-nbctl lsp-set-port-security sw0-p4 "10:14:00:00:00:06 10.0.0.6"
-+check ovn-nbctl lsp-add sw0 sw0-p4
-+check ovn-nbctl lsp-set-addresses sw0-p4 "10:14:00:00:00:06 10.0.0.6"
-+check ovn-nbctl lsp-set-port-security sw0-p4 "10:14:00:00:00:06 10.0.0.6"
-
- as hv1
- ovs-vsctl -- add-port br-int hv1-vif1 -- \
-@@ -21934,88 +22449,101 @@ ovs-vsctl -- add-port br-int hv1-vif4 -- \
- options:rxq_pcap=hv1/vif4-rx.pcap \
- ofport-request=4
-
--OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p1) = xup])
--OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p2) = xup])
--OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p3) = xup])
--OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p4) = xup])
-+wait_for_ports_up
-
--ovn-nbctl pg-add pg0 sw0-p1 sw0-p2
--ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && tcp.dst >= 80 && tcp.dst <= 82" allow
--ovn-nbctl --wait=hv sync
-+check ovn-nbctl pg-add pg0 sw0-p1 sw0-p2
-+check ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && tcp.dst >= 80 && tcp.dst <= 82" allow
-+check ovn-nbctl --wait=hv sync
-
--OVS_WAIT_UNTIL([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id=2")])
-+# wait_conj_id_count COUNT ["ID COUNT [MATCH]"]...
-+#
-+# Waits until COUNT flows matching against conj_id appear in the
-+# table 45 on hv1's br-int bridge. Makes the flows available in
-+# "hv1flows", which will be logged on error.
-+#
-+# In addition, for each quoted "ID COUNT" or "ID COUNT MATCH",
-+# verifies that there are COUNT flows in table 45 that match
-+# aginst conj_id=ID and (if MATCH) is nonempty, match MATCH.
-+wait_conj_id_count() {
-+ AT_CAPTURE_FILE([hv1flows])
-+ local retval
-+ case $1 in
-+ (0) retval=1 ;;
-+ (*) retval=0 ;;
-+ esac
-+
-+ echo "waiting for $1 conj_id flows..."
-+ OVS_WAIT_FOR_OUTPUT_UNQUOTED(
-+ [ovs-ofctl dump-flows br-int > hv1flows
-+ grep table=45 hv1flows | grep -c conj_id],
-+ [$retval], [$1
-+])
-+
-+ shift
-+ for arg; do
-+ set -- $arg; id=$1 count=$2 match=$3
-+ echo "checking that there are $count ${match:+$match }flows with conj_id=$id..."
-+ AT_CHECK_UNQUOTED(
-+ [grep table=45 hv1flows | grep "$match" | grep -c conj_id=$id],
-+ [0], [$count
-+])
-+ done
-+}
-
--# Add sw0-p3 to the port group pg0. The conj_id should be 2.
--ovn-nbctl pg-set-ports pg0 sw0-p1 sw0-p2 sw0-p3
--OVS_WAIT_UNTIL([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id")])
--AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id=2")])
-+AS_BOX([Add sw0-p3 to the port group pg0. The conj_id should be 2.])
-+check ovn-nbctl --wait=hv pg-set-ports pg0 sw0-p1 sw0-p2 sw0-p3
-+wait_conj_id_count 1 "2 1"
-
--# Add sw0p4 to the port group pg0. The conj_id should be 2.
--ovn-nbctl pg-set-ports pg0 sw0-p1 sw0-p2 sw0-p3 sw0-p4
--OVS_WAIT_UNTIL([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id")])
--AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id=2")])
-+AS_BOX([Add sw0p4 to the port group pg0. The conj_id should be 2.])
-+check ovn-nbctl --wait=hv pg-set-ports pg0 sw0-p1 sw0-p2 sw0-p3 sw0-p4
-+wait_conj_id_count 1 "2 1"
-
--# Add another ACL with conjunction.
--ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && udp.dst >= 80 && udp.dst <= 82" allow
--OVS_WAIT_UNTIL([test 2 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id")])
--AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep tcp | grep -c "conj_id=2")])
--AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep udp | grep -c "conj_id=3")])
-+AS_BOX([Add another ACL with conjunction.])
-+check ovn-nbctl --wait=hv acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && udp.dst >= 80 && udp.dst <= 82" allow
-+wait_conj_id_count 2 "2 1 tcp" "3 1 udp"
-
--# Delete tcp ACL.
--ovn-nbctl acl-del pg0 to-lport 1002 "outport == @pg0 && ip4 && tcp.dst >= 80 && tcp.dst <= 82"
--OVS_WAIT_UNTIL([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id")])
--AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep udp | grep -c "conj_id=3")])
-+AS_BOX([Delete tcp ACL.])
-+check ovn-nbctl --wait=hv acl-del pg0 to-lport 1002 "outport == @pg0 && ip4 && tcp.dst >= 80 && tcp.dst <= 82"
-+wait_conj_id_count 1 "3 1 udp"
-
--# Add back the tcp ACL.
--ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && tcp.dst >= 80 && tcp.dst <= 82" allow
--OVS_WAIT_UNTIL([test 2 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id")])
-+AS_BOX([Add back the tcp ACL.])
-+check ovn-nbctl --wait=hv acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && tcp.dst >= 80 && tcp.dst <= 82" allow
-+wait_conj_id_count 2 "3 1 udp" "4 1 tcp"
- AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep udp | grep -c "conj_id=3")])
- AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep tcp | grep -c "conj_id=4")])
-
--ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && inport == @pg0 && ip4 && tcp.dst >= 84 && tcp.dst <= 86" allow
--OVS_WAIT_UNTIL([test 3 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id")])
--AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep udp | grep -c "conj_id=3")])
--AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep tcp | grep -c "conj_id=4")])
--AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep tcp | grep -c "conj_id=5")])
-+AS_BOX([Add another tcp ACL.])
-+check ovn-nbctl --wait=hv acl-add pg0 to-lport 1002 "outport == @pg0 && inport == @pg0 && ip4 && tcp.dst >= 84 && tcp.dst <= 86" allow
-+wait_conj_id_count 3 "3 1 udp" "4 1 tcp" "5 1 tcp"
-
--ovn-nbctl clear port_group pg0 acls
--OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id")])
-+AS_BOX([Clear ACLs.])
-+check ovn-nbctl --wait=hv clear port_group pg0 acls
-+wait_conj_id_count 0
-
--ovn-nbctl --wait=hv acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && tcp.dst >= 80 && tcp.dst <= 82" allow
--ovn-nbctl --wait=hv acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && udp.dst >= 80 && udp.dst <= 82" allow
--OVS_WAIT_UNTIL([test 2 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id")])
--AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep tcp | grep -c "conj_id=6")])
--AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep udp | grep -c "conj_id=7")])
-+AS_BOX([Add TCP ACL.])
-+check ovn-nbctl --wait=hv acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && tcp.dst >= 80 && tcp.dst <= 82" allow
-+check ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && udp.dst >= 80 && udp.dst <= 82" allow
-+wait_conj_id_count 2 "6 1 tcp" "7 1 udp"
-
--# Flush the lflow cache.
-+AS_BOX([Flush lflow cache.])
- as hv1 ovn-appctl -t ovn-controller flush-lflow-cache
--OVS_WAIT_UNTIL([test 2 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id")])
--AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id=2")])
--AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id=3")])
--
--# Disable lflow caching.
-+wait_conj_id_count 2 "2 1" "3 1"
-
-+AS_BOX([Disable lflow caching.])
- as hv1 ovs-vsctl set open . external_ids:ovn-enable-lflow-cache=false
-
--# Wait until ovn-enble-lflow-cache is processed by ovn-controller.
--OVS_WAIT_UNTIL([
-- test $(ovn-sbctl get chassis hv1 other_config:ovn-enable-lflow-cache) = '"false"'
--])
--
--AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id=2")])
--AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id=3")])
-+AS_BOX([Wait until ovn-enble-lflow-cache is processed by ovn-controller.])
-+wait_row_count Chassis 1 name=hv1 other_config:ovn-enable-lflow-cache=false
-+wait_conj_id_count 2 "2 1" "3 1"
-
--# Remove port sw0-p4 from port group.
--ovn-nbctl pg-set-ports pg0 sw0-p1 sw0-p2 sw0-p3
--OVS_WAIT_UNTIL([test 2 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id")])
--AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id=4")])
--AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id=5")])
-+AS_BOX([Remove port sw0-p4 from port group.])
-+check ovn-nbctl --wait=hv pg-set-ports pg0 sw0-p1 sw0-p2 sw0-p3
-+wait_conj_id_count 2 "4 1" "5 1"
-
-+AS_BOX([Recompute.])
- as hv1 ovn-appctl -t ovn-controller recompute
-
--OVS_WAIT_UNTIL([test 2 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id")])
--OVS_WAIT_UNTIL([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id=2")])
--AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id=3")])
-+wait_conj_id_count 2 "2 1" "3 1"
-
- OVN_CLEANUP([hv1])
-
-@@ -22131,6 +22659,77 @@ AT_CHECK_UNQUOTED([grep -c "output:4" offlows_table65_2.txt], [0], [dnl
- OVN_CLEANUP([hv1])
- AT_CLEANUP
-
-+AT_SETUP([ovn -- Container port Incremental Processing])
-+ovn_start
-+
-+net_add n1
-+sim_add hv1
-+as hv1
-+ovs-vsctl add-br br-phys
-+ovn_attach n1 br-phys 192.168.0.10
-+
-+as hv1
-+ovs-vsctl \
-+ -- add-port br-int vif1 \
-+ -- set Interface vif1 external_ids:iface-id=lsp1 \
-+ ofport-request=1
-+
-+check ovn-nbctl ls-add ls1 \
-+ -- ls-add ls2 \
-+ -- lsp-add ls1 lsp1 \
-+ -- lsp-add ls2 lsp-cont1 lsp1 1
-+check ovn-nbctl --wait=hv sync
-+
-+# Wait for ports to be bound.
-+wait_row_count Chassis 1 name=hv1
-+ch=$(fetch_column Chassis _uuid name=hv1)
-+wait_row_count Port_Binding 1 logical_port=lsp1 chassis=$ch
-+wait_row_count Port_Binding 1 logical_port=lsp-cont1 chassis=$ch
-+
-+AS_BOX([delete OVS VIF and OVN container port])
-+as hv1 ovn-appctl -t ovn-controller debug/pause
-+as hv1 ovs-vsctl del-port vif1
-+
-+check ovn-nbctl --wait=sb lsp-del lsp-cont1
-+as hv1 ovn-appctl -t ovn-controller debug/resume
-+
-+check ovn-nbctl --wait=hv sync
-+check_row_count Port_Binding 1 logical_port=lsp1 chassis="[[]]"
-+
-+AS_BOX([readd OVS VIF])
-+as hv1
-+ovs-vsctl \
-+ -- add-port br-int vif1 \
-+ -- set Interface vif1 external_ids:iface-id=lsp1 \
-+ ofport-request=1
-+wait_row_count Port_Binding 1 logical_port=lsp1 chassis=$ch
-+
-+AS_BOX([readd OVN container port])
-+check ovn-nbctl lsp-add ls2 lsp-cont1 lsp1 1
-+check ovn-nbctl --wait=hv sync
-+check_row_count Port_Binding 1 logical_port=lsp-cont1 chassis=$ch
-+
-+AS_BOX([delete both OVN VIF and OVN container port])
-+as hv1 ovn-appctl -t ovn-controller debug/pause
-+check ovn-nbctl lsp-del lsp1 \
-+ -- lsp-del lsp-cont1
-+check ovn-nbctl --wait=sb sync
-+as hv1 ovn-appctl -t ovn-controller debug/resume
-+
-+AS_BOX([readd both OVN VIF and OVN container port])
-+as hv1 ovn-appctl -t ovn-controller debug/pause
-+check ovn-nbctl lsp-add ls1 lsp1 \
-+ -- lsp-add ls2 lsp-cont1 lsp1 1
-+check ovn-nbctl --wait=sb sync
-+as hv1 ovn-appctl -t ovn-controller debug/resume
-+
-+check ovn-nbctl --wait=hv sync
-+wait_row_count Port_Binding 1 logical_port=lsp1 chassis=$ch
-+wait_row_count Port_Binding 1 logical_port=lsp-cont1 chassis=$ch
-+
-+OVN_CLEANUP([hv1])
-+AT_CLEANUP
-+
- # Test dropping traffic destined to router owned IPs.
- AT_SETUP([ovn -- gateway router drop traffic for own IPs])
- ovn_start
-@@ -22145,7 +22744,8 @@ ovn-nbctl lsp-add s1 lsp-s1-r1 -- set Logical_Switch_Port lsp-s1-r1 type=router
-
- # Create logical port p1 in s1
- ovn-nbctl lsp-add s1 p1 \
---- lsp-set-addresses p1 "f0:00:00:00:01:02 10.0.1.2"
-+-- lsp-set-addresses p1 "f0:00:00:00:01:02 10.0.1.2" \
-+-- lsp-set-port-security p1 "f0:00:00:00:01:02 10.0.1.2"
-
- # Create two hypervisor and create OVS ports corresponding to logical ports.
- net_add n1
-@@ -22165,6 +22765,7 @@ ovs-vsctl -- add-port br-int hv1-vif1 -- \
- # for ARP resolution).
- OVN_POPULATE_ARP
-
-+wait_for_ports_up
- ovn-nbctl --wait=hv sync
-
- sw_key=$(ovn-sbctl --bare --columns tunnel_key list datapath_binding r1)
-@@ -22208,7 +22809,7 @@ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep "actions=controller" | grep
- ])
-
- # The packet should've been dropped in the lr_in_arp_resolve stage.
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=21, n_packets=1,.* priority=1,ip,metadata=0x${sw_key},nw_dst=10.0.1.1 actions=drop" -c], [0], [dnl
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=22, n_packets=1,.* priority=1,ip,metadata=0x${sw_key},nw_dst=10.0.1.1 actions=drop" -c], [0], [dnl
- 1
- ])
-
-@@ -22281,6 +22882,7 @@ check test "$hvt2" -gt 0
- # Then wait for 9 out of 10
- sleep 1
- check as hv3 ovn-appctl -t ovn-controller exit --restart
-+wait_for_ports_up
- ovn-nbctl --wait=sb sync
- wait_row_count Chassis_Private 9 name!=hv3 nb_cfg=2
- check_row_count Chassis_Private 1 name=hv3 nb_cfg=1
-@@ -22454,6 +23056,7 @@ ovn-nbctl set logical_router gw_router options:chassis=hv3
- ovn-nbctl lr-nat-add gw_router snat 172.16.0.200 30.0.0.0/24
- ovn-nbctl lr-nat-add gw_router snat 172.16.0.201 30.0.0.3
-
-+wait_for_ports_up
- ovn-nbctl --wait=hv sync
-
- # Create an interface in br-phys in hv2 and send ARP request for 172.16.0.100
-@@ -22643,6 +23246,7 @@ check ovn-nbctl acl-add ls1 to-lport 1001 \
- check ovn-nbctl acl-add ls1 to-lport 1001 \
- 'outport == "lsp1" && ip4 && ip4.src == {10.0.0.2, 10.0.0.3}' allow
-
-+wait_for_ports_up
- check ovn-nbctl --wait=hv sync
-
- sip=`ip_to_hex 10 0 0 2`
-@@ -22811,6 +23415,7 @@ ovs-vsctl -- add-port br-int hv1-vif1 -- \
- options:rxq_pcap=hv1/vif1-rx.pcap \
- ofport-request=1
-
-+wait_for_ports_up
- ovn-nbctl --wait=hv sync
-
- # Expected conjunction flows:
-@@ -22869,6 +23474,7 @@ as hv1 ovs-vsctl \
- ovn-nbctl --wait=hv sync
-
- # hv1 ovn-controller should not bind sw0-p2.
-+wait_for_ports_up sw0-p1
- check_row_count Port_Binding 0 logical_port=sw0-p2 chassis=$c
-
- # Trigger recompute and sw0-p2 should not be claimed.
-@@ -22976,93 +23582,79 @@ check ovn-nbctl lb-add lb-ipv4-udp 88.88.88.88:4040 42.42.42.1:2021 udp
- check ovn-nbctl lb-add lb-ipv6-tcp [[8800::0088]]:8080 [[4200::1]]:4041 tcp
- check ovn-nbctl --wait=hv lb-add lb-ipv6-udp [[8800::0088]]:4040 [[4200::1]]:2021 udp
-
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68], [0], [dnl
--NXST_FLOW reply (xid=0x8):
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST], [1], [dnl
- ])
-
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=69], [0], [dnl
--NXST_FLOW reply (xid=0x8):
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl
- ])
-
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70], [0], [dnl
--NXST_FLOW reply (xid=0x8):
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | grep -v NXST], [1], [dnl
- ])
-
--AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68], [0], [dnl
--NXST_FLOW reply (xid=0x8):
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST], [1], [dnl
- ])
-
--AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69], [0], [dnl
--NXST_FLOW reply (xid=0x8):
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl
- ])
-
--AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70], [0], [dnl
--NXST_FLOW reply (xid=0x8):
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | grep -v NXST], [1], [dnl
- ])
-
- check ovn-nbctl --wait=hv ls-lb-add sw0 lb-ipv4-tcp
-
- OVS_WAIT_UNTIL(
-- [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 1]
-+ [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 1]
- )
-
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d ' ' -f8-], [0], [dnl
--priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
- ])
-
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | cut -d ' ' -f8-], [0], [dnl
--priority=100,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69], [0], [dnl
-+NXST_FLOW reply (xid=0x8):
- ])
-
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d ' ' -f8-], [0], [dnl
--priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=70, priority=100,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
- ])
-
--AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68], [0], [dnl
--NXST_FLOW reply (xid=0x8):
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST], [1], [dnl
- ])
-
--AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69], [0], [dnl
--NXST_FLOW reply (xid=0x8):
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl
- ])
-
--AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70], [0], [dnl
--NXST_FLOW reply (xid=0x8):
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | grep -v NXST], [1], [dnl
- ])
-
- check ovn-nbctl lb-add lb-ipv4-tcp 88.88.88.90:8080 42.42.42.42:4041,52.52.52.52:4042 tcp
-
- OVS_WAIT_UNTIL(
-- [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 3]
-+ [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 3]
- )
-
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
--priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,tcp,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]]
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
- ])
-
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
--priority=100,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=88.88.88.90,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=88.88.88.90,tp_src=4042 actions=load:0x1->NXM_NX_REG10[[7]]
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69], [0], [dnl
-+NXST_FLOW reply (xid=0x8):
- ])
-
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d ' ' -f8-], [0], [dnl
--priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
--priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=70, priority=100,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
-+ table=70, priority=100,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
- ])
-
--AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68], [0], [dnl
--NXST_FLOW reply (xid=0x8):
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST], [1], [dnl
- ])
-
--AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69], [0], [dnl
--NXST_FLOW reply (xid=0x8):
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl
- ])
-
--AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70], [0], [dnl
--NXST_FLOW reply (xid=0x8):
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | grep -v NXST], [1], [dnl
- ])
-
- check ovn-nbctl lsp-add sw0 sw0-p2
-@@ -23070,184 +23662,159 @@ check ovn-nbctl lsp-add sw0 sw0-p2
- OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p2) = xup])
-
- OVS_WAIT_UNTIL(
-- [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 3]
-+ [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 3]
- )
-
--AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
--priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,tcp,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]]
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
- ])
-
--AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
--priority=100,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=88.88.88.90,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=88.88.88.90,tp_src=4042 actions=load:0x1->NXM_NX_REG10[[7]]
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl
- ])
-
--AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d ' ' -f8-], [0], [dnl
--priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
--priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=70, priority=100,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
-+ table=70, priority=100,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
- ])
-
- check ovn-nbctl --wait=hv ls-lb-add sw0 lb-ipv4-udp
-
- OVS_WAIT_UNTIL(
-- [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 4]
-+ [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 4]
- )
-
- OVS_WAIT_UNTIL(
-- [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 4]
-+ [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 4]
- )
-
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
--priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,tcp,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,udp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,udp,reg1=0x58585858,reg2=0xfc8/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
- ])
-
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
--priority=100,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=88.88.88.90,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=88.88.88.90,tp_src=4042 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl
- ])
-
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
--priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
--priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=70, priority=100,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
-+ table=70, priority=100,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
-+ table=70, priority=100,udp,reg1=0x58585858,reg2=0xfc8/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
- ])
-
--AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
--priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,tcp,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,udp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,udp,reg1=0x58585858,reg2=0xfc8/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
- ])
-
--AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
--priority=100,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=88.88.88.90,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=88.88.88.90,tp_src=4042 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl
- ])
-
--AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
--priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
--priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=70, priority=100,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
-+ table=70, priority=100,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
-+ table=70, priority=100,udp,reg1=0x58585858,reg2=0xfc8/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
- ])
-
- check ovn-nbctl --wait=hv ls-lb-add sw0 lb-ipv6-tcp
-
- OVS_WAIT_UNTIL(
-- [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 5]
-+ [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 5]
- )
-
- OVS_WAIT_UNTIL(
-- [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 5]
-+ [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 5]
- )
-
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
--priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,tcp,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,tcp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,udp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,udp,reg1=0x58585858,reg2=0xfc8/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
- ])
-
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
--priority=100,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=88.88.88.90,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=88.88.88.90,tp_src=4042 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl
- ])
-
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
--priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
--priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
--priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=70, priority=100,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
-+ table=70, priority=100,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
-+ table=70, priority=100,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
-+ table=70, priority=100,udp,reg1=0x58585858,reg2=0xfc8/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
- ])
-
--AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
--priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,tcp,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,tcp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,udp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,udp,reg1=0x58585858,reg2=0xfc8/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
- ])
-
--AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
--priority=100,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=88.88.88.90,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=88.88.88.90,tp_src=4042 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl
- ])
-
--AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
--priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
--priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
--priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=70, priority=100,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
-+ table=70, priority=100,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
-+ table=70, priority=100,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
-+ table=70, priority=100,udp,reg1=0x58585858,reg2=0xfc8/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
- ])
-
- check ovn-nbctl --wait=hv ls-lb-add sw0 lb-ipv6-udp
-
- OVS_WAIT_UNTIL(
-- [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 6]
-+ [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 6]
- )
-
- OVS_WAIT_UNTIL(
-- [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 6]
-+ [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 6]
- )
-
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
--priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,tcp,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,tcp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,udp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,udp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,udp,reg1=0x58585858,reg2=0xfc8/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
- ])
-
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
--priority=100,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=88.88.88.90,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=88.88.88.90,tp_src=4042 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl
- ])
-
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
--priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
--priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
--priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=70, priority=100,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
-+ table=70, priority=100,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
-+ table=70, priority=100,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
-+ table=70, priority=100,udp,reg1=0x58585858,reg2=0xfc8/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
-+ table=70, priority=100,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
- ])
-
--AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
--priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,tcp,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,tcp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,udp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,udp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,udp,reg1=0x58585858,reg2=0xfc8/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
- ])
-
--AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
--priority=100,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=88.88.88.90,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=88.88.88.90,tp_src=4042 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl
- ])
-
--AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
--priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
--priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
--priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=70, priority=100,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
-+ table=70, priority=100,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
-+ table=70, priority=100,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
-+ table=70, priority=100,udp,reg1=0x58585858,reg2=0xfc8/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
-+ table=70, priority=100,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
- ])
-
- check ovn-nbctl --wait=hv ls-lb-add sw1 lb-ipv6-udp
-@@ -23255,65 +23822,115 @@ check ovn-nbctl --wait=hv ls-lb-add sw1 lb-ipv6-udp
- # Number of hairpin flows shouldn't change as it doesn't depend on how many
- # datapaths the LB is applied.
- OVS_WAIT_UNTIL(
-- [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 6]
-+ [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 6]
- )
-
- OVS_WAIT_UNTIL(
-- [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 6]
-+ [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 6]
- )
-
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
--priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,tcp,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,tcp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,udp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,udp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,udp,reg1=0x58585858,reg2=0xfc8/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+])
-+
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl
-+])
-+
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=70, priority=100,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
-+ table=70, priority=100,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
-+ table=70, priority=100,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
-+ table=70, priority=100,udp,reg1=0x58585858,reg2=0xfc8/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
-+ table=70, priority=100,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
-+ table=70, priority=100,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x2 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
-+])
-+
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,udp,reg1=0x58585858,reg2=0xfc8/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+])
-+
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl
-+])
-+
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=70, priority=100,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
-+ table=70, priority=100,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
-+ table=70, priority=100,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
-+ table=70, priority=100,udp,reg1=0x58585858,reg2=0xfc8/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
-+ table=70, priority=100,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
-+ table=70, priority=100,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x2 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
-+])
-+
-+# Check backwards compatibility with ovn-northd versions that don't store the
-+# original destination tuple.
-+#
-+# ovn-controller should fall back to matching on ct_nw_dst()/ct_tp_dst().
-+as northd-backup ovn-appctl -t ovn-northd pause
-+as northd ovn-appctl -t ovn-northd pause
-+
-+check ovn-sbctl \
-+ -- remove load_balancer lb-ipv4-tcp options hairpin_orig_tuple \
-+ -- remove load_balancer lb-ipv6-tcp options hairpin_orig_tuple \
-+ -- remove load_balancer lb-ipv4-udp options hairpin_orig_tuple \
-+ -- remove load_balancer lb-ipv6-udp options hairpin_orig_tuple
-+
-+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=68, priority=100,ct_state=+trk+dnat,ct_label=0x2/0x2,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_state=+trk+dnat,ct_label=0x2/0x2,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_state=+trk+dnat,ct_label=0x2/0x2,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_state=+trk+dnat,ct_label=0x2/0x2,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_state=+trk+dnat,ct_label=0x2/0x2,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_state=+trk+dnat,ct_label=0x2/0x2,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
- ])
-
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
--priority=100,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=88.88.88.90,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=88.88.88.90,tp_src=4042 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,udp6,metadata=0x2,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl
- ])
-
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
--priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
--priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x2 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
--priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
--priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
-+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=70, priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
-+ table=70, priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x2 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
-+ table=70, priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
-+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
-+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
-+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
- ])
-
--AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
--priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,tcp,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,tcp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,udp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,udp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+OVS_WAIT_FOR_OUTPUT([as hv2 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=68, priority=100,ct_state=+trk+dnat,ct_label=0x2/0x2,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_state=+trk+dnat,ct_label=0x2/0x2,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_state=+trk+dnat,ct_label=0x2/0x2,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_state=+trk+dnat,ct_label=0x2/0x2,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_state=+trk+dnat,ct_label=0x2/0x2,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_state=+trk+dnat,ct_label=0x2/0x2,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
- ])
-
--AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
--priority=100,tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,tcp,metadata=0x1,nw_src=42.42.42.42,nw_dst=88.88.88.90,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,tcp,metadata=0x1,nw_src=52.52.52.52,nw_dst=88.88.88.90,tp_src=4042 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,udp6,metadata=0x2,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl
- ])
-
--AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
--priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
--priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x2 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
--priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
--priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
-+OVS_WAIT_FOR_OUTPUT([as hv2 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=70, priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
-+ table=70, priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x2 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
-+ table=70, priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
-+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
-+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
-+ table=70, priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
- ])
-
-+# Resume ovn-northd.
-+as northd ovn-appctl -t ovn-northd resume
-+as northd-backup ovn-appctl -t ovn-northd resume
-+check ovn-nbctl --wait=hv sync
-+
- as hv2 ovs-vsctl del-port hv2-vif1
- OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p2) = xdown])
-
-@@ -23321,75 +23938,73 @@ OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p2) = xdown])
- as hv2 ovn-appctl -t ovn-controller recompute
-
- OVS_WAIT_UNTIL(
-- [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 0]
-+ [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 0]
- )
-
- OVS_WAIT_UNTIL(
-- [test $(as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | wc -l) -eq 0]
-+ [test $(as hv2 ovs-ofctl dump-flows br-int table=69 | grep -c -v NXST) -eq 0]
- )
-
- OVS_WAIT_UNTIL(
-- [test $(as hv2 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | wc -l) -eq 0]
-+ [test $(as hv2 ovs-ofctl dump-flows br-int table=70 | grep -c -v NXST) -eq 0]
- )
-
- OVS_WAIT_UNTIL(
-- [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 6]
-+ [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 6]
- )
-
- check ovn-nbctl --wait=hv lb-del lb-ipv4-tcp
-
- OVS_WAIT_UNTIL(
-- [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 3]
-+ [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 3]
- )
-
- OVS_WAIT_UNTIL(
-- [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 0]
-+ [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 0]
- )
-
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
--priority=100,ct_label=0x2/0x2,tcp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,udp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,ct_label=0x2/0x2,udp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=68, priority=100,ct_label=0x2/0x2,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,udp,reg1=0x58585858,reg2=0xfc8/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
-+ table=68, priority=100,ct_label=0x2/0x2,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
- ])
-
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
--priority=100,tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
--priority=100,udp6,metadata=0x2,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=2021 actions=load:0x1->NXM_NX_REG10[[7]]
-+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69], [0], [dnl
-+NXST_FLOW reply (xid=0x8):
- ])
-
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
--priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
--priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ipv6,metadata=0x2 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
--priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ip,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST], [0], [dnl
-+ table=70, priority=100,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
-+ table=70, priority=100,udp,reg1=0x58585858,reg2=0xfc8/0xffff,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
-+ table=70, priority=100,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x1 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
-+ table=70, priority=100,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x2 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
- ])
-
- check ovn-nbctl --wait=hv ls-del sw0
- check ovn-nbctl --wait=hv ls-del sw1
-
- OVS_WAIT_UNTIL(
-- [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 0]
-+ [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 0]
- )
-
- OVS_WAIT_UNTIL(
-- [test $(as hv1 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | wc -l) -eq 0]
-+ [test $(as hv1 ovs-ofctl dump-flows br-int table=69 | grep -c -v NXST) -eq 0]
- )
-
- OVS_WAIT_UNTIL(
-- [test $(as hv1 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | wc -l) -eq 0]
-+ [test $(as hv1 ovs-ofctl dump-flows br-int table=70 | grep -c -v NXST) -eq 0]
- )
-
- OVS_WAIT_UNTIL(
-- [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST | wc -l) -eq 0]
-+ [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 0]
- )
-
- OVS_WAIT_UNTIL(
-- [test $(as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST | wc -l) -eq 0]
-+ [test $(as hv2 ovs-ofctl dump-flows br-int table=69 | grep -c -v NXST) -eq 0]
- )
-
- OVS_WAIT_UNTIL(
-- [test $(as hv2 ovs-ofctl dump-flows br-int table=70 | grep -v NXST | wc -l) -eq 0]
-+ [test $(as hv2 ovs-ofctl dump-flows br-int table=70 | grep -c -v NXST) -eq 0]
- )
-
- OVN_CLEANUP([hv1], [hv2])
-@@ -23541,3 +24156,680 @@ as ovn-nb
- OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
- AT_CLEANUP
-+
-+AT_SETUP([ovn -- propagate Port_Binding.up to NB and OVS])
-+ovn_start
-+
-+net_add n1
-+sim_add hv1
-+as hv1
-+ovs-vsctl add-br br-phys
-+ovn_attach n1 br-phys 192.168.0.1
-+
-+check ovn-nbctl ls-add ls
-+
-+AS_BOX([add OVS port for existing LSP])
-+check ovn-nbctl lsp-add ls lsp1
-+check ovn-nbctl --wait=hv sync
-+check_column "false" Port_Binding up logical_port=lsp1
-+
-+check ovs-vsctl add-port br-int lsp1 -- set Interface lsp1 external-ids:iface-id=lsp1
-+wait_column "true" Port_Binding up logical_port=lsp1
-+wait_column "true" nb:Logical_Switch_Port up name=lsp1
-+OVS_WAIT_UNTIL([test `ovs-vsctl get Interface lsp1 external_ids:ovn-installed` = '"true"'])
-+
-+AS_BOX([add LSP for existing OVS port])
-+check ovs-vsctl add-port br-int lsp2 -- set Interface lsp2 external-ids:iface-id=lsp2
-+check ovn-nbctl lsp-add ls lsp2
-+check ovn-nbctl --wait=hv sync
-+check_column "true" Port_Binding up logical_port=lsp2
-+wait_column "true" nb:Logical_Switch_Port up name=lsp2
-+OVS_WAIT_UNTIL([test `ovs-vsctl get Interface lsp2 external_ids:ovn-installed` = '"true"'])
-+
-+AS_BOX([ovn-controller should not reset Port_Binding.up without northd])
-+# Pause northd and clear the "up" field to simulate older ovn-northd
-+# versions writing to the Southbound DB.
-+as northd ovn-appctl -t ovn-northd pause
-+as northd-backup ovn-appctl -t ovn-northd pause
-+
-+as hv1 ovn-appctl -t ovn-controller debug/pause
-+check ovn-sbctl clear Port_Binding lsp1 up
-+as hv1 ovn-appctl -t ovn-controller debug/resume
-+
-+# Forcefully release the Port_Binding so ovn-controller reclaims it.
-+# Make sure the Port_Binding.up field is not updated though.
-+check ovn-sbctl clear Port_Binding lsp1 chassis
-+hv1_uuid=$(fetch_column Chassis _uuid name=hv1)
-+wait_column "$hv1_uuid" Port_Binding chassis logical_port=lsp1
-+check_column "" Port_Binding up logical_port=lsp1
-+
-+# Once northd should explicitly set the Port_Binding.up field to 'false' and
-+# ovn-controller sets it to 'true' as soon as the update is processed.
-+as northd ovn-appctl -t ovn-northd resume
-+as northd-backup ovn-appctl -t ovn-northd resume
-+wait_column "true" Port_Binding up logical_port=lsp1
-+wait_column "true" nb:Logical_Switch_Port up name=lsp1
-+
-+AS_BOX([ovn-controller should reset Port_Binding.up - from NULL])
-+# If Port_Binding.up is cleared externally, ovn-northd resets it to 'false'
-+# and ovn-controller finally sets it to 'true' once the update is processed.
-+as hv1 ovn-appctl -t ovn-controller debug/pause
-+check ovn-sbctl clear Port_Binding lsp1 up
-+check ovn-nbctl --wait=sb sync
-+wait_column "false" nb:Logical_Switch_Port up name=lsp1
-+as hv1 ovn-appctl -t ovn-controller debug/resume
-+wait_column "true" Port_Binding up logical_port=lsp1
-+wait_column "true" nb:Logical_Switch_Port up name=lsp1
-+
-+AS_BOX([ovn-controller should reset Port_Binding.up - from false])
-+# If Port_Binding.up is externally set to 'false', ovn-controller should sets
-+# it to 'true' once the update is processed.
-+as hv1 ovn-appctl -t ovn-controller debug/pause
-+check ovn-sbctl set Port_Binding lsp1 up=false
-+check ovn-nbctl --wait=sb sync
-+wait_column "false" nb:Logical_Switch_Port up name=lsp1
-+as hv1 ovn-appctl -t ovn-controller debug/resume
-+wait_column "true" Port_Binding up logical_port=lsp1
-+wait_column "true" nb:Logical_Switch_Port up name=lsp1
-+
-+OVN_CLEANUP([hv1])
-+AT_CLEANUP
-+
-+# Test case to check that ovn-controller doesn't assert when
-+# handling port group updates.
-+AT_SETUP([ovn -- No ovn-controller assert for port group updates])
-+ovn_start
-+
-+net_add n1
-+sim_add hv1
-+as hv1
-+ovs-vsctl add-br br-phys
-+ovn_attach n1 br-phys 192.168.0.10
-+
-+as hv1
-+ovs-vsctl \
-+ -- add-port br-int vif1 \
-+ -- set Interface vif1 external_ids:iface-id=sw0-port1 \
-+ ofport-request=1
-+
-+check ovn-nbctl ls-add sw0
-+check ovn-nbctl lsp-add sw0 sw0-port1
-+check ovn-nbctl lsp-set-addresses sw0-port1 "10:14:00:00:00:01 192.168.0.2"
-+
-+check ovn-nbctl lsp-add sw0 sw0-port2
-+check ovn-nbctl lsp-add sw0 sw0-port3
-+check ovn-nbctl lsp-add sw0 sw0-port4
-+check ovn-nbctl lsp-add sw0 sw0-port5
-+check ovn-nbctl lsp-add sw0 sw0-port6
-+check ovn-nbctl lsp-add sw0 sw0-port7
-+
-+ovn-nbctl create address_set name=as1
-+ovn-nbctl set address_set . addresses="10.0.0.10,10.0.0.11,10.0.0.12"
-+
-+ovn-nbctl pg-add pg1 sw0-port1 sw0-port2 sw0-port3
-+ovn-nbctl acl-add pg1 to-lport 1002 "outport == @pg1 && ip4.dst == \$as1 && icmp4" drop
-+ovn-nbctl acl-add pg1 to-lport 1002 "outport == @pg1 && ip4.dst == \$as1 && tcp && tcp.dst >=10000 && tcp.dst <= 20000" drop
-+ovn-nbctl acl-add pg1 to-lport 1002 "outport == @pg1 && ip4.dst == \$as1 && udp && udp.dst >=10000 && udp.dst <= 20000" drop
-+
-+ovn-nbctl pg-add pg2 sw0-port2 sw0-port3 sw0-port4 sw0-port5
-+ovn-nbctl acl-add pg2 to-lport 1002 "outport == @pg2 && ip4.dst == \$as1 && icmp4" allow-related
-+ovn-nbctl acl-add pg2 to-lport 1002 "outport == @pg2 && ip4.dst == \$as1 && tcp && tcp.dst >=30000 && tcp.dst <= 40000" drop
-+ovn-nbctl acl-add pg2 to-lport 1002 "outport == @pg2 && ip4.dst == \$as1 && udp && udp.dst >=30000 && udp.dst <= 40000" drop
-+
-+ovn-nbctl pg-add pg3 sw0-port1 sw0-port5
-+ovn-nbctl acl-add pg3 to-lport 1002 "outport == @pg3 && ip4.dst == \$as1 && icmp4" allow-related
-+ovn-nbctl acl-add pg3 to-lport 1002 "outport == @pg3 && ip4.dst == \$as1 && tcp && tcp.dst >=20000 && tcp.dst <= 30000" allow-related
-+ovn-nbctl acl-add pg3 to-lport 1002 "outport == @pg3 && ip4.dst == \$as1 && udp && udp.dst >=20000 && udp.dst <= 30000" allow-related
-+
-+AS_BOX([Delete and add the port groups multiple times])
-+
-+for i in $(seq 1 10)
-+do
-+ check ovn-nbctl --wait=hv clear port_Group pg1 ports
-+ check ovn-nbctl --wait=hv clear port_Group pg2 ports
-+ check ovn-nbctl --wait=hv clear port_Group pg3 ports
-+ check ovn-nbctl --wait=hv pg-set-ports pg1 sw0-port1
-+ check ovn-nbctl --wait=hv pg-set-ports pg1 sw0-port1 sw0-port4
-+ check ovn-nbctl --wait=hv pg-set-ports pg1 sw0-port1 sw0-port4 sw0-port5
-+
-+ check ovn-nbctl --wait=hv pg-set-ports pg2 sw0-port2
-+ check ovn-nbctl --wait=hv pg-set-ports pg2 sw0-port2 sw0-port6
-+ check ovn-nbctl --wait=hv pg-set-ports pg2 sw0-port2 sw0-port6 sw0-port7
-+
-+ check ovn-nbctl --wait=hv pg-set-ports pg3 sw0-port1
-+ check ovn-nbctl --wait=hv pg-set-ports pg3 sw0-port1 sw0-port3
-+ check ovn-nbctl --wait=hv pg-set-ports pg3 sw0-port1 sw0-port3 sw0-port6
-+
-+ # Make sure that ovn-controller has not asserted.
-+ AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)])
-+done
-+
-+OVN_CLEANUP([hv1])
-+AT_CLEANUP
-+
-+# Test case to check that ovn-controller doesn't assert when
-+# handling conjunction flows. When ovn-controller claims
-+# the first port of a logical switch datapath, it programs the flows
-+# for this datapath incrementally (without full recompute). If
-+# suppose, in the same SB update from ovsdb-server, a logical flow is added
-+# which results in conjunction action, then this logical flow is also
-+# handled incrementally. The newly added logical flow is processed
-+# twice which results in wrong oflows and it results in an assertion
-+# in ovn-controller. Test this ovn-controller handles this scenario
-+# properly and doesn't assert.
-+AT_SETUP([ovn -- No ovn-controller assert when generating conjunction flows])
-+ovn_start
-+
-+net_add n1
-+sim_add hv1
-+as hv1
-+ovs-vsctl add-br br-phys
-+ovn_attach n1 br-phys 192.168.0.10
-+
-+as hv1
-+ovs-vsctl \
-+ -- add-port br-int vif1 \
-+ -- set Interface vif1 external_ids:iface-id=sw0-p1 \
-+ ofport-request=1
-+
-+check as hv1
-+ovs-vsctl set open . external_ids:ovn-monitor-all=true
-+
-+check ovn-nbctl ls-add sw0
-+check ovn-nbctl pg-add pg1
-+check ovn-nbctl pg-add pg2
-+check ovn-nbctl lsp-add sw0 sw0-p2
-+check ovn-nbctl lsp-set-addresses sw0-p2 "00:00:00:00:00:02 192.168.47.2"
-+check ovn-nbctl lsp-add sw0 sw0-p3
-+check ovn-nbctl lsp-set-addresses sw0-p3 "00:00:00:00:00:03 192.168.47.3"
-+
-+# Pause ovn-northd. When it is resumed, all the below NB updates
-+# will be sent in one transaction.
-+
-+check as northd ovn-appctl -t ovn-northd pause
-+check as northd-backup ovn-appctl -t ovn-northd pause
-+
-+check ovn-nbctl lsp-add sw0 sw0-p1
-+check ovn-nbctl lsp-set-addresses sw0-p1 "00:00:00:00:00:01 192.168.47.1"
-+check ovn-nbctl pg-set-ports pg1 sw0-p1 sw0-p2
-+check ovn-nbctl pg-set-ports pg2 sw0-p3
-+check ovn-nbctl acl-add pg1 to-lport 1002 "outport == @pg1 && ip4 && ip4.src == \$pg2_ip4 && udp && udp.dst >= 1 && udp.dst <= 65535" allow-related
-+
-+# resume ovn-northd now. This should result in a single update message
-+# from SB ovsdb-server to ovn-controller for all the above NB updates.
-+check as northd ovn-appctl -t ovn-northd resume
-+
-+AS_BOX([Wait for sw0-p1 to be up])
-+wait_for_ports_up sw0-p1
-+
-+# When the port group pg1 is updated, it should not result in
-+# any assert in ovn-controller.
-+ovn-nbctl --wait=hv pg-set-ports pg1 sw0-p1 sw0-p2 sw0-p3
-+AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)])
-+check ovn-nbctl --wait=hv sync
-+
-+# Check OVS flows are installed properly.
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \
-+ grep "priority=2002" | grep conjunction | \
-+ sed 's/conjunction([[^)]]*)/conjunction()/g' | sort], [0], [dnl
-+ table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x10/0xfff0 actions=conjunction()
-+ table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x100/0xff00 actions=conjunction()
-+ table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x1000/0xf000 actions=conjunction()
-+ table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x2/0xfffe actions=conjunction()
-+ table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x20/0xffe0 actions=conjunction()
-+ table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x200/0xfe00 actions=conjunction()
-+ table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x2000/0xe000 actions=conjunction()
-+ table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x4/0xfffc actions=conjunction()
-+ table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x40/0xffc0 actions=conjunction()
-+ table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x400/0xfc00 actions=conjunction()
-+ table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x4000/0xc000 actions=conjunction()
-+ table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x8/0xfff8 actions=conjunction()
-+ table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x80/0xff80 actions=conjunction()
-+ table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x800/0xf800 actions=conjunction()
-+ table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x8000/0x8000 actions=conjunction()
-+ table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=1 actions=conjunction()
-+ table=45, priority=2002,udp,reg0=0x100/0x100,reg15=0x3,metadata=0x1,nw_src=192.168.47.3 actions=conjunction()
-+ table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x10/0xfff0 actions=conjunction()
-+ table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x100/0xff00 actions=conjunction()
-+ table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x1000/0xf000 actions=conjunction()
-+ table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x2/0xfffe actions=conjunction()
-+ table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x20/0xffe0 actions=conjunction()
-+ table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x200/0xfe00 actions=conjunction()
-+ table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x2000/0xe000 actions=conjunction()
-+ table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x4/0xfffc actions=conjunction()
-+ table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x40/0xffc0 actions=conjunction()
-+ table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x400/0xfc00 actions=conjunction()
-+ table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x4000/0xc000 actions=conjunction()
-+ table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x8/0xfff8 actions=conjunction()
-+ table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x80/0xff80 actions=conjunction()
-+ table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x800/0xf800 actions=conjunction()
-+ table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x8000/0x8000 actions=conjunction()
-+ table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=1 actions=conjunction()
-+ table=45, priority=2002,udp,reg0=0x80/0x80,reg15=0x3,metadata=0x1,nw_src=192.168.47.3 actions=conjunction()
-+])
-+
-+OVN_CLEANUP([hv1])
-+AT_CLEANUP
-+
-+AT_SETUP([ovn -- OVN FDB (MAC learning) - 2 HVs, 2 LS, 1 LR ])
-+ovn_start
-+
-+# Create the first logical switch with one port
-+check ovn-nbctl ls-add sw0
-+check ovn-nbctl lsp-add sw0 sw0-p1
-+check ovn-nbctl lsp-set-addresses sw0-p1 "50:54:00:00:00:03 10.0.0.3" unknown
-+
-+check ovn-nbctl lsp-add sw0 sw0-p2
-+check ovn-nbctl lsp-set-addresses sw0-p2 "50:54:00:00:00:04 10.0.0.4"
-+# Port security is set for sw0-p2
-+check ovn-nbctl lsp-set-port-security sw0-p2 "50:54:00:00:00:04 10.0.0.4"
-+
-+# sw0-p1 and sw0-p3 have unknown address and no port security.
-+# FDB should be enabled for these lports.
-+check ovn-nbctl lsp-add sw0 sw0-p3
-+check ovn-nbctl lsp-set-addresses sw0-p3 unknown
-+
-+# Create the second logical switch with one port
-+check ovn-nbctl ls-add sw1
-+check ovn-nbctl lsp-add sw1 sw1-p1
-+check ovn-nbctl lsp-set-addresses sw1-p1 "40:54:00:00:00:03 11.0.0.3" unknown
-+
-+check ovn-nbctl lsp-add sw1 sw1-p2
-+check ovn-nbctl lsp-set-addresses sw1-p2 "40:54:00:00:00:04 11.0.0.4"
-+check ovn-nbctl lsp-set-port-security sw1-p2 "40:54:00:00:00:04 11.0.0.4"
-+
-+# Create a logical router and attach both logical switches
-+check ovn-nbctl lr-add lr0
-+check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24
-+check ovn-nbctl lsp-add sw0 sw0-lr0
-+check ovn-nbctl lsp-set-type sw0-lr0 router
-+check ovn-nbctl lsp-set-addresses sw0-lr0 router
-+check ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
-+
-+check ovn-nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 11.0.0.1/24
-+check ovn-nbctl lsp-add sw1 sw1-lr0
-+check ovn-nbctl lsp-set-type sw1-lr0 router
-+check ovn-nbctl lsp-set-addresses sw1-lr0 router
-+check ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1
-+ovn-nbctl --wait=hv sync
-+
-+net_add n1
-+
-+sim_add hv1
-+as hv1
-+ovs-vsctl add-br br-phys
-+ovn_attach n1 br-phys 192.168.0.1
-+ovs-vsctl -- add-port br-int hv1-vif1 -- \
-+ set interface hv1-vif1 external-ids:iface-id=sw0-p1 \
-+ options:tx_pcap=hv1/vif1-tx.pcap \
-+ options:rxq_pcap=hv1/vif1-rx.pcap \
-+ ofport-request=1
-+ovs-vsctl -- add-port br-int hv1-vif2 -- \
-+ set interface hv1-vif2 external-ids:iface-id=sw1-p2 \
-+ options:tx_pcap=hv1/vif2-tx.pcap \
-+ options:rxq_pcap=hv1/vif2-rx.pcap \
-+ ofport-request=2
-+ovs-vsctl -- add-port br-int hv1-vif3 -- \
-+ set interface hv1-vif3 external-ids:iface-id=sw0-p3 \
-+ options:tx_pcap=hv1/vif3-tx.pcap \
-+ options:rxq_pcap=hv1/vif3-rx.pcap \
-+ ofport-request=3
-+
-+sim_add hv2
-+as hv2
-+ovs-vsctl add-br br-phys
-+ovn_attach n1 br-phys 192.168.0.2
-+ovs-vsctl -- add-port br-int hv2-vif1 -- \
-+ set interface hv2-vif1 external-ids:iface-id=sw0-p2 \
-+ options:tx_pcap=hv2/vif1-tx.pcap \
-+ options:rxq_pcap=hv2/vif1-rx.pcap \
-+ ofport-request=1
-+ovs-vsctl -- add-port br-int hv2-vif2 -- \
-+ set interface hv2-vif2 external-ids:iface-id=sw1-p1 \
-+ options:tx_pcap=hv2/vif2-tx.pcap \
-+ options:rxq_pcap=hv2/vif2-rx.pcap \
-+ ofport-request=2
-+
-+OVN_POPULATE_ARP
-+
-+ip_to_hex() {
-+ printf "%02x%02x%02x%02x" "$@"
-+}
-+
-+send_icmp_packet() {
-+ local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv4_src=$5 ipv4_dst=$6 ip_chksum=$7 data=$8
-+ shift 8
-+
-+ local ip_ttl=ff
-+ local ip_len=001c
-+ local packet=${eth_dst}${eth_src}08004500${ip_len}00004000${ip_ttl}01${ip_chksum}${ipv4_src}${ipv4_dst}${data}
-+ echo $packet > expected
-+ as hv$hv ovs-appctl netdev-dummy/receive hv$hv-vif$inport $packet
-+}
-+
-+reset_pcap_file() {
-+ local iface=$1
-+ local pcap_file=$2
-+ ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
-+options:rxq_pcap=dummy-rx.pcap
-+ rm -f ${pcap_file}*.pcap
-+ ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \
-+options:rxq_pcap=${pcap_file}-rx.pcap
-+}
-+
-+trim_zeros() {
-+ sed 's/\(00\)\{1,\}$//'
-+}
-+
-+AS_BOX([Wait for all ports to be up])
-+wait_for_ports_up
-+
-+# Check that there is put_fdb() flow added by ovn-northd for sw0-p1
-+ovn-sbctl dump-flows sw0 > sw0flows
-+AT_CAPTURE_FILE([sw0flows])
-+
-+AT_CHECK([grep "ls_in_lookup_fdb" sw0flows | sort], [0], [dnl
-+ table=3 (ls_in_lookup_fdb ), priority=0 , dnl
-+match=(1), action=(next;)
-+ table=3 (ls_in_lookup_fdb ), priority=100 , dnl
-+match=(inport == "sw0-p1"), action=(reg0[[11]] = lookup_fdb(inport, eth.src); next;)
-+ table=3 (ls_in_lookup_fdb ), priority=100 , dnl
-+match=(inport == "sw0-p3"), action=(reg0[[11]] = lookup_fdb(inport, eth.src); next;)
-+])
-+
-+AT_CHECK([grep "ls_in_put_fdb" sw0flows | sort], [0], [dnl
-+ table=4 (ls_in_put_fdb ), priority=0 , dnl
-+match=(1), action=(next;)
-+ table=4 (ls_in_put_fdb ), priority=100 , dnl
-+match=(inport == "sw0-p1" && reg0[[11]] == 0), action=(put_fdb(inport, eth.src); next;)
-+ table=4 (ls_in_put_fdb ), priority=100 , dnl
-+match=(inport == "sw0-p3" && reg0[[11]] == 0), action=(put_fdb(inport, eth.src); next;)
-+])
-+
-+# Send a packet from sw0-p1 with a different mac not present
-+# in it's addresses.
-+AS_BOX([Send a pkt from sw0-p1 with a different mac address])
-+
-+# Use the src mac 50:54:00:00:00:13 instead of 50:54:00:00:00:03
-+src_mac=505400000013
-+src_ip=$(ip_to_hex 10 0 0 13)
-+
-+# send the packet to sw0-p2
-+dst_mac=505400000004
-+dst_ip=$(ip_to_hex 10 0 0 4)
-+
-+data=0800bee4391a0001
-+send_icmp_packet 1 1 $src_mac $dst_mac $src_ip $dst_ip 0000 $data
-+OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
-+
-+# There should be one row in fdb
-+AS_BOX([Check that the FDB entry is created])
-+wait_row_count FDB 1
-+
-+sw0_dpkey=$(fetch_column datapath_binding tunnel_key external_ids:name=sw0)
-+sw0p1_dpkey=$(fetch_column port_binding tunnel_key logical_port=sw0-p1)
-+sw0p3_dpkey=$(fetch_column port_binding tunnel_key logical_port=sw0-p3)
-+
-+check_column '50:54:00:00:00:13' fdb mac
-+check_column $sw0_dpkey fdb dp_key
-+check_column $sw0p1_dpkey fdb port_key
-+
-+# Make sure that OVS tables 71 and 72 are populated on both hv1 and hv2.
-+AS_BOX([Check that ovn-controller programs the flows for FDB])
-+as hv1 ovs-ofctl dump-flows br-int table=71 > hv1_offlows_table71.txt
-+as hv2 ovs-ofctl dump-flows br-int table=71 > hv2_offlows_table71.txt
-+
-+AT_CAPTURE_FILE([hv1_offlows_table71.txt])
-+AT_CAPTURE_FILE([hv2_offlows_table71.txt])
-+AT_CHECK([cat hv1_offlows_table71.txt | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
-+priority=100,metadata=0x1,dl_dst=50:54:00:00:00:13 actions=load:0x1->NXM_NX_REG15[[]]
-+])
-+
-+AT_CHECK([cat hv2_offlows_table71.txt | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
-+priority=100,metadata=0x1,dl_dst=50:54:00:00:00:13 actions=load:0x1->NXM_NX_REG15[[]]
-+])
-+
-+as hv1 ovs-ofctl dump-flows br-int table=72 > hv1_offlows_table72.txt
-+as hv2 ovs-ofctl dump-flows br-int table=72 > hv2_offlows_table72.txt
-+
-+AT_CAPTURE_FILE([hv1_offlows_table72.txt])
-+AT_CAPTURE_FILE([hv2_offlows_table72.txt])
-+AT_CHECK([cat hv1_offlows_table72.txt | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
-+priority=100,reg14=0x1,metadata=0x1,dl_src=50:54:00:00:00:13 actions=load:0x1->NXM_NX_REG10[[8]]
-+])
-+
-+AT_CHECK([cat hv2_offlows_table72.txt | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
-+priority=100,reg14=0x1,metadata=0x1,dl_src=50:54:00:00:00:13 actions=load:0x1->NXM_NX_REG10[[8]]
-+])
-+
-+# Use the src mac 50:54:00:00:00:14 instead of 50:54:00:00:00:03
-+src_mac=505400000014
-+src_ip=$(ip_to_hex 10 0 0 14)
-+
-+as hv2 reset_pcap_file hv2-vif1 hv2/vif1
-+
-+send_icmp_packet 1 1 $src_mac $dst_mac $src_ip $dst_ip 0000 $data
-+OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
-+
-+# There should be two rows in fdb
-+wait_row_count FDB 2
-+
-+check_column "50:54:00:00:00:13 50:54:00:00:00:14" fdb mac
-+check_column "$sw0_dpkey $sw0_dpkey" fdb dp_key
-+check_column "$sw0p1_dpkey $sw0p1_dpkey" fdb port_key
-+
-+# Make sure that OVS tables 71 and 72 are populated on both hv1 and hv2.
-+as hv1 ovs-ofctl dump-flows br-int table=71 > hv1_offlows_table71.txt
-+as hv2 ovs-ofctl dump-flows br-int table=71 > hv2_offlows_table71.txt
-+
-+AT_CAPTURE_FILE([hv1_offlows_table71.txt])
-+AT_CAPTURE_FILE([hv2_offlows_table71.txt])
-+AT_CHECK([cat hv1_offlows_table71.txt | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
-+priority=100,metadata=0x1,dl_dst=50:54:00:00:00:13 actions=load:0x1->NXM_NX_REG15[[]]
-+priority=100,metadata=0x1,dl_dst=50:54:00:00:00:14 actions=load:0x1->NXM_NX_REG15[[]]
-+])
-+
-+AT_CHECK([cat hv2_offlows_table71.txt | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
-+priority=100,metadata=0x1,dl_dst=50:54:00:00:00:13 actions=load:0x1->NXM_NX_REG15[[]]
-+priority=100,metadata=0x1,dl_dst=50:54:00:00:00:14 actions=load:0x1->NXM_NX_REG15[[]]
-+])
-+
-+as hv1 ovs-ofctl dump-flows br-int table=72 > hv1_offlows_table72.txt
-+as hv2 ovs-ofctl dump-flows br-int table=72 > hv2_offlows_table72.txt
-+
-+AT_CAPTURE_FILE([hv1_offlows_table72.txt])
-+AT_CAPTURE_FILE([hv2_offlows_table72.txt])
-+AT_CHECK([cat hv1_offlows_table72.txt | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
-+priority=100,reg14=0x1,metadata=0x1,dl_src=50:54:00:00:00:13 actions=load:0x1->NXM_NX_REG10[[8]]
-+priority=100,reg14=0x1,metadata=0x1,dl_src=50:54:00:00:00:14 actions=load:0x1->NXM_NX_REG10[[8]]
-+])
-+
-+AT_CHECK([cat hv2_offlows_table72.txt | grep -v NXST | cut -d ' ' -f8- | sort], [0], [dnl
-+priority=100,reg14=0x1,metadata=0x1,dl_src=50:54:00:00:00:13 actions=load:0x1->NXM_NX_REG10[[8]]
-+priority=100,reg14=0x1,metadata=0x1,dl_src=50:54:00:00:00:14 actions=load:0x1->NXM_NX_REG10[[8]]
-+])
-+
-+as hv1 reset_pcap_file hv1-vif1 hv1/vif1
-+as hv2 reset_pcap_file hv2-vif1 hv2/vif1
-+
-+# Send the packet from sw0-p2 to sw0-p1 with the dst mac 50:54:00:00:00:13
-+src_mac=505400000004
-+src_ip=$(ip_to_hex 10 0 0 4)
-+
-+dst_mac=505400000013
-+dst_ip=$(ip_to_hex 10 0 0 13)
-+
-+send_icmp_packet 1 2 $src_mac $dst_mac $src_ip $dst_ip 0000 $data
-+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
-+
-+as hv1 reset_pcap_file hv1-vif1 hv1/vif1
-+dst_mac=505400000014
-+dst_ip=$(ip_to_hex 10 0 0 14)
-+
-+send_icmp_packet 1 2 $src_mac $dst_mac $src_ip $dst_ip 0000 $data
-+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
-+
-+as hv1 reset_pcap_file hv1-vif1 hv1/vif1
-+as hv1 reset_pcap_file hv1-vif3 hv1/vif3
-+
-+# Send a packet from sw0-p2 to an unknown mac. Should be received
-+# by both sw0-p1 and sw0-p3 (as unknown is set).
-+AS_BOX([Send pkt from sw0-p2 to an unknown mac])
-+
-+src_mac=505400000004
-+src_ip=$(ip_to_hex 10 0 0 4)
-+
-+dst_mac=505400000023
-+dst_ip=$(ip_to_hex 10 0 0 23)
-+
-+send_icmp_packet 1 2 $src_mac $dst_mac $src_ip $dst_ip 0000 $data
-+
-+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
-+OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected])
-+
-+AS_BOX([Flip the mac - 50:54:00:00:00:13 from sw0-p1 to sw0-p3])
-+
-+# Use the src mac 50:54:00:00:00:13
-+src_mac=505400000013
-+src_ip=$(ip_to_hex 10 0 0 23)
-+
-+# send the packet to sw0-p2
-+dst_mac=505400000004
-+dst_ip=$(ip_to_hex 10 0 0 4)
-+
-+data=0800bee4391a0001
-+
-+as hv2 reset_pcap_file hv2-vif1 hv2/vif1
-+as hv1 reset_pcap_file hv1-vif3 hv1/vif3
-+
-+# Send the pkt from sw0-p3 to sw0-p2.
-+send_icmp_packet 3 1 $src_mac $dst_mac $src_ip $dst_ip 0000 $data
-+OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
-+
-+# fdb row count should be still 2. But the mac 50:54:00:00:00:13
-+# should be learnt on sw0-p3.
-+
-+wait_row_count FDB 2
-+
-+check_column "50:54:00:00:00:13 50:54:00:00:00:14" fdb mac
-+check_column "$sw0_dpkey $sw0_dpkey" fdb dp_key
-+check_column "$sw0p1_dpkey $sw0p3_dpkey" fdb port_key
-+
-+check_column "$sw0p3_dpkey" fdb port_key mac="50\:54\:00\:00\:00\:13"
-+
-+as hv1 reset_pcap_file hv1-vif1 hv1/vif1
-+as hv1 reset_pcap_file hv1-vif3 hv1/vif3
-+
-+# Send the packet from sw0-p2 to sw0-p3 with the dst mac 50:54:00:00:00:13
-+src_mac=505400000004
-+src_ip=$(ip_to_hex 10 0 0 4)
-+
-+dst_mac=505400000013
-+dst_ip=$(ip_to_hex 10 0 0 13)
-+
-+send_icmp_packet 1 2 $src_mac $dst_mac $src_ip $dst_ip 0000 $data
-+OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected])
-+
-+# sw0-p1 should not receive the packet.
-+: > expected
-+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
-+
-+AS_BOX([Test routing])
-+
-+# Test the routing.
-+# Send the packet from sw1-p2 (hv1) to sw0-p1 (hv1) with dst ip 10.0.0.14
-+# The packet should be delivered to sw0-p1 with dst mac 50:54:00:00:00:14
-+# Before sending add mac_binding entry for 10.0.0.14
-+
-+lr0_dp_uuid=$(fetch_column datapath_binding _uuid external_ids:name=lr0)
-+
-+ovn-sbctl create mac_binding ip=10.0.0.14 logical_port=lr0-sw0 \
-+mac="50\:54\:00\:00\:00\:14" datapath=$lr0_dp_uuid
-+
-+# Wait till the mac_binding flows appear in hv1
-+OVS_WAIT_UNTIL([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=66 \
-+| grep -c reg0=0xa00000e)])
-+
-+src_mac=405400000004
-+src_ip=$(ip_to_hex 11 0 0 4)
-+
-+dst_mac=00000000ff02 # lr0-sw1 mac
-+dst_ip=$(ip_to_hex 10 0 0 14)
-+
-+as hv1 reset_pcap_file hv1-vif1 hv1/vif1
-+as hv1 reset_pcap_file hv1-vif3 hv1/vif3
-+
-+send_icmp_packet 2 1 $src_mac $dst_mac $src_ip $dst_ip 0000 $data
-+
-+exp_packet=50540000001400000000ff0108004500001c00004000fe010100${src_ip}${dst_ip}${data}
-+echo $exp_packet > expected
-+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
-+
-+# sw0-p3 should not receive the packet.
-+: > expected
-+OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected])
-+
-+# Now the send the packet from sw1-p1 (hv2) to sw0-p1 (hv1) with dst ip 10.0.0.14
-+# The acket should be delivered to sw0-p1 with dst mac 50:54:00:00:00:14
-+
-+src_mac=405400000003
-+src_ip=$(ip_to_hex 11 0 0 3)
-+
-+dst_mac=00000000ff02 # lr0-sw1 mac
-+dst_ip=$(ip_to_hex 10 0 0 14)
-+
-+as hv1 reset_pcap_file hv1-vif1 hv1/vif1
-+send_icmp_packet 2 2 $src_mac $dst_mac $src_ip $dst_ip 0000 $data
-+
-+exp_packet=50540000001400000000ff0108004500001c00004000fe010100${src_ip}${dst_ip}${data}
-+echo $exp_packet > expected
-+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
-+
-+AS_BOX([Clear the FDB rows])
-+
-+# Clear the fdb rows.
-+check ovn-sbctl --all destroy fdb
-+ovn-sbctl list fdb
-+
-+as hv1 reset_pcap_file hv1-vif1 hv1/vif1
-+as hv1 reset_pcap_file hv1-vif3 hv1/vif3
-+
-+# Send the packet from sw0-p2 to sw0-p1 with the dst mac 50:54:00:00:00:14
-+# It should be delivered to both sw0-p1 and sw0-p3 since we have cleared the
-+# FDB table.
-+src_mac=505400000004
-+src_ip=$(ip_to_hex 10 0 0 4)
-+
-+dst_mac=505400000014
-+dst_ip=$(ip_to_hex 10 0 0 13)
-+
-+send_icmp_packet 1 2 $src_mac $dst_mac $src_ip $dst_ip 0000 $data
-+
-+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
-+OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected])
-+
-+# Make sure that OVS tables 71 and 72 are empty.
-+as hv1 ovs-ofctl dump-flows br-int table=71 > hv1_offlows_table71.txt
-+as hv2 ovs-ofctl dump-flows br-int table=71 > hv2_offlows_table71.txt
-+
-+AT_CAPTURE_FILE([hv1_offlows_table71.txt])
-+AT_CAPTURE_FILE([hv2_offlows_table71.txt])
-+AT_CHECK([cat hv1_offlows_table71.txt | grep -v NXST], [1], [dnl
-+])
-+
-+AT_CHECK([cat hv2_offlows_table71.txt | grep -v NXST], [1], [dnl
-+])
-+
-+as hv1 ovs-ofctl dump-flows br-int table=72 > hv1_offlows_table72.txt
-+as hv2 ovs-ofctl dump-flows br-int table=72 > hv2_offlows_table72.txt
-+
-+AT_CAPTURE_FILE([hv1_offlows_table72.txt])
-+AT_CAPTURE_FILE([hv2_offlows_table72.txt])
-+AT_CHECK([cat hv1_offlows_table72.txt | grep -v NXST], [1], [dnl
-+])
-+
-+AT_CHECK([cat hv2_offlows_table72.txt | grep -v NXST], [1], [dnl
-+])
-+
-+OVN_CLEANUP([hv1], [hv2])
-+AT_CLEANUP
-diff --git a/tests/ovs-macros.at b/tests/ovs-macros.at
-index 05b17ebce..8b1b03e24 100644
---- a/tests/ovs-macros.at
-+++ b/tests/ovs-macros.at
-@@ -266,14 +266,16 @@ m4_define([OVS_WAIT_UNTIL],
- [OVS_WAIT([$1], [$2], [AT_LINE], [until $1])])
-
- dnl OVS_WAIT_FOR_OUTPUT(COMMAND, EXIT-STATUS, STDOUT, STDERR)
-+dnl OVS_WAIT_FOR_OUTPUT_UNQUOTED(COMMAND, EXIT-STATUS, STDOUT, STDERR)
- dnl
- dnl Executes shell COMMAND in a loop until it exits with status EXIT-STATUS,
- dnl prints STDOUT on stdout, and prints STDERR on stderr. If this doesn't
- dnl happen within a reasonable time limit, then the test fails.
--m4_define([OVS_WAIT_FOR_OUTPUT], [dnl
-+dnl
-+dnl The UNQUOTED version expands shell $variables, $(command)s, and so on.
-+dnl The plain version does not
-+m4_define([OVS_WAIT_FOR_OUTPUT__], [dnl
- wait_expected_status=m4_if([$2], [], [0], [$2])
--AT_DATA([wait-expected-stdout], [$3])
--AT_DATA([wait-expected-stderr], [$4])
- ovs_wait_command() {
- $1
- }
-@@ -293,6 +295,18 @@ ovs_wait_failed () {
- }
- ovs_wait "AS_ESCAPE([AT_LINE])" "for output from AS_ESCAPE([$1])"
- ])
-+m4_define([OVS_WAIT_FOR_OUTPUT], [dnl
-+AT_DATA([wait-expected-stdout], [$3])
-+AT_DATA([wait-expected-stderr], [$4])
-+OVS_WAIT_FOR_OUTPUT__([$1], [$2])
-+])
-+m4_define([OVS_WAIT_FOR_OUTPUT_UNQUOTED], [dnl
-+cat > wait-expected-stdout < wait-expected-stderr < rst.pcap &])
-+sleep 1
-+NS_CHECK_EXEC([foo1], [wget -q 30.0.0.10],[4])
-+
-+OVS_WAIT_UNTIL([
-+ n_reset=$(cat rst.pcap | wc -l)
-+ test "${n_reset}" = "1"
-+])
-+
- OVS_APP_EXIT_AND_WAIT([ovn-controller])
-
- as ovn-sb
-@@ -2212,6 +2224,144 @@ tcp,orig=(src=172.16.1.2,dst=192.168.2.2,sport=,dport=),reply=
-
- OVS_WAIT_UNTIL([check_est_flows], [check established flows])
-
-+ovn-nbctl set logical_router R2 options:lb_force_snat_ip=router_ip
-+
-+# Destroy the load balancer and create again. ovn-controller will
-+# clear the OF flows and re add again and clears the n_packets
-+# for these flows.
-+ovn-nbctl destroy load_balancer $uuid
-+uuid=`ovn-nbctl create load_balancer vips:30.0.0.1="192.168.1.2,192.168.2.2"`
-+ovn-nbctl set logical_router R2 load_balancer=$uuid
-+
-+# Config OVN load-balancer with another VIP (this time with ports).
-+ovn-nbctl set load_balancer $uuid vips:'"30.0.0.2:8000"'='"192.168.1.2:80,192.168.2.2:80"'
-+
-+ovn-nbctl list load_balancer
-+ovn-sbctl dump-flows R2
-+OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-flows br-int table=41 | \
-+grep 'nat(src=20.0.0.2)'])
-+
-+rm -f wget*.log
-+
-+dnl Test load-balancing that includes L4 ports in NAT.
-+for i in `seq 1 20`; do
-+ echo Request $i
-+ NS_CHECK_EXEC([alice1], [wget 30.0.0.2:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
-+done
-+
-+dnl Each server should have at least one connection.
-+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.2) |
-+sed -e 's/zone=[[0-9]]*/zone=/'], [0], [dnl
-+tcp,orig=(src=172.16.1.2,dst=30.0.0.2,sport=,dport=),reply=(src=192.168.1.2,dst=172.16.1.2,sport=,dport=),zone=,labels=0x2,protoinfo=(state=)
-+tcp,orig=(src=172.16.1.2,dst=30.0.0.2,sport=,dport=),reply=(src=192.168.2.2,dst=172.16.1.2,sport=,dport=),zone=,labels=0x2,protoinfo=(state=)
-+])
-+
-+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(20.0.0.2) |
-+sed -e 's/zone=[[0-9]]*/zone=/'], [0], [dnl
-+tcp,orig=(src=172.16.1.2,dst=192.168.1.2,sport=,dport=),reply=(src=192.168.1.2,dst=20.0.0.2,sport=,dport=),zone=,protoinfo=(state=)
-+tcp,orig=(src=172.16.1.2,dst=192.168.2.2,sport=,dport=),reply=(src=192.168.2.2,dst=20.0.0.2,sport=,dport=),zone=,protoinfo=(state=)
-+])
-+
-+OVS_WAIT_UNTIL([check_est_flows], [check established flows])
-+
-+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
-+
-+AT_SETUP([ovn -- load balancing in gateway router hairpin scenario])
-+AT_KEYWORDS([ovnlb])
-+
-+CHECK_CONNTRACK()
-+CHECK_CONNTRACK_NAT()
-+ovn_start
-+OVS_TRAFFIC_VSWITCHD_START()
-+ADD_BR([br-int])
-+check ovs-vsctl add-br br-ext
-+
-+
-+# Set external-ids in br-int needed for ovn-controller
-+ovs-vsctl \
-+ -- set Open_vSwitch . external-ids:system-id=hv1 \
-+ -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
-+ -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
-+ -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
-+ -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
-+
-+# Start ovn-controller
-+start_daemon ovn-controller
-+
-+check ovn-nbctl lr-add R1
-+
-+check ovn-nbctl ls-add sw0
-+check ovn-nbctl ls-add public
-+
-+check ovn-nbctl lrp-add R1 rp-sw0 00:00:01:01:02:03 192.168.1.1/24
-+check ovn-nbctl lrp-add R1 rp-public 00:00:02:01:02:03 172.16.1.1/24
-+
-+check ovn-nbctl set logical_router R1 options:chassis=hv1
-+
-+check ovn-nbctl lsp-add sw0 sw0-rp -- set Logical_Switch_Port sw0-rp \
-+ type=router options:router-port=rp-sw0 \
-+ -- lsp-set-addresses sw0-rp router
-+
-+check ovn-nbctl lsp-add public public-rp -- set Logical_Switch_Port public-rp \
-+ type=router options:router-port=rp-public \
-+ -- lsp-set-addresses public-rp router
-+
-+check ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=phynet:br-ext
-+
-+check ovn-nbctl lsp-add public public1 \
-+ -- lsp-set-addresses public1 unknown \
-+ -- lsp-set-type public1 localnet \
-+ -- lsp-set-options public1 network_name=phynet
-+
-+ADD_NAMESPACES(server)
-+ADD_VETH(s1, server, br-ext, "172.16.1.100/24", "1a:00:00:00:00:01", \
-+ "172.16.1.1")
-+
-+OVS_WAIT_UNTIL([test "$(ip netns exec server ip a | grep fe80 | grep tentative)" = ""])
-+
-+ADD_NAMESPACES(client)
-+ADD_VETH(c1, client, br-ext, "172.16.1.110/24", "1a:00:00:00:00:02", \
-+ "172.16.1.1")
-+
-+OVS_WAIT_UNTIL([test "$(ip netns exec client ip a | grep fe80 | grep tentative)" = ""])
-+
-+# Start webservers in 'server'.
-+OVS_START_L7([server], [http])
-+
-+# Create a load balancer and associate to R1
-+check ovn-nbctl lb-add lb1 172.16.1.150:80 172.16.1.100:80
-+check ovn-nbctl lr-lb-add R1 lb1
-+
-+check ovn-nbctl --wait=hv sync
-+
-+for i in $(seq 1 5); do
-+ echo Request $i
-+ NS_CHECK_EXEC([client], [wget 172.16.1.100 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
-+done
-+
-+# Now send the traffic from client to the VIP - 172.16.1.150
-+check ovn-nbctl set logical_router R1 options:lb_force_snat_ip=router_ip
-+check ovn-nbctl --wait=hv sync
-+
-+for i in $(seq 1 5); do
-+ echo Request $i
-+ NS_CHECK_EXEC([client], [wget 172.16.1.150 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
-+done
-+
- OVS_APP_EXIT_AND_WAIT([ovn-controller])
-
- as ovn-sb
-@@ -2225,6 +2375,7 @@ OVS_APP_EXIT_AND_WAIT([ovn-northd])
-
- as
- OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
-+/Failed to acquire.*/d
- /connection dropped.*/d"])
- AT_CLEANUP
-
-@@ -4151,7 +4302,7 @@ ovn-nbctl lsp-set-type sw1-lr0 router
- ovn-nbctl lsp-set-addresses sw1-lr0 router
- ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1
-
--ovn-nbctl lb-add lb1 10.0.0.10:80 10.0.0.3:80,20.0.0.3:80
-+ovn-nbctl --reject lb-add lb1 10.0.0.10:80 10.0.0.3:80,20.0.0.3:80
-
- ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:10.0.0.3=sw0-p1:10.0.0.2
- ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:20.0.0.3=sw1-p1:20.0.0.2
-@@ -4266,6 +4417,20 @@ ovn-sbctl list service_monitor
- OVS_WAIT_UNTIL([test 2 = `ovn-sbctl --bare --columns status find \
- service_monitor protocol=udp | sed '/^$/d' | grep offline | wc -l`])
-
-+# Stop webserer in sw1-p1
-+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 &])
-+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)
-+ test "${n_reset}" = "1"
-+])
-+
- OVS_APP_EXIT_AND_WAIT([ovn-controller])
-
- as ovn-sb
-@@ -4309,10 +4474,14 @@ start_daemon ovn-controller
- # One logical switch with IPv4 load balancers that hairpin the traffic.
- ovn-nbctl ls-add sw
- ovn-nbctl lsp-add sw lsp -- lsp-set-addresses lsp 00:00:00:00:00:01
--ovn-nbctl lb-add lb-ipv4-tcp 88.88.88.88:8080 42.42.42.1:4041 tcp
--ovn-nbctl lb-add lb-ipv4-udp 88.88.88.88:4040 42.42.42.1:2021 udp
-+ovn-nbctl lb-add lb-ipv4-tcp 88.88.88.88:8080 42.42.42.1:4041 tcp
-+ovn-nbctl lb-add lb-ipv4-tcp-dup 88.88.88.89:8080 42.42.42.1:4041 tcp
-+ovn-nbctl lb-add lb-ipv4-udp 88.88.88.88:4040 42.42.42.1:2021 udp
-+ovn-nbctl lb-add lb-ipv4-udp-dup 88.88.88.89:4040 42.42.42.1:2021 udp
- ovn-nbctl ls-lb-add sw lb-ipv4-tcp
-+ovn-nbctl ls-lb-add sw lb-ipv4-tcp-dup
- ovn-nbctl ls-lb-add sw lb-ipv4-udp
-+ovn-nbctl ls-lb-add sw lb-ipv4-udp-dup
-
- ovn-nbctl lr-add rtr
- ovn-nbctl lrp-add rtr rtr-sw 00:00:00:00:01:00 42.42.42.254/24
-@@ -4328,24 +4497,26 @@ ADD_VETH(lsp, lsp, br-int, "42.42.42.1/24", "00:00:00:00:00:01", \
- ovn-nbctl --wait=hv -t 3 sync
-
- # Start IPv4 TCP server on lsp.
--NS_CHECK_EXEC([lsp], [timeout 2s nc -l 42.42.42.1 4041 &], [0])
-+NS_CHECK_EXEC([lsp], [timeout 2s nc -k -l 42.42.42.1 4041 &], [0])
-
--# Check that IPv4 TCP hairpin connection succeeds.
-+# Check that IPv4 TCP hairpin connection succeeds on both VIPs.
- NS_CHECK_EXEC([lsp], [nc 88.88.88.88 8080 -z], [0])
-+NS_CHECK_EXEC([lsp], [nc 88.88.88.89 8080 -z], [0])
-
- # Capture IPv4 UDP hairpinned packets.
--filter="src 88.88.88.88 and dst 42.42.42.1 and dst port 2021 and udp"
--NS_CHECK_EXEC([lsp], [tcpdump -n -c 1 -i lsp ${filter} > lsp.pcap &])
-+filter="dst 42.42.42.1 and dst port 2021 and udp"
-+NS_CHECK_EXEC([lsp], [tcpdump -n -c 2 -i lsp ${filter} > lsp.pcap &])
-
- sleep 1
-
- # Generate IPv4 UDP hairpin traffic.
- NS_CHECK_EXEC([lsp], [nc -u 88.88.88.88 4040 -z &], [0])
-+NS_CHECK_EXEC([lsp], [nc -u 88.88.88.89 4040 -z &], [0])
-
- # Check hairpin traffic.
- OVS_WAIT_UNTIL([
- total_pkts=$(cat lsp.pcap | wc -l)
-- test "${total_pkts}" = "1"
-+ test "${total_pkts}" = "2"
- ])
-
- OVS_APP_EXIT_AND_WAIT([ovn-controller])
-@@ -4388,10 +4559,14 @@ start_daemon ovn-controller
- # One logical switch with IPv6 load balancers that hairpin the traffic.
- ovn-nbctl ls-add sw
- ovn-nbctl lsp-add sw lsp -- lsp-set-addresses lsp 00:00:00:00:00:01
--ovn-nbctl lb-add lb-ipv6-tcp [[8800::0088]]:8080 [[4200::1]]:4041 tcp
--ovn-nbctl lb-add lb-ipv6-udp [[8800::0088]]:4040 [[4200::1]]:2021 udp
-+ovn-nbctl lb-add lb-ipv6-tcp [[8800::0088]]:8080 [[4200::1]]:4041 tcp
-+ovn-nbctl lb-add lb-ipv6-tcp-dup [[8800::0089]]:8080 [[4200::1]]:4041 tcp
-+ovn-nbctl lb-add lb-ipv6-udp [[8800::0088]]:4040 [[4200::1]]:2021 udp
-+ovn-nbctl lb-add lb-ipv6-udp-dup [[8800::0089]]:4040 [[4200::1]]:2021 udp
- ovn-nbctl ls-lb-add sw lb-ipv6-tcp
-+ovn-nbctl ls-lb-add sw lb-ipv6-tcp-dup
- ovn-nbctl ls-lb-add sw lb-ipv6-udp
-+ovn-nbctl ls-lb-add sw lb-ipv6-udp-dup
-
- ovn-nbctl lr-add rtr
- ovn-nbctl lrp-add rtr rtr-sw 00:00:00:00:01:00 4200::00ff/64
-@@ -4406,24 +4581,26 @@ OVS_WAIT_UNTIL([test "$(ip netns exec lsp ip a | grep 4200::1 | grep tentative)"
- ovn-nbctl --wait=hv -t 3 sync
-
- # Start IPv6 TCP server on lsp.
--NS_CHECK_EXEC([lsp], [timeout 2s nc -l 4200::1 4041 &], [0])
-+NS_CHECK_EXEC([lsp], [timeout 2s nc -k -l 4200::1 4041 &], [0])
-
--# Check that IPv6 TCP hairpin connection succeeds.
-+# Check that IPv6 TCP hairpin connection succeeds on both VIPs.
- NS_CHECK_EXEC([lsp], [nc 8800::0088 8080 -z], [0])
-+NS_CHECK_EXEC([lsp], [nc 8800::0089 8080 -z], [0])
-
- # Capture IPv4 UDP hairpinned packets.
--filter="src 8800::0088 and dst 4200::1 and dst port 2021 and udp"
--NS_CHECK_EXEC([lsp], [tcpdump -n -c 1 -i lsp $filter > lsp.pcap &])
-+filter="dst 4200::1 and dst port 2021 and udp"
-+NS_CHECK_EXEC([lsp], [tcpdump -n -c 2 -i lsp $filter > lsp.pcap &])
-
- sleep 1
-
- # Generate IPv6 UDP hairpin traffic.
- NS_CHECK_EXEC([lsp], [nc -u 8800::0088 4040 -z &], [0])
-+NS_CHECK_EXEC([lsp], [nc -u 8800::0089 4040 -z &], [0])
-
- # Check hairpin traffic.
- OVS_WAIT_UNTIL([
- total_pkts=$(cat lsp.pcap | wc -l)
-- test "${total_pkts}" = "1"
-+ test "${total_pkts}" = "2"
- ])
-
- OVS_APP_EXIT_AND_WAIT([ovn-controller])
-@@ -5505,3 +5682,152 @@ as
- OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d
- /.*terminating with signal 15.*/d"])
- AT_CLEANUP
-+
-+AT_SETUP([ovn -- BFD])
-+AT_SKIP_IF([test $HAVE_BFDD_BEACON = no])
-+AT_SKIP_IF([test $HAVE_TCPDUMP = no])
-+AT_KEYWORDS([ovn-bfd])
-+
-+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
-+
-+# Start ovn-controller
-+start_daemon ovn-controller
-+
-+check ovn-nbctl lr-add R1
-+
-+check ovn-nbctl ls-add sw0
-+check ovn-nbctl ls-add sw1
-+check ovn-nbctl ls-add public
-+
-+check ovn-nbctl lrp-add R1 rp-sw0 00:00:01:01:02:03 192.168.1.1/24
-+check ovn-nbctl lrp-add R1 rp-sw1 00:00:03:01:02:03 192.168.2.1/24
-+check ovn-nbctl lrp-add R1 rp-public 00:00:02:01:02:03 172.16.1.1/24 1000::a/64 \
-+ -- lrp-set-gateway-chassis rp-public hv1
-+
-+check ovn-nbctl lsp-add sw0 sw0-rp -- set Logical_Switch_Port sw0-rp \
-+ type=router options:router-port=rp-sw0 \
-+ -- lsp-set-addresses sw0-rp router
-+check ovn-nbctl lsp-add sw1 sw1-rp -- set Logical_Switch_Port sw1-rp \
-+ type=router options:router-port=rp-sw1 \
-+ -- lsp-set-addresses sw1-rp router
-+
-+check ovn-nbctl lsp-add public public-rp -- set Logical_Switch_Port public-rp \
-+ type=router options:router-port=rp-public \
-+ -- lsp-set-addresses public-rp router
-+
-+ADD_NAMESPACES(sw01)
-+ADD_VETH(sw01, sw01, br-int, "192.168.1.2/24", "f0:00:00:01:02:03", \
-+ "192.168.1.1")
-+check ovn-nbctl lsp-add sw0 sw01 \
-+ -- lsp-set-addresses sw01 "f0:00:00:01:02:03 192.168.1.2"
-+
-+ADD_NAMESPACES(sw11)
-+ADD_VETH(sw11, sw11, br-int, "192.168.2.2/24", "f0:00:00:02:02:03", \
-+ "192.168.2.1")
-+check ovn-nbctl lsp-add sw1 sw11 \
-+ -- lsp-set-addresses sw11 "f0:00:00:02:02:03 192.168.2.2"
-+
-+ADD_NAMESPACES(server)
-+NS_CHECK_EXEC([server], [ip link set dev lo up])
-+ADD_VETH(s1, server, br-ext, "172.16.1.50/24", "f0:00:00:01:02:05", \
-+ "172.16.1.1")
-+NS_CHECK_EXEC([server], [ip addr add 1000::b/64 dev s1])
-+
-+AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=phynet:br-ext])
-+check ovn-nbctl lsp-add public public1 \
-+ -- lsp-set-addresses public1 unknown \
-+ -- lsp-set-type public1 localnet \
-+ -- lsp-set-options public1 network_name=phynet
-+
-+NS_CHECK_EXEC([server], [bfdd-beacon --listen=172.16.1.50], [0])
-+NS_CHECK_EXEC([server], [bfdd-control allow 172.16.1.1], [0], [dnl
-+Allowing connections from 172.16.1.1
-+])
-+
-+check ovn-nbctl --bfd lr-route-add R1 100.0.0.0/8 172.16.1.50 rp-public
-+uuid=$(fetch_column nb:bfd _uuid logical_port="rp-public")
-+route_uuid=$(fetch_column nb:logical_router_static_route _uuid ip_prefix="100.0.0.0/8")
-+check ovn-nbctl --wait=hv sync
-+
-+wait_column "up" nb:bfd status logical_port=rp-public
-+OVS_WAIT_UNTIL([ovn-sbctl dump-flows R1 | grep 'match=(ip4.dst == 100.0.0.0/8)' | grep -q 172.16.1.50])
-+
-+# un-associate the bfd connection and the static route
-+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])
-+NS_CHECK_EXEC([server], [tcpdump -nni s1 udp port 3784 -Q in > bfd.pcap &])
-+sleep 5
-+kill $(pidof tcpdump)
-+AT_CHECK([grep -qi bfd bfd.pcap],[1])
-+
-+# restart the connection
-+check ovn-nbctl set logical_router_static_route $route_uuid bfd=$uuid
-+wait_column "up" nb:bfd status logical_port=rp-public
-+
-+# switch to gw router configuration
-+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 clear logical_router_port rp-public gateway_chassis
-+check ovn-nbctl set logical_router R1 options:chassis=hv1
-+check ovn-nbctl set logical_router_static_route $route_uuid bfd=$uuid
-+wait_column "up" nb:bfd status logical_port=rp-public
-+
-+# stop bfd endpoint
-+NS_CHECK_EXEC([server], [bfdd-control stop], [0], [dnl
-+stopping
-+])
-+
-+wait_column "down" nb:bfd status logical_port=rp-public
-+OVS_WAIT_UNTIL([test "$(ovn-sbctl dump-flows R1 | grep 'match=(ip4.dst == 100.0.0.0/8)' | grep 172.16.1.50)" = ""])
-+
-+# 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 &])
-+sleep 5
-+kill $(pidof tcpdump)
-+AT_CHECK([grep -qi bfd bfd.pcap],[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
-+route_uuid_v6=$(fetch_column nb:logical_router_static_route _uuid ip_prefix=\"2000::/64\")
-+ovn-nbctl set logical_router_static_route $route_uuid_v6 bfd=$uuid_v6
-+check ovn-nbctl --wait=hv sync
-+NS_CHECK_EXEC([server], [bfdd-beacon --listen=1000::b], [0])
-+NS_CHECK_EXEC([server], [bfdd-control allow 1000::a], [0], [dnl
-+Allowing connections from 1000::a
-+])
-+
-+wait_column "up" nb:bfd status logical_port=rp-public
-+ovn-nbctl destroy bfd $uuid_v6
-+
-+kill $(pidof 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(["/.*error receiving.*/d
-+/.*terminating with signal 15.*/d"])
-+AT_CLEANUP
-diff --git a/tests/test-ovn.c b/tests/test-ovn.c
-index 49a1947f6..3fbe90b32 100644
---- a/tests/test-ovn.c
-+++ b/tests/test-ovn.c
-@@ -1346,6 +1346,8 @@ test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED)
- .lb_hairpin_ptable = OFTABLE_CHK_LB_HAIRPIN,
- .lb_hairpin_reply_ptable = OFTABLE_CHK_LB_HAIRPIN_REPLY,
- .ct_snat_vip_ptable = OFTABLE_CT_SNAT_FOR_VIP,
-+ .fdb_ptable = OFTABLE_GET_FDB,
-+ .fdb_lookup_ptable = OFTABLE_LOOKUP_FDB,
- };
- struct ofpbuf ofpacts;
- ofpbuf_init(&ofpacts, 0);
-diff --git a/tests/testsuite.at b/tests/testsuite.at
-index 960227dcc..3eba785c6 100644
---- a/tests/testsuite.at
-+++ b/tests/testsuite.at
-@@ -26,6 +26,7 @@ m4_include([tests/ovn.at])
- m4_include([tests/ovn-performance.at])
- m4_include([tests/ovn-northd.at])
- m4_include([tests/ovn-nbctl.at])
-+m4_include([tests/ovn-ofctrl-seqno.at])
- m4_include([tests/ovn-sbctl.at])
- m4_include([tests/ovn-ic-nbctl.at])
- m4_include([tests/ovn-ic-sbctl.at])
-diff --git a/utilities/checkpatch.py b/utilities/checkpatch.py
-index 981a433be..fb96fd66b 100755
---- a/utilities/checkpatch.py
-+++ b/utilities/checkpatch.py
-@@ -189,7 +189,8 @@ line_length_blacklist = re.compile(
- # Don't enforce a requirement that leading whitespace be all spaces on
- # files that include these characters in their name, since these kinds
- # of files need lines with leading tabs.
--leading_whitespace_blacklist = re.compile(r'\.(mk|am|at)$|debian/rules')
-+leading_whitespace_blacklist = re.compile(
-+ r'\.(mk|am|at)$|debian/rules|\.gitmodules$')
-
-
- def is_subtracted_line(line):
-diff --git a/utilities/ovn-ctl b/utilities/ovn-ctl
-index c44201ccf..211c764a6 100755
---- a/utilities/ovn-ctl
-+++ b/utilities/ovn-ctl
-@@ -251,6 +251,11 @@ $cluster_remote_port
-
- [ "$OVN_USER" != "" ] && set "$@" --user "$OVN_USER"
-
-+ if test X"$OVSDB_DISABLE_FILE_COLUMN_DIFF" = Xyes; then
-+ (ovsdb-server --help | grep -q disable-file-column-diff) \
-+ && set "$@" --disable-file-column-diff
-+ fi
-+
- if test X"$detach" != Xno; then
- set "$@" --detach --monitor
- else
-@@ -715,6 +720,8 @@ set_defaults () {
- OVSDB_NB_WRAPPER=
- OVSDB_SB_WRAPPER=
-
-+ OVSDB_DISABLE_FILE_COLUMN_DIFF=no
-+
- OVN_USER=
-
- OVN_CONTROLLER_LOG="-vconsole:emer -vsyslog:err -vfile:info"
-@@ -932,6 +939,11 @@ Options:
- --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
-+ Specifies whether or not ovsdb-server
-+ processes should be started with
-+ --disable-file-column-diff.
-+ More details in ovsdb(7). (default: no)
- -h, --help display this help message
-
- File location options:
-diff --git a/utilities/ovn-nbctl.8.xml b/utilities/ovn-nbctl.8.xml
-index 59302296b..2cab592ce 100644
---- a/utilities/ovn-nbctl.8.xml
-+++ b/utilities/ovn-nbctl.8.xml
-@@ -659,6 +659,7 @@
-
- - [
--may-exist
] [--policy
=POLICY]
- [--ecmp
] [--ecmp-symmetric-reply
]
-+ [--bfd[=UUID
]]
- lr-route-add
router
- prefix nexthop [port]
- -
-@@ -695,6 +696,16 @@
- it is not necessary to set both.
-
-
-+
-+ --bfd
option is used to link a BFD session to the
-+ OVN route. If the BFD session UUID is provided, it will be used
-+ for the OVN route otherwise the next-hop will be used to perform
-+ a lookup in the OVN BFD table.
-+ If the lookup fails and port is specified, a new entry
-+ in the BFD table will be created using the nexthop as
-+ dst_ip and port as logical_port.
-+
-+
-
- It is an error if a route with prefix and
- POLICY already exists, unless --may-exist
,
-@@ -739,7 +750,7 @@
-
- - [
--may-exist
]lr-policy-add
- router priority match
-- action [nexthop]
-+ action [nexthop[,nexthop,...]]
- [options key=value]]
- -
-
-@@ -748,10 +759,12 @@
- are similar to OVN ACLs, but exist on the logical-router. Reroute
- policies are needed for service-insertion and service-chaining.
- nexthop is an optional parameter. It needs to be provided
-- only when action is reroute. A policy is
-- uniquely identified by priority and match.
-- Multiple policies can have the same priority.
-- options sets the router policy options as key-value pair.
-+ only when action is reroute. Multiple
-+ nexthops
can be specified for ECMP routing.
-+ A policy is uniquely identified by priority and
-+ match. Multiple policies can have the same
-+ priority. options sets the router policy
-+ options as key-value pair.
- The supported option is : pkt_mark
.
-
-
-@@ -903,7 +916,7 @@
-
- Load Balancer Commands
-
-- - [
--may-exist
| --add-duplicate
] lb-add
lb vip ips [protocol]
-+ - [
--may-exist
| --add-duplicate
| --reject
| --event
] lb-add
lb vip ips [protocol]
- -
-
- Creates a new load balancer named lb with the provided
-@@ -936,6 +949,23 @@
- creates a new load balancer with a duplicate name.
-
-
-+
-+ If the load balancer is created with --reject
option and
-+ it has no active backends, a TCP reset segment (for tcp) or an ICMP
-+ port unreachable packet (for all other kind of traffic) will be sent
-+ whenever an incoming packet is received for this load-balancer.
-+ Please note using --reject
option will disable
-+ empty_lb SB controller event for this load balancer.
-+
-+
-+
-+ If the load balancer is created with --event
option and
-+ it has no active backends, whenever the lb receives traffic, the event
-+ is reported in the Controller_Event table in the SB db.
-+ Please note --event
option can't be specified with
-+ --reject
one.
-+
-+
-
- The following example adds a load balancer.
-
-diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
-index d19e1b6c6..dc0c50854 100644
---- a/utilities/ovn-nbctl.c
-+++ b/utilities/ovn-nbctl.c
-@@ -125,6 +125,65 @@ static char * OVS_WARN_UNUSED_RESULT main_loop(const char *args,
- const struct timer *);
- static void server_loop(struct ovsdb_idl *idl, int argc, char *argv[]);
-
-+/* A context for keeping track of which switch/router certain ports are
-+ * connected to.
-+ *
-+ * It is required to track changes that we did within current set of commands
-+ * because partial updates of sets in database are not reflected in the idl
-+ * until transaction is committed and updates received from the server. */
-+struct nbctl_context {
-+ struct ctl_context base;
-+ struct shash lsp_to_ls_map;
-+ struct shash lrp_to_lr_map;
-+ bool context_valid;
-+};
-+
-+static void
-+nbctl_context_init(struct nbctl_context *nbctx)
-+{
-+ nbctx->context_valid = false;
-+ shash_init(&nbctx->lsp_to_ls_map);
-+ shash_init(&nbctx->lrp_to_lr_map);
-+}
-+
-+static void
-+nbctl_context_destroy(struct nbctl_context *nbctx)
-+{
-+ nbctx->context_valid = false;
-+ shash_destroy(&nbctx->lsp_to_ls_map);
-+ shash_destroy(&nbctx->lrp_to_lr_map);
-+}
-+
-+/* Casts 'base' into 'struct nbctl_context' and initializes it if needed. */
-+static struct nbctl_context *
-+nbctl_context_get(struct ctl_context *base)
-+{
-+ struct nbctl_context *nbctx;
-+
-+ nbctx = CONTAINER_OF(base, struct nbctl_context, base);
-+
-+ if (nbctx->context_valid) {
-+ return nbctx;
-+ }
-+
-+ const struct nbrec_logical_switch *ls;
-+ NBREC_LOGICAL_SWITCH_FOR_EACH (ls, base->idl) {
-+ for (size_t i = 0; i < ls->n_ports; i++) {
-+ shash_add_once(&nbctx->lsp_to_ls_map, ls->ports[i]->name, ls);
-+ }
-+ }
-+
-+ const struct nbrec_logical_router *lr;
-+ NBREC_LOGICAL_ROUTER_FOR_EACH (lr, base->idl) {
-+ for (size_t i = 0; i < lr->n_ports; i++) {
-+ shash_add_once(&nbctx->lrp_to_lr_map, lr->ports[i]->name, lr);
-+ }
-+ }
-+
-+ nbctx->context_valid = true;
-+ return nbctx;
-+}
-+
- int
- main(int argc, char *argv[])
- {
-@@ -707,7 +766,7 @@ Route commands:\n\
- lr-route-list ROUTER print routes for ROUTER\n\
- \n\
- Policy commands:\n\
-- lr-policy-add ROUTER PRIORITY MATCH ACTION [NEXTHOP] \
-+ lr-policy-add ROUTER PRIORITY MATCH ACTION [NEXTHOP,[NEXTHOP,...]] \
- [OPTIONS KEY=VALUE ...] \n\
- add a policy to router\n\
- lr-policy-del ROUTER [{PRIORITY | UUID} [MATCH]]\n\
-@@ -1249,6 +1308,7 @@ static void
- nbctl_ls_del(struct ctl_context *ctx)
- {
- bool must_exist = !shash_find(&ctx->options, "--if-exists");
-+ struct nbctl_context *nbctx = nbctl_context_get(ctx);
- const char *id = ctx->argv[1];
- const struct nbrec_logical_switch *ls = NULL;
-
-@@ -1261,6 +1321,11 @@ nbctl_ls_del(struct ctl_context *ctx)
- return;
- }
-
-+ /* Updating runtime cache. */
-+ for (size_t i = 0; i < ls->n_ports; i++) {
-+ shash_find_and_delete(&nbctx->lsp_to_ls_map, ls->ports[i]->name);
-+ }
-+
- nbrec_logical_switch_delete(ls);
- }
-
-@@ -1317,22 +1382,19 @@ lsp_by_name_or_uuid(struct ctl_context *ctx, const char *id,
-
- /* Returns the logical switch that contains 'lsp'. */
- static char * OVS_WARN_UNUSED_RESULT
--lsp_to_ls(const struct ovsdb_idl *idl,
-+lsp_to_ls(struct ctl_context *ctx,
- const struct nbrec_logical_switch_port *lsp,
- const struct nbrec_logical_switch **ls_p)
- {
-+ struct nbctl_context *nbctx = nbctl_context_get(ctx);
- const struct nbrec_logical_switch *ls;
- *ls_p = NULL;
-
-- NBREC_LOGICAL_SWITCH_FOR_EACH (ls, idl) {
-- for (size_t i = 0; i < ls->n_ports; i++) {
-- if (ls->ports[i] == lsp) {
-- *ls_p = ls;
-- return NULL;
-- }
-- }
-+ ls = shash_find_data(&nbctx->lsp_to_ls_map, lsp->name);
-+ if (ls) {
-+ *ls_p = ls;
-+ return NULL;
- }
--
- /* Can't happen because of the database schema */
- return xasprintf("logical port %s is not part of any logical switch",
- lsp->name);
-@@ -1353,6 +1415,7 @@ static void
- nbctl_lsp_add(struct ctl_context *ctx)
- {
- bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
-+ struct nbctl_context *nbctx = nbctl_context_get(ctx);
-
- const struct nbrec_logical_switch *ls = NULL;
- char *error = ls_by_name_or_uuid(ctx, ctx->argv[1], true, &ls);
-@@ -1395,7 +1458,7 @@ nbctl_lsp_add(struct ctl_context *ctx)
- }
-
- const struct nbrec_logical_switch *lsw;
-- error = lsp_to_ls(ctx->idl, lsp, &lsw);
-+ error = lsp_to_ls(ctx, lsp, &lsw);
- if (error) {
- ctx->error = error;
- return;
-@@ -1448,31 +1511,27 @@ nbctl_lsp_add(struct ctl_context *ctx)
- }
-
- /* Insert the logical port into the logical switch. */
-- nbrec_logical_switch_verify_ports(ls);
-- struct nbrec_logical_switch_port **new_ports = xmalloc(sizeof *new_ports *
-- (ls->n_ports + 1));
-- nullable_memcpy(new_ports, ls->ports, sizeof *new_ports * ls->n_ports);
-- new_ports[ls->n_ports] = CONST_CAST(struct nbrec_logical_switch_port *,
-- lsp);
-- nbrec_logical_switch_set_ports(ls, new_ports, ls->n_ports + 1);
-- free(new_ports);
-+ nbrec_logical_switch_update_ports_addvalue(ls, lsp);
-+
-+ /* Updating runtime cache. */
-+ shash_add(&nbctx->lsp_to_ls_map, lsp_name, ls);
- }
-
--/* Removes logical switch port 'ls->ports[idx]'. */
-+/* Removes logical switch port 'lsp' from the logical switch 'ls'. */
- static void
--remove_lsp(const struct nbrec_logical_switch *ls, size_t idx)
-+remove_lsp(struct ctl_context *ctx,
-+ const struct nbrec_logical_switch *ls,
-+ const struct nbrec_logical_switch_port *lsp)
- {
-- const struct nbrec_logical_switch_port *lsp = ls->ports[idx];
-+ struct nbctl_context *nbctx = nbctl_context_get(ctx);
-+
-+ /* Updating runtime cache. */
-+ shash_find_and_delete(&nbctx->lsp_to_ls_map, lsp->name);
-
- /* First remove 'lsp' from the array of ports. This is what will
- * actually cause the logical port to be deleted when the transaction is
- * sent to the database server (due to garbage collection). */
-- struct nbrec_logical_switch_port **new_ports
-- = xmemdup(ls->ports, sizeof *new_ports * ls->n_ports);
-- new_ports[idx] = new_ports[ls->n_ports - 1];
-- nbrec_logical_switch_verify_ports(ls);
-- nbrec_logical_switch_set_ports(ls, new_ports, ls->n_ports - 1);
-- free(new_ports);
-+ nbrec_logical_switch_update_ports_delvalue(ls, lsp);
-
- /* Delete 'lsp' from the IDL. This won't have a real effect on the
- * database server (the IDL will suppress it in fact) but it means that it
-@@ -1498,18 +1557,13 @@ nbctl_lsp_del(struct ctl_context *ctx)
-
- /* Find the switch that contains 'lsp', then delete it. */
- const struct nbrec_logical_switch *ls;
-- NBREC_LOGICAL_SWITCH_FOR_EACH (ls, ctx->idl) {
-- for (size_t i = 0; i < ls->n_ports; i++) {
-- if (ls->ports[i] == lsp) {
-- remove_lsp(ls, i);
-- return;
-- }
-- }
-- }
-
-- /* Can't happen because of the database schema. */
-- ctl_error(ctx, "logical port %s is not part of any logical switch",
-- ctx->argv[1]);
-+ error = lsp_to_ls(ctx, lsp, &ls);
-+ if (error) {
-+ ctx->error = error;
-+ return;
-+ }
-+ remove_lsp(ctx, ls, lsp);
- }
-
- static void
-@@ -1658,7 +1712,7 @@ nbctl_lsp_set_addresses(struct ctl_context *ctx)
- }
-
- const struct nbrec_logical_switch *ls;
-- error = lsp_to_ls(ctx->idl, lsp, &ls);
-+ error = lsp_to_ls(ctx, lsp, &ls);
- if (error) {
- ctx->error = error;
- return;
-@@ -2299,17 +2353,11 @@ nbctl_acl_add(struct ctl_context *ctx)
- }
-
- /* Insert the acl into the logical switch/port group. */
-- struct nbrec_acl **new_acls = xmalloc(sizeof *new_acls * (n_acls + 1));
-- nullable_memcpy(new_acls, acls, sizeof *new_acls * n_acls);
-- new_acls[n_acls] = acl;
- if (pg) {
-- nbrec_port_group_verify_acls(pg);
-- nbrec_port_group_set_acls(pg, new_acls, n_acls + 1);
-+ nbrec_port_group_update_acls_addvalue(pg, acl);
- } else {
-- nbrec_logical_switch_verify_acls(ls);
-- nbrec_logical_switch_set_acls(ls, new_acls, n_acls + 1);
-+ nbrec_logical_switch_update_acls_addvalue(ls, acl);
- }
-- free(new_acls);
- }
-
- static void
-@@ -2349,23 +2397,15 @@ nbctl_acl_del(struct ctl_context *ctx)
- /* If priority and match are not specified, delete all ACLs with the
- * specified direction. */
- if (ctx->argc == 3) {
-- struct nbrec_acl **new_acls = xmalloc(sizeof *new_acls * n_acls);
--
-- int n_new_acls = 0;
- for (size_t i = 0; i < n_acls; i++) {
-- if (strcmp(direction, acls[i]->direction)) {
-- new_acls[n_new_acls++] = acls[i];
-+ if (!strcmp(direction, acls[i]->direction)) {
-+ if (pg) {
-+ nbrec_port_group_update_acls_delvalue(pg, acls[i]);
-+ } else {
-+ nbrec_logical_switch_update_acls_delvalue(ls, acls[i]);
-+ }
- }
- }
--
-- if (pg) {
-- nbrec_port_group_verify_acls(pg);
-- nbrec_port_group_set_acls(pg, new_acls, n_new_acls);
-- } else {
-- nbrec_logical_switch_verify_acls(ls);
-- nbrec_logical_switch_set_acls(ls, new_acls, n_new_acls);
-- }
-- free(new_acls);
- return;
- }
-
-@@ -2387,19 +2427,11 @@ nbctl_acl_del(struct ctl_context *ctx)
-
- if (priority == acl->priority && !strcmp(ctx->argv[4], acl->match) &&
- !strcmp(direction, acl->direction)) {
-- struct nbrec_acl **new_acls
-- = xmemdup(acls, sizeof *new_acls * n_acls);
-- new_acls[i] = acls[n_acls - 1];
- if (pg) {
-- nbrec_port_group_verify_acls(pg);
-- nbrec_port_group_set_acls(pg, new_acls,
-- n_acls - 1);
-+ nbrec_port_group_update_acls_delvalue(pg, acl);
- } else {
-- nbrec_logical_switch_verify_acls(ls);
-- nbrec_logical_switch_set_acls(ls, new_acls,
-- n_acls - 1);
-+ nbrec_logical_switch_update_acls_delvalue(ls, acl);
- }
-- free(new_acls);
- return;
- }
- }
-@@ -2552,15 +2584,7 @@ nbctl_qos_add(struct ctl_context *ctx)
- }
-
- /* Insert the qos rule the logical switch. */
-- nbrec_logical_switch_verify_qos_rules(ls);
-- struct nbrec_qos **new_qos_rules
-- = xmalloc(sizeof *new_qos_rules * (ls->n_qos_rules + 1));
-- nullable_memcpy(new_qos_rules,
-- ls->qos_rules, sizeof *new_qos_rules * ls->n_qos_rules);
-- new_qos_rules[ls->n_qos_rules] = qos;
-- nbrec_logical_switch_set_qos_rules(ls, new_qos_rules,
-- ls->n_qos_rules + 1);
-- free(new_qos_rules);
-+ nbrec_logical_switch_update_qos_rules_addvalue(ls, qos);
- }
-
- static void
-@@ -2597,34 +2621,31 @@ nbctl_qos_del(struct ctl_context *ctx)
- /* If uuid was specified, delete qos_rule with the
- * specified uuid. */
- if (ctx->argc == 3) {
-- struct nbrec_qos **new_qos_rules
-- = xmalloc(sizeof *new_qos_rules * ls->n_qos_rules);
-+ size_t i;
-
-- int n_qos_rules = 0;
- if (qos_rule_uuid) {
-- for (size_t i = 0; i < ls->n_qos_rules; i++) {
-- if (!uuid_equals(qos_rule_uuid,
-- &(ls->qos_rules[i]->header_.uuid))) {
-- new_qos_rules[n_qos_rules++] = ls->qos_rules[i];
-+ for (i = 0; i < ls->n_qos_rules; i++) {
-+ if (uuid_equals(qos_rule_uuid,
-+ &(ls->qos_rules[i]->header_.uuid))) {
-+ nbrec_logical_switch_update_qos_rules_delvalue(
-+ ls, ls->qos_rules[i]);
-+ break;
- }
- }
-- if (n_qos_rules == ls->n_qos_rules) {
-+ if (i == ls->n_qos_rules) {
- ctl_error(ctx, "uuid is not found");
- }
-
- /* If priority and match are not specified, delete all qos_rules
- * with the specified direction. */
- } else {
-- for (size_t i = 0; i < ls->n_qos_rules; i++) {
-- if (strcmp(direction, ls->qos_rules[i]->direction)) {
-- new_qos_rules[n_qos_rules++] = ls->qos_rules[i];
-+ for (i = 0; i < ls->n_qos_rules; i++) {
-+ if (!strcmp(direction, ls->qos_rules[i]->direction)) {
-+ nbrec_logical_switch_update_qos_rules_delvalue(
-+ ls, ls->qos_rules[i]);
- }
- }
- }
--
-- nbrec_logical_switch_verify_qos_rules(ls);
-- nbrec_logical_switch_set_qos_rules(ls, new_qos_rules, n_qos_rules);
-- free(new_qos_rules);
- return;
- }
-
-@@ -2651,14 +2672,7 @@ nbctl_qos_del(struct ctl_context *ctx)
-
- if (priority == qos->priority && !strcmp(ctx->argv[4], qos->match) &&
- !strcmp(direction, qos->direction)) {
-- struct nbrec_qos **new_qos_rules
-- = xmemdup(ls->qos_rules,
-- sizeof *new_qos_rules * ls->n_qos_rules);
-- new_qos_rules[i] = ls->qos_rules[ls->n_qos_rules - 1];
-- nbrec_logical_switch_verify_qos_rules(ls);
-- nbrec_logical_switch_set_qos_rules(ls, new_qos_rules,
-- ls->n_qos_rules - 1);
-- free(new_qos_rules);
-+ nbrec_logical_switch_update_qos_rules_delvalue(ls, qos);
- return;
- }
- }
-@@ -2821,6 +2835,14 @@ nbctl_lb_add(struct ctl_context *ctx)
-
- bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
- bool add_duplicate = shash_find(&ctx->options, "--add-duplicate") != NULL;
-+ bool empty_backend_rej = shash_find(&ctx->options, "--reject") != NULL;
-+ bool empty_backend_event = shash_find(&ctx->options, "--event") != NULL;
-+
-+ if (empty_backend_event && empty_backend_rej) {
-+ ctl_error(ctx,
-+ "--reject and --event can't specified at the same time");
-+ return;
-+ }
-
- const char *lb_proto;
- bool is_update_proto = false;
-@@ -2934,6 +2956,14 @@ nbctl_lb_add(struct ctl_context *ctx)
- smap_add(CONST_CAST(struct smap *, &lb->vips),
- lb_vip_normalized, ds_cstr(&lb_ips_new));
- nbrec_load_balancer_set_vips(lb, &lb->vips);
-+ if (empty_backend_rej) {
-+ const struct smap options = SMAP_CONST1(&options, "reject", "true");
-+ nbrec_load_balancer_set_options(lb, &options);
-+ }
-+ if (empty_backend_event) {
-+ const struct smap options = SMAP_CONST1(&options, "event", "true");
-+ nbrec_load_balancer_set_options(lb, &options);
-+ }
- out:
- ds_destroy(&lb_ips_new);
-
-@@ -3115,17 +3145,7 @@ nbctl_lr_lb_add(struct ctl_context *ctx)
- }
-
- /* Insert the load balancer into the logical router. */
-- nbrec_logical_router_verify_load_balancer(lr);
-- struct nbrec_load_balancer **new_lbs
-- = xmalloc(sizeof *new_lbs * (lr->n_load_balancer + 1));
--
-- nullable_memcpy(new_lbs, lr->load_balancer,
-- sizeof *new_lbs * lr->n_load_balancer);
-- new_lbs[lr->n_load_balancer] = CONST_CAST(struct nbrec_load_balancer *,
-- new_lb);
-- nbrec_logical_router_set_load_balancer(lr, new_lbs,
-- lr->n_load_balancer + 1);
-- free(new_lbs);
-+ nbrec_logical_router_update_load_balancer_addvalue(lr, new_lb);
- }
-
- static void
-@@ -3158,15 +3178,7 @@ nbctl_lr_lb_del(struct ctl_context *ctx)
-
- if (uuid_equals(&del_lb->header_.uuid, &lb->header_.uuid)) {
- /* Remove the matching rule. */
-- nbrec_logical_router_verify_load_balancer(lr);
--
-- struct nbrec_load_balancer **new_lbs
-- = xmemdup(lr->load_balancer,
-- sizeof *new_lbs * lr->n_load_balancer);
-- new_lbs[i] = lr->load_balancer[lr->n_load_balancer - 1];
-- nbrec_logical_router_set_load_balancer(lr, new_lbs,
-- lr->n_load_balancer - 1);
-- free(new_lbs);
-+ nbrec_logical_router_update_load_balancer_delvalue(lr, lb);
- return;
- }
- }
-@@ -3240,17 +3252,7 @@ nbctl_ls_lb_add(struct ctl_context *ctx)
- }
-
- /* Insert the load balancer into the logical switch. */
-- nbrec_logical_switch_verify_load_balancer(ls);
-- struct nbrec_load_balancer **new_lbs
-- = xmalloc(sizeof *new_lbs * (ls->n_load_balancer + 1));
--
-- nullable_memcpy(new_lbs, ls->load_balancer,
-- sizeof *new_lbs * ls->n_load_balancer);
-- new_lbs[ls->n_load_balancer] = CONST_CAST(struct nbrec_load_balancer *,
-- new_lb);
-- nbrec_logical_switch_set_load_balancer(ls, new_lbs,
-- ls->n_load_balancer + 1);
-- free(new_lbs);
-+ nbrec_logical_switch_update_load_balancer_addvalue(ls, new_lb);
- }
-
- static void
-@@ -3283,15 +3285,7 @@ nbctl_ls_lb_del(struct ctl_context *ctx)
-
- if (uuid_equals(&del_lb->header_.uuid, &lb->header_.uuid)) {
- /* Remove the matching rule. */
-- nbrec_logical_switch_verify_load_balancer(ls);
--
-- struct nbrec_load_balancer **new_lbs
-- = xmemdup(ls->load_balancer,
-- sizeof *new_lbs * ls->n_load_balancer);
-- new_lbs[i] = ls->load_balancer[ls->n_load_balancer - 1];
-- nbrec_logical_switch_set_load_balancer(ls, new_lbs,
-- ls->n_load_balancer - 1);
-- free(new_lbs);
-+ nbrec_logical_switch_update_load_balancer_delvalue(ls, lb);
- return;
- }
- }
-@@ -3378,6 +3372,7 @@ static void
- nbctl_lr_del(struct ctl_context *ctx)
- {
- bool must_exist = !shash_find(&ctx->options, "--if-exists");
-+ struct nbctl_context *nbctx = nbctl_context_get(ctx);
- const char *id = ctx->argv[1];
- const struct nbrec_logical_router *lr = NULL;
-
-@@ -3390,6 +3385,11 @@ nbctl_lr_del(struct ctl_context *ctx)
- return;
- }
-
-+ /* Updating runtime cache. */
-+ for (size_t i = 0; i < lr->n_ports; i++) {
-+ shash_find_and_delete(&nbctx->lrp_to_lr_map, lr->ports[i]->name);
-+ }
-+
- nbrec_logical_router_delete(lr);
- }
-
-@@ -3645,7 +3645,8 @@ nbctl_lr_policy_add(struct ctl_context *ctx)
- return;
- }
- const char *action = ctx->argv[4];
-- char *next_hop = NULL;
-+ size_t n_nexthops = 0;
-+ char **nexthops = NULL;
-
- bool reroute = false;
- /* Validate action. */
-@@ -3665,7 +3666,8 @@ nbctl_lr_policy_add(struct ctl_context *ctx)
- /* Check if same routing policy already exists.
- * A policy is uniquely identified by priority and match */
- bool may_exist = !!shash_find(&ctx->options, "--may-exist");
-- for (int i = 0; i < lr->n_policies; i++) {
-+ size_t i;
-+ for (i = 0; i < lr->n_policies; i++) {
- const struct nbrec_logical_router_policy *policy = lr->policies[i];
- if (policy->priority == priority &&
- !strcmp(policy->match, ctx->argv[3])) {
-@@ -3676,12 +3678,53 @@ nbctl_lr_policy_add(struct ctl_context *ctx)
- return;
- }
- }
-+
- if (reroute) {
-- next_hop = normalize_prefix_str(ctx->argv[5]);
-- if (!next_hop) {
-- ctl_error(ctx, "bad next hop argument: %s", ctx->argv[5]);
-- return;
-+ char *nexthops_arg = xstrdup(ctx->argv[5]);
-+ char *save_ptr, *next_hop, *token;
-+
-+ n_nexthops = 0;
-+ size_t n_allocs = 0;
-+
-+ bool nexthops_is_ipv4 = true;
-+ for (token = strtok_r(nexthops_arg, ",", &save_ptr);
-+ token != NULL; token = strtok_r(NULL, ",", &save_ptr)) {
-+ next_hop = normalize_addr_str(token);
-+
-+ if (!next_hop) {
-+ ctl_error(ctx, "bad next hop argument: %s", ctx->argv[5]);
-+ free(nexthops_arg);
-+ for (i = 0; i < n_nexthops; i++) {
-+ free(nexthops[i]);
-+ }
-+ free(nexthops);
-+ return;
-+ }
-+ if (n_nexthops == n_allocs) {
-+ nexthops = x2nrealloc(nexthops, &n_allocs, sizeof *nexthops);
-+ }
-+
-+ bool is_ipv4 = strchr(next_hop, '.') ? true : false;
-+ if (n_nexthops == 0) {
-+ nexthops_is_ipv4 = is_ipv4;
-+ }
-+
-+ if (is_ipv4 != nexthops_is_ipv4) {
-+ ctl_error(ctx, "bad next hops argument, not in the same "
-+ "addr family : %s", ctx->argv[5]);
-+ free(nexthops_arg);
-+ free(next_hop);
-+ for (i = 0; i < n_nexthops; i++) {
-+ free(nexthops[i]);
-+ }
-+ free(nexthops);
-+ return;
-+ }
-+ nexthops[n_nexthops] = next_hop;
-+ n_nexthops++;
- }
-+
-+ free(nexthops_arg);
- }
-
- struct nbrec_logical_router_policy *policy;
-@@ -3690,12 +3733,13 @@ nbctl_lr_policy_add(struct ctl_context *ctx)
- nbrec_logical_router_policy_set_match(policy, ctx->argv[3]);
- nbrec_logical_router_policy_set_action(policy, action);
- if (reroute) {
-- nbrec_logical_router_policy_set_nexthop(policy, next_hop);
-+ nbrec_logical_router_policy_set_nexthops(
-+ policy, (const char **)nexthops, n_nexthops);
- }
-
- /* Parse the options. */
- struct smap options = SMAP_INITIALIZER(&options);
-- for (size_t i = reroute ? 6 : 5; i < ctx->argc; i++) {
-+ for (i = reroute ? 6 : 5; i < ctx->argc; i++) {
- char *key, *value;
- value = xstrdup(ctx->argv[i]);
- key = strsep(&value, "=");
-@@ -3705,7 +3749,10 @@ nbctl_lr_policy_add(struct ctl_context *ctx)
- ctl_error(ctx, "No value specified for the option : %s", key);
- smap_destroy(&options);
- free(key);
-- free(next_hop);
-+ for (i = 0; i < n_nexthops; i++) {
-+ free(nexthops[i]);
-+ }
-+ free(nexthops);
- return;
- }
- free(key);
-@@ -3713,18 +3760,12 @@ nbctl_lr_policy_add(struct ctl_context *ctx)
- nbrec_logical_router_policy_set_options(policy, &options);
- smap_destroy(&options);
-
-- nbrec_logical_router_verify_policies(lr);
-- struct nbrec_logical_router_policy **new_policies
-- = xmalloc(sizeof *new_policies * (lr->n_policies + 1));
-- memcpy(new_policies, lr->policies,
-- sizeof *new_policies * lr->n_policies);
-- new_policies[lr->n_policies] = policy;
-- nbrec_logical_router_set_policies(lr, new_policies,
-- lr->n_policies + 1);
-- free(new_policies);
-- if (next_hop != NULL) {
-- free(next_hop);
-+ nbrec_logical_router_update_policies_addvalue(lr, policy);
-+
-+ for (i = 0; i < n_nexthops; i++) {
-+ free(nexthops[i]);
- }
-+ free(nexthops);
- }
-
- static void
-@@ -3758,38 +3799,34 @@ nbctl_lr_policy_del(struct ctl_context *ctx)
- /* If uuid was specified, delete routing policy with the
- * specified uuid. */
- if (ctx->argc == 3) {
-- struct nbrec_logical_router_policy **new_policies
-- = xmemdup(lr->policies,
-- sizeof *new_policies * lr->n_policies);
-- int n_policies = 0;
-+ size_t i;
-
- if (lr_policy_uuid) {
-- for (size_t i = 0; i < lr->n_policies; i++) {
-- if (!uuid_equals(lr_policy_uuid,
-- &(lr->policies[i]->header_.uuid))) {
-- new_policies[n_policies++] = lr->policies[i];
-+ for (i = 0; i < lr->n_policies; i++) {
-+ if (uuid_equals(lr_policy_uuid,
-+ &(lr->policies[i]->header_.uuid))) {
-+ nbrec_logical_router_update_policies_delvalue(
-+ lr, lr->policies[i]);
-+ break;
- }
- }
-- if (n_policies == lr->n_policies) {
-+ if (i == lr->n_policies) {
- if (!shash_find(&ctx->options, "--if-exists")) {
- ctl_error(ctx, "Logical router policy uuid is not found.");
- }
-- free(new_policies);
- return;
- }
-
-- /* If match is not specified, delete all routing policies with the
-- * specified priority. */
-+ /* If match is not specified, delete all routing policies with the
-+ * specified priority. */
- } else {
-- for (int i = 0; i < lr->n_policies; i++) {
-- if (priority != lr->policies[i]->priority) {
-- new_policies[n_policies++] = lr->policies[i];
-+ for (i = 0; i < lr->n_policies; i++) {
-+ if (priority == lr->policies[i]->priority) {
-+ nbrec_logical_router_update_policies_delvalue(
-+ lr, lr->policies[i]);
- }
- }
- }
-- nbrec_logical_router_verify_policies(lr);
-- nbrec_logical_router_set_policies(lr, new_policies, n_policies);
-- free(new_policies);
- return;
- }
-
-@@ -3798,14 +3835,7 @@ nbctl_lr_policy_del(struct ctl_context *ctx)
- struct nbrec_logical_router_policy *routing_policy = lr->policies[i];
- if (priority == routing_policy->priority &&
- !strcmp(ctx->argv[3], routing_policy->match)) {
-- struct nbrec_logical_router_policy **new_policies
-- = xmemdup(lr->policies,
-- sizeof *new_policies * lr->n_policies);
-- new_policies[i] = lr->policies[lr->n_policies - 1];
-- nbrec_logical_router_verify_policies(lr);
-- nbrec_logical_router_set_policies(lr, new_policies,
-- lr->n_policies - 1);
-- free(new_policies);
-+ nbrec_logical_router_update_policies_delvalue(lr, routing_policy);
- return;
- }
- }
-@@ -3884,6 +3914,47 @@ nbctl_lr_policy_list(struct ctl_context *ctx)
- }
- free(policies);
- }
-+
-+static struct nbrec_logical_router_static_route *
-+nbctl_lr_get_route(const struct nbrec_logical_router *lr, char *prefix,
-+ char *next_hop, bool is_src_route, bool ecmp)
-+{
-+ for (int i = 0; i < lr->n_static_routes; i++) {
-+ struct nbrec_logical_router_static_route *route = lr->static_routes[i];
-+
-+ /* Compare route policy. */
-+ char *nb_policy = route->policy;
-+ bool nb_is_src_route = false;
-+ if (nb_policy && !strcmp(nb_policy, "src-ip")) {
-+ nb_is_src_route = true;
-+ }
-+ if (is_src_route != nb_is_src_route) {
-+ continue;
-+ }
-+
-+ /* Compare route prefix. */
-+ char *rt_prefix = normalize_prefix_str(route->ip_prefix);
-+ if (!rt_prefix) {
-+ /* Ignore existing prefix we couldn't parse. */
-+ continue;
-+ }
-+
-+ if (strcmp(rt_prefix, prefix)) {
-+ free(rt_prefix);
-+ continue;
-+ }
-+
-+ if (ecmp && strcmp(next_hop, route->nexthop)) {
-+ free(rt_prefix);
-+ continue;
-+ }
-+
-+ free(rt_prefix);
-+ return route;
-+ }
-+ return NULL;
-+}
-+
-
- static void
- nbctl_lr_route_add(struct ctl_context *ctx)
-@@ -3927,44 +3998,42 @@ nbctl_lr_route_add(struct ctl_context *ctx)
- goto cleanup;
- }
-
-+ struct shash_node *bfd = shash_find(&ctx->options, "--bfd");
-+ const struct nbrec_bfd *nb_bt = NULL;
-+ if (bfd) {
-+ if (bfd->data) {
-+ struct uuid bfd_uuid;
-+ if (uuid_from_string(&bfd_uuid, bfd->data)) {
-+ nb_bt = nbrec_bfd_get_for_uuid(ctx->idl, &bfd_uuid);
-+ }
-+ if (!nb_bt) {
-+ ctl_error(ctx, "no entry found in the BFD table");
-+ goto cleanup;
-+ }
-+ } else {
-+ const struct nbrec_bfd *iter;
-+ NBREC_BFD_FOR_EACH (iter, ctx->idl) {
-+ if (!strcmp(iter->dst_ip, next_hop)) {
-+ nb_bt = iter;
-+ break;
-+ }
-+ }
-+ }
-+ }
-+
- bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
- bool ecmp_symmetric_reply = shash_find(&ctx->options,
- "--ecmp-symmetric-reply") != NULL;
- bool ecmp = shash_find(&ctx->options, "--ecmp") != NULL ||
- ecmp_symmetric_reply;
-+ struct nbrec_logical_router_static_route *route =
-+ nbctl_lr_get_route(lr, prefix, next_hop, is_src_route, ecmp);
- if (!ecmp) {
-- for (int i = 0; i < lr->n_static_routes; i++) {
-- const struct nbrec_logical_router_static_route *route
-- = lr->static_routes[i];
-- char *rt_prefix;
--
-- /* Compare route policy. */
-- char *nb_policy = lr->static_routes[i]->policy;
-- bool nb_is_src_route = false;
-- if (nb_policy && !strcmp(nb_policy, "src-ip")) {
-- nb_is_src_route = true;
-- }
-- if (is_src_route != nb_is_src_route) {
-- continue;
-- }
--
-- /* Compare route prefix. */
-- rt_prefix = normalize_prefix_str(lr->static_routes[i]->ip_prefix);
-- if (!rt_prefix) {
-- /* Ignore existing prefix we couldn't parse. */
-- continue;
-- }
--
-- if (strcmp(rt_prefix, prefix)) {
-- free(rt_prefix);
-- continue;
-- }
--
-+ if (route) {
- if (!may_exist) {
- ctl_error(ctx, "duplicate prefix: %s (policy: %s). Use option"
- " --ecmp to allow this for ECMP routing.",
- prefix, is_src_route ? "src-ip" : "dst-ip");
-- free(rt_prefix);
- goto cleanup;
- }
-
-@@ -3981,12 +4050,25 @@ nbctl_lr_route_add(struct ctl_context *ctx)
- if (policy) {
- nbrec_logical_router_static_route_set_policy(route, policy);
- }
-- free(rt_prefix);
-+ if (bfd) {
-+ if (!nb_bt) {
-+ if (ctx->argc != 5) {
-+ ctl_error(ctx, "insert entry in the BFD table failed");
-+ goto cleanup;
-+ }
-+ nb_bt = nbrec_bfd_insert(ctx->txn);
-+ nbrec_bfd_set_dst_ip(nb_bt, next_hop);
-+ nbrec_bfd_set_logical_port(nb_bt, ctx->argv[4]);
-+ }
-+ nbrec_logical_router_static_route_set_bfd(route, nb_bt);
-+ }
- goto cleanup;
- }
-+ } else if (route) {
-+ ctl_error(ctx, "duplicate nexthop for the same ECMP route");
-+ goto cleanup;
- }
-
-- struct nbrec_logical_router_static_route *route;
- route = nbrec_logical_router_static_route_insert(ctx->txn);
- nbrec_logical_router_static_route_set_ip_prefix(route, prefix);
- nbrec_logical_router_static_route_set_nexthop(route, next_hop);
-@@ -4004,15 +4086,19 @@ nbctl_lr_route_add(struct ctl_context *ctx)
- nbrec_logical_router_static_route_set_options(route, &options);
- }
-
-- nbrec_logical_router_verify_static_routes(lr);
-- struct nbrec_logical_router_static_route **new_routes
-- = xmalloc(sizeof *new_routes * (lr->n_static_routes + 1));
-- nullable_memcpy(new_routes, lr->static_routes,
-- sizeof *new_routes * lr->n_static_routes);
-- new_routes[lr->n_static_routes] = route;
-- nbrec_logical_router_set_static_routes(lr, new_routes,
-- lr->n_static_routes + 1);
-- free(new_routes);
-+ nbrec_logical_router_update_static_routes_addvalue(lr, route);
-+ if (bfd) {
-+ if (!nb_bt) {
-+ if (ctx->argc != 5) {
-+ ctl_error(ctx, "insert entry in the BFD table failed");
-+ goto cleanup;
-+ }
-+ nb_bt = nbrec_bfd_insert(ctx->txn);
-+ nbrec_bfd_set_dst_ip(nb_bt, next_hop);
-+ nbrec_bfd_set_logical_port(nb_bt, ctx->argv[4]);
-+ }
-+ nbrec_logical_router_static_route_set_bfd(route, nb_bt);
-+ }
-
- cleanup:
- free(next_hop);
-@@ -4069,11 +4155,8 @@ nbctl_lr_route_del(struct ctl_context *ctx)
- output_port = ctx->argv[4];
- }
-
-- struct nbrec_logical_router_static_route **new_routes
-- = xmemdup(lr->static_routes,
-- sizeof *new_routes * lr->n_static_routes);
-- size_t n_new = 0;
-- for (int i = 0; i < lr->n_static_routes; i++) {
-+ size_t n_removed = 0;
-+ for (size_t i = 0; i < lr->n_static_routes; i++) {
- /* Compare route policy, if specified. */
- if (policy) {
- char *nb_policy = lr->static_routes[i]->policy;
-@@ -4082,7 +4165,6 @@ nbctl_lr_route_del(struct ctl_context *ctx)
- nb_is_src_route = true;
- }
- if (is_src_route != nb_is_src_route) {
-- new_routes[n_new++] = lr->static_routes[i];
- continue;
- }
- }
-@@ -4093,14 +4175,12 @@ nbctl_lr_route_del(struct ctl_context *ctx)
- normalize_prefix_str(lr->static_routes[i]->ip_prefix);
- if (!rt_prefix) {
- /* Ignore existing prefix we couldn't parse. */
-- new_routes[n_new++] = lr->static_routes[i];
- continue;
- }
-
- int ret = strcmp(prefix, rt_prefix);
- free(rt_prefix);
- if (ret) {
-- new_routes[n_new++] = lr->static_routes[i];
- continue;
- }
- }
-@@ -4111,13 +4191,11 @@ nbctl_lr_route_del(struct ctl_context *ctx)
- normalize_prefix_str(lr->static_routes[i]->nexthop);
- if (!rt_nexthop) {
- /* Ignore existing nexthop we couldn't parse. */
-- new_routes[n_new++] = lr->static_routes[i];
- continue;
- }
- int ret = strcmp(nexthop, rt_nexthop);
- free(rt_nexthop);
- if (ret) {
-- new_routes[n_new++] = lr->static_routes[i];
- continue;
- }
- }
-@@ -4126,18 +4204,17 @@ nbctl_lr_route_del(struct ctl_context *ctx)
- if (output_port) {
- char *rt_output_port = lr->static_routes[i]->output_port;
- if (!rt_output_port || strcmp(output_port, rt_output_port)) {
-- new_routes[n_new++] = lr->static_routes[i];
-+ continue;
- }
- }
-- }
-
-- if (n_new < lr->n_static_routes) {
-- nbrec_logical_router_verify_static_routes(lr);
-- nbrec_logical_router_set_static_routes(lr, new_routes, n_new);
-- goto out;
-+ /* Everything matched. Removing. */
-+ nbrec_logical_router_update_static_routes_delvalue(
-+ lr, lr->static_routes[i]);
-+ n_removed++;
- }
-
-- if (!shash_find(&ctx->options, "--if-exists")) {
-+ if (!n_removed && !shash_find(&ctx->options, "--if-exists")) {
- ctl_error(ctx, "no matching route: policy '%s', prefix '%s', nexthop "
- "'%s', output_port '%s'.",
- policy ? policy : "any",
-@@ -4146,8 +4223,6 @@ nbctl_lr_route_del(struct ctl_context *ctx)
- output_port ? output_port : "any");
- }
-
--out:
-- free(new_routes);
- free(prefix);
- free(nexthop);
- }
-@@ -4418,12 +4493,7 @@ nbctl_lr_nat_add(struct ctl_context *ctx)
- smap_destroy(&nat_options);
-
- /* Insert the NAT into the logical router. */
-- nbrec_logical_router_verify_nat(lr);
-- struct nbrec_nat **new_nats = xmalloc(sizeof *new_nats * (lr->n_nat + 1));
-- nullable_memcpy(new_nats, lr->nat, sizeof *new_nats * lr->n_nat);
-- new_nats[lr->n_nat] = nat;
-- nbrec_logical_router_set_nat(lr, new_nats, lr->n_nat + 1);
-- free(new_nats);
-+ nbrec_logical_router_update_nat_addvalue(lr, nat);
-
- cleanup:
- free(new_logical_ip);
-@@ -4459,17 +4529,11 @@ nbctl_lr_nat_del(struct ctl_context *ctx)
-
- if (ctx->argc == 3) {
- /*Deletes all NATs with the specified type. */
-- struct nbrec_nat **new_nats = xmalloc(sizeof *new_nats * lr->n_nat);
-- int n_nat = 0;
- for (size_t i = 0; i < lr->n_nat; i++) {
-- if (strcmp(nat_type, lr->nat[i]->type)) {
-- new_nats[n_nat++] = lr->nat[i];
-+ if (!strcmp(nat_type, lr->nat[i]->type)) {
-+ nbrec_logical_router_update_nat_delvalue(lr, lr->nat[i]);
- }
- }
--
-- nbrec_logical_router_verify_nat(lr);
-- nbrec_logical_router_set_nat(lr, new_nats, n_nat);
-- free(new_nats);
- return;
- }
-
-@@ -4491,13 +4555,7 @@ nbctl_lr_nat_del(struct ctl_context *ctx)
- continue;
- }
- if (!strcmp(nat_type, nat->type) && !strcmp(nat_ip, old_ip)) {
-- struct nbrec_nat **new_nats
-- = xmemdup(lr->nat, sizeof *new_nats * lr->n_nat);
-- new_nats[i] = lr->nat[lr->n_nat - 1];
-- nbrec_logical_router_verify_nat(lr);
-- nbrec_logical_router_set_nat(lr, new_nats,
-- lr->n_nat - 1);
-- free(new_nats);
-+ nbrec_logical_router_update_nat_delvalue(lr, nat);
- should_return = true;
- }
- free(old_ip);
-@@ -4667,20 +4725,18 @@ lrp_by_name_or_uuid(struct ctl_context *ctx, const char *id, bool must_exist,
-
- /* Returns the logical router that contains 'lrp'. */
- static char * OVS_WARN_UNUSED_RESULT
--lrp_to_lr(const struct ovsdb_idl *idl,
-+lrp_to_lr(struct ctl_context *ctx,
- const struct nbrec_logical_router_port *lrp,
- const struct nbrec_logical_router **lr_p)
- {
-+ struct nbctl_context *nbctx = nbctl_context_get(ctx);
- const struct nbrec_logical_router *lr;
- *lr_p = NULL;
-
-- NBREC_LOGICAL_ROUTER_FOR_EACH (lr, idl) {
-- for (size_t i = 0; i < lr->n_ports; i++) {
-- if (lr->ports[i] == lrp) {
-- *lr_p = lr;
-- return NULL;
-- }
-- }
-+ lr = shash_find_data(&nbctx->lrp_to_lr_map, lrp->name);
-+ if (lr) {
-+ *lr_p = lr;
-+ return NULL;
- }
-
- /* Can't happen because of the database schema */
-@@ -4777,15 +4833,7 @@ nbctl_lrp_set_gateway_chassis(struct ctl_context *ctx)
- nbrec_gateway_chassis_set_priority(gc, priority);
-
- /* Insert the logical gateway chassis into the logical router port. */
-- nbrec_logical_router_port_verify_gateway_chassis(lrp);
-- struct nbrec_gateway_chassis **new_gc = xmalloc(
-- sizeof *new_gc * (lrp->n_gateway_chassis + 1));
-- nullable_memcpy(new_gc, lrp->gateway_chassis,
-- sizeof *new_gc * lrp->n_gateway_chassis);
-- new_gc[lrp->n_gateway_chassis] = gc;
-- nbrec_logical_router_port_set_gateway_chassis(
-- lrp, new_gc, lrp->n_gateway_chassis + 1);
-- free(new_gc);
-+ nbrec_logical_router_port_update_gateway_chassis_addvalue(lrp, gc);
- free(gc_name);
- }
-
-@@ -4802,14 +4850,7 @@ remove_gc(const struct nbrec_logical_router_port *lrp, size_t idx)
- * will actually cause the gateway chassis to be deleted when the
- * transaction is sent to the database server (due to garbage
- * collection). */
-- struct nbrec_gateway_chassis **new_gc
-- = xmemdup(lrp->gateway_chassis,
-- sizeof *new_gc * lrp->n_gateway_chassis);
-- new_gc[idx] = new_gc[lrp->n_gateway_chassis - 1];
-- nbrec_logical_router_port_verify_gateway_chassis(lrp);
-- nbrec_logical_router_port_set_gateway_chassis(
-- lrp, new_gc, lrp->n_gateway_chassis - 1);
-- free(new_gc);
-+ nbrec_logical_router_port_update_gateway_chassis_delvalue(lrp, gc);
- }
-
- /* Delete 'gc' from the IDL. This won't have a real effect on
-@@ -4893,6 +4934,7 @@ static void
- nbctl_lrp_add(struct ctl_context *ctx)
- {
- bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
-+ struct nbctl_context *nbctx = nbctl_context_get(ctx);
-
- const struct nbrec_logical_router *lr = NULL;
- char *error = lr_by_name_or_uuid(ctx, ctx->argv[1], true, &lr);
-@@ -4942,7 +4984,7 @@ nbctl_lrp_add(struct ctl_context *ctx)
- }
-
- const struct nbrec_logical_router *bound_lr;
-- error = lrp_to_lr(ctx->idl, lrp, &bound_lr);
-+ error = lrp_to_lr(ctx, lrp, &bound_lr);
- if (error) {
- ctx->error = error;
- return;
-@@ -5040,31 +5082,27 @@ nbctl_lrp_add(struct ctl_context *ctx)
- }
-
- /* Insert the logical port into the logical router. */
-- nbrec_logical_router_verify_ports(lr);
-- struct nbrec_logical_router_port **new_ports = xmalloc(sizeof *new_ports *
-- (lr->n_ports + 1));
-- nullable_memcpy(new_ports, lr->ports, sizeof *new_ports * lr->n_ports);
-- new_ports[lr->n_ports] = CONST_CAST(struct nbrec_logical_router_port *,
-- lrp);
-- nbrec_logical_router_set_ports(lr, new_ports, lr->n_ports + 1);
-- free(new_ports);
-+ nbrec_logical_router_update_ports_addvalue(lr, lrp);
-+
-+ /* Updating runtime cache. */
-+ shash_add(&nbctx->lrp_to_lr_map, lrp->name, lr);
- }
-
--/* Removes logical router port 'lr->ports[idx]'. */
-+/* Removes logical router port 'lrp' from logical router 'lr'. */
- static void
--remove_lrp(const struct nbrec_logical_router *lr, size_t idx)
-+remove_lrp(struct ctl_context *ctx,
-+ const struct nbrec_logical_router *lr,
-+ const struct nbrec_logical_router_port *lrp)
- {
-- const struct nbrec_logical_router_port *lrp = lr->ports[idx];
-+ struct nbctl_context *nbctx = nbctl_context_get(ctx);
-+
-+ /* Updating runtime cache. */
-+ shash_find_and_delete(&nbctx->lrp_to_lr_map, lrp->name);
-
- /* First remove 'lrp' from the array of ports. This is what will
- * actually cause the logical port to be deleted when the transaction is
- * sent to the database server (due to garbage collection). */
-- struct nbrec_logical_router_port **new_ports
-- = xmemdup(lr->ports, sizeof *new_ports * lr->n_ports);
-- new_ports[idx] = new_ports[lr->n_ports - 1];
-- nbrec_logical_router_verify_ports(lr);
-- nbrec_logical_router_set_ports(lr, new_ports, lr->n_ports - 1);
-- free(new_ports);
-+ nbrec_logical_router_update_ports_delvalue(lr, lrp);
-
- /* Delete 'lrp' from the IDL. This won't have a real effect on
- * the database server (the IDL will suppress it in fact) but it
-@@ -5090,18 +5128,13 @@ nbctl_lrp_del(struct ctl_context *ctx)
-
- /* Find the router that contains 'lrp', then delete it. */
- const struct nbrec_logical_router *lr;
-- NBREC_LOGICAL_ROUTER_FOR_EACH (lr, ctx->idl) {
-- for (size_t i = 0; i < lr->n_ports; i++) {
-- if (lr->ports[i] == lrp) {
-- remove_lrp(lr, i);
-- return;
-- }
-- }
-- }
-
-- /* Can't happen because of the database schema. */
-- ctl_error(ctx, "logical port %s is not part of any logical router",
-- ctx->argv[1]);
-+ error = lrp_to_lr(ctx, lrp, &lr);
-+ if (error) {
-+ ctx->error = error;
-+ return;
-+ }
-+ remove_lrp(ctx, lr, lrp);
- }
-
- /* Print a list of logical router ports. */
-@@ -5275,7 +5308,7 @@ fwd_group_to_logical_switch(struct ctl_context *ctx,
- }
-
- const struct nbrec_logical_switch *ls;
-- error = lsp_to_ls(ctx->idl, lsp, &ls);
-+ error = lsp_to_ls(ctx, lsp, &ls);
- if (error) {
- ctx->error = error;
- return NULL;
-@@ -5350,7 +5383,7 @@ nbctl_fwd_group_add(struct ctl_context *ctx)
- return;
- }
- if (lsp) {
-- error = lsp_to_ls(ctx->idl, lsp, &ls);
-+ error = lsp_to_ls(ctx, lsp, &ls);
- if (error) {
- ctx->error = error;
- return;
-@@ -5373,15 +5406,7 @@ nbctl_fwd_group_add(struct ctl_context *ctx)
- nbrec_forwarding_group_set_liveness(fwd_group, true);
- }
-
-- struct nbrec_forwarding_group **new_fwd_groups =
-- xmalloc(sizeof(*new_fwd_groups) * (ls->n_forwarding_groups + 1));
-- memcpy(new_fwd_groups, ls->forwarding_groups,
-- sizeof *new_fwd_groups * ls->n_forwarding_groups);
-- new_fwd_groups[ls->n_forwarding_groups] = fwd_group;
-- nbrec_logical_switch_set_forwarding_groups(ls, new_fwd_groups,
-- (ls->n_forwarding_groups + 1));
-- free(new_fwd_groups);
--
-+ nbrec_logical_switch_update_forwarding_groups_addvalue(ls, fwd_group);
- }
-
- static void
-@@ -5403,14 +5428,8 @@ nbctl_fwd_group_del(struct ctl_context *ctx)
-
- for (int i = 0; i < ls->n_forwarding_groups; ++i) {
- if (!strcmp(ls->forwarding_groups[i]->name, fwd_group->name)) {
-- struct nbrec_forwarding_group **new_fwd_groups =
-- xmemdup(ls->forwarding_groups,
-- sizeof *new_fwd_groups * ls->n_forwarding_groups);
-- new_fwd_groups[i] =
-- ls->forwarding_groups[ls->n_forwarding_groups - 1];
-- nbrec_logical_switch_set_forwarding_groups(ls, new_fwd_groups,
-- (ls->n_forwarding_groups - 1));
-- free(new_fwd_groups);
-+ nbrec_logical_switch_update_forwarding_groups_delvalue(
-+ ls, ls->forwarding_groups[i]);
- nbrec_forwarding_group_delete(fwd_group);
- return;
- }
-@@ -5498,17 +5517,27 @@ struct ipv4_route {
- const struct nbrec_logical_router_static_route *route;
- };
-
-+static int
-+__ipv4_route_cmp(const struct ipv4_route *r1, const struct ipv4_route *r2)
-+{
-+ if (r1->priority != r2->priority) {
-+ return r1->priority > r2->priority ? -1 : 1;
-+ }
-+ if (r1->addr != r2->addr) {
-+ return ntohl(r1->addr) < ntohl(r2->addr) ? -1 : 1;
-+ }
-+ return 0;
-+}
-+
- static int
- ipv4_route_cmp(const void *route1_, const void *route2_)
- {
- const struct ipv4_route *route1p = route1_;
- const struct ipv4_route *route2p = route2_;
-
-- if (route1p->priority != route2p->priority) {
-- return route1p->priority > route2p->priority ? -1 : 1;
-- }
-- if (route1p->addr != route2p->addr) {
-- return ntohl(route1p->addr) < ntohl(route2p->addr) ? -1 : 1;
-+ int ret = __ipv4_route_cmp(route1p, route2p);
-+ if (ret) {
-+ return ret;
- }
- return route_cmp_details(route1p->route, route2p->route);
- }
-@@ -5519,16 +5548,22 @@ struct ipv6_route {
- const struct nbrec_logical_router_static_route *route;
- };
-
-+static int
-+__ipv6_route_cmp(const struct ipv6_route *r1, const struct ipv6_route *r2)
-+{
-+ if (r1->priority != r2->priority) {
-+ return r1->priority > r2->priority ? -1 : 1;
-+ }
-+ return memcmp(&r1->addr, &r2->addr, sizeof(r1->addr));
-+}
-+
- static int
- ipv6_route_cmp(const void *route1_, const void *route2_)
- {
- const struct ipv6_route *route1p = route1_;
- const struct ipv6_route *route2p = route2_;
-
-- if (route1p->priority != route2p->priority) {
-- return route1p->priority > route2p->priority ? -1 : 1;
-- }
-- int ret = memcmp(&route1p->addr, &route2p->addr, sizeof(route1p->addr));
-+ int ret = __ipv6_route_cmp(route1p, route2p);
- if (ret) {
- return ret;
- }
-@@ -5536,7 +5571,8 @@ ipv6_route_cmp(const void *route1_, const void *route2_)
- }
-
- static void
--print_route(const struct nbrec_logical_router_static_route *route, struct ds *s)
-+print_route(const struct nbrec_logical_router_static_route *route,
-+ struct ds *s, bool ecmp)
- {
-
- char *prefix = normalize_prefix_str(route->ip_prefix);
-@@ -5558,6 +5594,19 @@ print_route(const struct nbrec_logical_router_static_route *route, struct ds *s)
- if (smap_get(&route->external_ids, "ic-learned-route")) {
- ds_put_format(s, " (learned)");
- }
-+
-+ if (ecmp) {
-+ ds_put_cstr(s, " ecmp");
-+ }
-+
-+ if (smap_get_bool(&route->options, "ecmp_symmetric_reply", false)) {
-+ ds_put_cstr(s, " ecmp-symmetric-reply");
-+ }
-+
-+ if (route->bfd) {
-+ ds_put_cstr(s, " bfd");
-+ }
-+
- ds_put_char(s, '\n');
- }
-
-@@ -5623,7 +5672,16 @@ nbctl_lr_route_list(struct ctl_context *ctx)
- ds_put_cstr(&ctx->output, "IPv4 Routes\n");
- }
- for (int i = 0; i < n_ipv4_routes; i++) {
-- print_route(ipv4_routes[i].route, &ctx->output);
-+ bool ecmp = false;
-+ if (i < n_ipv4_routes - 1 &&
-+ !__ipv4_route_cmp(&ipv4_routes[i], &ipv4_routes[i + 1])) {
-+ ecmp = true;
-+ } else if (i > 0 &&
-+ !__ipv4_route_cmp(&ipv4_routes[i],
-+ &ipv4_routes[i - 1])) {
-+ ecmp = true;
-+ }
-+ print_route(ipv4_routes[i].route, &ctx->output, ecmp);
- }
-
- if (n_ipv6_routes) {
-@@ -5631,7 +5689,16 @@ nbctl_lr_route_list(struct ctl_context *ctx)
- n_ipv4_routes ? "\n" : "");
- }
- for (int i = 0; i < n_ipv6_routes; i++) {
-- print_route(ipv6_routes[i].route, &ctx->output);
-+ bool ecmp = false;
-+ if (i < n_ipv6_routes - 1 &&
-+ !__ipv6_route_cmp(&ipv6_routes[i], &ipv6_routes[i + 1])) {
-+ ecmp = true;
-+ } else if (i > 0 &&
-+ !__ipv6_route_cmp(&ipv6_routes[i],
-+ &ipv6_routes[i - 1])) {
-+ ecmp = true;
-+ }
-+ print_route(ipv6_routes[i].route, &ctx->output, ecmp);
- }
-
- free(ipv4_routes);
-@@ -6007,17 +6074,7 @@ cmd_ha_ch_grp_add_chassis(struct ctl_context *ctx)
- nbrec_ha_chassis_set_chassis_name(ha_chassis, chassis_name);
- nbrec_ha_chassis_set_priority(ha_chassis, priority);
-
-- nbrec_ha_chassis_group_verify_ha_chassis(ha_ch_grp);
--
-- struct nbrec_ha_chassis **new_ha_chs =
-- xmalloc(sizeof *new_ha_chs * (ha_ch_grp->n_ha_chassis + 1));
-- nullable_memcpy(new_ha_chs, ha_ch_grp->ha_chassis,
-- sizeof *new_ha_chs * ha_ch_grp->n_ha_chassis);
-- new_ha_chs[ha_ch_grp->n_ha_chassis] =
-- CONST_CAST(struct nbrec_ha_chassis *, ha_chassis);
-- nbrec_ha_chassis_group_set_ha_chassis(ha_ch_grp, new_ha_chs,
-- ha_ch_grp->n_ha_chassis + 1);
-- free(new_ha_chs);
-+ nbrec_ha_chassis_group_update_ha_chassis_addvalue(ha_ch_grp, ha_chassis);
- }
-
- static void
-@@ -6032,11 +6089,9 @@ cmd_ha_ch_grp_remove_chassis(struct ctl_context *ctx)
-
- const char *chassis_name = ctx->argv[2];
- struct nbrec_ha_chassis *ha_chassis = NULL;
-- size_t idx = 0;
- for (size_t i = 0; i < ha_ch_grp->n_ha_chassis; i++) {
- if (!strcmp(ha_ch_grp->ha_chassis[i]->chassis_name, chassis_name)) {
- ha_chassis = ha_ch_grp->ha_chassis[i];
-- idx = i;
- break;
- }
- }
-@@ -6047,14 +6102,7 @@ cmd_ha_ch_grp_remove_chassis(struct ctl_context *ctx)
- return;
- }
-
-- struct nbrec_ha_chassis **new_ha_ch
-- = xmemdup(ha_ch_grp->ha_chassis,
-- sizeof *new_ha_ch * ha_ch_grp->n_ha_chassis);
-- new_ha_ch[idx] = new_ha_ch[ha_ch_grp->n_ha_chassis - 1];
-- nbrec_ha_chassis_group_verify_ha_chassis(ha_ch_grp);
-- nbrec_ha_chassis_group_set_ha_chassis(ha_ch_grp, new_ha_ch,
-- ha_ch_grp->n_ha_chassis - 1);
-- free(new_ha_ch);
-+ nbrec_ha_chassis_group_update_ha_chassis_delvalue(ha_ch_grp, ha_chassis);
- nbrec_ha_chassis_delete(ha_chassis);
- }
-
-@@ -6231,7 +6279,7 @@ do_nbctl(const char *args, struct ctl_command *commands, size_t n_commands,
- struct ovsdb_idl_txn *txn;
- enum ovsdb_idl_txn_status status;
- struct ovsdb_symbol_table *symtab;
-- struct ctl_context ctx;
-+ struct nbctl_context ctx;
- struct ctl_command *c;
- struct shash_node *node;
- int64_t next_cfg = 0;
-@@ -6268,25 +6316,26 @@ do_nbctl(const char *args, struct ctl_command *commands, size_t n_commands,
- ds_init(&c->output);
- c->table = NULL;
- }
-- ctl_context_init(&ctx, NULL, idl, txn, symtab, NULL);
-+ nbctl_context_init(&ctx);
-+ ctl_context_init(&ctx.base, NULL, idl, txn, symtab, NULL);
- for (c = commands; c < &commands[n_commands]; c++) {
-- ctl_context_init_command(&ctx, c);
-+ ctl_context_init_command(&ctx.base, c);
- if (c->syntax->run) {
-- (c->syntax->run)(&ctx);
-+ (c->syntax->run)(&ctx.base);
- }
-- if (ctx.error) {
-- error = xstrdup(ctx.error);
-- ctl_context_done(&ctx, c);
-+ if (ctx.base.error) {
-+ error = xstrdup(ctx.base.error);
-+ ctl_context_done(&ctx.base, c);
- goto out_error;
- }
-- ctl_context_done_command(&ctx, c);
-+ ctl_context_done_command(&ctx.base, c);
-
-- if (ctx.try_again) {
-- ctl_context_done(&ctx, NULL);
-+ if (ctx.base.try_again) {
-+ ctl_context_done(&ctx.base, NULL);
- goto try_again;
- }
- }
-- ctl_context_done(&ctx, NULL);
-+ ctl_context_done(&ctx.base, NULL);
-
- SHASH_FOR_EACH (node, &symtab->sh) {
- struct ovsdb_symbol *symbol = node->data;
-@@ -6317,14 +6366,14 @@ do_nbctl(const char *args, struct ctl_command *commands, size_t n_commands,
- if (status == TXN_UNCHANGED || status == TXN_SUCCESS) {
- for (c = commands; c < &commands[n_commands]; c++) {
- if (c->syntax->postprocess) {
-- ctl_context_init(&ctx, c, idl, txn, symtab, NULL);
-- (c->syntax->postprocess)(&ctx);
-- if (ctx.error) {
-- error = xstrdup(ctx.error);
-- ctl_context_done(&ctx, c);
-+ ctl_context_init(&ctx.base, c, idl, txn, symtab, NULL);
-+ (c->syntax->postprocess)(&ctx.base);
-+ if (ctx.base.error) {
-+ error = xstrdup(ctx.base.error);
-+ ctl_context_done(&ctx.base, c);
- goto out_error;
- }
-- ctl_context_done(&ctx, c);
-+ ctl_context_done(&ctx.base, c);
- }
- }
- }
-@@ -6412,6 +6461,7 @@ do_nbctl(const char *args, struct ctl_command *commands, size_t n_commands,
- done: ;
- }
-
-+ nbctl_context_destroy(&ctx);
- ovsdb_symbol_table_destroy(symtab);
- ovsdb_idl_txn_destroy(txn);
- the_idl_txn = NULL;
-@@ -6429,6 +6479,7 @@ out_error:
- ovsdb_idl_txn_destroy(txn);
- the_idl_txn = NULL;
-
-+ nbctl_context_destroy(&ctx);
- ovsdb_symbol_table_destroy(symtab);
- return error;
- }
-@@ -6561,7 +6612,7 @@ static const struct ctl_command_syntax nbctl_commands[] = {
- /* logical router route commands. */
- { "lr-route-add", 3, 4, "ROUTER PREFIX NEXTHOP [PORT]", NULL,
- nbctl_lr_route_add, NULL, "--may-exist,--ecmp,--ecmp-symmetric-reply,"
-- "--policy=", RW },
-+ "--policy=,--bfd?", RW },
- { "lr-route-del", 1, 4, "ROUTER [PREFIX [NEXTHOP [PORT]]]", NULL,
- nbctl_lr_route_del, NULL, "--if-exists,--policy=", RW },
- { "lr-route-list", 1, 1, "ROUTER", NULL, nbctl_lr_route_list, NULL,
-@@ -6588,7 +6639,7 @@ static const struct ctl_command_syntax nbctl_commands[] = {
- nbctl_lr_nat_set_ext_ips, NULL, "--is-exempted", RW},
- /* load balancer commands. */
- { "lb-add", 3, 4, "LB VIP[:PORT] IP[:PORT]... [PROTOCOL]", NULL,
-- nbctl_lb_add, NULL, "--may-exist,--add-duplicate", RW },
-+ nbctl_lb_add, NULL, "--may-exist,--add-duplicate,--reject,--event", RW },
- { "lb-del", 1, 2, "LB [VIP]", NULL, nbctl_lb_del, NULL,
- "--if-exists", RW },
- { "lb-list", 0, 1, "[LB]", NULL, nbctl_lb_list, NULL, "", RO },
-diff --git a/utilities/ovn-sbctl.c b/utilities/ovn-sbctl.c
-index 0a1b9ffdc..c38e8ec3b 100644
---- a/utilities/ovn-sbctl.c
-+++ b/utilities/ovn-sbctl.c
-@@ -526,6 +526,7 @@ pre_get_info(struct ctl_context *ctx)
- ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_tunnel_key);
- ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_chassis);
- ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_datapath);
-+ ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_up);
-
- ovsdb_idl_add_column(ctx->idl, &sbrec_logical_flow_col_logical_datapath);
- ovsdb_idl_add_column(ctx->idl, &sbrec_logical_flow_col_logical_dp_group);
-@@ -665,6 +666,7 @@ cmd_lsp_bind(struct ctl_context *ctx)
- struct sbctl_chassis *sbctl_ch;
- struct sbctl_port_binding *sbctl_bd;
- char *lport_name, *ch_name;
-+ bool up = true;
-
- /* port_binding must exist, chassis must exist! */
- lport_name = ctx->argv[1];
-@@ -683,6 +685,7 @@ cmd_lsp_bind(struct ctl_context *ctx)
- }
- }
- sbrec_port_binding_set_chassis(sbctl_bd->bd_cfg, sbctl_ch->ch_cfg);
-+ sbrec_port_binding_set_up(sbctl_bd->bd_cfg, &up, 1);
- sbctl_context_invalidate_cache(ctx);
- }
-
-@@ -699,6 +702,7 @@ cmd_lsp_unbind(struct ctl_context *ctx)
- sbctl_bd = find_port_binding(sbctl_ctx, lport_name, must_exist);
- if (sbctl_bd) {
- sbrec_port_binding_set_chassis(sbctl_bd->bd_cfg, NULL);
-+ sbrec_port_binding_set_up(sbctl_bd->bd_cfg, NULL, 0);
- }
- }
-
-diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c
-index 6fad36512..fb88bc06c 100644
---- a/utilities/ovn-trace.c
-+++ b/utilities/ovn-trace.c
-@@ -405,6 +405,7 @@ struct ovntrace_datapath {
- size_t n_flows, allocated_flows;
-
- struct hmap mac_bindings; /* Contains "struct ovntrace_mac_binding"s. */
-+ struct hmap fdbs; /* Contains "struct ovntrace_fdb"s. */
-
- bool has_local_l3gateway;
- };
-@@ -453,12 +454,24 @@ struct ovntrace_mac_binding {
- struct eth_addr mac;
- };
-
-+struct ovntrace_fdb {
-+ struct hmap_node node;
-+ uint16_t port_key;
-+ struct eth_addr mac;
-+};
-+
- static inline uint32_t
- hash_mac_binding(uint16_t port_key, const struct in6_addr *ip)
- {
- return hash_bytes(ip, sizeof *ip, port_key);
- }
-
-+static inline uint32_t
-+hash_fdb(const struct eth_addr *mac)
-+{
-+ return hash_bytes(mac, sizeof *mac, 0);
-+}
-+
- /* Every ovntrace_datapath, by southbound Datapath_Binding record UUID. */
- static struct hmap datapaths;
-
-@@ -478,6 +491,7 @@ static struct shash port_groups;
- static struct hmap dhcp_opts; /* Contains "struct gen_opts_map"s. */
- static struct hmap dhcpv6_opts; /* Contains "struct gen_opts_map"s. */
- static struct hmap nd_ra_opts; /* Contains "struct gen_opts_map"s. */
-+static struct controller_event_options event_opts;
-
- static struct ovntrace_datapath *
- ovntrace_datapath_find_by_sb_uuid(const struct uuid *sb_uuid)
-@@ -517,6 +531,18 @@ ovntrace_datapath_find_by_name(const char *name)
- return match;
- }
-
-+static struct ovntrace_datapath *
-+ovntrace_datapath_find_by_key(uint32_t tunnel_key)
-+{
-+ struct ovntrace_datapath *dp;
-+ HMAP_FOR_EACH (dp, sb_uuid_node, &datapaths) {
-+ if (dp->tunnel_key == tunnel_key) {
-+ return dp;
-+ }
-+ }
-+ return NULL;
-+}
-+
- static const struct ovntrace_port *
- ovntrace_port_find_by_key(const struct ovntrace_datapath *dp,
- uint16_t tunnel_key)
-@@ -597,6 +623,20 @@ ovntrace_mac_binding_find_mac_ip(const struct ovntrace_datapath *dp,
- return NULL;
- }
-
-+static const struct ovntrace_fdb *
-+ovntrace_fdb_find(const struct ovntrace_datapath *dp,
-+ const struct eth_addr *mac)
-+{
-+ const struct ovntrace_fdb *fdb;
-+ HMAP_FOR_EACH_WITH_HASH (fdb, node, hash_fdb(mac),
-+ &dp->fdbs) {
-+ if (eth_addr_equals(fdb->mac, *mac)) {
-+ return fdb;
-+ }
-+ }
-+ return NULL;
-+}
-+
- /* If 's' ends with a UUID, returns a copy of it with the UUID truncated to
- * just the first 6 characters; otherwise, returns a copy of 's'. */
- static char *
-@@ -637,7 +677,7 @@ read_datapaths(void)
-
- ovs_list_init(&dp->mcgroups);
- hmap_init(&dp->mac_bindings);
--
-+ hmap_init(&dp->fdbs);
- hmap_insert(&datapaths, &dp->sb_uuid_node, uuid_hash(&dp->sb_uuid));
- }
- }
-@@ -901,10 +941,11 @@ parse_lflow_for_datapath(const struct sbrec_logical_flow *sblf,
- .dhcp_opts = &dhcp_opts,
- .dhcpv6_opts = &dhcpv6_opts,
- .nd_ra_opts = &nd_ra_opts,
-+ .controller_event_opts = &event_opts,
- .pipeline = (!strcmp(sblf->pipeline, "ingress")
- ? OVNACT_P_INGRESS
- : OVNACT_P_EGRESS),
-- .n_tables = 24,
-+ .n_tables = LOG_PIPELINE_LEN,
- .cur_ltable = sblf->table_id,
- };
- uint64_t stub[1024 / 8];
-@@ -1006,6 +1047,8 @@ read_gen_opts(void)
-
- hmap_init(&nd_ra_opts);
- nd_ra_opts_init(&nd_ra_opts);
-+
-+ controller_event_opts_init(&event_opts);
- }
-
- static void
-@@ -1049,6 +1092,30 @@ read_mac_bindings(void)
- }
- }
-
-+static void
-+read_fdbs(void)
-+{
-+ const struct sbrec_fdb *fdb;
-+ SBREC_FDB_FOR_EACH (fdb, ovnsb_idl) {
-+ struct eth_addr mac;
-+ if (!eth_addr_from_string(fdb->mac, &mac)) {
-+ VLOG_WARN("%s: bad Ethernet address", fdb->mac);
-+ continue;
-+ }
-+
-+ struct ovntrace_datapath *dp =
-+ ovntrace_datapath_find_by_key(fdb->dp_key);
-+ if (!dp) {
-+ continue;
-+ }
-+
-+ struct ovntrace_fdb *fdb_t = xmalloc(sizeof *fdb_t);
-+ fdb_t->mac = mac;
-+ fdb_t->port_key = fdb->port_key;
-+ hmap_insert(&dp->fdbs, &fdb_t->node, hash_fdb(&mac));
-+ }
-+}
-+
- static void
- read_db(void)
- {
-@@ -1060,6 +1127,7 @@ read_db(void)
- read_gen_opts();
- read_flows();
- read_mac_bindings();
-+ read_fdbs();
- }
-
- static const struct ovntrace_port *
-@@ -1116,6 +1184,11 @@ ovntrace_lookup_port(const void *dp_, const char *port_name,
- return true;
- }
-
-+ if (!strcmp(port_name, "none")) {
-+ *portp = 0;
-+ return true;
-+ }
-+
- const struct ovntrace_port *port = ovntrace_port_lookup_by_name(port_name);
- if (port) {
- if (port->dp == dp) {
-@@ -1802,6 +1875,91 @@ execute_tcp_reset(const struct ovnact_nest *on,
- execute_tcp6_reset(on, dp, uflow, table_id, loopback, pipeline, super);
- }
- }
-+
-+static void
-+execute_sctp4_abort(const struct ovnact_nest *on,
-+ const struct ovntrace_datapath *dp,
-+ const struct flow *uflow, uint8_t table_id,
-+ bool loopback, enum ovnact_pipeline pipeline,
-+ struct ovs_list *super)
-+{
-+ struct flow sctp_flow = *uflow;
-+
-+ /* Update fields for TCP SCTP. */
-+ if (loopback) {
-+ sctp_flow.dl_dst = uflow->dl_src;
-+ sctp_flow.dl_src = uflow->dl_dst;
-+ sctp_flow.nw_dst = uflow->nw_src;
-+ sctp_flow.nw_src = uflow->nw_dst;
-+ } else {
-+ sctp_flow.dl_dst = uflow->dl_dst;
-+ sctp_flow.dl_src = uflow->dl_src;
-+ sctp_flow.nw_dst = uflow->nw_dst;
-+ sctp_flow.nw_src = uflow->nw_src;
-+ }
-+ sctp_flow.nw_proto = IPPROTO_SCTP;
-+ sctp_flow.nw_ttl = 255;
-+ sctp_flow.tp_src = uflow->tp_src;
-+ sctp_flow.tp_dst = uflow->tp_dst;
-+
-+ struct ovntrace_node *node = ovntrace_node_append(
-+ super, OVNTRACE_NODE_TRANSFORMATION, "sctp_abort");
-+
-+ trace_actions(on->nested, on->nested_len, dp, &sctp_flow,
-+ table_id, pipeline, &node->subs);
-+}
-+
-+static void
-+execute_sctp6_abort(const struct ovnact_nest *on,
-+ const struct ovntrace_datapath *dp,
-+ const struct flow *uflow, uint8_t table_id,
-+ bool loopback, enum ovnact_pipeline pipeline,
-+ struct ovs_list *super)
-+{
-+ struct flow sctp_flow = *uflow;
-+
-+ /* Update fields for SCTP. */
-+ if (loopback) {
-+ sctp_flow.dl_dst = uflow->dl_src;
-+ sctp_flow.dl_src = uflow->dl_dst;
-+ sctp_flow.ipv6_dst = uflow->ipv6_src;
-+ sctp_flow.ipv6_src = uflow->ipv6_dst;
-+ } else {
-+ sctp_flow.dl_dst = uflow->dl_dst;
-+ sctp_flow.dl_src = uflow->dl_src;
-+ sctp_flow.ipv6_dst = uflow->ipv6_dst;
-+ sctp_flow.ipv6_src = uflow->ipv6_src;
-+ }
-+ sctp_flow.nw_proto = IPPROTO_TCP;
-+ sctp_flow.nw_ttl = 255;
-+ sctp_flow.tp_src = uflow->tp_src;
-+ sctp_flow.tp_dst = uflow->tp_dst;
-+ sctp_flow.tcp_flags = htons(TCP_RST);
-+
-+ struct ovntrace_node *node = ovntrace_node_append(
-+ super, OVNTRACE_NODE_TRANSFORMATION, "sctp_abort");
-+
-+ trace_actions(on->nested, on->nested_len, dp, &sctp_flow,
-+ table_id, pipeline, &node->subs);
-+}
-+
-+static void
-+execute_sctp_abort(const struct ovnact_nest *on,
-+ const struct ovntrace_datapath *dp,
-+ const struct flow *uflow, uint8_t table_id,
-+ bool loopback, enum ovnact_pipeline pipeline,
-+ struct ovs_list *super)
-+{
-+ if (get_dl_type(uflow) == htons(ETH_TYPE_IP)) {
-+ execute_sctp4_abort(on, dp, uflow, table_id, loopback,
-+ pipeline, super);
-+ } else {
-+ execute_sctp6_abort(on, dp, uflow, table_id, loopback,
-+ pipeline, super);
-+ }
-+}
-+
-+
- static void
- execute_reject(const struct ovnact_nest *on,
- const struct ovntrace_datapath *dp,
-@@ -1810,6 +1968,8 @@ execute_reject(const struct ovnact_nest *on,
- {
- if (uflow->nw_proto == IPPROTO_TCP) {
- execute_tcp_reset(on, dp, uflow, table_id, true, pipeline, super);
-+ } else if (uflow->nw_proto == IPPROTO_SCTP) {
-+ execute_sctp_abort(on, dp, uflow, table_id, true, pipeline, super);
- } else {
- if (get_dl_type(uflow) == htons(ETH_TYPE_IP)) {
- execute_icmp4(on, dp, uflow, table_id, true, pipeline, super);
-@@ -1938,6 +2098,66 @@ execute_lookup_mac_bind_ip(const struct ovnact_lookup_mac_bind_ip *bind,
- mf_write_subfield_flow(&dst, &sv, uflow);
- }
-
-+static void
-+execute_lookup_fdb(const struct ovnact_lookup_fdb *lookup_fdb,
-+ const struct ovntrace_datapath *dp,
-+ struct flow *uflow,
-+ struct ovs_list *super)
-+{
-+ /* Get logical port number.*/
-+ struct mf_subfield port_sf = expr_resolve_field(&lookup_fdb->port);
-+ ovs_assert(port_sf.n_bits == 32);
-+ uint32_t port_key = mf_get_subfield(&port_sf, uflow);
-+
-+ /* Get MAC. */
-+ struct mf_subfield mac_sf = expr_resolve_field(&lookup_fdb->mac);
-+ ovs_assert(mac_sf.n_bits == 48);
-+ union mf_subvalue mac_sv;
-+ mf_read_subfield(&mac_sf, uflow, &mac_sv);
-+
-+ const struct ovntrace_fdb *fdb_t
-+ = ovntrace_fdb_find(dp, &mac_sv.mac);
-+
-+ struct mf_subfield dst = expr_resolve_field(&lookup_fdb->dst);
-+ uint8_t val = 0;
-+
-+ if (fdb_t && fdb_t->port_key == port_key) {
-+ val = 1;
-+ ovntrace_node_append(super, OVNTRACE_NODE_ACTION,
-+ "/* MAC lookup for "ETH_ADDR_FMT" found in "
-+ "FDB. */", ETH_ADDR_ARGS(uflow->dl_dst));
-+ } else {
-+ ovntrace_node_append(super, OVNTRACE_NODE_ACTION,
-+ "/* lookup mac failed in mac learning table. */");
-+ }
-+ union mf_subvalue sv = { .u8_val = val };
-+ mf_write_subfield_flow(&dst, &sv, uflow);
-+}
-+
-+static void
-+execute_get_fdb(const struct ovnact_get_fdb *get_fdb,
-+ const struct ovntrace_datapath *dp,
-+ struct flow *uflow)
-+{
-+ /* Get MAC. */
-+ struct mf_subfield mac_sf = expr_resolve_field(&get_fdb->mac);
-+ ovs_assert(mac_sf.n_bits == 48);
-+ union mf_subvalue mac_sv;
-+ mf_read_subfield(&mac_sf, uflow, &mac_sv);
-+
-+ const struct ovntrace_fdb *fdb_t
-+ = ovntrace_fdb_find(dp, &mac_sv.mac);
-+
-+ struct mf_subfield dst = expr_resolve_field(&get_fdb->dst);
-+ uint32_t val = 0;
-+ if (fdb_t) {
-+ val = fdb_t->port_key;
-+ }
-+
-+ union mf_subvalue sv = { .be32_int = htonl(val) };
-+ mf_write_subfield_flow(&dst, &sv, uflow);
-+}
-+
- static void
- execute_put_opts(const struct ovnact_put_opts *po,
- const char *name, struct flow *uflow,
-@@ -2503,6 +2723,11 @@ trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len,
- false, pipeline, super);
- break;
-
-+ case OVNACT_SCTP_ABORT:
-+ execute_sctp_abort(ovnact_get_SCTP_ABORT(a), dp, uflow, table_id,
-+ false, pipeline, super);
-+ break;
-+
- case OVNACT_OVNFIELD_LOAD:
- execute_ovnfield_load(ovnact_get_OVNFIELD_LOAD(a), super);
- break;
-@@ -2540,6 +2765,20 @@ trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len,
- break;
- case OVNACT_DHCP6_REPLY:
- break;
-+ case OVNACT_BFD_MSG:
-+ break;
-+
-+ case OVNACT_PUT_FDB:
-+ /* Nothing to do for tracing. */
-+ break;
-+
-+ case OVNACT_GET_FDB:
-+ execute_get_fdb(ovnact_get_GET_FDB(a), dp, uflow);
-+ break;
-+
-+ case OVNACT_LOOKUP_FDB:
-+ execute_lookup_fdb(ovnact_get_LOOKUP_FDB(a), dp, uflow, super);
-+ break;
- }
- }
- ds_destroy(&s);
diff --git a/SOURCES/ovn-2021.patch b/SOURCES/ovn-2021.patch
new file mode 100644
index 0000000..adf62ad
--- /dev/null
+++ b/SOURCES/ovn-2021.patch
@@ -0,0 +1,652 @@
+diff --git a/NEWS b/NEWS
+index 75f26ddb7..4043ecf20 100644
+--- a/NEWS
++++ b/NEWS
+@@ -1,3 +1,6 @@
++OVN v21.12.1 - xx xxx xxxx
++--------------------------
++
+ OVN v21.12.0 - 22 Dec 2021
+ --------------------------
+ - Set ignore_lsp_down to true as default, so that ARP responder flows are
+diff --git a/configure.ac b/configure.ac
+index 48b4662f0..e44c86c74 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -13,7 +13,7 @@
+ # limitations under the License.
+
+ AC_PREREQ(2.63)
+-AC_INIT(ovn, 21.12.0, bugs@openvswitch.org)
++AC_INIT(ovn, 21.12.1, bugs@openvswitch.org)
+ AC_CONFIG_MACRO_DIR([m4])
+ AC_CONFIG_AUX_DIR([build-aux])
+ AC_CONFIG_HEADERS([config.h])
+diff --git a/controller/physical.c b/controller/physical.c
+index 836fc769a..aa651b876 100644
+--- a/controller/physical.c
++++ b/controller/physical.c
+@@ -1477,10 +1477,12 @@ consider_mc_group(struct ovsdb_idl_index *sbrec_port_binding_by_name,
+ put_load(port->tunnel_key, MFF_LOG_OUTPORT, 0, 32,
+ &remote_ofpacts);
+ put_resubmit(OFTABLE_CHECK_LOOPBACK, &remote_ofpacts);
+- } else if (local_binding_get_primary_pb(local_bindings, lport_name)
+- || simap_contains(patch_ofports, port->logical_port)
+- || (!strcmp(port->type, "l3gateway")
+- && port->chassis == chassis)) {
++ } else if (port->chassis == chassis
++ && (local_binding_get_primary_pb(local_bindings, lport_name)
++ || !strcmp(port->type, "l3gateway"))) {
++ put_load(port->tunnel_key, MFF_LOG_OUTPORT, 0, 32, &ofpacts);
++ put_resubmit(OFTABLE_CHECK_LOOPBACK, &ofpacts);
++ } else if (simap_contains(patch_ofports, port->logical_port)) {
+ put_load(port->tunnel_key, MFF_LOG_OUTPORT, 0, 32, &ofpacts);
+ put_resubmit(OFTABLE_CHECK_LOOPBACK, &ofpacts);
+ } else if (!strcmp(port->type, "chassisredirect")
+diff --git a/controller/pinctrl.c b/controller/pinctrl.c
+index f0667807e..d2bb7f441 100644
+--- a/controller/pinctrl.c
++++ b/controller/pinctrl.c
+@@ -1624,12 +1624,8 @@ pinctrl_handle_icmp(struct rconn *swconn, const struct flow *ip_flow,
+ struct dp_packet packet;
+
+ dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
+- dp_packet_clear(&packet);
+- packet.packet_type = htonl(PT_ETH);
+-
+- struct eth_header *eh = dp_packet_put_zeros(&packet, sizeof *eh);
+- eh->eth_dst = loopback ? ip_flow->dl_src : ip_flow->dl_dst;
+- eh->eth_src = loopback ? ip_flow->dl_dst : ip_flow->dl_src;
++ struct eth_addr eth_src = loopback ? ip_flow->dl_dst : ip_flow->dl_src;
++ struct eth_addr eth_dst = loopback ? ip_flow->dl_src : ip_flow->dl_dst;
+
+ if (get_dl_type(ip_flow) == htons(ETH_TYPE_IP)) {
+ struct ip_header *in_ip = dp_packet_l3(pkt_in);
+@@ -1642,27 +1638,8 @@ pinctrl_handle_icmp(struct rconn *swconn, const struct flow *ip_flow,
+ return;
+ }
+
+- struct ip_header *nh = dp_packet_put_zeros(&packet, sizeof *nh);
+-
+- eh->eth_type = htons(ETH_TYPE_IP);
+- dp_packet_set_l3(&packet, nh);
+- nh->ip_ihl_ver = IP_IHL_VER(5, 4);
+- nh->ip_tot_len = htons(sizeof(struct ip_header) +
+- sizeof(struct icmp_header));
+- nh->ip_proto = IPPROTO_ICMP;
+- nh->ip_frag_off = htons(IP_DF);
+ ovs_be32 nw_src = loopback ? ip_flow->nw_dst : ip_flow->nw_src;
+ ovs_be32 nw_dst = loopback ? ip_flow->nw_src : ip_flow->nw_dst;
+- packet_set_ipv4(&packet, nw_src, nw_dst, ip_flow->nw_tos, 255);
+-
+- uint8_t icmp_code = 1;
+- if (set_icmp_code && in_ip->ip_proto == IPPROTO_UDP) {
+- icmp_code = 3;
+- }
+-
+- struct icmp_header *ih = dp_packet_put_zeros(&packet, sizeof *ih);
+- dp_packet_set_l4(&packet, ih);
+- packet_set_icmp(&packet, ICMP4_DST_UNREACH, icmp_code);
+
+ /* RFC 1122: 3.2.2 MUST send at least the IP header and 8 bytes
+ * of header. MAY send more.
+@@ -1671,51 +1648,40 @@ pinctrl_handle_icmp(struct rconn *swconn, const struct flow *ip_flow,
+ * So, lets return as much as we can. */
+
+ /* Calculate available room to include the original IP + data. */
+- nh = dp_packet_l3(&packet);
+- uint16_t room = 576 - (sizeof *eh + ntohs(nh->ip_tot_len));
++ uint16_t room = 576 - (sizeof(struct eth_header)
++ + sizeof(struct ip_header)
++ + sizeof(struct icmp_header));
+ if (in_ip_len > room) {
+ in_ip_len = room;
+ }
+- dp_packet_put(&packet, in_ip, in_ip_len);
+
+- /* dp_packet_put may reallocate the buffer. Get the l3 and l4
+- * header pointers again. */
+- nh = dp_packet_l3(&packet);
+- ih = dp_packet_l4(&packet);
+- uint16_t ip_total_len = ntohs(nh->ip_tot_len) + in_ip_len;
+- nh->ip_tot_len = htons(ip_total_len);
++ uint16_t ip_total_len = sizeof(struct icmp_header) + in_ip_len;
++
++ pinctrl_compose_ipv4(&packet, eth_src, eth_dst, nw_src, nw_dst,
++ IPPROTO_ICMP, 255, ip_total_len);
++
++ uint8_t icmp_code = 1;
++ if (set_icmp_code && in_ip->ip_proto == IPPROTO_UDP) {
++ icmp_code = 3;
++ }
++
++ struct icmp_header *ih = dp_packet_l4(&packet);
++ packet_set_icmp(&packet, ICMP4_DST_UNREACH, icmp_code);
++
++ /* Include original IP + data. */
++ void *data = ih + 1;
++ memcpy(data, in_ip, in_ip_len);
++
+ ih->icmp_csum = 0;
+ ih->icmp_csum = csum(ih, sizeof *ih + in_ip_len);
+- nh->ip_csum = 0;
+- nh->ip_csum = csum(nh, sizeof *nh);
+-
+ } else {
+- struct ip6_hdr *nh = dp_packet_put_zeros(&packet, sizeof *nh);
+- struct icmp6_data_header *ih;
+- uint32_t icmpv6_csum;
+- struct ip6_hdr *in_ip = dp_packet_l3(pkt_in);
+-
+- eh->eth_type = htons(ETH_TYPE_IPV6);
+- dp_packet_set_l3(&packet, nh);
+- nh->ip6_vfc = 0x60;
+- nh->ip6_nxt = IPPROTO_ICMPV6;
+- nh->ip6_plen = htons(ICMP6_DATA_HEADER_LEN);
++ struct ovs_16aligned_ip6_hdr *in_ip = dp_packet_l3(pkt_in);
++ uint16_t in_ip_len = (uint16_t) sizeof *in_ip + ntohs(in_ip->ip6_plen);
++
+ const struct in6_addr *ip6_src =
+ loopback ? &ip_flow->ipv6_dst : &ip_flow->ipv6_src;
+ const struct in6_addr *ip6_dst =
+ loopback ? &ip_flow->ipv6_src : &ip_flow->ipv6_dst;
+- packet_set_ipv6(&packet, ip6_src, ip6_dst, ip_flow->nw_tos,
+- ip_flow->ipv6_label, 255);
+-
+- ih = dp_packet_put_zeros(&packet, sizeof *ih);
+- dp_packet_set_l4(&packet, ih);
+- ih->icmp6_base.icmp6_type = ICMP6_DST_UNREACH;
+- ih->icmp6_base.icmp6_code = 1;
+-
+- if (set_icmp_code && in_ip->ip6_nxt == IPPROTO_UDP) {
+- ih->icmp6_base.icmp6_code = ICMP6_DST_UNREACH_NOPORT;
+- }
+- ih->icmp6_base.icmp6_cksum = 0;
+
+ /* RFC 4443: 3.1.
+ *
+@@ -1730,20 +1696,33 @@ pinctrl_handle_icmp(struct rconn *swconn, const struct flow *ip_flow,
+ * | exceeding the minimum IPv6 MTU [IPv6] |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+-
+- uint16_t room = 1280 - (sizeof *eh + sizeof *nh +
+- ICMP6_DATA_HEADER_LEN);
+- uint16_t in_ip_len = (uint16_t) sizeof *in_ip + ntohs(in_ip->ip6_plen);
++ uint16_t room = 1280 - (sizeof(struct eth_header)
++ + sizeof(struct ip6_hdr)
++ + ICMP6_DATA_HEADER_LEN);
+ if (in_ip_len > room) {
+ in_ip_len = room;
+ }
+
+- dp_packet_put(&packet, in_ip, in_ip_len);
+- nh = dp_packet_l3(&packet);
+- nh->ip6_plen = htons(ICMP6_DATA_HEADER_LEN + in_ip_len);
++ uint16_t ip_total_len =
++ sizeof(struct icmp6_data_header) + in_ip_len;
++ pinctrl_compose_ipv6(&packet, eth_src, eth_dst,
++ CONST_CAST(struct in6_addr *, ip6_src),
++ CONST_CAST(struct in6_addr *, ip6_dst),
++ IPPROTO_ICMPV6, 255, ip_total_len);
+
+- icmpv6_csum = packet_csum_pseudoheader6(dp_packet_l3(&packet));
+- ih = dp_packet_l4(&packet);
++ struct icmp6_data_header *ih = dp_packet_l4(&packet);
++ ih->icmp6_base.icmp6_type = ICMP6_DST_UNREACH;
++ ih->icmp6_base.icmp6_code = 1;
++
++ if (set_icmp_code && in_ip->ip6_nxt == IPPROTO_UDP) {
++ ih->icmp6_base.icmp6_code = ICMP6_DST_UNREACH_NOPORT;
++ }
++ ih->icmp6_base.icmp6_cksum = 0;
++
++ void *data = ih + 1;
++ memcpy(data, in_ip, in_ip_len);
++ uint32_t icmpv6_csum =
++ packet_csum_pseudoheader6(dp_packet_l3(&packet));
+ ih->icmp6_base.icmp6_cksum = csum_finish(
+ csum_continue(icmpv6_csum, ih,
+ in_ip_len + ICMP6_DATA_HEADER_LEN));
+@@ -1777,7 +1756,6 @@ pinctrl_handle_tcp_reset(struct rconn *swconn, const struct flow *ip_flow,
+ struct dp_packet packet;
+
+ dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
+- packet.packet_type = htonl(PT_ETH);
+
+ struct eth_addr eth_src = loopback ? ip_flow->dl_dst : ip_flow->dl_src;
+ struct eth_addr eth_dst = loopback ? ip_flow->dl_src : ip_flow->dl_dst;
+@@ -1798,8 +1776,8 @@ pinctrl_handle_tcp_reset(struct rconn *swconn, const struct flow *ip_flow,
+ IPPROTO_TCP, 63, TCP_HEADER_LEN);
+ }
+
+- struct tcp_header *th = dp_packet_put_zeros(&packet, sizeof *th);
+- dp_packet_set_l4(&packet, th);
++ struct tcp_header *th = dp_packet_l4(&packet);
++
+ th->tcp_dst = ip_flow->tp_src;
+ th->tcp_src = ip_flow->tp_dst;
+
+@@ -1825,18 +1803,6 @@ pinctrl_handle_tcp_reset(struct rconn *swconn, const struct flow *ip_flow,
+ dp_packet_uninit(&packet);
+ }
+
+-static void dp_packet_put_sctp_abort(struct dp_packet *packet,
+- bool reflect_tag)
+-{
+- struct sctp_chunk_header abort = {
+- .sctp_chunk_type = SCTP_CHUNK_TYPE_ABORT,
+- .sctp_chunk_flags = reflect_tag ? SCTP_ABORT_CHUNK_FLAG_T : 0,
+- .sctp_chunk_len = htons(SCTP_CHUNK_HEADER_LEN),
+- };
+-
+- dp_packet_put(packet, &abort, sizeof abort);
+-}
+-
+ static void
+ pinctrl_handle_sctp_abort(struct rconn *swconn, const struct flow *ip_flow,
+ struct dp_packet *pkt_in,
+@@ -1870,7 +1836,7 @@ pinctrl_handle_sctp_abort(struct rconn *swconn, const struct flow *ip_flow,
+ return;
+ }
+
+- const struct sctp_init_chunk *sh_in_init = NULL;
++ const struct sctp_16aligned_init_chunk *sh_in_init = NULL;
+ if (sh_in_chunk->sctp_chunk_type == SCTP_CHUNK_TYPE_INIT) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+ sh_in_init = dp_packet_at(pkt_in, pkt_in->l4_ofs +
+@@ -1909,8 +1875,7 @@ pinctrl_handle_sctp_abort(struct rconn *swconn, const struct flow *ip_flow,
+ SCTP_CHUNK_HEADER_LEN);
+ }
+
+- struct sctp_header *sh = dp_packet_put_zeros(&packet, sizeof *sh);
+- dp_packet_set_l4(&packet, sh);
++ struct sctp_header *sh = dp_packet_l4(&packet);
+ sh->sctp_dst = ip_flow->tp_src;
+ sh->sctp_src = ip_flow->tp_dst;
+ put_16aligned_be32(&sh->sctp_csum, 0);
+@@ -1918,7 +1883,7 @@ pinctrl_handle_sctp_abort(struct rconn *swconn, const struct flow *ip_flow,
+ bool tag_reflected;
+ if (get_16aligned_be32(&sh_in->sctp_vtag) == 0 && sh_in_init) {
+ /* See RFC 4960 Section 8.4, item 3. */
+- put_16aligned_be32(&sh->sctp_vtag, sh_in_init->initiate_tag);
++ sh->sctp_vtag = sh_in_init->initiate_tag;
+ tag_reflected = false;
+ } else {
+ /* See RFC 4960 Section 8.4, item 8. */
+@@ -1926,7 +1891,11 @@ pinctrl_handle_sctp_abort(struct rconn *swconn, const struct flow *ip_flow,
+ tag_reflected = true;
+ }
+
+- dp_packet_put_sctp_abort(&packet, tag_reflected);
++ struct sctp_chunk_header *ah =
++ ALIGNED_CAST(struct sctp_chunk_header *, sh + 1);
++ ah->sctp_chunk_type = SCTP_CHUNK_TYPE_ABORT;
++ ah->sctp_chunk_flags = tag_reflected ? SCTP_ABORT_CHUNK_FLAG_T : 0,
++ ah->sctp_chunk_len = htons(SCTP_CHUNK_HEADER_LEN),
+
+ put_16aligned_be32(&sh->sctp_csum, crc32c((void *) sh,
+ dp_packet_l4_size(&packet)));
+@@ -2942,7 +2911,7 @@ pinctrl_handle_dns_lookup(
+ goto exit;
+ }
+
+- uint16_t query_type = ntohs(*ALIGNED_CAST(const ovs_be16 *, in_dns_data));
++ uint16_t query_type = ntohs(get_unaligned_be16((void *) in_dns_data));
+ /* Supported query types - A, AAAA, ANY and PTR */
+ if (!(query_type == DNS_QUERY_TYPE_A || query_type == DNS_QUERY_TYPE_AAAA
+ || query_type == DNS_QUERY_TYPE_ANY
+@@ -4564,16 +4533,15 @@ pinctrl_compose_ipv4(struct dp_packet *packet, struct eth_addr eth_src,
+ ovs_be32 ipv4_dst, uint8_t ip_proto, uint8_t ttl,
+ uint16_t ip_payload_len)
+ {
+- dp_packet_clear(packet);
+- packet->packet_type = htonl(PT_ETH);
+-
+- struct eth_header *eh = dp_packet_put_zeros(packet, sizeof *eh);
+- struct ip_header *nh = dp_packet_put_zeros(packet, sizeof *nh);
++ struct ip_header *nh;
++ size_t ip_tot_len = sizeof *nh + ip_payload_len;
+
+- eh->eth_dst = eth_dst;
+- eh->eth_src = eth_src;
+- eh->eth_type = htons(ETH_TYPE_IP);
+- dp_packet_set_l3(packet, nh);
++ /* Need to deal with odd-sized IP length while making sure that the
++ * packet is still aligned. Reserve 2 bytes for potential VLAN tags.
++ */
++ dp_packet_reserve(packet,
++ sizeof(struct eth_header) + ROUND_UP(ip_tot_len, 2) + 2);
++ nh = eth_compose(packet, eth_dst, eth_src, ETH_TYPE_IP, ip_tot_len);
+ nh->ip_ihl_ver = IP_IHL_VER(5, 4);
+ nh->ip_tot_len = htons(sizeof *nh + ip_payload_len);
+ nh->ip_tos = IP_DSCP_CS6;
+@@ -4584,6 +4552,7 @@ pinctrl_compose_ipv4(struct dp_packet *packet, struct eth_addr eth_src,
+
+ nh->ip_csum = 0;
+ nh->ip_csum = csum(nh, sizeof *nh);
++ dp_packet_set_l4(packet, nh + 1);
+ }
+
+ static void
+@@ -4592,23 +4561,21 @@ pinctrl_compose_ipv6(struct dp_packet *packet, struct eth_addr eth_src,
+ struct in6_addr *ipv6_dst, uint8_t ip_proto, uint8_t ttl,
+ uint16_t ip_payload_len)
+ {
+- dp_packet_clear(packet);
+- packet->packet_type = htonl(PT_ETH);
+-
+- struct eth_header *eh = dp_packet_put_zeros(packet, sizeof *eh);
+- struct ip6_hdr *nh = dp_packet_put_zeros(packet, sizeof *nh);
+-
+- eh->eth_dst = eth_dst;
+- eh->eth_src = eth_src;
+- eh->eth_type = htons(ETH_TYPE_IPV6);
+- dp_packet_set_l3(packet, nh);
+- dp_packet_set_l4(packet, nh + 1);
++ struct ip6_hdr *nh;
++ size_t ip_tot_len = sizeof *nh + ip_payload_len;
+
++ /* Need to deal with odd-sized IP length while making sure that the
++ * packet is still aligned. Reserve 2 bytes for potential VLAN tags.
++ */
++ dp_packet_reserve(packet,
++ sizeof(struct eth_header) + ROUND_UP(ip_tot_len, 2) + 2);
++ nh = eth_compose(packet, eth_dst, eth_src, ETH_TYPE_IPV6, ip_tot_len);
+ nh->ip6_vfc = 0x60;
+ nh->ip6_nxt = ip_proto;
+ nh->ip6_plen = htons(ip_payload_len);
+
+ packet_set_ipv6(packet, ipv6_src, ipv6_dst, 0, 0, ttl);
++ dp_packet_set_l4(packet, nh + 1);
+ }
+
+ /*
+@@ -5400,10 +5367,6 @@ ip_mcast_querier_send_igmp(struct rconn *swconn, struct ip_mcast_snoop *ip_ms)
+ ip_ms->cfg.query_ipv4_dst,
+ IPPROTO_IGMP, 1, sizeof(struct igmpv3_query_header));
+
+- struct igmpv3_query_header *igh =
+- dp_packet_put_zeros(&packet, sizeof *igh);
+- dp_packet_set_l4(&packet, igh);
+-
+ /* IGMP query max-response in tenths of seconds. */
+ uint8_t max_response = ip_ms->cfg.query_max_resp_s * 10;
+ uint8_t qqic = max_response;
+@@ -5449,15 +5412,10 @@ ip_mcast_querier_send_mld(struct rconn *swconn, struct ip_mcast_snoop *ip_ms)
+ IPPROTO_HOPOPTS, 1,
+ IPV6_EXT_HEADER_LEN + MLD_QUERY_HEADER_LEN);
+
+- struct ipv6_ext_header *ext_hdr =
+- dp_packet_put_zeros(&packet, IPV6_EXT_HEADER_LEN);
++ struct ipv6_ext_header *ext_hdr = dp_packet_l4(&packet);
+ packet_set_ipv6_ext_header(ext_hdr, IPPROTO_ICMPV6, 0, mld_router_alert,
+ ARRAY_SIZE(mld_router_alert));
+
+- struct mld_header *mh =
+- dp_packet_put_zeros(&packet, MLD_QUERY_HEADER_LEN);
+- dp_packet_set_l4(&packet, mh);
+-
+ /* MLD query max-response in milliseconds. */
+ uint16_t max_response = ip_ms->cfg.query_max_resp_s * 1000;
+ uint8_t qqic = ip_ms->cfg.query_max_resp_s;
+@@ -6033,6 +5991,8 @@ pinctrl_handle_put_nd_ra_opts(
+ struct dp_packet pkt_out;
+ dp_packet_init(&pkt_out, new_packet_size);
+ dp_packet_clear(&pkt_out);
++ /* Properly align after the ethernet header */
++ dp_packet_reserve(&pkt_out, 2);
+ dp_packet_prealloc_tailroom(&pkt_out, new_packet_size);
+ pkt_out_ptr = &pkt_out;
+
+@@ -6155,23 +6115,26 @@ wait_controller_event(struct ovsdb_idl_txn *ovnsb_idl_txn)
+ static bool
+ pinctrl_handle_empty_lb_backends_opts(struct ofpbuf *userdata)
+ {
+- struct controller_event_opt_header *userdata_opt;
++ struct controller_event_opt_header opt_hdr;
++ void *userdata_opt;
+ uint32_t hash = 0;
+ char *vip = NULL;
+ char *protocol = NULL;
+ char *load_balancer = NULL;
+
+ while (userdata->size) {
+- userdata_opt = ofpbuf_try_pull(userdata, sizeof *userdata_opt);
++ userdata_opt = ofpbuf_try_pull(userdata, sizeof opt_hdr);
+ if (!userdata_opt) {
+ return false;
+ }
+- size_t size = ntohs(userdata_opt->size);
++ memcpy(&opt_hdr, userdata_opt, sizeof opt_hdr);
++
++ size_t size = ntohs(opt_hdr.size);
+ char *userdata_opt_data = ofpbuf_try_pull(userdata, size);
+ if (!userdata_opt_data) {
+ return false;
+ }
+- switch (ntohs(userdata_opt->opt_code)) {
++ switch (ntohs(opt_hdr.opt_code)) {
+ case EMPTY_LB_VIP:
+ vip = xmemdup0(userdata_opt_data, size);
+ break;
+@@ -6820,8 +6783,6 @@ bfd_monitor_put_bfd_msg(struct bfd_entry *entry, struct dp_packet *packet,
+ {
+ int payload_len = sizeof(struct udp_header) + sizeof(struct bfd_msg);
+
+- /* Properly align after the ethernet header */
+- dp_packet_reserve(packet, 2);
+ if (IN6_IS_ADDR_V4MAPPED(&entry->ip_src)) {
+ ovs_be32 ip_src = in6_addr_get_mapped_ipv4(&entry->ip_src);
+ ovs_be32 ip_dst = in6_addr_get_mapped_ipv4(&entry->ip_dst);
+@@ -6833,13 +6794,13 @@ bfd_monitor_put_bfd_msg(struct bfd_entry *entry, struct dp_packet *packet,
+ MAXTTL, payload_len);
+ }
+
+- struct udp_header *udp = dp_packet_put_zeros(packet, sizeof *udp);
++ struct udp_header *udp = dp_packet_l4(packet);
+ udp->udp_len = htons(payload_len);
+ udp->udp_csum = 0;
+ udp->udp_src = htons(entry->udp_src);
+ udp->udp_dst = htons(BFD_DEST_PORT);
+
+- struct bfd_msg *msg = dp_packet_put_zeros(packet, sizeof *msg);
++ struct bfd_msg *msg = ALIGNED_CAST(struct bfd_msg *, udp + 1);
+ msg->vers_diag = (BFD_VERSION << 5);
+ msg->mult = entry->local_mult;
+ msg->length = BFD_PACKET_LEN;
+@@ -7383,7 +7344,7 @@ svc_monitor_send_tcp_health_check__(struct rconn *swconn,
+ ip4_src, in6_addr_get_mapped_ipv4(&svc_mon->ip),
+ IPPROTO_TCP, 63, TCP_HEADER_LEN);
+
+- struct tcp_header *th = dp_packet_put_zeros(&packet, sizeof *th);
++ struct tcp_header *th = dp_packet_l4(&packet);
+ dp_packet_set_l4(&packet, th);
+ th->tcp_dst = htons(svc_mon->proto_port);
+ th->tcp_src = tcp_src;
+@@ -7446,13 +7407,12 @@ svc_monitor_send_udp_health_check(struct rconn *swconn,
+ ip4_src, in6_addr_get_mapped_ipv4(&svc_mon->ip),
+ IPPROTO_UDP, 63, UDP_HEADER_LEN + 8);
+
+- struct udp_header *uh = dp_packet_put_zeros(&packet, sizeof *uh);
++ struct udp_header *uh = dp_packet_l4(&packet);
+ dp_packet_set_l4(&packet, uh);
+ uh->udp_dst = htons(svc_mon->proto_port);
+ uh->udp_src = udp_src;
+ uh->udp_len = htons(UDP_HEADER_LEN + 8);
+ uh->udp_csum = 0;
+- dp_packet_put_zeros(&packet, 8);
+
+ uint64_t ofpacts_stub[4096 / 8];
+ struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub);
+diff --git a/debian/changelog b/debian/changelog
+index 0cc5f14ac..0d8593083 100644
+--- a/debian/changelog
++++ b/debian/changelog
+@@ -1,3 +1,9 @@
++OVN (21.12.1-1) unstable; urgency=low
++ [ OVN team ]
++ * New upstream version
++
++ -- OVN team Wed, 22 Dec 2021 09:45:52 -0500
++
+ ovn (21.12.0-1) unstable; urgency=low
+
+ * New upstream version
+diff --git a/lib/actions.c b/lib/actions.c
+index da00ee349..a78d01198 100644
+--- a/lib/actions.c
++++ b/lib/actions.c
+@@ -1842,19 +1842,20 @@ encode_event_empty_lb_backends_opts(struct ofpbuf *ofpacts,
+ {
+ for (const struct ovnact_gen_option *o = event->options;
+ o < &event->options[event->n_options]; o++) {
+- struct controller_event_opt_header *hdr =
+- ofpbuf_put_uninit(ofpacts, sizeof *hdr);
++
++ /* All empty_lb_backends fields are of type 'str' */
++ ovs_assert(!strcmp(o->option->type, "str"));
++
+ const union expr_constant *c = o->value.values;
+- size_t size;
+- hdr->opt_code = htons(o->option->code);
+- if (!strcmp(o->option->type, "str")) {
+- size = strlen(c->string);
+- hdr->size = htons(size);
+- ofpbuf_put(ofpacts, c->string, size);
+- } else {
+- /* All empty_lb_backends fields are of type 'str' */
+- OVS_NOT_REACHED();
+- }
++ size_t size = strlen(c->string);
++ struct controller_event_opt_header hdr =
++ (struct controller_event_opt_header) {
++ .opt_code = htons(o->option->code),
++ .size = htons(size),
++ };
++
++ memcpy(ofpbuf_put_uninit(ofpacts, sizeof hdr), &hdr, sizeof hdr);
++ ofpbuf_put(ofpacts, c->string, size);
+ }
+ }
+
+diff --git a/lib/ovn-l7.h b/lib/ovn-l7.h
+index 9a33f5cda..256e963d9 100644
+--- a/lib/ovn-l7.h
++++ b/lib/ovn-l7.h
+@@ -502,7 +502,7 @@ struct mld_query_header {
+ ovs_be16 csum;
+ ovs_be16 max_resp;
+ ovs_be16 rsvd;
+- struct in6_addr group;
++ union ovs_16aligned_in6_addr group;
+ uint8_t srs_qrv;
+ uint8_t qqic;
+ ovs_be16 nsrcs;
+@@ -518,7 +518,9 @@ packet_set_mld_query(struct dp_packet *packet, uint16_t max_resp,
+ const struct in6_addr *group,
+ bool srs, uint8_t qrv, uint8_t qqic)
+ {
+- struct mld_query_header *mqh = dp_packet_l4(packet);
++ struct ipv6_ext_header *ext_hdr = dp_packet_l4(packet);
++ struct mld_query_header *mqh = ALIGNED_CAST(struct mld_query_header *,
++ ext_hdr + 1);
+ mqh->type = MLD_QUERY;
+ mqh->code = 0;
+ mqh->max_resp = htons(max_resp);
+diff --git a/lib/ovn-util.h b/lib/ovn-util.h
+index a923c3b65..b212c64b7 100644
+--- a/lib/ovn-util.h
++++ b/lib/ovn-util.h
+@@ -261,14 +261,16 @@ struct sctp_chunk_header {
+ BUILD_ASSERT_DECL(SCTP_CHUNK_HEADER_LEN == sizeof(struct sctp_chunk_header));
+
+ #define SCTP_INIT_CHUNK_LEN 16
+-struct sctp_init_chunk {
+- ovs_be32 initiate_tag;
+- ovs_be32 a_rwnd;
++struct sctp_16aligned_init_chunk {
++ ovs_16aligned_be32 initiate_tag;
++ ovs_16aligned_be32 a_rwnd;
+ ovs_be16 num_outbound_streams;
+ ovs_be16 num_inbound_streams;
+- ovs_be32 initial_tsn;
++ ovs_16aligned_be32 initial_tsn;
+ };
+-BUILD_ASSERT_DECL(SCTP_INIT_CHUNK_LEN == sizeof(struct sctp_init_chunk));
++BUILD_ASSERT_DECL(
++ SCTP_INIT_CHUNK_LEN == sizeof(struct sctp_16aligned_init_chunk)
++);
+
+ /* These are the only SCTP chunk types that OVN cares about.
+ * There is no need to define the other chunk types until they are
+diff --git a/tests/ovn.at b/tests/ovn.at
+index 9ec62e321..92e284e8a 100644
+--- a/tests/ovn.at
++++ b/tests/ovn.at
+@@ -9658,8 +9658,8 @@ as hv1 ovs-appctl netdev-dummy/receive vm1 $packet
+
+ # expected packet at foo2
+ packet=${dst_mac}${src_mac}8100000208004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
+-echo $packet > expected
+-OVN_CHECK_PACKETS([hv2/vm2-tx.pcap], [expected])
++echo $packet > expected2
++OVN_CHECK_PACKETS([hv2/vm2-tx.pcap], [expected2])
+
+ # Send ip packets between foo1 and bar2 (different switch, different HV)
+ src_mac="f00000010205"
+@@ -9673,8 +9673,8 @@ as hv1 ovs-appctl netdev-dummy/receive vm1 $packet
+ src_mac="000000010204"
+ dst_mac="f00000010208"
+ packet=${dst_mac}${src_mac}8100000108004500001c000000003f110100${src_ip}${dst_ip}0035111100080000
+-echo $packet >> expected
+-OVN_CHECK_PACKETS([hv2/vm2-tx.pcap], [expected])
++echo $packet >> expected2
++OVN_CHECK_PACKETS([hv2/vm2-tx.pcap], [expected2])
+
+ # Send ip packets between foo1 and bar1
+ # (different switch, loopback to same vm but different tag)
+@@ -9703,11 +9703,11 @@ as hv1 ovs-appctl netdev-dummy/receive vm1 $packet
+
+ # expected packet at bar3
+ packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
+-echo $packet > expected
+-OVN_CHECK_PACKETS([hv1/bar3-tx.pcap], [expected])
++echo $packet > expected3
++OVN_CHECK_PACKETS([hv1/bar3-tx.pcap], [expected3])
+
+ # Send ip packets between foo1 and vm1.
+-(different switch, container to the VM hosting it.)
++# (different switch, container to the VM hosting it.)
+ src_mac="f00000010205"
+ dst_mac="000000010203"
+ src_ip=`ip_to_hex 192 168 1 2`
+@@ -9723,7 +9723,7 @@ echo $packet >> expected1
+ OVN_CHECK_PACKETS([hv1/vm1-tx.pcap], [expected1])
+
+ # Send packets from vm1 to bar1.
+-(different switch, A hosting VM to a container inside it)
++# (different switch, A hosting VM to a container inside it)
+ src_mac="f00000010203"
+ dst_mac="000000010202"
+ src_ip=`ip_to_hex 172 16 1 2`
+@@ -9739,6 +9739,7 @@ echo $packet >> expected1
+ OVN_CHECK_PACKETS([hv1/vm1-tx.pcap], [expected1])
+
+ # Send broadcast packet from foo1. foo1 should not receive the same packet.
++# But foo2 should.
+ src_mac="f00000010205"
+ dst_mac="ffffffffffff"
+ src_ip=`ip_to_hex 192 168 1 2`
+@@ -9749,6 +9750,11 @@ as hv1 ovs-appctl netdev-dummy/receive vm1 $packet
+ # expected packet at VM1
+ OVN_CHECK_PACKETS([hv1/vm1-tx.pcap], [expected1])
+
++# expected packet at foo2
++packet=${dst_mac}${src_mac}8100000208004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
++echo $packet >> expected2
++OVN_CHECK_PACKETS([hv2/vm2-tx.pcap], [expected2])
++
+ # Test binding of parent and container ports.
+ ovn-nbctl lsp-set-options vm1 requested-chassis=foo
+
diff --git a/SOURCES/ovn-21.03.0.patch b/SOURCES/ovn-21.03.0.patch
deleted file mode 100644
index 57474ee..0000000
--- a/SOURCES/ovn-21.03.0.patch
+++ /dev/null
@@ -1,8444 +0,0 @@
-diff --git a/.ci/linux-prepare.sh b/.ci/linux-prepare.sh
-index 0bb0ff096..83ad3958b 100755
---- a/.ci/linux-prepare.sh
-+++ b/.ci/linux-prepare.sh
-@@ -12,5 +12,5 @@ set -ev
- git clone git://git.kernel.org/pub/scm/devel/sparse/sparse.git
- cd sparse && make -j4 HAVE_LLVM= HAVE_SQLITE= install && cd ..
-
--pip install --disable-pip-version-check --user six flake8 hacking
--pip install --user --upgrade docutils
-+pip3 install --disable-pip-version-check --user flake8 hacking sphinx pyOpenSSL
-+pip3 install --upgrade --user docutils
-diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
-index f3a53a8b6..91bd1e538 100644
---- a/.github/workflows/test.yml
-+++ b/.github/workflows/test.yml
-@@ -13,7 +13,6 @@ jobs:
- dependencies: |
- automake libtool gcc bc libjemalloc1 libjemalloc-dev \
- libssl-dev llvm-dev libelf-dev libnuma-dev libpcap-dev \
-- python3-openssl python3-pip python3-sphinx \
- selinux-policy-dev
- m32_dependecies: gcc-multilib
- CC: ${{ matrix.compiler }}
-@@ -88,11 +87,21 @@ jobs:
- if: matrix.m32 != ''
- run: sudo apt install -y ${{ env.m32_dependecies }}
-
-+ - name: update PATH
-+ run: |
-+ echo "$HOME/bin" >> $GITHUB_PATH
-+ echo "$HOME/.local/bin" >> $GITHUB_PATH
-+
-+ - name: set up python
-+ uses: actions/setup-python@v2
-+ with:
-+ python-version: '3.x'
-+
- - name: prepare
- run: ./.ci/linux-prepare.sh
-
- - name: build
-- run: PATH="$PATH:$HOME/bin" ./.ci/linux-build.sh
-+ run: ./.ci/linux-build.sh
-
- - name: copy logs on failure
- if: failure() || cancelled()
-@@ -145,10 +154,18 @@ jobs:
- ref: 'master'
- - name: install dependencies
- run: brew install automake libtool
-+ - name: update PATH
-+ run: |
-+ echo "$HOME/bin" >> $GITHUB_PATH
-+ echo "$HOME/.local/bin" >> $GITHUB_PATH
-+ - name: set up python
-+ uses: actions/setup-python@v2
-+ with:
-+ python-version: '3.x'
- - name: prepare
- run: ./.ci/osx-prepare.sh
- - name: build
-- run: PATH="$PATH:$HOME/bin" ./.ci/osx-build.sh
-+ run: ./.ci/osx-build.sh
- - name: upload logs on failure
- if: failure()
- uses: actions/upload-artifact@v2
-diff --git a/Makefile.am b/Makefile.am
-index 80247b62d..1fe730dc4 100644
---- a/Makefile.am
-+++ b/Makefile.am
-@@ -221,6 +221,7 @@ dist-hook-git: distfiles
- grep -v '\.gitattributes$$' | \
- grep -v '\.gitmodules$$' | \
- grep -v "$(submodules)" | \
-+ grep -v 'redhat' | \
- LC_ALL=C sort -u > all-gitfiles; \
- LC_ALL=C comm -1 -3 distfiles all-gitfiles > missing-distfiles; \
- if test -s missing-distfiles; then \
-@@ -332,7 +333,7 @@ check-tabs:
- @cd $(srcdir); \
- if test -e .git && (git --version) >/dev/null 2>&1 && \
- grep -ln "^ " \
-- `git ls-files | grep -v $(submodules) \
-+ `git ls-files | grep -v $(submodules) | grep -v redhat \
- | grep -v -f build-aux/initial-tab-whitelist` /dev/null \
- | $(EGREP) -v ':[ ]*/?\*'; \
- then \
-diff --git a/NEWS b/NEWS
-index 5372668bf..530c5d42f 100644
---- a/NEWS
-+++ b/NEWS
-@@ -1,3 +1,13 @@
-+Post-v21.03.0
-+-------------------------
-+ - ovn-northd-ddlog: New implementation of northd, based on DDlog. This
-+ implementation is incremental, meaning that it only recalculates what is
-+ needed for the southbound database when northbound changes occur. It is
-+ expected to scale better than the C implementation, for large deployments.
-+ (This may take testing and tuning to be effective.) This version of OVN
-+ requires DDLog 0.36.
-+ - Introduce ovn-controller incremetal processing engine statistics
-+
- OVN v21.03.0 - 12 Mar 2021
- -------------------------
- - Support ECMP multiple nexthops for reroute router policies.
-diff --git a/configure.ac b/configure.ac
-index 37b476d53..f3de6fef2 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -13,7 +13,7 @@
- # limitations under the License.
-
- AC_PREREQ(2.63)
--AC_INIT(ovn, 21.03.0, bugs@openvswitch.org)
-+AC_INIT(ovn, 21.03.1, bugs@openvswitch.org)
- AC_CONFIG_MACRO_DIR([m4])
- AC_CONFIG_AUX_DIR([build-aux])
- AC_CONFIG_HEADERS([config.h])
-diff --git a/controller/automake.mk b/controller/automake.mk
-index e664f1980..2f6c50890 100644
---- a/controller/automake.mk
-+++ b/controller/automake.mk
-@@ -10,6 +10,8 @@ controller_ovn_controller_SOURCES = \
- controller/encaps.h \
- controller/ha-chassis.c \
- controller/ha-chassis.h \
-+ controller/if-status.c \
-+ controller/if-status.h \
- controller/ip-mcast.c \
- controller/ip-mcast.h \
- controller/lflow.c \
-diff --git a/controller/binding.c b/controller/binding.c
-index 4e6c75696..31f3a210f 100644
---- a/controller/binding.c
-+++ b/controller/binding.c
-@@ -16,9 +16,9 @@
- #include
- #include "binding.h"
- #include "ha-chassis.h"
-+#include "if-status.h"
- #include "lflow.h"
- #include "lport.h"
--#include "ofctrl-seqno.h"
- #include "patch.h"
-
- #include "lib/bitmap.h"
-@@ -41,32 +41,6 @@ VLOG_DEFINE_THIS_MODULE(binding);
- */
- #define OVN_INSTALLED_EXT_ID "ovn-installed"
-
--/* Set of OVS interface IDs that have been released in the most recent
-- * processing iterations. This gets updated in release_lport() and is
-- * periodically emptied in binding_seqno_run().
-- */
--static struct sset binding_iface_released_set =
-- SSET_INITIALIZER(&binding_iface_released_set);
--
--/* Set of OVS interface IDs that have been bound in the most recent
-- * processing iterations. This gets updated in release_lport() and is
-- * periodically emptied in binding_seqno_run().
-- */
--static struct sset binding_iface_bound_set =
-- SSET_INITIALIZER(&binding_iface_bound_set);
--
--static void
--binding_iface_released_add(const char *iface_id)
--{
-- sset_add(&binding_iface_released_set, iface_id);
--}
--
--static void
--binding_iface_bound_add(const char *iface_id)
--{
-- sset_add(&binding_iface_bound_set, iface_id);
--}
--
- #define OVN_QOS_TYPE "linux-htb"
-
- struct qos_queue {
-@@ -597,6 +571,23 @@ remove_local_lport_ids(const struct sbrec_port_binding *pb,
- }
- }
-
-+/* Corresponds to each Port_Binding.type. */
-+enum en_lport_type {
-+ LP_UNKNOWN,
-+ LP_VIF,
-+ LP_CONTAINER,
-+ LP_PATCH,
-+ LP_L3GATEWAY,
-+ LP_LOCALNET,
-+ LP_LOCALPORT,
-+ LP_L2GATEWAY,
-+ LP_VTEP,
-+ LP_CHASSISREDIRECT,
-+ LP_VIRTUAL,
-+ LP_EXTERNAL,
-+ LP_REMOTE
-+};
-+
- /* Local bindings. binding.c module binds the logical port (represented by
- * Port_Binding rows) and sets the 'chassis' column when it sees the
- * OVS interface row (of type "" or "internal") with the
-@@ -608,134 +599,270 @@ remove_local_lport_ids(const struct sbrec_port_binding *pb,
- * 'struct local_binding' is used. A shash of these local bindings is
- * maintained with the 'external_ids:iface-id' as the key to the shash.
- *
-- * struct local_binding (defined in binding.h) has 3 main fields:
-- * - type
-- * - OVS interface row object
-- * - Port_Binding row object
-- *
-- * An instance of 'struct local_binding' can be one of 3 types.
-- *
-- * BT_VIF: Represent a local binding for an OVS interface of
-- * type "" or "internal" with the external_ids:iface-id
-- * set.
-- *
-- * This can be a
-- * * probable local binding - external_ids:iface-id is
-- * set, but the corresponding Port_Binding row is not
-- * created or is not visible to the local ovn-controller
-- * instance.
-- *
-- * * a local binding - external_ids:iface-id is set and
-- * which is already bound to the corresponding Port_Binding
-- * row.
-- *
-- * It maintains a list of children
-- * (of type BT_CONTAINER/BT_VIRTUAL) if any.
-- *
-- * BT_CONTAINER: Represents a local binding which has a parent of type
-- * BT_VIF. Its Port_Binding row's 'parent' column is set to
-- * its parent's Port_Binding. It shares the OVS interface row
-- * with the parent.
-- * Each ovn-controller when it sees a container Port_Binding,
-- * it creates 'struct local_binding' for the parent
-- * Port_Binding and for its even if the OVS interface row for
-- * the parent is not present.
-- *
-- * BT_VIRTUAL: Represents a local binding which has a parent of type BT_VIF.
-- * Its Port_Binding type is "virtual" and it shares the OVS
-- * interface row with the parent.
-- * Port_Binding of type "virtual" is claimed by pinctrl module
-- * when it sees the ARP packet from the parent's VIF.
-- *
-+ * struct local_binding has 3 main fields:
-+ * - name : 'external_ids:iface-id' of the OVS interface (key).
-+ * - OVS interface row object.
-+ * - List of 'binding_lport' objects with the primary lport
-+ * in the front of the list (if present).
- *
- * An object of 'struct local_binding' is created:
-- * - For each interface that has iface-id configured with the type - BT_VIF.
-+ * - For each interface that has external_ids:iface-id configured.
-+ *
-+ * - For each port binding (also referred as lport) of type 'LP_VIF'
-+ * if it is a parent lport of container lports even if there is no
-+ * corresponding OVS interface.
-+ */
-+struct local_binding {
-+ char *name;
-+ const struct ovsrec_interface *iface;
-+ struct ovs_list binding_lports;
-+};
-+
-+/* This structure represents a logical port (or port binding)
-+ * which is associated with 'struct local_binding'.
- *
-- * - For each container Port Binding (of type BT_CONTAINER) and its
-- * parent Port_Binding (of type BT_VIF), no matter if
-- * they are bound to this chassis i.e even if OVS interface row for the
-- * parent is not present.
-+ * An instance of 'struct binding_lport' is created for a logical port
-+ * - If the OVS interface's iface-id corresponds to the logical port.
-+ * - If it is a container or virtual logical port and its parent
-+ * has a 'local binding'.
- *
-- * - For each 'virtual' Port Binding (of type BT_VIRTUAL) provided its parent
-- * is bound to this chassis.
- */
-+struct binding_lport {
-+ struct ovs_list list_node; /* Node in local_binding.binding_lports. */
-
--static struct local_binding *
--local_binding_create(const char *name, const struct ovsrec_interface *iface,
-- const struct sbrec_port_binding *pb,
-- enum local_binding_type type)
-+ char *name;
-+ const struct sbrec_port_binding *pb;
-+ struct local_binding *lbinding;
-+ enum en_lport_type type;
-+};
-+
-+static struct local_binding *local_binding_create(
-+ const char *name, const struct ovsrec_interface *);
-+static void local_binding_add(struct shash *local_bindings,
-+ struct local_binding *);
-+static struct local_binding *local_binding_find(
-+ struct shash *local_bindings, const char *name);
-+static void local_binding_destroy(struct local_binding *,
-+ struct shash *binding_lports);
-+static void local_binding_delete(struct local_binding *,
-+ struct shash *local_bindings,
-+ struct shash *binding_lports,
-+ struct if_status_mgr *if_mgr);
-+static struct binding_lport *local_binding_add_lport(
-+ struct shash *binding_lports,
-+ struct local_binding *,
-+ const struct sbrec_port_binding *,
-+ enum en_lport_type);
-+static struct binding_lport *local_binding_get_primary_lport(
-+ struct local_binding *);
-+static bool local_binding_handle_stale_binding_lports(
-+ struct local_binding *lbinding, struct binding_ctx_in *b_ctx_in,
-+ struct binding_ctx_out *b_ctx_out, struct hmap *qos_map);
-+
-+static struct binding_lport *binding_lport_create(
-+ const struct sbrec_port_binding *,
-+ struct local_binding *, enum en_lport_type);
-+static void binding_lport_destroy(struct binding_lport *);
-+static void binding_lport_delete(struct shash *binding_lports,
-+ struct binding_lport *);
-+static void binding_lport_add(struct shash *binding_lports,
-+ struct binding_lport *);
-+static void binding_lport_set_up(struct binding_lport *, bool sb_readonly);
-+static void binding_lport_set_down(struct binding_lport *, bool sb_readonly);
-+static struct binding_lport *binding_lport_find(
-+ struct shash *binding_lports, const char *lport_name);
-+static const struct sbrec_port_binding *binding_lport_get_parent_pb(
-+ struct binding_lport *b_lprt);
-+static struct binding_lport *binding_lport_check_and_cleanup(
-+ struct binding_lport *, struct shash *b_lports);
-+
-+static char *get_lport_type_str(enum en_lport_type lport_type);
-+
-+void
-+local_binding_data_init(struct local_binding_data *lbinding_data)
- {
-- struct local_binding *lbinding = xzalloc(sizeof *lbinding);
-- lbinding->name = xstrdup(name);
-- lbinding->type = type;
-- lbinding->pb = pb;
-- lbinding->iface = iface;
-- shash_init(&lbinding->children);
-- return lbinding;
-+ shash_init(&lbinding_data->bindings);
-+ shash_init(&lbinding_data->lports);
- }
-
--static void
--local_binding_add(struct shash *local_bindings, struct local_binding *lbinding)
-+void
-+local_binding_data_destroy(struct local_binding_data *lbinding_data)
- {
-- shash_add(local_bindings, lbinding->name, lbinding);
-+ struct shash_node *node, *next;
-+
-+ SHASH_FOR_EACH_SAFE (node, next, &lbinding_data->lports) {
-+ struct binding_lport *b_lport = node->data;
-+ binding_lport_destroy(b_lport);
-+ shash_delete(&lbinding_data->lports, node);
-+ }
-+
-+ SHASH_FOR_EACH_SAFE (node, next, &lbinding_data->bindings) {
-+ struct local_binding *lbinding = node->data;
-+ local_binding_destroy(lbinding, &lbinding_data->lports);
-+ shash_delete(&lbinding_data->bindings, node);
-+ }
-+
-+ shash_destroy(&lbinding_data->lports);
-+ shash_destroy(&lbinding_data->bindings);
- }
-
--static void
--local_binding_destroy(struct local_binding *lbinding)
-+const struct sbrec_port_binding *
-+local_binding_get_primary_pb(struct shash *local_bindings, const char *pb_name)
- {
-- local_bindings_destroy(&lbinding->children);
-+ struct local_binding *lbinding =
-+ local_binding_find(local_bindings, pb_name);
-+ struct binding_lport *b_lport = local_binding_get_primary_lport(lbinding);
-
-- free(lbinding->name);
-- free(lbinding);
-+ return b_lport ? b_lport->pb : NULL;
- }
-
--void
--local_bindings_init(struct shash *local_bindings)
-+bool
-+local_binding_is_up(struct shash *local_bindings, const char *pb_name)
- {
-- shash_init(local_bindings);
-+ struct local_binding *lbinding =
-+ local_binding_find(local_bindings, pb_name);
-+ struct binding_lport *b_lport = local_binding_get_primary_lport(lbinding);
-+ if (lbinding && b_lport && lbinding->iface) {
-+ if (b_lport->pb->n_up && !b_lport->pb->up[0]) {
-+ return false;
-+ }
-+ return smap_get_bool(&lbinding->iface->external_ids,
-+ OVN_INSTALLED_EXT_ID, false);
-+ }
-+ return false;
- }
-
--void
--local_bindings_destroy(struct shash *local_bindings)
-+bool
-+local_binding_is_down(struct shash *local_bindings, const char *pb_name)
- {
-- struct shash_node *node, *next;
-- SHASH_FOR_EACH_SAFE (node, next, local_bindings) {
-- struct local_binding *lbinding = node->data;
-- local_binding_destroy(lbinding);
-- shash_delete(local_bindings, node);
-+ struct local_binding *lbinding =
-+ local_binding_find(local_bindings, pb_name);
-+
-+ struct binding_lport *b_lport = local_binding_get_primary_lport(lbinding);
-+
-+ if (!lbinding) {
-+ return true;
- }
-
-- shash_destroy(local_bindings);
--}
-+ if (lbinding->iface && smap_get_bool(&lbinding->iface->external_ids,
-+ OVN_INSTALLED_EXT_ID, false)) {
-+ return false;
-+ }
-
--static
--void local_binding_delete(struct shash *local_bindings,
-- struct local_binding *lbinding)
--{
-- shash_find_and_delete(local_bindings, lbinding->name);
-- local_binding_destroy(lbinding);
-+ if (b_lport && b_lport->pb->n_up && b_lport->pb->up[0]) {
-+ return false;
-+ }
-+
-+ return true;
- }
-
--static void
--local_binding_add_child(struct local_binding *lbinding,
-- struct local_binding *child)
-+void
-+local_binding_set_up(struct shash *local_bindings, const char *pb_name,
-+ bool sb_readonly, bool ovs_readonly)
- {
-- local_binding_add(&lbinding->children, child);
-- child->parent = lbinding;
-+ struct local_binding *lbinding =
-+ local_binding_find(local_bindings, pb_name);
-+ struct binding_lport *b_lport = local_binding_get_primary_lport(lbinding);
-+
-+ if (!ovs_readonly && lbinding && lbinding->iface
-+ && !smap_get_bool(&lbinding->iface->external_ids,
-+ OVN_INSTALLED_EXT_ID, false)) {
-+ ovsrec_interface_update_external_ids_setkey(lbinding->iface,
-+ OVN_INSTALLED_EXT_ID,
-+ "true");
-+ }
-+
-+ if (!sb_readonly && lbinding && b_lport && b_lport->pb->n_up) {
-+ binding_lport_set_up(b_lport, sb_readonly);
-+ LIST_FOR_EACH (b_lport, list_node, &lbinding->binding_lports) {
-+ binding_lport_set_up(b_lport, sb_readonly);
-+ }
-+ }
- }
-
--static struct local_binding *
--local_binding_find_child(struct local_binding *lbinding,
-- const char *child_name)
-+void
-+local_binding_set_down(struct shash *local_bindings, const char *pb_name,
-+ bool sb_readonly, bool ovs_readonly)
- {
-- return local_binding_find(&lbinding->children, child_name);
-+ struct local_binding *lbinding =
-+ local_binding_find(local_bindings, pb_name);
-+ struct binding_lport *b_lport = local_binding_get_primary_lport(lbinding);
-+
-+ if (!ovs_readonly && lbinding && lbinding->iface
-+ && smap_get_bool(&lbinding->iface->external_ids,
-+ OVN_INSTALLED_EXT_ID, false)) {
-+ ovsrec_interface_update_external_ids_delkey(lbinding->iface,
-+ OVN_INSTALLED_EXT_ID);
-+ }
-+
-+ if (!sb_readonly && b_lport && b_lport->pb->n_up) {
-+ binding_lport_set_down(b_lport, sb_readonly);
-+ LIST_FOR_EACH (b_lport, list_node, &lbinding->binding_lports) {
-+ binding_lport_set_down(b_lport, sb_readonly);
-+ }
-+ }
- }
-
--static void
--local_binding_delete_child(struct local_binding *lbinding,
-- struct local_binding *child)
-+void
-+binding_dump_local_bindings(struct local_binding_data *lbinding_data,
-+ struct ds *out_data)
- {
-- shash_find_and_delete(&lbinding->children, child->name);
-+ const struct shash_node **nodes;
-+
-+ nodes = shash_sort(&lbinding_data->bindings);
-+ size_t n = shash_count(&lbinding_data->bindings);
-+
-+ ds_put_cstr(out_data, "Local bindings:\n");
-+ for (size_t i = 0; i < n; i++) {
-+ const struct shash_node *node = nodes[i];
-+ struct local_binding *lbinding = node->data;
-+ size_t num_lports = ovs_list_size(&lbinding->binding_lports);
-+ ds_put_format(out_data, "name: [%s], OVS interface name : [%s], "
-+ "num binding lports : [%"PRIuSIZE"]\n",
-+ lbinding->name,
-+ lbinding->iface ? lbinding->iface->name : "NULL",
-+ num_lports);
-+
-+ if (num_lports) {
-+ struct shash child_lports = SHASH_INITIALIZER(&child_lports);
-+ struct binding_lport *primary_lport = NULL;
-+ struct binding_lport *b_lport;
-+ bool first_elem = true;
-+
-+ LIST_FOR_EACH (b_lport, list_node, &lbinding->binding_lports) {
-+ if (first_elem && b_lport->type == LP_VIF) {
-+ primary_lport = b_lport;
-+ } else {
-+ shash_add(&child_lports, b_lport->name, b_lport);
-+ }
-+ first_elem = false;
-+ }
-+
-+ if (primary_lport) {
-+ ds_put_format(out_data, "primary lport : [%s]\n",
-+ primary_lport->name);
-+ } else {
-+ ds_put_format(out_data, "no primary lport\n");
-+ }
-+
-+ if (!shash_is_empty(&child_lports)) {
-+ const struct shash_node **c_nodes =
-+ shash_sort(&child_lports);
-+ for (size_t j = 0; j < shash_count(&child_lports); j++) {
-+ b_lport = c_nodes[j]->data;
-+ ds_put_format(out_data, "child lport[%"PRIuSIZE"] : [%s], "
-+ "type : [%s]\n", j + 1, b_lport->name,
-+ get_lport_type_str(b_lport->type));
-+ }
-+ free(c_nodes);
-+ }
-+ shash_destroy(&child_lports);
-+ }
-+
-+ ds_put_cstr(out_data, "----------------------------------------\n");
-+ }
-+
-+ free(nodes);
- }
-
- static bool
-@@ -744,12 +871,6 @@ is_lport_vif(const struct sbrec_port_binding *pb)
- return !pb->type[0];
- }
-
--static bool
--is_lport_container(const struct sbrec_port_binding *pb)
--{
-- return is_lport_vif(pb) && pb->parent_port && pb->parent_port[0];
--}
--
- static struct tracked_binding_datapath *
- tracked_binding_datapath_create(const struct sbrec_datapath_binding *dp,
- bool is_new,
-@@ -818,26 +939,13 @@ binding_tracked_dp_destroy(struct hmap *tracked_datapaths)
- hmap_destroy(tracked_datapaths);
- }
-
--/* Corresponds to each Port_Binding.type. */
--enum en_lport_type {
-- LP_UNKNOWN,
-- LP_VIF,
-- LP_PATCH,
-- LP_L3GATEWAY,
-- LP_LOCALNET,
-- LP_LOCALPORT,
-- LP_L2GATEWAY,
-- LP_VTEP,
-- LP_CHASSISREDIRECT,
-- LP_VIRTUAL,
-- LP_EXTERNAL,
-- LP_REMOTE
--};
--
- static enum en_lport_type
- get_lport_type(const struct sbrec_port_binding *pb)
- {
- if (is_lport_vif(pb)) {
-+ if (pb->parent_port && pb->parent_port[0]) {
-+ return LP_CONTAINER;
-+ }
- return LP_VIF;
- } else if (!strcmp(pb->type, "patch")) {
- return LP_PATCH;
-@@ -864,6 +972,41 @@ get_lport_type(const struct sbrec_port_binding *pb)
- return LP_UNKNOWN;
- }
-
-+static char *
-+get_lport_type_str(enum en_lport_type lport_type)
-+{
-+ switch (lport_type) {
-+ case LP_VIF:
-+ return "VIF";
-+ case LP_CONTAINER:
-+ return "CONTAINER";
-+ case LP_VIRTUAL:
-+ return "VIRTUAL";
-+ case LP_PATCH:
-+ return "PATCH";
-+ case LP_CHASSISREDIRECT:
-+ return "CHASSISREDIRECT";
-+ case LP_L3GATEWAY:
-+ return "L3GATEWAT";
-+ case LP_LOCALNET:
-+ return "PATCH";
-+ case LP_LOCALPORT:
-+ return "LOCALPORT";
-+ case LP_L2GATEWAY:
-+ return "L2GATEWAY";
-+ case LP_EXTERNAL:
-+ return "EXTERNAL";
-+ case LP_REMOTE:
-+ return "REMOTE";
-+ case LP_VTEP:
-+ return "VTEP";
-+ case LP_UNKNOWN:
-+ return "UNKNOWN";
-+ }
-+
-+ OVS_NOT_REACHED();
-+}
-+
- /* For newly claimed ports, if 'notify_up' is 'false':
- * - set the 'pb.up' field to true if 'pb' has no 'parent_pb'.
- * - set the 'pb.up' field to true if 'parent_pb.up' is 'true' (e.g., for
-@@ -880,7 +1023,7 @@ static void
- claimed_lport_set_up(const struct sbrec_port_binding *pb,
- const struct sbrec_port_binding *parent_pb,
- const struct sbrec_chassis *chassis_rec,
-- bool notify_up)
-+ bool notify_up, struct if_status_mgr *if_mgr)
- {
- if (!notify_up) {
- bool up = true;
-@@ -891,7 +1034,7 @@ claimed_lport_set_up(const struct sbrec_port_binding *pb,
- }
-
- if (pb->chassis != chassis_rec || (pb->n_up && !pb->up[0])) {
-- binding_iface_bound_add(pb->logical_port);
-+ if_status_mgr_claim_iface(if_mgr, pb->logical_port);
- }
- }
-
-@@ -904,10 +1047,11 @@ claim_lport(const struct sbrec_port_binding *pb,
- const struct sbrec_chassis *chassis_rec,
- const struct ovsrec_interface *iface_rec,
- bool sb_readonly, bool notify_up,
-- struct hmap *tracked_datapaths)
-+ struct hmap *tracked_datapaths,
-+ struct if_status_mgr *if_mgr)
- {
- if (!sb_readonly) {
-- claimed_lport_set_up(pb, parent_pb, chassis_rec, notify_up);
-+ claimed_lport_set_up(pb, parent_pb, chassis_rec, notify_up, if_mgr);
- }
-
- if (pb->chassis != chassis_rec) {
-@@ -955,7 +1099,7 @@ claim_lport(const struct sbrec_port_binding *pb,
- */
- static bool
- release_lport(const struct sbrec_port_binding *pb, bool sb_readonly,
-- struct hmap *tracked_datapaths)
-+ struct hmap *tracked_datapaths, struct if_status_mgr *if_mgr)
- {
- if (pb->encap) {
- if (sb_readonly) {
-@@ -978,12 +1122,8 @@ release_lport(const struct sbrec_port_binding *pb, bool sb_readonly,
- sbrec_port_binding_set_virtual_parent(pb, NULL);
- }
-
-- if (pb->n_up) {
-- bool up = false;
-- sbrec_port_binding_set_up(pb, &up, 1);
-- }
- update_lport_tracking(pb, tracked_datapaths);
-- binding_iface_released_add(pb->logical_port);
-+ if_status_mgr_release_iface(if_mgr, pb->logical_port);
- VLOG_INFO("Releasing lport %s from this chassis.", pb->logical_port);
- return true;
- }
-@@ -991,14 +1131,15 @@ release_lport(const struct sbrec_port_binding *pb, bool sb_readonly,
- static bool
- is_lbinding_set(struct local_binding *lbinding)
- {
-- return lbinding && lbinding->pb && lbinding->iface;
-+ return lbinding && lbinding->iface;
- }
-
- static bool
--is_lbinding_this_chassis(struct local_binding *lbinding,
-- const struct sbrec_chassis *chassis)
-+is_binding_lport_this_chassis(struct binding_lport *b_lport,
-+ const struct sbrec_chassis *chassis)
- {
-- return lbinding && lbinding->pb && lbinding->pb->chassis == chassis;
-+ return (b_lport && b_lport->pb && chassis &&
-+ b_lport->pb->chassis == chassis);
- }
-
- static bool
-@@ -1010,15 +1151,14 @@ can_bind_on_this_chassis(const struct sbrec_chassis *chassis_rec,
- || !strcmp(requested_chassis, chassis_rec->hostname);
- }
-
--/* Returns 'true' if the 'lbinding' has children of type BT_CONTAINER,
-+/* Returns 'true' if the 'lbinding' has binding lports of type LP_CONTAINER,
- * 'false' otherwise. */
- static bool
- is_lbinding_container_parent(struct local_binding *lbinding)
- {
-- struct shash_node *node;
-- SHASH_FOR_EACH (node, &lbinding->children) {
-- struct local_binding *l = node->data;
-- if (l->type == BT_CONTAINER) {
-+ struct binding_lport *b_lport;
-+ LIST_FOR_EACH (b_lport, list_node, &lbinding->binding_lports) {
-+ if (b_lport->type == LP_CONTAINER) {
- return true;
- }
- }
-@@ -1027,66 +1167,44 @@ is_lbinding_container_parent(struct local_binding *lbinding)
- }
-
- static bool
--release_local_binding_children(const struct sbrec_chassis *chassis_rec,
-- struct local_binding *lbinding,
-- bool sb_readonly,
-- struct hmap *tracked_dp_bindings)
--{
-- struct shash_node *node;
-- SHASH_FOR_EACH (node, &lbinding->children) {
-- struct local_binding *l = node->data;
-- if (is_lbinding_this_chassis(l, chassis_rec)) {
-- if (!release_lport(l->pb, sb_readonly, tracked_dp_bindings)) {
-- return false;
-- }
-+release_binding_lport(const struct sbrec_chassis *chassis_rec,
-+ struct binding_lport *b_lport, bool sb_readonly,
-+ struct binding_ctx_out *b_ctx_out)
-+{
-+ if (is_binding_lport_this_chassis(b_lport, chassis_rec)) {
-+ remove_local_lport_ids(b_lport->pb, b_ctx_out);
-+ if (!release_lport(b_lport->pb, sb_readonly,
-+ b_ctx_out->tracked_dp_bindings,
-+ b_ctx_out->if_mgr)) {
-+ return false;
- }
--
-- /* Clear the local bindings' 'iface'. */
-- l->iface = NULL;
-+ binding_lport_set_down(b_lport, sb_readonly);
- }
-
- return true;
- }
-
--static bool
--release_local_binding(const struct sbrec_chassis *chassis_rec,
-- struct local_binding *lbinding, bool sb_readonly,
-- struct hmap *tracked_dp_bindings)
--{
-- if (!release_local_binding_children(chassis_rec, lbinding,
-- sb_readonly, tracked_dp_bindings)) {
-- return false;
-- }
--
-- bool retval = true;
-- if (is_lbinding_this_chassis(lbinding, chassis_rec)) {
-- retval = release_lport(lbinding->pb, sb_readonly, tracked_dp_bindings);
-- }
--
-- lbinding->pb = NULL;
-- lbinding->iface = NULL;
-- return retval;
--}
--
- static bool
- consider_vif_lport_(const struct sbrec_port_binding *pb,
- bool can_bind, const char *vif_chassis,
- struct binding_ctx_in *b_ctx_in,
- struct binding_ctx_out *b_ctx_out,
-- struct local_binding *lbinding,
-+ struct binding_lport *b_lport,
- struct hmap *qos_map)
- {
-- bool lbinding_set = is_lbinding_set(lbinding);
-+ bool lbinding_set = b_lport && is_lbinding_set(b_lport->lbinding);
-+
- if (lbinding_set) {
- if (can_bind) {
- /* We can claim the lport. */
- const struct sbrec_port_binding *parent_pb =
-- lbinding->parent ? lbinding->parent->pb : NULL;
-+ binding_lport_get_parent_pb(b_lport);
-
- if (!claim_lport(pb, parent_pb, b_ctx_in->chassis_rec,
-- lbinding->iface, !b_ctx_in->ovnsb_idl_txn,
-- !lbinding->parent,
-- b_ctx_out->tracked_dp_bindings)){
-+ b_lport->lbinding->iface,
-+ !b_ctx_in->ovnsb_idl_txn,
-+ !parent_pb, b_ctx_out->tracked_dp_bindings,
-+ b_ctx_out->if_mgr)){
- return false;
- }
-
-@@ -1098,7 +1216,7 @@ consider_vif_lport_(const struct sbrec_port_binding *pb,
- b_ctx_out->tracked_dp_bindings);
- update_local_lport_ids(pb, b_ctx_out);
- update_local_lports(pb->logical_port, b_ctx_out);
-- if (lbinding->iface && qos_map && b_ctx_in->ovs_idl_txn) {
-+ if (b_lport->lbinding->iface && qos_map && b_ctx_in->ovs_idl_txn) {
- get_qos_params(pb, qos_map);
- }
- } else {
-@@ -1117,7 +1235,8 @@ consider_vif_lport_(const struct sbrec_port_binding *pb,
- /* Release the lport if there is no lbinding. */
- if (!lbinding_set || !can_bind) {
- return release_lport(pb, !b_ctx_in->ovnsb_idl_txn,
-- b_ctx_out->tracked_dp_bindings);
-+ b_ctx_out->tracked_dp_bindings,
-+ b_ctx_out->if_mgr);
- }
- }
-
-@@ -1136,16 +1255,19 @@ consider_vif_lport(const struct sbrec_port_binding *pb,
- vif_chassis);
-
- if (!lbinding) {
-- lbinding = local_binding_find(b_ctx_out->local_bindings,
-+ lbinding = local_binding_find(&b_ctx_out->lbinding_data->bindings,
- pb->logical_port);
- }
-
-+ struct binding_lport *b_lport = NULL;
- if (lbinding) {
-- lbinding->pb = pb;
-+ struct shash *binding_lports =
-+ &b_ctx_out->lbinding_data->lports;
-+ b_lport = local_binding_add_lport(binding_lports, lbinding, pb, LP_VIF);
- }
-
- return consider_vif_lport_(pb, can_bind, vif_chassis, b_ctx_in,
-- b_ctx_out, lbinding, qos_map);
-+ b_ctx_out, b_lport, qos_map);
- }
-
- static bool
-@@ -1154,9 +1276,9 @@ consider_container_lport(const struct sbrec_port_binding *pb,
- struct binding_ctx_out *b_ctx_out,
- struct hmap *qos_map)
- {
-+ struct shash *local_bindings = &b_ctx_out->lbinding_data->bindings;
- struct local_binding *parent_lbinding;
-- parent_lbinding = local_binding_find(b_ctx_out->local_bindings,
-- pb->parent_port);
-+ parent_lbinding = local_binding_find(local_bindings, pb->parent_port);
-
- if (!parent_lbinding) {
- /* There is no local_binding for parent port. Create it
-@@ -1171,54 +1293,62 @@ consider_container_lport(const struct sbrec_port_binding *pb,
- * we want the these container ports also be claimed by the
- * chassis.
- * */
-- parent_lbinding = local_binding_create(pb->parent_port, NULL, NULL,
-- BT_VIF);
-- local_binding_add(b_ctx_out->local_bindings, parent_lbinding);
-+ parent_lbinding = local_binding_create(pb->parent_port, NULL);
-+ local_binding_add(local_bindings, parent_lbinding);
- }
-
-- struct local_binding *container_lbinding =
-- local_binding_find_child(parent_lbinding, pb->logical_port);
-+ struct shash *binding_lports = &b_ctx_out->lbinding_data->lports;
-+ struct binding_lport *container_b_lport =
-+ local_binding_add_lport(binding_lports, parent_lbinding, pb,
-+ LP_CONTAINER);
-
-- if (!container_lbinding) {
-- container_lbinding = local_binding_create(pb->logical_port,
-- parent_lbinding->iface,
-- pb, BT_CONTAINER);
-- local_binding_add_child(parent_lbinding, container_lbinding);
-- } else {
-- ovs_assert(container_lbinding->type == BT_CONTAINER);
-- container_lbinding->pb = pb;
-- container_lbinding->iface = parent_lbinding->iface;
-- }
-+ struct binding_lport *parent_b_lport =
-+ binding_lport_find(binding_lports, pb->parent_port);
-
-- if (!parent_lbinding->pb) {
-- parent_lbinding->pb = lport_lookup_by_name(
-+ bool can_consider_c_lport = true;
-+ if (!parent_b_lport || !parent_b_lport->pb) {
-+ const struct sbrec_port_binding *parent_pb = lport_lookup_by_name(
- b_ctx_in->sbrec_port_binding_by_name, pb->parent_port);
-
-- if (parent_lbinding->pb) {
-+ if (parent_pb && get_lport_type(parent_pb) == LP_VIF) {
- /* Its possible that the parent lport is not considered yet.
- * So call consider_vif_lport() to process it first. */
-- consider_vif_lport(parent_lbinding->pb, b_ctx_in, b_ctx_out,
-+ consider_vif_lport(parent_pb, b_ctx_in, b_ctx_out,
- parent_lbinding, qos_map);
-+ parent_b_lport = binding_lport_find(binding_lports,
-+ pb->parent_port);
- } else {
-- /* The parent lport doesn't exist. Call release_lport() to
-- * release the container lport, if it was bound earlier. */
-- if (is_lbinding_this_chassis(container_lbinding,
-- b_ctx_in->chassis_rec)) {
-- return release_lport(pb, !b_ctx_in->ovnsb_idl_txn,
-- b_ctx_out->tracked_dp_bindings);
-- }
-+ /* The parent lport doesn't exist. Cannot consider the container
-+ * lport for binding. */
-+ can_consider_c_lport = false;
-+ }
-+ }
-
-- return true;
-+ if (parent_b_lport && parent_b_lport->type != LP_VIF) {
-+ can_consider_c_lport = false;
-+ }
-+
-+ if (!can_consider_c_lport) {
-+ /* Call release_lport() to release the container lport,
-+ * if it was bound earlier. */
-+ if (is_binding_lport_this_chassis(container_b_lport,
-+ b_ctx_in->chassis_rec)) {
-+ return release_lport(pb, !b_ctx_in->ovnsb_idl_txn,
-+ b_ctx_out->tracked_dp_bindings,
-+ b_ctx_out->if_mgr);
- }
-+
-+ return true;
- }
-
-- const char *vif_chassis = smap_get(&parent_lbinding->pb->options,
-+ ovs_assert(parent_b_lport && parent_b_lport->pb);
-+ const char *vif_chassis = smap_get(&parent_b_lport->pb->options,
- "requested-chassis");
- bool can_bind = can_bind_on_this_chassis(b_ctx_in->chassis_rec,
- vif_chassis);
-
- return consider_vif_lport_(pb, can_bind, vif_chassis, b_ctx_in, b_ctx_out,
-- container_lbinding, qos_map);
-+ container_b_lport, qos_map);
- }
-
- static bool
-@@ -1227,46 +1357,58 @@ consider_virtual_lport(const struct sbrec_port_binding *pb,
- struct binding_ctx_out *b_ctx_out,
- struct hmap *qos_map)
- {
-- struct local_binding * parent_lbinding =
-- pb->virtual_parent ? local_binding_find(b_ctx_out->local_bindings,
-+ struct shash *local_bindings = &b_ctx_out->lbinding_data->bindings;
-+ struct local_binding *parent_lbinding =
-+ pb->virtual_parent ? local_binding_find(local_bindings,
- pb->virtual_parent)
- : NULL;
-
-- if (parent_lbinding && !parent_lbinding->pb) {
-- parent_lbinding->pb = lport_lookup_by_name(
-- b_ctx_in->sbrec_port_binding_by_name, pb->virtual_parent);
--
-- if (parent_lbinding->pb) {
-- /* Its possible that the parent lport is not considered yet.
-- * So call consider_vif_lport() to process it first. */
-- consider_vif_lport(parent_lbinding->pb, b_ctx_in, b_ctx_out,
-- parent_lbinding, qos_map);
-- }
-- }
--
-+ struct binding_lport *virtual_b_lport = NULL;
- /* Unlike container lports, we don't have to create parent_lbinding if
- * it is NULL. This is because, if parent_lbinding is not present, it
- * means the virtual port can't bind in this chassis.
- * Note: pinctrl module binds the virtual lport when it sees ARP
- * packet from the parent lport. */
-- struct local_binding *virtual_lbinding = NULL;
-- if (is_lbinding_this_chassis(parent_lbinding, b_ctx_in->chassis_rec)) {
-- virtual_lbinding =
-- local_binding_find_child(parent_lbinding, pb->logical_port);
-- if (!virtual_lbinding) {
-- virtual_lbinding = local_binding_create(pb->logical_port,
-- parent_lbinding->iface,
-- pb, BT_VIRTUAL);
-- local_binding_add_child(parent_lbinding, virtual_lbinding);
-- } else {
-- ovs_assert(virtual_lbinding->type == BT_VIRTUAL);
-- virtual_lbinding->pb = pb;
-- virtual_lbinding->iface = parent_lbinding->iface;
-+ if (parent_lbinding) {
-+ struct shash *binding_lports = &b_ctx_out->lbinding_data->lports;
-+
-+ struct binding_lport *parent_b_lport =
-+ binding_lport_find(binding_lports, pb->virtual_parent);
-+
-+ if (!parent_b_lport || !parent_b_lport->pb) {
-+ const struct sbrec_port_binding *parent_pb = lport_lookup_by_name(
-+ b_ctx_in->sbrec_port_binding_by_name, pb->virtual_parent);
-+
-+ if (parent_pb && get_lport_type(parent_pb) == LP_VIF) {
-+ /* Its possible that the parent lport is not considered yet.
-+ * So call consider_vif_lport() to process it first. */
-+ consider_vif_lport(parent_pb, b_ctx_in, b_ctx_out,
-+ parent_lbinding, qos_map);
-+ }
-+ }
-+
-+ parent_b_lport = local_binding_get_primary_lport(parent_lbinding);
-+ if (is_binding_lport_this_chassis(parent_b_lport,
-+ b_ctx_in->chassis_rec)) {
-+ virtual_b_lport =
-+ local_binding_add_lport(binding_lports, parent_lbinding, pb,
-+ LP_VIRTUAL);
- }
- }
-
-- return consider_vif_lport_(pb, true, NULL, b_ctx_in, b_ctx_out,
-- virtual_lbinding, qos_map);
-+ if (!consider_vif_lport_(pb, true, NULL, b_ctx_in, b_ctx_out,
-+ virtual_b_lport, qos_map)) {
-+ return false;
-+ }
-+
-+ /* If the virtual lport is not bound to this chassis, then remove
-+ * its entry from the local_lport_ids if present. This is required
-+ * when a virtual port moves from one chassis to other.*/
-+ if (!virtual_b_lport) {
-+ remove_local_lport_ids(pb, b_ctx_out);
-+ }
-+
-+ return true;
- }
-
- /* Considers either claiming the lport or releasing the lport
-@@ -1291,10 +1433,12 @@ consider_nonvif_lport_(const struct sbrec_port_binding *pb,
- update_local_lport_ids(pb, b_ctx_out);
- return claim_lport(pb, NULL, b_ctx_in->chassis_rec, NULL,
- !b_ctx_in->ovnsb_idl_txn, false,
-- b_ctx_out->tracked_dp_bindings);
-+ b_ctx_out->tracked_dp_bindings,
-+ b_ctx_out->if_mgr);
- } else if (pb->chassis == b_ctx_in->chassis_rec) {
- return release_lport(pb, !b_ctx_in->ovnsb_idl_txn,
-- b_ctx_out->tracked_dp_bindings);
-+ b_ctx_out->tracked_dp_bindings,
-+ b_ctx_out->if_mgr);
- }
-
- return true;
-@@ -1407,6 +1551,8 @@ build_local_bindings(struct binding_ctx_in *b_ctx_in,
- continue;
- }
-
-+ struct shash *local_bindings =
-+ &b_ctx_out->lbinding_data->bindings;
- for (j = 0; j < port_rec->n_interfaces; j++) {
- const struct ovsrec_interface *iface_rec;
-
-@@ -1416,11 +1562,10 @@ build_local_bindings(struct binding_ctx_in *b_ctx_in,
-
- if (iface_id && ofport > 0) {
- struct local_binding *lbinding =
-- local_binding_find(b_ctx_out->local_bindings, iface_id);
-+ local_binding_find(local_bindings, iface_id);
- if (!lbinding) {
-- lbinding = local_binding_create(iface_id, iface_rec, NULL,
-- BT_VIF);
-- local_binding_add(b_ctx_out->local_bindings, lbinding);
-+ lbinding = local_binding_create(iface_id, iface_rec);
-+ local_binding_add(local_bindings, lbinding);
- } else {
- static struct vlog_rate_limit rl =
- VLOG_RATE_LIMIT_INIT(1, 5);
-@@ -1431,7 +1576,6 @@ build_local_bindings(struct binding_ctx_in *b_ctx_in,
- "configuration on interface [%s]",
- lbinding->iface->name, iface_rec->name,
- iface_rec->name);
-- ovs_assert(lbinding->type == BT_VIF);
- }
-
- update_local_lports(iface_id, b_ctx_out);
-@@ -1494,11 +1638,11 @@ binding_run(struct binding_ctx_in *b_ctx_in, struct binding_ctx_out *b_ctx_out)
- break;
-
- case LP_VIF:
-- if (is_lport_container(pb)) {
-- consider_container_lport(pb, b_ctx_in, b_ctx_out, qos_map_ptr);
-- } else {
-- consider_vif_lport(pb, b_ctx_in, b_ctx_out, NULL, qos_map_ptr);
-- }
-+ consider_vif_lport(pb, b_ctx_in, b_ctx_out, NULL, qos_map_ptr);
-+ break;
-+
-+ case LP_CONTAINER:
-+ consider_container_lport(pb, b_ctx_in, b_ctx_out, qos_map_ptr);
- break;
-
- case LP_VIRTUAL:
-@@ -1799,39 +1943,44 @@ consider_iface_claim(const struct ovsrec_interface *iface_rec,
- update_local_lports(iface_id, b_ctx_out);
- smap_replace(b_ctx_out->local_iface_ids, iface_rec->name, iface_id);
-
-- struct local_binding *lbinding =
-- local_binding_find(b_ctx_out->local_bindings, iface_id);
-+ struct shash *local_bindings = &b_ctx_out->lbinding_data->bindings;
-+ struct shash *binding_lports = &b_ctx_out->lbinding_data->lports;
-+ struct local_binding *lbinding = local_binding_find(local_bindings,
-+ iface_id);
-
- if (!lbinding) {
-- lbinding = local_binding_create(iface_id, iface_rec, NULL, BT_VIF);
-- local_binding_add(b_ctx_out->local_bindings, lbinding);
-+ lbinding = local_binding_create(iface_id, iface_rec);
-+ local_binding_add(local_bindings, lbinding);
- } else {
- lbinding->iface = iface_rec;
- }
-
-- if (!lbinding->pb || strcmp(lbinding->name, lbinding->pb->logical_port)) {
-- lbinding->pb = lport_lookup_by_name(
-- b_ctx_in->sbrec_port_binding_by_name, lbinding->name);
-- if (lbinding->pb && !strcmp(lbinding->pb->type, "virtual")) {
-- lbinding->pb = NULL;
-+ struct binding_lport *b_lport = local_binding_get_primary_lport(lbinding);
-+ const struct sbrec_port_binding *pb = NULL;
-+ if (!b_lport) {
-+ pb = lport_lookup_by_name(b_ctx_in->sbrec_port_binding_by_name,
-+ lbinding->name);
-+ if (pb && get_lport_type(pb) == LP_VIF) {
-+ b_lport = local_binding_add_lport(binding_lports, lbinding, pb,
-+ LP_VIF);
- }
- }
-
-- if (lbinding->pb) {
-- if (!consider_vif_lport(lbinding->pb, b_ctx_in, b_ctx_out,
-- lbinding, qos_map)) {
-- return false;
-- }
-+ if (!b_lport) {
-+ /* There is no binding lport for this local binding. */
-+ return true;
-+ }
-+
-+ if (!consider_vif_lport(b_lport->pb, b_ctx_in, b_ctx_out,
-+ lbinding, qos_map)) {
-+ return false;
- }
-
- /* Update the child local_binding's iface (if any children) and try to
- * claim the container lbindings. */
-- struct shash_node *node;
-- SHASH_FOR_EACH (node, &lbinding->children) {
-- struct local_binding *child = node->data;
-- child->iface = iface_rec;
-- if (child->type == BT_CONTAINER) {
-- if (!consider_container_lport(child->pb, b_ctx_in, b_ctx_out,
-+ LIST_FOR_EACH (b_lport, list_node, &lbinding->binding_lports) {
-+ if (b_lport->type == LP_CONTAINER) {
-+ if (!consider_container_lport(b_lport->pb, b_ctx_in, b_ctx_out,
- qos_map)) {
- return false;
- }
-@@ -1862,32 +2011,43 @@ consider_iface_release(const struct ovsrec_interface *iface_rec,
- struct binding_ctx_out *b_ctx_out)
- {
- struct local_binding *lbinding;
-- lbinding = local_binding_find(b_ctx_out->local_bindings,
-- iface_id);
-- if (is_lbinding_this_chassis(lbinding, b_ctx_in->chassis_rec)) {
-+ struct shash *local_bindings = &b_ctx_out->lbinding_data->bindings;
-+ struct shash *binding_lports = &b_ctx_out->lbinding_data->lports;
-+
-+ lbinding = local_binding_find(local_bindings, iface_id);
-+ struct binding_lport *b_lport = local_binding_get_primary_lport(lbinding);
-+ if (is_binding_lport_this_chassis(b_lport, b_ctx_in->chassis_rec)) {
- struct local_datapath *ld =
- get_local_datapath(b_ctx_out->local_datapaths,
-- lbinding->pb->datapath->tunnel_key);
-+ b_lport->pb->datapath->tunnel_key);
- if (ld) {
-- remove_pb_from_local_datapath(lbinding->pb,
-- b_ctx_in->chassis_rec,
-- b_ctx_out, ld);
-+ remove_pb_from_local_datapath(b_lport->pb,
-+ b_ctx_in->chassis_rec,
-+ b_ctx_out, ld);
- }
-
-- /* Note: release_local_binding() resets lbinding->pb and
-- * lbinding->iface.
-- * Cannot access these members of lbinding after this call. */
-- if (!release_local_binding(b_ctx_in->chassis_rec, lbinding,
-- !b_ctx_in->ovnsb_idl_txn,
-- b_ctx_out->tracked_dp_bindings)) {
-- return false;
-+ /* Release the primary binding lport and other children lports if
-+ * any. */
-+ LIST_FOR_EACH (b_lport, list_node, &lbinding->binding_lports) {
-+ if (!release_binding_lport(b_ctx_in->chassis_rec, b_lport,
-+ !b_ctx_in->ovnsb_idl_txn,
-+ b_ctx_out)) {
-+ return false;
-+ }
- }
-+
-+ }
-+
-+ if (lbinding) {
-+ /* Clear the iface of the local binding. */
-+ lbinding->iface = NULL;
- }
-
- /* Check if the lbinding has children of type PB_CONTAINER.
- * If so, don't delete the local_binding. */
- if (lbinding && !is_lbinding_container_parent(lbinding)) {
-- local_binding_delete(b_ctx_out->local_bindings, lbinding);
-+ local_binding_delete(lbinding, local_bindings, binding_lports,
-+ b_ctx_out->if_mgr);
- }
-
- remove_local_lports(iface_id, b_ctx_out);
-@@ -2088,56 +2248,35 @@ handle_deleted_lport(const struct sbrec_port_binding *pb,
- }
- }
-
--static struct local_binding *
--get_lbinding_for_lport(const struct sbrec_port_binding *pb,
-- enum en_lport_type lport_type,
-- struct binding_ctx_out *b_ctx_out)
--{
-- ovs_assert(lport_type == LP_VIF || lport_type == LP_VIRTUAL);
--
-- if (lport_type == LP_VIF && !is_lport_container(pb)) {
-- return local_binding_find(b_ctx_out->local_bindings, pb->logical_port);
-- }
--
-- struct local_binding *parent_lbinding = NULL;
--
-- if (lport_type == LP_VIRTUAL) {
-- if (pb->virtual_parent) {
-- parent_lbinding = local_binding_find(b_ctx_out->local_bindings,
-- pb->virtual_parent);
-- }
-- } else {
-- if (pb->parent_port) {
-- parent_lbinding = local_binding_find(b_ctx_out->local_bindings,
-- pb->parent_port);
-- }
-- }
--
-- return parent_lbinding
-- ? local_binding_find(&parent_lbinding->children, pb->logical_port)
-- : NULL;
--}
--
- static bool
- handle_deleted_vif_lport(const struct sbrec_port_binding *pb,
- enum en_lport_type lport_type,
- struct binding_ctx_in *b_ctx_in,
- struct binding_ctx_out *b_ctx_out)
- {
-- struct local_binding *lbinding =
-- get_lbinding_for_lport(pb, lport_type, b_ctx_out);
-+ struct local_binding *lbinding = NULL;
-+ bool bound = false;
-
-- if (lbinding) {
-- lbinding->pb = NULL;
-- /* The port_binding 'pb' is deleted. So there is no need to
-- * clear the 'chassis' column of 'pb'. But we need to do
-- * for the local_binding's children. */
-- if (lbinding->type == BT_VIF &&
-- !release_local_binding_children(
-- b_ctx_in->chassis_rec, lbinding,
-- !b_ctx_in->ovnsb_idl_txn,
-- b_ctx_out->tracked_dp_bindings)) {
-- return false;
-+ struct shash *binding_lports = &b_ctx_out->lbinding_data->lports;
-+ struct binding_lport *b_lport = binding_lport_find(binding_lports, pb->logical_port);
-+ if (b_lport) {
-+ lbinding = b_lport->lbinding;
-+ bound = is_binding_lport_this_chassis(b_lport, b_ctx_in->chassis_rec);
-+
-+ /* Remove b_lport from local_binding. */
-+ binding_lport_delete(binding_lports, b_lport);
-+ }
-+
-+ if (bound && lbinding && lport_type == LP_VIF) {
-+ /* We need to release the container/virtual binding lports (if any) if
-+ * deleted 'pb' type is LP_VIF. */
-+ struct binding_lport *c_lport;
-+ LIST_FOR_EACH (c_lport, list_node, &lbinding->binding_lports) {
-+ if (!release_binding_lport(b_ctx_in->chassis_rec, c_lport,
-+ !b_ctx_in->ovnsb_idl_txn,
-+ b_ctx_out)) {
-+ return false;
-+ }
- }
- }
-
-@@ -2147,18 +2286,8 @@ handle_deleted_vif_lport(const struct sbrec_port_binding *pb,
- * it from local_lports if there is a VIF entry.
- * consider_iface_release() takes care of removing from the local_lports
- * when the interface change happens. */
-- if (is_lport_container(pb)) {
-+ if (lport_type == LP_CONTAINER) {
- remove_local_lports(pb->logical_port, b_ctx_out);
--
-- /* If the container port is removed we should also remove it from
-- * its parent's children set.
-- */
-- if (lbinding) {
-- if (lbinding->parent) {
-- local_binding_delete_child(lbinding->parent, lbinding);
-- }
-- local_binding_destroy(lbinding);
-- }
- }
-
- handle_deleted_lport(pb, b_ctx_in, b_ctx_out);
-@@ -2177,7 +2306,7 @@ handle_updated_vif_lport(const struct sbrec_port_binding *pb,
-
- if (lport_type == LP_VIRTUAL) {
- handled = consider_virtual_lport(pb, b_ctx_in, b_ctx_out, qos_map);
-- } else if (lport_type == LP_VIF && is_lport_container(pb)) {
-+ } else if (lport_type == LP_CONTAINER) {
- handled = consider_container_lport(pb, b_ctx_in, b_ctx_out, qos_map);
- } else {
- handled = consider_vif_lport(pb, b_ctx_in, b_ctx_out, NULL, qos_map);
-@@ -2189,14 +2318,14 @@ handle_updated_vif_lport(const struct sbrec_port_binding *pb,
-
- bool now_claimed = (pb->chassis == b_ctx_in->chassis_rec);
-
-- if (lport_type == LP_VIRTUAL ||
-- (lport_type == LP_VIF && is_lport_container(pb)) ||
-+ if (lport_type == LP_VIRTUAL || lport_type == LP_CONTAINER ||
- claimed == now_claimed) {
- return true;
- }
-
-- struct local_binding *lbinding =
-- local_binding_find(b_ctx_out->local_bindings, pb->logical_port);
-+ struct shash *local_bindings = &b_ctx_out->lbinding_data->bindings;
-+ struct local_binding *lbinding = local_binding_find(local_bindings,
-+ pb->logical_port);
-
- /* If the ovs port backing this binding previously was removed in the
- * meantime, we won't have a local_binding for it.
-@@ -2206,12 +2335,11 @@ handle_updated_vif_lport(const struct sbrec_port_binding *pb,
- return true;
- }
-
-- struct shash_node *node;
-- SHASH_FOR_EACH (node, &lbinding->children) {
-- struct local_binding *child = node->data;
-- if (child->type == BT_CONTAINER) {
-- handled = consider_container_lport(child->pb, b_ctx_in, b_ctx_out,
-- qos_map);
-+ struct binding_lport *b_lport;
-+ LIST_FOR_EACH (b_lport, list_node, &lbinding->binding_lports) {
-+ if (b_lport->type == LP_CONTAINER) {
-+ handled = consider_container_lport(b_lport->pb, b_ctx_in,
-+ b_ctx_out, qos_map);
- if (!handled) {
- return false;
- }
-@@ -2256,12 +2384,25 @@ binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in,
-
- enum en_lport_type lport_type = get_lport_type(pb);
-
-- if (lport_type == LP_VIF) {
-- if (is_lport_container(pb)) {
-- shash_add(&deleted_container_pbs, pb->logical_port, pb);
-- } else {
-- shash_add(&deleted_vif_pbs, pb->logical_port, pb);
-+ struct binding_lport *b_lport =
-+ binding_lport_find(&b_ctx_out->lbinding_data->lports,
-+ pb->logical_port);
-+ if (b_lport) {
-+ /* If the 'b_lport->type' and 'lport_type' don't match, then update
-+ * the b_lport->type to the updated 'lport_type'. The function
-+ * binding_lport_check_and_cleanup() will cleanup the 'b_lport'
-+ * if required. */
-+ if (b_lport->type != lport_type) {
-+ b_lport->type = lport_type;
- }
-+ b_lport = binding_lport_check_and_cleanup(
-+ b_lport, &b_ctx_out->lbinding_data->lports);
-+ }
-+
-+ if (lport_type == LP_VIF) {
-+ shash_add(&deleted_vif_pbs, pb->logical_port, pb);
-+ } else if (lport_type == LP_CONTAINER) {
-+ shash_add(&deleted_container_pbs, pb->logical_port, pb);
- } else if (lport_type == LP_VIRTUAL) {
- shash_add(&deleted_virtual_pbs, pb->logical_port, pb);
- } else {
-@@ -2272,7 +2413,7 @@ binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in,
- struct shash_node *node;
- struct shash_node *node_next;
- SHASH_FOR_EACH_SAFE (node, node_next, &deleted_container_pbs) {
-- handled = handle_deleted_vif_lport(node->data, LP_VIF, b_ctx_in,
-+ handled = handle_deleted_vif_lport(node->data, LP_CONTAINER, b_ctx_in,
- b_ctx_out);
- shash_delete(&deleted_container_pbs, node);
- if (!handled) {
-@@ -2326,12 +2467,33 @@ delete_done:
-
- enum en_lport_type lport_type = get_lport_type(pb);
-
-+ struct binding_lport *b_lport =
-+ binding_lport_find(&b_ctx_out->lbinding_data->lports,
-+ pb->logical_port);
-+ if (b_lport) {
-+ ovs_assert(b_lport->pb == pb);
-+
-+ if (b_lport->type != lport_type) {
-+ b_lport->type = lport_type;
-+ }
-+
-+ if (b_lport->lbinding) {
-+ handled = local_binding_handle_stale_binding_lports(
-+ b_lport->lbinding, b_ctx_in, b_ctx_out, qos_map_ptr);
-+ if (!handled) {
-+ /* Backout from the handling. */
-+ break;
-+ }
-+ }
-+ }
-+
- struct local_datapath *ld =
- get_local_datapath(b_ctx_out->local_datapaths,
- pb->datapath->tunnel_key);
-
- switch (lport_type) {
- case LP_VIF:
-+ case LP_CONTAINER:
- case LP_VIRTUAL:
- handled = handle_updated_vif_lport(pb, lport_type, b_ctx_in,
- b_ctx_out, qos_map_ptr);
-@@ -2440,154 +2602,327 @@ delete_done:
- return handled;
- }
-
--/* Registered ofctrl seqno type for port_binding flow installation. */
--static size_t binding_seq_type_pb_cfg;
-+/* Static functions for local_lbindind and binding_lport. */
-+static struct local_binding *
-+local_binding_create(const char *name, const struct ovsrec_interface *iface)
-+{
-+ struct local_binding *lbinding = xzalloc(sizeof *lbinding);
-+ lbinding->name = xstrdup(name);
-+ lbinding->iface = iface;
-+ ovs_list_init(&lbinding->binding_lports);
-+
-+ return lbinding;
-+}
-
--/* Binding specific seqno to be acked by ofctrl when flows for new interfaces
-- * have been installed.
-- */
--static uint32_t binding_iface_seqno = 0;
-+static struct local_binding *
-+local_binding_find(struct shash *local_bindings, const char *name)
-+{
-+ return shash_find_data(local_bindings, name);
-+}
-
--/* Map indexed by iface-id containing the sequence numbers that when acked
-- * indicate that the OVS flows for the iface-id have been installed.
-- */
--static struct simap binding_iface_seqno_map =
-- SIMAP_INITIALIZER(&binding_iface_seqno_map);
-+static void
-+local_binding_add(struct shash *local_bindings, struct local_binding *lbinding)
-+{
-+ shash_add(local_bindings, lbinding->name, lbinding);
-+}
-
--void
--binding_init(void)
-+static void
-+local_binding_destroy(struct local_binding *lbinding,
-+ struct shash *binding_lports)
- {
-- binding_seq_type_pb_cfg = ofctrl_seqno_add_type();
-+ struct binding_lport *b_lport;
-+ LIST_FOR_EACH_POP (b_lport, list_node, &lbinding->binding_lports) {
-+ b_lport->lbinding = NULL;
-+ binding_lport_delete(binding_lports, b_lport);
-+ }
-+
-+ free(lbinding->name);
-+ free(lbinding);
- }
-
--/* Processes new release/bind operations OVN ports. For newly bound ports
-- * it creates ofctrl seqno update requests that will be acked when
-- * corresponding OVS flows have been installed.
-- *
-- * NOTE: Should be called only when valid SB and OVS transactions are
-- * available.
-+static void
-+local_binding_delete(struct local_binding *lbinding,
-+ struct shash *local_bindings,
-+ struct shash *binding_lports,
-+ struct if_status_mgr *if_mgr)
-+{
-+ shash_find_and_delete(local_bindings, lbinding->name);
-+ if_status_mgr_delete_iface(if_mgr, lbinding->name);
-+ local_binding_destroy(lbinding, binding_lports);
-+}
-+
-+/* Returns the primary binding lport if present in lbinding's
-+ * binding lports list. A binding lport is considered primary
-+ * if binding lport's type is LP_VIF and the name matches
-+ * with the 'lbinding'.
- */
--void
--binding_seqno_run(struct shash *local_bindings)
-+static struct binding_lport *
-+local_binding_get_primary_lport(struct local_binding *lbinding)
- {
-- const char *iface_id;
-- const char *iface_id_next;
-+ if (!lbinding) {
-+ return NULL;
-+ }
-
-- SSET_FOR_EACH_SAFE (iface_id, iface_id_next, &binding_iface_released_set) {
-- struct shash_node *lb_node = shash_find(local_bindings, iface_id);
-+ if (!ovs_list_is_empty(&lbinding->binding_lports)) {
-+ struct binding_lport *b_lport = NULL;
-+ b_lport = CONTAINER_OF(ovs_list_front(&lbinding->binding_lports),
-+ struct binding_lport, list_node);
-
-- /* If the local binding still exists (i.e., the OVS interface is
-- * still configured locally) then remove the external id and remove
-- * it from the in-flight seqno map.
-- */
-- if (lb_node) {
-- struct local_binding *lb = lb_node->data;
-+ if (b_lport->type == LP_VIF &&
-+ !strcmp(lbinding->name, b_lport->name)) {
-+ return b_lport;
-+ }
-+ }
-
-- if (lb->iface && smap_get(&lb->iface->external_ids,
-- OVN_INSTALLED_EXT_ID)) {
-- ovsrec_interface_update_external_ids_delkey(
-- lb->iface, OVN_INSTALLED_EXT_ID);
-- }
-+ return NULL;
-+}
-+
-+static struct binding_lport *
-+local_binding_add_lport(struct shash *binding_lports,
-+ struct local_binding *lbinding,
-+ const struct sbrec_port_binding *pb,
-+ enum en_lport_type b_type)
-+{
-+ struct binding_lport *b_lport =
-+ binding_lport_find(binding_lports, pb->logical_port);
-+ bool add_to_lport_list = false;
-+ if (!b_lport) {
-+ b_lport = binding_lport_create(pb, lbinding, b_type);
-+ binding_lport_add(binding_lports, b_lport);
-+ add_to_lport_list = true;
-+ } else if (b_lport->lbinding != lbinding) {
-+ add_to_lport_list = true;
-+ if (!ovs_list_is_empty(&b_lport->list_node)) {
-+ ovs_list_remove(&b_lport->list_node);
- }
-- simap_find_and_delete(&binding_iface_seqno_map, iface_id);
-- sset_delete(&binding_iface_released_set,
-- SSET_NODE_FROM_NAME(iface_id));
-+ b_lport->lbinding = lbinding;
-+ b_lport->type = b_type;
- }
-
-- bool new_ifaces = false;
-- uint32_t new_seqno = binding_iface_seqno + 1;
-+ if (add_to_lport_list) {
-+ if (b_type == LP_VIF) {
-+ ovs_list_push_front(&lbinding->binding_lports, &b_lport->list_node);
-+ } else {
-+ ovs_list_push_back(&lbinding->binding_lports, &b_lport->list_node);
-+ }
-+ }
-
-- SSET_FOR_EACH_SAFE (iface_id, iface_id_next, &binding_iface_bound_set) {
-- struct shash_node *lb_node = shash_find(local_bindings, iface_id);
-+ return b_lport;
-+}
-
-- struct local_binding *lb = lb_node ? lb_node->data : NULL;
-+/* This function handles the stale binding lports of 'lbinding' if 'lbinding'
-+ * doesn't have a primary binding lport.
-+ */
-+static bool
-+local_binding_handle_stale_binding_lports(struct local_binding *lbinding,
-+ struct binding_ctx_in *b_ctx_in,
-+ struct binding_ctx_out *b_ctx_out,
-+ struct hmap *qos_map)
-+{
-+ /* Check if this lbinding has a primary binding_lport or not. */
-+ struct binding_lport *p_lport = local_binding_get_primary_lport(lbinding);
-+ if (p_lport) {
-+ /* Nothing to be done. */
-+ return true;
-+ }
-
-- /* Make sure the binding is still complete, i.e., both SB port_binding
-- * and OVS interface still exist.
-- *
-- * If so, then this is a newly bound interface, make sure we reset the
-- * Port_Binding 'up' field and the OVS Interface 'external-id'.
-- */
-- if (lb && lb->pb && lb->iface) {
-- new_ifaces = true;
--
-- if (smap_get(&lb->iface->external_ids, OVN_INSTALLED_EXT_ID)) {
-- ovsrec_interface_update_external_ids_delkey(
-- lb->iface, OVN_INSTALLED_EXT_ID);
-- }
-- if (lb->pb->n_up) {
-- bool up = false;
-- sbrec_port_binding_set_up(lb->pb, &up, 1);
-- }
-- simap_put(&binding_iface_seqno_map, lb->name, new_seqno);
-+ bool handled = true;
-+ struct binding_lport *b_lport, *next;
-+ const struct sbrec_port_binding *pb;
-+ LIST_FOR_EACH_SAFE (b_lport, next, list_node, &lbinding->binding_lports) {
-+ /* Get the lport type again from the pb. Its possible that the
-+ * pb type has changed. */
-+ enum en_lport_type pb_lport_type = get_lport_type(b_lport->pb);
-+ if (b_lport->type == LP_VIRTUAL && pb_lport_type == LP_VIRTUAL) {
-+ pb = b_lport->pb;
-+ binding_lport_delete(&b_ctx_out->lbinding_data->lports,
-+ b_lport);
-+ handled = consider_virtual_lport(pb, b_ctx_in, b_ctx_out, qos_map);
-+ } else if (b_lport->type == LP_CONTAINER &&
-+ pb_lport_type == LP_CONTAINER) {
-+ /* For container lport, binding_lport is preserved so that when
-+ * the parent port is created, it can be considered.
-+ * consider_container_lport() creates the binding_lport for the parent
-+ * port (with iface set to NULL). */
-+ handled = consider_container_lport(b_lport->pb, b_ctx_in, b_ctx_out, qos_map);
-+ } else {
-+ /* This can happen when the lport type changes from one type
-+ * to another. Eg. from normal lport to external. Release the
-+ * lport if it was claimed earlier and delete the b_lport. */
-+ handled = release_binding_lport(b_ctx_in->chassis_rec, b_lport,
-+ !b_ctx_in->ovnsb_idl_txn,
-+ b_ctx_out);
-+ binding_lport_delete(&b_ctx_out->lbinding_data->lports,
-+ b_lport);
-+ }
-+
-+ if (!handled) {
-+ return false;
- }
-- sset_delete(&binding_iface_bound_set, SSET_NODE_FROM_NAME(iface_id));
- }
-
-- /* Request a seqno update when the flows for new interfaces have been
-- * installed in OVS.
-- */
-- if (new_ifaces) {
-- binding_iface_seqno = new_seqno;
-- ofctrl_seqno_update_create(binding_seq_type_pb_cfg, new_seqno);
-+ return handled;
-+}
-+
-+static struct binding_lport *
-+binding_lport_create(const struct sbrec_port_binding *pb,
-+ struct local_binding *lbinding,
-+ enum en_lport_type type)
-+{
-+ struct binding_lport *b_lport = xzalloc(sizeof *b_lport);
-+ b_lport->name = xstrdup(pb->logical_port);
-+ b_lport->pb = pb;
-+ b_lport->type = type;
-+ b_lport->lbinding = lbinding;
-+ ovs_list_init(&b_lport->list_node);
-+
-+ return b_lport;
-+}
-+
-+static void
-+binding_lport_add(struct shash *binding_lports, struct binding_lport *b_lport)
-+{
-+ shash_add(binding_lports, b_lport->pb->logical_port, b_lport);
-+}
-+
-+static struct binding_lport *
-+binding_lport_find(struct shash *binding_lports, const char *lport_name)
-+{
-+ if (!lport_name) {
-+ return NULL;
- }
-+
-+ return shash_find_data(binding_lports, lport_name);
- }
-
--/* Processes ofctrl seqno ACKs for new bindings. Sets the
-- * 'OVN_INSTALLED_EXT_ID' external-id in the OVS interface and the
-- * Port_Binding.up field for all ports for which OVS flows have been
-- * installed.
-+static void
-+binding_lport_destroy(struct binding_lport *b_lport)
-+{
-+ if (!ovs_list_is_empty(&b_lport->list_node)) {
-+ ovs_list_remove(&b_lport->list_node);
-+ }
-+
-+ free(b_lport->name);
-+ free(b_lport);
-+}
-+
-+static void
-+binding_lport_delete(struct shash *binding_lports,
-+ struct binding_lport *b_lport)
-+{
-+ shash_find_and_delete(binding_lports, b_lport->name);
-+ binding_lport_destroy(b_lport);
-+}
-+
-+static void
-+binding_lport_set_up(struct binding_lport *b_lport, bool sb_readonly)
-+{
-+ if (sb_readonly || !b_lport || !b_lport->pb->n_up || b_lport->pb->up[0]) {
-+ return;
-+ }
-+
-+ bool up = true;
-+ sbrec_port_binding_set_up(b_lport->pb, &up, 1);
-+}
-+
-+static void
-+binding_lport_set_down(struct binding_lport *b_lport, bool sb_readonly)
-+{
-+ if (sb_readonly || !b_lport || !b_lport->pb->n_up || !b_lport->pb->up[0]) {
-+ return;
-+ }
-+
-+ bool up = false;
-+ sbrec_port_binding_set_up(b_lport->pb, &up, 1);
-+}
-+
-+static const struct sbrec_port_binding *
-+binding_lport_get_parent_pb(struct binding_lport *b_lport)
-+{
-+ if (!b_lport) {
-+ return NULL;
-+ }
-+
-+ if (b_lport->type == LP_VIF) {
-+ return NULL;
-+ }
-+
-+ struct local_binding *lbinding = b_lport->lbinding;
-+ ovs_assert(lbinding);
-+
-+ struct binding_lport *parent_b_lport =
-+ local_binding_get_primary_lport(lbinding);
-+
-+ return parent_b_lport ? parent_b_lport->pb : NULL;
-+}
-+
-+/* This function checks and cleans up the 'b_lport' if it is
-+ * not in the correct state.
-+ *
-+ * If the 'b_lport' type is LP_VIF, then its name and its lbinding->name
-+ * should match. Otherwise this should be cleaned up.
- *
-- * NOTE: Should be called only when valid SB and OVS transactions are
-- * available.
-+ * If the 'b_lport' type is LP_CONTAINER, then its parent_port name should
-+ * be the same as its lbinding's name. Otherwise this should be
-+ * cleaned up.
-+ *
-+ * If the 'b_lport' type is LP_VIRTUAL, then its virtual parent name
-+ * should be the same as its lbinding's name. Otherwise this
-+ * should be cleaned up.
-+ *
-+ * If the 'b_lport' type is not LP_VIF, LP_CONTAINER or LP_VIRTUAL, it
-+ * should be cleaned up. This can happen if the CMS changes
-+ * the port binding type.
- */
--void
--binding_seqno_install(struct shash *local_bindings)
-+static struct binding_lport *
-+binding_lport_check_and_cleanup(struct binding_lport *b_lport,
-+ struct shash *binding_lports)
- {
-- struct ofctrl_acked_seqnos *acked_seqnos =
-- ofctrl_acked_seqnos_get(binding_seq_type_pb_cfg);
-- struct simap_node *node;
-- struct simap_node *node_next;
-+ bool cleanup_blport = false;
-
-- SIMAP_FOR_EACH_SAFE (node, node_next, &binding_iface_seqno_map) {
-- struct shash_node *lb_node = shash_find(local_bindings, node->name);
--
-- if (!lb_node) {
-- goto del_seqno;
-- }
-+ if (!b_lport->lbinding) {
-+ cleanup_blport = true;
-+ goto cleanup;
-+ }
-
-- struct local_binding *lb = lb_node->data;
-- if (!lb->pb || !lb->iface) {
-- goto del_seqno;
-+ switch (b_lport->type) {
-+ case LP_VIF:
-+ if (strcmp(b_lport->name, b_lport->lbinding->name)) {
-+ cleanup_blport = true;
- }
-+ break;
-
-- if (!ofctrl_acked_seqnos_contains(acked_seqnos, node->data)) {
-- continue;
-+ case LP_CONTAINER:
-+ if (strcmp(b_lport->pb->parent_port, b_lport->lbinding->name)) {
-+ cleanup_blport = true;
- }
-+ break;
-
-- ovsrec_interface_update_external_ids_setkey(lb->iface,
-- OVN_INSTALLED_EXT_ID,
-- "true");
-- if (lb->pb->n_up) {
-- bool up = true;
--
-- sbrec_port_binding_set_up(lb->pb, &up, 1);
-- struct shash_node *child_node;
-- SHASH_FOR_EACH (child_node, &lb->children) {
-- struct local_binding *lb_child = child_node->data;
-- sbrec_port_binding_set_up(lb_child->pb, &up, 1);
-- }
-+ case LP_VIRTUAL:
-+ if (!b_lport->pb->virtual_parent ||
-+ strcmp(b_lport->pb->virtual_parent, b_lport->lbinding->name)) {
-+ cleanup_blport = true;
- }
-+ break;
-
--del_seqno:
-- simap_delete(&binding_iface_seqno_map, node);
-+ case LP_PATCH:
-+ case LP_LOCALPORT:
-+ case LP_VTEP:
-+ case LP_L2GATEWAY:
-+ case LP_L3GATEWAY:
-+ case LP_CHASSISREDIRECT:
-+ case LP_EXTERNAL:
-+ case LP_LOCALNET:
-+ case LP_REMOTE:
-+ case LP_UNKNOWN:
-+ cleanup_blport = true;
- }
-
-- ofctrl_acked_seqnos_destroy(acked_seqnos);
--}
-+cleanup:
-+ if (cleanup_blport) {
-+ binding_lport_delete(binding_lports, b_lport);
-+ return NULL;
-+ }
-
--void
--binding_seqno_flush(void)
--{
-- simap_clear(&binding_iface_seqno_map);
-+ return b_lport;
- }
-diff --git a/controller/binding.h b/controller/binding.h
-index c9ebef4b1..7a6495320 100644
---- a/controller/binding.h
-+++ b/controller/binding.h
-@@ -36,6 +36,8 @@ struct sbrec_chassis;
- struct sbrec_port_binding_table;
- struct sset;
- struct sbrec_port_binding;
-+struct ds;
-+struct if_status_mgr;
-
- struct binding_ctx_in {
- struct ovsdb_idl_txn *ovnsb_idl_txn;
-@@ -56,7 +58,7 @@ struct binding_ctx_in {
-
- struct binding_ctx_out {
- struct hmap *local_datapaths;
-- struct shash *local_bindings;
-+ struct local_binding_data *lbinding_data;
-
- /* sset of (potential) local lports. */
- struct sset *local_lports;
-@@ -84,30 +86,26 @@ struct binding_ctx_out {
- * binding_handle_port_binding_changes) fills in for
- * the changed datapaths and port bindings. */
- struct hmap *tracked_dp_bindings;
--};
-
--enum local_binding_type {
-- BT_VIF,
-- BT_CONTAINER,
-- BT_VIRTUAL
-+ struct if_status_mgr *if_mgr;
- };
-
--struct local_binding {
-- char *name;
-- enum local_binding_type type;
-- const struct ovsrec_interface *iface;
-- const struct sbrec_port_binding *pb;
--
-- /* shash of 'struct local_binding' representing children. */
-- struct shash children;
-- struct local_binding *parent;
-+struct local_binding_data {
-+ struct shash bindings;
-+ struct shash lports;
- };
-
--static inline struct local_binding *
--local_binding_find(struct shash *local_bindings, const char *name)
--{
-- return shash_find_data(local_bindings, name);
--}
-+void local_binding_data_init(struct local_binding_data *);
-+void local_binding_data_destroy(struct local_binding_data *);
-+
-+const struct sbrec_port_binding *local_binding_get_primary_pb(
-+ struct shash *local_bindings, const char *pb_name);
-+bool local_binding_is_up(struct shash *local_bindings, const char *pb_name);
-+bool local_binding_is_down(struct shash *local_bindings, const char *pb_name);
-+void local_binding_set_up(struct shash *local_bindings, const char *pb_name,
-+ bool sb_readonly, bool ovs_readonly);
-+void local_binding_set_down(struct shash *local_bindings, const char *pb_name,
-+ bool sb_readonly, bool ovs_readonly);
-
- /* Represents a tracked binding logical port. */
- struct tracked_binding_lport {
-@@ -128,16 +126,11 @@ bool binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
- const struct sbrec_port_binding_table *,
- const struct sbrec_chassis *);
-
--void local_bindings_init(struct shash *local_bindings);
--void local_bindings_destroy(struct shash *local_bindings);
- bool binding_handle_ovs_interface_changes(struct binding_ctx_in *,
- struct binding_ctx_out *);
- bool binding_handle_port_binding_changes(struct binding_ctx_in *,
- struct binding_ctx_out *);
- void binding_tracked_dp_destroy(struct hmap *tracked_datapaths);
-
--void binding_init(void);
--void binding_seqno_run(struct shash *local_bindings);
--void binding_seqno_install(struct shash *local_bindings);
--void binding_seqno_flush(void);
-+void binding_dump_local_bindings(struct local_binding_data *, struct ds *);
- #endif /* controller/binding.h */
-diff --git a/controller/if-status.c b/controller/if-status.c
-new file mode 100644
-index 000000000..8d8c8d436
---- /dev/null
-+++ b/controller/if-status.c
-@@ -0,0 +1,415 @@
-+/* Copyright (c) 2021, Red Hat, Inc.
-+ *
-+ * Licensed under the Apache License, Version 2.0 (the "License");
-+ * you may not use this file except in compliance with the License.
-+ * You may obtain a copy of the License at:
-+ *
-+ * http://www.apache.org/licenses/LICENSE-2.0
-+ *
-+ * Unless required by applicable law or agreed to in writing, software
-+ * distributed under the License is distributed on an "AS IS" BASIS,
-+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-+ * See the License for the specific language governing permissions and
-+ * limitations under the License.
-+ */
-+
-+#include
-+
-+#include "binding.h"
-+#include "if-status.h"
-+#include "ofctrl-seqno.h"
-+
-+#include "lib/hmapx.h"
-+#include "lib/util.h"
-+#include "openvswitch/vlog.h"
-+
-+VLOG_DEFINE_THIS_MODULE(if_status);
-+
-+/* This module implements an interface manager that maintains the state of
-+ * the interfaces wrt. their flows being completely installed in OVS and
-+ * their corresponding bindings being marked up/down.
-+ *
-+ * A state machine is maintained for each interface.
-+ *
-+ * Transitions are triggered between states by three types of events:
-+ * A. Events received from the binding module:
-+ * - interface is claimed: if_status_mgr_claim_iface()
-+ * - interface is released: if_status_mgr_release_iface()
-+ * - interface is deleted: if_status_mgr_delete_iface()
-+ *
-+ * B. At every iteration, based on SB/OVS updates, handled in
-+ * if_status_mgr_update():
-+ * - an interface binding has been marked "up" both in the Southbound and OVS
-+ * databases.
-+ * - an interface binding has been marked "down" both in the Southbound and OVS
-+ * databases.
-+ * - new interface has been claimed.
-+ *
-+ * C. At every iteration, based on ofctrl_seqno updates, handled in
-+ * if_status_mgr_run():
-+ * - the flows for a previously claimed interface have been installed in OVS.
-+ */
-+
-+enum if_state {
-+ OIF_CLAIMED, /* Newly claimed interface. */
-+ OIF_INSTALL_FLOWS, /* Already claimed interface for which flows are still
-+ * being installed.
-+ */
-+ OIF_MARK_UP, /* Interface with flows successfully installed in OVS
-+ * but not yet marked "up" in the binding module (in
-+ * SB and OVS databases).
-+ */
-+ OIF_MARK_DOWN, /* Released interface but not yet marked "down" in the
-+ * binding module (in SB and/or OVS databases).
-+ */
-+ OIF_INSTALLED, /* Interface flows programmed in OVS and binding marked
-+ * "up" in the binding module.
-+ */
-+ OIF_MAX,
-+};
-+
-+static const char *if_state_names[] = {
-+ [OIF_CLAIMED] = "CLAIMED",
-+ [OIF_INSTALL_FLOWS] = "INSTALL_FLOWS",
-+ [OIF_MARK_UP] = "MARK_UP",
-+ [OIF_MARK_DOWN] = "MARK_DOWN",
-+ [OIF_INSTALLED] = "INSTALLED",
-+};
-+
-+struct ovs_iface {
-+ char *id; /* Extracted from OVS external_ids.iface_id. */
-+ enum if_state state; /* State of the interface in the state machine. */
-+ uint32_t install_seqno; /* Seqno at which this interface is expected to
-+ * be fully programmed in OVS. Only used in state
-+ * OIF_INSTALL_FLOWS.
-+ */
-+};
-+
-+/* State machine manager for all local OVS interfaces. */
-+struct if_status_mgr {
-+ /* All local interfaces, mapping from 'iface-id' to 'struct ovs_iface'. */
-+ struct shash ifaces;
-+
-+ /* All local interfaces, stored per state. */
-+ struct hmapx ifaces_per_state[OIF_MAX];
-+
-+ /* Registered ofctrl seqno type for port_binding flow installation. */
-+ size_t iface_seq_type_pb_cfg;
-+
-+ /* Interface specific seqno to be acked by ofctrl when flows for new
-+ * interfaces have been installed.
-+ */
-+ uint32_t iface_seqno;
-+};
-+
-+static struct ovs_iface *ovs_iface_create(struct if_status_mgr *,
-+ const char *iface_id,
-+ enum if_state );
-+static void ovs_iface_destroy(struct if_status_mgr *, struct ovs_iface *);
-+static void ovs_iface_set_state(struct if_status_mgr *, struct ovs_iface *,
-+ enum if_state);
-+
-+static void if_status_mgr_update_bindings(
-+ struct if_status_mgr *mgr, struct local_binding_data *binding_data,
-+ bool sb_readonly, bool ovs_readonly);
-+
-+struct if_status_mgr *
-+if_status_mgr_create(void)
-+{
-+ struct if_status_mgr *mgr = xzalloc(sizeof *mgr);
-+
-+ mgr->iface_seq_type_pb_cfg = ofctrl_seqno_add_type();
-+ for (size_t i = 0; i < ARRAY_SIZE(mgr->ifaces_per_state); i++) {
-+ hmapx_init(&mgr->ifaces_per_state[i]);
-+ }
-+ shash_init(&mgr->ifaces);
-+ return mgr;
-+}
-+
-+void
-+if_status_mgr_clear(struct if_status_mgr *mgr)
-+{
-+ struct shash_node *node_next;
-+ struct shash_node *node;
-+
-+ SHASH_FOR_EACH_SAFE (node, node_next, &mgr->ifaces) {
-+ ovs_iface_destroy(mgr, node->data);
-+ }
-+ ovs_assert(shash_is_empty(&mgr->ifaces));
-+
-+ for (size_t i = 0; i < ARRAY_SIZE(mgr->ifaces_per_state); i++) {
-+ ovs_assert(hmapx_is_empty(&mgr->ifaces_per_state[i]));
-+ }
-+}
-+
-+void
-+if_status_mgr_destroy(struct if_status_mgr *mgr)
-+{
-+ if_status_mgr_clear(mgr);
-+ shash_destroy(&mgr->ifaces);
-+ for (size_t i = 0; i < ARRAY_SIZE(mgr->ifaces_per_state); i++) {
-+ hmapx_destroy(&mgr->ifaces_per_state[i]);
-+ }
-+ free(mgr);
-+}
-+
-+void
-+if_status_mgr_claim_iface(struct if_status_mgr *mgr, const char *iface_id)
-+{
-+ struct ovs_iface *iface = shash_find_data(&mgr->ifaces, iface_id);
-+
-+ if (!iface) {
-+ iface = ovs_iface_create(mgr, iface_id, OIF_CLAIMED);
-+ }
-+
-+ switch (iface->state) {
-+ case OIF_CLAIMED:
-+ case OIF_INSTALL_FLOWS:
-+ case OIF_MARK_UP:
-+ /* Nothing to do here. */
-+ break;
-+ case OIF_INSTALLED:
-+ case OIF_MARK_DOWN:
-+ ovs_iface_set_state(mgr, iface, OIF_CLAIMED);
-+ break;
-+ case OIF_MAX:
-+ OVS_NOT_REACHED();
-+ break;
-+ }
-+}
-+
-+void
-+if_status_mgr_release_iface(struct if_status_mgr *mgr, const char *iface_id)
-+{
-+ struct ovs_iface *iface = shash_find_data(&mgr->ifaces, iface_id);
-+
-+ if (!iface) {
-+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-+ VLOG_WARN_RL(&rl, "Trying to release unknown interface %s", iface_id);
-+ return;
-+ }
-+
-+ switch (iface->state) {
-+ case OIF_CLAIMED:
-+ case OIF_INSTALL_FLOWS:
-+ /* Not yet fully installed interfaces can be safely deleted. */
-+ ovs_iface_destroy(mgr, iface);
-+ break;
-+ case OIF_MARK_UP:
-+ case OIF_INSTALLED:
-+ /* Properly mark interfaces "down" if their flows were already
-+ * programmed in OVS.
-+ */
-+ ovs_iface_set_state(mgr, iface, OIF_MARK_DOWN);
-+ break;
-+ case OIF_MARK_DOWN:
-+ /* Nothing to do here. */
-+ break;
-+ case OIF_MAX:
-+ OVS_NOT_REACHED();
-+ break;
-+ }
-+}
-+
-+void
-+if_status_mgr_delete_iface(struct if_status_mgr *mgr, const char *iface_id)
-+{
-+ struct ovs_iface *iface = shash_find_data(&mgr->ifaces, iface_id);
-+
-+ if (!iface) {
-+ return;
-+ }
-+
-+ switch (iface->state) {
-+ case OIF_CLAIMED:
-+ case OIF_INSTALL_FLOWS:
-+ /* Not yet fully installed interfaces can be safely deleted. */
-+ ovs_iface_destroy(mgr, iface);
-+ break;
-+ case OIF_MARK_UP:
-+ case OIF_INSTALLED:
-+ /* Properly mark interfaces "down" if their flows were already
-+ * programmed in OVS.
-+ */
-+ ovs_iface_set_state(mgr, iface, OIF_MARK_DOWN);
-+ break;
-+ case OIF_MARK_DOWN:
-+ /* Nothing to do here. */
-+ break;
-+ case OIF_MAX:
-+ OVS_NOT_REACHED();
-+ break;
-+ }
-+}
-+
-+void
-+if_status_mgr_update(struct if_status_mgr *mgr,
-+ struct local_binding_data *binding_data)
-+{
-+ if (!binding_data) {
-+ return;
-+ }
-+
-+ struct shash *bindings = &binding_data->bindings;
-+ struct hmapx_node *node_next;
-+ struct hmapx_node *node;
-+
-+ /* Move all interfaces that have been confirmed "up" by the binding module,
-+ * from OIF_MARK_UP to OIF_INSTALLED.
-+ */
-+ HMAPX_FOR_EACH_SAFE (node, node_next,
-+ &mgr->ifaces_per_state[OIF_MARK_UP]) {
-+ struct ovs_iface *iface = node->data;
-+
-+ if (local_binding_is_up(bindings, iface->id)) {
-+ ovs_iface_set_state(mgr, iface, OIF_INSTALLED);
-+ }
-+ }
-+
-+ /* Cleanup all interfaces that have been confirmed "down" by the binding
-+ * module.
-+ */
-+ HMAPX_FOR_EACH_SAFE (node, node_next,
-+ &mgr->ifaces_per_state[OIF_MARK_DOWN]) {
-+ struct ovs_iface *iface = node->data;
-+
-+ if (local_binding_is_down(bindings, iface->id)) {
-+ ovs_iface_destroy(mgr, iface);
-+ }
-+ }
-+
-+ /* Register for a notification about flows being installed in OVS for all
-+ * newly claimed interfaces.
-+ *
-+ * Move them from OIF_CLAIMED to OIF_INSTALL_FLOWS.
-+ */
-+ bool new_ifaces = false;
-+ HMAPX_FOR_EACH_SAFE (node, node_next,
-+ &mgr->ifaces_per_state[OIF_CLAIMED]) {
-+ struct ovs_iface *iface = node->data;
-+
-+ ovs_iface_set_state(mgr, iface, OIF_INSTALL_FLOWS);
-+ iface->install_seqno = mgr->iface_seqno + 1;
-+ new_ifaces = true;
-+ }
-+
-+ /* Request a seqno update when the flows for new interfaces have been
-+ * installed in OVS.
-+ */
-+ if (new_ifaces) {
-+ mgr->iface_seqno++;
-+ ofctrl_seqno_update_create(mgr->iface_seq_type_pb_cfg,
-+ mgr->iface_seqno);
-+ VLOG_DBG("Seqno requested: %"PRIu32, mgr->iface_seqno);
-+ }
-+}
-+
-+void
-+if_status_mgr_run(struct if_status_mgr *mgr,
-+ struct local_binding_data *binding_data,
-+ bool sb_readonly, bool ovs_readonly)
-+{
-+ struct ofctrl_acked_seqnos *acked_seqnos =
-+ ofctrl_acked_seqnos_get(mgr->iface_seq_type_pb_cfg);
-+ struct hmapx_node *node_next;
-+ struct hmapx_node *node;
-+
-+ /* Move interfaces from state OIF_INSTALL_FLOWS to OIF_MARK_UP if a
-+ * notification has been received aabout their flows being installed
-+ * in OVS.
-+ */
-+ HMAPX_FOR_EACH_SAFE (node, node_next,
-+ &mgr->ifaces_per_state[OIF_INSTALL_FLOWS]) {
-+ struct ovs_iface *iface = node->data;
-+
-+ if (!ofctrl_acked_seqnos_contains(acked_seqnos,
-+ iface->install_seqno)) {
-+ continue;
-+ }
-+ ovs_iface_set_state(mgr, iface, OIF_MARK_UP);
-+ }
-+ ofctrl_acked_seqnos_destroy(acked_seqnos);
-+
-+ /* Update binding states. */
-+ if_status_mgr_update_bindings(mgr, binding_data, sb_readonly,
-+ ovs_readonly);
-+}
-+
-+static struct ovs_iface *
-+ovs_iface_create(struct if_status_mgr *mgr, const char *iface_id,
-+ enum if_state state)
-+{
-+ struct ovs_iface *iface = xzalloc(sizeof *iface);
-+
-+ VLOG_DBG("Interface %s create.", iface->id);
-+ iface->id = xstrdup(iface_id);
-+ shash_add(&mgr->ifaces, iface_id, iface);
-+ ovs_iface_set_state(mgr, iface, state);
-+ return iface;
-+}
-+
-+static void
-+ovs_iface_destroy(struct if_status_mgr *mgr, struct ovs_iface *iface)
-+{
-+ VLOG_DBG("Interface %s destroy: state %s", iface->id,
-+ if_state_names[iface->state]);
-+ hmapx_find_and_delete(&mgr->ifaces_per_state[iface->state], iface);
-+ shash_find_and_delete(&mgr->ifaces, iface->id);
-+ free(iface->id);
-+ free(iface);
-+}
-+
-+static void
-+ovs_iface_set_state(struct if_status_mgr *mgr, struct ovs_iface *iface,
-+ enum if_state state)
-+{
-+ VLOG_DBG("Interface %s set state: old %s, new %s", iface->id,
-+ if_state_names[iface->state],
-+ if_state_names[state]);
-+
-+ hmapx_find_and_delete(&mgr->ifaces_per_state[iface->state], iface);
-+ iface->state = state;
-+ hmapx_add(&mgr->ifaces_per_state[iface->state], iface);
-+ iface->install_seqno = 0;
-+}
-+
-+static void
-+if_status_mgr_update_bindings(struct if_status_mgr *mgr,
-+ struct local_binding_data *binding_data,
-+ bool sb_readonly, bool ovs_readonly)
-+{
-+ if (!binding_data) {
-+ return;
-+ }
-+
-+ struct shash *bindings = &binding_data->bindings;
-+ struct hmapx_node *node;
-+
-+ /* Notify the binding module to set "down" all bindings that are still
-+ * in the process of being installed in OVS, i.e., are not yet instsalled.
-+ */
-+ HMAPX_FOR_EACH (node, &mgr->ifaces_per_state[OIF_INSTALL_FLOWS]) {
-+ struct ovs_iface *iface = node->data;
-+
-+ local_binding_set_down(bindings, iface->id, sb_readonly, ovs_readonly);
-+ }
-+
-+ /* Notifiy the binding module to set "up" all bindings that have had
-+ * their flows installed but are not yet marked "up" in the binding
-+ * module.
-+ */
-+ HMAPX_FOR_EACH (node, &mgr->ifaces_per_state[OIF_MARK_UP]) {
-+ struct ovs_iface *iface = node->data;
-+
-+ local_binding_set_up(bindings, iface->id, sb_readonly, ovs_readonly);
-+ }
-+
-+ /* Notify the binding module to set "down" all bindings that have been
-+ * released but are not yet marked as "down" in the binding module.
-+ */
-+ HMAPX_FOR_EACH (node, &mgr->ifaces_per_state[OIF_MARK_DOWN]) {
-+ struct ovs_iface *iface = node->data;
-+
-+ local_binding_set_down(bindings, iface->id, sb_readonly, ovs_readonly);
-+ }
-+}
-diff --git a/controller/if-status.h b/controller/if-status.h
-new file mode 100644
-index 000000000..51fe7c684
---- /dev/null
-+++ b/controller/if-status.h
-@@ -0,0 +1,37 @@
-+/* Copyright (c) 2021, Red Hat, Inc.
-+ *
-+ * Licensed under the Apache License, Version 2.0 (the "License");
-+ * you may not use this file except in compliance with the License.
-+ * You may obtain a copy of the License at:
-+ *
-+ * http://www.apache.org/licenses/LICENSE-2.0
-+ *
-+ * Unless required by applicable law or agreed to in writing, software
-+ * distributed under the License is distributed on an "AS IS" BASIS,
-+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-+ * See the License for the specific language governing permissions and
-+ * limitations under the License.
-+ */
-+
-+#ifndef IF_STATUS_H
-+#define IF_STATUS_H 1
-+
-+#include "openvswitch/shash.h"
-+
-+#include "binding.h"
-+
-+struct if_status_mgr;
-+
-+struct if_status_mgr *if_status_mgr_create(void);
-+void if_status_mgr_clear(struct if_status_mgr *);
-+void if_status_mgr_destroy(struct if_status_mgr *);
-+
-+void if_status_mgr_claim_iface(struct if_status_mgr *, const char *iface_id);
-+void if_status_mgr_release_iface(struct if_status_mgr *, const char *iface_id);
-+void if_status_mgr_delete_iface(struct if_status_mgr *, const char *iface_id);
-+
-+void if_status_mgr_update(struct if_status_mgr *, struct local_binding_data *);
-+void if_status_mgr_run(struct if_status_mgr *mgr, struct local_binding_data *,
-+ bool sb_readonly, bool ovs_readonly);
-+
-+# endif /* controller/if-status.h */
-diff --git a/controller/ovn-controller.8.xml b/controller/ovn-controller.8.xml
-index 51c0c372c..8886df568 100644
---- a/controller/ovn-controller.8.xml
-+++ b/controller/ovn-controller.8.xml
-@@ -578,6 +578,28 @@
- Displays logical flow cache statistics: enabled/disabled, per cache
- type entry counts.
-
-+
-+ inc-engine/show-stats
-+ -
-+ Display
ovn-controller
engine counters. For each engine
-+ node the following counters have been added:
-+
-+ -
-+
recompute
-+
-+ -
-+
compute
-+
-+ -
-+
abort
-+
-+
-+
-+
-+ inc-engine/clear-stats
-+ -
-+ Reset
ovn-controller
engine counters.
-+
-
-
-
-diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
-index 5dd643f52..b4eee4848 100644
---- a/controller/ovn-controller.c
-+++ b/controller/ovn-controller.c
-@@ -33,6 +33,7 @@
- #include "openvswitch/dynamic-string.h"
- #include "encaps.h"
- #include "fatal-signal.h"
-+#include "if-status.h"
- #include "ip-mcast.h"
- #include "openvswitch/hmap.h"
- #include "lflow.h"
-@@ -81,6 +82,7 @@ static unixctl_cb_func cluster_state_reset_cmd;
- static unixctl_cb_func debug_pause_execution;
- static unixctl_cb_func debug_resume_execution;
- static unixctl_cb_func debug_status_execution;
-+static unixctl_cb_func debug_dump_local_bindings;
- static unixctl_cb_func lflow_cache_flush_cmd;
- static unixctl_cb_func lflow_cache_show_stats_cmd;
- static unixctl_cb_func debug_delay_nb_cfg_report;
-@@ -102,6 +104,7 @@ OVS_NO_RETURN static void usage(void);
-
- struct controller_engine_ctx {
- struct lflow_cache *lflow_cache;
-+ struct if_status_mgr *if_mgr;
- };
-
- /* Pending packet to be injected into connected OVS. */
-@@ -258,23 +261,15 @@ update_sb_monitors(struct ovsdb_idl *ovnsb_idl,
- uuid);
- }
-
-- /* Updating conditions to receive logical flows that references
-- * datapath groups containing local datapaths. */
-- const struct sbrec_logical_dp_group *group;
-- SBREC_LOGICAL_DP_GROUP_FOR_EACH (group, ovnsb_idl) {
-- struct uuid *uuid = CONST_CAST(struct uuid *,
-- &group->header_.uuid);
-- size_t i;
--
-- for (i = 0; i < group->n_datapaths; i++) {
-- if (get_local_datapath(local_datapaths,
-- group->datapaths[i]->tunnel_key)) {
-- sbrec_logical_flow_add_clause_logical_dp_group(
-- &lf, OVSDB_F_EQ, uuid);
-- break;
-- }
-- }
-- }
-+ /* Datapath groups are immutable, which means a new group record is
-+ * created when a datapath is added to a group. The logical flows
-+ * referencing a datapath group are also updated in such cases but the
-+ * new group UUID is not known by ovn-controller until the SB update
-+ * is received. To avoid unnecessarily removing and adding lflows
-+ * that reference datapath groups, set the monitor condition to always
-+ * request all of them.
-+ */
-+ sbrec_logical_flow_add_clause_logical_dp_group(&lf, OVSDB_F_NE, NULL);
- }
-
- out:;
-@@ -420,6 +415,10 @@ process_br_int(struct ovsdb_idl_txn *ovs_idl_txn,
- if (datapath_type && strcmp(br_int->datapath_type, datapath_type)) {
- ovsrec_bridge_set_datapath_type(br_int, datapath_type);
- }
-+ if (!br_int->fail_mode || strcmp(br_int->fail_mode, "secure")) {
-+ ovsrec_bridge_set_fail_mode(br_int, "secure");
-+ VLOG_WARN("Integration bridge fail-mode changed to 'secure'.");
-+ }
- }
- return br_int;
- }
-@@ -1003,6 +1002,7 @@ en_ofctrl_is_connected_cleanup(void *data OVS_UNUSED)
- static void
- en_ofctrl_is_connected_run(struct engine_node *node, void *data)
- {
-+ struct controller_engine_ctx *ctrl_ctx = engine_get_context()->client_ctx;
- struct ed_type_ofctrl_is_connected *of_data = data;
- if (of_data->connected != ofctrl_is_connected()) {
- of_data->connected = !of_data->connected;
-@@ -1010,7 +1010,7 @@ en_ofctrl_is_connected_run(struct engine_node *node, void *data)
- /* Flush ofctrl seqno requests when the ofctrl connection goes down. */
- if (!of_data->connected) {
- ofctrl_seqno_flush();
-- binding_seqno_flush();
-+ if_status_mgr_clear(ctrl_ctx->if_mgr);
- }
- engine_set_node_state(node, EN_UPDATED);
- return;
-@@ -1182,8 +1182,7 @@ struct ed_type_runtime_data {
- /* Contains "struct local_datapath" nodes. */
- struct hmap local_datapaths;
-
-- /* Contains "struct local_binding" nodes. */
-- struct shash local_bindings;
-+ struct local_binding_data lbinding_data;
-
- /* Contains the name of each logical port resident on the local
- * hypervisor. These logical ports include the VIFs (and their child
-@@ -1222,9 +1221,9 @@ struct ed_type_runtime_data {
- * | | Interface and Port Binding changes store the |
- * | @tracked_dp_bindings | changed datapaths (datapaths added/removed from |
- * | | local_datapaths) and changed port bindings |
-- * | | (added/updated/deleted in 'local_bindings'). |
-+ * | | (added/updated/deleted in 'lbinding_data'). |
- * | | So any changes to the runtime data - |
-- * | | local_datapaths and local_bindings is captured |
-+ * | | local_datapaths and lbinding_data is captured |
- * | | here. |
- * ------------------------------------------------------------------------
- * | | This is a bool which represents if the runtime |
-@@ -1251,7 +1250,7 @@ struct ed_type_runtime_data {
- *
- * ---------------------------------------------------------------------
- * | local_datapaths | The changes to these runtime data is captured in |
-- * | local_bindings | the @tracked_dp_bindings indirectly and hence it |
-+ * | lbinding_data | the @tracked_dp_bindings indirectly and hence it |
- * | local_lport_ids | is not tracked explicitly. |
- * ---------------------------------------------------------------------
- * | local_iface_ids | This is used internally within the runtime data |
-@@ -1294,7 +1293,7 @@ en_runtime_data_init(struct engine_node *node OVS_UNUSED,
- sset_init(&data->active_tunnels);
- sset_init(&data->egress_ifaces);
- smap_init(&data->local_iface_ids);
-- local_bindings_init(&data->local_bindings);
-+ local_binding_data_init(&data->lbinding_data);
-
- /* Init the tracked data. */
- hmap_init(&data->tracked_dp_bindings);
-@@ -1322,7 +1321,7 @@ en_runtime_data_cleanup(void *data)
- free(cur_node);
- }
- hmap_destroy(&rt_data->local_datapaths);
-- local_bindings_destroy(&rt_data->local_bindings);
-+ local_binding_data_destroy(&rt_data->lbinding_data);
- hmapx_destroy(&rt_data->ct_updated_datapaths);
- }
-
-@@ -1383,6 +1382,8 @@ init_binding_ctx(struct engine_node *node,
- engine_get_input("SB_port_binding", node),
- "datapath");
-
-+ struct controller_engine_ctx *ctrl_ctx = engine_get_context()->client_ctx;
-+
- b_ctx_in->ovnsb_idl_txn = engine_get_context()->ovnsb_idl_txn;
- b_ctx_in->ovs_idl_txn = engine_get_context()->ovs_idl_txn;
- b_ctx_in->sbrec_datapath_binding_by_key = sbrec_datapath_binding_by_key;
-@@ -1405,10 +1406,10 @@ init_binding_ctx(struct engine_node *node,
- b_ctx_out->local_lport_ids_changed = false;
- b_ctx_out->non_vif_ports_changed = false;
- b_ctx_out->egress_ifaces = &rt_data->egress_ifaces;
-- b_ctx_out->local_bindings = &rt_data->local_bindings;
-+ b_ctx_out->lbinding_data = &rt_data->lbinding_data;
- b_ctx_out->local_iface_ids = &rt_data->local_iface_ids;
- b_ctx_out->tracked_dp_bindings = NULL;
-- b_ctx_out->local_lports_changed = NULL;
-+ b_ctx_out->if_mgr = ctrl_ctx->if_mgr;
- }
-
- static void
-@@ -1449,7 +1450,7 @@ en_runtime_data_run(struct engine_node *node, void *data)
- free(cur_node);
- }
- hmap_clear(local_datapaths);
-- local_bindings_destroy(&rt_data->local_bindings);
-+ local_binding_data_destroy(&rt_data->lbinding_data);
- sset_destroy(local_lports);
- sset_destroy(local_lport_ids);
- sset_destroy(active_tunnels);
-@@ -1460,7 +1461,7 @@ en_runtime_data_run(struct engine_node *node, void *data)
- sset_init(active_tunnels);
- sset_init(&rt_data->egress_ifaces);
- smap_init(&rt_data->local_iface_ids);
-- local_bindings_init(&rt_data->local_bindings);
-+ local_binding_data_init(&rt_data->lbinding_data);
- hmapx_clear(&rt_data->ct_updated_datapaths);
- }
-
-@@ -1715,6 +1716,7 @@ en_physical_flow_changes_run(struct engine_node *node, void *data)
- {
- struct ed_type_pfc_data *pfc_tdata = data;
- pfc_tdata->recompute_physical_flows = true;
-+ pfc_tdata->ovs_ifaces_changed = true;
- engine_set_node_state(node, EN_UPDATED);
- }
-
-@@ -1822,7 +1824,7 @@ static void init_physical_ctx(struct engine_node *node,
- p_ctx->local_lports = &rt_data->local_lports;
- p_ctx->ct_zones = ct_zones;
- p_ctx->mff_ovn_geneve = ed_mff_ovn_geneve->mff_ovn_geneve;
-- p_ctx->local_bindings = &rt_data->local_bindings;
-+ p_ctx->local_bindings = &rt_data->lbinding_data.bindings;
- p_ctx->ct_updated_datapaths = &rt_data->ct_updated_datapaths;
- }
-
-@@ -2448,7 +2450,6 @@ main(int argc, char *argv[])
- /* Register ofctrl seqno types. */
- ofctrl_seq_type_nb_cfg = ofctrl_seqno_add_type();
-
-- binding_init();
- patch_init();
- pinctrl_init();
- lflow_init();
-@@ -2685,7 +2686,8 @@ main(int argc, char *argv[])
- engine_get_internal_data(&en_flow_output);
- struct ed_type_ct_zones *ct_zones_data =
- engine_get_internal_data(&en_ct_zones);
-- struct ed_type_runtime_data *runtime_data = NULL;
-+ struct ed_type_runtime_data *runtime_data =
-+ engine_get_internal_data(&en_runtime_data);
-
- ofctrl_init(&flow_output_data->group_table,
- &flow_output_data->meter_table,
-@@ -2738,13 +2740,19 @@ main(int argc, char *argv[])
- unixctl_command_register("debug/delay-nb-cfg-report", "SECONDS", 1, 1,
- debug_delay_nb_cfg_report, &delay_nb_cfg_report);
-
-+ unixctl_command_register("debug/dump-local-bindings", "", 0, 0,
-+ debug_dump_local_bindings,
-+ &runtime_data->lbinding_data);
-+
- unsigned int ovs_cond_seqno = UINT_MAX;
- unsigned int ovnsb_cond_seqno = UINT_MAX;
- unsigned int ovnsb_expected_cond_seqno = UINT_MAX;
-
- struct controller_engine_ctx ctrl_engine_ctx = {
- .lflow_cache = lflow_cache_create(),
-+ .if_mgr = if_status_mgr_create(),
- };
-+ struct if_status_mgr *if_mgr = ctrl_engine_ctx.if_mgr;
-
- char *ovn_version = ovn_get_internal_version();
- VLOG_INFO("OVN internal version is : [%s]", ovn_version);
-@@ -2954,9 +2962,10 @@ main(int argc, char *argv[])
- ovnsb_idl_loop.idl),
- ovnsb_cond_seqno,
- ovnsb_expected_cond_seqno));
-- if (runtime_data && ovs_idl_txn && ovnsb_idl_txn) {
-- binding_seqno_run(&runtime_data->local_bindings);
-- }
-+
-+ struct local_binding_data *binding_data =
-+ runtime_data ? &runtime_data->lbinding_data : NULL;
-+ if_status_mgr_update(if_mgr, binding_data);
-
- flow_output_data = engine_get_data(&en_flow_output);
- if (flow_output_data && ct_zones_data) {
-@@ -2967,9 +2976,8 @@ main(int argc, char *argv[])
- engine_node_changed(&en_flow_output));
- }
- ofctrl_seqno_run(ofctrl_get_cur_cfg());
-- if (runtime_data && ovs_idl_txn && ovnsb_idl_txn) {
-- binding_seqno_install(&runtime_data->local_bindings);
-- }
-+ if_status_mgr_run(if_mgr, binding_data, !ovnsb_idl_txn,
-+ !ovs_idl_txn);
- }
-
- }
-@@ -3135,6 +3143,7 @@ loop_done:
- ofctrl_destroy();
- pinctrl_destroy();
- patch_destroy();
-+ if_status_mgr_destroy(if_mgr);
-
- ovsdb_idl_loop_destroy(&ovs_idl_loop);
- ovsdb_idl_loop_destroy(&ovnsb_idl_loop);
-@@ -3408,3 +3417,13 @@ debug_delay_nb_cfg_report(struct unixctl_conn *conn, int argc OVS_UNUSED,
- unixctl_command_reply(conn, "no delay for nb_cfg report.");
- }
- }
-+
-+static void
-+debug_dump_local_bindings(struct unixctl_conn *conn, int argc OVS_UNUSED,
-+ const char *argv[] OVS_UNUSED, void *local_bindings)
-+{
-+ struct ds binding_data = DS_EMPTY_INITIALIZER;
-+ binding_dump_local_bindings(local_bindings, &binding_data);
-+ unixctl_command_reply(conn, ds_cstr(&binding_data));
-+ ds_destroy(&binding_data);
-+}
-diff --git a/controller/physical.c b/controller/physical.c
-index fa5d0d692..c7090b351 100644
---- a/controller/physical.c
-+++ b/controller/physical.c
-@@ -1160,6 +1160,11 @@ consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name,
-
- load_logical_ingress_metadata(binding, &zone_ids, ofpacts_p);
-
-+ if (!strcmp(binding->type, "localport")) {
-+ /* mark the packet as incoming from a localport */
-+ put_load(1, MFF_LOG_FLAGS, MLF_LOCALPORT_BIT, 1, ofpacts_p);
-+ }
-+
- /* Resubmit to first logical ingress pipeline table. */
- put_resubmit(OFTABLE_LOG_INGRESS_PIPELINE, ofpacts_p);
- ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG,
-@@ -1219,6 +1224,24 @@ consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name,
- ofport, flow_table);
- }
-
-+ /* Table 39, priority 160.
-+ * =======================
-+ *
-+ * Do not forward local traffic from a localport to a localnet port.
-+ */
-+ if (!strcmp(binding->type, "localnet")) {
-+ /* do not forward traffic from localport to localnet port */
-+ match_init_catchall(&match);
-+ ofpbuf_clear(ofpacts_p);
-+ match_set_metadata(&match, htonll(dp_key));
-+ match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0, port_key);
-+ match_set_reg_masked(&match, MFF_LOG_FLAGS - MFF_REG0,
-+ MLF_LOCALPORT, MLF_LOCALPORT);
-+ ofctrl_add_flow(flow_table, OFTABLE_CHECK_LOOPBACK, 160,
-+ binding->header_.uuid.parts[0], &match,
-+ ofpacts_p, &binding->header_.uuid);
-+ }
-+
- } else if (!tun && !is_ha_remote) {
- /* Remote port connected by localnet port */
- /* Table 33, priority 100.
-@@ -1839,20 +1862,29 @@ physical_handle_ovs_iface_changes(struct physical_ctx *p_ctx,
- continue;
- }
-
-- const struct local_binding *lb =
-- local_binding_find(p_ctx->local_bindings, iface_id);
--
-- if (!lb || !lb->pb) {
-- continue;
-+ const struct sbrec_port_binding *lb_pb =
-+ local_binding_get_primary_pb(p_ctx->local_bindings, iface_id);
-+ if (!lb_pb) {
-+ /* For regular VIFs (e.g. lsp) the upcoming port-binding update
-+ * will remove lfows related to the unclaimed ovs port.
-+ * Localport is a special case and it needs to be managed here
-+ * since the port is not binded and otherwise the related lfows
-+ * will not be cleared removing the ovs port.
-+ */
-+ lb_pb = lport_lookup_by_name(p_ctx->sbrec_port_binding_by_name,
-+ iface_id);
-+ if (!lb_pb || strcmp(lb_pb->type, "localport")) {
-+ continue;
-+ }
- }
-
- int64_t ofport = iface_rec->n_ofport ? *iface_rec->ofport : 0;
- if (ovsrec_interface_is_deleted(iface_rec)) {
-- ofctrl_remove_flows(flow_table, &lb->pb->header_.uuid);
-+ ofctrl_remove_flows(flow_table, &lb_pb->header_.uuid);
- simap_find_and_delete(&localvif_to_ofport, iface_id);
- } else {
- if (!ovsrec_interface_is_new(iface_rec)) {
-- ofctrl_remove_flows(flow_table, &lb->pb->header_.uuid);
-+ ofctrl_remove_flows(flow_table, &lb_pb->header_.uuid);
- }
-
- simap_put(&localvif_to_ofport, iface_id, ofport);
-@@ -1860,7 +1892,7 @@ physical_handle_ovs_iface_changes(struct physical_ctx *p_ctx,
- p_ctx->mff_ovn_geneve, p_ctx->ct_zones,
- p_ctx->active_tunnels,
- p_ctx->local_datapaths,
-- lb->pb, p_ctx->chassis,
-+ lb_pb, p_ctx->chassis,
- flow_table, &ofpacts);
- }
- }
-diff --git a/controller/pinctrl.c b/controller/pinctrl.c
-index b42288ea5..523a45b9a 100644
---- a/controller/pinctrl.c
-+++ b/controller/pinctrl.c
-@@ -4240,6 +4240,12 @@ send_garp_rarp_update(struct ovsdb_idl_txn *ovnsb_idl_txn,
- struct shash *nat_addresses)
- {
- volatile struct garp_rarp_data *garp_rarp = NULL;
-+
-+ /* Skip localports as they don't need to be announced */
-+ if (!strcmp(binding_rec->type, "localport")) {
-+ return;
-+ }
-+
- /* Update GARP for NAT IP if it exists. Consider port bindings with type
- * "l3gateway" for logical switch ports attached to gateway routers, and
- * port bindings with type "patch" for logical switch ports attached to
-diff --git a/debian/changelog b/debian/changelog
-index 51f9bcc91..25a04f8ae 100644
---- a/debian/changelog
-+++ b/debian/changelog
-@@ -1,3 +1,9 @@
-+ovn (21.03.1-1) unstable; urgency=low
-+
-+ * New upstream version
-+
-+ -- OVN team Fri, 12 Mar 2021 12:00:00 -0500
-+
- ovn (21.03.0-1) unstable; urgency=low
-
- * New upstream version
-diff --git a/include/ovn/logical-fields.h b/include/ovn/logical-fields.h
-index 017176f98..ef97117b9 100644
---- a/include/ovn/logical-fields.h
-+++ b/include/ovn/logical-fields.h
-@@ -66,6 +66,8 @@ enum mff_log_flags_bits {
- MLF_LOOKUP_MAC_BIT = 6,
- MLF_LOOKUP_LB_HAIRPIN_BIT = 7,
- MLF_LOOKUP_FDB_BIT = 8,
-+ MLF_SKIP_SNAT_FOR_LB_BIT = 9,
-+ MLF_LOCALPORT_BIT = 10,
- };
-
- /* MFF_LOG_FLAGS_REG flag assignments */
-@@ -102,6 +104,13 @@ enum mff_log_flags {
-
- /* Indicate that the lookup in the fdb table was successful. */
- MLF_LOOKUP_FDB = (1 << MLF_LOOKUP_FDB_BIT),
-+
-+ /* Indicate that a packet must not SNAT in the gateway router when
-+ * load-balancing has taken place. */
-+ MLF_SKIP_SNAT_FOR_LB = (1 << MLF_SKIP_SNAT_FOR_LB_BIT),
-+
-+ /* Indicate the packet has been received from a localport */
-+ MLF_LOCALPORT = (1 << MLF_LOCALPORT_BIT),
- };
-
- /* OVN logical fields
-diff --git a/lib/expr.c b/lib/expr.c
-index f061a8fbe..7b3d3ddb3 100644
---- a/lib/expr.c
-+++ b/lib/expr.c
-@@ -2452,7 +2452,7 @@ crush_and_numeric(struct expr *expr, const struct expr_symbol *symbol)
- free(or);
- return cmp;
- } else {
-- return or;
-+ return crush_cmps(or, symbol);
- }
- } else {
- /* Transform "x && (a0 || a1) && (b0 || b1) && ..." into
-diff --git a/lib/inc-proc-eng.c b/lib/inc-proc-eng.c
-index 916dbbe39..a6337a1d9 100644
---- a/lib/inc-proc-eng.c
-+++ b/lib/inc-proc-eng.c
-@@ -27,6 +27,7 @@
- #include "openvswitch/hmap.h"
- #include "openvswitch/vlog.h"
- #include "inc-proc-eng.h"
-+#include "unixctl.h"
-
- VLOG_DEFINE_THIS_MODULE(inc_proc_eng);
-
-@@ -102,6 +103,40 @@ engine_get_nodes(struct engine_node *node, size_t *n_count)
- return engine_topo_sort(node, NULL, n_count, &n_size);
- }
-
-+static void
-+engine_clear_stats(struct unixctl_conn *conn, int argc OVS_UNUSED,
-+ const char *argv[] OVS_UNUSED, void *arg OVS_UNUSED)
-+{
-+ for (size_t i = 0; i < engine_n_nodes; i++) {
-+ struct engine_node *node = engine_nodes[i];
-+
-+ memset(&node->stats, 0, sizeof node->stats);
-+ }
-+ unixctl_command_reply(conn, NULL);
-+}
-+
-+static void
-+engine_dump_stats(struct unixctl_conn *conn, int argc OVS_UNUSED,
-+ const char *argv[] OVS_UNUSED, void *arg OVS_UNUSED)
-+{
-+ struct ds dump = DS_EMPTY_INITIALIZER;
-+
-+ for (size_t i = 0; i < engine_n_nodes; i++) {
-+ struct engine_node *node = engine_nodes[i];
-+
-+ ds_put_format(&dump,
-+ "Node: %s\n"
-+ "- recompute: %12"PRIu64"\n"
-+ "- compute: %12"PRIu64"\n"
-+ "- abort: %12"PRIu64"\n",
-+ node->name, node->stats.recompute,
-+ node->stats.compute, node->stats.abort);
-+ }
-+ unixctl_command_reply(conn, ds_cstr(&dump));
-+
-+ ds_destroy(&dump);
-+}
-+
- void
- engine_init(struct engine_node *node, struct engine_arg *arg)
- {
-@@ -115,6 +150,11 @@ engine_init(struct engine_node *node, struct engine_arg *arg)
- engine_nodes[i]->data = NULL;
- }
- }
-+
-+ unixctl_command_register("inc-engine/show-stats", "", 0, 0,
-+ engine_dump_stats, NULL);
-+ unixctl_command_register("inc-engine/clear-stats", "", 0, 0,
-+ engine_clear_stats, NULL);
- }
-
- void
-@@ -288,6 +328,7 @@ engine_recompute(struct engine_node *node, bool forced, bool allowed)
-
- /* Run the node handler which might change state. */
- node->run(node, node->data);
-+ node->stats.recompute++;
- }
-
- /* Return true if the node could be computed, false otherwise. */
-@@ -312,6 +353,8 @@ engine_compute(struct engine_node *node, bool recompute_allowed)
- }
- }
- }
-+ node->stats.compute++;
-+
- return true;
- }
-
-@@ -321,6 +364,7 @@ engine_run_node(struct engine_node *node, bool recompute_allowed)
- if (!node->n_inputs) {
- /* Run the node handler which might change state. */
- node->run(node, node->data);
-+ node->stats.recompute++;
- return;
- }
-
-@@ -377,6 +421,7 @@ engine_run(bool recompute_allowed)
- engine_run_node(engine_nodes[i], recompute_allowed);
-
- if (engine_nodes[i]->state == EN_ABORTED) {
-+ engine_nodes[i]->stats.abort++;
- engine_run_aborted = true;
- return;
- }
-@@ -393,6 +438,7 @@ engine_need_run(void)
- }
-
- engine_nodes[i]->run(engine_nodes[i], engine_nodes[i]->data);
-+ engine_nodes[i]->stats.recompute++;
- VLOG_DBG("input node: %s, state: %s", engine_nodes[i]->name,
- engine_node_state_name[engine_nodes[i]->state]);
- if (engine_nodes[i]->state == EN_UPDATED) {
-diff --git a/lib/inc-proc-eng.h b/lib/inc-proc-eng.h
-index 857234677..7e9f5bb70 100644
---- a/lib/inc-proc-eng.h
-+++ b/lib/inc-proc-eng.h
-@@ -107,6 +107,12 @@ enum engine_node_state {
- EN_STATE_MAX,
- };
-
-+struct engine_stats {
-+ uint64_t recompute;
-+ uint64_t compute;
-+ uint64_t abort;
-+};
-+
- struct engine_node {
- /* A unique name for each node. */
- char *name;
-@@ -154,6 +160,9 @@ struct engine_node {
- /* Method to clear up tracked data maintained by the engine node in the
- * engine 'data'. It may be NULL. */
- void (*clear_tracked_data)(void *tracked_data);
-+
-+ /* Engine stats. */
-+ struct engine_stats stats;
- };
-
- /* Initialize the data for the engine nodes. It calls each node's
-diff --git a/lib/logical-fields.c b/lib/logical-fields.c
-index 9d08b44c2..72853013e 100644
---- a/lib/logical-fields.c
-+++ b/lib/logical-fields.c
-@@ -121,6 +121,10 @@ ovn_init_symtab(struct shash *symtab)
- MLF_FORCE_SNAT_FOR_LB_BIT);
- expr_symtab_add_subfield(symtab, "flags.force_snat_for_lb", NULL,
- flags_str);
-+ snprintf(flags_str, sizeof flags_str, "flags[%d]",
-+ MLF_SKIP_SNAT_FOR_LB_BIT);
-+ expr_symtab_add_subfield(symtab, "flags.skip_snat_for_lb", NULL,
-+ flags_str);
-
- /* Connection tracking state. */
- expr_symtab_add_field_scoped(symtab, "ct_mark", MFF_CT_MARK, NULL, false,
-diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
-index c272cc922..37d1728b8 100644
---- a/northd/ovn-northd.8.xml
-+++ b/northd/ovn-northd.8.xml
-@@ -407,12 +407,13 @@
- it contains a priority-110 flow to move IPv6 Neighbor Discovery and MLD
- traffic to the next table. If load balancing rules with virtual IP
- addresses (and ports) are configured in OVN_Northbound
-- database for alogical switch datapath, a priority-100 flow is added
-+ database for a logical switch datapath, a priority-100 flow is added
- with the match ip
to match on IP packets and sets the action
-- reg0[0] = 1; next;
to act as a hint for table
-+ reg0[2] = 1; next;
to act as a hint for table
- Pre-stateful
to send IP packets to the connection tracker
-- for packet de-fragmentation before eventually advancing to ingress
-- table LB
.
-+ for packet de-fragmentation (and to possibly do DNAT for already
-+ established load balanced traffic) before eventually advancing to ingress
-+ table Stateful
.
- If controller_event has been enabled and load balancing rules with
- empty backends have been added in OVN_Northbound
, a 130 flow
- is added to trigger ovn-controller events whenever the chassis receives a
-@@ -470,11 +471,38 @@
-
- This table prepares flows for all possible stateful processing
- in next tables. It contains a priority-0 flow that simply moves
-- traffic to the next table. A priority-100 flow sends the packets to
-- connection tracker based on a hint provided by the previous tables
-- (with a match for reg0[0] == 1
) by using the
-- ct_next;
action.
-+ traffic to the next table.
-
-+
-+ -
-+ Priority-120 flows that send the packets to connection tracker using
-+
ct_lb;
as the action so that the already established
-+ traffic destined to the load balancer VIP gets DNATted based on a hint
-+ provided by the previous tables (with a match
-+ for reg0[2] == 1
and on supported load balancer protocols
-+ and address families). For IPv4 traffic the flows also load the
-+ original destination IP and transport port in registers
-+ reg1
and reg2
. For IPv6 traffic the flows
-+ also load the original destination IP and transport port in
-+ registers xxreg1
and reg2
.
-+
-+
-+ -
-+ A priority-110 flow sends the packets to connection tracker based
-+ on a hint provided by the previous tables
-+ (with a match for
reg0[2] == 1
) by using the
-+ ct_lb;
action. This flow is added to handle
-+ the traffic for load balancer VIPs whose protocol is not defined
-+ (mainly for ICMP traffic).
-+
-+
-+ -
-+ A priority-100 flow sends the packets to connection tracker based
-+ on a hint provided by the previous tables
-+ (with a match for
reg0[0] == 1
) by using the
-+ ct_next;
action.
-+
-+
-
- Ingress Table 8: from-lport
ACL hints
-
-@@ -511,6 +539,14 @@
-
- The table contains the following flows:
-
-+
-+ -
-+ A priority-65535 flow to advance to the next table if the logical
-+ switch has
no
ACLs configured, otherwise a
-+ priority-0 flow to advance to the next table.
-+
-+
-+
-
- -
- A priority-7 flow that matches on packets that initiate a new session.
-@@ -551,9 +587,6 @@
- This flow sets
reg0[10]
and then advances to the next
- table.
-
-- -
-- A priority-0 flow to advance to the next table.
--
-
-
- Ingress table 9: from-lport
ACLs
-@@ -599,9 +632,14 @@
-
-
-
-- This table also contains a priority 0 flow with action
-- next;
, so that ACLs allow packets by default. If the
-- logical datapath has a stateful ACL or a load balancer with VIP
-+ This table contains a priority-65535 flow to advance to the next table
-+ if the logical switch has no
ACLs configured, otherwise a
-+ priority-0 flow to advance to the next table so that ACLs allow
-+ packets by default.
-+
-+
-+
-+ If the logical datapath has a stateful ACL or a load balancer with VIP
- configured, the following flows will also be added:
-
-
-@@ -615,7 +653,7 @@
-
-
-
-- A priority-65535 flow that allows any traffic in the reply
-+ A priority-65532 flow that allows any traffic in the reply
- direction for a connection that has been committed to the
- connection tracker (i.e., established flows), as long as
- the committed flow does not have ct_label.blocked
set.
-@@ -628,19 +666,19 @@
-
-
-
-- A priority-65535 flow that allows any traffic that is considered
-+ A priority-65532 flow that allows any traffic that is considered
- related to a committed flow in the connection tracker (e.g., an
- ICMP Port Unreachable from a non-listening UDP port), as long
- as the committed flow does not have ct_label.blocked
set.
-
-
-
-- A priority-65535 flow that drops all traffic marked by the
-+ A priority-65532 flow that drops all traffic marked by the
- connection tracker as invalid.
-
-
-
-- A priority-65535 flow that drops all traffic in the reply direction
-+ A priority-65532 flow that drops all traffic in the reply direction
- with ct_label.blocked
set meaning that the connection
- should no longer be allowed due to a policy change. Packets
- in the request direction are skipped here to let a newly created
-@@ -648,11 +686,18 @@
-
-
-
-- A priority-65535 flow that allows IPv6 Neighbor solicitation,
-+ A priority-65532 flow that allows IPv6 Neighbor solicitation,
- Neighbor discover, Router solicitation, Router advertisement and MLD
- packets.
-
-+
-
-+
-+ If the logical datapath has any ACL or a load balancer with VIP
-+ configured, the following flow will also be added:
-+
-+
-+
- -
- A priority 34000 logical flow is added for each logical switch datapath
- with the match
eth.dst = E
to allow the service
-@@ -709,33 +754,7 @@
-
-
-
-- Ingress Table 12: LB
--
--
-- It contains a priority-0 flow that simply moves traffic to the next
-- table.
--
--
--
-- A priority-65535 flow with the match
-- inport == I
for all logical switch
-- datapaths to move traffic to the next table. Where I
-- is the peer of a logical router port. This flow is added to
-- skip the connection tracking of packets which enter from
-- logical router datapath to logical switch datapath.
--
--
--
-- For established connections a priority 65534 flow matches on
-- ct.est && !ct.rel && !ct.new &&
-- !ct.inv
and sets an action reg0[2] = 1; next;
to act
-- as a hint for table Stateful
to send packets through
-- connection tracker to NAT the packets. (The packet will automatically
-- get DNATed to the same IP address as the first packet in that
-- connection.)
--
--
-- Ingress Table 13: Stateful
-+ Ingress Table 12: Stateful
-
-
- -
-@@ -792,23 +811,12 @@
-
ct_commit; next;
action based on a hint provided by
- the previous tables (with a match for reg0[1] == 1
).
-
-- -
-- Priority-100 flows that send the packets to connection tracker using
--
ct_lb;
as the action based on a hint provided by the
-- previous tables (with a match for reg0[2] == 1
and
-- on supported load balancer protocols and address families).
-- For IPv4 traffic the flows also load the original destination
-- IP and transport port in registers reg1
and
-- reg2
. For IPv6 traffic the flows also load the original
-- destination IP and transport port in registers xxreg1
and
-- reg2
.
--
- -
- A priority-0 flow that simply moves traffic to the next table.
-
-
-
-- Ingress Table 14: Pre-Hairpin
-+ Ingress Table 13: Pre-Hairpin
-
- -
- If the logical switch has load balancer(s) configured, then a
-@@ -826,7 +834,7 @@
-
-
-
-- Ingress Table 15: Nat-Hairpin
-+ Ingress Table 14: Nat-Hairpin
-
- -
- If the logical switch has load balancer(s) configured, then a
-@@ -861,7 +869,7 @@
-
-
-
-- Ingress Table 16: Hairpin
-+ Ingress Table 15: Hairpin
-
- -
- A priority-1 flow that hairpins traffic matched by non-default
-@@ -874,7 +882,7 @@
-
-
-
-- Ingress Table 17: ARP/ND responder
-+ Ingress Table 16: ARP/ND responder
-
-
- This table implements ARP/ND responder in a logical switch for known
-@@ -1164,7 +1172,7 @@ output;
-
-
-
--
Ingress Table 18: DHCP option processing
-+ Ingress Table 17: DHCP option processing
-
-
- This table adds the DHCPv4 options to a DHCPv4 packet from the
-@@ -1225,7 +1233,7 @@ next;
-
-
-
--
Ingress Table 19: DHCP responses
-+ Ingress Table 18: DHCP responses
-
-
- This table implements DHCP responder for the DHCP replies generated by
-@@ -1306,7 +1314,7 @@ output;
-
-
-
--
Ingress Table 20 DNS Lookup
-+ Ingress Table 19 DNS Lookup
-
-
- This table looks up and resolves the DNS names to the corresponding
-@@ -1335,7 +1343,7 @@ reg0[4] = dns_lookup(); next;
-
-
-
--
Ingress Table 21 DNS Responses
-+ Ingress Table 20 DNS Responses
-
-
- This table implements DNS responder for the DNS replies generated by
-@@ -1370,7 +1378,7 @@ output;
-
-
-
--
Ingress table 22 External ports
-+ Ingress table 21 External ports
-
-
- Traffic from the external
logical ports enter the ingress
-@@ -1413,7 +1421,7 @@ output;
-
-
-
--
Ingress Table 23 Destination Lookup
-+ Ingress Table 22 Destination Lookup
-
-
- This table implements switching behavior. It contains these logical
-@@ -1639,9 +1647,11 @@ output;
- Moreover it contains a priority-110 flow to move IPv6 Neighbor Discovery
- traffic to the next table. If any load balancing rules exist for the
- datapath, a priority-100 flow is added with a match of ip
-- and action of reg0[0] = 1; next;
to act as a hint for
-+ and action of reg0[2] = 1; next;
to act as a hint for
- table Pre-stateful
to send IP packets to the connection
-- tracker for packet de-fragmentation.
-+ tracker for packet de-fragmentation and possibly DNAT the destination
-+ VIP to one of the selected backend for already commited load balanced
-+ traffic.
-
-
-
-@@ -1683,20 +1693,39 @@ output;
-
Egress Table 2: Pre-stateful
-
-
-- This is similar to ingress table Pre-stateful
.
-+ This is similar to ingress table Pre-stateful
. This table
-+ adds the below 3 logical flows.
-
-
-- Egress Table 3: LB
--
-- This is similar to ingress table LB
.
--
-+
-+ -
-+ A Priority-120 flow that send the packets to connection tracker using
-+
ct_lb;
as the action so that the already established
-+ traffic gets unDNATted from the backend IP to the load balancer VIP
-+ based on a hint provided by the previous tables with a match
-+ for reg0[2] == 1
. If the packet was not DNATted earlier,
-+ then ct_lb
functions like ct_next
.
-+
-
-- Egress Table 4: from-lport
ACL hints
-+ -
-+ A priority-100 flow sends the packets to connection tracker based
-+ on a hint provided by the previous tables
-+ (with a match for
reg0[0] == 1
) by using the
-+ ct_next;
action.
-+
-+
-+ -
-+ A priority-0 flow that matches all packets to advance to the next
-+ table.
-+
-+
-+
-+ Egress Table 3: from-lport
ACL hints
-
- This is similar to ingress table ACL hints
.
-
-
-- Egress Table 5: to-lport
ACLs
-+ Egress Table 4: to-lport
ACLs
-
-
- This is similar to ingress table ACLs
except for
-@@ -1733,28 +1762,28 @@ output;
-
-
-
--
Egress Table 6: to-lport
QoS Marking
-+ Egress Table 5: to-lport
QoS Marking
-
-
- This is similar to ingress table QoS marking
except
- they apply to to-lport
QoS rules.
-
-
-- Egress Table 7: to-lport
QoS Meter
-+ Egress Table 6: to-lport
QoS Meter
-
-
- This is similar to ingress table QoS meter
except
- they apply to to-lport
QoS rules.
-
-
-- Egress Table 8: Stateful
-+ Egress Table 7: Stateful
-
-
- This is similar to ingress table Stateful
except that
- there are no rules added for load balancing new connections.
-
-
-- Egress Table 9: Egress Port Security - IP
-+ Egress Table 8: Egress Port Security - IP
-
-
- This is similar to the port security logic in table
-@@ -1764,7 +1793,7 @@ output;
- ip4.src
and ip6.src
-
-
-- Egress Table 10: Egress Port Security - L2
-+ Egress Table 9: Egress Port Security - L2
-
-
- This is similar to the ingress port security logic in ingress table
-@@ -2283,8 +2312,7 @@ eth.src = xreg0[0..47];
- arp.op = 2; /* ARP reply. */
- arp.tha = arp.sha;
- arp.sha = xreg0[0..47];
--arp.tpa = arp.spa;
--arp.spa = A;
-+arp.tpa <-> arp.spa;
- outport = inport;
- flags.loopback = 1;
- output;
-@@ -2720,7 +2748,11 @@ icmp6 {
- (and optional port numbers) to load balance to. If the router is
- configured to force SNAT any load-balanced packets, the above action
- will be replaced by flags.force_snat_for_lb = 1;
-- ct_lb(args);
. If health check is enabled, then
-+ ct_lb(args);.
-+ If the load balancing rule is configured with skip_snat
-+ set to true, the above action will be replaced by
-+ flags.skip_snat_for_lb = 1; ct_lb(args);
.
-+ If health check is enabled, then
- args will only contain those endpoints whose service
- monitor status entry in OVN_Southbound
db is
- either online
or empty.
-@@ -2737,6 +2769,9 @@ icmp6 {
- with an action of ct_dnat;
. If the router is
- configured to force SNAT any load-balanced packets, the above action
- will be replaced by flags.force_snat_for_lb = 1; ct_dnat;
.
-+ If the load balancing rule is configured with skip_snat
-+ set to true, the above action will be replaced by
-+ flags.skip_snat_for_lb = 1; ct_dnat;
.
-
-
-
-@@ -2751,6 +2786,9 @@ icmp6 {
- to force SNAT any load-balanced packets, the above action will be
- replaced by flags.force_snat_for_lb = 1;
- ct_lb(args);
.
-+ If the load balancing rule is configured with skip_snat
-+ set to true, the above action will be replaced by
-+ flags.skip_snat_for_lb = 1; ct_lb(args);
.
-
-
-
-@@ -2763,6 +2801,9 @@ icmp6 {
- If the router is configured to force SNAT any load-balanced
- packets, the above action will be replaced by
- flags.force_snat_for_lb = 1; ct_dnat;
.
-+ If the load balancing rule is configured with skip_snat
-+ set to true, the above action will be replaced by
-+ flags.skip_snat_for_lb = 1; ct_dnat;
.
-
-
-
-@@ -3795,6 +3836,15 @@ nd_ns {
-
-
-
-+
-+
-+ If a load balancer configured to skip snat has been applied to
-+ the Gateway router pipeline, a priority-120 flow matches
-+ flags.skip_snat_for_lb == 1 && ip
with an
-+ action next;
.
-+
-+
-+
-
-
- If the Gateway router in the OVN Northbound database has been
-diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
-index 5a2018c2e..a478d3324 100644
---- a/northd/ovn-northd.c
-+++ b/northd/ovn-northd.c
-@@ -97,6 +97,10 @@ static bool check_lsp_is_up;
- static char svc_monitor_mac[ETH_ADDR_STRLEN + 1];
- static struct eth_addr svc_monitor_mac_ea;
-
-+/* If this option is 'true' northd will make use of ct.inv match fields.
-+ * Otherwise, it will avoid using it. The default is true. */
-+static bool use_ct_inv_match = true;
-+
- /* Default probe interval for NB and SB DB connections. */
- #define DEFAULT_PROBE_INTERVAL_MSEC 5000
- static int northd_probe_interval_nb = 0;
-@@ -147,32 +151,30 @@ enum ovn_stage {
- PIPELINE_STAGE(SWITCH, IN, ACL, 9, "ls_in_acl") \
- PIPELINE_STAGE(SWITCH, IN, QOS_MARK, 10, "ls_in_qos_mark") \
- PIPELINE_STAGE(SWITCH, IN, QOS_METER, 11, "ls_in_qos_meter") \
-- PIPELINE_STAGE(SWITCH, IN, LB, 12, "ls_in_lb") \
-- PIPELINE_STAGE(SWITCH, IN, STATEFUL, 13, "ls_in_stateful") \
-- PIPELINE_STAGE(SWITCH, IN, PRE_HAIRPIN, 14, "ls_in_pre_hairpin") \
-- PIPELINE_STAGE(SWITCH, IN, NAT_HAIRPIN, 15, "ls_in_nat_hairpin") \
-- PIPELINE_STAGE(SWITCH, IN, HAIRPIN, 16, "ls_in_hairpin") \
-- PIPELINE_STAGE(SWITCH, IN, ARP_ND_RSP, 17, "ls_in_arp_rsp") \
-- PIPELINE_STAGE(SWITCH, IN, DHCP_OPTIONS, 18, "ls_in_dhcp_options") \
-- PIPELINE_STAGE(SWITCH, IN, DHCP_RESPONSE, 19, "ls_in_dhcp_response") \
-- PIPELINE_STAGE(SWITCH, IN, DNS_LOOKUP, 20, "ls_in_dns_lookup") \
-- PIPELINE_STAGE(SWITCH, IN, DNS_RESPONSE, 21, "ls_in_dns_response") \
-- PIPELINE_STAGE(SWITCH, IN, EXTERNAL_PORT, 22, "ls_in_external_port") \
-- PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 23, "ls_in_l2_lkup") \
-- PIPELINE_STAGE(SWITCH, IN, L2_UNKNOWN, 24, "ls_in_l2_unknown") \
-+ PIPELINE_STAGE(SWITCH, IN, STATEFUL, 12, "ls_in_stateful") \
-+ PIPELINE_STAGE(SWITCH, IN, PRE_HAIRPIN, 13, "ls_in_pre_hairpin") \
-+ PIPELINE_STAGE(SWITCH, IN, NAT_HAIRPIN, 14, "ls_in_nat_hairpin") \
-+ PIPELINE_STAGE(SWITCH, IN, HAIRPIN, 15, "ls_in_hairpin") \
-+ PIPELINE_STAGE(SWITCH, IN, ARP_ND_RSP, 16, "ls_in_arp_rsp") \
-+ PIPELINE_STAGE(SWITCH, IN, DHCP_OPTIONS, 17, "ls_in_dhcp_options") \
-+ PIPELINE_STAGE(SWITCH, IN, DHCP_RESPONSE, 18, "ls_in_dhcp_response") \
-+ PIPELINE_STAGE(SWITCH, IN, DNS_LOOKUP, 19, "ls_in_dns_lookup") \
-+ PIPELINE_STAGE(SWITCH, IN, DNS_RESPONSE, 20, "ls_in_dns_response") \
-+ PIPELINE_STAGE(SWITCH, IN, EXTERNAL_PORT, 21, "ls_in_external_port") \
-+ PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 22, "ls_in_l2_lkup") \
-+ PIPELINE_STAGE(SWITCH, IN, L2_UNKNOWN, 23, "ls_in_l2_unknown") \
- \
- /* Logical switch egress stages. */ \
- PIPELINE_STAGE(SWITCH, OUT, PRE_LB, 0, "ls_out_pre_lb") \
- PIPELINE_STAGE(SWITCH, OUT, PRE_ACL, 1, "ls_out_pre_acl") \
- PIPELINE_STAGE(SWITCH, OUT, PRE_STATEFUL, 2, "ls_out_pre_stateful") \
-- PIPELINE_STAGE(SWITCH, OUT, LB, 3, "ls_out_lb") \
-- PIPELINE_STAGE(SWITCH, OUT, ACL_HINT, 4, "ls_out_acl_hint") \
-- PIPELINE_STAGE(SWITCH, OUT, ACL, 5, "ls_out_acl") \
-- PIPELINE_STAGE(SWITCH, OUT, QOS_MARK, 6, "ls_out_qos_mark") \
-- PIPELINE_STAGE(SWITCH, OUT, QOS_METER, 7, "ls_out_qos_meter") \
-- PIPELINE_STAGE(SWITCH, OUT, STATEFUL, 8, "ls_out_stateful") \
-- PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_IP, 9, "ls_out_port_sec_ip") \
-- PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_L2, 10, "ls_out_port_sec_l2") \
-+ PIPELINE_STAGE(SWITCH, OUT, ACL_HINT, 3, "ls_out_acl_hint") \
-+ PIPELINE_STAGE(SWITCH, OUT, ACL, 4, "ls_out_acl") \
-+ PIPELINE_STAGE(SWITCH, OUT, QOS_MARK, 5, "ls_out_qos_mark") \
-+ PIPELINE_STAGE(SWITCH, OUT, QOS_METER, 6, "ls_out_qos_meter") \
-+ PIPELINE_STAGE(SWITCH, OUT, STATEFUL, 7, "ls_out_stateful") \
-+ PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_IP, 8, "ls_out_port_sec_ip") \
-+ PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_L2, 9, "ls_out_port_sec_l2") \
- \
- /* Logical router ingress stages. */ \
- PIPELINE_STAGE(ROUTER, IN, ADMISSION, 0, "lr_in_admission") \
-@@ -626,6 +628,7 @@ struct ovn_datapath {
- bool has_stateful_acl;
- bool has_lb_vip;
- bool has_unknown;
-+ bool has_acls;
-
- /* IPAM data. */
- struct ipam_info ipam_info;
-@@ -664,9 +667,6 @@ struct ovn_datapath {
- struct hmap nb_pgs;
- };
-
--static bool ls_has_stateful_acl(struct ovn_datapath *od);
--static bool ls_has_lb_vip(struct ovn_datapath *od);
--
- /* Contains a NAT entry with the external addresses pre-parsed. */
- struct ovn_nat {
- const struct nbrec_nat *nb;
-@@ -4729,27 +4729,38 @@ ovn_ls_port_group_destroy(struct hmap *nb_pgs)
- hmap_destroy(nb_pgs);
- }
-
--static bool
--ls_has_stateful_acl(struct ovn_datapath *od)
-+static void
-+ls_get_acl_flags(struct ovn_datapath *od)
- {
-- for (size_t i = 0; i < od->nbs->n_acls; i++) {
-- struct nbrec_acl *acl = od->nbs->acls[i];
-- if (!strcmp(acl->action, "allow-related")) {
-- return true;
-+ od->has_acls = false;
-+ od->has_stateful_acl = false;
-+
-+ if (od->nbs->n_acls) {
-+ od->has_acls = true;
-+
-+ for (size_t i = 0; i < od->nbs->n_acls; i++) {
-+ struct nbrec_acl *acl = od->nbs->acls[i];
-+ if (!strcmp(acl->action, "allow-related")) {
-+ od->has_stateful_acl = true;
-+ return;
-+ }
- }
- }
-
- struct ovn_ls_port_group *ls_pg;
- HMAP_FOR_EACH (ls_pg, key_node, &od->nb_pgs) {
-- for (size_t i = 0; i < ls_pg->nb_pg->n_acls; i++) {
-- struct nbrec_acl *acl = ls_pg->nb_pg->acls[i];
-- if (!strcmp(acl->action, "allow-related")) {
-- return true;
-+ if (ls_pg->nb_pg->n_acls) {
-+ od->has_acls = true;
-+
-+ for (size_t i = 0; i < ls_pg->nb_pg->n_acls; i++) {
-+ struct nbrec_acl *acl = ls_pg->nb_pg->acls[i];
-+ if (!strcmp(acl->action, "allow-related")) {
-+ od->has_stateful_acl = true;
-+ return;
-+ }
- }
- }
- }
--
-- return false;
- }
-
- /* Logical switch ingress table 0: Ingress port security - L2
-@@ -5128,8 +5139,8 @@ build_pre_lb(struct ovn_datapath *od, struct hmap *lflows,
- vip_configured = (vip_configured || lb->n_vips);
- }
-
-- /* 'REGBIT_CONNTRACK_DEFRAG' is set to let the pre-stateful table send
-- * packet to conntrack for defragmentation.
-+ /* 'REGBIT_CONNTRACK_NAT' is set to let the pre-stateful table send
-+ * packet to conntrack for defragmentation and possibly for unNATting.
- *
- * Send all the packets to conntrack in the ingress pipeline if the
- * logical switch has a load balancer with VIP configured. Earlier
-@@ -5159,9 +5170,9 @@ build_pre_lb(struct ovn_datapath *od, struct hmap *lflows,
- */
- if (vip_configured) {
- ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB,
-- 100, "ip", REGBIT_CONNTRACK_DEFRAG" = 1; next;");
-+ 100, "ip", REGBIT_CONNTRACK_NAT" = 1; next;");
- ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_LB,
-- 100, "ip", REGBIT_CONNTRACK_DEFRAG" = 1; next;");
-+ 100, "ip", REGBIT_CONNTRACK_NAT" = 1; next;");
- }
- }
-
-@@ -5173,10 +5184,46 @@ build_pre_stateful(struct ovn_datapath *od, struct hmap *lflows)
- ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_STATEFUL, 0, "1", "next;");
- ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_STATEFUL, 0, "1", "next;");
-
-+ const char *lb_protocols[] = {"tcp", "udp", "sctp"};
-+ struct ds actions = DS_EMPTY_INITIALIZER;
-+ struct ds match = DS_EMPTY_INITIALIZER;
-+
-+ for (size_t i = 0; i < ARRAY_SIZE(lb_protocols); i++) {
-+ ds_clear(&match);
-+ ds_clear(&actions);
-+ ds_put_format(&match, REGBIT_CONNTRACK_NAT" == 1 && ip4 && %s",
-+ lb_protocols[i]);
-+ ds_put_format(&actions, REG_ORIG_DIP_IPV4 " = ip4.dst; "
-+ REG_ORIG_TP_DPORT " = %s.dst; ct_lb;",
-+ lb_protocols[i]);
-+ ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_STATEFUL, 120,
-+ ds_cstr(&match), ds_cstr(&actions));
-+
-+ ds_clear(&match);
-+ ds_clear(&actions);
-+ ds_put_format(&match, REGBIT_CONNTRACK_NAT" == 1 && ip6 && %s",
-+ lb_protocols[i]);
-+ ds_put_format(&actions, REG_ORIG_DIP_IPV6 " = ip6.dst; "
-+ REG_ORIG_TP_DPORT " = %s.dst; ct_lb;",
-+ lb_protocols[i]);
-+ ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_STATEFUL, 120,
-+ ds_cstr(&match), ds_cstr(&actions));
-+ }
-+
-+ ds_destroy(&actions);
-+ ds_destroy(&match);
-+
-+ ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_STATEFUL, 110,
-+ REGBIT_CONNTRACK_NAT" == 1", "ct_lb;");
-+
-+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_STATEFUL, 110,
-+ REGBIT_CONNTRACK_NAT" == 1", "ct_lb;");
-+
- /* If REGBIT_CONNTRACK_DEFRAG is set as 1, then the packets should be
- * sent to conntrack for tracking and defragmentation. */
- ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_STATEFUL, 100,
- REGBIT_CONNTRACK_DEFRAG" == 1", "ct_next;");
-+
- ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_STATEFUL, 100,
- REGBIT_CONNTRACK_DEFRAG" == 1", "ct_next;");
- }
-@@ -5206,7 +5253,11 @@ build_acl_hints(struct ovn_datapath *od, struct hmap *lflows)
- enum ovn_stage stage = stages[i];
-
- /* In any case, advance to the next stage. */
-- ovn_lflow_add(lflows, od, stage, 0, "1", "next;");
-+ if (!od->has_acls && !od->has_lb_vip) {
-+ ovn_lflow_add(lflows, od, stage, UINT16_MAX, "1", "next;");
-+ } else {
-+ ovn_lflow_add(lflows, od, stage, 0, "1", "next;");
-+ }
-
- if (!od->has_stateful_acl && !od->has_lb_vip) {
- continue;
-@@ -5606,10 +5657,19 @@ build_acls(struct ovn_datapath *od, struct hmap *lflows,
- bool has_stateful = od->has_stateful_acl || od->has_lb_vip;
-
- /* Ingress and Egress ACL Table (Priority 0): Packets are allowed by
-- * default. A related rule at priority 1 is added below if there
-+ * default. If the logical switch has no ACLs or no load balancers,
-+ * then add 65535-priority flow to advance the packet to next
-+ * stage.
-+ *
-+ * A related rule at priority 1 is added below if there
- * are any stateful ACLs in this datapath. */
-- ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, 0, "1", "next;");
-- ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, 0, "1", "next;");
-+ if (!od->has_acls && !od->has_lb_vip) {
-+ ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, UINT16_MAX, "1", "next;");
-+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX, "1", "next;");
-+ } else {
-+ ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, 0, "1", "next;");
-+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, 0, "1", "next;");
-+ }
-
- if (has_stateful) {
- /* Ingress and Egress ACL Table (Priority 1).
-@@ -5640,21 +5700,23 @@ build_acls(struct ovn_datapath *od, struct hmap *lflows,
- "ip && (!ct.est || (ct.est && ct_label.blocked == 1))",
- REGBIT_CONNTRACK_COMMIT" = 1; next;");
-
-- /* Ingress and Egress ACL Table (Priority 65535).
-+ /* Ingress and Egress ACL Table (Priority 65532).
- *
- * Always drop traffic that's in an invalid state. Also drop
- * reply direction packets for connections that have been marked
- * for deletion (bit 0 of ct_label is set).
- *
- * This is enforced at a higher priority than ACLs can be defined. */
-- ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, UINT16_MAX,
-- "ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)",
-- "drop;");
-- ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX,
-- "ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)",
-- "drop;");
-+ char *match =
-+ xasprintf("%s(ct.est && ct.rpl && ct_label.blocked == 1)",
-+ use_ct_inv_match ? "ct.inv || " : "");
-+ ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, UINT16_MAX - 3,
-+ match, "drop;");
-+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX - 3,
-+ match, "drop;");
-+ free(match);
-
-- /* Ingress and Egress ACL Table (Priority 65535).
-+ /* Ingress and Egress ACL Table (Priority 65535 - 3).
- *
- * Allow reply traffic that is part of an established
- * conntrack entry that has not been marked for deletion
-@@ -5663,14 +5725,15 @@ build_acls(struct ovn_datapath *od, struct hmap *lflows,
- * direction to hit the currently defined policy from ACLs.
- *
- * This is enforced at a higher priority than ACLs can be defined. */
-- ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, UINT16_MAX,
-- "ct.est && !ct.rel && !ct.new && !ct.inv "
-- "&& ct.rpl && ct_label.blocked == 0",
-- "next;");
-- ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX,
-- "ct.est && !ct.rel && !ct.new && !ct.inv "
-- "&& ct.rpl && ct_label.blocked == 0",
-- "next;");
-+ match = xasprintf("ct.est && !ct.rel && !ct.new%s && "
-+ "ct.rpl && ct_label.blocked == 0",
-+ use_ct_inv_match ? " && !ct.inv" : "");
-+
-+ ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, UINT16_MAX - 3,
-+ match, "next;");
-+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX - 3,
-+ match, "next;");
-+ free(match);
-
- /* Ingress and Egress ACL Table (Priority 65535).
- *
-@@ -5683,21 +5746,21 @@ build_acls(struct ovn_datapath *od, struct hmap *lflows,
- * a dynamically negotiated FTP data channel), but will allow
- * related traffic such as an ICMP Port Unreachable through
- * that's generated from a non-listening UDP port. */
-- ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, UINT16_MAX,
-- "!ct.est && ct.rel && !ct.new && !ct.inv "
-- "&& ct_label.blocked == 0",
-- "next;");
-- ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX,
-- "!ct.est && ct.rel && !ct.new && !ct.inv "
-- "&& ct_label.blocked == 0",
-- "next;");
-+ match = xasprintf("!ct.est && ct.rel && !ct.new%s && "
-+ "ct_label.blocked == 0",
-+ use_ct_inv_match ? " && !ct.inv" : "");
-+ ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, UINT16_MAX - 3,
-+ match, "next;");
-+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX - 3,
-+ match, "next;");
-+ free(match);
-
-- /* Ingress and Egress ACL Table (Priority 65535).
-+ /* Ingress and Egress ACL Table (Priority 65532).
- *
- * Not to do conntrack on ND packets. */
-- ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, UINT16_MAX,
-+ ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, UINT16_MAX - 3,
- "nd || nd_ra || nd_rs || mldv1 || mldv2", "next;");
-- ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX,
-+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX - 3,
- "nd || nd_ra || nd_rs || mldv1 || mldv2", "next;");
- }
-
-@@ -5784,15 +5847,18 @@ build_acls(struct ovn_datapath *od, struct hmap *lflows,
- actions);
- }
-
-- /* Add a 34000 priority flow to advance the service monitor reply
-- * packets to skip applying ingress ACLs. */
-- ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, 34000,
-- "eth.dst == $svc_monitor_mac", "next;");
-
-- /* Add a 34000 priority flow to advance the service monitor packets
-- * generated by ovn-controller to skip applying egress ACLs. */
-- ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, 34000,
-- "eth.src == $svc_monitor_mac", "next;");
-+ if (od->has_acls || od->has_lb_vip) {
-+ /* Add a 34000 priority flow to advance the service monitor reply
-+ * packets to skip applying ingress ACLs. */
-+ ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, 34000,
-+ "eth.dst == $svc_monitor_mac", "next;");
-+
-+ /* Add a 34000 priority flow to advance the service monitor packets
-+ * generated by ovn-controller to skip applying egress ACLs. */
-+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, 34000,
-+ "eth.src == $svc_monitor_mac", "next;");
-+ }
- }
-
- static void
-@@ -5856,37 +5922,6 @@ build_qos(struct ovn_datapath *od, struct hmap *lflows) {
- }
- }
-
--static void
--build_lb(struct ovn_datapath *od, struct hmap *lflows)
--{
-- /* Ingress and Egress LB Table (Priority 0): Packets are allowed by
-- * default. */
-- ovn_lflow_add(lflows, od, S_SWITCH_IN_LB, 0, "1", "next;");
-- ovn_lflow_add(lflows, od, S_SWITCH_OUT_LB, 0, "1", "next;");
--
-- if (od->nbs->n_load_balancer) {
-- for (size_t i = 0; i < od->n_router_ports; i++) {
-- skip_port_from_conntrack(od, od->router_ports[i],
-- S_SWITCH_IN_LB, S_SWITCH_OUT_LB,
-- UINT16_MAX, lflows);
-- }
-- }
--
-- if (od->has_lb_vip) {
-- /* Ingress and Egress LB Table (Priority 65534).
-- *
-- * Send established traffic through conntrack for just NAT. */
-- ovn_lflow_add(lflows, od, S_SWITCH_IN_LB, UINT16_MAX - 1,
-- "ct.est && !ct.rel && !ct.new && !ct.inv && "
-- "ct_label.natted == 1",
-- REGBIT_CONNTRACK_NAT" = 1; next;");
-- ovn_lflow_add(lflows, od, S_SWITCH_OUT_LB, UINT16_MAX - 1,
-- "ct.est && !ct.rel && !ct.new && !ct.inv && "
-- "ct_label.natted == 1",
-- REGBIT_CONNTRACK_NAT" = 1; next;");
-- }
--}
--
- static void
- build_lb_rules(struct ovn_datapath *od, struct hmap *lflows,
- struct ovn_northd_lb *lb)
-@@ -5971,48 +6006,6 @@ build_stateful(struct ovn_datapath *od, struct hmap *lflows, struct hmap *lbs)
- REGBIT_CONNTRACK_COMMIT" == 1",
- "ct_commit { ct_label.blocked = 0; }; next;");
-
-- /* If REGBIT_CONNTRACK_NAT is set as 1, then packets should just be sent
-- * through nat (without committing).
-- *
-- * REGBIT_CONNTRACK_COMMIT is set for new connections and
-- * REGBIT_CONNTRACK_NAT is set for established connections. So they
-- * don't overlap.
-- *
-- * In the ingress pipeline, also store the original destination IP and
-- * transport port to be used when detecting hairpin packets.
-- */
-- const char *lb_protocols[] = {"tcp", "udp", "sctp"};
-- struct ds actions = DS_EMPTY_INITIALIZER;
-- struct ds match = DS_EMPTY_INITIALIZER;
--
-- for (size_t i = 0; i < ARRAY_SIZE(lb_protocols); i++) {
-- ds_clear(&match);
-- ds_clear(&actions);
-- ds_put_format(&match, REGBIT_CONNTRACK_NAT" == 1 && ip4 && %s",
-- lb_protocols[i]);
-- ds_put_format(&actions, REG_ORIG_DIP_IPV4 " = ip4.dst; "
-- REG_ORIG_TP_DPORT " = %s.dst; ct_lb;",
-- lb_protocols[i]);
-- ovn_lflow_add(lflows, od, S_SWITCH_IN_STATEFUL, 100,
-- ds_cstr(&match), ds_cstr(&actions));
--
-- ds_clear(&match);
-- ds_clear(&actions);
-- ds_put_format(&match, REGBIT_CONNTRACK_NAT" == 1 && ip6 && %s",
-- lb_protocols[i]);
-- ds_put_format(&actions, REG_ORIG_DIP_IPV6 " = ip6.dst; "
-- REG_ORIG_TP_DPORT " = %s.dst; ct_lb;",
-- lb_protocols[i]);
-- ovn_lflow_add(lflows, od, S_SWITCH_IN_STATEFUL, 100,
-- ds_cstr(&match), ds_cstr(&actions));
-- }
--
-- ds_destroy(&actions);
-- ds_destroy(&match);
--
-- ovn_lflow_add(lflows, od, S_SWITCH_OUT_STATEFUL, 100,
-- REGBIT_CONNTRACK_NAT" == 1", "ct_lb;");
--
- /* Load balancing rules for new connections get committed to conntrack
- * table. So even if REGBIT_CONNTRACK_COMMIT is set in a previous table
- * a higher priority rule for load balancing below also commits the
-@@ -6759,7 +6752,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *lflows)
- struct ds actions = DS_EMPTY_INITIALIZER;
- struct ovn_datapath *od;
-
-- /* Ingress table 24: Destination lookup for unknown MACs (priority 0). */
-+ /* Ingress table 23: Destination lookup for unknown MACs (priority 0). */
- HMAP_FOR_EACH (od, key_node, datapaths) {
- if (!od->nbs) {
- continue;
-@@ -6794,8 +6787,8 @@ build_lswitch_lflows_pre_acl_and_acl(struct ovn_datapath *od,
- struct hmap *lbs)
- {
- if (od->nbs) {
-- od->has_stateful_acl = ls_has_stateful_acl(od);
- od->has_lb_vip = ls_has_lb_vip(od);
-+ ls_get_acl_flags(od);
-
- build_pre_acls(od, lflows);
- build_pre_lb(od, lflows, meter_groups, lbs);
-@@ -6803,7 +6796,6 @@ build_lswitch_lflows_pre_acl_and_acl(struct ovn_datapath *od,
- build_acl_hints(od, lflows);
- build_acls(od, lflows, port_groups, meter_groups);
- build_qos(od, lflows);
-- build_lb(od, lflows);
- build_stateful(od, lflows, lbs);
- build_lb_hairpin(od, lflows);
- }
-@@ -8573,10 +8565,16 @@ get_force_snat_ip(struct ovn_datapath *od, const char *key_type,
- return true;
- }
-
-+enum lb_snat_type {
-+ NO_FORCE_SNAT,
-+ FORCE_SNAT,
-+ SKIP_SNAT,
-+};
-+
- static void
- add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od,
- struct ds *match, struct ds *actions, int priority,
-- bool force_snat_for_lb, struct ovn_lb_vip *lb_vip,
-+ enum lb_snat_type snat_type, struct ovn_lb_vip *lb_vip,
- const char *proto, struct nbrec_load_balancer *lb,
- struct shash *meter_groups, struct sset *nat_entries)
- {
-@@ -8585,9 +8583,10 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od,
-
- /* A match and actions for new connections. */
- char *new_match = xasprintf("ct.new && %s", ds_cstr(match));
-- if (force_snat_for_lb) {
-- char *new_actions = xasprintf("flags.force_snat_for_lb = 1; %s",
-- ds_cstr(actions));
-+ if (snat_type == FORCE_SNAT || snat_type == SKIP_SNAT) {
-+ char *new_actions = xasprintf("flags.%s_snat_for_lb = 1; %s",
-+ snat_type == SKIP_SNAT ? "skip" : "force",
-+ ds_cstr(actions));
- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, priority,
- new_match, new_actions, &lb->header_);
- free(new_actions);
-@@ -8598,11 +8597,12 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od,
-
- /* A match and actions for established connections. */
- char *est_match = xasprintf("ct.est && %s", ds_cstr(match));
-- if (force_snat_for_lb) {
-+ if (snat_type == FORCE_SNAT || snat_type == SKIP_SNAT) {
-+ char *est_actions = xasprintf("flags.%s_snat_for_lb = 1; ct_dnat;",
-+ snat_type == SKIP_SNAT ? "skip" : "force");
- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, priority,
-- est_match,
-- "flags.force_snat_for_lb = 1; ct_dnat;",
-- &lb->header_);
-+ est_match, est_actions, &lb->header_);
-+ free(est_actions);
- } else {
- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, priority,
- est_match, "ct_dnat;", &lb->header_);
-@@ -8675,11 +8675,13 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od,
- ds_put_format(&undnat_match, ") && outport == %s && "
- "is_chassis_resident(%s)", od->l3dgw_port->json_key,
- od->l3redirect_port->json_key);
-- if (force_snat_for_lb) {
-+ if (snat_type == FORCE_SNAT || snat_type == SKIP_SNAT) {
-+ char *action = xasprintf("flags.%s_snat_for_lb = 1; ct_dnat;",
-+ snat_type == SKIP_SNAT ? "skip" : "force");
- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 120,
-- ds_cstr(&undnat_match),
-- "flags.force_snat_for_lb = 1; ct_dnat;",
-+ ds_cstr(&undnat_match), action,
- &lb->header_);
-+ free(action);
- } else {
- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 120,
- ds_cstr(&undnat_match), "ct_dnat;",
-@@ -8689,6 +8691,105 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od,
- ds_destroy(&undnat_match);
- }
-
-+static void
-+build_lrouter_lb_flows(struct hmap *lflows, struct ovn_datapath *od,
-+ struct hmap *lbs, struct shash *meter_groups,
-+ struct sset *nat_entries, struct ds *match,
-+ struct ds *actions)
-+{
-+ /* A set to hold all ips that need defragmentation and tracking. */
-+ struct sset all_ips = SSET_INITIALIZER(&all_ips);
-+ bool lb_force_snat_ip =
-+ !lport_addresses_is_empty(&od->lb_force_snat_addrs);
-+
-+ for (int i = 0; i < od->nbr->n_load_balancer; i++) {
-+ struct nbrec_load_balancer *nb_lb = od->nbr->load_balancer[i];
-+ struct ovn_northd_lb *lb =
-+ ovn_northd_lb_find(lbs, &nb_lb->header_.uuid);
-+ ovs_assert(lb);
-+
-+ bool lb_skip_snat = smap_get_bool(&nb_lb->options, "skip_snat", false);
-+ if (lb_skip_snat) {
-+ ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 120,
-+ "flags.skip_snat_for_lb == 1 && ip", "next;");
-+ }
-+
-+ for (size_t j = 0; j < lb->n_vips; j++) {
-+ struct ovn_lb_vip *lb_vip = &lb->vips[j];
-+ struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[j];
-+ ds_clear(actions);
-+ build_lb_vip_actions(lb_vip, lb_vip_nb, actions,
-+ lb->selection_fields, false);
-+
-+ if (!sset_contains(&all_ips, lb_vip->vip_str)) {
-+ sset_add(&all_ips, lb_vip->vip_str);
-+ /* If there are any load balancing rules, we should send
-+ * the packet to conntrack for defragmentation and
-+ * tracking. This helps with two things.
-+ *
-+ * 1. With tracking, we can send only new connections to
-+ * pick a DNAT ip address from a group.
-+ * 2. If there are L4 ports in load balancing rules, we
-+ * need the defragmentation to match on L4 ports. */
-+ ds_clear(match);
-+ if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
-+ ds_put_format(match, "ip && ip4.dst == %s",
-+ lb_vip->vip_str);
-+ } else {
-+ ds_put_format(match, "ip && ip6.dst == %s",
-+ lb_vip->vip_str);
-+ }
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DEFRAG,
-+ 100, ds_cstr(match), "ct_next;",
-+ &nb_lb->header_);
-+ }
-+
-+ /* Higher priority rules are added for load-balancing in DNAT
-+ * table. For every match (on a VIP[:port]), we add two flows
-+ * via add_router_lb_flow(). One flow is for specific matching
-+ * on ct.new with an action of "ct_lb($targets);". The other
-+ * flow is for ct.est with an action of "ct_dnat;". */
-+ ds_clear(match);
-+ if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
-+ ds_put_format(match, "ip && ip4.dst == %s",
-+ lb_vip->vip_str);
-+ } else {
-+ ds_put_format(match, "ip && ip6.dst == %s",
-+ lb_vip->vip_str);
-+ }
-+
-+ int prio = 110;
-+ bool is_udp = nullable_string_is_equal(nb_lb->protocol, "udp");
-+ bool is_sctp = nullable_string_is_equal(nb_lb->protocol,
-+ "sctp");
-+ const char *proto = is_udp ? "udp" : is_sctp ? "sctp" : "tcp";
-+
-+ if (lb_vip->vip_port) {
-+ ds_put_format(match, " && %s && %s.dst == %d", proto,
-+ proto, lb_vip->vip_port);
-+ prio = 120;
-+ }
-+
-+ if (od->l3redirect_port &&
-+ (lb_vip->n_backends || !lb_vip->empty_backend_rej)) {
-+ ds_put_format(match, " && is_chassis_resident(%s)",
-+ od->l3redirect_port->json_key);
-+ }
-+
-+ enum lb_snat_type snat_type = NO_FORCE_SNAT;
-+ if (lb_skip_snat) {
-+ snat_type = SKIP_SNAT;
-+ } else if (lb_force_snat_ip || od->lb_force_snat_router_ip) {
-+ snat_type = FORCE_SNAT;
-+ }
-+ add_router_lb_flow(lflows, od, match, actions, prio,
-+ snat_type, lb_vip, proto, nb_lb,
-+ meter_groups, nat_entries);
-+ }
-+ }
-+ sset_destroy(&all_ips);
-+}
-+
- #define ND_RA_MAX_INTERVAL_MAX 1800
- #define ND_RA_MAX_INTERVAL_MIN 4
-
-@@ -8893,14 +8994,12 @@ build_lrouter_arp_flow(struct ovn_datapath *od, struct ovn_port *op,
- "arp.op = 2; /* ARP reply */ "
- "arp.tha = arp.sha; "
- "arp.sha = %s; "
-- "arp.tpa = arp.spa; "
-- "arp.spa = %s; "
-+ "arp.tpa <-> arp.spa; "
- "outport = inport; "
- "flags.loopback = 1; "
- "output;",
- eth_addr,
-- eth_addr,
-- ip_address);
-+ eth_addr);
- }
-
- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_IP_INPUT, priority,
-@@ -10855,16 +10954,24 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
- get_router_load_balancer_ips(op->od, &all_ips_v4, &all_ips_v6);
-
- const char *ip_address;
-- SSET_FOR_EACH (ip_address, &all_ips_v4) {
-+ if (sset_count(&all_ips_v4)) {
- ds_clear(match);
- if (op == op->od->l3dgw_port) {
- ds_put_format(match, "is_chassis_resident(%s)",
- op->od->l3redirect_port->json_key);
- }
-
-- build_lrouter_arp_flow(op->od, op,
-- ip_address, REG_INPORT_ETH_ADDR,
-+ struct ds load_balancer_ips_v4 = DS_EMPTY_INITIALIZER;
-+
-+ /* For IPv4 we can just create one rule with all required IPs. */
-+ ds_put_cstr(&load_balancer_ips_v4, "{ ");
-+ ds_put_and_free_cstr(&load_balancer_ips_v4,
-+ sset_join(&all_ips_v4, ", ", " }"));
-+
-+ build_lrouter_arp_flow(op->od, op, ds_cstr(&load_balancer_ips_v4),
-+ REG_INPORT_ETH_ADDR,
- match, false, 90, NULL, lflows);
-+ ds_destroy(&load_balancer_ips_v4);
- }
-
- SSET_FOR_EACH (ip_address, &all_ips_v6) {
-@@ -11002,668 +11109,643 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
- }
- }
-
--/* NAT, Defrag and load balancing. */
- static void
--build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od,
-- struct hmap *lflows,
-- struct shash *meter_groups,
-- struct hmap *lbs,
-- struct ds *match, struct ds *actions)
-+build_lrouter_in_unsnat_flow(struct hmap *lflows, struct ovn_datapath *od,
-+ const struct nbrec_nat *nat, struct ds *match,
-+ struct ds *actions, bool distributed, bool is_v6)
- {
-- if (od->nbr) {
-+ /* Ingress UNSNAT table: It is for already established connections'
-+ * reverse traffic. i.e., SNAT has already been done in egress
-+ * pipeline and now the packet has entered the ingress pipeline as
-+ * part of a reply. We undo the SNAT here.
-+ *
-+ * Undoing SNAT has to happen before DNAT processing. This is
-+ * because when the packet was DNATed in ingress pipeline, it did
-+ * not know about the possibility of eventual additional SNAT in
-+ * egress pipeline. */
-+ if (strcmp(nat->type, "snat") && strcmp(nat->type, "dnat_and_snat")) {
-+ return;
-+ }
-
-- /* Packets are allowed by default. */
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_DEFRAG, 0, "1", "next;");
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 0, "1", "next;");
-- ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 0, "1", "next;");
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 0, "1", "next;");
-- ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 0, "1", "next;");
-- ovn_lflow_add(lflows, od, S_ROUTER_OUT_EGR_LOOP, 0, "1", "next;");
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 0, "1", "next;");
--
-- /* Send the IPv6 NS packets to next table. When ovn-controller
-- * generates IPv6 NS (for the action - nd_ns{}), the injected
-- * packet would go through conntrack - which is not required. */
-- ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 120, "nd_ns", "next;");
--
-- /* NAT rules are only valid on Gateway routers and routers with
-- * l3dgw_port (router has a port with gateway chassis
-- * specified). */
-- if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) {
-- return;
-+ bool stateless = lrouter_nat_is_stateless(nat);
-+ if (!od->l3dgw_port) {
-+ /* Gateway router. */
-+ ds_clear(match);
-+ ds_clear(actions);
-+ ds_put_format(match, "ip && ip%s.dst == %s",
-+ is_v6 ? "6" : "4", nat->external_ip);
-+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-+ ds_put_format(actions, "ip%s.dst=%s; next;",
-+ is_v6 ? "6" : "4", nat->logical_ip);
-+ } else {
-+ ds_put_cstr(actions, "ct_snat;");
- }
-
-- struct sset nat_entries = SSET_INITIALIZER(&nat_entries);
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT,
-+ 90, ds_cstr(match), ds_cstr(actions),
-+ &nat->header_);
-+ } else {
-+ /* Distributed router. */
-
-- bool dnat_force_snat_ip =
-- !lport_addresses_is_empty(&od->dnat_force_snat_addrs);
-- bool lb_force_snat_ip =
-- !lport_addresses_is_empty(&od->lb_force_snat_addrs);
-+ /* Traffic received on l3dgw_port is subject to NAT. */
-+ ds_clear(match);
-+ ds_clear(actions);
-+ ds_put_format(match, "ip && ip%s.dst == %s && inport == %s",
-+ is_v6 ? "6" : "4", nat->external_ip,
-+ od->l3dgw_port->json_key);
-+ if (!distributed && od->l3redirect_port) {
-+ /* Flows for NAT rules that are centralized are only
-+ * programmed on the gateway chassis. */
-+ ds_put_format(match, " && is_chassis_resident(%s)",
-+ od->l3redirect_port->json_key);
-+ }
-
-- for (int i = 0; i < od->nbr->n_nat; i++) {
-- const struct nbrec_nat *nat;
-+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-+ ds_put_format(actions, "ip%s.dst=%s; next;",
-+ is_v6 ? "6" : "4", nat->logical_ip);
-+ } else {
-+ ds_put_cstr(actions, "ct_snat;");
-+ }
-
-- nat = od->nbr->nat[i];
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT,
-+ 100, ds_cstr(match), ds_cstr(actions),
-+ &nat->header_);
-+ }
-+}
-
-- ovs_be32 ip, mask;
-- struct in6_addr ipv6, mask_v6, v6_exact = IN6ADDR_EXACT_INIT;
-- bool is_v6 = false;
-- bool stateless = lrouter_nat_is_stateless(nat);
-- struct nbrec_address_set *allowed_ext_ips =
-- nat->allowed_ext_ips;
-- struct nbrec_address_set *exempted_ext_ips =
-- nat->exempted_ext_ips;
-+static void
-+build_lrouter_in_dnat_flow(struct hmap *lflows, struct ovn_datapath *od,
-+ const struct nbrec_nat *nat, struct ds *match,
-+ struct ds *actions, bool distributed,
-+ ovs_be32 mask, bool is_v6)
-+{
-+ /* Ingress DNAT table: Packets enter the pipeline with destination
-+ * IP address that needs to be DNATted from a external IP address
-+ * to a logical IP address. */
-+ if (!strcmp(nat->type, "dnat") || !strcmp(nat->type, "dnat_and_snat")) {
-+ bool stateless = lrouter_nat_is_stateless(nat);
-
-- if (allowed_ext_ips && exempted_ext_ips) {
-- static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-- VLOG_WARN_RL(&rl, "NAT rule: "UUID_FMT" not applied, since "
-- "both allowed and exempt external ips set",
-- UUID_ARGS(&(nat->header_.uuid)));
-- continue;
-+ if (!od->l3dgw_port) {
-+ /* Gateway router. */
-+ /* Packet when it goes from the initiator to destination.
-+ * We need to set flags.loopback because the router can
-+ * send the packet back through the same interface. */
-+ ds_clear(match);
-+ ds_put_format(match, "ip && ip%s.dst == %s",
-+ is_v6 ? "6" : "4", nat->external_ip);
-+ ds_clear(actions);
-+ if (nat->allowed_ext_ips || nat->exempted_ext_ips) {
-+ lrouter_nat_add_ext_ip_match(od, lflows, match, nat,
-+ is_v6, true, mask);
- }
-
-- char *error = ip_parse_masked(nat->external_ip, &ip, &mask);
-- if (error || mask != OVS_BE32_MAX) {
-- free(error);
-- error = ipv6_parse_masked(nat->external_ip, &ipv6, &mask_v6);
-- if (error || memcmp(&mask_v6, &v6_exact, sizeof(mask_v6))) {
-- /* Invalid for both IPv4 and IPv6 */
-- static struct vlog_rate_limit rl =
-- VLOG_RATE_LIMIT_INIT(5, 1);
-- VLOG_WARN_RL(&rl, "bad external ip %s for nat",
-- nat->external_ip);
-- free(error);
-- continue;
-- }
-- /* It was an invalid IPv4 address, but valid IPv6.
-- * Treat the rest of the handling of this NAT rule
-- * as IPv6. */
-- is_v6 = true;
-- }
--
-- /* Check the validity of nat->logical_ip. 'logical_ip' can
-- * be a subnet when the type is "snat". */
-- int cidr_bits;
-- if (is_v6) {
-- error = ipv6_parse_masked(nat->logical_ip, &ipv6, &mask_v6);
-- cidr_bits = ipv6_count_cidr_bits(&mask_v6);
-- } else {
-- error = ip_parse_masked(nat->logical_ip, &ip, &mask);
-- cidr_bits = ip_count_cidr_bits(mask);
-+ if (!lport_addresses_is_empty(&od->dnat_force_snat_addrs)) {
-+ /* Indicate to the future tables that a DNAT has taken
-+ * place and a force SNAT needs to be done in the
-+ * Egress SNAT table. */
-+ ds_put_format(actions, "flags.force_snat_for_dnat = 1; ");
- }
-- if (!strcmp(nat->type, "snat")) {
-- if (error) {
-- /* Invalid for both IPv4 and IPv6 */
-- static struct vlog_rate_limit rl =
-- VLOG_RATE_LIMIT_INIT(5, 1);
-- VLOG_WARN_RL(&rl, "bad ip network or ip %s for snat "
-- "in router "UUID_FMT"",
-- nat->logical_ip, UUID_ARGS(&od->key));
-- free(error);
-- continue;
-- }
-+
-+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-+ ds_put_format(actions, "flags.loopback = 1; "
-+ "ip%s.dst=%s; next;",
-+ is_v6 ? "6" : "4", nat->logical_ip);
- } else {
-- if (error || (!is_v6 && mask != OVS_BE32_MAX)
-- || (is_v6 && memcmp(&mask_v6, &v6_exact,
-- sizeof mask_v6))) {
-- /* Invalid for both IPv4 and IPv6 */
-- static struct vlog_rate_limit rl =
-- VLOG_RATE_LIMIT_INIT(5, 1);
-- VLOG_WARN_RL(&rl, "bad ip %s for dnat in router "
-- ""UUID_FMT"", nat->logical_ip, UUID_ARGS(&od->key));
-- free(error);
-- continue;
-+ ds_put_format(actions, "flags.loopback = 1; ct_dnat(%s",
-+ nat->logical_ip);
-+
-+ if (nat->external_port_range[0]) {
-+ ds_put_format(actions, ",%s", nat->external_port_range);
- }
-+ ds_put_format(actions, ");");
- }
-
-- /* For distributed router NAT, determine whether this NAT rule
-- * satisfies the conditions for distributed NAT processing. */
-- bool distributed = false;
-- struct eth_addr mac;
-- if (od->l3dgw_port && !strcmp(nat->type, "dnat_and_snat") &&
-- nat->logical_port && nat->external_mac) {
-- if (eth_addr_from_string(nat->external_mac, &mac)) {
-- distributed = true;
-- } else {
-- static struct vlog_rate_limit rl =
-- VLOG_RATE_LIMIT_INIT(5, 1);
-- VLOG_WARN_RL(&rl, "bad mac %s for dnat in router "
-- ""UUID_FMT"", nat->external_mac, UUID_ARGS(&od->key));
-- continue;
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100,
-+ ds_cstr(match), ds_cstr(actions),
-+ &nat->header_);
-+ } else {
-+ /* Distributed router. */
-+
-+ /* Traffic received on l3dgw_port is subject to NAT. */
-+ ds_clear(match);
-+ ds_put_format(match, "ip && ip%s.dst == %s && inport == %s",
-+ is_v6 ? "6" : "4", nat->external_ip,
-+ od->l3dgw_port->json_key);
-+ if (!distributed && od->l3redirect_port) {
-+ /* Flows for NAT rules that are centralized are only
-+ * programmed on the gateway chassis. */
-+ ds_put_format(match, " && is_chassis_resident(%s)",
-+ od->l3redirect_port->json_key);
-+ }
-+ ds_clear(actions);
-+ if (nat->allowed_ext_ips || nat->exempted_ext_ips) {
-+ lrouter_nat_add_ext_ip_match(od, lflows, match, nat,
-+ is_v6, true, mask);
-+ }
-+
-+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-+ ds_put_format(actions, "ip%s.dst=%s; next;",
-+ is_v6 ? "6" : "4", nat->logical_ip);
-+ } else {
-+ ds_put_format(actions, "ct_dnat(%s", nat->logical_ip);
-+ if (nat->external_port_range[0]) {
-+ ds_put_format(actions, ",%s", nat->external_port_range);
- }
-+ ds_put_format(actions, ");");
- }
-
-- /* Ingress UNSNAT table: It is for already established connections'
-- * reverse traffic. i.e., SNAT has already been done in egress
-- * pipeline and now the packet has entered the ingress pipeline as
-- * part of a reply. We undo the SNAT here.
-- *
-- * Undoing SNAT has to happen before DNAT processing. This is
-- * because when the packet was DNATed in ingress pipeline, it did
-- * not know about the possibility of eventual additional SNAT in
-- * egress pipeline. */
-- if (!strcmp(nat->type, "snat")
-- || !strcmp(nat->type, "dnat_and_snat")) {
-- if (!od->l3dgw_port) {
-- /* Gateway router. */
-- ds_clear(match);
-- ds_clear(actions);
-- ds_put_format(match, "ip && ip%s.dst == %s",
-- is_v6 ? "6" : "4",
-- nat->external_ip);
-- if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-- ds_put_format(actions, "ip%s.dst=%s; next;",
-- is_v6 ? "6" : "4", nat->logical_ip);
-- } else {
-- ds_put_cstr(actions, "ct_snat;");
-- }
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100,
-+ ds_cstr(match), ds_cstr(actions),
-+ &nat->header_);
-+ }
-+ }
-+}
-
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT,
-- 90, ds_cstr(match),
-- ds_cstr(actions),
-- &nat->header_);
-- } else {
-- /* Distributed router. */
-+static void
-+build_lrouter_out_undnat_flow(struct hmap *lflows, struct ovn_datapath *od,
-+ const struct nbrec_nat *nat, struct ds *match,
-+ struct ds *actions, bool distributed,
-+ struct eth_addr mac, bool is_v6)
-+{
-+ /* Egress UNDNAT table: It is for already established connections'
-+ * reverse traffic. i.e., DNAT has already been done in ingress
-+ * pipeline and now the packet has entered the egress pipeline as
-+ * part of a reply. We undo the DNAT here.
-+ *
-+ * Note that this only applies for NAT on a distributed router.
-+ * Undo DNAT on a gateway router is done in the ingress DNAT
-+ * pipeline stage. */
-+ if (!od->l3dgw_port ||
-+ (strcmp(nat->type, "dnat") && strcmp(nat->type, "dnat_and_snat"))) {
-+ return;
-+ }
-
-- /* Traffic received on l3dgw_port is subject to NAT. */
-- ds_clear(match);
-- ds_clear(actions);
-- ds_put_format(match, "ip && ip%s.dst == %s"
-- " && inport == %s",
-- is_v6 ? "6" : "4",
-- nat->external_ip,
-- od->l3dgw_port->json_key);
-- if (!distributed && od->l3redirect_port) {
-- /* Flows for NAT rules that are centralized are only
-- * programmed on the gateway chassis. */
-- ds_put_format(match, " && is_chassis_resident(%s)",
-- od->l3redirect_port->json_key);
-- }
-+ ds_clear(match);
-+ ds_put_format(match, "ip && ip%s.src == %s && outport == %s",
-+ is_v6 ? "6" : "4", nat->logical_ip,
-+ od->l3dgw_port->json_key);
-+ if (!distributed && od->l3redirect_port) {
-+ /* Flows for NAT rules that are centralized are only
-+ * programmed on the gateway chassis. */
-+ ds_put_format(match, " && is_chassis_resident(%s)",
-+ od->l3redirect_port->json_key);
-+ }
-+ ds_clear(actions);
-+ if (distributed) {
-+ ds_put_format(actions, "eth.src = "ETH_ADDR_FMT"; ",
-+ ETH_ADDR_ARGS(mac));
-+ }
-
-- if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-- ds_put_format(actions, "ip%s.dst=%s; next;",
-- is_v6 ? "6" : "4", nat->logical_ip);
-- } else {
-- ds_put_cstr(actions, "ct_snat;");
-- }
-+ if (!strcmp(nat->type, "dnat_and_snat") &&
-+ lrouter_nat_is_stateless(nat)) {
-+ ds_put_format(actions, "ip%s.src=%s; next;",
-+ is_v6 ? "6" : "4", nat->external_ip);
-+ } else {
-+ ds_put_format(actions, "ct_dnat;");
-+ }
-
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT,
-- 100,
-- ds_cstr(match), ds_cstr(actions),
-- &nat->header_);
-- }
-- }
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 100,
-+ ds_cstr(match), ds_cstr(actions),
-+ &nat->header_);
-+}
-
-- /* Ingress DNAT table: Packets enter the pipeline with destination
-- * IP address that needs to be DNATted from a external IP address
-- * to a logical IP address. */
-- if (!strcmp(nat->type, "dnat")
-- || !strcmp(nat->type, "dnat_and_snat")) {
-- if (!od->l3dgw_port) {
-- /* Gateway router. */
-- /* Packet when it goes from the initiator to destination.
-- * We need to set flags.loopback because the router can
-- * send the packet back through the same interface. */
-- ds_clear(match);
-- ds_put_format(match, "ip && ip%s.dst == %s",
-- is_v6 ? "6" : "4",
-- nat->external_ip);
-- ds_clear(actions);
-- if (allowed_ext_ips || exempted_ext_ips) {
-- lrouter_nat_add_ext_ip_match(od, lflows, match, nat,
-- is_v6, true, mask);
-- }
-+static void
-+build_lrouter_out_snat_flow(struct hmap *lflows, struct ovn_datapath *od,
-+ const struct nbrec_nat *nat, struct ds *match,
-+ struct ds *actions, bool distributed,
-+ struct eth_addr mac, ovs_be32 mask,
-+ int cidr_bits, bool is_v6)
-+{
-+ /* Egress SNAT table: Packets enter the egress pipeline with
-+ * source ip address that needs to be SNATted to a external ip
-+ * address. */
-+ if (strcmp(nat->type, "snat") && strcmp(nat->type, "dnat_and_snat")) {
-+ return;
-+ }
-
-- if (dnat_force_snat_ip) {
-- /* Indicate to the future tables that a DNAT has taken
-- * place and a force SNAT needs to be done in the
-- * Egress SNAT table. */
-- ds_put_format(actions,
-- "flags.force_snat_for_dnat = 1; ");
-- }
-+ bool stateless = lrouter_nat_is_stateless(nat);
-+ if (!od->l3dgw_port) {
-+ /* Gateway router. */
-+ ds_clear(match);
-+ ds_put_format(match, "ip && ip%s.src == %s",
-+ is_v6 ? "6" : "4", nat->logical_ip);
-+ ds_clear(actions);
-
-- if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-- ds_put_format(actions, "flags.loopback = 1; "
-- "ip%s.dst=%s; next;",
-- is_v6 ? "6" : "4", nat->logical_ip);
-- } else {
-- ds_put_format(actions, "flags.loopback = 1; "
-- "ct_dnat(%s", nat->logical_ip);
-+ if (nat->allowed_ext_ips || nat->exempted_ext_ips) {
-+ lrouter_nat_add_ext_ip_match(od, lflows, match, nat,
-+ is_v6, false, mask);
-+ }
-
-- if (nat->external_port_range[0]) {
-- ds_put_format(actions, ",%s",
-- nat->external_port_range);
-- }
-- ds_put_format(actions, ");");
-- }
-+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-+ ds_put_format(actions, "ip%s.src=%s; next;",
-+ is_v6 ? "6" : "4", nat->external_ip);
-+ } else {
-+ ds_put_format(actions, "ct_snat(%s", nat->external_ip);
-
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100,
-- ds_cstr(match), ds_cstr(actions),
-- &nat->header_);
-- } else {
-- /* Distributed router. */
-+ if (nat->external_port_range[0]) {
-+ ds_put_format(actions, ",%s",
-+ nat->external_port_range);
-+ }
-+ ds_put_format(actions, ");");
-+ }
-
-- /* Traffic received on l3dgw_port is subject to NAT. */
-- ds_clear(match);
-- ds_put_format(match, "ip && ip%s.dst == %s"
-- " && inport == %s",
-- is_v6 ? "6" : "4",
-- nat->external_ip,
-- od->l3dgw_port->json_key);
-- if (!distributed && od->l3redirect_port) {
-- /* Flows for NAT rules that are centralized are only
-- * programmed on the gateway chassis. */
-- ds_put_format(match, " && is_chassis_resident(%s)",
-- od->l3redirect_port->json_key);
-- }
-- ds_clear(actions);
-- if (allowed_ext_ips || exempted_ext_ips) {
-- lrouter_nat_add_ext_ip_match(od, lflows, match, nat,
-- is_v6, true, mask);
-- }
-+ /* The priority here is calculated such that the
-+ * nat->logical_ip with the longest mask gets a higher
-+ * priority. */
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT,
-+ cidr_bits + 1, ds_cstr(match),
-+ ds_cstr(actions), &nat->header_);
-+ } else {
-+ uint16_t priority = cidr_bits + 1;
-
-- if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-- ds_put_format(actions, "ip%s.dst=%s; next;",
-- is_v6 ? "6" : "4", nat->logical_ip);
-- } else {
-- ds_put_format(actions, "ct_dnat(%s", nat->logical_ip);
-- if (nat->external_port_range[0]) {
-- ds_put_format(actions, ",%s",
-- nat->external_port_range);
-- }
-- ds_put_format(actions, ");");
-- }
-+ /* Distributed router. */
-+ ds_clear(match);
-+ ds_put_format(match, "ip && ip%s.src == %s && outport == %s",
-+ is_v6 ? "6" : "4", nat->logical_ip,
-+ od->l3dgw_port->json_key);
-+ if (!distributed && od->l3redirect_port) {
-+ /* Flows for NAT rules that are centralized are only
-+ * programmed on the gateway chassis. */
-+ priority += 128;
-+ ds_put_format(match, " && is_chassis_resident(%s)",
-+ od->l3redirect_port->json_key);
-+ }
-+ ds_clear(actions);
-
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100,
-- ds_cstr(match), ds_cstr(actions),
-- &nat->header_);
-- }
-- }
-+ if (nat->allowed_ext_ips || nat->exempted_ext_ips) {
-+ lrouter_nat_add_ext_ip_match(od, lflows, match, nat,
-+ is_v6, false, mask);
-+ }
-
-- /* ARP resolve for NAT IPs. */
-- if (od->l3dgw_port) {
-- if (!strcmp(nat->type, "snat")) {
-- ds_clear(match);
-- ds_put_format(
-- match, "inport == %s && %s == %s",
-- od->l3dgw_port->json_key,
-- is_v6 ? "ip6.src" : "ip4.src", nat->external_ip);
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_IP_INPUT,
-- 120, ds_cstr(match), "next;",
-- &nat->header_);
-- }
-+ if (distributed) {
-+ ds_put_format(actions, "eth.src = "ETH_ADDR_FMT"; ",
-+ ETH_ADDR_ARGS(mac));
-+ }
-
-- if (!sset_contains(&nat_entries, nat->external_ip)) {
-- ds_clear(match);
-- ds_put_format(
-- match, "outport == %s && %s == %s",
-- od->l3dgw_port->json_key,
-- is_v6 ? REG_NEXT_HOP_IPV6 : REG_NEXT_HOP_IPV4,
-+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-+ ds_put_format(actions, "ip%s.src=%s; next;",
-+ is_v6 ? "6" : "4", nat->external_ip);
-+ } else {
-+ ds_put_format(actions, "ct_snat(%s",
- nat->external_ip);
-- ds_clear(actions);
-- ds_put_format(
-- actions, "eth.dst = %s; next;",
-- distributed ? nat->external_mac :
-- od->l3dgw_port->lrp_networks.ea_s);
-- ovn_lflow_add_with_hint(lflows, od,
-- S_ROUTER_IN_ARP_RESOLVE,
-- 100, ds_cstr(match),
-- ds_cstr(actions),
-- &nat->header_);
-- sset_add(&nat_entries, nat->external_ip);
-- }
-- } else {
-- /* Add the NAT external_ip to the nat_entries even for
-- * gateway routers. This is required for adding load balancer
-- * flows.*/
-- sset_add(&nat_entries, nat->external_ip);
-+ if (nat->external_port_range[0]) {
-+ ds_put_format(actions, ",%s", nat->external_port_range);
- }
-+ ds_put_format(actions, ");");
-+ }
-
-- /* Egress UNDNAT table: It is for already established connections'
-- * reverse traffic. i.e., DNAT has already been done in ingress
-- * pipeline and now the packet has entered the egress pipeline as
-- * part of a reply. We undo the DNAT here.
-- *
-- * Note that this only applies for NAT on a distributed router.
-- * Undo DNAT on a gateway router is done in the ingress DNAT
-- * pipeline stage. */
-- if (od->l3dgw_port && (!strcmp(nat->type, "dnat")
-- || !strcmp(nat->type, "dnat_and_snat"))) {
-- ds_clear(match);
-- ds_put_format(match, "ip && ip%s.src == %s"
-- " && outport == %s",
-- is_v6 ? "6" : "4",
-- nat->logical_ip,
-- od->l3dgw_port->json_key);
-- if (!distributed && od->l3redirect_port) {
-- /* Flows for NAT rules that are centralized are only
-- * programmed on the gateway chassis. */
-- ds_put_format(match, " && is_chassis_resident(%s)",
-- od->l3redirect_port->json_key);
-- }
-- ds_clear(actions);
-- if (distributed) {
-- ds_put_format(actions, "eth.src = "ETH_ADDR_FMT"; ",
-- ETH_ADDR_ARGS(mac));
-- }
-+ /* The priority here is calculated such that the
-+ * nat->logical_ip with the longest mask gets a higher
-+ * priority. */
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT,
-+ priority, ds_cstr(match),
-+ ds_cstr(actions), &nat->header_);
-+ }
-+}
-
-- if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-- ds_put_format(actions, "ip%s.src=%s; next;",
-- is_v6 ? "6" : "4", nat->external_ip);
-- } else {
-- ds_put_format(actions, "ct_dnat;");
-- }
-+static void
-+build_lrouter_ingress_flow(struct hmap *lflows, struct ovn_datapath *od,
-+ const struct nbrec_nat *nat, struct ds *match,
-+ struct ds *actions, struct eth_addr mac,
-+ bool distributed, bool is_v6)
-+{
-+ if (od->l3dgw_port && !strcmp(nat->type, "snat")) {
-+ ds_clear(match);
-+ ds_put_format(
-+ match, "inport == %s && %s == %s",
-+ od->l3dgw_port->json_key,
-+ is_v6 ? "ip6.src" : "ip4.src", nat->external_ip);
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_IP_INPUT,
-+ 120, ds_cstr(match), "next;",
-+ &nat->header_);
-+ }
-+ /* Logical router ingress table 0:
-+ * For NAT on a distributed router, add rules allowing
-+ * ingress traffic with eth.dst matching nat->external_mac
-+ * on the l3dgw_port instance where nat->logical_port is
-+ * resident. */
-+ if (distributed) {
-+ /* Store the ethernet address of the port receiving the packet.
-+ * This will save us from having to match on inport further
-+ * down in the pipeline.
-+ */
-+ ds_clear(actions);
-+ ds_put_format(actions, REG_INPORT_ETH_ADDR " = %s; next;",
-+ od->l3dgw_port->lrp_networks.ea_s);
-
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 100,
-- ds_cstr(match), ds_cstr(actions),
-- &nat->header_);
-- }
-+ ds_clear(match);
-+ ds_put_format(match,
-+ "eth.dst == "ETH_ADDR_FMT" && inport == %s"
-+ " && is_chassis_resident(\"%s\")",
-+ ETH_ADDR_ARGS(mac),
-+ od->l3dgw_port->json_key,
-+ nat->logical_port);
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ADMISSION, 50,
-+ ds_cstr(match), ds_cstr(actions),
-+ &nat->header_);
-+ }
-+}
-
-- /* Egress SNAT table: Packets enter the egress pipeline with
-- * source ip address that needs to be SNATted to a external ip
-- * address. */
-- if (!strcmp(nat->type, "snat")
-- || !strcmp(nat->type, "dnat_and_snat")) {
-- if (!od->l3dgw_port) {
-- /* Gateway router. */
-- ds_clear(match);
-- ds_put_format(match, "ip && ip%s.src == %s",
-- is_v6 ? "6" : "4",
-- nat->logical_ip);
-- ds_clear(actions);
-+static int
-+lrouter_check_nat_entry(struct ovn_datapath *od, const struct nbrec_nat *nat,
-+ ovs_be32 *mask, bool *is_v6, int *cidr_bits,
-+ struct eth_addr *mac, bool *distributed)
-+{
-+ struct in6_addr ipv6, mask_v6, v6_exact = IN6ADDR_EXACT_INIT;
-+ ovs_be32 ip;
-
-- if (allowed_ext_ips || exempted_ext_ips) {
-- lrouter_nat_add_ext_ip_match(od, lflows, match, nat,
-- is_v6, false, mask);
-- }
-+ if (nat->allowed_ext_ips && nat->exempted_ext_ips) {
-+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-+ VLOG_WARN_RL(&rl, "NAT rule: "UUID_FMT" not applied, since "
-+ "both allowed and exempt external ips set",
-+ UUID_ARGS(&(nat->header_.uuid)));
-+ return -EINVAL;
-+ }
-
-- if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-- ds_put_format(actions, "ip%s.src=%s; next;",
-- is_v6 ? "6" : "4", nat->external_ip);
-- } else {
-- ds_put_format(actions, "ct_snat(%s",
-- nat->external_ip);
-+ char *error = ip_parse_masked(nat->external_ip, &ip, mask);
-+ *is_v6 = false;
-
-- if (nat->external_port_range[0]) {
-- ds_put_format(actions, ",%s",
-- nat->external_port_range);
-- }
-- ds_put_format(actions, ");");
-- }
-+ if (error || *mask != OVS_BE32_MAX) {
-+ free(error);
-+ error = ipv6_parse_masked(nat->external_ip, &ipv6, &mask_v6);
-+ if (error || memcmp(&mask_v6, &v6_exact, sizeof(mask_v6))) {
-+ /* Invalid for both IPv4 and IPv6 */
-+ static struct vlog_rate_limit rl =
-+ VLOG_RATE_LIMIT_INIT(5, 1);
-+ VLOG_WARN_RL(&rl, "bad external ip %s for nat",
-+ nat->external_ip);
-+ free(error);
-+ return -EINVAL;
-+ }
-+ /* It was an invalid IPv4 address, but valid IPv6.
-+ * Treat the rest of the handling of this NAT rule
-+ * as IPv6. */
-+ *is_v6 = true;
-+ }
-
-- /* The priority here is calculated such that the
-- * nat->logical_ip with the longest mask gets a higher
-- * priority. */
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT,
-- cidr_bits + 1,
-- ds_cstr(match), ds_cstr(actions),
-- &nat->header_);
-- } else {
-- uint16_t priority = cidr_bits + 1;
-+ /* Check the validity of nat->logical_ip. 'logical_ip' can
-+ * be a subnet when the type is "snat". */
-+ if (*is_v6) {
-+ error = ipv6_parse_masked(nat->logical_ip, &ipv6, &mask_v6);
-+ *cidr_bits = ipv6_count_cidr_bits(&mask_v6);
-+ } else {
-+ error = ip_parse_masked(nat->logical_ip, &ip, mask);
-+ *cidr_bits = ip_count_cidr_bits(*mask);
-+ }
-+ if (!strcmp(nat->type, "snat")) {
-+ if (error) {
-+ /* Invalid for both IPv4 and IPv6 */
-+ static struct vlog_rate_limit rl =
-+ VLOG_RATE_LIMIT_INIT(5, 1);
-+ VLOG_WARN_RL(&rl, "bad ip network or ip %s for snat "
-+ "in router "UUID_FMT"",
-+ nat->logical_ip, UUID_ARGS(&od->key));
-+ free(error);
-+ return -EINVAL;
-+ }
-+ } else {
-+ if (error || (*is_v6 == false && *mask != OVS_BE32_MAX)
-+ || (*is_v6 && memcmp(&mask_v6, &v6_exact,
-+ sizeof mask_v6))) {
-+ /* Invalid for both IPv4 and IPv6 */
-+ static struct vlog_rate_limit rl =
-+ VLOG_RATE_LIMIT_INIT(5, 1);
-+ VLOG_WARN_RL(&rl, "bad ip %s for dnat in router "
-+ ""UUID_FMT"", nat->logical_ip, UUID_ARGS(&od->key));
-+ free(error);
-+ return -EINVAL;
-+ }
-+ }
-
-- /* Distributed router. */
-- ds_clear(match);
-- ds_put_format(match, "ip && ip%s.src == %s"
-- " && outport == %s",
-- is_v6 ? "6" : "4",
-- nat->logical_ip,
-- od->l3dgw_port->json_key);
-- if (!distributed && od->l3redirect_port) {
-- /* Flows for NAT rules that are centralized are only
-- * programmed on the gateway chassis. */
-- priority += 128;
-- ds_put_format(match, " && is_chassis_resident(%s)",
-- od->l3redirect_port->json_key);
-- }
-- ds_clear(actions);
-+ /* For distributed router NAT, determine whether this NAT rule
-+ * satisfies the conditions for distributed NAT processing. */
-+ *distributed = false;
-+ if (od->l3dgw_port && !strcmp(nat->type, "dnat_and_snat") &&
-+ nat->logical_port && nat->external_mac) {
-+ if (eth_addr_from_string(nat->external_mac, mac)) {
-+ *distributed = true;
-+ } else {
-+ static struct vlog_rate_limit rl =
-+ VLOG_RATE_LIMIT_INIT(5, 1);
-+ VLOG_WARN_RL(&rl, "bad mac %s for dnat in router "
-+ ""UUID_FMT"", nat->external_mac, UUID_ARGS(&od->key));
-+ return -EINVAL;
-+ }
-+ }
-
-- if (allowed_ext_ips || exempted_ext_ips) {
-- lrouter_nat_add_ext_ip_match(od, lflows, match, nat,
-- is_v6, false, mask);
-- }
-+ return 0;
-+}
-
-- if (distributed) {
-- ds_put_format(actions, "eth.src = "ETH_ADDR_FMT"; ",
-- ETH_ADDR_ARGS(mac));
-- }
-+/* NAT, Defrag and load balancing. */
-+static void
-+build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od,
-+ struct hmap *lflows,
-+ struct shash *meter_groups,
-+ struct hmap *lbs,
-+ struct ds *match, struct ds *actions)
-+{
-+ if (!od->nbr) {
-+ return;
-+ }
-
-- if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
-- ds_put_format(actions, "ip%s.src=%s; next;",
-- is_v6 ? "6" : "4", nat->external_ip);
-- } else {
-- ds_put_format(actions, "ct_snat(%s",
-- nat->external_ip);
-- if (nat->external_port_range[0]) {
-- ds_put_format(actions, ",%s",
-- nat->external_port_range);
-- }
-- ds_put_format(actions, ");");
-- }
-+ /* Packets are allowed by default. */
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_DEFRAG, 0, "1", "next;");
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 0, "1", "next;");
-+ ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 0, "1", "next;");
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 0, "1", "next;");
-+ ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 0, "1", "next;");
-+ ovn_lflow_add(lflows, od, S_ROUTER_OUT_EGR_LOOP, 0, "1", "next;");
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 0, "1", "next;");
-+
-+ /* Send the IPv6 NS packets to next table. When ovn-controller
-+ * generates IPv6 NS (for the action - nd_ns{}), the injected
-+ * packet would go through conntrack - which is not required. */
-+ ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 120, "nd_ns", "next;");
-+
-+ /* NAT rules are only valid on Gateway routers and routers with
-+ * l3dgw_port (router has a port with gateway chassis
-+ * specified). */
-+ if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) {
-+ return;
-+ }
-
-- /* The priority here is calculated such that the
-- * nat->logical_ip with the longest mask gets a higher
-- * priority. */
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT,
-- priority, ds_cstr(match),
-- ds_cstr(actions),
-- &nat->header_);
-- }
-- }
-+ struct sset nat_entries = SSET_INITIALIZER(&nat_entries);
-
-- /* Logical router ingress table 0:
-- * For NAT on a distributed router, add rules allowing
-- * ingress traffic with eth.dst matching nat->external_mac
-- * on the l3dgw_port instance where nat->logical_port is
-- * resident. */
-- if (distributed) {
-- /* Store the ethernet address of the port receiving the packet.
-- * This will save us from having to match on inport further
-- * down in the pipeline.
-- */
-- ds_clear(actions);
-- ds_put_format(actions, REG_INPORT_ETH_ADDR " = %s; next;",
-- od->l3dgw_port->lrp_networks.ea_s);
-+ bool dnat_force_snat_ip =
-+ !lport_addresses_is_empty(&od->dnat_force_snat_addrs);
-+ bool lb_force_snat_ip =
-+ !lport_addresses_is_empty(&od->lb_force_snat_addrs);
-
-- ds_clear(match);
-- ds_put_format(match,
-- "eth.dst == "ETH_ADDR_FMT" && inport == %s"
-- " && is_chassis_resident(\"%s\")",
-- ETH_ADDR_ARGS(mac),
-- od->l3dgw_port->json_key,
-- nat->logical_port);
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ADMISSION, 50,
-- ds_cstr(match), ds_cstr(actions),
-- &nat->header_);
-- }
-+ for (int i = 0; i < od->nbr->n_nat; i++) {
-+ const struct nbrec_nat *nat = nat = od->nbr->nat[i];
-+ struct eth_addr mac = eth_addr_broadcast;
-+ bool is_v6, distributed;
-+ ovs_be32 mask;
-+ int cidr_bits;
-
-- /* Ingress Gateway Redirect Table: For NAT on a distributed
-- * router, add flows that are specific to a NAT rule. These
-- * flows indicate the presence of an applicable NAT rule that
-- * can be applied in a distributed manner.
-- * In particulr REG_SRC_IPV4/REG_SRC_IPV6 and eth.src are set to
-- * NAT external IP and NAT external mac so the ARP request
-- * generated in the following stage is sent out with proper IP/MAC
-- * src addresses.
-- */
-- if (distributed) {
-- ds_clear(match);
-- ds_clear(actions);
-- ds_put_format(match,
-- "ip%s.src == %s && outport == %s && "
-- "is_chassis_resident(\"%s\")",
-- is_v6 ? "6" : "4", nat->logical_ip,
-- od->l3dgw_port->json_key, nat->logical_port);
-- ds_put_format(actions, "eth.src = %s; %s = %s; next;",
-- nat->external_mac,
-- is_v6 ? REG_SRC_IPV6 : REG_SRC_IPV4,
-- nat->external_ip);
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_GW_REDIRECT,
-- 100, ds_cstr(match),
-- ds_cstr(actions), &nat->header_);
-- }
-+ if (lrouter_check_nat_entry(od, nat, &mask, &is_v6, &cidr_bits,
-+ &mac, &distributed) < 0) {
-+ continue;
-+ }
-
-- /* Egress Loopback table: For NAT on a distributed router.
-- * If packets in the egress pipeline on the distributed
-- * gateway port have ip.dst matching a NAT external IP, then
-- * loop a clone of the packet back to the beginning of the
-- * ingress pipeline with inport = outport. */
-- if (od->l3dgw_port) {
-- /* Distributed router. */
-- ds_clear(match);
-- ds_put_format(match, "ip%s.dst == %s && outport == %s",
-- is_v6 ? "6" : "4",
-- nat->external_ip,
-- od->l3dgw_port->json_key);
-- if (!distributed) {
-- ds_put_format(match, " && is_chassis_resident(%s)",
-- od->l3redirect_port->json_key);
-- } else {
-- ds_put_format(match, " && is_chassis_resident(\"%s\")",
-- nat->logical_port);
-- }
-+ /* S_ROUTER_IN_UNSNAT */
-+ build_lrouter_in_unsnat_flow(lflows, od, nat, match, actions, distributed,
-+ is_v6);
-+ /* S_ROUTER_IN_DNAT */
-+ build_lrouter_in_dnat_flow(lflows, od, nat, match, actions, distributed,
-+ mask, is_v6);
-
-+ /* ARP resolve for NAT IPs. */
-+ if (od->l3dgw_port) {
-+ if (!sset_contains(&nat_entries, nat->external_ip)) {
-+ ds_clear(match);
-+ ds_put_format(
-+ match, "outport == %s && %s == %s",
-+ od->l3dgw_port->json_key,
-+ is_v6 ? REG_NEXT_HOP_IPV6 : REG_NEXT_HOP_IPV4,
-+ nat->external_ip);
- ds_clear(actions);
-- ds_put_format(actions,
-- "clone { ct_clear; "
-- "inport = outport; outport = \"\"; "
-- "flags = 0; flags.loopback = 1; ");
-- for (int j = 0; j < MFF_N_LOG_REGS; j++) {
-- ds_put_format(actions, "reg%d = 0; ", j);
-- }
-- ds_put_format(actions, REGBIT_EGRESS_LOOPBACK" = 1; "
-- "next(pipeline=ingress, table=%d); };",
-- ovn_stage_get_table(S_ROUTER_IN_ADMISSION));
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_EGR_LOOP, 100,
-- ds_cstr(match), ds_cstr(actions),
-+ ds_put_format(
-+ actions, "eth.dst = %s; next;",
-+ distributed ? nat->external_mac :
-+ od->l3dgw_port->lrp_networks.ea_s);
-+ ovn_lflow_add_with_hint(lflows, od,
-+ S_ROUTER_IN_ARP_RESOLVE,
-+ 100, ds_cstr(match),
-+ ds_cstr(actions),
- &nat->header_);
-+ sset_add(&nat_entries, nat->external_ip);
- }
-- }
--
-- /* Handle force SNAT options set in the gateway router. */
-- if (!od->l3dgw_port) {
-- if (dnat_force_snat_ip) {
-- if (od->dnat_force_snat_addrs.n_ipv4_addrs) {
-- build_lrouter_force_snat_flows(lflows, od, "4",
-- od->dnat_force_snat_addrs.ipv4_addrs[0].addr_s,
-- "dnat");
-- }
-- if (od->dnat_force_snat_addrs.n_ipv6_addrs) {
-- build_lrouter_force_snat_flows(lflows, od, "6",
-- od->dnat_force_snat_addrs.ipv6_addrs[0].addr_s,
-- "dnat");
-- }
-- }
-- if (lb_force_snat_ip) {
-- if (od->lb_force_snat_addrs.n_ipv4_addrs) {
-- build_lrouter_force_snat_flows(lflows, od, "4",
-- od->lb_force_snat_addrs.ipv4_addrs[0].addr_s, "lb");
-- }
-- if (od->lb_force_snat_addrs.n_ipv6_addrs) {
-- build_lrouter_force_snat_flows(lflows, od, "6",
-- od->lb_force_snat_addrs.ipv6_addrs[0].addr_s, "lb");
-- }
-+ } else {
-+ /* Add the NAT external_ip to the nat_entries even for
-+ * gateway routers. This is required for adding load balancer
-+ * flows.*/
-+ sset_add(&nat_entries, nat->external_ip);
-+ }
-+
-+ /* S_ROUTER_OUT_UNDNAT */
-+ build_lrouter_out_undnat_flow(lflows, od, nat, match, actions, distributed,
-+ mac, is_v6);
-+ /* S_ROUTER_OUT_SNAT */
-+ build_lrouter_out_snat_flow(lflows, od, nat, match, actions, distributed,
-+ mac, mask, cidr_bits, is_v6);
-+
-+ /* S_ROUTER_IN_ADMISSION - S_ROUTER_IN_IP_INPUT */
-+ build_lrouter_ingress_flow(lflows, od, nat, match, actions,
-+ mac, distributed, is_v6);
-+
-+ /* Ingress Gateway Redirect Table: For NAT on a distributed
-+ * router, add flows that are specific to a NAT rule. These
-+ * flows indicate the presence of an applicable NAT rule that
-+ * can be applied in a distributed manner.
-+ * In particulr REG_SRC_IPV4/REG_SRC_IPV6 and eth.src are set to
-+ * NAT external IP and NAT external mac so the ARP request
-+ * generated in the following stage is sent out with proper IP/MAC
-+ * src addresses.
-+ */
-+ if (distributed) {
-+ ds_clear(match);
-+ ds_clear(actions);
-+ ds_put_format(match,
-+ "ip%s.src == %s && outport == %s && "
-+ "is_chassis_resident(\"%s\")",
-+ is_v6 ? "6" : "4", nat->logical_ip,
-+ od->l3dgw_port->json_key, nat->logical_port);
-+ ds_put_format(actions, "eth.src = %s; %s = %s; next;",
-+ nat->external_mac,
-+ is_v6 ? REG_SRC_IPV6 : REG_SRC_IPV4,
-+ nat->external_ip);
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_GW_REDIRECT,
-+ 100, ds_cstr(match),
-+ ds_cstr(actions), &nat->header_);
-+ }
-+
-+ /* Egress Loopback table: For NAT on a distributed router.
-+ * If packets in the egress pipeline on the distributed
-+ * gateway port have ip.dst matching a NAT external IP, then
-+ * loop a clone of the packet back to the beginning of the
-+ * ingress pipeline with inport = outport. */
-+ if (od->l3dgw_port) {
-+ /* Distributed router. */
-+ ds_clear(match);
-+ ds_put_format(match, "ip%s.dst == %s && outport == %s",
-+ is_v6 ? "6" : "4",
-+ nat->external_ip,
-+ od->l3dgw_port->json_key);
-+ if (!distributed) {
-+ ds_put_format(match, " && is_chassis_resident(%s)",
-+ od->l3redirect_port->json_key);
-+ } else {
-+ ds_put_format(match, " && is_chassis_resident(\"%s\")",
-+ nat->logical_port);
- }
-
-- /* For gateway router, re-circulate every packet through
-- * the DNAT zone. This helps with the following.
-- *
-- * Any packet that needs to be unDNATed in the reverse
-- * direction gets unDNATed. Ideally this could be done in
-- * the egress pipeline. But since the gateway router
-- * does not have any feature that depends on the source
-- * ip address being external IP address for IP routing,
-- * we can do it here, saving a future re-circulation. */
-- ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 50,
-- "ip", "flags.loopback = 1; ct_dnat;");
-+ ds_clear(actions);
-+ ds_put_format(actions,
-+ "clone { ct_clear; "
-+ "inport = outport; outport = \"\"; "
-+ "flags = 0; flags.loopback = 1; ");
-+ for (int j = 0; j < MFF_N_LOG_REGS; j++) {
-+ ds_put_format(actions, "reg%d = 0; ", j);
-+ }
-+ ds_put_format(actions, REGBIT_EGRESS_LOOPBACK" = 1; "
-+ "next(pipeline=ingress, table=%d); };",
-+ ovn_stage_get_table(S_ROUTER_IN_ADMISSION));
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_EGR_LOOP, 100,
-+ ds_cstr(match), ds_cstr(actions),
-+ &nat->header_);
- }
-+ }
-
-- /* Load balancing and packet defrag are only valid on
-- * Gateway routers or router with gateway port. */
-- if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) {
-- sset_destroy(&nat_entries);
-- return;
-+ /* Handle force SNAT options set in the gateway router. */
-+ if (!od->l3dgw_port) {
-+ if (dnat_force_snat_ip) {
-+ if (od->dnat_force_snat_addrs.n_ipv4_addrs) {
-+ build_lrouter_force_snat_flows(lflows, od, "4",
-+ od->dnat_force_snat_addrs.ipv4_addrs[0].addr_s,
-+ "dnat");
-+ }
-+ if (od->dnat_force_snat_addrs.n_ipv6_addrs) {
-+ build_lrouter_force_snat_flows(lflows, od, "6",
-+ od->dnat_force_snat_addrs.ipv6_addrs[0].addr_s,
-+ "dnat");
-+ }
- }
--
-- /* A set to hold all ips that need defragmentation and tracking. */
-- struct sset all_ips = SSET_INITIALIZER(&all_ips);
--
-- for (int i = 0; i < od->nbr->n_load_balancer; i++) {
-- struct nbrec_load_balancer *nb_lb = od->nbr->load_balancer[i];
-- struct ovn_northd_lb *lb =
-- ovn_northd_lb_find(lbs, &nb_lb->header_.uuid);
-- ovs_assert(lb);
--
-- for (size_t j = 0; j < lb->n_vips; j++) {
-- struct ovn_lb_vip *lb_vip = &lb->vips[j];
-- struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[j];
-- ds_clear(actions);
-- build_lb_vip_actions(lb_vip, lb_vip_nb, actions,
-- lb->selection_fields, false);
--
-- if (!sset_contains(&all_ips, lb_vip->vip_str)) {
-- sset_add(&all_ips, lb_vip->vip_str);
-- /* If there are any load balancing rules, we should send
-- * the packet to conntrack for defragmentation and
-- * tracking. This helps with two things.
-- *
-- * 1. With tracking, we can send only new connections to
-- * pick a DNAT ip address from a group.
-- * 2. If there are L4 ports in load balancing rules, we
-- * need the defragmentation to match on L4 ports. */
-- ds_clear(match);
-- if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
-- ds_put_format(match, "ip && ip4.dst == %s",
-- lb_vip->vip_str);
-- } else {
-- ds_put_format(match, "ip && ip6.dst == %s",
-- lb_vip->vip_str);
-- }
-- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DEFRAG,
-- 100, ds_cstr(match), "ct_next;",
-- &nb_lb->header_);
-- }
--
-- /* Higher priority rules are added for load-balancing in DNAT
-- * table. For every match (on a VIP[:port]), we add two flows
-- * via add_router_lb_flow(). One flow is for specific matching
-- * on ct.new with an action of "ct_lb($targets);". The other
-- * flow is for ct.est with an action of "ct_dnat;". */
-- ds_clear(match);
-- if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
-- ds_put_format(match, "ip && ip4.dst == %s",
-- lb_vip->vip_str);
-- } else {
-- ds_put_format(match, "ip && ip6.dst == %s",
-- lb_vip->vip_str);
-- }
--
-- int prio = 110;
-- bool is_udp = nullable_string_is_equal(nb_lb->protocol, "udp");
-- bool is_sctp = nullable_string_is_equal(nb_lb->protocol,
-- "sctp");
-- const char *proto = is_udp ? "udp" : is_sctp ? "sctp" : "tcp";
--
-- if (lb_vip->vip_port) {
-- ds_put_format(match, " && %s && %s.dst == %d", proto,
-- proto, lb_vip->vip_port);
-- prio = 120;
-- }
--
-- if (od->l3redirect_port &&
-- (lb_vip->n_backends || !lb_vip->empty_backend_rej)) {
-- ds_put_format(match, " && is_chassis_resident(%s)",
-- od->l3redirect_port->json_key);
-- }
-- bool force_snat_for_lb =
-- lb_force_snat_ip || od->lb_force_snat_router_ip;
-- add_router_lb_flow(lflows, od, match, actions, prio,
-- force_snat_for_lb, lb_vip, proto,
-- nb_lb, meter_groups, &nat_entries);
-+ if (lb_force_snat_ip) {
-+ if (od->lb_force_snat_addrs.n_ipv4_addrs) {
-+ build_lrouter_force_snat_flows(lflows, od, "4",
-+ od->lb_force_snat_addrs.ipv4_addrs[0].addr_s, "lb");
-+ }
-+ if (od->lb_force_snat_addrs.n_ipv6_addrs) {
-+ build_lrouter_force_snat_flows(lflows, od, "6",
-+ od->lb_force_snat_addrs.ipv6_addrs[0].addr_s, "lb");
- }
- }
-- sset_destroy(&all_ips);
-+
-+ /* For gateway router, re-circulate every packet through
-+ * the DNAT zone. This helps with the following.
-+ *
-+ * Any packet that needs to be unDNATed in the reverse
-+ * direction gets unDNATed. Ideally this could be done in
-+ * the egress pipeline. But since the gateway router
-+ * does not have any feature that depends on the source
-+ * ip address being external IP address for IP routing,
-+ * we can do it here, saving a future re-circulation. */
-+ ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 50,
-+ "ip", "flags.loopback = 1; ct_dnat;");
-+ }
-+
-+ /* Load balancing and packet defrag are only valid on
-+ * Gateway routers or router with gateway port. */
-+ if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) {
- sset_destroy(&nat_entries);
-+ return;
- }
-+
-+ build_lrouter_lb_flows(lflows, od, lbs, meter_groups, &nat_entries,
-+ match, actions);
-+
-+ sset_destroy(&nat_entries);
- }
-
-
-@@ -12909,6 +12991,9 @@ ovnnb_db_run(struct northd_context *ctx,
-
- use_logical_dp_groups = smap_get_bool(&nb->options,
- "use_logical_dp_groups", false);
-+ use_ct_inv_match = smap_get_bool(&nb->options,
-+ "use_ct_inv_match", true);
-+
- /* deprecated, use --event instead */
- controller_event_en = smap_get_bool(&nb->options,
- "controller_event", false);
-diff --git a/ovn-nb.xml b/ovn-nb.xml
-index b0a4adffe..046d053e9 100644
---- a/ovn-nb.xml
-+++ b/ovn-nb.xml
-@@ -227,6 +227,21 @@
-
-
-
-+
-+
-+ If set to false, ovn-northd
will not use the
-+ ct.inv
field in any of the logical flow matches.
-+ The default value is true. If the NIC supports offloading
-+ OVS datapath flows but doesn't support offloading ct_state
-+ inv
flag, then the datapath flows matching on this flag
-+ (either +inv
or -inv
) will not be
-+ offloaded. CMS should consider setting use_ct_inv_match
-+ to false
in such cases. This results in a side effect
-+ of the invalid packets getting delivered to the destination VIF,
-+ which otherwise would have been dropped by OVN
.
-+
-+
-+
-
-
- These options control how routes are advertised between OVN
-@@ -1653,6 +1668,12 @@
- exactly one IPv4 and/or one IPv6 address on it, separated by a space
- character.
-
-+
-+
-+ If the load balancing rule is configured with skip_snat
-+ option, the force_snat_for_lb option configured for the router
-+ pipeline will not be applied for this load balancer.
-+
-
-
-
-diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at
-index 2cd3e261f..5c64fff12 100644
---- a/tests/ovn-controller.at
-+++ b/tests/ovn-controller.at
-@@ -431,3 +431,83 @@ OVS_WAIT_UNTIL([
-
- OVN_CLEANUP([hv1])
- AT_CLEANUP
-+
-+# Test that changes of a port binding from one type to another doesn'that
-+# result in any ovn-controller asserts or crashes.
-+AT_SETUP([ovn-controller - port binding type change handling])
-+AT_KEYWORDS([ovn])
-+ovn_start
-+
-+net_add n1
-+sim_add hv1
-+ovs-vsctl add-br br-phys
-+ovn_attach n1 br-phys 192.168.0.1
-+
-+check ovn-nbctl ls-add ls1 -- lsp-add ls1 lsp1
-+
-+as hv1
-+check ovs-vsctl \
-+ -- add-port br-int vif1 \
-+ -- set Interface vif1 external_ids:iface-id=lsp1
-+
-+# ovn-controller should bind the interface.
-+wait_for_ports_up
-+hv_uuid=$(fetch_column Chassis _uuid name=hv1)
-+check_column "$hv_uuid" Port_Binding chassis logical_port=lsp1
-+
-+AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
-+Local bindings:
-+name: [[lsp1]], OVS interface name : [[vif1]], num binding lports : [[1]]
-+primary lport : [[lsp1]]
-+----------------------------------------
-+])
-+
-+# pause ovn-northd
-+check as northd ovn-appctl -t ovn-northd pause
-+check as northd-backup ovn-appctl -t ovn-northd pause
-+
-+as northd ovn-appctl -t ovn-northd status
-+as northd-backup ovn-appctl -t ovn-northd status
-+
-+pb_types=(patch chassisredirect l3gateway localnet localport l2gateway
-+ virtual external remote vtep)
-+for type in ${pb_types[[@]]}
-+do
-+ for update_type in ${pb_types[[@]]}
-+ do
-+ check ovn-sbctl set port_binding lsp1 type=$type
-+ check as hv1 ovs-vsctl set open . external_ids:ovn-cms-options=$type
-+ OVS_WAIT_UNTIL([test $type = $(ovn-sbctl get chassis . other_config:ovn-cms-options)])
-+
-+ AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
-+Local bindings:
-+name: [[lsp1]], OVS interface name : [[vif1]], num binding lports : [[0]]
-+----------------------------------------
-+])
-+
-+ echo "Updating to $update_type from $type"
-+ check ovn-sbctl set port_binding lsp1 type=$update_type
-+ check as hv1 ovs-vsctl set open . external_ids:ovn-cms-options=$update_type
-+ OVS_WAIT_UNTIL([test $update_type = $(ovn-sbctl get chassis . other_config:ovn-cms-options)])
-+
-+ AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
-+Local bindings:
-+name: [[lsp1]], OVS interface name : [[vif1]], num binding lports : [[0]]
-+----------------------------------------
-+])
-+ # Set the port binding type back to VIF.
-+ check ovn-sbctl set port_binding lsp1 type=\"\"
-+ check as hv1 ovs-vsctl set open . external_ids:ovn-cms-options=foo
-+ OVS_WAIT_UNTIL([test foo = $(ovn-sbctl get chassis . other_config:ovn-cms-options)])
-+
-+ AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
-+Local bindings:
-+name: [[lsp1]], OVS interface name : [[vif1]], num binding lports : [[1]]
-+primary lport : [[lsp1]]
-+----------------------------------------
-+])
-+ done
-+done
-+
-+OVN_CLEANUP([hv1])
-+AT_CLEANUP
-diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at
-index 2ba29a960..4cf14b1f2 100644
---- a/tests/ovn-macros.at
-+++ b/tests/ovn-macros.at
-@@ -433,6 +433,24 @@ wait_for_ports_up() {
- done
- fi
- }
-+
-+# reset_pcap_file iface pcap_file
-+# Resets the pcap file associates with OVS interface. should be used
-+# with dummy datapath.
-+reset_iface_pcap_file() {
-+ local iface=$1
-+ local pcap_file=$2
-+ check rm -f dummy-*.pcap
-+ check ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
-+options:rxq_pcap=dummy-rx.pcap
-+ OVS_WAIT_WHILE([test 24 = $(wc -c dummy-tx.pcap | cut -d " " -f1)])
-+ check rm -f ${pcap_file}*.pcap
-+ check ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \
-+options:rxq_pcap=${pcap_file}-rx.pcap
-+
-+ OVS_WAIT_WHILE([test 24 = $(wc -c ${pcap_file}-tx.pcap | cut -d " " -f1)])
-+}
-+
- OVS_END_SHELL_HELPERS
-
- m4_define([OVN_POPULATE_ARP], [AT_CHECK(ovn_populate_arp__, [0], [ignore])])
-diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
-index 6d91aa4c5..8af55161f 100644
---- a/tests/ovn-nbctl.at
-+++ b/tests/ovn-nbctl.at
-@@ -1551,6 +1551,7 @@ IPv4 Routes
- AT_CHECK([ovn-nbctl --ecmp lr-route-add lr0 10.0.0.0/24 11.0.0.2], [1], [],
- [ovn-nbctl: duplicate nexthop for the same ECMP route
- ])
-+AT_CHECK([ovn-nbctl --may-exist --ecmp lr-route-add lr0 10.0.0.0/24 11.0.0.2])
-
- dnl Delete ecmp routes
- AT_CHECK([ovn-nbctl lr-route-del lr0 10.0.0.0/24 11.0.0.1])
-@@ -1614,6 +1615,7 @@ AT_CHECK([ovn-nbctl --ecmp-symmetric-reply lr-route-add lr0 2003:0db8:1::/64 200
- AT_CHECK([ovn-nbctl --ecmp-symmetric-reply lr-route-add lr0 2003:0db8:1::/64 2001:0db8:0:f103::6], [1], [],
- [ovn-nbctl: duplicate nexthop for the same ECMP route
- ])
-+AT_CHECK([ovn-nbctl --may-exist --ecmp-symmetric-reply lr-route-add lr0 2003:0db8:1::/64 2001:0db8:0:f103::6])
-
- AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl
- IPv4 Routes
-diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
-index b78baa708..8ca915302 100644
---- a/tests/ovn-northd.at
-+++ b/tests/ovn-northd.at
-@@ -1077,7 +1077,7 @@ check ovn-nbctl --wait=sb ls-lb-add sw0 lb1
-
- AT_CAPTURE_FILE([sbflows])
- OVS_WAIT_FOR_OUTPUT(
-- [ovn-sbctl dump-flows sw0 | tee sbflows | grep 'priority=120.*ct_lb' | sed 's/table=..//'], 0, [dnl
-+ [ovn-sbctl dump-flows sw0 | tee sbflows | grep 'priority=120.*backends' | sed 's/table=..//'], 0, [dnl
- (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
- ])
-
-@@ -1087,7 +1087,7 @@ wait_row_count Service_Monitor 0
-
- AT_CAPTURE_FILE([sbflows2])
- OVS_WAIT_FOR_OUTPUT(
-- [ovn-sbctl dump-flows sw0 | tee sbflows2 | grep 'priority=120.*ct_lb' | sed 's/table=..//'], [0],
-+ [ovn-sbctl dump-flows sw0 | tee sbflows2 | grep 'priority=120.*backends' | sed 's/table=..//'], [0],
- [ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
- ])
-
-@@ -1098,7 +1098,7 @@ health_check @hc
- wait_row_count Service_Monitor 2
- check ovn-nbctl --wait=sb sync
-
--ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
-+ovn-sbctl dump-flows sw0 | grep backends | grep priority=120 > lflows.txt
- AT_CHECK([cat lflows.txt | sed 's/table=..//'], [0], [dnl
- (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
- ])
-@@ -1109,7 +1109,7 @@ sm_sw1_p1=$(fetch_column Service_Monitor _uuid logical_port=sw1-p1)
-
- AT_CAPTURE_FILE([sbflows3])
- OVS_WAIT_FOR_OUTPUT(
-- [ovn-sbctl dump-flows sw0 | tee sbflows 3 | grep 'priority=120.*ct_lb' | sed 's/table=..//'], [0],
-+ [ovn-sbctl dump-flows sw0 | tee sbflows 3 | grep 'priority=120.*backends' | sed 's/table=..//'], [0],
- [ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
- ])
-
-@@ -1120,7 +1120,7 @@ check ovn-nbctl --wait=sb sync
-
- AT_CAPTURE_FILE([sbflows4])
- OVS_WAIT_FOR_OUTPUT(
-- [ovn-sbctl dump-flows sw0 | tee sbflows4 | grep 'priority=120.*ct_lb' | sed 's/table=..//'], [0],
-+ [ovn-sbctl dump-flows sw0 | tee sbflows4 | grep 'priority=120.*backends' | sed 's/table=..//'], [0],
- [ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80);)
- ])
-
-@@ -1132,7 +1132,7 @@ check ovn-nbctl --wait=sb sync
-
- AT_CAPTURE_FILE([sbflows5])
- OVS_WAIT_FOR_OUTPUT(
-- [ovn-sbctl dump-flows sw0 | tee sbflows5 | grep 'priority=120.*ct_lb'], 1)
-+ [ovn-sbctl dump-flows sw0 | tee sbflows5 | grep 'priority=120.*backends'], 1)
-
- AT_CAPTURE_FILE([sbflows6])
- OVS_WAIT_FOR_OUTPUT(
-@@ -1149,7 +1149,7 @@ check ovn-nbctl --wait=sb sync
-
- AT_CAPTURE_FILE([sbflows7])
- OVS_WAIT_FOR_OUTPUT(
-- [ovn-sbctl dump-flows sw0 | tee sbflows7 | grep ct_lb | grep priority=120 | sed 's/table=..//'], 0,
-+ [ovn-sbctl dump-flows sw0 | tee sbflows7 | grep backends | grep priority=120 | sed 's/table=..//'], 0,
- [ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
- ])
-
-@@ -1185,7 +1185,7 @@ wait_row_count Service_Monitor 1 port=1000
-
- AT_CAPTURE_FILE([sbflows9])
- OVS_WAIT_FOR_OUTPUT(
-- [ovn-sbctl dump-flows sw0 | tee sbflows9 | grep ct_lb | grep priority=120 | sed 's/table=..//' | sort],
-+ [ovn-sbctl dump-flows sw0 | tee sbflows9 | grep backends | grep priority=120 | sed 's/table=..//' | sort],
- 0,
- [ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80);)
- (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(reg1 = 10.0.0.40; reg2[[0..15]] = 1000; ct_lb(backends=10.0.0.3:1000);)
-@@ -1199,7 +1199,7 @@ check ovn-nbctl --wait=sb sync
-
- AT_CAPTURE_FILE([sbflows10])
- OVS_WAIT_FOR_OUTPUT(
-- [ovn-sbctl dump-flows sw0 | tee sbflows10 | grep ct_lb | grep priority=120 | sed 's/table=..//' | sort],
-+ [ovn-sbctl dump-flows sw0 | tee sbflows10 | grep backends | grep priority=120 | sed 's/table=..//' | sort],
- 0,
- [ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
- (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(reg1 = 10.0.0.40; reg2[[0..15]] = 1000; ct_lb(backends=10.0.0.3:1000,20.0.0.3:80);)
-@@ -1209,7 +1209,7 @@ AS_BOX([Associate lb1 to sw1])
- check ovn-nbctl --wait=sb ls-lb-add sw1 lb1
- AT_CAPTURE_FILE([sbflows11])
- OVS_WAIT_FOR_OUTPUT(
-- [ovn-sbctl dump-flows sw1 | tee sbflows11 | grep ct_lb | grep priority=120 | sed 's/table=..//' | sort],
-+ [ovn-sbctl dump-flows sw1 | tee sbflows11 | grep backends | grep priority=120 | sed 's/table=..//' | sort],
- 0, [dnl
- (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
- (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(reg1 = 10.0.0.40; reg2[[0..15]] = 1000; ct_lb(backends=10.0.0.3:1000,20.0.0.3:80);)
-@@ -1269,7 +1269,7 @@ ovn-sbctl set service_monitor $sm_sw1_p1 status=offline
- AT_CAPTURE_FILE([sbflows12])
- OVS_WAIT_FOR_OUTPUT(
- [ovn-sbctl dump-flows sw0 | tee sbflows12 | grep "ip4.dst == 10.0.0.10 && tcp.dst == 80" | grep priority=120 | sed 's/table=..//'], [0], [dnl
-- (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg0 = 0; reject { outport <-> inport; next(pipeline=egress,table=6);};)
-+ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg0 = 0; reject { outport <-> inport; next(pipeline=egress,table=5);};)
- ])
-
- AT_CLEANUP
-@@ -1504,6 +1504,19 @@ ovn-nbctl lr-nat-add lr dnat_and_snat 43.43.43.4 42.42.42.4 ls-vm 00:00:00:00:00
- ovn-nbctl lr-nat-add lr snat 43.43.43.150 43.43.43.50
- ovn-nbctl lr-nat-add lr snat 43.43.43.150 43.43.43.51
-
-+ovn-nbctl lb-add lb1 "192.168.2.1:8080" "10.0.0.4:8080"
-+ovn-nbctl lb-add lb2 "192.168.2.4:8080" "10.0.0.5:8080" udp
-+ovn-nbctl lb-add lb3 "192.168.2.5:8080" "10.0.0.6:8080"
-+ovn-nbctl lb-add lb4 "192.168.2.6:8080" "10.0.0.7:8080"
-+ovn-nbctl lb-add lb5 "fe80::200:ff:fe00:101:8080" "fe02::200:ff:fe00:101:8080"
-+ovn-nbctl lb-add lb5 "fe80::200:ff:fe00:102:8080" "fe02::200:ff:fe00:102:8080"
-+
-+ovn-nbctl lr-lb-add lr lb1
-+ovn-nbctl lr-lb-add lr lb2
-+ovn-nbctl lr-lb-add lr lb3
-+ovn-nbctl lr-lb-add lr lb4
-+ovn-nbctl lr-lb-add lr lb5
-+
- ovn-nbctl --wait=sb sync
-
- # Ingress router port ETH address is stored in lr_in_admission.
-@@ -1526,28 +1539,46 @@ action=(xreg0[[0..47]] = 00:00:00:00:01:00; next;)
- AT_CHECK([ovn-sbctl lflow-list | grep -E "lr_in_ip_input.*priority=90" | grep "arp\|nd" | sort], [0], [dnl
- table=3 (lr_in_ip_input ), priority=90 , dnl
- match=(arp.op == 1 && arp.tpa == 43.43.43.150), dnl
--action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa = arp.spa; arp.spa = 43.43.43.150; outport = inport; flags.loopback = 1; output;)
-+action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;)
- table=3 (lr_in_ip_input ), priority=90 , dnl
- match=(arp.op == 1 && arp.tpa == 43.43.43.2), dnl
--action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa = arp.spa; arp.spa = 43.43.43.2; outport = inport; flags.loopback = 1; output;)
-+action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;)
- table=3 (lr_in_ip_input ), priority=90 , dnl
- match=(arp.op == 1 && arp.tpa == 43.43.43.3), dnl
--action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa = arp.spa; arp.spa = 43.43.43.3; outport = inport; flags.loopback = 1; output;)
-+action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;)
- table=3 (lr_in_ip_input ), priority=90 , dnl
- match=(arp.op == 1 && arp.tpa == 43.43.43.4), dnl
--action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa = arp.spa; arp.spa = 43.43.43.4; outport = inport; flags.loopback = 1; output;)
-+action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;)
- table=3 (lr_in_ip_input ), priority=90 , dnl
- match=(inport == "lrp" && arp.op == 1 && arp.tpa == 42.42.42.1 && arp.spa == 42.42.42.0/24), dnl
--action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa = arp.spa; arp.spa = 42.42.42.1; outport = inport; flags.loopback = 1; output;)
-+action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;)
-+ table=3 (lr_in_ip_input ), priority=90 , dnl
-+match=(inport == "lrp" && arp.op == 1 && arp.tpa == { 192.168.2.1, 192.168.2.4, 192.168.2.5, 192.168.2.6 }), dnl
-+action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;)
- table=3 (lr_in_ip_input ), priority=90 , dnl
- match=(inport == "lrp" && ip6.dst == {fe80::200:ff:fe00:1, ff02::1:ff00:1} && nd_ns && nd.target == fe80::200:ff:fe00:1), dnl
- action=(nd_na_router { eth.src = xreg0[[0..47]]; ip6.src = fe80::200:ff:fe00:1; nd.target = fe80::200:ff:fe00:1; nd.tll = xreg0[[0..47]]; outport = inport; flags.loopback = 1; output; };)
- table=3 (lr_in_ip_input ), priority=90 , dnl
-+match=(inport == "lrp" && nd_ns && nd.target == fe80::200:ff:fe00:101:8080), dnl
-+action=(nd_na { eth.src = xreg0[[0..47]]; ip6.src = fe80::200:ff:fe00:101:8080; nd.target = fe80::200:ff:fe00:101:8080; nd.tll = xreg0[[0..47]]; outport = inport; flags.loopback = 1; output; };)
-+ table=3 (lr_in_ip_input ), priority=90 , dnl
-+match=(inport == "lrp" && nd_ns && nd.target == fe80::200:ff:fe00:102:8080), dnl
-+action=(nd_na { eth.src = xreg0[[0..47]]; ip6.src = fe80::200:ff:fe00:102:8080; nd.target = fe80::200:ff:fe00:102:8080; nd.tll = xreg0[[0..47]]; outport = inport; flags.loopback = 1; output; };)
-+ table=3 (lr_in_ip_input ), priority=90 , dnl
- match=(inport == "lrp-public" && arp.op == 1 && arp.tpa == 43.43.43.1 && arp.spa == 43.43.43.0/24), dnl
--action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa = arp.spa; arp.spa = 43.43.43.1; outport = inport; flags.loopback = 1; output;)
-+action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;)
-+ table=3 (lr_in_ip_input ), priority=90 , dnl
-+match=(inport == "lrp-public" && arp.op == 1 && arp.tpa == { 192.168.2.1, 192.168.2.4, 192.168.2.5, 192.168.2.6 }), dnl
-+action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;)
- table=3 (lr_in_ip_input ), priority=90 , dnl
- match=(inport == "lrp-public" && ip6.dst == {fe80::200:ff:fe00:100, ff02::1:ff00:100} && nd_ns && nd.target == fe80::200:ff:fe00:100), dnl
- action=(nd_na_router { eth.src = xreg0[[0..47]]; ip6.src = fe80::200:ff:fe00:100; nd.target = fe80::200:ff:fe00:100; nd.tll = xreg0[[0..47]]; outport = inport; flags.loopback = 1; output; };)
-+ table=3 (lr_in_ip_input ), priority=90 , dnl
-+match=(inport == "lrp-public" && nd_ns && nd.target == fe80::200:ff:fe00:101:8080), dnl
-+action=(nd_na { eth.src = xreg0[[0..47]]; ip6.src = fe80::200:ff:fe00:101:8080; nd.target = fe80::200:ff:fe00:101:8080; nd.tll = xreg0[[0..47]]; outport = inport; flags.loopback = 1; output; };)
-+ table=3 (lr_in_ip_input ), priority=90 , dnl
-+match=(inport == "lrp-public" && nd_ns && nd.target == fe80::200:ff:fe00:102:8080), dnl
-+action=(nd_na { eth.src = xreg0[[0..47]]; ip6.src = fe80::200:ff:fe00:102:8080; nd.target = fe80::200:ff:fe00:102:8080; nd.tll = xreg0[[0..47]]; outport = inport; flags.loopback = 1; output; };)
- ])
-
- # xreg0[0..47] isn't used anywhere else.
-@@ -1583,28 +1614,46 @@ action=(xreg0[[0..47]] = 00:00:00:00:01:00; next;)
- AT_CHECK([ovn-sbctl lflow-list | grep -E "lr_in_ip_input.*priority=90" | grep "arp\|nd" | sort], [0], [dnl
- table=3 (lr_in_ip_input ), priority=90 , dnl
- match=(arp.op == 1 && arp.tpa == 43.43.43.150), dnl
--action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa = arp.spa; arp.spa = 43.43.43.150; outport = inport; flags.loopback = 1; output;)
-+action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;)
- table=3 (lr_in_ip_input ), priority=90 , dnl
- match=(arp.op == 1 && arp.tpa == 43.43.43.2), dnl
--action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa = arp.spa; arp.spa = 43.43.43.2; outport = inport; flags.loopback = 1; output;)
-+action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;)
- table=3 (lr_in_ip_input ), priority=90 , dnl
- match=(arp.op == 1 && arp.tpa == 43.43.43.3), dnl
--action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa = arp.spa; arp.spa = 43.43.43.3; outport = inport; flags.loopback = 1; output;)
-+action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;)
- table=3 (lr_in_ip_input ), priority=90 , dnl
- match=(arp.op == 1 && arp.tpa == 43.43.43.4), dnl
--action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa = arp.spa; arp.spa = 43.43.43.4; outport = inport; flags.loopback = 1; output;)
-+action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;)
- table=3 (lr_in_ip_input ), priority=90 , dnl
- match=(inport == "lrp" && arp.op == 1 && arp.tpa == 42.42.42.1 && arp.spa == 42.42.42.0/24), dnl
--action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa = arp.spa; arp.spa = 42.42.42.1; outport = inport; flags.loopback = 1; output;)
-+action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;)
-+ table=3 (lr_in_ip_input ), priority=90 , dnl
-+match=(inport == "lrp" && arp.op == 1 && arp.tpa == { 192.168.2.1, 192.168.2.4, 192.168.2.5, 192.168.2.6 }), dnl
-+action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;)
- table=3 (lr_in_ip_input ), priority=90 , dnl
- match=(inport == "lrp" && ip6.dst == {fe80::200:ff:fe00:1, ff02::1:ff00:1} && nd_ns && nd.target == fe80::200:ff:fe00:1), dnl
- action=(nd_na_router { eth.src = xreg0[[0..47]]; ip6.src = fe80::200:ff:fe00:1; nd.target = fe80::200:ff:fe00:1; nd.tll = xreg0[[0..47]]; outport = inport; flags.loopback = 1; output; };)
- table=3 (lr_in_ip_input ), priority=90 , dnl
-+match=(inport == "lrp" && nd_ns && nd.target == fe80::200:ff:fe00:101:8080), dnl
-+action=(nd_na { eth.src = xreg0[[0..47]]; ip6.src = fe80::200:ff:fe00:101:8080; nd.target = fe80::200:ff:fe00:101:8080; nd.tll = xreg0[[0..47]]; outport = inport; flags.loopback = 1; output; };)
-+ table=3 (lr_in_ip_input ), priority=90 , dnl
-+match=(inport == "lrp" && nd_ns && nd.target == fe80::200:ff:fe00:102:8080), dnl
-+action=(nd_na { eth.src = xreg0[[0..47]]; ip6.src = fe80::200:ff:fe00:102:8080; nd.target = fe80::200:ff:fe00:102:8080; nd.tll = xreg0[[0..47]]; outport = inport; flags.loopback = 1; output; };)
-+ table=3 (lr_in_ip_input ), priority=90 , dnl
- match=(inport == "lrp-public" && arp.op == 1 && arp.tpa == 43.43.43.1 && arp.spa == 43.43.43.0/24), dnl
--action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa = arp.spa; arp.spa = 43.43.43.1; outport = inport; flags.loopback = 1; output;)
-+action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;)
-+ table=3 (lr_in_ip_input ), priority=90 , dnl
-+match=(inport == "lrp-public" && arp.op == 1 && arp.tpa == { 192.168.2.1, 192.168.2.4, 192.168.2.5, 192.168.2.6 } && is_chassis_resident("cr-lrp-public")), dnl
-+action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;)
- table=3 (lr_in_ip_input ), priority=90 , dnl
- match=(inport == "lrp-public" && ip6.dst == {fe80::200:ff:fe00:100, ff02::1:ff00:100} && nd_ns && nd.target == fe80::200:ff:fe00:100 && is_chassis_resident("cr-lrp-public")), dnl
- action=(nd_na_router { eth.src = xreg0[[0..47]]; ip6.src = fe80::200:ff:fe00:100; nd.target = fe80::200:ff:fe00:100; nd.tll = xreg0[[0..47]]; outport = inport; flags.loopback = 1; output; };)
-+ table=3 (lr_in_ip_input ), priority=90 , dnl
-+match=(inport == "lrp-public" && nd_ns && nd.target == fe80::200:ff:fe00:101:8080 && is_chassis_resident("cr-lrp-public")), dnl
-+action=(nd_na { eth.src = xreg0[[0..47]]; ip6.src = fe80::200:ff:fe00:101:8080; nd.target = fe80::200:ff:fe00:101:8080; nd.tll = xreg0[[0..47]]; outport = inport; flags.loopback = 1; output; };)
-+ table=3 (lr_in_ip_input ), priority=90 , dnl
-+match=(inport == "lrp-public" && nd_ns && nd.target == fe80::200:ff:fe00:102:8080 && is_chassis_resident("cr-lrp-public")), dnl
-+action=(nd_na { eth.src = xreg0[[0..47]]; ip6.src = fe80::200:ff:fe00:102:8080; nd.target = fe80::200:ff:fe00:102:8080; nd.tll = xreg0[[0..47]]; outport = inport; flags.loopback = 1; output; };)
- ])
-
- # Priority 91 drop flows (per distributed gw port), if port is not resident.
-@@ -1626,16 +1675,16 @@ action=(drop;)
- AT_CHECK([ovn-sbctl lflow-list | grep -E "lr_in_ip_input.*priority=92" | grep "arp\|nd" | sort], [0], [dnl
- table=3 (lr_in_ip_input ), priority=92 , dnl
- match=(inport == "lrp-public" && arp.op == 1 && arp.tpa == 43.43.43.150 && is_chassis_resident("cr-lrp-public")), dnl
--action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa = arp.spa; arp.spa = 43.43.43.150; outport = inport; flags.loopback = 1; output;)
-+action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;)
- table=3 (lr_in_ip_input ), priority=92 , dnl
- match=(inport == "lrp-public" && arp.op == 1 && arp.tpa == 43.43.43.2 && is_chassis_resident("cr-lrp-public")), dnl
--action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa = arp.spa; arp.spa = 43.43.43.2; outport = inport; flags.loopback = 1; output;)
-+action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;)
- table=3 (lr_in_ip_input ), priority=92 , dnl
- match=(inport == "lrp-public" && arp.op == 1 && arp.tpa == 43.43.43.3 && is_chassis_resident("cr-lrp-public")), dnl
--action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa = arp.spa; arp.spa = 43.43.43.3; outport = inport; flags.loopback = 1; output;)
-+action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;)
- table=3 (lr_in_ip_input ), priority=92 , dnl
- match=(inport == "lrp-public" && arp.op == 1 && arp.tpa == 43.43.43.4 && is_chassis_resident("ls-vm")), dnl
--action=(eth.dst = eth.src; eth.src = 00:00:00:00:00:02; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = 00:00:00:00:00:02; arp.tpa = arp.spa; arp.spa = 43.43.43.4; outport = inport; flags.loopback = 1; output;)
-+action=(eth.dst = eth.src; eth.src = 00:00:00:00:00:02; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = 00:00:00:00:00:02; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;)
- ])
-
- # xreg0[0..47] isn't used anywhere else.
-@@ -1671,13 +1720,13 @@ AT_CHECK([ovn-sbctl lflow-list | grep "ls_out_pre_lb.*priority=100" | grep reg0
- ovn-nbctl ls-lb-add sw0 lb1
- ovn-nbctl --wait=sb sync
- AT_CHECK([ovn-sbctl lflow-list | grep "ls_out_pre_lb.*priority=100" | grep reg0 | sort], [0], [dnl
-- table=0 (ls_out_pre_lb ), priority=100 , match=(ip), action=(reg0[[0]] = 1; next;)
-+ table=0 (ls_out_pre_lb ), priority=100 , match=(ip), action=(reg0[[2]] = 1; next;)
- ])
-
- ovn-nbctl ls-lb-add sw0 lb2
- ovn-nbctl --wait=sb sync
- AT_CHECK([ovn-sbctl lflow-list | grep "ls_out_pre_lb.*priority=100" | grep reg0 | sort], [0], [dnl
-- table=0 (ls_out_pre_lb ), priority=100 , match=(ip), action=(reg0[[0]] = 1; next;)
-+ table=0 (ls_out_pre_lb ), priority=100 , match=(ip), action=(reg0[[2]] = 1; next;)
- ])
-
- lb1_uuid=$(ovn-nbctl --bare --columns _uuid find load_balancer name=lb1)
-@@ -1686,7 +1735,7 @@ lb2_uuid=$(ovn-nbctl --bare --columns _uuid find load_balancer name=lb2)
- ovn-nbctl clear load_balancer $lb1_uuid vips
- ovn-nbctl --wait=sb sync
- AT_CHECK([ovn-sbctl lflow-list | grep "ls_out_pre_lb.*priority=100" | grep reg0 | sort], [0], [dnl
-- table=0 (ls_out_pre_lb ), priority=100 , match=(ip), action=(reg0[[0]] = 1; next;)
-+ table=0 (ls_out_pre_lb ), priority=100 , match=(ip), action=(reg0[[2]] = 1; next;)
- ])
-
- ovn-nbctl clear load_balancer $lb2_uuid vips
-@@ -1699,14 +1748,14 @@ ovn-nbctl set load_balancer $lb2_uuid vips:"10.0.0.11"="10.0.0.4"
-
- ovn-nbctl --wait=sb sync
- AT_CHECK([ovn-sbctl lflow-list | grep "ls_out_pre_lb.*priority=100" | grep reg0 | sort], [0], [dnl
-- table=0 (ls_out_pre_lb ), priority=100 , match=(ip), action=(reg0[[0]] = 1; next;)
-+ table=0 (ls_out_pre_lb ), priority=100 , match=(ip), action=(reg0[[2]] = 1; next;)
- ])
-
- # Now reverse the order of clearing the vip.
- ovn-nbctl clear load_balancer $lb2_uuid vips
- ovn-nbctl --wait=sb sync
- AT_CHECK([ovn-sbctl lflow-list | grep "ls_out_pre_lb.*priority=100" | grep reg0 | sort], [0], [dnl
-- table=0 (ls_out_pre_lb ), priority=100 , match=(ip), action=(reg0[[0]] = 1; next;)
-+ table=0 (ls_out_pre_lb ), priority=100 , match=(ip), action=(reg0[[2]] = 1; next;)
- ])
-
- ovn-nbctl clear load_balancer $lb1_uuid vips
-@@ -1754,10 +1803,10 @@ AT_CAPTURE_FILE([sw1flows])
-
- AT_CHECK(
- [grep -E 'ls_(in|out)_acl' sw0flows sw1flows | grep pg0 | sort], [0], [dnl
--sw0flows: table=5 (ls_out_acl ), priority=2003 , match=(outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
--sw0flows: table=9 (ls_in_acl ), priority=2002 , match=(inport == @pg0 && ip4 && tcp && tcp.dst == 80), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=egress,table=6); };)
--sw1flows: table=5 (ls_out_acl ), priority=2003 , match=(outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
--sw1flows: table=9 (ls_in_acl ), priority=2002 , match=(inport == @pg0 && ip4 && tcp && tcp.dst == 80), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=egress,table=6); };)
-+sw0flows: table=4 (ls_out_acl ), priority=2003 , match=(outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=22); };)
-+sw0flows: table=9 (ls_in_acl ), priority=2002 , match=(inport == @pg0 && ip4 && tcp && tcp.dst == 80), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=egress,table=5); };)
-+sw1flows: table=4 (ls_out_acl ), priority=2003 , match=(outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=22); };)
-+sw1flows: table=9 (ls_in_acl ), priority=2002 , match=(inport == @pg0 && ip4 && tcp && tcp.dst == 80), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=egress,table=5); };)
- ])
-
- AS_BOX([2])
-@@ -1770,10 +1819,10 @@ ovn-sbctl dump-flows sw1 > sw1flows2
- AT_CAPTURE_FILE([sw1flows2])
-
- AT_CHECK([grep "ls_out_acl" sw0flows2 sw1flows2 | grep pg0 | sort], [0], [dnl
--sw0flows2: table=5 (ls_out_acl ), priority=2002 , match=(outport == @pg0 && ip4 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
--sw0flows2: table=5 (ls_out_acl ), priority=2003 , match=(outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
--sw1flows2: table=5 (ls_out_acl ), priority=2002 , match=(outport == @pg0 && ip4 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
--sw1flows2: table=5 (ls_out_acl ), priority=2003 , match=(outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
-+sw0flows2: table=4 (ls_out_acl ), priority=2002 , match=(outport == @pg0 && ip4 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=22); };)
-+sw0flows2: table=4 (ls_out_acl ), priority=2003 , match=(outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=22); };)
-+sw1flows2: table=4 (ls_out_acl ), priority=2002 , match=(outport == @pg0 && ip4 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=22); };)
-+sw1flows2: table=4 (ls_out_acl ), priority=2003 , match=(outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=22); };)
- ])
-
- AS_BOX([3])
-@@ -1786,18 +1835,18 @@ ovn-sbctl dump-flows sw1 > sw1flows3
- AT_CAPTURE_FILE([sw1flows3])
-
- AT_CHECK([grep "ls_out_acl" sw0flows3 sw1flows3 | grep pg0 | sort], [0], [dnl
--sw0flows3: table=5 (ls_out_acl ), priority=2001 , match=(reg0[[7]] == 1 && (outport == @pg0 && ip)), action=(reg0[[1]] = 1; next;)
--sw0flows3: table=5 (ls_out_acl ), priority=2001 , match=(reg0[[8]] == 1 && (outport == @pg0 && ip)), action=(next;)
--sw0flows3: table=5 (ls_out_acl ), priority=2002 , match=((reg0[[10]] == 1) && outport == @pg0 && ip4 && udp), action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
--sw0flows3: table=5 (ls_out_acl ), priority=2002 , match=((reg0[[9]] == 1) && outport == @pg0 && ip4 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
--sw0flows3: table=5 (ls_out_acl ), priority=2003 , match=((reg0[[10]] == 1) && outport == @pg0 && ip6 && udp), action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
--sw0flows3: table=5 (ls_out_acl ), priority=2003 , match=((reg0[[9]] == 1) && outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
--sw1flows3: table=5 (ls_out_acl ), priority=2001 , match=(reg0[[7]] == 1 && (outport == @pg0 && ip)), action=(reg0[[1]] = 1; next;)
--sw1flows3: table=5 (ls_out_acl ), priority=2001 , match=(reg0[[8]] == 1 && (outport == @pg0 && ip)), action=(next;)
--sw1flows3: table=5 (ls_out_acl ), priority=2002 , match=((reg0[[10]] == 1) && outport == @pg0 && ip4 && udp), action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
--sw1flows3: table=5 (ls_out_acl ), priority=2002 , match=((reg0[[9]] == 1) && outport == @pg0 && ip4 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
--sw1flows3: table=5 (ls_out_acl ), priority=2003 , match=((reg0[[10]] == 1) && outport == @pg0 && ip6 && udp), action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
--sw1flows3: table=5 (ls_out_acl ), priority=2003 , match=((reg0[[9]] == 1) && outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=23); };)
-+sw0flows3: table=4 (ls_out_acl ), priority=2001 , match=(reg0[[7]] == 1 && (outport == @pg0 && ip)), action=(reg0[[1]] = 1; next;)
-+sw0flows3: table=4 (ls_out_acl ), priority=2001 , match=(reg0[[8]] == 1 && (outport == @pg0 && ip)), action=(next;)
-+sw0flows3: table=4 (ls_out_acl ), priority=2002 , match=((reg0[[10]] == 1) && outport == @pg0 && ip4 && udp), action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=22); };)
-+sw0flows3: table=4 (ls_out_acl ), priority=2002 , match=((reg0[[9]] == 1) && outport == @pg0 && ip4 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=22); };)
-+sw0flows3: table=4 (ls_out_acl ), priority=2003 , match=((reg0[[10]] == 1) && outport == @pg0 && ip6 && udp), action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=22); };)
-+sw0flows3: table=4 (ls_out_acl ), priority=2003 , match=((reg0[[9]] == 1) && outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=22); };)
-+sw1flows3: table=4 (ls_out_acl ), priority=2001 , match=(reg0[[7]] == 1 && (outport == @pg0 && ip)), action=(reg0[[1]] = 1; next;)
-+sw1flows3: table=4 (ls_out_acl ), priority=2001 , match=(reg0[[8]] == 1 && (outport == @pg0 && ip)), action=(next;)
-+sw1flows3: table=4 (ls_out_acl ), priority=2002 , match=((reg0[[10]] == 1) && outport == @pg0 && ip4 && udp), action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=22); };)
-+sw1flows3: table=4 (ls_out_acl ), priority=2002 , match=((reg0[[9]] == 1) && outport == @pg0 && ip4 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=22); };)
-+sw1flows3: table=4 (ls_out_acl ), priority=2003 , match=((reg0[[10]] == 1) && outport == @pg0 && ip6 && udp), action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=22); };)
-+sw1flows3: table=4 (ls_out_acl ), priority=2003 , match=((reg0[[9]] == 1) && outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=22); };)
- ])
-
- AT_CLEANUP
-@@ -1932,17 +1981,17 @@ check ovn-nbctl --wait=sb \
- -- acl-add ls from-lport 2 "udp" allow-related \
- -- acl-add ls to-lport 2 "udp" allow-related
- AT_CHECK([ovn-sbctl lflow-list ls | grep -e ls_in_acl_hint -e ls_out_acl_hint -e ls_in_acl -e ls_out_acl | grep 'ct\.' | sort], [0], [dnl
-- table=4 (ls_out_acl_hint ), priority=1 , match=(ct.est && ct_label.blocked == 0), action=(reg0[[10]] = 1; next;)
-- table=4 (ls_out_acl_hint ), priority=2 , match=(ct.est && ct_label.blocked == 1), action=(reg0[[9]] = 1; next;)
-- table=4 (ls_out_acl_hint ), priority=3 , match=(!ct.est), action=(reg0[[9]] = 1; next;)
-- table=4 (ls_out_acl_hint ), priority=4 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 0), action=(reg0[[8]] = 1; reg0[[10]] = 1; next;)
-- table=4 (ls_out_acl_hint ), priority=5 , match=(!ct.trk), action=(reg0[[8]] = 1; reg0[[9]] = 1; next;)
-- table=4 (ls_out_acl_hint ), priority=6 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 1), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;)
-- table=4 (ls_out_acl_hint ), priority=7 , match=(ct.new && !ct.est), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;)
-- table=5 (ls_out_acl ), priority=1 , match=(ip && (!ct.est || (ct.est && ct_label.blocked == 1))), action=(reg0[[1]] = 1; next;)
-- table=5 (ls_out_acl ), priority=65535, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;)
-- table=5 (ls_out_acl ), priority=65535, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;)
-- table=5 (ls_out_acl ), priority=65535, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;)
-+ table=3 (ls_out_acl_hint ), priority=1 , match=(ct.est && ct_label.blocked == 0), action=(reg0[[10]] = 1; next;)
-+ table=3 (ls_out_acl_hint ), priority=2 , match=(ct.est && ct_label.blocked == 1), action=(reg0[[9]] = 1; next;)
-+ table=3 (ls_out_acl_hint ), priority=3 , match=(!ct.est), action=(reg0[[9]] = 1; next;)
-+ table=3 (ls_out_acl_hint ), priority=4 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 0), action=(reg0[[8]] = 1; reg0[[10]] = 1; next;)
-+ table=3 (ls_out_acl_hint ), priority=5 , match=(!ct.trk), action=(reg0[[8]] = 1; reg0[[9]] = 1; next;)
-+ table=3 (ls_out_acl_hint ), priority=6 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 1), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;)
-+ table=3 (ls_out_acl_hint ), priority=7 , match=(ct.new && !ct.est), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;)
-+ table=4 (ls_out_acl ), priority=1 , match=(ip && (!ct.est || (ct.est && ct_label.blocked == 1))), action=(reg0[[1]] = 1; next;)
-+ table=4 (ls_out_acl ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;)
-+ table=4 (ls_out_acl ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;)
-+ table=4 (ls_out_acl ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;)
- table=8 (ls_in_acl_hint ), priority=1 , match=(ct.est && ct_label.blocked == 0), action=(reg0[[10]] = 1; next;)
- table=8 (ls_in_acl_hint ), priority=2 , match=(ct.est && ct_label.blocked == 1), action=(reg0[[9]] = 1; next;)
- table=8 (ls_in_acl_hint ), priority=3 , match=(!ct.est), action=(reg0[[9]] = 1; next;)
-@@ -1951,9 +2000,9 @@ AT_CHECK([ovn-sbctl lflow-list ls | grep -e ls_in_acl_hint -e ls_out_acl_hint -e
- table=8 (ls_in_acl_hint ), priority=6 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 1), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;)
- table=8 (ls_in_acl_hint ), priority=7 , match=(ct.new && !ct.est), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;)
- table=9 (ls_in_acl ), priority=1 , match=(ip && (!ct.est || (ct.est && ct_label.blocked == 1))), action=(reg0[[1]] = 1; next;)
-- table=9 (ls_in_acl ), priority=65535, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;)
-- table=9 (ls_in_acl ), priority=65535, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;)
-- table=9 (ls_in_acl ), priority=65535, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;)
-+ table=9 (ls_in_acl ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;)
-+ table=9 (ls_in_acl ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;)
-+ table=9 (ls_in_acl ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;)
- ])
-
- AS_BOX([Check match ct_state with load balancer])
-@@ -1963,18 +2012,25 @@ check ovn-nbctl --wait=sb \
- -- lb-add lb "10.0.0.1" "10.0.0.2" \
- -- ls-lb-add ls lb
-
--AT_CHECK([ovn-sbctl lflow-list ls | grep -e ls_in_acl_hint -e ls_out_acl_hint -e ls_in_acl -e ls_out_acl | grep 'ct\.' | sort], [0], [dnl
-- table=4 (ls_out_acl_hint ), priority=1 , match=(ct.est && ct_label.blocked == 0), action=(reg0[[10]] = 1; next;)
-- table=4 (ls_out_acl_hint ), priority=2 , match=(ct.est && ct_label.blocked == 1), action=(reg0[[9]] = 1; next;)
-- table=4 (ls_out_acl_hint ), priority=3 , match=(!ct.est), action=(reg0[[9]] = 1; next;)
-- table=4 (ls_out_acl_hint ), priority=4 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 0), action=(reg0[[8]] = 1; reg0[[10]] = 1; next;)
-- table=4 (ls_out_acl_hint ), priority=5 , match=(!ct.trk), action=(reg0[[8]] = 1; reg0[[9]] = 1; next;)
-- table=4 (ls_out_acl_hint ), priority=6 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 1), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;)
-- table=4 (ls_out_acl_hint ), priority=7 , match=(ct.new && !ct.est), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;)
-- table=5 (ls_out_acl ), priority=1 , match=(ip && (!ct.est || (ct.est && ct_label.blocked == 1))), action=(reg0[[1]] = 1; next;)
-- table=5 (ls_out_acl ), priority=65535, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;)
-- table=5 (ls_out_acl ), priority=65535, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;)
-- table=5 (ls_out_acl ), priority=65535, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;)
-+AT_CHECK([ovn-sbctl lflow-list ls | grep -e ls_in_acl_hint -e ls_out_acl_hint -e ls_in_acl -e ls_out_acl | sort], [0], [dnl
-+ table=3 (ls_out_acl_hint ), priority=0 , match=(1), action=(next;)
-+ table=3 (ls_out_acl_hint ), priority=1 , match=(ct.est && ct_label.blocked == 0), action=(reg0[[10]] = 1; next;)
-+ table=3 (ls_out_acl_hint ), priority=2 , match=(ct.est && ct_label.blocked == 1), action=(reg0[[9]] = 1; next;)
-+ table=3 (ls_out_acl_hint ), priority=3 , match=(!ct.est), action=(reg0[[9]] = 1; next;)
-+ table=3 (ls_out_acl_hint ), priority=4 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 0), action=(reg0[[8]] = 1; reg0[[10]] = 1; next;)
-+ table=3 (ls_out_acl_hint ), priority=5 , match=(!ct.trk), action=(reg0[[8]] = 1; reg0[[9]] = 1; next;)
-+ table=3 (ls_out_acl_hint ), priority=6 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 1), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;)
-+ table=3 (ls_out_acl_hint ), priority=7 , match=(ct.new && !ct.est), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;)
-+ table=4 (ls_out_acl ), priority=0 , match=(1), action=(next;)
-+ table=4 (ls_out_acl ), priority=1 , match=(ip && (!ct.est || (ct.est && ct_label.blocked == 1))), action=(reg0[[1]] = 1; next;)
-+ table=4 (ls_out_acl ), priority=1001 , match=(reg0[[7]] == 1 && (ip)), action=(reg0[[1]] = 1; next;)
-+ table=4 (ls_out_acl ), priority=1001 , match=(reg0[[8]] == 1 && (ip)), action=(next;)
-+ table=4 (ls_out_acl ), priority=34000, match=(eth.src == $svc_monitor_mac), action=(next;)
-+ table=4 (ls_out_acl ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;)
-+ table=4 (ls_out_acl ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;)
-+ table=4 (ls_out_acl ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;)
-+ table=4 (ls_out_acl ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
-+ table=8 (ls_in_acl_hint ), priority=0 , match=(1), action=(next;)
- table=8 (ls_in_acl_hint ), priority=1 , match=(ct.est && ct_label.blocked == 0), action=(reg0[[10]] = 1; next;)
- table=8 (ls_in_acl_hint ), priority=2 , match=(ct.est && ct_label.blocked == 1), action=(reg0[[9]] = 1; next;)
- table=8 (ls_in_acl_hint ), priority=3 , match=(!ct.est), action=(reg0[[9]] = 1; next;)
-@@ -1982,12 +2038,28 @@ AT_CHECK([ovn-sbctl lflow-list ls | grep -e ls_in_acl_hint -e ls_out_acl_hint -e
- table=8 (ls_in_acl_hint ), priority=5 , match=(!ct.trk), action=(reg0[[8]] = 1; reg0[[9]] = 1; next;)
- table=8 (ls_in_acl_hint ), priority=6 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 1), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;)
- table=8 (ls_in_acl_hint ), priority=7 , match=(ct.new && !ct.est), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;)
-+ table=9 (ls_in_acl ), priority=0 , match=(1), action=(next;)
- table=9 (ls_in_acl ), priority=1 , match=(ip && (!ct.est || (ct.est && ct_label.blocked == 1))), action=(reg0[[1]] = 1; next;)
-- table=9 (ls_in_acl ), priority=65535, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;)
-- table=9 (ls_in_acl ), priority=65535, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;)
-- table=9 (ls_in_acl ), priority=65535, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;)
-+ table=9 (ls_in_acl ), priority=1001 , match=(reg0[[7]] == 1 && (ip)), action=(reg0[[1]] = 1; next;)
-+ table=9 (ls_in_acl ), priority=1001 , match=(reg0[[8]] == 1 && (ip)), action=(next;)
-+ table=9 (ls_in_acl ), priority=34000, match=(eth.dst == $svc_monitor_mac), action=(next;)
-+ table=9 (ls_in_acl ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;)
-+ table=9 (ls_in_acl ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;)
-+ table=9 (ls_in_acl ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;)
-+ table=9 (ls_in_acl ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
-+])
-+
-+ovn-nbctl --wait=sb clear logical_switch ls acls
-+ovn-nbctl --wait=sb clear logical_switch ls load_balancer
-+
-+AT_CHECK([ovn-sbctl lflow-list ls | grep -e ls_in_acl_hint -e ls_out_acl_hint -e ls_in_acl -e ls_out_acl | sort], [0], [dnl
-+ table=3 (ls_out_acl_hint ), priority=65535, match=(1), action=(next;)
-+ table=4 (ls_out_acl ), priority=65535, match=(1), action=(next;)
-+ table=8 (ls_in_acl_hint ), priority=65535, match=(1), action=(next;)
-+ table=9 (ls_in_acl ), priority=65535, match=(1), action=(next;)
- ])
-
-+
- AT_CLEANUP
-
- AT_SETUP([datapath requested-tnl-key])
-@@ -2197,20 +2269,20 @@ check ovn-nbctl \
- check ovn-nbctl --wait=sb sync
-
- AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_pre_hairpin | sort], [0], [dnl
-- table=14(ls_in_pre_hairpin ), priority=0 , match=(1), action=(next;)
-- table=14(ls_in_pre_hairpin ), priority=100 , match=(ip && ct.trk), action=(reg0[[6]] = chk_lb_hairpin(); reg0[[12]] = chk_lb_hairpin_reply(); next;)
-+ table=13(ls_in_pre_hairpin ), priority=0 , match=(1), action=(next;)
-+ table=13(ls_in_pre_hairpin ), priority=100 , match=(ip && ct.trk), action=(reg0[[6]] = chk_lb_hairpin(); reg0[[12]] = chk_lb_hairpin_reply(); next;)
- ])
-
- AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_nat_hairpin | sort], [0], [dnl
-- table=15(ls_in_nat_hairpin ), priority=0 , match=(1), action=(next;)
-- table=15(ls_in_nat_hairpin ), priority=100 , match=(ip && ct.est && ct.trk && reg0[[6]] == 1), action=(ct_snat;)
-- table=15(ls_in_nat_hairpin ), priority=100 , match=(ip && ct.new && ct.trk && reg0[[6]] == 1), action=(ct_snat_to_vip; next;)
-- table=15(ls_in_nat_hairpin ), priority=90 , match=(ip && reg0[[12]] == 1), action=(ct_snat;)
-+ table=14(ls_in_nat_hairpin ), priority=0 , match=(1), action=(next;)
-+ table=14(ls_in_nat_hairpin ), priority=100 , match=(ip && ct.est && ct.trk && reg0[[6]] == 1), action=(ct_snat;)
-+ table=14(ls_in_nat_hairpin ), priority=100 , match=(ip && ct.new && ct.trk && reg0[[6]] == 1), action=(ct_snat_to_vip; next;)
-+ table=14(ls_in_nat_hairpin ), priority=90 , match=(ip && reg0[[12]] == 1), action=(ct_snat;)
- ])
-
- AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_hairpin | sort], [0], [dnl
-- table=16(ls_in_hairpin ), priority=0 , match=(1), action=(next;)
-- table=16(ls_in_hairpin ), priority=1 , match=((reg0[[6]] == 1 || reg0[[12]] == 1)), action=(eth.dst <-> eth.src; outport = inport; flags.loopback = 1; output;)
-+ table=15(ls_in_hairpin ), priority=0 , match=(1), action=(next;)
-+ table=15(ls_in_hairpin ), priority=1 , match=((reg0[[6]] == 1 || reg0[[12]] == 1)), action=(eth.dst <-> eth.src; outport = inport; flags.loopback = 1; output;)
- ])
-
- AT_CLEANUP
-@@ -2324,6 +2396,13 @@ check ovn-nbctl lsp-set-options public-lr0 router-port=lr0-public
-
- check ovn-nbctl --wait=sb lr-policy-add lr0 10 "ip4.src == 10.0.0.3" reroute 172.168.0.101,172.168.0.102
-
-+ovn-nbctl lr-policy-list lr0 > policy-list
-+AT_CAPTURE_FILE([policy-list])
-+AT_CHECK([cat policy-list], [0], [dnl
-+Routing Policies
-+ 10 ip4.src == 10.0.0.3 reroute 172.168.0.101, 172.168.0.102
-+])
-+
- ovn-sbctl dump-flows lr0 > lr0flows3
- AT_CAPTURE_FILE([lr0flows3])
-
-@@ -2551,7 +2630,7 @@ wait_row_count nb:Logical_Switch_Port 1 up=false name=lsp1
-
- AT_CLEANUP
-
--AT_SETUP([ovn -- lb_force_snat_ip for Gateway Routers])
-+AT_SETUP([ovn -- Load Balancers and lb_force_snat_ip for Gateway Routers])
- ovn_start
-
- check ovn-nbctl ls-add sw0
-@@ -2589,11 +2668,11 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
- table=5 (lr_in_unsnat ), priority=0 , match=(1), action=(next;)
- ])
-
--AT_CHECK([grep "lr_in_dnat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl
--])
--
--
--AT_CHECK([grep "lr_out_snat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl
-+AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
-+ table=6 (lr_in_dnat ), priority=0 , match=(1), action=(next;)
-+ table=6 (lr_in_dnat ), priority=120 , match=(ct.est && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(ct_dnat;)
-+ table=6 (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(ct_lb(backends=10.0.0.4:8080);)
-+ table=6 (lr_in_dnat ), priority=50 , match=(ip), action=(flags.loopback = 1; ct_dnat;)
- ])
-
- check ovn-nbctl --wait=sb set logical_router lr0 options:lb_force_snat_ip="20.0.0.4 aef0::4"
-@@ -2608,14 +2687,18 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
- table=5 (lr_in_unsnat ), priority=110 , match=(ip6 && ip6.dst == aef0::4), action=(ct_snat;)
- ])
-
--AT_CHECK([grep "lr_in_dnat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl
-+AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
-+ table=6 (lr_in_dnat ), priority=0 , match=(1), action=(next;)
- table=6 (lr_in_dnat ), priority=120 , match=(ct.est && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_dnat;)
- table=6 (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_lb(backends=10.0.0.4:8080);)
-+ table=6 (lr_in_dnat ), priority=50 , match=(ip), action=(flags.loopback = 1; ct_dnat;)
- ])
-
--AT_CHECK([grep "lr_out_snat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl
-+AT_CHECK([grep "lr_out_snat" lr0flows | sort], [0], [dnl
-+ table=1 (lr_out_snat ), priority=0 , match=(1), action=(next;)
- table=1 (lr_out_snat ), priority=100 , match=(flags.force_snat_for_lb == 1 && ip4), action=(ct_snat(20.0.0.4);)
- table=1 (lr_out_snat ), priority=100 , match=(flags.force_snat_for_lb == 1 && ip6), action=(ct_snat(aef0::4);)
-+ table=1 (lr_out_snat ), priority=120 , match=(nd_ns), action=(next;)
- ])
-
- check ovn-nbctl --wait=sb set logical_router lr0 options:lb_force_snat_ip="router_ip"
-@@ -2633,15 +2716,19 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
- table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-sw1" && ip4.dst == 20.0.0.1), action=(ct_snat;)
- ])
-
--AT_CHECK([grep "lr_in_dnat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl
-+AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
-+ table=6 (lr_in_dnat ), priority=0 , match=(1), action=(next;)
- table=6 (lr_in_dnat ), priority=120 , match=(ct.est && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_dnat;)
- table=6 (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_lb(backends=10.0.0.4:8080);)
-+ table=6 (lr_in_dnat ), priority=50 , match=(ip), action=(flags.loopback = 1; ct_dnat;)
- ])
-
--AT_CHECK([grep "lr_out_snat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl
-+AT_CHECK([grep "lr_out_snat" lr0flows | sort], [0], [dnl
-+ table=1 (lr_out_snat ), priority=0 , match=(1), action=(next;)
- table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-public"), action=(ct_snat(172.168.0.100);)
- table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw0"), action=(ct_snat(10.0.0.1);)
- table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw1"), action=(ct_snat(20.0.0.1);)
-+ table=1 (lr_out_snat ), priority=120 , match=(nd_ns), action=(next;)
- ])
-
- check ovn-nbctl --wait=sb remove logical_router lr0 options chassis
-@@ -2653,7 +2740,9 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
- table=5 (lr_in_unsnat ), priority=0 , match=(1), action=(next;)
- ])
-
--AT_CHECK([grep "lr_out_snat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl
-+AT_CHECK([grep "lr_out_snat" lr0flows | sort], [0], [dnl
-+ table=1 (lr_out_snat ), priority=0 , match=(1), action=(next;)
-+ table=1 (lr_out_snat ), priority=120 , match=(nd_ns), action=(next;)
- ])
-
- check ovn-nbctl set logical_router lr0 options:chassis=ch1
-@@ -2670,16 +2759,43 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
- table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-sw1" && ip6.dst == bef0::1), action=(ct_snat;)
- ])
-
--AT_CHECK([grep "lr_in_dnat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl
-+AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
-+ table=6 (lr_in_dnat ), priority=0 , match=(1), action=(next;)
- table=6 (lr_in_dnat ), priority=120 , match=(ct.est && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_dnat;)
- table=6 (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_lb(backends=10.0.0.4:8080);)
-+ table=6 (lr_in_dnat ), priority=50 , match=(ip), action=(flags.loopback = 1; ct_dnat;)
- ])
-
--AT_CHECK([grep "lr_out_snat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl
-+AT_CHECK([grep "lr_out_snat" lr0flows | sort], [0], [dnl
-+ table=1 (lr_out_snat ), priority=0 , match=(1), action=(next;)
- table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-public"), action=(ct_snat(172.168.0.100);)
- table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw0"), action=(ct_snat(10.0.0.1);)
- table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw1"), action=(ct_snat(20.0.0.1);)
- table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip6 && outport == "lr0-sw1"), action=(ct_snat(bef0::1);)
-+ table=1 (lr_out_snat ), priority=120 , match=(nd_ns), action=(next;)
-+])
-+
-+check ovn-nbctl --wait=sb lb-add lb2 10.0.0.20:80 10.0.0.40:8080
-+check ovn-nbctl --wait=sb set load_balancer lb2 options:skip_snat=true
-+check ovn-nbctl lr-lb-add lr0 lb2
-+check ovn-nbctl --wait=sb lb-del lb1
-+ovn-sbctl dump-flows lr0 > lr0flows
-+
-+AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
-+ table=5 (lr_in_unsnat ), priority=0 , match=(1), action=(next;)
-+ table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-public" && ip4.dst == 172.168.0.100), action=(ct_snat;)
-+ table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-sw0" && ip4.dst == 10.0.0.1), action=(ct_snat;)
-+ table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-sw1" && ip4.dst == 20.0.0.1), action=(ct_snat;)
-+ table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-sw1" && ip6.dst == bef0::1), action=(ct_snat;)
-+])
-+
-+AT_CHECK([grep "lr_in_dnat" lr0flows | grep skip_snat_for_lb | sort], [0], [dnl
-+ table=6 (lr_in_dnat ), priority=120 , match=(ct.est && ip && ip4.dst == 10.0.0.20 && tcp && tcp.dst == 80), action=(flags.skip_snat_for_lb = 1; ct_dnat;)
-+ table=6 (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.20 && tcp && tcp.dst == 80), action=(flags.skip_snat_for_lb = 1; ct_lb(backends=10.0.0.40:8080);)
-+])
-+
-+AT_CHECK([grep "lr_out_snat" lr0flows | grep skip_snat_for_lb | sort], [0], [dnl
-+ table=1 (lr_out_snat ), priority=120 , match=(flags.skip_snat_for_lb == 1 && ip), action=(next;)
- ])
-
- AT_CLEANUP
-@@ -2783,3 +2899,206 @@ wait_row_count FDB 0
- ovn-sbctl list FDB
-
- AT_CLEANUP
-+
-+AT_SETUP([ovn -- LS load balancer logical flows])
-+ovn_start
-+
-+check ovn-nbctl \
-+ -- ls-add sw0 \
-+ -- lb-add lb0 10.0.0.10:80 10.0.0.4:8080 \
-+ -- ls-lb-add sw0 lb0
-+
-+check ovn-nbctl lr-add lr0
-+check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24
-+check ovn-nbctl lsp-add sw0 sw0-lr0
-+check ovn-nbctl lsp-set-type sw0-lr0 router
-+check ovn-nbctl lsp-set-addresses sw0-lr0 00:00:00:00:ff:01
-+check ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
-+
-+check ovn-nbctl --wait=sb sync
-+
-+check_stateful_flows() {
-+ ovn-sbctl dump-flows sw0 > sw0flows
-+ AT_CAPTURE_FILE([sw0flows])
-+
-+ AT_CHECK([grep "ls_in_pre_lb" sw0flows | sort], [0], [dnl
-+ table=6 (ls_in_pre_lb ), priority=0 , match=(1), action=(next;)
-+ table=6 (ls_in_pre_lb ), priority=100 , match=(ip), action=(reg0[[2]] = 1; next;)
-+ table=6 (ls_in_pre_lb ), priority=110 , match=(eth.dst == $svc_monitor_mac), action=(next;)
-+ table=6 (ls_in_pre_lb ), priority=110 , match=(ip && inport == "sw0-lr0"), action=(next;)
-+ table=6 (ls_in_pre_lb ), priority=110 , match=(nd || nd_rs || nd_ra || mldv1 || mldv2), action=(next;)
-+])
-+
-+ AT_CHECK([grep "ls_in_pre_stateful" sw0flows | sort], [0], [dnl
-+ table=7 (ls_in_pre_stateful ), priority=0 , match=(1), action=(next;)
-+ table=7 (ls_in_pre_stateful ), priority=100 , match=(reg0[[0]] == 1), action=(ct_next;)
-+ table=7 (ls_in_pre_stateful ), priority=110 , match=(reg0[[2]] == 1), action=(ct_lb;)
-+ table=7 (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip4 && sctp), action=(reg1 = ip4.dst; reg2[[0..15]] = sctp.dst; ct_lb;)
-+ table=7 (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip4 && tcp), action=(reg1 = ip4.dst; reg2[[0..15]] = tcp.dst; ct_lb;)
-+ table=7 (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip4 && udp), action=(reg1 = ip4.dst; reg2[[0..15]] = udp.dst; ct_lb;)
-+ table=7 (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip6 && sctp), action=(xxreg1 = ip6.dst; reg2[[0..15]] = sctp.dst; ct_lb;)
-+ table=7 (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip6 && tcp), action=(xxreg1 = ip6.dst; reg2[[0..15]] = tcp.dst; ct_lb;)
-+ table=7 (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip6 && udp), action=(xxreg1 = ip6.dst; reg2[[0..15]] = udp.dst; ct_lb;)
-+])
-+
-+ AT_CHECK([grep "ls_in_stateful" sw0flows | sort], [0], [dnl
-+ table=12(ls_in_stateful ), priority=0 , match=(1), action=(next;)
-+ table=12(ls_in_stateful ), priority=100 , match=(reg0[[1]] == 1), action=(ct_commit { ct_label.blocked = 0; }; next;)
-+ table=12(ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.4:8080);)
-+])
-+
-+ AT_CHECK([grep "ls_out_pre_lb" sw0flows | sort], [0], [dnl
-+ table=0 (ls_out_pre_lb ), priority=0 , match=(1), action=(next;)
-+ table=0 (ls_out_pre_lb ), priority=100 , match=(ip), action=(reg0[[2]] = 1; next;)
-+ table=0 (ls_out_pre_lb ), priority=110 , match=(eth.src == $svc_monitor_mac), action=(next;)
-+ table=0 (ls_out_pre_lb ), priority=110 , match=(ip && outport == "sw0-lr0"), action=(next;)
-+ table=0 (ls_out_pre_lb ), priority=110 , match=(nd || nd_rs || nd_ra || mldv1 || mldv2), action=(next;)
-+])
-+
-+ AT_CHECK([grep "ls_out_pre_stateful" sw0flows | sort], [0], [dnl
-+ table=2 (ls_out_pre_stateful), priority=0 , match=(1), action=(next;)
-+ table=2 (ls_out_pre_stateful), priority=100 , match=(reg0[[0]] == 1), action=(ct_next;)
-+ table=2 (ls_out_pre_stateful), priority=110 , match=(reg0[[2]] == 1), action=(ct_lb;)
-+])
-+
-+ AT_CHECK([grep "ls_out_lb" sw0flows | sort], [0], [])
-+
-+ AT_CHECK([grep "ls_out_stateful" sw0flows | sort], [0], [dnl
-+ table=7 (ls_out_stateful ), priority=0 , match=(1), action=(next;)
-+ table=7 (ls_out_stateful ), priority=100 , match=(reg0[[1]] == 1), action=(ct_commit { ct_label.blocked = 0; }; next;)
-+])
-+}
-+
-+check_stateful_flows
-+
-+# Add few ACLs
-+check ovn-nbctl --wait=sb acl-add sw0 from-lport 1002 "ip4 && tcp && tcp.dst == 80" allow-related
-+check ovn-nbctl --wait=sb acl-add sw0 to-lport 1002 "ip4 && tcp && tcp.src == 80" drop
-+
-+check_stateful_flows
-+
-+# Remove load balancer from sw0
-+check ovn-nbctl --wait=sb ls-lb-del sw0 lb0
-+
-+ovn-sbctl dump-flows sw0 > sw0flows
-+AT_CAPTURE_FILE([sw0flows])
-+
-+AT_CHECK([grep "ls_in_pre_lb" sw0flows | sort], [0], [dnl
-+ table=6 (ls_in_pre_lb ), priority=0 , match=(1), action=(next;)
-+ table=6 (ls_in_pre_lb ), priority=110 , match=(eth.dst == $svc_monitor_mac), action=(next;)
-+ table=6 (ls_in_pre_lb ), priority=110 , match=(ip && inport == "sw0-lr0"), action=(next;)
-+ table=6 (ls_in_pre_lb ), priority=110 , match=(nd || nd_rs || nd_ra || mldv1 || mldv2), action=(next;)
-+])
-+
-+AT_CHECK([grep "ls_in_pre_stateful" sw0flows | sort], [0], [dnl
-+ table=7 (ls_in_pre_stateful ), priority=0 , match=(1), action=(next;)
-+ table=7 (ls_in_pre_stateful ), priority=100 , match=(reg0[[0]] == 1), action=(ct_next;)
-+ table=7 (ls_in_pre_stateful ), priority=110 , match=(reg0[[2]] == 1), action=(ct_lb;)
-+ table=7 (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip4 && sctp), action=(reg1 = ip4.dst; reg2[[0..15]] = sctp.dst; ct_lb;)
-+ table=7 (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip4 && tcp), action=(reg1 = ip4.dst; reg2[[0..15]] = tcp.dst; ct_lb;)
-+ table=7 (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip4 && udp), action=(reg1 = ip4.dst; reg2[[0..15]] = udp.dst; ct_lb;)
-+ table=7 (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip6 && sctp), action=(xxreg1 = ip6.dst; reg2[[0..15]] = sctp.dst; ct_lb;)
-+ table=7 (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip6 && tcp), action=(xxreg1 = ip6.dst; reg2[[0..15]] = tcp.dst; ct_lb;)
-+ table=7 (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip6 && udp), action=(xxreg1 = ip6.dst; reg2[[0..15]] = udp.dst; ct_lb;)
-+])
-+
-+AT_CHECK([grep "ls_in_stateful" sw0flows | sort], [0], [dnl
-+ table=12(ls_in_stateful ), priority=0 , match=(1), action=(next;)
-+ table=12(ls_in_stateful ), priority=100 , match=(reg0[[1]] == 1), action=(ct_commit { ct_label.blocked = 0; }; next;)
-+])
-+
-+AT_CHECK([grep "ls_out_pre_lb" sw0flows | sort], [0], [dnl
-+ table=0 (ls_out_pre_lb ), priority=0 , match=(1), action=(next;)
-+ table=0 (ls_out_pre_lb ), priority=110 , match=(eth.src == $svc_monitor_mac), action=(next;)
-+ table=0 (ls_out_pre_lb ), priority=110 , match=(ip && outport == "sw0-lr0"), action=(next;)
-+ table=0 (ls_out_pre_lb ), priority=110 , match=(nd || nd_rs || nd_ra || mldv1 || mldv2), action=(next;)
-+])
-+
-+AT_CHECK([grep "ls_out_pre_stateful" sw0flows | sort], [0], [dnl
-+ table=2 (ls_out_pre_stateful), priority=0 , match=(1), action=(next;)
-+ table=2 (ls_out_pre_stateful), priority=100 , match=(reg0[[0]] == 1), action=(ct_next;)
-+ table=2 (ls_out_pre_stateful), priority=110 , match=(reg0[[2]] == 1), action=(ct_lb;)
-+])
-+
-+AT_CHECK([grep "ls_out_stateful" sw0flows | sort], [0], [dnl
-+ table=7 (ls_out_stateful ), priority=0 , match=(1), action=(next;)
-+ table=7 (ls_out_stateful ), priority=100 , match=(reg0[[1]] == 1), action=(ct_commit { ct_label.blocked = 0; }; next;)
-+])
-+
-+AT_CLEANUP
-+])
-+
-+AT_SETUP([ovn -- ct.inv usage])
-+ovn_start
-+
-+check ovn-nbctl ls-add sw0
-+check ovn-nbctl lsp-add sw0 sw0p1
-+
-+check ovn-nbctl --wait=sb acl-add sw0 to-lport 1002 ip allow-related
-+
-+ovn-sbctl dump-flows sw0 > sw0flows
-+AT_CAPTURE_FILE([sw0flows])
-+
-+AT_CHECK([grep -w "ls_in_acl" sw0flows | grep 6553 | sort], [0], [dnl
-+ table=9 (ls_in_acl ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;)
-+ table=9 (ls_in_acl ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;)
-+ table=9 (ls_in_acl ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;)
-+ table=9 (ls_in_acl ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
-+])
-+
-+AT_CHECK([grep -w "ls_out_acl" sw0flows | grep 6553 | sort], [0], [dnl
-+ table=4 (ls_out_acl ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;)
-+ table=4 (ls_out_acl ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;)
-+ table=4 (ls_out_acl ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;)
-+ table=4 (ls_out_acl ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
-+])
-+
-+# Disable ct.inv usage.
-+check ovn-nbctl --wait=sb set NB_Global . options:use_ct_inv_match=false
-+
-+ovn-sbctl dump-flows sw0 > sw0flows
-+AT_CAPTURE_FILE([sw0flows])
-+
-+AT_CHECK([grep -w "ls_in_acl" sw0flows | grep 6553 | sort], [0], [dnl
-+ table=9 (ls_in_acl ), priority=65532, match=(!ct.est && ct.rel && !ct.new && ct_label.blocked == 0), action=(next;)
-+ table=9 (ls_in_acl ), priority=65532, match=((ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;)
-+ table=9 (ls_in_acl ), priority=65532, match=(ct.est && !ct.rel && !ct.new && ct.rpl && ct_label.blocked == 0), action=(next;)
-+ table=9 (ls_in_acl ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
-+])
-+
-+AT_CHECK([grep -w "ls_out_acl" sw0flows | grep 6553 | sort], [0], [dnl
-+ table=4 (ls_out_acl ), priority=65532, match=(!ct.est && ct.rel && !ct.new && ct_label.blocked == 0), action=(next;)
-+ table=4 (ls_out_acl ), priority=65532, match=((ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;)
-+ table=4 (ls_out_acl ), priority=65532, match=(ct.est && !ct.rel && !ct.new && ct.rpl && ct_label.blocked == 0), action=(next;)
-+ table=4 (ls_out_acl ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
-+])
-+
-+AT_CHECK([grep -c "ct.inv" sw0flows], [1], [dnl
-+0
-+])
-+
-+# Enable ct.inv usage.
-+check ovn-nbctl --wait=sb set NB_Global . options:use_ct_inv_match=true
-+
-+ovn-sbctl dump-flows sw0 > sw0flows
-+AT_CAPTURE_FILE([sw0flows])
-+
-+AT_CHECK([grep -w "ls_in_acl" sw0flows | grep 6553 | sort], [0], [dnl
-+ table=9 (ls_in_acl ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;)
-+ table=9 (ls_in_acl ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;)
-+ table=9 (ls_in_acl ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;)
-+ table=9 (ls_in_acl ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
-+])
-+
-+AT_CHECK([grep -w "ls_out_acl" sw0flows | grep 6553 | sort], [0], [dnl
-+ table=4 (ls_out_acl ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;)
-+ table=4 (ls_out_acl ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;)
-+ table=4 (ls_out_acl ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;)
-+ table=4 (ls_out_acl ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
-+])
-+
-+AT_CHECK([grep -c "ct.inv" sw0flows], [0], [dnl
-+6
-+])
-+
-+AT_CLEANUP
-diff --git a/tests/ovn.at b/tests/ovn.at
-index b465784cd..0377b75c3 100644
---- a/tests/ovn.at
-+++ b/tests/ovn.at
-@@ -693,6 +693,11 @@ ip,nw_src=4.0.0.0/4.0.0.0
- ip,nw_src=64.0.0.0/64.0.0.0
- ip,nw_src=8.0.0.0/8.0.0.0
- ])
-+AT_CHECK([expr_to_flow 'ip4.dst == 172.27.0.65 && ip4.src == $set1 && ip4.dst != 10.128.0.0/14'], [0], [dnl
-+ip,nw_src=10.0.0.1,nw_dst=172.27.0.65
-+ip,nw_src=10.0.0.2,nw_dst=172.27.0.65
-+ip,nw_src=10.0.0.3,nw_dst=172.27.0.65
-+])
- AT_CLEANUP
-
- AT_SETUP([ovn -- converting expressions to flows -- port groups])
-@@ -9878,15 +9883,12 @@ AT_CHECK([ovn-nbctl --wait=sb sync], [0], [ignore])
- ovn-sbctl dump-flows > sbflows
- AT_CAPTURE_FILE([sbflows])
-
--reset_pcap_file() {
-- local iface=$1
-- local pcap_file=$2
-- check ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
--options:rxq_pcap=dummy-rx.pcap
-- rm -f ${pcap_file}*.pcap
-- check ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \
--options:rxq_pcap=${pcap_file}-rx.pcap
--}
-+hv1_gw1_ofport=$(as hv1 ovs-vsctl --bare --columns ofport find Interface name=ovn-gw1-0)
-+hv1_gw2_ofport=$(as hv1 ovs-vsctl --bare --columns ofport find Interface name=ovn-gw2-0)
-+
-+OVS_WAIT_UNTIL([
-+ test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=37 | grep -c "active_backup,ofport,members:$hv1_gw1_ofport,$hv1_gw2_ofport")
-+])
-
- test_ip_packet()
- {
-@@ -9932,13 +9934,13 @@ test_ip_packet()
- echo $expected > ext1-vif1.expected
- exp_gw_ip_garp=ffffffffffff00000201020308060001080006040001000002010203ac100101000000000000ac100101
- echo $exp_gw_ip_garp >> ext1-vif1.expected
-- as $active_gw reset_pcap_file br-phys_n1 $active_gw/br-phys_n1
-+ as $active_gw reset_iface_pcap_file br-phys_n1 $active_gw/br-phys_n1
-
- if test $backup_vswitchd_dead != 1; then
- # Reset the file only if vswitchd in backup gw is alive
-- as $backup_gw reset_pcap_file br-phys_n1 $backup_gw/br-phys_n1
-+ as $backup_gw reset_iface_pcap_file br-phys_n1 $backup_gw/br-phys_n1
- fi
-- as ext1 reset_pcap_file ext1-vif1 ext1/vif1
-+ as ext1 reset_iface_pcap_file ext1-vif1 ext1/vif1
-
- # Resend packet from foo1 to outside1
- check as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
-@@ -9990,6 +9992,10 @@ AT_CHECK(
- <1>
- ])
-
-+OVS_WAIT_UNTIL([
-+ test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=37 | grep -c "active_backup,ofport,members:$hv1_gw2_ofport,$hv1_gw1_ofport")
-+])
-+
- test_ip_packet gw2 gw1 0
-
- # Get the claim count of both gw1 and gw2.
-@@ -10010,6 +10016,12 @@ OVS_WAIT_UNTIL([test $gw1_claim_ct = `cat gw1/ovn-controller.log \
- AT_CHECK([test $gw2_claim_ct = `cat gw2/ovn-controller.log | \
- grep -c "cr-alice: Claiming"`])
-
-+OVS_WAIT_UNTIL([
-+ bfd_status=$(as hv1 ovs-vsctl get interface ovn-gw2-0 bfd_status:state)
-+ echo "bfd status = $bfd_status"
-+ test "$bfd_status" = "down"
-+])
-+
- test_ip_packet gw1 gw2 1
-
- as gw2
-@@ -11490,10 +11502,100 @@ for i in 1 2; do
- done
- done
-
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int |awk '/table=65/{print substr($8, 16, length($8))}' |sort -n], [0], [dnl
-+10
-+11
-+])
-+
-+# remove the localport from br-int and re-create it
-+as hv1
-+check ovs-vsctl del-port vif01
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int |awk '/table=65/{print substr($8, 16, length($8))}' |sort -n], [0], [dnl
-+11
-+])
-+
-+as hv1
-+check ovs-vsctl add-port br-int vif01 \
-+ -- set Interface vif01 external-ids:iface-id=lp01
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int |awk '/table=65/{print substr($8, 16, length($8))}' |sort -n], [0], [dnl
-+2
-+11
-+])
-+
- OVN_CLEANUP([hv1],[hv2])
-
- AT_CLEANUP
-
-+AT_SETUP([ovn -- localport suppress gARP])
-+ovn_start
-+
-+send_garp() {
-+ local inport=$1 eth_src=$2 eth_dst=$3 spa=$4 tpa=$5
-+ local request=${eth_dst}${eth_src}08060001080006040001${eth_src}${spa}${eth_dst}${tpa}
-+ as hv1 ovs-appctl netdev-dummy/receive vif$inport $request
-+}
-+
-+net_add n1
-+sim_add hv1
-+as hv1
-+check 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.1
-+
-+check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
-+
-+check ovn-nbctl ls-add ls \
-+ -- lsp-add ls lp \
-+ -- lsp-set-type lp localport \
-+ -- lsp-set-addresses lp "00:00:00:00:00:01 10.0.0.1" \
-+ -- lsp-add ls ln \
-+ -- lsp-set-type ln localnet \
-+ -- lsp-set-addresses ln unknown \
-+ -- lsp-set-options ln network_name=phys \
-+ -- lsp-add ls lsp \
-+ -- lsp-set-addresses lsp "00:00:00:00:00:02 10.0.0.2"
-+
-+dnl First bind the localport.
-+check ovs-vsctl add-port br-int vif1 \
-+ -- set Interface vif1 external-ids:iface-id=lp
-+check ovn-nbctl --wait=hv sync
-+
-+dnl Then bind the regular vif.
-+check ovs-vsctl add-port br-int vif2 \
-+ -- set Interface vif2 external-ids:iface-id=lsp \
-+ options:tx_pcap=hv1/vif2-tx.pcap \
-+ options:rxq_pcap=hv1/vif2-rx.pcap
-+
-+wait_for_ports_up lsp
-+check ovn-nbctl --wait=hv sync
-+
-+dnl Wait for at least two gARPs from lsp (10.0.0.2).
-+lsp_garp=ffffffffffff000000000002080600010800060400010000000000020a0000020000000000000a000002
-+OVS_WAIT_UNTIL([
-+ garps=`$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/br-phys-tx.pcap | grep ${lsp_garp} -c`
-+ test $garps -ge 2
-+])
-+
-+dnl At this point it's safe to assume that ovn-controller skipped sending gARP
-+dnl for the localport. Check that there are no other packets than the gARPs
-+dnl for the regular vif.
-+AT_CHECK([
-+ pkts=`$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/br-phys-tx.pcap | grep -v ${lsp_garp} -c`
-+ test 0 -eq $pkts
-+])
-+
-+spa=$(ip_to_hex 10 0 0 1)
-+tpa=$(ip_to_hex 10 0 0 100)
-+send_garp 1 000000000001 ffffffffffff $spa $tpa
-+
-+dnl traffic from localport should not be sent to localnet
-+AT_CHECK([tcpdump -r hv1/br-phys_n1-tx.pcap arp[[24:4]]=0x0a000064 | wc -l],[0],[dnl
-+0
-+],[ignore])
-+
-+OVN_CLEANUP([hv1])
-+AT_CLEANUP
-+
- AT_SETUP([ovn -- 1 LR with HA distributed router gateway port])
- ovn_start
-
-@@ -13901,16 +14003,16 @@ check ovn-nbctl acl-add ls1 to-lport 3 'ip4.src==10.0.0.1' allow
- check ovn-nbctl --wait=hv sync
-
- # Check OVS flows, the less restrictive flows should have been installed.
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=44 | ofctl_strip_all | \
- grep "priority=1003" | \
- sed 's/conjunction([[^)]]*)/conjunction()/g' | sort], [0], [dnl
-- table=45, priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,46)
-- table=45, priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,46)
-- table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction()
-- table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction()
-- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,46)
-- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction()
-- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction()
-+ table=44, priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,45)
-+ table=44, priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,45)
-+ table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction()
-+ table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction()
-+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,45)
-+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction()
-+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction()
- ])
-
- # Traffic 10.0.0.1, 10.0.0.2 -> 10.0.0.3, 10.0.0.4 should be allowed.
-@@ -13945,16 +14047,16 @@ check ovn-nbctl acl-del ls1 to-lport 3 'ip4.src==10.0.0.1 || ip4.src==10.0.0.1'
- check ovn-nbctl --wait=hv sync
-
- # Check OVS flows, the second less restrictive allow ACL should have been installed.
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=44 | ofctl_strip_all | \
- grep "priority=1003" | \
- sed 's/conjunction([[^)]]*)/conjunction()/g' | sort], [0], [dnl
-- table=45, priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,46)
-- table=45, priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,46)
-- table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction()
-- table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction()
-- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,46)
-- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction()
-- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction()
-+ table=44, priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,45)
-+ table=44, priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,45)
-+ table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction()
-+ table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction()
-+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,45)
-+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction()
-+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction()
- ])
-
- # Remove the less restrictive allow ACL.
-@@ -13962,16 +14064,16 @@ check ovn-nbctl acl-del ls1 to-lport 3 'ip4.src==10.0.0.1'
- check ovn-nbctl --wait=hv sync
-
- # Check OVS flows, the 10.0.0.1 conjunction should have been reinstalled.
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=44 | ofctl_strip_all | \
- grep "priority=1003" | \
- sed 's/conjunction([[^)]]*)/conjunction()/g' | sort], [0], [dnl
-- table=45, priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,46)
-- table=45, priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,46)
-- table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction()
-- table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction()
-- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=conjunction(),conjunction()
-- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction()
-- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction()
-+ table=44, priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,45)
-+ table=44, priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,45)
-+ table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction()
-+ table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction()
-+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=conjunction(),conjunction()
-+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction()
-+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction()
- ])
-
- # Traffic 10.0.0.1, 10.0.0.2 -> 10.0.0.3, 10.0.0.4 should be allowed.
-@@ -14001,16 +14103,16 @@ check ovn-nbctl acl-add ls1 to-lport 3 'ip4.src==10.0.0.1' allow
- check ovn-nbctl --wait=hv sync
-
- # Check OVS flows, the less restrictive flows should have been installed.
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=44 | ofctl_strip_all | \
- grep "priority=1003" | \
- sed 's/conjunction([[^)]]*)/conjunction()/g' | sort], [0], [dnl
-- table=45, priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,46)
-- table=45, priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,46)
-- table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction()
-- table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction()
-- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,46)
-- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction()
-- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction()
-+ table=44, priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,45)
-+ table=44, priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,45)
-+ table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction()
-+ table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction()
-+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,45)
-+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction()
-+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction()
- ])
-
- # Add another ACL that overlaps with the existing less restrictive ones.
-@@ -14021,19 +14123,19 @@ check ovn-nbctl --wait=hv sync
- # with an additional conjunction action.
- #
- # New non-conjunctive flows should be added to match on 'udp'.
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=44 | ofctl_strip_all | \
- grep "priority=1003" | \
- sed 's/conjunction([[^)]]*)/conjunction()/g' | sort], [0], [dnl
-- table=45, priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,46)
-- table=45, priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,46)
-- table=45, priority=1003,conj_id=4,ip,metadata=0x1 actions=resubmit(,46)
-- table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction(),conjunction()
-- table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction(),conjunction()
-- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,46)
-- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction(),conjunction()
-- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction()
-- table=45, priority=1003,udp,metadata=0x1 actions=resubmit(,46)
-- table=45, priority=1003,udp6,metadata=0x1 actions=resubmit(,46)
-+ table=44, priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,45)
-+ table=44, priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,45)
-+ table=44, priority=1003,conj_id=4,ip,metadata=0x1 actions=resubmit(,45)
-+ table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction(),conjunction()
-+ table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction(),conjunction()
-+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,45)
-+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction(),conjunction()
-+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction()
-+ table=44, priority=1003,udp,metadata=0x1 actions=resubmit(,45)
-+ table=44, priority=1003,udp6,metadata=0x1 actions=resubmit(,45)
- ])
-
- OVN_CLEANUP([hv1])
-@@ -15375,7 +15477,7 @@ wait_for_ports_up ls1-lp_ext1
- # There should be a flow in hv2 to drop traffic from ls1-lp_ext1 destined
- # to router mac.
- AT_CHECK([as hv2 ovs-ofctl dump-flows br-int \
--table=30,dl_src=f0:00:00:00:00:03,dl_dst=a0:10:00:00:00:01 | \
-+table=29,dl_src=f0:00:00:00:00:03,dl_dst=a0:10:00:00:00:01 | \
- grep -c "actions=drop"], [0], [1
- ])
-
-@@ -16647,56 +16749,67 @@ ovs-vsctl -- add-port br-int hv2-vif2 -- \
-
- ovn-nbctl ls-add sw0
-
--ovn-nbctl lsp-add sw0 sw0-vir
--ovn-nbctl lsp-set-addresses sw0-vir "50:54:00:00:00:10 10.0.0.10"
--ovn-nbctl lsp-set-port-security sw0-vir "50:54:00:00:00:10 10.0.0.10"
--ovn-nbctl lsp-set-type sw0-vir virtual
--ovn-nbctl set logical_switch_port sw0-vir options:virtual-ip=10.0.0.10
--ovn-nbctl set logical_switch_port sw0-vir options:virtual-parents=sw0-p1,sw0-p2,sw0-p3
-+check ovn-nbctl lsp-add sw0 sw0-vir
-+check ovn-nbctl lsp-set-addresses sw0-vir "50:54:00:00:00:10 10.0.0.10"
-+check ovn-nbctl lsp-set-port-security sw0-vir "50:54:00:00:00:10 10.0.0.10"
-+check ovn-nbctl lsp-set-type sw0-vir virtual
-+check ovn-nbctl set logical_switch_port sw0-vir options:virtual-ip=10.0.0.10
-+check ovn-nbctl set logical_switch_port sw0-vir options:virtual-parents=sw0-p1,sw0-p2,sw0-p3
-
--ovn-nbctl lsp-add sw0 sw0-p1
--ovn-nbctl lsp-set-addresses sw0-p1 "50:54:00:00:00:03 10.0.0.3"
--ovn-nbctl lsp-set-port-security sw0-p1 "50:54:00:00:00:03 10.0.0.3 10.0.0.10"
-+check ovn-nbctl lsp-add sw0 sw0-p1
-+check ovn-nbctl lsp-set-addresses sw0-p1 "50:54:00:00:00:03 10.0.0.3"
-+check ovn-nbctl lsp-set-port-security sw0-p1 "50:54:00:00:00:03 10.0.0.3 10.0.0.10"
-
--ovn-nbctl lsp-add sw0 sw0-p2
--ovn-nbctl lsp-set-addresses sw0-p2 "50:54:00:00:00:04 10.0.0.4"
--ovn-nbctl lsp-set-port-security sw0-p2 "50:54:00:00:00:04 10.0.0.4 10.0.0.10"
-+check ovn-nbctl lsp-add sw0 sw0-p2
-+check ovn-nbctl lsp-set-addresses sw0-p2 "50:54:00:00:00:04 10.0.0.4"
-+check ovn-nbctl lsp-set-port-security sw0-p2 "50:54:00:00:00:04 10.0.0.4 10.0.0.10"
-
--ovn-nbctl lsp-add sw0 sw0-p3
--ovn-nbctl lsp-set-addresses sw0-p3 "50:54:00:00:00:05 10.0.0.5"
--ovn-nbctl lsp-set-port-security sw0-p3 "50:54:00:00:00:05 10.0.0.5 10.0.0.10"
-+check ovn-nbctl lsp-add sw0 sw0-p3
-+check ovn-nbctl lsp-set-addresses sw0-p3 "50:54:00:00:00:05 10.0.0.5"
-+check ovn-nbctl lsp-set-port-security sw0-p3 "50:54:00:00:00:05 10.0.0.5 10.0.0.10"
-
- # Create the second logical switch with one port
--ovn-nbctl ls-add sw1
--ovn-nbctl lsp-add sw1 sw1-p1
--ovn-nbctl lsp-set-addresses sw1-p1 "40:54:00:00:00:03 20.0.0.3"
--ovn-nbctl lsp-set-port-security sw1-p1 "40:54:00:00:00:03 20.0.0.3"
-+check ovn-nbctl ls-add sw1
-+check ovn-nbctl lsp-add sw1 sw1-p1
-+check ovn-nbctl lsp-set-addresses sw1-p1 "40:54:00:00:00:03 20.0.0.3"
-+check ovn-nbctl lsp-set-port-security sw1-p1 "40:54:00:00:00:03 20.0.0.3"
-
- # Create a logical router and attach both logical switches
--ovn-nbctl lr-add lr0
--ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24
--ovn-nbctl lsp-add sw0 sw0-lr0
--ovn-nbctl lsp-set-type sw0-lr0 router
--ovn-nbctl lsp-set-addresses sw0-lr0 00:00:00:00:ff:01
--ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
-+check ovn-nbctl lr-add lr0
-+check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24
-+check ovn-nbctl lsp-add sw0 sw0-lr0
-+check ovn-nbctl lsp-set-type sw0-lr0 router
-+check ovn-nbctl lsp-set-addresses sw0-lr0 00:00:00:00:ff:01
-+check ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
-
--ovn-nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 20.0.0.1/24
--ovn-nbctl lsp-add sw1 sw1-lr0
--ovn-nbctl lsp-set-type sw1-lr0 router
--ovn-nbctl lsp-set-addresses sw1-lr0 00:00:00:00:ff:02
--ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1
-+check ovn-nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 20.0.0.1/24
-+check ovn-nbctl lsp-add sw1 sw1-lr0
-+check ovn-nbctl lsp-set-type sw1-lr0 router
-+check ovn-nbctl lsp-set-addresses sw1-lr0 00:00:00:00:ff:02
-+check ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1
-
--OVN_POPULATE_ARP
-+# Add an ACL that matches on sw0-vir being bound locally.
-+check ovn-nbctl acl-add sw0 to-lport 1000 'is_chassis_resident("sw0-vir") && ip' allow
-
--# Delete sw0-vir and add again.
--ovn-nbctl lsp-del sw0-vir
-+check ovn-nbctl ls-add public
-+check ovn-nbctl lrp-add lr0 lr0-public 00:00:20:20:12:13 172.168.0.100/24
-+check ovn-nbctl lsp-add public public-lr0
-+check ovn-nbctl lsp-set-type public-lr0 router
-+check ovn-nbctl lsp-set-addresses public-lr0 router
-+check ovn-nbctl lsp-set-options public-lr0 router-port=lr0-public
-
--ovn-nbctl lsp-add sw0 sw0-vir
--ovn-nbctl lsp-set-addresses sw0-vir "50:54:00:00:00:10 10.0.0.10"
--ovn-nbctl lsp-set-port-security sw0-vir "50:54:00:00:00:10 10.0.0.10"
--ovn-nbctl lsp-set-type sw0-vir virtual
--ovn-nbctl set logical_switch_port sw0-vir options:virtual-ip=10.0.0.10
--ovn-nbctl set logical_switch_port sw0-vir options:virtual-parents=sw0-p1,sw0-p2,sw0-p3
-+# localnet port
-+check ovn-nbctl lsp-add public ln-public
-+check ovn-nbctl lsp-set-type ln-public localnet
-+check ovn-nbctl lsp-set-addresses ln-public unknown
-+check ovn-nbctl lsp-set-options ln-public network_name=public
-+
-+# schedule the gw router port to a chassis. Change the name of the chassis
-+check ovn-nbctl --wait=hv lrp-set-gateway-chassis lr0-public hv1 20
-+
-+check ovn-nbctl lr-nat-add lr0 dnat_and_snat 172.168.0.50 10.0.0.10 sw0-vir 10:54:00:00:00:10
-+
-+OVN_POPULATE_ARP
-
- wait_for_ports_up
- ovn-nbctl --wait=hv sync
-@@ -16746,6 +16859,30 @@ ovs-vsctl del-port hv1-vif3
- AT_CHECK([test x$(ovn-sbctl --bare --columns chassis find port_binding \
- logical_port=sw0-vir) = x], [0], [])
-
-+check_virtual_offlows_present() {
-+ hv=$1
-+
-+ AT_CHECK([as $hv ovs-ofctl dump-flows br-int table=44 | ofctl_strip_all | grep "priority=2000"], [0], [dnl
-+ table=44, priority=2000,ip,metadata=0x1 actions=resubmit(,45)
-+ table=44, priority=2000,ipv6,metadata=0x1 actions=resubmit(,45)
-+])
-+
-+ AT_CHECK([as $hv ovs-ofctl dump-flows br-int table=11 | ofctl_strip_all | \
-+ grep "priority=92" | grep 172.168.0.50], [0], [dnl
-+ table=11, priority=92,arp,reg14=0x3,metadata=0x3,arp_tpa=172.168.0.50,arp_op=1 actions=move:NXM_OF_ETH_SRC[[]]->NXM_OF_ETH_DST[[]],mod_dl_src:10:54:00:00:00:10,load:0x2->NXM_OF_ARP_OP[[]],move:NXM_NX_ARP_SHA[[]]->NXM_NX_ARP_THA[[]],load:0x105400000010->NXM_NX_ARP_SHA[[]],push:NXM_OF_ARP_SPA[[]],push:NXM_OF_ARP_TPA[[]],pop:NXM_OF_ARP_SPA[[]],pop:NXM_OF_ARP_TPA[[]],move:NXM_NX_REG14[[]]->NXM_NX_REG15[[]],load:0x1->NXM_NX_REG10[[0]],resubmit(,37)
-+])
-+}
-+
-+check_virtual_offlows_not_present() {
-+ hv=$1
-+ AT_CHECK([as $hv ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | grep "priority=2000"], [1], [dnl
-+])
-+
-+ AT_CHECK([as $hv ovs-ofctl dump-flows br-int table=11 | ofctl_strip_all | \
-+ grep "priority=92" | grep 172.168.0.50], [1], [dnl
-+])
-+}
-+
- # From sw0-p0 send GARP for 10.0.0.10. hv1 should claim sw0-vir
- # and sw0-p1 should be its virtual_parent.
- eth_src=505400000003
-@@ -16767,6 +16904,13 @@ AT_CHECK([grep lr_in_arp_resolve lr0-flows2 | grep "reg0 == 10.0.0.10" | sed 's/
- table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:03; next;)
- ])
-
-+# hv1 should add the flow for the ACL with is_chassis_redirect check for sw0-vir and
-+# arp responder flow in lr0 pipeline.
-+check_virtual_offlows_present hv1
-+
-+# hv2 should not have the above flows.
-+check_virtual_offlows_not_present hv2
-+
- # Forcibly clear virtual_parent. ovn-controller should release the binding
- # gracefully.
- pb_uuid=$(ovn-sbctl --bare --columns _uuid find port_binding logical_port=sw0-vir)
-@@ -16777,6 +16921,13 @@ logical_port=sw0-vir) = x])
-
- wait_row_count nb:Logical_Switch_Port 1 up=false name=sw0-vir
-
-+check ovn-nbctl --wait=hv sync
-+# hv1 should remove the flow for the ACL with is_chassis_redirect check for sw0-vir.
-+check_virtual_offlows_not_present hv1
-+
-+# hv2 should not have the flow for ACL.
-+check_virtual_offlows_not_present hv2
-+
- # From sw0-p0 resend GARP for 10.0.0.10. hv1 should reclaim sw0-vir
- # and sw0-p1 should be its virtual_parent.
- send_garp 1 1 $eth_src $eth_dst $spa $tpa
-@@ -16789,6 +16940,58 @@ logical_port=sw0-vir) = xsw0-p1])
-
- wait_for_ports_up sw0-vir
-
-+check ovn-nbctl --wait=hv sync
-+# hv1 should add the flow for the ACL with is_chassis_redirect check for sw0-vir and
-+# arp responder flow in lr0 pipeline.
-+check_virtual_offlows_present hv1
-+
-+# hv2 should not have the above flows.
-+check_virtual_offlows_not_present hv2
-+
-+# Release sw0-p1.
-+as hv1 ovs-vsctl set interface hv1-vif1 external-ids:iface-id=sw0-px
-+wait_column "false" nb:Logical_Switch_Port up name=sw0-p1
-+wait_column "false" nb:Logical_Switch_Port up name=sw0-vir
-+
-+check ovn-nbctl --wait=hv sync
-+# hv1 should remove the flow for the ACL with is_chassis_redirect check for sw0-vir and
-+# arp responder flow in lr0 pipeline.
-+check_virtual_offlows_not_present hv1
-+
-+# hv2 should not have the above flows.
-+check_virtual_offlows_not_present hv2
-+
-+# Claim sw0-p1 again.
-+as hv1 ovs-vsctl set interface hv1-vif1 external-ids:iface-id=sw0-p1
-+wait_for_ports_up sw0-p1
-+
-+# hv1 should not have the flow for the ACL with is_chassis_redirect check for sw0-vir and
-+# arp responder flow in lr0 pipeline.
-+check_virtual_offlows_not_present hv1
-+
-+# hv2 should not have the above flows.
-+check_virtual_offlows_not_present hv2
-+
-+# From sw0-p0 send GARP for 10.0.0.10. hv1 should claim sw0-vir
-+# and sw0-p1 should be its virtual_parent.
-+eth_src=505400000003
-+eth_dst=ffffffffffff
-+spa=$(ip_to_hex 10 0 0 10)
-+tpa=$(ip_to_hex 10 0 0 10)
-+send_garp 1 1 $eth_src $eth_dst $spa $tpa
-+
-+wait_row_count Port_Binding 1 logical_port=sw0-vir chassis=$hv1_ch_uuid
-+check_row_count Port_Binding 1 logical_port=sw0-vir virtual_parent=sw0-p1
-+wait_for_ports_up sw0-vir
-+check ovn-nbctl --wait=hv sync
-+
-+# hv1 should add the flow for the ACL with is_chassis_redirect check for sw0-vir and
-+# arp responder flow in lr0 pipeline.
-+check_virtual_offlows_present hv1
-+
-+# hv2 should not have the above flows.
-+check_virtual_offlows_not_present hv2
-+
- # From sw0-p3 send GARP for 10.0.0.10. hv1 should claim sw0-vir
- # and sw0-p3 should be its virtual_parent.
- eth_src=505400000005
-@@ -16806,8 +17009,8 @@ logical_port=sw0-vir) = xsw0-p3])
- wait_for_ports_up sw0-vir
-
- # There should be an arp resolve flow to resolve the virtual_ip with the
--# sw0-p2's MAC.
--sleep 1
-+# sw0-p3's MAC.
-+check ovn-nbctl --wait=hv sync
- ovn-sbctl dump-flows lr0 > lr0-flows3
- AT_CAPTURE_FILE([lr0-flows3])
- cp ovn-sb/ovn-sb.db lr0-flows3.db
-@@ -16815,6 +17018,13 @@ AT_CHECK([grep lr_in_arp_resolve lr0-flows3 | grep "reg0 == 10.0.0.10" | sed 's
- table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:05; next;)
- ])
-
-+# hv1 should add the flow for the ACL with is_chassis_redirect check for sw0-vir and
-+# arp responder flow in lr0 pipeline.
-+check_virtual_offlows_present hv1
-+
-+# hv2 should not have the above flows.
-+check_virtual_offlows_not_present hv2
-+
- # send the garp from sw0-p2 (in hv2). hv2 should claim sw0-vir
- # and sw0-p2 shpuld be its virtual_parent.
- eth_src=505400000004
-@@ -16832,14 +17042,21 @@ logical_port=sw0-vir) = xsw0-p2])
- wait_for_ports_up sw0-vir
-
- # There should be an arp resolve flow to resolve the virtual_ip with the
--# sw0-p3's MAC.
--sleep 1
-+# sw0-p2's MAC.
-+check ovn-nbctl --wait=hv sync
- ovn-sbctl dump-flows lr0 > lr0-flows4
- AT_CAPTURE_FILE([lr0-flows4])
- AT_CHECK([grep lr_in_arp_resolve lr0-flows4 | grep "reg0 == 10.0.0.10" | sed 's/table=../table=??/'], [0], [dnl
- table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:04; next;)
- ])
-
-+# hv2 should add the flow for the ACL with is_chassis_redirect check for sw0-vir and
-+# arp responder flow in lr0 pipeline.
-+check_virtual_offlows_present hv2
-+
-+# hv1 should not have the above flows.
-+check_virtual_offlows_not_present hv1
-+
- # Now send arp reply from sw0-p1. hv1 should claim sw0-vir
- # and sw0-p1 shpuld be its virtual_parent.
- eth_src=505400000003
-@@ -16863,6 +17080,14 @@ AT_CHECK([grep lr_in_arp_resolve lr0-flows5 | grep "reg0 == 10.0.0.10" | sed 's/
- table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:03; next;)
- ])
-
-+check ovn-nbctl --wait=hv sync
-+# hv1 should add the flow for the ACL with is_chassis_redirect check for sw0-vir and
-+# arp responder flow in lr0 pipeline.
-+check_virtual_offlows_present hv1
-+
-+# hv2 should not have the above flows.
-+check_virtual_offlows_not_present hv2
-+
- # Delete hv1-vif1 port. hv1 should release sw0-vir
- as hv1 ovs-vsctl del-port hv1-vif1
-
-@@ -16883,6 +17108,15 @@ AT_CHECK([grep lr_in_arp_resolve lr0-flows6 | grep "reg0 == 10.0.0.10" | sed 's/
- table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 00:00:00:00:00:00; next;)
- ])
-
-+check ovn-nbctl --wait=hv sync
-+# hv1 should remove the flow for the ACL with is_chassis_redirect check for sw0-vir and
-+# arp responder flow in lr0 pipeline.
-+check_virtual_offlows_not_present hv1
-+
-+# hv2 should not have the above flows.
-+check_virtual_offlows_not_present hv2
-+
-+
- # Now send arp reply from sw0-p2. hv2 should claim sw0-vir
- # and sw0-p2 should be its virtual_parent.
- eth_src=505400000004
-@@ -16906,6 +17140,14 @@ AT_CHECK([grep lr_in_arp_resolve lr0-flows7 | grep "reg0 == 10.0.0.10" | sed 's/
- table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:04; next;)
- ])
-
-+check ovn-nbctl --wait=hv sync
-+# hv2 should add the flow for the ACL with is_chassis_redirect check for sw0-vir and
-+# arp responder flow in lr0 pipeline.
-+check_virtual_offlows_present hv2
-+
-+# hv1 should not have the above flows.
-+check_virtual_offlows_not_present hv1
-+
- # Delete sw0-p2 logical port
- ovn-nbctl lsp-del sw0-p2
-
-@@ -16933,6 +17175,14 @@ AT_CHECK([grep ls_in_arp_rsp sw0-flows3 | grep bind_vport | sed 's/table=../tabl
- table=??(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p3" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;)
- ])
-
-+check ovn-nbctl --wait=hv sync
-+# hv2 should remove the flow for the ACL with is_chassis_redirect check for sw0-vir and
-+# arp responder flow in lr0 pipeline.
-+check_virtual_offlows_not_present hv2
-+
-+# hv1 should not have the above flows.
-+check_virtual_offlows_not_present hv2
-+
- ovn-nbctl --wait=hv remove logical_switch_port sw0-vir options virtual-parents
- ovn-sbctl dump-flows sw0 > sw0-flows4
- AT_CAPTURE_FILE([sw0-flows4])
-@@ -16942,6 +17192,38 @@ ovn-sbctl dump-flows lr0 > lr0-flows8
- AT_CAPTURE_FILE([lr0-flows8])
- AT_CHECK([grep lr_in_arp_resolve lr0-flows8 | grep "reg0 == 10.0.0.10"], [1])
-
-+# Delete sw0-vir and add again.
-+ovn-nbctl lsp-del sw0-vir
-+
-+ovn-nbctl lsp-add sw0 sw0-vir
-+ovn-nbctl lsp-set-addresses sw0-vir "50:54:00:00:00:10 10.0.0.10"
-+ovn-nbctl lsp-set-port-security sw0-vir "50:54:00:00:00:10 10.0.0.10"
-+ovn-nbctl lsp-set-type sw0-vir virtual
-+ovn-nbctl set logical_switch_port sw0-vir options:virtual-ip=10.0.0.10
-+ovn-nbctl set logical_switch_port sw0-vir options:virtual-parents=sw0-p1,sw0-p2,sw0-p3
-+
-+ovn-nbctl --wait=hv sync
-+
-+# Check that logical flows are added for sw0-vir in lsp_in_arp_rsp pipeline
-+# with bind_vport action.
-+
-+ovn-sbctl dump-flows sw0 > sw0-flows
-+AT_CAPTURE_FILE([sw0-flows])
-+
-+AT_CHECK([grep ls_in_arp_rsp sw0-flows | grep bind_vport | sed 's/table=../table=??/' | sort], [0], [dnl
-+ table=??(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p1" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;)
-+ table=??(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p3" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;)
-+])
-+
-+ovn-sbctl dump-flows lr0 > lr0-flows
-+AT_CAPTURE_FILE([lr0-flows])
-+
-+# Since the sw0-vir is not claimed by any chassis, eth.dst should be set to
-+# zero if the ip4.dst is the virtual ip in the router pipeline.
-+AT_CHECK([grep lr_in_arp_resolve lr0-flows | grep "reg0 == 10.0.0.10" | sed 's/table=../table=??/'], [0], [dnl
-+ table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 00:00:00:00:00:00; next;)
-+])
-+
- OVN_CLEANUP([hv1], [hv2])
- AT_CLEANUP
-
-@@ -17321,6 +17603,27 @@ check ovs-vsctl -- add-port br-int hv2-vif4 -- \
- ofport-request=1
- ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
-
-+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_POPULATE_ARP
-
- # Enable IGMP snooping on sw1.
-@@ -17337,21 +17640,16 @@ ovn-sbctl dump-flows > sbflows
- AT_CAPTURE_FILE([expected])
- AT_CAPTURE_FILE([received])
- > expected
--> received
--for i in 1 2; do
-- for j in 1 2; do
-- pcap=hv$i/vif$j-tx.pcap
-- echo "--- $pcap" | tee -a expected >> received
-- $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $pcap | sort >> received
-- echo | tee -a expected >> received
-- done
--done
--check $at_diff -F'^---' expected received
-+OVS_WAIT_UNTIL(
-+ [check_packets 'hv1/vif1-tx.pcap expected' \
-+ 'hv1/vif2-tx.pcap expected' \
-+ 'hv2/vif1-tx.pcap expected' \
-+ 'hv2/vif2-tx.pcap expected'],
-+ [$at_diff -F'^---' exp rcv])
-
- check ovn-nbctl --wait=hv sync
-
- AT_CAPTURE_FILE([sbflows2])
--cp ovn-sb/ovn-sb.db ovn-sb2.db
- ovn-sbctl dump-flows > sbflows2
-
- # Inject IGMP Join for 239.0.1.68 on sw1-p11.
-@@ -17369,7 +17667,6 @@ wait_row_count IGMP_Group 2 address=239.0.1.68
- check ovn-nbctl --wait=hv sync
-
- AT_CAPTURE_FILE([sbflows3])
--cp ovn-sb/ovn-sb.db ovn-sb3.db
- ovn-sbctl dump-flows > sbflows3
-
- AS_BOX([IGMP traffic test 1])
-@@ -17386,22 +17683,6 @@ store_ip_multicast_pkt \
- $(ip_to_hex 10 0 0 42) $(ip_to_hex 239 0 1 68) 1e 20 ca70 11 \
- e518e518000a3b3a0000 expected
-
--AT_CAPTURE_FILE([exp])
--AT_CAPTURE_FILE([rcv])
--check_packets() {
-- > exp
-- > rcv
-- 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
--}
--
- OVS_WAIT_UNTIL(
- [check_packets 'hv1/vif1-tx.pcap expected' \
- 'hv2/vif1-tx.pcap expected' \
-@@ -17492,15 +17773,26 @@ check ovn-nbctl set Logical_Switch sw2 \
- other_config:mcast_ip4_src="20.0.0.254"
-
- AS_BOX([IGMP traffic test 4])
--# Wait for 1 query interval (1 sec) and check that two queries are generated.
-+# Check that multiple queries are generated over time.
- > expected
- store_igmp_v3_query 0000000002fe $(ip_to_hex 20 0 0 254) 84dd expected
- store_igmp_v3_query 0000000002fe $(ip_to_hex 20 0 0 254) 84dd expected
-
--OVS_WAIT_UNTIL(
-- [check_packets 'hv1/vif3-tx.pcap expected' \
-- 'hv2/vif3-tx.pcap expected'],
-- [$at_diff -F'^---' exp rcv])
-+for count in 1 2 3; do
-+ as hv1 reset_pcap_file hv1-vif1 hv1/vif1
-+ as hv1 reset_pcap_file hv1-vif2 hv1/vif2
-+ as hv1 reset_pcap_file hv1-vif3 hv1/vif3
-+ as hv1 reset_pcap_file hv1-vif4 hv1/vif4
-+ as hv2 reset_pcap_file hv2-vif1 hv2/vif1
-+ as hv2 reset_pcap_file hv2-vif2 hv2/vif2
-+ as hv2 reset_pcap_file hv2-vif3 hv2/vif3
-+ as hv2 reset_pcap_file hv2-vif4 hv2/vif4
-+ OVS_WAIT_UNTIL(
-+ [check_packets --uniq \
-+ 'hv1/vif3-tx.pcap expected' \
-+ 'hv2/vif3-tx.pcap expected'],
-+ [$at_diff -F'^---' exp rcv])
-+done
-
- # Disable IGMP querier on sw2.
- check ovn-nbctl set Logical_Switch sw2 \
-@@ -19776,7 +20068,14 @@ AT_CAPTURE_FILE([sbflows])
- OVS_WAIT_FOR_OUTPUT(
- [ovn-sbctl dump-flows > sbflows
- ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 | sed 's/table=..//'], 0,
-- [ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80; hash_fields="ip_dst,ip_src,tcp_dst,tcp_src");)
-+ [dnl
-+ (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip4 && sctp), action=(reg1 = ip4.dst; reg2[[0..15]] = sctp.dst; ct_lb;)
-+ (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip4 && tcp), action=(reg1 = ip4.dst; reg2[[0..15]] = tcp.dst; ct_lb;)
-+ (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip4 && udp), action=(reg1 = ip4.dst; reg2[[0..15]] = udp.dst; ct_lb;)
-+ (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip6 && sctp), action=(xxreg1 = ip6.dst; reg2[[0..15]] = sctp.dst; ct_lb;)
-+ (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip6 && tcp), action=(xxreg1 = ip6.dst; reg2[[0..15]] = tcp.dst; ct_lb;)
-+ (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip6 && udp), action=(xxreg1 = ip6.dst; reg2[[0..15]] = udp.dst; ct_lb;)
-+ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80; hash_fields="ip_dst,ip_src,tcp_dst,tcp_src");)
- ])
-
- AT_CAPTURE_FILE([sbflows2])
-@@ -22463,7 +22762,7 @@ check ovn-nbctl --wait=hv sync
- # wait_conj_id_count COUNT ["ID COUNT [MATCH]"]...
- #
- # Waits until COUNT flows matching against conj_id appear in the
--# table 45 on hv1's br-int bridge. Makes the flows available in
-+# table 44 on hv1's br-int bridge. Makes the flows available in
- # "hv1flows", which will be logged on error.
- #
- # In addition, for each quoted "ID COUNT" or "ID COUNT MATCH",
-@@ -22480,7 +22779,7 @@ wait_conj_id_count() {
- echo "waiting for $1 conj_id flows..."
- OVS_WAIT_FOR_OUTPUT_UNQUOTED(
- [ovs-ofctl dump-flows br-int > hv1flows
-- grep table=45 hv1flows | grep -c conj_id],
-+ grep table=44 hv1flows | grep -c conj_id],
- [$retval], [$1
- ])
-
-@@ -22489,7 +22788,7 @@ wait_conj_id_count() {
- set -- $arg; id=$1 count=$2 match=$3
- echo "checking that there are $count ${match:+$match }flows with conj_id=$id..."
- AT_CHECK_UNQUOTED(
-- [grep table=45 hv1flows | grep "$match" | grep -c conj_id=$id],
-+ [grep table=44 hv1flows | grep "$match" | grep -c conj_id=$id],
- [0], [$count
- ])
- done
-@@ -22514,8 +22813,8 @@ wait_conj_id_count 1 "3 1 udp"
- AS_BOX([Add back the tcp ACL.])
- check ovn-nbctl --wait=hv acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && tcp.dst >= 80 && tcp.dst <= 82" allow
- wait_conj_id_count 2 "3 1 udp" "4 1 tcp"
--AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep udp | grep -c "conj_id=3")])
--AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep tcp | grep -c "conj_id=4")])
-+AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=44 | grep udp | grep -c "conj_id=3")])
-+AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=44 | grep tcp | grep -c "conj_id=4")])
-
- AS_BOX([Add another tcp ACL.])
- check ovn-nbctl --wait=hv acl-add pg0 to-lport 1002 "outport == @pg0 && inport == @pg0 && ip4 && tcp.dst >= 84 && tcp.dst <= 86" allow
-@@ -24317,6 +24616,14 @@ as hv1 ovn-appctl -t ovn-controller debug/resume
- wait_column "true" Port_Binding up logical_port=lsp1
- wait_column "true" nb:Logical_Switch_Port up name=lsp1
-
-+AS_BOX([ovn-controller should set Port_Binding.up - to false when OVS port is released])
-+check ovs-vsctl remove Interface lsp1 external_ids iface-id
-+check ovs-vsctl remove Interface lsp2 external_ids iface-id
-+wait_column "false" Port_Binding up logical_port=lsp1
-+wait_column "false" Port_Binding up logical_port=lsp2
-+wait_column "false" Port_Binding up logical_port=lsp1
-+wait_column "false" nb:Logical_Switch_Port up name=lsp1
-+
- OVN_CLEANUP([hv1])
- AT_CLEANUP
-
-@@ -24454,43 +24761,43 @@ AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)])
- check ovn-nbctl --wait=hv sync
-
- # Check OVS flows are installed properly.
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=44 | ofctl_strip_all | \
- grep "priority=2002" | grep conjunction | \
- sed 's/conjunction([[^)]]*)/conjunction()/g' | sort], [0], [dnl
-- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x10/0xfff0 actions=conjunction()
-- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x100/0xff00 actions=conjunction()
-- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x1000/0xf000 actions=conjunction()
-- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x2/0xfffe actions=conjunction()
-- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x20/0xffe0 actions=conjunction()
-- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x200/0xfe00 actions=conjunction()
-- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x2000/0xe000 actions=conjunction()
-- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x4/0xfffc actions=conjunction()
-- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x40/0xffc0 actions=conjunction()
-- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x400/0xfc00 actions=conjunction()
-- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x4000/0xc000 actions=conjunction()
-- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x8/0xfff8 actions=conjunction()
-- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x80/0xff80 actions=conjunction()
-- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x800/0xf800 actions=conjunction()
-- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x8000/0x8000 actions=conjunction()
-- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=1 actions=conjunction()
-- table=45, priority=2002,udp,reg0=0x100/0x100,reg15=0x3,metadata=0x1,nw_src=192.168.47.3 actions=conjunction()
-- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x10/0xfff0 actions=conjunction()
-- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x100/0xff00 actions=conjunction()
-- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x1000/0xf000 actions=conjunction()
-- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x2/0xfffe actions=conjunction()
-- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x20/0xffe0 actions=conjunction()
-- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x200/0xfe00 actions=conjunction()
-- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x2000/0xe000 actions=conjunction()
-- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x4/0xfffc actions=conjunction()
-- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x40/0xffc0 actions=conjunction()
-- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x400/0xfc00 actions=conjunction()
-- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x4000/0xc000 actions=conjunction()
-- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x8/0xfff8 actions=conjunction()
-- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x80/0xff80 actions=conjunction()
-- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x800/0xf800 actions=conjunction()
-- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x8000/0x8000 actions=conjunction()
-- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=1 actions=conjunction()
-- table=45, priority=2002,udp,reg0=0x80/0x80,reg15=0x3,metadata=0x1,nw_src=192.168.47.3 actions=conjunction()
-+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x10/0xfff0 actions=conjunction()
-+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x100/0xff00 actions=conjunction()
-+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x1000/0xf000 actions=conjunction()
-+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x2/0xfffe actions=conjunction()
-+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x20/0xffe0 actions=conjunction()
-+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x200/0xfe00 actions=conjunction()
-+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x2000/0xe000 actions=conjunction()
-+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x4/0xfffc actions=conjunction()
-+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x40/0xffc0 actions=conjunction()
-+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x400/0xfc00 actions=conjunction()
-+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x4000/0xc000 actions=conjunction()
-+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x8/0xfff8 actions=conjunction()
-+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x80/0xff80 actions=conjunction()
-+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x800/0xf800 actions=conjunction()
-+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x8000/0x8000 actions=conjunction()
-+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=1 actions=conjunction()
-+ table=44, priority=2002,udp,reg0=0x100/0x100,reg15=0x3,metadata=0x1,nw_src=192.168.47.3 actions=conjunction()
-+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x10/0xfff0 actions=conjunction()
-+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x100/0xff00 actions=conjunction()
-+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x1000/0xf000 actions=conjunction()
-+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x2/0xfffe actions=conjunction()
-+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x20/0xffe0 actions=conjunction()
-+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x200/0xfe00 actions=conjunction()
-+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x2000/0xe000 actions=conjunction()
-+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x4/0xfffc actions=conjunction()
-+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x40/0xffc0 actions=conjunction()
-+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x400/0xfc00 actions=conjunction()
-+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x4000/0xc000 actions=conjunction()
-+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x8/0xfff8 actions=conjunction()
-+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x80/0xff80 actions=conjunction()
-+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x800/0xf800 actions=conjunction()
-+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x8000/0x8000 actions=conjunction()
-+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=1 actions=conjunction()
-+ table=44, priority=2002,udp,reg0=0x80/0x80,reg15=0x3,metadata=0x1,nw_src=192.168.47.3 actions=conjunction()
- ])
-
- OVN_CLEANUP([hv1])
-@@ -24918,3 +25225,633 @@ AT_CHECK([cat hv2_offlows_table72.txt | grep -v NXST], [1], [dnl
-
- OVN_CLEANUP([hv1], [hv2])
- AT_CLEANUP
-+
-+AT_SETUP([ovn -- container port changed to normal port and then deleted])
-+ovn_start
-+
-+net_add n1
-+
-+sim_add hv1
-+as hv1
-+ovs-vsctl add-br br-phys
-+ovn_attach n1 br-phys 192.168.0.1
-+ovs-vsctl -- add-port br-int vm1
-+
-+check ovn-nbctl ls-add ls
-+check ovn-nbctl lsp-add ls vm1
-+check ovn-nbctl lsp-add ls vm-cont vm1 1
-+check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=vm1
-+
-+wait_for_ports_up
-+
-+check as hv1 ovn-appctl -t ovn-controller debug/pause
-+check ovn-nbctl clear logical_switch_port vm-cont parent_name
-+check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=foo
-+check ovn-nbctl lsp-del vm-cont
-+check as hv1 ovn-appctl -t ovn-controller debug/resume
-+
-+ovn-nbctl --wait=hv sync
-+
-+# Make sure that ovn-controller has not asserted.
-+AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)])
-+
-+wait_column "false" nb:Logical_Switch_Port up name=vm1
-+
-+check ovn-nbctl lsp-add ls vm-cont1 vm1 1
-+check ovn-nbctl lsp-add ls vm-cont2 vm1 2
-+
-+check ovn-nbctl --wait=sb lsp-del vm1
-+
-+check as hv1 ovn-appctl -t ovn-controller debug/pause
-+check ovn-nbctl clear logical_switch_port vm-cont1 parent_name
-+check ovn-nbctl clear logical_switch_port vm-cont2 parent_name
-+
-+check as hv1 ovn-appctl -t ovn-controller debug/resume
-+
-+check ovn-nbctl --wait=hv sync
-+
-+# Make sure that ovn-controller has not crashed.
-+AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)])
-+
-+check ovn-nbctl lsp-add ls vm1
-+check ovn-nbctl set logical_switch_port vm-cont1 parent_name=vm1
-+check ovn-nbctl --wait=sb set logical_switch_port vm-cont2 parent_name=vm1
-+check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=vm1
-+
-+wait_for_ports_up
-+
-+check as hv1 ovn-appctl -t ovn-controller debug/pause
-+check ovn-nbctl --wait=sb lsp-del vm1
-+check ovn-nbctl clear logical_switch_port vm-cont1 parent_name
-+check ovn-nbctl --wait=sb clear logical_switch_port vm-cont2 parent_name
-+check ovn-nbctl lsp-del vm-cont1
-+check ovn-nbctl --wait=sb lsp-del vm-cont2
-+check as hv1 ovn-appctl -t ovn-controller debug/resume
-+
-+check ovn-nbctl --wait=hv sync
-+
-+# Make sure that ovn-controller has not crashed.
-+AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)])
-+
-+check ovn-nbctl lsp-add ls vm1
-+check ovn-nbctl lsp-add ls vm-cont1 vm1 1
-+check ovn-nbctl lsp-add ls vm-cont2 vm1 2
-+
-+wait_for_ports_up
-+
-+check as hv1 ovn-appctl -t ovn-controller debug/pause
-+check ovn-nbctl clear logical_switch_port vm-cont1 parent_name
-+check ovn-nbctl --wait=sb clear logical_switch_port vm-cont2 parent_name
-+check ovn-nbctl lsp-del vm-cont1
-+check ovn-nbctl lsp-del vm-cont2
-+check as hv1 ovn-appctl -t ovn-controller debug/resume
-+
-+check ovn-nbctl --wait=hv sync
-+
-+# Make sure that ovn-controller has not crashed.
-+AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)])
-+
-+check ovn-nbctl lsp-add ls vm-cont1 vm1 1
-+check ovn-nbctl lsp-add ls vm-cont2 vm1 2
-+
-+wait_for_ports_up
-+
-+check as hv1 ovn-appctl -t ovn-controller debug/pause
-+check ovn-nbctl clear logical_switch_port vm-cont1 parent_name
-+check ovn-nbctl --wait=sb clear logical_switch_port vm-cont2 parent_name
-+
-+check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=foo
-+
-+check as hv1 ovn-appctl -t ovn-controller debug/resume
-+
-+wait_column "false" nb:Logical_Switch_Port up name=vm1
-+wait_column "false" nb:Logical_Switch_Port up name=vm-cont1
-+wait_column "false" nb:Logical_Switch_Port up name=vm-cont2
-+
-+check ovn-nbctl set logical_switch_port vm-cont1 parent_name=vm1
-+check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=vm1
-+check ovn-nbctl --wait=sb set logical_switch_port vm-cont2 parent_name=vm1
-+
-+wait_for_ports_up
-+
-+check as hv1 ovn-appctl -t ovn-controller debug/pause
-+check ovn-nbctl clear logical_switch_port vm-cont1 parent_name
-+check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=vm-cont1
-+check as hv1 ovn-appctl -t ovn-controller debug/resume
-+
-+wait_column "false" nb:Logical_Switch_Port up name=vm1
-+wait_column "true" nb:Logical_Switch_Port up name=vm-cont1
-+wait_column "false" nb:Logical_Switch_Port up name=vm-cont2
-+
-+check ovn-nbctl --wait=sb set logical_switch_port vm-cont2 parent_name=vm-cont1
-+check ovn-nbctl --wait=sb set logical_switch_port vm1 parent_name=vm-cont1
-+
-+wait_for_ports_up
-+
-+# Delete vm1, vm-cont1 and vm-cont2 and recreate again.
-+check ovn-nbctl lsp-del vm1
-+check ovn-nbctl lsp-del vm-cont1
-+check ovn-nbctl --wait=hv lsp-del vm-cont2
-+
-+check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=vm1
-+check ovn-nbctl lsp-add ls vm1
-+check ovn-nbctl lsp-add ls vm-cont1 vm1 1
-+check ovn-nbctl lsp-add ls vm-cont2 vm1 2
-+
-+wait_for_ports_up
-+
-+# Make vm1 as a child port of some non existent lport - foo. vm1, vm1-cont1 and
-+# vm1-cont2 should be released.
-+check ovn-nbctl --wait=sb set logical_switch_port vm1 parent_name=bar
-+wait_column "false" nb:Logical_Switch_Port up name=vm1
-+wait_column "false" nb:Logical_Switch_Port up name=vm-cont1
-+wait_column "false" nb:Logical_Switch_Port up name=vm-cont2
-+
-+OVN_CLEANUP([hv1])
-+AT_CLEANUP
-+
-+AT_SETUP([ovn -- container port changed from one parent to another])
-+ovn_start
-+
-+net_add n1
-+
-+sim_add hv1
-+as hv1
-+ovs-vsctl add-br br-phys
-+ovn_attach n1 br-phys 192.168.0.1
-+ovs-vsctl -- add-port br-int vm1 -- set interface vm1 ofport-request=1
-+ovs-vsctl -- add-port br-int vm2 -- set interface vm1 ofport-request=2
-+
-+check ovn-nbctl ls-add ls
-+check ovn-nbctl lsp-add ls vm1
-+check ovn-nbctl lsp-add ls vm1-cont vm1 1
-+check ovn-nbctl lsp-add ls vm2
-+check ovn-nbctl lsp-add ls vm2-cont vm2 2
-+
-+check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=vm1
-+check as hv1 ovs-vsctl set Interface vm2 external_ids:iface-id=vm2
-+
-+wait_for_ports_up
-+
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep -c dl_vlan=1], [0], [dnl
-+1
-+])
-+
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep -c dl_vlan=2], [0], [dnl
-+1
-+])
-+
-+# change the parent of vm1-cont to vm2.
-+as hv1 ovn-appctl -t ovn-controller vlog/set dbg
-+check ovn-nbctl --wait=sb set logical_switch_port vm1-cont parent_name=vm2 \
-+-- set logical_switch_port vm1-cont tag_request=3
-+
-+wait_for_ports_up
-+
-+check ovn-nbctl --wait=hv sync
-+
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep -c dl_vlan=1], [1], [dnl
-+0
-+])
-+
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep -c dl_vlan=2], [0], [dnl
-+1
-+])
-+
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep -c dl_vlan=3], [0], [dnl
-+1
-+])
-+
-+OVN_CLEANUP([hv1])
-+AT_CLEANUP
-+
-+AT_SETUP([ovn -- container port use-after-free test])
-+ovn_start
-+
-+net_add n1
-+
-+sim_add hv1
-+as hv1
-+ovs-vsctl add-br br-phys
-+ovn_attach n1 br-phys 192.168.0.1
-+ovs-vsctl -- add-port br-int vm1
-+
-+check ovn-nbctl ls-add ls
-+check ovn-nbctl lsp-add ls vm1
-+check ovn-nbctl lsp-add ls vm-cont vm1 1
-+check ovs-vsctl set Interface vm1 external_ids:iface-id=vm1
-+check ovn-nbctl clear logical_switch_port vm-cont parent_name
-+check ovs-vsctl set Interface vm1 external_ids:iface-id=foo
-+check ovn-nbctl lsp-del vm-cont
-+check ovn-nbctl ls-del ls
-+check ovn-nbctl ls-add ls
-+check ovn-nbctl lsp-add ls vm1
-+check ovn-nbctl lsp-add ls vm-cont vm1 1
-+check ovs-vsctl set Interface vm1 external_ids:iface-id=vm1
-+check as hv1 ovn-appctl -t ovn-controller debug/pause
-+check ovn-nbctl clear logical_switch_port vm-cont parent_name
-+check ovn-nbctl lsp-del vm-cont
-+check as hv1 ovn-appctl -t ovn-controller debug/resume
-+check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=foo
-+
-+ovn-nbctl --wait=hv sync
-+
-+# Make sure that ovn-controller has not asserted.
-+AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)])
-+
-+wait_column "false" nb:Logical_Switch_Port up name=vm1
-+
-+OVN_CLEANUP([hv1])
-+AT_CLEANUP
-+
-+# Test that OVS.external_ids:iface-id doesn't affect non-VIF port bindings.
-+AT_SETUP([ovn -- Non-VIF ports incremental processing])
-+ovn_start
-+
-+net_add n1
-+sim_add hv1
-+as hv1
-+ovs-vsctl add-br br-phys
-+ovn_attach n1 br-phys 192.168.0.10
-+
-+check ovn-nbctl ls-add ls1 -- lsp-add ls1 lsp1
-+
-+as hv1
-+check ovs-vsctl \
-+ -- add-port br-int vif1 \
-+ -- set Interface vif1 external_ids:iface-id=lsp1
-+
-+# ovn-controller should bind the interface.
-+wait_for_ports_up
-+hv_uuid=$(fetch_column Chassis _uuid name=hv1)
-+check_column "$hv_uuid" Port_Binding chassis logical_port=lsp1
-+
-+# Change the port type to router, ovn-controller should release it.
-+check ovn-nbctl --wait=hv lsp-set-type lsp1 router
-+check_column "" Port_Binding chassis logical_port=lsp1
-+
-+# Clear port type, ovn-controller should rebind it.
-+check ovn-nbctl --wait=hv lsp-set-type lsp1 ''
-+check_column "$hv_uuid" Port_Binding chassis logical_port=lsp1
-+
-+# Change the port type to localnet, ovn-controller should release it.
-+check ovn-nbctl --wait=hv lsp-set-type lsp1 localnet
-+check_column "" Port_Binding chassis logical_port=lsp1
-+
-+# Clear port type, ovn-controller should rebind it.
-+check ovn-nbctl --wait=hv lsp-set-type lsp1 ''
-+check_column "$hv_uuid" Port_Binding chassis logical_port=lsp1
-+
-+# Change the port type to localport, ovn-controller should release it.
-+check ovn-nbctl --wait=hv lsp-set-type lsp1 localport
-+check_column "" Port_Binding chassis logical_port=lsp1
-+
-+# Clear port type, ovn-controller should rebind it.
-+check ovn-nbctl --wait=hv lsp-set-type lsp1 ''
-+check_column "$hv_uuid" Port_Binding chassis logical_port=lsp1
-+
-+# Change the port type to localnet and then delete it.
-+# ovn-controller should handle this properly.
-+check as hv1 ovn-appctl -t ovn-controller debug/pause
-+check ovn-nbctl --wait=sb lsp-set-type lsp1 localport
-+check ovn-nbctl --wait=sb lsp-del lsp1
-+check as hv1 ovn-appctl -t ovn-controller debug/resume
-+
-+check ovn-nbctl --wait=hv sync
-+
-+# Make sure that ovn-controller has not asserted.
-+AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)])
-+
-+check ovn-nbctl lsp-add ls1 lsp1
-+wait_for_ports_up
-+
-+# Change the port type to virtual and then delete it.
-+# ovn-controller should handle this properly.
-+check as hv1 ovn-appctl -t ovn-controller debug/pause
-+check ovn-nbctl --wait=sb lsp-set-type lsp1 virtual
-+check ovn-nbctl --wait=sb lsp-del lsp1
-+check as hv1 ovn-appctl -t ovn-controller debug/resume
-+
-+check ovn-nbctl --wait=hv sync
-+
-+# Make sure that ovn-controller has not asserted.
-+AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)])
-+
-+OVN_CLEANUP([hv1])
-+AT_CLEANUP
-+
-+# Tests that ovn-controller creates local bindings correctly by running
-+# ovn-appctl -t ovn-controller debug/dump-local-bindings.
-+# Ideally this test case should have been a unit test case.
-+AT_SETUP([ovn -- ovn-controller local bindings])
-+ovn_start
-+
-+net_add n1
-+
-+sim_add hv1
-+as hv1
-+ovs-vsctl add-br br-phys
-+ovn_attach n1 br-phys 192.168.0.1
-+ovs-vsctl -- add-port br-int hv1-vm1
-+
-+sim_add hv2
-+as hv2
-+ovs-vsctl add-br br-phys
-+ovn_attach n1 br-phys 192.168.0.2
-+ovs-vsctl -- add-port br-int hv2-vm1
-+
-+check ovn-nbctl ls-add sw0
-+check ovn-nbctl lsp-add sw0 sw0p1
-+check ovn-nbctl lsp-add sw0 sw0p2
-+
-+check as hv1 ovs-vsctl set interface hv1-vm1 external_ids:iface-id=sw0p1
-+check as hv2 ovs-vsctl set interface hv2-vm1 external_ids:iface-id=sw0p2
-+
-+wait_for_ports_up
-+
-+AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
-+Local bindings:
-+name: [[sw0p1]], OVS interface name : [[hv1-vm1]], num binding lports : [[1]]
-+primary lport : [[sw0p1]]
-+----------------------------------------
-+])
-+
-+AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
-+Local bindings:
-+name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[1]]
-+primary lport : [[sw0p2]]
-+----------------------------------------
-+])
-+
-+# Create an ovs interface in hv1
-+check as hv1 ovs-vsctl add-port br-int hv1-vm2 -- set interface hv1-vm2 external_ids:iface-id=sw1p1
-+check ovn-nbctl --wait=hv sync
-+AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
-+Local bindings:
-+name: [[sw0p1]], OVS interface name : [[hv1-vm1]], num binding lports : [[1]]
-+primary lport : [[sw0p1]]
-+----------------------------------------
-+name: [[sw1p1]], OVS interface name : [[hv1-vm2]], num binding lports : [[0]]
-+----------------------------------------
-+])
-+
-+# Create lport sw1p1
-+check ovn-nbctl ls-add sw1 -- lsp-add sw1 sw1p1
-+
-+wait_for_ports_up
-+
-+AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
-+Local bindings:
-+name: [[sw0p1]], OVS interface name : [[hv1-vm1]], num binding lports : [[1]]
-+primary lport : [[sw0p1]]
-+----------------------------------------
-+name: [[sw1p1]], OVS interface name : [[hv1-vm2]], num binding lports : [[1]]
-+primary lport : [[sw1p1]]
-+----------------------------------------
-+])
-+
-+# Swap sw0p1 and sw0p2.
-+check as hv1 ovs-vsctl set interface hv1-vm1 external_ids:iface-id=sw0p2
-+check as hv2 ovs-vsctl set interface hv2-vm1 external_ids:iface-id=sw0p1
-+
-+check ovn-nbctl --wait=hv sync
-+
-+AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
-+Local bindings:
-+name: [[sw0p2]], OVS interface name : [[hv1-vm1]], num binding lports : [[1]]
-+primary lport : [[sw0p2]]
-+----------------------------------------
-+name: [[sw1p1]], OVS interface name : [[hv1-vm2]], num binding lports : [[1]]
-+primary lport : [[sw1p1]]
-+----------------------------------------
-+])
-+
-+AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
-+Local bindings:
-+name: [[sw0p1]], OVS interface name : [[hv2-vm1]], num binding lports : [[1]]
-+primary lport : [[sw0p1]]
-+----------------------------------------
-+])
-+
-+# Create child port for sw0p1
-+check ovn-nbctl --wait=hv lsp-add sw0 sw0p1-c1 sw0p1 1
-+AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
-+Local bindings:
-+name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[2]]
-+primary lport : [[sw0p1]]
-+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]]
-+----------------------------------------
-+name: [[sw0p2]], OVS interface name : [[hv1-vm1]], num binding lports : [[1]]
-+primary lport : [[sw0p2]]
-+----------------------------------------
-+name: [[sw1p1]], OVS interface name : [[hv1-vm2]], num binding lports : [[1]]
-+primary lport : [[sw1p1]]
-+----------------------------------------
-+])
-+
-+AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
-+Local bindings:
-+name: [[sw0p1]], OVS interface name : [[hv2-vm1]], num binding lports : [[2]]
-+primary lport : [[sw0p1]]
-+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]]
-+----------------------------------------
-+])
-+
-+# Create another child port for sw0p1
-+check ovn-nbctl --wait=hv lsp-add sw0 sw0p1-c2 sw0p1 2
-+
-+wait_for_ports_up
-+
-+AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
-+Local bindings:
-+name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[3]]
-+primary lport : [[sw0p1]]
-+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]]
-+child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]]
-+----------------------------------------
-+name: [[sw0p2]], OVS interface name : [[hv1-vm1]], num binding lports : [[1]]
-+primary lport : [[sw0p2]]
-+----------------------------------------
-+name: [[sw1p1]], OVS interface name : [[hv1-vm2]], num binding lports : [[1]]
-+primary lport : [[sw1p1]]
-+----------------------------------------
-+])
-+
-+AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
-+Local bindings:
-+name: [[sw0p1]], OVS interface name : [[hv2-vm1]], num binding lports : [[3]]
-+primary lport : [[sw0p1]]
-+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]]
-+child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]]
-+----------------------------------------
-+])
-+
-+# Swap sw0p1 and sw0p2 again.
-+check as hv1 ovs-vsctl set interface hv1-vm1 external_ids:iface-id=sw0p1
-+check as hv2 ovs-vsctl set interface hv2-vm1 external_ids:iface-id=sw0p2
-+
-+check ovn-nbctl --wait=hv sync
-+
-+AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
-+Local bindings:
-+name: [[sw0p1]], OVS interface name : [[hv1-vm1]], num binding lports : [[3]]
-+primary lport : [[sw0p1]]
-+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]]
-+child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]]
-+----------------------------------------
-+name: [[sw1p1]], OVS interface name : [[hv1-vm2]], num binding lports : [[1]]
-+primary lport : [[sw1p1]]
-+----------------------------------------
-+])
-+
-+AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
-+Local bindings:
-+name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[3]]
-+primary lport : [[sw0p1]]
-+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]]
-+child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]]
-+----------------------------------------
-+name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[1]]
-+primary lport : [[sw0p2]]
-+----------------------------------------
-+])
-+
-+# Make sw0p1 as child port of non existent lport - foo
-+check ovn-nbctl --wait=hv set logical_switch_port sw0p1 parent_name=foo
-+
-+AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
-+Local bindings:
-+name: [[foo]], OVS interface name : [[NULL]], num binding lports : [[1]]
-+no primary lport
-+child lport[[1]] : [[sw0p1]], type : [[CONTAINER]]
-+----------------------------------------
-+name: [[sw0p1]], OVS interface name : [[hv1-vm1]], num binding lports : [[2]]
-+no primary lport
-+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]]
-+child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]]
-+----------------------------------------
-+name: [[sw1p1]], OVS interface name : [[hv1-vm2]], num binding lports : [[1]]
-+primary lport : [[sw1p1]]
-+----------------------------------------
-+])
-+
-+AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
-+Local bindings:
-+name: [[foo]], OVS interface name : [[NULL]], num binding lports : [[1]]
-+no primary lport
-+child lport[[1]] : [[sw0p1]], type : [[CONTAINER]]
-+----------------------------------------
-+name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[2]]
-+no primary lport
-+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]]
-+child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]]
-+----------------------------------------
-+name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[1]]
-+primary lport : [[sw0p2]]
-+----------------------------------------
-+])
-+
-+# Change the lport type of sw0p2 to different types and make sure that
-+# local bindings are correct.
-+
-+hv2_uuid=$(fetch_column Chassis _uuid name=hv2)
-+check_column "$hv2_uuid" Port_Binding chassis logical_port=sw0p2
-+
-+# Change the port type to router, ovn-controller should release it.
-+check ovn-nbctl --wait=hv lsp-set-type sw0p2 router
-+check_column "" Port_Binding chassis logical_port=sw0p2
-+
-+AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
-+Local bindings:
-+name: [[foo]], OVS interface name : [[NULL]], num binding lports : [[1]]
-+no primary lport
-+child lport[[1]] : [[sw0p1]], type : [[CONTAINER]]
-+----------------------------------------
-+name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[2]]
-+no primary lport
-+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]]
-+child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]]
-+----------------------------------------
-+name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[0]]
-+----------------------------------------
-+])
-+
-+# change the port type to external from router.
-+check ovn-nbctl --wait=hv lsp-set-type sw0p2 external
-+check_column "" Port_Binding chassis logical_port=sw0p2
-+
-+AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
-+Local bindings:
-+name: [[foo]], OVS interface name : [[NULL]], num binding lports : [[1]]
-+no primary lport
-+child lport[[1]] : [[sw0p1]], type : [[CONTAINER]]
-+----------------------------------------
-+name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[2]]
-+no primary lport
-+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]]
-+child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]]
-+----------------------------------------
-+name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[0]]
-+----------------------------------------
-+])
-+
-+# change the port type to localnet from external.
-+check ovn-nbctl --wait=hv lsp-set-type sw0p2 localnet
-+check_column "" Port_Binding chassis logical_port=sw0p2
-+
-+AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
-+Local bindings:
-+name: [[foo]], OVS interface name : [[NULL]], num binding lports : [[1]]
-+no primary lport
-+child lport[[1]] : [[sw0p1]], type : [[CONTAINER]]
-+----------------------------------------
-+name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[2]]
-+no primary lport
-+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]]
-+child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]]
-+----------------------------------------
-+name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[0]]
-+----------------------------------------
-+])
-+
-+# change the port type to localport from localnet.
-+check ovn-nbctl --wait=hv lsp-set-type sw0p2 localnet
-+check_column "" Port_Binding chassis logical_port=sw0p2
-+
-+AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
-+Local bindings:
-+name: [[foo]], OVS interface name : [[NULL]], num binding lports : [[1]]
-+no primary lport
-+child lport[[1]] : [[sw0p1]], type : [[CONTAINER]]
-+----------------------------------------
-+name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[2]]
-+no primary lport
-+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]]
-+child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]]
-+----------------------------------------
-+name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[0]]
-+----------------------------------------
-+])
-+
-+# change the port type back to vif.
-+check ovn-nbctl --wait=hv lsp-set-type sw0p2 ""
-+wait_column "$hv2_uuid" Port_Binding chassis logical_port=sw0p2
-+
-+AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl
-+Local bindings:
-+name: [[foo]], OVS interface name : [[NULL]], num binding lports : [[1]]
-+no primary lport
-+child lport[[1]] : [[sw0p1]], type : [[CONTAINER]]
-+----------------------------------------
-+name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[2]]
-+no primary lport
-+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]]
-+child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]]
-+----------------------------------------
-+name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[1]]
-+primary lport : [[sw0p2]]
-+----------------------------------------
-+])
-+
-+OVN_CLEANUP([hv1], [hv2])
-+AT_CLEANUP
-diff --git a/tests/system-ovn.at b/tests/system-ovn.at
-index 9819573bb..bd27b01a0 100644
---- a/tests/system-ovn.at
-+++ b/tests/system-ovn.at
-@@ -4722,7 +4722,7 @@ OVS_WAIT_UNTIL([
- ])
-
- OVS_WAIT_UNTIL([
-- n_pkt=$(ovs-ofctl dump-flows br-int table=45 | grep -v n_packets=0 | \
-+ n_pkt=$(ovs-ofctl dump-flows br-int table=44 | grep -v n_packets=0 | \
- grep controller | grep tp_dst=84 -c)
- test $n_pkt -eq 1
- ])
-@@ -5831,3 +5831,131 @@ as
- OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d
- /.*terminating with signal 15.*/d"])
- AT_CLEANUP
-+
-+AT_SETUP([ovn -- No ct_state matches in dp flows when no ACLs in an LS])
-+AT_KEYWORDS([no ct_state match])
-+ovn_start
-+
-+OVS_TRAFFIC_VSWITCHD_START()
-+ADD_BR([br-int])
-+
-+# Set external-ids in br-int needed for ovn-controller
-+ovs-vsctl \
-+ -- set Open_vSwitch . external-ids:system-id=hv1 \
-+ -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
-+ -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
-+ -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
-+ -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
-+
-+# Start ovn-controller
-+start_daemon ovn-controller
-+
-+check ovn-nbctl ls-add sw0
-+
-+check ovn-nbctl lsp-add sw0 sw0-p1
-+check ovn-nbctl lsp-set-addresses sw0-p1 "50:54:00:00:00:03"
-+check ovn-nbctl lsp-set-port-security sw0-p1 "50:54:00:00:00:03"
-+
-+check ovn-nbctl lsp-add sw0 sw0-p2
-+check ovn-nbctl lsp-set-addresses sw0-p2 "50:54:00:00:00:04 10.0.0.4"
-+check ovn-nbctl lsp-set-port-security sw0-p2 "50:54:00:00:00:04 10.0.0.4"
-+
-+
-+# Create the second logical switch with one port and configure some ACLs.
-+check ovn-nbctl ls-add sw1
-+check ovn-nbctl lsp-add sw1 sw1-p1
-+
-+# Create port group and ACLs for sw1 ports.
-+check ovn-nbctl pg-add pg1 sw1-p1
-+check ovn-nbctl acl-add pg1 from-lport 1002 "ip" allow-related
-+check ovn-nbctl acl-add pg1 to-lport 1002 "ip" allow-related
-+
-+
-+OVN_POPULATE_ARP
-+ovn-nbctl --wait=hv sync
-+
-+ADD_NAMESPACES(sw0-p1)
-+ADD_VETH(sw0-p1, sw0-p1, br-int, "10.0.0.3/24", "50:54:00:00:00:03", \
-+ "10.0.0.1")
-+
-+
-+ADD_NAMESPACES(sw0-p2)
-+ADD_VETH(sw0-p2, sw0-p2, br-int, "10.0.0.4/24", "50:54:00:00:00:04", \
-+ "10.0.0.1")
-+
-+ADD_NAMESPACES(sw1-p1)
-+ADD_VETH(sw1-p1, sw1-p1, br-int, "20.0.0.4/24", "30:54:00:00:00:04", \
-+ "20.0.0.1")
-+
-+wait_for_ports_up
-+
-+NS_CHECK_EXEC([sw0-p1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.4 | FORMAT_PING], \
-+[0], [dnl
-+3 packets transmitted, 3 received, 0% packet loss, time 0ms
-+])
-+
-+ovs-appctl dpctl/dump-flows
-+
-+# sw1-p1 may send IPv6 traffic. So filter this out. Since sw1-p1 has
-+# ACLs configured, the datapath flows for the packets from sw1-p1 will have
-+# matches on ct_state and ct_label fields.
-+# Since sw0 doesn't have any ACLs, there should be no match on ct fields.
-+AT_CHECK([ovs-appctl dpctl/dump-flows | grep ct_state | grep -v ipv6 -c], [1], [dnl
-+0
-+])
-+
-+AT_CHECK([ovs-appctl dpctl/dump-flows | grep ct_label | grep -v ipv6 -c], [1], [dnl
-+0
-+])
-+
-+# Add an ACL to sw0.
-+check ovn-nbctl --wait=hv acl-add sw0 to-lport 1002 ip allow-related
-+
-+NS_CHECK_EXEC([sw0-p1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.4 | FORMAT_PING], \
-+[0], [dnl
-+3 packets transmitted, 3 received, 0% packet loss, time 0ms
-+])
-+
-+ovs-appctl dpctl/dump-flows
-+
-+AT_CHECK([ovs-appctl dpctl/dump-flows | grep ct_state | grep -v ipv6 -c], [0], [ignore])
-+
-+AT_CHECK([ovs-appctl dpctl/dump-flows | grep ct_label | grep -v ipv6 -c], [0], [ignore])
-+
-+# Clear ACL for sw0
-+check ovn-nbctl --wait=hv clear logical_switch sw0 acls
-+
-+check ovs-appctl dpctl/del-flows
-+
-+check ovn-nbctl --wait=hv sync
-+
-+NS_CHECK_EXEC([sw0-p1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.4 | FORMAT_PING], \
-+[0], [dnl
-+3 packets transmitted, 3 received, 0% packet loss, time 0ms
-+])
-+
-+ovs-appctl dpctl/dump-flows
-+
-+AT_CHECK([ovs-appctl dpctl/dump-flows | grep ct_state | grep -v ipv6 -c], [1], [dnl
-+0
-+])
-+
-+AT_CHECK([ovs-appctl dpctl/dump-flows | grep ct_label | grep -v ipv6 -c], [1], [dnl
-+0
-+])
-+
-+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([NORTHD_TYPE])
-+
-+as
-+OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
-+/connection dropped.*/d"])
-+AT_CLEANUP
-diff --git a/utilities/ovn-ctl b/utilities/ovn-ctl
-index 967db6d6c..c52c17ee0 100755
---- a/utilities/ovn-ctl
-+++ b/utilities/ovn-ctl
-@@ -45,18 +45,12 @@ pidfile_is_running () {
- test -e "$pidfile" && [ -s "$pidfile" ] && pid=`cat "$pidfile"` && pid_exists "$pid"
- } >/dev/null 2>&1
-
--stop_xx_ovsdb() {
-- if pidfile_is_running $1; then
-- ovn-appctl -t $OVN_RUNDIR/$2 exit
-- fi
--}
--
- stop_nb_ovsdb() {
-- stop_xx_ovsdb $DB_NB_PID ovnnb_db.ctl
-+ OVS_RUNDIR=${OVS_RUNDIR} stop_ovn_daemon ovnnb_db $DB_NB_PID $OVN_RUNDIR/ovnnb_db.ctl
- }
-
- stop_sb_ovsdb() {
-- stop_xx_ovsdb $DB_SB_PID ovnsb_db.ctl
-+ OVS_RUNDIR=${OVS_RUNDIR} stop_ovn_daemon ovnsb_db $DB_SB_PID $OVN_RUNDIR/ovnsb_db.ctl
- }
-
- stop_ovsdb () {
-@@ -65,11 +59,11 @@ stop_ovsdb () {
- }
-
- stop_ic_nb_ovsdb() {
-- stop_xx_ovsdb $DB_IC_NB_PID ovn_ic_nb_db.ctl
-+ OVS_RUNDIR=${OVS_RUNDIR} stop_ovn_daemon ovn_ic_nb_db $DB_IC_NB_PID $OVN_RUNDIR/ovn_ic_nb_db.ctl
- }
-
- stop_ic_sb_ovsdb() {
-- stop_xx_ovsdb $DB_IC_SB_PID ovn_ic_sb_db.ctl
-+ OVS_RUNDIR=${OVS_RUNDIR} stop_ovn_daemon ovn_ic_sb_db $DB_IC_SB_PID $OVN_RUNDIR/ovn_ic_sb_db.ctl
- }
-
- stop_ic_ovsdb () {
-@@ -590,7 +584,7 @@ stop_ic () {
- }
-
- stop_controller () {
-- OVS_RUNDIR=${OVS_RUNDIR} stop_ovn_daemon ovn-controller "$@"
-+ OVS_RUNDIR=${OVS_RUNDIR} stop_ovn_daemon ovn-controller "" "" "$@"
- }
-
- stop_controller_vtep () {
-diff --git a/utilities/ovn-lib.in b/utilities/ovn-lib.in
-index 016815626..301cc5712 100644
---- a/utilities/ovn-lib.in
-+++ b/utilities/ovn-lib.in
-@@ -137,10 +137,22 @@ start_ovn_daemon () {
- }
-
- stop_ovn_daemon () {
-- if test -e "$ovn_rundir/$1.pid"; then
-- if pid=`cat "$ovn_rundir/$1.pid"`; then
-+ local pid_file=$2
-+ local ctl_file=$3
-+ local other_args=$4
-+
-+ if [ -z "$pid_file" ]; then
-+ pid_file="$ovn_rundir/$1.pid"
-+ fi
-+
-+ if test -e "$pid_file"; then
-+ if pid=`cat "$pid_file"`; then
-+ if [ -z "$ctl_file" ]; then
-+ ctl_file="$ovn_rundir/$1.$pid.ctl"
-+ fi
-+
- if pid_exists "$pid" >/dev/null 2>&1; then :; else
-- rm -f $ovn_rundir/$1.$pid.ctl $ovn_rundir/$1.$pid
-+ rm -f $ctl_file $pid_file
- return 0
- fi
-
-@@ -148,7 +160,7 @@ stop_ovn_daemon () {
- actions="TERM .1 .25 .65 1 1 1 1 \
- KILL 1 1 1 2 10 15 30 \
- FAIL"
-- version=`ovs-appctl -T 1 -t $ovn_rundir/$1.$pid.ctl version \
-+ version=`ovs-appctl -T 1 -t $ctl_file version \
- | awk 'NR==1{print $NF}'`
-
- # Use `ovs-appctl exit` only if the running daemon version
-@@ -159,20 +171,36 @@ stop_ovn_daemon () {
- if version_geq "$version" "2.5.90"; then
- actions="$graceful $actions"
- fi
-+ actiontype=""
- for action in $actions; do
- if pid_exists "$pid" >/dev/null 2>&1; then :; else
-- return 0
-+ # pid does not exist.
-+ if [ -n "$actiontype" ]; then
-+ return 0
-+ fi
-+ # But, does the file exist? We may have had a daemon
-+ # segfault with `ovs-appctl exit`. Check one more time
-+ # before deciding that the daemon is dead.
-+ [ -e "$pid_file" ] && sleep 2 && pid=`cat "$pid_file"` 2>/dev/null
-+ if pid_exists "$pid" >/dev/null 2>&1; then :; else
-+ return 0
-+ fi
- fi
- case $action in
- EXIT)
- action "Exiting $1 ($pid)" \
-- ${bindir}/ovs-appctl -T 1 -t $ovn_rundir/$1.$pid.ctl exit $2
-+ ${bindir}/ovs-appctl -T 1 -t $ctl_file exit $other_args
-+ # The above command could have resulted in delayed
-+ # daemon segfault. And if a monitor is running, it
-+ # would restart the daemon giving it a new pid.
- ;;
- TERM)
- action "Killing $1 ($pid)" kill $pid
-+ actiontype="force"
- ;;
- KILL)
- action "Killing $1 ($pid) with SIGKILL" kill -9 $pid
-+ actiontype="force"
- ;;
- FAIL)
- log_failure_msg "Killing $1 ($pid) failed"
-diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
-index 2c77f4ba7..51af138c6 100644
---- a/utilities/ovn-nbctl.c
-+++ b/utilities/ovn-nbctl.c
-@@ -3866,11 +3866,15 @@ static void
- print_routing_policy(const struct nbrec_logical_router_policy *policy,
- struct ds *s)
- {
-- if (policy->nexthop != NULL) {
-- char *next_hop = normalize_prefix_str(policy->nexthop);
-- ds_put_format(s, "%10"PRId64" %50s %15s %25s", policy->priority,
-- policy->match, policy->action, next_hop);
-- free(next_hop);
-+ if (policy->n_nexthops) {
-+ ds_put_format(s, "%10"PRId64" %50s %15s", policy->priority,
-+ policy->match, policy->action);
-+ for (int i = 0; i < policy->n_nexthops; i++) {
-+ char *next_hop = normalize_prefix_str(policy->nexthops[i]);
-+ char *fmt = i ? ", %s" : " %25s";
-+ ds_put_format(s, fmt, next_hop);
-+ free(next_hop);
-+ }
- } else {
- ds_put_format(s, "%10"PRId64" %50s %15s", policy->priority,
- policy->match, policy->action);
-@@ -4068,7 +4072,9 @@ nbctl_lr_route_add(struct ctl_context *ctx)
- goto cleanup;
- }
- } else if (route) {
-- ctl_error(ctx, "duplicate nexthop for the same ECMP route");
-+ if (!may_exist) {
-+ ctl_error(ctx, "duplicate nexthop for the same ECMP route");
-+ }
- goto cleanup;
- }
-
-diff --git a/utilities/ovndb-servers.ocf b/utilities/ovndb-servers.ocf
-index 7351c7d64..eba9c97a1 100755
---- a/utilities/ovndb-servers.ocf
-+++ b/utilities/ovndb-servers.ocf
-@@ -259,6 +259,9 @@ ovsdb_server_notify() {
- ovn-nbctl -- --id=@conn_uuid create Connection \
- target="p${NB_MASTER_PROTO}\:${NB_MASTER_PORT}\:${LISTEN_ON_IP}" \
- inactivity_probe=$INACTIVE_PROBE -- set NB_Global . connections=@conn_uuid
-+ else
-+ CONN_UID=$(sed -e 's/^\[//' -e 's/\]$//' <<< ${conn})
-+ ovn-nbctl set connection "${CONN_UID}" target="p${NB_MASTER_PROTO}\:${NB_MASTER_PORT}\:${LISTEN_ON_IP}"
- fi
-
- conn=`ovn-sbctl get SB_global . connections`
-@@ -267,6 +270,9 @@ inactivity_probe=$INACTIVE_PROBE -- set NB_Global . connections=@conn_uuid
- ovn-sbctl -- --id=@conn_uuid create Connection \
- target="p${SB_MASTER_PROTO}\:${SB_MASTER_PORT}\:${LISTEN_ON_IP}" \
- inactivity_probe=$INACTIVE_PROBE -- set SB_Global . connections=@conn_uuid
-+ else
-+ CONN_UID=$(sed -e 's/^\[//' -e 's/\]$//' <<< ${conn})
-+ ovn-sbctl set connection "${CONN_UID}" target="p${SB_MASTER_PROTO}\:${SB_MASTER_PORT}\:${LISTEN_ON_IP}"
- fi
-
- else
diff --git a/SOURCES/ovn-21.06.0.patch b/SOURCES/ovn-21.06.0.patch
deleted file mode 100644
index a7d4f84..0000000
--- a/SOURCES/ovn-21.06.0.patch
+++ /dev/null
@@ -1,4279 +0,0 @@
-diff --git a/AUTHORS.rst b/AUTHORS.rst
-index 9f9b4fbaa..c243c5358 100644
---- a/AUTHORS.rst
-+++ b/AUTHORS.rst
-@@ -271,6 +271,7 @@ Miguel Angel Ajo majopela@redhat.com
- Mijo Safradin mijo@linux.vnet.ibm.com
- Mika Vaisanen mika.vaisanen@gmail.com
- Minoru TAKAHASHI takahashi.minoru7@gmail.com
-+Mohammad Heib mheib@redhat.com
- Moshe Levi moshele@mellanox.com
- Murphy McCauley murphy.mccauley@gmail.com
- Natasha Gude
-diff --git a/NEWS b/NEWS
-index 839ab2cfe..237a9d8f6 100644
---- a/NEWS
-+++ b/NEWS
-@@ -1,3 +1,7 @@
-+OVN v21.06.1 - xx xxx xxxx
-+--------------------------
-+ - Allow static routes without nexthops.
-+
- OVN v21.06.0 - 18 Jun 2021
- -------------------------
- - ovn-northd-ddlog: New implementation of northd, based on DDlog. This
-diff --git a/TODO.rst b/TODO.rst
-index c89fe203e..618ea4844 100644
---- a/TODO.rst
-+++ b/TODO.rst
-@@ -164,3 +164,9 @@ OVN To-do List
- to find a way of determining if routing has already been executed (on a
- different hypervisor) for the IP multicast packet being processed locally
- in the router pipeline.
-+
-+* ovn-controller Incremental processing
-+
-+ * physical.c has a global simap -localvif_to_ofport which stores the
-+ local OVS interfaces and the ofport numbers. Move this to the engine data
-+ of the engine data node - ed_type_pflow_output.
-diff --git a/configure.ac b/configure.ac
-index 53034388a..a1cdcb7a9 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -13,7 +13,7 @@
- # limitations under the License.
-
- AC_PREREQ(2.63)
--AC_INIT(ovn, 21.06.0, bugs@openvswitch.org)
-+AC_INIT(ovn, 21.06.1, bugs@openvswitch.org)
- 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 7fde0fdbb..ba558efdb 100644
---- a/controller/binding.c
-+++ b/controller/binding.c
-@@ -22,6 +22,7 @@
- #include "patch.h"
-
- #include "lib/bitmap.h"
-+#include "lib/hmapx.h"
- #include "openvswitch/poll-loop.h"
- #include "lib/sset.h"
- #include "lib/util.h"
-@@ -108,6 +109,7 @@ add_local_datapath__(struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
- hmap_insert(local_datapaths, &ld->hmap_node, dp_key);
- ld->datapath = datapath;
- ld->localnet_port = NULL;
-+ shash_init(&ld->external_ports);
- ld->has_local_l3gateway = has_local_l3gateway;
-
- if (tracked_datapaths) {
-@@ -474,6 +476,18 @@ is_network_plugged(const struct sbrec_port_binding *binding_rec,
- return network ? !!shash_find_data(bridge_mappings, network) : false;
- }
-
-+static void
-+update_ld_external_ports(const struct sbrec_port_binding *binding_rec,
-+ struct hmap *local_datapaths)
-+{
-+ struct local_datapath *ld = get_local_datapath(
-+ local_datapaths, binding_rec->datapath->tunnel_key);
-+ if (ld) {
-+ shash_replace(&ld->external_ports, binding_rec->logical_port,
-+ binding_rec);
-+ }
-+}
-+
- static void
- update_ld_localnet_port(const struct sbrec_port_binding *binding_rec,
- struct shash *bridge_mappings,
-@@ -531,38 +545,41 @@ remove_local_lports(const char *iface_id, struct binding_ctx_out *b_ctx)
- }
- }
-
--/* Add a port binding ID (of the form "dp-key"_"port-key") to the set of local
-- * lport IDs. Also track if the set has changed.
-+/* Add a port binding to the set of locally relevant lports.
-+ * Also track if the set has changed.
- */
- static void
--update_local_lport_ids(const struct sbrec_port_binding *pb,
-- struct binding_ctx_out *b_ctx)
-+update_related_lport(const struct sbrec_port_binding *pb,
-+ struct binding_ctx_out *b_ctx)
- {
- char buf[16];
- get_unique_lport_key(pb->datapath->tunnel_key, pb->tunnel_key,
- buf, sizeof(buf));
-- if (sset_add(b_ctx->local_lport_ids, buf) != NULL) {
-- b_ctx->local_lport_ids_changed = true;
-+ if (sset_add(&b_ctx->related_lports->lport_ids, buf) != NULL) {
-+ b_ctx->related_lports_changed = true;
-
- if (b_ctx->tracked_dp_bindings) {
- /* Add the 'pb' to the tracked_datapaths. */
- tracked_binding_datapath_lport_add(pb, b_ctx->tracked_dp_bindings);
- }
- }
-+ sset_add(&b_ctx->related_lports->lport_names, pb->logical_port);
- }
-
--/* Remove a port binding id from the set of local lport IDs. Also track if
-- * the set has changed.
-+/* Remove a port binding id from the set of locally relevant lports.
-+ * Also track if the set has changed.
- */
- static void
--remove_local_lport_ids(const struct sbrec_port_binding *pb,
-- struct binding_ctx_out *b_ctx)
-+remove_related_lport(const struct sbrec_port_binding *pb,
-+ struct binding_ctx_out *b_ctx)
- {
- char buf[16];
- get_unique_lport_key(pb->datapath->tunnel_key, pb->tunnel_key,
- buf, sizeof(buf));
-- if (sset_find_and_delete(b_ctx->local_lport_ids, buf)) {
-- b_ctx->local_lport_ids_changed = true;
-+ sset_find_and_delete(&b_ctx->related_lports->lport_names,
-+ pb->logical_port);
-+ if (sset_find_and_delete(&b_ctx->related_lports->lport_ids, buf)) {
-+ b_ctx->related_lports_changed = true;
-
- if (b_ctx->tracked_dp_bindings) {
- /* Add the 'pb' to the tracked_datapaths. */
-@@ -678,6 +695,20 @@ static struct binding_lport *binding_lport_check_and_cleanup(
-
- static char *get_lport_type_str(enum en_lport_type lport_type);
-
-+void
-+related_lports_init(struct related_lports *rp)
-+{
-+ sset_init(&rp->lport_names);
-+ sset_init(&rp->lport_ids);
-+}
-+
-+void
-+related_lports_destroy(struct related_lports *rp)
-+{
-+ sset_destroy(&rp->lport_names);
-+ sset_destroy(&rp->lport_ids);
-+}
-+
- void
- local_binding_data_init(struct local_binding_data *lbinding_data)
- {
-@@ -1172,7 +1203,7 @@ release_binding_lport(const struct sbrec_chassis *chassis_rec,
- struct binding_ctx_out *b_ctx_out)
- {
- if (is_binding_lport_this_chassis(b_lport, chassis_rec)) {
-- remove_local_lport_ids(b_lport->pb, b_ctx_out);
-+ remove_related_lport(b_lport->pb, b_ctx_out);
- if (!release_lport(b_lport->pb, sb_readonly,
- b_ctx_out->tracked_dp_bindings,
- b_ctx_out->if_mgr)) {
-@@ -1214,7 +1245,7 @@ consider_vif_lport_(const struct sbrec_port_binding *pb,
- pb->datapath, false,
- b_ctx_out->local_datapaths,
- b_ctx_out->tracked_dp_bindings);
-- update_local_lport_ids(pb, b_ctx_out);
-+ update_related_lport(pb, b_ctx_out);
- update_local_lports(pb->logical_port, b_ctx_out);
- if (b_lport->lbinding->iface && qos_map && b_ctx_in->ovs_idl_txn) {
- get_qos_params(pb, qos_map);
-@@ -1405,7 +1436,7 @@ consider_virtual_lport(const struct sbrec_port_binding *pb,
- * its entry from the local_lport_ids if present. This is required
- * when a virtual port moves from one chassis to other.*/
- if (!virtual_b_lport) {
-- remove_local_lport_ids(pb, b_ctx_out);
-+ remove_related_lport(pb, b_ctx_out);
- }
-
- return true;
-@@ -1430,7 +1461,7 @@ consider_nonvif_lport_(const struct sbrec_port_binding *pb,
- b_ctx_out->local_datapaths,
- b_ctx_out->tracked_dp_bindings);
-
-- update_local_lport_ids(pb, b_ctx_out);
-+ update_related_lport(pb, b_ctx_out);
- return claim_lport(pb, NULL, b_ctx_in->chassis_rec, NULL,
- !b_ctx_in->ovnsb_idl_txn, false,
- b_ctx_out->tracked_dp_bindings,
-@@ -1482,7 +1513,7 @@ consider_localnet_lport(const struct sbrec_port_binding *pb,
- get_qos_params(pb, qos_map);
- }
-
-- update_local_lport_ids(pb, b_ctx_out);
-+ update_related_lport(pb, b_ctx_out);
- }
-
- static bool
-@@ -1512,7 +1543,7 @@ consider_ha_lport(const struct sbrec_port_binding *pb,
- pb->datapath, false,
- b_ctx_out->local_datapaths,
- b_ctx_out->tracked_dp_bindings);
-- update_local_lport_ids(pb, b_ctx_out);
-+ update_related_lport(pb, b_ctx_out);
- }
-
- return consider_nonvif_lport_(pb, our_chassis, false, b_ctx_in, b_ctx_out);
-@@ -1614,8 +1645,9 @@ binding_run(struct binding_ctx_in *b_ctx_in, struct binding_ctx_out *b_ctx_out)
- !sset_is_empty(b_ctx_out->egress_ifaces) ? &qos_map : NULL;
-
- struct ovs_list localnet_lports = OVS_LIST_INITIALIZER(&localnet_lports);
-+ struct ovs_list external_lports = OVS_LIST_INITIALIZER(&external_lports);
-
-- struct localnet_lport {
-+ struct lport {
- struct ovs_list list_node;
- const struct sbrec_port_binding *pb;
- };
-@@ -1634,7 +1666,7 @@ binding_run(struct binding_ctx_in *b_ctx_in, struct binding_ctx_out *b_ctx_out)
- case LP_PATCH:
- case LP_LOCALPORT:
- case LP_VTEP:
-- update_local_lport_ids(pb, b_ctx_out);
-+ update_related_lport(pb, b_ctx_out);
- break;
-
- case LP_VIF:
-@@ -1663,11 +1695,14 @@ binding_run(struct binding_ctx_in *b_ctx_in, struct binding_ctx_out *b_ctx_out)
-
- case LP_EXTERNAL:
- consider_external_lport(pb, b_ctx_in, b_ctx_out);
-+ struct lport *ext_lport = xmalloc(sizeof *ext_lport);
-+ ext_lport->pb = pb;
-+ ovs_list_push_back(&external_lports, &ext_lport->list_node);
- break;
-
- case LP_LOCALNET: {
- consider_localnet_lport(pb, b_ctx_in, b_ctx_out, &qos_map);
-- struct localnet_lport *lnet_lport = xmalloc(sizeof *lnet_lport);
-+ struct lport *lnet_lport = xmalloc(sizeof *lnet_lport);
- lnet_lport->pb = pb;
- ovs_list_push_back(&localnet_lports, &lnet_lport->list_node);
- break;
-@@ -1694,7 +1729,7 @@ binding_run(struct binding_ctx_in *b_ctx_in, struct binding_ctx_out *b_ctx_out)
- /* Run through each localnet lport list to see if it is a localnet port
- * on local datapaths discovered from above loop, and update the
- * corresponding local datapath accordingly. */
-- struct localnet_lport *lnet_lport;
-+ struct lport *lnet_lport;
- LIST_FOR_EACH_POP (lnet_lport, list_node, &localnet_lports) {
- update_ld_localnet_port(lnet_lport->pb, &bridge_mappings,
- b_ctx_out->egress_ifaces,
-@@ -1702,6 +1737,15 @@ binding_run(struct binding_ctx_in *b_ctx_in, struct binding_ctx_out *b_ctx_out)
- free(lnet_lport);
- }
-
-+ /* Run through external lport list to see if these are external ports
-+ * on local datapaths discovered from above loop, and update the
-+ * corresponding local datapath accordingly. */
-+ struct lport *ext_lport;
-+ LIST_FOR_EACH_POP (ext_lport, list_node, &external_lports) {
-+ update_ld_external_ports(ext_lport->pb, b_ctx_out->local_datapaths);
-+ free(ext_lport);
-+ }
-+
- shash_destroy(&bridge_mappings);
-
- if (!sset_is_empty(b_ctx_out->egress_ifaces)
-@@ -1895,7 +1939,7 @@ remove_pb_from_local_datapath(const struct sbrec_port_binding *pb,
- struct binding_ctx_out *b_ctx_out,
- struct local_datapath *ld)
- {
-- remove_local_lport_ids(pb, b_ctx_out);
-+ remove_related_lport(pb, b_ctx_out);
- if (!strcmp(pb->type, "patch") ||
- !strcmp(pb->type, "l3gateway")) {
- remove_local_datapath_peer_port(pb, ld, b_ctx_out->local_datapaths);
-@@ -1904,6 +1948,8 @@ remove_pb_from_local_datapath(const struct sbrec_port_binding *pb,
- pb->logical_port)) {
- ld->localnet_port = NULL;
- }
-+ } else if (!strcmp(pb->type, "external")) {
-+ shash_find_and_delete(&ld->external_ports, pb->logical_port);
- }
-
- if (!strcmp(pb->type, "l3gateway")) {
-@@ -2407,6 +2453,9 @@ binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in,
- shash_add(&deleted_virtual_pbs, pb->logical_port, pb);
- } else {
- shash_add(&deleted_other_pbs, pb->logical_port, pb);
-+ if (lport_type == LP_EXTERNAL) {
-+ hmapx_add(b_ctx_out->extport_updated_datapaths, pb->datapath);
-+ }
- }
- }
-
-@@ -2502,7 +2551,7 @@ delete_done:
- case LP_PATCH:
- case LP_LOCALPORT:
- case LP_VTEP:
-- update_local_lport_ids(pb, b_ctx_out);
-+ update_related_lport(pb, b_ctx_out);
- if (lport_type == LP_PATCH) {
- if (!ld) {
- /* If 'ld' for this lport is not present, then check if
-@@ -2561,6 +2610,8 @@ delete_done:
-
- case LP_EXTERNAL:
- handled = consider_external_lport(pb, b_ctx_in, b_ctx_out);
-+ update_ld_external_ports(pb, b_ctx_out->local_datapaths);
-+ hmapx_add(b_ctx_out->extport_updated_datapaths, pb->datapath);
- break;
-
- case LP_LOCALNET: {
-@@ -2926,23 +2977,3 @@ cleanup:
-
- return b_lport;
- }
--
--struct sset *
--binding_collect_local_binding_lports(struct local_binding_data *lbinding_data)
--{
-- struct sset *lports = xzalloc(sizeof *lports);
-- sset_init(lports);
-- struct shash_node *shash_node;
-- SHASH_FOR_EACH (shash_node, &lbinding_data->lports) {
-- struct binding_lport *b_lport = shash_node->data;
-- sset_add(lports, b_lport->name);
-- }
-- return lports;
--}
--
--void
--binding_destroy_local_binding_lports(struct sset *lports)
--{
-- sset_destroy(lports);
-- free(lports);
--}
-diff --git a/controller/binding.h b/controller/binding.h
-index 8f3289476..8fd54092e 100644
---- a/controller/binding.h
-+++ b/controller/binding.h
-@@ -22,6 +22,7 @@
- #include "openvswitch/hmap.h"
- #include "openvswitch/uuid.h"
- #include "openvswitch/list.h"
-+#include "sset.h"
-
- struct hmap;
- struct ovsdb_idl;
-@@ -56,6 +57,19 @@ struct binding_ctx_in {
- const struct ovsrec_interface_table *iface_table;
- };
-
-+/* Locally relevant port bindings, e.g., VIFs that might be bound locally,
-+ * patch ports.
-+ */
-+struct related_lports {
-+ struct sset lport_names; /* Set of port names. */
-+ struct sset lport_ids; /* Set of _
-+ * IDs for fast lookup.
-+ */
-+};
-+
-+void related_lports_init(struct related_lports *);
-+void related_lports_destroy(struct related_lports *);
-+
- struct binding_ctx_out {
- struct hmap *local_datapaths;
- struct local_binding_data *lbinding_data;
-@@ -65,11 +79,9 @@ struct binding_ctx_out {
- /* Track if local_lports have been updated. */
- bool local_lports_changed;
-
-- /* sset of local lport ids in the format
-- * _. */
-- struct sset *local_lport_ids;
-- /* Track if local_lport_ids has been updated. */
-- bool local_lport_ids_changed;
-+ /* Port bindings that are relevant to the local chassis. */
-+ struct related_lports *related_lports;
-+ bool related_lports_changed;
-
- /* Track if non-vif port bindings (e.g., patch, external) have been
- * added/deleted.
-@@ -88,6 +100,8 @@ struct binding_ctx_out {
- struct hmap *tracked_dp_bindings;
-
- struct if_status_mgr *if_mgr;
-+
-+ struct hmapx *extport_updated_datapaths;
- };
-
- struct local_binding_data {
-@@ -133,13 +147,4 @@ bool binding_handle_port_binding_changes(struct binding_ctx_in *,
- void binding_tracked_dp_destroy(struct hmap *tracked_datapaths);
-
- void binding_dump_local_bindings(struct local_binding_data *, struct ds *);
--
--/* Generates a sset of lport names from local_binding_data.
-- * Note: the caller is responsible for destroying and freeing the returned
-- * sset, by calling binding_detroy_local_binding_lports(). */
--struct sset *binding_collect_local_binding_lports(struct local_binding_data *);
--
--/* Destroy and free the lports sset returned by
-- * binding_collect_local_binding_lports(). */
--void binding_destroy_local_binding_lports(struct sset *lports);
- #endif /* controller/binding.h */
-diff --git a/controller/lflow.c b/controller/lflow.c
-index 680b8cca1..4270d0a33 100644
---- a/controller/lflow.c
-+++ b/controller/lflow.c
-@@ -611,7 +611,7 @@ add_matches_to_flow_table(const struct sbrec_logical_flow *lflow,
- get_unique_lport_key(dp_id, port_id, buf, sizeof(buf));
- lflow_resource_add(l_ctx_out->lfrr, REF_TYPE_PORTBINDING, buf,
- &lflow->header_.uuid);
-- if (!sset_contains(l_ctx_in->local_lport_ids, buf)) {
-+ if (!sset_contains(l_ctx_in->related_lport_ids, buf)) {
- VLOG_DBG("lflow "UUID_FMT
- " port %s in match is not local, skip",
- UUID_ARGS(&lflow->header_.uuid),
-diff --git a/controller/lflow.h b/controller/lflow.h
-index 3c929d8a6..076b05beb 100644
---- a/controller/lflow.h
-+++ b/controller/lflow.h
-@@ -143,7 +143,7 @@ struct lflow_ctx_in {
- const struct shash *addr_sets;
- const struct shash *port_groups;
- const struct sset *active_tunnels;
-- const struct sset *local_lport_ids;
-+ const struct sset *related_lport_ids;
- };
-
- struct lflow_ctx_out {
-diff --git a/controller/ofctrl.c b/controller/ofctrl.c
-index c29c3d180..053631590 100644
---- a/controller/ofctrl.c
-+++ b/controller/ofctrl.c
-@@ -173,7 +173,7 @@ struct sb_flow_ref {
- struct uuid sb_uuid;
- };
-
--/* A installed flow, in static variable installed_flows.
-+/* An installed flow, in static variable installed_lflows/installed_pflows.
- *
- * Installed flows are updated in ofctrl_put for maintaining the flow
- * installation to OVS. They are updated according to desired flows: either by
-@@ -234,7 +234,7 @@ static struct desired_flow *desired_flow_lookup_conjunctive(
- static void desired_flow_destroy(struct desired_flow *);
-
- static struct installed_flow *installed_flow_lookup(
-- const struct ovn_flow *target);
-+ const struct ovn_flow *target, struct hmap *installed_flows);
- static void installed_flow_destroy(struct installed_flow *);
- static struct installed_flow *installed_flow_dup(struct desired_flow *);
- static struct desired_flow *installed_flow_get_active(struct installed_flow *);
-@@ -302,9 +302,12 @@ static ovs_be32 xid, xid2;
- * zero, to avoid unbounded buffering. */
- static struct rconn_packet_counter *tx_counter;
-
--/* Flow table of "struct ovn_flow"s, that holds the flow table currently
-- * installed in the switch. */
--static struct hmap installed_flows;
-+/* Flow table of "struct ovn_flow"s, that holds the logical flow table
-+ * currently installed in the switch. */
-+static struct hmap installed_lflows;
-+/* Flow table of "struct ovn_flow"s, that holds the physical flow table
-+ * currently installed in the switch. */
-+static struct hmap installed_pflows;
-
- /* A reference to the group_table. */
- static struct ovn_extend_table *groups;
-@@ -343,7 +346,8 @@ ofctrl_init(struct ovn_extend_table *group_table,
- swconn = rconn_create(inactivity_probe_interval, 0,
- DSCP_DEFAULT, 1 << OFP15_VERSION);
- tx_counter = rconn_packet_counter_create();
-- hmap_init(&installed_flows);
-+ hmap_init(&installed_lflows);
-+ hmap_init(&installed_pflows);
- ovs_list_init(&flow_updates);
- ovn_init_symtab(&symtab);
- groups = group_table;
-@@ -1426,11 +1430,12 @@ desired_flow_lookup_conjunctive(struct ovn_desired_flow_table *flow_table,
- /* Finds and returns an installed_flow in installed_flows whose key is
- * identical to 'target''s key, or NULL if there is none. */
- static struct installed_flow *
--installed_flow_lookup(const struct ovn_flow *target)
-+installed_flow_lookup(const struct ovn_flow *target,
-+ struct hmap *installed_flows)
- {
- struct installed_flow *i;
- HMAP_FOR_EACH_WITH_HASH (i, match_hmap_node, target->hash,
-- &installed_flows) {
-+ installed_flows) {
- struct ovn_flow *f = &i->flow;
- if (f->table_id == target->table_id
- && f->priority == target->priority
-@@ -1542,8 +1547,14 @@ static void
- ovn_installed_flow_table_clear(void)
- {
- struct installed_flow *f, *next;
-- HMAP_FOR_EACH_SAFE (f, next, match_hmap_node, &installed_flows) {
-- hmap_remove(&installed_flows, &f->match_hmap_node);
-+ HMAP_FOR_EACH_SAFE (f, next, match_hmap_node, &installed_lflows) {
-+ hmap_remove(&installed_lflows, &f->match_hmap_node);
-+ unlink_all_refs_for_installed_flow(f);
-+ installed_flow_destroy(f);
-+ }
-+
-+ HMAP_FOR_EACH_SAFE (f, next, match_hmap_node, &installed_pflows) {
-+ hmap_remove(&installed_pflows, &f->match_hmap_node);
- unlink_all_refs_for_installed_flow(f);
- installed_flow_destroy(f);
- }
-@@ -1553,7 +1564,8 @@ static void
- ovn_installed_flow_table_destroy(void)
- {
- ovn_installed_flow_table_clear();
-- hmap_destroy(&installed_flows);
-+ hmap_destroy(&installed_lflows);
-+ hmap_destroy(&installed_pflows);
- }
-
- /* Flow table update. */
-@@ -1829,6 +1841,7 @@ installed_flow_del(struct ovn_flow *i,
- static void
- update_installed_flows_by_compare(struct ovn_desired_flow_table *flow_table,
- struct ofputil_bundle_ctrl_msg *bc,
-+ struct hmap *installed_flows,
- struct ovs_list *msgs)
- {
- ovs_assert(ovs_list_is_empty(&flow_table->tracked_flows));
-@@ -1836,7 +1849,7 @@ update_installed_flows_by_compare(struct ovn_desired_flow_table *flow_table,
- * longer desired, delete them; if any of them should have different
- * actions, update them. */
- struct installed_flow *i, *next;
-- HMAP_FOR_EACH_SAFE (i, next, match_hmap_node, &installed_flows) {
-+ HMAP_FOR_EACH_SAFE (i, next, match_hmap_node, installed_flows) {
- unlink_all_refs_for_installed_flow(i);
- struct desired_flow *d = desired_flow_lookup(flow_table, &i->flow);
- if (!d) {
-@@ -1845,7 +1858,7 @@ update_installed_flows_by_compare(struct ovn_desired_flow_table *flow_table,
- installed_flow_del(&i->flow, bc, msgs);
- ovn_flow_log(&i->flow, "removing installed");
-
-- hmap_remove(&installed_flows, &i->match_hmap_node);
-+ hmap_remove(installed_flows, &i->match_hmap_node);
- installed_flow_destroy(i);
- } else {
- if (!ofpacts_equal(i->flow.ofpacts, i->flow.ofpacts_len,
-@@ -1863,14 +1876,14 @@ update_installed_flows_by_compare(struct ovn_desired_flow_table *flow_table,
- * in the installed flow table. */
- struct desired_flow *d;
- HMAP_FOR_EACH (d, match_hmap_node, &flow_table->match_flow_table) {
-- i = installed_flow_lookup(&d->flow);
-+ i = installed_flow_lookup(&d->flow, installed_flows);
- if (!i) {
- ovn_flow_log(&d->flow, "adding installed");
- installed_flow_add(&d->flow, bc, msgs);
-
- /* Copy 'd' from 'flow_table' to installed_flows. */
- i = installed_flow_dup(d);
-- hmap_insert(&installed_flows, &i->match_hmap_node, i->flow.hash);
-+ hmap_insert(installed_flows, &i->match_hmap_node, i->flow.hash);
- link_installed_to_desired(i, d);
- } else if (!d->installed_flow) {
- /* This is a desired_flow that conflicts with one installed
-@@ -1961,6 +1974,7 @@ merge_tracked_flows(struct ovn_desired_flow_table *flow_table)
- static void
- update_installed_flows_by_track(struct ovn_desired_flow_table *flow_table,
- struct ofputil_bundle_ctrl_msg *bc,
-+ struct hmap *installed_flows,
- struct ovs_list *msgs)
- {
- merge_tracked_flows(flow_table);
-@@ -1979,7 +1993,7 @@ update_installed_flows_by_track(struct ovn_desired_flow_table *flow_table,
- installed_flow_del(&i->flow, bc, msgs);
- ovn_flow_log(&i->flow, "removing installed (tracked)");
-
-- hmap_remove(&installed_flows, &i->match_hmap_node);
-+ hmap_remove(installed_flows, &i->match_hmap_node);
- installed_flow_destroy(i);
- } else if (was_active) {
- /* There are other desired flow(s) referencing this
-@@ -1993,7 +2007,8 @@ update_installed_flows_by_track(struct ovn_desired_flow_table *flow_table,
- desired_flow_destroy(f);
- } else {
- /* The desired flow was added or modified. */
-- struct installed_flow *i = installed_flow_lookup(&f->flow);
-+ struct installed_flow *i = installed_flow_lookup(&f->flow,
-+ installed_flows);
- if (!i) {
- /* Adding a new flow. */
- installed_flow_add(&f->flow, bc, msgs);
-@@ -2001,7 +2016,7 @@ update_installed_flows_by_track(struct ovn_desired_flow_table *flow_table,
-
- /* Copy 'f' from 'flow_table' to installed_flows. */
- struct installed_flow *new_node = installed_flow_dup(f);
-- hmap_insert(&installed_flows, &new_node->match_hmap_node,
-+ hmap_insert(installed_flows, &new_node->match_hmap_node,
- new_node->flow.hash);
- link_installed_to_desired(new_node, f);
- } else if (installed_flow_get_active(i) == f) {
-@@ -2055,16 +2070,19 @@ ofctrl_can_put(void)
- *
- * This should be called after ofctrl_run() within the main loop. */
- void
--ofctrl_put(struct ovn_desired_flow_table *flow_table,
-+ofctrl_put(struct ovn_desired_flow_table *lflow_table,
-+ struct ovn_desired_flow_table *pflow_table,
- struct shash *pending_ct_zones,
- const struct sbrec_meter_table *meter_table,
- uint64_t req_cfg,
-- bool flow_changed)
-+ bool lflows_changed,
-+ bool pflows_changed)
- {
- static bool skipped_last_time = false;
- static uint64_t old_req_cfg = 0;
- bool need_put = false;
-- if (flow_changed || skipped_last_time || need_reinstall_flows) {
-+ if (lflows_changed || pflows_changed || skipped_last_time ||
-+ need_reinstall_flows) {
- need_put = true;
- old_req_cfg = req_cfg;
- } else if (req_cfg != old_req_cfg) {
-@@ -2093,7 +2111,6 @@ ofctrl_put(struct ovn_desired_flow_table *flow_table,
- return;
- }
-
-- skipped_last_time = false;
- need_reinstall_flows = false;
-
- /* OpenFlow messages to send to the switch to bring it up-to-date. */
-@@ -2159,12 +2176,35 @@ ofctrl_put(struct ovn_desired_flow_table *flow_table,
- bundle_open = ofputil_encode_bundle_ctrl_request(OFP15_VERSION, &bc);
- ovs_list_push_back(&msgs, &bundle_open->list_node);
-
-- if (flow_table->change_tracked) {
-- update_installed_flows_by_track(flow_table, &bc, &msgs);
-- } else {
-- update_installed_flows_by_compare(flow_table, &bc, &msgs);
-+ /* If skipped last time, then process the flow table
-+ * (tracked) flows even if lflows_changed is not set.
-+ * Same for pflows_changed. */
-+ if (lflows_changed || skipped_last_time) {
-+ if (lflow_table->change_tracked) {
-+ update_installed_flows_by_track(lflow_table, &bc,
-+ &installed_lflows,
-+ &msgs);
-+ } else {
-+ update_installed_flows_by_compare(lflow_table, &bc,
-+ &installed_lflows,
-+ &msgs);
-+ }
-+ }
-+
-+ if (pflows_changed || skipped_last_time) {
-+ if (pflow_table->change_tracked) {
-+ update_installed_flows_by_track(pflow_table, &bc,
-+ &installed_pflows,
-+ &msgs);
-+ } else {
-+ update_installed_flows_by_compare(pflow_table, &bc,
-+ &installed_pflows,
-+ &msgs);
-+ }
- }
-
-+ skipped_last_time = false;
-+
- if (ovs_list_back(&msgs) == &bundle_open->list_node) {
- /* No flow updates. Removing the bundle open request. */
- ovs_list_pop_back(&msgs);
-@@ -2287,8 +2327,11 @@ ofctrl_put(struct ovn_desired_flow_table *flow_table,
- cur_cfg = req_cfg;
- }
-
-- flow_table->change_tracked = true;
-- ovs_assert(ovs_list_is_empty(&flow_table->tracked_flows));
-+ lflow_table->change_tracked = true;
-+ ovs_assert(ovs_list_is_empty(&lflow_table->tracked_flows));
-+
-+ pflow_table->change_tracked = true;
-+ ovs_assert(ovs_list_is_empty(&pflow_table->tracked_flows));
- }
-
- /* Looks up the logical port with the name 'port_name' in 'br_int_'. If
-diff --git a/controller/ofctrl.h b/controller/ofctrl.h
-index 88769566a..ead8088c5 100644
---- a/controller/ofctrl.h
-+++ b/controller/ofctrl.h
-@@ -52,11 +52,13 @@ void ofctrl_init(struct ovn_extend_table *group_table,
- void ofctrl_run(const struct ovsrec_bridge *br_int,
- struct shash *pending_ct_zones);
- enum mf_field_id ofctrl_get_mf_field_id(void);
--void ofctrl_put(struct ovn_desired_flow_table *,
-+void ofctrl_put(struct ovn_desired_flow_table *lflow_table,
-+ struct ovn_desired_flow_table *pflow_table,
- struct shash *pending_ct_zones,
- const struct sbrec_meter_table *,
- uint64_t nb_cfg,
-- bool flow_changed);
-+ bool lflow_changed,
-+ bool pflow_changed);
- bool ofctrl_can_put(void);
- void ofctrl_wait(void);
- void ofctrl_destroy(void);
-diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
-index 07c6fcfd1..ea03638a9 100644
---- a/controller/ovn-controller.c
-+++ b/controller/ovn-controller.c
-@@ -46,6 +46,7 @@
- #include "openvswitch/vconn.h"
- #include "openvswitch/vlog.h"
- #include "ovn/actions.h"
-+#include "ovn/features.h"
- #include "lib/chassis-index.h"
- #include "lib/extend-table.h"
- #include "lib/ip-mcast-index.h"
-@@ -88,6 +89,7 @@ static unixctl_cb_func lflow_cache_show_stats_cmd;
- static unixctl_cb_func debug_delay_nb_cfg_report;
-
- #define DEFAULT_BRIDGE_NAME "br-int"
-+#define DEFAULT_DATAPATH "system"
- #define DEFAULT_PROBE_INTERVAL_MSEC 5000
- #define OFCTRL_DEFAULT_PROBE_INTERVAL_SEC 0
-
-@@ -319,10 +321,6 @@ static const struct ovsrec_bridge *
- create_br_int(struct ovsdb_idl_txn *ovs_idl_txn,
- const struct ovsrec_open_vswitch_table *ovs_table)
- {
-- if (!ovs_idl_txn) {
-- return NULL;
-- }
--
- const struct ovsrec_open_vswitch *cfg;
- cfg = ovsrec_open_vswitch_table_first(ovs_table);
- if (!cfg) {
-@@ -386,6 +384,21 @@ create_br_int(struct ovsdb_idl_txn *ovs_idl_txn,
- return bridge;
- }
-
-+static const struct ovsrec_datapath *
-+create_br_datapath(struct ovsdb_idl_txn *ovs_idl_txn,
-+ const struct ovsrec_open_vswitch *cfg,
-+ const char *datapath_type)
-+{
-+ ovsdb_idl_txn_add_comment(ovs_idl_txn,
-+ "ovn-controller: creating bridge datapath '%s'",
-+ datapath_type);
-+
-+ struct ovsrec_datapath *dp = ovsrec_datapath_insert(ovs_idl_txn);
-+ ovsrec_open_vswitch_verify_datapaths(cfg);
-+ ovsrec_open_vswitch_update_datapaths_setkey(cfg, datapath_type, dp);
-+ return dp;
-+}
-+
- static const struct ovsrec_bridge *
- get_br_int(const struct ovsrec_bridge_table *bridge_table,
- const struct ovsrec_open_vswitch_table *ovs_table)
-@@ -399,33 +412,69 @@ get_br_int(const struct ovsrec_bridge_table *bridge_table,
- return get_bridge(bridge_table, br_int_name(cfg));
- }
-
--static const struct ovsrec_bridge *
-+static const struct ovsrec_datapath *
-+get_br_datapath(const struct ovsrec_open_vswitch *cfg,
-+ const char *datapath_type)
-+{
-+ for (size_t i = 0; i < cfg->n_datapaths; i++) {
-+ if (!strcmp(cfg->key_datapaths[i], datapath_type)) {
-+ return cfg->value_datapaths[i];
-+ }
-+ }
-+ return NULL;
-+}
-+
-+static void
- process_br_int(struct ovsdb_idl_txn *ovs_idl_txn,
- const struct ovsrec_bridge_table *bridge_table,
-- const struct ovsrec_open_vswitch_table *ovs_table)
-+ const struct ovsrec_open_vswitch_table *ovs_table,
-+ const struct ovsrec_bridge **br_int_,
-+ const struct ovsrec_datapath **br_int_dp_)
- {
-- const struct ovsrec_bridge *br_int = get_br_int(bridge_table,
-- ovs_table);
-- if (!br_int) {
-- br_int = create_br_int(ovs_idl_txn, ovs_table);
-- }
-- if (br_int && ovs_idl_txn) {
-- const struct ovsrec_open_vswitch *cfg;
-- cfg = ovsrec_open_vswitch_table_first(ovs_table);
-- ovs_assert(cfg);
-- const char *datapath_type = smap_get(&cfg->external_ids,
-- "ovn-bridge-datapath-type");
-- /* Check for the datapath_type and set it only if it is defined in
-- * cfg. */
-- if (datapath_type && strcmp(br_int->datapath_type, datapath_type)) {
-- ovsrec_bridge_set_datapath_type(br_int, datapath_type);
-+ const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table);
-+ const struct ovsrec_datapath *br_int_dp = NULL;
-+
-+ ovs_assert(br_int_ && br_int_dp_);
-+ if (ovs_idl_txn) {
-+ if (!br_int) {
-+ br_int = create_br_int(ovs_idl_txn, ovs_table);
- }
-- if (!br_int->fail_mode || strcmp(br_int->fail_mode, "secure")) {
-- ovsrec_bridge_set_fail_mode(br_int, "secure");
-- VLOG_WARN("Integration bridge fail-mode changed to 'secure'.");
-+
-+ if (br_int) {
-+ const struct ovsrec_open_vswitch *cfg =
-+ ovsrec_open_vswitch_table_first(ovs_table);
-+ ovs_assert(cfg);
-+
-+ /* Propagate "ovn-bridge-datapath-type" from OVS table, if any.
-+ * Otherwise use the datapath-type set in br-int, if any.
-+ * Finally, assume "system" datapath if none configured.
-+ */
-+ const char *datapath_type =
-+ smap_get(&cfg->external_ids, "ovn-bridge-datapath-type");
-+
-+ if (!datapath_type) {
-+ if (br_int->datapath_type[0]) {
-+ datapath_type = br_int->datapath_type;
-+ } else {
-+ datapath_type = DEFAULT_DATAPATH;
-+ }
-+ }
-+ if (strcmp(br_int->datapath_type, datapath_type)) {
-+ ovsrec_bridge_set_datapath_type(br_int, datapath_type);
-+ }
-+ if (!br_int->fail_mode || strcmp(br_int->fail_mode, "secure")) {
-+ ovsrec_bridge_set_fail_mode(br_int, "secure");
-+ VLOG_WARN("Integration bridge fail-mode changed to 'secure'.");
-+ }
-+ br_int_dp = get_br_datapath(cfg, datapath_type);
-+ if (!br_int_dp) {
-+ br_int_dp = create_br_datapath(ovs_idl_txn, cfg,
-+ datapath_type);
-+ }
- }
- }
-- return br_int;
-+ *br_int_ = br_int;
-+ *br_int_dp_ = br_int_dp;
- }
-
- static const char *
-@@ -563,7 +612,7 @@ add_pending_ct_zone_entry(struct shash *pending_ct_zones,
- static void
- update_ct_zones(const struct sset *lports, const struct hmap *local_datapaths,
- struct simap *ct_zones, unsigned long *ct_zone_bitmap,
-- struct shash *pending_ct_zones, struct hmapx *updated_dps)
-+ struct shash *pending_ct_zones)
- {
- struct simap_node *ct_zone, *ct_zone_next;
- int scan_start = 1;
-@@ -653,11 +702,6 @@ update_ct_zones(const struct sset *lports, const struct hmap *local_datapaths,
-
- bitmap_set1(ct_zone_bitmap, snat_req_node->data);
- simap_put(ct_zones, snat_req_node->name, snat_req_node->data);
-- struct shash_node *ld_node = shash_find(&all_lds, snat_req_node->name);
-- if (ld_node) {
-- struct local_datapath *dp = ld_node->data;
-- hmapx_add(updated_dps, (void *) dp->datapath);
-- }
- }
-
- /* xxx This is wasteful to assign a zone to each port--even if no
-@@ -686,12 +730,6 @@ update_ct_zones(const struct sset *lports, const struct hmap *local_datapaths,
-
- bitmap_set1(ct_zone_bitmap, zone);
- simap_put(ct_zones, user, zone);
--
-- struct shash_node *ld_node = shash_find(&all_lds, user);
-- if (ld_node) {
-- struct local_datapath *dp = ld_node->data;
-- hmapx_add(updated_dps, (void *) dp->datapath);
-- }
- }
-
- simap_destroy(&req_snat_zones);
-@@ -848,6 +886,7 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
- ovsdb_idl_add_table(ovs_idl, &ovsrec_table_open_vswitch);
- ovsdb_idl_add_column(ovs_idl, &ovsrec_open_vswitch_col_external_ids);
- ovsdb_idl_add_column(ovs_idl, &ovsrec_open_vswitch_col_bridges);
-+ ovsdb_idl_add_column(ovs_idl, &ovsrec_open_vswitch_col_datapaths);
- ovsdb_idl_add_table(ovs_idl, &ovsrec_table_interface);
- ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_name);
- ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_bfd);
-@@ -870,6 +909,8 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
- ovsdb_idl_add_column(ovs_idl, &ovsrec_ssl_col_ca_cert);
- ovsdb_idl_add_column(ovs_idl, &ovsrec_ssl_col_certificate);
- ovsdb_idl_add_column(ovs_idl, &ovsrec_ssl_col_private_key);
-+ ovsdb_idl_add_table(ovs_idl, &ovsrec_table_datapath);
-+ ovsdb_idl_add_column(ovs_idl, &ovsrec_datapath_col_capabilities);
- chassis_register_ovs_idl(ovs_idl);
- encaps_register_ovs_idl(ovs_idl);
- binding_register_ovs_idl(ovs_idl);
-@@ -970,9 +1011,10 @@ struct ed_type_runtime_data {
- * local hypervisor, and localnet ports. */
- struct sset local_lports;
-
-- /* Contains the same ports as local_lports, but in the format:
-- * _ */
-- struct sset local_lport_ids;
-+ /* Port bindings that are relevant to the local chassis (VIFs bound
-+ * localy, patch ports).
-+ */
-+ struct related_lports related_lports;
- struct sset active_tunnels;
-
- /* runtime data engine private data. */
-@@ -986,6 +1028,9 @@ struct ed_type_runtime_data {
-
- /* CT zone data. Contains datapaths that had updated CT zones */
- struct hmapx ct_updated_datapaths;
-+
-+ /* Contains datapaths that had updated external ports. */
-+ struct hmapx extport_updated_datapaths;
- };
-
- /* struct ed_type_runtime_data has the below members for tracking the
-@@ -1068,7 +1113,7 @@ en_runtime_data_init(struct engine_node *node OVS_UNUSED,
-
- hmap_init(&data->local_datapaths);
- sset_init(&data->local_lports);
-- sset_init(&data->local_lport_ids);
-+ related_lports_init(&data->related_lports);
- sset_init(&data->active_tunnels);
- sset_init(&data->egress_ifaces);
- smap_init(&data->local_iface_ids);
-@@ -1078,6 +1123,7 @@ en_runtime_data_init(struct engine_node *node OVS_UNUSED,
- hmap_init(&data->tracked_dp_bindings);
-
- hmapx_init(&data->ct_updated_datapaths);
-+ hmapx_init(&data->extport_updated_datapaths);
-
- return data;
- }
-@@ -1088,7 +1134,7 @@ en_runtime_data_cleanup(void *data)
- struct ed_type_runtime_data *rt_data = data;
-
- sset_destroy(&rt_data->local_lports);
-- sset_destroy(&rt_data->local_lport_ids);
-+ related_lports_destroy(&rt_data->related_lports);
- sset_destroy(&rt_data->active_tunnels);
- sset_destroy(&rt_data->egress_ifaces);
- smap_destroy(&rt_data->local_iface_ids);
-@@ -1096,12 +1142,14 @@ en_runtime_data_cleanup(void *data)
- HMAP_FOR_EACH_SAFE (cur_node, next_node, hmap_node,
- &rt_data->local_datapaths) {
- free(cur_node->peer_ports);
-+ shash_destroy(&cur_node->external_ports);
- hmap_remove(&rt_data->local_datapaths, &cur_node->hmap_node);
- free(cur_node);
- }
- hmap_destroy(&rt_data->local_datapaths);
- local_binding_data_destroy(&rt_data->lbinding_data);
- hmapx_destroy(&rt_data->ct_updated_datapaths);
-+ hmapx_destroy(&rt_data->extport_updated_datapaths);
- }
-
- static void
-@@ -1181,14 +1229,15 @@ init_binding_ctx(struct engine_node *node,
- b_ctx_out->local_datapaths = &rt_data->local_datapaths;
- b_ctx_out->local_lports = &rt_data->local_lports;
- b_ctx_out->local_lports_changed = false;
-- b_ctx_out->local_lport_ids = &rt_data->local_lport_ids;
-- b_ctx_out->local_lport_ids_changed = false;
-+ b_ctx_out->related_lports = &rt_data->related_lports;
-+ b_ctx_out->related_lports_changed = false;
- b_ctx_out->non_vif_ports_changed = false;
- b_ctx_out->egress_ifaces = &rt_data->egress_ifaces;
- b_ctx_out->lbinding_data = &rt_data->lbinding_data;
- b_ctx_out->local_iface_ids = &rt_data->local_iface_ids;
- b_ctx_out->tracked_dp_bindings = NULL;
- b_ctx_out->if_mgr = ctrl_ctx->if_mgr;
-+ b_ctx_out->extport_updated_datapaths = &rt_data->extport_updated_datapaths;
- }
-
- static void
-@@ -1197,7 +1246,6 @@ en_runtime_data_run(struct engine_node *node, void *data)
- struct ed_type_runtime_data *rt_data = data;
- struct hmap *local_datapaths = &rt_data->local_datapaths;
- struct sset *local_lports = &rt_data->local_lports;
-- struct sset *local_lport_ids = &rt_data->local_lport_ids;
- struct sset *active_tunnels = &rt_data->active_tunnels;
-
- static bool first_run = true;
-@@ -1208,23 +1256,25 @@ en_runtime_data_run(struct engine_node *node, void *data)
- struct local_datapath *cur_node, *next_node;
- HMAP_FOR_EACH_SAFE (cur_node, next_node, hmap_node, local_datapaths) {
- free(cur_node->peer_ports);
-+ shash_destroy(&cur_node->external_ports);
- hmap_remove(local_datapaths, &cur_node->hmap_node);
- free(cur_node);
- }
- hmap_clear(local_datapaths);
- local_binding_data_destroy(&rt_data->lbinding_data);
- sset_destroy(local_lports);
-- sset_destroy(local_lport_ids);
-+ related_lports_destroy(&rt_data->related_lports);
- sset_destroy(active_tunnels);
- sset_destroy(&rt_data->egress_ifaces);
- smap_destroy(&rt_data->local_iface_ids);
- sset_init(local_lports);
-- sset_init(local_lport_ids);
-+ related_lports_init(&rt_data->related_lports);
- sset_init(active_tunnels);
- sset_init(&rt_data->egress_ifaces);
- smap_init(&rt_data->local_iface_ids);
- local_binding_data_init(&rt_data->lbinding_data);
- hmapx_clear(&rt_data->ct_updated_datapaths);
-+ hmapx_clear(&rt_data->extport_updated_datapaths);
- }
-
- struct binding_ctx_in b_ctx_in;
-@@ -1289,7 +1339,7 @@ runtime_data_sb_port_binding_handler(struct engine_node *node, void *data)
- return false;
- }
-
-- if (b_ctx_out.local_lport_ids_changed ||
-+ if (b_ctx_out.related_lports_changed ||
- b_ctx_out.non_vif_ports_changed ||
- !hmap_is_empty(b_ctx_out.tracked_dp_bindings)) {
- engine_set_node_state(node, EN_UPDATED);
-@@ -1599,11 +1649,8 @@ en_port_groups_run(struct engine_node *node, void *data)
- struct ed_type_runtime_data *rt_data =
- engine_get_input_data("runtime_data", node);
-
-- struct sset *local_b_lports = binding_collect_local_binding_lports(
-- &rt_data->lbinding_data);
-- port_groups_init(pg_table, local_b_lports, &pg->port_group_ssets,
-- &pg->port_groups_cs_local);
-- binding_destroy_local_binding_lports(local_b_lports);
-+ port_groups_init(pg_table, &rt_data->related_lports.lport_names,
-+ &pg->port_group_ssets, &pg->port_groups_cs_local);
-
- engine_set_node_state(node, EN_UPDATED);
- }
-@@ -1620,12 +1667,9 @@ port_groups_sb_port_group_handler(struct engine_node *node, void *data)
- struct ed_type_runtime_data *rt_data =
- engine_get_input_data("runtime_data", node);
-
-- struct sset *local_b_lports = binding_collect_local_binding_lports(
-- &rt_data->lbinding_data);
-- port_groups_update(pg_table, local_b_lports, &pg->port_group_ssets,
-- &pg->port_groups_cs_local, &pg->new, &pg->deleted,
-- &pg->updated);
-- binding_destroy_local_binding_lports(local_b_lports);
-+ port_groups_update(pg_table, &rt_data->related_lports.lport_names,
-+ &pg->port_group_ssets, &pg->port_groups_cs_local,
-+ &pg->new, &pg->deleted, &pg->updated);
-
- if (!sset_is_empty(&pg->new) || !sset_is_empty(&pg->deleted) ||
- !sset_is_empty(&pg->updated)) {
-@@ -1658,9 +1702,6 @@ port_groups_runtime_data_handler(struct engine_node *node, void *data)
- goto out;
- }
-
-- struct sset *local_b_lports = binding_collect_local_binding_lports(
-- &rt_data->lbinding_data);
--
- const struct sbrec_port_group *pg_sb;
- SBREC_PORT_GROUP_TABLE_FOR_EACH (pg_sb, pg_table) {
- struct sset *pg_lports = shash_find_data(&pg->port_group_ssets,
-@@ -1687,13 +1728,12 @@ port_groups_runtime_data_handler(struct engine_node *node, void *data)
- if (need_update) {
- expr_const_sets_add_strings(&pg->port_groups_cs_local, pg_sb->name,
- (const char *const *) pg_sb->ports,
-- pg_sb->n_ports, local_b_lports);
-+ pg_sb->n_ports,
-+ &rt_data->related_lports.lport_names);
- sset_add(&pg->updated, pg_sb->name);
- }
- }
-
-- binding_destroy_local_binding_lports(local_b_lports);
--
- out:
- if (!sset_is_empty(&pg->new) || !sset_is_empty(&pg->deleted) ||
- !sset_is_empty(&pg->updated)) {
-@@ -1748,10 +1788,9 @@ en_ct_zones_run(struct engine_node *node, void *data)
- struct ed_type_runtime_data *rt_data =
- engine_get_input_data("runtime_data", node);
-
-- hmapx_clear(&rt_data->ct_updated_datapaths);
- update_ct_zones(&rt_data->local_lports, &rt_data->local_datapaths,
- &ct_zones_data->current, ct_zones_data->bitmap,
-- &ct_zones_data->pending, &rt_data->ct_updated_datapaths);
-+ &ct_zones_data->pending);
-
-
- engine_set_node_state(node, EN_UPDATED);
-@@ -1794,107 +1833,13 @@ en_mff_ovn_geneve_run(struct engine_node *node, void *data)
- engine_set_node_state(node, EN_UNCHANGED);
- }
-
--/* Engine node en_physical_flow_changes indicates whether
-- * there is a need to
-- * - recompute only physical flows or
-- * - we can incrementally process the physical flows.
-- *
-- * en_physical_flow_changes is an input to flow_output engine node.
-- * If the engine node 'en_physical_flow_changes' gets updated during
-- * engine run, it means the handler for this -
-- * flow_output_physical_flow_changes_handler() will either
-- * - recompute the physical flows by calling 'physical_run() or
-- * - incrementlly process some of the changes for physical flow
-- * calculation. Right now we handle OVS interfaces changes
-- * for physical flow computation.
-- *
-- * When ever a port binding happens, the follow up
-- * activity is the zone id allocation for that port binding.
-- * With this intermediate engine node, we avoid full recomputation.
-- * Instead we do physical flow computation (either full recomputation
-- * by calling physical_run() or handling the changes incrementally.
-- *
-- * Hence this is an intermediate engine node to indicate the
-- * flow_output engine to recomputes/compute the physical flows.
-- *
-- * TODO 1. Ideally this engine node should recompute/compute the physical
-- * flows instead of relegating it to the flow_output node.
-- * But this requires splitting the flow_output node to
-- * logical_flow_output and physical_flow_output.
-- *
-- * TODO 2. We can further optimise the en_ct_zone changes to
-- * compute the phsyical flows for changed zone ids.
-- *
-- * TODO 3: physical.c has a global simap -localvif_to_ofport which stores the
-- * local OVS interfaces and the ofport numbers. Ideally this should be
-- * part of the engine data.
-- */
--struct ed_type_pfc_data {
-- /* Both these variables are tracked and set in each engine run. */
-- bool recompute_physical_flows;
-- bool ovs_ifaces_changed;
--};
--
--static void
--en_physical_flow_changes_clear_tracked_data(void *data_)
--{
-- struct ed_type_pfc_data *data = data_;
-- data->recompute_physical_flows = false;
-- data->ovs_ifaces_changed = false;
--}
--
--static void *
--en_physical_flow_changes_init(struct engine_node *node OVS_UNUSED,
-- struct engine_arg *arg OVS_UNUSED)
--{
-- struct ed_type_pfc_data *data = xzalloc(sizeof *data);
-- return data;
--}
--
--static void
--en_physical_flow_changes_cleanup(void *data OVS_UNUSED)
--{
--}
--
--/* Indicate to the flow_output engine that we need to recompute physical
-- * flows. */
--static void
--en_physical_flow_changes_run(struct engine_node *node, void *data)
--{
-- struct ed_type_pfc_data *pfc_tdata = data;
-- pfc_tdata->recompute_physical_flows = true;
-- pfc_tdata->ovs_ifaces_changed = true;
-- engine_set_node_state(node, EN_UPDATED);
--}
--
--/* ct_zone changes are not handled incrementally but a handler is required
-- * to avoid skipping the ovs_iface incremental change handler.
-- */
--static bool
--physical_flow_changes_ct_zones_handler(struct engine_node *node OVS_UNUSED,
-- void *data OVS_UNUSED)
--{
-- return false;
--}
--
--/* There are OVS interface changes. Indicate to the flow_output engine
-- * to handle these OVS interface changes for physical flow computations. */
--static bool
--physical_flow_changes_ovs_iface_handler(struct engine_node *node, void *data)
--{
-- struct ed_type_pfc_data *pfc_tdata = data;
-- pfc_tdata->ovs_ifaces_changed = true;
-- engine_set_node_state(node, EN_UPDATED);
-- return true;
--}
--
--struct flow_output_persistent_data {
-+struct lflow_output_persistent_data {
- uint32_t conj_id_ofs;
- struct lflow_cache *lflow_cache;
- };
-
--struct ed_type_flow_output {
-- /* desired flows */
-+struct ed_type_lflow_output {
-+ /* Logical flow table */
- struct ovn_desired_flow_table flow_table;
- /* group ids for load balancing */
- struct ovn_extend_table group_table;
-@@ -1905,81 +1850,15 @@ struct ed_type_flow_output {
-
- /* Data which is persistent and not cleared during
- * full recompute. */
-- struct flow_output_persistent_data pd;
-+ struct lflow_output_persistent_data pd;
- };
-
--static void init_physical_ctx(struct engine_node *node,
-- struct ed_type_runtime_data *rt_data,
-- struct physical_ctx *p_ctx)
--{
-- struct ovsdb_idl_index *sbrec_port_binding_by_name =
-- engine_ovsdb_node_get_index(
-- engine_get_input("SB_port_binding", node),
-- "name");
--
-- struct sbrec_multicast_group_table *multicast_group_table =
-- (struct sbrec_multicast_group_table *)EN_OVSDB_GET(
-- engine_get_input("SB_multicast_group", node));
--
-- struct sbrec_port_binding_table *port_binding_table =
-- (struct sbrec_port_binding_table *)EN_OVSDB_GET(
-- engine_get_input("SB_port_binding", node));
--
-- struct sbrec_chassis_table *chassis_table =
-- (struct sbrec_chassis_table *)EN_OVSDB_GET(
-- engine_get_input("SB_chassis", node));
--
-- struct ed_type_mff_ovn_geneve *ed_mff_ovn_geneve =
-- engine_get_input_data("mff_ovn_geneve", node);
--
-- struct ovsrec_open_vswitch_table *ovs_table =
-- (struct ovsrec_open_vswitch_table *)EN_OVSDB_GET(
-- engine_get_input("OVS_open_vswitch", node));
-- struct ovsrec_bridge_table *bridge_table =
-- (struct ovsrec_bridge_table *)EN_OVSDB_GET(
-- engine_get_input("OVS_bridge", node));
-- const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table);
-- const char *chassis_id = get_ovs_chassis_id(ovs_table);
-- const struct sbrec_chassis *chassis = NULL;
-- struct ovsdb_idl_index *sbrec_chassis_by_name =
-- engine_ovsdb_node_get_index(
-- engine_get_input("SB_chassis", node),
-- "name");
-- if (chassis_id) {
-- chassis = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
-- }
--
-- ovs_assert(br_int && chassis);
--
-- struct ovsrec_interface_table *iface_table =
-- (struct ovsrec_interface_table *)EN_OVSDB_GET(
-- engine_get_input("OVS_interface", node));
--
-- struct ed_type_ct_zones *ct_zones_data =
-- engine_get_input_data("ct_zones", node);
-- struct simap *ct_zones = &ct_zones_data->current;
--
-- p_ctx->sbrec_port_binding_by_name = sbrec_port_binding_by_name;
-- p_ctx->port_binding_table = port_binding_table;
-- p_ctx->mc_group_table = multicast_group_table;
-- p_ctx->br_int = br_int;
-- p_ctx->chassis_table = chassis_table;
-- p_ctx->iface_table = iface_table;
-- p_ctx->chassis = chassis;
-- p_ctx->active_tunnels = &rt_data->active_tunnels;
-- p_ctx->local_datapaths = &rt_data->local_datapaths;
-- p_ctx->local_lports = &rt_data->local_lports;
-- p_ctx->ct_zones = ct_zones;
-- p_ctx->mff_ovn_geneve = ed_mff_ovn_geneve->mff_ovn_geneve;
-- p_ctx->local_bindings = &rt_data->lbinding_data.bindings;
-- p_ctx->ct_updated_datapaths = &rt_data->ct_updated_datapaths;
--}
--
--static void init_lflow_ctx(struct engine_node *node,
-- struct ed_type_runtime_data *rt_data,
-- struct ed_type_flow_output *fo,
-- struct lflow_ctx_in *l_ctx_in,
-- struct lflow_ctx_out *l_ctx_out)
-+static void
-+init_lflow_ctx(struct engine_node *node,
-+ struct ed_type_runtime_data *rt_data,
-+ struct ed_type_lflow_output *fo,
-+ struct lflow_ctx_in *l_ctx_in,
-+ struct lflow_ctx_out *l_ctx_out)
- {
- struct ovsdb_idl_index *sbrec_port_binding_by_name =
- engine_ovsdb_node_get_index(
-@@ -2077,7 +1956,7 @@ static void init_lflow_ctx(struct engine_node *node,
- l_ctx_in->addr_sets = addr_sets;
- l_ctx_in->port_groups = port_groups;
- l_ctx_in->active_tunnels = &rt_data->active_tunnels;
-- l_ctx_in->local_lport_ids = &rt_data->local_lport_ids;
-+ l_ctx_in->related_lport_ids = &rt_data->related_lports.lport_ids;
-
- l_ctx_out->flow_table = &fo->flow_table;
- l_ctx_out->group_table = &fo->group_table;
-@@ -2089,11 +1968,10 @@ static void init_lflow_ctx(struct engine_node *node,
- }
-
- static void *
--en_flow_output_init(struct engine_node *node OVS_UNUSED,
-- struct engine_arg *arg OVS_UNUSED)
-+en_lflow_output_init(struct engine_node *node OVS_UNUSED,
-+ struct engine_arg *arg OVS_UNUSED)
- {
-- struct ed_type_flow_output *data = xzalloc(sizeof *data);
--
-+ struct ed_type_lflow_output *data = xzalloc(sizeof *data);
- ovn_desired_flow_table_init(&data->flow_table);
- ovn_extend_table_init(&data->group_table);
- ovn_extend_table_init(&data->meter_table);
-@@ -2103,9 +1981,9 @@ en_flow_output_init(struct engine_node *node OVS_UNUSED,
- }
-
- static void
--en_flow_output_cleanup(void *data)
-+en_lflow_output_cleanup(void *data)
- {
-- struct ed_type_flow_output *flow_output_data = data;
-+ struct ed_type_lflow_output *flow_output_data = data;
- ovn_desired_flow_table_destroy(&flow_output_data->flow_table);
- ovn_extend_table_destroy(&flow_output_data->group_table);
- ovn_extend_table_destroy(&flow_output_data->meter_table);
-@@ -2114,7 +1992,7 @@ en_flow_output_cleanup(void *data)
- }
-
- static void
--en_flow_output_run(struct engine_node *node, void *data)
-+en_lflow_output_run(struct engine_node *node, void *data)
- {
- struct ed_type_runtime_data *rt_data =
- engine_get_input_data("runtime_data", node);
-@@ -2140,8 +2018,8 @@ en_flow_output_run(struct engine_node *node, void *data)
-
- ovs_assert(br_int && chassis);
-
-- struct ed_type_flow_output *fo = data;
-- struct ovn_desired_flow_table *flow_table = &fo->flow_table;
-+ struct ed_type_lflow_output *fo = data;
-+ struct ovn_desired_flow_table *lflow_table = &fo->flow_table;
- struct ovn_extend_table *group_table = &fo->group_table;
- struct ovn_extend_table *meter_table = &fo->meter_table;
- struct lflow_resource_ref *lfrr = &fo->lflow_resource_ref;
-@@ -2150,7 +2028,7 @@ en_flow_output_run(struct engine_node *node, void *data)
- if (first_run) {
- first_run = false;
- } else {
-- ovn_desired_flow_table_clear(flow_table);
-+ ovn_desired_flow_table_clear(lflow_table);
- ovn_extend_table_clear(group_table, false /* desired */);
- ovn_extend_table_clear(meter_table, false /* desired */);
- lflow_resource_clear(lfrr);
-@@ -2172,7 +2050,7 @@ en_flow_output_run(struct engine_node *node, void *data)
- if (l_ctx_out.conj_id_overflow) {
- /* Conjunction ids overflow. There can be many holes in between.
- * Destroy lflow cache and call lflow_run() again. */
-- ovn_desired_flow_table_clear(flow_table);
-+ ovn_desired_flow_table_clear(lflow_table);
- ovn_extend_table_clear(group_table, false /* desired */);
- ovn_extend_table_clear(meter_table, false /* desired */);
- lflow_resource_clear(lfrr);
-@@ -2185,16 +2063,11 @@ en_flow_output_run(struct engine_node *node, void *data)
- }
- }
-
-- struct physical_ctx p_ctx;
-- init_physical_ctx(node, rt_data, &p_ctx);
--
-- physical_run(&p_ctx, &fo->flow_table);
--
- engine_set_node_state(node, EN_UPDATED);
- }
-
- static bool
--flow_output_sb_logical_flow_handler(struct engine_node *node, void *data)
-+lflow_output_sb_logical_flow_handler(struct engine_node *node, void *data)
- {
- struct ed_type_runtime_data *rt_data =
- engine_get_input_data("runtime_data", node);
-@@ -2207,7 +2080,7 @@ flow_output_sb_logical_flow_handler(struct engine_node *node, void *data)
- const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table);
- ovs_assert(br_int);
-
-- struct ed_type_flow_output *fo = data;
-+ struct ed_type_lflow_output *fo = data;
- struct lflow_ctx_in l_ctx_in;
- struct lflow_ctx_out l_ctx_out;
- init_lflow_ctx(node, rt_data, fo, &l_ctx_in, &l_ctx_out);
-@@ -2219,7 +2092,7 @@ flow_output_sb_logical_flow_handler(struct engine_node *node, void *data)
- }
-
- static bool
--flow_output_sb_mac_binding_handler(struct engine_node *node, void *data)
-+lflow_output_sb_mac_binding_handler(struct engine_node *node, void *data)
- {
- struct ovsdb_idl_index *sbrec_port_binding_by_name =
- engine_ovsdb_node_get_index(
-@@ -2234,60 +2107,17 @@ flow_output_sb_mac_binding_handler(struct engine_node *node, void *data)
- engine_get_input_data("runtime_data", node);
- const struct hmap *local_datapaths = &rt_data->local_datapaths;
-
-- struct ed_type_flow_output *fo = data;
-- struct ovn_desired_flow_table *flow_table = &fo->flow_table;
-+ struct ed_type_lflow_output *lfo = data;
-
- lflow_handle_changed_neighbors(sbrec_port_binding_by_name,
-- mac_binding_table, local_datapaths, flow_table);
-+ mac_binding_table, local_datapaths, &lfo->flow_table);
-
- engine_set_node_state(node, EN_UPDATED);
- return true;
- }
-
- static bool
--flow_output_sb_port_binding_handler(struct engine_node *node,
-- void *data)
--{
-- struct ed_type_runtime_data *rt_data =
-- engine_get_input_data("runtime_data", node);
--
-- struct ed_type_flow_output *fo = data;
-- struct ovn_desired_flow_table *flow_table = &fo->flow_table;
--
-- struct physical_ctx p_ctx;
-- init_physical_ctx(node, rt_data, &p_ctx);
--
-- /* We handle port-binding changes for physical flow processing
-- * only. flow_output runtime data handler takes care of processing
-- * logical flows for any port binding changes.
-- */
-- physical_handle_port_binding_changes(&p_ctx, flow_table);
--
-- engine_set_node_state(node, EN_UPDATED);
-- return true;
--}
--
--static bool
--flow_output_sb_multicast_group_handler(struct engine_node *node, void *data)
--{
-- struct ed_type_runtime_data *rt_data =
-- engine_get_input_data("runtime_data", node);
--
-- struct ed_type_flow_output *fo = data;
-- struct ovn_desired_flow_table *flow_table = &fo->flow_table;
--
-- struct physical_ctx p_ctx;
-- init_physical_ctx(node, rt_data, &p_ctx);
--
-- physical_handle_mc_group_changes(&p_ctx, flow_table);
--
-- engine_set_node_state(node, EN_UPDATED);
-- return true;
--
--}
--
--static bool
--_flow_output_resource_ref_handler(struct engine_node *node, void *data,
-+_lflow_output_resource_ref_handler(struct engine_node *node, void *data,
- enum ref_type ref_type)
- {
- struct ed_type_runtime_data *rt_data =
-@@ -2319,7 +2149,7 @@ _flow_output_resource_ref_handler(struct engine_node *node, void *data,
-
- ovs_assert(br_int && chassis);
-
-- struct ed_type_flow_output *fo = data;
-+ struct ed_type_lflow_output *fo = data;
-
- struct lflow_ctx_in l_ctx_in;
- struct lflow_ctx_out l_ctx_out;
-@@ -2388,53 +2218,20 @@ _flow_output_resource_ref_handler(struct engine_node *node, void *data,
- }
-
- static bool
--flow_output_addr_sets_handler(struct engine_node *node, void *data)
-+lflow_output_addr_sets_handler(struct engine_node *node, void *data)
- {
-- return _flow_output_resource_ref_handler(node, data, REF_TYPE_ADDRSET);
-+ return _lflow_output_resource_ref_handler(node, data, REF_TYPE_ADDRSET);
- }
-
- static bool
--flow_output_port_groups_handler(struct engine_node *node, void *data)
-+lflow_output_port_groups_handler(struct engine_node *node, void *data)
- {
-- return _flow_output_resource_ref_handler(node, data, REF_TYPE_PORTGROUP);
-+ return _lflow_output_resource_ref_handler(node, data, REF_TYPE_PORTGROUP);
- }
-
- static bool
--flow_output_physical_flow_changes_handler(struct engine_node *node, void *data)
--{
-- struct ed_type_runtime_data *rt_data =
-- engine_get_input_data("runtime_data", node);
--
-- struct ed_type_flow_output *fo = data;
-- struct physical_ctx p_ctx;
-- init_physical_ctx(node, rt_data, &p_ctx);
--
-- engine_set_node_state(node, EN_UPDATED);
-- struct ed_type_pfc_data *pfc_data =
-- engine_get_input_data("physical_flow_changes", node);
--
-- /* If there are OVS interface changes. Try to handle them incrementally. */
-- if (pfc_data->ovs_ifaces_changed) {
-- if (!physical_handle_ovs_iface_changes(&p_ctx, &fo->flow_table)) {
-- return false;
-- }
-- }
--
-- if (pfc_data->recompute_physical_flows) {
-- /* This indicates that we need to recompute the physical flows. */
-- physical_clear_unassoc_flows_with_db(&fo->flow_table);
-- physical_clear_dp_flows(&p_ctx, &rt_data->ct_updated_datapaths,
-- &fo->flow_table);
-- physical_run(&p_ctx, &fo->flow_table);
-- return true;
-- }
--
-- return true;
--}
--
--static bool
--flow_output_runtime_data_handler(struct engine_node *node,
-- void *data OVS_UNUSED)
-+lflow_output_runtime_data_handler(struct engine_node *node,
-+ void *data OVS_UNUSED)
- {
- struct ed_type_runtime_data *rt_data =
- engine_get_input_data("runtime_data", node);
-@@ -2455,12 +2252,9 @@ flow_output_runtime_data_handler(struct engine_node *node,
-
- struct lflow_ctx_in l_ctx_in;
- struct lflow_ctx_out l_ctx_out;
-- struct ed_type_flow_output *fo = data;
-+ struct ed_type_lflow_output *fo = data;
- init_lflow_ctx(node, rt_data, fo, &l_ctx_in, &l_ctx_out);
-
-- struct physical_ctx p_ctx;
-- init_physical_ctx(node, rt_data, &p_ctx);
--
- struct tracked_binding_datapath *tdp;
- HMAP_FOR_EACH (tdp, node, tracked_dp_bindings) {
- if (tdp->is_new) {
-@@ -2485,12 +2279,12 @@ flow_output_runtime_data_handler(struct engine_node *node,
- }
-
- static bool
--flow_output_sb_load_balancer_handler(struct engine_node *node, void *data)
-+lflow_output_sb_load_balancer_handler(struct engine_node *node, void *data)
- {
- struct ed_type_runtime_data *rt_data =
- engine_get_input_data("runtime_data", node);
-
-- struct ed_type_flow_output *fo = data;
-+ struct ed_type_lflow_output *fo = data;
- struct lflow_ctx_in l_ctx_in;
- struct lflow_ctx_out l_ctx_out;
- init_lflow_ctx(node, rt_data, fo, &l_ctx_in, &l_ctx_out);
-@@ -2502,12 +2296,12 @@ flow_output_sb_load_balancer_handler(struct engine_node *node, void *data)
- }
-
- static bool
--flow_output_sb_fdb_handler(struct engine_node *node, void *data)
-+lflow_output_sb_fdb_handler(struct engine_node *node, void *data)
- {
- struct ed_type_runtime_data *rt_data =
- engine_get_input_data("runtime_data", node);
-
-- struct ed_type_flow_output *fo = data;
-+ struct ed_type_lflow_output *fo = data;
- struct lflow_ctx_in l_ctx_in;
- struct lflow_ctx_out l_ctx_out;
- init_lflow_ctx(node, rt_data, fo, &l_ctx_in, &l_ctx_out);
-@@ -2518,6 +2312,205 @@ flow_output_sb_fdb_handler(struct engine_node *node, void *data)
- return handled;
- }
-
-+struct ed_type_pflow_output {
-+ /* Desired physical flows. */
-+ struct ovn_desired_flow_table flow_table;
-+};
-+
-+static void init_physical_ctx(struct engine_node *node,
-+ struct ed_type_runtime_data *rt_data,
-+ struct physical_ctx *p_ctx)
-+{
-+ struct ovsdb_idl_index *sbrec_port_binding_by_name =
-+ engine_ovsdb_node_get_index(
-+ engine_get_input("SB_port_binding", node),
-+ "name");
-+
-+ struct sbrec_multicast_group_table *multicast_group_table =
-+ (struct sbrec_multicast_group_table *)EN_OVSDB_GET(
-+ engine_get_input("SB_multicast_group", node));
-+
-+ struct sbrec_port_binding_table *port_binding_table =
-+ (struct sbrec_port_binding_table *)EN_OVSDB_GET(
-+ engine_get_input("SB_port_binding", node));
-+
-+ struct sbrec_chassis_table *chassis_table =
-+ (struct sbrec_chassis_table *)EN_OVSDB_GET(
-+ engine_get_input("SB_chassis", node));
-+
-+ struct ed_type_mff_ovn_geneve *ed_mff_ovn_geneve =
-+ engine_get_input_data("mff_ovn_geneve", node);
-+
-+ struct ovsrec_open_vswitch_table *ovs_table =
-+ (struct ovsrec_open_vswitch_table *)EN_OVSDB_GET(
-+ engine_get_input("OVS_open_vswitch", node));
-+ struct ovsrec_bridge_table *bridge_table =
-+ (struct ovsrec_bridge_table *)EN_OVSDB_GET(
-+ engine_get_input("OVS_bridge", node));
-+ const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table);
-+ const char *chassis_id = get_ovs_chassis_id(ovs_table);
-+ const struct sbrec_chassis *chassis = NULL;
-+ struct ovsdb_idl_index *sbrec_chassis_by_name =
-+ engine_ovsdb_node_get_index(
-+ engine_get_input("SB_chassis", node),
-+ "name");
-+ if (chassis_id) {
-+ chassis = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
-+ }
-+
-+ ovs_assert(br_int && chassis);
-+
-+ struct ovsrec_interface_table *iface_table =
-+ (struct ovsrec_interface_table *)EN_OVSDB_GET(
-+ engine_get_input("OVS_interface", node));
-+
-+ struct ed_type_ct_zones *ct_zones_data =
-+ engine_get_input_data("ct_zones", node);
-+ struct simap *ct_zones = &ct_zones_data->current;
-+
-+ p_ctx->sbrec_port_binding_by_name = sbrec_port_binding_by_name;
-+ p_ctx->port_binding_table = port_binding_table;
-+ p_ctx->mc_group_table = multicast_group_table;
-+ p_ctx->br_int = br_int;
-+ p_ctx->chassis_table = chassis_table;
-+ p_ctx->iface_table = iface_table;
-+ p_ctx->chassis = chassis;
-+ p_ctx->active_tunnels = &rt_data->active_tunnels;
-+ p_ctx->local_datapaths = &rt_data->local_datapaths;
-+ p_ctx->local_lports = &rt_data->local_lports;
-+ p_ctx->ct_zones = ct_zones;
-+ p_ctx->mff_ovn_geneve = ed_mff_ovn_geneve->mff_ovn_geneve;
-+ p_ctx->local_bindings = &rt_data->lbinding_data.bindings;
-+}
-+
-+static void *
-+en_pflow_output_init(struct engine_node *node OVS_UNUSED,
-+ struct engine_arg *arg OVS_UNUSED)
-+{
-+ struct ed_type_pflow_output *data = xzalloc(sizeof *data);
-+ ovn_desired_flow_table_init(&data->flow_table);
-+ return data;
-+}
-+
-+static void
-+en_pflow_output_cleanup(void *data OVS_UNUSED)
-+{
-+ struct ed_type_pflow_output *pfo = data;
-+ ovn_desired_flow_table_destroy(&pfo->flow_table);
-+}
-+
-+static void
-+en_pflow_output_run(struct engine_node *node, void *data)
-+{
-+ struct ed_type_pflow_output *pfo = data;
-+ struct ovn_desired_flow_table *pflow_table = &pfo->flow_table;
-+ static bool first_run = true;
-+ if (first_run) {
-+ first_run = false;
-+ } else {
-+ ovn_desired_flow_table_clear(pflow_table);
-+ }
-+
-+ struct ed_type_runtime_data *rt_data =
-+ engine_get_input_data("runtime_data", node);
-+
-+ struct physical_ctx p_ctx;
-+ init_physical_ctx(node, rt_data, &p_ctx);
-+ physical_run(&p_ctx, pflow_table);
-+
-+ engine_set_node_state(node, EN_UPDATED);
-+}
-+
-+static bool
-+pflow_output_sb_port_binding_handler(struct engine_node *node,
-+ void *data)
-+{
-+ struct ed_type_runtime_data *rt_data =
-+ engine_get_input_data("runtime_data", node);
-+
-+ struct ed_type_pflow_output *pfo = data;
-+
-+ struct physical_ctx p_ctx;
-+ init_physical_ctx(node, rt_data, &p_ctx);
-+
-+ /* We handle port-binding changes for physical flow processing
-+ * only. flow_output runtime data handler takes care of processing
-+ * logical flows for any port binding changes.
-+ */
-+ physical_handle_port_binding_changes(&p_ctx, &pfo->flow_table);
-+
-+ engine_set_node_state(node, EN_UPDATED);
-+ return true;
-+}
-+
-+static bool
-+pflow_output_sb_multicast_group_handler(struct engine_node *node, void *data)
-+{
-+ struct ed_type_runtime_data *rt_data =
-+ engine_get_input_data("runtime_data", node);
-+
-+ struct ed_type_pflow_output *pfo = data;
-+
-+ struct physical_ctx p_ctx;
-+ init_physical_ctx(node, rt_data, &p_ctx);
-+
-+ physical_handle_mc_group_changes(&p_ctx, &pfo->flow_table);
-+
-+ engine_set_node_state(node, EN_UPDATED);
-+ return true;
-+}
-+
-+static bool
-+pflow_output_ovs_iface_handler(struct engine_node *node OVS_UNUSED,
-+ void *data OVS_UNUSED)
-+{
-+ struct ed_type_runtime_data *rt_data =
-+ engine_get_input_data("runtime_data", node);
-+
-+ struct ed_type_pflow_output *pfo = data;
-+
-+ struct physical_ctx p_ctx;
-+ init_physical_ctx(node, rt_data, &p_ctx);
-+
-+ engine_set_node_state(node, EN_UPDATED);
-+ return physical_handle_ovs_iface_changes(&p_ctx, &pfo->flow_table);
-+}
-+
-+static void *
-+en_flow_output_init(struct engine_node *node OVS_UNUSED,
-+ struct engine_arg *arg OVS_UNUSED)
-+{
-+ return NULL;
-+}
-+
-+static void
-+en_flow_output_cleanup(void *data OVS_UNUSED)
-+{
-+
-+}
-+
-+static void
-+en_flow_output_run(struct engine_node *node OVS_UNUSED, void *data OVS_UNUSED)
-+{
-+ engine_set_node_state(node, EN_UPDATED);
-+}
-+
-+static bool
-+flow_output_pflow_output_handler(struct engine_node *node,
-+ void *data OVS_UNUSED)
-+{
-+ engine_set_node_state(node, EN_UPDATED);
-+ return true;
-+}
-+
-+static bool
-+flow_output_lflow_output_handler(struct engine_node *node,
-+ void *data OVS_UNUSED)
-+{
-+ engine_set_node_state(node, EN_UPDATED);
-+ return true;
-+}
-+
- struct ovn_controller_exit_args {
- bool *exiting;
- bool *restart;
-@@ -2710,8 +2703,8 @@ main(int argc, char *argv[])
- ENGINE_NODE_WITH_CLEAR_TRACK_DATA(runtime_data, "runtime_data");
- ENGINE_NODE(mff_ovn_geneve, "mff_ovn_geneve");
- ENGINE_NODE(ofctrl_is_connected, "ofctrl_is_connected");
-- ENGINE_NODE_WITH_CLEAR_TRACK_DATA(physical_flow_changes,
-- "physical_flow_changes");
-+ ENGINE_NODE(pflow_output, "physical_flow_output");
-+ ENGINE_NODE(lflow_output, "logical_flow_output");
- ENGINE_NODE(flow_output, "flow_output");
- ENGINE_NODE(addr_sets, "addr_sets");
- ENGINE_NODE_WITH_CLEAR_TRACK_DATA(port_groups, "port_groups");
-@@ -2735,58 +2728,68 @@ main(int argc, char *argv[])
- engine_add_input(&en_port_groups, &en_runtime_data,
- port_groups_runtime_data_handler);
-
-- /* Engine node physical_flow_changes indicates whether
-- * we can recompute only physical flows or we can
-- * incrementally process the physical flows.
-- *
-- * Note: The order of inputs is important, all OVS interface changes must
-+ /* Note: The order of inputs is important, all OVS interface changes must
- * be handled before any ct_zone changes.
- */
-- engine_add_input(&en_physical_flow_changes, &en_ovs_interface,
-- physical_flow_changes_ovs_iface_handler);
-- engine_add_input(&en_physical_flow_changes, &en_ct_zones,
-- physical_flow_changes_ct_zones_handler);
--
-- engine_add_input(&en_flow_output, &en_addr_sets,
-- flow_output_addr_sets_handler);
-- engine_add_input(&en_flow_output, &en_port_groups,
-- flow_output_port_groups_handler);
-- engine_add_input(&en_flow_output, &en_runtime_data,
-- flow_output_runtime_data_handler);
-- engine_add_input(&en_flow_output, &en_mff_ovn_geneve, NULL);
-- engine_add_input(&en_flow_output, &en_physical_flow_changes,
-- flow_output_physical_flow_changes_handler);
--
-- /* We need this input nodes for only data. Hence the noop handler. */
-- engine_add_input(&en_flow_output, &en_ct_zones, engine_noop_handler);
-- engine_add_input(&en_flow_output, &en_ovs_interface, engine_noop_handler);
--
-- engine_add_input(&en_flow_output, &en_ovs_open_vswitch, NULL);
-- engine_add_input(&en_flow_output, &en_ovs_bridge, NULL);
--
-- engine_add_input(&en_flow_output, &en_sb_chassis, NULL);
-- engine_add_input(&en_flow_output, &en_sb_encap, NULL);
-- engine_add_input(&en_flow_output, &en_sb_multicast_group,
-- flow_output_sb_multicast_group_handler);
-- engine_add_input(&en_flow_output, &en_sb_port_binding,
-- flow_output_sb_port_binding_handler);
-- engine_add_input(&en_flow_output, &en_sb_mac_binding,
-- flow_output_sb_mac_binding_handler);
-- engine_add_input(&en_flow_output, &en_sb_logical_flow,
-- flow_output_sb_logical_flow_handler);
-+ engine_add_input(&en_pflow_output, &en_ovs_interface,
-+ pflow_output_ovs_iface_handler);
-+ engine_add_input(&en_pflow_output, &en_ct_zones, NULL);
-+ engine_add_input(&en_pflow_output, &en_sb_chassis, NULL);
-+ engine_add_input(&en_pflow_output, &en_sb_port_binding,
-+ pflow_output_sb_port_binding_handler);
-+ engine_add_input(&en_pflow_output, &en_sb_multicast_group,
-+ pflow_output_sb_multicast_group_handler);
-+
-+ engine_add_input(&en_pflow_output, &en_runtime_data,
-+ NULL);
-+ engine_add_input(&en_pflow_output, &en_sb_encap, NULL);
-+ engine_add_input(&en_pflow_output, &en_mff_ovn_geneve, NULL);
-+ engine_add_input(&en_pflow_output, &en_ovs_open_vswitch, NULL);
-+ engine_add_input(&en_pflow_output, &en_ovs_bridge, NULL);
-+
-+ engine_add_input(&en_lflow_output, &en_addr_sets,
-+ lflow_output_addr_sets_handler);
-+ engine_add_input(&en_lflow_output, &en_port_groups,
-+ lflow_output_port_groups_handler);
-+ engine_add_input(&en_lflow_output, &en_runtime_data,
-+ lflow_output_runtime_data_handler);
-+
-+ /* We need these input nodes only for the data. Hence the noop handler.
-+ * Changes to en_sb_multicast_group is handled by the pflow_output engine
-+ * node.
-+ * */
-+ engine_add_input(&en_lflow_output, &en_sb_multicast_group,
-+ engine_noop_handler);
-+
-+ engine_add_input(&en_lflow_output, &en_sb_chassis, NULL);
-+
-+ /* Any changes to the port binding, need not be handled
-+ * for lflow_outout engine. We still need sb_port_binding
-+ * as input to access the port binding data in lflow.c and
-+ * hence the noop handler. */
-+ engine_add_input(&en_lflow_output, &en_sb_port_binding,
-+ engine_noop_handler);
-+
-+ engine_add_input(&en_lflow_output, &en_ovs_open_vswitch, NULL);
-+ engine_add_input(&en_lflow_output, &en_ovs_bridge, NULL);
-+
-+ engine_add_input(&en_lflow_output, &en_sb_mac_binding,
-+ lflow_output_sb_mac_binding_handler);
-+ engine_add_input(&en_lflow_output, &en_sb_logical_flow,
-+ lflow_output_sb_logical_flow_handler);
- /* Using a noop handler since we don't really need any data from datapath
- * groups or a full recompute. Update of a datapath group will put
- * logical flow into the tracked list, so the logical flow handler will
- * process all changes. */
-- engine_add_input(&en_flow_output, &en_sb_logical_dp_group,
-+ engine_add_input(&en_lflow_output, &en_sb_logical_dp_group,
- engine_noop_handler);
-- engine_add_input(&en_flow_output, &en_sb_dhcp_options, NULL);
-- engine_add_input(&en_flow_output, &en_sb_dhcpv6_options, NULL);
-- engine_add_input(&en_flow_output, &en_sb_dns, NULL);
-- engine_add_input(&en_flow_output, &en_sb_load_balancer,
-- flow_output_sb_load_balancer_handler);
-- engine_add_input(&en_flow_output, &en_sb_fdb,
-- flow_output_sb_fdb_handler);
-+ engine_add_input(&en_lflow_output, &en_sb_dhcp_options, NULL);
-+ engine_add_input(&en_lflow_output, &en_sb_dhcpv6_options, NULL);
-+ engine_add_input(&en_lflow_output, &en_sb_dns, NULL);
-+ engine_add_input(&en_lflow_output, &en_sb_load_balancer,
-+ lflow_output_sb_load_balancer_handler);
-+ engine_add_input(&en_lflow_output, &en_sb_fdb,
-+ lflow_output_sb_fdb_handler);
-
- engine_add_input(&en_ct_zones, &en_ovs_open_vswitch, NULL);
- engine_add_input(&en_ct_zones, &en_ovs_bridge, NULL);
-@@ -2808,12 +2811,20 @@ main(int argc, char *argv[])
- /* The OVS interface handler for runtime_data changes MUST be executed
- * after the sb_port_binding_handler as port_binding deletes must be
- * processed first.
-+ *
-+ * runtime_data needs to access the OVS Port data and hence a noop
-+ * handler.
- */
- engine_add_input(&en_runtime_data, &en_ovs_port,
- engine_noop_handler);
- engine_add_input(&en_runtime_data, &en_ovs_interface,
- runtime_data_ovs_interface_handler);
-
-+ engine_add_input(&en_flow_output, &en_lflow_output,
-+ flow_output_lflow_output_handler);
-+ engine_add_input(&en_flow_output, &en_pflow_output,
-+ flow_output_pflow_output_handler);
-+
- struct engine_arg engine_arg = {
- .sb_idl = ovnsb_idl_loop.idl,
- .ovs_idl = ovs_idl_loop.idl,
-@@ -2836,25 +2847,27 @@ main(int argc, char *argv[])
- engine_ovsdb_node_add_index(&en_sb_datapath_binding, "key",
- sbrec_datapath_binding_by_key);
-
-- struct ed_type_flow_output *flow_output_data =
-- engine_get_internal_data(&en_flow_output);
-+ struct ed_type_lflow_output *lflow_output_data =
-+ engine_get_internal_data(&en_lflow_output);
-+ struct ed_type_lflow_output *pflow_output_data =
-+ engine_get_internal_data(&en_pflow_output);
- struct ed_type_ct_zones *ct_zones_data =
- engine_get_internal_data(&en_ct_zones);
- struct ed_type_runtime_data *runtime_data =
- engine_get_internal_data(&en_runtime_data);
-
-- ofctrl_init(&flow_output_data->group_table,
-- &flow_output_data->meter_table,
-+ ofctrl_init(&lflow_output_data->group_table,
-+ &lflow_output_data->meter_table,
- get_ofctrl_probe_interval(ovs_idl_loop.idl));
- ofctrl_seqno_init();
-
- unixctl_command_register("group-table-list", "", 0, 0,
- extend_table_list,
-- &flow_output_data->group_table);
-+ &lflow_output_data->group_table);
-
- unixctl_command_register("meter-table-list", "", 0, 0,
- extend_table_list,
-- &flow_output_data->meter_table);
-+ &lflow_output_data->meter_table);
-
- unixctl_command_register("ct-zone-list", "", 0, 0,
- ct_zone_list,
-@@ -2868,14 +2881,14 @@ main(int argc, char *argv[])
- NULL);
- unixctl_command_register("lflow-cache/flush", "", 0, 0,
- lflow_cache_flush_cmd,
-- &flow_output_data->pd);
-+ &lflow_output_data->pd);
- /* Keep deprecated 'flush-lflow-cache' command for now. */
- unixctl_command_register("flush-lflow-cache", "[deprecated]", 0, 0,
- lflow_cache_flush_cmd,
-- &flow_output_data->pd);
-+ &lflow_output_data->pd);
- unixctl_command_register("lflow-cache/show-stats", "", 0, 0,
- lflow_cache_show_stats_cmd,
-- &flow_output_data->pd);
-+ &lflow_output_data->pd);
-
- bool reset_ovnsb_idl_min_index = false;
- unixctl_command_register("sb-cluster-state-reset", "", 0, 0,
-@@ -2981,8 +2994,10 @@ main(int argc, char *argv[])
- ovsrec_bridge_table_get(ovs_idl_loop.idl);
- const struct ovsrec_open_vswitch_table *ovs_table =
- ovsrec_open_vswitch_table_get(ovs_idl_loop.idl);
-- const struct ovsrec_bridge *br_int =
-- process_br_int(ovs_idl_txn, bridge_table, ovs_table);
-+ const struct ovsrec_bridge *br_int = NULL;
-+ const struct ovsrec_datapath *br_int_dp = NULL;
-+ process_br_int(ovs_idl_txn, bridge_table, ovs_table,
-+ &br_int, &br_int_dp);
-
- if (ovsdb_idl_has_ever_connected(ovnsb_idl_loop.idl) &&
- northd_version_match) {
-@@ -3013,6 +3028,13 @@ main(int argc, char *argv[])
- &chassis_private);
- }
-
-+ /* If any OVS feature support changed, force a full recompute. */
-+ if (br_int_dp
-+ && ovs_feature_support_update(&br_int_dp->capabilities)) {
-+ VLOG_INFO("OVS feature set changed, force recompute.");
-+ engine_set_force_recompute(true);
-+ }
-+
- if (br_int) {
- ct_zones_data = engine_get_data(&en_ct_zones);
- if (ct_zones_data) {
-@@ -3121,13 +3143,17 @@ main(int argc, char *argv[])
- runtime_data ? &runtime_data->lbinding_data : NULL;
- if_status_mgr_update(if_mgr, binding_data);
-
-- flow_output_data = engine_get_data(&en_flow_output);
-- if (flow_output_data && ct_zones_data) {
-- ofctrl_put(&flow_output_data->flow_table,
-+ lflow_output_data = engine_get_data(&en_lflow_output);
-+ pflow_output_data = engine_get_data(&en_pflow_output);
-+ if (lflow_output_data && pflow_output_data &&
-+ ct_zones_data) {
-+ ofctrl_put(&lflow_output_data->flow_table,
-+ &pflow_output_data->flow_table,
- &ct_zones_data->pending,
- sbrec_meter_table_get(ovnsb_idl_loop.idl),
- ofctrl_seqno_get_req_cfg(),
-- engine_node_changed(&en_flow_output));
-+ engine_node_changed(&en_lflow_output),
-+ engine_node_changed(&en_pflow_output));
- }
- ofctrl_seqno_run(ofctrl_get_cur_cfg());
- if_status_mgr_run(if_mgr, binding_data, !ovnsb_idl_txn,
-@@ -3495,7 +3521,7 @@ lflow_cache_flush_cmd(struct unixctl_conn *conn OVS_UNUSED,
- void *arg_)
- {
- VLOG_INFO("User triggered lflow cache flush.");
-- struct flow_output_persistent_data *fo_pd = arg_;
-+ struct lflow_output_persistent_data *fo_pd = arg_;
- lflow_cache_flush(fo_pd->lflow_cache);
- fo_pd->conj_id_ofs = 1;
- engine_set_force_recompute(true);
-@@ -3507,7 +3533,7 @@ static void
- lflow_cache_show_stats_cmd(struct unixctl_conn *conn, int argc OVS_UNUSED,
- const char *argv[] OVS_UNUSED, void *arg_)
- {
-- struct flow_output_persistent_data *fo_pd = arg_;
-+ struct lflow_output_persistent_data *fo_pd = arg_;
- struct lflow_cache *lc = fo_pd->lflow_cache;
- struct ds ds = DS_EMPTY_INITIALIZER;
-
-diff --git a/controller/ovn-controller.h b/controller/ovn-controller.h
-index 5d9466880..2bf1fecbf 100644
---- a/controller/ovn-controller.h
-+++ b/controller/ovn-controller.h
-@@ -67,6 +67,8 @@ struct local_datapath {
-
- size_t n_peer_ports;
- size_t n_allocated_peer_ports;
-+
-+ struct shash external_ports;
- };
-
- struct local_datapath *get_local_datapath(const struct hmap *,
-diff --git a/controller/physical.c b/controller/physical.c
-index 018e09540..a9a3dc720 100644
---- a/controller/physical.c
-+++ b/controller/physical.c
-@@ -1272,6 +1272,52 @@ consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name,
- ofctrl_add_flow(flow_table, OFTABLE_CHECK_LOOPBACK, 160,
- binding->header_.uuid.parts[0], &match,
- ofpacts_p, &binding->header_.uuid);
-+
-+ /* localport traffic directed to external is *not* local */
-+ struct shash_node *node;
-+ SHASH_FOR_EACH (node, &ld->external_ports) {
-+ const struct sbrec_port_binding *pb = node->data;
-+
-+ /* skip ports that are not claimed by this chassis */
-+ if (!pb->chassis) {
-+ continue;
-+ }
-+ if (strcmp(pb->chassis->name, chassis->name)) {
-+ continue;
-+ }
-+
-+ ofpbuf_clear(ofpacts_p);
-+ for (int i = 0; i < MFF_N_LOG_REGS; i++) {
-+ put_load(0, MFF_REG0 + i, 0, 32, ofpacts_p);
-+ }
-+ put_resubmit(OFTABLE_LOG_EGRESS_PIPELINE, ofpacts_p);
-+
-+ /* allow traffic directed to external MAC address */
-+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-+ for (int i = 0; i < pb->n_mac; i++) {
-+ char *err_str;
-+ struct eth_addr peer_mac;
-+ if ((err_str = str_to_mac(pb->mac[i], &peer_mac))) {
-+ VLOG_WARN_RL(
-+ &rl, "Parsing MAC failed for external port: %s, "
-+ "with error: %s", pb->logical_port, err_str);
-+ free(err_str);
-+ continue;
-+ }
-+
-+ match_init_catchall(&match);
-+ match_set_metadata(&match, htonll(dp_key));
-+ match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0,
-+ port_key);
-+ match_set_reg_masked(&match, MFF_LOG_FLAGS - MFF_REG0,
-+ MLF_LOCALPORT, MLF_LOCALPORT);
-+ match_set_dl_dst(&match, peer_mac);
-+
-+ ofctrl_add_flow(flow_table, OFTABLE_CHECK_LOOPBACK, 170,
-+ binding->header_.uuid.parts[0], &match,
-+ ofpacts_p, &binding->header_.uuid);
-+ }
-+ }
- }
-
- } else if (!tun && !is_ha_remote) {
-@@ -1953,22 +1999,3 @@ physical_clear_unassoc_flows_with_db(struct ovn_desired_flow_table *flow_table)
- ofctrl_remove_flows(flow_table, hc_uuid);
- }
- }
--
--void
--physical_clear_dp_flows(struct physical_ctx *p_ctx,
-- struct hmapx *ct_updated_datapaths,
-- struct ovn_desired_flow_table *flow_table)
--{
-- const struct sbrec_port_binding *binding;
-- SBREC_PORT_BINDING_TABLE_FOR_EACH (binding, p_ctx->port_binding_table) {
-- if (!hmapx_find(ct_updated_datapaths, binding->datapath)) {
-- continue;
-- }
-- const struct sbrec_port_binding *peer =
-- get_binding_peer(p_ctx->sbrec_port_binding_by_name, binding);
-- ofctrl_remove_flows(flow_table, &binding->header_.uuid);
-- if (peer) {
-- ofctrl_remove_flows(flow_table, &peer->header_.uuid);
-- }
-- }
--}
-diff --git a/controller/physical.h b/controller/physical.h
-index 0bf13f268..feab41df4 100644
---- a/controller/physical.h
-+++ b/controller/physical.h
-@@ -56,16 +56,12 @@ struct physical_ctx {
- const struct simap *ct_zones;
- enum mf_field_id mff_ovn_geneve;
- struct shash *local_bindings;
-- struct hmapx *ct_updated_datapaths;
- };
-
- void physical_register_ovs_idl(struct ovsdb_idl *);
- void physical_run(struct physical_ctx *,
- struct ovn_desired_flow_table *);
- void physical_clear_unassoc_flows_with_db(struct ovn_desired_flow_table *);
--void physical_clear_dp_flows(struct physical_ctx *p_ctx,
-- struct hmapx *ct_updated_datapaths,
-- struct ovn_desired_flow_table *flow_table);
- void physical_handle_port_binding_changes(struct physical_ctx *,
- struct ovn_desired_flow_table *);
- void physical_handle_mc_group_changes(struct physical_ctx *,
-diff --git a/controller/pinctrl.c b/controller/pinctrl.c
-index 78ecfed84..1859d33d6 100644
---- a/controller/pinctrl.c
-+++ b/controller/pinctrl.c
-@@ -768,6 +768,13 @@ pinctrl_parse_dhcpv6_advt(struct rconn *swconn, const struct flow *ip_flow,
-
- pfd->state = PREFIX_REQUEST;
-
-+ char ip6_s[INET6_ADDRSTRLEN + 1];
-+ if (ipv6_string_mapped(ip6_s, &ip_flow->ipv6_src)) {
-+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(20, 40);
-+ VLOG_DBG_RL(&rl, "Received DHCPv6 advt from %s with aid %d"
-+ " sending DHCPv6 request", ip6_s, aid);
-+ }
-+
- uint64_t packet_stub[256 / 8];
- struct dp_packet packet;
-
-@@ -936,6 +943,14 @@ pinctrl_parse_dhcpv6_reply(struct dp_packet *pkt_in,
- in_dhcpv6_data += opt_len;
- }
- if (status) {
-+ char prefix[INET6_ADDRSTRLEN + 1];
-+ char ip6_s[INET6_ADDRSTRLEN + 1];
-+ if (ipv6_string_mapped(ip6_s, &ip_flow->ipv6_src) &&
-+ ipv6_string_mapped(prefix, &ipv6)) {
-+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(20, 40);
-+ 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,
- in_ip->ip6_src, prefix_len, t1, t2,
- plife_time, vlife_time, uuid, uuid_len);
-@@ -1226,18 +1241,26 @@ fill_ipv6_prefix_state(struct ovsdb_idl_txn *ovnsb_idl_txn,
- }
- } else if (pfd->state == PREFIX_PENDING && ovnsb_idl_txn) {
- char prefix_str[INET6_ADDRSTRLEN + 1] = {};
-- struct smap options;
-+ if (!ipv6_string_mapped(prefix_str, &pfd->prefix)) {
-+ goto out;
-+ }
-+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(20, 40);
-+ VLOG_DBG_RL(&rl, "updating port_binding for %s with prefix %s/%d"
-+ " aid %d", pb->logical_port, prefix_str, pfd->plen,
-+ pfd->aid);
-
- pfd->state = PREFIX_DONE;
- pfd->last_complete = time_msec();
- pfd->next_announce = pfd->last_complete + pfd->t1;
-- ipv6_string_mapped(prefix_str, &pfd->prefix);
-+ struct smap options;
- smap_clone(&options, &pb->options);
-+ smap_remove(&options, "ipv6_ra_pd_list");
- smap_add_format(&options, "ipv6_ra_pd_list", "%d:%s/%d",
- pfd->aid, prefix_str, pfd->plen);
- sbrec_port_binding_set_options(pb, &options);
- smap_destroy(&options);
- }
-+out:
- pfd->last_used = time_msec();
- destroy_lport_addresses(&c_addrs);
- }
-@@ -1288,7 +1311,8 @@ prepare_ipv6_prefixd(struct ovsdb_idl_txn *ovnsb_idl_txn,
- sbrec_port_binding_by_name, chassis, active_tunnels,
- redirect_name);
- free(redirect_name);
-- if (!resident && strcmp(pb->type, "l3gateway")) {
-+ if ((strcmp(pb->type, "l3gateway") || pb->chassis != chassis) &&
-+ !resident) {
- continue;
- }
-
-diff --git a/debian/changelog b/debian/changelog
-index 9e6e5215d..42b952144 100644
---- a/debian/changelog
-+++ b/debian/changelog
-@@ -1,3 +1,9 @@
-+ovn (21.06.1-1) unstable; urgency=low
-+
-+ * New upstream version
-+
-+ -- OVN team Fri, 18 Jun 2021 13:21:08 -0400
-+
- ovn (21.06.0-1) unstable; urgency=low
-
- * New upstream version
-diff --git a/include/ovn/actions.h b/include/ovn/actions.h
-index 040213177..f5eb01eb7 100644
---- a/include/ovn/actions.h
-+++ b/include/ovn/actions.h
-@@ -25,6 +25,7 @@
- #include "openvswitch/hmap.h"
- #include "openvswitch/uuid.h"
- #include "util.h"
-+#include "ovn/features.h"
-
- struct expr;
- struct lexer;
-diff --git a/include/ovn/features.h b/include/ovn/features.h
-index 10ee46fcd..c35d59b14 100644
---- a/include/ovn/features.h
-+++ b/include/ovn/features.h
-@@ -16,7 +16,25 @@
- #ifndef OVN_FEATURES_H
- #define OVN_FEATURES_H 1
-
-+#include
-+
-+#include "smap.h"
-+
- /* ovn-controller supported feature names. */
- #define OVN_FEATURE_PORT_UP_NOTIF "port-up-notif"
-
-+/* OVS datapath supported features. Based on availability OVN might generate
-+ * different types of openflows.
-+ */
-+enum ovs_feature_support_bits {
-+ OVS_CT_ZERO_SNAT_SUPPORT_BIT,
-+};
-+
-+enum ovs_feature_value {
-+ OVS_CT_ZERO_SNAT_SUPPORT = (1 << OVS_CT_ZERO_SNAT_SUPPORT_BIT),
-+};
-+
-+bool ovs_feature_is_supported(enum ovs_feature_value feature);
-+bool ovs_feature_support_update(const struct smap *ovs_capabilities);
-+
- #endif
-diff --git a/lib/actions.c b/lib/actions.c
-index b3433f49e..7010fab2b 100644
---- a/lib/actions.c
-+++ b/lib/actions.c
-@@ -742,6 +742,22 @@ encode_CT_COMMIT_V1(const struct ovnact_ct_commit_v1 *cc,
- ct->zone_src.ofs = 0;
- ct->zone_src.n_bits = 16;
-
-+ /* If the datapath supports all-zero SNAT then use it to avoid tuple
-+ * collisions at commit time between NATed and firewalled-only sessions.
-+ */
-+
-+ if (ovs_feature_is_supported(OVS_CT_ZERO_SNAT_SUPPORT)) {
-+ size_t nat_offset = ofpacts->size;
-+ ofpbuf_pull(ofpacts, nat_offset);
-+
-+ struct ofpact_nat *nat = ofpact_put_NAT(ofpacts);
-+ nat->flags = 0;
-+ nat->range_af = AF_UNSPEC;
-+ nat->flags |= NX_NAT_F_SRC;
-+ ofpacts->header = ofpbuf_push_uninit(ofpacts, nat_offset);
-+ ct = ofpacts->header;
-+ }
-+
- size_t set_field_offset = ofpacts->size;
- ofpbuf_pull(ofpacts, set_field_offset);
-
-@@ -792,6 +808,21 @@ encode_CT_COMMIT_V2(const struct ovnact_nest *on,
- ct->zone_src.ofs = 0;
- ct->zone_src.n_bits = 16;
-
-+ /* If the datapath supports all-zero SNAT then use it to avoid tuple
-+ * collisions at commit time between NATed and firewalled-only sessions.
-+ */
-+ if (ovs_feature_is_supported(OVS_CT_ZERO_SNAT_SUPPORT)) {
-+ size_t nat_offset = ofpacts->size;
-+ ofpbuf_pull(ofpacts, nat_offset);
-+
-+ struct ofpact_nat *nat = ofpact_put_NAT(ofpacts);
-+ nat->flags = 0;
-+ nat->range_af = AF_UNSPEC;
-+ nat->flags |= NX_NAT_F_SRC;
-+ ofpacts->header = ofpbuf_push_uninit(ofpacts, nat_offset);
-+ ct = ofpacts->header;
-+ }
-+
- size_t set_field_offset = ofpacts->size;
- ofpbuf_pull(ofpacts, set_field_offset);
-
-diff --git a/lib/automake.mk b/lib/automake.mk
-index 781be2109..917b28e1e 100644
---- a/lib/automake.mk
-+++ b/lib/automake.mk
-@@ -13,6 +13,7 @@ lib_libovn_la_SOURCES = \
- lib/expr.c \
- lib/extend-table.h \
- lib/extend-table.c \
-+ lib/features.c \
- lib/ovn-parallel-hmap.h \
- lib/ovn-parallel-hmap.c \
- lib/ip-mcast-index.c \
-diff --git a/lib/features.c b/lib/features.c
-new file mode 100644
-index 000000000..87d04ee3f
---- /dev/null
-+++ b/lib/features.c
-@@ -0,0 +1,84 @@
-+/* Copyright (c) 2021, Red Hat, Inc.
-+ *
-+ * Licensed under the Apache License, Version 2.0 (the "License");
-+ * you may not use this file except in compliance with the License.
-+ * You may obtain a copy of the License at:
-+ *
-+ * http://www.apache.org/licenses/LICENSE-2.0
-+ *
-+ * Unless required by applicable law or agreed to in writing, software
-+ * distributed under the License is distributed on an "AS IS" BASIS,
-+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-+ * See the License for the specific language governing permissions and
-+ * limitations under the License.
-+ */
-+
-+#include
-+#include
-+#include
-+
-+#include "lib/util.h"
-+#include "openvswitch/vlog.h"
-+#include "ovn/features.h"
-+
-+VLOG_DEFINE_THIS_MODULE(features);
-+
-+struct ovs_feature {
-+ enum ovs_feature_value value;
-+ const char *name;
-+};
-+
-+static struct ovs_feature all_ovs_features[] = {
-+ {
-+ .value = OVS_CT_ZERO_SNAT_SUPPORT,
-+ .name = "ct_zero_snat"
-+ },
-+};
-+
-+/* A bitmap of OVS features that have been detected as 'supported'. */
-+static uint32_t supported_ovs_features;
-+
-+static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5);
-+
-+static bool
-+ovs_feature_is_valid(enum ovs_feature_value feature)
-+{
-+ switch (feature) {
-+ case OVS_CT_ZERO_SNAT_SUPPORT:
-+ return true;
-+ default:
-+ return false;
-+ }
-+}
-+
-+bool
-+ovs_feature_is_supported(enum ovs_feature_value feature)
-+{
-+ ovs_assert(ovs_feature_is_valid(feature));
-+ return supported_ovs_features & feature;
-+}
-+
-+/* Returns 'true' if the set of tracked OVS features has been updated. */
-+bool
-+ovs_feature_support_update(const struct smap *ovs_capabilities)
-+{
-+ bool updated = false;
-+
-+ for (size_t i = 0; i < ARRAY_SIZE(all_ovs_features); i++) {
-+ enum ovs_feature_value value = all_ovs_features[i].value;
-+ const char *name = all_ovs_features[i].name;
-+ bool old_state = supported_ovs_features & value;
-+ bool new_state = smap_get_bool(ovs_capabilities, name, false);
-+ if (new_state != old_state) {
-+ updated = true;
-+ if (new_state) {
-+ supported_ovs_features |= value;
-+ } else {
-+ supported_ovs_features &= ~value;
-+ }
-+ VLOG_INFO_RL(&rl, "OVS Feature: %s, state: %s", name,
-+ new_state ? "supported" : "not supported");
-+ }
-+ }
-+ return updated;
-+}
-diff --git a/lib/test-ovn-features.c b/lib/test-ovn-features.c
-new file mode 100644
-index 000000000..deb97581e
---- /dev/null
-+++ b/lib/test-ovn-features.c
-@@ -0,0 +1,56 @@
-+/* Copyright (c) 2021, Red Hat, Inc.
-+ *
-+ * Licensed under the Apache License, Version 2.0 (the "License");
-+ * you may not use this file except in compliance with the License.
-+ * You may obtain a copy of the License at:
-+ *
-+ * http://www.apache.org/licenses/LICENSE-2.0
-+ *
-+ * Unless required by applicable law or agreed to in writing, software
-+ * distributed under the License is distributed on an "AS IS" BASIS,
-+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-+ * See the License for the specific language governing permissions and
-+ * limitations under the License.
-+ */
-+
-+#include
-+
-+#include "ovn/features.h"
-+#include "tests/ovstest.h"
-+
-+static void
-+test_ovn_features(struct ovs_cmdl_context *ctx OVS_UNUSED)
-+{
-+ ovs_assert(!ovs_feature_is_supported(OVS_CT_ZERO_SNAT_SUPPORT));
-+
-+ struct smap features = SMAP_INITIALIZER(&features);
-+
-+ smap_add(&features, "ct_zero_snat", "false");
-+ ovs_assert(!ovs_feature_support_update(&features));
-+ ovs_assert(!ovs_feature_is_supported(OVS_CT_ZERO_SNAT_SUPPORT));
-+
-+ smap_replace(&features, "ct_zero_snat", "true");
-+ ovs_assert(ovs_feature_support_update(&features));
-+ ovs_assert(ovs_feature_is_supported(OVS_CT_ZERO_SNAT_SUPPORT));
-+
-+ smap_add(&features, "unknown_feature", "true");
-+ ovs_assert(!ovs_feature_support_update(&features));
-+
-+ smap_destroy(&features);
-+}
-+
-+static void
-+test_ovn_features_main(int argc, char *argv[])
-+{
-+ set_program_name(argv[0]);
-+ static const struct ovs_cmdl_command commands[] = {
-+ {"run", NULL, 0, 0, test_ovn_features, OVS_RO},
-+ {NULL, NULL, 0, 0, NULL, OVS_RO},
-+ };
-+ struct ovs_cmdl_context ctx;
-+ ctx.argc = argc - 1;
-+ ctx.argv = argv + 1;
-+ ovs_cmdl_run_command(&ctx, commands);
-+}
-+
-+OVSTEST_REGISTER("test-ovn-features", test_ovn_features_main);
-diff --git a/northd/lrouter.dl b/northd/lrouter.dl
-index 6c25b1ca9..6805b9036 100644
---- a/northd/lrouter.dl
-+++ b/northd/lrouter.dl
-@@ -692,6 +692,17 @@ relation &StaticRoute(lrsr: nb::Logical_Router_Static_Route,
- },
- var esr = lrsr.options.get_bool_def("ecmp_symmetric_reply", false).
-
-+relation &StaticRouteEmptyNextHop(lrsr: nb::Logical_Router_Static_Route,
-+ key: route_key,
-+ output_port: Option)
-+&StaticRouteEmptyNextHop(.lrsr = lrsr,
-+ .key = RouteKey{policy, ip_prefix, plen},
-+ .output_port = lrsr.output_port) :-
-+ lrsr in nb::Logical_Router_Static_Route(.nexthop = ""),
-+ not StaticRouteDown(lrsr._uuid),
-+ var policy = route_policy_from_string(lrsr.policy),
-+ Some{(var ip_prefix, var plen)} = ip46_parse_cidr(lrsr.ip_prefix).
-+
- /* Returns the IP address of the router port 'op' that
- * overlaps with 'ip'. If one is not found, returns None. */
- function find_lrp_member_ip(networks: lport_addresses, ip: v46_ip): Option =
-@@ -743,6 +754,19 @@ RouterStaticRoute_(.router = router,
- var route_id = FlatMap(routes),
- route in &StaticRoute(.lrsr = nb::Logical_Router_Static_Route{._uuid = route_id}).
-
-+relation RouterStaticRouteEmptyNextHop_(
-+ router : Intern,
-+ key : route_key,
-+ output_port : Option)
-+
-+RouterStaticRouteEmptyNextHop_(.router = router,
-+ .key = route.key,
-+ .output_port = route.output_port) :-
-+ router in &Router(),
-+ nb::Logical_Router(._uuid = router._uuid, .static_routes = routes),
-+ var route_id = FlatMap(routes),
-+ route in &StaticRouteEmptyNextHop(.lrsr = nb::Logical_Router_Static_Route{._uuid = route_id}).
-+
- /* Step-2: compute output_port for each pair */
- typedef route_dst = RouteDst {
- nexthop: v46_ip,
-@@ -805,6 +829,42 @@ RouterStaticRoute(router, key, dsts) :-
- },
- var dsts = set_singleton(RouteDst{nexthop, src_ip, port, ecmp_symmetric_reply}).
-
-+relation RouterStaticRouteEmptyNextHop(
-+ router : Intern,
-+ key : route_key,
-+ dsts : Set)
-+
-+RouterStaticRouteEmptyNextHop(router, key, dsts) :-
-+ RouterStaticRouteEmptyNextHop_(.router = router,
-+ .key = key,
-+ .output_port = Some{oport}),
-+ /* output_port specified */
-+ port in &RouterPort(.lrp = &nb::Logical_Router_Port{.name = oport},
-+ .networks = networks),
-+ /* There are no IP networks configured on the router's port via
-+ * which 'route->nexthop' is theoretically reachable. But since
-+ * 'out_port' has been specified, we honor it by trying to reach
-+ * 'route->nexthop' via the first IP address of 'out_port'.
-+ * (There are cases, e.g in GCE, where each VM gets a /32 IP
-+ * address and the default gateway is still reachable from it.) */
-+ Some{var src_ip} = match (key.ip_prefix) {
-+ IPv4{_} -> match (networks.ipv4_addrs.nth(0)) {
-+ Some{addr} -> Some{IPv4{addr.addr}},
-+ None -> {
-+ warn("No path for static route ${key.ip_prefix}");
-+ None
-+ }
-+ },
-+ IPv6{_} -> match (networks.ipv6_addrs.nth(0)) {
-+ Some{addr} -> Some{IPv6{addr.addr}},
-+ None -> {
-+ warn("No path for static route ${key.ip_prefix}");
-+ None
-+ }
-+ }
-+ },
-+ var dsts = set_singleton(RouteDst{src_ip, src_ip, port, false}).
-+
- /* compute route-route pairs for nexthop = "discard" routes */
- relation &DiscardRoute(lrsr: nb::Logical_Router_Static_Route,
- key: route_key)
-diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
-index 407464602..890775797 100644
---- a/northd/ovn-northd.8.xml
-+++ b/northd/ovn-northd.8.xml
-@@ -1072,8 +1072,10 @@ output;
- localport
ports) that are down (unless
- ignore_lsp_down
is configured as true in options
- column of NB_Global
table of the Northbound
-- database), for logical ports of type virtual
and for
-- logical ports with 'unknown' address set.
-+ database), for logical ports of type virtual
, for
-+ logical ports with 'unknown' address set and for logical ports of
-+ a logical switch configured with
-+ other_config:vlan-passthru=true
.
-
-
-
-@@ -3710,6 +3712,13 @@ icmp6 {
- external ip and D is NAT external mac.
-
-
-+
-+ For each NAT rule in the OVN Northbound database that can
-+ be handled in a distributed manner, a priority-80 logical flow
-+ with drop action if the NAT logical port is a virtual port not
-+ claimed by any chassis yet.
-+
-+
-
- A priority-50 logical flow with match
- outport == GW
has actions
-diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
-index 3dae7bb1c..148e3ee21 100644
---- a/northd/ovn-northd.c
-+++ b/northd/ovn-northd.c
-@@ -7007,6 +7007,10 @@ build_lswitch_arp_nd_responder_known_ips(struct ovn_port *op,
- return;
- }
-
-+ if (is_vlan_transparent(op->od)) {
-+ return;
-+ }
-+
- for (size_t i = 0; i < op->n_lsp_addrs; i++) {
- for (size_t j = 0; j < op->lsp_addrs[i].n_ipv4_addrs; j++) {
- ds_clear(match);
-@@ -7371,6 +7375,7 @@ build_lswitch_ip_mcast_igmp_mld(struct ovn_igmp_group *igmp_group,
-
- struct mcast_switch_info *mcast_sw_info =
- &igmp_group->datapath->mcast_info.sw;
-+ uint64_t table_size = mcast_sw_info->table_size;
-
- if (IN6_IS_ADDR_V4MAPPED(&igmp_group->address)) {
- /* RFC 4541, section 2.1.2, item 2: Skip groups in the 224.0.0.X
-@@ -7381,10 +7386,8 @@ build_lswitch_ip_mcast_igmp_mld(struct ovn_igmp_group *igmp_group,
- if (ip_is_local_multicast(group_address)) {
- return;
- }
--
- if (atomic_compare_exchange_strong(
-- &mcast_sw_info->active_v4_flows,
-- (uint64_t *) &mcast_sw_info->table_size,
-+ &mcast_sw_info->active_v4_flows, &table_size,
- mcast_sw_info->table_size)) {
- return;
- }
-@@ -7399,8 +7402,7 @@ build_lswitch_ip_mcast_igmp_mld(struct ovn_igmp_group *igmp_group,
- return;
- }
- if (atomic_compare_exchange_strong(
-- &mcast_sw_info->active_v6_flows,
-- (uint64_t *) &mcast_sw_info->table_size,
-+ &mcast_sw_info->active_v6_flows, &table_size,
- mcast_sw_info->table_size)) {
- return;
- }
-@@ -8039,10 +8041,16 @@ route_hash(struct parsed_route *route)
-
- static struct ovs_mutex bfd_lock = OVS_MUTEX_INITIALIZER;
-
-+static bool
-+find_static_route_outport(struct ovn_datapath *od, struct hmap *ports,
-+ const struct nbrec_logical_router_static_route *route, bool is_ipv4,
-+ const char **p_lrp_addr_s, struct ovn_port **p_out_port);
-+
- /* Parse and validate the route. Return the parsed route if successful.
- * Otherwise return NULL. */
- static struct parsed_route *
--parsed_routes_add(struct ovs_list *routes,
-+parsed_routes_add(struct ovn_datapath *od, struct hmap *ports,
-+ struct ovs_list *routes,
- const struct nbrec_logical_router_static_route *route,
- struct hmap *bfd_connections)
- {
-@@ -8050,7 +8058,8 @@ parsed_routes_add(struct ovs_list *routes,
- struct in6_addr nexthop;
- unsigned int plen;
- bool is_discard_route = !strcmp(route->nexthop, "discard");
-- if (!is_discard_route) {
-+ bool valid_nexthop = strlen(route->nexthop) && !is_discard_route;
-+ if (valid_nexthop) {
- if (!ip46_parse_cidr(route->nexthop, &nexthop, &plen)) {
- static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
- VLOG_WARN_RL(&rl, "bad 'nexthop' %s in static route"
-@@ -8079,7 +8088,7 @@ parsed_routes_add(struct ovs_list *routes,
- }
-
- /* Verify that ip_prefix and nexthop have same address familiy. */
-- if (!is_discard_route) {
-+ if (valid_nexthop) {
- if (IN6_IS_ADDR_V4MAPPED(&prefix) != IN6_IS_ADDR_V4MAPPED(&nexthop)) {
- static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
- VLOG_WARN_RL(&rl, "Address family doesn't match between 'ip_prefix'"
-@@ -8090,6 +8099,14 @@ parsed_routes_add(struct ovs_list *routes,
- }
- }
-
-+ /* Verify that ip_prefix and nexthop are on the same network. */
-+ if (!is_discard_route &&
-+ !find_static_route_outport(od, ports, route,
-+ IN6_IS_ADDR_V4MAPPED(&prefix),
-+ NULL, NULL)) {
-+ return NULL;
-+ }
-+
- const struct nbrec_bfd *nb_bt = route->bfd;
- if (nb_bt && !strcmp(nb_bt->dst_ip, route->nexthop)) {
- struct bfd_entry *bfd_e;
-@@ -8364,8 +8381,12 @@ find_static_route_outport(struct ovn_datapath *od, struct hmap *ports,
- route->ip_prefix, route->nexthop);
- return false;
- }
-- *p_out_port = out_port;
-- *p_lrp_addr_s = lrp_addr_s;
-+ if (p_out_port) {
-+ *p_out_port = out_port;
-+ }
-+ if (p_lrp_addr_s) {
-+ *p_lrp_addr_s = lrp_addr_s;
-+ }
-
- return true;
- }
-@@ -8563,7 +8584,7 @@ add_route(struct hmap *lflows, struct ovn_datapath *od,
- } else {
- ds_put_format(&common_actions, REG_ECMP_GROUP_ID" = 0; %s = ",
- is_ipv4 ? REG_NEXT_HOP_IPV4 : REG_NEXT_HOP_IPV6);
-- if (gateway) {
-+ if (gateway && strlen(gateway)) {
- ds_put_cstr(&common_actions, gateway);
- } else {
- ds_put_format(&common_actions, "ip%s.dst", is_ipv4 ? "4" : "6");
-@@ -9892,8 +9913,8 @@ build_static_route_flows_for_lrouter(
- struct ecmp_groups_node *group;
- for (int i = 0; i < od->nbr->n_static_routes; i++) {
- struct parsed_route *route =
-- parsed_routes_add(&parsed_routes, od->nbr->static_routes[i],
-- bfd_connections);
-+ parsed_routes_add(od, ports, &parsed_routes,
-+ od->nbr->static_routes[i], bfd_connections);
- if (!route) {
- continue;
- }
-@@ -11656,6 +11677,7 @@ lrouter_check_nat_entry(struct ovn_datapath *od, const struct nbrec_nat *nat,
- static void
- build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od,
- struct hmap *lflows,
-+ struct hmap *ports,
- struct shash *meter_groups,
- struct hmap *lbs,
- struct ds *match, struct ds *actions)
-@@ -11763,10 +11785,21 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od,
- ds_clear(match);
- ds_clear(actions);
- ds_put_format(match,
-- "ip%s.src == %s && outport == %s && "
-- "is_chassis_resident(\"%s\")",
-+ "ip%s.src == %s && outport == %s",
- is_v6 ? "6" : "4", nat->logical_ip,
-- od->l3dgw_port->json_key, nat->logical_port);
-+ od->l3dgw_port->json_key);
-+ /* Add a rule to drop traffic from a distributed NAT if
-+ * the virtual port has not claimed yet becaused otherwise
-+ * the traffic will be centralized misconfiguring the TOR switch.
-+ */
-+ struct ovn_port *op = ovn_port_find(ports, nat->logical_port);
-+ if (op && op->nbsp && !strcmp(op->nbsp->type, "virtual")) {
-+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_GW_REDIRECT,
-+ 80, ds_cstr(match), "drop;",
-+ &nat->header_);
-+ }
-+ ds_put_format(match, " && is_chassis_resident(\"%s\")",
-+ nat->logical_port);
- ds_put_format(actions, "eth.src = %s; %s = %s; next;",
- nat->external_mac,
- is_v6 ? REG_SRC_IPV6 : REG_SRC_IPV4,
-@@ -11800,6 +11833,7 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od,
- ds_put_format(actions,
- "clone { ct_clear; "
- "inport = outport; outport = \"\"; "
-+ "eth.dst <-> eth.src; "
- "flags = 0; flags.loopback = 1; ");
- for (int j = 0; j < MFF_N_LOG_REGS; j++) {
- ds_put_format(actions, "reg%d = 0; ", j);
-@@ -11925,8 +11959,9 @@ build_lswitch_and_lrouter_iterate_by_od(struct ovn_datapath *od,
- &lsi->actions);
- build_misc_local_traffic_drop_flows_for_lrouter(od, lsi->lflows);
- build_lrouter_arp_nd_for_datapath(od, lsi->lflows);
-- build_lrouter_nat_defrag_and_lb(od, lsi->lflows, lsi->meter_groups,
-- lsi->lbs, &lsi->match, &lsi->actions);
-+ build_lrouter_nat_defrag_and_lb(od, lsi->lflows, lsi->ports,
-+ lsi->meter_groups, lsi->lbs, &lsi->match,
-+ &lsi->actions);
- }
-
- /* Helper function to combine all lflow generation which is iterated by port.
-@@ -13271,7 +13306,7 @@ ovnnb_db_run(struct northd_context *ctx,
- struct smap options;
- smap_clone(&options, &nb->options);
-
-- smap_add(&options, "mac_prefix", mac_addr_prefix);
-+ smap_replace(&options, "mac_prefix", mac_addr_prefix);
-
- if (!monitor_mac) {
- eth_addr_random(&svc_monitor_mac_ea);
-@@ -13286,8 +13321,10 @@ ovnnb_db_run(struct northd_context *ctx,
-
- smap_replace(&options, "northd_internal_version", ovn_internal_version);
-
-- nbrec_nb_global_verify_options(nb);
-- nbrec_nb_global_set_options(nb, &options);
-+ if (!smap_equal(&nb->options, &options)) {
-+ nbrec_nb_global_verify_options(nb);
-+ nbrec_nb_global_set_options(nb, &options);
-+ }
-
- smap_destroy(&options);
-
-diff --git a/northd/ovn_northd.dl b/northd/ovn_northd.dl
-index 3afa80a3b..de6a0652e 100644
---- a/northd/ovn_northd.dl
-+++ b/northd/ovn_northd.dl
-@@ -3309,7 +3309,8 @@ for (CheckLspIsUp[check_lsp_is_up]) {
- ((lsp_is_up(lsp) or not check_lsp_is_up)
- or lsp.__type == "router" or lsp.__type == "localport") and
- lsp.__type != "external" and lsp.__type != "virtual" and
-- not lsp.addresses.contains("unknown"))
-+ not lsp.addresses.contains("unknown") and
-+ not sw.is_vlan_transparent)
- {
- var __match = "arp.tpa == ${addr.addr} && arp.op == 1" in
- {
-@@ -3359,7 +3360,8 @@ for (SwitchPortIPv6Address(.port = &SwitchPort{.lsp = lsp, .json_name = json_nam
- .ea = ea, .addr = addr)
- if lsp.is_enabled() and
- (lsp_is_up(lsp) or lsp.__type == "router" or lsp.__type == "localport") and
-- lsp.__type != "external" and lsp.__type != "virtual")
-+ lsp.__type != "external" and lsp.__type != "virtual" and
-+ not sw.is_vlan_transparent)
- {
- var __match = "nd_ns && ip6.dst == {${addr.addr}, ${addr.solicited_node()}} && nd.target == ${addr.addr}" in
- var actions = "${if (lsp.__type == \"router\") \"nd_na_router\" else \"nd_na\"} { "
-@@ -5555,6 +5557,10 @@ for (rp in &RouterPort(.router = &Router{._uuid = lr_uuid, .options = lr_options
- }
- }
-
-+relation VirtualLogicalPort(logical_port: Option)
-+VirtualLogicalPort(Some{logical_port}) :-
-+ lsp in &nb::Logical_Switch_Port(.name = logical_port, .__type = "virtual").
-+
- /* NAT rules are only valid on Gateway routers and routers with
- * l3dgw_port (router has a port with "redirect-chassis"
- * specified). */
-@@ -5649,7 +5655,7 @@ for (r in &Router(._uuid = lr_uuid,
- } in
- if (nat.nat.__type == "dnat" or nat.nat.__type == "dnat_and_snat") {
- None = l3dgw_port in
-- var __match = "ip && ip4.dst == ${nat.nat.external_ip}" in
-+ var __match = "ip && ${ipX}.dst == ${nat.nat.external_ip}" in
- (var ext_ip_match, var ext_flow) = lrouter_nat_add_ext_ip_match(
- r, nat, __match, ipX, true, mask) in
- {
-@@ -5900,6 +5906,17 @@ for (r in &Router(._uuid = lr_uuid,
- .actions = actions,
- .external_ids = stage_hint(nat.nat._uuid));
-
-+ for (VirtualLogicalPort(nat.nat.logical_port)) {
-+ Some{var gwport} = l3dgw_port in
-+ Flow(.logical_datapath = lr_uuid,
-+ .stage = s_ROUTER_IN_GW_REDIRECT(),
-+ .priority = 80,
-+ .__match = "${ipX}.src == ${nat.nat.logical_ip} && "
-+ "outport == ${json_string_escape(gwport.name)}",
-+ .actions = "drop;",
-+ .external_ids = stage_hint(nat.nat._uuid))
-+ };
-+
- /* Egress Loopback table: For NAT on a distributed router.
- * If packets in the egress pipeline on the distributed
- * gateway port have ip.dst matching a NAT external IP, then
-@@ -5925,6 +5942,7 @@ for (r in &Router(._uuid = lr_uuid,
- var actions =
- "clone { ct_clear; "
- "inport = outport; outport = \"\"; "
-+ "eth.dst <-> eth.src; "
- "flags = 0; flags.loopback = 1; " ++
- regs.join("") ++
- "${rEGBIT_EGRESS_LOOPBACK()} = 1; "
-@@ -6468,6 +6486,11 @@ Route(key, dst.port, dst.src_ip, Some{dst.nexthop}) :-
- dsts.size() == 1,
- Some{var dst} = dsts.nth(0).
-
-+Route(key, dst.port, dst.src_ip, None) :-
-+ RouterStaticRouteEmptyNextHop(.router = router, .key = key, .dsts = dsts),
-+ dsts.size() == 1,
-+ Some{var dst} = dsts.nth(0).
-+
- /* Return a vector of pairs (1, set[0]), ... (n, set[n - 1]). */
- function numbered_vec(set: Set<'A>) : Vec<(bit<16>, 'A)> = {
- var vec = vec_with_capacity(set.size());
-diff --git a/tests/automake.mk b/tests/automake.mk
-index 742e5cff2..a8ec64212 100644
---- a/tests/automake.mk
-+++ b/tests/automake.mk
-@@ -34,6 +34,7 @@ TESTSUITE_AT = \
- tests/ovn-performance.at \
- tests/ovn-ofctrl-seqno.at \
- tests/ovn-ipam.at \
-+ tests/ovn-features.at \
- tests/ovn-lflow-cache.at \
- tests/ovn-ipsec.at
-
-@@ -207,6 +208,7 @@ $(srcdir)/package.m4: $(top_srcdir)/configure.ac
-
- noinst_PROGRAMS += tests/ovstest
- tests_ovstest_SOURCES = \
-+ include/ovn/features.h \
- tests/ovstest.c \
- tests/ovstest.h \
- tests/test-utils.c \
-@@ -218,6 +220,7 @@ tests_ovstest_SOURCES = \
- controller/lflow-cache.h \
- controller/ofctrl-seqno.c \
- controller/ofctrl-seqno.h \
-+ lib/test-ovn-features.c \
- northd/test-ipam.c \
- northd/ipam.c \
- northd/ipam.h
-diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at
-index 72c07b3fa..1aab49ae8 100644
---- a/tests/ovn-controller.at
-+++ b/tests/ovn-controller.at
-@@ -151,23 +151,24 @@ sysid=$(ovs-vsctl get Open_vSwitch . external_ids:system-id)
- check_datapath_type () {
- datapath_type=$1
- chassis_datapath_type=$(ovn-sbctl get Chassis ${sysid} other_config:datapath-type | sed -e 's/"//g') #"
-- test "${datapath_type}" = "${chassis_datapath_type}"
-+ ovs_datapath_type=$(ovs-vsctl get Bridge br-int datapath-type)
-+ test "${datapath_type}" = "${chassis_datapath_type}" && test "${datapath_type}" = "${ovs_datapath_type}"
- }
-
--OVS_WAIT_UNTIL([check_datapath_type ""])
-+OVS_WAIT_UNTIL([check_datapath_type system])
-
- ovs-vsctl set Bridge br-int datapath-type=foo
- OVS_WAIT_UNTIL([check_datapath_type foo])
-
- # Change "ovn-bridge-mappings" value. It should not change the "datapath-type".
- ovs-vsctl set Open_vSwitch . external_ids:ovn-bridge-mappings=foo-mapping
--check_datapath_type foo
-+AT_CHECK([check_datapath_type foo])
-
- ovs-vsctl set Bridge br-int datapath-type=bar
- OVS_WAIT_UNTIL([check_datapath_type bar])
-
- ovs-vsctl set Bridge br-int datapath-type=\"\"
--OVS_WAIT_UNTIL([check_datapath_type ""])
-+OVS_WAIT_UNTIL([check_datapath_type system])
-
- # Set the datapath_type in external_ids:ovn-bridge-datapath-type.
- ovs-vsctl set Open_vSwitch . external_ids:ovn-bridge-datapath-type=foo
-@@ -176,11 +177,9 @@ OVS_WAIT_UNTIL([check_datapath_type foo])
- # Change the br-int's datapath type to bar.
- # It should be reset to foo since ovn-bridge-datapath-type is configured.
- ovs-vsctl set Bridge br-int datapath-type=bar
--OVS_WAIT_UNTIL([test foo = `ovs-vsctl get Bridge br-int datapath-type`])
- OVS_WAIT_UNTIL([check_datapath_type foo])
-
- ovs-vsctl set Open_vSwitch . external_ids:ovn-bridge-datapath-type=foobar
--OVS_WAIT_UNTIL([test foobar = `ovs-vsctl get Bridge br-int datapath-type`])
- OVS_WAIT_UNTIL([check_datapath_type foobar])
-
- expected_iface_types=$(ovs-vsctl get Open_vSwitch . iface_types | tr -d '[[]] ""')
-@@ -393,6 +392,37 @@ OVN_CLEANUP([hv])
- AT_CLEANUP
- ])
-
-+# check that nb_cfg overflow cases handled properly
-+AT_SETUP([ovn-controller - overflow the nb_cfg value across the tables])
-+AT_KEYWORDS([ovn])
-+ovn_start
-+
-+net_add n1
-+sim_add hv
-+as hv
-+ovs-vsctl add-br br-phys
-+ovn_attach n1 br-phys 192.168.0.1
-+
-+check ovn-nbctl --wait=hv sync
-+
-+# overflow the NB_Global nb_cfg value
-+check ovn-nbctl set NB_Global . nb_cfg=9223372036854775806
-+
-+# nb_cfg must be set to zero if it exceed the value of LLONG_MAX
-+# the command below will try incress the value of nb_cfg to be greater than LLONG_MAX and
-+# expect zero as a return value
-+check ovn-nbctl --wait=hv sync
-+check ovn-nbctl --wait=hv sync
-+
-+# nb_cfg should be set to 1 in the chassis_private/nb_global/sb_global table
-+check_column 1 chassis_private nb_cfg
-+check_column 1 sb_global nb_cfg
-+check_column 1 nb:nb_global nb_cfg
-+check_column 0 chassis nb_cfg
-+
-+OVN_CLEANUP([hv])
-+AT_CLEANUP
-+
- # Test unix command: debug/delay-nb-cfg-report
- OVN_FOR_EACH_NORTHD([
- AT_SETUP([ovn-controller - debug/delay-nb-cfg-report])
-diff --git a/tests/ovn-features.at b/tests/ovn-features.at
-new file mode 100644
-index 000000000..36bd83055
---- /dev/null
-+++ b/tests/ovn-features.at
-@@ -0,0 +1,8 @@
-+#
-+# Unit tests for the lib/features.c module.
-+#
-+AT_BANNER([OVN unit tests - features])
-+
-+AT_SETUP([ovn -- unit test -- OVS feature detection tests])
-+AT_CHECK([ovstest test-ovn-features run], [0], [])
-+AT_CLEANUP
-diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
-index 1058d418a..0922e1aa0 100644
---- a/tests/ovn-nbctl.at
-+++ b/tests/ovn-nbctl.at
-@@ -1442,11 +1442,16 @@ dnl ---------------------------------------------------------------------
-
- OVN_NBCTL_TEST([ovn_nbctl_routes], [routes], [
- AT_CHECK([ovn-nbctl lr-add lr0])
-+AT_CHECK([ovn-nbctl lrp-add lr0 lp0 f0:00:00:00:00:01 10.0.0.254/24])
-
- dnl Check IPv4 routes
- AT_CHECK([ovn-nbctl lr-route-add lr0 0.0.0.0/0 192.168.0.1])
- AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.1.0/24 11.0.1.1 lp0])
- AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.0.1/24 11.0.0.2])
-+AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.10.0/24 lp0])
-+AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.10.0/24 lp1], [1], [],
-+ [ovn-nbctl: bad IPv4 nexthop argument: lp1
-+])
-
- dnl Add overlapping route with 10.0.0.1/24
- AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.0.111/24 11.0.0.1], [1], [],
-@@ -1495,6 +1500,7 @@ AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl
- IPv4 Routes
- 10.0.0.0/24 11.0.0.1 dst-ip
- 10.0.1.0/24 11.0.1.1 dst-ip lp0
-+ 10.0.10.0/24 dst-ip lp0
- 20.0.0.0/24 discard dst-ip
- 9.16.1.0/24 11.0.0.1 src-ip
- 10.0.0.0/24 11.0.0.2 src-ip
-@@ -1502,11 +1508,13 @@ IPv4 Routes
- 0.0.0.0/0 192.168.0.1 dst-ip
- ])
-
-+AT_CHECK([ovn-nbctl lrp-add lr0 lp1 f0:00:00:00:00:02 11.0.0.254/24])
- AT_CHECK([ovn-nbctl --may-exist lr-route-add lr0 10.0.0.111/24 11.0.0.1 lp1])
- AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl
- IPv4 Routes
- 10.0.0.0/24 11.0.0.1 dst-ip lp1
- 10.0.1.0/24 11.0.1.1 dst-ip lp0
-+ 10.0.10.0/24 dst-ip lp0
- 20.0.0.0/24 discard dst-ip
- 9.16.1.0/24 11.0.0.1 src-ip
- 10.0.0.0/24 11.0.0.2 src-ip
-@@ -1535,6 +1543,7 @@ AT_CHECK([ovn-nbctl --policy=src-ip lr-route-del lr0 9.16.1.0/24])
- AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl
- IPv4 Routes
- 10.0.0.0/24 11.0.0.1 dst-ip lp1
-+ 10.0.10.0/24 dst-ip lp0
- 10.0.0.0/24 11.0.0.2 src-ip
- 0.0.0.0/0 192.168.0.1 dst-ip
- ])
-@@ -1544,6 +1553,7 @@ AT_CHECK([ovn-nbctl --policy=dst-ip lr-route-del lr0 10.0.0.0/24])
- AT_CHECK([ovn-nbctl --policy=src-ip lr-route-del lr0 10.0.0.0/24])
- AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl
- IPv4 Routes
-+ 10.0.10.0/24 dst-ip lp0
- 0.0.0.0/0 192.168.0.1 dst-ip
- ])
-
-@@ -1553,6 +1563,7 @@ AT_CHECK([ovn-nbctl --policy=src-ip lr-route-add lr0 10.0.0.0/24 11.0.0.2])
- AT_CHECK([ovn-nbctl lr-route-del lr0 10.0.0.0/24])
- AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl
- IPv4 Routes
-+ 10.0.10.0/24 dst-ip lp0
- 0.0.0.0/0 192.168.0.1 dst-ip
- ])
-
-diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
-index ad1732da3..2b70f48f6 100644
---- a/tests/ovn-northd.at
-+++ b/tests/ovn-northd.at
-@@ -3016,16 +3016,16 @@ for i in $(seq 1 5); do
- check ovn-nbctl --wait=sb lsp-set-addresses sw$i-r0 00:00:00:00:00:0$i
- done
-
--uuid=$(ovn-nbctl create bfd logical_port=r0-sw1 dst_ip=192.168.10.2 status=down min_tx=250 min_rx=250 detect_mult=10)
--ovn-nbctl create bfd logical_port=r0-sw2 dst_ip=192.168.20.2 status=down min_tx=500 min_rx=500 detect_mult=20
--ovn-nbctl create bfd logical_port=r0-sw3 dst_ip=192.168.30.2 status=down
--ovn-nbctl create bfd logical_port=r0-sw4 dst_ip=192.168.40.2 status=down min_tx=0 detect_mult=0
-+uuid=$(ovn-nbctl create bfd logical_port=r0-sw1 dst_ip=192.168.1.2 status=down min_tx=250 min_rx=250 detect_mult=10)
-+ovn-nbctl create bfd logical_port=r0-sw2 dst_ip=192.168.2.2 status=down min_tx=500 min_rx=500 detect_mult=20
-+ovn-nbctl create bfd logical_port=r0-sw3 dst_ip=192.168.3.2 status=down
-+ovn-nbctl create bfd logical_port=r0-sw4 dst_ip=192.168.4.2 status=down min_tx=0 detect_mult=0
-
--wait_row_count bfd 1 logical_port=r0-sw1 detect_mult=10 dst_ip=192.168.10.2 \
-+wait_row_count bfd 1 logical_port=r0-sw1 detect_mult=10 dst_ip=192.168.1.2 \
- min_rx=250 min_tx=250 status=admin_down
--wait_row_count bfd 1 logical_port=r0-sw2 detect_mult=20 dst_ip=192.168.20.2 \
-+wait_row_count bfd 1 logical_port=r0-sw2 detect_mult=20 dst_ip=192.168.2.2 \
- min_rx=500 min_tx=500 status=admin_down
--wait_row_count bfd 1 logical_port=r0-sw3 detect_mult=5 dst_ip=192.168.30.2 \
-+wait_row_count bfd 1 logical_port=r0-sw3 detect_mult=5 dst_ip=192.168.3.2 \
- min_rx=1000 min_tx=1000 status=admin_down
-
- uuid=$(fetch_column nb:bfd _uuid logical_port=r0-sw1)
-@@ -3036,17 +3036,17 @@ check ovn-nbctl clear bfd $uuid_2 min_rx
- wait_row_count bfd 1 logical_port=r0-sw2 min_rx=1000
- wait_row_count bfd 1 logical_port=r0-sw1 min_rx=1000 min_tx=1000 detect_mult=100
-
--check ovn-nbctl --bfd=$uuid lr-route-add r0 100.0.0.0/8 192.168.10.2
-+check ovn-nbctl --bfd=$uuid lr-route-add r0 100.0.0.0/8 192.168.1.2
- wait_column down bfd status logical_port=r0-sw1
--AT_CHECK([ovn-nbctl lr-route-list r0 | grep 192.168.10.2 | grep -q bfd],[0])
-+AT_CHECK([ovn-nbctl lr-route-list r0 | grep 192.168.1.2 | grep -q bfd],[0])
-
--check ovn-nbctl --bfd lr-route-add r0 200.0.0.0/8 192.168.20.2
-+check ovn-nbctl --bfd lr-route-add r0 200.0.0.0/8 192.168.2.2
- wait_column down bfd status logical_port=r0-sw2
--AT_CHECK([ovn-nbctl lr-route-list r0 | grep 192.168.20.2 | grep -q bfd],[0])
-+AT_CHECK([ovn-nbctl lr-route-list r0 | grep 192.168.2.2 | grep -q bfd],[0])
-
--check ovn-nbctl --bfd lr-route-add r0 240.0.0.0/8 192.168.50.2 r0-sw5
-+check ovn-nbctl --bfd lr-route-add r0 240.0.0.0/8 192.168.5.2 r0-sw5
- wait_column down bfd status logical_port=r0-sw5
--AT_CHECK([ovn-nbctl lr-route-list r0 | grep 192.168.50.2 | grep -q bfd],[0])
-+AT_CHECK([ovn-nbctl lr-route-list r0 | grep 192.168.5.2 | grep -q bfd],[0])
-
- route_uuid=$(fetch_column nb:logical_router_static_route _uuid ip_prefix="100.0.0.0/8")
- check ovn-nbctl clear logical_router_static_route $route_uuid bfd
-@@ -3659,3 +3659,73 @@ check ovn-nbctl --wait=sb sync
- OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
- AT_CLEANUP
- ])
-+
-+OVN_FOR_EACH_NORTHD([
-+AT_SETUP([ovn -- static routes flows])
-+AT_KEYWORDS([static-routes-flows])
-+ovn_start
-+
-+check ovn-sbctl chassis-add ch1 geneve 127.0.0.1
-+
-+check ovn-nbctl lr-add lr0
-+check ovn-nbctl ls-add public
-+check ovn-nbctl lrp-add lr0 lr0-public 00:00:20:20:12:13 192.168.0.1/24
-+check ovn-nbctl lsp-add public public-lr0
-+check ovn-nbctl lsp-set-type public-lr0 router
-+check ovn-nbctl lsp-set-addresses public-lr0 router
-+check ovn-nbctl lsp-set-options public-lr0 router-port=lr0-public
-+
-+check ovn-nbctl --wait=sb --ecmp-symmetric-reply lr-route-add lr0 1.0.0.1 192.168.0.10
-+
-+ovn-sbctl dump-flows lr0 > lr0flows
-+
-+AT_CHECK([grep -e "lr_in_ip_routing.*select" lr0flows |sort], [0], [dnl
-+])
-+AT_CHECK([grep -e "lr_in_ip_routing_ecmp" lr0flows |sort], [0], [dnl
-+ table=11(lr_in_ip_routing_ecmp), priority=150 , match=(reg8[[0..15]] == 0), action=(next;)
-+])
-+
-+check ovn-nbctl --wait=sb --ecmp-symmetric-reply lr-route-add lr0 1.0.0.1 192.168.0.20
-+
-+ovn-sbctl dump-flows lr0 > lr0flows
-+AT_CHECK([grep -e "lr_in_ip_routing.*select" lr0flows |sort], [0], [dnl
-+ table=10(lr_in_ip_routing ), priority=65 , match=(ip4.dst == 1.0.0.1/32), action=(ip.ttl--; flags.loopback = 1; reg8[[0..15]] = 1; reg8[[16..31]] = select(1, 2);)
-+])
-+AT_CHECK([grep -e "lr_in_ip_routing_ecmp" lr0flows | sed 's/192\.168\.0\..0/192.168.0.??/' |sort], [0], [dnl
-+ table=11(lr_in_ip_routing_ecmp), priority=100 , match=(reg8[[0..15]] == 1 && reg8[[16..31]] == 1), action=(reg0 = 192.168.0.??; reg1 = 192.168.0.1; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; next;)
-+ table=11(lr_in_ip_routing_ecmp), priority=100 , match=(reg8[[0..15]] == 1 && reg8[[16..31]] == 2), action=(reg0 = 192.168.0.??; reg1 = 192.168.0.1; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; next;)
-+ table=11(lr_in_ip_routing_ecmp), priority=150 , match=(reg8[[0..15]] == 0), action=(next;)
-+])
-+
-+# add ecmp route with wrong nexthop
-+check ovn-nbctl --wait=sb --ecmp-symmetric-reply lr-route-add lr0 1.0.0.1 192.168.1.20
-+
-+ovn-sbctl dump-flows lr0 > lr0flows
-+AT_CHECK([grep -e "lr_in_ip_routing.*select" lr0flows |sort], [0], [dnl
-+ table=10(lr_in_ip_routing ), priority=65 , match=(ip4.dst == 1.0.0.1/32), action=(ip.ttl--; flags.loopback = 1; reg8[[0..15]] = 1; reg8[[16..31]] = select(1, 2);)
-+])
-+AT_CHECK([grep -e "lr_in_ip_routing_ecmp" lr0flows | sed 's/192\.168\.0\..0/192.168.0.??/' |sort], [0], [dnl
-+ table=11(lr_in_ip_routing_ecmp), priority=100 , match=(reg8[[0..15]] == 1 && reg8[[16..31]] == 1), action=(reg0 = 192.168.0.??; reg1 = 192.168.0.1; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; next;)
-+ table=11(lr_in_ip_routing_ecmp), priority=100 , match=(reg8[[0..15]] == 1 && reg8[[16..31]] == 2), action=(reg0 = 192.168.0.??; reg1 = 192.168.0.1; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; next;)
-+ table=11(lr_in_ip_routing_ecmp), priority=150 , match=(reg8[[0..15]] == 0), action=(next;)
-+])
-+
-+check ovn-nbctl lr-route-del lr0
-+wait_row_count nb:Logical_Router_Static_Route 0
-+
-+check ovn-nbctl --wait=sb lr-route-add lr0 1.0.0.0/24 192.168.0.10
-+ovn-sbctl dump-flows lr0 > lr0flows
-+
-+AT_CHECK([grep -e "lr_in_ip_routing.*192.168.0.10" lr0flows |sort], [0], [dnl
-+ table=10(lr_in_ip_routing ), priority=49 , match=(ip4.dst == 1.0.0.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 192.168.0.10; reg1 = 192.168.0.1; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;)
-+])
-+
-+check ovn-nbctl --wait=sb lr-route-add lr0 2.0.0.0/24 lr0-public
-+
-+ovn-sbctl dump-flows lr0 > lr0flows
-+AT_CHECK([grep -e "lr_in_ip_routing.*2.0.0.0" lr0flows |sort], [0], [dnl
-+ table=10(lr_in_ip_routing ), priority=49 , match=(ip4.dst == 2.0.0.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = ip4.dst; reg1 = 192.168.0.1; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;)
-+])
-+
-+AT_CLEANUP
-+])
-diff --git a/tests/ovn.at b/tests/ovn.at
-index aa80a7c48..5eb7f8b7b 100644
---- a/tests/ovn.at
-+++ b/tests/ovn.at
-@@ -3169,6 +3169,118 @@ OVN_CLEANUP([hv-1],[hv-2])
- AT_CLEANUP
- ])
-
-+OVN_FOR_EACH_NORTHD([
-+AT_SETUP([ovn -- VLAN transparency, passthru=true, ARP responder disabled])
-+ovn_start
-+
-+net_add net
-+check ovs-vsctl add-br br-phys
-+ovn_attach net br-phys 192.168.0.1
-+
-+check ovn-nbctl ls-add ls
-+check ovn-nbctl --wait=sb add Logical-Switch ls other_config vlan-passthru=true
-+
-+for i in 1 2; do
-+ check ovn-nbctl lsp-add ls lsp$i
-+ check ovn-nbctl lsp-set-addresses lsp$i "f0:00:00:00:00:0$i 10.0.0.$i"
-+done
-+
-+for i in 1 2; do
-+ check ovs-vsctl add-port br-int vif$i -- set Interface vif$i external-ids:iface-id=lsp$i \
-+ options:tx_pcap=vif$i-tx.pcap \
-+ options:rxq_pcap=vif$i-rx.pcap \
-+ ofport-request=$i
-+done
-+
-+wait_for_ports_up
-+
-+ovn-sbctl dump-flows ls > lsflows
-+AT_CAPTURE_FILE([lsflows])
-+
-+AT_CHECK([grep -w "ls_in_arp_rsp" lsflows | sort], [0], [dnl
-+ table=16(ls_in_arp_rsp ), priority=0 , match=(1), action=(next;)
-+])
-+
-+test_arp() {
-+ local inport=$1 outport=$2 sha=$3 spa=$4 tpa=$5 reply_ha=$6
-+ tag=8100fefe
-+ local request=ffffffffffff${sha}${tag}08060001080006040001${sha}${spa}ffffffffffff${tpa}
-+ ovs-appctl netdev-dummy/receive vif$inport $request
-+ echo $request >> $outport.expected
-+
-+ local reply=${sha}${reply_ha}${tag}08060001080006040002${reply_ha}${tpa}${sha}${spa}
-+ ovs-appctl netdev-dummy/receive vif$outport $reply
-+ echo $reply >> $inport.expected
-+}
-+
-+test_arp 1 2 f00000000001 0a000001 0a000002 f00000000002
-+test_arp 2 1 f00000000002 0a000002 0a000001 f00000000001
-+
-+for i in 1 2; do
-+ OVN_CHECK_PACKETS([vif$i-tx.pcap], [$i.expected])
-+done
-+
-+AT_CLEANUP
-+])
-+
-+OVN_FOR_EACH_NORTHD([
-+AT_SETUP([ovn -- VLAN transparency, passthru=true, ND/NA responder disabled])
-+ovn_start
-+
-+net_add net
-+check ovs-vsctl add-br br-phys
-+ovn_attach net br-phys 192.168.0.1
-+
-+check ovn-nbctl ls-add ls
-+check ovn-nbctl --wait=sb add Logical-Switch ls other_config vlan-passthru=true
-+
-+for i in 1 2; do
-+ check ovn-nbctl lsp-add ls lsp$i
-+ check ovn-nbctl lsp-set-addresses lsp$i "f0:00:00:00:00:0$i fe00::$i"
-+done
-+
-+for i in 1 2; do
-+ check ovs-vsctl add-port br-int vif$i -- set Interface vif$i external-ids:iface-id=lsp$i \
-+ options:tx_pcap=vif$i-tx.pcap \
-+ options:rxq_pcap=vif$i-rx.pcap \
-+ ofport-request=$i
-+done
-+
-+wait_for_ports_up
-+
-+ovn-sbctl dump-flows ls > lsflows
-+AT_CAPTURE_FILE([lsflows])
-+
-+AT_CHECK([grep -w "ls_in_arp_rsp" lsflows | sort], [0], [dnl
-+ table=16(ls_in_arp_rsp ), priority=0 , match=(1), action=(next;)
-+])
-+
-+test_nd_na() {
-+ local inport=$1 outport=$2 sha=$3 spa=$4 tpa=$5 reply_ha=$6
-+ tag=8100fefe
-+ icmp_type=87
-+ local request=ffffffffffff${sha}${tag}86dd6000000000183aff${spa}ff0200000000000000000001ff${tpa: -6}${icmp_type}007ea100000000${tpa}
-+ ovs-appctl netdev-dummy/receive vif$inport $request
-+ echo $request >> $outport.expected
-+ echo $request
-+
-+ icmp_type=88
-+ local reply=${sha}${reply_ha}${tag}86dd6000000000183aff${tpa}${spa}${icmp_type}003da540000000${tpa}
-+ ovs-appctl netdev-dummy/receive vif$outport $reply
-+ echo $reply >> $inport.expected
-+ echo $reply
-+}
-+
-+test_nd_na 1 2 f00000000001 fe000000000000000000000000000001 fe000000000000000000000000000002 f00000000002
-+test_nd_na 2 1 f00000000002 fe000000000000000000000000000002 fe000000000000000000000000000001 f00000000001
-+
-+for i in 1 2; do
-+ OVN_CHECK_PACKETS([vif$i-tx.pcap], [$i.expected])
-+done
-+
-+AT_CLEANUP
-+])
-+
- OVN_FOR_EACH_NORTHD([
- AT_SETUP([ovn -- VLAN transparency, passthru=true, multiple hosts])
- ovn_start
-@@ -7821,6 +7933,19 @@ mac_prefix=$(ovn-nbctl --wait=sb get NB_Global . options:mac_prefix | tr -d \")
- port_addr=$(ovn-nbctl get Logical-Switch-Port p91 dynamic_addresses | tr -d \")
- AT_CHECK([test "$port_addr" = "${mac_prefix}:00:00:09"], [0], [])
-
-+# set mac_prefix to all-zeroes and check it is allocated in a random manner
-+ovn-nbctl --wait=hv set NB_Global . options:mac_prefix="00:00:00:00:00:00"
-+ovn-nbctl ls-add sw14
-+ovn-nbctl --wait=sb set Logical-Switch sw14 other_config:mac_only=true
-+ovn-nbctl --wait=sb lsp-add sw14 p141 -- lsp-set-addresses p141 dynamic
-+
-+mac_prefix=$(ovn-nbctl --wait=sb get NB_Global . options:mac_prefix | tr -d \")
-+port_addr=$(ovn-nbctl get Logical-Switch-Port p141 dynamic_addresses | tr -d \")
-+AT_CHECK([test "$mac_prefix" != "00:00:00:00:00:00"], [0], [])
-+AT_CHECK([test "$port_addr" = "${mac_prefix}:00:00:0a"], [0], [])
-+ovn-nbctl --wait=sb lsp-del sw14 p141
-+ovn-nbctl --wait=sb ls-del sw14
-+
- ovn-nbctl --wait=hv set NB_Global . options:mac_prefix="00:11:22"
- ovn-nbctl ls-add sw10
- ovn-nbctl --wait=sb set Logical-Switch sw10 other_config:ipv6_prefix="ae01::"
-@@ -11260,7 +11385,7 @@ ovn-nbctl lsp-add foo ln-foo
- ovn-nbctl lsp-set-addresses ln-foo unknown
- ovn-nbctl lsp-set-options ln-foo network_name=public
- ovn-nbctl lsp-set-type ln-foo localnet
--AT_CHECK([ovn-nbctl set Logical_Switch_Port ln-foo tag=2])
-+check ovn-nbctl set Logical_Switch_Port ln-foo tag_request=2
-
- # Create localnet port in alice
- ovn-nbctl lsp-add alice ln-alice
-@@ -12024,6 +12149,91 @@ OVN_CLEANUP([hv1])
- AT_CLEANUP
- ])
-
-+OVN_FOR_EACH_NORTHD([
-+AT_SETUP([localport doesn't suppress ARP directed to external port])
-+
-+ovn_start
-+net_add n1
-+
-+check ovs-vsctl add-br br-phys
-+check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
-+ovn_attach n1 br-phys 192.168.0.1
-+
-+check ovn-nbctl ls-add ls
-+
-+# create topology to allow to talk from localport through localnet to external port
-+check ovn-nbctl lsp-add ls lp
-+check ovn-nbctl lsp-set-addresses lp "00:00:00:00:00:01 10.0.0.1"
-+check ovn-nbctl lsp-set-type lp localport
-+check ovs-vsctl add-port br-int lp -- set Interface lp external-ids:iface-id=lp
-+
-+check ovn-nbctl --wait=sb ha-chassis-group-add hagrp
-+check ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp main 10
-+check ovn-nbctl lsp-add ls lext
-+check ovn-nbctl lsp-set-addresses lext "00:00:00:00:00:02 10.0.0.2"
-+check ovn-nbctl lsp-set-type lext external
-+hagrp_uuid=`ovn-nbctl --bare --columns _uuid find ha_chassis_group name=hagrp`
-+check ovn-nbctl set logical_switch_port lext ha_chassis_group=$hagrp_uuid
-+
-+check ovn-nbctl lsp-add ls ln
-+check ovn-nbctl lsp-set-addresses ln unknown
-+check ovn-nbctl lsp-set-type ln localnet
-+check ovn-nbctl lsp-set-options ln network_name=phys
-+check ovn-nbctl --wait=hv sync
-+
-+# also create second external port AFTER localnet to check that order is irrelevant
-+check ovn-nbctl lsp-add ls lext2
-+check ovn-nbctl lsp-set-addresses lext2 "00:00:00:00:00:10 10.0.0.10"
-+check ovn-nbctl lsp-set-type lext2 external
-+check ovn-nbctl set logical_switch_port lext2 ha_chassis_group=$hagrp_uuid
-+check ovn-nbctl --wait=hv sync
-+
-+# create and immediately delete an external port to later check that flows for
-+# deleted ports are not left over in flow table
-+check ovn-nbctl lsp-add ls lext-deleted
-+check ovn-nbctl lsp-set-addresses lext-deleted "00:00:00:00:00:03 10.0.0.3"
-+check ovn-nbctl lsp-set-type lext-deleted external
-+check ovn-nbctl set logical_switch_port lext-deleted ha_chassis_group=$hagrp_uuid
-+check ovn-nbctl --wait=hv sync
-+check ovn-nbctl lsp-del lext-deleted
-+check ovn-nbctl --wait=hv sync
-+
-+send_garp() {
-+ local inport=$1 eth_src=$2 eth_dst=$3 spa=$4 tpa=$5
-+ local request=${eth_dst}${eth_src}08060001080006040001${eth_src}${spa}${eth_dst}${tpa}
-+ ovs-appctl netdev-dummy/receive $inport $request
-+}
-+
-+spa=$(ip_to_hex 10 0 0 1)
-+tpa=$(ip_to_hex 10 0 0 2)
-+send_garp lp 000000000001 000000000002 $spa $tpa
-+
-+spa=$(ip_to_hex 10 0 0 1)
-+tpa=$(ip_to_hex 10 0 0 10)
-+send_garp lp 000000000001 000000000010 $spa $tpa
-+
-+spa=$(ip_to_hex 10 0 0 1)
-+tpa=$(ip_to_hex 10 0 0 3)
-+send_garp lp 000000000001 000000000003 $spa $tpa
-+
-+dnl external traffic from localport should be sent to localnet
-+AT_CHECK([tcpdump -r main/br-phys_n1-tx.pcap arp[[24:4]]=0x0a000002 | wc -l],[0],[dnl
-+1
-+],[ignore])
-+
-+#dnl ...regardless of localnet / external ports creation order
-+AT_CHECK([tcpdump -r main/br-phys_n1-tx.pcap arp[[24:4]]=0x0a00000a | wc -l],[0],[dnl
-+1
-+],[ignore])
-+
-+dnl traffic from localport should not be sent to deleted external port
-+AT_CHECK([tcpdump -r main/br-phys_n1-tx.pcap arp[[24:4]]=0x0a000003 | wc -l],[0],[dnl
-+0
-+],[ignore])
-+
-+AT_CLEANUP
-+])
-+
- OVN_FOR_EACH_NORTHD([
- AT_SETUP([ovn -- 1 LR with HA distributed router gateway port])
- ovn_start
-@@ -12668,7 +12878,7 @@ $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv2/br-phys_n1-tx.pcap | trim_zeros
- AT_CHECK([grep $garp hv2_br_phys_tx | sort], [0], [])
-
- # change localnet port tag.
--AT_CHECK([ovn-nbctl set Logical_Switch_Port ln_port tag=2014])
-+check ovn-nbctl set Logical_Switch_Port ln_port tag_request=2014
-
- # wait for earlier changes to take effect
- OVS_WAIT_UNTIL([test 1 = `as hv2 ovs-ofctl dump-flows br-int table=65 | \
-@@ -17172,6 +17382,16 @@ send_arp_reply() {
- as hv$hv ovs-appctl netdev-dummy/receive hv${hv}-vif$inport $request
- }
-
-+send_icmp_packet() {
-+ local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv4_src=$5 ipv4_dst=$6 ip_chksum=$7 data=$8
-+ shift 8
-+
-+ local ip_ttl=ff
-+ local ip_len=001c
-+ local packet=${eth_dst}${eth_src}08004500${ip_len}00004000${ip_ttl}01${ip_chksum}${ipv4_src}${ipv4_dst}${data}
-+ as hv$hv ovs-appctl netdev-dummy/receive hv${hv}-vif$inport $packet
-+}
-+
- net_add n1
-
- sim_add hv1
-@@ -17311,27 +17531,29 @@ logical_port=sw0-vir) = x])
- as hv1
- ovs-vsctl set interface hv1-vif3 external-ids:iface-id=sw0-vir
-
--AT_CHECK([test x$(ovn-sbctl --bare --columns chassis find port_binding \
--logical_port=sw0-vir) = x], [0], [])
-+wait_column "" Port_Binding chassis logical_port=sw0-vir
-
- # Cleanup hv1-vif3.
- as hv1
- ovs-vsctl del-port hv1-vif3
-
--AT_CHECK([test x$(ovn-sbctl --bare --columns chassis find port_binding \
--logical_port=sw0-vir) = x], [0], [])
-+wait_column "" Port_Binding chassis logical_port=sw0-vir
-
- check_virtual_offlows_present() {
- hv=$1
-
-- AT_CHECK([as $hv ovs-ofctl dump-flows br-int table=44 | ofctl_strip_all | grep "priority=2000"], [0], [dnl
-- table=44, priority=2000,ip,metadata=0x1 actions=resubmit(,45)
-- table=44, priority=2000,ipv6,metadata=0x1 actions=resubmit(,45)
-+ sw0_dp_key=$(printf "%x" $(fetch_column Datapath_Binding tunnel_key external_ids:name=sw0))
-+ lr0_dp_key=$(printf "%x" $(fetch_column Datapath_Binding tunnel_key external_ids:name=lr0))
-+ lr0_public_dp_key=$(printf "%x" $(fetch_column Port_Binding tunnel_key logical_port=lr0-public))
-+
-+ AT_CHECK_UNQUOTED([as $hv ovs-ofctl dump-flows br-int table=44 | ofctl_strip_all | grep "priority=2000"], [0], [dnl
-+ table=44, priority=2000,ip,metadata=0x$sw0_dp_key actions=resubmit(,45)
-+ table=44, priority=2000,ipv6,metadata=0x$sw0_dp_key actions=resubmit(,45)
- ])
-
-- AT_CHECK([as $hv ovs-ofctl dump-flows br-int table=11 | ofctl_strip_all | \
-+ AT_CHECK_UNQUOTED([as $hv ovs-ofctl dump-flows br-int table=11 | ofctl_strip_all | \
- grep "priority=92" | grep 172.168.0.50], [0], [dnl
-- table=11, priority=92,arp,reg14=0x3,metadata=0x3,arp_tpa=172.168.0.50,arp_op=1 actions=move:NXM_OF_ETH_SRC[[]]->NXM_OF_ETH_DST[[]],mod_dl_src:10:54:00:00:00:10,load:0x2->NXM_OF_ARP_OP[[]],move:NXM_NX_ARP_SHA[[]]->NXM_NX_ARP_THA[[]],load:0x105400000010->NXM_NX_ARP_SHA[[]],push:NXM_OF_ARP_SPA[[]],push:NXM_OF_ARP_TPA[[]],pop:NXM_OF_ARP_SPA[[]],pop:NXM_OF_ARP_TPA[[]],move:NXM_NX_REG14[[]]->NXM_NX_REG15[[]],load:0x1->NXM_NX_REG10[[0]],resubmit(,37)
-+ table=11, priority=92,arp,reg14=0x$lr0_public_dp_key,metadata=0x$lr0_dp_key,arp_tpa=172.168.0.50,arp_op=1 actions=move:NXM_OF_ETH_SRC[[]]->NXM_OF_ETH_DST[[]],mod_dl_src:10:54:00:00:00:10,load:0x2->NXM_OF_ARP_OP[[]],move:NXM_NX_ARP_SHA[[]]->NXM_NX_ARP_THA[[]],load:0x105400000010->NXM_NX_ARP_SHA[[]],push:NXM_OF_ARP_SPA[[]],push:NXM_OF_ARP_TPA[[]],pop:NXM_OF_ARP_SPA[[]],pop:NXM_OF_ARP_TPA[[]],move:NXM_NX_REG14[[]]->NXM_NX_REG15[[]],load:0x1->NXM_NX_REG10[[0]],resubmit(,37)
- ])
- }
-
-@@ -17384,6 +17606,22 @@ logical_port=sw0-vir) = x])
- wait_row_count nb:Logical_Switch_Port 1 up=false name=sw0-vir
-
- check ovn-nbctl --wait=hv sync
-+
-+# verify the traffic from virtual port is discarded if the port is not claimed
-+AT_CHECK([grep lr_in_gw_redirect lr0-flows2 | grep "ip4.src == 10.0.0.10"], [0], [dnl
-+ table=17(lr_in_gw_redirect ), priority=100 , match=(ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("sw0-vir")), action=(eth.src = 10:54:00:00:00:10; reg1 = 172.168.0.50; next;)
-+ table=17(lr_in_gw_redirect ), priority=80 , match=(ip4.src == 10.0.0.10 && outport == "lr0-public"), action=(drop;)
-+])
-+
-+eth_src=505400000003
-+eth_dst=00000000ff01
-+ip_src=$(ip_to_hex 10 0 0 10)
-+ip_dst=$(ip_to_hex 172 168 0 101)
-+send_icmp_packet 1 1 $eth_src $eth_dst $ip_src $ip_dst c4c9 0000000000000000000000
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | awk '/table=25, n_packets=1, n_bytes=45/{print $7" "$8}'],[0],[dnl
-+priority=80,ip,reg15=0x3,metadata=0x3,nw_src=10.0.0.10 actions=drop
-+])
-+
- # hv1 should remove the flow for the ACL with is_chassis_redirect check for sw0-vir.
- check_virtual_offlows_not_present hv1
-
-@@ -23116,7 +23354,7 @@ AT_CHECK([
- for hv in 1 2; do
- grep table=15 hv${hv}flows | \
- grep "priority=100" | \
-- grep -c "ct(commit,zone=NXM_NX_REG11\\[[0..15\\]],exec(move:NXM_OF_ETH_SRC\\[[\\]]->NXM_NX_CT_LABEL\\[[32..79\\]],load:0x[[0-9]]->NXM_NX_CT_LABEL\\[[80..95\\]]))"
-+ grep -c "ct(commit,zone=NXM_NX_REG11\\[[0..15\\]],.*exec(move:NXM_OF_ETH_SRC\\[[\\]]->NXM_NX_CT_LABEL\\[[32..79\\]],load:0x[[0-9]]->NXM_NX_CT_LABEL\\[[80..95\\]]))"
-
- grep table=22 hv${hv}flows | \
- grep "priority=200" | \
-@@ -23241,7 +23479,7 @@ AT_CHECK([
- for hv in 1 2; do
- grep table=15 hv${hv}flows | \
- grep "priority=100" | \
-- grep -c "ct(commit,zone=NXM_NX_REG11\\[[0..15\\]],exec(move:NXM_OF_ETH_SRC\\[[\\]]->NXM_NX_CT_LABEL\\[[32..79\\]],load:0x[[0-9]]->NXM_NX_CT_LABEL\\[[80..95\\]]))"
-+ grep -c "ct(commit,zone=NXM_NX_REG11\\[[0..15\\]],.*exec(move:NXM_OF_ETH_SRC\\[[\\]]->NXM_NX_CT_LABEL\\[[32..79\\]],load:0x[[0-9]]->NXM_NX_CT_LABEL\\[[80..95\\]]))"
-
- grep table=22 hv${hv}flows | \
- grep "priority=200" | \
-@@ -26688,6 +26926,50 @@ OVN_CLEANUP([hv1])
- AT_CLEANUP
- ])
-
-+# Tests that ACLs referencing port groups that include ports connected to
-+# logical routers are correctly applied.
-+OVN_FOR_EACH_NORTHD([
-+AT_SETUP([ovn -- ACL with Port Group including router ports])
-+ovn_start
-+net_add n1
-+
-+sim_add hv1
-+as hv1
-+ovs-vsctl add-br br-phys
-+ovn_attach n1 br-phys 192.168.0.1
-+
-+check ovn-nbctl \
-+ -- lr-add lr \
-+ -- ls-add ls \
-+ -- lrp-add lr lrp_ls 00:00:00:00:00:01 42.42.42.1/24 \
-+ -- lsp-add ls ls_lr \
-+ -- lsp-set-addresses ls_lr router \
-+ -- lsp-set-type ls_lr router \
-+ -- lsp-set-options ls_lr router-port=lr_ls \
-+ -- lsp-add ls vm1
-+
-+check ovn-nbctl pg-add pg ls_lr \
-+ -- acl-add pg from-lport 1 'inport == @pg && ip4.dst == 42.42.42.42' drop
-+
-+check ovs-vsctl add-port br-int vm1 \
-+ -- set interface vm1 external_ids:iface-id=vm1
-+
-+wait_for_ports_up
-+check ovn-nbctl --wait=hv sync
-+
-+dp_key=$(fetch_column Datapath_Binding tunnel_key external_ids:name=ls)
-+rtr_port_key=$(fetch_column Port_Binding tunnel_key logical_port=ls_lr)
-+
-+# Check that ovn-controller adds a flow to drop packets with dest IP
-+# 42.42.42.42 coming from the router port.
-+AT_CHECK([ovs-ofctl dump-flows br-int table=17 | grep "reg14=0x${rtr_port_key},metadata=0x${dp_key},nw_dst=42.42.42.42 actions=drop" -c], [0], [dnl
-+1
-+])
-+
-+OVN_CLEANUP([hv1])
-+AT_CLEANUP
-+])
-+
- OVN_FOR_EACH_NORTHD([
- AT_SETUP([ovn -- Static route with discard nexthop])
- ovn_start
-diff --git a/tests/system-common-macros.at b/tests/system-common-macros.at
-index c8fa6f03f..b742a2cb9 100644
---- a/tests/system-common-macros.at
-+++ b/tests/system-common-macros.at
-@@ -330,3 +330,7 @@ m4_define([OVS_CHECK_IPROUTE_ENCAP],
- # OVS_CHECK_CT_CLEAR()
- m4_define([OVS_CHECK_CT_CLEAR],
- [AT_SKIP_IF([! grep -q "Datapath supports ct_clear action" ovs-vswitchd.log])])
-+
-+# OVS_CHECK_CT_ZERO_SNAT()
-+m4_define([OVS_CHECK_CT_ZERO_SNAT],
-+ [AT_SKIP_IF([! grep -q "Datapath supports ct_zero_snat" ovs-vswitchd.log])]))
-diff --git a/tests/system-ovn.at b/tests/system-ovn.at
-index 310bd3d5a..56cd26535 100644
---- a/tests/system-ovn.at
-+++ b/tests/system-ovn.at
-@@ -1348,7 +1348,7 @@ as ovn-nb
- OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
- as northd
--OVS_APP_EXIT_AND_WAIT([ovn-northd])
-+OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
-
- as
- OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
-@@ -3121,7 +3121,7 @@ as ovn-nb
- OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
- as northd
--OVS_APP_EXIT_AND_WAIT([ovn-northd])
-+OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
-
- as
- OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
-@@ -4577,7 +4577,7 @@ as ovn-nb
- OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
- as northd
--OVS_APP_EXIT_AND_WAIT([ovn-northd])
-+OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
-
- as
- OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
-@@ -4663,7 +4663,7 @@ as ovn-nb
- OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
- as northd
--OVS_APP_EXIT_AND_WAIT([ovn-northd])
-+OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
-
- as
- OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
-@@ -4903,7 +4903,7 @@ as ovn-nb
- OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
- as northd
--OVS_APP_EXIT_AND_WAIT([ovn-northd])
-+OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
-
- as
- OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
-@@ -5287,7 +5287,7 @@ as ovn-nb
- OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
- as northd
--OVS_APP_EXIT_AND_WAIT([ovn-northd])
-+OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
-
- as
- OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
-@@ -5296,6 +5296,196 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
- AT_CLEANUP
- ])
-
-+OVN_FOR_EACH_NORTHD([
-+AT_SETUP([ovn -- load-balancer and firewall tuple conflict IPv4])
-+AT_SKIP_IF([test $HAVE_NC = no])
-+AT_KEYWORDS([ovnlb])
-+
-+CHECK_CONNTRACK()
-+CHECK_CONNTRACK_NAT()
-+ovn_start
-+OVS_TRAFFIC_VSWITCHD_START()
-+OVS_CHECK_CT_ZERO_SNAT()
-+ADD_BR([br-int])
-+
-+# Set external-ids in br-int needed for ovn-controller
-+ovs-vsctl \
-+ -- set Open_vSwitch . external-ids:system-id=hv1 \
-+ -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
-+ -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
-+ -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
-+ -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
-+
-+# Start ovn-controller
-+start_daemon ovn-controller
-+
-+# Logical network:
-+# 1 logical switch connetected to one logical router.
-+# 2 VMs, one used as backend for a load balancer.
-+
-+check ovn-nbctl \
-+ -- lr-add rtr \
-+ -- lrp-add rtr rtr-ls 00:00:00:00:01:00 42.42.42.1/24 \
-+ -- ls-add ls \
-+ -- lsp-add ls ls-rtr \
-+ -- lsp-set-addresses ls-rtr 00:00:00:00:01:00 \
-+ -- lsp-set-type ls-rtr router \
-+ -- lsp-set-options ls-rtr router-port=rtr-ls \
-+ -- lsp-add ls vm1 -- lsp-set-addresses vm1 00:00:00:00:00:01 \
-+ -- lsp-add ls vm2 -- lsp-set-addresses vm2 00:00:00:00:00:02 \
-+ -- lb-add lb-test 66.66.66.66:666 42.42.42.2:4242 tcp \
-+ -- ls-lb-add ls lb-test
-+
-+ADD_NAMESPACES(vm1)
-+ADD_VETH(vm1, vm1, br-int, "42.42.42.2/24", "00:00:00:00:00:01", "42.42.42.1")
-+
-+ADD_NAMESPACES(vm2)
-+ADD_VETH(vm2, vm2, br-int, "42.42.42.3/24", "00:00:00:00:00:02", "42.42.42.1")
-+
-+# Wait for ovn-controller to catch up.
-+wait_for_ports_up
-+check ovn-nbctl --wait=hv sync
-+
-+# Start IPv4 TCP server on vm1.
-+NETNS_DAEMONIZE([vm1], [nc -k -l 42.42.42.2 4242], [nc-vm1.pid])
-+
-+# Make sure connecting to the VIP works.
-+NS_CHECK_EXEC([vm2], [nc 66.66.66.66 666 -p 2000 -z])
-+
-+# Start IPv4 TCP connection to VIP from vm2.
-+NS_CHECK_EXEC([vm2], [nc 66.66.66.66 666 -p 2001 -z])
-+
-+# Check conntrack. We expect two entries:
-+# - one in vm1's zone (firewall)
-+# - one in vm2's zone (dnat)
-+AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep 2001 | \
-+grep "orig=.src=42\.42\.42\.3" | \
-+sed -e 's/port=2001/port=/g' \
-+ -e 's/sport=4242,dport=[[0-9]]\+/sport=4242,dport=/g' \
-+ -e 's/state=[[0-9_A-Z]]*/state=/g' \
-+ -e 's/zone=[[0-9]]*/zone=/' | sort], [0], [dnl
-+tcp,orig=(src=42.42.42.3,dst=42.42.42.2,sport=,dport=4242),reply=(src=42.42.42.2,dst=42.42.42.3,sport=4242,dport=),zone=,protoinfo=(state=)
-+tcp,orig=(src=42.42.42.3,dst=66.66.66.66,sport=,dport=666),reply=(src=42.42.42.2,dst=42.42.42.3,sport=4242,dport=),zone=,labels=0x2,protoinfo=(state=)
-+])
-+
-+# Start IPv4 TCP connection to backend IP from vm2 which would require
-+# additional source port translation to avoid a tuple conflict.
-+NS_CHECK_EXEC([vm2], [nc 42.42.42.2 4242 -p 2001 -z])
-+
-+# Check conntrack. We expect three entries:
-+# - one in vm1's zone (firewall) - reused from the previous connection.
-+# - one in vm2's zone (dnat) - still in TIME_WAIT after the previous connection.
-+# - one in vm2's zone (firewall + additional all-zero SNAT)
-+AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep 2001 | \
-+grep "orig=.src=42\.42\.42\.3" | \
-+sed -e 's/port=2001/port=/g' \
-+ -e 's/sport=4242,dport=[[0-9]]\+/sport=4242,dport=/g' \
-+ -e 's/state=[[0-9_A-Z]]*/state=/g' \
-+ -e 's/zone=[[0-9]]*/zone=/' | sort], [0], [dnl
-+tcp,orig=(src=42.42.42.3,dst=42.42.42.2,sport=,dport=4242),reply=(src=42.42.42.2,dst=42.42.42.3,sport=4242,dport=),zone=,protoinfo=(state=)
-+tcp,orig=(src=42.42.42.3,dst=42.42.42.2,sport=,dport=4242),reply=(src=42.42.42.2,dst=42.42.42.3,sport=4242,dport=),zone=,protoinfo=(state=)
-+tcp,orig=(src=42.42.42.3,dst=66.66.66.66,sport=,dport=666),reply=(src=42.42.42.2,dst=42.42.42.3,sport=4242,dport=),zone=,labels=0x2,protoinfo=(state=)
-+])
-+
-+AT_CLEANUP
-+])
-+
-+OVN_FOR_EACH_NORTHD([
-+AT_SETUP([ovn -- load-balancer and firewall tuple conflict IPv6])
-+AT_SKIP_IF([test $HAVE_NC = no])
-+AT_KEYWORDS([ovnlb])
-+
-+CHECK_CONNTRACK()
-+CHECK_CONNTRACK_NAT()
-+ovn_start
-+OVS_TRAFFIC_VSWITCHD_START()
-+OVS_CHECK_CT_ZERO_SNAT()
-+ADD_BR([br-int])
-+
-+# Set external-ids in br-int needed for ovn-controller
-+ovs-vsctl \
-+ -- set Open_vSwitch . external-ids:system-id=hv1 \
-+ -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
-+ -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
-+ -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
-+ -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
-+
-+# Start ovn-controller
-+start_daemon ovn-controller
-+
-+# Logical network:
-+# 1 logical switch connetected to one logical router.
-+# 2 VMs, one used as backend for a load balancer.
-+
-+check ovn-nbctl \
-+ -- lr-add rtr \
-+ -- lrp-add rtr rtr-ls 00:00:00:00:01:00 4242::1/64 \
-+ -- ls-add ls \
-+ -- lsp-add ls ls-rtr \
-+ -- lsp-set-addresses ls-rtr 00:00:00:00:01:00 \
-+ -- lsp-set-type ls-rtr router \
-+ -- lsp-set-options ls-rtr router-port=rtr-ls \
-+ -- lsp-add ls vm1 -- lsp-set-addresses vm1 00:00:00:00:00:01 \
-+ -- lsp-add ls vm2 -- lsp-set-addresses vm2 00:00:00:00:00:02 \
-+ -- lb-add lb-test [[6666::1]]:666 [[4242::2]]:4242 tcp \
-+ -- ls-lb-add ls lb-test
-+
-+ADD_NAMESPACES(vm1)
-+ADD_VETH(vm1, vm1, br-int, "4242::2/64", "00:00:00:00:00:01", "4242::1")
-+OVS_WAIT_UNTIL([test "$(ip netns exec vm1 ip a | grep 4242::2 | grep tentative)" = ""])
-+
-+ADD_NAMESPACES(vm2)
-+ADD_VETH(vm2, vm2, br-int, "4242::3/64", "00:00:00:00:00:02", "4242::1")
-+OVS_WAIT_UNTIL([test "$(ip netns exec vm2 ip a | grep 4242::3 | grep tentative)" = ""])
-+
-+# Wait for ovn-controller to catch up.
-+wait_for_ports_up
-+check ovn-nbctl --wait=hv sync
-+
-+# Start IPv6 TCP server on vm1.
-+NETNS_DAEMONIZE([vm1], [nc -k -l 4242::2 4242], [nc-vm1.pid])
-+
-+# Make sure connecting to the VIP works.
-+NS_CHECK_EXEC([vm2], [nc 6666::1 666 -p 2000 -z])
-+
-+# Start IPv6 TCP connection to VIP from vm2.
-+NS_CHECK_EXEC([vm2], [nc 6666::1 666 -p 2001 -z])
-+
-+# Check conntrack. We expect two entries:
-+# - one in vm1's zone (firewall)
-+# - one in vm2's zone (dnat)
-+AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep 2001 | \
-+grep "orig=.src=4242::3" | \
-+sed -e 's/port=2001/port=/g' \
-+ -e 's/sport=4242,dport=[[0-9]]\+/sport=4242,dport=/g' \
-+ -e 's/state=[[0-9_A-Z]]*/state=/g' \
-+ -e 's/zone=[[0-9]]*/zone=/' | sort], [0], [dnl
-+tcp,orig=(src=4242::3,dst=4242::2,sport=,dport=4242),reply=(src=4242::2,dst=4242::3,sport=4242,dport=),zone=,protoinfo=(state=)
-+tcp,orig=(src=4242::3,dst=6666::1,sport=,dport=666),reply=(src=4242::2,dst=4242::3,sport=4242,dport=),zone=,labels=0x2,protoinfo=(state=)
-+])
-+
-+# Start IPv6 TCP connection to backend IP from vm2 which would require
-+# additional source port translation to avoid a tuple conflict.
-+NS_CHECK_EXEC([vm2], [nc 4242::2 4242 -p 2001 -z])
-+
-+# Check conntrack. We expect three entries:
-+# - one in vm1's zone (firewall) - reused from the previous connection.
-+# - one in vm2's zone (dnat) - still in TIME_WAIT after the previous connection.
-+# - one in vm2's zone (firewall + additional all-zero SNAT)
-+AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep 2001 | \
-+grep "orig=.src=4242::3" | \
-+sed -e 's/port=2001/port=/g' \
-+ -e 's/sport=4242,dport=[[0-9]]\+/sport=4242,dport=/g' \
-+ -e 's/state=[[0-9_A-Z]]*/state=/g' \
-+ -e 's/zone=[[0-9]]*/zone=/' | sort], [0], [dnl
-+tcp,orig=(src=4242::3,dst=4242::2,sport=,dport=4242),reply=(src=4242::2,dst=4242::3,sport=4242,dport=),zone=,protoinfo=(state=)
-+tcp,orig=(src=4242::3,dst=4242::2,sport=,dport=4242),reply=(src=4242::2,dst=4242::3,sport=4242,dport=),zone=,protoinfo=(state=)
-+tcp,orig=(src=4242::3,dst=6666::1,sport=,dport=666),reply=(src=4242::2,dst=4242::3,sport=4242,dport=),zone=,labels=0x2,protoinfo=(state=)
-+])
-+
-+AT_CLEANUP
-+])
-+
- # When a lport is released on a chassis, ovn-controller was
- # not clearing some of the flowss in the table 33 leading
- # to packet drops if ct() is hit.
-@@ -5527,7 +5717,7 @@ as ovn-nb
- OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
- as northd
--OVS_APP_EXIT_AND_WAIT([ovn-northd])
-+OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
-
- as
- OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
-@@ -5689,7 +5879,7 @@ as ovn-nb
- OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
- as northd
--OVS_APP_EXIT_AND_WAIT([ovn-northd])
-+OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
-
- as
- OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
-@@ -5738,7 +5928,7 @@ as ovn-nb
- OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
- as northd
--OVS_APP_EXIT_AND_WAIT([ovn-northd])
-+OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
-
- as
- OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
-@@ -5831,7 +6021,7 @@ as ovn-nb
- OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
- as northd
--OVS_APP_EXIT_AND_WAIT([ovn-northd])
-+OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
-
- as
- OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d
-@@ -5893,7 +6083,7 @@ as ovn-nb
- OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
- as northd
--OVS_APP_EXIT_AND_WAIT([ovn-northd])
-+OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
-
- as
- OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d
-@@ -6044,7 +6234,7 @@ as ovn-nb
- OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
- as northd
--OVS_APP_EXIT_AND_WAIT([ovn-northd])
-+OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
-
- as
- OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d
-@@ -6091,7 +6281,6 @@ check ovn-nbctl pg-add pg1 sw1-p1
- check ovn-nbctl acl-add pg1 from-lport 1002 "ip" allow-related
- check ovn-nbctl acl-add pg1 to-lport 1002 "ip" allow-related
-
--
- OVN_POPULATE_ARP
- ovn-nbctl --wait=hv sync
-
-@@ -6179,5 +6368,117 @@ OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
- as
- OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
- /connection dropped.*/d"])
-+
-+AT_CLEANUP
-+])
-+
-+OVN_FOR_EACH_NORTHD([
-+AT_SETUP(ovn -- DNAT LR hairpin IPv4)
-+AT_KEYWORDS(hairpin)
-+
-+ovn_start
-+
-+OVS_TRAFFIC_VSWITCHD_START()
-+ADD_BR([br-int])
-+
-+# Set external-ids in br-int needed for ovn-controller
-+ovs-vsctl \
-+ -- set Open_vSwitch . external-ids:system-id=hv1 \
-+ -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
-+ -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
-+ -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
-+ -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
-+
-+start_daemon ovn-controller
-+
-+# Logical network:
-+# Two VMs
-+# * VM1 with IP address 192.168.100.5
-+# * VM2 with IP address 192.168.100.6
-+# The VMs connect to logical switch ls1.
-+#
-+# An external router with IP address 172.18.1.2. We simulate this with a network namespace.
-+# There will be no traffic going here in this test.
-+# The external router connects to logical switch ls-pub
-+#
-+# One logical router (lr1) connects to ls1 and ls-pub. The router port connected to ls-pub is
-+# a gateway port.
-+# * The subnet connected to ls1 is 192.168.100.0/24. The Router IP address is 192.168.100.1
-+# * The subnet connected to ls-pub is 172.18.1.0/24. The Router IP address is 172.168.1.1
-+# lr1 has the following attributes:
-+# * It has a "default" static route that sends traffic out the gateway router port.
-+# * It has a DNAT rule that translates 172.18.2.10 to 192.168.100.6 (VM2)
-+#
-+# In this test, we want to ensure that a ping from VM1 to IP address 172.18.2.10 reaches VM2.
-+
-+ovn-nbctl ls-add ls1
-+ovn-nbctl lsp-add ls1 vm1 -- lsp-set-addresses vm1 "00:00:00:00:00:05 192.168.100.5"
-+ovn-nbctl lsp-add ls1 vm2 -- lsp-set-addresses vm2 "00:00:00:00:00:06 192.168.100.6"
-+
-+ovn-nbctl ls-add ls-pub
-+ovn-nbctl lsp-add ls-pub ext-router -- lsp-set-addresses ext-router "00:00:00:00:01:02 172.18.1.2"
-+
-+ovn-nbctl lr-add lr1
-+ovn-nbctl lrp-add lr1 lr1-ls1 00:00:00:00:00:01 192.168.100.1/24
-+ovn-nbctl lsp-add ls1 ls1-lr1 \
-+ -- lsp-set-type ls1-lr1 router \
-+ -- lsp-set-addresses ls1-lr1 00:00:00:00:00:01 \
-+ -- lsp-set-options ls1-lr1 router-port=lr1-ls1
-+
-+ovn-nbctl lrp-add lr1 lr1-ls-pub 00:00:00:00:01:01 172.18.1.1/24
-+ovn-nbctl lrp-set-gateway-chassis lr1-ls-pub hv1
-+ovn-nbctl lsp-add ls-pub ls-pub-lr1 \
-+ -- lsp-set-type ls-pub-lr1 router \
-+ -- lsp-set-addresses ls-pub-lr1 00:00:00:00:01:01 \
-+ -- lsp-set-options ls-pub-lr1 router-port=lr1-ls-pub
-+
-+ovn-nbctl lr-nat-add lr1 snat 172.18.1.1 192.168.100.0/24
-+ovn-nbctl lr-nat-add lr1 dnat_and_snat 172.18.2.10 192.168.100.6
-+ovn-nbctl lr-route-add lr1 0.0.0.0/0 172.18.1.2
-+
-+#ls1_uuid=$(fetch_column Port_Binding datapath logical_port=vm1)
-+#ovn-sbctl create MAC_Binding ip=172.18.2.10 datapath=$ls1_uuid logical_port=vm2 mac="00:00:00:00:00:06"
-+
-+OVN_POPULATE_ARP
-+ovn-nbctl --wait=hv sync
-+
-+ADD_NAMESPACES(vm1)
-+ADD_VETH(vm1, vm1, br-int, "192.168.100.5/24", "00:00:00:00:00:05", \
-+ "192.168.100.1")
-+
-+ADD_NAMESPACES(vm2)
-+ADD_VETH(vm2, vm2, br-int, "192.168.100.6/24", "00:00:00:00:00:06", \
-+ "192.168.100.1")
-+
-+ADD_NAMESPACES(ext-router)
-+ADD_VETH(ext-router, ext-router, br-int, "172.18.1.2/24", "00:00:00:00:01:02", \
-+ "172.18.1.1")
-+
-+# Let's take a quick look at the logical flows
-+ovn-sbctl lflow-list
-+
-+# Let's check what ovn-trace says...
-+ovn-trace ls1 'inport == "vm1" && eth.src == 00:00:00:00:00:05 && ip4.src == 192.168.100.5 && eth.dst == 00:00:00:00:00:01 && ip4.dst == 172.18.2.10 && ip.ttl == 32'
-+
-+# A ping from vm1 should hairpin in lr1 and successfully DNAT to vm2
-+NS_CHECK_EXEC([vm1], [ping -q -c 3 -i 0.3 -w 2 172.18.2.10 | FORMAT_PING], \
-+[0], [dnl
-+3 packets transmitted, 3 received, 0% packet loss, time 0ms
-+])
-+kill $(pidof 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([NORTHD_TYPE])
-+
-+as
-+OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d
-+/.*terminating with signal 15.*/d"])
-+
- AT_CLEANUP
- ])
-diff --git a/tests/testsuite.at b/tests/testsuite.at
-index ddc3f11d6..b716a1ad9 100644
---- a/tests/testsuite.at
-+++ b/tests/testsuite.at
-@@ -27,6 +27,7 @@ m4_include([tests/ovn.at])
- m4_include([tests/ovn-performance.at])
- m4_include([tests/ovn-northd.at])
- m4_include([tests/ovn-nbctl.at])
-+m4_include([tests/ovn-features.at])
- m4_include([tests/ovn-lflow-cache.at])
- m4_include([tests/ovn-ofctrl-seqno.at])
- m4_include([tests/ovn-sbctl.at])
-diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
-index dc13fa9ca..3a0b7c3e3 100644
---- a/utilities/ovn-nbctl.c
-+++ b/utilities/ovn-nbctl.c
-@@ -805,6 +805,8 @@ static void
- nbctl_pre_sync(struct ctl_context *base OVS_UNUSED)
- {
- force_wait = true;
-+ /* Monitor nb_cfg to detect and handle potential overflows. */
-+ ovsdb_idl_add_column(base->idl, &nbrec_nb_global_col_nb_cfg);
- }
-
- static void
-@@ -3976,6 +3978,8 @@ nbctl_pre_lr_route_add(struct ctl_context *ctx)
- ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_col_name);
- ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_col_static_routes);
-
-+ ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_port_col_name);
-+
- ovsdb_idl_add_column(ctx->idl, &nbrec_bfd_col_dst_ip);
-
- ovsdb_idl_add_column(ctx->idl,
-@@ -3992,6 +3996,10 @@ nbctl_pre_lr_route_add(struct ctl_context *ctx)
- &nbrec_logical_router_static_route_col_options);
- }
-
-+static char * OVS_WARN_UNUSED_RESULT
-+lrp_by_name_or_uuid(struct ctl_context *ctx, const char *id, bool must_exist,
-+ const struct nbrec_logical_router_port **lrp_p);
-+
- static void
- nbctl_lr_route_add(struct ctl_context *ctx)
- {
-@@ -4001,6 +4009,7 @@ nbctl_lr_route_add(struct ctl_context *ctx)
- ctx->error = error;
- return;
- }
-+ const struct nbrec_logical_router_port *out_lrp = NULL;
- char *prefix = NULL, *next_hop = NULL;
-
- const char *policy = shash_find_data(&ctx->options, "--policy");
-@@ -4034,9 +4043,15 @@ nbctl_lr_route_add(struct ctl_context *ctx)
- ? normalize_ipv6_addr_str(ctx->argv[3])
- : normalize_ipv4_addr_str(ctx->argv[3]);
- if (!next_hop) {
-- ctl_error(ctx, "bad %s nexthop argument: %s",
-- v6_prefix ? "IPv6" : "IPv4", ctx->argv[3]);
-- goto cleanup;
-+ /* check if it is a output port. */
-+ error = lrp_by_name_or_uuid(ctx, ctx->argv[3], true, &out_lrp);
-+ if (error) {
-+ ctl_error(ctx, "bad %s nexthop argument: %s",
-+ v6_prefix ? "IPv6" : "IPv4", ctx->argv[3]);
-+ free(error);
-+ goto cleanup;
-+ }
-+ next_hop = "";
- }
- }
-
-@@ -4063,6 +4078,15 @@ nbctl_lr_route_add(struct ctl_context *ctx)
- }
- }
-
-+ if (ctx->argc == 5) {
-+ /* validate output port. */
-+ error = lrp_by_name_or_uuid(ctx, ctx->argv[4], true, &out_lrp);
-+ if (error) {
-+ ctx->error = error;
-+ goto cleanup;
-+ }
-+ }
-+
- bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
- bool ecmp_symmetric_reply = shash_find(&ctx->options,
- "--ecmp-symmetric-reply") != NULL;
-@@ -4081,7 +4105,7 @@ nbctl_lr_route_add(struct ctl_context *ctx)
- ctl_error(ctx, "bfd dst_ip cannot be discard.");
- goto cleanup;
- }
-- if (ctx->argc == 5) {
-+ if (out_lrp) {
- if (is_discard_route) {
- ctl_error(ctx, "outport is not valid for discard routes.");
- goto cleanup;
-@@ -4104,22 +4128,22 @@ nbctl_lr_route_add(struct ctl_context *ctx)
- nbrec_logical_router_static_route_verify_nexthop(route);
- nbrec_logical_router_static_route_set_ip_prefix(route, prefix);
- nbrec_logical_router_static_route_set_nexthop(route, next_hop);
-- if (ctx->argc == 5) {
-+ if (out_lrp) {
- nbrec_logical_router_static_route_set_output_port(
-- route, ctx->argv[4]);
-+ route, out_lrp->name);
- }
- if (policy) {
- nbrec_logical_router_static_route_set_policy(route, policy);
- }
- if (bfd) {
- if (!nb_bt) {
-- if (ctx->argc != 5) {
-+ if (!out_lrp) {
- ctl_error(ctx, "insert entry in the BFD table failed");
- goto cleanup;
- }
- nb_bt = nbrec_bfd_insert(ctx->txn);
- nbrec_bfd_set_dst_ip(nb_bt, next_hop);
-- nbrec_bfd_set_logical_port(nb_bt, ctx->argv[4]);
-+ nbrec_bfd_set_logical_port(nb_bt, out_lrp->name);
- }
- nbrec_logical_router_static_route_set_bfd(route, nb_bt);
- }
-@@ -4142,8 +4166,9 @@ nbctl_lr_route_add(struct ctl_context *ctx)
- route = nbrec_logical_router_static_route_insert(ctx->txn);
- nbrec_logical_router_static_route_set_ip_prefix(route, prefix);
- nbrec_logical_router_static_route_set_nexthop(route, next_hop);
-- if (ctx->argc == 5) {
-- nbrec_logical_router_static_route_set_output_port(route, ctx->argv[4]);
-+ if (out_lrp) {
-+ nbrec_logical_router_static_route_set_output_port(route,
-+ out_lrp->name);
- }
- if (policy) {
- nbrec_logical_router_static_route_set_policy(route, policy);
-@@ -4159,19 +4184,21 @@ nbctl_lr_route_add(struct ctl_context *ctx)
- nbrec_logical_router_update_static_routes_addvalue(lr, route);
- if (bfd) {
- if (!nb_bt) {
-- if (ctx->argc != 5) {
-+ if (!out_lrp) {
- ctl_error(ctx, "insert entry in the BFD table failed");
- goto cleanup;
- }
- nb_bt = nbrec_bfd_insert(ctx->txn);
- nbrec_bfd_set_dst_ip(nb_bt, next_hop);
-- nbrec_bfd_set_logical_port(nb_bt, ctx->argv[4]);
-+ nbrec_bfd_set_logical_port(nb_bt, out_lrp->name);
- }
- nbrec_logical_router_static_route_set_bfd(route, nb_bt);
- }
-
- cleanup:
-- free(next_hop);
-+ if (next_hop && strlen(next_hop)) {
-+ free(next_hop);
-+ }
- free(prefix);
- }
-
-@@ -5847,12 +5874,18 @@ print_route(const struct nbrec_logical_router_static_route *route,
- {
-
- char *prefix = normalize_prefix_str(route->ip_prefix);
-- char *next_hop = !strcmp(route->nexthop, "discard")
-- ? xasprintf("discard")
-- : normalize_prefix_str(route->nexthop);
-+ char *next_hop = "";
-+
-+ if (!strcmp(route->nexthop, "discard")) {
-+ next_hop = xasprintf("discard");
-+ } else if (strlen(route->nexthop)) {
-+ next_hop = normalize_prefix_str(route->nexthop);
-+ }
- ds_put_format(s, "%25s %25s", prefix, next_hop);
- free(prefix);
-- free(next_hop);
-+ if (strlen(next_hop)) {
-+ free(next_hop);
-+ }
-
- if (route->policy) {
- ds_put_format(s, " %s", route->policy);
diff --git a/SPECS/ovn-2021.spec b/SPECS/ovn-2021.spec
index 951f225..f2880f1 100644
--- a/SPECS/ovn-2021.spec
+++ b/SPECS/ovn-2021.spec
@@ -50,8 +50,8 @@ Name: %{pkgname}
Summary: Open Virtual Network support
Group: System Environment/Daemons
URL: http://www.ovn.org/
-Version: 21.06.0
-Release: 29%{?commit0:.%{date}git%{shortcommit0}}%{?dist}
+Version: 21.12.0
+Release: 11%{?commit0:.%{date}git%{shortcommit0}}%{?dist}
Provides: openvswitch%{pkgver}-ovn-common = %{?epoch:%{epoch}:}%{version}-%{release}
Obsoletes: openvswitch%{pkgver}-ovn-common < 2.11.0-1
@@ -62,8 +62,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/v%{version}.tar.gz#/ovn-%{version}.tar.gz
-%define ovscommit e6ad4d8d9c9273f226ec9a993b64fccfb50bdf4c
-%define ovsshortcommit e6ad4d8
+%define ovscommit 91e1ff5dde396fbcc8623ac0726066e970e6de15
+%define ovsshortcommit 91e1ff5
Source10: https://github.com/openvswitch/ovs/archive/%{ovscommit}.tar.gz#/openvswitch-%{ovsshortcommit}.tar.gz
%define ovsdir ovs-%{ovscommit}
@@ -85,7 +85,7 @@ Source504: arm64-armv8a-linuxapp-gcc-config
Source505: ppc_64-power8-linuxapp-gcc-config
Source506: x86_64-native-linuxapp-gcc-config
-Patch: ovn-%{version}.patch
+Patch: %{pkgname}.patch
# FIXME Sphinx is used to generate some manpages, unfortunately, on RHEL, it's
# in the -optional repository and so we can't require it directly since RHV
@@ -116,7 +116,7 @@ BuildRequires: unbound-devel
# make check dependencies
BuildRequires: procps-ng
-%if 0%{?rhel} > 7 || 0%{?fedora}
+%if 0%{?rhel} == 8 || 0%{?fedora}
BuildRequires: python3-pyOpenSSL
%endif
BuildRequires: tcpdump
@@ -477,6 +477,7 @@ fi
%{_bindir}/ovn-sbctl
%{_bindir}/ovn-trace
%{_bindir}/ovn-detrace
+%{_bindir}/ovn_detrace.py
%{_bindir}/ovn-appctl
%{_bindir}/ovn-ic-nbctl
%{_bindir}/ovn-ic-sbctl
@@ -527,133 +528,43 @@ fi
%{_unitdir}/ovn-controller-vtep.service
%changelog
-* Tue Sep 14 2021 Ilya Maximets - 21.06.0-29
-- ovn-northd: Avoid verification of NB_Global.options if nothing changed.
- [Gerrit: 50dc6ec9b9d1312caf2d6f9d19cf19d6bc73e3f3]
- [Upstream: 27064c554fe6a3dfe4dd5800e884eec70cd3d793]
-
-* Tue Sep 14 2021 Ilya Maximets - 21.06.0-28
-- ovn-northd: Fix update of a mac prefix.
- [Gerrit: ea2eb903d7220c01931c7e05f53dad8a84f0a715]
- [Upstream: b6bf5e99b5a76c9a9df83896c2d1e40e3c5f4471]
-
-* Thu Sep 02 2021 Mohammad Heib - 21.06.0-27
-- ovn-nbctl: Monitor nb_cfg to detect and handle potential overflows (#1979774)
- [Gerrit: 5a7342c8f1340e5739a8bceb2d3a81a07b371d1d]
- [Upstream: b1a07090740ac9a29e4a2475ad07bf9c37991b43]
-
-* Mon Aug 30 2021 Numan Siddique - 21.06.0-26
-- Revert "Revert features detection and zero-snat patches."
- [Gerrit: 666d6640be7155ca0b0bc12b0d5af626090e0375]
+* Thu Jan 06 2022 Dumitru Ceara - 21.12.0-11
+- pinctrl: Avoid misaligned access to ovs_ra_msg.
+ [Gerrit: 290523cdfadc5cb401939cc21c1f8de66a6b79b2]
+ [Upstream: N/A]
+
+* Thu Jan 06 2022 Dumitru Ceara - 21.12.0-10
+- pinctrl: Avoid misaligned access to controller_event_opt_header.
+ [Gerrit: 2280d3a3f63e026657564f34793b9143323afaf6]
+ [Upstream: N/A]
+
+* Thu Jan 06 2022 Dumitru Ceara - 21.12.0-9
+- pinctrl: Ensure packet headers are properly aligned for ICMP errors.
+ [Gerrit: ca764d60c6cfd4e7121b15faf6ca3a026928da4e]
+ [Upstream: N/A]
+
+* Thu Jan 06 2022 Dumitru Ceara - 21.12.0-8
+- pinctrl: Ensure aligned accesses when processing DNS.
+ [Gerrit: 67829e142dcc71b1f8e4f01aab9e73333420a76f]
[Upstream: N/A]
-* Mon Aug 30 2021 Mark Michelson - 21.06.0-25
-- Revert features detection and zero-snat patches. (#1992705)
- [Gerrit: 8f5c081c322a25576aea1cc7d2f5d996f5804196]
+* Thu Jan 06 2022 Dumitru Ceara - 21.12.0-7
+- pinctrl: Ensure no misaligned accesses for SCTP packets.
+ [Gerrit: b5083fdb4dce839fe70ce5e1c059f11c917105f4]
[Upstream: N/A]
-* Wed Aug 25 2021 Lorenzo Bianconi - 21.06.0-24
-- northd: allow to configure routes with no nexthop
- [Gerrit: c86436ca8edbb7c4cdf3b965269792c51592c385]
- [Upstream: c0085228893e7bf07190fcccf50cf588b028edaa]
-
-* Wed Aug 25 2021 Lorenzo Bianconi - 21.06.0-23
-- nbctl: validate outport in nbctl_lr_route_add
- [Gerrit: ddccb1dbaf54c78c810d4d32da6d96197cc16caf]
- [Upstream: d866959ba6ea066f88558071eda9943258ee7fa7]
-
-* Wed Aug 18 2021 Lorenzo Bianconi - 21.06.0-22
-- controller: run ipv6_pd only on a proper controller for l3gateway mode
- [Gerrit: 11d6a1bac2e38a81646271c127b602533a3f9dc4]
- [Upstream: b8b24398657e5bb82dcbdb5ce55a74e261e30767]
-
-* Wed Aug 18 2021 Lorenzo Bianconi - 21.06.0-21
-- controller: ipv6_pd: properly update ipv6_ra_pd_list pb option in sb db
- [Gerrit: 55ec986ed1a506e3c149c5a7d69fdd661970d777]
- [Upstream: 9704cfe54dc2a0a6752139d07bb9f563e7e27aae]
-
-* Wed Aug 18 2021 Lorenzo Bianconi - 21.06.0-20
-- controller: add ipv6_pd debug messages
- [Gerrit: 969495b1f078b8e52fb2c1b1b53bb561c8f66d22]
- [Upstream: c3afcb44846116fd6e311ca9a250a46dbff6b2a8]
-
-* Fri Aug 06 2021 Lorenzo Bianconi - 21.06.0-19
-- northd: do not configure ECMP routes with wrong next-hop
- [Gerrit: c8b215179d2408ce1d1e0dd6569f72a3c7e5724d]
- [Upstream: 9cd64780d89c4acd2ae0c12693be66962ada97cd]
-
-* Tue Jul 27 2021 Numan Siddique - 21.06.0-18
-- ovn-controller: Split logical flow and physical flow processing. (#1986484)
- [Gerrit: 6e1e90064ad1f5769fdc96e3b735ee236c30b7e2]
- [Upstream: ceb12c9190a124c70bc938e8e1bea17612b498be]
-
-* Tue Jul 27 2021 Dumitru Ceara - 21.06.0-17
-- ovn.at: Fix "Symmetric IPv6 ECMP reply flows" test.
- [Gerrit: 801f6c69c3bb45f981135ac6c197fdbd3f18118d]
- [Upstream: 4e6c498068dc4fa9546d3661f78f0a42e99c74bb]
-
-* Tue Jul 27 2021 Dumitru Ceara - 21.06.0-16
-- ovn-controller: Handle DNAT/no-NAT conntrack tuple collisions. (#1939676)
- [Gerrit: abfd62cb228b7d311ae7cae18adfe9cfcf68affc]
- [Upstream: 58683a4271e6a885f2f2aea27f3df88e69a5c388]
-
-* Tue Jul 27 2021 Dumitru Ceara - 21.06.0-15
-- ovn-controller: Detect OVS datapath capabilities.
- [Gerrit: ca1df0396e6e6eb016c3cad82db7c49cc05ec99a]
- [Upstream: 56e2cd3a2f06b79b7d57cc8637fc0d258652aff5]
-
-* Mon Jul 26 2021 Lorenzo Bianconi - 21.06.0-14
-- northd: do not centralize traffic for unclaimed virtual ports
- [Gerrit: 5b6826906a76779b527d72d1c49d211ce492e62e]
+* Thu Jan 06 2022 Dumitru Ceara - 21.12.0-6
+- pinctrl: Ensure proper alignment when using pinctrl_compose_ipv*().
+ [Gerrit: 31e933469d862915b8c83131f72728fe32ecac51]
[Upstream: N/A]
-* Thu Jul 15 2021 Ihar Hrachyshka - 21.06.0-13
-- Don't suppress localport traffic directed to external port (#1974062)
- [Gerrit: 330e6e7400e1d5e4e6ef4fc6446eeaa945ac6a13]
- [Upstream: 1148580290d0ace803f20aeaa0241dd51c100630]
-
-* Thu Jul 15 2021 Dumitru Ceara - 21.06.0-12
-- northd: Fix multicast table full comparison. (#1979870)
- [Gerrit: 38f44df1b8a0ed1ebb86183de29d9e5c3423abdb]
- [Upstream: 969c98d7297b526c704c6fd2a7138f584f9ad577]
-
-* Thu Jul 15 2021 Dumitru Ceara - 21.06.0-11
-- northd-ddlog: Fix IP family match for DNAT flows.
- [Gerrit: 518ea2e15df2c77fc19afe74b68d616983638743]
- [Upstream: 38467229905bdf09a3afa325eaa7a98183f44c72]
-
-* Thu Jul 15 2021 Ihar Hrachyshka - 21.06.0-10
-- Disable ARP/NA responders for vlan-passthru switches
- [Gerrit: 56fbcfaf71d9a6df0b4cdee583c8d17ca7a82aab]
- [Upstream: ea57f666f6eef1eb1d578f0e975baa14c5d23ec9]
-
-* Thu Jul 15 2021 Ben Pfaff - 21.06.0-9
-- tests: Fix "vlan traffic for external network with distributed..."
- [Gerrit: ca26e77c4206a39ae6eab4a1d430ef04b726b640]
- [Upstream: 5453cc8ca5535e3f33d1b191929e1a3c9ad30f20]
-
-* Thu Jul 15 2021 Dumitru Ceara - 21.06.0-8
-- ovn-controller: Fix port group I-P when they contain non-vif ports.
- [Gerrit: 3c7f29238c889b248155cbb2c866c0adbf8b46c1]
- [Upstream: 1bb32e0f8146d7f4fff84af5e3d2836ebe939e04]
-
-* Thu Jul 15 2021 Numan Siddique - 21.06.0-7
-- system-tests: Fix the test file.
- [Gerrit: 85337cec3f2e5967a14afc5a552ac17dff6c15f6]
- [Upstream: 9c1978300fa12709e01df07ed8403d8ad43f61fb]
-
-* Thu Jul 15 2021 Mark Michelson - 21.06.0-6
-- northd: Swap src and dst eth addresses in router egress loop.
- [Gerrit: 86207fcac41b639d14de05e1b0965ad9d8293218]
- [Upstream: 9be470dc69daf16ac1fbbe13cc295f46862226ad]
-
-* Tue Jun 29 2021 Han Zhou - 21.06.0-5
-- ovn.at: Fix test "virtual ports -- ovn-northd-ddlog".
- [Gerrit: d61cfca4cadca33e598ba1a23cfdbe81a72d3501]
- [Upstream: 9e3404e03620f183adc4f05db13bf5a38618b757]
-
-* Fri Jun 18 2021 Mark Michelson - 21.06.0-4
-- Prepare for 21.06.1.
- [Gerrit: 9ae4001f70b4a828018a55a36a1b228f4846b624]
+* Thu Jan 06 2022 Dumitru Ceara - 21.12.0-5
+- physical: Add remote parent ports to OFTABLE_REMOTE_OUTPUT flows. (#2036970)
+ [Gerrit: 627b25bd14085a78f1f9611f2a218ce639515e26]
+ [Upstream: e101e45f355a91e277630243e64897f91f13f8bc]
+
+* Wed Dec 22 2021 Mark Michelson - 21.12.0-4
+- Prepare for 21.12.1.
+ [Gerrit: 9c67f93b92d9864ac0e06dd3d96e8172441343c3]
[Upstream: N/A]