Add a new "pass-related" action that behaves like "pass" for
verdict (no verdict bit is set, so evaluation continues to
the next tier) but follows the stateful path like
"allow-related": it matches on hint registers, commits the
connection via conntrack, and allows established/related
traffic flows.

Reported-at: https://redhat.atlassian.net/browse/FDP-3166
Signed-off-by: Mairtin O'Loingsigh <[email protected]>
---
 NEWS                    |   3 +
 northd/en-acl-ids.c     |   3 +-
 northd/en-ls-stateful.c |   5 +-
 northd/northd.c         |  17 ++++--
 ovn-nb.ovsschema        |   7 ++-
 ovn-nb.xml              |  24 +++++++-
 tests/ovn-nbctl.at      |   6 +-
 tests/ovn-northd.at     |  84 +++++++++++++++++++++++++++
 tests/ovn.at            | 123 ++++++++++++++++++++++++++++++++++++++++
 tests/system-ovn.at     |  87 ++++++++++++++++++++++++++++
 utilities/ovn-nbctl.c   |  19 ++++---
 11 files changed, 353 insertions(+), 25 deletions(-)

diff --git a/NEWS b/NEWS
index d1a6ad27f..d18800599 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,8 @@
 Post v26.03.0
 -------------
+   - Added "pass-related" ACL action, a stateful version of "pass" that
+     commits connections and allows established/related traffic through
+     the fast-path.
    - Load balancer health checks now "fail closed" for newly added backends:
      ovn-northd initializes a newly created Service_Monitor row to the
      "offline" state, so traffic is not forwarded to a backend until its first
