diff mbox series

[ovs-dev] northd: Support the option to apply from-lport ACLs after load balancer.

Message ID 20220307174632.1087082-1-numans@ovn.org
State Accepted
Headers show
Series [ovs-dev] northd: Support the option to apply from-lport ACLs after load balancer. | expand

Checks

Context Check Description
ovsrobot/apply-robot warning apply and check: warning
ovsrobot/github-robot-_Build_and_Test success github build: passed
ovsrobot/github-robot-_ovn-kubernetes success github build: passed

Commit Message

Numan Siddique March 7, 2022, 5:46 p.m. UTC
From: Numan Siddique <numans@ovn.org>

Presently for ACLs and LBs we do the following in the logical switch
ingress pipeline

   1.  Send the packet to conntrack.
   2.  Apply ACLs (from-lport)
   3a. If the packet is a new connection and it is destined to the LB
       VIP, then select a backend (and commit to conntrack with DNAT).
   3b. If the packet is a new connection and it doesn't match 3a, then
       commit to conntrack.

With the above approach, we cannot address the scenario of applying
ACLs after the load balancing.  There can be ACLs which could match
on the load balancer backend ips.

This patch addresses this usecase by

   1. Send the packet to conntrack.
   2. Apply ACLs (from-lport, not configured with apply-after-lb=true)
   3. If the packet is a new connection and it is destined to the LB
      VIP, then select a backend (and commit to conntrack with DNAT).
   4. Apply ACLs (from-lport, configured with apply-after-lb=true)
   5. If the packet is a new connection and it didn't match (2), then
      commit to conntrack.

In order to support this usecase, this patch supports an option
"apply-after-lb=true" in the ACL table.  This option is valid
only for "from-lport" ACLs.

Suggested-by: Dumitru Ceara <dceara@redhat.com>
Signed-off-by: Numan Siddique <numans@ovn.org>
---
 northd/northd.c           |  90 +++---
 northd/ovn-northd.8.xml   | 110 +++++--
 ovn-nb.xml                |  28 ++
 tests/ovn-northd.at       | 359 ++++++++++++++++------
 tests/ovn.at              |  46 +--
 tests/system-ovn.at       | 614 +++++++++++++++++++++++++++++++++++++-
 utilities/ovn-nbctl.8.xml |   9 +-
 utilities/ovn-nbctl.c     |  12 +-
 8 files changed, 1093 insertions(+), 175 deletions(-)

Comments

0-day Robot March 7, 2022, 6:59 p.m. UTC | #1
Bleep bloop.  Greetings Numan Siddique, I am a robot and I have tried out your patch.
Thanks for your contribution.

I encountered some error that I wasn't expecting.  See the details below.


checkpatch:
WARNING: Line is 444 characters long (recommended limit is 79)
#1987 FILE: utilities/ovn-nbctl.8.xml:402:
      <dt>[<code>--type=</code>{<code>switch</code> | <code>port-group</code>}] [<code>--log</code>] [<code>--meter=</code><var>meter</var>] [<code>--severity=</code><var>severity</var>] [<code>--name=</code><var>name</var>] [<code>--label=</code><var>label</var>] [<code>--may-exist</code>] [<code>--apply-after-lb</code>] <code>acl-add</code> <var>entity</var> <var>direction</var> <var>priority</var> <var>match</var> <var>verdict</var></dt>

Lines checked: 2051, Warnings: 1, Errors: 0


Please check this out.  If you feel there has been an error, please email aconole@redhat.com

Thanks,
0-day Robot
Han Zhou March 8, 2022, 4:54 a.m. UTC | #2
On Mon, Mar 7, 2022 at 9:46 AM <numans@ovn.org> wrote:
>
> From: Numan Siddique <numans@ovn.org>
>
> Presently for ACLs and LBs we do the following in the logical switch
> ingress pipeline
>
>    1.  Send the packet to conntrack.
>    2.  Apply ACLs (from-lport)
>    3a. If the packet is a new connection and it is destined to the LB
>        VIP, then select a backend (and commit to conntrack with DNAT).
>    3b. If the packet is a new connection and it doesn't match 3a, then
>        commit to conntrack.
>
> With the above approach, we cannot address the scenario of applying
> ACLs after the load balancing.  There can be ACLs which could match
> on the load balancer backend ips.
>
> This patch addresses this usecase by
>
>    1. Send the packet to conntrack.
>    2. Apply ACLs (from-lport, not configured with apply-after-lb=true)
>    3. If the packet is a new connection and it is destined to the LB
>       VIP, then select a backend (and commit to conntrack with DNAT).
>    4. Apply ACLs (from-lport, configured with apply-after-lb=true)
>    5. If the packet is a new connection and it didn't match (2), then
>       commit to conntrack.
>
> In order to support this usecase, this patch supports an option
> "apply-after-lb=true" in the ACL table.  This option is valid
> only for "from-lport" ACLs.
>
> Suggested-by: Dumitru Ceara <dceara@redhat.com>
> Signed-off-by: Numan Siddique <numans@ovn.org>

Thanks Numan. Looks good except some minor comments for documentation below.
Acked-by: Han Zhou <hzhou@ovn.org>

> ---
>  northd/northd.c           |  90 +++---
>  northd/ovn-northd.8.xml   | 110 +++++--
>  ovn-nb.xml                |  28 ++
>  tests/ovn-northd.at       | 359 ++++++++++++++++------
>  tests/ovn.at              |  46 +--
>  tests/system-ovn.at       | 614 +++++++++++++++++++++++++++++++++++++-
>  utilities/ovn-nbctl.8.xml |   9 +-
>  utilities/ovn-nbctl.c     |  12 +-
>  8 files changed, 1093 insertions(+), 175 deletions(-)
>
> diff --git a/northd/northd.c b/northd/northd.c
> index 294a59bd7e..b22da67e9c 100644
> --- a/northd/northd.c
> +++ b/northd/northd.c
> @@ -112,18 +112,20 @@ 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,  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")
 \
> +    PIPELINE_STAGE(SWITCH, IN,  LB,            12, "ls_in_lb")  \
> +    PIPELINE_STAGE(SWITCH, IN,  ACL_AFTER_LB,  13, "ls_in_acl_after_lb")
 \
> +    PIPELINE_STAGE(SWITCH, IN,  STATEFUL,      14, "ls_in_stateful")
 \
> +    PIPELINE_STAGE(SWITCH, IN,  PRE_HAIRPIN,   15, "ls_in_pre_hairpin")
  \
> +    PIPELINE_STAGE(SWITCH, IN,  NAT_HAIRPIN,   16, "ls_in_nat_hairpin")
  \
> +    PIPELINE_STAGE(SWITCH, IN,  HAIRPIN,       17, "ls_in_hairpin")
  \
> +    PIPELINE_STAGE(SWITCH, IN,  ARP_ND_RSP,    18, "ls_in_arp_rsp")
  \
> +    PIPELINE_STAGE(SWITCH, IN,  DHCP_OPTIONS,  19, "ls_in_dhcp_options")
 \
> +    PIPELINE_STAGE(SWITCH, IN,  DHCP_RESPONSE, 20,
"ls_in_dhcp_response") \
> +    PIPELINE_STAGE(SWITCH, IN,  DNS_LOOKUP,    21, "ls_in_dns_lookup")
 \
> +    PIPELINE_STAGE(SWITCH, IN,  DNS_RESPONSE,  22, "ls_in_dns_response")
 \
> +    PIPELINE_STAGE(SWITCH, IN,  EXTERNAL_PORT, 23,
"ls_in_external_port") \
> +    PIPELINE_STAGE(SWITCH, IN,  L2_LKUP,       24, "ls_in_l2_lkup")
  \
> +    PIPELINE_STAGE(SWITCH, IN,  L2_UNKNOWN,    25, "ls_in_l2_unknown")
 \
>
 \
>      /* Logical switch egress stages. */
  \
>      PIPELINE_STAGE(SWITCH, OUT, PRE_LB,       0, "ls_out_pre_lb")
  \
