未使用のローカルポートを見つける最も簡単な方法は何ですか?


52

未使用のローカルポートを見つける最も簡単な方法は何ですか?

現在、私はこれに似たものを使用しています:

port=$RANDOM
quit=0

while [ "$quit" -ne 1 ]; do
  netstat -a | grep $port >> /dev/null
  if [ $? -gt 0 ]; then
    quit=1
  else
    port=`expr $port + 1`
  fi
done

それはひどく回り道をしているので、私が見逃したビルトインのようなもっと簡単な道があるのだろうかと思っています。


2
どうしてそんなことをしたいのですか?それは本質的に際立っています(そして非効率的です-そして、少なくとも-nnetstatとより選択的なgrepに追加します)。それを行う方法は、必要なモードでポートを試行して開き、使用できない場合は別のポートを試行することです。
マット

1
@Mat ssh -DSOCKSサーバーとして使用するオープンポートを自動的に見つけようとしています。
-mybuddymichael

回答:


24

アプリケーションがサポートしている場合は、ポート0をアプリケーションに渡すことができます。アプリケーションがこれをカーネルに渡すと、ポートはリクエスト時に動的に割り当てられ、使用されないことが保証されます(すべてのポートが既に使用されている場合、割り当ては失敗します)。

それ以外の場合は、これを手動で行うことができます。回答のスクリプトには競合状態があります。それを回避する唯一の方法は、スクリプトを開こうとすることで、開いているかどうかをアトミックにチェックすることです。ポートが使用中の場合、プログラムはポートを開くことに失敗して終了するはずです。

たとえば、GNU netcatでリッスンしようとしているとします。

#!/bin/bash
read lower_port upper_port < /proc/sys/net/ipv4/ip_local_port_range
while :; do
    for (( port = lower_port ; port <= upper_port ; port++ )); do
        nc -l -p "$port" 2>/dev/null && break 2
    done
done

1
@Lekensteyn:ここで競合状態はどこにありますか?
クリスダウン14年

1
このポートは、使用可能な最初のポートを使用しようとします。2つの並行プロセスがある場合、チェックされたばかりのポートが再利用される可能性があります。答えを読み直すと、すべてのポートが使い果たされるまで、使用可能なポートでバインドを再試行することをお勧めしているようです。問題のプログラムが「使用中のポート」と他のエラーを区別できると仮定すると、それは問題ないはずです(ただし、ランダム化することで予測不可能性が改善されます)。
Lekensteyn

1
@Lekensteynポートバインディングが成功すると、カーネルがEADDRINUSEを返します。これを再度使用しようとすると、「チェックしたばかりのポートが再利用される可能性はありません」。
クリスダウン14年

はい、ループを終了$portし、実際のプログラムでを使用すると誤って想定しましたwhile ...; done; program --port $port
Lekensteyn

マニュアルページから:-p source_port ncが使用するソースポートを指定します。権限の制限と可用性が条件となります。このオプションを-lオプションと併用するとエラーになります。
僧ksたち

54

私の解決策は、ポート0にバインドすることです。これは、カーネルにip_local_port_rangeからポートを割り当てるように要求します。次に、ソケットを閉じて、構成でそのポート番号を使用します。

これは、カーネルが絶対に必要になるまでポート番号を再利用しないためです。ポート0への後続のバインドは、異なるポート番号を割り当てます。Pythonコード:

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('', 0))
addr = s.getsockname()
print addr[1]
s.close()

これにより、ポートの数だけが得られます。60123

このプログラムを10000回実行します(これらを同時に実行する必要があります)。10000個の異なるポート番号を取得します。したがって、ポートを使用するのはかなり安全だと思います。


20
ここに1行があります(Python 2およびPython 3で有効):python -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1]); s.close()'
Lekensteyn 14年

