Was ist der einfachste Weg, um einen ungenutzten lokalen Port zu finden?

52

Was ist der einfachste Weg, um einen ungenutzten lokalen Port zu finden?

Momentan verwende ich etwas Ähnliches:

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

Es fühlt sich schrecklich rund an, also frage ich mich, ob es einen einfacheren Weg gibt, wie zum Beispiel einen eingebauten, den ich verpasst habe.

mybuddymichael
quelle
2
Warum willst du das machen? Es ist von Natur aus rassig (und ineffizient - und am wenigsten -nzu Netstat und einem selektiveren Grep). Versuchen Sie, einen Port in dem von Ihnen benötigten Modus zu öffnen, und versuchen Sie es mit einem anderen, falls dieser nicht verfügbar ist.
Mat
1
@Mat Ich versuche automatisch, einen offenen Port zu finden, der ssh -Dals SOCKS-Server verwendet werden kann.
Mybuddymichael

Antworten:

24

Wenn Ihre Anwendung dies unterstützt, können Sie versuchen, Port 0 an die Anwendung zu übergeben. Wenn Ihre Anwendung dies an den Kernel weiterleitet, wird der Port zum Zeitpunkt der Anforderung dynamisch zugewiesen und wird garantiert nicht verwendet (die Zuordnung schlägt fehl, wenn alle Ports bereits verwendet werden).

Andernfalls können Sie dies manuell tun. Das Skript in Ihrer Antwort weist eine Race-Bedingung auf. Sie können dies nur vermeiden, indem Sie atomar überprüfen, ob es geöffnet ist, indem Sie versuchen, es zu öffnen. Wenn der Port verwendet wird, sollte das Programm beendet werden, wenn der Port nicht geöffnet werden kann.

Angenommen, Sie versuchen, mit GNU Netcat zuzuhören.

#!/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
quelle
1
@Lekensteyn: Wo siehst du hier einen Rennzustand?
Chris Down
1
Dieser Port versucht, den ersten verfügbaren Port zu verwenden. Wenn zwei Prozesse gleichzeitig ausgeführt werden, wird der gerade überprüfte Port möglicherweise wiederverwendet. Wenn Sie Ihre Antwort erneut lesen, wird anscheinend empfohlen, die Bindung für einen verfügbaren Port zu wiederholen, bis alle Ports erschöpft sind. Unter der Annahme, dass das betreffende Programm zwischen "Port in Use" und anderen Fehlern unterscheiden kann, sollte dies in Ordnung sein (obwohl die Unvorhersehbarkeit durch Randomisierung immer noch verbessert würde).
Lekensteyn
1
@Lekensteyn Eine erfolgreiche Portbindung führt dazu, dass der Kernel EADDRINUSE zurückgibt. Wenn Sie versuchen, ihn erneut zu verwenden, ist es nicht möglich, dass "der gerade überprüfte Port wiederverwendet wird".
Chris Down
Ja, ich habe fälschlicherweise angenommen, dass Sie die Schleife verlassen und $portim eigentlichen Programm wie in verwenden würden while ...; done; program --port $port.
Lekensteyn
Von der Manpage aus: -p Quellport Gibt den Quellport an, den nc verwenden soll, vorbehaltlich der Einschränkung der Berechtigungen und der Verfügbarkeit. Es ist ein Fehler, diese Option in Verbindung mit der Option -l zu verwenden.
Monksy
54

Meine Lösung besteht darin, an Port 0 zu binden, der den Kernel auffordert, einen Port aus dessen ip_local_port_range zuzuweisen. Schließen Sie dann den Socket und verwenden Sie diese Portnummer in Ihrer Konfiguration.

Dies funktioniert, weil der Kernel die Portnummern erst wiederzuverwenden scheint, wenn dies unbedingt erforderlich ist. Nachfolgende Bindungen an Port 0 weisen eine andere Portnummer zu. Python-Code:

import socket

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

Dies gibt nur eine Nummer eines Ports, z. 60123.

Führen Sie dieses Programm 10 000 Mal aus (Sie sollten es gleichzeitig ausführen), und Sie erhalten 10 000 verschiedene Portnummern. Daher denke ich, dass es ziemlich sicher ist, die Ports zu verwenden.

