Each row in this table represents one ACL rule for a logical switch
diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
index 619051d..b55ee03 100644
--- a/tests/ovn-nbctl.at
+++ b/tests/ovn-nbctl.at
@@ -270,6 +270,59 @@ AT_CHECK([ovn-nbctl --type=port-group acl-add ls0 to-lport 100 ip drop], [0], [i
dnl ---------------------------------------------------------------------
+OVN_NBCTL_TEST([ovn_nbctl_stateless_filters], [Stateless_Filters], [
+ovn_nbctl_test_stateless_filters() {
+ AT_CHECK([ovn-nbctl $2 stateless-filter-add $1 300 udp])
+ AT_CHECK([ovn-nbctl $2 stateless-filter-add $1 200 tcp])
+ AT_CHECK([ovn-nbctl $2 stateless-filter-add $1 100 ip])
+ dnl Add duplicated Stateless_Filter
+ AT_CHECK([ovn-nbctl $2 stateless-filter-add $1 100 ip], [1], [], [stderr])
+ AT_CHECK([grep 'already existed' stderr], [0], [ignore])
+ AT_CHECK([ovn-nbctl $2 --may-exist stateless-filter-add $1 100 ip])
+
+ AT_CHECK([ovn-nbctl $2 stateless-filter-list $1], [0], [dnl
+ 300 (udp)
+ 200 (tcp)
+ 100 (ip)
+])
+
+ dnl Delete all Stateless_Filters.
+ AT_CHECK([ovn-nbctl $2 stateless-filter-del $1])
+ AT_CHECK([ovn-nbctl $2 stateless-filter-list $1], [0], [dnl
+])
+
+ AT_CHECK([ovn-nbctl $2 stateless-filter-add $1 300 udp])
+ AT_CHECK([ovn-nbctl $2 stateless-filter-add $1 200 tcp])
+ AT_CHECK([ovn-nbctl $2 stateless-filter-add $1 100 ip])
+
+ dnl Delete a single filter.
+ AT_CHECK([ovn-nbctl $2 stateless-filter-del $1 200 tcp])
+ AT_CHECK([ovn-nbctl $2 stateless-filter-list $1], [0], [dnl
+ 300 (udp)
+ 100 (ip)
+])
+}
+
+AT_CHECK([ovn-nbctl ls-add ls0])
+ovn_nbctl_test_stateless_filters ls0
+AT_CHECK([ovn-nbctl ls-add ls1])
+ovn_nbctl_test_stateless_filters ls1 --type=switch
+AT_CHECK([ovn-nbctl create port_group name=pg0], [0], [ignore])
+ovn_nbctl_test_stateless_filters pg0 --type=port-group
+
+dnl Test when port group doesn't exist
+AT_CHECK([ovn-nbctl --type=port-group stateless-filter-add pg1 100 ip], [1], [], [dnl
+ovn-nbctl: pg1: port group name not found
+])
+
+dnl Test when same name exists in logical switches and portgroups
+AT_CHECK([ovn-nbctl create port_group name=ls0], [0], [ignore])
+AT_CHECK([ovn-nbctl stateless-filter-add ls0 100 ip], [1], [], [stderr])
+AT_CHECK([grep 'exists in both' stderr], [0], [ignore])
+AT_CHECK([ovn-nbctl --type=port-group stateless-filter-add ls0 100 ip], [0], [ignore])])
+
+dnl ---------------------------------------------------------------------
+
OVN_NBCTL_TEST([ovn_nbctl_qos], [QoS], [
AT_CHECK([ovn-nbctl ls-add ls0])
AT_CHECK([ovn-nbctl qos-add ls0 from-lport 600 tcp dscp=63])
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 87644bd..2fc7dc3 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -1781,3 +1781,266 @@ AT_CHECK([ovn-sbctl lflow-list | grep "ls_out_pre_lb.*priority=100" | grep reg0
])
AT_CLEANUP
+
+AT_SETUP([ovn -- ACL Stateful Bypass - Logical_Switch])
+ovn_start
+
+ovn-nbctl ls-add ls
+ovn-nbctl lsp-add ls lsp1
+ovn-nbctl lsp-set-addresses lsp1 00:00:00:00:00:01
+ovn-nbctl lsp-add ls lsp2
+ovn-nbctl lsp-set-addresses lsp2 00:00:00:00:00:02
+
+ovn-nbctl acl-add ls from-lport 3 "tcp" allow
+ovn-nbctl acl-add ls from-lport 2 "udp" allow-related
+ovn-nbctl acl-add ls from-lport 1 "ip" drop
+ovn-nbctl --wait=sb sync
+
+flow_eth='eth.src == 00:00:00:00:00:01 && eth.dst == 00:00:00:00:00:02'
+flow_ip='ip.ttl==64 && ip4.src == 42.42.42.1 && ip4.dst == 66.66.66.66'
+flow_tcp='tcp && tcp.dst == 80'
+flow_udp='udp && udp.dst == 80'
+
+# TCP packets should go to conntrack.
+flow="inport == \"lsp1\" && ${flow_eth} && ${flow_ip} && ${flow_tcp}"
+AT_CHECK([ovn-trace --ct new --ct new --minimal ls "${flow}"], [0], [dnl
+# tcp,reg14=0x1,vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02,nw_src=42.42.42.1,nw_dst=66.66.66.66,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=0,tp_dst=80,tcp_flags=0
+ct_next(ct_state=new|trk) {
+ ct_next(ct_state=new|trk) {
+ output("lsp2");
+ };
+};
+])
+
+# UDP packets should go to conntrack.
+flow="inport == \"lsp1\" && ${flow_eth} && ${flow_ip} && ${flow_udp}"
+AT_CHECK([ovn-trace --ct new --ct new --minimal ls "${flow}"], [0], [dnl
+# udp,reg14=0x1,vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02,nw_src=42.42.42.1,nw_dst=66.66.66.66,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=0,tp_dst=80
+ct_next(ct_state=new|trk) {
+ ct_next(ct_state=new|trk) {
+ output("lsp2");
+ };
+};
+])
+
+# Enable Stateful Bypass for TCP.
+ovn-nbctl stateless-filter-add ls 1 tcp
+ovn-nbctl --wait=sb sync
+
+# TCP packets should not go to conntrack anymore.
+flow="inport == \"lsp1\" && ${flow_eth} && ${flow_ip} && ${flow_tcp}"
+AT_CHECK([ovn-trace --minimal ls "${flow}"], [0], [dnl
+# tcp,reg14=0x1,vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02,nw_src=42.42.42.1,nw_dst=66.66.66.66,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=0,tp_dst=80,tcp_flags=0
+output("lsp2");
+])
+
+# UDP packets still go to conntrack.
+flow="inport == \"lsp1\" && ${flow_eth} && ${flow_ip} && ${flow_udp}"
+AT_CHECK([ovn-trace --ct new --ct new --minimal ls "${flow}"], [0], [dnl
+# udp,reg14=0x1,vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02,nw_src=42.42.42.1,nw_dst=66.66.66.66,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=0,tp_dst=80
+ct_next(ct_state=new|trk) {
+ ct_next(ct_state=new|trk) {
+ output("lsp2");
+ };
+};
+])
+
+# Add a load balancer.
+ovn-nbctl lb-add lb-tcp 66.66.66.66:80 42.42.42.2:8080 tcp
+ovn-nbctl lb-add lb-udp 66.66.66.66:80 42.42.42.2:8080 udp
+ovn-nbctl ls-lb-add ls lb-tcp
+ovn-nbctl ls-lb-add ls lb-udp
+
+# Disable Stateful Bypass for TCP.
+ovn-nbctl stateless-filter-del ls
+ovn-nbctl --wait=sb sync
+
+# TCP packets should go to conntrack.
+flow="inport == \"lsp1\" && ${flow_eth} && ${flow_ip} && ${flow_tcp}"
+AT_CHECK([ovn-trace --ct new --ct new --minimal ls "${flow}"], [0], [dnl
+# tcp,reg14=0x1,vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02,nw_src=42.42.42.1,nw_dst=66.66.66.66,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=0,tp_dst=80,tcp_flags=0
+ct_next(ct_state=new|trk) {
+ ct_lb {
+ ct_next(ct_state=new|trk) {
+ output("lsp2");
+ };
+ };
+};
+])
+
+# UDP packets should go to conntrack.
+flow="inport == \"lsp1\" && ${flow_eth} && ${flow_ip} && ${flow_udp}"
+AT_CHECK([ovn-trace --ct new --ct new --minimal ls "${flow}"], [0], [dnl
+# udp,reg14=0x1,vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02,nw_src=42.42.42.1,nw_dst=66.66.66.66,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=0,tp_dst=80
+ct_next(ct_state=new|trk) {
+ ct_lb {
+ ct_next(ct_state=new|trk) {
+ output("lsp2");
+ };
+ };
+};
+])
+
+# Enable Stateful Bypass for TCP.
+ovn-nbctl stateless-filter-add ls 1 tcp
+ovn-nbctl --wait=sb sync
+
+# TCP packets should go to conntrack for load balancing.
+flow="inport == \"lsp1\" && ${flow_eth} && ${flow_ip} && ${flow_tcp}"
+AT_CHECK([ovn-trace --ct new --ct new --minimal ls "${flow}"], [0], [dnl
+# tcp,reg14=0x1,vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02,nw_src=42.42.42.1,nw_dst=66.66.66.66,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=0,tp_dst=80,tcp_flags=0
+ct_next(ct_state=new|trk) {
+ ct_lb {
+ ct_next(ct_state=new|trk) {
+ output("lsp2");
+ };
+ };
+};
+])
+
+# UDP packets still go to conntrack.
+flow="inport == \"lsp1\" && ${flow_eth} && ${flow_ip} && ${flow_udp}"
+AT_CHECK([ovn-trace --ct new --ct new --minimal ls "${flow}"], [0], [dnl
+# udp,reg14=0x1,vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02,nw_src=42.42.42.1,nw_dst=66.66.66.66,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=0,tp_dst=80
+ct_next(ct_state=new|trk) {
+ ct_lb {
+ ct_next(ct_state=new|trk) {
+ output("lsp2");
+ };
+ };
+};
+])
+
+AT_CLEANUP
+
+AT_SETUP([ovn -- ACL Stateful Bypass - Port_Group])
+ovn_start
+
+ovn-nbctl ls-add ls
+ovn-nbctl lsp-add ls lsp1
+ovn-nbctl lsp-set-addresses lsp1 00:00:00:00:00:01
+ovn-nbctl lsp-add ls lsp2
+ovn-nbctl lsp-set-addresses lsp2 00:00:00:00:00:02
+
+ovn-nbctl pg-add pg lsp1 lsp2
+ovn-nbctl acl-add pg from-lport 3 "tcp" allow
+ovn-nbctl acl-add pg from-lport 2 "udp" allow-related
+ovn-nbctl acl-add pg from-lport 1 "ip" drop
+ovn-nbctl --wait=sb sync
+
+flow_eth='eth.src == 00:00:00:00:00:01 && eth.dst == 00:00:00:00:00:02'
+flow_ip='ip.ttl==64 && ip4.src == 42.42.42.1 && ip4.dst == 66.66.66.66'
+flow_tcp='tcp && tcp.dst == 80'
+flow_udp='udp && udp.dst == 80'
+
+# TCP packets should go to conntrack.
+flow="inport == \"lsp1\" && ${flow_eth} && ${flow_ip} && ${flow_tcp}"
+AT_CHECK([ovn-trace --ct new --ct new --minimal ls "${flow}"], [0], [dnl
+# tcp,reg14=0x1,vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02,nw_src=42.42.42.1,nw_dst=66.66.66.66,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=0,tp_dst=80,tcp_flags=0
+ct_next(ct_state=new|trk) {
+ ct_next(ct_state=new|trk) {
+ output("lsp2");
+ };
+};
+])
+
+# UDP packets should go to conntrack.
+flow="inport == \"lsp1\" && ${flow_eth} && ${flow_ip} && ${flow_udp}"
+AT_CHECK([ovn-trace --ct new --ct new --minimal ls "${flow}"], [0], [dnl
+# udp,reg14=0x1,vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02,nw_src=42.42.42.1,nw_dst=66.66.66.66,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=0,tp_dst=80
+ct_next(ct_state=new|trk) {
+ ct_next(ct_state=new|trk) {
+ output("lsp2");
+ };
+};
+])
+
+# Enable Stateful Bypass for TCP.
+ovn-nbctl stateless-filter-add pg 1 tcp
+ovn-nbctl --wait=sb sync
+
+# TCP packets should not go to conntrack anymore.
+flow="inport == \"lsp1\" && ${flow_eth} && ${flow_ip} && ${flow_tcp}"
+AT_CHECK([ovn-trace --minimal ls "${flow}"], [0], [dnl
+# tcp,reg14=0x1,vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02,nw_src=42.42.42.1,nw_dst=66.66.66.66,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=0,tp_dst=80,tcp_flags=0
+output("lsp2");
+])
+
+# UDP packets still go to conntrack.
+flow="inport == \"lsp1\" && ${flow_eth} && ${flow_ip} && ${flow_udp}"
+AT_CHECK([ovn-trace --ct new --ct new --minimal ls "${flow}"], [0], [dnl
+# udp,reg14=0x1,vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02,nw_src=42.42.42.1,nw_dst=66.66.66.66,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=0,tp_dst=80
+ct_next(ct_state=new|trk) {
+ ct_next(ct_state=new|trk) {
+ output("lsp2");
+ };
+};
+])
+
+# Add a load balancer.
+ovn-nbctl lb-add lb-tcp 66.66.66.66:80 42.42.42.2:8080 tcp
+ovn-nbctl lb-add lb-udp 66.66.66.66:80 42.42.42.2:8080 udp
+ovn-nbctl ls-lb-add ls lb-tcp
+ovn-nbctl ls-lb-add ls lb-udp
+
+# Disable Stateful Bypass for TCP.
+ovn-nbctl stateless-filter-del pg
+ovn-nbctl --wait=sb sync
+
+# TCP packets should go to conntrack.
+flow="inport == \"lsp1\" && ${flow_eth} && ${flow_ip} && ${flow_tcp}"
+AT_CHECK([ovn-trace --ct new --ct new --minimal ls "${flow}"], [0], [dnl
+# tcp,reg14=0x1,vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02,nw_src=42.42.42.1,nw_dst=66.66.66.66,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=0,tp_dst=80,tcp_flags=0
+ct_next(ct_state=new|trk) {
+ ct_lb {
+ ct_next(ct_state=new|trk) {
+ output("lsp2");
+ };
+ };
+};
+])
+
+# UDP packets should go to conntrack.
+flow="inport == \"lsp1\" && ${flow_eth} && ${flow_ip} && ${flow_udp}"
+AT_CHECK([ovn-trace --ct new --ct new --minimal ls "${flow}"], [0], [dnl
+# udp,reg14=0x1,vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02,nw_src=42.42.42.1,nw_dst=66.66.66.66,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=0,tp_dst=80
+ct_next(ct_state=new|trk) {
+ ct_lb {
+ ct_next(ct_state=new|trk) {
+ output("lsp2");
+ };
+ };
+};
+])
+
+# Enable Stateful Bypass for TCP.
+ovn-nbctl stateless-filter-add pg 1 tcp
+ovn-nbctl --wait=sb sync
+
+# TCP packets should go to conntrack for load balancing.
+flow="inport == \"lsp1\" && ${flow_eth} && ${flow_ip} && ${flow_tcp}"
+AT_CHECK([ovn-trace --ct new --ct new --minimal ls "${flow}"], [0], [dnl
+# tcp,reg14=0x1,vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02,nw_src=42.42.42.1,nw_dst=66.66.66.66,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=0,tp_dst=80,tcp_flags=0
+ct_next(ct_state=new|trk) {
+ ct_lb {
+ ct_next(ct_state=new|trk) {
+ output("lsp2");
+ };
+ };
+};
+])
+
+# UDP packets still go to conntrack.
+flow="inport == \"lsp1\" && ${flow_eth} && ${flow_ip} && ${flow_udp}"
+AT_CHECK([ovn-trace --ct new --ct new --minimal ls "${flow}"], [0], [dnl
+# udp,reg14=0x1,vlan_tci=0x0000,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02,nw_src=42.42.42.1,nw_dst=66.66.66.66,nw_tos=0,nw_ecn=0,nw_ttl=64,tp_src=0,tp_dst=80
+ct_next(ct_state=new|trk) {
+ ct_lb {
+ ct_next(ct_state=new|trk) {
+ output("lsp2");
+ };
+ };
+};
+])
+
+AT_CLEANUP
diff --git a/tests/system-common-macros.at b/tests/system-common-macros.at
index c8fa6f0..65904ed 100644
--- a/tests/system-common-macros.at
+++ b/tests/system-common-macros.at
@@ -234,6 +234,14 @@ m4_define([FORMAT_PING], [grep "transmitted" | sed 's/time.*ms$/time 0ms/'])
#
m4_define([STRIP_MONITOR_CSUM], [grep "csum:" | sed 's/csum:.*/csum: /'])
+# FORMAT_CT_STATE([ip-addr])
+#
+# Strip content from the piped input which would differ from test to test
+# and limit the output to the rows containing 'ip-addr'. Don't strip state.
+#
+m4_define([FORMAT_CT_STATE],
+ [[grep "dst=$1" | sed -e 's/port=[0-9]*/port=/g' -e 's/id=[0-9]*/id=/g' | sort | uniq]])
+
# FORMAT_CT([ip-addr])
#
# Strip content from the piped input which would differ from test to test
diff --git a/tests/system-ovn.at b/tests/system-ovn.at
index b9b5eaa..32f9acc 100644
--- a/tests/system-ovn.at
+++ b/tests/system-ovn.at
@@ -5397,3 +5397,116 @@ as
OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d
/.*terminating with signal 15.*/d"])
AT_CLEANUP
+
+AT_SETUP([ovn -- ACL Stateful Bypass + Load balancer])
+AT_SKIP_IF([test $HAVE_NC = no])
+AT_KEYWORDS([lb])
+AT_KEYWORDS([conntrack])
+ovn_start
+
+OVS_TRAFFIC_VSWITCHD_START()
+ADD_BR([br-int])
+
+# Set external-ids in br-int needed for ovn-controller
+ovs-vsctl \
+ -- set Open_vSwitch . external-ids:system-id=hv1 \
+ -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
+ -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
+ -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
+ -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
+
+# Start ovn-controller
+start_daemon ovn-controller
+
+# Logical network:
+# One logical switch with a load balancer with one backend.
+# On the LS we add "allow" ACLs for TCP and "allow-related" ACLs for UDP.
+# The "allow-related" ACL normally forces all traffic to go to conntrack.
+# We enable ACL stateful bypass for TCP so TCP traffic should not be
+# sent to conntrack for ACLs (only for LB).
+
+ovn-nbctl ls-add ls
+ovn-nbctl lsp-add ls lsp1
+ovn-nbctl lsp-set-addresses lsp1 00:00:00:00:00:01
+ovn-nbctl lsp-add ls lsp2
+ovn-nbctl lsp-set-addresses lsp2 00:00:00:00:00:02
+
+ovn-nbctl acl-add ls from-lport 3 "tcp" allow
+ovn-nbctl acl-add ls from-lport 2 "udp" allow-related
+ovn-nbctl acl-add ls from-lport 1 "ip" drop
+
+ovn-nbctl lr-add rtr
+ovn-nbctl lrp-add rtr rtr-ls 00:00:00:00:01:00 42.42.42.254/24
+ovn-nbctl lsp-add ls ls-rtr \
+ -- lsp-set-type ls-rtr router \
+ -- lsp-set-addresses ls-rtr 00:00:00:00:01:00 \
+ -- lsp-set-options ls-rtr router-port=rtr-ls
+
+# Add a load balancer.
+ovn-nbctl lb-add lb-tcp 66.66.66.66:80 42.42.42.2:8080 tcp
+ovn-nbctl lb-add lb-udp 66.66.66.66:80 42.42.42.2:8080 udp
+ovn-nbctl ls-lb-add ls lb-tcp
+ovn-nbctl ls-lb-add ls lb-udp
+
+# Enable Stateful Bypass for TCP.
+ovn-nbctl \
+ --id=@f1 create Stateless_Filter priority=1 match="tcp" -- \
+ set Logical_Switch ls stateless_filters='@f1'
+
+ADD_NAMESPACES(lsp1)
+ADD_VETH(lsp1, lsp1, br-int, "42.42.42.1/24", "00:00:00:00:00:01", \
+ "42.42.42.254")
+
+ADD_NAMESPACES(lsp2)
+ADD_VETH(lsp2, lsp2, br-int, "42.42.42.2/24", "00:00:00:00:00:02", \
+ "42.42.42.254")
+
+ovn-nbctl --wait=hv sync
+
+# Start a UDP server on lsp2.
+NETNS_DAEMONIZE([lsp2], [nc -l --no-shutdown -u 42.42.42.2 8080], [nc2.pid])
+
+# Start a UDP connection.
+NS_CHECK_EXEC([lsp1], [echo "foo" | nc --no-shutdown -u 66.66.66.66 80])
+
+# There should be 2 UDP conntrack entries:
+# - one for the allow-related ACL.
+# - one for the LB dnat.
+OVS_WAIT_UNTIL([test "$(ovs-appctl dpctl/dump-conntrack | grep udp | grep '42.42.42.1' -c)" = "2"])
+
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT_STATE(42.42.42.1) | grep udp | \
+sed -e 's/zone=[[0-9]]*/zone=/'], [0], [dnl
+udp,orig=(src=42.42.42.1,dst=42.42.42.2,sport=,dport=),reply=(src=42.42.42.2,dst=42.42.42.1,sport=,dport=),zone=
+udp,orig=(src=42.42.42.1,dst=66.66.66.66,sport=,dport=),reply=(src=42.42.42.2,dst=42.42.42.1,sport=,dport=),zone=,labels=0x2
+])
+
+# Start a TCP server on lsp2.
+NETNS_DAEMONIZE([lsp2], [nc -l --no-shutdown 42.42.42.2 8080], [nc0.pid])
+
+# Start a TCP connection.
+NETNS_DAEMONIZE([lsp1], [nc --no-shutdown 66.66.66.66 80], [nc1.pid])
+
+OVS_WAIT_UNTIL([test "$(ovs-appctl dpctl/dump-conntrack | grep tcp | grep '42.42.42.1' -c)" = "1"])
+
+# There should be only one TCP conntrack entry, for the LB dnat.
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT_STATE(42.42.42.1) | grep tcp | \
+sed -e 's/zone=[[0-9]]*/zone=/'], [0], [dnl
+tcp,orig=(src=42.42.42.1,dst=66.66.66.66,sport=,dport=),reply=(src=42.42.42.2,dst=42.42.42.1,sport=,dport=),zone=,labels=0x2,protoinfo=(state=ESTABLISHED)
+])
+
+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([ovn-northd])
+
+as
+OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
+/connection dropped.*/d"])
+
+AT_CLEANUP
diff --git a/utilities/ovn-detrace.in b/utilities/ovn-detrace.in
index 4f8dd5f..343965d 100755
--- a/utilities/ovn-detrace.in
+++ b/utilities/ovn-detrace.in
@@ -232,6 +232,17 @@ class StaticRouteHintHandler(CookieHandlerByUUUID):
route.ip_prefix, route.nexthop, route.output_port,
route.policy))
+class StatelessFilterHintHandler(CookieHandlerByUUUID):
+ def __init__(self, ovnnb_db):
+ super(StatelessFilterHintHandler, self).__init__(ovnnb_db,
+ 'Stateless_Filter')
+
+ def print_record(self, s_filter):
+ output = 'Stateless_Filter: priority=%s, match=(%s)' % (
+ s_filter.priority,
+ s_filter.match.strip('"'))
+ print_h(output)
+
class QoSHintHandler(CookieHandlerByUUUID):
def __init__(self, ovnnb_db):
super(QoSHintHandler, self).__init__(ovnnb_db, 'QoS')
@@ -254,6 +265,7 @@ class LogicalFlowHandler(CookieHandlerByUUUID):
LoadBalancerHintHandler(ovnnb_db),
NATHintHandler(ovnnb_db),
StaticRouteHintHandler(ovnnb_db),
+ StatelessFilterHintHandler(ovnnb_db),
QoSHintHandler(ovnnb_db),
]
diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
index d7bb4b4..7716dcd 100644
--- a/utilities/ovn-nbctl.c
+++ b/utilities/ovn-nbctl.c
@@ -601,6 +601,17 @@ ACL commands:\n\
acl-list {SWITCH | PORTGROUP}\n\
print ACLs for SWITCH\n\
\n\
+Stateless filter commands:\n\
+ [--type={switch | port-group}] [--may-exist]\n\
+ stateless-filter-add {SWITCH | PORTGROUP} PRIORITY MATCH \n\
+ add a stateless filter to SWITCH/PORTGROUP\n\
+ [--type={switch | port-group}]\n\
+ stateless-filter-del {SWITCH | PORTGROUP} [PRIORITY MATCH]\n\
+ remove stateless filters from SWITCH/PORTGROUP\n\
+ [--type={switch | port-group}]\n\
+ stateless-filter-list {SWITCH | PORTGROUP}\n\
+ print stateless filters for SWITCH\n\
+\n\
QoS commands:\n\
qos-add SWITCH DIRECTION PRIORITY MATCH [rate=RATE [burst=BURST]] [dscp=DSCP]\n\
add an QoS rule to SWITCH\n\
@@ -725,7 +736,8 @@ LB commands:\n\
ls-lb-add SWITCH LB add a load-balancer to SWITCH\n\
ls-lb-del SWITCH [LB] remove load-balancers from SWITCH\n\
ls-lb-list SWITCH print load-balancers\n\
-\n\
+\n\n",program_name, program_name);
+ printf("\
DHCP Options commands:\n\
dhcp-options-create CIDR [EXTERNAL_IDS]\n\
create a DHCP options row with CIDR\n\
@@ -743,8 +755,7 @@ Connection commands:\n\
del-connection delete the connections\n\
[--inactivity-probe=MSECS]\n\
set-connection TARGET... set the list of connections to TARGET...\n\
-\n\n",program_name, program_name);
- printf("\
+\n\
SSL commands:\n\
get-ssl print the SSL configuration\n\
del-ssl delete the SSL configuration\n\
@@ -2021,9 +2032,9 @@ acl_cmp(const void *acl1_, const void *acl2_)
}
static char * OVS_WARN_UNUSED_RESULT
-acl_cmd_get_pg_or_ls(struct ctl_context *ctx,
- const struct nbrec_logical_switch **ls,
- const struct nbrec_port_group **pg)
+cmd_get_pg_or_ls(struct ctl_context *ctx,
+ const struct nbrec_logical_switch **ls,
+ const struct nbrec_port_group **pg)
{
const char *opt_type = shash_find_data(&ctx->options, "--type");
char *error;
@@ -2073,7 +2084,7 @@ nbctl_acl_list(struct ctl_context *ctx)
const struct nbrec_acl **acls;
size_t i;
- char *error = acl_cmd_get_pg_or_ls(ctx, &ls, &pg);
+ char *error = cmd_get_pg_or_ls(ctx, &ls, &pg);
if (error) {
ctx->error = error;
return;
@@ -2173,7 +2184,7 @@ nbctl_acl_add(struct ctl_context *ctx)
const struct nbrec_port_group *pg = NULL;
const char *action = ctx->argv[5];
- char *error = acl_cmd_get_pg_or_ls(ctx, &ls, &pg);
+ char *error = cmd_get_pg_or_ls(ctx, &ls, &pg);
if (error) {
ctx->error = error;
return;
@@ -2264,7 +2275,7 @@ nbctl_acl_del(struct ctl_context *ctx)
const struct nbrec_logical_switch *ls = NULL;
const struct nbrec_port_group *pg = NULL;
- char *error = acl_cmd_get_pg_or_ls(ctx, &ls, &pg);
+ char *error = cmd_get_pg_or_ls(ctx, &ls, &pg);
if (error) {
ctx->error = error;
return;
@@ -2351,6 +2362,181 @@ nbctl_acl_del(struct ctl_context *ctx)
}
}
+static int
+stateless_filter_cmp(const void *filter1_, const void *filter2_)
+{
+ const struct nbrec_stateless_filter *const *filter1p = filter1_;
+ const struct nbrec_stateless_filter *const *filter2p = filter2_;
+ const struct nbrec_stateless_filter *filter1 = *filter1p;
+ const struct nbrec_stateless_filter *filter2 = *filter2p;
+
+ if (filter1->priority != filter2->priority) {
+ return filter1->priority > filter2->priority ? -1 : 1;
+ } else {
+ return strcmp(filter1->match, filter2->match);
+ }
+}
+
+static void
+nbctl_stateless_filter_list(struct ctl_context *ctx)
+{
+ const struct nbrec_logical_switch *ls = NULL;
+ const struct nbrec_port_group *pg = NULL;
+ const struct nbrec_stateless_filter **filters;
+ size_t i;
+
+ char *error = cmd_get_pg_or_ls(ctx, &ls, &pg);
+ if (error) {
+ ctx->error = error;
+ return;
+ }
+
+ size_t n_filters = pg ? pg->n_stateless_filters : ls->n_stateless_filters;
+ struct nbrec_stateless_filter **nb_filters = pg
+ ? pg->stateless_filters
+ : ls->stateless_filters;
+
+ filters = xmalloc(sizeof *filters * n_filters);
+ for (i = 0; i < n_filters; i++) {
+ filters[i] = nb_filters[i];
+ }
+
+ qsort(filters, n_filters, sizeof *filters, stateless_filter_cmp);
+
+ for (i = 0; i < n_filters; i++) {
+ const struct nbrec_stateless_filter *filter = filters[i];
+ ds_put_format(&ctx->output, "%5"PRId64" (%s)\n",
+ filter->priority, filter->match);
+ }
+
+ free(filters);
+}
+
+static void
+nbctl_stateless_filter_add(struct ctl_context *ctx)
+{
+ const struct nbrec_logical_switch *ls = NULL;
+ const struct nbrec_port_group *pg = NULL;
+
+ char *error = cmd_get_pg_or_ls(ctx, &ls, &pg);
+ if (error) {
+ ctx->error = error;
+ return;
+ }
+
+ int64_t priority;
+ error = parse_priority(ctx->argv[2], &priority);
+ if (error) {
+ ctx->error = error;
+ return;
+ }
+
+ /* Create the filter. */
+ struct nbrec_stateless_filter *filter =
+ nbrec_stateless_filter_insert(ctx->txn);
+ nbrec_stateless_filter_set_priority(filter, priority);
+ nbrec_stateless_filter_set_match(filter, ctx->argv[3]);
+
+ /* Check if same filter already exists for the ls/portgroup */
+ size_t n_filters = pg ? pg->n_stateless_filters : ls->n_stateless_filters;
+ struct nbrec_stateless_filter **filters = pg
+ ? pg->stateless_filters
+ : ls->stateless_filters;
+ for (size_t i = 0; i < n_filters; i++) {
+ if (!stateless_filter_cmp(&filters[i], &filter)) {
+ bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
+ if (!may_exist) {
+ ctl_error(ctx,
+ "Same filter already existed on ls or pg %s.",
+ ctx->argv[1]);
+ return;
+ }
+ return;
+ }
+ }
+
+ /* Insert the filter into the logical switch/port group. */
+ struct nbrec_stateless_filter **new_filters =
+ xmalloc(sizeof *new_filters * (n_filters + 1));
+ nullable_memcpy(new_filters, filters, sizeof *new_filters * n_filters);
+ new_filters[n_filters] = filter;
+ if (pg) {
+ nbrec_port_group_verify_stateless_filters(pg);
+ nbrec_port_group_set_stateless_filters(pg, new_filters,
+ n_filters + 1);
+ } else {
+ nbrec_logical_switch_verify_stateless_filters(ls);
+ nbrec_logical_switch_set_stateless_filters(ls, new_filters,
+ n_filters + 1);
+ }
+ free(new_filters);
+}
+
+static void
+nbctl_stateless_filter_del(struct ctl_context *ctx)
+{
+ const struct nbrec_logical_switch *ls = NULL;
+ const struct nbrec_port_group *pg = NULL;
+
+ char *error = cmd_get_pg_or_ls(ctx, &ls, &pg);
+ if (error) {
+ ctx->error = error;
+ return;
+ }
+
+ if (ctx->argc == 2) {
+ /* If priority and match are not specified, delete filters. */
+ if (pg) {
+ nbrec_port_group_verify_stateless_filters(pg);
+ nbrec_port_group_set_stateless_filters(pg, NULL, 0);
+ } else {
+ nbrec_logical_switch_verify_stateless_filters(ls);
+ nbrec_logical_switch_set_stateless_filters(ls, NULL, 0);
+ }
+ return;
+ }
+
+ int64_t priority;
+ error = parse_priority(ctx->argv[2], &priority);
+ if (error) {
+ ctx->error = error;
+ return;
+ }
+
+ if (ctx->argc == 3) {
+ ctl_error(ctx, "cannot specify priority without match");
+ return;
+ }
+
+ size_t n_filters = pg ? pg->n_stateless_filters : ls->n_stateless_filters;
+ struct nbrec_stateless_filter **filters = pg
+ ? pg->stateless_filters
+ : ls->stateless_filters;
+
+ /* Remove the matching rule. */
+ for (size_t i = 0; i < n_filters; i++) {
+ struct nbrec_stateless_filter *filter = filters[i];
+
+ if (priority == filter->priority
+ && !strcmp(ctx->argv[3], filter->match)) {
+ struct nbrec_stateless_filter **new_filters
+ = xmemdup(filters, sizeof *new_filters * n_filters);
+ new_filters[i] = filters[n_filters - 1];
+ if (pg) {
+ nbrec_port_group_verify_stateless_filters(pg);
+ nbrec_port_group_set_stateless_filters(pg, new_filters,
+ n_filters - 1);
+ } else {
+ nbrec_logical_switch_verify_stateless_filters(ls);
+ nbrec_logical_switch_set_stateless_filters(ls, new_filters,
+ n_filters - 1);
+ }
+ free(new_filters);
+ return;
+ }
+ }
+}
+
static void
nbctl_qos_list(struct ctl_context *ctx)
{
@@ -6283,6 +6469,15 @@ static const struct ctl_command_syntax nbctl_commands[] = {
{ "acl-list", 1, 1, "{SWITCH | PORTGROUP}",
NULL, nbctl_acl_list, NULL, "--type=", RO },
+ /* stateless filter commands. */
+ { "stateless-filter-add", 3, 4, "{SWITCH | PORTGROUP} PRIORITY MATCH",
+ NULL, nbctl_stateless_filter_add, NULL,
+ "--may-exist,--type=", RW },
+ { "stateless-filter-del", 1, 4, "{SWITCH | PORTGROUP} [PRIORITY MATCH]",
+ NULL, nbctl_stateless_filter_del, NULL, "--type=", RW },
+ { "stateless-filter-list", 1, 1, "{SWITCH | PORTGROUP}",
+ NULL, nbctl_stateless_filter_list, NULL, "--type=", RO },
+
/* qos commands. */
{ "qos-add", 5, 7,
"SWITCH DIRECTION PRIORITY MATCH [rate=RATE [burst=BURST]] [dscp=DSCP]",