Kullanılmayan bir yerel liman bulmanın en kolay yolu nedir?


52

Kullanılmayan bir yerel liman bulmanın en kolay yolu nedir?

Şu anda buna benzer bir şey kullanıyorum:

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

Çok dolambaçlı hissediyor, bu yüzden özlediğim bir yerleşik gibi daha basit bir yol olup olmadığını merak ediyorum.


2
Neden bunu yapmak istiyorsun? Doğası gereği açıktır (ve verimsizdir - ve en azından -nnetstat ve daha seçici bir grep ekler ). Bunu yapmanın yolu, istediğiniz modda bir bağlantı noktasını denemek ve açmak ve mevcut değilse başka bir tane denemek.
Mat

1
@Mat otomatik ssh -Dolarak SOCKS sunucusu olarak kullanılacak açık bir port bulmaya çalışıyorum .
mybuddymichael

Yanıtlar:


24

Uygulamanız destekliyorsa, 0 numaralı bağlantı noktasını uygulamaya geçirmeyi deneyebilirsiniz. Uygulamanız bunu çekirdeğe geçirirse, bağlantı noktası istek anında dinamik olarak tahsis edilir ve kullanılmaması garanti edilir (tüm bağlantı noktaları zaten kullanımdaysa tahsisat başarısız olur).

Aksi takdirde, bunu manuel olarak yapabilirsiniz. Cevabınızdaki betiğin yarış koşulu vardır, kaçınmanın tek yolu açmaya çalışarak açık olup olmadığını atomik olarak kontrol etmektir. Bağlantı noktası kullanılıyorsa, program bağlantı noktasını açmamak üzere sonlandırılmalıdır.

Örneğin, GNU netcat ile dinlemeye çalıştığınızı söyleyin.

#!/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: Burada bir yarış durumunu nerede görüyorsunuz?
Chris Down

1
Bu port mevcut ilk portu kullanmaya çalışır. İki eşzamanlı işleminiz olduğunda, yeni kontrol edilen bağlantı noktası yeniden kullanılabilir. Cevabınızı tekrar okuduğunuzda, tüm bağlantı noktaları tükenene kadar mevcut bir bağlantı noktasında bağlanmayı yeniden denemenizi öneririz. Söz konusu programın "kullanılan bağlantı noktası" ile diğer hatalar arasında ayrım yapabileceğini varsayarak, iyi olması gerekir (yine de randomizasyon hala öngörülemeyen için daha iyi hale getirecektir).
Lekensteyn

1
@Lekensteyn Başarılı bir port ciltleme işlemi, tekrar denemeyi ve kullanmayı denerseniz, EADDRINUSE döndüren çekirdeğe neden olur, "yeni kontrol edilen portun yeniden kullanılması" mümkün değildir.
Chris Down,

Evet, yanlış olarak döngüden çıkacağınızı $portve asıl programdaki gibi kullanacağınızı varsaydım while ...; done; program --port $port.
Lekensteyn

Man sayfasından: -p source_port Ayrıcalık sınırlamalarına ve kullanılabilirliğine bağlı olarak, nc'nin kullanması gereken kaynak bağlantı noktasını belirtir. Bu seçeneği -l seçeneğiyle birlikte kullanmak bir hatadır.
monksy

54

Benim çözümüm, çekirdeğin ip_local_port_range'den bir bağlantı noktası tahsis etmesini isteyen 0 numaralı bağlantı noktasına bağlanmak. Ardından soketi kapatın ve konfigürasyonunuzdaki bu port numarasını kullanın.

Bu, çekirdeğin kesinlikle gerekene kadar bağlantı noktası numaralarını yeniden kullanmadığı için işe yarıyor. Sonraki bağlantı noktaları 0 numaralı bağlantı noktasına farklı bir bağlantı noktası numarası tahsis eder. Python kodu:

import socket

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

Bu sadece birkaç port verir, örn. 60123.

Bu programı 10 000 kez çalıştırın (bunları aynı anda çalıştırmanız gerekir) ve 10 000 farklı bağlantı noktası numarası alırsınız. Bu nedenle, portları kullanmak oldukça güvenli olduğunu düşünüyorum.


