web-dev-qa-db-fra.com

Quelle est la manière la plus simple de trouver un port local inutilisé?

Quelle est la manière la plus simple de trouver un port local inutilisé?

Actuellement, j'utilise quelque chose de similaire à ceci:

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

Cela semble terriblement détourné, alors je me demande s'il y a un chemin plus simple, comme un intégré, que j'ai manqué.

58
mybuddymichael

Si votre application le prend en charge, vous pouvez essayer de passer le port 0 à l'application. Si votre application transmet cela au noyau, le port sera alloué dynamiquement au moment de la demande et il est garanti qu'il ne sera pas utilisé (l'allocation échouera si tous les ports sont déjà utilisés).

Sinon, vous pouvez le faire manuellement. Le script dans votre réponse a une condition de concurrence, la seule façon de l'éviter est de vérifier atomiquement s'il est ouvert en essayant de l'ouvrir. Si le port est en cours d'utilisation, le programme doit se fermer avec un échec d'ouverture du port.

Par exemple, supposons que vous essayez d'écouter avec 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
28
Chris Down

Ma solution est de lier au port 0, qui demande au noyau d'allouer un port à partir de son ip_local_port_range. Ensuite, fermez le socket et utilisez ce numéro de port dans votre configuration.

Cela fonctionne parce que le noyau ne semble pas réutiliser les numéros de port jusqu'à ce qu'il le soit absolument. Les liaisons ultérieures au port 0 alloueront un numéro de port différent. Python:

import socket

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

Cela donne juste un certain nombre d'un port, par exemple. 60123.

Exécutez ce programme 10 000 fois (vous devez les exécuter simultanément) et vous obtiendrez 10 000 numéros de port différents. Par conséquent, je pense qu'il est assez sûr d'utiliser les ports.

56
Mark Theunissen

Bon mot

J'ai mis en place un Nice one-liner qui sert rapidement l'objectif, permettant de saisir un nombre arbitraire de ports dans une plage arbitraire (ici, il est divisé en 4 lignes pour plus de lisibilité):

comm -23 \
<(seq "$FROM" "$TO" | sort) \
<(ss -Htan | awk '{print $4}' | cut -d':' -f2 | sort -u) \
| shuf | head -n "$HOWMANY"

Ligne par ligne

comm est un utilitaire qui compare les lignes de deux fichiers qui doivent apparaître triés alphabétiquement. Il affiche trois colonnes: des lignes qui n'apparaissent que dans le premier fichier, des lignes qui n'apparaissent que dans le second et des lignes communes. En spécifiant -23 nous supprimons les dernières colonnes et ne gardons que la première. Nous pouvons l'utiliser pour obtenir la différence de deux ensembles, exprimée comme une séquence de lignes de texte. J'ai découvert commici .

Le premier fichier est la gamme de ports que nous pouvons sélectionner. seq produit une séquence triée de nombres à partir de $FROM à $TO. Le résultat est trié alphabétiquement (au lieu de numériquement, afin de se conformer à l'exigence comms) et redirigé vers comm comme premier fichier en utilisant substitution de processus .

Le deuxième fichier est la liste triée des ports, que nous obtenons en appelant la commande ss (avec -t signifiant TCP ports, -a signifie tout - établi et à l'écoute - et -n numérique - n'essayez pas de résoudre, par exemple, 22 à ssh). Nous sélectionnons ensuite uniquement la quatrième colonne avec awk, qui contient l'adresse locale et le port. Nous utilisons cut pour diviser l'adresse et le port avec le : délimiter et ne conserver que ce dernier (-f2). Nous respectons ensuite l'exigence de comm en sorting sans doublons -u.

Nous avons maintenant une liste triée de ports ouverts, que nous pouvons shuffle pour récupérer ensuite le premier "$HOWMANY" ceux avec head -n.

Exemple

Prenez les trois ports ouverts aléatoires dans la plage privée (49152-65535)

comm -23 <(seq 49152 65535 | sort) <(ss -Htan | awk '{print $4}' | cut -d':' -f2 | sort -u) | shuf | head -n 3

pourrait revenir par exemple

54930
57937
51399

Remarques

  • commutateur -t avec -u dans ss pour obtenir des ports UDP gratuits à la place.
  • remplacez shuf par sort -n si vous préférez que les ports disponibles soient triés numériquement plutôt qu'aléatoirement
13
stefanobaghino
#!/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édits à Chris Down

11
Stefan Leisten

Apparemment les connexions TCP peuvent être utilisées comme descripteurs de fichiers sur linux depuis avec dans bash/zsh. La fonction suivante utilise cette technique et doit être plus rapide que d'appeler 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
}

Instructions d'utilisation: liez la sortie à une variable et utilisez-la dans des scripts. Testé sur Ubuntu 16.04

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

Sous Linux, vous pourriez faire quelque chose comme:

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

Pour trouver le premier port libre au-dessus de 1080. Notez que ssh -D se lierait à l'interface de bouclage, donc en théorie, vous pourriez réutiliser le port 1080 si un socket l'a lié à une autre adresse. Une autre façon serait d'essayer de le lier:

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'
4
Stéphane Chazelas

Voici un "oneliner" multiplateforme et efficace qui engloutit tous les ports en cours d'utilisation et vous donne le premier disponible à partir de 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
  }
