Jak najłatwiej znaleźć nieużywany port lokalny?

52

Jak najłatwiej znaleźć nieużywany port lokalny?

Obecnie używam czegoś podobnego do tego:

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

Czuje się strasznie rondo, więc zastanawiam się, czy istnieje prostsza ścieżka, na przykład wbudowana, za którą tęskniłem.

mybuddymichael
źródło
2
Dlaczego chcesz to zrobić? Jest z natury racjonalny (i nieefektywny - a przynajmniej dodaje -ndo netstat i bardziej selektywnego grep). Można to zrobić, próbując otworzyć port w dowolnym trybie, którego potrzebujesz, i wypróbować inny, jeśli nie jest dostępny.
Mat
1
@Mat Próbuję automatycznie znaleźć otwarty port, z którego można korzystać ssh -Djako serwer SOCKS.
mybuddymichael
Podobne pytanie: stackoverflow.com/questions/13308144/… || POSIX: stackoverflow.com/questions/913501/…
Ciro Santilli 19 改造 中心 法轮功 六四 事件

Odpowiedzi:

24

Jeśli aplikacja obsługuje tę funkcję, możesz spróbować przekazać port 0 do aplikacji. Jeśli aplikacja przekaże to do jądra, port zostanie przydzielony dynamicznie w czasie żądania i gwarantuje, że nie będzie używany (przydział nie powiedzie się, jeśli wszystkie porty są już w użyciu).

W przeciwnym razie możesz to zrobić ręcznie. Skrypt w twojej odpowiedzi ma warunek wyścigu, jedynym sposobem na uniknięcie go jest atomowe sprawdzenie, czy jest otwarte, próbując je otworzyć. Jeśli port jest w użyciu, program powinien zakończyć pracę z niepowodzeniem otwarcia portu.

Powiedzmy na przykład, że próbujesz słuchać za pomocą 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
Chris Down
źródło
1
@Lekensteyn: Gdzie widzisz warunki wyścigu?
Chris Down
1
Ten port próbuje użyć pierwszego dostępnego portu. Jeśli masz dwa współbieżne procesy, wtedy właśnie sprawdzony port może zostać ponownie użyty. Ponownie czytając twoją odpowiedź, wydaje się, że sugerujesz ponowną próbę powiązania dostępnego portu, aż wszystkie porty zostaną wyczerpane. Zakładając, że omawiany program potrafi rozróżnić „używany port” od innych błędów, powinno być w porządku (chociaż losowość nadal poprawiłaby to z powodu nieprzewidywalności).
Lekensteyn,
1
@ Lekensteyn Pomyślne powiązanie portu powoduje, że jądro zwraca EADDRINUSE, jeśli spróbujesz go użyć ponownie, nie jest możliwe, że „właśnie sprawdzony port może zostać ponownie użyty”.
Chris Down
Tak, błędnie założyłem, że wyjdziesz z pętli i użyjesz $portw rzeczywistym programie jak w while ...; done; program --port $port.
Lekensteyn,
Ze strony podręcznika: -p port_źródłowy Określa port źródłowy, z którego nc powinien korzystać, z zastrzeżeniem ograniczeń uprawnień i dostępności. Używanie tej opcji w połączeniu z opcją -l jest błędem.
mnichów
54

Moim rozwiązaniem jest powiązanie z portem 0, który prosi jądro o przydzielenie portu z jego ip_local_port_range. Następnie zamknij gniazdo i użyj tego numeru portu w konfiguracji.

Działa to, ponieważ jądro wydaje się nie używać numerów portów, dopóki nie będzie to absolutnie konieczne. Kolejne powiązania z portem 0 przypiszą inny numer portu. Kod Python:

import socket

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

Daje to tylko numer portu, np. 60123.

Uruchom ten program 10 000 razy (powinieneś je uruchomić jednocześnie), a otrzymasz 10 000 różnych numerów portów. Dlatego myślę, że korzystanie z portów jest dość bezpieczne.

