通信に割り当てるポートの範囲が事前に定義されている

インターネットプロトコル(IP)を用いた通信を行うため、TCP/IPプロトコルスタックが事前に定義されている範囲内から自動的に割り当てるポートをエフェメラルポートという。

RFC 6056ではポート番号1024から65535までの範囲を使うよう提言されている。

自動割り当て可能なポートの範囲を取得する

Linuxでは/proc/sys/net/ipv4/ip_local_port_rangeでエフェメラルポートの範囲を取得できる。

$ docker run -it debian:bookworm cat /proc/sys/net/ipv4/ip_local_port_range
32768   60999

macOSではsysctlコマンドでエフェメラルポートの範囲を取得できる。

$ sysctl net.inet.ip.portrange.first net.inet.ip.portrange.last
net.inet.ip.portrange.first: 49152
net.inet.ip.portrange.last: 65535

ランダムなポートを取得する

Linuxではshufコマンドでランダムなポートを取得できる。

$ docker run -it debian:bookworm sh -c "shuf -i \$(cat /proc/sys/net/ipv4/ip_local_port_range | awk '{print \$1 \"-\" \$2}') -n 1"
51234

macOSではjotコマンドでランダムなポートを取得できる。

$ jot -r 1 $(sysctl net.inet.ip.portrange.first | awk '{print $2}') $(sysctl net.inet.ip.portrange.last | awk '{print $2}')
51234

自動割り当て可能なポートの範囲内で空きポートを検索する

ssコマンドはソケットの統計情報を表示するコマンドでnetstatコマンドと同様の情報を表示でき、使用中のソケットが返されるため空きポートの検索に使用できる。

$ docker run -it debian:bookworm sh -c "\
    apt-get update && apt-get install -y --no-install-recommends iproute2 ; \
    for port in \$(shuf -i \$(cat /proc/sys/net/ipv4/ip_local_port_range | awk '{print \$1 \"-\" \$2}') -n 100) ; \
    do ss -H -ltn \"sport = :\$port\" | grep -q . > /dev/null || break ; done ; \
    echo \$port"
51234

ncコマンドはネットワーク診断ツールでポートが使用中かどうかを確認できる。

$ docker run -it debian:bookworm sh -c "\
    apt-get update && apt-get install -y --no-install-recommends netcat-openbsd ; \
    for port in \$(shuf -i \$(cat /proc/sys/net/ipv4/ip_local_port_range | awk '{print \$1 \"-\" \$2}') -n 100) ; \
    do nc -z localhost \$port > /dev/null || break ; done ; \
    echo \$port"
51234

macOSではnetstatコマンドとncコマンドが標準搭載されているため空きポートの検索に使用できる。

for port in $(jot -r 100 $(sysctl net.inet.ip.portrange.first | awk '{print $2}') $(sysctl net.inet.ip.portrange.last | awk '{print $2}'))
do
    netstat -a -n | grep "\*\.$port.*LISTEN" > /dev/null || break
done
echo $port
for port in $(jot -r 100 $(sysctl net.inet.ip.portrange.first | awk '{print $2}') $(sysctl net.inet.ip.portrange.last | awk '{print $2}'))
do
    nc -z localhost $port > /dev/null || break
done
echo $port