20
İşte bir astar (Python 2 ve Python 3 ile geçerlidir):python -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1]); s.close()'
Lekensteyn

4
Bahsedilen deneyi yaptım ve sonuçların hepsi benzersiz değildi. Histogramım şuydu:{ 1: 7006, 2: 1249, 3: 151, 4: 8, 5: 1, 6: 1}
bukzor

2
Bağlantı noktasının güvenlik duvarı tarafından engellenmediğini veya yalnızca açık bağlantı noktalarını aradığını kontrol etmenin kolay bir yolu var mı?
Mark Lakata

1
@dshepherd Bir öncekini kapatmazsanız (ve hepsini bir kerede kapatırsanız) farklı portlar alacağınıza inanıyorum.
Franklin Yu

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

12

Tek astar

Amacına hızlı bir şekilde hizmet eden ve isteğe bağlı bir aralıkta rasgele sayıda bağlantı noktası alma olanağı sağlayan güzel bir astarı bir araya getirdim (burada okunabilirlik için 4 satıra bölünmüştür):

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"

Satır satır

commSatırları alfabetik olarak sıralanmış iki dosyada karşılaştıran bir yardımcı programdır. Üç sütun çıkarır: yalnızca ilk dosyada görünen satırlar, yalnızca ikinci dosyada görünen satırlar ve genel satırlar. Belirterek -23, ikinci sütunları bastırır ve yalnızca birincisini tutarız. Bunu, metin satırlarının bir sırası olarak ifade edilen iki küme farkını elde etmek için kullanabiliriz. comm Burada öğrendim .

İlk dosya, seçebileceğimiz portların aralığıdır. seqgelen bir sayı kriteri dizisi üreten $FROMiçin $TO. Sonuç, alfabetik olarak (sayısal yerine) sıralanır ve işlem değiştirmecomm kullanılarak ilk dosya olarak aktarılır .

İkinci dosya biz çağırarak elde olduğunu, bağlantı noktalarının sıralı listesi sskomutu ile ( -t, anlam TCP bağlantı noktaları -a- kurulmuş ve dinleme - ve tüm anlam -nsayısal -, diyelim ki, çözümlemeye kalkma 22için ssh). Daha sonra sadece awkyerel adresi ve portu içeren dördüncü sütunu seçiyoruz . Biz kullanmak cutile adresi ve port bölünmüş :(sınırlayıcı ve tek ikincisi tutmak -f2). ssaynı şekilde bir başlık, biz tarafından kurtulmak olduğunu grepartık Sonra uymak 5'ten olan sayıların boş olmayan diziler için ping commtarafından 'ın gereksinimi sortyineleme olmadan ing -u.

Şimdi shufdaha sonra ilkleri kapmak için el koyabileceğimiz sıralı bir açık portlar listemiz "$HOWMANY"var head -n.

Örnek

Özel serideki üç rastgele açık portu al (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

örneğin geri dönebilir

54930
57937
51399

notlar

  • geçiş -tile -uin ssyerine ücretsiz UDP portlarını alır.
  • rastgele yerine sayısal olarak sıralanan kullanılabilir bağlantı noktaları almayı tercih ediyorsanız shufile değiştirinsort -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

Chris Down Kredileri


6

Görünüşe göre TCP bağlantıları linux'ta bash / zsh ile olan dosya tanıtıcıları olarak kullanılabilir . Aşağıdaki işlev bu tekniği kullanır ve netcat / telnet'i çağırmaktan daha hızlı olmalıdır.

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
}

Kullanım talimatları: Çıktıyı bir değişkene bağlayın ve kodlarda kullanın. Ubuntu 16.04 tarihinde test edildi.

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

ksh93Aynı zamanda çalışır .
fpmurphy

UPORT'u 32768 olarak değiştirirseniz, yine de EG 35835'i alabilirsiniz. RANDOM [0,32767] içinde bir sayı döndürür. Bunu maksimum değerden daha büyük bir sayı ile kodlamanın etkisi yoktur. Gibi bir şey istiyorsun $[$LPORT + ($RANDOM % ($UPORT-$LPORT))].
LXS

Aksi halde olsa güzel!
LXS