> @@ -6145,7 +6147,7 @@ build_reject_acl_rules(struct ovn_datapath *od,
struct hmap *lflows,
>  {
>      struct ds match = DS_EMPTY_INITIALIZER;
>      struct ds actions = DS_EMPTY_INITIALIZER;
> -    bool ingress = (stage == S_SWITCH_IN_ACL);
> +    bool ingress = (ovn_stage_get_pipeline(stage) == P_IN);
>
>      char *next_action =
>          xasprintf("next(pipeline=%s,table=%d);",
> @@ -6186,7 +6188,15 @@ consider_acl(struct hmap *lflows, struct
ovn_datapath *od,
>               struct ds *actions)
>  {
>      bool ingress = !strcmp(acl->direction, "from-lport") ? true :false;
> -    enum ovn_stage stage = ingress ? S_SWITCH_IN_ACL : S_SWITCH_OUT_ACL;
> +    enum ovn_stage stage;
> +
> +    if (ingress && smap_get_bool(&acl->options, "apply-after-lb",
false)) {
> +        stage = S_SWITCH_IN_ACL_AFTER_LB;
> +    } else if (ingress) {
> +        stage = S_SWITCH_IN_ACL;
> +    } else {
> +        stage = S_SWITCH_OUT_ACL;
> +    }
>
>      if (!strcmp(acl->action, "allow-stateless")) {
>          ds_clear(actions);
> @@ -6471,6 +6481,8 @@ build_acls(struct ovn_datapath *od, struct hmap
*lflows,
>          ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, 0, "1", "next;");
>      }
>
> +    ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL_AFTER_LB, 0, "1", "next;");
> +
>      if (has_stateful) {
>          /* Ingress and Egress ACL Table (Priority 1).
>           *
> @@ -6529,7 +6541,8 @@ build_acls(struct ovn_datapath *od, struct hmap
*lflows,
>                        "ct.rpl && ct_label.blocked == 0",
>                        use_ct_inv_match ? " && !ct.inv" : "");
>          ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, UINT16_MAX - 3,
> -                      ds_cstr(&match), "next;");
> +                      ds_cstr(&match), REGBIT_ACL_HINT_DROP" = 0; "
> +                      REGBIT_ACL_HINT_BLOCK" = 0; next;");
>          ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX - 3,
>                        ds_cstr(&match), "next;");
>
> @@ -6736,6 +6749,10 @@ build_lb_rules(struct hmap *lflows, struct
ovn_northd_lb *lb,
>          ds_clear(action);
>          ds_clear(match);
>
> +        /* Make sure that we clear the REGBIT_CONNTRACK_COMMIT flag.
Otherwise
> +         * the load balanced packet will be committed again in
> +         * S_SWITCH_IN_STATEFUL. */
> +        ds_put_format(action, REGBIT_CONNTRACK_COMMIT" = 0; ");
>          /* Store the original destination IP to be used when generating
>           * hairpin flows.
>           */
> @@ -6782,8 +6799,8 @@ build_lb_rules(struct hmap *lflows, struct
ovn_northd_lb *lb,
>
>          struct ovn_lflow *lflow_ref = NULL;
>          uint32_t hash = ovn_logical_flow_hash(
> -                ovn_stage_get_table(S_SWITCH_IN_STATEFUL),
> -                ovn_stage_get_pipeline(S_SWITCH_IN_STATEFUL), priority,
> +                ovn_stage_get_table(S_SWITCH_IN_LB),
> +                ovn_stage_get_pipeline(S_SWITCH_IN_LB), priority,
>                  ds_cstr(match), ds_cstr(action));
>
>          for (size_t j = 0; j < lb->n_nb_ls; j++) {
> @@ -6796,7 +6813,7 @@ build_lb_rules(struct hmap *lflows, struct
ovn_northd_lb *lb,
>                  continue;
>              }
>              lflow_ref = ovn_lflow_add_at_with_hash(lflows, od,
> -                    S_SWITCH_IN_STATEFUL, priority,
> +                    S_SWITCH_IN_LB, priority,
>                      ds_cstr(match), ds_cstr(action),
>                      NULL, meter, &lb->nlb->header_,
>                      OVS_SOURCE_LOCATOR, hash);
> @@ -6807,8 +6824,9 @@ build_lb_rules(struct hmap *lflows, struct
ovn_northd_lb *lb,
>  static void
>  build_stateful(struct ovn_datapath *od, struct hmap *lflows)
>  {
> -    /* Ingress and Egress stateful Table (Priority 0): Packets are
> +    /* Ingress LB, Ingress and Egress stateful 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_IN_STATEFUL, 0, "1", "next;");
>      ovn_lflow_add(lflows, od, S_SWITCH_OUT_STATEFUL, 0, "1", "next;");
>
> @@ -7072,7 +7090,7 @@ build_lrouter_groups(struct hmap *ports, struct
ovs_list *lr_list)
>  }
>
>  /*
> - * Ingress table 22: Flows that flood self originated ARP/ND packets in
the
> + * Ingress table 24: Flows that flood self originated ARP/ND packets in
the
>   * switching domain.
>   */
>  static void
> @@ -7185,7 +7203,7 @@ lrouter_port_ipv6_reachable(const struct ovn_port
*op,
>  }
>
>  /*
> - * Ingress table 22: Flows that forward ARP/ND requests only to the
routers
> + * Ingress table 24: Flows that forward ARP/ND requests only to the
routers
>   * that own the addresses. Other ARP/ND packets are still flooded in the
>   * switching domain as regular broadcast.
>   */
> @@ -7222,7 +7240,7 @@ build_lswitch_rport_arp_req_flow(const char *ips,
>  }
>
>  /*
> - * Ingress table 22: Flows that forward ARP/ND requests only to the
routers
> + * Ingress table 24: Flows that forward ARP/ND requests only to the
routers
>   * that own the addresses.
>   * Priorities:
>   * - 80: self originated GARPs that need to follow regular processing.
> @@ -7550,7 +7568,7 @@ build_lswitch_flows(const struct hmap *datapaths,
>
>      struct ovn_datapath *od;
>
> -    /* Ingress table 23: Destination lookup for unknown MACs (priority
0). */
> +    /* Ingress table 25: Destination lookup for unknown MACs (priority
0). */
>      HMAP_FOR_EACH (od, key_node, datapaths) {
>          if (!od->nbs) {
>              continue;
> @@ -7619,7 +7637,7 @@ build_lswitch_lflows_admission_control(struct
ovn_datapath *od,
>      }
>  }
>
> -/* Ingress table 16: ARP/ND responder, skip requests coming from localnet
> +/* Ingress table 18: ARP/ND responder, skip requests coming from localnet
>   * and vtep ports. (priority 100); see ovn-northd.8.xml for the
>   * rationale. */
>
> @@ -7641,7 +7659,7 @@ build_lswitch_arp_nd_responder_skip_local(struct
ovn_port *op,
>      }
>  }
>
> -/* Ingress table 16: ARP/ND responder, reply for known IPs.
> +/* Ingress table 18: ARP/ND responder, reply for known IPs.
>   * (priority 50). */
>  static void
>  build_lswitch_arp_nd_responder_known_ips(struct ovn_port *op,
> @@ -7901,7 +7919,7 @@ build_lswitch_arp_nd_responder_known_ips(struct
ovn_port *op,
>      }
>  }
>
> -/* Ingress table 16: ARP/ND responder, by default goto next.
> +/* Ingress table 18: ARP/ND responder, by default goto next.
>   * (priority 0)*/
>  static void
>  build_lswitch_arp_nd_responder_default(struct ovn_datapath *od,
> @@ -7912,7 +7930,7 @@ build_lswitch_arp_nd_responder_default(struct
ovn_datapath *od,
>      }
>  }
>
> -/* Ingress table 16: ARP/ND responder for service monitor source ip.
> +/* Ingress table 18: ARP/ND responder for service monitor source ip.
>   * (priority 110)*/
>  static void
>  build_lswitch_arp_nd_service_monitor(struct ovn_northd_lb *lb,
> @@ -7960,7 +7978,7 @@ build_lswitch_arp_nd_service_monitor(struct
ovn_northd_lb *lb,
>  }
>
>
> -/* Logical switch ingress table 14 and 15: DHCP options and response
> +/* Logical switch ingress table 19 and 20: DHCP options and response
>   * priority 100 flows. */
>  static void
>  build_lswitch_dhcp_options_and_response(struct ovn_port *op,
> @@ -8012,11 +8030,11 @@ build_lswitch_dhcp_options_and_response(struct
ovn_port *op,
>      }
>  }
>
> -/* Ingress table 14 and 15: DHCP options and response, by default goto
> +/* Ingress table 19 and 20: DHCP options and response, by default goto
>   * next. (priority 0).
> - * Ingress table 16 and 17: DNS lookup and response, by default goto
next.
> + * Ingress table 21 and 22: DNS lookup and response, by default goto
next.
>   * (priority 0).
> - * Ingress table 18 - External port handling, by default goto next.
> + * Ingress table 23 - External port handling, by default goto next.
>   * (priority 0). */
>  static void
>  build_lswitch_dhcp_and_dns_defaults(struct ovn_datapath *od,
> @@ -8031,7 +8049,7 @@ build_lswitch_dhcp_and_dns_defaults(struct
ovn_datapath *od,
>      }
>  }
>
> -/* Logical switch ingress table 17 and 18: DNS lookup and response
> +/* Logical switch ingress table 21 and 22: DNS lookup and response
>  * priority 100 flows.
>  */
>  static void
> @@ -8059,7 +8077,7 @@ build_lswitch_dns_lookup_and_response(struct
ovn_datapath *od,
>      }
>  }
>
> -/* Table 18: External port. Drop ARP request for router ips from
> +/* Table 23: External port. Drop ARP request for router ips from
>   * external ports  on chassis not binding those ports.
>   * This makes the router pipeline to be run only on the chassis
>   * binding the external ports. */
> @@ -8076,7 +8094,7 @@ build_lswitch_external_port(struct ovn_port *op,
>      }
>  }
>
> -/* Ingress table 22: Destination lookup, broadcast and multicast handling
> +/* Ingress table 24: Destination lookup, broadcast and multicast handling
>   * (priority 70 - 100). */
>  static void
>  build_lswitch_destination_lookup_bmcast(struct ovn_datapath *od,
> @@ -8168,7 +8186,7 @@ build_lswitch_destination_lookup_bmcast(struct
ovn_datapath *od,
>  }
>
>
> -/* Ingress table 22: Add IP multicast flows learnt from IGMP/MLD
> +/* Ingress table 24: Add IP multicast flows learnt from IGMP/MLD
>   * (priority 90). */
>  static void
>  build_lswitch_ip_mcast_igmp_mld(struct ovn_igmp_group *igmp_group,
> @@ -8246,7 +8264,7 @@ build_lswitch_ip_mcast_igmp_mld(struct
ovn_igmp_group *igmp_group,
>
>  static struct ovs_mutex mcgroup_mutex = OVS_MUTEX_INITIALIZER;
>
> -/* Ingress table 22: Destination lookup, unicast handling (priority 50),
*/
> +/* Ingress table 24: Destination lookup, unicast handling (priority 50),
*/
>  static void
>  build_lswitch_ip_unicast_lookup(struct ovn_port *op,
>                                  struct hmap *lflows,
> diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
> index e495db46a0..1c9d408afe 100644
> --- a/northd/ovn-northd.8.xml
> +++ b/northd/ovn-northd.8.xml
> @@ -712,15 +712,16 @@
>        </li>
>      </ul>
>
> -    <h3>Ingress table 9: <code>from-lport</code> ACLs</h3>
> +    <h3>Ingress table 9: <code>from-lport</code> ACLs before LB</h3>
>
>      <p>
>        Logical flows in this table closely reproduce those in the
>        <code>ACL</code> table in the <code>OVN_Northbound</code> database
> -      for the <code>from-lport</code> direction. The
<code>priority</code>
> -      values from the <code>ACL</code> table have a limited range and
have
> -      1000 added to them to leave room for OVN default flows at both
> -      higher and lower priorities.
> +      for the <code>from-lport</code> direction without the option
> +      <code>apply-after-lb</code> set or set to <code>false</code>.
> +      The <code>priority</code> values from the <code>ACL</code> table
have a
> +      limited range and have 1000 added to them to leave room for OVN
default
> +      flows at both higher and lower priorities.
>      </p>
>      <ul>
>        <li>
> @@ -795,10 +796,11 @@
>          go through the flows that implement the currently defined
>          policy based on ACLs.  If a connection is no longer allowed by
>          policy, <code>ct_label.blocked</code> will get set and packets
in the
> -        reply direction will no longer be allowed, either. If ACL logging
> -        and logging of related packets is enabled, then a companion
priority-
> -        65533 flow will be installed that accomplishes the same thing but
> -        also logs the traffic.
> +        reply direction will no longer be allowed, either. This flow also
> +        clears the register bits <code>reg0[9]</code> and
> +        <code>reg0[10]</code>.  If ACL logging and logging of related
packets
> +        is enabled, then a companion priority-65533 flow will be
installed that
> +        accomplishes the same thing but also logs the traffic.
>        </li>
>
>        <li>
> @@ -893,7 +895,7 @@
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 12: Stateful</h3>
> +    <h3>Ingress Table 12: LB</h3>
>
>      <ul>
>        <li>
> @@ -944,7 +946,73 @@
>          Please note using <code>--reject</code> option will disable
>          empty_lb SB controller event for this load balancer.
>        </li>
> +    </ul>
> +
> +    <h3>Ingress table 13: <code>from-lport</code> ACLs after LB</h3>
> +
> +    <p>
> +      Logical flows in this table closely reproduce those in the
> +      <code>ACL</code> table in the <code>OVN_Northbound</code> database
> +      for the <code>from-lport</code> direction with the option
> +      <code>apply-after-lb</code> set to <code>true</code>.
> +      The <code>priority</code> values from the <code>ACL</code> table
have a
> +      limited range and have 1000 added to them to leave room for OVN
default
> +      flows at both higher and lower priorities.
> +    </p>
> +
> +    <ul>
> +      <li>
> +        <code>allow</code> apply-after-lb ACLs translate into logical
flows
> +        with the <code>next;</code> action.  If there are any stateful
ACLs
> +        (including both before-lb and after-lb ACLs)
> +        on this datapath, then <code>allow</code> ACLs translate to
> +        <code>ct_commit; next;</code> (which acts as a hint for the next
tables
> +        to commit the connection to conntrack). In case the
<code>ACL</code>
> +        has a label then <code>reg3</code> is loaded with the label
value and
> +        <code>reg0[13]</code> bit is set to 1 (which acts as a hint for
the
> +        next tables to commit the label to conntrack).
> +      </li>
> +      <li>
> +        <code>allow-related</code> apply-after-lb ACLs translate into
logical
> +        flows with the <code>ct_commit(ct_label=0/1); next;</code>
actions
> +        for new connections and <code>reg0[1] = 1; next;</code> for
existing
> +        connections.  In case the <code>ACL</code> has a label then
> +        <code>reg3</code> is loaded with the label value and
> +        <code>reg0[13]</code> bit is set to 1 (which acts as a hint for
the
> +        next tables to commit the label to conntrack).
> +      </li>
> +      <li>
> +        <code>allow-stateless</code> apply-after-lb ACLs translate into
logical
> +        flows with the <code>next;</code> action.
> +      </li>
> +      <li>
> +        <code>reject</code> apply-after-lb ACLs translate into logical
> +        flows with the
> +        <code>tcp_reset { output &lt;-&gt; inport;
> +        next(pipeline=egress,table=5);}</code>
> +        action for TCP connections,<code>icmp4/icmp6</code> action
> +        for UDP connections, and <code>sctp_abort {output &lt;-%gt;
inport;
> +        next(pipeline=egress,table=5);}</code> action for SCTP
associations.
> +      </li>
> +      <li>
> +        Other apply-after-lb ACLs translate to <code>drop;</code> for new
> +        or untracked connections and
<code>ct_commit(ct_label=1/1);</code> for
> +        known connections.  Setting <code>ct_label</code> marks a
connection
> +        as one that was previously allowed, but should no longer be
> +        allowed due to a policy change.
> +      </li>
> +    </ul>
> +
> +    <ul>
> +      <li>
> +        One priority-0 fallback flow that matches all packets and
advances to
> +        the next table.
> +      </li>
> +    </ul>
> +
> +    <h3>Ingress Table 14: Stateful</h3>
>
> +    <ul>
>        <li>
>          A priority 100 flow is added which commits the packet to the
conntrack
>          and sets the most significant 32-bits of <code>ct_label</code>
with the
> @@ -965,7 +1033,7 @@
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 13: Pre-Hairpin</h3>
> +    <h3>Ingress Table 15: Pre-Hairpin</h3>
>      <ul>
>        <li>
>          If the logical switch has load balancer(s) configured, then a
> @@ -983,7 +1051,7 @@
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 14: Nat-Hairpin</h3>
> +    <h3>Ingress Table 16: Nat-Hairpin</h3>
>      <ul>
>        <li>
>           If the logical switch has load balancer(s) configured, then a
> @@ -1018,7 +1086,7 @@
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 15: Hairpin</h3>
> +    <h3>Ingress Table 17: Hairpin</h3>
>      <ul>
>        <li>
>          A priority-1 flow that hairpins traffic matched by non-default
> @@ -1031,7 +1099,7 @@
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 16: ARP/ND responder</h3>
> +    <h3>Ingress Table 18: ARP/ND responder</h3>
>
>      <p>
>        This table implements ARP/ND responder in a logical switch for
known
> @@ -1333,7 +1401,7 @@ output;
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 17: DHCP option processing</h3>
> +    <h3>Ingress Table 19: DHCP option processing</h3>
>
>      <p>
>        This table adds the DHCPv4 options to a DHCPv4 packet from the
> @@ -1394,7 +1462,7 @@ next;
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 18: DHCP responses</h3>
> +    <h3>Ingress Table 20: DHCP responses</h3>
>
>      <p>
>        This table implements DHCP responder for the DHCP replies
generated by
> @@ -1475,7 +1543,7 @@ output;
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 19 DNS Lookup</h3>
> +    <h3>Ingress Table 21 DNS Lookup</h3>
>
>      <p>
>        This table looks up and resolves the DNS names to the corresponding
> @@ -1504,7 +1572,7 @@ reg0[4] = dns_lookup(); next;
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 20 DNS Responses</h3>
> +    <h3>Ingress Table 22 DNS Responses</h3>
>
>      <p>
>        This table implements DNS responder for the DNS replies generated
by
> @@ -1539,7 +1607,7 @@ output;
>        </li>
>      </ul>
>
> -    <h3>Ingress table 21 External ports</h3>
> +    <h3>Ingress table 23 External ports</h3>
>
>      <p>
>        Traffic from the <code>external</code> logical ports enter the
ingress
> @@ -1582,7 +1650,7 @@ output;
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 22 Destination Lookup</h3>
> +    <h3>Ingress Table 24 Destination Lookup</h3>
>
>      <p>
>        This table implements switching behavior.  It contains these
logical
> @@ -1754,7 +1822,7 @@ output;
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 24 Destination unknown</h3>
> +    <h3>Ingress Table 25 Destination unknown</h3>
>
>      <p>
>        This table handles the packets whose destination was not found or
> diff --git a/ovn-nb.xml b/ovn-nb.xml
> index beb3ded798..cea105b545 100644
> --- a/ovn-nb.xml
> +++ b/ovn-nb.xml
> @@ -2029,6 +2029,34 @@
>        </ul>
>      </column>
>
> +    <group title="options">
> +      <p>
> +        ACLs options.
> +      </p>
> +      <column name="options" key="apply-after-lb">
> +        <p>
> +          If set to true, the ACL will be applied after load balancing
> +          stage.  Supported only for <code>from-lport</code> direction.
> +        </p>
> +
> +        <p>
> +          The main usecase of this option is to support ACLs matching on

s/usecase/use case

> +          the destination IP address of the packet for the backend IPs
> +          of load balancers.
> +        </p>
> +
> +        <p>
> +          <code>OVN</code> will apply the <code>from-lport</code>ACLs in
two

Need a space before "ACLs".

> +          stages.  ACLs without this option <code>apply-after-lb</code>
> +          set, will be applied before the load balancer stage and ACLs
> +          with this option set will be applied after the load balancer
> +          stage.  Hence CMS should be extra careful when using this
option
> +          and should carefully evaluate the priorities of all the ACLs
and
> +          the default deny/allow ACLs if any.

Before "Hence CMS ...", it would be better to point out that the priorities
are independent between the two stages, since it may not be obvious to
users.

Thanks,
Han

> +        </p>
> +      </column>
> +    </group>
> +
>      <group title="Logging">
>        <p>
>          These columns control whether and how OVN logs packets that
match an
> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> index fe27869737..3865003bf8 100644
> --- a/tests/ovn-northd.at
> +++ b/tests/ovn-northd.at
> @@ -1226,7 +1226,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.*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);)
> +  (ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst ==
10.0.0.10 && tcp.dst == 80), action=(reg0[[1]] = 0; reg1 = 10.0.0.10;
reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
>  ])
>
>  AS_BOX([Delete the Load_Balancer_Health_Check])
> @@ -1236,7 +1236,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.*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);)
> +[  (ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst ==
10.0.0.10 && tcp.dst == 80), action=(reg0[[1]] = 0; reg1 = 10.0.0.10;
reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
>  ])
>
>  AS_BOX([Create the Load_Balancer_Health_Check again.])
> @@ -1248,7 +1248,7 @@ check ovn-nbctl --wait=sb sync
>
>  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);)
> +  (ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst ==
10.0.0.10 && tcp.dst == 80), action=(reg0[[1]] = 0; reg1 = 10.0.0.10;
reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
>  ])
>
>  AS_BOX([Get the uuid of both the service_monitor])
> @@ -1258,7 +1258,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.*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);)
> +[  (ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst ==
10.0.0.10 && tcp.dst == 80), action=(reg0[[1]] = 0; reg1 = 10.0.0.10;
reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
>  ])
>
>  AS_BOX([Set the service monitor for sw1-p1 to offline])
> @@ -1269,7 +1269,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.*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);)
> +[  (ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst ==
10.0.0.10 && tcp.dst == 80), action=(reg0[[1]] = 0; reg1 = 10.0.0.10;
reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80);)
>  ])
>
>  AS_BOX([Set the service monitor for sw0-p1 to offline])
> @@ -1285,7 +1285,7 @@ OVS_WAIT_FOR_OUTPUT(
>  AT_CAPTURE_FILE([sbflows6])
>  OVS_WAIT_FOR_OUTPUT(
>    [ovn-sbctl dump-flows sw0 | tee sbflows6 | 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=(drop;)
> +  (ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst ==
10.0.0.10 && tcp.dst == 80), action=(drop;)
>  ])
>
>  AS_BOX([Set the service monitor for sw0-p1 and sw1-p1 to online])
> @@ -1298,7 +1298,7 @@ check ovn-nbctl --wait=sb sync
>  AT_CAPTURE_FILE([sbflows7])
>  OVS_WAIT_FOR_OUTPUT(
>    [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);)
> +[  (ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst ==
10.0.0.10 && tcp.dst == 80), action=(reg0[[1]] = 0; reg1 = 10.0.0.10;
reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
>  ])
>
>  AS_BOX([Set the service monitor for sw1-p1 to error])
> @@ -1309,7 +1309,7 @@ check ovn-nbctl --wait=sb sync
>  ovn-sbctl dump-flows sw0 | grep "ip4.dst == 10.0.0.10 && tcp.dst == 80" \
>  | grep priority=120 > lflows.txt
>  AT_CHECK([cat lflows.txt | sed 's/table=..//'], [0], [dnl
> -  (ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst ==
10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80;
ct_lb(backends=10.0.0.3:80);)
> +  (ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst ==
10.0.0.10 && tcp.dst == 80), action=(reg0[[1]] = 0; reg1 = 10.0.0.10;
reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80);)
>  ])
>
>  AS_BOX([Add one more vip to lb1])
> @@ -1335,8 +1335,8 @@ AT_CAPTURE_FILE([sbflows9])
>  OVS_WAIT_FOR_OUTPUT(
>    [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);)
> +[  (ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst ==
10.0.0.10 && tcp.dst == 80), action=(reg0[[1]] = 0; reg1 = 10.0.0.10;
reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80);)
> +  (ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst ==
10.0.0.40 && tcp.dst == 1000), action=(reg0[[1]] = 0; reg1 = 10.0.0.40;
reg2[[0..15]] = 1000; ct_lb(backends=10.0.0.3:1000);)
>  ])
>
>  AS_BOX([Set the service monitor for sw1-p1 to online])
> @@ -1349,8 +1349,8 @@ AT_CAPTURE_FILE([sbflows10])
>  OVS_WAIT_FOR_OUTPUT(
>    [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);)
> +[  (ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst ==
10.0.0.10 && tcp.dst == 80), action=(reg0[[1]] = 0; reg1 = 10.0.0.10;
reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
> +  (ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst ==
10.0.0.40 && tcp.dst == 1000), action=(reg0[[1]] = 0; reg1 = 10.0.0.40;
reg2[[0..15]] = 1000; ct_lb(backends=10.0.0.3:1000,20.0.0.3:80);)
>  ])
>
>  AS_BOX([Associate lb1 to sw1])
> @@ -1359,8 +1359,8 @@ AT_CAPTURE_FILE([sbflows11])
>  OVS_WAIT_FOR_OUTPUT(
>    [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);)
> +  (ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst ==
10.0.0.10 && tcp.dst == 80), action=(reg0[[1]] = 0; reg1 = 10.0.0.10;
reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
> +  (ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst ==
10.0.0.40 && tcp.dst == 1000), action=(reg0[[1]] = 0; reg1 = 10.0.0.40;
reg2[[0..15]] = 1000; ct_lb(backends=10.0.0.3:1000,20.0.0.3:80);)
>  ])
>
>  AS_BOX([Now create lb2 same as lb1 but udp protocol.])
> @@ -1417,7 +1417,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=5);};)
> +  (ls_in_lb           ), 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
> @@ -2037,9 +2037,9 @@ AT_CAPTURE_FILE([sw1flows])
>
>  AT_CHECK(
>    [grep -E 'ls_(in|out)_acl' sw0flows sw1flows | grep pg0 | sort], [0],
[dnl
> -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=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=24); };)
>  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=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=24); };)
>  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); };)
>  ])
>
> @@ -2053,10 +2053,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=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); };)
> +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=24); };)
> +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=24); };)
> +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=24); };)
> +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=24); };)
>  ])
>
>  AS_BOX([3])
> @@ -2071,16 +2071,16 @@ AT_CAPTURE_FILE([sw1flows3])
>  AT_CHECK([grep "ls_out_acl" sw0flows3 sw1flows3 | grep pg0 | sort], [0],
[dnl
>  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); };)
> +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=24); };)
> +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=24); };)
> +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=24); };)
> +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=24); };)
>  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); };)
> +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=24); };)
> +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=24); };)
> +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=24); };)
> +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=24); };)
>  ])
>  AT_CLEANUP
>  ])
> @@ -2240,7 +2240,7 @@ 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=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=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.est &&
!ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0),
action=(reg0[[9]] = 0; reg0[[10]] = 0; next;)
>    table=9 (ls_in_acl          ), priority=65532, match=(ct.inv ||
(ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;)
>  ])
>
> @@ -2252,6 +2252,7 @@ check ovn-nbctl --wait=sb \
>      -- 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 | sort], [0], [dnl
> +  table=13(ls_in_acl_after_lb ), priority=0    , match=(1),
action=(next;)
>    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;)
> @@ -2283,7 +2284,7 @@ AT_CHECK([ovn-sbctl lflow-list ls | grep -e
ls_in_acl_hint -e ls_out_acl_hint -e
>    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.est &&
!ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0),
action=(reg0[[9]] = 0; reg0[[10]] = 0; 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;)
>  ])
> @@ -2292,6 +2293,7 @@ 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=13(ls_in_acl_after_lb ), priority=0    , match=(1),
action=(next;)
>    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;)
> @@ -2578,56 +2580,56 @@ check ovn-nbctl \
>      -- ls-lb-add sw0 lb0
>  check ovn-nbctl --wait=sb sync
>
> -AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_pre_hairpin | sort],
[0], [dnl
> -  table=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_pre_hairpin | sort | sed
's/table=../table=??/'], [0], [dnl
> +  table=??(ls_in_pre_hairpin  ), priority=0    , match=(1),
action=(next;)
> +  table=??(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=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_nat_hairpin | sort | sed
's/table=../table=??/'], [0], [dnl
> +  table=??(ls_in_nat_hairpin  ), priority=0    , match=(1),
action=(next;)
> +  table=??(ls_in_nat_hairpin  ), priority=100  , match=(ip && ct.est &&
ct.trk && reg0[[6]] == 1), action=(ct_snat;)
> +  table=??(ls_in_nat_hairpin  ), priority=100  , match=(ip && ct.new &&
ct.trk && reg0[[6]] == 1), action=(ct_snat_to_vip; next;)
> +  table=??(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=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_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_hairpin | sort | sed
's/table=../table=??/'], [0], [dnl
> +  table=??(ls_in_hairpin      ), priority=0    , match=(1),
action=(next;)
> +  table=??(ls_in_hairpin      ), priority=1    , match=((reg0[[6]] == 1
|| reg0[[12]] == 1)), action=(eth.dst <-> eth.src; outport = inport;
flags.loopback = 1; output;)
>  ])
>
>  check ovn-nbctl -- ls-lb-del sw0 lb0
>  check ovn-nbctl --wait=sb sync
>
> -AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_pre_hairpin | sort],
[0], [dnl
> -  table=13(ls_in_pre_hairpin  ), priority=0    , match=(1),
action=(next;)
> +AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_pre_hairpin | sort | sed
's/table=../table=??/'], [0], [dnl
> +  table=??(ls_in_pre_hairpin  ), priority=0    , match=(1),
action=(next;)
>  ])
>
> -AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_nat_hairpin | sort],
[0], [dnl
> -  table=14(ls_in_nat_hairpin  ), priority=0    , match=(1),
action=(next;)
> +AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_nat_hairpin | sort | sed
's/table=../table=??/'], [0], [dnl
> +  table=??(ls_in_nat_hairpin  ), priority=0    , match=(1),
action=(next;)
>  ])
>
> -AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_hairpin | sort], [0],
[dnl
> -  table=15(ls_in_hairpin      ), priority=0    , match=(1),
action=(next;)
> +AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_hairpin | sort | sed
's/table=../table=??/'], [0], [dnl
> +  table=??(ls_in_hairpin      ), priority=0    , match=(1),
action=(next;)
>  ])
>
>  check ovn-nbctl -- add load_balancer_group $lbg load_balancer $lb0
>  check ovn-nbctl --wait=sb sync
>
> -AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_pre_hairpin | sort],
[0], [dnl
> -  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_pre_hairpin | sort | sed
's/table=../table=??/'], [0], [dnl
> +  table=??(ls_in_pre_hairpin  ), priority=0    , match=(1),
action=(next;)
> +  table=??(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=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_nat_hairpin | sort | sed
's/table=../table=??/'], [0], [dnl
> +  table=??(ls_in_nat_hairpin  ), priority=0    , match=(1),
action=(next;)
> +  table=??(ls_in_nat_hairpin  ), priority=100  , match=(ip && ct.est &&
ct.trk && reg0[[6]] == 1), action=(ct_snat;)
> +  table=??(ls_in_nat_hairpin  ), priority=100  , match=(ip && ct.new &&
ct.trk && reg0[[6]] == 1), action=(ct_snat_to_vip; next;)
> +  table=??(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=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_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_hairpin | sort | sed
's/table=../table=??/'], [0], [dnl
> +  table=??(ls_in_hairpin      ), priority=0    , match=(1),
action=(next;)
> +  table=??(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
> @@ -3948,12 +3950,16 @@ check_stateful_flows() {
>    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_lb" sw0flows | sort], [0], [dnl
> +  table=12(ls_in_lb           ), priority=0    , match=(1),
action=(next;)
> +  table=12(ls_in_lb           ), priority=120  , match=(ct.new &&
ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg0[[1]] = 0; reg1 =
10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.4:8080);)
> +  table=12(ls_in_lb           ), priority=120  , match=(ct.new &&
ip4.dst == 10.0.0.20 && tcp.dst == 80), action=(reg0[[1]] = 0; reg1 =
10.0.0.20; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.40:8080);)
> +])
> +
>      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
&& reg0[[13]] == 0), action=(ct_commit { ct_label.blocked = 0; }; next;)
> -  table=12(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1
&& reg0[[13]] == 1), action=(ct_commit { ct_label.blocked = 0;
ct_label.label = reg3; }; 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);)
> -  table=12(ls_in_stateful     ), priority=120  , match=(ct.new &&
ip4.dst == 10.0.0.20 && tcp.dst == 80), action=(reg1 = 10.0.0.20;
reg2[[0..15]] = 80; ct_lb(backends=10.0.0.40:8080);)
> +  table=14(ls_in_stateful     ), priority=0    , match=(1),
action=(next;)
> +  table=14(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1
&& reg0[[13]] == 0), action=(ct_commit { ct_label.blocked = 0; }; next;)
> +  table=14(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1
&& reg0[[13]] == 1), action=(ct_commit { ct_label.blocked = 0;
ct_label.label = reg3; }; next;)
>  ])
>
>      AT_CHECK([grep "ls_out_pre_lb" sw0flows | sort], [0], [dnl
> @@ -4016,10 +4022,14 @@ AT_CHECK([grep "ls_in_pre_stateful" sw0flows |
sort], [0], [dnl
>    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_lb" sw0flows | sort], [0], [dnl
> +  table=12(ls_in_lb           ), priority=0    , match=(1),
action=(next;)
> +])
> +
>  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
&& reg0[[13]] == 0), action=(ct_commit { ct_label.blocked = 0; }; next;)
> -  table=12(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1
&& reg0[[13]] == 1), action=(ct_commit { ct_label.blocked = 0;
ct_label.label = reg3; }; next;)
> +  table=14(ls_in_stateful     ), priority=0    , match=(1),
action=(next;)
> +  table=14(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1
&& reg0[[13]] == 0), action=(ct_commit { ct_label.blocked = 0; }; next;)
> +  table=14(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1
&& reg0[[13]] == 1), action=(ct_commit { ct_label.blocked = 0;
ct_label.label = reg3; }; next;)
>  ])
>
>  AT_CHECK([grep "ls_out_pre_lb" sw0flows | sort], [0], [dnl
> @@ -4063,9 +4073,9 @@ AT_CHECK([grep -w "ls_in_acl" sw0flows | grep 2002
| sort], [0], [dnl
>    table=9 (ls_in_acl          ), priority=2002 , match=(reg0[[8]] == 1
&& (tcp)), action=(reg0[[1]] = 1; reg0[[13]] = 1; reg3 = 1234; next;)
>  ])
>  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
&& reg0[[13]] == 0), action=(ct_commit { ct_label.blocked = 0; }; next;)
> -  table=12(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1
&& reg0[[13]] == 1), action=(ct_commit { ct_label.blocked = 0;
ct_label.label = reg3; }; next;)
> +  table=14(ls_in_stateful     ), priority=0    , match=(1),
action=(next;)
> +  table=14(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1
&& reg0[[13]] == 0), action=(ct_commit { ct_label.blocked = 0; }; next;)
> +  table=14(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1
&& reg0[[13]] == 1), action=(ct_commit { ct_label.blocked = 0;
ct_label.label = reg3; }; next;)
>  ])
>
>  AT_CHECK([grep -w "ls_out_acl" sw0flows | grep 2002 | sort], [0], [dnl
> @@ -4092,9 +4102,9 @@ AT_CHECK([grep -w "ls_in_acl" sw0flows | grep 2002
| sort], [0], [dnl
>    table=9 (ls_in_acl          ), priority=2002 , match=(reg0[[8]] == 1
&& (udp)), action=(next;)
>  ])
>  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
&& reg0[[13]] == 0), action=(ct_commit { ct_label.blocked = 0; }; next;)
> -  table=12(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1
&& reg0[[13]] == 1), action=(ct_commit { ct_label.blocked = 0;
ct_label.label = reg3; }; next;)
> +  table=14(ls_in_stateful     ), priority=0    , match=(1),
action=(next;)
> +  table=14(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1
&& reg0[[13]] == 0), action=(ct_commit { ct_label.blocked = 0; }; next;)
> +  table=14(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1
&& reg0[[13]] == 1), action=(ct_commit { ct_label.blocked = 0;
ct_label.label = reg3; }; next;)
>  ])
>
>  AT_CHECK([grep -w "ls_out_acl" sw0flows | grep 2002 | sort], [0], [dnl
> @@ -4121,9 +4131,9 @@ AT_CHECK([grep -w "ls_in_acl" sw0flows | grep 2002
| sort], [0], [dnl
>    table=9 (ls_in_acl          ), priority=2002 , match=(reg0[[8]] == 1
&& (udp)), action=(next;)
>  ])
>  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
&& reg0[[13]] == 0), action=(ct_commit { ct_label.blocked = 0; }; next;)
> -  table=12(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1
&& reg0[[13]] == 1), action=(ct_commit { ct_label.blocked = 0;
ct_label.label = reg3; }; next;)
> +  table=14(ls_in_stateful     ), priority=0    , match=(1),
action=(next;)
> +  table=14(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1
&& reg0[[13]] == 0), action=(ct_commit { ct_label.blocked = 0; }; next;)
> +  table=14(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1
&& reg0[[13]] == 1), action=(ct_commit { ct_label.blocked = 0;
ct_label.label = reg3; }; next;)
>  ])
>
>  AT_CHECK([grep -w "ls_out_acl" sw0flows | grep 2002 | sort], [0], [dnl
> @@ -4152,7 +4162,7 @@ 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.est &&
!ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0),
action=(reg0[[9]] = 0; reg0[[10]] = 0; 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;)
>  ])
> @@ -4173,7 +4183,7 @@ 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=(ct.est &&
!ct.rel && !ct.new && ct.rpl && ct_label.blocked == 0), action=(reg0[[9]] =
0; reg0[[10]] = 0; next;)
>    table=9 (ls_in_acl          ), priority=65532, match=(nd || nd_ra ||
nd_rs || mldv1 || mldv2), action=(next;)
>  ])
>
> @@ -4196,7 +4206,7 @@ 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.est &&
!ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0),
action=(reg0[[9]] = 0; reg0[[10]] = 0; 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;)
>  ])
> @@ -4368,20 +4378,20 @@ ovn-nbctl --wait=sb lsp-set-dhcpv4-options
sw0-port1 $CIDR_UUID
>  ovn-sbctl dump-flows sw0 > sw0flows
>  AT_CAPTURE_FILE([sw0flows])
>
> -AT_CHECK([grep -w "ls_in_dhcp_options" sw0flows | sort], [0], [dnl
> -  table=17(ls_in_dhcp_options ), priority=0    , match=(1),
action=(next;)
> -  table=17(ls_in_dhcp_options ), priority=100  , match=(inport ==
"sw0-port1" && eth.src == 50:54:00:00:00:01 && ip4.src == 0.0.0.0 &&
ip4.dst == 255.255.255.255 && udp.src == 68 && udp.dst == 67),
action=(reg0[[3]] = put_dhcp_opts(offerip = 10.0.0.2, hostname = "foo",
lease_time = 3600, netmask = 255.255.255.0, router = 10.0.0.1, server_id =
10.0.0.1); next;)
> -  table=17(ls_in_dhcp_options ), priority=100  , match=(inport ==
"sw0-port1" && eth.src == 50:54:00:00:00:01 && ip4.src == 10.0.0.2 &&
ip4.dst == {10.0.0.1, 255.255.255.255} && udp.src == 68 && udp.dst == 67),
action=(reg0[[3]] = put_dhcp_opts(offerip = 10.0.0.2, hostname = "foo",
lease_time = 3600, netmask = 255.255.255.0, router = 10.0.0.1, server_id =
10.0.0.1); next;)
> +AT_CHECK([grep -w "ls_in_dhcp_options" sw0flows | sort | sed
's/table=../table=??/'], [0], [dnl
> +  table=??(ls_in_dhcp_options ), priority=0    , match=(1),
action=(next;)
> +  table=??(ls_in_dhcp_options ), priority=100  , match=(inport ==
"sw0-port1" && eth.src == 50:54:00:00:00:01 && ip4.src == 0.0.0.0 &&
ip4.dst == 255.255.255.255 && udp.src == 68 && udp.dst == 67),
action=(reg0[[3]] = put_dhcp_opts(offerip = 10.0.0.2, hostname = "foo",
lease_time = 3600, netmask = 255.255.255.0, router = 10.0.0.1, server_id =
10.0.0.1); next;)
> +  table=??(ls_in_dhcp_options ), priority=100  , match=(inport ==
"sw0-port1" && eth.src == 50:54:00:00:00:01 && ip4.src == 10.0.0.2 &&
ip4.dst == {10.0.0.1, 255.255.255.255} && udp.src == 68 && udp.dst == 67),
action=(reg0[[3]] = put_dhcp_opts(offerip = 10.0.0.2, hostname = "foo",
lease_time = 3600, netmask = 255.255.255.0, router = 10.0.0.1, server_id =
10.0.0.1); next;)
>  ])
>
>  check ovn-nbctl --wait=sb lsp-set-options sw0-port1 hostname="\"port1\""
>  ovn-sbctl dump-flows sw0 > sw0flows
>  AT_CAPTURE_FILE([sw0flows])
>
> -AT_CHECK([grep -w "ls_in_dhcp_options" sw0flows | sort], [0], [dnl
> -  table=17(ls_in_dhcp_options ), priority=0    , match=(1),
action=(next;)
> -  table=17(ls_in_dhcp_options ), priority=100  , match=(inport ==
"sw0-port1" && eth.src == 50:54:00:00:00:01 && ip4.src == 0.0.0.0 &&
ip4.dst == 255.255.255.255 && udp.src == 68 && udp.dst == 67),
action=(reg0[[3]] = put_dhcp_opts(offerip = 10.0.0.2, hostname = "port1",
lease_time = 3600, netmask = 255.255.255.0, router = 10.0.0.1, server_id =
10.0.0.1); next;)
> -  table=17(ls_in_dhcp_options ), priority=100  , match=(inport ==
"sw0-port1" && eth.src == 50:54:00:00:00:01 && ip4.src == 10.0.0.2 &&
ip4.dst == {10.0.0.1, 255.255.255.255} && udp.src == 68 && udp.dst == 67),
action=(reg0[[3]] = put_dhcp_opts(offerip = 10.0.0.2, hostname = "port1",
lease_time = 3600, netmask = 255.255.255.0, router = 10.0.0.1, server_id =
10.0.0.1); next;)
> +AT_CHECK([grep -w "ls_in_dhcp_options" sw0flows | sort | sed
's/table=../table=??/'], [0], [dnl
> +  table=??(ls_in_dhcp_options ), priority=0    , match=(1),
action=(next;)
> +  table=??(ls_in_dhcp_options ), priority=100  , match=(inport ==
"sw0-port1" && eth.src == 50:54:00:00:00:01 && ip4.src == 0.0.0.0 &&
ip4.dst == 255.255.255.255 && udp.src == 68 && udp.dst == 67),
action=(reg0[[3]] = put_dhcp_opts(offerip = 10.0.0.2, hostname = "port1",
lease_time = 3600, netmask = 255.255.255.0, router = 10.0.0.1, server_id =
10.0.0.1); next;)
> +  table=??(ls_in_dhcp_options ), priority=100  , match=(inport ==
"sw0-port1" && eth.src == 50:54:00:00:00:01 && ip4.src == 10.0.0.2 &&
ip4.dst == {10.0.0.1, 255.255.255.255} && udp.src == 68 && udp.dst == 67),
action=(reg0[[3]] = put_dhcp_opts(offerip = 10.0.0.2, hostname = "port1",
lease_time = 3600, netmask = 255.255.255.0, router = 10.0.0.1, server_id =
10.0.0.1); next;)
>  ])
>
>  ovn-nbctl dhcp-options-set-options $CIDR_UUID  lease_time=3600
router=10.0.0.1   server_id=10.0.0.1   server_mac=c0:ff:ee:00:00:01
> @@ -4389,10 +4399,10 @@ check ovn-nbctl --wait=sb lsp-set-options
sw0-port1 hostname="\"bar\""
>  ovn-sbctl dump-flows sw0 > sw0flows
>  AT_CAPTURE_FILE([sw0flows])
>
> -AT_CHECK([grep -w "ls_in_dhcp_options" sw0flows | sort], [0], [dnl
> -  table=17(ls_in_dhcp_options ), priority=0    , match=(1),
action=(next;)
> -  table=17(ls_in_dhcp_options ), priority=100  , match=(inport ==
"sw0-port1" && eth.src == 50:54:00:00:00:01 && ip4.src == 0.0.0.0 &&
ip4.dst == 255.255.255.255 && udp.src == 68 && udp.dst == 67),
action=(reg0[[3]] = put_dhcp_opts(offerip = 10.0.0.2, hostname = "bar",
lease_time = 3600, netmask = 255.255.255.0, router = 10.0.0.1, server_id =
10.0.0.1); next;)
> -  table=17(ls_in_dhcp_options ), priority=100  , match=(inport ==
"sw0-port1" && eth.src == 50:54:00:00:00:01 && ip4.src == 10.0.0.2 &&
ip4.dst == {10.0.0.1, 255.255.255.255} && udp.src == 68 && udp.dst == 67),
action=(reg0[[3]] = put_dhcp_opts(offerip = 10.0.0.2, hostname = "bar",
lease_time = 3600, netmask = 255.255.255.0, router = 10.0.0.1, server_id =
10.0.0.1); next;)
> +AT_CHECK([grep -w "ls_in_dhcp_options" sw0flows | sort | sed
's/table=../table=??/'], [0], [dnl
> +  table=??(ls_in_dhcp_options ), priority=0    , match=(1),
action=(next;)
> +  table=??(ls_in_dhcp_options ), priority=100  , match=(inport ==
"sw0-port1" && eth.src == 50:54:00:00:00:01 && ip4.src == 0.0.0.0 &&
ip4.dst == 255.255.255.255 && udp.src == 68 && udp.dst == 67),
action=(reg0[[3]] = put_dhcp_opts(offerip = 10.0.0.2, hostname = "bar",
lease_time = 3600, netmask = 255.255.255.0, router = 10.0.0.1, server_id =
10.0.0.1); next;)
> +  table=??(ls_in_dhcp_options ), priority=100  , match=(inport ==
"sw0-port1" && eth.src == 50:54:00:00:00:01 && ip4.src == 10.0.0.2 &&
ip4.dst == {10.0.0.1, 255.255.255.255} && udp.src == 68 && udp.dst == 67),
action=(reg0[[3]] = put_dhcp_opts(offerip = 10.0.0.2, hostname = "bar",
lease_time = 3600, netmask = 255.255.255.0, router = 10.0.0.1, server_id =
10.0.0.1); next;)
>  ])
>
>  AT_CLEANUP
> @@ -6249,3 +6259,170 @@ check_log_flows_count 0 in
>
>  AT_CLEANUP
>  ])
> +
> +AT_SETUP([ACLs after lb])
> +AT_KEYWORDS([acl])
> +ovn_start
> +
> +check ovn-nbctl --wait=sb \
> +    -- ls-add ls \
> +    -- lsp-add ls lsp
> +
> +check ovn-nbctl pg-add pg0 lsp
> +
> +check ovn-nbctl acl-add pg0 from-lport 1004 "ip4 && ip4.dst == 10.0.0.2"
drop
> +check ovn-nbctl acl-add pg0 from-lport 1002 "ip4 && tcp" allow-related
> +check ovn-nbctl acl-add pg0 from-lport 1003 "ip4 && icmp" allow-related
> +check ovn-nbctl acl-add pg0 from-lport 1001 "ip4" drop
> +
> +check ovn-nbctl lb-add lb0 10.0.0.2 10.0.0.10
> +check ovn-nbctl ls-lb-add ls lb0
> +check ovn-nbctl --wait=sb sync
> +
> +ovn-sbctl dump-flows ls > lsflows
> +AT_CAPTURE_FILE([lsflows])
> +
> +AT_CHECK([grep -e "ls_in_acl" lsflows | sed 's/table=../table=??/' |
sort], [0], [dnl
> +  table=??(ls_in_acl          ), priority=0    , match=(1),
action=(next;)
> +  table=??(ls_in_acl          ), priority=1    , match=(ip && (!ct.est
|| (ct.est && ct_label.blocked == 1))), action=(reg0[[1]] = 1; next;)
> +  table=??(ls_in_acl          ), priority=2001 , match=(reg0[[10]] == 1
&& (ip4)), action=(ct_commit { ct_label.blocked = 1; }; /* drop */)
> +  table=??(ls_in_acl          ), priority=2001 , match=(reg0[[9]] == 1
&& (ip4)), action=(/* drop */)
> +  table=??(ls_in_acl          ), priority=2002 , match=(reg0[[7]] == 1
&& (ip4 && tcp)), action=(reg0[[1]] = 1; next;)
> +  table=??(ls_in_acl          ), priority=2002 , match=(reg0[[8]] == 1
&& (ip4 && tcp)), action=(next;)
> +  table=??(ls_in_acl          ), priority=2003 , match=(reg0[[7]] == 1
&& (ip4 && icmp)), action=(reg0[[1]] = 1; next;)
> +  table=??(ls_in_acl          ), priority=2003 , match=(reg0[[8]] == 1
&& (ip4 && icmp)), action=(next;)
> +  table=??(ls_in_acl          ), priority=2004 , match=(reg0[[10]] == 1
&& (ip4 && ip4.dst == 10.0.0.2)), action=(ct_commit { ct_label.blocked = 1;
}; /* drop */)
> +  table=??(ls_in_acl          ), priority=2004 , match=(reg0[[9]] == 1
&& (ip4 && ip4.dst == 10.0.0.2)), action=(/* drop */)
> +  table=??(ls_in_acl          ), priority=34000, match=(eth.dst ==
$svc_monitor_mac), action=(next;)
> +  table=??(ls_in_acl          ), priority=65532, match=(!ct.est &&
ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;)
> +  table=??(ls_in_acl          ), priority=65532, match=(ct.est &&
!ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0),
action=(reg0[[9]] = 0; reg0[[10]] = 0; next;)
> +  table=??(ls_in_acl          ), priority=65532, match=(ct.inv ||
(ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;)
> +  table=??(ls_in_acl          ), priority=65532, match=(nd || nd_ra ||
nd_rs || mldv1 || mldv2), action=(next;)
> +  table=??(ls_in_acl_after_lb ), priority=0    , match=(1),
action=(next;)
> +  table=??(ls_in_acl_hint     ), priority=0    , match=(1),
action=(next;)
> +  table=??(ls_in_acl_hint     ), priority=1    , match=(ct.est &&
ct_label.blocked == 0), action=(reg0[[10]] = 1; next;)
> +  table=??(ls_in_acl_hint     ), priority=2    , match=(ct.est &&
ct_label.blocked == 1), action=(reg0[[9]] = 1; next;)
> +  table=??(ls_in_acl_hint     ), priority=3    , match=(!ct.est),
action=(reg0[[9]] = 1; next;)
> +  table=??(ls_in_acl_hint     ), priority=4    , match=(!ct.new &&
ct.est && !ct.rpl && ct_label.blocked == 0), action=(reg0[[8]] = 1;
reg0[[10]] = 1; next;)
> +  table=??(ls_in_acl_hint     ), priority=5    , match=(!ct.trk),
action=(reg0[[8]] = 1; reg0[[9]] = 1; next;)
> +  table=??(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=??(ls_in_acl_hint     ), priority=7    , match=(ct.new &&
!ct.est), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;)
> +])
> +
> +AT_CHECK([grep -e "ls_in_lb" lsflows | sed 's/table=../table=??/' |
sort], [0], [dnl
> +  table=??(ls_in_lb           ), priority=0    , match=(1),
action=(next;)
> +  table=??(ls_in_lb           ), priority=110  , match=(ct.new &&
ip4.dst == 10.0.0.2), action=(reg0[[1]] = 0; reg1 = 10.0.0.2;
ct_lb(backends=10.0.0.10);)
> +])
> +
> +AT_CHECK([grep -e "ls_in_stateful" lsflows | sed 's/table=../table=??/'
| sort], [0], [dnl
> +  table=??(ls_in_stateful     ), priority=0    , match=(1),
action=(next;)
> +  table=??(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1
&& reg0[[13]] == 0), action=(ct_commit { ct_label.blocked = 0; }; next;)
> +  table=??(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1
&& reg0[[13]] == 1), action=(ct_commit { ct_label.blocked = 0;
ct_label.label = reg3; }; next;)
> +])
> +
> +AS_BOX([Remove and add the ACLs back with the apply-after-lb option])
> +
> +check ovn-nbctl clear port_group . acls
> +
> +check ovn-nbctl --apply-after-lb acl-add pg0 from-lport 1004 "ip4 &&
ip4.dst == 10.0.0.2" drop
> +check ovn-nbctl --apply-after-lb acl-add pg0 from-lport 1002 "ip4 &&
tcp" allow-related
> +check ovn-nbctl --apply-after-lb acl-add pg0 from-lport 1003 "ip4 &&
icmp" allow-related
> +check ovn-nbctl --apply-after-lb acl-add pg0 from-lport 1001 "ip4" drop
> +
> +check ovn-nbctl --wait=sb sync
> +
> +ovn-sbctl dump-flows ls > lsflows
> +AT_CAPTURE_FILE([lsflows])
> +
> +AT_CHECK([grep -e "ls_in_acl" lsflows | sed 's/table=../table=??/' |
sort], [0], [dnl
> +  table=??(ls_in_acl          ), priority=0    , match=(1),
action=(next;)
> +  table=??(ls_in_acl          ), priority=1    , match=(ip && (!ct.est
|| (ct.est && ct_label.blocked == 1))), action=(reg0[[1]] = 1; next;)
> +  table=??(ls_in_acl          ), priority=34000, match=(eth.dst ==
$svc_monitor_mac), action=(next;)
> +  table=??(ls_in_acl          ), priority=65532, match=(!ct.est &&
ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;)
> +  table=??(ls_in_acl          ), priority=65532, match=(ct.est &&
!ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0),
action=(reg0[[9]] = 0; reg0[[10]] = 0; next;)
> +  table=??(ls_in_acl          ), priority=65532, match=(ct.inv ||
(ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;)
> +  table=??(ls_in_acl          ), priority=65532, match=(nd || nd_ra ||
nd_rs || mldv1 || mldv2), action=(next;)
> +  table=??(ls_in_acl_after_lb ), priority=0    , match=(1),
action=(next;)
> +  table=??(ls_in_acl_after_lb ), priority=2001 , match=(reg0[[10]] == 1
&& (ip4)), action=(ct_commit { ct_label.blocked = 1; }; /* drop */)
> +  table=??(ls_in_acl_after_lb ), priority=2001 , match=(reg0[[9]] == 1
&& (ip4)), action=(/* drop */)
> +  table=??(ls_in_acl_after_lb ), priority=2002 , match=(reg0[[7]] == 1
&& (ip4 && tcp)), action=(reg0[[1]] = 1; next;)
> +  table=??(ls_in_acl_after_lb ), priority=2002 , match=(reg0[[8]] == 1
&& (ip4 && tcp)), action=(next;)
> +  table=??(ls_in_acl_after_lb ), priority=2003 , match=(reg0[[7]] == 1
&& (ip4 && icmp)), action=(reg0[[1]] = 1; next;)
> +  table=??(ls_in_acl_after_lb ), priority=2003 , match=(reg0[[8]] == 1
&& (ip4 && icmp)), action=(next;)
> +  table=??(ls_in_acl_after_lb ), priority=2004 , match=(reg0[[10]] == 1
&& (ip4 && ip4.dst == 10.0.0.2)), action=(ct_commit { ct_label.blocked = 1;
}; /* drop */)
> +  table=??(ls_in_acl_after_lb ), priority=2004 , match=(reg0[[9]] == 1
&& (ip4 && ip4.dst == 10.0.0.2)), action=(/* drop */)
> +  table=??(ls_in_acl_hint     ), priority=0    , match=(1),
action=(next;)
> +  table=??(ls_in_acl_hint     ), priority=1    , match=(ct.est &&
ct_label.blocked == 0), action=(reg0[[10]] = 1; next;)
> +  table=??(ls_in_acl_hint     ), priority=2    , match=(ct.est &&
ct_label.blocked == 1), action=(reg0[[9]] = 1; next;)
> +  table=??(ls_in_acl_hint     ), priority=3    , match=(!ct.est),
action=(reg0[[9]] = 1; next;)
> +  table=??(ls_in_acl_hint     ), priority=4    , match=(!ct.new &&
ct.est && !ct.rpl && ct_label.blocked == 0), action=(reg0[[8]] = 1;
reg0[[10]] = 1; next;)
> +  table=??(ls_in_acl_hint     ), priority=5    , match=(!ct.trk),
action=(reg0[[8]] = 1; reg0[[9]] = 1; next;)
> +  table=??(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=??(ls_in_acl_hint     ), priority=7    , match=(ct.new &&
!ct.est), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;)
> +])
> +
> +AT_CHECK([grep -e "ls_in_lb" lsflows | sed 's/table=../table=??/' |
sort], [0], [dnl
> +  table=??(ls_in_lb           ), priority=0    , match=(1),
action=(next;)
> +  table=??(ls_in_lb           ), priority=110  , match=(ct.new &&
ip4.dst == 10.0.0.2), action=(reg0[[1]] = 0; reg1 = 10.0.0.2;
ct_lb(backends=10.0.0.10);)
> +])
> +
> +AT_CHECK([grep -e "ls_in_stateful" lsflows | sed 's/table=../table=??/'
| sort], [0], [dnl
> +  table=??(ls_in_stateful     ), priority=0    , match=(1),
action=(next;)
> +  table=??(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1
&& reg0[[13]] == 0), action=(ct_commit { ct_label.blocked = 0; }; next;)
> +  table=??(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1
&& reg0[[13]] == 1), action=(ct_commit { ct_label.blocked = 0;
ct_label.label = reg3; }; next;)
> +])
> +
> +AS_BOX([Remove and add the ACLs back with a few ACLs with apply-after-lb
option])
> +
> +check ovn-nbctl clear port_group . acls
> +
> +check ovn-nbctl --apply-after-lb acl-add pg0 from-lport 1004 "ip4 &&
ip4.dst == 10.0.0.2" drop
> +check ovn-nbctl acl-add pg0 from-lport 1002 "ip4 && tcp" allow-related
> +check ovn-nbctl acl-add pg0 from-lport 1003 "ip4 && icmp" allow-related
> +check ovn-nbctl --apply-after-lb acl-add pg0 from-lport 1001 "ip4" drop
> +
> +check ovn-nbctl --wait=sb sync
> +
> +ovn-sbctl dump-flows ls > lsflows
> +AT_CAPTURE_FILE([lsflows])
> +
> +AT_CHECK([grep -e "ls_in_acl" lsflows | sed 's/table=../table=??/' |
sort], [0], [dnl
> +  table=??(ls_in_acl          ), priority=0    , match=(1),
action=(next;)
> +  table=??(ls_in_acl          ), priority=1    , match=(ip && (!ct.est
|| (ct.est && ct_label.blocked == 1))), action=(reg0[[1]] = 1; next;)
> +  table=??(ls_in_acl          ), priority=2002 , match=(reg0[[7]] == 1
&& (ip4 && tcp)), action=(reg0[[1]] = 1; next;)
> +  table=??(ls_in_acl          ), priority=2002 , match=(reg0[[8]] == 1
&& (ip4 && tcp)), action=(next;)
> +  table=??(ls_in_acl          ), priority=2003 , match=(reg0[[7]] == 1
&& (ip4 && icmp)), action=(reg0[[1]] = 1; next;)
> +  table=??(ls_in_acl          ), priority=2003 , match=(reg0[[8]] == 1
&& (ip4 && icmp)), action=(next;)
> +  table=??(ls_in_acl          ), priority=34000, match=(eth.dst ==
$svc_monitor_mac), action=(next;)
> +  table=??(ls_in_acl          ), priority=65532, match=(!ct.est &&
ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;)
> +  table=??(ls_in_acl          ), priority=65532, match=(ct.est &&
!ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0),
action=(reg0[[9]] = 0; reg0[[10]] = 0; next;)
> +  table=??(ls_in_acl          ), priority=65532, match=(ct.inv ||
(ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;)
> +  table=??(ls_in_acl          ), priority=65532, match=(nd || nd_ra ||
nd_rs || mldv1 || mldv2), action=(next;)
> +  table=??(ls_in_acl_after_lb ), priority=0    , match=(1),
action=(next;)
> +  table=??(ls_in_acl_after_lb ), priority=2001 , match=(reg0[[10]] == 1
&& (ip4)), action=(ct_commit { ct_label.blocked = 1; }; /* drop */)
> +  table=??(ls_in_acl_after_lb ), priority=2001 , match=(reg0[[9]] == 1
&& (ip4)), action=(/* drop */)
> +  table=??(ls_in_acl_after_lb ), priority=2004 , match=(reg0[[10]] == 1
&& (ip4 && ip4.dst == 10.0.0.2)), action=(ct_commit { ct_label.blocked = 1;
}; /* drop */)
> +  table=??(ls_in_acl_after_lb ), priority=2004 , match=(reg0[[9]] == 1
&& (ip4 && ip4.dst == 10.0.0.2)), action=(/* drop */)
> +  table=??(ls_in_acl_hint     ), priority=0    , match=(1),
action=(next;)
> +  table=??(ls_in_acl_hint     ), priority=1    , match=(ct.est &&
ct_label.blocked == 0), action=(reg0[[10]] = 1; next;)
> +  table=??(ls_in_acl_hint     ), priority=2    , match=(ct.est &&
ct_label.blocked == 1), action=(reg0[[9]] = 1; next;)
> +  table=??(ls_in_acl_hint     ), priority=3    , match=(!ct.est),
action=(reg0[[9]] = 1; next;)
> +  table=??(ls_in_acl_hint     ), priority=4    , match=(!ct.new &&
ct.est && !ct.rpl && ct_label.blocked == 0), action=(reg0[[8]] = 1;
reg0[[10]] = 1; next;)
> +  table=??(ls_in_acl_hint     ), priority=5    , match=(!ct.trk),
action=(reg0[[8]] = 1; reg0[[9]] = 1; next;)
> +  table=??(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=??(ls_in_acl_hint     ), priority=7    , match=(ct.new &&
!ct.est), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;)
> +])
> +
> +AT_CHECK([grep -e "ls_in_lb" lsflows | sed 's/table=../table=??/' |
sort], [0], [dnl
> +  table=??(ls_in_lb           ), priority=0    , match=(1),
action=(next;)
> +  table=??(ls_in_lb           ), priority=110  , match=(ct.new &&
ip4.dst == 10.0.0.2), action=(reg0[[1]] = 0; reg1 = 10.0.0.2;
ct_lb(backends=10.0.0.10);)
> +])
> +
> +AT_CHECK([grep -e "ls_in_stateful" lsflows | sed 's/table=../table=??/'
| sort], [0], [dnl
> +  table=??(ls_in_stateful     ), priority=0    , match=(1),
action=(next;)
> +  table=??(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1
&& reg0[[13]] == 0), action=(ct_commit { ct_label.blocked = 0; }; next;)
> +  table=??(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1
&& reg0[[13]] == 1), action=(ct_commit { ct_label.blocked = 0;
ct_label.label = reg3; }; next;)
> +])
> +
> +AT_CLEANUP
> +])
> diff --git a/tests/ovn.at b/tests/ovn.at
> index 69270601ab..a19ab4651d 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -3393,8 +3393,8 @@ wait_for_ports_up
>  ovn-sbctl dump-flows ls > lsflows
>  AT_CAPTURE_FILE([lsflows])
>
> -AT_CHECK([grep -w "ls_in_arp_rsp" lsflows | sort], [0], [dnl
> -  table=16(ls_in_arp_rsp      ), priority=0    , match=(1),
action=(next;)
> +AT_CHECK([grep -w "ls_in_arp_rsp" lsflows | sed 's/table=../table=??/' |
sort], [0], [dnl
> +  table=??(ls_in_arp_rsp      ), priority=0    , match=(1),
action=(next;)
>  ])
>
>  for i in 1 2; do
> @@ -3456,8 +3456,8 @@ wait_for_ports_up
>  ovn-sbctl dump-flows ls > lsflows
>  AT_CAPTURE_FILE([lsflows])
>
> -AT_CHECK([grep -w "ls_in_arp_rsp" lsflows | sort], [0], [dnl
> -  table=16(ls_in_arp_rsp      ), priority=0    , match=(1),
action=(next;)
> +AT_CHECK([grep -w "ls_in_arp_rsp" lsflows | sed 's/table=../table=??/' |
sort], [0], [dnl
> +  table=??(ls_in_arp_rsp      ), priority=0    , match=(1),
action=(next;)
>  ])
>
>  test_nd_na() {
> @@ -14457,7 +14457,7 @@ ovn-sbctl dump-flows sw0 > sw0-flows
>  AT_CAPTURE_FILE([sw0-flows])
>
>  AT_CHECK([grep -E 'ls_(in|out)_acl' sw0-flows |grep reject| sed
's/table=../table=??/' | sort], [0], [dnl
> -  table=??(ls_out_acl         ), priority=2002 , match=(ip),
action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is
implicit. */ outport <-> inport; next(pipeline=ingress,table=22); };)
> +  table=??(ls_out_acl         ), priority=2002 , match=(ip),
action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is
implicit. */ outport <-> inport; next(pipeline=ingress,table=24); };)
>  ])
>
>
> @@ -16348,17 +16348,17 @@ ovs-vsctl set open .
external-ids:ovn-bridge-mappings=phys:br-phys
>  AT_CHECK([ovn-sbctl dump-flows ls1 | grep "offerip = 10.0.0.6" | \
>  wc -l], [0], [0
>  ])
> -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=23 | \
> +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=25 | \
>  grep controller | grep "0a.00.00.06" | wc -l], [0], [0
>  ])
> -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=23 | \
> +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=25 | \
>  grep controller | grep "0a.00.00.06" | wc -l], [0], [0
>  ])
> -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=23 | \
> +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=25 | \
>  grep controller | grep tp_src=546 | grep \
>  "ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0
>  ])
> -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=23 | \
> +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=25 | \
>  grep controller | grep tp_src=546 | grep \
>  "ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0
>  ])
> @@ -16966,7 +16966,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=29,dl_src=f0:00:00:00:00:03,dl_dst=a0:10:00:00:00:01 | \
> +table=31,dl_src=f0:00:00:00:00:03,dl_dst=a0:10:00:00:00:01 | \
>  grep -c "actions=drop"], [0], [1
>  ])
>
> @@ -18643,7 +18643,7 @@ check_row_count Port_Binding 1
logical_port=sw0-vir virtual_parent=sw0-p1
>  wait_for_ports_up sw0-vir
>  check ovn-nbctl --wait=hv sync
>  AT_CHECK([test 2 = `cat hv1/ovn-controller.log | grep "pinctrl received
 packet-in" | \
> -grep opcode=BIND_VPORT | grep OF_Table_ID=24 | wc -l`])
> +grep opcode=BIND_VPORT | grep OF_Table_ID=26 | wc -l`])
>
>  wait_row_count Port_Binding 1 logical_port=sw0-vir6 chassis=$hv1_ch_uuid
>  check_row_count Port_Binding 1 logical_port=sw0-vir6
virtual_parent=sw0-p1
> @@ -18692,7 +18692,7 @@ eth_dst=00000000ff01
>  ip_src=$(ip_to_hex 10 0 0 10)
>  ip_dst=$(ip_to_hex 172 168 0 101)
>  send_icmp_packet 1 1 $eth_src $eth_dst $ip_src $ip_dst c4c9
0000000000000000000000
> -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | awk '/table=26,
n_packets=1, n_bytes=45/{print $7" "$8}'],[0],[dnl
> +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int metadata=0x$lr0_dp_key |
awk '/table=26, n_packets=1, n_bytes=45/{print $7" "$8}'],[0],[dnl
>  priority=80,ip,reg15=0x3,metadata=0x3,nw_src=10.0.0.10 actions=drop
>  ])
>
> @@ -21832,7 +21832,7 @@ OVS_WAIT_FOR_OUTPUT(
>    (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");)
> +  (ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst ==
10.0.0.10 && tcp.dst == 80), action=(reg0[[1]] = 0; 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])
> @@ -21874,7 +21874,7 @@ ovn-sbctl dump-flows sw0 > sbflows3
>  AT_CHECK(
>    [grep "ip4.dst == 10.0.0.10 && tcp.dst == 80" sbflows3 | grep
priority=120 |\
>     sed 's/table=../table=??/'], [0],
> -  [  table=??(ls_in_stateful     ), priority=120  , match=(ct.new &&
ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(drop;)
> +  [  table=??(ls_in_lb           ), priority=120  , match=(ct.new &&
ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(drop;)
>  ])
>
>  AT_CAPTURE_FILE([sbflows4])
> @@ -28772,8 +28772,8 @@ options arp_proxy='"169.254.239.254
169.254.239.2"'
>  ovn-sbctl dump-flows > sbflows
>  AT_CAPTURE_FILE([sbflows])
>
> -AT_CHECK([ovn-sbctl dump-flows | grep ls_in_arp_rsp | grep
"169.254.239.2"], [0], [dnl
> -  table=16(ls_in_arp_rsp      ), priority=50   , match=(arp.op == 1 &&
arp.tpa == {169.254.239.254,169.254.239.2}), dnl
> +AT_CHECK([ovn-sbctl dump-flows | grep ls_in_arp_rsp | grep
"169.254.239.2" | sed 's/table=../table=??/'], [0], [dnl
> +  table=??(ls_in_arp_rsp      ), priority=50   , match=(arp.op == 1 &&
arp.tpa == {169.254.239.254,169.254.239.2}), dnl
>  action=(eth.dst = eth.src; eth.src = 00:00:00:01:02:f1; arp.op = 2; /*
ARP reply */ arp.tha = arp.sha; arp.sha = 00:00:00:01:02:f1; arp.tpa <->
arp.spa; outport = inport; flags.loopback = 1; output;)
>  ])
>
> @@ -29254,26 +29254,26 @@ done
>  check ovn-nbctl --wait=hv sync
>
>  # hv0 should see flows for lsp1 but not lsp2
> -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=24 | grep 10.0.1.2],
[0], [ignore])
> -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=24 | grep 10.0.2.2],
[1])
> +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=26 | grep 10.0.1.2],
[0], [ignore])
> +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=26 | grep 10.0.2.2],
[1])
>  # hv2 should see flows for lsp2 but not lsp1
> -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=24 | grep 10.0.2.2],
[0], [ignore])
> -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=24 | grep 10.0.1.2],
[1])
> +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=26 | grep 10.0.2.2],
[0], [ignore])
> +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=26 | grep 10.0.1.2],
[1])
>
>  # Change lrp_lr_ls1 to a regular lrp, hv2 should see flows for lsp1
>  check ovn-nbctl --wait=hv lrp-del-gateway-chassis lrp_lr_ls1 hv1
> -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=24 | grep 10.0.1.2],
[0], [ignore])
> +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=26 | grep 10.0.1.2],
[0], [ignore])
>
>  # Change it back, and trigger recompute to make sure extra flows are
removed
>  # from hv2 (recompute is needed because currently I-P adds local
datapaths but
>  # doesn't remove.)
>  check ovn-nbctl --wait=hv lrp-set-gateway-chassis lrp_lr_ls1 hv1 1
>  as hv2 check ovn-appctl -t ovn-controller recompute
> -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=24 | grep 10.0.1.2],
[1])
> +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=26 | grep 10.0.1.2],
[1])
>
>  # Enable dnat_and_snat on lr, and now hv2 should see flows for lsp1.
>  AT_CHECK([ovn-nbctl --wait=hv lr-nat-add lr dnat_and_snat 192.168.0.1
10.0.1.3 lsp1 f0:00:00:00:00:03])
> -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=24 | grep 10.0.1.2],
[0], [ignore])
> +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=26 | grep 10.0.1.2],
[0], [ignore])
>
>  OVN_CLEANUP([hv1],[hv2])
>  AT_CLEANUP
> diff --git a/tests/system-ovn.at b/tests/system-ovn.at
> index f57d752d44..d4c22e7e90 100644
> --- a/tests/system-ovn.at
> +++ b/tests/system-ovn.at
> @@ -1448,6 +1448,32 @@ OVS_START_L7([bar1], [http])
>  OVS_START_L7([bar2], [http])
>  OVS_START_L7([bar3], [http])
>
> +# Add ACLs (after lb) to drop the traffic if destined to backend  ips.
> +check ovn-nbctl --apply-after-lb acl-add foo from-lport 1002 "ip4 &&
ip4.dst == {172.16.1.2,172.16.1.3,172.16.1.4} && ct.new" drop
> +check ovn-nbctl --wait=hv sync
> +
> +AT_CHECK([ip netns exec foo1 wget 30.0.0.1 -t 3 -T 1], [4], [ignore],
[ignore])
> +
> +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
> +
> +# Clear the apply-after-lb option.  The traffic will be allowed.
> +check ovn-nbctl clear acl . options
> +ovn-nbctl --wait=hv sync
> +
> +OVS_WAIT_FOR_OUTPUT([
> +    for i in `seq 1 20`; do
> +        ip netns exec foo1 wget 30.0.0.1 -t 5 -T 1 --retry-connrefused
-v -o wget$i.log;
> +    done
> +    ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | \
> +      sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>
+tcp,orig=(src=192.168.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.2,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,labels=0x2,protoinfo=(state=<cleared>)
>
+tcp,orig=(src=192.168.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.3,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,labels=0x2,protoinfo=(state=<cleared>)
>
+tcp,orig=(src=192.168.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.4,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,labels=0x2,protoinfo=(state=<cleared>)
> +])
> +
> +ovn-nbctl acl-del foo from-lport 1002 "ip4 && ip4.dst ==
{172.16.1.2,172.16.1.3,172.16.1.4} && ct.new"
> +ovn-nbctl --wait=hv sync
> +
>  dnl Should work with the virtual IP 30.0.0.1 address through NAT
>  dnl Each server should have at least one connection.
>  dnl With 20 requests, one server might not receive any connection
> @@ -4895,6 +4921,247 @@ aef0::3 udp port 90" | uniq | wc -l)
>  ])
>
>
> +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
> +])
> +
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([ACL after lb - reject])
> +AT_SKIP_IF([test $HAVE_NC = no])
> +AT_KEYWORDS([lb])
> +
> +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
> +
> +ovn-nbctl ls-add sw0
> +
> +ovn-nbctl lsp-add sw0 sw0-p1-rej
> +ovn-nbctl lsp-set-addresses sw0-p1-rej "50:54:00:00:00:03 10.0.0.3
aef0::3"
> +ovn-nbctl lsp-set-port-security sw0-p1-rej "50:54:00:00:00:03 10.0.0.3
aef0::3"
> +
> +ovn-nbctl lsp-add sw0 sw0-p2-rej
> +ovn-nbctl lsp-set-addresses sw0-p2-rej "50:54:00:00:00:04 10.0.0.4
aef0::4"
> +ovn-nbctl lsp-set-port-security sw0-p2-rej "50:54:00:00:00:04 10.0.0.4
aef0::4"
> +
> +# Create port group and ACLs for sw0 ports.
> +ovn-nbctl pg-add pg0_drop sw0-p1-rej sw0-p2-rej
> +ovn-nbctl --apply-after-lb acl-add pg0_drop from-lport 1001 "inport ==
@pg0_drop && ip" drop
> +ovn-nbctl acl-add pg0_drop to-lport 1001 "outport == @pg0_drop && ip"
drop
> +
> +ovn-nbctl pg-add pg0 sw0-p1-rej sw0-p2-rej
> +ovn-nbctl --apply-after-lb acl-add pg0 from-lport 1002 "inport == @pg0
&& ip" allow-related
> +ovn-nbctl --log --apply-after-lb acl-add pg0 from-lport 1004 "inport ==
@pg0 && ip && tcp && tcp.dst == 80" reject
> +ovn-nbctl --log --apply-after-lb acl-add pg0 from-lport 1004 "inport ==
@pg0 && ip && udp && udp.dst == 90" reject
> +
> +ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && ip4.src
== 0.0.0.0/0 && tcp && tcp.dst == 82" allow-related
> +ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && ip4.src
== 0.0.0.0/0 && udp && udp.dst == 82" allow-related
> +ovn-nbctl --log acl-add pg0 to-lport 1004 "inport == @pg0 && ip && tcp
&& tcp.dst == 84" reject
> +ovn-nbctl --log acl-add pg0 to-lport 1004 "inport == @pg0 && ip && udp
&& udp.dst == 94" reject
> +
> +ovn-nbctl ls-add sw1
> +ovn-nbctl lsp-add sw1 sw1-p1-rej
> +ovn-nbctl lsp-set-addresses sw1-p1-rej "40:54:00:00:00:03 20.0.0.3"
> +ovn-nbctl lsp-set-port-security sw1-p1-rej "40:54:00:00:00:03 20.0.0.3"
> +
> +ovn-nbctl lr-add lr0
> +ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24
> +ovn-nbctl lsp-add sw0 sw0-lr0
> +ovn-nbctl lsp-set-type sw0-lr0 router
> +ovn-nbctl lsp-set-addresses sw0-lr0 router
> +ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
> +
> +ovn-nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 20.0.0.1/24
> +ovn-nbctl lsp-add sw1 sw1-lr0
> +ovn-nbctl lsp-set-type sw1-lr0 router
> +ovn-nbctl lsp-set-addresses sw1-lr0 router
> +ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1
> +
> +OVN_POPULATE_ARP
> +ovn-nbctl --wait=hv sync
> +
> +ADD_NAMESPACES(sw0-p1-rej)
> +ADD_VETH(sw0-p1-rej, sw0-p1-rej, br-int, "10.0.0.3/24",
"50:54:00:00:00:03", \
> +         "10.0.0.1")
> +
> +ADD_NAMESPACES(sw0-p2-rej)
> +ADD_VETH(sw0-p2-rej, sw0-p2-rej, br-int, "10.0.0.4/24",
"50:54:00:00:00:04", \
> +         "10.0.0.1")
> +
> +NS_CHECK_EXEC([sw0-p1-rej], [ip a a aef0::3/64 dev sw0-p1-rej], [0])
> +NS_CHECK_EXEC([sw0-p2-rej], [ip a a aef0::4/64 dev sw0-p2-rej], [0])
> +
> +ADD_NAMESPACES(sw1-p1-rej)
> +ADD_VETH(sw1-p1-rej, sw1-p1-rej, br-int, "20.0.0.3/24",
"40:54:00:00:00:03", \
> +         "20.0.0.1")
> +
> +sleep 1
> +
> +# Capture packets in sw0-p1-rej.
> +NS_CHECK_EXEC([sw0-p1-rej], [tcpdump -nn -c 4 -i sw0-p1-rej tcp >
sw0-p1-rej-ip4.pcap &], [0])
> +
> +sleep 1
> +
> +OVS_WAIT_UNTIL([
> +    ip netns exec sw0-p1-rej nc -vz 10.0.0.4 80 2>&1 | grep -i
'connection refused'
> +])
> +
> +# Now send traffic to port 84
> +OVS_WAIT_UNTIL([
> +    ip netns exec sw0-p1-rej nc -vz 10.0.0.4 84 2>&1 | grep -i
'connection refused'
> +])
> +
> +OVS_WAIT_UNTIL([
> +    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
> +])
> +
> +OVS_WAIT_UNTIL([
> +    total=`cat sw0-p1-rej-ip4.pcap |  wc -l`
> +    echo "total = $total"
> +    test "${total}" = "4"
> +])
> +
> +# Without this sleep, test case fails intermittently.
> +sleep 3
> +
> +NS_CHECK_EXEC([sw0-p2-rej], [tcpdump -nn -c 2 -i sw0-p2-rej tcp port 80
> sw0-p2-rej-ip6.pcap &], [0])
> +
> +sleep 1
> +
> +OVS_WAIT_UNTIL([
> +    ip netns exec sw0-p2-rej nc -vz6 aef0::3 80 2>&1 | grep -i
'connection refused'
> +])
> +
> +
> +OVS_WAIT_UNTIL([
> +    total=`cat sw0-p2-rej-ip6.pcap |  wc -l`
> +    echo "total = $total"
> +    test "${total}" = "2"
> +])
> +
> +ovn-nbctl --apply-after-lb acl-add sw1 from-lport 1004 "ip" allow-related
> +ovn-nbctl acl-add sw1 to-lport 1004 "ip" allow-related
> +ovn-nbctl --log acl-add pg0 to-lport 1004 "outport == @pg0 && ip && tcp
&& tcp.dst == 84" reject
> +
> +OVS_WAIT_UNTIL([
> +    ip netns exec sw1-p1-rej nc -vz 10.0.0.4 84 2>&1 | grep -i
'connection refused'
> +])
> +
> +# Now test for IPv4 UDP.
> +NS_CHECK_EXEC([sw0-p1-rej], [tcpdump -nn -c 1 -i sw0-p1-rej udp port 90
> sw0-p1-rej-udp.pcap &], [0])
> +NS_CHECK_EXEC([sw0-p1-rej], [tcpdump -nn -c 1 -i sw0-p1-rej icmp >
sw0-p1-rej-icmp.pcap &], [0])
> +
> +printf '.%.0s' {1..100} > foo
> +OVS_WAIT_UNTIL([
> +    ip netns exec sw0-p1-rej nc -u 10.0.0.4 90 < foo
> +    c=$(cat sw0-p1-rej-icmp.pcap | grep \
> +"10.0.0.4 > 10.0.0.3: ICMP 10.0.0.4 udp port 90 unreachable" | uniq | wc
-l)
> +    test $c -eq 1
> +])
> +
> +rm -f *.pcap
> +
> +NS_CHECK_EXEC([sw0-p1-rej], [tcpdump -nn -c 1 -i sw0-p1-rej udp port 94
> sw0-p1-rej-udp.pcap &], [0])
> +NS_CHECK_EXEC([sw0-p1-rej], [tcpdump -nn -c 1 -i sw0-p1-rej icmp >
sw0-p1-rej-icmp.pcap &], [0])
> +
> +OVS_WAIT_UNTIL([
> +    ip netns exec sw0-p1-rej nc -u 10.0.0.4 94 < foo
> +    c=$(cat sw0-p1-rej-icmp.pcap | grep \
> +"10.0.0.4 > 10.0.0.3: ICMP 10.0.0.4 udp port 94 unreachable" | uniq | wc
-l)
> +    test $c -eq 1
> +])
> +
> +# Now test for IPv6 UDP.
> +NS_CHECK_EXEC([sw0-p2-rej], [tcpdump -nn -c 1 -i sw0-p2-rej udp port 90
> sw0-p2-rej-ip6-udp.pcap &], [0])
> +NS_CHECK_EXEC([sw0-p2-rej], [tcpdump -nn -c 1 -i sw0-p2-rej icmp6 >
sw0-p2-rej-icmp6.pcap &], [0])
> +
> +OVS_WAIT_UNTIL([
> +    ip netns exec sw0-p2-rej nc -u -6 aef0::3 90 < foo
> +    c=$(cat sw0-p2-rej-icmp6.pcap | grep \
> +"IP6 aef0::3 > aef0::4: ICMP6, destination unreachable, unreachable
port, \
> +aef0::3 udp port 90" | uniq | wc -l)
> +    test $c -eq 1
> +])
> +
> +rm -f *.pcap
> +
> +NS_CHECK_EXEC([sw0-p2-rej], [tcpdump -nn -c 1 -i sw0-p2-rej udp port 94
> sw0-p2-rej-ip6-udp.pcap &], [0])
> +NS_CHECK_EXEC([sw0-p2-rej], [tcpdump -nn -c 1 -i sw0-p2-rej icmp6 >
sw0-p2-rej-icmp6.pcap &], [0])
> +
> +OVS_WAIT_UNTIL([
> +    ip netns exec sw0-p2-rej nc -u -6 aef0::3 94 < foo
> +    c=$(cat sw0-p2-rej-icmp6.pcap | grep \
> +"IP6 aef0::3 > aef0::4: ICMP6, destination unreachable, unreachable
port, \
> +aef0::3 udp port 94" | uniq | wc -l)
> +    test $c -eq 1
> +])
> +
> +# Delete all the ACLs of pg0 and add the ACL with a generic match with
reject action.
> +ovn-nbctl pg-del pg0
> +ovn-nbctl pg-add pg0 sw0-p1-rej sw0-p2-rej
> +ovn-nbctl --log --apply-after-lb acl-add pg0 from-lport 1004 "inport ==
@pg0 && ip && (tcp || udp)" reject
> +
> +OVS_WAIT_UNTIL([
> +    ip netns exec sw0-p1-rej nc -vz 10.0.0.4 80 2>&1 | grep -i
'connection refused'
> +])
> +
> +OVS_WAIT_UNTIL([
> +    ip netns exec sw0-p2-rej nc -vz6 aef0::3 80 2>&1 | grep -i
'connection refused'
> +])
> +
> +rm -f *.pcap
> +
> +NS_CHECK_EXEC([sw0-p1-rej], [tcpdump -nn -c 1 -i sw0-p1-rej icmp >
sw0-p1-rej-icmp.pcap &], [0])
> +
> +printf '.%.0s' {1..100} > foo
> +OVS_WAIT_UNTIL([
> +    ip netns exec sw0-p1-rej nc -u 10.0.0.4 90 < foo
> +    c=$(cat sw0-p1-rej-icmp.pcap | grep \
> +"10.0.0.4 > 10.0.0.3: ICMP 10.0.0.4 udp port 90 unreachable" | uniq | wc
-l)
> +    test $c -eq 1
> +])
> +
> +rm -f *.pcap
> +# Now test for IPv6 UDP.
> +NS_CHECK_EXEC([sw0-p2-rej], [tcpdump -nn -c 1 -i sw0-p2-rej icmp6 >
sw0-p2-rej-icmp6.pcap &], [0])
> +
> +OVS_WAIT_UNTIL([
> +    ip netns exec sw0-p2-rej nc -u -6 aef0::3 90 < foo
> +    c=$(cat sw0-p2-rej-icmp6.pcap | grep \
> +"IP6 aef0::3 > aef0::4: ICMP6, destination unreachable, unreachable
port, \
> +aef0::3 udp port 90" | uniq | wc -l)
> +    test $c -eq 1
> +])
> +
> +
>  OVS_APP_EXIT_AND_WAIT([ovn-controller])
>
>  as ovn-sb
> @@ -6907,8 +7174,150 @@ AT_CLEANUP
>  ])
>
>  OVN_FOR_EACH_NORTHD([
> -AT_SETUP([ACL label - conntrack label change])
> -AT_KEYWORDS([acl label ct_commit label change])
> +AT_SETUP([ACL label - conntrack ct_label - acl after lb])
> +AT_KEYWORDS([acl label ct_commit])
> +
> +CHECK_CONNTRACK()
> +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:02 10.0.0.2"
> +check ovn-nbctl lsp-set-port-security sw0-p1 "50:54:00:00:00:02 10.0.0.2"
> +
> +check ovn-nbctl lsp-add sw0 sw0-p2
> +check ovn-nbctl lsp-set-addresses sw0-p2 "50:54:00:00:00:03 10.0.0.3"
> +check ovn-nbctl lsp-set-port-security sw0-p2 "50:54:00:00:00:03 10.0.0.3"
> +
> +check ovn-nbctl lsp-add sw0 sw0-p3
> +check ovn-nbctl lsp-set-addresses sw0-p3 "50:54:00:00:00:04 10.0.0.4"
> +check ovn-nbctl lsp-set-port-security sw0-p3 "50:54:00:00:00:04 10.0.0.4"
> +
> +# ACLs
> +# Case 1: sw0-p1 ---> sw0-p3 allowed, label=1234
> +# Case 2: sw0-p3 ---> sw0-p1 allowed, label=1235
> +# Case 3: sw0-p1 ---> sw0-p2 allowed, no label
> +# Case 4: sw0-p2 ---> sw0-p1 allowed, no label
> +
> +check ovn-nbctl --label=1234 --apply-after-lb acl-add sw0 from-lport
1002 'ip4 && inport == "sw0-p1" && ip4.dst == 10.0.0.4' allow-related
> +check ovn-nbctl --label=1235 acl-add sw0 to-lport 1002 'ip4 && outport
== "sw0-p1" && ip4.src == 10.0.0.4' allow-related
> +check ovn-nbctl --apply-after-lb acl-add sw0 from-lport 1001 "ip"
allow-related
> +check ovn-nbctl acl-add sw0 to-lport 1001 "ip" allow-related
> +
> +
> +ADD_NAMESPACES(sw0-p1)
> +ADD_VETH(sw0-p1, sw0-p1, br-int, "10.0.0.2/24", "50:54:00:00:00:02", \
> +         "10.0.0.1")
> +ADD_NAMESPACES(sw0-p2)
> +ADD_VETH(sw0-p2, sw0-p2, br-int, "10.0.0.3/24", "50:54:00:00:00:03", \
> +         "10.0.0.1")
> +ADD_NAMESPACES(sw0-p3)
> +ADD_VETH(sw0-p3, sw0-p3, br-int, "10.0.0.4/24", "50:54:00:00:00:04", \
> +         "10.0.0.1")
> +
> +# Ensure ovn-controller is caught up
> +ovn-nbctl --wait=hv sync
> +
> +on_exit 'ovn-nbctl acl-list sw0'
> +on_exit 'ovn-sbctl lflow-list'
> +on_exit 'ovs-ofctl dump-flows br-int'
> +
> +wait_for_ports_up
> +
> +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
> +# 'sw0-p1' should be able to ping 'sw0-p3'.
> +NS_CHECK_EXEC([sw0-p1], [ping -q -c 10 -i 0.3 -w 15 10.0.0.4 |
FORMAT_PING], \
> +[0], [dnl
> +10 packets transmitted, 10 received, 0% packet loss, time 0ms
> +])
> +
> +# Ensure conntrack entry is present and ct_label is set.
> +AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.0.0.4) | \
> +sed -e 's/zone=[[0-9]]*/zone=<cleared>/' | \
> +sed -e
's/labels=0x4d2[[0-9a-f]]*/labels=0x4d2000000000000000000000000/'], [0],
[dnl
>
+icmp,orig=(src=10.0.0.2,dst=10.0.0.4,id=<cleared>,type=8,code=0),reply=(src=10.0.0.4,dst=10.0.0.2,id=<cleared>,type=0,code=0),zone=<cleared>,labels=0x4d2000000000000000000000000
>
+icmp,orig=(src=10.0.0.2,dst=10.0.0.4,id=<cleared>,type=8,code=0),reply=(src=10.0.0.4,dst=10.0.0.2,id=<cleared>,type=0,code=0),zone=<cleared>
> +])
> +
> +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
> +# 'sw0-p3' should be able to ping 'sw0-p1'.
> +NS_CHECK_EXEC([sw0-p3], [ping -q -c 10 -i 0.3 -w 15 10.0.0.2 |
FORMAT_PING], \
> +[0], [dnl
> +10 packets transmitted, 10 received, 0% packet loss, time 0ms
> +])
> +
> +# Ensure conntrack entry is present and ct_label is set.
> +AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.0.0.2) | \
> +sed -e 's/zone=[[0-9]]*/zone=<cleared>/' | \
> +sed -e
's/labels=0x4d3[[0-9a-f]]*/labels=0x4d3000000000000000000000000/'], [0],
[dnl
>
+icmp,orig=(src=10.0.0.4,dst=10.0.0.2,id=<cleared>,type=8,code=0),reply=(src=10.0.0.2,dst=10.0.0.4,id=<cleared>,type=0,code=0),zone=<cleared>,labels=0x4d3000000000000000000000000
>
+icmp,orig=(src=10.0.0.4,dst=10.0.0.2,id=<cleared>,type=8,code=0),reply=(src=10.0.0.2,dst=10.0.0.4,id=<cleared>,type=0,code=0),zone=<cleared>
> +])
> +
> +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
> +# 'sw0-p1' should be able to ping 'sw0-p2'.
> +NS_CHECK_EXEC([sw0-p1], [ping -q -c 10 -i 0.3 -w 15 10.0.0.3 |
FORMAT_PING], \
> +[0], [dnl
> +10 packets transmitted, 10 received, 0% packet loss, time 0ms
> +])
> +
> +# Ensure conntrack entry is present and ct_label is not set.
> +AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.0.0.3) | \
> +sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>
+icmp,orig=(src=10.0.0.2,dst=10.0.0.3,id=<cleared>,type=8,code=0),reply=(src=10.0.0.3,dst=10.0.0.2,id=<cleared>,type=0,code=0),zone=<cleared>
>
+icmp,orig=(src=10.0.0.2,dst=10.0.0.3,id=<cleared>,type=8,code=0),reply=(src=10.0.0.3,dst=10.0.0.2,id=<cleared>,type=0,code=0),zone=<cleared>
> +])
> +
> +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
> +# 'sw0-p2' should be able to ping 'sw0-p1'.
> +NS_CHECK_EXEC([sw0-p2], [ping -q -c 10 -i 0.3 -w 15 10.0.0.2 |
FORMAT_PING], \
> +[0], [dnl
> +10 packets transmitted, 10 received, 0% packet loss, time 0ms
> +])
> +
> +# Ensure conntrack entry is present and ct_label is not set.
> +AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.0.0.2) | \
> +sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>
+icmp,orig=(src=10.0.0.3,dst=10.0.0.2,id=<cleared>,type=8,code=0),reply=(src=10.0.0.2,dst=10.0.0.3,id=<cleared>,type=0,code=0),zone=<cleared>
>
+icmp,orig=(src=10.0.0.3,dst=10.0.0.2,id=<cleared>,type=8,code=0),reply=(src=10.0.0.2,dst=10.0.0.3,id=<cleared>,type=0,code=0),zone=<cleared>
> +])
> +
> +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
> +])
> +
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([ACL label - conntrack label change])
> +AT_KEYWORDS([acl label ct_commit label change])
>
>  CHECK_CONNTRACK()
>  ovn_start
> @@ -7007,6 +7416,207 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port
patch-.*/d
>  AT_CLEANUP
>  ])
>
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([ACL label - conntrack label change - acl after lb])
> +AT_KEYWORDS([acl label ct_commit label change])
> +
> +CHECK_CONNTRACK()
> +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:02 10.0.0.2"
> +check ovn-nbctl lsp-set-port-security sw0-p1 "50:54:00:00:00:02 10.0.0.2"
> +
> +check ovn-nbctl lsp-add sw0 sw0-p2
> +check ovn-nbctl lsp-set-addresses sw0-p2 "50:54:00:00:00:03 10.0.0.3"
> +check ovn-nbctl lsp-set-port-security sw0-p2 "50:54:00:00:00:03 10.0.0.3"
> +
> +# ACLs
> +# sw0-p1 ---> sw0-p2 allowed, label=1234
> +
> +check ovn-nbctl --label=1234 --apply-after-lb acl-add sw0 from-lport
1002 'ip4 && inport == "sw0-p1" && ip4.dst == 10.0.0.3' allow-related
> +
> +ADD_NAMESPACES(sw0-p1)
> +ADD_VETH(sw0-p1, sw0-p1, br-int, "10.0.0.2/24", "50:54:00:00:00:02", \
> +         "10.0.0.1")
> +ADD_NAMESPACES(sw0-p2)
> +ADD_VETH(sw0-p2, sw0-p2, br-int, "10.0.0.3/24", "50:54:00:00:00:03", \
> +         "10.0.0.1")
> +
> +# Ensure ovn-controller is caught up
> +ovn-nbctl --wait=hv sync
> +
> +on_exit 'ovn-nbctl acl-list sw0'
> +on_exit 'ovn-sbctl lflow-list'
> +on_exit 'ovs-ofctl dump-flows br-int'
> +
> +wait_for_ports_up
> +
> +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
> +
> +# start a background ping for ~30 secs.
> +NETNS_DAEMONIZE([sw0-p1], [[ping -q -c 100 -i 0.3 -w 15 10.0.0.3]],
[ns-sw0-p1.pid])
> +
> +sleep 3s
> +
> +# Ensure conntrack entry is present and ct_label is set.
> +AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.0.0.3) | \
> +sed -e 's/zone=[[0-9]]*/zone=<cleared>/' | \
> +sed -e
's/labels=0x4d2[[0-9a-f]]*/labels=0x4d2000000000000000000000000/'], [0],
[dnl
>
+icmp,orig=(src=10.0.0.2,dst=10.0.0.3,id=<cleared>,type=8,code=0),reply=(src=10.0.0.3,dst=10.0.0.2,id=<cleared>,type=0,code=0),zone=<cleared>,labels=0x4d2000000000000000000000000
>
+icmp,orig=(src=10.0.0.2,dst=10.0.0.3,id=<cleared>,type=8,code=0),reply=(src=10.0.0.3,dst=10.0.0.2,id=<cleared>,type=0,code=0),zone=<cleared>
> +])
> +
> +# Add a higher priority ACL with different label.
> +# This ACL also allows the ping running in background.
> +
> +check ovn-nbctl --label=1235 --apply-after-lb acl-add sw0 from-lport
1003 'ip4 && inport == "sw0-p1" && ip4.dst == 10.0.0.3' allow-related
> +ovn-nbctl --wait=hv sync
> +
> +sleep 3s
> +
> +# Ensure conntrack entry is updated with new ct_label is set.
> +AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.0.0.3) | \
> +sed -e 's/zone=[[0-9]]*/zone=<cleared>/' | \
> +sed -e
's/labels=0x4d3[[0-9a-f]]*/labels=0x4d3000000000000000000000000/'], [0],
[dnl
>
+icmp,orig=(src=10.0.0.2,dst=10.0.0.3,id=<cleared>,type=8,code=0),reply=(src=10.0.0.3,dst=10.0.0.2,id=<cleared>,type=0,code=0),zone=<cleared>,labels=0x4d3000000000000000000000000
>
+icmp,orig=(src=10.0.0.2,dst=10.0.0.3,id=<cleared>,type=8,code=0),reply=(src=10.0.0.3,dst=10.0.0.2,id=<cleared>,type=0,code=0),zone=<cleared>
> +])
> +
> +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
> +])
> +
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([ACL all drop and allow related - acl after lb])
> +AT_KEYWORDS([ACL all drop and allow related])
> +
> +CHECK_CONNTRACK()
> +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
> +
> +
> +# No ACLs in sw0.
> +check ovn-nbctl ls-add sw0
> +
> +check ovn-nbctl lsp-add sw0 sw0p1
> +check ovn-nbctl lsp-set-addresses sw0p1 "50:54:00:00:00:02 10.0.0.3"
> +
> +# ACLs to drop every thing and just allow-related.
> +check ovn-nbctl ls-add sw1
> +
> +check ovn-nbctl lsp-add sw1 sw1p1
> +check ovn-nbctl lsp-set-addresses sw1p1 "50:54:00:00:00:03 20.0.0.3"
> +
> +check ovn-nbctl --apply-after-lb acl-add sw1 from-lport 1001 'inport ==
"sw1p1" && ip4' drop
> +
> +check ovn-nbctl acl-add sw1 to-lport 1002 'ip4 && tcp && tcp.dst == 80'
allow-related
> +check ovn-nbctl acl-add sw1 to-lport 1001 'ip4' drop
> +
> +ADD_NAMESPACES(sw0p1)
> +ADD_VETH(sw0p1, sw0p1, br-int, "10.0.0.3/24", "50:54:00:00:00:02", \
> +         "10.0.0.1")
> +ADD_NAMESPACES(sw1p1)
> +ADD_VETH(sw1p1, sw1p1, br-int, "20.0.0.3/24", "50:54:00:00:00:03", \
> +         "20.0.0.1")
> +
> +# Create a logical router and attach both logical switches
> +check ovn-nbctl lr-add lr0
> +check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24
> +check ovn-nbctl lsp-add sw0 sw0-lr0
> +check ovn-nbctl lsp-set-type sw0-lr0 router
> +check ovn-nbctl lsp-set-addresses sw0-lr0 00:00:00:00:ff:01
> +check ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
> +
> +check ovn-nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 20.0.0.1/24
> +check ovn-nbctl lsp-add sw1 sw1-lr0
> +check ovn-nbctl lsp-set-type sw1-lr0 router
> +check ovn-nbctl lsp-set-addresses sw1-lr0 00:00:00:00:ff:02
> +check ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1
> +
> +# Ensure ovn-controller is caught up
> +ovn-nbctl --wait=hv sync
> +
> +on_exit 'ovn-nbctl acl-list sw0'
> +on_exit 'ovn-sbctl lflow-list'
> +on_exit 'ovs-ofctl dump-flows br-int'
> +
> +wait_for_ports_up
> +
> +# Start webservers in 'sw1-p1'
> +OVS_START_L7([sw1p1], [http])
> +
> +AT_CHECK([ip netns exec sw0p1 wget 20.0.0.3 -t 3 -T 1], [0], [ignore],
[ignore])
> +
> +# Clear the apply-after-lb option for the ACL
> +check ovn-nbctl acl-del sw1 from-lport 1001 'inport == "sw1p1" && ip4'
> +check ovn-nbctl acl-add sw1 from-lport 1001 'inport == "sw1p1" && ip4'
drop
> +
> +check ovn-nbctl --wait=hv sync
> +
> +AT_CHECK([ip netns exec sw0p1 wget 20.0.0.3 -t 3 -T 1], [0], [ignore],
[ignore])
> +
> +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
> +])
> +
>  OVN_FOR_EACH_NORTHD([
>  AT_SETUP([ACL log_related])
>
> diff --git a/utilities/ovn-nbctl.8.xml b/utilities/ovn-nbctl.8.xml
> index 545f3bf27b..74651c7623 100644
> --- a/utilities/ovn-nbctl.8.xml
> +++ b/utilities/ovn-nbctl.8.xml
> @@ -399,7 +399,7 @@
>        must be either <code>switch</code> or <code>port-group</code>.
>      </p>
>      <dl>
> -      <dt>[<code>--type=</code>{<code>switch</code> |
<code>port-group</code>}] [<code>--log</code>]
[<code>--meter=</code><var>meter</var>]
[<code>--severity=</code><var>severity</var>]
[<code>--name=</code><var>name</var>]
[<code>--label=</code><var>label</var>] [<code>--may-exist</code>]
<code>acl-add</code> <var>entity</var> <var>direction</var>
<var>priority</var> <var>match</var> <var>verdict</var></dt>
> +      <dt>[<code>--type=</code>{<code>switch</code> |
<code>port-group</code>}] [<code>--log</code>]
[<code>--meter=</code><var>meter</var>]
[<code>--severity=</code><var>severity</var>]
[<code>--name=</code><var>name</var>]
[<code>--label=</code><var>label</var>] [<code>--may-exist</code>]
[<code>--apply-after-lb</code>] <code>acl-add</code> <var>entity</var>
<var>direction</var> <var>priority</var> <var>match</var>
<var>verdict</var></dt>
>        <dd>
>          <p>
>            Adds the specified ACL to <var>entity</var>.
 <var>direction</var>
> @@ -423,6 +423,13 @@
>            is used to rate-limit packet logging.  The <var>meter</var>
argument
>            names a meter configured by <code>meter-add</code>.
>          </p>
> +
> +        <p>
> +          The <code>--apply-after-lb</code> option sets
> +          <code>apply-after-lb=true</code> in the <code>options</code>
column
> +          of the <code>ACL</code> table.  As the option name suggests,
the ACL
> +          will be applied after the logical switch load balancer stage.
> +        </p>
>        </dd>
>
>        <dt>[<code>--type=</code>{<code>switch</code> |
<code>port-group</code>}] <code>acl-del</code> <var>entity</var>
[<var>direction</var> [<var>priority</var> <var>match</var>]]</dt>
> diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
> index adb08c6c96..7bcc2c66a8 100644
> --- a/utilities/ovn-nbctl.c
> +++ b/utilities/ovn-nbctl.c
> @@ -2132,6 +2132,7 @@ nbctl_pre_acl(struct ctl_context *ctx)
>      ovsdb_idl_add_column(ctx->idl, &nbrec_acl_col_direction);
>      ovsdb_idl_add_column(ctx->idl, &nbrec_acl_col_priority);
>      ovsdb_idl_add_column(ctx->idl, &nbrec_acl_col_match);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_acl_col_options);
>  }
>
>  static void
> @@ -2145,6 +2146,7 @@ nbctl_pre_acl_list(struct ctl_context *ctx)
>      ovsdb_idl_add_column(ctx->idl, &nbrec_acl_col_severity);
>      ovsdb_idl_add_column(ctx->idl, &nbrec_acl_col_meter);
>      ovsdb_idl_add_column(ctx->idl, &nbrec_acl_col_label);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_acl_col_options);
>  }
>
>  static void
> @@ -2231,6 +2233,13 @@ nbctl_acl_add(struct ctl_context *ctx)
>        nbrec_acl_set_label(acl, label_value);
>      }
>
> +    if (!strcmp(direction, "from-lport") &&
> +        (shash_find(&ctx->options, "--apply-after-lb") != NULL)) {
> +        const struct smap options = SMAP_CONST1(&options,
"apply-after-lb",
> +                                                "true");
> +        nbrec_acl_set_options(acl, &options);
> +    }
> +
>      /* Check if same acl already exists for the ls/portgroup */
>      size_t n_acls = pg ? pg->n_acls : ls->n_acls;
>      struct nbrec_acl **acls = pg ? pg->acls : ls->acls;
> @@ -6959,7 +6968,8 @@ static const struct ctl_command_syntax
nbctl_commands[] = {
>      /* acl commands. */
>      { "acl-add", 5, 6, "{SWITCH | PORTGROUP} DIRECTION PRIORITY MATCH
ACTION",
>        nbctl_pre_acl, nbctl_acl_add, NULL,
> -      "--log,--may-exist,--type=,--name=,--severity=,--meter=,--label=",
RW },
> +      "--log,--may-exist,--type=,--name=,--severity=,--meter=,--label=,"
> +      "--apply-after-lb", RW },
>      { "acl-del", 1, 4, "{SWITCH | PORTGROUP} [DIRECTION [PRIORITY
MATCH]]",
>        nbctl_pre_acl, nbctl_acl_del, NULL, "--type=", RW },
>      { "acl-list", 1, 1, "{SWITCH | PORTGROUP}",
> --
> 2.34.1
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
Dumitru Ceara March 8, 2022, 2:50 p.m. UTC | #3
On 3/7/22 18:46, numans@ovn.org wrote:
> From: Numan Siddique <numans@ovn.org>
> 
> Presently for ACLs and LBs we do the following in the logical switch
> ingress pipeline
> 
>    1.  Send the packet to conntrack.
>    2.  Apply ACLs (from-lport)
>    3a. If the packet is a new connection and it is destined to the LB
>        VIP, then select a backend (and commit to conntrack with DNAT).
>    3b. If the packet is a new connection and it doesn't match 3a, then
>        commit to conntrack.
> 
> With the above approach, we cannot address the scenario of applying
> ACLs after the load balancing.  There can be ACLs which could match
> on the load balancer backend ips.
> 
> This patch addresses this usecase by
> 
>    1. Send the packet to conntrack.
>    2. Apply ACLs (from-lport, not configured with apply-after-lb=true)
>    3. If the packet is a new connection and it is destined to the LB
>       VIP, then select a backend (and commit to conntrack with DNAT).
>    4. Apply ACLs (from-lport, configured with apply-after-lb=true)
>    5. If the packet is a new connection and it didn't match (2), then
>       commit to conntrack.
> 
> In order to support this usecase, this patch supports an option
> "apply-after-lb=true" in the ACL table.  This option is valid
> only for "from-lport" ACLs.
> 
> Suggested-by: Dumitru Ceara <dceara@redhat.com>
> Signed-off-by: Numan Siddique <numans@ovn.org>
> ---

Aside from Han's comments, the rest looks good to me, thanks!

Acked-by: Dumitru Ceara <dceara@redhat.com>
Numan Siddique March 8, 2022, 3:56 p.m. UTC | #4
On Tue, Mar 8, 2022 at 9:51 AM Dumitru Ceara <dceara@redhat.com> wrote:
>
> On 3/7/22 18:46, numans@ovn.org wrote:
> > From: Numan Siddique <numans@ovn.org>
> >
> > Presently for ACLs and LBs we do the following in the logical switch
> > ingress pipeline
> >
> >    1.  Send the packet to conntrack.
> >    2.  Apply ACLs (from-lport)
> >    3a. If the packet is a new connection and it is destined to the LB
> >        VIP, then select a backend (and commit to conntrack with DNAT).
> >    3b. If the packet is a new connection and it doesn't match 3a, then
> >        commit to conntrack.
> >
> > With the above approach, we cannot address the scenario of applying
> > ACLs after the load balancing.  There can be ACLs which could match
> > on the load balancer backend ips.
> >
> > This patch addresses this usecase by
> >
> >    1. Send the packet to conntrack.
> >    2. Apply ACLs (from-lport, not configured with apply-after-lb=true)
> >    3. If the packet is a new connection and it is destined to the LB
> >       VIP, then select a backend (and commit to conntrack with DNAT).
> >    4. Apply ACLs (from-lport, configured with apply-after-lb=true)
> >    5. If the packet is a new connection and it didn't match (2), then
> >       commit to conntrack.
> >
> > In order to support this usecase, this patch supports an option
> > "apply-after-lb=true" in the ACL table.  This option is valid
> > only for "from-lport" ACLs.
> >
> > Suggested-by: Dumitru Ceara <dceara@redhat.com>
> > Signed-off-by: Numan Siddique <numans@ovn.org>
> > ---
>
> Aside from Han's comments, the rest looks good to me, thanks!
>
> Acked-by: Dumitru Ceara <dceara@redhat.com>

Thanks Han and Dumitru.

I applied this patch to the main branch with the below changes.

--

diff --git a/ovn-nb.xml b/ovn-nb.xml
index cea105b545..4d7a23c527 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -2040,19 +2040,20 @@
         </p>

         <p>
-          The main usecase of this option is to support ACLs matching on
+          The main use case of this option is to support ACLs matching on
           the destination IP address of the packet for the backend IPs
           of load balancers.
         </p>

         <p>
-          <code>OVN</code> will apply the <code>from-lport</code>ACLs in two
+          <code>OVN</code> will apply the <code>from-lport</code> ACLs in two
           stages.  ACLs without this option <code>apply-after-lb</code>
           set, will be applied before the load balancer stage and ACLs
           with this option set will be applied after the load balancer
-          stage.  Hence CMS should be extra careful when using this option
-          and should carefully evaluate the priorities of all the ACLs and
-          the default deny/allow ACLs if any.
+          stage.  The priorities are indepedent between these stages and
+          may not be obvious to the CMS.  Hence CMS should be extra careful
+          when using this option and should carefully evaluate the priorities
+          of all the ACLs and the default deny/allow ACLs if any.
         </p>
       </column>
     </group>



Numan

>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
Han Zhou March 9, 2022, 10:52 p.m. UTC | #5
On Tue, Mar 8, 2022 at 8:02 AM Numan Siddique <numans@ovn.org> wrote:
>
> On Tue, Mar 8, 2022 at 9:51 AM Dumitru Ceara <dceara@redhat.com> wrote:
> >
> > On 3/7/22 18:46, numans@ovn.org wrote:
> > > From: Numan Siddique <numans@ovn.org>
> > >
> > > Presently for ACLs and LBs we do the following in the logical switch
> > > ingress pipeline
> > >
> > >    1.  Send the packet to conntrack.
> > >    2.  Apply ACLs (from-lport)
> > >    3a. If the packet is a new connection and it is destined to the LB
> > >        VIP, then select a backend (and commit to conntrack with DNAT).
> > >    3b. If the packet is a new connection and it doesn't match 3a, then
> > >        commit to conntrack.
> > >
> > > With the above approach, we cannot address the scenario of applying
> > > ACLs after the load balancing.  There can be ACLs which could match
> > > on the load balancer backend ips.
> > >
> > > This patch addresses this usecase by
> > >
> > >    1. Send the packet to conntrack.
> > >    2. Apply ACLs (from-lport, not configured with apply-after-lb=true)
> > >    3. If the packet is a new connection and it is destined to the LB
> > >       VIP, then select a backend (and commit to conntrack with DNAT).
> > >    4. Apply ACLs (from-lport, configured with apply-after-lb=true)
> > >    5. If the packet is a new connection and it didn't match (2), then
> > >       commit to conntrack.
> > >
> > > In order to support this usecase, this patch supports an option
> > > "apply-after-lb=true" in the ACL table.  This option is valid
> > > only for "from-lport" ACLs.
> > >
> > > Suggested-by: Dumitru Ceara <dceara@redhat.com>
> > > Signed-off-by: Numan Siddique <numans@ovn.org>
> > > ---
> >
> > Aside from Han's comments, the rest looks good to me, thanks!
> >
> > Acked-by: Dumitru Ceara <dceara@redhat.com>
>
> Thanks Han and Dumitru.
>
> I applied this patch to the main branch with the below changes.

Thanks Numan. Since this is an important feature, would it be ok to
backport to branch-21.12? cc @Mark Michelson <mmichels@redhat.com>

Han

>
> --
>
> diff --git a/ovn-nb.xml b/ovn-nb.xml
> index cea105b545..4d7a23c527 100644
> --- a/ovn-nb.xml
> +++ b/ovn-nb.xml
> @@ -2040,19 +2040,20 @@
>          </p>
>
>          <p>
> -          The main usecase of this option is to support ACLs matching on
> +          The main use case of this option is to support ACLs matching on
>            the destination IP address of the packet for the backend IPs
>            of load balancers.
>          </p>
>
>          <p>
> -          <code>OVN</code> will apply the <code>from-lport</code>ACLs in
two
> +          <code>OVN</code> will apply the <code>from-lport</code> ACLs
in two
>            stages.  ACLs without this option <code>apply-after-lb</code>
>            set, will be applied before the load balancer stage and ACLs
>            with this option set will be applied after the load balancer
> -          stage.  Hence CMS should be extra careful when using this
option
> -          and should carefully evaluate the priorities of all the ACLs
and
> -          the default deny/allow ACLs if any.
> +          stage.  The priorities are indepedent between these stages and
> +          may not be obvious to the CMS.  Hence CMS should be extra
careful
> +          when using this option and should carefully evaluate the
priorities
> +          of all the ACLs and the default deny/allow ACLs if any.
>          </p>
>        </column>
>      </group>
>
>
>
> Numan
>
> >
> > _______________________________________________
> > dev mailing list
> > dev@openvswitch.org
> > https://mail.openvswitch.org/mailman/listinfo/ovs-dev
> >
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
Numan Siddique March 10, 2022, 12:06 a.m. UTC | #6
On Wed, Mar 9, 2022, 5:53 PM Han Zhou <zhouhan@gmail.com> wrote:

> On Tue, Mar 8, 2022 at 8:02 AM Numan Siddique <numans@ovn.org> wrote:
> >
> > On Tue, Mar 8, 2022 at 9:51 AM Dumitru Ceara <dceara@redhat.com> wrote:
> > >
> > > On 3/7/22 18:46, numans@ovn.org wrote:
> > > > From: Numan Siddique <numans@ovn.org>
> > > >
> > > > Presently for ACLs and LBs we do the following in the logical switch
> > > > ingress pipeline
> > > >
> > > >    1.  Send the packet to conntrack.
> > > >    2.  Apply ACLs (from-lport)
> > > >    3a. If the packet is a new connection and it is destined to the LB
> > > >        VIP, then select a backend (and commit to conntrack with
> DNAT).
> > > >    3b. If the packet is a new connection and it doesn't match 3a,
> then
> > > >        commit to conntrack.
> > > >
> > > > With the above approach, we cannot address the scenario of applying
> > > > ACLs after the load balancing.  There can be ACLs which could match
> > > > on the load balancer backend ips.
> > > >
> > > > This patch addresses this usecase by
> > > >
> > > >    1. Send the packet to conntrack.
> > > >    2. Apply ACLs (from-lport, not configured with
> apply-after-lb=true)
> > > >    3. If the packet is a new connection and it is destined to the LB
> > > >       VIP, then select a backend (and commit to conntrack with DNAT).
> > > >    4. Apply ACLs (from-lport, configured with apply-after-lb=true)
> > > >    5. If the packet is a new connection and it didn't match (2), then
> > > >       commit to conntrack.
> > > >
> > > > In order to support this usecase, this patch supports an option
> > > > "apply-after-lb=true" in the ACL table.  This option is valid
> > > > only for "from-lport" ACLs.
> > > >
> > > > Suggested-by: Dumitru Ceara <dceara@redhat.com>
> > > > Signed-off-by: Numan Siddique <numans@ovn.org>
> > > > ---
> > >
> > > Aside from Han's comments, the rest looks good to me, thanks!
> > >
> > > Acked-by: Dumitru Ceara <dceara@redhat.com>
> >
> > Thanks Han and Dumitru.
> >
> > I applied this patch to the main branch with the below changes.
>
> Thanks Numan. Since this is an important feature, would it be ok to
> backport to branch-21.12? cc @Mark Michelson <mmichels@redhat.com>
>


Hi Han

I caught up with other things and couldn't reply here.

I backported to 22.03 and 22.12 since ovn-k8s needs it quite urgently.

Thanks
Numan


> Han
>
> >
> > --
> >
> > diff --git a/ovn-nb.xml b/ovn-nb.xml
> > index cea105b545..4d7a23c527 100644
> > --- a/ovn-nb.xml
> > +++ b/ovn-nb.xml
> > @@ -2040,19 +2040,20 @@
> >          </p>
> >
> >          <p>
> > -          The main usecase of this option is to support ACLs matching on
> > +          The main use case of this option is to support ACLs matching
> on
> >            the destination IP address of the packet for the backend IPs
> >            of load balancers.
> >          </p>
> >
> >          <p>
> > -          <code>OVN</code> will apply the <code>from-lport</code>ACLs in
> two
> > +          <code>OVN</code> will apply the <code>from-lport</code> ACLs
> in two
> >            stages.  ACLs without this option <code>apply-after-lb</code>
> >            set, will be applied before the load balancer stage and ACLs
> >            with this option set will be applied after the load balancer
> > -          stage.  Hence CMS should be extra careful when using this
> option
> > -          and should carefully evaluate the priorities of all the ACLs
> and
> > -          the default deny/allow ACLs if any.
> > +          stage.  The priorities are indepedent between these stages and
> > +          may not be obvious to the CMS.  Hence CMS should be extra
> careful
> > +          when using this option and should carefully evaluate the
> priorities
> > +          of all the ACLs and the default deny/allow ACLs if any.
> >          </p>
> >        </column>
> >      </group>
> >
> >
> >
> > Numan
> >
> > >
> > > _______________________________________________
> > > dev mailing list
> > > dev@openvswitch.org
> > > https://mail.openvswitch.org/mailman/listinfo/ovs-dev
> > >
> > _______________________________________________
> > dev mailing list
> > dev@openvswitch.org
> > https://mail.openvswitch.org/mailman/listinfo/ovs-dev
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
>
diff mbox series

Patch

diff --git a/northd/northd.c b/northd/northd.c
index 294a59bd7e..b22da67e9c 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -112,18 +112,20 @@  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,  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")    \
+    PIPELINE_STAGE(SWITCH, IN,  LB,            12, "ls_in_lb")  \
+    PIPELINE_STAGE(SWITCH, IN,  ACL_AFTER_LB,  13, "ls_in_acl_after_lb")  \
+    PIPELINE_STAGE(SWITCH, IN,  STATEFUL,      14, "ls_in_stateful")      \
+    PIPELINE_STAGE(SWITCH, IN,  PRE_HAIRPIN,   15, "ls_in_pre_hairpin")   \
+    PIPELINE_STAGE(SWITCH, IN,  NAT_HAIRPIN,   16, "ls_in_nat_hairpin")   \
+    PIPELINE_STAGE(SWITCH, IN,  HAIRPIN,       17, "ls_in_hairpin")       \
+    PIPELINE_STAGE(SWITCH, IN,  ARP_ND_RSP,    18, "ls_in_arp_rsp")       \
+    PIPELINE_STAGE(SWITCH, IN,  DHCP_OPTIONS,  19, "ls_in_dhcp_options")  \
+    PIPELINE_STAGE(SWITCH, IN,  DHCP_RESPONSE, 20, "ls_in_dhcp_response") \
+    PIPELINE_STAGE(SWITCH, IN,  DNS_LOOKUP,    21, "ls_in_dns_lookup")    \
+    PIPELINE_STAGE(SWITCH, IN,  DNS_RESPONSE,  22, "ls_in_dns_response")  \
+    PIPELINE_STAGE(SWITCH, IN,  EXTERNAL_PORT, 23, "ls_in_external_port") \
+    PIPELINE_STAGE(SWITCH, IN,  L2_LKUP,       24, "ls_in_l2_lkup")       \
+    PIPELINE_STAGE(SWITCH, IN,  L2_UNKNOWN,    25, "ls_in_l2_unknown")    \
                                                                           \
     /* Logical switch egress stages. */                                   \
     PIPELINE_STAGE(SWITCH, OUT, PRE_LB,       0, "ls_out_pre_lb")         \
@@ -6145,7 +6147,7 @@  build_reject_acl_rules(struct ovn_datapath *od, struct hmap *lflows,
 {
     struct ds match = DS_EMPTY_INITIALIZER;
     struct ds actions = DS_EMPTY_INITIALIZER;
-    bool ingress = (stage == S_SWITCH_IN_ACL);
+    bool ingress = (ovn_stage_get_pipeline(stage) == P_IN);
 
     char *next_action =
         xasprintf("next(pipeline=%s,table=%d);",
@@ -6186,7 +6188,15 @@  consider_acl(struct hmap *lflows, struct ovn_datapath *od,
              struct ds *actions)
 {
     bool ingress = !strcmp(acl->direction, "from-lport") ? true :false;
-    enum ovn_stage stage = ingress ? S_SWITCH_IN_ACL : S_SWITCH_OUT_ACL;
+    enum ovn_stage stage;
+
+    if (ingress && smap_get_bool(&acl->options, "apply-after-lb", false)) {
+        stage = S_SWITCH_IN_ACL_AFTER_LB;
+    } else if (ingress) {
+        stage = S_SWITCH_IN_ACL;
+    } else {
+        stage = S_SWITCH_OUT_ACL;
+    }
 
     if (!strcmp(acl->action, "allow-stateless")) {
         ds_clear(actions);
@@ -6471,6 +6481,8 @@  build_acls(struct ovn_datapath *od, struct hmap *lflows,
         ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, 0, "1", "next;");
     }
 
+    ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL_AFTER_LB, 0, "1", "next;");
+
     if (has_stateful) {
         /* Ingress and Egress ACL Table (Priority 1).
          *
@@ -6529,7 +6541,8 @@  build_acls(struct ovn_datapath *od, struct hmap *lflows,
                       "ct.rpl && ct_label.blocked == 0",
                       use_ct_inv_match ? " && !ct.inv" : "");
         ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, UINT16_MAX - 3,
-                      ds_cstr(&match), "next;");
+                      ds_cstr(&match), REGBIT_ACL_HINT_DROP" = 0; "
+                      REGBIT_ACL_HINT_BLOCK" = 0; next;");
         ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX - 3,
                       ds_cstr(&match), "next;");
 
@@ -6736,6 +6749,10 @@  build_lb_rules(struct hmap *lflows, struct ovn_northd_lb *lb,
         ds_clear(action);
         ds_clear(match);
 
+        /* Make sure that we clear the REGBIT_CONNTRACK_COMMIT flag.  Otherwise
+         * the load balanced packet will be committed again in
+         * S_SWITCH_IN_STATEFUL. */
+        ds_put_format(action, REGBIT_CONNTRACK_COMMIT" = 0; ");
         /* Store the original destination IP to be used when generating
          * hairpin flows.
          */
@@ -6782,8 +6799,8 @@  build_lb_rules(struct hmap *lflows, struct ovn_northd_lb *lb,
 
         struct ovn_lflow *lflow_ref = NULL;
         uint32_t hash = ovn_logical_flow_hash(
-                ovn_stage_get_table(S_SWITCH_IN_STATEFUL),
-                ovn_stage_get_pipeline(S_SWITCH_IN_STATEFUL), priority,
+                ovn_stage_get_table(S_SWITCH_IN_LB),
+                ovn_stage_get_pipeline(S_SWITCH_IN_LB), priority,
                 ds_cstr(match), ds_cstr(action));
 
         for (size_t j = 0; j < lb->n_nb_ls; j++) {
@@ -6796,7 +6813,7 @@  build_lb_rules(struct hmap *lflows, struct ovn_northd_lb *lb,
                 continue;
             }
             lflow_ref = ovn_lflow_add_at_with_hash(lflows, od,
-                    S_SWITCH_IN_STATEFUL, priority,
+                    S_SWITCH_IN_LB, priority,
                     ds_cstr(match), ds_cstr(action),
                     NULL, meter, &lb->nlb->header_,
                     OVS_SOURCE_LOCATOR, hash);
@@ -6807,8 +6824,9 @@  build_lb_rules(struct hmap *lflows, struct ovn_northd_lb *lb,
 static void
 build_stateful(struct ovn_datapath *od, struct hmap *lflows)
 {
-    /* Ingress and Egress stateful Table (Priority 0): Packets are
+    /* Ingress LB, Ingress and Egress stateful 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_IN_STATEFUL, 0, "1", "next;");
     ovn_lflow_add(lflows, od, S_SWITCH_OUT_STATEFUL, 0, "1", "next;");
 
@@ -7072,7 +7090,7 @@  build_lrouter_groups(struct hmap *ports, struct ovs_list *lr_list)
 }
 
 /*
- * Ingress table 22: Flows that flood self originated ARP/ND packets in the
+ * Ingress table 24: Flows that flood self originated ARP/ND packets in the
  * switching domain.
  */
 static void
@@ -7185,7 +7203,7 @@  lrouter_port_ipv6_reachable(const struct ovn_port *op,
 }
 
 /*
- * Ingress table 22: Flows that forward ARP/ND requests only to the routers
+ * Ingress table 24: Flows that forward ARP/ND requests only to the routers
  * that own the addresses. Other ARP/ND packets are still flooded in the
  * switching domain as regular broadcast.
  */
@@ -7222,7 +7240,7 @@  build_lswitch_rport_arp_req_flow(const char *ips,
 }
 
 /*
- * Ingress table 22: Flows that forward ARP/ND requests only to the routers
+ * Ingress table 24: Flows that forward ARP/ND requests only to the routers
  * that own the addresses.
  * Priorities:
  * - 80: self originated GARPs that need to follow regular processing.
@@ -7550,7 +7568,7 @@  build_lswitch_flows(const struct hmap *datapaths,
 
     struct ovn_datapath *od;
 
-    /* Ingress table 23: Destination lookup for unknown MACs (priority 0). */
+    /* Ingress table 25: Destination lookup for unknown MACs (priority 0). */
     HMAP_FOR_EACH (od, key_node, datapaths) {
         if (!od->nbs) {
             continue;
@@ -7619,7 +7637,7 @@  build_lswitch_lflows_admission_control(struct ovn_datapath *od,
     }
 }
 
-/* Ingress table 16: ARP/ND responder, skip requests coming from localnet
+/* Ingress table 18: ARP/ND responder, skip requests coming from localnet
  * and vtep ports. (priority 100); see ovn-northd.8.xml for the
  * rationale. */
 
@@ -7641,7 +7659,7 @@  build_lswitch_arp_nd_responder_skip_local(struct ovn_port *op,
     }
 }
 
-/* Ingress table 16: ARP/ND responder, reply for known IPs.
+/* Ingress table 18: ARP/ND responder, reply for known IPs.
  * (priority 50). */
 static void
 build_lswitch_arp_nd_responder_known_ips(struct ovn_port *op,
@@ -7901,7 +7919,7 @@  build_lswitch_arp_nd_responder_known_ips(struct ovn_port *op,
     }
 }
 
-/* Ingress table 16: ARP/ND responder, by default goto next.
+/* Ingress table 18: ARP/ND responder, by default goto next.
  * (priority 0)*/
 static void
 build_lswitch_arp_nd_responder_default(struct ovn_datapath *od,
@@ -7912,7 +7930,7 @@  build_lswitch_arp_nd_responder_default(struct ovn_datapath *od,
     }
 }
 
-/* Ingress table 16: ARP/ND responder for service monitor source ip.
+/* Ingress table 18: ARP/ND responder for service monitor source ip.
  * (priority 110)*/
 static void
 build_lswitch_arp_nd_service_monitor(struct ovn_northd_lb *lb,
@@ -7960,7 +7978,7 @@  build_lswitch_arp_nd_service_monitor(struct ovn_northd_lb *lb,
 }
 
 
-/* Logical switch ingress table 14 and 15: DHCP options and response
+/* Logical switch ingress table 19 and 20: DHCP options and response
  * priority 100 flows. */
 static void
 build_lswitch_dhcp_options_and_response(struct ovn_port *op,
@@ -8012,11 +8030,11 @@  build_lswitch_dhcp_options_and_response(struct ovn_port *op,
     }
 }
 
-/* Ingress table 14 and 15: DHCP options and response, by default goto
+/* Ingress table 19 and 20: DHCP options and response, by default goto
  * next. (priority 0).
- * Ingress table 16 and 17: DNS lookup and response, by default goto next.
+ * Ingress table 21 and 22: DNS lookup and response, by default goto next.
  * (priority 0).
- * Ingress table 18 - External port handling, by default goto next.
+ * Ingress table 23 - External port handling, by default goto next.
  * (priority 0). */
 static void
 build_lswitch_dhcp_and_dns_defaults(struct ovn_datapath *od,
@@ -8031,7 +8049,7 @@  build_lswitch_dhcp_and_dns_defaults(struct ovn_datapath *od,
     }
 }
 
-/* Logical switch ingress table 17 and 18: DNS lookup and response
+/* Logical switch ingress table 21 and 22: DNS lookup and response
 * priority 100 flows.
 */
 static void
@@ -8059,7 +8077,7 @@  build_lswitch_dns_lookup_and_response(struct ovn_datapath *od,
     }
 }
 
-/* Table 18: External port. Drop ARP request for router ips from
+/* Table 23: External port. Drop ARP request for router ips from
  * external ports  on chassis not binding those ports.
  * This makes the router pipeline to be run only on the chassis
  * binding the external ports. */
@@ -8076,7 +8094,7 @@  build_lswitch_external_port(struct ovn_port *op,
     }
 }
 
-/* Ingress table 22: Destination lookup, broadcast and multicast handling
+/* Ingress table 24: Destination lookup, broadcast and multicast handling
  * (priority 70 - 100). */
 static void
 build_lswitch_destination_lookup_bmcast(struct ovn_datapath *od,
@@ -8168,7 +8186,7 @@  build_lswitch_destination_lookup_bmcast(struct ovn_datapath *od,
 }
 
 
-/* Ingress table 22: Add IP multicast flows learnt from IGMP/MLD
+/* Ingress table 24: Add IP multicast flows learnt from IGMP/MLD
  * (priority 90). */
 static void
 build_lswitch_ip_mcast_igmp_mld(struct ovn_igmp_group *igmp_group,
@@ -8246,7 +8264,7 @@  build_lswitch_ip_mcast_igmp_mld(struct ovn_igmp_group *igmp_group,
 
 static struct ovs_mutex mcgroup_mutex = OVS_MUTEX_INITIALIZER;
 
-/* Ingress table 22: Destination lookup, unicast handling (priority 50), */
+/* Ingress table 24: Destination lookup, unicast handling (priority 50), */
 static void
 build_lswitch_ip_unicast_lookup(struct ovn_port *op,
                                 struct hmap *lflows,
diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
index e495db46a0..1c9d408afe 100644
--- a/northd/ovn-northd.8.xml
+++ b/northd/ovn-northd.8.xml
@@ -712,15 +712,16 @@ 
       </li>
     </ul>
 
-    <h3>Ingress table 9: <code>from-lport</code> ACLs</h3>
+    <h3>Ingress table 9: <code>from-lport</code> ACLs before LB</h3>
 
     <p>
       Logical flows in this table closely reproduce those in the
       <code>ACL</code> table in the <code>OVN_Northbound</code> database
-      for the <code>from-lport</code> direction. The <code>priority</code>
-      values from the <code>ACL</code> table have a limited range and have
-      1000 added to them to leave room for OVN default flows at both
-      higher and lower priorities.
+      for the <code>from-lport</code> direction without the option
+      <code>apply-after-lb</code> set or set to <code>false</code>.
+      The <code>priority</code> values from the <code>ACL</code> table have a
+      limited range and have 1000 added to them to leave room for OVN default
+      flows at both higher and lower priorities.
     </p>
     <ul>
       <li>
@@ -795,10 +796,11 @@ 
         go through the flows that implement the currently defined
         policy based on ACLs.  If a connection is no longer allowed by
         policy, <code>ct_label.blocked</code> will get set and packets in the
-        reply direction will no longer be allowed, either. If ACL logging
-        and logging of related packets is enabled, then a companion priority-
-        65533 flow will be installed that accomplishes the same thing but
-        also logs the traffic.
+        reply direction will no longer be allowed, either. This flow also
+        clears the register bits <code>reg0[9]</code> and
+        <code>reg0[10]</code>.  If ACL logging and logging of related packets
+        is enabled, then a companion priority-65533 flow will be installed that
+        accomplishes the same thing but also logs the traffic.
       </li>
 
       <li>
@@ -893,7 +895,7 @@ 
       </li>
     </ul>
 
-    <h3>Ingress Table 12: Stateful</h3>
+    <h3>Ingress Table 12: LB</h3>
 
     <ul>
       <li>
@@ -944,7 +946,73 @@ 
         Please note using <code>--reject</code> option will disable
         empty_lb SB controller event for this load balancer.
       </li>
+    </ul>
+
+    <h3>Ingress table 13: <code>from-lport</code> ACLs after LB</h3>
+
+    <p>
+      Logical flows in this table closely reproduce those in the
+      <code>ACL</code> table in the <code>OVN_Northbound</code> database
+      for the <code>from-lport</code> direction with the option
+      <code>apply-after-lb</code> set to <code>true</code>.
+      The <code>priority</code> values from the <code>ACL</code> table have a
+      limited range and have 1000 added to them to leave room for OVN default
+      flows at both higher and lower priorities.
+    </p>
+
+    <ul>
+      <li>
+        <code>allow</code> apply-after-lb ACLs translate into logical flows
+        with the <code>next;</code> action.  If there are any stateful ACLs
+        (including both before-lb and after-lb ACLs)
+        on this datapath, then <code>allow</code> ACLs translate to
+        <code>ct_commit; next;</code> (which acts as a hint for the next tables
+        to commit the connection to conntrack). In case the <code>ACL</code>
+        has a label then <code>reg3</code> is loaded with the label value and
+        <code>reg0[13]</code> bit is set to 1 (which acts as a hint for the
+        next tables to commit the label to conntrack).
+      </li>
+      <li>
+        <code>allow-related</code> apply-after-lb ACLs translate into logical
+        flows with the <code>ct_commit(ct_label=0/1); next;</code> actions
+        for new connections and <code>reg0[1] = 1; next;</code> for existing
+        connections.  In case the <code>ACL</code> has a label then
+        <code>reg3</code> is loaded with the label value and
+        <code>reg0[13]</code> bit is set to 1 (which acts as a hint for the
+        next tables to commit the label to conntrack).
+      </li>
+      <li>
+        <code>allow-stateless</code> apply-after-lb ACLs translate into logical
+        flows with the <code>next;</code> action.
+      </li>
+      <li>
+        <code>reject</code> apply-after-lb ACLs translate into logical
+        flows with the
+        <code>tcp_reset { output &lt;-&gt; inport;
+        next(pipeline=egress,table=5);}</code>
+        action for TCP connections,<code>icmp4/icmp6</code> action
+        for UDP connections, and <code>sctp_abort {output &lt;-%gt; inport;
+        next(pipeline=egress,table=5);}</code> action for SCTP associations.
+      </li>
+      <li>
+        Other apply-after-lb ACLs translate to <code>drop;</code> for new
+        or untracked connections and <code>ct_commit(ct_label=1/1);</code> for
+        known connections.  Setting <code>ct_label</code> marks a connection
+        as one that was previously allowed, but should no longer be
+        allowed due to a policy change.
+      </li>
+    </ul>
+
+    <ul>
+      <li>
+        One priority-0 fallback flow that matches all packets and advances to
+        the next table.
+      </li>
+    </ul>
+
+    <h3>Ingress Table 14: Stateful</h3>
 
+    <ul>
       <li>
         A priority 100 flow is added which commits the packet to the conntrack
         and sets the most significant 32-bits of <code>ct_label</code> with the
@@ -965,7 +1033,7 @@ 
       </li>
     </ul>
 
-    <h3>Ingress Table 13: Pre-Hairpin</h3>
+    <h3>Ingress Table 15: Pre-Hairpin</h3>
     <ul>
       <li>
         If the logical switch has load balancer(s) configured, then a
@@ -983,7 +1051,7 @@ 
       </li>
     </ul>
 
-    <h3>Ingress Table 14: Nat-Hairpin</h3>
+    <h3>Ingress Table 16: Nat-Hairpin</h3>
     <ul>
       <li>
          If the logical switch has load balancer(s) configured, then a
@@ -1018,7 +1086,7 @@ 
       </li>
     </ul>
 
-    <h3>Ingress Table 15: Hairpin</h3>
+    <h3>Ingress Table 17: Hairpin</h3>
     <ul>
       <li>
         A priority-1 flow that hairpins traffic matched by non-default
@@ -1031,7 +1099,7 @@ 
       </li>
     </ul>
 
-    <h3>Ingress Table 16: ARP/ND responder</h3>
+    <h3>Ingress Table 18: ARP/ND responder</h3>
 
     <p>
       This table implements ARP/ND responder in a logical switch for known
@@ -1333,7 +1401,7 @@  output;
       </li>
     </ul>
 
-    <h3>Ingress Table 17: DHCP option processing</h3>
+    <h3>Ingress Table 19: DHCP option processing</h3>
 
     <p>
       This table adds the DHCPv4 options to a DHCPv4 packet from the
@@ -1394,7 +1462,7 @@  next;
       </li>
     </ul>
 
-    <h3>Ingress Table 18: DHCP responses</h3>
+    <h3>Ingress Table 20: DHCP responses</h3>
 
     <p>
       This table implements DHCP responder for the DHCP replies generated by
@@ -1475,7 +1543,7 @@  output;
       </li>
     </ul>
 
-    <h3>Ingress Table 19 DNS Lookup</h3>
+    <h3>Ingress Table 21 DNS Lookup</h3>
 
     <p>
       This table looks up and resolves the DNS names to the corresponding
@@ -1504,7 +1572,7 @@  reg0[4] = dns_lookup(); next;
       </li>
     </ul>
 
-    <h3>Ingress Table 20 DNS Responses</h3>
+    <h3>Ingress Table 22 DNS Responses</h3>
 
     <p>
       This table implements DNS responder for the DNS replies generated by
@@ -1539,7 +1607,7 @@  output;
       </li>
     </ul>
 
-    <h3>Ingress table 21 External ports</h3>
+    <h3>Ingress table 23 External ports</h3>
 
     <p>
       Traffic from the <code>external</code> logical ports enter the ingress
@@ -1582,7 +1650,7 @@  output;
       </li>
     </ul>
 
-    <h3>Ingress Table 22 Destination Lookup</h3>
+    <h3>Ingress Table 24 Destination Lookup</h3>
 
     <p>
       This table implements switching behavior.  It contains these logical
@@ -1754,7 +1822,7 @@  output;
       </li>
     </ul>
 
-    <h3>Ingress Table 24 Destination unknown</h3>
+    <h3>Ingress Table 25 Destination unknown</h3>
 
     <p>
       This table handles the packets whose destination was not found or
diff --git a/ovn-nb.xml b/ovn-nb.xml
index beb3ded798..cea105b545 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -2029,6 +2029,34 @@ 
       </ul>
     </column>
 
+    <group title="options">
+      <p>
+        ACLs options.
+      </p>
+      <column name="options" key="apply-after-lb">
+        <p>
+          If set to true, the ACL will be applied after load balancing
+          stage.  Supported only for <code>from-lport</code> direction.
+        </p>
+
+        <p>
+          The main usecase of this option is to support ACLs matching on
+          the destination IP address of the packet for the backend IPs
+          of load balancers.
+        </p>
+
+        <p>
+          <code>OVN</code> will apply the <code>from-lport</code>ACLs in two
+          stages.  ACLs without this option <code>apply-after-lb</code>
+          set, will be applied before the load balancer stage and ACLs
+          with this option set will be applied after the load balancer
+          stage.  Hence CMS should be extra careful when using this option
+          and should carefully evaluate the priorities of all the ACLs and
+          the default deny/allow ACLs if any.
+        </p>
+      </column>
+    </group>
+
     <group title="Logging">
       <p>
         These columns control whether and how OVN logs packets that match an
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index fe27869737..3865003bf8 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -1226,7 +1226,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.*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);)
+  (ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg0[[1]] = 0; reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
 ])
 
 AS_BOX([Delete the Load_Balancer_Health_Check])
@@ -1236,7 +1236,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.*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);)
+[  (ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg0[[1]] = 0; reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
 ])
 
 AS_BOX([Create the Load_Balancer_Health_Check again.])
@@ -1248,7 +1248,7 @@  check ovn-nbctl --wait=sb sync
 
 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);)
+  (ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg0[[1]] = 0; reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
 ])
 
 AS_BOX([Get the uuid of both the service_monitor])
@@ -1258,7 +1258,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.*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);)
+[  (ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg0[[1]] = 0; reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
 ])
 
 AS_BOX([Set the service monitor for sw1-p1 to offline])
@@ -1269,7 +1269,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.*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);)
+[  (ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg0[[1]] = 0; reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80);)
 ])
 
 AS_BOX([Set the service monitor for sw0-p1 to offline])
@@ -1285,7 +1285,7 @@  OVS_WAIT_FOR_OUTPUT(
 AT_CAPTURE_FILE([sbflows6])
 OVS_WAIT_FOR_OUTPUT(
   [ovn-sbctl dump-flows sw0 | tee sbflows6 | 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=(drop;)
+  (ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(drop;)
 ])
 
 AS_BOX([Set the service monitor for sw0-p1 and sw1-p1 to online])
@@ -1298,7 +1298,7 @@  check ovn-nbctl --wait=sb sync
 AT_CAPTURE_FILE([sbflows7])
 OVS_WAIT_FOR_OUTPUT(
   [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);)
+[  (ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg0[[1]] = 0; reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
 ])
 
 AS_BOX([Set the service monitor for sw1-p1 to error])
@@ -1309,7 +1309,7 @@  check ovn-nbctl --wait=sb sync
 ovn-sbctl dump-flows sw0 | grep "ip4.dst == 10.0.0.10 && tcp.dst == 80" \
 | grep priority=120 > lflows.txt
 AT_CHECK([cat lflows.txt | sed 's/table=..//'], [0], [dnl
-  (ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80);)
+  (ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg0[[1]] = 0; reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80);)
 ])
 
 AS_BOX([Add one more vip to lb1])
@@ -1335,8 +1335,8 @@  AT_CAPTURE_FILE([sbflows9])
 OVS_WAIT_FOR_OUTPUT(
   [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);)
+[  (ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg0[[1]] = 0; reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80);)
+  (ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(reg0[[1]] = 0; reg1 = 10.0.0.40; reg2[[0..15]] = 1000; ct_lb(backends=10.0.0.3:1000);)
 ])
 
 AS_BOX([Set the service monitor for sw1-p1 to online])
@@ -1349,8 +1349,8 @@  AT_CAPTURE_FILE([sbflows10])
 OVS_WAIT_FOR_OUTPUT(
   [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);)
+[  (ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg0[[1]] = 0; reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
+  (ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(reg0[[1]] = 0; reg1 = 10.0.0.40; reg2[[0..15]] = 1000; ct_lb(backends=10.0.0.3:1000,20.0.0.3:80);)
 ])
 
 AS_BOX([Associate lb1 to sw1])
@@ -1359,8 +1359,8 @@  AT_CAPTURE_FILE([sbflows11])
 OVS_WAIT_FOR_OUTPUT(
   [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);)
+  (ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg0[[1]] = 0; reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
+  (ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.40 && tcp.dst == 1000), action=(reg0[[1]] = 0; reg1 = 10.0.0.40; reg2[[0..15]] = 1000; ct_lb(backends=10.0.0.3:1000,20.0.0.3:80);)
 ])
 
 AS_BOX([Now create lb2 same as lb1 but udp protocol.])
@@ -1417,7 +1417,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=5);};)
+  (ls_in_lb           ), 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
@@ -2037,9 +2037,9 @@  AT_CAPTURE_FILE([sw1flows])
 
 AT_CHECK(
   [grep -E 'ls_(in|out)_acl' sw0flows sw1flows | grep pg0 | sort], [0], [dnl
-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=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=24); };)
 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=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=24); };)
 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); };)
 ])
 
@@ -2053,10 +2053,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=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); };)
+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=24); };)
+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=24); };)
+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=24); };)
+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=24); };)
 ])
 
 AS_BOX([3])
@@ -2071,16 +2071,16 @@  AT_CAPTURE_FILE([sw1flows3])
 AT_CHECK([grep "ls_out_acl" sw0flows3 sw1flows3 | grep pg0 | sort], [0], [dnl
 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); };)
+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=24); };)
+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=24); };)
+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=24); };)
+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=24); };)
 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); };)
+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=24); };)
+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=24); };)
+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=24); };)
+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=24); };)
 ])
 AT_CLEANUP
 ])
@@ -2240,7 +2240,7 @@  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=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=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.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; next;)
   table=9 (ls_in_acl          ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;)
 ])
 
@@ -2252,6 +2252,7 @@  check ovn-nbctl --wait=sb \
     -- 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 | sort], [0], [dnl
+  table=13(ls_in_acl_after_lb ), priority=0    , match=(1), action=(next;)
   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;)
@@ -2283,7 +2284,7 @@  AT_CHECK([ovn-sbctl lflow-list ls | grep -e ls_in_acl_hint -e ls_out_acl_hint -e
   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.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; 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;)
 ])
@@ -2292,6 +2293,7 @@  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=13(ls_in_acl_after_lb ), priority=0    , match=(1), action=(next;)
   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;)
@@ -2578,56 +2580,56 @@  check ovn-nbctl \
     -- ls-lb-add sw0 lb0
 check ovn-nbctl --wait=sb sync
 
-AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_pre_hairpin | sort], [0], [dnl
-  table=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_pre_hairpin | sort | sed 's/table=../table=??/'], [0], [dnl
+  table=??(ls_in_pre_hairpin  ), priority=0    , match=(1), action=(next;)
+  table=??(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=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_nat_hairpin | sort | sed 's/table=../table=??/'], [0], [dnl
+  table=??(ls_in_nat_hairpin  ), priority=0    , match=(1), action=(next;)
+  table=??(ls_in_nat_hairpin  ), priority=100  , match=(ip && ct.est && ct.trk && reg0[[6]] == 1), action=(ct_snat;)
+  table=??(ls_in_nat_hairpin  ), priority=100  , match=(ip && ct.new && ct.trk && reg0[[6]] == 1), action=(ct_snat_to_vip; next;)
+  table=??(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=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_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_hairpin | sort | sed 's/table=../table=??/'], [0], [dnl
+  table=??(ls_in_hairpin      ), priority=0    , match=(1), action=(next;)
+  table=??(ls_in_hairpin      ), priority=1    , match=((reg0[[6]] == 1 || reg0[[12]] == 1)), action=(eth.dst <-> eth.src; outport = inport; flags.loopback = 1; output;)
 ])
 
 check ovn-nbctl -- ls-lb-del sw0 lb0
 check ovn-nbctl --wait=sb sync
 
-AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_pre_hairpin | sort], [0], [dnl
-  table=13(ls_in_pre_hairpin  ), priority=0    , match=(1), action=(next;)
+AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_pre_hairpin | sort | sed 's/table=../table=??/'], [0], [dnl
+  table=??(ls_in_pre_hairpin  ), priority=0    , match=(1), action=(next;)
 ])
 
-AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_nat_hairpin | sort], [0], [dnl
-  table=14(ls_in_nat_hairpin  ), priority=0    , match=(1), action=(next;)
+AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_nat_hairpin | sort | sed 's/table=../table=??/'], [0], [dnl
+  table=??(ls_in_nat_hairpin  ), priority=0    , match=(1), action=(next;)
 ])
 
-AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_hairpin | sort], [0], [dnl
-  table=15(ls_in_hairpin      ), priority=0    , match=(1), action=(next;)
+AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_hairpin | sort | sed 's/table=../table=??/'], [0], [dnl
+  table=??(ls_in_hairpin      ), priority=0    , match=(1), action=(next;)
 ])
 
 check ovn-nbctl -- add load_balancer_group $lbg load_balancer $lb0
 check ovn-nbctl --wait=sb sync
 
-AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_pre_hairpin | sort], [0], [dnl
-  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_pre_hairpin | sort | sed 's/table=../table=??/'], [0], [dnl
+  table=??(ls_in_pre_hairpin  ), priority=0    , match=(1), action=(next;)
+  table=??(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=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_nat_hairpin | sort | sed 's/table=../table=??/'], [0], [dnl
+  table=??(ls_in_nat_hairpin  ), priority=0    , match=(1), action=(next;)
+  table=??(ls_in_nat_hairpin  ), priority=100  , match=(ip && ct.est && ct.trk && reg0[[6]] == 1), action=(ct_snat;)
+  table=??(ls_in_nat_hairpin  ), priority=100  , match=(ip && ct.new && ct.trk && reg0[[6]] == 1), action=(ct_snat_to_vip; next;)
+  table=??(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=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_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_hairpin | sort | sed 's/table=../table=??/'], [0], [dnl
+  table=??(ls_in_hairpin      ), priority=0    , match=(1), action=(next;)
+  table=??(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
@@ -3948,12 +3950,16 @@  check_stateful_flows() {
   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_lb" sw0flows | sort], [0], [dnl
+  table=12(ls_in_lb           ), priority=0    , match=(1), action=(next;)
+  table=12(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg0[[1]] = 0; reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.4:8080);)
+  table=12(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.20 && tcp.dst == 80), action=(reg0[[1]] = 0; reg1 = 10.0.0.20; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.40:8080);)
+])
+
     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 && reg0[[13]] == 0), action=(ct_commit { ct_label.blocked = 0; }; next;)
-  table=12(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1 && reg0[[13]] == 1), action=(ct_commit { ct_label.blocked = 0; ct_label.label = reg3; }; 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);)
-  table=12(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.20 && tcp.dst == 80), action=(reg1 = 10.0.0.20; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.40:8080);)
+  table=14(ls_in_stateful     ), priority=0    , match=(1), action=(next;)
+  table=14(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1 && reg0[[13]] == 0), action=(ct_commit { ct_label.blocked = 0; }; next;)
+  table=14(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1 && reg0[[13]] == 1), action=(ct_commit { ct_label.blocked = 0; ct_label.label = reg3; }; next;)
 ])
 
     AT_CHECK([grep "ls_out_pre_lb" sw0flows | sort], [0], [dnl
@@ -4016,10 +4022,14 @@  AT_CHECK([grep "ls_in_pre_stateful" sw0flows | sort], [0], [dnl
   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_lb" sw0flows | sort], [0], [dnl
+  table=12(ls_in_lb           ), priority=0    , match=(1), action=(next;)
+])
+
 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 && reg0[[13]] == 0), action=(ct_commit { ct_label.blocked = 0; }; next;)
-  table=12(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1 && reg0[[13]] == 1), action=(ct_commit { ct_label.blocked = 0; ct_label.label = reg3; }; next;)
+  table=14(ls_in_stateful     ), priority=0    , match=(1), action=(next;)
+  table=14(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1 && reg0[[13]] == 0), action=(ct_commit { ct_label.blocked = 0; }; next;)
+  table=14(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1 && reg0[[13]] == 1), action=(ct_commit { ct_label.blocked = 0; ct_label.label = reg3; }; next;)
 ])
 
 AT_CHECK([grep "ls_out_pre_lb" sw0flows | sort], [0], [dnl
@@ -4063,9 +4073,9 @@  AT_CHECK([grep -w "ls_in_acl" sw0flows | grep 2002 | sort], [0], [dnl
   table=9 (ls_in_acl          ), priority=2002 , match=(reg0[[8]] == 1 && (tcp)), action=(reg0[[1]] = 1; reg0[[13]] = 1; reg3 = 1234; next;)
 ])
 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 && reg0[[13]] == 0), action=(ct_commit { ct_label.blocked = 0; }; next;)
-  table=12(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1 && reg0[[13]] == 1), action=(ct_commit { ct_label.blocked = 0; ct_label.label = reg3; }; next;)
+  table=14(ls_in_stateful     ), priority=0    , match=(1), action=(next;)
+  table=14(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1 && reg0[[13]] == 0), action=(ct_commit { ct_label.blocked = 0; }; next;)
+  table=14(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1 && reg0[[13]] == 1), action=(ct_commit { ct_label.blocked = 0; ct_label.label = reg3; }; next;)
 ])
 
 AT_CHECK([grep -w "ls_out_acl" sw0flows | grep 2002 | sort], [0], [dnl
@@ -4092,9 +4102,9 @@  AT_CHECK([grep -w "ls_in_acl" sw0flows | grep 2002 | sort], [0], [dnl
   table=9 (ls_in_acl          ), priority=2002 , match=(reg0[[8]] == 1 && (udp)), action=(next;)
 ])
 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 && reg0[[13]] == 0), action=(ct_commit { ct_label.blocked = 0; }; next;)
-  table=12(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1 && reg0[[13]] == 1), action=(ct_commit { ct_label.blocked = 0; ct_label.label = reg3; }; next;)
+  table=14(ls_in_stateful     ), priority=0    , match=(1), action=(next;)
+  table=14(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1 && reg0[[13]] == 0), action=(ct_commit { ct_label.blocked = 0; }; next;)
+  table=14(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1 && reg0[[13]] == 1), action=(ct_commit { ct_label.blocked = 0; ct_label.label = reg3; }; next;)
 ])
 
 AT_CHECK([grep -w "ls_out_acl" sw0flows | grep 2002 | sort], [0], [dnl
@@ -4121,9 +4131,9 @@  AT_CHECK([grep -w "ls_in_acl" sw0flows | grep 2002 | sort], [0], [dnl
   table=9 (ls_in_acl          ), priority=2002 , match=(reg0[[8]] == 1 && (udp)), action=(next;)
 ])
 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 && reg0[[13]] == 0), action=(ct_commit { ct_label.blocked = 0; }; next;)
-  table=12(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1 && reg0[[13]] == 1), action=(ct_commit { ct_label.blocked = 0; ct_label.label = reg3; }; next;)
+  table=14(ls_in_stateful     ), priority=0    , match=(1), action=(next;)
+  table=14(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1 && reg0[[13]] == 0), action=(ct_commit { ct_label.blocked = 0; }; next;)
+  table=14(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1 && reg0[[13]] == 1), action=(ct_commit { ct_label.blocked = 0; ct_label.label = reg3; }; next;)
 ])
 
 AT_CHECK([grep -w "ls_out_acl" sw0flows | grep 2002 | sort], [0], [dnl
@@ -4152,7 +4162,7 @@  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.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; 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;)
 ])
@@ -4173,7 +4183,7 @@  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=(ct.est && !ct.rel && !ct.new && ct.rpl && ct_label.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; next;)
   table=9 (ls_in_acl          ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
 ])
 
@@ -4196,7 +4206,7 @@  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.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; 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;)
 ])
@@ -4368,20 +4378,20 @@  ovn-nbctl --wait=sb lsp-set-dhcpv4-options sw0-port1 $CIDR_UUID
 ovn-sbctl dump-flows sw0 > sw0flows
 AT_CAPTURE_FILE([sw0flows])
 
-AT_CHECK([grep -w "ls_in_dhcp_options" sw0flows | sort], [0], [dnl
-  table=17(ls_in_dhcp_options ), priority=0    , match=(1), action=(next;)
-  table=17(ls_in_dhcp_options ), priority=100  , match=(inport == "sw0-port1" && eth.src == 50:54:00:00:00:01 && ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && udp.src == 68 && udp.dst == 67), action=(reg0[[3]] = put_dhcp_opts(offerip = 10.0.0.2, hostname = "foo", lease_time = 3600, netmask = 255.255.255.0, router = 10.0.0.1, server_id = 10.0.0.1); next;)
-  table=17(ls_in_dhcp_options ), priority=100  , match=(inport == "sw0-port1" && eth.src == 50:54:00:00:00:01 && ip4.src == 10.0.0.2 && ip4.dst == {10.0.0.1, 255.255.255.255} && udp.src == 68 && udp.dst == 67), action=(reg0[[3]] = put_dhcp_opts(offerip = 10.0.0.2, hostname = "foo", lease_time = 3600, netmask = 255.255.255.0, router = 10.0.0.1, server_id = 10.0.0.1); next;)
+AT_CHECK([grep -w "ls_in_dhcp_options" sw0flows | sort | sed 's/table=../table=??/'], [0], [dnl
+  table=??(ls_in_dhcp_options ), priority=0    , match=(1), action=(next;)
+  table=??(ls_in_dhcp_options ), priority=100  , match=(inport == "sw0-port1" && eth.src == 50:54:00:00:00:01 && ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && udp.src == 68 && udp.dst == 67), action=(reg0[[3]] = put_dhcp_opts(offerip = 10.0.0.2, hostname = "foo", lease_time = 3600, netmask = 255.255.255.0, router = 10.0.0.1, server_id = 10.0.0.1); next;)
+  table=??(ls_in_dhcp_options ), priority=100  , match=(inport == "sw0-port1" && eth.src == 50:54:00:00:00:01 && ip4.src == 10.0.0.2 && ip4.dst == {10.0.0.1, 255.255.255.255} && udp.src == 68 && udp.dst == 67), action=(reg0[[3]] = put_dhcp_opts(offerip = 10.0.0.2, hostname = "foo", lease_time = 3600, netmask = 255.255.255.0, router = 10.0.0.1, server_id = 10.0.0.1); next;)
 ])
 
 check ovn-nbctl --wait=sb lsp-set-options sw0-port1 hostname="\"port1\""
 ovn-sbctl dump-flows sw0 > sw0flows
 AT_CAPTURE_FILE([sw0flows])
 
-AT_CHECK([grep -w "ls_in_dhcp_options" sw0flows | sort], [0], [dnl
-  table=17(ls_in_dhcp_options ), priority=0    , match=(1), action=(next;)
-  table=17(ls_in_dhcp_options ), priority=100  , match=(inport == "sw0-port1" && eth.src == 50:54:00:00:00:01 && ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && udp.src == 68 && udp.dst == 67), action=(reg0[[3]] = put_dhcp_opts(offerip = 10.0.0.2, hostname = "port1", lease_time = 3600, netmask = 255.255.255.0, router = 10.0.0.1, server_id = 10.0.0.1); next;)
-  table=17(ls_in_dhcp_options ), priority=100  , match=(inport == "sw0-port1" && eth.src == 50:54:00:00:00:01 && ip4.src == 10.0.0.2 && ip4.dst == {10.0.0.1, 255.255.255.255} && udp.src == 68 && udp.dst == 67), action=(reg0[[3]] = put_dhcp_opts(offerip = 10.0.0.2, hostname = "port1", lease_time = 3600, netmask = 255.255.255.0, router = 10.0.0.1, server_id = 10.0.0.1); next;)
+AT_CHECK([grep -w "ls_in_dhcp_options" sw0flows | sort | sed 's/table=../table=??/'], [0], [dnl
+  table=??(ls_in_dhcp_options ), priority=0    , match=(1), action=(next;)
+  table=??(ls_in_dhcp_options ), priority=100  , match=(inport == "sw0-port1" && eth.src == 50:54:00:00:00:01 && ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && udp.src == 68 && udp.dst == 67), action=(reg0[[3]] = put_dhcp_opts(offerip = 10.0.0.2, hostname = "port1", lease_time = 3600, netmask = 255.255.255.0, router = 10.0.0.1, server_id = 10.0.0.1); next;)
+  table=??(ls_in_dhcp_options ), priority=100  , match=(inport == "sw0-port1" && eth.src == 50:54:00:00:00:01 && ip4.src == 10.0.0.2 && ip4.dst == {10.0.0.1, 255.255.255.255} && udp.src == 68 && udp.dst == 67), action=(reg0[[3]] = put_dhcp_opts(offerip = 10.0.0.2, hostname = "port1", lease_time = 3600, netmask = 255.255.255.0, router = 10.0.0.1, server_id = 10.0.0.1); next;)
 ])
 
 ovn-nbctl dhcp-options-set-options $CIDR_UUID  lease_time=3600   router=10.0.0.1   server_id=10.0.0.1   server_mac=c0:ff:ee:00:00:01
@@ -4389,10 +4399,10 @@  check ovn-nbctl --wait=sb lsp-set-options sw0-port1 hostname="\"bar\""
 ovn-sbctl dump-flows sw0 > sw0flows
 AT_CAPTURE_FILE([sw0flows])
 
-AT_CHECK([grep -w "ls_in_dhcp_options" sw0flows | sort], [0], [dnl
-  table=17(ls_in_dhcp_options ), priority=0    , match=(1), action=(next;)
-  table=17(ls_in_dhcp_options ), priority=100  , match=(inport == "sw0-port1" && eth.src == 50:54:00:00:00:01 && ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && udp.src == 68 && udp.dst == 67), action=(reg0[[3]] = put_dhcp_opts(offerip = 10.0.0.2, hostname = "bar", lease_time = 3600, netmask = 255.255.255.0, router = 10.0.0.1, server_id = 10.0.0.1); next;)
-  table=17(ls_in_dhcp_options ), priority=100  , match=(inport == "sw0-port1" && eth.src == 50:54:00:00:00:01 && ip4.src == 10.0.0.2 && ip4.dst == {10.0.0.1, 255.255.255.255} && udp.src == 68 && udp.dst == 67), action=(reg0[[3]] = put_dhcp_opts(offerip = 10.0.0.2, hostname = "bar", lease_time = 3600, netmask = 255.255.255.0, router = 10.0.0.1, server_id = 10.0.0.1); next;)
+AT_CHECK([grep -w "ls_in_dhcp_options" sw0flows | sort | sed 's/table=../table=??/'], [0], [dnl
+  table=??(ls_in_dhcp_options ), priority=0    , match=(1), action=(next;)
+  table=??(ls_in_dhcp_options ), priority=100  , match=(inport == "sw0-port1" && eth.src == 50:54:00:00:00:01 && ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && udp.src == 68 && udp.dst == 67), action=(reg0[[3]] = put_dhcp_opts(offerip = 10.0.0.2, hostname = "bar", lease_time = 3600, netmask = 255.255.255.0, router = 10.0.0.1, server_id = 10.0.0.1); next;)
+  table=??(ls_in_dhcp_options ), priority=100  , match=(inport == "sw0-port1" && eth.src == 50:54:00:00:00:01 && ip4.src == 10.0.0.2 && ip4.dst == {10.0.0.1, 255.255.255.255} && udp.src == 68 && udp.dst == 67), action=(reg0[[3]] = put_dhcp_opts(offerip = 10.0.0.2, hostname = "bar", lease_time = 3600, netmask = 255.255.255.0, router = 10.0.0.1, server_id = 10.0.0.1); next;)
 ])
 
 AT_CLEANUP
@@ -6249,3 +6259,170 @@  check_log_flows_count 0 in
 
 AT_CLEANUP
 ])
+
+AT_SETUP([ACLs after lb])
+AT_KEYWORDS([acl])
+ovn_start
+
+check ovn-nbctl --wait=sb \
+    -- ls-add ls \
+    -- lsp-add ls lsp
+
+check ovn-nbctl pg-add pg0 lsp
+
+check ovn-nbctl acl-add pg0 from-lport 1004 "ip4 && ip4.dst == 10.0.0.2" drop
+check ovn-nbctl acl-add pg0 from-lport 1002 "ip4 && tcp" allow-related
+check ovn-nbctl acl-add pg0 from-lport 1003 "ip4 && icmp" allow-related
+check ovn-nbctl acl-add pg0 from-lport 1001 "ip4" drop
+
+check ovn-nbctl lb-add lb0 10.0.0.2 10.0.0.10
+check ovn-nbctl ls-lb-add ls lb0
+check ovn-nbctl --wait=sb sync
+
+ovn-sbctl dump-flows ls > lsflows
+AT_CAPTURE_FILE([lsflows])
+
+AT_CHECK([grep -e "ls_in_acl" lsflows | sed 's/table=../table=??/' | sort], [0], [dnl
+  table=??(ls_in_acl          ), priority=0    , match=(1), action=(next;)
+  table=??(ls_in_acl          ), priority=1    , match=(ip && (!ct.est || (ct.est && ct_label.blocked == 1))), action=(reg0[[1]] = 1; next;)
+  table=??(ls_in_acl          ), priority=2001 , match=(reg0[[10]] == 1 && (ip4)), action=(ct_commit { ct_label.blocked = 1; }; /* drop */)
+  table=??(ls_in_acl          ), priority=2001 , match=(reg0[[9]] == 1 && (ip4)), action=(/* drop */)
+  table=??(ls_in_acl          ), priority=2002 , match=(reg0[[7]] == 1 && (ip4 && tcp)), action=(reg0[[1]] = 1; next;)
+  table=??(ls_in_acl          ), priority=2002 , match=(reg0[[8]] == 1 && (ip4 && tcp)), action=(next;)
+  table=??(ls_in_acl          ), priority=2003 , match=(reg0[[7]] == 1 && (ip4 && icmp)), action=(reg0[[1]] = 1; next;)
+  table=??(ls_in_acl          ), priority=2003 , match=(reg0[[8]] == 1 && (ip4 && icmp)), action=(next;)
+  table=??(ls_in_acl          ), priority=2004 , match=(reg0[[10]] == 1 && (ip4 && ip4.dst == 10.0.0.2)), action=(ct_commit { ct_label.blocked = 1; }; /* drop */)
+  table=??(ls_in_acl          ), priority=2004 , match=(reg0[[9]] == 1 && (ip4 && ip4.dst == 10.0.0.2)), action=(/* drop */)
+  table=??(ls_in_acl          ), priority=34000, match=(eth.dst == $svc_monitor_mac), action=(next;)
+  table=??(ls_in_acl          ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;)
+  table=??(ls_in_acl          ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; next;)
+  table=??(ls_in_acl          ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;)
+  table=??(ls_in_acl          ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+  table=??(ls_in_acl_after_lb ), priority=0    , match=(1), action=(next;)
+  table=??(ls_in_acl_hint     ), priority=0    , match=(1), action=(next;)
+  table=??(ls_in_acl_hint     ), priority=1    , match=(ct.est && ct_label.blocked == 0), action=(reg0[[10]] = 1; next;)
+  table=??(ls_in_acl_hint     ), priority=2    , match=(ct.est && ct_label.blocked == 1), action=(reg0[[9]] = 1; next;)
+  table=??(ls_in_acl_hint     ), priority=3    , match=(!ct.est), action=(reg0[[9]] = 1; next;)
+  table=??(ls_in_acl_hint     ), priority=4    , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 0), action=(reg0[[8]] = 1; reg0[[10]] = 1; next;)
+  table=??(ls_in_acl_hint     ), priority=5    , match=(!ct.trk), action=(reg0[[8]] = 1; reg0[[9]] = 1; next;)
+  table=??(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=??(ls_in_acl_hint     ), priority=7    , match=(ct.new && !ct.est), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;)
+])
+
+AT_CHECK([grep -e "ls_in_lb" lsflows | sed 's/table=../table=??/' | sort], [0], [dnl
+  table=??(ls_in_lb           ), priority=0    , match=(1), action=(next;)
+  table=??(ls_in_lb           ), priority=110  , match=(ct.new && ip4.dst == 10.0.0.2), action=(reg0[[1]] = 0; reg1 = 10.0.0.2; ct_lb(backends=10.0.0.10);)
+])
+
+AT_CHECK([grep -e "ls_in_stateful" lsflows | sed 's/table=../table=??/' | sort], [0], [dnl
+  table=??(ls_in_stateful     ), priority=0    , match=(1), action=(next;)
+  table=??(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1 && reg0[[13]] == 0), action=(ct_commit { ct_label.blocked = 0; }; next;)
+  table=??(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1 && reg0[[13]] == 1), action=(ct_commit { ct_label.blocked = 0; ct_label.label = reg3; }; next;)
+])
+
+AS_BOX([Remove and add the ACLs back with the apply-after-lb option])
+
+check ovn-nbctl clear port_group . acls
+
+check ovn-nbctl --apply-after-lb acl-add pg0 from-lport 1004 "ip4 && ip4.dst == 10.0.0.2" drop
+check ovn-nbctl --apply-after-lb acl-add pg0 from-lport 1002 "ip4 && tcp" allow-related
+check ovn-nbctl --apply-after-lb acl-add pg0 from-lport 1003 "ip4 && icmp" allow-related
+check ovn-nbctl --apply-after-lb acl-add pg0 from-lport 1001 "ip4" drop
+
+check ovn-nbctl --wait=sb sync
+
+ovn-sbctl dump-flows ls > lsflows
+AT_CAPTURE_FILE([lsflows])
+
+AT_CHECK([grep -e "ls_in_acl" lsflows | sed 's/table=../table=??/' | sort], [0], [dnl
+  table=??(ls_in_acl          ), priority=0    , match=(1), action=(next;)
+  table=??(ls_in_acl          ), priority=1    , match=(ip && (!ct.est || (ct.est && ct_label.blocked == 1))), action=(reg0[[1]] = 1; next;)
+  table=??(ls_in_acl          ), priority=34000, match=(eth.dst == $svc_monitor_mac), action=(next;)
+  table=??(ls_in_acl          ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;)
+  table=??(ls_in_acl          ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; next;)
+  table=??(ls_in_acl          ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;)
+  table=??(ls_in_acl          ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+  table=??(ls_in_acl_after_lb ), priority=0    , match=(1), action=(next;)
+  table=??(ls_in_acl_after_lb ), priority=2001 , match=(reg0[[10]] == 1 && (ip4)), action=(ct_commit { ct_label.blocked = 1; }; /* drop */)
+  table=??(ls_in_acl_after_lb ), priority=2001 , match=(reg0[[9]] == 1 && (ip4)), action=(/* drop */)
+  table=??(ls_in_acl_after_lb ), priority=2002 , match=(reg0[[7]] == 1 && (ip4 && tcp)), action=(reg0[[1]] = 1; next;)
+  table=??(ls_in_acl_after_lb ), priority=2002 , match=(reg0[[8]] == 1 && (ip4 && tcp)), action=(next;)
+  table=??(ls_in_acl_after_lb ), priority=2003 , match=(reg0[[7]] == 1 && (ip4 && icmp)), action=(reg0[[1]] = 1; next;)
+  table=??(ls_in_acl_after_lb ), priority=2003 , match=(reg0[[8]] == 1 && (ip4 && icmp)), action=(next;)
+  table=??(ls_in_acl_after_lb ), priority=2004 , match=(reg0[[10]] == 1 && (ip4 && ip4.dst == 10.0.0.2)), action=(ct_commit { ct_label.blocked = 1; }; /* drop */)
+  table=??(ls_in_acl_after_lb ), priority=2004 , match=(reg0[[9]] == 1 && (ip4 && ip4.dst == 10.0.0.2)), action=(/* drop */)
+  table=??(ls_in_acl_hint     ), priority=0    , match=(1), action=(next;)
+  table=??(ls_in_acl_hint     ), priority=1    , match=(ct.est && ct_label.blocked == 0), action=(reg0[[10]] = 1; next;)
+  table=??(ls_in_acl_hint     ), priority=2    , match=(ct.est && ct_label.blocked == 1), action=(reg0[[9]] = 1; next;)
+  table=??(ls_in_acl_hint     ), priority=3    , match=(!ct.est), action=(reg0[[9]] = 1; next;)
+  table=??(ls_in_acl_hint     ), priority=4    , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 0), action=(reg0[[8]] = 1; reg0[[10]] = 1; next;)
+  table=??(ls_in_acl_hint     ), priority=5    , match=(!ct.trk), action=(reg0[[8]] = 1; reg0[[9]] = 1; next;)
+  table=??(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=??(ls_in_acl_hint     ), priority=7    , match=(ct.new && !ct.est), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;)
+])
+
+AT_CHECK([grep -e "ls_in_lb" lsflows | sed 's/table=../table=??/' | sort], [0], [dnl
+  table=??(ls_in_lb           ), priority=0    , match=(1), action=(next;)
+  table=??(ls_in_lb           ), priority=110  , match=(ct.new && ip4.dst == 10.0.0.2), action=(reg0[[1]] = 0; reg1 = 10.0.0.2; ct_lb(backends=10.0.0.10);)
+])
+
+AT_CHECK([grep -e "ls_in_stateful" lsflows | sed 's/table=../table=??/' | sort], [0], [dnl
+  table=??(ls_in_stateful     ), priority=0    , match=(1), action=(next;)
+  table=??(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1 && reg0[[13]] == 0), action=(ct_commit { ct_label.blocked = 0; }; next;)
+  table=??(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1 && reg0[[13]] == 1), action=(ct_commit { ct_label.blocked = 0; ct_label.label = reg3; }; next;)
+])
+
+AS_BOX([Remove and add the ACLs back with a few ACLs with apply-after-lb option])
+
+check ovn-nbctl clear port_group . acls
+
+check ovn-nbctl --apply-after-lb acl-add pg0 from-lport 1004 "ip4 && ip4.dst == 10.0.0.2" drop
+check ovn-nbctl acl-add pg0 from-lport 1002 "ip4 && tcp" allow-related
+check ovn-nbctl acl-add pg0 from-lport 1003 "ip4 && icmp" allow-related
+check ovn-nbctl --apply-after-lb acl-add pg0 from-lport 1001 "ip4" drop
+
+check ovn-nbctl --wait=sb sync
+
+ovn-sbctl dump-flows ls > lsflows
+AT_CAPTURE_FILE([lsflows])
+
+AT_CHECK([grep -e "ls_in_acl" lsflows | sed 's/table=../table=??/' | sort], [0], [dnl
+  table=??(ls_in_acl          ), priority=0    , match=(1), action=(next;)
+  table=??(ls_in_acl          ), priority=1    , match=(ip && (!ct.est || (ct.est && ct_label.blocked == 1))), action=(reg0[[1]] = 1; next;)
+  table=??(ls_in_acl          ), priority=2002 , match=(reg0[[7]] == 1 && (ip4 && tcp)), action=(reg0[[1]] = 1; next;)
+  table=??(ls_in_acl          ), priority=2002 , match=(reg0[[8]] == 1 && (ip4 && tcp)), action=(next;)
+  table=??(ls_in_acl          ), priority=2003 , match=(reg0[[7]] == 1 && (ip4 && icmp)), action=(reg0[[1]] = 1; next;)
+  table=??(ls_in_acl          ), priority=2003 , match=(reg0[[8]] == 1 && (ip4 && icmp)), action=(next;)
+  table=??(ls_in_acl          ), priority=34000, match=(eth.dst == $svc_monitor_mac), action=(next;)
+  table=??(ls_in_acl          ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;)
+  table=??(ls_in_acl          ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; next;)
+  table=??(ls_in_acl          ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;)
+  table=??(ls_in_acl          ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+  table=??(ls_in_acl_after_lb ), priority=0    , match=(1), action=(next;)
+  table=??(ls_in_acl_after_lb ), priority=2001 , match=(reg0[[10]] == 1 && (ip4)), action=(ct_commit { ct_label.blocked = 1; }; /* drop */)
+  table=??(ls_in_acl_after_lb ), priority=2001 , match=(reg0[[9]] == 1 && (ip4)), action=(/* drop */)
+  table=??(ls_in_acl_after_lb ), priority=2004 , match=(reg0[[10]] == 1 && (ip4 && ip4.dst == 10.0.0.2)), action=(ct_commit { ct_label.blocked = 1; }; /* drop */)
+  table=??(ls_in_acl_after_lb ), priority=2004 , match=(reg0[[9]] == 1 && (ip4 && ip4.dst == 10.0.0.2)), action=(/* drop */)
+  table=??(ls_in_acl_hint     ), priority=0    , match=(1), action=(next;)
+  table=??(ls_in_acl_hint     ), priority=1    , match=(ct.est && ct_label.blocked == 0), action=(reg0[[10]] = 1; next;)
+  table=??(ls_in_acl_hint     ), priority=2    , match=(ct.est && ct_label.blocked == 1), action=(reg0[[9]] = 1; next;)
+  table=??(ls_in_acl_hint     ), priority=3    , match=(!ct.est), action=(reg0[[9]] = 1; next;)
+  table=??(ls_in_acl_hint     ), priority=4    , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 0), action=(reg0[[8]] = 1; reg0[[10]] = 1; next;)
+  table=??(ls_in_acl_hint     ), priority=5    , match=(!ct.trk), action=(reg0[[8]] = 1; reg0[[9]] = 1; next;)
+  table=??(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=??(ls_in_acl_hint     ), priority=7    , match=(ct.new && !ct.est), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;)
+])
+
+AT_CHECK([grep -e "ls_in_lb" lsflows | sed 's/table=../table=??/' | sort], [0], [dnl
+  table=??(ls_in_lb           ), priority=0    , match=(1), action=(next;)
+  table=??(ls_in_lb           ), priority=110  , match=(ct.new && ip4.dst == 10.0.0.2), action=(reg0[[1]] = 0; reg1 = 10.0.0.2; ct_lb(backends=10.0.0.10);)
+])
+
+AT_CHECK([grep -e "ls_in_stateful" lsflows | sed 's/table=../table=??/' | sort], [0], [dnl
+  table=??(ls_in_stateful     ), priority=0    , match=(1), action=(next;)
+  table=??(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1 && reg0[[13]] == 0), action=(ct_commit { ct_label.blocked = 0; }; next;)
+  table=??(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1 && reg0[[13]] == 1), action=(ct_commit { ct_label.blocked = 0; ct_label.label = reg3; }; next;)
+])
+
+AT_CLEANUP
+])
diff --git a/tests/ovn.at b/tests/ovn.at
index 69270601ab..a19ab4651d 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -3393,8 +3393,8 @@  wait_for_ports_up
 ovn-sbctl dump-flows ls > lsflows
 AT_CAPTURE_FILE([lsflows])
 
-AT_CHECK([grep -w "ls_in_arp_rsp" lsflows | sort], [0], [dnl
-  table=16(ls_in_arp_rsp      ), priority=0    , match=(1), action=(next;)
+AT_CHECK([grep -w "ls_in_arp_rsp" lsflows | sed 's/table=../table=??/' | sort], [0], [dnl
+  table=??(ls_in_arp_rsp      ), priority=0    , match=(1), action=(next;)
 ])
 
 for i in 1 2; do
@@ -3456,8 +3456,8 @@  wait_for_ports_up
 ovn-sbctl dump-flows ls > lsflows
 AT_CAPTURE_FILE([lsflows])
 
-AT_CHECK([grep -w "ls_in_arp_rsp" lsflows | sort], [0], [dnl
-  table=16(ls_in_arp_rsp      ), priority=0    , match=(1), action=(next;)
+AT_CHECK([grep -w "ls_in_arp_rsp" lsflows | sed 's/table=../table=??/' | sort], [0], [dnl
+  table=??(ls_in_arp_rsp      ), priority=0    , match=(1), action=(next;)
 ])
 
 test_nd_na() {
@@ -14457,7 +14457,7 @@  ovn-sbctl dump-flows sw0 > sw0-flows
 AT_CAPTURE_FILE([sw0-flows])
 
 AT_CHECK([grep -E 'ls_(in|out)_acl' sw0-flows |grep reject| sed 's/table=../table=??/' | sort], [0], [dnl
-  table=??(ls_out_acl         ), priority=2002 , match=(ip), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=22); };)
+  table=??(ls_out_acl         ), priority=2002 , match=(ip), action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=24); };)
 ])
 
 
@@ -16348,17 +16348,17 @@  ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
 AT_CHECK([ovn-sbctl dump-flows ls1 | grep "offerip = 10.0.0.6" | \
 wc -l], [0], [0
 ])
-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=23 | \
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=25 | \
 grep controller | grep "0a.00.00.06" | wc -l], [0], [0
 ])
-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=23 | \
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=25 | \
 grep controller | grep "0a.00.00.06" | wc -l], [0], [0
 ])
-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=23 | \
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=25 | \
 grep controller | grep tp_src=546 | grep \
 "ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0
 ])
-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=23 | \
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=25 | \
 grep controller | grep tp_src=546 | grep \
 "ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0
 ])
@@ -16966,7 +16966,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=29,dl_src=f0:00:00:00:00:03,dl_dst=a0:10:00:00:00:01 | \
+table=31,dl_src=f0:00:00:00:00:03,dl_dst=a0:10:00:00:00:01 | \
 grep -c "actions=drop"], [0], [1
 ])
 
@@ -18643,7 +18643,7 @@  check_row_count Port_Binding 1 logical_port=sw0-vir virtual_parent=sw0-p1
 wait_for_ports_up sw0-vir
 check ovn-nbctl --wait=hv sync
 AT_CHECK([test 2 = `cat hv1/ovn-controller.log | grep "pinctrl received  packet-in" | \