'

Vous pouvez simplement joindre toutes les lignes pour l'avoir sur une seule ligne. Si vous voulez que le premier soit disponible à partir d'un autre numéro de port, changez l'affectation en i dans la boucle for.

Il fonctionne à la fois sur Mac et Linux, c'est pourquoi le [:.] regex est nécessaire.

4
w00t

Voici la version que j'utilise:

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

echo "$port"

La commande shuf -n 1 -i 49152-65535 vous donne un port "aléatoire" dans la plage dynamique. S'il est déjà utilisé, un autre port de cette plage est essayé.

La commande netstat -atun répertorie tous les ports (-a) TCP (-t) et UDP (-u) sans perdre de temps à déterminer les noms d'hôte (-n).

3
pfo
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

Ma combinaison des autres réponses ci-dessus. Tu piges:

Avec shuf -n1, nous prenons un nombre aléatoire dans la plage (-i) dans/proc/sys/net/ipv4/ip_local_port_range. shuf a besoin de la syntaxe avec tiret, nous utilisons donc tr pour changer l'onglet dans un tiret.

Ensuite, utilisez netstat pour nous montrer toutes les connexions (-a) tcp et udp (-u -t) en nombres (-n), si nous trouvons notre port aléatoire $ port dans ce (commencez par a: et terminez par w espaces blancs (\s) alors nous avons besoin d'un autre port et ainsi de suite. Sinon (grep -q a un code retour> 0, nous avons quitté la boucle while et $ port est défini.

1
Winfried Münch

Si vous avez python qui traîne, je ferais ceci:

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

Encore une autre course à ce vieux cheval de bataille:

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

Ce code fait une utilisation purement portable de netstat, egrep, awk, & al. Notez que seul un appel est émis vers des commandes externes, pour obtenir une liste des ports pris au début. On peut demander un ou plusieurs ports libres:

:;  random_free_tcp_port
2070
:;  random_free_tcp_port 2
2073
2114

et commencer à un port arbitraire:

:;  random_free_tcp_port 2 10240
10245
10293
1
solidsnack

Cela fait partie d'une fonction que j'ai dans mon .bashrc, qui crée dynamiquement des tunnels SSH et essaie d'utiliser n'importe quel port d'une plage:

   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
mills013

J'ai écrit ce petit script Python2/3 qui pourrait vous aider la prochaine fois:

https://github.com/sdenel/find-unused-local-port

0
simon.denel

Mon avis ... la fonction essaie de trouver n ports libres consécutifs:

#!/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
0
stefanct