Mark Theunissen
quelle
20
Hier ist ein python -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1]); s.close()'
Einzeiler
4
Ich habe das erwähnte Experiment durchgeführt und nicht alle Ergebnisse waren eindeutig. Mein Histogramm war:{ 1: 7006, 2: 1249, 3: 151, 4: 8, 5: 1, 6: 1}
bukzor
2
Gibt es eine einfache Möglichkeit, um zu überprüfen, ob der Port nicht durch eine Firewall blockiert ist oder nur offene Ports durchsucht?
Mark Lakata
1
@dshepherd Ich glaube, Sie werden verschiedene Ports erhalten, wenn Sie den vorherigen nicht schließen (und alle auf einmal schließen).
Franklin Yu
1
Ein Liner für Ruby 2.3.1:ruby -e 'puts Addrinfo.tcp("", 0).bind { |s| s.local_address.ip_port }'
Franklin Yu
12

Einzeiler

Ich habe einen netten Einzeiler zusammengestellt, der schnell seinen Zweck erfüllt und es ermöglicht, eine beliebige Anzahl von Ports in einem beliebigen Bereich zu erfassen (hier ist er zur besseren Lesbarkeit in 4 Zeilen unterteilt):

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"

Zeile für Zeile

commist ein Dienstprogramm, das Zeilen in zwei Dateien vergleicht, die alphabetisch sortiert angezeigt werden müssen. Es gibt drei Spalten aus: Zeilen, die nur in der ersten Datei erscheinen, Zeilen, die nur in der zweiten erscheinen, und gemeinsame Zeilen. Durch die Angabe -23unterdrücken wir letztere Spalten und behalten nur die erste. Wir können dies verwenden, um die Differenz zweier Mengen zu erhalten, ausgedrückt als eine Folge von Textzeilen. Ich habe comm hier davon erfahren .

Die erste Datei ist der Portbereich, aus dem wir auswählen können. seqErzeugt eine sortierte Folge von Zahlen von $FROMbis $TO. Das Ergebnis wird alphabetisch (anstatt numerisch) sortiert und commals erste Datei mithilfe der Prozessersetzung weitergeleitet .

Die zweite Datei ist die sortierte Liste von Ports, dass wir durch den Aufruf den erhaltenen ssBefehls (mit -tBedeutung TCP - Ports, -aalle Bedeutung - etabliert und zuhören - und -nnumerisch - versuchen Sie nicht zu lösen, sagen sie, 22zu ssh). Wir wählen dann nur die vierte Spalte mit aus awk, die die lokale Adresse und den Port enthält. Wir verwenden cut, um Adresse und Port mit dem :Trennzeichen zu teilen und nur das letztere zu behalten ( -f2). ssgib auch einen Header aus, den wir loswerden, indem wir grepnach nicht leeren Zahlenfolgen pingen, die nicht länger als 5 sind. Wir erfüllen dann die commAnforderung, indem wir sortohne Duplikate arbeiten -u.

Jetzt haben wir eine sortierte Liste der offenen Ports, dass wir shuffle , um dann die ersten greifen "$HOWMANY"mit Einsen head -n.

Beispiel

Besorgen Sie sich die drei zufällig geöffneten Ports im privaten Bereich (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

könnte zum Beispiel zurückkehren

54930
57937
51399

Anmerkungen

  • Wechseln Sie -tmit -uin ss, um stattdessen freie UDP-Ports zu erhalten.
  • Ersetzen Sie shufdurch, sort -nwenn Sie verfügbare Ports nicht zufällig, sondern numerisch sortiert erhalten möchten
stefanobaghino
quelle
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

Dank an Chris Down

Stefan Leisten
quelle
6

Anscheinend können TCP-Verbindungen als Dateideskriptoren unter Linux ab mit in bash / zsh verwendet werden. Die folgende Funktion verwendet diese Technik und sollte schneller sein als der Aufruf von 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
}

Verwendungsanweisungen: Binden Sie die Ausgabe an eine Variable und verwenden Sie sie in Skripten. Getestet unter Ubuntu 16.04

root@ubuntu:~> EPHEMERAL_PORT
59453
root@ubuntu:~> PORT=$(EPHEMERAL_PORT)
Sandeep
quelle
Funktioniert auch mit ksh93.
Fpmurphy
Wenn Sie UPORT in 32768 ändern, können Sie immer noch EG 35835 erhalten. RANDOM gibt eine Zahl in [0,32767] zurück. Eine Änderung um eine Zahl größer als das Maximum hat keine Auswirkung. Du willst so etwas wie $[$LPORT + ($RANDOM % ($UPORT-$LPORT))].
Am
Ansonsten aber ziemlich cool!
Am
Dies wird jedoch \nan jeden Abhörport gesendet :) Ich würde vorschlagen, dies hinzuzufügen -n. Dadurch wird weiterhin versucht, eine Verbindung herzustellen, es wird jedoch nichts gesendet, sondern die Verbindung sofort getrennt.
Stefanct
4