-grep opcode=BIND_VPORT | grep OF_Table_ID=24 | wc -l`])
+grep opcode=BIND_VPORT | grep OF_Table_ID=26 | wc -l`])
 
 wait_row_count Port_Binding 1 logical_port=sw0-vir6 chassis=$hv1_ch_uuid
 check_row_count Port_Binding 1 logical_port=sw0-vir6 virtual_parent=sw0-p1
@@ -18692,7 +18692,7 @@  eth_dst=00000000ff01
 ip_src=$(ip_to_hex 10 0 0 10)
 ip_dst=$(ip_to_hex 172 168 0 101)
 send_icmp_packet 1 1 $eth_src $eth_dst $ip_src $ip_dst c4c9 0000000000000000000000
-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | awk '/table=26, n_packets=1, n_bytes=45/{print $7" "$8}'],[0],[dnl
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int metadata=0x$lr0_dp_key | awk '/table=26, n_packets=1, n_bytes=45/{print $7" "$8}'],[0],[dnl
 priority=80,ip,reg15=0x3,metadata=0x3,nw_src=10.0.0.10 actions=drop
 ])
 
@@ -21832,7 +21832,7 @@  OVS_WAIT_FOR_OUTPUT(
   (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");)
+  (ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg0[[1]] = 0; 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])
@@ -21874,7 +21874,7 @@  ovn-sbctl dump-flows sw0 > sbflows3
 AT_CHECK(
   [grep "ip4.dst == 10.0.0.10 && tcp.dst == 80" sbflows3 | grep priority=120 |\
    sed 's/table=../table=??/'], [0],
-  [  table=??(ls_in_stateful     ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(drop;)
+  [  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(drop;)
 ])
 
 AT_CAPTURE_FILE([sbflows4])
@@ -28772,8 +28772,8 @@  options arp_proxy='"169.254.239.254 169.254.239.2"'
 ovn-sbctl dump-flows > sbflows
 AT_CAPTURE_FILE([sbflows])
 
-AT_CHECK([ovn-sbctl dump-flows | grep ls_in_arp_rsp | grep "169.254.239.2"], [0], [dnl
-  table=16(ls_in_arp_rsp      ), priority=50   , match=(arp.op == 1 && arp.tpa == {169.254.239.254,169.254.239.2}), dnl
+AT_CHECK([ovn-sbctl dump-flows | grep ls_in_arp_rsp | grep "169.254.239.2" | sed 's/table=../table=??/'], [0], [dnl
+  table=??(ls_in_arp_rsp      ), priority=50   , match=(arp.op == 1 && arp.tpa == {169.254.239.254,169.254.239.2}), dnl
 action=(eth.dst = eth.src; eth.src = 00:00:00:01:02:f1; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha = 00:00:00:01:02:f1; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;)
 ])
 
@@ -29254,26 +29254,26 @@  done
 check ovn-nbctl --wait=hv sync
 
 # hv0 should see flows for lsp1 but not lsp2
-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=24 | grep 10.0.1.2], [0], [ignore])
-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=24 | grep 10.0.2.2], [1])
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=26 | grep 10.0.1.2], [0], [ignore])
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=26 | grep 10.0.2.2], [1])
 # hv2 should see flows for lsp2 but not lsp1
