¿Cuál es la forma más fácil de encontrar un puerto local no utilizado?

52

¿Cuál es la forma más fácil de encontrar un puerto local no utilizado?

Actualmente estoy usando algo similar a esto:

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

Se siente terriblemente indirecto, por lo que me pregunto si hay un camino más simple como una construcción que he echado de menos.

mybuddymichael
fuente
2
¿Por qué quieres hacer eso? Es intrínsecamente picante (e ineficiente, y menos se agrega -na netstat y a un grep más selectivo). La forma de hacerlo es intentar abrir un puerto en el modo que necesite y probar con otro si no está disponible.
Mat
1
@ Mat Estoy tratando de encontrar automáticamente un puerto abierto para usar ssh -Dcomo servidor SOCKS.
mybuddymichael
Pregunta similar: stackoverflow.com/questions/13308144/… || POSIX: stackoverflow.com/questions/913501/…
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

Respuestas:

24

Si su aplicación lo admite, puede intentar pasar el puerto 0 a la aplicación. Si su aplicación pasa esto al kernel, el puerto se asignará dinámicamente en el momento de la solicitud y se garantiza que no estará en uso (la asignación fallará si todos los puertos ya están en uso).

De lo contrario, puede hacerlo manualmente. El guión en su respuesta tiene una condición de carrera, la única forma de evitarlo es verificar atómicamente si está abierto intentando abrirlo. Si el puerto está en uso, el programa debería cerrarse con una falla al abrir el puerto.

Por ejemplo, supongamos que está tratando de escuchar 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
Chris Down
fuente
1
@Lekensteyn: ¿Dónde ves una condición de carrera aquí?
Chris Down
1
Este puerto intenta usar el primer puerto disponible. Cuando tiene dos procesos simultáneos, el puerto que acaba de comprobar podría reutilizarse. Al volver a leer su respuesta, parece que sugiere volver a intentar el enlace en un puerto disponible hasta que se agoten todos los puertos. Suponiendo que el programa en cuestión puede distinguir entre "puerto en uso" y otros errores, debería estar bien (aunque la aleatorización aún mejoraría la imprevisibilidad).
Lekensteyn
1
@Lekensteyn El enlace de puerto exitoso da como resultado que el núcleo devuelva EADDRINUSE si intenta usarlo nuevamente, no es posible que "el puerto que acaba de ser revisado pueda reutilizarse".
Chris Down
Sí, supuse erróneamente que saldría del bucle y lo usaría $porten el programa real como en while ...; done; program --port $port.
Lekensteyn el
Desde la página de manual: -p source_port Especifica el puerto de origen que nc debe usar, sujeto a restricciones de privilegios y disponibilidad. Es un error usar esta opción junto con la opción -l.
monksy
54

Mi solución es enlazar al puerto 0, que le pide al núcleo que asigne un puerto desde su ip_local_port_range. Luego, cierre el socket y use ese número de puerto en su configuración.

Esto funciona porque el núcleo no parece reutilizar los números de puerto hasta que sea absolutamente necesario. Los enlaces posteriores al puerto 0 asignarán un número de puerto diferente. Código de Python:

import socket

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

Esto proporciona solo un número de puertos, por ejemplo. 60123.

Ejecute este programa 10 000 veces (debe ejecutarlas simultáneamente) y obtendrá 10 000 números de puerto diferentes. Por lo tanto, creo que es bastante seguro usar los puertos.

Mark Theunissen
fuente
20
Aquí hay una frase (válida con Python 2 y Python 3):python -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1]); s.close()'
Lekensteyn
44
Ejecuté el experimento mencionado, y no todos los resultados fueron únicos. Mi histograma fue:{ 1: 7006, 2: 1249, 3: 151, 4: 8, 5: 1, 6: 1}
bukzor
2
¿Hay una manera fácil de agregar en un cheque que el puerto no está bloqueado por un firewall, o más bien solo busca puertos abiertos?
Mark Lakata
1
@dshepherd Creo que obtendrá diferentes puertos si no cierra el anterior (y los cierra todos a la vez por fin).
Franklin Yu el
1
One liner for Ruby 2.3.1:ruby -e 'puts Addrinfo.tcp("", 0).bind { |s| s.local_address.ip_port }'
Franklin Yu
12

Un trazador de líneas

He reunido un buen one-liner que cumple rápidamente el propósito, lo que permite capturar un número arbitrario de puertos en un rango arbitrario (aquí se divide en 4 líneas para facilitar la lectura):

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 por linea

commes una utilidad que compara líneas en dos archivos que deben aparecer ordenadas alfabéticamente. Produce tres columnas: líneas que aparecen solo en el primer archivo, líneas que solo aparecen en el segundo y líneas comunes. Al especificar -23, suprimimos las últimas columnas y solo conservamos la primera. Podemos usar esto para obtener la diferencia de dos conjuntos, expresados ​​como una secuencia de líneas de texto. Aprendí sobre comm aquí .

El primer archivo es el rango de puertos que podemos seleccionar. seqproduce una secuencia ordenada de números de $FROMa $TO. El resultado se ordena alfabéticamente (en lugar de numéricamente) y se canaliza commcomo el primer archivo usando la sustitución del proceso .

