Apa cara termudah untuk menemukan port lokal yang tidak digunakan?


52

Apa cara termudah untuk menemukan port lokal yang tidak digunakan?

Saat ini saya menggunakan sesuatu yang mirip dengan ini:

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

Rasanya sangat bundaran, jadi saya bertanya-tanya apakah ada jalan yang lebih sederhana seperti builtin yang saya lewatkan.


2
Mengapa kamu ingin melakukan itu? Secara inheren bersemangat (dan tidak efisien - dan paling tidak menambah -nnetstat dan grep yang lebih selektif). Cara untuk melakukannya adalah dengan mencoba dan membuka port dalam mode apa pun yang Anda butuhkan, dan coba yang lain jika tidak tersedia.
Mat

1
@Mat Saya mencoba mencari port terbuka secara otomatis untuk digunakan ssh -Dsebagai server SOCKS.
mybuddymichael

Jawaban:


24

Jika aplikasi Anda mendukungnya, Anda dapat mencoba mengirimkan port 0 ke aplikasi tersebut. Jika aplikasi Anda meneruskan ini ke kernel, port akan dialokasikan secara dinamis pada waktu permintaan, dan dijamin tidak akan digunakan (alokasi akan gagal jika semua port sudah digunakan).

Jika tidak, Anda dapat melakukan ini secara manual. Skrip dalam jawaban Anda memiliki kondisi balapan, satu-satunya cara untuk menghindarinya adalah dengan memeriksa secara atomik apakah itu terbuka dengan mencoba membukanya. Jika port sedang digunakan, program harus berhenti dengan kegagalan untuk membuka port.

Misalnya, Anda mencoba mendengarkan dengan 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: Di mana Anda melihat kondisi balapan di sini?
Chris Down

1
Port ini mencoba menggunakan port pertama yang tersedia. Ketika Anda memiliki dua proses bersamaan, maka port yang baru saja diperiksa mungkin digunakan kembali. Membaca ulang jawaban Anda, tampaknya Anda menyarankan untuk mencoba lagi mengikat pada port yang tersedia sampai semua port habis. Dengan asumsi bahwa program tersebut dapat membedakan antara "port in use" dan kesalahan lainnya, itu harus baik-baik saja (walaupun pengacakan masih akan membuatnya lebih baik untuk ketidakpastian).
Lekensteyn

1
@Lekensteyn Pengikatan port yang berhasil menghasilkan kernel mengembalikan EADDRINUSE jika Anda mencoba dan menggunakannya lagi, itu tidak mungkin bahwa "port yang baru saja diperiksa mungkin digunakan kembali".
Chris Down

Ya, saya salah berasumsi bahwa Anda akan keluar dari loop dan menggunakan $portprogram yang sebenarnya seperti pada while ...; done; program --port $port.
Lekensteyn

Dari halaman manual: -p source_port Menentukan port sumber yang harus digunakan nc, tunduk pada pembatasan hak istimewa dan ketersediaan. Ini adalah kesalahan untuk menggunakan opsi ini bersamaan dengan opsi -l.
monksy

54

Solusi saya adalah mengikat ke port 0, yang meminta kernel untuk mengalokasikan port dari itu ip_local_port_range. Kemudian, tutup soket dan gunakan nomor port itu dalam konfigurasi Anda.

Ini berfungsi karena kernel tampaknya tidak menggunakan kembali nomor port sampai benar-benar harus. Ikatan selanjutnya ke port 0 akan mengalokasikan nomor port yang berbeda. Kode python:

import socket

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

Ini hanya memberikan sejumlah port, misalnya. 60123.

Jalankan program ini 10.000 kali (Anda harus menjalankannya secara bersamaan), dan Anda akan mendapatkan 10.000 nomor port yang berbeda. Karena itu, saya pikir cukup aman untuk menggunakan porta.


20
Berikut ini adalah satu-liner (berlaku dengan Python 2 dan Python 3):python -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1]); s.close()'
Lekensteyn