-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=24 | grep 10.0.2.2], [0], [ignore])
-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=24 | grep 10.0.1.2], [1])
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=26 | grep 10.0.2.2], [0], [ignore])
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=26 | grep 10.0.1.2], [1])
 
 # Change lrp_lr_ls1 to a regular lrp, hv2 should see flows for lsp1
 check ovn-nbctl --wait=hv lrp-del-gateway-chassis lrp_lr_ls1 hv1
-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=24 | grep 10.0.1.2], [0], [ignore])
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=26 | grep 10.0.1.2], [0], [ignore])
 
 # Change it back, and trigger recompute to make sure extra flows are removed
 # from hv2 (recompute is needed because currently I-P adds local datapaths but
 # doesn't remove.)
 check ovn-nbctl --wait=hv lrp-set-gateway-chassis lrp_lr_ls1 hv1 1
 as hv2 check ovn-appctl -t ovn-controller recompute
-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=24 | grep 10.0.1.2], [1])
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=26 | grep 10.0.1.2], [1])
 
 # Enable dnat_and_snat on lr, and now hv2 should see flows for lsp1.
 AT_CHECK([ovn-nbctl --wait=hv lr-nat-add lr dnat_and_snat 192.168.0.1 10.0.1.3 lsp1 f0:00:00:00:00:03])