Hier ist ein plattformübergreifender, effizienter "Oneliner", der alle in Verwendung befindlichen Ports aufnimmt und Ihnen den ersten verfügbaren ab 3000 bereitstellt:

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

Sie können einfach alle Zeilen verbinden, um sie in einer Zeile zu haben. Wenn Sie den ersten von einer anderen Portnummer abrufen möchten, ändern Sie die Zuordnung zu iin der forSchleife.

Es funktioniert sowohl auf Mac als auch auf Linux, weshalb der [:.]reguläre Ausdruck benötigt wird.

w00t
quelle
Warum -aund nicht -tnur auf TCP (6) Sockets schauen?
Stefanct
Und während wir gerade dabei sind, könnte das Parsen der Ausgabe von ss -Htnlbesser (und schneller! - nicht, dass mir das etwas ausmacht: P) sein.
Stefanct
@stefanct BSD netstat hat -tzumindest nicht das , mit dem Apple ausgeliefert wird, und ssist auch nicht auf macOS vorhanden. netstat -alnfunktioniert sogar unter Solaris.
25.
3

Unter Linux können Sie Folgendes tun:

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

So finden Sie den ersten freien Port über 1080. Beachten Sie, dass ssh -Ddies die Loopback-Schnittstelle beeinträchtigen würde. Theoretisch könnten Sie also Port 1080 wiederverwenden, wenn ein Socket eine Bindung an eine andere Adresse hat. Ein anderer Weg wäre, es tatsächlich zu versuchen und zu binden:

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
quelle
Dies setzt jedoch eine Wettlaufsituation zwischen dem Versuch, den Port zu öffnen, und der tatsächlichen Nutzung voraus.
Chris Down
@ ChrisDown, in der Tat, aber mit ssh -D, ich kann keine bessere Option sehen. Die -O forwardOption von sshgibt keinen Fehler zurück, wenn die Weiterleitung fehlschlägt.
Stéphane Chazelas
3

Dies ist die Version, die ich benutze:

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

echo "$port"

Der Befehl shuf -n 1 -i 49152-65535gibt Ihnen einen "zufälligen" Port im Dynamikbereich. Wenn es bereits verwendet wird, wird ein anderer Port in diesem Bereich ausprobiert.

Der Befehl netstat -atunlistet alle (-a) TCP (-t) - und UDP (-u) -Ports auf, ohne Zeit für die Ermittlung der Hostnamen (-n) zu verschwenden.

pfo
quelle
1

Dies ist Teil einer Funktion in meinem .bashrc, die dynamisch SSH-Tunnel erstellt und versucht, einen beliebigen Port in einem Bereich zu verwenden:

   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

mills013
quelle
1

Noch ein Lauf auf diesem alten Steckenpferd:

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

Dieser Code macht rein mobilen Einsatz von netstat, egrep, awk, & al. Beachten Sie, dass nur externe Befehle aufgerufen werden, um am Anfang eine Liste der belegten Ports zu erhalten. Man kann einen oder mehrere freie Ports anfordern:

:;  random_free_tcp_port
2070
:;  random_free_tcp_port 2
2073
2114

und starte an einem beliebigen Port:

:;  random_free_tcp_port 2 10240
10245
10293
SolidSnack
quelle
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

Meine Kombination aus anderen Antworten oben. Kapiert:

Mit shuf -n1 nehmen wir eine Zufallszahl aus dem Bereich (-i) in / proc / sys / net / ipv4 / ip_local_port_range. Benötigen Sie die Syntax mit Bindestrich, so verwenden wir tr, um die Registerkarte in einem Bindestrich zu ändern.

Als nächstes zeigen wir uns mit netstat alle (-a) tcp und udp (-u -t) Verbindungen in Zahlen (-n), wenn wir unseren zufälligen Port $ port darin finden (beginnen mit a: und enden mit w whitespace ( \ s) dann brauchen wir einen anderen Port und fahren so fort. Andernfalls (grep -q hat einen Returncode> 0, wir haben die while-Schleife verlassen und $ port ist gesetzt.

Winfried Münch
quelle
1

Wenn Sie Python herumliegen haben, würde ich dies tun:

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

Meine Meinung dazu ... die Funktion versucht, naufeinanderfolgende freie Ports zu finden :

#!/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
quelle