4
上記の実験を実行しましたが、すべての結果がユニークではありませんでした。私のヒストグラムは次の{ 1: 7006, 2: 1249, 3: 151, 4: 8, 5: 1, 6: 1}
とおりでした。– bukzor

2
ポートがファイアウォールによってブロックされていないこと、または開いているポートのみを検索することをチェックに追加する簡単な方法はありますか?
マークラカタ

1
@dshepherd前のポートを閉じない場合(そして最後に一度にすべて閉じた場合)、異なるポートを取得できると思います。
フランクリンゆう

1
Ruby 2.3.1のライナー:ruby -e 'puts Addrinfo.tcp("", 0).bind { |s| s.local_address.ip_port }'
フランクリンYu

12

一発ギャグ

すぐに目的を果たし、任意の範囲で任意の数のポートを取得できるようにする素敵なワンライナーをまとめました(ここでは読みやすくするために4行に分割しています)。

comm -23 \
<(seq "$FROM" "$TO" | sort) \
<(ss -tan | awk '{print $4}' | cut -d':' -f2 | grep '[0-9]\{1,5\}' | sort -u) \
| shuf | head -n "$HOWMANY"

1行ずつ

commアルファベット順にソートして表示する必要がある2つのファイルの行を比較するユーティリティです。最初のファイルにのみ表示される行、2番目のファイルにのみ表示される行、および共通行の3つの列を出力します。指定-23することで、後者の列を抑制し、最初の列のみを保持します。これを使用して、テキスト行のシーケンスとして表される2つのセットの差を取得できます。comm ここで知りまし

最初のファイルは、選択可能なポートの範囲です。seqからの番号のソートされたシーケンスを生成$FROM$TOます。結果は(数値ではなく)アルファベット順にソートさcommれ、プロセス置換を使用して最初のファイルとしてパイプされます。

2番目のファイルが、我々は呼び出すことによって取得することを、ポートのソートされたリストであるss(とコマンドを-t、TCPポートを意味する-a-設立とリスニング-とすべてを意味する-n、と言う、解決しようとしていない-数値22ssh)。次にawk、ローカルアドレスとポートを含むの4番目の列のみを選択します。cutアドレスとポートを:区切り文字で分割し、後者のみを保持するために使用します(-f2)。ssまた、ヘッダーを出力しgrepます。これは、5を超えない空ではない数字のシーケンスに対してpingを実行することで削除します。その後、重複なしでingすることcommにより、要件に準拠します。sort-u

今、私たちは私たちができることを、開いているポートのソートされたリストを持ってshuf、最初つかむためにFLE "$HOWMANY"を持つものをhead -n

プライベート範囲(49152-65535)で3つのランダムに開いているポートを取得します

comm -23 <(seq 49152 65535 | sort) <(ss -tan | awk '{print $4}' | cut -d':' -f2 | grep "[0-9]\{1,5\}" | sort -u) | shuf | head -n 3

たとえば返すことができます

54930
57937
51399

ノート

  • in に切り替え-t-uss代わりに無料のUDPポートを取得します。
  • 使用可能なポートをランダムではなく数値的にソートshufするsort -n場合に置き換えます

11
#!/bin/bash
read LOWERPORT UPPERPORT < /proc/sys/net/ipv4/ip_local_port_range
while :
do
        PORT="`shuf -i $LOWERPORT-$UPPERPORT -n 1`"
        ss -lpn | grep -q ":$PORT " || break
done
echo $PORT

クリスダウンへのクレジット


6

どうやらTCP接続は、bash / zshのLinuxでファイル記述子として使用できるようです。次の関数はその手法を使用しており、netcat / telnetを呼び出すよりも高速である必要があります。

function EPHEMERAL_PORT() {
    LOW_BOUND=49152
    RANGE=16384
    while true; do
        CANDIDATE=$[$LOW_BOUND + ($RANDOM % $RANGE)]
        (echo "" >/dev/tcp/127.0.0.1/${CANDIDATE}) >/dev/null 2>&1
        if [ $? -ne 0 ]; then
            echo $CANDIDATE
            break
        fi
    done
}

