|
| 1 | +#!/bin/bash |
| 2 | +# |
| 3 | +# This tests tproxy on the following scenario: |
| 4 | +# |
| 5 | +# +------------+ |
| 6 | +# +-------+ | nsrouter | +-------+ |
| 7 | +# |ns1 |.99 .1| |.1 .99| ns2| |
| 8 | +# | eth0|---------------|veth0 veth1|------------------|eth0 | |
| 9 | +# | | 10.0.1.0/24 | | 10.0.2.0/24 | | |
| 10 | +# +-------+ dead:1::/64 | veth2 | dead:2::/64 +-------+ |
| 11 | +# +------------+ |
| 12 | +# |.1 |
| 13 | +# | |
| 14 | +# | |
| 15 | +# | +-------+ |
| 16 | +# | .99| ns3| |
| 17 | +# +------------------------|eth0 | |
| 18 | +# 10.0.3.0/24 | | |
| 19 | +# dead:3::/64 +-------+ |
| 20 | +# |
| 21 | +# The tproxy implementation acts as an echo server so the client |
| 22 | +# must receive the same message it sent if it has been proxied. |
| 23 | +# If is not proxied the servers return PONG_NS# with the number |
| 24 | +# of the namespace the server is running. |
| 25 | +# |
| 26 | +# shellcheck disable=SC2162,SC2317 |
| 27 | + |
| 28 | +source lib.sh |
| 29 | +ret=0 |
| 30 | +timeout=5 |
| 31 | + |
| 32 | +cleanup() |
| 33 | +{ |
| 34 | + ip netns pids "$ns1" | xargs kill 2>/dev/null |
| 35 | + ip netns pids "$ns2" | xargs kill 2>/dev/null |
| 36 | + ip netns pids "$ns3" | xargs kill 2>/dev/null |
| 37 | + ip netns pids "$nsrouter" | xargs kill 2>/dev/null |
| 38 | + |
| 39 | + cleanup_all_ns |
| 40 | +} |
| 41 | + |
| 42 | +checktool "nft --version" "test without nft tool" |
| 43 | +checktool "socat -h" "run test without socat" |
| 44 | + |
| 45 | +trap cleanup EXIT |
| 46 | +setup_ns ns1 ns2 ns3 nsrouter |
| 47 | + |
| 48 | +if ! ip link add veth0 netns "$nsrouter" type veth peer name eth0 netns "$ns1" > /dev/null 2>&1; then |
| 49 | + echo "SKIP: No virtual ethernet pair device support in kernel" |
| 50 | + exit $ksft_skip |
| 51 | +fi |
| 52 | +ip link add veth1 netns "$nsrouter" type veth peer name eth0 netns "$ns2" |
| 53 | +ip link add veth2 netns "$nsrouter" type veth peer name eth0 netns "$ns3" |
| 54 | + |
| 55 | +ip -net "$nsrouter" link set veth0 up |
| 56 | +ip -net "$nsrouter" addr add 10.0.1.1/24 dev veth0 |
| 57 | +ip -net "$nsrouter" addr add dead:1::1/64 dev veth0 nodad |
| 58 | + |
| 59 | +ip -net "$nsrouter" link set veth1 up |
| 60 | +ip -net "$nsrouter" addr add 10.0.2.1/24 dev veth1 |
| 61 | +ip -net "$nsrouter" addr add dead:2::1/64 dev veth1 nodad |
| 62 | + |
| 63 | +ip -net "$nsrouter" link set veth2 up |
| 64 | +ip -net "$nsrouter" addr add 10.0.3.1/24 dev veth2 |
| 65 | +ip -net "$nsrouter" addr add dead:3::1/64 dev veth2 nodad |
| 66 | + |
| 67 | +ip -net "$ns1" link set eth0 up |
| 68 | +ip -net "$ns2" link set eth0 up |
| 69 | +ip -net "$ns3" link set eth0 up |
| 70 | + |
| 71 | +ip -net "$ns1" addr add 10.0.1.99/24 dev eth0 |
| 72 | +ip -net "$ns1" addr add dead:1::99/64 dev eth0 nodad |
| 73 | +ip -net "$ns1" route add default via 10.0.1.1 |
| 74 | +ip -net "$ns1" route add default via dead:1::1 |
| 75 | + |
| 76 | +ip -net "$ns2" addr add 10.0.2.99/24 dev eth0 |
| 77 | +ip -net "$ns2" addr add dead:2::99/64 dev eth0 nodad |
| 78 | +ip -net "$ns2" route add default via 10.0.2.1 |
| 79 | +ip -net "$ns2" route add default via dead:2::1 |
| 80 | + |
| 81 | +ip -net "$ns3" addr add 10.0.3.99/24 dev eth0 |
| 82 | +ip -net "$ns3" addr add dead:3::99/64 dev eth0 nodad |
| 83 | +ip -net "$ns3" route add default via 10.0.3.1 |
| 84 | +ip -net "$ns3" route add default via dead:3::1 |
| 85 | + |
| 86 | +ip netns exec "$nsrouter" sysctl net.ipv6.conf.all.forwarding=1 > /dev/null |
| 87 | +ip netns exec "$nsrouter" sysctl net.ipv4.conf.veth0.forwarding=1 > /dev/null |
| 88 | +ip netns exec "$nsrouter" sysctl net.ipv4.conf.veth1.forwarding=1 > /dev/null |
| 89 | +ip netns exec "$nsrouter" sysctl net.ipv4.conf.veth2.forwarding=1 > /dev/null |
| 90 | + |
| 91 | +test_ping() { |
| 92 | + if ! ip netns exec "$ns1" ping -c 1 -q 10.0.2.99 > /dev/null; then |
| 93 | + return 1 |
| 94 | + fi |
| 95 | + |
| 96 | + if ! ip netns exec "$ns1" ping -c 1 -q dead:2::99 > /dev/null; then |
| 97 | + return 2 |
| 98 | + fi |
| 99 | + |
| 100 | + if ! ip netns exec "$ns1" ping -c 1 -q 10.0.3.99 > /dev/null; then |
| 101 | + return 1 |
| 102 | + fi |
| 103 | + |
| 104 | + if ! ip netns exec "$ns1" ping -c 1 -q dead:3::99 > /dev/null; then |
| 105 | + return 2 |
| 106 | + fi |
| 107 | + |
| 108 | + return 0 |
| 109 | +} |
| 110 | + |
| 111 | +test_ping_router() { |
| 112 | + if ! ip netns exec "$ns1" ping -c 1 -q 10.0.2.1 > /dev/null; then |
| 113 | + return 3 |
| 114 | + fi |
| 115 | + |
| 116 | + if ! ip netns exec "$ns1" ping -c 1 -q dead:2::1 > /dev/null; then |
| 117 | + return 4 |
| 118 | + fi |
| 119 | + |
| 120 | + return 0 |
| 121 | +} |
| 122 | + |
| 123 | + |
| 124 | +listener_ready() |
| 125 | +{ |
| 126 | + local ns="$1" |
| 127 | + local port="$2" |
| 128 | + local proto="$3" |
| 129 | + ss -N "$ns" -ln "$proto" -o "sport = :$port" | grep -q "$port" |
| 130 | +} |
| 131 | + |
| 132 | +test_tproxy() |
| 133 | +{ |
| 134 | + local traffic_origin="$1" |
| 135 | + local ip_proto="$2" |
| 136 | + local expect_ns1_ns2="$3" |
| 137 | + local expect_ns1_ns3="$4" |
| 138 | + local expect_nsrouter_ns2="$5" |
| 139 | + local expect_nsrouter_ns3="$6" |
| 140 | + |
| 141 | + # derived variables |
| 142 | + local testname="test_${ip_proto}_tcp_${traffic_origin}" |
| 143 | + local socat_ipproto |
| 144 | + local ns1_ip |
| 145 | + local ns2_ip |
| 146 | + local ns3_ip |
| 147 | + local ns2_target |
| 148 | + local ns3_target |
| 149 | + local nftables_subject |
| 150 | + local ip_command |
| 151 | + |
| 152 | + # socat 1.8.0 has a bug that requires to specify the IP family to bind (fixed in 1.8.0.1) |
| 153 | + case $ip_proto in |
| 154 | + "ip") |
| 155 | + socat_ipproto="-4" |
| 156 | + ns1_ip=10.0.1.99 |
| 157 | + ns2_ip=10.0.2.99 |
| 158 | + ns3_ip=10.0.3.99 |
| 159 | + ns2_target="tcp:$ns2_ip:8080" |
| 160 | + ns3_target="tcp:$ns3_ip:8080" |
| 161 | + nftables_subject="ip daddr $ns2_ip tcp dport 8080" |
| 162 | + ip_command="ip" |
| 163 | + ;; |
| 164 | + "ip6") |
| 165 | + socat_ipproto="-6" |
| 166 | + ns1_ip=dead:1::99 |
| 167 | + ns2_ip=dead:2::99 |
| 168 | + ns3_ip=dead:3::99 |
| 169 | + ns2_target="tcp:[$ns2_ip]:8080" |
| 170 | + ns3_target="tcp:[$ns3_ip]:8080" |
| 171 | + nftables_subject="ip6 daddr $ns2_ip tcp dport 8080" |
| 172 | + ip_command="ip -6" |
| 173 | + ;; |
| 174 | + *) |
| 175 | + echo "FAIL: unsupported protocol" |
| 176 | + exit 255 |
| 177 | + ;; |
| 178 | + esac |
| 179 | + |
| 180 | + case $traffic_origin in |
| 181 | + # to capture the local originated traffic we need to mark the outgoing |
| 182 | + # traffic so the policy based routing rule redirects it and can be processed |
| 183 | + # in the prerouting chain. |
| 184 | + "local") |
| 185 | + nftables_rules=" |
| 186 | +flush ruleset |
| 187 | +table inet filter { |
| 188 | + chain divert { |
| 189 | + type filter hook prerouting priority 0; policy accept; |
| 190 | + $nftables_subject tproxy $ip_proto to :12345 meta mark set 1 accept |
| 191 | + } |
| 192 | + chain output { |
| 193 | + type route hook output priority 0; policy accept; |
| 194 | + $nftables_subject meta mark set 1 accept |
| 195 | + } |
| 196 | +}" |
| 197 | + ;; |
| 198 | + "forward") |
| 199 | + nftables_rules=" |
| 200 | +flush ruleset |
| 201 | +table inet filter { |
| 202 | + chain divert { |
| 203 | + type filter hook prerouting priority 0; policy accept; |
| 204 | + $nftables_subject tproxy $ip_proto to :12345 meta mark set 1 accept |
| 205 | + } |
| 206 | +}" |
| 207 | + ;; |
| 208 | + *) |
| 209 | + echo "FAIL: unsupported parameter for traffic origin" |
| 210 | + exit 255 |
| 211 | + ;; |
| 212 | + esac |
| 213 | + |
| 214 | + # shellcheck disable=SC2046 # Intended splitting of ip_command |
| 215 | + ip netns exec "$nsrouter" $ip_command rule add fwmark 1 table 100 |
| 216 | + ip netns exec "$nsrouter" $ip_command route add local "${ns2_ip}" dev lo table 100 |
| 217 | + echo "$nftables_rules" | ip netns exec "$nsrouter" nft -f /dev/stdin |
| 218 | + |
| 219 | + timeout "$timeout" ip netns exec "$nsrouter" socat "$socat_ipproto" tcp-listen:12345,fork,ip-transparent SYSTEM:"cat" 2>/dev/null & |
| 220 | + local tproxy_pid=$! |
| 221 | + |
| 222 | + timeout "$timeout" ip netns exec "$ns2" socat "$socat_ipproto" tcp-listen:8080,fork SYSTEM:"echo PONG_NS2" 2>/dev/null & |
| 223 | + local server2_pid=$! |
| 224 | + |
| 225 | + timeout "$timeout" ip netns exec "$ns3" socat "$socat_ipproto" tcp-listen:8080,fork SYSTEM:"echo PONG_NS3" 2>/dev/null & |
| 226 | + local server3_pid=$! |
| 227 | + |
| 228 | + busywait "$BUSYWAIT_TIMEOUT" listener_ready "$nsrouter" 12345 "-t" |
| 229 | + busywait "$BUSYWAIT_TIMEOUT" listener_ready "$ns2" 8080 "-t" |
| 230 | + busywait "$BUSYWAIT_TIMEOUT" listener_ready "$ns3" 8080 "-t" |
| 231 | + |
| 232 | + local result |
| 233 | + # request from ns1 to ns2 (forwarded traffic) |
| 234 | + result=$(echo I_M_PROXIED | ip netns exec "$ns1" socat -t 2 -T 2 STDIO "$ns2_target") |
| 235 | + if [ "$result" == "$expect_ns1_ns2" ] ;then |
| 236 | + echo "PASS: tproxy test $testname: ns1 got reply \"$result\" connecting to ns2" |
| 237 | + else |
| 238 | + echo "ERROR: tproxy test $testname: ns1 got reply \"$result\" connecting to ns2, not \"${expect_ns1_ns2}\" as intended" |
| 239 | + ret=1 |
| 240 | + fi |
| 241 | + |
| 242 | + # request from ns1 to ns3(forwarded traffic) |
| 243 | + result=$(echo I_M_PROXIED | ip netns exec "$ns1" socat -t 2 -T 2 STDIO "$ns3_target") |
| 244 | + if [ "$result" = "$expect_ns1_ns3" ] ;then |
| 245 | + echo "PASS: tproxy test $testname: ns1 got reply \"$result\" connecting to ns3" |
| 246 | + else |
| 247 | + echo "ERROR: tproxy test $testname: ns1 got reply \"$result\" connecting to ns3, not \"$expect_ns1_ns3\" as intended" |
| 248 | + ret=1 |
| 249 | + fi |
| 250 | + |
| 251 | + # request from nsrouter to ns2 (localy originated traffic) |
| 252 | + result=$(echo I_M_PROXIED | ip netns exec "$nsrouter" socat -t 2 -T 2 STDIO "$ns2_target") |
| 253 | + if [ "$result" == "$expect_nsrouter_ns2" ] ;then |
| 254 | + echo "PASS: tproxy test $testname: nsrouter got reply \"$result\" connecting to ns2" |
| 255 | + else |
| 256 | + echo "ERROR: tproxy test $testname: nsrouter got reply \"$result\" connecting to ns2, not \"$expect_nsrouter_ns2\" as intended" |
| 257 | + ret=1 |
| 258 | + fi |
| 259 | + |
| 260 | + # request from nsrouter to ns3 (localy originated traffic) |
| 261 | + result=$(echo I_M_PROXIED | ip netns exec "$nsrouter" socat -t 2 -T 2 STDIO "$ns3_target") |
| 262 | + if [ "$result" = "$expect_nsrouter_ns3" ] ;then |
| 263 | + echo "PASS: tproxy test $testname: nsrouter got reply \"$result\" connecting to ns3" |
| 264 | + else |
| 265 | + echo "ERROR: tproxy test $testname: nsrouter got reply \"$result\" connecting to ns3, not \"$expect_nsrouter_ns3\" as intended" |
| 266 | + ret=1 |
| 267 | + fi |
| 268 | + |
| 269 | + # cleanup |
| 270 | + kill "$tproxy_pid" "$server2_pid" "$server3_pid" 2>/dev/null |
| 271 | + # shellcheck disable=SC2046 # Intended splitting of ip_command |
| 272 | + ip netns exec "$nsrouter" $ip_command rule del fwmark 1 table 100 |
| 273 | + ip netns exec "$nsrouter" $ip_command route flush table 100 |
| 274 | +} |
| 275 | + |
| 276 | + |
| 277 | +test_ipv4_tcp_forward() |
| 278 | +{ |
| 279 | + local traffic_origin="forward" |
| 280 | + local ip_proto="ip" |
| 281 | + local expect_ns1_ns2="I_M_PROXIED" |
| 282 | + local expect_ns1_ns3="PONG_NS3" |
| 283 | + local expect_nsrouter_ns2="PONG_NS2" |
| 284 | + local expect_nsrouter_ns3="PONG_NS3" |
| 285 | + |
| 286 | + test_tproxy "$traffic_origin" \ |
| 287 | + "$ip_proto" \ |
| 288 | + "$expect_ns1_ns2" \ |
| 289 | + "$expect_ns1_ns3" \ |
| 290 | + "$expect_nsrouter_ns2" \ |
| 291 | + "$expect_nsrouter_ns3" |
| 292 | +} |
| 293 | + |
| 294 | +test_ipv4_tcp_local() |
| 295 | +{ |
| 296 | + local traffic_origin="local" |
| 297 | + local ip_proto="ip" |
| 298 | + local expect_ns1_ns2="I_M_PROXIED" |
| 299 | + local expect_ns1_ns3="PONG_NS3" |
| 300 | + local expect_nsrouter_ns2="I_M_PROXIED" |
| 301 | + local expect_nsrouter_ns3="PONG_NS3" |
| 302 | + |
| 303 | + test_tproxy "$traffic_origin" \ |
| 304 | + "$ip_proto" \ |
| 305 | + "$expect_ns1_ns2" \ |
| 306 | + "$expect_ns1_ns3" \ |
| 307 | + "$expect_nsrouter_ns2" \ |
| 308 | + "$expect_nsrouter_ns3" |
| 309 | +} |
| 310 | + |
| 311 | +test_ipv6_tcp_forward() |
| 312 | +{ |
| 313 | + local traffic_origin="forward" |
| 314 | + local ip_proto="ip6" |
| 315 | + local expect_ns1_ns2="I_M_PROXIED" |
| 316 | + local expect_ns1_ns3="PONG_NS3" |
| 317 | + local expect_nsrouter_ns2="PONG_NS2" |
| 318 | + local expect_nsrouter_ns3="PONG_NS3" |
| 319 | + |
| 320 | + test_tproxy "$traffic_origin" \ |
| 321 | + "$ip_proto" \ |
| 322 | + "$expect_ns1_ns2" \ |
| 323 | + "$expect_ns1_ns3" \ |
| 324 | + "$expect_nsrouter_ns2" \ |
| 325 | + "$expect_nsrouter_ns3" |
| 326 | +} |
| 327 | + |
| 328 | +test_ipv6_tcp_local() |
| 329 | +{ |
| 330 | + local traffic_origin="local" |
| 331 | + local ip_proto="ip6" |
| 332 | + local expect_ns1_ns2="I_M_PROXIED" |
| 333 | + local expect_ns1_ns3="PONG_NS3" |
| 334 | + local expect_nsrouter_ns2="I_M_PROXIED" |
| 335 | + local expect_nsrouter_ns3="PONG_NS3" |
| 336 | + |
| 337 | + test_tproxy "$traffic_origin" \ |
| 338 | + "$ip_proto" \ |
| 339 | + "$expect_ns1_ns2" \ |
| 340 | + "$expect_ns1_ns3" \ |
| 341 | + "$expect_nsrouter_ns2" \ |
| 342 | + "$expect_nsrouter_ns3" |
| 343 | +} |
| 344 | + |
| 345 | +if test_ping; then |
| 346 | + # queue bypass works (rules were skipped, no listener) |
| 347 | + echo "PASS: ${ns1} can reach ${ns2}" |
| 348 | +else |
| 349 | + echo "FAIL: ${ns1} cannot reach ${ns2}: $ret" 1>&2 |
| 350 | + exit $ret |
| 351 | +fi |
| 352 | + |
| 353 | +test_ipv4_tcp_forward |
| 354 | +test_ipv4_tcp_local |
| 355 | +test_ipv6_tcp_forward |
| 356 | +test_ipv6_tcp_local |
| 357 | + |
| 358 | +exit $ret |
0 commit comments