ebb439
From 13ceb66f0deb1c1c1384041d3146e0189e7328d8 Mon Sep 17 00:00:00 2001
ebb439
From: Numan Siddique <numans@ovn.org>
ebb439
Date: Thu, 12 Nov 2020 17:27:23 +0530
ebb439
Subject: [PATCH 08/10] northd: Make use of new hairpin actions.
ebb439
ebb439
This patch makes use of the new hairpin OVN actions - chk_lb_hairpin, chk_lb_hairpin_reply
ebb439
and ct_snat_to_vip.
ebb439
ebb439
Suppose there are 'm' load balancers associated to a logical switch and each load balancer
ebb439
has 'n' VIPs and each VIP has 'p' backends then ovn-northd adds (m * ((n * p) + n))
ebb439
hairpin logical flows. After this patch, ovn-northd adds just 5 hairpin logical flows.
ebb439
ebb439
With this patch number of hairpin related OF flows on a chassis are almost the same as before,
ebb439
but in a large scale deployment, this reduces memory consumption and load on ovn-northd and
ebb439
SB DB ovsdb-servers.
ebb439
ebb439
Acked-by: Dumitru Ceara <dceara@redhat.com>
ebb439
Acked-by: Mark Michelson <mmichels@redhat.com>
ebb439
Signed-off-by: Numan Siddique <numans@ovn.org>
ebb439
ebb439
(cherry-picked from master commit 3357ab14076f0a7e91fe690538b4315c7219de60)
ebb439
Conflicts:
ebb439
	northd/ovn-northd.c
ebb439
---
ebb439
 northd/ovn-northd.8.xml |  65 +++++++++++-----
ebb439
 northd/ovn-northd.c     | 160 +++++++++++++---------------------------
ebb439
 tests/ovn-northd.at     |  28 +++----
ebb439
 tests/ovn.at            |  52 ++++++-------
ebb439
 4 files changed, 141 insertions(+), 164 deletions(-)
ebb439
ebb439
diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
ebb439
index 820698228..27d996944 100644
ebb439
--- a/northd/ovn-northd.8.xml
ebb439
+++ b/northd/ovn-northd.8.xml
ebb439
@@ -718,24 +718,55 @@
ebb439
     

Ingress Table 12: Pre-Hairpin