Mark Theunissen
źródło
20
Oto linijka (ważna w Pythonie 2 i Pythonie 3):python -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1]); s.close()'
Lekensteyn,
4
Przeprowadziłem wspomniany eksperyment i nie wszystkie wyniki były wyjątkowe. Mój histogram to:{ 1: 7006, 2: 1249, 3: 151, 4: 8, 5: 1, 6: 1}
bukzor
2
Czy istnieje prosty sposób na dodanie sprawdzenia, czy port nie jest blokowany przez zaporę ogniową, czy raczej przeszukuje otwarte porty?
Mark Lakata,
1
@dshepherd Wierzę, że dostaniesz różne porty, jeśli nie zamkniesz poprzedniego (i zamkniesz je wszystkie naraz).
Franklin Yu
1
Jedna linijka dla Ruby 2.3.1:ruby -e 'puts Addrinfo.tcp("", 0).bind { |s| s.local_address.ip_port }'
Franklin Yu
12

Jednowarstwowa

Stworzyłem ładny jednowarstwowy, który szybko służy temu celowi, pozwalając złapać dowolną liczbę portów w dowolnym zakresie (tutaj jest podzielony na 4 linie dla czytelności):

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"

Linia po linii

commto narzędzie, które porównuje linie w dwóch plikach, które muszą pojawiać się posortowane alfabetycznie. Wyprowadza trzy kolumny: linie, które pojawiają się tylko w pierwszym pliku, linie, które pojawiają się tylko w drugim, i linie wspólne. Określając -23, pomijamy te ostatnie kolumny i zachowujemy tylko pierwszą. Możemy tego użyć, aby uzyskać różnicę dwóch zestawów, wyrażoną jako ciąg wierszy tekstu. Dowiedziałem się comm tutaj .

Pierwszy plik to zakres portów, które możemy wybrać. seqtworzy posortowaną sekwencję liczb od $FROMdo $TO. Wynik jest sortowany alfabetycznie (zamiast numerycznie) i przesyłany do commpierwszego pliku za pomocą podstawienia procesu .

Drugi plik to posortowana lista portów, którą uzyskujemy przez wywołanie sspolecenia (w -tznaczeniu portów TCP, -aco oznacza , że wszystkie - ustanowione i nasłuchujące - i -nliczbowe - nie próbuj rozstrzygać, powiedzmy, 22na ssh). Następnie wybieramy tylko czwartą kolumnę awk, która zawiera adres lokalny i port. Używamy cutdo dzielenia adresu i portu za pomocą :separatora i zatrzymywania tylko tego ostatniego ( -f2). sswypisujemy również nagłówek, którego pozbywamy się przez grepping dla niepustych ciągów liczb, które nie są dłuższe niż 5. Następnie spełniamy commwymaganie poprzez sorting bez duplikatów -u.

Teraz mamy posortowaną listę otwartych portów, które możemy shufFLE aby następnie chwycić pierwsze "$HOWMANY"te z head -n.

Przykład

Chwyć trzy losowe otwarte porty z zakresu prywatnego (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

może na przykład wrócić

54930
57937
51399

Notatki

  • przełączyć -tz -uIN ssaby uzyskać bezpłatne porty UDP zamiast.
  • wymienić shufze sort -njeśli wolisz, aby uzyskać dostępne porty numerycznie posortowane zamiast losowo
stefanobaghino
źródło
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

Podziękowania dla Chrisa Downa

Stefan Leisten
źródło
6

Najwyraźniej połączenia TCP mogą być używane jako deskryptory plików w systemie Linux od w bash / zsh. Poniższa funkcja wykorzystuje tę technikę i powinna być szybsza niż wywoływanie 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
}

Instrukcje użycia: Powiąż dane wyjściowe ze zmienną i użyj w skryptach. Testowane na Ubuntu 16.04

root@ubuntu:~> EPHEMERAL_PORT
59453
root@ubuntu:~> PORT=$(EPHEMERAL_PORT)
Sandeep
źródło
Działa ksh93również z .
fpmurphy
Jeśli zmienisz UPORT na 32768, nadal możesz dostać EG 35835. RANDOM zwraca liczbę w [0,32767]. Zmodyfikowanie tego o liczbę większą niż maksymalna nie ma żadnego efektu. Chcesz coś takiego $[$LPORT + ($RANDOM % ($UPORT-$LPORT))].
lxs
Poza tym całkiem fajne!
lxs
To jednak wysyła \ndo dowolnego portu nasłuchującego :) Sugeruję dodanie -n. To nadal będzie próbowało otworzyć połączenie, ale niczego nie wyśle, ale natychmiast rozłączy.
stefanct
4