4
Saya menjalankan percobaan yang disebutkan, dan tidak semua hasil unik. Histogram saya adalah:{ 1: 7006, 2: 1249, 3: 151, 4: 8, 5: 1, 6: 1}
bukzor

2
Apakah ada cara mudah untuk menambahkan cek bahwa port tidak diblokir oleh firewall, atau lebih tepatnya hanya mencari port yang terbuka?
Mark Lakata

1
@dshepherd Saya yakin Anda akan mendapatkan port yang berbeda jika Anda tidak menutup yang sebelumnya (dan akhirnya menutup semuanya sekaligus).
Franklin Yu

1
Satu liner untuk Ruby 2.3.1:ruby -e 'puts Addrinfo.tcp("", 0).bind { |s| s.local_address.ip_port }'
Franklin Yu

12

Satu garis

Saya telah mengumpulkan satu-liner bagus yang dengan cepat melayani tujuan, memungkinkan untuk mengambil sejumlah port yang sewenang-wenang dalam rentang sewenang-wenang (di sini dibagi dalam 4 baris untuk dibaca):

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"

Baris demi baris

commadalah utilitas yang membandingkan baris dalam dua file yang harus ditampilkan secara alfabet. Ini menghasilkan tiga kolom: baris yang hanya muncul di file pertama, baris yang hanya muncul di baris kedua dan umum. Dengan menentukan -23kami menekan kolom terakhir dan hanya menyimpan yang pertama. Kita bisa menggunakan ini untuk mendapatkan perbedaan dua set, yang dinyatakan sebagai urutan baris teks. Saya belajar di comm sini .

File pertama adalah kisaran port yang dapat kita pilih. seqmenghasilkan urutan angka yang diurutkan dari $FROMke $TO. Hasilnya disortir berdasarkan abjad (bukan secara numerik) dan disalurkan ke commsebagai file pertama yang menggunakan proses substitusi .

File kedua adalah daftar port yang diurutkan, yang kami peroleh dengan memanggil ssperintah (dengan -tarti port TCP, -aartinya semua - mapan dan mendengarkan - dan -nnumerik - jangan mencoba menyelesaikan, katakanlah, 22untuk ssh). Kami kemudian hanya memilih kolom keempat dengan awk, yang berisi alamat dan port lokal. Kami menggunakan cutuntuk membagi alamat dan port dengan :pembatas dan hanya menyimpan yang terakhir ( -f2). ssjuga menampilkan tajuk, yang kami singkirkan dengan melakukan grepping untuk urutan angka yang tidak kosong yang tidak lebih dari 5. Kami kemudian memenuhi commpersyaratan dengan sorttanpa duplikat -u.

Sekarang kita memiliki daftar diurutkan dari port terbuka, bahwa kita dapat shufPHK untuk kemudian ambil pertama "$HOWMANY"-orang dengan head -n.

Contoh

Raih tiga porta acak yang terbuka dalam jangkauan privat (49152-65535)

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

bisa kembali misalnya

54930
57937
51399

Catatan

  • beralih -tdengan -udi ssuntuk mendapatkan port UDP gratis sebaliknya.
  • ganti shufdengan sort -njika Anda lebih suka mendapatkan port yang tersedia diurutkan secara numerik daripada secara acak

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

Penghargaan untuk Chris Down


6

Rupanya koneksi TCP dapat digunakan sebagai deskriptor file di linux dari dengan di bash / zsh. Fungsi berikut menggunakan teknik itu dan harus lebih cepat daripada memanggil 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
}

Instruksi penggunaan: Bind output ke variabel dan gunakan dalam skrip. Diuji pada Ubuntu 16.04

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

Bekerja dengan baik ksh93juga.
fpmurphy

Jika Anda mengubah UPORT ke 32768, Anda masih bisa mendapatkan EG 35835. RANDOM mengembalikan nomor dalam [0,32767]. Memodifikasi ini dengan angka yang lebih besar dari maks tidak berpengaruh. Anda menginginkan sesuatu seperti $[$LPORT + ($RANDOM % ($UPORT-$LPORT))].
lxs

Kalau tidak, cukup keren!
lxs

