1# 2# SPDX-License-Identifier: BSD-2-Clause 3# 4# Copyright (c) 2026 Rubicon Communications, LLC (Netgate) 5# 6# Redistribution and use in source and binary forms, with or without 7# modification, are permitted provided that the following conditions 8# are met: 9# 1. Redistributions of source code must retain the above copyright 10# notice, this list of conditions and the following disclaimer. 11# 2. Redistributions in binary form must reproduce the above copyright 12# notice, this list of conditions and the following disclaimer in the 13# documentation and/or other materials provided with the distribution. 14# 15# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25# SUCH DAMAGE. 26 27. $(atf_get_srcdir)/utils.subr 28 29atf_test_case "state_basic" "cleanup" 30state_basic_head() 31{ 32 atf_set descr 'Basic state limiter test' 33 atf_set require.user root 34} 35 36state_basic_body() 37{ 38 pft_init 39 40 epair=$(vnet_mkepair) 41 42 ifconfig ${epair}a 192.0.2.2/24 up 43 44 vnet_mkjail alcatraz ${epair}b 45 jexec alcatraz ifconfig ${epair}b 192.0.2.1/24 up 46 47 # Sanity check 48 atf_check -s exit:0 -o ignore \ 49 ping -c 1 192.0.2.1 50 51 jexec alcatraz pfctl -e 52 # Allow up to one ICMP state. 53 pft_set_rules alcatraz \ 54 "set timeout icmp.error 120" \ 55 "state limiter \"server\" id 1 limit 1" \ 56 "block in proto icmp" \ 57 "pass in proto icmp state limiter \"server\" (no-match)" 58 59 atf_check -s exit:0 -o ignore \ 60 ping -c 2 192.0.2.1 61 62 # This should now fail 63 atf_check -s exit:2 -o ignore \ 64 ping -c 2 192.0.2.1 65 66 jexec alcatraz pfctl -sLimiterStates 67 hardlim=$(jexec alcatraz pfctl -sLimiterStates | awk 'NR>1 { print $5; }') 68 if [ $hardlim -eq 0 ]; then 69 atf_fail "Hard limit not incremented" 70 fi 71} 72 73state_basic_cleanup() 74{ 75 pft_cleanup 76} 77 78atf_test_case "state_rate" "cleanup" 79state_rate_head() 80{ 81 atf_set descr 'State rate limiting test' 82 atf_set require.user root 83} 84 85state_rate_body() 86{ 87 pft_init 88 89 epair=$(vnet_mkepair) 90 91 ifconfig ${epair}a 192.0.2.2/24 up 92 93 vnet_mkjail alcatraz ${epair}b 94 jexec alcatraz ifconfig ${epair}b 192.0.2.1/24 up 95 96 # Sanity check 97 atf_check -s exit:0 -o ignore \ 98 ping -c 1 192.0.2.1 99 100 jexec alcatraz pfctl -e 101 # Allow one ICMP state per 5 seconds 102 pft_set_rules alcatraz \ 103 "set timeout icmp.error 120" \ 104 "state limiter \"server\" id 1 limit 1000 rate 1/5" \ 105 "block in proto icmp" \ 106 "pass in proto icmp state limiter \"server\" (no-match)" 107 108 atf_check -s exit:0 -o ignore \ 109 ping -c 2 192.0.2.1 110 111 # This should now fail 112 atf_check -s exit:2 -o ignore \ 113 ping -c 2 192.0.2.1 114 115 jexec alcatraz pfctl -sLimiterStates 116 ratelim=$(jexec alcatraz pfctl -sLimiterStates | awk 'NR>1 { print $6; }') 117 if [ $ratelim -eq 0 ]; then 118 atf_fail "Rate limit not incremented" 119 fi 120 121 sleep 6 122 123 # We can now create another state 124 atf_check -s exit:0 -o ignore \ 125 ping -c 2 192.0.2.1 126} 127 128state_rate_cleanup() 129{ 130 pft_cleanup 131} 132 133atf_test_case "state_block" "cleanup" 134state_block_head() 135{ 136 atf_set descr 'Test block mode state limiter' 137 atf_set require.user root 138} 139 140state_block_body() 141{ 142 pft_init 143 144 epair=$(vnet_mkepair) 145 146 ifconfig ${epair}a 192.0.2.2/24 up 147 148 vnet_mkjail alcatraz ${epair}b 149 jexec alcatraz ifconfig ${epair}b 192.0.2.1/24 up 150 151 # Sanity check 152 atf_check -s exit:0 -o ignore \ 153 ping -c 1 192.0.2.1 154 155 jexec alcatraz pfctl -e 156 # Allow one ICMP state per 5 seconds 157 pft_set_rules alcatraz \ 158 "set timeout icmp.error 120" \ 159 "state limiter \"server\" id 1 limit 1000 rate 1/5" \ 160 "pass" \ 161 "pass in proto icmp state limiter \"server\" (block)" 162 163 atf_check -s exit:0 -o ignore \ 164 ping -c 2 192.0.2.1 165 166 # This should now fail 167 atf_check -s exit:2 -o ignore \ 168 ping -c 2 192.0.2.1 169 170 # However, if we set no-match and exceed the limit we just pass 171 pft_set_rules alcatraz \ 172 "set timeout icmp.error 120" \ 173 "state limiter \"server\" id 1 limit 1000 rate 1/5" \ 174 "pass" \ 175 "pass in proto icmp state limiter \"server\" (no-match)" 176 177 atf_check -s exit:0 -o ignore \ 178 ping -c 2 192.0.2.1 179 atf_check -s exit:0 -o ignore \ 180 ping -c 2 192.0.2.1 181} 182 183state_block_cleanup() 184{ 185 pft_cleanup 186} 187 188atf_test_case "source_basic" "cleanup" 189source_basic_head() 190{ 191 atf_set descr 'Basic source limiter test' 192 atf_set require.user root 193} 194 195source_basic_body() 196{ 197 pft_init 198 199 epair=$(vnet_mkepair) 200 201 ifconfig ${epair}a 192.0.2.2/24 up 202 ifconfig ${epair}a inet alias 192.0.2.3/24 up 203 204 vnet_mkjail alcatraz ${epair}b 205 jexec alcatraz ifconfig ${epair}b 192.0.2.1/24 up 206 207 # Sanity check 208 atf_check -s exit:0 -o ignore \ 209 ping -S 192.0.2.2 -c 1 192.0.2.1 210 atf_check -s exit:0 -o ignore \ 211 ping -S 192.0.2.3 -c 1 192.0.2.1 212 213 jexec alcatraz pfctl -e 214 215 # Allow up to one source for ICMP. 216 pft_set_rules alcatraz \ 217 "set timeout icmp.error 120" \ 218 "source limiter \"server\" id 1 entries 128 limit 1" \ 219 "block in proto icmp" \ 220 "pass in proto icmp source limiter \"server\" (no-match)" 221 222 atf_check -s exit:0 -o ignore \ 223 ping -S 192.0.2.2 -c 2 192.0.2.1 224 225 # This should now fail 226 atf_check -s exit:2 -o ignore \ 227 ping -S 192.0.2.2 -c 2 192.0.2.1 228 229 jexec alcatraz pfctl -sLimiterSrcs 230 hardlim=$(jexec alcatraz pfctl -sLimiterSrcs | awk 'NR>1 { print $5; }') 231 if [ $hardlim -eq 0 ]; then 232 atf_fail "Hard limit not incremented" 233 fi 234 235 # However, a different source will succeed 236 atf_check -s exit:0 -o ignore \ 237 ping -S 192.0.2.3 -c 2 192.0.2.1 238 239 atf_check -o match:"192.0.2.2/32 .*hardlim 2 ratelim 0" \ 240 -e ignore \ 241 jexec alcatraz pfctl -sLimiterSrcs -v 242 atf_check -o match:"192.0.2.3/32 .*hardlim 0 ratelim 0" \ 243 -e ignore \ 244 jexec alcatraz pfctl -sLimiterSrcs -v 245 246 # Kill the source entry 247 atf_check -s exit:0 -e ignore \ 248 jexec alcatraz pfctl -I 1 -k source -k 192.0.2.2 249 # Now we can ping again from it 250 atf_check -s exit:0 -o ignore \ 251 ping -S 192.0.2.2 -c 2 192.0.2.1 252} 253 254source_basic_cleanup() 255{ 256 pft_cleanup 257} 258 259atf_init_test_cases() 260{ 261 atf_add_test_case "state_basic" 262 atf_add_test_case "state_rate" 263 atf_add_test_case "state_block" 264 atf_add_test_case "source_basic" 265} 266