使用方法:出力を変数にバインドし、スクリプトで使用します。Ubuntu 16.04でテスト済み

root@ubuntu:~> EPHEMERAL_PORT
59453
root@ubuntu:~> PORT=$(EPHEMERAL_PORT)

で動作ksh93も。
fpmurphy

UPORTを32768に変更しても、EG 35835を取得できます。RANDOMは[0,32767]の数値を返します。これを最大値よりも大きい数に変更しても効果はありません。のようなものが欲しい$[$LPORT + ($RANDOM % ($UPORT-$LPORT))]
lxs

それ以外はかなりクールです!
lxs

\nただし、これはリスニングポートに送信されます-n。これはまだ接続を開こうとしますが、何も送信せずにすぐに切断します。
ステファンクト

4

これは、使用中のすべてのポートを丸andみし、3000以降の最初の使用可能なポートを提供する、クロスプラットフォームで効率的な「oneliner」です。

netstat -aln | awk '
  $6 == "LISTEN" {
    if ($4 ~ "[.:][0-9]+$") {
      split($4, a, /[:.]/);
      port = a[length(a)];
      p[port] = 1
    }
  }
  END {
    for (i = 3000; i < 65000 && p[i]; i++){};
    if (i == 65000) {exit 1};
    print i
  }
'

単純にすべての行を結合して、1行にすることができます。別のポート番号から最初に利用可能にしたい場合iは、forループ内で割り当てを変更します。

MacとLinuxの両方で動作するため、[:.]正規表現が必要です。


なぜTCP(6)ソケットを見るだけ-aではないの-tですか?
ステファンクト

そして、私たちがそれに取り組んでいる間に、の出力を解析するss -Htnlことはより良いかもしれません(そしてより速く!- はそれ気にしているわけではありません:P)。
ステファンクト

@stefanct BSD netstatには-t、少なくともAppleに同梱されているものssはなく、macOSにも存在しません。netstat -alnSolarisでも動作します。
w00t

3

Linuxでは、次のようなことができます。

ss -tln | 
  awk 'NR > 1{gsub(/.*:/,"",$4); print $4}' |
  sort -un |
  awk -v n=1080 '$0 < n {next}; $0 == n {n++; next}; {exit}; END {print n}'

1080を超える最初の空きポートを見つけるにはssh -D、ループバックインターフェイスにバインドすることに注意してください。したがって、理論的には、ソケットが別のアドレスにバインドしている場合はポート1080を再利用できます。別の方法は、実際に試してバインドすることです。

perl -MSocket -le 'socket S, PF_INET, SOCK_STREAM,getprotobyname("tcp");
  $port = 1080;
  ++$port until bind S, sockaddr_in($port,inet_aton("127.1"));
  print $port'

ただし、これには、ポートを開こうとしてから実際に使用するまでの間に競合状態が含まれます。
クリスダウン

@ChrisDown、確かに、しかし、ssh -Dでは、私はこれ以上良い選択肢を見つけることができません。の-O forwardオプションはssh、転送が失敗してもエラーを返しません。
ステファンシャゼル

3

これは私が使用しているバージョンです:

while
  port=$(shuf -n 1 -i 49152-65535)
  netstat -atun | grep -q "$port"
do
  continue
done

echo "$port"

このコマンドshuf -n 1 -i 49152-65535は、ダイナミックレンジの「ランダム」ポートを提供します。既に使用されている場合、その範囲内の別のポートが試行されます。

このコマンドnetstat -atunは、ホスト名(-n)を決定する時間を無駄にすることなく、すべての(-a)TCP(-t)およびUDP(-u)ポートをリストします。


1

