github.com/containers/podman/v5@v5.1.0-rc1/test/system/helpers.network.bash (about) 1 # -*- bash -*- 2 3 _cached_has_pasta= 4 _cached_has_slirp4netns= 5 6 ### Feature Checks ############################################################# 7 8 # has_ipv4() - Check if one default route is available for IPv4 9 function has_ipv4() { 10 [ -n "$(ip -j -4 route show | jq -rM '.[] | select(.dst == "default")')" ] 11 } 12 13 # has_ipv6() - Check if one default route is available for IPv6 14 function has_ipv6() { 15 [ -n "$(ip -j -6 route show | jq -rM '.[] | select(.dst == "default")')" ] 16 } 17 18 # skip_if_no_ipv4() - Skip current test if IPv4 traffic can't be routed 19 # $1: Optional message to display 20 function skip_if_no_ipv4() { 21 if ! has_ipv4; then 22 local msg=$(_add_label_if_missing "$1" "IPv4") 23 skip "${msg:-not applicable with no routable IPv4}" 24 fi 25 } 26 27 # skip_if_no_ipv6() - Skip current test if IPv6 traffic can't be routed 28 # $1: Optional message to display 29 function skip_if_no_ipv6() { 30 if ! has_ipv6; then 31 local msg=$(_add_label_if_missing "$1" "IPv6") 32 skip "${msg:-not applicable with no routable IPv6}" 33 fi 34 } 35 36 # has_slirp4netns - Check if the slirp4netns(1) command is available 37 function has_slirp4netns() { 38 if [[ -z "$_cached_has_slirp4netns" ]]; then 39 _cached_has_slirp4netns=n 40 run_podman info --format '{{.Host.Slirp4NetNS.Executable}}' 41 if [[ -n "$output" ]]; then 42 _cached_has_slirp4netns=y 43 fi 44 fi 45 test "$_cached_has_slirp4netns" = "y" 46 } 47 48 # has_pasta() - Check if the pasta(1) command is available 49 function has_pasta() { 50 if [[ -z "$_cached_has_pasta" ]]; then 51 _cached_has_pasta=n 52 run_podman info --format '{{.Host.Pasta.Executable}}' 53 if [[ -n "$output" ]]; then 54 _cached_has_pasta=y 55 fi 56 fi 57 test "$_cached_has_pasta" = "y" 58 } 59 60 # skip_if_no_pasta() - Skip current test if pasta(1) is not available 61 # $1: Optional message to display 62 function skip_if_no_pasta() { 63 if ! has_pasta; then 64 local msg=$(_add_label_if_missing "$1" "pasta") 65 skip "${msg:-not applicable with no pasta binary}" 66 fi 67 } 68 69 70 ### procfs access ############################################################## 71 72 # ipv6_to_procfs() - RFC 5952 IPv6 address text representation to procfs format 73 # $1: Address in any notation described by RFC 5952 74 function ipv6_to_procfs() { 75 local addr="${1}" 76 77 # Add leading zero if missing 78 case ${addr} in 79 "::"*) addr=0"${addr}" ;; 80 esac 81 82 # Double colon can mean any number of all-zero fields. Expand to fill 83 # as many colons as are missing. (This will not be a valid IPv6 form, 84 # but we don't need it for long). E.g., 0::1 -> 0:::::::1 85 case ${addr} in 86 *"::"*) 87 # All the colons in the address 88 local colons 89 colons=$(tr -dc : <<<$addr) 90 # subtract those from a string of eight colons; this gives us 91 # a string of two to six colons... 92 local pad 93 pad=$(sed -e "s/$colons//" <<<":::::::") 94 # ...which we then inject in place of the double colon. 95 addr=$(sed -e "s/::/::$pad/" <<<$addr) 96 ;; 97 esac 98 99 # Print as a contiguous string of zero-filled 16-bit words 100 # (The additional ":" below is needed because 'read -d x' actually 101 # means "x is a TERMINATOR, not a delimiter") 102 local group 103 while read -d : group; do 104 printf "%04X" "0x${group:-0}" 105 done <<<"${addr}:" 106 } 107 108 # __ipv4_to_procfs() - Print bytes in hexadecimal notation reversing arguments 109 # $@: IPv4 address as separate bytes 110 function __ipv4_to_procfs() { 111 printf "%02X%02X%02X%02X" ${4} ${3} ${2} ${1} 112 } 113 114 # ipv4_to_procfs() - IPv4 address representation to big-endian procfs format 115 # $1: Text representation of IPv4 address 116 function ipv4_to_procfs() { 117 IFS='.' read -r o1 o2 o3 o4 <<< $1 118 __ipv4_to_procfs $o1 $o2 $o3 $o4 119 } 120 121 122 ### Addresses, Routes, Links ################################################### 123 124 # ipv4_get_addr_global() - Print first global IPv4 address reported by netlink 125 # $1: Optional output of 'ip -j -4 address show' from a different context 126 function ipv4_get_addr_global() { 127 local expr='[.[].addr_info[] | select(.scope=="global")] | .[0].local' 128 echo "${1:-$(ip -j -4 address show)}" | jq -rM "${expr}" 129 } 130 131 # ipv6_get_addr_global() - Print first global IPv6 address reported by netlink 132 # $1: Optional output of 'ip -j -6 address show' from a different context 133 function ipv6_get_addr_global() { 134 local expr='[.[].addr_info[] | select(.scope=="global")] | .[0].local' 135 echo "${1:-$(ip -j -6 address show)}" | jq -rM "${expr}" 136 } 137 138 # random_rfc1918_subnet() - Pseudorandom unused subnet in 172.16/12 prefix 139 # 140 # Use the class B set, because much of our CI environment (Google, RH) 141 # already uses up much of the class A, and it's really hard to test 142 # if a block is in use. 143 # 144 # This returns THREE OCTETS! It is up to our caller to append .0/24, .255, &c. 145 # 146 function random_rfc1918_subnet() { 147 local retries=1024 148 149 while [ "$retries" -gt 0 ];do 150 # 172.16.0.0 -> 172.31.255.255 151 local n1=172 152 local n2=$(( 16 + $RANDOM & 15 )) 153 local n3=$(( $RANDOM & 255 )) 154 155 if ! subnet_in_use $n1 $n2 $n3; then 156 echo "$n1.$n2.$n3" 157 return 158 fi 159 160 retries=$(( retries - 1 )) 161 done 162 163 die "Could not find a random not-in-use rfc1918 subnet" 164 } 165 166 # subnet_in_use() - true if subnet already routed on host 167 function subnet_in_use() { 168 local subnet_script=${PODMAN_TMPDIR-/var/tmp}/subnet-in-use 169 rm -f $subnet_script 170 171 # This would be a nightmare to do in bash. ipcalc, ipcalc-ng, sipcalc 172 # would be nice but are unavailable some environments (cough RHEL). 173 # Likewise python/perl netmask modules. So, use bare-bones perl. 174 cat >$subnet_script <<"EOF" 175 #!/usr/bin/env perl 176 177 use strict; 178 use warnings; 179 180 # 3 octets, in binary: 172.16.x -> 1010 1100 0000 1000 xxxx xxxx ... 181 my $subnet_to_check = sprintf("%08b%08b%08b", @ARGV); 182 183 my $found = 0; 184 185 # Input is "ip route list", one or more lines like '10.0.0.0/8 via ...' 186 while (<STDIN>) { 187 # Only interested in x.x.x.x/n lines 188 if (m!^([\d.]+)/(\d+)!) { 189 my ($ip, $bits) = ($1, $2); 190 191 # Our caller has /24 granularity, so treat /30 on host as /24. 192 $bits = 24 if $bits > 24; 193 194 # Temporary: entire subnet as binary string. 4 octets, split, 195 # then represented as a 32-bit binary string. 196 my $net = sprintf("%08b%08b%08b%08b", split(/\./, $ip)); 197 198 # Now truncate those 32 bits down to the route's netmask size. 199 # This is the actual subnet range in use on the host. 200 my $net_truncated = sprintf("%.*s", $bits, $net); 201 202 # Desired subnet is in use if it matches a host route prefix 203 # print STDERR "--- $subnet_to_check in $net_truncated (@ARGV in $ip/$bits)\n"; 204 $found = 1 if $subnet_to_check =~ /^$net_truncated/; 205 } 206 } 207 208 # Convert to shell exit status (0 = success) 209 exit !$found; 210 EOF 211 212 chmod 755 $subnet_script 213 214 # This runs 'ip route list', converts x.x.x.x/n to its binary prefix, 215 # then checks if our desired subnet matches that prefix (i.e. is in 216 # that range). Existing routes with size greater than 24 are 217 # normalized to /24 because that's the granularity of our 218 # random_rfc1918_subnet code. 219 # 220 # Contrived examples: 221 # 127.0.0.0/1 -> 0 222 # 128.0.0.0/1 -> 1 223 # 10.0.0.0/8 -> 00001010 224 # 225 # I'm so sorry for the ugliness. 226 ip route list | $subnet_script $* 227 } 228 229 # ipv4_get_route_default() - Print first default IPv4 route reported by netlink 230 # $1: Optional output of 'ip -j -4 route show' from a different context 231 function ipv4_get_route_default() { 232 local jq_gw='[.[] | select(.dst == "default").gateway] | .[0]' 233 local jq_nh='[.[] | select(.dst == "default").nexthops[0].gateway] | .[0]' 234 local out 235 236 out="$(echo "${1:-$(ip -j -4 route show)}" | jq -rM "${jq_gw}")" 237 if [ "${out}" = "null" ]; then 238 out="$(echo "${1:-$(ip -j -4 route show)}" | jq -rM "${jq_nh}")" 239 fi 240 241 echo "${out}" 242 } 243 244 # ipv6_get_route_default() - Print first default IPv6 route reported by netlink 245 # $1: Optional output of 'ip -j -6 route show' from a different context 246 function ipv6_get_route_default() { 247 local jq_gw='[.[] | select(.dst == "default").gateway] | .[0]' 248 local jq_nh='[.[] | select(.dst == "default").nexthops[0].gateway] | .[0]' 249 local out 250 251 out="$(echo "${1:-$(ip -j -6 route show)}" | jq -rM "${jq_gw}")" 252 if [ "${out}" = "null" ]; then 253 out="$(echo "${1:-$(ip -j -6 route show)}" | jq -rM "${jq_nh}")" 254 fi 255 256 echo "${out}" 257 } 258 259 # ether_get_mtu() - Get MTU of first Ethernet-like link 260 # $1: Optional output of 'ip -j link show' from a different context 261 function ether_get_mtu() { 262 local jq_expr='[.[] | select(.link_type == "ether").mtu] | .[0]' 263 echo "${1:-$(ip -j link show)}" | jq -rM "${jq_expr}" 264 } 265 266 # ether_get_name() - Get name of first Ethernet-like interface 267 # $1: Optional output of 'ip -j link show' from a different context 268 function ether_get_name() { 269 local jq_expr='[.[] | select(.link_type == "ether").ifname] | .[0]' 270 echo "${1:-$(ip -j link show)}" | jq -rM "${jq_expr}" 271 } 272 273 274 ### Ports and Ranges ########################################################### 275 276 # random_free_port() - Get unbound port with pseudorandom number 277 # $1: Optional, dash-separated interval, [5000, 5999] by default 278 # $2: Optional binding address, any IPv4 address by default 279 # $3: Optional protocol, tcp or udp 280 function random_free_port() { 281 local range=${1:-5000-5999} 282 local address=${2:-0.0.0.0} 283 local protocol=${3:-tcp} 284 285 local port 286 for port in $(shuf -i ${range}); do 287 if port_is_free $port $address $protocol; then 288 echo $port 289 return 290 fi 291 done 292 293 die "Could not find open port in range $range" 294 } 295 296 # random_free_port_range() - Get range of unbound ports with pseudorandom start 297 # $1: Size of range (i.e. number of ports) 298 # $2: Optional binding address, any IPv4 address by default 299 # $3: Optional protocol, tcp or udp 300 function random_free_port_range() { 301 local size=${1?Usage: random_free_port_range SIZE [ADDRESS [tcp|udp]]} 302 local address=${2:-0.0.0.0} 303 local protocol=${3:-tcp} 304 305 local maxtries=10 306 while [[ $maxtries -gt 0 ]]; do 307 local firstport=$(random_free_port) 308 local lastport= 309 for i in $(seq 1 $((size - 1))); do 310 lastport=$((firstport + i)) 311 if ! port_is_free $lastport $address $protocol; then 312 echo "# port $lastport is in use; trying another." >&3 313 lastport= 314 break 315 fi 316 done 317 if [[ -n "$lastport" ]]; then 318 echo "$firstport-$lastport" 319 return 320 fi 321 322 maxtries=$((maxtries - 1)) 323 done 324 325 die "Could not find free port range with size $size" 326 } 327 328 # port_is_bound() - Check if TCP or UDP port is bound for a given address 329 # $1: Port number 330 # $2: Optional protocol, or optional IPv4 or IPv6 address, default: tcp 331 # $3: Optional IPv4 or IPv6 address, or optional protocol, default: any 332 function port_is_bound() { 333 local port=${1?Usage: port_is_bound PORT [tcp|udp] [ADDRESS]} 334 335 if [ "${2}" = "tcp" ] || [ "${2}" = "udp" ]; then 336 local address="${3}" 337 local proto="${2}" 338 elif [ "${3}" = "tcp" ] || [ "${3}" = "udp" ]; then 339 local address="${2}" 340 local proto="${3}" 341 else 342 local address="${2}" # Might be empty 343 local proto="tcp" 344 fi 345 346 # /proc/net/tcp is insufficient: it does not show some rootless ports. 347 # ss does, so check it first. 348 run ss -${proto:0:1}nlH sport = $port 349 if [[ -n "$output" ]]; then 350 return 351 fi 352 353 port=$(printf %04X ${port}) 354 case "${address}" in 355 *":"*) 356 grep -e "^[^:]*: $(ipv6_to_procfs "${address}"):${port} .*" \ 357 -e "^[^:]*: $(ipv6_to_procfs "::0"):${port} .*" \ 358 -q "/proc/net/${proto}6" 359 ;; 360 *"."*) 361 grep -e "^[^:]*: $(ipv4_to_procfs "${address}"):${port}" \ 362 -e "^[^:]*: $(ipv4_to_procfs "0.0.0.0"):${port}" \ 363 -e "^[^:]*: $(ipv4_to_procfs "127.0.0.1"):${port}" \ 364 -q "/proc/net/${proto}" 365 ;; 366 *) 367 # No address: check both IPv4 and IPv6, for any bound address 368 grep "^[^:]*: [^:]*:${port} .*" -q "/proc/net/${proto}6" || \ 369 grep "^[^:]*: [^:]*:${port} .*" -q "/proc/net/${proto}" 370 ;; 371 esac 372 } 373 374 # port_is_free() - Check if TCP or UDP port is free to bind for a given address 375 # $1: Port number 376 # $2: Optional protocol, or optional IPv4 or IPv6 address, default: tcp 377 # $3: Optional IPv4 or IPv6 address, or optional protocol, default: any 378 function port_is_free() { 379 ! port_is_bound ${@} 380 } 381 382 # wait_for_port() - Return once port is bound (available for use by clients) 383 # $1: Host or address to check for possible binding 384 # $2: Port number 385 # $3: Optional timeout, 5 seconds if not given 386 function wait_for_port() { 387 local host=$1 388 local port=$2 389 local _timeout=${3:-5} 390 391 # Wait 392 while [ $_timeout -gt 0 ]; do 393 port_is_bound ${port} "${host}" && return 394 sleep 1 395 _timeout=$(( $_timeout - 1 )) 396 done 397 398 die "Timed out waiting for $host:$port" 399 } 400 401 # tcp_port_probe() - Check if a TCP port has an active listener 402 # $1: Port number 403 # $2: Optional address, 0.0.0.0 by default 404 function tcp_port_probe() { 405 local address="${2:-0.0.0.0}" 406 407 : | nc "${address}" "${1}" 408 } 409 410 ### Pasta Helpers ############################################################## 411 412 function default_ifname() { 413 local jq_expr='[.[] | select(.dst == "default").dev] | .[0]' 414 local jq_expr_nh='[.[] | select(.dst == "default").nexthops[0].dev] | .[0]' 415 local ip_ver="${1}" 416 local out 417 418 out="$(ip -j -"${ip_ver}" route show | jq -rM "${jq_expr}")" 419 if [ "${out}" = "null" ]; then 420 out="$(ip -j -"${ip_ver}" route show | jq -rM "${jq_expr_nh}")" 421 fi 422 423 echo "${out}" 424 } 425 426 function default_addr() { 427 local ip_ver="${1}" 428 local ifname="${2:-$(default_ifname "${ip_ver}")}" 429 430 local expr='.[0] | .addr_info[0].local' 431 ip -j -"${ip_ver}" addr show "${ifname}" | jq -rM "${expr}" 432 }