diff --git a/northd/northd.c b/northd/northd.c
index b01e40ecda..8fc49a63a2 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -6065,6 +6065,27 @@ skip_port_from_conntrack(const struct ovn_datapath *od, struct ovn_port *op,
free(egress_match);
}
+static void
+build_stateless_for_lb_filter(const struct ovn_datapath *od,
+ const struct nbrec_acl *acl,
+ struct lflow_table *lflows,
+ struct lflow_ref *lflow_ref)
+{
+ const char *action = REGBIT_ACL_VERDICT_ALLOW" = 1;"
+ REGBIT_CONNTRACK_COMMIT" = 1; next;";
+ if (!strcmp(acl->direction, "from-lport")) {
+ ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_PRE_ACL, 65532,
+ acl->match, "next;", &acl->header_, lflow_ref);
+ ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_ACL_EVAL,
+ 65532, acl->match, action, &acl->header_, lflow_ref);
+ } else {
+ ovn_lflow_add_with_hint(lflows, od, S_SWITCH_OUT_PRE_ACL, 65532,
+ acl->match, "next;", &acl->header_, lflow_ref);
+ ovn_lflow_add_with_hint(lflows, od, S_SWITCH_OUT_ACL_EVAL,
+ 65532, acl->match, action, &acl->header_, lflow_ref);
+ }
+}
+
static void
build_stateless_filter(const struct ovn_datapath *od,
const struct nbrec_acl *acl,
@@ -6095,10 +6116,17 @@ build_stateless_filters(const struct ovn_datapath *od,
struct lflow_table *lflows,
struct lflow_ref *lflow_ref)
{
+ bool skip_stateless = smap_get_bool(&od->nbs->other_config,
+ "skip_stateless", false);
+
for (size_t i = 0; i < od->nbs->n_acls; i++) {
const struct nbrec_acl *acl = od->nbs->acls[i];
if (!strcmp(acl->action, "allow-stateless")) {
- build_stateless_filter(od, acl, lflows, lflow_ref);
+ if (skip_stateless) {
+ build_stateless_for_lb_filter(od, acl, lflows, lflow_ref);
+ } else {
+ build_stateless_filter(od, acl, lflows, lflow_ref);
+ }
}
}
@@ -6114,7 +6142,11 @@ build_stateless_filters(const struct ovn_datapath *od,
const struct nbrec_acl *acl = ls_pg_rec->nb_pg->acls[i];
if (!strcmp(acl->action, "allow-stateless")) {
- build_stateless_filter(od, acl, lflows, lflow_ref);
+ if (skip_stateless) {
+ build_stateless_for_lb_filter(od, acl, lflows, lflow_ref);
+ } else {
+ build_stateless_filter(od, acl, lflows, lflow_ref);
+ }
}
}
}
diff --git a/ovn-nb.xml b/ovn-nb.xml
index 4b86f432db..b093219c26 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -795,6 +795,13 @@
to 0, which means disabled.
+
+ For the correct operation of the load balancer, when using stateless
+ ACLs and for the load balancer on the logical switch that matches these
+ rules, enable this option. Without this option, packets governed
+ by stateless ACLs will be processed as stateless and LB will not work.
+
+
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 21d9d63ab8..38524b06ae 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -14384,3 +14384,74 @@ AT_CHECK([ovn-sbctl lflow-list S1 | grep ls_out_acl_action | grep priority=500 |
AT_CLEANUP
])
+
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([ACL with skip_stateless option])
+ovn_start
+
+check ovn-nbctl ls-add ls1
+
+check ovn-nbctl --wait=sb acl-add ls1 to-lport 100 1 allow-stateless
+check ovn-nbctl --wait=sb acl-add ls1 from-lport 150 1 allow-stateless
+ovn-sbctl dump-flows ls1 > sw0flows
+
+AT_CHECK([grep -e "ls_in_pre_acl" -e "ls_out_pre_acl" sw0flows | ovn_strip_lflows], [0], [dnl
+ table=??(ls_in_pre_acl ), priority=0 , match=(1), action=(next;)
+ table=??(ls_in_pre_acl ), priority=110 , match=(eth.dst == $svc_monitor_mac), action=(next;)
+ table=??(ls_out_pre_acl ), priority=0 , match=(1), action=(next;)
+ table=??(ls_out_pre_acl ), priority=110 , match=(eth.src == $svc_monitor_mac), action=(next;)
+])
+
+check ovn-nbctl lb-add lb1 10.0.0.10:80 10.0.0.3:80
+check ovn-nbctl --wait=sb ls-lb-add ls1 lb1
+
+ovn-sbctl dump-flows ls1 > ls1flows
+AT_CHECK([grep -e "ls_in_pre_acl" -e "ls_out_pre_acl" ls1flows | ovn_strip_lflows], [0], [dnl
+ table=??(ls_in_pre_acl ), priority=0 , match=(1), action=(next;)
+ table=??(ls_in_pre_acl ), priority=110 , match=(eth.dst == $svc_monitor_mac), action=(next;)
+ table=??(ls_in_pre_acl ), priority=1150 , match=(1), action=(reg0[[16]] = 1; next;)
+ table=??(ls_out_pre_acl ), priority=0 , match=(1), action=(next;)
+ table=??(ls_out_pre_acl ), priority=110 , match=(eth.src == $svc_monitor_mac), action=(next;)
+ table=??(ls_out_pre_acl ), priority=1100 , match=(1), action=(reg0[[16]] = 1; next;)
+])
+
+AT_CHECK([grep "ls_out_acl_eval" ls1flows | ovn_strip_lflows], [0], [dnl
+ table=??(ls_out_acl_eval ), priority=0 , match=(1), action=(next;)
+ table=??(ls_out_acl_eval ), priority=1 , match=(ip && !ct.est), action=(reg0[[1]] = 1; next;)
+ table=??(ls_out_acl_eval ), priority=1 , match=(ip && ct.est && ct_mark.blocked == 1), action=(reg0[[1]] = 1; reg8[[16]] = 1; next;)
+ table=??(ls_out_acl_eval ), priority=1100 , match=((1)), 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=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_mark.blocked == 0), action=(reg8[[16]] = 1; ct_commit_nat;)
+ table=??(ls_out_acl_eval ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_mark.blocked == 0), action=(reg8[[16]] = 1; next;)
+ table=??(ls_out_acl_eval ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_mark.blocked == 1)), action=(reg8[[17]] = 1; next;)
+ table=??(ls_out_acl_eval ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(reg8[[16]] = 1; next;)
+])
+
+ovn-nbctl set logical-switch ls1 other_config:skip_stateless=true
+
+ovn-sbctl dump-flows ls1 > ls1flows
+
+AT_CHECK([grep -e "ls_in_pre_acl" -e "ls_out_pre_acl" ls1flows | ovn_strip_lflows], [0], [dnl
+ table=??(ls_in_pre_acl ), priority=0 , match=(1), action=(next;)
+ table=??(ls_in_pre_acl ), priority=110 , match=(eth.dst == $svc_monitor_mac), action=(next;)
+ table=??(ls_in_pre_acl ), priority=65532, match=(1), action=(next;)
+ table=??(ls_out_pre_acl ), priority=0 , match=(1), action=(next;)
+ table=??(ls_out_pre_acl ), priority=110 , match=(eth.src == $svc_monitor_mac), action=(next;)
+ table=??(ls_out_pre_acl ), priority=65532, match=(1), action=(next;)
+])
+
+AT_CHECK([grep "ls_out_acl_eval" ls1flows | ovn_strip_lflows], [0], [dnl
+ table=??(ls_out_acl_eval ), priority=0 , match=(1), action=(next;)
+ table=??(ls_out_acl_eval ), priority=1 , match=(ip && !ct.est), action=(reg0[[1]] = 1; next;)
+ table=??(ls_out_acl_eval ), priority=1 , match=(ip && ct.est && ct_mark.blocked == 1), action=(reg0[[1]] = 1; reg8[[16]] = 1; next;)
+ table=??(ls_out_acl_eval ), priority=1100 , match=((1)), 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=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_mark.blocked == 0), action=(reg8[[16]] = 1; ct_commit_nat;)
+ table=??(ls_out_acl_eval ), priority=65532, match=(1), action=(reg8[[16]] = 1;reg0[[1]] = 1; next;)
+ table=??(ls_out_acl_eval ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_mark.blocked == 0), action=(reg8[[16]] = 1; next;)
+ table=??(ls_out_acl_eval ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_mark.blocked == 1)), action=(reg8[[17]] = 1; next;)
+ table=??(ls_out_acl_eval ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(reg8[[16]] = 1; next;)
+])
+
+AT_CLEANUP
+])
diff --git a/tests/system-ovn.at b/tests/system-ovn.at
index 19ec1eb8df..ac0d287648 100644
--- a/tests/system-ovn.at
+++ b/tests/system-ovn.at
@@ -9839,6 +9839,42 @@ sed -e 's/zone=[[0-9]]*/zone=/'], [0], [dnl
AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+#add skip_stateless options on ls
+check ovn-nbctl set logical_switch foo other_config skip_stateless=true
+check ovn-nbctl set logical_switch bar other_config skip_stateless=true
+
+# Wait for ovn-controller to catch up.
+ovn-nbctl --wait=hv sync
+
+OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-groups br-int | \
+grep 'nat(dst=192.168.2.2:80)'])
+zone_id=$(ovn-appctl -t ovn-controller ct-zone-list | grep foo1 | cut -d ' ' -f2)
+
+OVS_START_L7([bar1], [http])
+
+AT_CHECK([ip netns exec foo1 wget 192.168.2.2 -t 3 -T 1], [0], [ignore], [ignore])
+
+# check conntrack zone has tcp entry
+AT_CHECK([ovs-appctl dpctl/dump-conntrack zone=$zone_id | \
+FORMAT_CT(192.168.1.2) | \
+sed -e 's/zone=[[0-9]]*/zone=/'], [0], [dnl
+tcp,orig=(src=192.168.1.2,dst=192.168.2.2,sport=,dport=),reply=(src=192.168.2.2,dst=192.168.1.2,sport=,dport=),zone=,protoinfo=(state=)
+])
+
+AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+
+# now check with VIP
+AT_CHECK([ip netns exec foo1 wget 30.30.30.30 -t 3 -T 1], [0], [ignore], [ignore])
+
+# check conntrack zone has tcp entry
+AT_CHECK([ovs-appctl dpctl/dump-conntrack zone=$zone_id | \
+FORMAT_CT(30.30.30.30) | \
+sed -e 's/zone=[[0-9]]*/zone=/'], [0], [dnl
+tcp,orig=(src=192.168.1.2,dst=30.30.30.30,sport=,dport=),reply=(src=192.168.2.2,dst=192.168.1.2,sport=,dport=),zone=,mark=2,protoinfo=(state=)
+])
+
+AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+
OVS_APP_EXIT_AND_WAIT([ovn-controller])
as ovn-sb
@@ -9983,6 +10019,42 @@ sed -e 's/zone=[[0-9]]*/zone=/'], [0], [dnl
AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+check ovn-nbctl set logical_switch foo other_config skip_stateless=true
+check ovn-nbctl set logical_switch bar other_config skip_stateless=true
+
+# Wait for ovn-controller to catch up.
+ovn-nbctl --wait=hv sync
+
+OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-groups br-int | \
+grep 'nat(dst=\[[fd12::2\]]:80)'])
+
+zone_id=$(ovn-appctl -t ovn-controller ct-zone-list | grep foo1 | cut -d ' ' -f2)
+
+OVS_START_L7([bar1], [http6])
+AT_CHECK([ip netns exec foo1 wget http://[[fd12::2]] -t 3 -T 1], [0], [ignore], [ignore])
+
+# check conntrack zone has tcp entry
+AT_CHECK([ovs-appctl dpctl/dump-conntrack zone=$zone_id | \
+FORMAT_CT(fd12::2) | grep -v fe80 | \
+sed -e 's/zone=[[0-9]]*/zone=/'], [0], [dnl
+tcp,orig=(src=fd11::2,dst=fd12::2,sport=,dport=),reply=(src=fd12::2,dst=fd11::2,sport=,dport=),zone=,protoinfo=(state=)
+])
+
+AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+
+# now check with VIP
+AT_CHECK([ip netns exec foo1 wget http://[[fd30::2]] -t 3 -T 1], [0], [ignore], [ignore])
+
+# check conntrack zone has tcp entry
+AT_CHECK([ovs-appctl dpctl/dump-conntrack zone=$zone_id | \
+FORMAT_CT(fd30::2) | grep -v fe80 | \
+sed -e 's/zone=[[0-9]]*/zone=/'], [0], [dnl
+tcp,orig=(src=fd11::2,dst=fd30::2,sport=,dport=),reply=(src=fd12::2,dst=fd11::2,sport=,dport=),zone=,mark=2,protoinfo=(state=)
+])
+
+AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+
+#
OVS_APP_EXIT_AND_WAIT([ovn-controller])
as ovn-sb