-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=24 | grep 10.0.1.2], [0], [ignore])
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=26 | grep 10.0.1.2], [0], [ignore])
 
 OVN_CLEANUP([hv1],[hv2])
 AT_CLEANUP
diff --git a/tests/system-ovn.at b/tests/system-ovn.at
index f57d752d44..d4c22e7e90 100644
--- a/tests/system-ovn.at
+++ b/tests/system-ovn.at
@@ -1448,6 +1448,32 @@  OVS_START_L7([bar1], [http])
 OVS_START_L7([bar2], [http])
 OVS_START_L7([bar3], [http])
 
+# Add ACLs (after lb) to drop the traffic if destined to backend  ips.
+check ovn-nbctl --apply-after-lb acl-add foo from-lport 1002 "ip4 && ip4.dst == {172.16.1.2,172.16.1.3,172.16.1.4} && ct.new" drop
+check ovn-nbctl --wait=hv sync
+
+AT_CHECK([ip netns exec foo1 wget 30.0.0.1 -t 3 -T 1], [4], [ignore], [ignore])
+
+AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+
+# Clear the apply-after-lb option.  The traffic will be allowed.
+check ovn-nbctl clear acl . options
+ovn-nbctl --wait=hv sync
+
+OVS_WAIT_FOR_OUTPUT([
+    for i in `seq 1 20`; do
+        ip netns exec foo1 wget 30.0.0.1 -t 5 -T 1 --retry-connrefused -v -o wget$i.log;
+    done
+    ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | \
+      sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+tcp,orig=(src=192.168.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.2,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,labels=0x2,protoinfo=(state=<cleared>)
+tcp,orig=(src=192.168.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.3,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,labels=0x2,protoinfo=(state=<cleared>)
+tcp,orig=(src=192.168.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.4,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,labels=0x2,protoinfo=(state=<cleared>)
+])
+
+ovn-nbctl acl-del foo from-lport 1002 "ip4 && ip4.dst == {172.16.1.2,172.16.1.3,172.16.1.4} && ct.new"
+ovn-nbctl --wait=hv sync
+
 dnl Should work with the virtual IP 30.0.0.1 address through NAT
 dnl Each server should have at least one connection.
 dnl With 20 requests, one server might not receive any connection
@@ -4895,6 +4921,247 @@  aef0::3 udp port 90" | uniq | wc -l)
 ])
 
 
+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
+])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([ACL after lb - reject])
+AT_SKIP_IF([test $HAVE_NC = no])
+AT_KEYWORDS([lb])
+
+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
+
+ovn-nbctl ls-add sw0
+
+ovn-nbctl lsp-add sw0 sw0-p1-rej
+ovn-nbctl lsp-set-addresses sw0-p1-rej "50:54:00:00:00:03 10.0.0.3 aef0::3"
+ovn-nbctl lsp-set-port-security sw0-p1-rej "50:54:00:00:00:03 10.0.0.3 aef0::3"
+
+ovn-nbctl lsp-add sw0 sw0-p2-rej
+ovn-nbctl lsp-set-addresses sw0-p2-rej "50:54:00:00:00:04 10.0.0.4 aef0::4"
+ovn-nbctl lsp-set-port-security sw0-p2-rej "50:54:00:00:00:04 10.0.0.4 aef0::4"
+
+# Create port group and ACLs for sw0 ports.
+ovn-nbctl pg-add pg0_drop sw0-p1-rej sw0-p2-rej
+ovn-nbctl --apply-after-lb acl-add pg0_drop from-lport 1001 "inport == @pg0_drop && ip" drop
+ovn-nbctl acl-add pg0_drop to-lport 1001 "outport == @pg0_drop && ip" drop
+
+ovn-nbctl pg-add pg0 sw0-p1-rej sw0-p2-rej
+ovn-nbctl --apply-after-lb acl-add pg0 from-lport 1002 "inport == @pg0 && ip" allow-related
+ovn-nbctl --log --apply-after-lb acl-add pg0 from-lport 1004 "inport == @pg0 && ip && tcp && tcp.dst == 80" reject
+ovn-nbctl --log --apply-after-lb acl-add pg0 from-lport 1004 "inport == @pg0 && ip && udp && udp.dst == 90" reject
+
+ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && ip4.src == 0.0.0.0/0 && tcp && tcp.dst == 82" allow-related
+ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && ip4.src == 0.0.0.0/0 && udp && udp.dst == 82" allow-related
+ovn-nbctl --log acl-add pg0 to-lport 1004 "inport == @pg0 && ip && tcp && tcp.dst == 84" reject
+ovn-nbctl --log acl-add pg0 to-lport 1004 "inport == @pg0 && ip && udp && udp.dst == 94" reject
+
+ovn-nbctl ls-add sw1
+ovn-nbctl lsp-add sw1 sw1-p1-rej
+ovn-nbctl lsp-set-addresses sw1-p1-rej "40:54:00:00:00:03 20.0.0.3"
+ovn-nbctl lsp-set-port-security sw1-p1-rej "40:54:00:00:00:03 20.0.0.3"
+
+ovn-nbctl lr-add lr0
+ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24
+ovn-nbctl lsp-add sw0 sw0-lr0
+ovn-nbctl lsp-set-type sw0-lr0 router
+ovn-nbctl lsp-set-addresses sw0-lr0 router
+ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
+
+ovn-nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 20.0.0.1/24
+ovn-nbctl lsp-add sw1 sw1-lr0
+ovn-nbctl lsp-set-type sw1-lr0 router
+ovn-nbctl lsp-set-addresses sw1-lr0 router
+ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1
+
+OVN_POPULATE_ARP
+ovn-nbctl --wait=hv sync
+
+ADD_NAMESPACES(sw0-p1-rej)
+ADD_VETH(sw0-p1-rej, sw0-p1-rej, br-int, "10.0.0.3/24", "50:54:00:00:00:03", \
+         "10.0.0.1")
+
+ADD_NAMESPACES(sw0-p2-rej)
+ADD_VETH(sw0-p2-rej, sw0-p2-rej, br-int, "10.0.0.4/24", "50:54:00:00:00:04", \
+         "10.0.0.1")
+
+NS_CHECK_EXEC([sw0-p1-rej], [ip a a aef0::3/64 dev sw0-p1-rej], [0])
+NS_CHECK_EXEC([sw0-p2-rej], [ip a a aef0::4/64 dev sw0-p2-rej], [0])
+
+ADD_NAMESPACES(sw1-p1-rej)
+ADD_VETH(sw1-p1-rej, sw1-p1-rej, br-int, "20.0.0.3/24", "40:54:00:00:00:03", \
+         "20.0.0.1")
+
+sleep 1
+
+# Capture packets in sw0-p1-rej.
+NS_CHECK_EXEC([sw0-p1-rej], [tcpdump -nn -c 4 -i sw0-p1-rej tcp > sw0-p1-rej-ip4.pcap &], [0])
+
+sleep 1
+
+OVS_WAIT_UNTIL([
+    ip netns exec sw0-p1-rej nc -vz 10.0.0.4 80 2>&1 | grep -i 'connection refused'
+])
+
+# Now send traffic to port 84
+OVS_WAIT_UNTIL([
+    ip netns exec sw0-p1-rej nc -vz 10.0.0.4 84 2>&1 | grep -i 'connection refused'
+])
+
+OVS_WAIT_UNTIL([
+    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
+])
+
+OVS_WAIT_UNTIL([
+    total=`cat sw0-p1-rej-ip4.pcap |  wc -l`
+    echo "total = $total"
+    test "${total}" = "4"
+])
+
+# Without this sleep, test case fails intermittently.
+sleep 3
+
+NS_CHECK_EXEC([sw0-p2-rej], [tcpdump -nn -c 2 -i sw0-p2-rej tcp port 80 > sw0-p2-rej-ip6.pcap &], [0])
+
+sleep 1
+
+OVS_WAIT_UNTIL([
+    ip netns exec sw0-p2-rej nc -vz6 aef0::3 80 2>&1 | grep -i 'connection refused'
+])
+
+
+OVS_WAIT_UNTIL([
+    total=`cat sw0-p2-rej-ip6.pcap |  wc -l`
+    echo "total = $total"
+    test "${total}" = "2"
+])
+
+ovn-nbctl --apply-after-lb acl-add sw1 from-lport 1004 "ip" allow-related
+ovn-nbctl acl-add sw1 to-lport 1004 "ip" allow-related
+ovn-nbctl --log acl-add pg0 to-lport 1004 "outport == @pg0 && ip && tcp && tcp.dst == 84" reject
+
+OVS_WAIT_UNTIL([
+    ip netns exec sw1-p1-rej nc -vz 10.0.0.4 84 2>&1 | grep -i 'connection refused'
+])
+
+# Now test for IPv4 UDP.
+NS_CHECK_EXEC([sw0-p1-rej], [tcpdump -nn -c 1 -i sw0-p1-rej udp port 90 > sw0-p1-rej-udp.pcap &], [0])
+NS_CHECK_EXEC([sw0-p1-rej], [tcpdump -nn -c 1 -i sw0-p1-rej icmp > sw0-p1-rej-icmp.pcap &], [0])
+
+printf '.%.0s' {1..100} > foo
+OVS_WAIT_UNTIL([
+    ip netns exec sw0-p1-rej nc -u 10.0.0.4 90 < foo
+    c=$(cat sw0-p1-rej-icmp.pcap | grep \
+"10.0.0.4 > 10.0.0.3: ICMP 10.0.0.4 udp port 90 unreachable" | uniq | wc -l)
+    test $c -eq 1
+])
+
+rm -f *.pcap
+
+NS_CHECK_EXEC([sw0-p1-rej], [tcpdump -nn -c 1 -i sw0-p1-rej udp port 94 > sw0-p1-rej-udp.pcap &], [0])
+NS_CHECK_EXEC([sw0-p1-rej], [tcpdump -nn -c 1 -i sw0-p1-rej icmp > sw0-p1-rej-icmp.pcap &], [0])
+
+OVS_WAIT_UNTIL([
+    ip netns exec sw0-p1-rej nc -u 10.0.0.4 94 < foo
+    c=$(cat sw0-p1-rej-icmp.pcap | grep \
+"10.0.0.4 > 10.0.0.3: ICMP 10.0.0.4 udp port 94 unreachable" | uniq | wc -l)
+    test $c -eq 1
+])
+
+# Now test for IPv6 UDP.
+NS_CHECK_EXEC([sw0-p2-rej], [tcpdump -nn -c 1 -i sw0-p2-rej udp port 90 > sw0-p2-rej-ip6-udp.pcap &], [0])
+NS_CHECK_EXEC([sw0-p2-rej], [tcpdump -nn -c 1 -i sw0-p2-rej icmp6 > sw0-p2-rej-icmp6.pcap &], [0])
+
+OVS_WAIT_UNTIL([
+    ip netns exec sw0-p2-rej nc -u -6 aef0::3 90 < foo
+    c=$(cat sw0-p2-rej-icmp6.pcap | grep \
+"IP6 aef0::3 > aef0::4: ICMP6, destination unreachable, unreachable port, \
+aef0::3 udp port 90" | uniq | wc -l)
+    test $c -eq 1
+])
+
+rm -f *.pcap
+
+NS_CHECK_EXEC([sw0-p2-rej], [tcpdump -nn -c 1 -i sw0-p2-rej udp port 94 > sw0-p2-rej-ip6-udp.pcap &], [0])
+NS_CHECK_EXEC([sw0-p2-rej], [tcpdump -nn -c 1 -i sw0-p2-rej icmp6 > sw0-p2-rej-icmp6.pcap &], [0])
+
+OVS_WAIT_UNTIL([
+    ip netns exec sw0-p2-rej nc -u -6 aef0::3 94 < foo
+    c=$(cat sw0-p2-rej-icmp6.pcap | grep \
+"IP6 aef0::3 > aef0::4: ICMP6, destination unreachable, unreachable port, \
+aef0::3 udp port 94" | uniq | wc -l)
+    test $c -eq 1
+])
+
+# Delete all the ACLs of pg0 and add the ACL with a generic match with reject action.
+ovn-nbctl pg-del pg0
+ovn-nbctl pg-add pg0 sw0-p1-rej sw0-p2-rej
+ovn-nbctl --log --apply-after-lb acl-add pg0 from-lport 1004 "inport == @pg0 && ip && (tcp || udp)" reject
+
+OVS_WAIT_UNTIL([
+    ip netns exec sw0-p1-rej nc -vz 10.0.0.4 80 2>&1 | grep -i 'connection refused'
+])
+
+OVS_WAIT_UNTIL([
+    ip netns exec sw0-p2-rej nc -vz6 aef0::3 80 2>&1 | grep -i 'connection refused'
+])
+
+rm -f *.pcap
+
+NS_CHECK_EXEC([sw0-p1-rej], [tcpdump -nn -c 1 -i sw0-p1-rej icmp > sw0-p1-rej-icmp.pcap &], [0])
+
+printf '.%.0s' {1..100} > foo
+OVS_WAIT_UNTIL([
+    ip netns exec sw0-p1-rej nc -u 10.0.0.4 90 < foo
+    c=$(cat sw0-p1-rej-icmp.pcap | grep \
+"10.0.0.4 > 10.0.0.3: ICMP 10.0.0.4 udp port 90 unreachable" | uniq | wc -l)
+    test $c -eq 1
+])
+
+rm -f *.pcap
+# Now test for IPv6 UDP.
+NS_CHECK_EXEC([sw0-p2-rej], [tcpdump -nn -c 1 -i sw0-p2-rej icmp6 > sw0-p2-rej-icmp6.pcap &], [0])
+
+OVS_WAIT_UNTIL([
+    ip netns exec sw0-p2-rej nc -u -6 aef0::3 90 < foo
+    c=$(cat sw0-p2-rej-icmp6.pcap | grep \
+"IP6 aef0::3 > aef0::4: ICMP6, destination unreachable, unreachable port, \
+aef0::3 udp port 90" | uniq | wc -l)
+    test $c -eq 1
+])
+
+
 OVS_APP_EXIT_AND_WAIT([ovn-controller])
 
 as ovn-sb