El segundo archivo es la lista ordenada de puertos, que obtenemos al llamar al sscomando (con el -tsignificado de puertos TCP, que -asignifica todo - establecido y escuchando - y -nnumérico - no intente resolver, digamos, 22a ssh). Luego seleccionamos solo la cuarta columna con awk, que contiene la dirección local y el puerto. Usamos cutdividir la dirección y el puerto con el :delimitador y mantener solo el último ( -f2). sstambién genera un encabezado, del cual nos deshacemos haciendo grepping para secuencias de números no vacías que no son más largas que 5. Luego cumplimos con commel requisito de sortin sin duplicados -u.

Ahora tenemos una lista ordenada de los puertos abiertos, que podemos shufFLE a continuación, apoderarse de los primeros "$HOWMANY"que tienen head -n.

Ejemplo

Tome los tres puertos abiertos aleatorios en el rango privado (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

podría volver por ejemplo

54930
57937
51399

Notas

  • cambiar -tcon el -ude ssconseguir los puertos UDP libres en su lugar.
  • reemplace shufcon sort -nsi prefiere obtener puertos disponibles ordenados numéricamente en lugar de al azar
stefanobaghino
fuente
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

Créditos a Chris Down

Stefan Leisten
fuente
6

Aparentemente, las conexiones TCP se pueden usar como descriptores de archivos en Linux desde bash / zsh. La siguiente función utiliza esa técnica y debería ser más rápida que invocar 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
}

Instrucciones de uso: enlaza la salida a una variable y úsala en scripts. Probado en Ubuntu 16.04

root@ubuntu:~> EPHEMERAL_PORT
59453
root@ubuntu:~> PORT=$(EPHEMERAL_PORT)
Sandeep
fuente
Trabaja con ksh93también.
fpmurphy
Si cambia UPORT a 32768, aún puede obtener EG 35835. RANDOM devuelve un número en [0,32767]. Modificar esto por un número mayor que el máximo no tiene ningún efecto. ¿Quieres algo así $[$LPORT + ($RANDOM % ($UPORT-$LPORT))].
lxs
De lo contrario, ¡genial!
lxs
Sin \nembargo, esto se envía a cualquier puerto de escucha :) Sugeriría agregar -n. Esto aún intentará abrir una conexión pero no enviará nada más que desconectarse inmediatamente.
stefanct
4

Aquí hay un "oneliner" eficiente y multiplataforma que absorbe todos los puertos en uso y le ofrece el primero disponible desde 3000 en adelante:

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

Simplemente puede unir todas las líneas para tenerlo en una sola línea. Si desea obtener el primero disponible de un número de puerto diferente, cambie la asignación a ien el forbucle.

Funciona tanto en Mac como en Linux, por eso [:.]se necesita la expresión regular.

w00t
fuente
¿Por qué -ay no -tsolo mirar los sockets TCP (6)?
stefanct
Y mientras lo hacemos, analizar la salida de ss -Htnlpodría ser mejor (¡y más rápido! - no es que me importe eso: P).
stefanct
@stefanct BSD netstat no tiene -t, al menos con el que Apple incluye, y sstampoco está presente en macOS. netstat -alnincluso funciona en Solaris.
w00t
3

En Linux, podría hacer algo como:

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

Para encontrar el primer puerto libre por encima de 1080. Tenga en cuenta que se ssh -Duniría a la interfaz de bucle invertido, por lo que, en teoría, podría reutilizar el puerto 1080 si un socket lo tiene vinculado a otra dirección. Otra forma sería tratar de vincularlo:

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
fuente
Sin embargo, esto implica una condición de carrera entre intentar abrir el puerto y usarlo.
Chris Down
@ChrisDown, de hecho, pero con ssh -D, no puedo ver ninguna mejor opción. La -O forwardopción de sshno devuelve un error cuando falla el reenvío.
Stéphane Chazelas
3

Esta es la versión que uso:

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

echo "$port"

El comando shuf -n 1 -i 49152-65535le proporciona un puerto "aleatorio" en el rango dinámico. Si ya se usa, se intenta otro puerto en ese rango.

El comando netstat -atunenumera todos los puertos (-a) TCP (-t) y UDP (-u) sin perder tiempo para determinar los nombres de host (-n).

pfo
fuente
1

Esto es parte de una función que tengo en mi .bashrc, que crea dinámicamente túneles SSH e intenta usar cualquier puerto en un rango:

   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
fuente
1

Otra carrera más en este viejo caballo de pasatiempo:

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

Este código hace uso puramente portátil de netstat, egrep, awk, y col. Tenga en cuenta que solo se emite una llamada a comandos externos para obtener una lista de los puertos tomados al principio. Se puede solicitar uno o más puertos libres:

:;  random_free_tcp_port
2070
:;  random_free_tcp_port 2
2073
2114

y comenzar en un puerto arbitrario:

:;  random_free_tcp_port 2 10240
10245
10293
merienda
fuente
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

Mi combinación de otras respuestas anteriores. Consíguelo:

Con shuf -n1 tomamos un número aleatorio del rango (-i) en / proc / sys / net / ipv4 / ip_local_port_range. shuf necesita la sintaxis con guión, por lo que usamos tr para cambiar la pestaña en un guión.

Luego, use netstat para mostrarnos todas las conexiones (-a) tcp y udp (-u -t) en números (-n), si encontramos nuestro puerto aleatorio $ port en esto (comience con a: y termine con w espacio en blanco ( \ s) entonces necesitamos otro puerto y así continuar. De lo contrario (grep -q tiene un código de retorno> 0, dejamos el bucle while y se establece $ port.

Winfried Münch
fuente
1

Si tienes Python por ahí, haría esto:

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

Mi opinión sobre esto ... la función intenta encontrar npuertos libres consecutivos:

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