これは、.bashrcにある関数の一部であり、SSHトンネルを動的に作成し、範囲内の任意のポートを使用しようとします。

   lps=( 7002 7003 7004 7005 7006 7007 7008 7009 7010 7011 )
   lp=null

   # find a free listening port
   for port in ${lps[@]}; do
      lsof -i -n -P |grep LISTEN |grep -q ":${port}"
      [ $? -eq 1 ] && { lp=$port; break; }
   done
   [ "$lp" = "null" ] && { echo "no free local ports available"; return 2; }
   return $port

YMMV


1

この古い趣味の馬での別の実行:

function random_free_tcp_port {
  local ports="${1:-1}" interim="${2:-2048}" spacing=32
  local free_ports=( )
  local taken_ports=( $( netstat -aln | egrep ^tcp | fgrep LISTEN |
                         awk '{print $4}' | egrep -o '[0-9]+$' |
                         sort -n | uniq ) )
  interim=$(( interim + (RANDOM % spacing) ))

  for taken in "${taken_ports[@]}" 65535
  do
    while [[ $interim -lt $taken && ${#free_ports[@]} -lt $ports ]]
    do
      free_ports+=( $interim )
      interim=$(( interim + spacing + (RANDOM % spacing) ))
    done
    interim=$(( interim > taken + spacing
                ? interim
                : taken + spacing + (RANDOM % spacing) ))
  done

  [[ ${#free_ports[@]} -ge $ports ]] || return 2

  printf '%d\n' "${free_ports[@]}"
}

このコードでは、純粋に携帯して使用しますnetstategrepawk、&アル。最初に取得されたポートのリストを取得するために、外部コマンドに対して呼び出しのみが発行されることに注意してください。1つ以上の空きポートをリクエストできます。

:;  random_free_tcp_port
2070
:;  random_free_tcp_port 2
2073
2114

そして、任意のポートで開始します。

:;  random_free_tcp_port 2 10240
10245
10293

1
while port=$(shuf -n1 -i $(cat /proc/sys/net/ipv4/ip_local_port_range | tr '\011' '-'))
netstat -atun | grep -q ":$port\s" ; do
    continue
done
echo $port

上記の他の回答からの私の組み合わせ。それを得る:

shuf -n1を使用すると、/ proc / sys / net / ipv4 / ip_local_port_rangeの範囲(-i)から1つの乱数を取得します。shufにはダッシュ付きの構文が必要なので、trを使用してダッシュ内のタブを変更します。

次に、netstatを使用して、番号(-n)のすべての(-a)tcpおよびudp(-u -t)接続を表示します。 \ s)それから、他のポートが必要ですので、続けてください。


1

Pythonが横になっている場合は、次のようにします。

port="$(python -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1])')";
echo "Unused Port: $port"


0

それについての私の意見...関数はn連続した空きポートを見つけようとします:

#!/bin/bash

RANDOM=$$

# Based on 
# https://unix.stackexchange.com/a/55918/41065
# https://unix.stackexchange.com/a/248319/41065
find_n_ports () {
    local n=$1
    RANDOM=$$
    read lower_port upper_port < /proc/sys/net/ipv4/ip_local_port_range
    local tries=10
    while [ $((tries--)) -gt 0 ]; do
        # Sleep for 100 to 499 ms
        sleep "0.$((100+$RANDOM%399))"
        local start="`shuf -i $lower_port-$(($upper_port-$n-1)) -n 1`"
        local end=$(($start+$n-1))
        # create ss filter for $n consecutive ports starting with $start
        local filter=""
        local ports=$(seq $start $end)
        for p in $ports ; do
            filter="$filter or sport = $p"
        done
        # if none of the ports is in use return them
        if [ "$(ss -tHn "${filter# or}" | wc -l)" = "0" ] ; then
            echo "$ports"
            return 0
        fi
    done
    echo "Could not find $n consecutive ports">&2
    return 1
}

ports=$(find_n_ports 3)
[ $? -ne 0 ] && exit 1
exit 0
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.