Qual è il modo più semplice per trovare una porta locale inutilizzata?


52

Qual è il modo più semplice per trovare una porta locale inutilizzata?

Attualmente sto usando qualcosa di simile a questo:

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

È terribilmente rotonda, quindi mi chiedo se c'è un percorso più semplice come un builtin che mi è sfuggito.


2
Perché vuoi farlo? È intrinsecamente audace (e inefficiente - e almeno aggiunge -na netstat e un grep più selettivo). Il modo per farlo è provare ad aprire una porta in qualsiasi modalità sia necessaria e provarne un'altra se non è disponibile.
Mat

1
@Mat Sto cercando di trovare automaticamente una porta aperta da utilizzare ssh -Dcome server SOCKS.
mybuddymichael,

Risposte:


24

Se l'applicazione lo supporta, puoi provare a passare la porta 0 all'applicazione. Se l'applicazione passa questo al kernel, la porta verrà allocata in modo dinamico al momento della richiesta ed è garantito che non sarà in uso (l'allocazione fallirà se tutte le porte sono già in uso).

Altrimenti, puoi farlo manualmente. Lo script nella tua risposta ha una condizione di competizione, l'unico modo per evitarlo è verificare atomicamente se è aperto provando ad aprirlo. Se la porta è in uso, il programma dovrebbe chiudersi senza aprire la porta.

Ad esempio, supponiamo che tu stia provando ad ascoltare con 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: dove vedi le condizioni di gara qui?
Chris Down,

1
Questa porta tenta di utilizzare la prima porta disponibile. Quando si hanno due processi simultanei, la porta appena verificata potrebbe essere riutilizzata. Rileggendo la risposta, sembra che tu suggerisca di riprovare l'associazione su una porta disponibile fino a quando tutte le porte sono esaurite. Supponendo che il programma in questione sia in grado di distinguere tra "porta in uso" e altri errori, dovrebbe andare bene (anche se la randomizzazione renderebbe ancora meglio l'imprevedibilità).
Lekensteyn,

1
@Lekensteyn Il successo del binding delle porte fa sì che il kernel restituisca EADDRINUSE se si tenta di utilizzarlo nuovamente, non è possibile che "la porta appena verificata possa essere riutilizzata".
Chris Down,

Sì, ho erroneamente supposto che avresti chiuso il ciclo e utilizzato $portnel programma reale come in while ...; done; program --port $port.
Lekensteyn,

Dalla pagina man: -p source_port Specifica la porta di origine che nc deve usare, soggetta a restrizioni e disponibilità dei privilegi. È un errore usare questa opzione insieme all'opzione -l.
monaco il

54

La mia soluzione è associare alla porta 0, che chiede al kernel di allocare una porta dal suo ip_local_port_range. Quindi, chiudere il socket e utilizzare quel numero di porta nella configurazione.

Questo funziona perché il kernel non sembra riutilizzare i numeri di porta fino a quando non è assolutamente necessario. I successivi bind alla porta 0 assegneranno un numero di porta diverso. Codice Python:

import socket

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

Questo dà solo un numero di una porta, ad es. 60123.

Esegui questo programma 10.000 volte (dovresti eseguirli contemporaneamente) e otterrai 10.000 numeri di porta diversi. Pertanto, penso che sia abbastanza sicuro usare le porte.


20
Ecco un one-liner (valido con Python 2 e Python 3):python -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1]); s.close()'
Lekensteyn

4
Ho eseguito l'esperimento menzionato e non tutti i risultati sono stati unici. Il mio istogramma era:{ 1: 7006, 2: 1249, 3: 151, 4: 8, 5: 1, 6: 1}
bukzor

2
C'è un modo semplice per aggiungere un controllo che la porta non è bloccata da un firewall, o piuttosto cerca solo porte aperte?
Mark Lakata,

1
@dshepherd Credo che otterrai porte diverse se non chiudi la precedente (e alla fine le chiudi tutte insieme).
Franklin Yu,

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

12

One-liner

Ho messo insieme un bel one-liner che serve rapidamente allo scopo, consentendo di afferrare un numero arbitrario di porte in un intervallo arbitrario (qui è diviso in 4 righe per la leggibilità):

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"

Linea per linea

commè un'utilità che confronta le righe in due file che devono apparire in ordine alfabetico. Produce tre colonne: linee che appaiono solo nel primo file, linee che appaiono solo nel secondo e linee comuni. Specificando -23sopprimiamo le ultime colonne e conserviamo solo la prima. Possiamo usarlo per ottenere la differenza di due insiemi, espressa come una sequenza di righe di testo. Ho imparato comm qui .

Il primo file è l'intervallo di porte che possiamo selezionare. seqproduce una sequenza ordinata di numeri da $FROMa $TO. Il risultato viene ordinato alfabeticamente (anziché numericamente) e convogliato commcome primo file usando la sostituzione del processo .

Il secondo file è l'elenco ordinato delle porte, che otteniamo chiamando il sscomando (con -tsignificato porte TCP, che -asignifica tutto - stabilito e in ascolto - e -nnumerico - non tentare di risolvere, diciamo, 22a ssh). Selezioniamo quindi solo la quarta colonna con awk, che contiene l'indirizzo e la porta locali. Usiamo cutper dividere indirizzo e porta con il :delimitatore e mantenere solo quest'ultimo ( -f2). ssemette anche un'intestazione, di cui ci liberiamo grepeseguendo il ping per sequenze di numeri non vuote che non sono più lunghe di 5. Quindi rispettiamo commil requisito sorting senza duplicati -u.