@@ -6907,8 +7174,150 @@  AT_CLEANUP
 ])
 
 OVN_FOR_EACH_NORTHD([
-AT_SETUP([ACL label - conntrack label change])
-AT_KEYWORDS([acl label ct_commit label change])
+AT_SETUP([ACL label - conntrack ct_label - acl after lb])
+AT_KEYWORDS([acl label ct_commit])
+
+CHECK_CONNTRACK()
+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:02 10.0.0.2"
+check ovn-nbctl lsp-set-port-security sw0-p1 "50:54:00:00:00:02 10.0.0.2"
+
+check ovn-nbctl lsp-add sw0 sw0-p2
+check ovn-nbctl lsp-set-addresses sw0-p2 "50:54:00:00:00:03 10.0.0.3"
+check ovn-nbctl lsp-set-port-security sw0-p2 "50:54:00:00:00:03 10.0.0.3"
+
+check ovn-nbctl lsp-add sw0 sw0-p3
+check ovn-nbctl lsp-set-addresses sw0-p3 "50:54:00:00:00:04 10.0.0.4"
+check ovn-nbctl lsp-set-port-security sw0-p3 "50:54:00:00:00:04 10.0.0.4"
+
+# ACLs
+# Case 1: sw0-p1 ---> sw0-p3 allowed, label=1234
+# Case 2: sw0-p3 ---> sw0-p1 allowed, label=1235
+# Case 3: sw0-p1 ---> sw0-p2 allowed, no label
+# Case 4: sw0-p2 ---> sw0-p1 allowed, no label
+
+check ovn-nbctl --label=1234 --apply-after-lb acl-add sw0 from-lport 1002 'ip4 && inport == "sw0-p1" && ip4.dst == 10.0.0.4' allow-related
+check ovn-nbctl --label=1235 acl-add sw0 to-lport 1002 'ip4 && outport == "sw0-p1" && ip4.src == 10.0.0.4' allow-related
+check ovn-nbctl --apply-after-lb acl-add sw0 from-lport 1001 "ip" allow-related
+check ovn-nbctl acl-add sw0 to-lport 1001 "ip" allow-related
+
+
+ADD_NAMESPACES(sw0-p1)
+ADD_VETH(sw0-p1, sw0-p1, br-int, "10.0.0.2/24", "50:54:00:00:00:02", \
+         "10.0.0.1")
+ADD_NAMESPACES(sw0-p2)
+ADD_VETH(sw0-p2, sw0-p2, br-int, "10.0.0.3/24", "50:54:00:00:00:03", \
+         "10.0.0.1")
+ADD_NAMESPACES(sw0-p3)
+ADD_VETH(sw0-p3, sw0-p3, br-int, "10.0.0.4/24", "50:54:00:00:00:04", \
+         "10.0.0.1")
+
+# Ensure ovn-controller is caught up
+ovn-nbctl --wait=hv sync
+
+on_exit 'ovn-nbctl acl-list sw0'
+on_exit 'ovn-sbctl lflow-list'
+on_exit 'ovs-ofctl dump-flows br-int'
+
+wait_for_ports_up
+
+AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+# 'sw0-p1' should be able to ping 'sw0-p3'.
+NS_CHECK_EXEC([sw0-p1], [ping -q -c 10 -i 0.3 -w 15 10.0.0.4 | FORMAT_PING], \
+[0], [dnl
+10 packets transmitted, 10 received, 0% packet loss, time 0ms
+])
+
+# Ensure conntrack entry is present and ct_label is set.
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.0.0.4) | \
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/' | \
+sed -e 's/labels=0x4d2[[0-9a-f]]*/labels=0x4d2000000000000000000000000/'], [0], [dnl
+icmp,orig=(src=10.0.0.2,dst=10.0.0.4,id=<cleared>,type=8,code=0),reply=(src=10.0.0.4,dst=10.0.0.2,id=<cleared>,type=0,code=0),zone=<cleared>,labels=0x4d2000000000000000000000000
+icmp,orig=(src=10.0.0.2,dst=10.0.0.4,id=<cleared>,type=8,code=0),reply=(src=10.0.0.4,dst=10.0.0.2,id=<cleared>,type=0,code=0),zone=<cleared>
+])
+
+AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+# 'sw0-p3' should be able to ping 'sw0-p1'.
+NS_CHECK_EXEC([sw0-p3], [ping -q -c 10 -i 0.3 -w 15 10.0.0.2 | FORMAT_PING], \
+[0], [dnl
+10 packets transmitted, 10 received, 0% packet loss, time 0ms
+])
+
+# Ensure conntrack entry is present and ct_label is set.
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.0.0.2) | \
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/' | \
+sed -e 's/labels=0x4d3[[0-9a-f]]*/labels=0x4d3000000000000000000000000/'], [0], [dnl
+icmp,orig=(src=10.0.0.4,dst=10.0.0.2,id=<cleared>,type=8,code=0),reply=(src=10.0.0.2,dst=10.0.0.4,id=<cleared>,type=0,code=0),zone=<cleared>,labels=0x4d3000000000000000000000000
+icmp,orig=(src=10.0.0.4,dst=10.0.0.2,id=<cleared>,type=8,code=0),reply=(src=10.0.0.2,dst=10.0.0.4,id=<cleared>,type=0,code=0),zone=<cleared>
+])
+
+AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+# 'sw0-p1' should be able to ping 'sw0-p2'.
+NS_CHECK_EXEC([sw0-p1], [ping -q -c 10 -i 0.3 -w 15 10.0.0.3 | FORMAT_PING], \
+[0], [dnl
+10 packets transmitted, 10 received, 0% packet loss, time 0ms
+])
+
+# Ensure conntrack entry is present and ct_label is not set.
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.0.0.3) | \
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+icmp,orig=(src=10.0.0.2,dst=10.0.0.3,id=<cleared>,type=8,code=0),reply=(src=10.0.0.3,dst=10.0.0.2,id=<cleared>,type=0,code=0),zone=<cleared>
+icmp,orig=(src=10.0.0.2,dst=10.0.0.3,id=<cleared>,type=8,code=0),reply=(src=10.0.0.3,dst=10.0.0.2,id=<cleared>,type=0,code=0),zone=<cleared>
+])
+
+AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+# 'sw0-p2' should be able to ping 'sw0-p1'.
+NS_CHECK_EXEC([sw0-p2], [ping -q -c 10 -i 0.3 -w 15 10.0.0.2 | FORMAT_PING], \
+[0], [dnl
+10 packets transmitted, 10 received, 0% packet loss, time 0ms
+])
+
+# Ensure conntrack entry is present and ct_label is not set.
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.0.0.2) | \
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+icmp,orig=(src=10.0.0.3,dst=10.0.0.2,id=<cleared>,type=8,code=0),reply=(src=10.0.0.2,dst=10.0.0.3,id=<cleared>,type=0,code=0),zone=<cleared>
+icmp,orig=(src=10.0.0.3,dst=10.0.0.2,id=<cleared>,type=8,code=0),reply=(src=10.0.0.2,dst=10.0.0.3,id=<cleared>,type=0,code=0),zone=<cleared>
+])
+
+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
+])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([ACL label - conntrack label change])
+AT_KEYWORDS([acl label ct_commit label change])
 
 CHECK_CONNTRACK()
 ovn_start
