From 9993d97443b074dd9e2e9416382fe96e05a581f7 Mon Sep 17 00:00:00 2001
From: yatinkarel
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.
+
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
.
++ 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).
++ reg0[0] == 1
) by using the
++ ct_next;
action.
++ from-lport
ACL hints+ The table contains the following flows: +
++no
ACLs configured, otherwise a
++ priority-0 flow to advance to the next table.
++ reg0[10]
and then advances to the next
+ table.
+ from-lport
ACLs
+- 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 @@ + + +ct_label.blocked
set.
+@@ -628,19 +666,19 @@
+ ct_label.blocked
set.
+ 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 @@
+ ++ If the logical datapath has any ACL or a load balancer with VIP ++ configured, the following flow will also be added: ++
++ ++eth.dst = E
to allow the service
+@@ -709,33 +754,7 @@
+ +- 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.)
+-
ct_commit; next;
action based on a hint provided by
+ the previous tables (with a match for reg0[1] == 1
).
+ 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
.
+- + This table implements ARP/ND responder in a logical switch for known +@@ -1164,7 +1172,7 @@ output; + + + +-
+ This table adds the DHCPv4 options to a DHCPv4 packet from the +@@ -1225,7 +1233,7 @@ next; + + + +-
+ This table implements DHCP responder for the DHCP replies generated by +@@ -1306,7 +1314,7 @@ output; + + + +-
+ This table looks up and resolves the DNS names to the corresponding +@@ -1335,7 +1343,7 @@ reg0[4] = dns_lookup(); next; + + + +-
+ This table implements DNS responder for the DNS replies generated by +@@ -1370,7 +1378,7 @@ output; + + + +-
+ Traffic from the external
logical ports enter the ingress
+@@ -1413,7 +1421,7 @@ output;
+
+
+
+-
+ 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; +
+- This is similar to ingress table Pre-stateful
.
++ This is similar to ingress table Pre-stateful
. This table
++ adds the below 3 logical flows.
+
+- This is similar to ingress table LB
.
+-
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
.
++ from-lport
ACL hintsreg0[0] == 1
) by using the
++ ct_next;
action.
++ from-lport
ACL hints
+ This is similar to ingress table ACL hints
.
+
to-lport
ACLsto-lport
ACLs
+ This is similar to ingress table ACLs
except for
+@@ -1733,28 +1762,28 @@ output;
+
+
- /* 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;
- }
+-
to-lport
QoS Markingto-lport
QoS Marking
+ This is similar to ingress table QoS marking
except
+ they apply to to-lport
QoS rules.
+
to-lport
QoS Meterto-lport
QoS Meter
+ This is similar to ingress table QoS meter
except
+ they apply to to-lport
QoS rules.
+
+ This is similar to ingress table Stateful
except that
+ there are no rules added for load balancing new connections.
+
+ This is similar to the port security logic in table
+@@ -1764,7 +1793,7 @@ output;
+ ip4.src
and ip6.src
+
+ 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;
@@ -2153,7 +3514,7 @@ index c272cc922..3300f7180 100644
args will only contain those endpoints whose service
monitor status entry in
OVN_Southbound
db is
either online
or empty.
-@@ -2737,6 +2741,9 @@ icmp6 {
+@@ -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;
.
@@ -2163,7 +3524,7 @@ index c272cc922..3300f7180 100644
flags.force_snat_for_lb = 1;
ct_lb(args);
.
@@ -2173,7 +3534,7 @@ index c272cc922..3300f7180 100644
flags.force_snat_for_lb = 1; ct_dnat;
.
@@ -2183,7 +3544,7 @@ index c272cc922..3300f7180 100644
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..4e406c594 100644 +index 5a2018c2e..a478d3324 100644 --- a/northd/ovn-northd.c +++ b/northd/ovn-northd.c -@@ -8573,10 +8573,16 @@ get_force_snat_ip(struct ovn_datapath *od, const char *key_type, +@@ -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; } @@ -2221,7 +4050,7 @@ index 5a2018c2e..4e406c594 100644 const char *proto, struct nbrec_load_balancer *lb, struct shash *meter_groups, struct sset *nat_entries) { -@@ -8585,9 +8591,10 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od, +@@ -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)); @@ -2235,7 +4064,7 @@ index 5a2018c2e..4e406c594 100644 ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, priority, new_match, new_actions, &lb->header_); free(new_actions); -@@ -8598,11 +8605,12 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od, +@@ -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)); @@ -2252,7 +4081,7 @@ index 5a2018c2e..4e406c594 100644 } else { ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, priority, est_match, "ct_dnat;", &lb->header_); -@@ -8675,11 +8683,13 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od, +@@ -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); @@ -2269,7 +4098,7 @@ index 5a2018c2e..4e406c594 100644 } else { ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 120, ds_cstr(&undnat_match), "ct_dnat;", -@@ -8689,6 +8699,105 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od, +@@ -8689,6 +8691,105 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od, ds_destroy(&undnat_match); } @@ -2375,7 +4204,52 @@ index 5a2018c2e..4e406c594 100644 #define ND_RA_MAX_INTERVAL_MAX 1800 #define ND_RA_MAX_INTERVAL_MIN 4 -@@ -11002,668 +11111,643 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op, +@@ -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, } } @@ -3615,11 +5489,43 @@ index 5a2018c2e..4e406c594 100644 } +@@ -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..408c98090 100644 +index b0a4adffe..046d053e9 100644 --- a/ovn-nb.xml +++ b/ovn-nb.xml -@@ -1653,6 +1653,12 @@ +@@ -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.
@@ -3715,16 +5621,569 @@ index 2cd3e261f..5c64fff12 100644
+primary lport : [[lsp1]]
+----------------------------------------
+])
-+ done
-+done
++ 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_CLEANUP([hv1])
-+AT_CLEANUP
-diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
-index b78baa708..6d5dce668 100644
---- a/tests/ovn-northd.at
-+++ b/tests/ovn-northd.at
-@@ -2551,7 +2551,7 @@ wait_row_count nb:Logical_Switch_Port 1 up=false name=lsp1
+ 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
@@ -3733,7 +6192,7 @@ index b78baa708..6d5dce668 100644
ovn_start
check ovn-nbctl ls-add sw0
-@@ -2589,11 +2589,11 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
+@@ -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;)
])
@@ -3750,7 +6209,7 @@ index b78baa708..6d5dce668 100644
])
check ovn-nbctl --wait=sb set logical_router lr0 options:lb_force_snat_ip="20.0.0.4 aef0::4"
-@@ -2608,14 +2608,18 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
+@@ -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;)
])
@@ -3771,7 +6230,7 @@ index b78baa708..6d5dce668 100644
])
check ovn-nbctl --wait=sb set logical_router lr0 options:lb_force_snat_ip="router_ip"
-@@ -2633,15 +2637,19 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
+@@ -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;)
])
@@ -3793,7 +6252,7 @@ index b78baa708..6d5dce668 100644
])
check ovn-nbctl --wait=sb remove logical_router lr0 options chassis
-@@ -2653,7 +2661,9 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
+@@ -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;)
])
@@ -3804,7 +6263,7 @@ index b78baa708..6d5dce668 100644
])
check ovn-nbctl set logical_router lr0 options:chassis=ch1
-@@ -2670,16 +2680,43 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
+@@ -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;)
])
@@ -3850,21 +6309,334 @@ index b78baa708..6d5dce668 100644
])
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..dbc6e549b 100644
+index b465784cd..0377b75c3 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
-@@ -11494,6 +11494,59 @@ OVN_CLEANUP([hv1],[hv2])
+@@ -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
@@ -3875,6 +6647,7 @@ index b465784cd..dbc6e549b 100644
+ -- 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"
@@ -3908,13 +6681,162 @@ index b465784cd..dbc6e549b 100644
+ 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
-@@ -16647,56 +16700,67 @@ ovs-vsctl -- add-port br-int hv2-vif2 -- \
+@@ -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
@@ -4021,21 +6943,21 @@ index b465784cd..dbc6e549b 100644
wait_for_ports_up
ovn-nbctl --wait=hv sync
-@@ -16746,6 +16810,30 @@ ovs-vsctl del-port hv1-vif3
+@@ -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=45 | ofctl_strip_all | grep "priority=2000"], [0], [dnl
-+ table=45, priority=2000,ip,metadata=0x1 actions=resubmit(,46)
-+ table=45, priority=2000,ipv6,metadata=0x1 actions=resubmit(,46)
++ 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[[]],move:NXM_OF_ARP_SPA[[]]->NXM_OF_ARP_TPA[[]],load:0xaca80032->NXM_OF_ARP_SPA[[]],move:NXM_NX_REG14[[]]->NXM_NX_REG15[[]],load:0x1->NXM_NX_REG10[[0]],resubmit(,37)
++ 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)
+])
+}
+
@@ -4052,7 +6974,7 @@ index b465784cd..dbc6e549b 100644
# 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 +16855,13 @@ AT_CHECK([grep lr_in_arp_resolve lr0-flows2 | grep "reg0 == 10.0.0.10" | sed 's/
+@@ -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;)
])
@@ -4066,7 +6988,7 @@ index b465784cd..dbc6e549b 100644
# 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 +16872,13 @@ logical_port=sw0-vir) = x])
+@@ -16777,6 +16921,13 @@ logical_port=sw0-vir) = x])
wait_row_count nb:Logical_Switch_Port 1 up=false name=sw0-vir
@@ -4080,7 +7002,7 @@ index b465784cd..dbc6e549b 100644
# 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 +16891,58 @@ logical_port=sw0-vir) = xsw0-p1])
+@@ -16789,6 +16940,58 @@ logical_port=sw0-vir) = xsw0-p1])
wait_for_ports_up sw0-vir
@@ -4139,7 +7061,7 @@ index b465784cd..dbc6e549b 100644
# 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 +16960,8 @@ logical_port=sw0-vir) = xsw0-p3])
+@@ -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
@@ -4150,7 +7072,7 @@ index b465784cd..dbc6e549b 100644
ovn-sbctl dump-flows lr0 > lr0-flows3
AT_CAPTURE_FILE([lr0-flows3])
cp ovn-sb/ovn-sb.db lr0-flows3.db
-@@ -16815,6 +16969,13 @@ AT_CHECK([grep lr_in_arp_resolve lr0-flows3 | grep "reg0 == 10.0.0.10" | sed 's
+@@ -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;)
])
@@ -4164,7 +7086,7 @@ index b465784cd..dbc6e549b 100644
# 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 +16993,21 @@ logical_port=sw0-vir) = xsw0-p2])
+@@ -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
@@ -4188,7 +7110,7 @@ index b465784cd..dbc6e549b 100644
# 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 +17031,14 @@ AT_CHECK([grep lr_in_arp_resolve lr0-flows5 | grep "reg0 == 10.0.0.10" | sed 's/
+@@ -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;)
])
@@ -4203,7 +7125,7 @@ index b465784cd..dbc6e549b 100644
# Delete hv1-vif1 port. hv1 should release sw0-vir
as hv1 ovs-vsctl del-port hv1-vif1
-@@ -16883,6 +17059,15 @@ AT_CHECK([grep lr_in_arp_resolve lr0-flows6 | grep "reg0 == 10.0.0.10" | sed 's/
+@@ -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;)
])
@@ -4219,7 +7141,7 @@ index b465784cd..dbc6e549b 100644
# 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 +17091,14 @@ AT_CHECK([grep lr_in_arp_resolve lr0-flows7 | grep "reg0 == 10.0.0.10" | sed 's/
+@@ -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;)
])
@@ -4234,7 +7156,7 @@ index b465784cd..dbc6e549b 100644
# Delete sw0-p2 logical port
ovn-nbctl lsp-del sw0-p2
-@@ -16933,6 +17126,14 @@ AT_CHECK([grep ls_in_arp_rsp sw0-flows3 | grep bind_vport | sed 's/table=../tabl
+@@ -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;)
])
@@ -4249,7 +7171,7 @@ index b465784cd..dbc6e549b 100644
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 +17143,38 @@ ovn-sbctl dump-flows lr0 > lr0-flows8
+@@ -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])
@@ -4288,7 +7210,274 @@ index b465784cd..dbc6e549b 100644
OVN_CLEANUP([hv1], [hv2])
AT_CLEANUP
-@@ -24918,3 +25151,633 @@ AT_CHECK([cat hv2_offlows_table72.txt | grep -v NXST], [1], [dnl
+@@ -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
@@ -4922,6 +8111,313 @@ index b465784cd..dbc6e549b 100644
+
+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
diff --git a/SPECS/ovn-2021.spec b/SPECS/ovn-2021.spec
index b85781f..4d13e3a 100644
--- a/SPECS/ovn-2021.spec
+++ b/SPECS/ovn-2021.spec
@@ -51,7 +51,7 @@ Summary: Open Virtual Network support
Group: System Environment/Daemons
URL: http://www.ovn.org/
Version: 21.03.0
-Release: 21%{?commit0:.%{date}git%{shortcommit0}}%{?dist}
+Release: 40%{?commit0:.%{date}git%{shortcommit0}}%{?dist}
Provides: openvswitch%{pkgver}-ovn-common = %{?epoch:%{epoch}:}%{version}-%{release}
Obsoletes: openvswitch%{pkgver}-ovn-common < 2.11.0-1
@@ -526,6 +526,101 @@ fi
%{_unitdir}/ovn-controller-vtep.service
%changelog
+* Thu May 27 2021 Dumitru Ceara