Ora abbiamo un elenco ordinato di porte aperte, che possiamo shufFLE per poi afferrare i primi "$HOWMANY"quelli con head -n.

Esempio

Prendi le tre porte aperte casuali nell'area privata (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

potrebbe tornare ad esempio

54930
57937
51399

Appunti

  • passare -tcon -uin ssper ottenere invece le porte UDP gratuite.
  • sostituirlo shufcon sort -nse si preferisce ottenere le porte disponibili ordinate numericamente anziché casualmente

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

Crediti a Chris Down


6

Apparentemente le connessioni TCP possono essere usate come descrittori di file su Linux da con in bash / zsh. La seguente funzione usa quella tecnica e dovrebbe essere più veloce di invocare 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
}

Istruzioni per l'uso: associare l'output a una variabile e utilizzarlo negli script. Testato su Ubuntu 16.04

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

Funziona ksh93anche con .
fpmurphy,

Se cambi UPORT in 32768, puoi comunque ottenere EG 35835. RANDOM restituisce un numero in [0,32767]. Modificarlo con un numero maggiore del massimo non ha alcun effetto. Vuoi qualcosa di simile $[$LPORT + ($RANDOM % ($UPORT-$LPORT))].
lxs

Altrimenti piuttosto bello però!
lxs

Questo invia \na qualsiasi porta di ascolto :) :) Suggerirei di aggiungere -n. Questo tenterà comunque di aprire una connessione ma non di inviare nulla ma di disconnettersi immediatamente.
stefanct,

4

Ecco un "oneliner" multipiattaforma ed efficiente che assorbe tutte le porte in uso e ti offre il primo disponibile dal 3000 in poi:

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

Puoi semplicemente unire tutte le linee per averlo su una linea. Se vuoi ottenere il primo disponibile da un numero di porta diverso, modifica l'assegnazione in inel forloop.

Funziona su Mac e Linux, motivo per cui è necessaria la [:.]regex.


Perché -ae non -tguardare solo ai socket TCP (6)?
stefanct,

E mentre ci siamo, analizzare l'output di ss -Htnlpotrebbe essere migliore (e più veloce! - Non che ci tenga a questo: P).
stefanct,

@stefanct BSD netstat non ha -t, almeno quello fornito da Apple, e ssnon è presente su macOS. netstat -alnfunziona anche su Solaris.
mercoledì

3

Su Linux, potresti fare qualcosa del tipo:

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

Per trovare la prima porta libera sopra 1080. Nota che ssh -Dsi legherebbe all'interfaccia di loopback, quindi in teoria potresti riutilizzare la porta 1080 se un socket ha un altro indirizzo associato. Un altro modo sarebbe effettivamente provare a legarlo:

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'

Ciò, tuttavia, comporta una condizione di competizione tra il tentativo di aprire il porto e il suo utilizzo effettivo.
Chris Down,

@ Chris, davvero, ma con ssh -D, non riesco a vedere alcuna opzione migliore. L' -O forwardopzione di sshnon restituisce un errore quando l'inoltro fallisce.
Stéphane Chazelas,

3

Questa è la versione che uso:

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

echo "$port"

Il comando shuf -n 1 -i 49152-65535ti dà una porta "casuale" nell'intervallo dinamico. Se è già utilizzato, viene provata un'altra porta in tale intervallo.

Il comando netstat -atunelenca tutte le porte (-a) TCP (-t) e UDP (-u) senza perdere tempo a determinare i nomi host (-n).


1

Questo fa parte di una funzione che ho nel mio .bashrc, che crea dinamicamente tunnel SSH e cerca di usare qualsiasi porta in un intervallo:

   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

Ancora un'altra corsa su questo vecchio cavallo per 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[@]}"
}

Questo codice fa uso puramente portatile netstat, egrep, awk, & al. Si noti che viene inviata solo la chiamata a comandi esterni, per ottenere un elenco di porte prese all'inizio. È possibile richiedere una o più porte libere:

:;  random_free_tcp_port
2070
:;  random_free_tcp_port 2
2073
2114

e inizia da una porta arbitraria:

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

La mia combinazione da altre risposte sopra. Prendilo:

Con shuf -n1 prendiamo un numero casuale dall'intervallo (-i) in / proc / sys / net / ipv4 / ip_local_port_range. shuf ha bisogno della sintassi con trattino, quindi usiamo tr per cambiare la scheda in un trattino.

Quindi usa netstat per mostrarci tutte le connessioni (-a) tcp e udp (-u -t) in numeri (-n), se troviamo la nostra porta casuale $ port in questo (inizia con un: e termina con w spazio bianco ( \ s) quindi abbiamo bisogno di un'altra porta e quindi continua. Altrimenti (grep -q ha un codice di ritorno> 0 abbiamo lasciato il ciclo while e $ port è impostato.


1

Se hai il pitone in giro, farei questo:

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


0

La mia opinione su di esso ... la funzione cerca di trovare nporte libere consecutive:

#!/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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.