@@ -7007,6 +7416,207 @@  OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
 AT_CLEANUP
 ])
 
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([ACL label - conntrack label change - acl after lb])
+AT_KEYWORDS([acl label ct_commit label change])
+
+CHECK_CONNTRACK()
+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:02 10.0.0.2"
+check ovn-nbctl lsp-set-port-security sw0-p1 "50:54:00:00:00:02 10.0.0.2"
+
+check ovn-nbctl lsp-add sw0 sw0-p2
+check ovn-nbctl lsp-set-addresses sw0-p2 "50:54:00:00:00:03 10.0.0.3"
+check ovn-nbctl lsp-set-port-security sw0-p2 "50:54:00:00:00:03 10.0.0.3"
+
+# ACLs
+# sw0-p1 ---> sw0-p2 allowed, label=1234
+
+check ovn-nbctl --label=1234 --apply-after-lb acl-add sw0 from-lport 1002 'ip4 && inport == "sw0-p1" && ip4.dst == 10.0.0.3' allow-related
+
+ADD_NAMESPACES(sw0-p1)
+ADD_VETH(sw0-p1, sw0-p1, br-int, "10.0.0.2/24", "50:54:00:00:00:02", \
+         "10.0.0.1")
+ADD_NAMESPACES(sw0-p2)
+ADD_VETH(sw0-p2, sw0-p2, br-int, "10.0.0.3/24", "50:54:00:00:00:03", \
+         "10.0.0.1")
+
+# Ensure ovn-controller is caught up
+ovn-nbctl --wait=hv sync
+
+on_exit 'ovn-nbctl acl-list sw0'
+on_exit 'ovn-sbctl lflow-list'
+on_exit 'ovs-ofctl dump-flows br-int'
+
+wait_for_ports_up
+
+AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+
+# start a background ping for ~30 secs.
+NETNS_DAEMONIZE([sw0-p1], [[ping -q -c 100 -i 0.3 -w 15 10.0.0.3]], [ns-sw0-p1.pid])
+
+sleep 3s
+
+# Ensure conntrack entry is present and ct_label is set.
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.0.0.3) | \
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/' | \
+sed -e 's/labels=0x4d2[[0-9a-f]]*/labels=0x4d2000000000000000000000000/'], [0], [dnl
+icmp,orig=(src=10.0.0.2,dst=10.0.0.3,id=<cleared>,type=8,code=0),reply=(src=10.0.0.3,dst=10.0.0.2,id=<cleared>,type=0,code=0),zone=<cleared>,labels=0x4d2000000000000000000000000
+icmp,orig=(src=10.0.0.2,dst=10.0.0.3,id=<cleared>,type=8,code=0),reply=(src=10.0.0.3,dst=10.0.0.2,id=<cleared>,type=0,code=0),zone=<cleared>
+])
+
+# Add a higher priority ACL with different label.
+# This ACL also allows the ping running in background.
+
+check ovn-nbctl --label=1235 --apply-after-lb acl-add sw0 from-lport 1003 'ip4 && inport == "sw0-p1" && ip4.dst == 10.0.0.3' allow-related
+ovn-nbctl --wait=hv sync
+
+sleep 3s
+
+# Ensure conntrack entry is updated with new ct_label is set.
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.0.0.3) | \
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/' | \
+sed -e 's/labels=0x4d3[[0-9a-f]]*/labels=0x4d3000000000000000000000000/'], [0], [dnl
+icmp,orig=(src=10.0.0.2,dst=10.0.0.3,id=<cleared>,type=8,code=0),reply=(src=10.0.0.3,dst=10.0.0.2,id=<cleared>,type=0,code=0),zone=<cleared>,labels=0x4d3000000000000000000000000
+icmp,orig=(src=10.0.0.2,dst=10.0.0.3,id=<cleared>,type=8,code=0),reply=(src=10.0.0.3,dst=10.0.0.2,id=<cleared>,type=0,code=0),zone=<cleared>
+])
+
+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
+])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([ACL all drop and allow related - acl after lb])
+AT_KEYWORDS([ACL all drop and allow related])
+
+CHECK_CONNTRACK()
+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
+
+
+# No ACLs in sw0.
+check ovn-nbctl ls-add sw0
+
+check ovn-nbctl lsp-add sw0 sw0p1
+check ovn-nbctl lsp-set-addresses sw0p1 "50:54:00:00:00:02 10.0.0.3"
+
+# ACLs to drop every thing and just allow-related.
+check ovn-nbctl ls-add sw1
+
+check ovn-nbctl lsp-add sw1 sw1p1
+check ovn-nbctl lsp-set-addresses sw1p1 "50:54:00:00:00:03 20.0.0.3"
+
+check ovn-nbctl --apply-after-lb acl-add sw1 from-lport 1001 'inport == "sw1p1" && ip4' drop
+
+check ovn-nbctl acl-add sw1 to-lport 1002 'ip4 && tcp && tcp.dst == 80' allow-related
+check ovn-nbctl acl-add sw1 to-lport 1001 'ip4' drop
+
+ADD_NAMESPACES(sw0p1)
+ADD_VETH(sw0p1, sw0p1, br-int, "10.0.0.3/24", "50:54:00:00:00:02", \
+         "10.0.0.1")
+ADD_NAMESPACES(sw1p1)
+ADD_VETH(sw1p1, sw1p1, br-int, "20.0.0.3/24", "50:54:00:00:00:03", \
+         "20.0.0.1")
+
+# Create a logical router and attach both logical switches
+check ovn-nbctl lr-add lr0
+check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24
+check ovn-nbctl lsp-add sw0 sw0-lr0
+check ovn-nbctl lsp-set-type sw0-lr0 router
+check ovn-nbctl lsp-set-addresses sw0-lr0 00:00:00:00:ff:01
+check ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
+
+check ovn-nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 20.0.0.1/24
+check ovn-nbctl lsp-add sw1 sw1-lr0
+check ovn-nbctl lsp-set-type sw1-lr0 router
+check ovn-nbctl lsp-set-addresses sw1-lr0 00:00:00:00:ff:02
+check ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1
+
+# Ensure ovn-controller is caught up
+ovn-nbctl --wait=hv sync
+
+on_exit 'ovn-nbctl acl-list sw0'
+on_exit 'ovn-sbctl lflow-list'
+on_exit 'ovs-ofctl dump-flows br-int'
+
+wait_for_ports_up
+
+# Start webservers in 'sw1-p1'
+OVS_START_L7([sw1p1], [http])
+
+AT_CHECK([ip netns exec sw0p1 wget 20.0.0.3 -t 3 -T 1], [0], [ignore], [ignore])
+
+# Clear the apply-after-lb option for the ACL
+check ovn-nbctl acl-del sw1 from-lport 1001 'inport == "sw1p1" && ip4'
+check ovn-nbctl acl-add sw1 from-lport 1001 'inport == "sw1p1" && ip4' drop
+
+check ovn-nbctl --wait=hv sync
+
+AT_CHECK([ip netns exec sw0p1 wget 20.0.0.3 -t 3 -T 1], [0], [ignore], [ignore])
+
+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
+])
+
 OVN_FOR_EACH_NORTHD([
 AT_SETUP([ACL log_related])
 
diff --git a/utilities/ovn-nbctl.8.xml b/utilities/ovn-nbctl.8.xml
index 545f3bf27b..74651c7623 100644
--- a/utilities/ovn-nbctl.8.xml
+++ b/utilities/ovn-nbctl.8.xml
@@ -399,7 +399,7 @@ 
       must be either <code>switch</code> or <code>port-group</code>.
     </p>
     <dl>
-      <dt>[<code>--type=</code>{<code>switch</code> | <code>port-group</code>}] [<code>--log</code>] [<code>--meter=</code><var>meter</var>] [<code>--severity=</code><var>severity</var>] [<code>--name=</code><var>name</var>] [<code>--label=</code><var>label</var>] [<code>--may-exist</code>] <code>acl-add</code> <var>entity</var> <var>direction</var> <var>priority</var> <var>match</var> <var>verdict</var></dt>
+      <dt>[<code>--type=</code>{<code>switch</code> | <code>port-group</code>}] [<code>--log</code>] [<code>--meter=</code><var>meter</var>] [<code>--severity=</code><var>severity</var>] [<code>--name=</code><var>name</var>] [<code>--label=</code><var>label</var>] [<code>--may-exist</code>] [<code>--apply-after-lb</code>] <code>acl-add</code> <var>entity</var> <var>direction</var> <var>priority</var> <var>match</var> <var>verdict</var></dt>
       <dd>
         <p>
           Adds the specified ACL to <var>entity</var>.  <var>direction</var>
@@ -423,6 +423,13 @@ 
           is used to rate-limit packet logging.  The <var>meter</var> argument
           names a meter configured by <code>meter-add</code>.
         </p>
+
+        <p>
+          The <code>--apply-after-lb</code> option sets
+          <code>apply-after-lb=true</code> in the <code>options</code> column
+          of the <code>ACL</code> table.  As the option name suggests, the ACL
+          will be applied after the logical switch load balancer stage.
+        </p>
       </dd>
 
       <dt>[<code>--type=</code>{<code>switch</code> | <code>port-group</code>}] <code>acl-del</code> <var>entity</var> [<var>direction</var> [<var>priority</var> <var>match</var>]]</dt>
diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
index adb08c6c96..7bcc2c66a8 100644
--- a/utilities/ovn-nbctl.c
+++ b/utilities/ovn-nbctl.c
@@ -2132,6 +2132,7 @@  nbctl_pre_acl(struct ctl_context *ctx)
     ovsdb_idl_add_column(ctx->idl, &nbrec_acl_col_direction);
     ovsdb_idl_add_column(ctx->idl, &nbrec_acl_col_priority);
     ovsdb_idl_add_column(ctx->idl, &nbrec_acl_col_match);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_acl_col_options);
 }
 
 static void
