With this commit, ACLs can now be arranged in hierarchical tiers. A tier
number can be assigned to an ACL. When evaluating ACLs, we first will
consider ACLs at tier 0. If no matching ACL is found, then we move to
tier 1. This continues until a matching ACL is found, or we reach the
maximum tier. If no match is found, then the default acl action is
applied.

Signed-off-by: Mark Michelson <mmich...@redhat.com>
Reviewed-by: Ales Musil <amu...@redhat.com>
---
 northd/northd.c         |  96 +++++++++++++++++-------
 northd/northd.h         |   1 +
 northd/ovn-northd.8.xml |  35 +++++++++
 ovn-nb.ovsschema        |   7 +-
 ovn-nb.xml              |  20 +++++
 tests/ovn-northd.at     | 161 +++++++++++++++++++++++++++++++++++-----
 tests/system-ovn.at     | 108 +++++++++++++++++++++++++++
 7 files changed, 383 insertions(+), 45 deletions(-)

diff --git a/northd/northd.c b/northd/northd.c
index c7969e0ba..212327b99 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -245,6 +245,7 @@ enum ovn_stage {
 #define REGBIT_ACL_VERDICT_ALLOW "reg8[16]"
 #define REGBIT_ACL_VERDICT_DROP "reg8[17]"
 #define REGBIT_ACL_VERDICT_REJECT "reg8[18]"
+#define REG_ACL_TIER "reg8[30..31]"
 
 /* Indicate that this packet has been recirculated using egress
  * loopback.  This allows certain checks to be bypassed, such as a
@@ -5656,36 +5657,51 @@ ovn_ls_port_group_destroy(struct hmap *nb_pgs)
     hmap_destroy(nb_pgs);
 }
 
+static bool
+od_set_acl_flags(struct ovn_datapath *od, struct nbrec_acl **acls,
+                 size_t n_acls)
+{
+    /* A true return indicates that there are no possible ACL flags
+     * left to set on od. A false return indicates that further ACLs
+     * should be explored in case more flags need to be set on od
+     */
+    if (!n_acls) {
+        return false;
+    }
+
+    od->has_acls = true;
+    for (size_t i = 0; i < n_acls; i++) {
+        const struct nbrec_acl *acl = acls[i];
+        if (acl->tier > od->max_acl_tier) {
+            od->max_acl_tier = acl->tier;
+        }
+        if (!od->has_stateful_acl && !strcmp(acl->action, "allow-related")) {
+            od->has_stateful_acl = true;
+        }
+        if (od->has_stateful_acl &&
+            od->max_acl_tier == nbrec_acl_col_tier.type.value.integer.max) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
 static void
 ls_get_acl_flags(struct ovn_datapath *od)
 {
     od->has_acls = false;
     od->has_stateful_acl = false;
+    od->max_acl_tier = 0;
 
-    if (od->nbs->n_acls) {
-        od->has_acls = true;
-
-        for (size_t i = 0; i < od->nbs->n_acls; i++) {
-            struct nbrec_acl *acl = od->nbs->acls[i];
-            if (!strcmp(acl->action, "allow-related")) {
-                od->has_stateful_acl = true;
-                return;
-            }
-        }
+    if (od_set_acl_flags(od, od->nbs->acls, od->nbs->n_acls)) {
+        return;
     }
 
     struct ovn_ls_port_group *ls_pg;
     HMAP_FOR_EACH (ls_pg, key_node, &od->nb_pgs) {
-        if (ls_pg->nb_pg->n_acls) {
-            od->has_acls = true;
-
-            for (size_t i = 0; i < ls_pg->nb_pg->n_acls; i++) {
-                struct nbrec_acl *acl = ls_pg->nb_pg->acls[i];
-                if (!strcmp(acl->action, "allow-related")) {
-                    od->has_stateful_acl = true;
-                    return;
-                }
-            }
+        if (od_set_acl_flags(od, ls_pg->nb_pg->acls, ls_pg->nb_pg->n_acls)) {
+            return;
         }
     }
 }
@@ -6410,10 +6426,19 @@ consider_acl(struct hmap *lflows, struct ovn_datapath 
*od,
     size_t log_verdict_len = actions->length;
     uint16_t priority = acl->priority + OVN_ACL_PRI_OFFSET;
 
+    /* All ACLS will start by matching on their respective tier. */
+    size_t match_tier_len = 0;
+    ds_clear(match);
+    if (od->max_acl_tier) {
+        ds_put_format(match, REG_ACL_TIER " == %"PRId64" && ", acl->tier);
+        match_tier_len = match->length;
+    }
+
     if (!has_stateful || !strcmp(acl->action, "allow-stateless")) {
         ds_put_cstr(actions, "next;");
+        ds_put_format(match, "(%s)", acl->match);
         ovn_lflow_add_with_hint(lflows, od, stage, priority,
-                                acl->match, ds_cstr(actions),
+                                ds_cstr(match), ds_cstr(actions),
                                 &acl->header_);
         return;
     }
@@ -6438,7 +6463,7 @@ consider_acl(struct hmap *lflows, struct ovn_datapath *od,
          * by ct_commit in the "stateful" stage) to indicate that the
          * connection should be allowed to resume.
          */
-        ds_clear(match);
+        ds_truncate(match, match_tier_len);
         ds_put_format(match, REGBIT_ACL_HINT_ALLOW_NEW " == 1 && (%s)",
                       acl->match);
 
@@ -6461,7 +6486,7 @@ consider_acl(struct hmap *lflows, struct ovn_datapath *od,
          * Commit the connection only if the ACL has a label. This is done
          * to update the connection tracking entry label in case the ACL
          * allowing the connection changes. */
-        ds_clear(match);
+        ds_truncate(match, match_tier_len);
         ds_truncate(actions, log_verdict_len);
         ds_put_format(match, REGBIT_ACL_HINT_ALLOW " == 1 && (%s)",
                       acl->match);
@@ -6482,7 +6507,7 @@ consider_acl(struct hmap *lflows, struct ovn_datapath *od,
          * to the connection tracker with ct_commit. */
         /* If the packet is not tracked or not part of an established
          * connection, then we can simply reject/drop it. */
-        ds_clear(match);
+        ds_truncate(match, match_tier_len);
         ds_put_cstr(match, REGBIT_ACL_HINT_DROP " == 1");
         ds_put_format(match, " && (%s)", acl->match);
 
@@ -6502,7 +6527,7 @@ consider_acl(struct hmap *lflows, struct ovn_datapath *od,
          * ct_commit() to the "stateful" stage, but since we're
          * rejecting/dropping the packet, we go ahead and do it here.
          */
-        ds_clear(match);
+        ds_truncate(match, match_tier_len);
         ds_put_cstr(match, REGBIT_ACL_HINT_BLOCK " == 1");
         ds_put_format(match, " && (%s)", acl->match);
 
@@ -6656,6 +6681,7 @@ static void
 build_acl_action_lflows(struct ovn_datapath *od, struct hmap *lflows,
                         const char *default_acl_action,
                         const struct shash *meter_groups,
+                        struct ds *match,
                         struct ds *actions)
 {
     enum ovn_stage stages [] = {
@@ -6668,6 +6694,10 @@ build_acl_action_lflows(struct ovn_datapath *od, struct 
hmap *lflows,
     ds_put_cstr(actions, REGBIT_ACL_VERDICT_ALLOW " = 0; "
                         REGBIT_ACL_VERDICT_DROP " = 0; "
                         REGBIT_ACL_VERDICT_REJECT " = 0; ");
+    if (od->max_acl_tier) {
+        ds_put_cstr(actions, REG_ACL_TIER " = 0; ");
+    }
+
     size_t verdict_len = actions->length;
 
     for (size_t i = 0; i < ARRAY_SIZE(stages); i++) {
@@ -6705,6 +6735,20 @@ build_acl_action_lflows(struct ovn_datapath *od, struct 
hmap *lflows,
         ds_truncate(actions, verdict_len);
         ds_put_cstr(actions, default_acl_action);
         ovn_lflow_add(lflows, od, stage, 0, "1", ds_cstr(actions));
+
+        struct ds tier_actions = DS_EMPTY_INITIALIZER;
+        for (size_t j = 0; j < od->max_acl_tier; j++) {
+            ds_clear(match);
+            ds_put_format(match, REG_ACL_TIER " == %"PRIuSIZE, j);
+            ds_clear(&tier_actions);
+            ds_put_format(&tier_actions, REG_ACL_TIER " = %"PRIuSIZE"; "
+                          "next(pipeline=%s,table=%d);",
+                          j + 1, ingress ? "ingress" : "egress",
+                          ovn_stage_get_table(stage) - 1);
+            ovn_lflow_add(lflows, od, stage, 500, ds_cstr(match),
+                         ds_cstr(&tier_actions));
+        }
+        ds_destroy(&tier_actions);
     }
 }
 
@@ -7074,7 +7118,7 @@ build_acls(struct ovn_datapath *od, const struct 
chassis_features *features,
     }
 
     build_acl_action_lflows(od, lflows, default_acl_action, meter_groups,
-                            &actions);
+                            &match, &actions);
 
     ds_destroy(&match);
     ds_destroy(&actions);
diff --git a/northd/northd.h b/northd/northd.h
index a503f4a66..ad6ccef5e 100644
--- a/northd/northd.h
+++ b/northd/northd.h
@@ -230,6 +230,7 @@ struct ovn_datapath {
     bool has_lb_vip;
     bool has_unknown;
     bool has_acls;
+    uint64_t max_acl_tier;
     bool has_vtep_lports;
     bool has_arp_proxy_port;
 
diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
index 40c5ec95b..c0b7a69f2 100644
--- a/northd/ovn-northd.8.xml
+++ b/northd/ovn-northd.8.xml
@@ -709,6 +709,12 @@
         referred to as the "allow", "drop", and "reject" bits in the upcoming
         paragraphs.
       </li>
+      <li>
+        If the <code>tier</code> column has been configured on the ACL, then
+        OVN will also match the current tier counter against the configured
+        ACL tier. OVN keeps count of the current tier in
+        <code>reg8[30..31]</code>.
+      </li>
       <li>
         <code>allow</code> ACLs translate into logical flows that set the allow
         bit to 1 and advance the packet to the next table. If there are any
@@ -884,6 +890,13 @@
         for UDP connections, and <code>sctp_abort {output &lt;-%gt; inport;
         next(pipeline=egress,table=5);}</code> action for SCTP associations.
       </li>
+
+      <li>
+        If any ACLs have tiers configured on them, then three priority 500
+        flows are installed. If the current tier counter is 0, 1, or 2, then
+        the current tier counter is incremented by one and the packet is sent
+        back to the previous table for re-evaluation.
+      </li>
     </ul>
 
     <h3>Ingress Table 10: <code>from-lport</code> QoS Marking</h3>
@@ -1174,6 +1187,13 @@
       table and the next one.
     </p>
 
+    <p>
+      Like with ACLs that are evaluated before load balancers, if the ACL is
+      configured with a tier value, then the current tier counter, supplied
+      in reg8[30..31] is matched against the ACL's configured tier in addition
+      to the ACL's match.
+    </p>
+
     <ul>
       <li>
         <code>allow</code> apply-after-lb ACLs translate into logical flows
@@ -1259,6 +1279,13 @@
         for UDP connections, and <code>sctp_abort {output &lt;-%gt; inport;
         next(pipeline=egress,table=5);}</code> action for SCTP associations.
       </li>
+
+      <li>
+        If any ACLs have tiers configured on them, then three priority 500
+        flows are installed. If the current tier counter is 0, 1, or 2, then
+        the current tier counter is incremented by one and the packet is sent
+        back to the previous table for re-evaluation.
+      </li>
     </ul>
 
     <h3>Ingress Table 20: Stateful</h3>
@@ -2197,6 +2224,14 @@ output;
       <code>reg8[18]</code>.
     </p>
 
+    <p>
+      Also like with ingress ACLs, egress ACLs can have a configured
+      <code>tier</code>. If a tier is configured, then the current tier
+      counter is evaluated against the ACL's configured tier in addition
+      to the ACL's match. The current tier counter is stored in
+      <code>reg8[30..31]</code>.
+    </p>
+
     <p>
       Similar to ingress table, a priority-65532 flow is added to allow IPv6
       Neighbor solicitation, Neighbor discover, Router solicitation, Router
diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
index ff3026664..789275f37 100644
--- a/ovn-nb.ovsschema
+++ b/ovn-nb.ovsschema
@@ -1,7 +1,7 @@
 {
     "name": "OVN_Northbound",
-    "version": "7.0.2",
-    "cksum": "190109735 33605",
+    "version": "7.0.3",
+    "cksum": "3667000751 33787",
     "tables": {
         "NB_Global": {
             "columns": {
@@ -272,6 +272,9 @@
                 "label": {"type": {"key": {"type": "integer",
                                            "minInteger": 0,
                                            "maxInteger": 4294967295}}},
+                "tier": {"type": {"key": {"type": "integer",
+                                          "minInteger": 0,
+                                          "maxInteger": 3}}},
                 "options": {
                      "type": {"key": "string",
                               "value": "string",
diff --git a/ovn-nb.xml b/ovn-nb.xml
index d8114aa48..4a8279f6a 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -2284,6 +2284,26 @@ or
       </ul>
     </column>
 
+    <column name="tier">
+      <p>The hierarchical tier that this ACL belongs to.</p>
+
+      <p>
+        ACLs can be assigned to numerical tiers. When evaluating ACLs, an
+        internal counter is used to determine which tier of ACLs should be
+        evaluated. Tier 0 ACLs are evaluated first. If no verdict can be
+        determined, then tier 1 ACLs are evaluated next. This continues
+        until the maximum tier value is reached. If all tiers of ACLs are
+        evaluated and no verdict is reached, then the <ref column="options"
+        key="default_acl_drop" table="NB_Global" /> option from table
+        <ref table="NB_Global" /> is used to determine how to proceed.
+      </p>
+
+      <p>
+        In this version of OVN, the maximum tier value for ACLs is 3,
+        meaning there are 4 tiers of ACLs allowed (0-3).
+      </p>
+    </column>
+
     <group title="options">
       <p>
         ACLs options.
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 1c9958c6b..7d94cd6ac 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -2161,10 +2161,10 @@ 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_eval    ), priority=2003 , match=(outport == 
@pg0 && ip6 && udp), action=(reg8[[18]] = 1; next;)
-sw0flows:  table=8 (ls_in_acl_eval     ), priority=2002 , match=(inport == 
@pg0 && ip4 && tcp && tcp.dst == 80), action=(reg8[[18]] = 1; next;)
-sw1flows:  table=4 (ls_out_acl_eval    ), priority=2003 , match=(outport == 
@pg0 && ip6 && udp), action=(reg8[[18]] = 1; next;)
-sw1flows:  table=8 (ls_in_acl_eval     ), priority=2002 , match=(inport == 
@pg0 && ip4 && tcp && tcp.dst == 80), action=(reg8[[18]] = 1; next;)
+sw0flows:  table=4 (ls_out_acl_eval    ), priority=2003 , match=((outport == 
@pg0 && ip6 && udp)), action=(reg8[[18]] = 1; next;)
+sw0flows:  table=8 (ls_in_acl_eval     ), priority=2002 , match=((inport == 
@pg0 && ip4 && tcp && tcp.dst == 80)), action=(reg8[[18]] = 1; next;)
+sw1flows:  table=4 (ls_out_acl_eval    ), priority=2003 , match=((outport == 
@pg0 && ip6 && udp)), action=(reg8[[18]] = 1; next;)
+sw1flows:  table=8 (ls_in_acl_eval     ), priority=2002 , match=((inport == 
@pg0 && ip4 && tcp && tcp.dst == 80)), action=(reg8[[18]] = 1; next;)
 ])
 
 AS_BOX([2])
@@ -2177,10 +2177,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_eval    ), priority=2002 , match=(outport == 
@pg0 && ip4 && udp), action=(reg8[[18]] = 1; next;)
-sw0flows2:  table=4 (ls_out_acl_eval    ), priority=2003 , match=(outport == 
@pg0 && ip6 && udp), action=(reg8[[18]] = 1; next;)
-sw1flows2:  table=4 (ls_out_acl_eval    ), priority=2002 , match=(outport == 
@pg0 && ip4 && udp), action=(reg8[[18]] = 1; next;)
-sw1flows2:  table=4 (ls_out_acl_eval    ), priority=2003 , match=(outport == 
@pg0 && ip6 && udp), action=(reg8[[18]] = 1; next;)
+sw0flows2:  table=4 (ls_out_acl_eval    ), priority=2002 , match=((outport == 
@pg0 && ip4 && udp)), action=(reg8[[18]] = 1; next;)
+sw0flows2:  table=4 (ls_out_acl_eval    ), priority=2003 , match=((outport == 
@pg0 && ip6 && udp)), action=(reg8[[18]] = 1; next;)
+sw1flows2:  table=4 (ls_out_acl_eval    ), priority=2002 , match=((outport == 
@pg0 && ip4 && udp)), action=(reg8[[18]] = 1; next;)
+sw1flows2:  table=4 (ls_out_acl_eval    ), priority=2003 , match=((outport == 
@pg0 && ip6 && udp)), action=(reg8[[18]] = 1; next;)
 ])
 
 AS_BOX([3])
@@ -7526,7 +7526,7 @@ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | 
sed 's/table=../table=??/
   table=??(ls_in_acl_after_lb_eval), priority=0    , match=(1), action=(next;)
   table=??(ls_in_acl_after_lb_eval), priority=65532, match=(nd || nd_ra || 
nd_rs || mldv1 || mldv2), action=(reg8[[16]] = 1; next;)
   table=??(ls_in_acl_eval     ), priority=0    , match=(1), action=(next;)
-  table=??(ls_in_acl_eval     ), priority=1001 , match=(ip4 && tcp), 
action=(reg8[[16]] = 1; next;)
+  table=??(ls_in_acl_eval     ), priority=1001 , match=((ip4 && tcp)), 
action=(reg8[[16]] = 1; next;)
   table=??(ls_in_acl_eval     ), priority=34000, match=(eth.dst == 
$svc_monitor_mac), action=(reg8[[16]] = 1; next;)
   table=??(ls_in_acl_eval     ), priority=65532, match=(nd || nd_ra || nd_rs 
|| mldv1 || mldv2), action=(reg8[[16]] = 1; next;)
   table=??(ls_in_acl_hint     ), priority=0    , match=(1), action=(next;)
@@ -7563,7 +7563,7 @@ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | 
sed 's/table=../table=??/
   table=??(ls_in_acl_after_lb_eval), priority=0    , match=(1), action=(next;)
   table=??(ls_in_acl_after_lb_eval), priority=65532, match=(nd || nd_ra || 
nd_rs || mldv1 || mldv2), action=(reg8[[16]] = 1; next;)
   table=??(ls_in_acl_eval     ), priority=0    , match=(1), action=(next;)
-  table=??(ls_in_acl_eval     ), priority=1001 , match=(ip4 && tcp), 
action=(reg8[[16]] = 1; next;)
+  table=??(ls_in_acl_eval     ), priority=1001 , match=((ip4 && tcp)), 
action=(reg8[[16]] = 1; next;)
   table=??(ls_in_acl_eval     ), priority=34000, match=(eth.dst == 
$svc_monitor_mac), action=(reg8[[16]] = 1; next;)
   table=??(ls_in_acl_eval     ), priority=65532, match=(nd || nd_ra || nd_rs 
|| mldv1 || mldv2), action=(reg8[[16]] = 1; next;)
   table=??(ls_in_acl_hint     ), priority=0    , match=(1), action=(next;)
@@ -7600,7 +7600,7 @@ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | 
sed 's/table=../table=??/
   table=??(ls_in_acl_after_lb_eval), priority=0    , match=(1), action=(next;)
   table=??(ls_in_acl_after_lb_eval), priority=65532, match=(nd || nd_ra || 
nd_rs || mldv1 || mldv2), action=(reg8[[16]] = 1; next;)
   table=??(ls_in_acl_eval     ), priority=0    , match=(1), action=(next;)
-  table=??(ls_in_acl_eval     ), priority=1001 , match=(ip4 && tcp), 
action=(reg8[[16]] = 1; next;)
+  table=??(ls_in_acl_eval     ), priority=1001 , match=((ip4 && tcp)), 
action=(reg8[[16]] = 1; next;)
   table=??(ls_in_acl_eval     ), priority=34000, match=(eth.dst == 
$svc_monitor_mac), action=(reg8[[16]] = 1; next;)
   table=??(ls_in_acl_eval     ), priority=65532, match=(nd || nd_ra || nd_rs 
|| mldv1 || mldv2), action=(reg8[[16]] = 1; next;)
   table=??(ls_in_acl_hint     ), priority=0    , match=(1), action=(next;)
@@ -7708,7 +7708,7 @@ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | 
sed 's/table=../table=??/
   table=??(ls_in_acl_after_lb_action), priority=1000 , match=(reg8[[17]] == 
1), action=(reg8[[16]] = 0; reg8[[17]] = 0; reg8[[18]] = 0; /* drop */)
   table=??(ls_in_acl_after_lb_action), priority=1000 , match=(reg8[[18]] == 
1), action=(reg8[[16]] = 0; reg8[[17]] = 0; reg8[[18]] = 0; reg0 = 0; reject { 
/* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; 
next(pipeline=egress,table=6); };)
   table=??(ls_in_acl_after_lb_eval), priority=0    , match=(1), action=(next;)
-  table=??(ls_in_acl_after_lb_eval), priority=1001 , match=(ip4 && tcp), 
action=(reg8[[16]] = 1; next;)
+  table=??(ls_in_acl_after_lb_eval), priority=1001 , match=((ip4 && tcp)), 
action=(reg8[[16]] = 1; next;)
   table=??(ls_in_acl_after_lb_eval), priority=65532, match=(nd || nd_ra || 
nd_rs || mldv1 || mldv2), action=(reg8[[16]] = 1; next;)
   table=??(ls_in_acl_eval     ), priority=0    , match=(1), action=(next;)
   table=??(ls_in_acl_eval     ), priority=34000, match=(eth.dst == 
$svc_monitor_mac), action=(reg8[[16]] = 1; next;)
@@ -7745,7 +7745,7 @@ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | 
sed 's/table=../table=??/
   table=??(ls_in_acl_after_lb_action), priority=1000 , match=(reg8[[17]] == 
1), action=(reg8[[16]] = 0; reg8[[17]] = 0; reg8[[18]] = 0; /* drop */)
   table=??(ls_in_acl_after_lb_action), priority=1000 , match=(reg8[[18]] == 
1), action=(reg8[[16]] = 0; reg8[[17]] = 0; reg8[[18]] = 0; reg0 = 0; reject { 
/* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; 
next(pipeline=egress,table=6); };)
   table=??(ls_in_acl_after_lb_eval), priority=0    , match=(1), action=(next;)
-  table=??(ls_in_acl_after_lb_eval), priority=1001 , match=(ip4 && tcp), 
action=(reg8[[16]] = 1; next;)
+  table=??(ls_in_acl_after_lb_eval), priority=1001 , match=((ip4 && tcp)), 
action=(reg8[[16]] = 1; next;)
   table=??(ls_in_acl_after_lb_eval), priority=65532, match=(nd || nd_ra || 
nd_rs || mldv1 || mldv2), action=(reg8[[16]] = 1; next;)
   table=??(ls_in_acl_eval     ), priority=0    , match=(1), action=(next;)
   table=??(ls_in_acl_eval     ), priority=34000, match=(eth.dst == 
$svc_monitor_mac), action=(reg8[[16]] = 1; next;)
@@ -7782,7 +7782,7 @@ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | 
sed 's/table=../table=??/
   table=??(ls_in_acl_after_lb_action), priority=1000 , match=(reg8[[17]] == 
1), action=(reg8[[16]] = 0; reg8[[17]] = 0; reg8[[18]] = 0; /* drop */)
   table=??(ls_in_acl_after_lb_action), priority=1000 , match=(reg8[[18]] == 
1), action=(reg8[[16]] = 0; reg8[[17]] = 0; reg8[[18]] = 0; reg0 = 0; reject { 
/* eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; 
next(pipeline=egress,table=6); };)
   table=??(ls_in_acl_after_lb_eval), priority=0    , match=(1), action=(next;)
-  table=??(ls_in_acl_after_lb_eval), priority=1001 , match=(ip4 && tcp), 
action=(reg8[[16]] = 1; next;)
+  table=??(ls_in_acl_after_lb_eval), priority=1001 , match=((ip4 && tcp)), 
action=(reg8[[16]] = 1; next;)
   table=??(ls_in_acl_after_lb_eval), priority=65532, match=(nd || nd_ra || 
nd_rs || mldv1 || mldv2), action=(reg8[[16]] = 1; next;)
   table=??(ls_in_acl_eval     ), priority=0    , match=(1), action=(next;)
   table=??(ls_in_acl_eval     ), priority=34000, match=(eth.dst == 
$svc_monitor_mac), action=(reg8[[16]] = 1; next;)
@@ -7904,7 +7904,7 @@ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | 
sed 's/table=../table=??/
   table=??(ls_out_acl_action  ), priority=1000 , match=(reg8[[17]] == 1), 
action=(reg8[[16]] = 0; reg8[[17]] = 0; reg8[[18]] = 0; /* drop */)
   table=??(ls_out_acl_action  ), priority=1000 , match=(reg8[[18]] == 1), 
action=(reg8[[16]] = 0; reg8[[17]] = 0; reg8[[18]] = 0; reg0 = 0; reject { /* 
eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; 
next(pipeline=ingress,table=27); };)
   table=??(ls_out_acl_eval    ), priority=0    , match=(1), action=(next;)
-  table=??(ls_out_acl_eval    ), priority=1001 , match=(ip4 && tcp), 
action=(reg8[[16]] = 1; next;)
+  table=??(ls_out_acl_eval    ), priority=1001 , match=((ip4 && tcp)), 
action=(reg8[[16]] = 1; next;)
   table=??(ls_out_acl_eval    ), priority=34000, match=(eth.src == 
$svc_monitor_mac), action=(reg8[[16]] = 1; next;)
   table=??(ls_out_acl_eval    ), priority=65532, match=(nd || nd_ra || nd_rs 
|| mldv1 || mldv2), action=(reg8[[16]] = 1; next;)
   table=??(ls_out_acl_hint    ), priority=0    , match=(1), action=(next;)
@@ -7941,7 +7941,7 @@ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | 
sed 's/table=../table=??/
   table=??(ls_out_acl_action  ), priority=1000 , match=(reg8[[17]] == 1), 
action=(reg8[[16]] = 0; reg8[[17]] = 0; reg8[[18]] = 0; /* drop */)
   table=??(ls_out_acl_action  ), priority=1000 , match=(reg8[[18]] == 1), 
action=(reg8[[16]] = 0; reg8[[17]] = 0; reg8[[18]] = 0; reg0 = 0; reject { /* 
eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; 
next(pipeline=ingress,table=27); };)
   table=??(ls_out_acl_eval    ), priority=0    , match=(1), action=(next;)
-  table=??(ls_out_acl_eval    ), priority=1001 , match=(ip4 && tcp), 
action=(reg8[[16]] = 1; next;)
+  table=??(ls_out_acl_eval    ), priority=1001 , match=((ip4 && tcp)), 
action=(reg8[[16]] = 1; next;)
   table=??(ls_out_acl_eval    ), priority=34000, match=(eth.src == 
$svc_monitor_mac), action=(reg8[[16]] = 1; next;)
   table=??(ls_out_acl_eval    ), priority=65532, match=(nd || nd_ra || nd_rs 
|| mldv1 || mldv2), action=(reg8[[16]] = 1; next;)
   table=??(ls_out_acl_hint    ), priority=0    , match=(1), action=(next;)
@@ -7978,7 +7978,7 @@ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | 
sed 's/table=../table=??/
   table=??(ls_out_acl_action  ), priority=1000 , match=(reg8[[17]] == 1), 
action=(reg8[[16]] = 0; reg8[[17]] = 0; reg8[[18]] = 0; /* drop */)
   table=??(ls_out_acl_action  ), priority=1000 , match=(reg8[[18]] == 1), 
action=(reg8[[16]] = 0; reg8[[17]] = 0; reg8[[18]] = 0; reg0 = 0; reject { /* 
eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport; 
next(pipeline=ingress,table=27); };)
   table=??(ls_out_acl_eval    ), priority=0    , match=(1), action=(next;)
-  table=??(ls_out_acl_eval    ), priority=1001 , match=(ip4 && tcp), 
action=(reg8[[16]] = 1; next;)
+  table=??(ls_out_acl_eval    ), priority=1001 , match=((ip4 && tcp)), 
action=(reg8[[16]] = 1; next;)
   table=??(ls_out_acl_eval    ), priority=34000, match=(eth.src == 
$svc_monitor_mac), action=(reg8[[16]] = 1; next;)
   table=??(ls_out_acl_eval    ), priority=65532, match=(nd || nd_ra || nd_rs 
|| mldv1 || mldv2), action=(reg8[[16]] = 1; next;)
   table=??(ls_out_acl_hint    ), priority=0    , match=(1), action=(next;)
@@ -9252,3 +9252,130 @@ AT_CHECK([fetch_column sb:Port_Binding options 
logical_port=public |grep -q 'qos
 
 AT_CLEANUP
 ])
+
+AT_SETUP([Tiered ACL logical flows])
+AT_KEYWORDS([acl])
+
+ovn_start
+
+check ovn-nbctl ls-add ls
+check ovn-nbctl lsp-add ls lsp
+check ovn-nbctl pg-add pg lsp
+
+m4_define([ACL_FLOWS], [grep -w $1 lflows | grep "$2" | sed 
's/table=../table=??/' | sed "s/\($1[[^)]]*\)/$1/" | sort])
+
+acl_test() {
+    direction=$1
+    options=$2
+    thing=$3
+    eval_stage=$4
+    action_stage=$5
+    eval_stage_table=$6
+
+    if test "$direction" = "from-lport" ; then
+        pipeline=ingress
+    else
+        pipeline=egress
+    fi
+
+    # Baseline test. Ensure that no ACL evaluation or tier-related flows are
+    # installed.
+    ovn-sbctl lflow-list ls > lflows
+    AT_CHECK([ACL_FLOWS([$eval_stage], [priority=2000])], [0], [])
+
+    AT_CHECK([ACL_FLOWS([$action_stage], [priority=500])], [0], [])
+
+    # Add an untiered ACL. Ensure that the ACL appears in the eval stage, and
+    # that no tier-related flows appear in the action stage.
+    check ovn-nbctl --wait=sb $options acl-add $thing $direction 1000 
"ip4.addr == 80.111.111.112" drop
+    acl1_uuid=$(ovn-nbctl --bare --columns _uuid find ACL priority=1000)
+
+    ovn-sbctl lflow-list ls > lflows
+    AT_CAPTURE_FILE([lflows])
+    AT_CHECK_UNQUOTED([ACL_FLOWS([$eval_stage], [priority=2000])], [0], [dnl
+  table=??($eval_stage), priority=2000 , match=((ip4.addr == 80.111.111.112)), 
action=(reg8[[17]] = 1; next;)
+])
+
+    AT_CHECK([ACL_FLOWS([$action_stage], [priority=500])], [0], [])
+
+    # Explicitly name the tier on the ACL to be tier 0. This should have no
+    # effect on the logical flows.
+    check ovn-nbctl --wait=sb set ACL $acl1_uuid tier=0
+    ovn-sbctl lflow-list ls > lflows
+    AT_CHECK_UNQUOTED([ACL_FLOWS([$eval_stage], [priority=2000])], [0], [dnl
+  table=??($eval_stage), priority=2000 , match=((ip4.addr == 80.111.111.112)), 
action=(reg8[[17]] = 1; next;)
+])
+    AT_CHECK([ACL_FLOWS([$action_stage], [priority=500])], [0], [])
+
+    # Change the ACL to tier 1. Now we should see the tier as part of the ACL
+    # match, and we should see a flow in the action stage to bump the tier
+    # to 1 if there was no match on tier 0.
+    check ovn-nbctl --wait=sb set ACL $acl1_uuid tier=1
+    ovn-sbctl lflow-list ls > lflows
+    AT_CHECK_UNQUOTED([ACL_FLOWS([$eval_stage], [priority=2000])], [0], [dnl
+  table=??($eval_stage), priority=2000 , match=(reg8[[30..31]] == 1 && 
(ip4.addr == 80.111.111.112)), action=(reg8[[17]] = 1; next;)
+])
+
+    AT_CHECK_UNQUOTED([ACL_FLOWS([$action_stage], [priority=500])], [0], [dnl
+  table=??($action_stage), priority=500  , match=(reg8[[30..31]] == 0), 
action=(reg8[[30..31]] = 1; next(pipeline=$pipeline,table=$eval_stage_table);)
+])
+
+    # Change the ACL to tier 3. Ensure the tier match on the ACL has been
+    # updated, and ensure we see three flows present for incrementing the
+    # tier value in the action stage.
+    check ovn-nbctl --wait=sb set ACL $acl1_uuid tier=3
+    ovn-sbctl lflow-list ls > lflows
+    AT_CHECK_UNQUOTED([ACL_FLOWS([$eval_stage], [priority=2000])], [0], [dnl
+  table=??($eval_stage), priority=2000 , match=(reg8[[30..31]] == 3 && 
(ip4.addr == 80.111.111.112)), action=(reg8[[17]] = 1; next;)
+])
+
+    AT_CHECK_UNQUOTED([ACL_FLOWS([$action_stage], [priority=500])], [0], [dnl
+  table=??($action_stage), priority=500  , match=(reg8[[30..31]] == 0), 
action=(reg8[[30..31]] = 1; next(pipeline=$pipeline,table=$eval_stage_table);)
+  table=??($action_stage), priority=500  , match=(reg8[[30..31]] == 1), 
action=(reg8[[30..31]] = 2; next(pipeline=$pipeline,table=$eval_stage_table);)
+  table=??($action_stage), priority=500  , match=(reg8[[30..31]] == 2), 
action=(reg8[[30..31]] = 3; next(pipeline=$pipeline,table=$eval_stage_table);)
+])
+
+    # Add an untiered ACL. Ensure that it matches on tier 0, but otherwise,
+    # nothing else should have changed in the logical flows.
+    check ovn-nbctl --wait=sb $options acl-add $thing $direction 1000 
"ip4.addr == 83.104.105.116" allow
+    ovn-sbctl lflow-list ls > lflows
+    AT_CHECK_UNQUOTED([ACL_FLOWS([$eval_stage], [priority=2000])], [0], [dnl
+  table=??($eval_stage), priority=2000 , match=(reg8[[30..31]] == 0 && 
(ip4.addr == 83.104.105.116)), action=(reg8[[16]] = 1; next;)
+  table=??($eval_stage), priority=2000 , match=(reg8[[30..31]] == 3 && 
(ip4.addr == 80.111.111.112)), action=(reg8[[17]] = 1; next;)
+])
+
+    AT_CHECK_UNQUOTED([ACL_FLOWS([$action_stage], [priority=500])], [0], [dnl
+  table=??($action_stage), priority=500  , match=(reg8[[30..31]] == 0), 
action=(reg8[[30..31]] = 1; next(pipeline=$pipeline,table=$eval_stage_table);)
+  table=??($action_stage), priority=500  , match=(reg8[[30..31]] == 1), 
action=(reg8[[30..31]] = 2; next(pipeline=$pipeline,table=$eval_stage_table);)
+  table=??($action_stage), priority=500  , match=(reg8[[30..31]] == 2), 
action=(reg8[[30..31]] = 3; next(pipeline=$pipeline,table=$eval_stage_table);)
+])
+
+    # Remove the tier 3 ACL. The remaining ACL is untiered, and there are no
+    # other tiered ACLs. So we should go back to not checking the tier
+    # number in the ACL match, and there should be no tier-related flows
+    # in the action stage.
+    check ovn-nbctl --wait=sb acl-del $thing $direction 1000 "ip4.addr == 
80.111.111.112"
+    ovn-sbctl lflow-list ls > lflows
+    AT_CHECK_UNQUOTED([ACL_FLOWS([$eval_stage], [priority=2000])], [0], [dnl
+  table=??($eval_stage), priority=2000 , match=((ip4.addr == 83.104.105.116)), 
action=(reg8[[16]] = 1; next;)
+])
+
+    AT_CHECK([ACL_FLOWS([$action_stage], [priority=500])], [0], [])
+
+    check ovn-nbctl --wait=sb acl-del $thing
+    ovn-sbctl lflow-list ls > lflows
+
+    AT_CHECK([ACL_FLOWS([$eval_stage], [priority=2000])], [0], [])
+
+    AT_CHECK([ACL_FLOWS([$action_stage], [priority=500])], [0], [])
+}
+
+acl_test from-lport "" ls ls_in_acl_eval ls_in_acl_action 8
+acl_test from-lport "--apply-after-lb" ls ls_in_acl_after_lb_eval 
ls_in_acl_after_lb_action 18
+acl_test to-lport "" ls ls_out_acl_eval ls_out_acl_action 4
+acl_test from-lport "" pg ls_in_acl_eval ls_in_acl_action 8
+acl_test from-lport "--apply-after-lb" pg ls_in_acl_after_lb_eval 
ls_in_acl_after_lb_action 18
+acl_test to-lport "" pg ls_out_acl_eval ls_out_acl_action 4
+
+AT_CLEANUP
+])
diff --git a/tests/system-ovn.at b/tests/system-ovn.at
index 5ee044e19..b9288c24f 100644
--- a/tests/system-ovn.at
+++ b/tests/system-ovn.at
@@ -11315,3 +11315,111 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port 
patch-.*/d
 /connection dropped.*/d"])
 AT_CLEANUP
 ])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([Tiered ACLs])
+AT_KEYWORDS([acl])
+
+ovn_start
+OVS_TRAFFIC_VSWITCHD_START()
+ADD_BR([br-int])
+
+# Set external-ids in br-int needed for ovn-controller
+check ovs-vsctl \
+        -- set Open_vSwitch . external-ids:system-id=hv1 \
+        -- set Open_vSwitch . 
external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
+        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
+        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
+        -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
+
+# Start ovn-controller
+
+start_daemon ovn-controller
+
+check ovn-nbctl ls-add ls
+check ovn-nbctl lsp-add ls lsp1 -- lsp-set-addresses lsp1 "00:00:00:00:00:01 
10.0.0.1"
+check ovn-nbctl lsp-add ls lsp2 -- lsp-set-addresses lsp2 "00:00:00:00:00:02 
10.0.0.2"
+
+check ovn-nbctl pg-add pg lsp1 lsp2
+
+ADD_NAMESPACES(lsp1)
+ADD_VETH(lsp1, lsp1, br-int, "10.0.0.1/24", "00:00:00:00:00:01")
+ADD_NAMESPACES(lsp2)
+ADD_VETH(lsp2, lsp2, br-int, "10.0.0.2/24", "00:00:00:00:00:02")
+
+m4_define([PING_PCT], [grep -o "[[0-9]]\{1,3\}% packet loss"])
+
+acl_test() {
+    direction=$1
+    options=$2
+    thing=$3
+
+    # First a baseline. If traffic isn't being allowed, then something is
+    # very wrong.
+    NS_CHECK_EXEC([lsp1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.2 | PING_PCT], \
+[0], [dnl
+0% packet loss
+])
+    # Add an untiered drop ACL. This should cause pings to fail.
+    check ovn-nbctl --wait=sb $options acl-add $thing $direction 1000 "ip4.dst 
== 10.0.0.2" drop
+    acl1_uuid=$(ovn-nbctl --bare --columns _uuid find ACL priority=1000)
+
+    NS_CHECK_EXEC([lsp1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.2 | PING_PCT], \
+[0], [dnl
+100% packet loss
+])
+
+    # Change the tier to 3. Despite there being "holes" in tiers 0, 1, and 2,
+    # the ACL should still apply, and pings should fail.
+    check ovn-nbctl --wait=sb set ACL $acl1_uuid tier=3
+    NS_CHECK_EXEC([lsp1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.2 | PING_PCT], \
+[0], [dnl
+100% packet loss
+])
+
+    # Add a tier-0 ACL that allows the traffic. The priority is only 4, but
+    # since it is a higher tier, the traffic should be allowed.
+    check ovn-nbctl --wait=sb $options acl-add $thing $direction 4 "ip4.dst == 
10.0.0.2" allow
+    NS_CHECK_EXEC([lsp1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.2 | PING_PCT], \
+[0], [dnl
+0% packet loss
+])
+
+    # Removing the 0-tier ACL should make traffic go back to being dropped.
+    check ovn-nbctl --wait=sb acl-del $thing $direction 4 "ip4.dst == 10.0.0.2"
+    NS_CHECK_EXEC([lsp1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.2 | PING_PCT], \
+[0], [dnl
+100% packet loss
+])
+
+    # Removing all ACLs should make traffic go back to passing.
+    check ovn-nbctl --wait=sb acl-del $thing
+    NS_CHECK_EXEC([lsp1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.2 | PING_PCT], \
+[0], [dnl
+0% packet loss
+])
+}
+
+acl_test from-lport "" ls
+acl_test from-lport "--apply-after-lb" ls
+acl_test to-lport "" ls
+acl_test from-lport "" pg
+acl_test from-lport "--apply-after-lb" pg
+acl_test to-lport "" pg
+
+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
+])
-- 
2.39.2

_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to