Bu \nolsa herhangi bir dinleme portuna gönderir :) Eklemenizi öneririm -n. Bu hala bir bağlantı açmaya çalışacak ancak hiçbir şey göndermeyecek, ancak derhal kesilecek.
stefanct

4

İşte kullanımdaki tüm portları bulan ve size ilk 3000'den itibaren mevcut olanı sunan çapraz platformlu, etkin bir "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
  }
'

Tek bir çizgide olması için tüm çizgilere katılabilirsiniz. Eğer farklı bir bağlantı noktası numarasından ilk kullanılabilir almak istiyorsanız, hiç atamasını değiştirmek iiçinde fordöngü.

Hem Mac hem de Linux üzerinde çalışır, bu nedenle [:.]regex'e ihtiyaç duyulur.


Neden -adeğil -tsadece (6) yuva TCP bakmak için?
stefanct

Biz çıkış ayrıştırma, ona iken Ve ss -Htnldaha iyi olabilir (! - ve daha hızlı değil ben : P umurumda).
stefanct

@stefanct BSD ağları -t, en azından Apple ile birlikte gelen ve macOS'ta bulunmayan bir ürüne sahip değil ss. netstat -alnSolaris'te bile çalışıyor.
w00t

3

Linux'ta şöyle bir şey yapabilirsin:

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

İlk boş bağlantı noktasını 1080'den daha yüksek bulmak için. Geri ssh -Ddöngü arabirimine bağlanacağını unutmayın; bu nedenle teoride bir soketin başka bir adrese bağlanması durumunda bağlantı noktasını 1080 yeniden kullanabilirsiniz. Başka bir yol aslında onu denemek ve bağlamak olacaktır:

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'

Ancak bu, limanı açmaya çalışmakla gerçekte kullanmak arasında bir yarış koşulu gerektirir.
Chris Down

@ChrisDown, Gerçekten, ama ile ssh -D, daha iyi bir seçenek göremiyorum. -O forwardSeçeneği sshöne başarısız olduğunda bir hata döndürmez.
Stéphane Chazelas

3

Kullandığım sürüm:

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

echo "$port"

Komut shuf -n 1 -i 49152-65535size dinamik aralıkta "rastgele" bir port verir. Zaten kullanılıyorsa, bu aralıktaki başka bir bağlantı noktası denenir.

Komut netstat -atun, ana bilgisayar adlarını (-n) belirlemek için zaman kaybetmeden tüm (-a) TCP (-t) ve UDP (-u) bağlantı noktalarını listeler.


1

Bu, dinamik olarak SSH tünelleri yaratan ve bir aralıktaki herhangi bir bağlantı noktasını kullanmaya çalışan .bashrc dosyamdaki bir fonksiyonun parçası:

   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

Bu eski hobi ata bir kez daha koşmak:

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

Bu kod, saf taşınabilir kullanır netstat, egrep, awk, ve diğ. Başlangıçta alınan bağlantı noktalarının bir listesini almak için yalnızca dış komutlara çağrı yapıldığını unutmayın. Biri bir veya daha fazla boş port talep edebilir:

:;  random_free_tcp_port
2070
:;  random_free_tcp_port 2
2073
2114

ve isteğe bağlı bir limanda başlayın:

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

Yukarıdaki diğer cevaplardan benim kombinasyonum. Anla:

Shuf -n1 ile / proc / sys / net / ipv4 / ip_local_port_range içindeki (-i) aralığından bir rastgele sayı alırız. shuf, tire işaretine ihtiyaç duyuyor, bu nedenle dizgiyi çizgi biçiminde değiştirmek için tr kullanıyoruz.

Bir sonraki wie, netstat'ı bize (-a) tcp ve udp (-u -t) bağlantılarını sayı olarak (-n) olarak gösterecek şekilde kullanır, eğer buradaki rasgele port $ portunu bulursak (: ile başlayın ve w boşluk ile bitirin () \ s) o zaman başka bir Bağlantı Noktasına ihtiyacımız var ve devam edin. Başka (grep -q bir dönüş koduna sahip> 0 iken döngüden ayrıldı ve $ port ayarlandı.


1

Yalan etrafında piton varsa, bunu yapardım:

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


0

Benim üstümdeki ... işlev nardışık boş limanlar bulmaya çalışır :

#!/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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.