1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
|
#!/bin/bash
# NFT_TEST_REQUIRES(NFT_TEST_HAVE_socat)
# Tests reject functionality for both IPv4 and IPv6 with TCP and ICMP traffic on
# the loopback interface.
#
# - check reject works, i.e. ping and connect fail
# - check we don't reply to tcp resets with another tcp reset
# - check we don't reply to icmp error with another icmp error
ret=0
port=14512
ip link set lo up
load_ruleset_netdev()
{
echo load netdev test ruleset
$NFT -f -<<EOF
table netdev t {
chain in {
type filter hook ingress device lo priority 0
ip protocol icmp counter reject
ip6 nexthdr icmpv6 counter reject
meta l4proto tcp counter reject with tcp reset
}
}
EOF
}
load_ruleset_inet()
{
echo load inet test ruleset
$NFT -f -<<EOF
table inet t {
chain in {
type filter hook prerouting priority filter; policy accept;
iifname lo jump {
icmp type echo-request counter reject with icmp port-unreachable
icmpv6 type echo-request counter reject with icmpv6 port-unreachable
meta l4proto tcp tcp flags syn counter reject with tcp reset
}
}
}
EOF
}
# try to get nf_tables to reset tcp rest with tcp reset and
# reject icmp port-unreach with port-unreach.
# Should NOT be possible.
# Note that this ruleset is nonsensical:
# meta l4proto tcp ... reject with tcp reset, on loopback,
# will drop the reset packet so the client times out.
#
# This isn't an issue for remote clients, as the reset
# won't appear in prerouting.
load_ruleset_inet_loop()
{
echo load inet loop ruleset
$NFT -f -<<EOF
table inet t {
counter tcprstc { }
counter icmp4c { }
counter icmp6c { }
chain in {
type filter hook prerouting priority filter; policy accept;
iifname lo jump {
ip protocol icmp counter name icmp4c reject with icmp port-unreachable
ip6 nexthdr icmpv6 counter name icmp6c reject with icmpv6 port-unreachable
meta l4proto tcp counter name tcprstc reject with tcp reset
}
}
}
EOF
}
load_ruleset_netdev_loop()
{
echo load netdev loop ruleset
$NFT -f -<<EOF
table netdev t {
counter tcprstc { }
counter icmp4c { }
counter icmp6c { }
chain in {
type filter hook ingress device lo priority 0
ip protocol icmp counter name icmp4c reject with icmp port-unreachable
ip6 nexthdr icmpv6 counter name icmp6c reject with icmpv6 port-unreachable
meta l4proto tcp counter name tcprstc reject with tcp reset
}
}
EOF
}
check_counter()
{
local family="$1"
local countername="$2"
local wanted_packetcount="$3"
local max_packetcount="$4"
echo "counter $family t $countername has $pcount packets"
if $NFT list counter "$family" "t" "$countername" | grep packets\ $wanted_packetcount;then
return
fi
# the _loop rulesets drop tcp resets, so we must tolerate retransmitted syns.
if [ "$max_packetcount" -gt 0 ];then
local pcount=$($NFT list counter "$family" "t" "$countername" | grep packets)
pcount=${pcount%bytes*}
pcount=${pcount#*packets}
if [ "$pcount" -gt 0 ] && [ "$pcount" -le "$max_packetcount" ];then
echo "Tolerated $pcount packets (max $max_packetcount)"
return
fi
fi
echo "Unexpected packetcount, expected $wanted_packetcount / max $max_packetcount"
$NFT list counter "$family" "t" "$countername"
ret=1
}
check_counters()
{
local family="$1"
# one syn, one rst
check_counter "$family" tcprstc 4 16
# one for echo, one for dst-unreach
check_counter "$family" icmp4c 2 2
check_counter "$family" icmp6c 2 2
}
maybe_error()
{
local ret="$1"
shift
local err_wanted="$1"
shift
local errmsg="$@"
if [ $ret -eq 0 ];then
errmsg="$errmsg succeeded"
if [ $err_wanted -ne 0 ]; then
echo "$errmsg but expected to fail"
ret=1
return
fi
else
errmsg="$errmsg failed ($ret)"
if [ $err_wanted -eq 0 ]; then
echo "$errmsg but expected to work"
ret=1
return
fi
fi
}
test_all()
{
local err_wanted="$1"
ping -W 1 -q -c 1 127.0.0.1 > /dev/null
maybe_error $? "$err_wanted" "ping 127.0.0.1"
socat -u STDIN TCP-CONNECT:127.0.0.1:$port,connect-timeout=1 < /dev/null 2>/dev/null
maybe_error $? "$err_wanted" "connect 127.0.0.1"
ping -W 1 -q -c 1 ::1 > /dev/null
maybe_error $? "$err_wanted" "connect 127.0.0.1"
socat -u STDIN TCP-CONNECT:[::1]:$port,connect-timeout=1 < /dev/null 2>/dev/null
maybe_error $? "$err_wanted" "connect ::1"
}
# Start socat listeners in background
timeout 10 socat TCP-LISTEN:$port,bind=127.0.0.1,reuseaddr PIPE &
SOCAT_PID4=$!
timeout 10 socat TCP6-LISTEN:$port,bind=::1,reuseaddr PIPE &
SOCAT_PID6=$!
# Give listeners time to start
sleep 1
# empty ruleset
test_all 0
load_ruleset_inet
test_all 1
$NFT delete table inet t
load_ruleset_netdev
test_all 1
$NFT delete table netdev t
load_ruleset_inet_loop
test_all 1
check_counters inet
$NFT delete table inet t
load_ruleset_netdev_loop
test_all 1
check_counters netdev
$NFT delete table netdev t
# Clean up listeners
kill $SOCAT_PID4 $SOCAT_PID6 2>/dev/null
echo "Exiting with $ret"
exit $ret
|