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  }