@@ -2145,6 +2146,7 @@  nbctl_pre_acl_list(struct ctl_context *ctx)
     ovsdb_idl_add_column(ctx->idl, &nbrec_acl_col_severity);
     ovsdb_idl_add_column(ctx->idl, &nbrec_acl_col_meter);
     ovsdb_idl_add_column(ctx->idl, &nbrec_acl_col_label);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_acl_col_options);
 }
 
 static void
@@ -2231,6 +2233,13 @@  nbctl_acl_add(struct ctl_context *ctx)
       nbrec_acl_set_label(acl, label_value);
     }
 
+    if (!strcmp(direction, "from-lport") &&
+        (shash_find(&ctx->options, "--apply-after-lb") != NULL)) {
+        const struct smap options = SMAP_CONST1(&options, "apply-after-lb",
+                                                "true");
+        nbrec_acl_set_options(acl, &options);
+    }
+
     /* Check if same acl already exists for the ls/portgroup */
     size_t n_acls = pg ? pg->n_acls : ls->n_acls;
     struct nbrec_acl **acls = pg ? pg->acls : ls->acls;
@@ -6959,7 +6968,8 @@  static const struct ctl_command_syntax nbctl_commands[] = {
     /* acl commands. */
     { "acl-add", 5, 6, "{SWITCH | PORTGROUP} DIRECTION PRIORITY MATCH ACTION",
       nbctl_pre_acl, nbctl_acl_add, NULL,
-      "--log,--may-exist,--type=,--name=,--severity=,--meter=,--label=", RW },
+      "--log,--may-exist,--type=,--name=,--severity=,--meter=,--label=,"
+      "--apply-after-lb", RW },
     { "acl-del", 1, 4, "{SWITCH | PORTGROUP} [DIRECTION [PRIORITY MATCH]]",
       nbctl_pre_acl, nbctl_acl_del, NULL, "--type=", RW },
     { "acl-list", 1, 1, "{SWITCH | PORTGROUP}",