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é.
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
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.
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"
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 comm
ici .
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 comm
s) 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 sort
ing sans doublons -u
.
Nous avons maintenant une liste triée de ports ouverts, que nous pouvons shuf
fle pour récupérer ensuite le premier "$HOWMANY"
ceux avec head -n
.
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
-t
avec -u
dans ss
pour obtenir des ports UDP gratuits à la place.shuf
par sort -n
si vous préférez que les ports disponibles soient triés numériquement plutôt qu'aléatoirement#!/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
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)
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'
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.
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).
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.
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"
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
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
J'ai écrit ce petit script Python2/3 qui pourrait vous aider la prochaine fois:
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