Oto wieloplatformowy, wydajny „oneliner”, który blokuje wszystkie używane porty i daje pierwszy dostępny od 3000:

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

Możesz po prostu połączyć wszystkie linie, aby mieć je w jednej linii. Jeśli chcesz uzyskać pierwszy dostępny z innego numeru portu, zmień przypisanie na iw forpętli.

Działa zarówno na Macu, jak i Linuksie, dlatego [:.]potrzebny jest regex.

w00t
źródło
Dlaczego -anie -tpatrzeć tylko na gniazda TCP (6)?
stefanct
I kiedy już to robimy, analizowanie wyników ss -Htnlmoże być lepsze (i szybsze! - nie to, że mnie to obchodzi: P).
stefanct
@stefanct BSD netstat nie ma -t, przynajmniej ten, który jest dostarczany przez Apple, a sstakże nie jest obecny na macOS. netstat -alndziała nawet w systemie Solaris.
w00t
3

W systemie Linux możesz zrobić coś takiego:

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

Aby znaleźć pierwszy wolny port powyżej 1080. Zauważ, że ssh -Dwiązałby się on z interfejsem sprzężenia zwrotnego, więc teoretycznie możesz ponownie użyć portu 1080, jeśli gniazdo ma przypisany inny adres. Innym sposobem może być próba powiązania go:

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'
Stéphane Chazelas
źródło
Wymaga to jednak wyścigu między próbą otwarcia portu a faktycznym użyciem go.
Chris Down,
@ChrisDown, rzeczywiście, ale z ssh -D, nie widzę lepszej opcji. -O forwardOpcja sshnie zwraca błąd, gdy nie do przodu.
Stéphane Chazelas,
3

To jest wersja, której używam:

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

echo "$port"

To polecenie shuf -n 1 -i 49152-65535daje „losowy” port w zakresie dynamicznym. Jeśli jest już używany, próbowany jest inny port w tym zakresie.

Polecenie netstat -atunwyświetla listę wszystkich portów (-a) TCP (-t) i UDP (-u) bez marnowania czasu na określenie nazw hostów (-n).

pfo
źródło
1

Jest to część funkcji, którą mam w moim .bashrc, która dynamicznie tworzy tunele SSH i próbuje użyć dowolnego portu w zakresie:

   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

młyny013
źródło
1

Kolejny bieg do tego starego konia hobby:

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

Kod ten umożliwia korzystanie z przenośnego czysto netstat, egrep, awk, i in. Zauważ, że wydawane jest tylko wywołanie zewnętrznych poleceń, aby uzyskać listę zajętych portów na początku. Można zażądać jednego lub więcej wolnych portów:

:;  random_free_tcp_port
2070
:;  random_free_tcp_port 2
2073
2114

i zacznij od dowolnego portu:

:;  random_free_tcp_port 2 10240
10245
10293
solidsnack
źródło
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

Moja kombinacja z innych odpowiedzi powyżej. Zdobyć:

Z shuf -n1 bierzemy jedną losową liczbę z zakresu (-i) w / proc / sys / net / ipv4 / ip_local_port_range. shuf potrzebuje składni z myślnikiem, więc używamy tr, aby zmienić tabulację w myślniku.

Następnie użyj netstat, aby pokazać nam wszystkie połączenia (-a) tcp i udp (-u -t) w liczbach (-n), jeśli znajdziemy w tym nasz losowy port $ port (zacznij od: i zakończ w w białym spacji ( \ s), potrzebujemy innego portu i kontynuujmy. W przeciwnym razie (grep -q ma kod powrotu> 0, opuściliśmy pętlę while i ustawiono $ port.

Winfried Münch
źródło
1

Jeśli masz wokół siebie pytona, zrobiłbym to:

port="$(python -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1])')";
echo "Unused Port: $port"
MatrixManAtYrService
źródło
0

Moje zdanie na ten temat ... funkcja próbuje znaleźć nkolejne wolne porty:

#!/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
stefanct
źródło