ebb439
     
    ebb439
           
  • ebb439
    -        For all configured load balancer VIPs a priority-2 flow that
    ebb439
    -        matches on traffic that needs to be hairpinned, i.e., after load
    ebb439
    -        balancing the destination IP matches the source IP, which sets
    ebb439
    -        reg0[6] = 1  and executes ct_snat(VIP)
    ebb439
    -        to force replies to these packets to come back through OVN.
    ebb439
    +        If the logical switch has load balancer(s) configured, then a
    ebb439
    +        priorirty-100 flow is added with the match
    ebb439
    +        ip && ct.trk&& ct.dnat to check if the
    ebb439
    +        packet needs to be hairpinned (if after load balancing the destination
    ebb439
    +        IP matches the source IP) or not by executing the action
    ebb439
    +        reg0[6] = chk_lb_hairpin(); and advances the packet to
    ebb439
    +        the next table.
    ebb439
    +      
    ebb439
    +
    ebb439
    +      
  • ebb439
    +        If the logical switch has load balancer(s) configured, then a
    ebb439
    +        priorirty-90 flow is added with the match ip to check if
    ebb439
    +        the packet is a reply for a hairpinned connection or not by executing
    ebb439
    +        the action reg0[6] = chk_lb_hairpin_reply(); and advances
    ebb439
    +        the packet to the next table.
    ebb439
           
    ebb439
    +
    ebb439
           
  • ebb439
    -        For all configured load balancer VIPs a priority-1 flow that
    ebb439
    -        matches on replies to hairpinned traffic, i.e., destination IP is VIP,
    ebb439
    -        source IP is the backend IP and source L4 port is backend port, which
    ebb439
    -        sets reg0[6] = 1  and executes ct_snat;.
    ebb439
    +        A priority-0 flow that simply moves traffic to the next table.
    ebb439
           
    ebb439
    +    
    ebb439
    +
    ebb439
    +    

    Ingress Table 13: Nat-Hairpin

    ebb439
    +    
      ebb439
      +      
    • ebb439
      +         If the logical switch has load balancer(s) configured, then a
      ebb439
      +         priorirty-100 flow is added with the match
      ebb439
      +         ip && (ct.new || ct.est) && ct.trk &&
      ebb439
      +         ct.dnat && reg0[6] == 1 which hairpins the traffic by
      ebb439
      +         NATting source IP to the load balancer VIP by executing the action
      ebb439
      +         ct_snat_to_vip and advances the packet to the next table.
      ebb439
      +      
      ebb439
      +
      ebb439
      +      
    • ebb439
      +         If the logical switch has load balancer(s) configured, then a
      ebb439
      +         priorirty-90 flow is added with the match
      ebb439
      +         ip && reg0[6] == 1 which matches on the replies
      ebb439
      +         of hairpinned traffic (i.e., destination IP is VIP,
      ebb439
      +         source IP is the backend IP and source L4 port is backend port for L4
      ebb439
      +         load balancers) and executes ct_snat and advances the
      ebb439
      +         packet to the next table.
      ebb439
      +      
      ebb439
      +
      ebb439
             
    • ebb439
               A priority-0 flow that simply moves traffic to the next table.
      ebb439
             
      ebb439
           
      ebb439
       
      ebb439
      -    

      Ingress Table 13: Hairpin

      ebb439
      +    

      Ingress Table 14: Hairpin

      ebb439
           
        ebb439
               
      • ebb439
                 A priority-1 flow that hairpins traffic matched by non-default
        ebb439
        @@ -748,7 +779,7 @@
        ebb439
               
        ebb439
             
        ebb439
         
        ebb439
        -    

        Ingress Table 14: ARP/ND responder

        ebb439
        +    

        Ingress Table 15: ARP/ND responder

        ebb439
         
        ebb439
             

        ebb439
               This table implements ARP/ND responder in a logical switch for known
        ebb439
        @@ -1038,7 +1069,7 @@ output;
        ebb439
               
        ebb439
             
        ebb439
         
        ebb439
        -    

        Ingress Table 15: DHCP option processing

        ebb439
        +    

        Ingress Table 16: DHCP option processing

        ebb439
         
        ebb439
             

        ebb439
               This table adds the DHCPv4 options to a DHCPv4 packet from the
        ebb439
        @@ -1099,7 +1130,7 @@ next;
        ebb439
               
        ebb439
             
        ebb439
         
        ebb439
        -    

        Ingress Table 16: DHCP responses

        ebb439
        +    

        Ingress Table 17: DHCP responses

        ebb439
         
        ebb439
             

        ebb439
               This table implements DHCP responder for the DHCP replies generated by
        ebb439
        @@ -1180,7 +1211,7 @@ output;
        ebb439
               
        ebb439
             
        ebb439
         
        ebb439
        -    

        Ingress Table 17 DNS Lookup

        ebb439
        +    

        Ingress Table 18 DNS Lookup

        ebb439
         
        ebb439
             

        ebb439
               This table looks up and resolves the DNS names to the corresponding
        ebb439
        @@ -1209,7 +1240,7 @@ reg0[4] = dns_lookup(); next;
        ebb439
               
        ebb439
             
        ebb439
         
        ebb439
        -    

        Ingress Table 18 DNS Responses

        ebb439
        +    

        Ingress Table 19 DNS Responses

        ebb439
         
        ebb439
             

        ebb439
               This table implements DNS responder for the DNS replies generated by
        ebb439
        @@ -1244,7 +1275,7 @@ output;
        ebb439
               
        ebb439
             
        ebb439
         
        ebb439
        -    

        Ingress table 19 External ports

        ebb439
        +    

        Ingress table 20 External ports

        ebb439
         
        ebb439
             

        ebb439
               Traffic from the external logical ports enter the ingress
        ebb439
        @@ -1287,7 +1318,7 @@ output;
        ebb439
               
        ebb439
             
        ebb439
         
        ebb439
        -    

        Ingress Table 20 Destination Lookup

        ebb439
        +    

        Ingress Table 21 Destination Lookup

        ebb439
         
        ebb439
             

        ebb439
               This table implements switching behavior.  It contains these logical
        ebb439
        diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
        ebb439
        index a7695bc63..bb31e04fa 100644
        ebb439
        --- a/northd/ovn-northd.c
        ebb439
        +++ b/northd/ovn-northd.c
        ebb439
        @@ -150,14 +150,15 @@ enum ovn_stage {
        ebb439
             PIPELINE_STAGE(SWITCH, IN,  LB,            10, "ls_in_lb")            \
        ebb439
             PIPELINE_STAGE(SWITCH, IN,  STATEFUL,      11, "ls_in_stateful")      \
        ebb439
             PIPELINE_STAGE(SWITCH, IN,  PRE_HAIRPIN,   12, "ls_in_pre_hairpin")   \
        ebb439
        -    PIPELINE_STAGE(SWITCH, IN,  HAIRPIN,       13, "ls_in_hairpin")       \
        ebb439
        -    PIPELINE_STAGE(SWITCH, IN,  ARP_ND_RSP,    14, "ls_in_arp_rsp")       \
        ebb439
        -    PIPELINE_STAGE(SWITCH, IN,  DHCP_OPTIONS,  15, "ls_in_dhcp_options")  \
        ebb439
        -    PIPELINE_STAGE(SWITCH, IN,  DHCP_RESPONSE, 16, "ls_in_dhcp_response") \
        ebb439
        -    PIPELINE_STAGE(SWITCH, IN,  DNS_LOOKUP,    17, "ls_in_dns_lookup")    \
        ebb439
        -    PIPELINE_STAGE(SWITCH, IN,  DNS_RESPONSE,  18, "ls_in_dns_response")  \
        ebb439
        -    PIPELINE_STAGE(SWITCH, IN,  EXTERNAL_PORT, 19, "ls_in_external_port") \
        ebb439
        -    PIPELINE_STAGE(SWITCH, IN,  L2_LKUP,       20, "ls_in_l2_lkup")       \
        ebb439
        +    PIPELINE_STAGE(SWITCH, IN,  NAT_HAIRPIN,   13, "ls_in_nat_hairpin")       \
        ebb439
        +    PIPELINE_STAGE(SWITCH, IN,  HAIRPIN,       14, "ls_in_hairpin")       \
        ebb439
        +    PIPELINE_STAGE(SWITCH, IN,  ARP_ND_RSP,    15, "ls_in_arp_rsp")       \
        ebb439
        +    PIPELINE_STAGE(SWITCH, IN,  DHCP_OPTIONS,  16, "ls_in_dhcp_options")  \
        ebb439
        +    PIPELINE_STAGE(SWITCH, IN,  DHCP_RESPONSE, 17, "ls_in_dhcp_response") \
        ebb439
        +    PIPELINE_STAGE(SWITCH, IN,  DNS_LOOKUP,    18, "ls_in_dns_lookup")    \
        ebb439
        +    PIPELINE_STAGE(SWITCH, IN,  DNS_RESPONSE,  19, "ls_in_dns_response")  \
        ebb439
        +    PIPELINE_STAGE(SWITCH, IN,  EXTERNAL_PORT, 20, "ls_in_external_port") \
        ebb439
        +    PIPELINE_STAGE(SWITCH, IN,  L2_LKUP,       21, "ls_in_l2_lkup")       \
        ebb439
                                                                                   \
        ebb439
             /* Logical switch egress stages. */                                   \
        ebb439
             PIPELINE_STAGE(SWITCH, OUT, PRE_LB,       0, "ls_out_pre_lb")         \
        ebb439
        @@ -5811,85 +5812,6 @@ build_lb(struct ovn_datapath *od, struct hmap *lflows)
        ebb439
             }
        ebb439
         }
        ebb439
         
        ebb439
        -static void
        ebb439
        -build_lb_hairpin_rules(struct ovn_datapath *od, struct hmap *lflows,
        ebb439
        -                       struct ovn_northd_lb *lb,
        ebb439
        -                       struct ovn_lb_vip *lb_vip,
        ebb439
        -                       const char *ip_match, const char *proto)
        ebb439
        -{
        ebb439
        -    if (lb_vip->n_backends == 0) {
        ebb439
        -        return;
        ebb439
        -    }
        ebb439
        -
        ebb439
        -    struct ds action = DS_EMPTY_INITIALIZER;
        ebb439
        -    struct ds match_initiator = DS_EMPTY_INITIALIZER;
        ebb439
        -    struct ds match_reply = DS_EMPTY_INITIALIZER;
        ebb439
        -    struct ds proto_match = DS_EMPTY_INITIALIZER;
        ebb439
        -
        ebb439
        -    /* Ingress Pre-Hairpin table.
        ebb439
        -     * - Priority 2: SNAT load balanced traffic that needs to be hairpinned:
        ebb439
        -     *   - Both SRC and DST IP match backend->ip and destination port
        ebb439
        -     *     matches backend->port.
        ebb439
        -     * - Priority 1: unSNAT replies to hairpinned load balanced traffic.
        ebb439
        -     *   - SRC IP matches backend->ip, DST IP matches LB VIP and source port
        ebb439
        -     *     matches backend->port.
        ebb439
        -     */
        ebb439
        -    ds_put_char(&match_reply, '(');
        ebb439
        -    for (size_t i = 0; i < lb_vip->n_backends; i++) {
        ebb439
        -        struct ovn_lb_backend *backend = &lb_vip->backends[i];
        ebb439
        -
        ebb439
        -        /* Packets that after load balancing have equal source and
        ebb439
        -         * destination IPs should be hairpinned.
        ebb439
        -         */
        ebb439
        -        if (lb_vip->vip_port) {
        ebb439
        -            ds_put_format(&proto_match, " && %s.dst == %"PRIu16,
        ebb439
        -                          proto, backend->port);
        ebb439
        -        }
        ebb439
        -        ds_put_format(&match_initiator, "(%s.src == %s && %s.dst == %s%s)",
        ebb439
        -                      ip_match, backend->ip_str, ip_match, backend->ip_str,
        ebb439
        -                      ds_cstr(&proto_match));
        ebb439
        -
        ebb439
        -        /* Replies to hairpinned traffic are originated by backend->ip:port. */
        ebb439
        -        ds_clear(&proto_match);
        ebb439
        -        if (lb_vip->vip_port) {
        ebb439
        -            ds_put_format(&proto_match, " && %s.src == %"PRIu16, proto,
        ebb439
        -                          backend->port);
        ebb439
        -        }
        ebb439
        -        ds_put_format(&match_reply, "(%s.src == %s%s)",
        ebb439
        -                      ip_match, backend->ip_str, ds_cstr(&proto_match));
        ebb439
        -        ds_clear(&proto_match);
        ebb439
        -
        ebb439
        -        if (i < lb_vip->n_backends - 1) {
        ebb439
        -            ds_put_cstr(&match_initiator, " || ");
        ebb439
        -            ds_put_cstr(&match_reply, " || ");
        ebb439
        -        }
        ebb439
        -    }
        ebb439
        -    ds_put_char(&match_reply, ')');
        ebb439
        -
        ebb439
        -    /* SNAT hairpinned initiator traffic so that the reply traffic is
        ebb439
        -     * also directed through OVN.
        ebb439
        -     */
        ebb439
        -    ds_put_format(&action, REGBIT_HAIRPIN " = 1; ct_snat(%s);",
        ebb439
        -                  lb_vip->vip_str);
        ebb439
        -    ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_PRE_HAIRPIN, 2,
        ebb439
        -                            ds_cstr(&match_initiator), ds_cstr(&action),
        ebb439
        -                            &lb->nlb->header_);
        ebb439
        -
        ebb439
        -    /* Replies to hairpinned traffic are destined to the LB VIP. */
        ebb439
        -    ds_put_format(&match_reply, " && %s.dst == %s", ip_match, lb_vip->vip_str);
        ebb439
        -
        ebb439
        -    /* UNSNAT replies for hairpinned traffic. */
        ebb439
        -    ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_PRE_HAIRPIN, 1,
        ebb439
        -                            ds_cstr(&match_reply),
        ebb439
        -                            REGBIT_HAIRPIN " = 1; ct_snat;",
        ebb439
        -                            &lb->nlb->header_);
        ebb439
        -
        ebb439
        -    ds_destroy(&action);
        ebb439
        -    ds_destroy(&match_initiator);
        ebb439
        -    ds_destroy(&match_reply);
        ebb439
        -    ds_destroy(&proto_match);
        ebb439
        -}
        ebb439
        -
        ebb439
         static void
        ebb439
         build_lb_rules(struct ovn_datapath *od, struct hmap *lflows,
        ebb439
                        struct ovn_northd_lb *lb)
        ebb439
        @@ -5938,12 +5860,6 @@ build_lb_rules(struct ovn_datapath *od, struct hmap *lflows,
        ebb439
         
        ebb439
                 ds_destroy(&match);
        ebb439
                 ds_destroy(&action);
        ebb439
        -
        ebb439
        -        /* Also install flows that allow hairpinning of traffic (i.e., if
        ebb439
        -         * a load balancer VIP is DNAT-ed to a backend that happens to be
        ebb439
        -         * the source of the traffic).
        ebb439
        -         */
        ebb439
        -        build_lb_hairpin_rules(od, lflows, lb, lb_vip, ip_match, proto);
        ebb439
             }
        ebb439
         }
        ebb439
         
        ebb439
        @@ -5990,24 +5906,53 @@ build_stateful(struct ovn_datapath *od, struct hmap *lflows, struct hmap *lbs)
        ebb439
                 ovs_assert(lb);
        ebb439
                 build_lb_rules(od, lflows, lb);
        ebb439
             }
        ebb439
        +}
        ebb439
         
        ebb439
        -    /* Ingress Pre-Hairpin table (Priority 0). Packets that don't need
        ebb439
        -     * hairpinning should continue processing.
        ebb439
        +static void
        ebb439
        +build_lb_hairpin(struct ovn_datapath *od, struct hmap *lflows)
        ebb439
        +{
        ebb439
        +    /* Ingress Pre-Hairpin/Nat-Hairpin/Hairpin tabled (Priority 0).
        ebb439
        +     * Packets that don't need hairpinning should continue processing.
        ebb439
              */
        ebb439
             ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_HAIRPIN, 0, "1", "next;");
        ebb439
        -
        ebb439
        -    /* Ingress Hairpin table.
        ebb439
        -     * - Priority 0: Packets that don't need hairpinning should continue
        ebb439
        -     *   processing.
        ebb439
        -     * - Priority 1: Packets that were SNAT-ed for hairpinning should be
        ebb439
        -     *   looped back (i.e., swap ETH addresses and send back on inport).
        ebb439
        -     */
        ebb439
        -    ovn_lflow_add(lflows, od, S_SWITCH_IN_HAIRPIN, 1, REGBIT_HAIRPIN " == 1",
        ebb439
        -                  "eth.dst <-> eth.src;"
        ebb439
        -                  "outport = inport;"
        ebb439
        -                  "flags.loopback = 1;"
        ebb439
        -                  "output;");
        ebb439
        +    ovn_lflow_add(lflows, od, S_SWITCH_IN_NAT_HAIRPIN, 0, "1", "next;");
        ebb439
             ovn_lflow_add(lflows, od, S_SWITCH_IN_HAIRPIN, 0, "1", "next;");
        ebb439
        +
        ebb439
        +    if (has_lb_vip(od)) {
        ebb439
        +        /* Check if the packet needs to be hairpinned. */
        ebb439
        +        ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_PRE_HAIRPIN, 100,
        ebb439
        +                                "ip && ct.trk && ct.dnat",
        ebb439
        +                                REGBIT_HAIRPIN " = chk_lb_hairpin(); next;",
        ebb439
        +                                &od->nbs->header_);
        ebb439
        +
        ebb439
        +        /* Check if the packet is a reply of hairpinned traffic. */
        ebb439
        +        ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_PRE_HAIRPIN, 90, "ip",
        ebb439
        +                                REGBIT_HAIRPIN " = chk_lb_hairpin_reply(); "
        ebb439
        +                                "next;", &od->nbs->header_);
        ebb439
        +
        ebb439
        +        /* If packet needs to be hairpinned, snat the src ip with the VIP. */
        ebb439
        +        ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_NAT_HAIRPIN, 100,
        ebb439
        +                                "ip && (ct.new || ct.est) && ct.trk && ct.dnat"
        ebb439
        +                                " && "REGBIT_HAIRPIN " == 1",
        ebb439
        +                                "ct_snat_to_vip; next;",
        ebb439
        +                                &od->nbs->header_);
        ebb439
        +
        ebb439
        +        /* For the reply of hairpinned traffic, snat the src ip to the VIP. */
        ebb439
        +        ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_NAT_HAIRPIN, 90,
        ebb439
        +                                "ip && "REGBIT_HAIRPIN " == 1", "ct_snat;",
        ebb439
        +                                &od->nbs->header_);
        ebb439
        +
        ebb439
        +        /* Ingress Hairpin table.
        ebb439
        +        * - Priority 1: Packets that were SNAT-ed for hairpinning should be
        ebb439
        +        *   looped back (i.e., swap ETH addresses and send back on inport).
        ebb439
        +        */
        ebb439
        +        ovn_lflow_add(lflows, od, S_SWITCH_IN_HAIRPIN, 1,
        ebb439
        +                      REGBIT_HAIRPIN " == 1",
        ebb439
        +                      "eth.dst <-> eth.src;"
        ebb439
        +                      "outport = inport;"
        ebb439
        +                      "flags.loopback = 1;"
        ebb439
        +                      "output;");
        ebb439
        +    }
        ebb439
         }
        ebb439
         
        ebb439
         static void
        ebb439
        @@ -6693,6 +6638,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
        ebb439
                 build_qos(od, lflows);
        ebb439
                 build_lb(od, lflows);
        ebb439
                 build_stateful(od, lflows, lbs);
        ebb439
        +        build_lb_hairpin(od, lflows);
        ebb439
             }
        ebb439
         
        ebb439
             /* Build logical flows for the forwarding groups */
        ebb439
        diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
        ebb439
        index 961fb3712..bf3a99a6c 100644
        ebb439
        --- a/tests/ovn-northd.at
        ebb439
        +++ b/tests/ovn-northd.at
        ebb439
        @@ -1775,13 +1775,13 @@ action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implici
        ebb439
         AT_CHECK([ovn-sbctl lflow-list sw0 | grep "ls_out_acl" | grep pg0 | sort], [0], [dnl
        ebb439
           table=5 (ls_out_acl         ), priority=2003 , dnl
        ebb439
         match=(outport == @pg0 && ip6 && udp), dnl
        ebb439
        -action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=20); };)
        ebb439
        +action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
        ebb439
         ])
        ebb439
         
        ebb439
         AT_CHECK([ovn-sbctl lflow-list sw1 | grep "ls_out_acl" | grep pg0 | sort], [0], [dnl
        ebb439
           table=5 (ls_out_acl         ), priority=2003 , dnl
        ebb439
         match=(outport == @pg0 && ip6 && udp), dnl
        ebb439
        -action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=20); };)
        ebb439
        +action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
        ebb439
         ])
        ebb439
         
        ebb439
         ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && udp" reject
        ebb439
        @@ -1789,19 +1789,19 @@ ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && udp" reject
        ebb439
         AT_CHECK([ovn-sbctl lflow-list sw0 | grep "ls_out_acl" | grep pg0 | sort], [0], [dnl
        ebb439
           table=5 (ls_out_acl         ), priority=2002 , dnl
        ebb439
         match=(outport == @pg0 && ip4 && udp), dnl
        ebb439
        -action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=20); };)
        ebb439
        +action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
        ebb439
           table=5 (ls_out_acl         ), priority=2003 , dnl
        ebb439
         match=(outport == @pg0 && ip6 && udp), dnl
        ebb439
        -action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=20); };)
        ebb439
        +action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
        ebb439
         ])
        ebb439
         
        ebb439
         AT_CHECK([ovn-sbctl lflow-list sw1 | grep "ls_out_acl" | grep pg0 | sort], [0], [dnl
        ebb439
           table=5 (ls_out_acl         ), priority=2002 , dnl
        ebb439
         match=(outport == @pg0 && ip4 && udp), dnl
        ebb439
        -action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=20); };)
        ebb439
        +action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
        ebb439
           table=5 (ls_out_acl         ), priority=2003 , dnl
        ebb439
         match=(outport == @pg0 && ip6 && udp), dnl
        ebb439
        -action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=20); };)
        ebb439
        +action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
        ebb439
         ])
        ebb439
         
        ebb439
         ovn-nbctl --wait=sb acl-add pg0 to-lport 1001 "outport == @pg0 && ip" allow-related
        ebb439
        @@ -1813,16 +1813,16 @@ match=(reg0[[7]] == 1 && (outport == @pg0 && ip)), action=(reg0[[1]] = 1; next;)
        ebb439
         match=(reg0[[8]] == 1 && (outport == @pg0 && ip)), action=(next;)
        ebb439
           table=5 (ls_out_acl         ), priority=2002 , dnl
        ebb439
         match=((reg0[[10]] == 1) && outport == @pg0 && ip4 && udp), dnl
        ebb439
        -action=(ct_commit { ct_label.blocked = 1; };  reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=20); };)
        ebb439
        +action=(ct_commit { ct_label.blocked = 1; };  reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
        ebb439
           table=5 (ls_out_acl         ), priority=2002 , dnl
        ebb439
         match=((reg0[[9]] == 1) && outport == @pg0 && ip4 && udp), dnl
        ebb439
        -action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=20); };)
        ebb439
        +action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
        ebb439
           table=5 (ls_out_acl         ), priority=2003 , dnl
        ebb439
         match=((reg0[[10]] == 1) && outport == @pg0 && ip6 && udp), dnl
        ebb439
        -action=(ct_commit { ct_label.blocked = 1; };  reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=20); };)
        ebb439
        +action=(ct_commit { ct_label.blocked = 1; };  reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
        ebb439
           table=5 (ls_out_acl         ), priority=2003 , dnl
        ebb439
         match=((reg0[[9]] == 1) && outport == @pg0 && ip6 && udp), dnl
        ebb439
        -action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=20); };)
        ebb439
        +action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
        ebb439
         ])
        ebb439
         
        ebb439
         AT_CHECK([ovn-sbctl lflow-list sw1 | grep "ls_out_acl" | grep pg0 | sort], [0], [dnl
        ebb439
        @@ -1832,16 +1832,16 @@ match=(reg0[[7]] == 1 && (outport == @pg0 && ip)), action=(reg0[[1]] = 1; next;)
        ebb439
         match=(reg0[[8]] == 1 && (outport == @pg0 && ip)), action=(next;)
        ebb439
           table=5 (ls_out_acl         ), priority=2002 , dnl
        ebb439
         match=((reg0[[10]] == 1) && outport == @pg0 && ip4 && udp), dnl
        ebb439
        -action=(ct_commit { ct_label.blocked = 1; };  reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=20); };)
        ebb439
        +action=(ct_commit { ct_label.blocked = 1; };  reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
        ebb439
           table=5 (ls_out_acl         ), priority=2002 , dnl
        ebb439
         match=((reg0[[9]] == 1) && outport == @pg0 && ip4 && udp), dnl
        ebb439
        -action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=20); };)
        ebb439
        +action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
        ebb439
           table=5 (ls_out_acl         ), priority=2003 , dnl
        ebb439
         match=((reg0[[10]] == 1) && outport == @pg0 && ip6 && udp), dnl
        ebb439
        -action=(ct_commit { ct_label.blocked = 1; };  reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=20); };)
        ebb439
        +action=(ct_commit { ct_label.blocked = 1; };  reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
        ebb439
           table=5 (ls_out_acl         ), priority=2003 , dnl
        ebb439
         match=((reg0[[9]] == 1) && outport == @pg0 && ip6 && udp), dnl
        ebb439
        -action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=20); };)
        ebb439
        +action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
        ebb439
         ])
        ebb439
         
        ebb439
         AT_CLEANUP
        ebb439
        diff --git a/tests/ovn.at b/tests/ovn.at
        ebb439
        index 8dbb13d3a..8f18ca9e5 100644
        ebb439
        --- a/tests/ovn.at
        ebb439
        +++ b/tests/ovn.at
        ebb439
        @@ -14657,17 +14657,17 @@ ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
        ebb439
         AT_CHECK([ovn-sbctl dump-flows ls1 | grep "offerip = 10.0.0.6" | \
        ebb439
         wc -l], [0], [0
        ebb439
         ])
        ebb439
        -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=23 | \
        ebb439
        +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=24 | \
        ebb439
         grep controller | grep "0a.00.00.06" | wc -l], [0], [0
        ebb439
         ])
        ebb439
        -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=23 | \
        ebb439
        +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=24 | \
        ebb439
         grep controller | grep "0a.00.00.06" | wc -l], [0], [0
        ebb439
         ])
        ebb439
        -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=23 | \
        ebb439
        +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=24 | \
        ebb439
         grep controller | grep tp_src=546 | grep \
        ebb439
         "ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0
        ebb439
         ])
        ebb439
        -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=23 | \
        ebb439
        +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=24 | \
        ebb439
         grep controller | grep tp_src=546 | grep \
        ebb439
         "ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0
        ebb439
         ])
        ebb439
        @@ -14698,17 +14698,17 @@ port_binding logical_port=ls1-lp_ext1`
        ebb439
         
        ebb439
         # No DHCPv4/v6 flows for the external port - ls1-lp_ext1 - 10.0.0.6 in hv1 and hv2
        ebb439
         # as no localnet port added to ls1 yet.
        ebb439
        -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=23 | \
        ebb439
        +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=24 | \
        ebb439
         grep controller | grep "0a.00.00.06" | wc -l], [0], [0
        ebb439
         ])
        ebb439
        -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=23 | \
        ebb439
        +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=24 | \
        ebb439
         grep controller | grep "0a.00.00.06" | wc -l], [0], [0
        ebb439
         ])
        ebb439
        -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=23 | \
        ebb439
        +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=24 | \
        ebb439
         grep controller | grep tp_src=546 | grep \
        ebb439
         "ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0
        ebb439
         ])
        ebb439
        -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=23 | \
        ebb439
        +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=24 | \
        ebb439
         grep controller | grep tp_src=546 | grep \
        ebb439
         "ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0
        ebb439
         ])
        ebb439
        @@ -14730,38 +14730,38 @@ logical_port=ls1-lp_ext1`
        ebb439
             test "$chassis" = "$hv1_uuid"])
        ebb439
         
        ebb439
         # There should be DHCPv4/v6 OF flows for the ls1-lp_ext1 port in hv1
        ebb439
        -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=23 | \
        ebb439
        +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=24 | \
        ebb439
         grep controller | grep "0a.00.00.06" | grep reg14=0x$ln_public_key | \
        ebb439
         wc -l], [0], [3
        ebb439
         ])
        ebb439
        -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=23 | \
        ebb439
        +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=24 | \
        ebb439
         grep controller | grep tp_src=546 | grep \
        ebb439
         "ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | \
        ebb439
         grep reg14=0x$ln_public_key | wc -l], [0], [1
        ebb439
         ])
        ebb439
         
        ebb439
         # There should be no DHCPv4/v6 flows for ls1-lp_ext1 on hv2
        ebb439
        -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=23 | \
        ebb439
        +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=24 | \
        ebb439
         grep controller | grep "0a.00.00.06" | wc -l], [0], [0
        ebb439
         ])
        ebb439
        -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=23 | \
        ebb439
        +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=24 | \
        ebb439
         grep controller | grep tp_src=546 | grep \
        ebb439
         "ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0
        ebb439
         ])
        ebb439
         
        ebb439
         # No DHCPv4/v6 flows for the external port - ls1-lp_ext2 - 10.0.0.7 in hv1 and
        ebb439
         # hv2 as requested-chassis option is not set.
        ebb439
        -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=23 | \
        ebb439
        +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=24 | \
        ebb439
         grep controller | grep "0a.00.00.07" | wc -l], [0], [0
        ebb439
         ])
        ebb439
        -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=23 | \
        ebb439
        +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=24 | \
        ebb439
         grep controller | grep "0a.00.00.07" | wc -l], [0], [0
        ebb439
         ])
        ebb439
        -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=23 | \
        ebb439
        +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=24 | \
        ebb439
         grep controller | grep tp_src=546 | grep \
        ebb439
         "ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.07" | wc -l], [0], [0
        ebb439
         ])
        ebb439
        -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=23 | \
        ebb439
        +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=24 | \
        ebb439
         grep controller | grep tp_src=546 | grep \
        ebb439
         "ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.07" | wc -l], [0], [0
        ebb439
         ])
        ebb439
        @@ -15013,21 +15013,21 @@ logical_port=ls1-lp_ext1`
        ebb439
             test "$chassis" = "$hv2_uuid"])
        ebb439
         
        ebb439
         # There should be OF flows for DHCP4/v6 for the ls1-lp_ext1 port in hv2
        ebb439
        -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=23 | \
        ebb439
        +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=24 | \
        ebb439
         grep controller | grep "0a.00.00.06" | grep reg14=0x$ln_public_key | \
        ebb439
         wc -l], [0], [3
        ebb439
         ])
        ebb439
        -AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=23 | \
        ebb439
        +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=24 | \
        ebb439
         grep controller | grep tp_src=546 | grep \
        ebb439
         "ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | \
        ebb439
         grep reg14=0x$ln_public_key | wc -l], [0], [1
        ebb439
         ])
        ebb439
         
        ebb439
         # There should be no DHCPv4/v6 flows for ls1-lp_ext1 on hv1
        ebb439
        -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=23 | \
        ebb439
        +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=24 | \
        ebb439
         grep controller | grep "0a.00.00.06" | wc -l], [0], [0
        ebb439
         ])
        ebb439
        -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=23 | \
        ebb439
        +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=24 | \
        ebb439
         grep controller | grep tp_src=546 | grep \
        ebb439
         "ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | \
        ebb439
         grep reg14=0x$ln_public_key | wc -l], [0], [0
        ebb439
        @@ -15293,7 +15293,7 @@ logical_port=ls1-lp_ext1`
        ebb439
         # There should be a flow in hv2 to drop traffic from ls1-lp_ext1 destined
        ebb439
         # to router mac.
        ebb439
         AT_CHECK([as hv2 ovs-ofctl dump-flows br-int \
        ebb439
        -table=27,dl_src=f0:00:00:00:00:03,dl_dst=a0:10:00:00:00:01 | \
        ebb439
        +table=28,dl_src=f0:00:00:00:00:03,dl_dst=a0:10:00:00:00:01 | \
        ebb439
         grep -c "actions=drop"], [0], [1
        ebb439
         ])
        ebb439
         
        ebb439
        @@ -16564,9 +16564,9 @@ ovn-nbctl --wait=hv sync
        ebb439
         ovn-sbctl dump-flows sw0 | grep ls_in_arp_rsp | grep bind_vport > lflows.txt
        ebb439
         
        ebb439
         AT_CHECK([cat lflows.txt], [0], [dnl
        ebb439
        -  table=14(ls_in_arp_rsp      ), priority=100  , match=(inport == "sw0-p1" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;)
        ebb439
        -  table=14(ls_in_arp_rsp      ), priority=100  , match=(inport == "sw0-p2" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;)
        ebb439
        -  table=14(ls_in_arp_rsp      ), priority=100  , match=(inport == "sw0-p3" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;)
        ebb439
        +  table=15(ls_in_arp_rsp      ), priority=100  , match=(inport == "sw0-p1" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;)
        ebb439
        +  table=15(ls_in_arp_rsp      ), priority=100  , match=(inport == "sw0-p2" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;)
        ebb439
        +  table=15(ls_in_arp_rsp      ), priority=100  , match=(inport == "sw0-p3" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;)
        ebb439
         ])
        ebb439
         
        ebb439
         ovn-sbctl dump-flows lr0 | grep lr_in_arp_resolve | grep "reg0 == 10.0.0.10" \
        ebb439
        @@ -16776,8 +16776,8 @@ ovn-nbctl --wait=hv set logical_switch_port sw0-vir options:virtual-ip=10.0.0.10
        ebb439
         ovn-sbctl dump-flows sw0 | grep ls_in_arp_rsp | grep bind_vport > lflows.txt
        ebb439
         
        ebb439
         AT_CHECK([cat lflows.txt], [0], [dnl
        ebb439
        -  table=14(ls_in_arp_rsp      ), priority=100  , match=(inport == "sw0-p1" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;)
        ebb439
        -  table=14(ls_in_arp_rsp      ), priority=100  , match=(inport == "sw0-p3" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;)
        ebb439
        +  table=15(ls_in_arp_rsp      ), priority=100  , match=(inport == "sw0-p1" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;)
        ebb439
        +  table=15(ls_in_arp_rsp      ), priority=100  , match=(inport == "sw0-p3" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;)
        ebb439
         ])
        ebb439
         
        ebb439
         ovn-nbctl --wait=hv remove logical_switch_port sw0-vir options virtual-parents
        ebb439
        -- 
        ebb439
        2.28.0
        ebb439