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 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451
|
# -*- bash -*-
_cached_has_slirp4netns=
### Feature Checks #############################################################
# has_ipv4() - Check if one default route is available for IPv4
function has_ipv4() {
[ -n "$(ip -j -4 route show | jq -rM '.[] | select(.dst == "default")')" ]
}
# has_ipv6() - Check if one default route is available for IPv6
function has_ipv6() {
[ -n "$(ip -j -6 route show | jq -rM '.[] | select(.dst == "default")')" ]
}
# skip_if_no_ipv4() - Skip current test if IPv4 traffic can't be routed
# $1: Optional message to display
function skip_if_no_ipv4() {
if ! has_ipv4; then
local msg=$(_add_label_if_missing "$1" "IPv4")
skip "${msg:-not applicable with no routable IPv4}"
fi
}
# skip_if_no_ipv6() - Skip current test if IPv6 traffic can't be routed
# $1: Optional message to display
function skip_if_no_ipv6() {
if ! has_ipv6; then
local msg=$(_add_label_if_missing "$1" "IPv6")
skip "${msg:-not applicable with no routable IPv6}"
fi
}
# has_slirp4netns - Check if the slirp4netns(1) command is available
function has_slirp4netns() {
if [[ -z "$_cached_has_slirp4netns" ]]; then
_cached_has_slirp4netns=n
run_podman info --format '{{.Host.Slirp4NetNS.Executable}}'
if [[ -n "$output" ]]; then
_cached_has_slirp4netns=y
fi
fi
test "$_cached_has_slirp4netns" = "y"
}
### procfs access ##############################################################
# ipv6_to_procfs() - RFC 5952 IPv6 address text representation to procfs format
# $1: Address in any notation described by RFC 5952
function ipv6_to_procfs() {
local addr="${1}"
# Add leading zero if missing
case ${addr} in
"::"*) addr=0"${addr}" ;;
esac
# Double colon can mean any number of all-zero fields. Expand to fill
# as many colons as are missing. (This will not be a valid IPv6 form,
# but we don't need it for long). E.g., 0::1 -> 0:::::::1
case ${addr} in
*"::"*)
# All the colons in the address
local colons
colons=$(tr -dc : <<<$addr)
# subtract those from a string of eight colons; this gives us
# a string of two to six colons...
local pad
pad=$(sed -e "s/$colons//" <<<":::::::")
# ...which we then inject in place of the double colon.
addr=$(sed -e "s/::/::$pad/" <<<$addr)
;;
esac
# Print as a contiguous string of zero-filled 16-bit words
# (The additional ":" below is needed because 'read -d x' actually
# means "x is a TERMINATOR, not a delimiter")
local group
while read -d : group; do
printf "%04X" "0x${group:-0}"
done <<<"${addr}:"
}
# __ipv4_to_procfs() - Print bytes in hexadecimal notation reversing arguments
# $@: IPv4 address as separate bytes
function __ipv4_to_procfs() {
printf "%02X%02X%02X%02X" ${4} ${3} ${2} ${1}
}
# ipv4_to_procfs() - IPv4 address representation to big-endian procfs format
# $1: Text representation of IPv4 address
function ipv4_to_procfs() {
IFS='.' read -r o1 o2 o3 o4 <<< $1
__ipv4_to_procfs $o1 $o2 $o3 $o4
}
### Addresses, Routes, Links ###################################################
# ipv4_get_addr_global() - Print first global IPv4 address reported by netlink
# $1: Optional output of 'ip -j -4 address show' from a different context
function ipv4_get_addr_global() {
local expr='[.[].addr_info[] | select(.scope=="global")] | .[0].local'
echo "${1:-$(ip -j -4 address show)}" | jq -rM "${expr}"
}
# ipv6_get_addr_global() - Print first global IPv6 address reported by netlink
# $1: Optional output of 'ip -j -6 address show' from a different context
function ipv6_get_addr_global() {
local expr='[.[].addr_info[] | select(.scope=="global")] | .[0].local'
echo "${1:-$(ip -j -6 address show)}" | jq -rM "${expr}"
}
# random_rfc1918_subnet() - Pseudorandom unused subnet in 172.16/12 prefix
#
# Use the class B set, because much of our CI environment (Google, RH)
# already uses up much of the class A, and it's really hard to test
# if a block is in use.
#
# This returns THREE OCTETS! It is up to our caller to append .0/24, .255, &c.
#
function random_rfc1918_subnet() {
local retries=1024
while [ "$retries" -gt 0 ];do
# 172.16.0.0 -> 172.31.255.255
local n1=172
local n2=$(( 16 + $RANDOM & 15 ))
local n3=$(( $RANDOM & 255 ))
if ! subnet_in_use $n1 $n2 $n3; then
echo "$n1.$n2.$n3"
return
fi
retries=$(( retries - 1 ))
done
die "Could not find a random not-in-use rfc1918 subnet"
}
# subnet_in_use() - true if subnet already routed on host
function subnet_in_use() {
local subnet_script=${PODMAN_TMPDIR-/var/tmp}/subnet-in-use
rm -f $subnet_script
# This would be a nightmare to do in bash. ipcalc, ipcalc-ng, sipcalc
# would be nice but are unavailable some environments (cough RHEL).
# Likewise python/perl netmask modules. So, use bare-bones perl.
cat >$subnet_script <<"EOF"
#!/usr/bin/env perl
use strict;
use warnings;
# 3 octets, in binary: 172.16.x -> 1010 1100 0000 1000 xxxx xxxx ...
my $subnet_to_check = sprintf("%08b%08b%08b", @ARGV);
my $found = 0;
# Input is "ip route list", one or more lines like '10.0.0.0/8 via ...'
while (<STDIN>) {
# Only interested in x.x.x.x/n lines
if (m!^([\d.]+)/(\d+)!) {
my ($ip, $bits) = ($1, $2);
# Our caller has /24 granularity, so treat /30 on host as /24.
$bits = 24 if $bits > 24;
# Temporary: entire subnet as binary string. 4 octets, split,
# then represented as a 32-bit binary string.
my $net = sprintf("%08b%08b%08b%08b", split(/\./, $ip));
# Now truncate those 32 bits down to the route's netmask size.
# This is the actual subnet range in use on the host.
my $net_truncated = sprintf("%.*s", $bits, $net);
# Desired subnet is in use if it matches a host route prefix
# print STDERR "--- $subnet_to_check in $net_truncated (@ARGV in $ip/$bits)\n";
$found = 1 if $subnet_to_check =~ /^$net_truncated/;
}
}
# Convert to shell exit status (0 = success)
exit !$found;
EOF
chmod 755 $subnet_script
# This runs 'ip route list', converts x.x.x.x/n to its binary prefix,
# then checks if our desired subnet matches that prefix (i.e. is in
# that range). Existing routes with size greater than 24 are
# normalized to /24 because that's the granularity of our
# random_rfc1918_subnet code.
#
# Contrived examples:
# 127.0.0.0/1 -> 0
# 128.0.0.0/1 -> 1
# 10.0.0.0/8 -> 00001010
#
# I'm so sorry for the ugliness.
ip route list | $subnet_script $*
}
# ipv4_get_route_default() - Print first default IPv4 route reported by netlink
# $1: Optional output of 'ip -j -4 route show' from a different context
function ipv4_get_route_default() {
local jq_gw='[.[] | select(.dst == "default").gateway] | .[0]'
local jq_nh='[.[] | select(.dst == "default").nexthops[0].gateway] | .[0]'
local out
out="$(echo "${1:-$(ip -j -4 route show)}" | jq -rM "${jq_gw}")"
if [ "${out}" = "null" ]; then
out="$(echo "${1:-$(ip -j -4 route show)}" | jq -rM "${jq_nh}")"
fi
echo "${out}"
}
# ipv6_get_route_default() - Print first default IPv6 route reported by netlink
# $1: Optional output of 'ip -j -6 route show' from a different context
function ipv6_get_route_default() {
local jq_gw='[.[] | select(.dst == "default").gateway] | .[0]'
local jq_nh='[.[] | select(.dst == "default").nexthops[0].gateway] | .[0]'
local out
out="$(echo "${1:-$(ip -j -6 route show)}" | jq -rM "${jq_gw}")"
if [ "${out}" = "null" ]; then
out="$(echo "${1:-$(ip -j -6 route show)}" | jq -rM "${jq_nh}")"
fi
echo "${out}"
}
# ether_get_mtu() - Get MTU of first Ethernet-like link
# $1: Optional output of 'ip -j link show' from a different context
function ether_get_mtu() {
local jq_expr='[.[] | select(.link_type == "ether").mtu] | .[0]'
echo "${1:-$(ip -j link show)}" | jq -rM "${jq_expr}"
}
# ether_get_name() - Get name of first Ethernet-like interface
# $1: Optional output of 'ip -j link show' from a different context
function ether_get_name() {
local jq_expr='[.[] | select(.link_type == "ether").ifname] | .[0]'
echo "${1:-$(ip -j link show)}" | jq -rM "${jq_expr}"
}
### Ports and Ranges ###########################################################
# reserve_port() - create a lock file reserving a port, or return false
function reserve_port() {
local port=$1
mkdir -p $PORT_LOCK_DIR
local lockfile=$PORT_LOCK_DIR/$port
local locktmp=$PORT_LOCK_DIR/.$port.$$
echo $BATS_SUITE_TEST_NUMBER >$locktmp
if ln $locktmp $lockfile; then
rm -f $locktmp
return
fi
# Port already reserved
rm -f $locktmp
false
}
# unreserve_port() - free a temporarily-reserved port
function unreserve_port() {
local port=$1
local lockfile=$PORT_LOCK_DIR/$port
-e $lockfile || die "Cannot unreserve non-reserved port $port"
assert "$(< $lockfile)" = "$BATS_SUITE_TEST_NUMBER" \
"Port $port is not reserved by this test"
rm -f $lockfile
}
# random_free_port() - Get unbound port with pseudorandom number
# $1: Optional, dash-separated interval, [5000, 5999] by default
# $2: Optional binding address, any IPv4 address by default
# $3: Optional protocol, tcp or udp
function random_free_port() {
local range=${1:-5000-5999}
local address=${2:-0.0.0.0}
local protocol=${3:-tcp}
local port
for port in $(shuf -i ${range}); do
# First make sure no other tests are using it
if reserve_port $port; then
if port_is_free $port $address $protocol; then
echo $port
return
fi
unreserve_port $port
fi
done
die "Could not find open port in range $range"
}
# random_free_port_range() - Get range of unbound ports with pseudorandom start
# $1: Size of range (i.e. number of ports)
# $2: Optional binding address, any IPv4 address by default
# $3: Optional protocol, tcp or udp
function random_free_port_range() {
local size=${1?Usage: random_free_port_range SIZE [ADDRESS [tcp|udp]]}
local address=${2:-0.0.0.0}
local protocol=${3:-tcp}
local maxtries=10
while [[ $maxtries -gt 0 ]]; do
local firstport=$(random_free_port)
local lastport=
for i in $(seq 1 $((size - 1))); do
lastport=$((firstport + i))
if ! reserve_port $lastport; then
lastport=
break
fi
if ! port_is_free $lastport $address $protocol; then
echo "# port $lastport is in use; trying another." >&3
unreserve_port $lastport
lastport=
break
fi
done
if [[ -n "$lastport" ]]; then
echo "$firstport-$lastport"
return
fi
unreserve_port $firstport
maxtries=$((maxtries - 1))
done
die "Could not find free port range with size $size"
}
# port_is_bound() - Check if TCP or UDP port is bound for a given address
# $1: Port number
# $2: Optional protocol, or optional IPv4 or IPv6 address, default: tcp
# $3: Optional IPv4 or IPv6 address, or optional protocol, default: any
function port_is_bound() {
local port=${1?Usage: port_is_bound PORT [tcp|udp] [ADDRESS]}
if [ "${2}" = "tcp" ] || [ "${2}" = "udp" ]; then
local address="${3}"
local proto="${2}"
elif [ "${3}" = "tcp" ] || [ "${3}" = "udp" ]; then
local address="${2}"
local proto="${3}"
else
local address="${2}" # Might be empty
local proto="tcp"
fi
# /proc/net/tcp is insufficient: it does not show some rootless ports.
# ss does, so check it first.
run ss -${proto:0:1}nlH sport = $port
if [[ -n "$output" ]]; then
return
fi
port=$(printf %04X ${port})
case "${address}" in
*":"*)
grep -e "^[^:]*: $(ipv6_to_procfs "${address}"):${port} .*" \
-e "^[^:]*: $(ipv6_to_procfs "::0"):${port} .*" \
-q "/proc/net/${proto}6"
;;
*"."*)
grep -e "^[^:]*: $(ipv4_to_procfs "${address}"):${port}" \
-e "^[^:]*: $(ipv4_to_procfs "0.0.0.0"):${port}" \
-e "^[^:]*: $(ipv4_to_procfs "127.0.0.1"):${port}" \
-q "/proc/net/${proto}"
;;
*)
# No address: check both IPv4 and IPv6, for any bound address
grep "^[^:]*: [^:]*:${port} .*" -q "/proc/net/${proto}6" || \
grep "^[^:]*: [^:]*:${port} .*" -q "/proc/net/${proto}"
;;
esac
}
# port_is_free() - Check if TCP or UDP port is free to bind for a given address
# $1: Port number
# $2: Optional protocol, or optional IPv4 or IPv6 address, default: tcp
# $3: Optional IPv4 or IPv6 address, or optional protocol, default: any
function port_is_free() {
! port_is_bound ${@}
}
# wait_for_port() - Return once port is bound (available for use by clients)
# $1: Host or address to check for possible binding
# $2: Port number
# $3: Optional timeout, 5 seconds if not given
function wait_for_port() {
local host=$1
local port=$2
local _timeout=${3:-5}
# Wait
while [ $_timeout -gt 0 ]; do
port_is_bound ${port} "${host}" && return
sleep 1
_timeout=$(( $_timeout - 1 ))
done
die "Timed out waiting for $host:$port"
}
# tcp_port_probe() - Check if a TCP port has an active listener
# $1: Port number
# $2: Optional address, 0.0.0.0 by default
function tcp_port_probe() {
local address="${2:-0.0.0.0}"
(exec echo -n >/dev/tcp/"$address/$1") >/dev/null 2>&1
}
### Pasta Helpers ##############################################################
function default_ifname() {
local jq_expr='[.[] | select(.dst == "default").dev] | .[0]'
local jq_expr_nh='[.[] | select(.dst == "default").nexthops[0].dev] | .[0]'
local ip_ver="${1}"
local out
out="$(ip -j -"${ip_ver}" route show | jq -rM "${jq_expr}")"
if [ "${out}" = "null" ]; then
out="$(ip -j -"${ip_ver}" route show | jq -rM "${jq_expr_nh}")"
fi
echo "${out}"
}
function default_addr() {
local ip_ver="${1}"
local ifname="${2:-$(default_ifname "${ip_ver}")}"
local expr='[.[0].addr_info[] | select(.deprecated != true)][0].local'
ip -j -"${ip_ver}" addr show "${ifname}" | jq -rM "${expr}"
}
|