diff --git a/northd/en-acl-ids.c b/northd/en-acl-ids.c
index 09c7955cb..79e8156d2 100644
--- a/northd/en-acl-ids.c
+++ b/northd/en-acl-ids.c
@@ -36,7 +36,8 @@ en_acl_id_init(struct engine_node *node OVS_UNUSED,
 static bool
 should_sync_to_sb(const struct nbrec_acl *nb_acl)
 {
-    return !strcmp(nb_acl->action, "allow-related") &&
+    return (!strcmp(nb_acl->action, "allow-related") ||
+            !strcmp(nb_acl->action, "pass-related")) &&
            smap_get_bool(&nb_acl->options,
                          "persist-established",
                          false);
diff --git a/northd/en-ls-stateful.c b/northd/en-ls-stateful.c
index 1127b7d50..ab97e917b 100644
--- a/northd/en-ls-stateful.c
+++ b/northd/en-ls-stateful.c
@@ -468,8 +468,9 @@ ls_stateful_record_set_acls_(struct ls_stateful_record 
*ls_stateful_rec,
         const struct nbrec_acl *acl = acls[i];
         update_ls_max_acl_tier(ls_stateful_rec, acl);
         uuidset_insert(&ls_stateful_rec->related_acls, &acl->header_.uuid);
-        if (!ls_stateful_rec->has_stateful_acl
-                && !strcmp(acl->action, "allow-related")) {
+        if (!ls_stateful_rec->has_stateful_acl &&
+            (!strcmp(acl->action, "allow-related") ||
+             !strcmp(acl->action, "pass-related"))) {
             ls_stateful_rec->has_stateful_acl = true;
         }
     }
diff --git a/northd/northd.c b/northd/northd.c
index 8ce06da78..035be09d7 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -7116,7 +7116,8 @@ build_acl_log(struct ds *actions, const struct nbrec_acl 
*acl,
         ds_put_cstr(actions, "verdict=drop, ");
     } else if (!strcmp(acl->action, "reject")) {
         ds_put_cstr(actions, "verdict=reject, ");
-    } else if (!strcmp(acl->action, "pass")) {
+    } else if (!strcmp(acl->action, "pass") ||
+               !strcmp(acl->action, "pass-related")) {
         ds_put_cstr(actions, "verdict=pass, ");
     } else if (!strcmp(acl->action, "allow")
         || !strcmp(acl->action, "allow-related")
@@ -7522,7 +7523,8 @@ build_acl_sample_flows(const struct ls_stateful_record 
*ls_stateful_rec,
     bool should_sample_established =
         ls_stateful_rec->has_stateful_acl
         && acl->sample_est
-        && !strcmp(acl->action, "allow-related");
+        && (!strcmp(acl->action, "allow-related") ||
+            !strcmp(acl->action, "pass-related"));
 
     bool stateful_match =
         ls_stateful_rec->has_stateful_acl
@@ -7631,7 +7633,8 @@ consider_acl(struct lflow_table *lflows, const struct 
ovn_datapath *od,
         verdict = REGBIT_ACL_VERDICT_DROP " = 1; ";
     } else if (!strcmp(acl->action, "reject")) {
         verdict = REGBIT_ACL_VERDICT_REJECT " = 1; ";
-    } else if (!strcmp(acl->action, "pass")) {
+    } else if (!strcmp(acl->action, "pass") ||
+               !strcmp(acl->action, "pass-related")) {
         verdict = "";
     } else {
         verdict = REGBIT_ACL_VERDICT_ALLOW " = 1; ";
@@ -7681,7 +7684,8 @@ consider_acl(struct lflow_table *lflows, const struct 
ovn_datapath *od,
     }
 
     if (!strcmp(acl->action, "allow")
-        || !strcmp(acl->action, "allow-related")) {
+        || !strcmp(acl->action, "allow-related")
+        || !strcmp(acl->action, "pass-related")) {
         /* If there are any stateful flows, we must even commit "allow"
          * actions.  This is because, while the initiater's
          * direction may not have any stateful rules, the server's
@@ -8035,8 +8039,9 @@ build_acl_log_related_flows(const struct ovn_datapath *od,
         return;
     }
 
-    if (strcmp(acl->action, "allow") && strcmp(acl->action, "allow-related")) {
-        /* Not an allow ACL */
+    if (strcmp(acl->action, "allow") && strcmp(acl->action, "allow-related") &&
+        strcmp(acl->action, "pass-related")) {
+        /* Not an allow or pass-related ACL */
         return;
     }
 
diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
index e5945b831..626b3def5 100644
--- a/ovn-nb.ovsschema
+++ b/ovn-nb.ovsschema
@@ -1,7 +1,7 @@
 {
     "name": "OVN_Northbound",
-    "version": "7.18.0",
-    "cksum": "1537030958 45190",
+    "version": "7.19.0",
+    "cksum": "956969323 45254",
     "tables": {
         "NB_Global": {
             "columns": {
@@ -386,7 +386,8 @@
                                             "enum": ["set",
                                                ["allow", "allow-related",
                                                 "allow-stateless", "drop",
-                                                "reject", "pass"]]}}},
+                                                "reject", "pass",
+                                                "pass-related"]]}}},
                 "network_function_group": {
                     "type": {"key": {"type": "uuid",
                                      "refTable": "Network_Function_Group",
diff --git a/ovn-nb.xml b/ovn-nb.xml
index 33a6dc676..73f84706c 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -2888,7 +2888,7 @@ or
         For example, through this "label" we can backtrack to the ACL rule
         which is causing a "leaked" connection. Connection tracker entries are
         created only for allowed connections so the label is valid only
-        for allow and allow-related actions.
+        for allow, allow-related, and pass-related actions.
       </p>
 
       <p>
@@ -2906,8 +2906,9 @@ or
       </p>
 
       <p>
-        Return traffic from an <code>allow-related</code> flow is always
-        allowed and cannot be changed through an ACL.
+        Return traffic from an <code>allow-related</code> or
+        <code>allow-related</code> flow is always allowed and cannot be
+        changed through an ACL.
       </p>
 
       <p>
@@ -3004,6 +3005,23 @@ or
           option from the <ref table="NB_Global" /> table is used to
           determine how to proceed.
         </li>
+
+        <li>
+          <code>pass-related</code>: A stateful version of
+          <code>pass</code>.  Like <code>pass</code>, a match on this
+          action stops evaluating ACLs at the current tier and moves to
+          the next one.  Unlike <code>pass</code>, the connection is
+          committed through conntrack, and established or related return
+          traffic (e.g. inbound replies to an outbound connection) is
+          allowed to bypass subsequent ACL evaluation, the same way it
+          does for <code>allow-related</code>.  If not using ACL tiers
+          or if a <code>pass-related</code> ACL is matched at the final
+          tier, then the
+          <ref column="options" key="default_acl_drop" table="NB_Global" />
+          option from the <ref table="NB_Global" /> table is used to
+          determine how to proceed.
+        </li>
+
       </ul>
     </column>
 
diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
index bf3896361..d33a2a675 100644
--- a/tests/ovn-nbctl.at
+++ b/tests/ovn-nbctl.at
@@ -222,7 +222,8 @@ ovn_nbctl_test_acl() {
    AT_CHECK([ovn-nbctl $2 acl-add $1 from-lport 200 ip drop])
    AT_CHECK([ovn-nbctl $2 acl-add $1 to-lport 100 ip drop])
    AT_CHECK([ovn-nbctl $2 --label=1234 acl-add $1 from-lport 70 icmp 
allow-related])
-   AT_CHECK([ovn-nbctl $2 --label=1235 acl-add $1 to-lport 70 icmp 
allow-related])
+   AT_CHECK([ovn-nbctl $2 --tier=0 --label=1235 acl-add $1 to-lport 80 icmp 
pass-related])
+   AT_CHECK([ovn-nbctl $2 --tier=1 --label=1235 acl-add $1 to-lport 70 icmp 
allow-related])
    AT_CHECK([ovn-nbctl $2 --apply-after-lb acl-add $1 from-lport 500 tcp 
allow])
    AT_CHECK([ovn-nbctl $2 --apply-after-lb acl-add $1 from-lport 300 tcp drop])
    AT_CHECK([ovn-nbctl $2 --apply-after-lb acl-add $1 from-lport 300 udp 
allow])
@@ -256,7 +257,8 @@ from-lport   300 (udp) allow [[tier 0]] [[after-lb]]
   to-lport   500 (udp) drop log(name=test,severity=info) [[tier 0]]
   to-lport   300 (tcp) drop [[tier 0]]
   to-lport   100 (ip) drop [[tier 0]]
-  to-lport    70 (icmp) allow-related label=1235 [[tier 0]]
+  to-lport    80 (icmp) pass-related label=1235 [[tier 0]]
+  to-lport    70 (icmp) allow-related label=1235 [[tier 1]]
 ])
 
    dnl Delete in one direction.
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 59ac23cdc..d2140ca09 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -11574,6 +11574,90 @@ OVN_CLEANUP_NORTHD
 AT_CLEANUP
 ])
 
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([ACL "pass-related" 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" | ovn_strip_lflows | sed 
"s/\($1[[^)]]*\)/$1/" ])
+
+acl_test() {
+    direction=$1
+    options=$2
+    thing=$3
+    eval_stage=$4
+
+    ovn-sbctl lflow-list ls > lflows
+    AT_CHECK([ACL_FLOWS([$eval_stage], [priority=2000])], [0], [])
+
+    # Unlike "pass" which generates a single stateless flow, "pass-related"
+    # goes through the stateful path and generates two flows: one matching
+    # REGBIT_ACL_HINT_ALLOW_NEW and one matching REGBIT_ACL_HINT_ALLOW.
+    # Neither sets a verdict bit.
+    check ovn-nbctl --wait=sb $options acl-add $thing $direction 1000 
"ip4.addr == 80.111.111.112" pass-related
+    ovn-sbctl lflow-list ls > lflows
+    AT_CHECK_UNQUOTED([ACL_FLOWS([$eval_stage], [priority=2000])], [0], [dnl
+  table=??($eval_stage), priority=2000 , match=(reg0[[7]] == 1 && (ip4.addr == 
80.111.111.112)), action=(next;)
+  table=??($eval_stage), priority=2000 , match=(reg0[[8]] == 1 && (ip4.addr == 
80.111.111.112)), action=(next;)
+])
+
+    check ovn-nbctl --wait=sb acl-del $thing
+    ovn-sbctl lflow-list ls > lflows
+    AT_CHECK([ACL_FLOWS([$eval_stage], [priority=2000])], [0], [])
+}
+
+acl_test from-lport "" ls ls_in_acl_eval
+acl_test from-lport "--apply-after-lb" ls ls_in_acl_after_lb_eval
+acl_test to-lport "" ls ls_out_acl_eval
+acl_test from-lport "" pg ls_in_acl_eval
+acl_test from-lport "--apply-after-lb" pg ls_in_acl_after_lb_eval
+acl_test to-lport "" pg ls_out_acl_eval
+
+OVN_CLEANUP_NORTHD
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([ACL "pass-related" with label])
+AT_KEYWORDS([acl])
+
+ovn_start
+
+check ovn-nbctl                                           \
+  -- ls-add ls                                            \
+  -- lsp-add ls lsp1                                      \
+  -- lsp-set-addresses lsp1 "00:00:00:00:00:01"           \
+  -- lsp-add ls lsp2                                      \
+  -- lsp-set-addresses lsp2 "00:00:00:00:00:02"           \
+  -- lb-add lb-test 42.42.42.42:4242 43.43.43.43:4343 udp \
+  -- ls-lb-add ls lb-test
+
+check ovn-nbctl --wait=sb --label=1234 acl-add ls from-lport 1 ip pass-related
+
+ovn-sbctl dump-flows ls > lflows
+AT_CAPTURE_FILE([lflows])
+
+dnl Verify label registers are set. Unlike allow-related, no verdict bit
+dnl (reg8[[16]]) appears in the action.
+AT_CHECK([grep -w "ls_in_acl_eval" lflows | grep 1001 | ovn_strip_lflows], 
[0], [dnl
+  table=??(ls_in_acl_eval     ), priority=1001 , match=(reg0[[7]] == 1 && 
(ip)), action=(reg0[[13]] = 1; reg3 = 1234; reg9 = 1234; reg8[[0..7]] = 0; 
reg8[[8..15]] = 0; reg8[[19..20]] = 0; next;)
+  table=??(ls_in_acl_eval     ), priority=1001 , match=(reg0[[8]] == 1 && 
(ip)), action=(reg0[[1]] = 1; reg0[[13]] = 1; reg3 = 1234; reg9 = 1234; 
reg8[[0..7]] = 0; reg8[[8..15]] = 0; reg8[[19..20]] = 0; next;)
+])
+
+dnl Verify commit happens via ovn_trace.
+flow="inport == \"lsp1\" && eth.src == 00:00:00:00:00:01 && eth.dst == 
00:00:00:00:00:02 && ip4.src == 42.42.42.1 && ip4.dst == 42.42.42.42 && 
ip.ttl==64 && udp.dst == 4242"
+AT_CHECK_UNQUOTED([ovn_trace --ct new --ct new --ct new ls "$flow" | grep -e 
ls_in_stateful -A 2 | grep commit], [0], [dnl
+    ct_commit { ct_mark.blocked = 0; ct_mark.allow_established = reg0[[20]]; 
ct_mark.obs_stage = reg8[[19..20]]; ct_mark.obs_collector_id = reg8[[8..15]]; 
ct_label.obs_point_id = reg9; ct_label.acl_id = reg2[[16..31]]; ct_label.nf = 
0; ct_label.nf_id = 0; };
+])
+
+OVN_CLEANUP_NORTHD
+AT_CLEANUP
+])
+
 OVN_FOR_EACH_NORTHD_NO_HV([
 AT_SETUP([ACL - ct state clear on router ports])
 AT_KEYWORDS([acl])
diff --git a/tests/ovn.at b/tests/ovn.at
index 286395298..0455753b3 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -43672,6 +43672,129 @@ OVN_CLEANUP_NORTHD
 AT_CLEANUP
 ])
 
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([ACL "pass-related" with drop on same tier])
+AT_KEYWORDS([acl])
+
+ovn_start
+
+check ovn-nbctl                                           \
+  -- ls-add ls                                            \
+  -- lsp-add ls lsp1                                      \
+  -- lsp-set-addresses lsp1 "00:00:00:00:00:01 10.0.0.1" \
+  -- lsp-add ls lsp2                                      \
+  -- lsp-set-addresses lsp2 "00:00:00:00:00:02 10.0.0.2"
+
+dnl Tier 0: pass-related at priority 500, drop at priority 450.
+dnl Tier 1: allow-related at priority 400.
+check ovn-nbctl --wait=sb \
+  --tier=0 acl-add ls from-lport 500 "ip4.src == 10.0.0.1" pass-related \
+  -- --tier=0 acl-add ls from-lport 450 "ip4" drop \
+  -- --tier=1 acl-add ls from-lport 400 "ip4" allow-related
+
+flow="inport == \"lsp1\" && eth.src == 00:00:00:00:00:01 && eth.dst == 
00:00:00:00:00:02 && ip4.src == 10.0.0.1 && ip4.dst == 10.0.0.2 && ip.ttl == 64 
&& udp.dst == 80"
+
+dnl Verify pass-related generates stateful-path flows with hint register
+dnl matches, unlike pass which uses the stateless path.
+ovn-sbctl lflow-list ls > lflows
+AT_CAPTURE_FILE([lflows])
+AT_CHECK([grep -w "ls_in_acl_eval" lflows | grep "10.0.0.1" | grep -qF "reg0"])
+
+dnl New connection: pass-related on tier 0 sets no verdict, falls through to
+dnl tier 1 where allow-related sets VERDICT_ALLOW. The ingress stateful stage
+dnl commits the connection.
+AT_CHECK_UNQUOTED([ovn_trace --ct new --ct new ls "$flow" | grep -e 
ls_in_stateful -A 2 | grep commit], [0], [dnl
+    ct_commit { ct_mark.blocked = 0; ct_mark.allow_established = reg0[[20]]; 
ct_label.acl_id = reg2[[16..31]]; ct_label.nf = 0; ct_label.nf_id = 0; };
+])
+
+dnl Established reply: hits the priority-65532 fast-path, bypasses the
+dnl drop ACL entirely. Verify output is reached.
+AT_CHECK([ovn_trace --ct est,rpl --ct est,rpl --minimal ls "$flow" | grep -q 
'output("lsp2")'])
+
+OVN_CLEANUP_NORTHD
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([ACL "pass-related" multi-tier])
+AT_KEYWORDS([acl])
+
+ovn_start
+
+check ovn-nbctl                                           \
+  -- ls-add ls                                            \
+  -- lsp-add ls lsp1                                      \
+  -- lsp-set-addresses lsp1 "00:00:00:00:00:01 10.0.0.1" \
+  -- lsp-add ls lsp2                                      \
+  -- lsp-set-addresses lsp2 "00:00:00:00:00:02 10.0.0.2"
+
+dnl Tier 0: pass-related. Tier 1: allow-related.
+check ovn-nbctl --wait=sb --tier=0 acl-add ls from-lport 500 "ip4.src == 
10.0.0.1" pass-related
+check ovn-nbctl --wait=sb --tier=1 acl-add ls from-lport 400 "ip4" 
allow-related
+
+flow="inport == \"lsp1\" && eth.src == 00:00:00:00:00:01 && eth.dst == 
00:00:00:00:00:02 && ip4.src == 10.0.0.1 && ip4.dst == 10.0.0.2 && ip.ttl == 64 
&& udp.dst == 80"
+
+dnl Verify tier increment flow exists in the action stage.
+ovn-sbctl lflow-list ls > lflows
+AT_CAPTURE_FILE([lflows])
+AT_CHECK([grep -w "ls_in_acl_action" lflows | grep "priority=500" | 
ovn_strip_lflows | grep -q "next(pipeline=ingress,table=??)"])
+dnl New connection passes tier 0 (pass-related) and tier 1 (allow-related),
+dnl gets committed in the ingress stateful stage.
+AT_CHECK_UNQUOTED([ovn_trace --ct new --ct new ls "$flow" | grep -e 
ls_in_stateful -A 2 | grep commit], [0], [dnl
+    ct_commit { ct_mark.blocked = 0; ct_mark.allow_established = reg0[[20]]; 
ct_label.acl_id = reg2[[16..31]]; ct_label.nf = 0; ct_label.nf_id = 0; };
+])
+
+dnl Established reply uses the fast-path.
+AT_CHECK([ovn_trace --ct est,rpl --ct est,rpl --minimal ls "$flow" | grep -q 
'output("lsp2")'])
+
+dnl Remove the tier 1 ACL. With only tier 0 pass-related and no explicit
+dnl tier 1 ACL, the default action (allow) applies after the pass.
+check ovn-nbctl --wait=sb acl-del ls
+check ovn-nbctl --wait=sb \
+  --tier=0 acl-add ls from-lport 500 "ip4.src == 10.0.0.1" pass-related
+
+dnl pass-related alone makes the switch stateful, so the connection
+dnl gets committed even without an explicit tier 1 ACL.
+AT_CHECK_UNQUOTED([ovn_trace --ct new --ct new ls "$flow" | grep -e 
ls_in_stateful -A 2 | grep commit], [0], [dnl
+    ct_commit { ct_mark.blocked = 0; ct_mark.allow_established = reg0[[20]]; 
ct_label.acl_id = reg2[[16..31]]; ct_label.nf = 0; ct_label.nf_id = 0; };
+])
+
+OVN_CLEANUP_NORTHD
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([ACL "pass-related" ICMP related traffic])
+AT_KEYWORDS([acl])
+
+ovn_start
+
+check ovn-nbctl                                           \
+  -- ls-add ls                                            \
+  -- lsp-add ls lsp1                                      \
+  -- lsp-set-addresses lsp1 "00:00:00:00:00:01 10.0.0.1" \
+  -- lsp-add ls lsp2                                      \
+  -- lsp-set-addresses lsp2 "00:00:00:00:00:02 10.0.0.2"
+
+dnl Tier 0: pass-related at priority 500, drop at priority 450.
+dnl Tier 1: allow-related at priority 400.
+check ovn-nbctl --wait=sb \
+  --tier=0 acl-add ls from-lport 500 "ip4.src == 10.0.0.1" pass-related \
+  -- --tier=0 acl-add ls from-lport 450 "ip4" drop \
+  -- --tier=1 acl-add ls from-lport 400 "ip4" allow-related
+
+flow="inport == \"lsp1\" && eth.src == 00:00:00:00:00:01 && eth.dst == 
00:00:00:00:00:02 && ip4.src == 10.0.0.1 && ip4.dst == 10.0.0.2 && ip.ttl == 64 
&& udp.dst == 80"
+
+dnl Related traffic (e.g., ICMP destination-unreachable) for a committed
+dnl connection hits the ct.rel fast-path at priority 65532 and is allowed
+dnl through with ct_commit_nat, bypassing the drop ACL on tier 0.
+AT_CHECK([ovn_trace --ct rel --ct rel ls "$flow" | grep -q "ct_commit_nat"])
+AT_CHECK([ovn_trace --ct rel --ct rel --minimal ls "$flow" | grep -q 
'output("lsp2")'])
+
+OVN_CLEANUP_NORTHD
+AT_CLEANUP
+])
+
 OVN_FOR_EACH_NORTHD_NO_HV([
 AT_SETUP([ovn-controller -- local datapaths check])
 ovn_start
diff --git a/tests/system-ovn.at b/tests/system-ovn.at
index 17b4dcb3d..8c7685b9b 100644
--- a/tests/system-ovn.at
+++ b/tests/system-ovn.at
@@ -21859,6 +21859,93 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port 
patch-.*/d
 AT_CLEANUP
 ])
 
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([ACL pass-related conntrack])
+AT_KEYWORDS([acl])
+
+CHECK_CONNTRACK()
+ovn_start
+
+OVS_TRAFFIC_VSWITCHD_START()
+ADD_BR([br-int])
+
+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_daemon ovn-controller
+
+check ovn-nbctl ls-add sw0
+
+check ovn-nbctl lsp-add sw0 sw0-p1
+check ovn-nbctl lsp-set-addresses sw0-p1 "50:54:00:00:00:03 10.0.0.3"
+check ovn-nbctl lsp-set-port-security sw0-p1 "50:54:00:00:00:03 10.0.0.3"
+
+check ovn-nbctl lsp-add sw0 sw0-p2
+check ovn-nbctl lsp-set-addresses sw0-p2 "50:54:00:00:00:04 10.0.0.4"
+check ovn-nbctl lsp-set-port-security sw0-p2 "50:54:00:00:00:04 10.0.0.4"
+
+ADD_NAMESPACES(sw0-p1)
+ADD_VETH(sw0-p1, sw0-p1, br-int, "10.0.0.3/24", "50:54:00:00:00:03", \
+         "10.0.0.1")
+
+ADD_NAMESPACES(sw0-p2)
+ADD_VETH(sw0-p2, sw0-p2, br-int, "10.0.0.4/24", "50:54:00:00:00:04", \
+         "10.0.0.1")
+
+wait_for_ports_up sw0-p1 sw0-p2
+check ovn-nbctl --wait=hv sync
+
+dnl Phase 1: pass-related alone creates conntrack entries.
+dnl Unlike plain "pass", pass-related makes the switch stateful and commits
+dnl connections to conntrack even without an explicit allow-related ACL.
+check ovn-nbctl --wait=sb acl-add sw0 from-lport 1000 "ip4.src == 10.0.0.3" 
pass-related
+check ovn-nbctl --wait=hv sync
+
+NS_CHECK_EXEC([sw0-p1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.4 | FORMAT_PING], \
+[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.0.0.4) | \
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/' | grep icmp], [0], [ignore])
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.0.0.3) | \
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/' | grep icmp], [0], [ignore])
+
+dnl Phase 2: Multi-tier with drop bypass.
+dnl pass-related at higher priority skips the drop, falls through to tier 1
+dnl where allow-related commits the connection. Reply traffic uses
+dnl the established fast-path, bypassing the tier 0 drop ACL.
+check ovn-nbctl acl-del sw0
+AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+check ovn-nbctl --wait=hv sync
+
+check ovn-nbctl --tier=0 acl-add sw0 from-lport 1000 "ip4.src == 10.0.0.3" 
pass-related
+check ovn-nbctl --tier=0 acl-add sw0 from-lport 500 "ip4" drop
+check ovn-nbctl --tier=1 acl-add sw0 from-lport 1000 "ip4" allow-related
+check ovn-nbctl --wait=hv sync
+
+NS_CHECK_EXEC([sw0-p1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.4 | FORMAT_PING], \
+[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.0.0.4) | \
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/' | grep icmp], [0], [ignore])
+
+OVN_CLEANUP_CONTROLLER([hv1])
+OVN_CLEANUP_NORTHD
+
+as
+OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
+/connection dropped.*/d"])
+
+AT_CLEANUP
+])
+
 OVN_FOR_EACH_NORTHD([
 AT_SETUP([DHCPv6 - Options heap overread])
 CHECK_SCAPY
diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
index 4e2271d07..b3a0290c9 100644
--- a/utilities/ovn-nbctl.c
+++ b/utilities/ovn-nbctl.c
@@ -2951,10 +2951,11 @@ nbctl_acl_add(struct ctl_context *ctx)
     /* Validate action. */
     if (strcmp(action, "allow") && strcmp(action, "allow-related")
         && strcmp(action, "allow-stateless") && strcmp(action, "drop")
-        && strcmp(action, "reject") && strcmp(action, "pass")) {
+        && strcmp(action, "reject") && strcmp(action, "pass")
+        && strcmp(action, "pass-related")) {
         ctl_error(ctx, "%s: action must be one of \"allow\", "
                   "\"allow-related\", \"allow-stateless\", \"drop\", "
-                  "and \"reject\"", action);
+                  "\"pass\", \"pass-related\", and \"reject\"", action);
         return;
     }
 
@@ -3027,12 +3028,14 @@ nbctl_acl_add(struct ctl_context *ctx)
     /* Set the ACL label */
     const char *label = shash_find_data(&ctx->options, "--label");
     if (label) {
-      /* Ensure that the action is either allow or allow-related */
-      if (strcmp(action, "allow") && strcmp(action, "allow-related")) {
-        ctl_error(ctx, "label can only be set with actions \"allow\" or "
-                  "\"allow-related\"");
-        return;
-      }
+        /* Ensure that the action is either allow, allow-related or
+         * pass-related */
+        if (strcmp(action, "allow") && strcmp(action, "allow-related") &&
+            strcmp(action, "pass-related")) {
+            ctl_error(ctx, "label can only be set with actions \"allow\", "
+                  "\"allow-related\" or \"pass-related\"");
+            return;
+        }
 
       int64_t label_value = 0;
       error = parse_acl_label(label, &label_value);
-- 
2.54.0

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to