Ini mengirim \nke port mendengarkan :) :) Saya sarankan menambahkan -n. Ini masih akan mencoba untuk membuka koneksi tetapi tidak mengirim apa pun tetapi segera putuskan.
stefanct

4

Inilah "oneliner" lintas-platform, efisien, yang menghirup semua port yang digunakan dan memberi Anda yang pertama dari 3000 dan seterusnya:

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
  }
'

Anda cukup bergabung dengan semua baris untuk memilikinya di satu baris. Jika Anda ingin mendapatkan yang pertama tersedia dari nomor port yang berbeda, ubah penetapan menjadi idalam forloop.

Ia bekerja pada Mac dan Linux, itulah sebabnya [:.]diperlukan regex.


Mengapa -adan tidak -thanya melihat soket TCP (6)?
stefanct

Dan sementara kita berada di dalamnya, parsing output ss -Htnlmungkin lebih baik (dan lebih cepat! - bukannya saya peduli tentang itu: P).
stefanct

@stefanct BSD netstat tidak memiliki -t, setidaknya yang dikirimkan Apple, dan ssjuga tidak ada di macOS. netstat -alnbahkan bekerja pada Solaris.
w00t

3

Di Linux, Anda dapat melakukan sesuatu seperti:

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}'

Untuk menemukan port gratis pertama di atas 1080. Catatan yang ssh -Dakan mengikat pada antarmuka loopback, jadi secara teori Anda bisa menggunakan kembali port 1080 jika soket mengikatnya pada alamat lain. Cara lain adalah dengan benar-benar mencoba dan mengikatnya:

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'

Ini, bagaimanapun, melibatkan kondisi perlombaan antara berusaha membuka pelabuhan dan benar-benar menggunakannya.
Chris Down

@ ChrisDown, Memang, tetapi dengan ssh -D, saya tidak bisa melihat opsi yang lebih baik. The -O forwardpilihan untuk sshtidak kembali kesalahan ketika maju gagal.
Stéphane Chazelas

3

Ini adalah versi yang saya gunakan:

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

echo "$port"

Perintah shuf -n 1 -i 49152-65535memberi Anda port "acak" dalam rentang dinamis. Jika sudah digunakan, port lain dalam kisaran itu dicoba.

Perintah ini netstat -atunmencantumkan semua port (-a) TCP (-t) dan UDP (-u) tanpa membuang waktu untuk menentukan nama host (-n).


1

Ini adalah bagian dari fungsi yang saya miliki di .bashrc saya, yang secara dinamis membuat terowongan SSH dan mencoba menggunakan port apa pun dalam rentang:

   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

Satu lagi berlari di kuda hobi lama ini:

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[@]}"
}

Kode ini membuat penggunaan murni portabel netstat, egrep, awk, & al. Perhatikan bahwa hanya panggilan yang dikeluarkan untuk perintah eksternal, untuk mendapatkan daftar port yang diambil di awal. Satu dapat meminta satu atau lebih port gratis:

:;  random_free_tcp_port
2070
:;  random_free_tcp_port 2
2073
2114

dan mulai dari porta arbitrer:

:;  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

Kombinasi saya dari jawaban lain di atas. Mendapatkan:

Dengan shuf -n1 kita mengambil satu angka acak dari rentang (-i) di / proc / sys / net / ipv4 / ip_local_port_range. shuf memerlukan sintaks dengan tanda hubung, jadi kami menggunakan tr untuk mengubah tab dalam tanda hubung.

Selanjutnya kita akan menggunakan netstat untuk menunjukkan kepada kita semua (-a) koneksi tcp dan udp (-u -t) dalam angka (-n), jika kita menemukan port acak $ port dalam hal ini (mulai dengan:: dan diakhiri dengan w whitespace ( \ s) maka kita memerlukan Port lain dan lanjutkan. Lain (grep -q memiliki kode pengembalian> 0 kita meninggalkan loop while dan $ port diatur.


1

Jika Anda memiliki python, Anda akan melakukan ini:

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


0

Pendapat saya ... fungsi mencoba menemukan nport bebas berurutan:

#!/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
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.