Tester si le port TCP distant est ouvert à partir d'un script shell

298

Je recherche une méthode simple et rapide pour tester correctement si un port TCP donné est ouvert sur un serveur distant, à partir d'un script Shell.

J'ai réussi à le faire avec la commande telnet, et cela fonctionne bien lorsque le port est ouvert, mais il ne semble pas expirer lorsqu'il ne l'est pas et se bloque simplement là ...

Voici un exemple:

l_TELNET=`echo "quit" | telnet $SERVER $PORT | grep "Escape character is"`
if [ "$?" -ne 0 ]; then
  echo "Connection to $SERVER on port $PORT failed"
  exit 1
else
  echo "Connection to $SERVER on port $PORT succeeded"
  exit 0
fi

J'ai besoin d'un meilleur moyen, ou d'un moyen de forcer telnet à expirer s'il ne se connecte pas en moins de 8 secondes par exemple, et de renvoyer quelque chose que je peux attraper dans Shell (code de retour ou chaîne dans stdout).

Je connais la méthode Perl, qui utilise le module IO :: Socket :: INET et a écrit un script réussi qui teste un port, mais je préfère éviter d'utiliser Perl si possible.

Remarque: c'est ce que mon serveur exécute (d'où je dois l'exécuter)

SunOS 5.10 Generic_139556-08 i86pc i386 i86pc

Yanick Girouard
la source
Et Netcat ou Nmap ?
Jeremiah Willcock
La réponse a menti avec Expect. Nous avons écrit un script simple qui envoie un telnet sur le port dont nous avions besoin, avec un délai d'attente de 8 secondes. Il existe également de nombreux exemples. Nous avons basé le nôtre sur ce post: unix.com/shell-programming-scripting/…
Yanick Girouard
1
check_tcp de github.com/monitoring-plugins/monitoring-plugins peut le faire, notamment en saisissant des chaînes et en recherchant une réponse attendue.

Réponses:

452

Comme l'a souligné B. Rhodes, ncfera le travail. Une façon plus compacte de l'utiliser:

nc -z <host> <port>

De cette façon nc, ne vérifiera que si le port est ouvert, en sortant avec 0 en cas de succès, 1 en cas d'échec.

Pour une vérification interactive rapide (avec un délai de 5 secondes):

nc -z -v -w5 <host> <port>
Alessio Gaeta
la source
48
centos7 utilise par défaut nmap netcat et n'a pas l'option -z.
jolestar
7
Cela ne fonctionne pas dans RHEL / Centos. Pour ces distributions, vous devez: nc -vn <host> <port>
Justin Dennahower
2
FWIW, j'ai complètement révisé ma réponse avec un exemple , applicable séparément à la fois à RHEL 6 et RHEL 7.
Acumenus
4
sur Mac au moins, vous devrez peut-être ajouter -G#pour définir un délai de connexion distinct de / en plus du -w#délai, qui fonctionne essentiellement comme un délai de lecture.
Doktor J
1
@jolestar Vous pouvez mettre à niveau manuellement Ncat sur Centos 7 pour obtenir l' -zoption. Vous voudrez peut-être considérer: unix.stackexchange.com/questions/393762/…
Damien Ó Ceallaigh
150

C'est assez facile à faire avec les options -zet , mais tous les systèmes ne sont pas installés. Si vous avez une version suffisamment récente de bash, cela fonctionnera:-w TIMEOUTncnc

# Connection successful:
$ timeout 1 bash -c 'cat < /dev/null > /dev/tcp/google.com/80'
$ echo $?
0

# Connection failure prior to the timeout
$ timeout 1 bash -c 'cat < /dev/null > /dev/tcp/sfsfdfdff.com/80'
bash: sfsfdfdff.com: Name or service not known
bash: /dev/tcp/sfsfdfdff.com/80: Invalid argument
$ echo $?
1

# Connection not established by the timeout
$ timeout 1 bash -c 'cat < /dev/null > /dev/tcp/google.com/81'
$ echo $?
124

Ce qui se passe ici, c'est qu'il timeoutexécutera la sous-commande et la tuera si elle ne se termine pas dans le délai spécifié (1 seconde dans l'exemple ci-dessus). Dans ce cas, bashla sous-commande utilise sa gestion spéciale / dev / tcp pour essayer d'ouvrir une connexion au serveur et au port spécifiés. Si bashpeut ouvrir la connexion dans le délai d'attente, la catfermera immédiatement immédiatement (car il lit à partir de /dev/null) et quittera avec un code d'état 0qui se propagera à travers bashpuis timeout. Si bashobtient un échec de connexion avant le délai spécifié, puis bashquittera avec un code de sortie de 1 qui timeoutreviendra également. Et si bash n'est pas en mesure d'établir une connexion et que le délai spécifié expire, alorstimeoutva tuer bashet quitter avec un statut de 124.

personne
la source
1
Est-il /dev/tcpdisponible sur des systèmes autres que Linux? Et les Mac en particulier?
Peter
8
La chose / dev / tcp est une fonctionnalité de bash, donc oui. Cependant, il semble que les macs n'ont pas de délai d'expiration ...
onlynone
1
@onlynone - On dirait qu'il timeoutn'existe pas non plus dans FreeBSD, et sur mon ancienne boîte Ubuntu, c'est un paquet installable, mais il n'est pas là par défaut. Ce serait génial s'il y avait un moyen de le faire en bash seul, sans outils tiers comme timeout ou netcat.
Graham
3
Je voulais juste mentionner que timeoutsemble faire partie de coreutils GNU, et peuvent être installés sur macs avec homebrew: brew install coreutils. Il sera ensuite disponible en tant que gtimeout.
onlynone
1
@Wildcard Un journal des modifications bash suggère bash-2.04-devel, bien que la prise en charge des noms d'hôtes ait pu être ajoutée bash-2.05-alpha1.
Acumenus
109

COT:

  • Utilisation de bash et timeout
    • Commander
    • Exemples
  • En utilisant nc
    • Commander
    • RHEL 6 (nc-1.84)
      • Installation
      • Exemples
    • RHEL 7 (nmap-ncat-6.40)
      • Installation
      • Exemples
  • Remarques

Utilisation de bash et timeout:

Notez que timeoutdevrait être présent avec RHEL 6+, ou se trouve alternativement dans GNU coreutils 8.22. Sur MacOS, installez-le en utilisant brew install coreutilset utilisez-le en tant que gtimeout.

Commander:

$ timeout $TIMEOUT_SECONDS bash -c "</dev/tcp/${HOST}/${PORT}"; echo $?

Si vous paramétrez l'hôte et le port, assurez-vous de les spécifier tels quels ${HOST}et ${PORT}comme ci-dessus. Ne les spécifiez pas simplement comme $HOSTet $PORT, c'est-à-dire sans les accolades; cela ne fonctionnera pas dans ce cas.

Exemple:

Succès:

$ timeout 2 bash -c "</dev/tcp/canyouseeme.org/80"; echo $?
0

Échec:

$ timeout 2 bash -c "</dev/tcp/canyouseeme.org/81"; echo $?
124

Si vous devez conserver le statut de sortie de bash,

$ timeout --preserve-status 2 bash -c "</dev/tcp/canyouseeme.org/81"; echo $?
143

En utilisant nc:

Notez qu'une version incompatible en amont de ncest installée sur RHEL 7.

Commander:

Notez que la commande ci-dessous est unique en ce qu'elle est identique pour RHEL 6 et 7. Ce sont juste l'installation et la sortie qui sont différentes.

$ nc -w $TIMEOUT_SECONDS -v $HOST $PORT </dev/null; echo $?

RHEL 6 (nc-1.84):

Installation:

$ sudo yum install nc

Exemples:

Succès:
$ nc -w 2 -v canyouseeme.org 80 </dev/null; echo $?
Connection to canyouseeme.org 80 port [tcp/http] succeeded!
0
Échec:
$ nc -w 2 -v canyouseeme.org 81 </dev/null; echo $?
nc: connect to canyouseeme.org port 81 (tcp) timed out: Operation now in progress
1

Si le nom d'hôte est mappé sur plusieurs adresses IP, la commande défaillante ci-dessus fera défiler la plupart ou la totalité d'entre elles. Par exemple:

$ nc -w 2 -v microsoft.com 81 </dev/null; echo $?
nc: connect to microsoft.com port 81 (tcp) timed out: Operation now in progress
nc: connect to microsoft.com port 81 (tcp) timed out: Operation now in progress
nc: connect to microsoft.com port 81 (tcp) timed out: Operation now in progress
nc: connect to microsoft.com port 81 (tcp) timed out: Operation now in progress
nc: connect to microsoft.com port 81 (tcp) timed out: Operation now in progress
1

RHEL 7 (nmap-ncat-6.40):

Installation:

$ sudo yum install nmap-ncat

Exemples:

Succès:
$ nc -w 2 -v canyouseeme.org 80 </dev/null; echo $?
Ncat: Version 6.40 ( http://nmap.org/ncat )
Ncat: Connected to 52.202.215.126:80.
Ncat: 0 bytes sent, 0 bytes received in 0.22 seconds.
0
Échec:
$ nc -w 2 -v canyouseeme.org 81 </dev/null; echo $?
Ncat: Version 6.40 ( http://nmap.org/ncat )
Ncat: Connection timed out.
1

Si le nom d'hôte est mappé sur plusieurs adresses IP, la commande défaillante ci-dessus fera défiler la plupart ou la totalité d'entre elles. Par exemple:

$ nc -w 2 -v microsoft.com 81 </dev/null; echo $?
Ncat: Version 6.40 ( http://nmap.org/ncat )
Ncat: Connection to 104.43.195.251 failed: Connection timed out.
Ncat: Trying next address...
Ncat: Connection to 23.100.122.175 failed: Connection timed out.
Ncat: Trying next address...
Ncat: Connection to 23.96.52.53 failed: Connection timed out.
Ncat: Trying next address...
Ncat: Connection to 191.239.213.197 failed: Connection timed out.
Ncat: Trying next address...
Ncat: Connection timed out.
1

Remarques:

L' argument -v( --verbose) et la echo $?commande sont bien sûr à titre d'illustration uniquement.

Acumenus
la source
3
timeout 2 bash -c "</dev/tcp/canyouseeme.org/81"; echo $?est un excellent point!
ipeacocks
ajouter 2>&1 au statut non écho result_test=$(nc -w 2 $ip_addreess 80 </dev/null 2>&1 ; echo $?)
Sanya Snex
56

Avec netcatvous pouvez vérifier si un port est ouvert comme ceci:

nc my.example.com 80 < /dev/null

La valeur de retour de ncsera succès si le port TCP a été ouvert et échec (généralement le code retour 1) s'il n'a pas pu établir la connexion TCP.

Certaines versions de ncse bloquent lorsque vous essayez ceci, car elles ne ferment pas la moitié d'envoi de leur socket même après avoir reçu la fin du fichier de /dev/null. Sur mon propre ordinateur portable Ubuntu (18.04), la netcat-openbsdversion de netcat que j'ai installée propose une solution: l' -Noption est nécessaire pour obtenir un résultat immédiat:

nc -N my.example.com 80 < /dev/null
Brandon Rhodes
la source
1
Fonctionne très bien pour les saveurs ncqui ne prennent pas en charge le -wdrapeau.
David Dossot
5
merci - fonctionne également pour les versions de nc sans -zsupport - fonctionne sur RHEL / CentOS 7, par exemple
Andrew
1
Bien que cette approche soit bonne, elle -west nécessaire, faute de quoi la commande utilisée dans un script peut se bloquer pendant plus de dix secondes. J'ai écrit une réponse qui comprend -w.
Acumenus
2
+1 c'est génial car nc est livré en standard avec alpine linux et ubuntu. probablement d'autres. pas besoin d'installer supplémentaire lorsque vous êtes à distance et que vous ne pouvez pas. Merci pour cela! Je pourrais ajouter,nc host port -w 2 && echo it works
std''OrgnlDave
2
Je préfère nc -vz localhost 3306. Il donne une sortie plus détaillée. Je l'ai essayé sur mac uniquement.
EFreak
29

Dans Bash, l'utilisation de fichiers pseudo-périphériques pour les connexions TCP / UDP est simple. Voici le script:

#!/usr/bin/env bash
SERVER=example.com
PORT=80
</dev/tcp/$SERVER/$PORT
if [ "$?" -ne 0 ]; then
  echo "Connection to $SERVER on port $PORT failed"
  exit 1
else
  echo "Connection to $SERVER on port $PORT succeeded"
  exit 0
fi

Essai:

$ ./test.sh 
Connection to example.com on port 80 succeeded

Voici une ligne (syntaxe Bash):

</dev/tcp/localhost/11211 && echo Port open. || echo Port closed.

Notez que certains serveurs peuvent être protégés par un pare-feu contre les attaques par inondation SYN, vous pouvez donc rencontrer un délai de connexion TCP (~ 75 secondes). Pour contourner le problème de délai d'attente, essayez:

timeout 1 bash -c "</dev/tcp/stackoverflow.com/81" && echo Port open. || echo Port closed.

Voir: Comment réduire le délai d'expiration des appels système TCP connect ()?

Kenorb
la source
1
@ABB Ceci est lié à la configuration du pare-feu du serveur qui ne donne aucune réponse pour empêcher toute attaque par inondation SYN. La course telnet canyouseeme.org 81se bloque également. Ceci est contrôlé par vos délais d'expiration qui sont probablement codés en dur dans bash. Voir: diminuer le délai d'expiration de l'appel système TCP connect () .
kenorb
Pour le paramétrage, il semble désormais nécessaire de spécifier $SERVERet $PORTavec des accolades, au moins comme ${SERVER}et ${PORT}.
Acumenus
13

J'avais besoin d'une solution plus flexible pour travailler sur plusieurs référentiels git, j'ai donc écrit le code sh suivant basé sur 1 et 2 . Vous pouvez utiliser votre adresse de serveur au lieu de gitlab.com et votre port en remplacement de 22.

SERVER=gitlab.com
PORT=22
nc -z -v -w5 $SERVER $PORT
result1=$?

#Do whatever you want

if [  "$result1" != 0 ]; then
  echo  'port 22 is closed'
else
  echo 'port 22 is open'
fi
Ahmad Yoosofan
la source
1
Merci! Cela rend ma vie facile pour /etc/rc.local
Shahin Khaled
10

Si vous utilisez ksh ou bash, ils prennent tous les deux en charge la redirection IO vers / depuis un socket en utilisant la construction / dev / tcp / IP / PORT . Dans ce shell Korn exemple , je suis pas redirigez-op de ( : ) std-in d'une prise:

W$ python -m SimpleHTTPServer &
[1]     16833
Serving HTTP on 0.0.0.0 port 8000 ...
W$ : </dev/tcp/127.0.0.1/8000

Le shell affiche une erreur si le socket n'est pas ouvert:

W$ : </dev/tcp/127.0.0.1/8001
ksh: /dev/tcp/127.0.0.1/8001: cannot open [Connection refused]

Vous pouvez donc l'utiliser comme test dans une condition if :

SERVER=127.0.0.1 PORT=8000
if (: < /dev/tcp/$SERVER/$PORT) 2>/dev/null
then
    print succeeded
else
    print failed
fi

Le no-op est dans un sous-shell donc je peux jeter std-err si la redirection std-in échoue.

J'utilise souvent / dev / tcp pour vérifier la disponibilité d'une ressource sur HTTP:

W$ print arghhh > grr.html
W$ python -m SimpleHTTPServer &
[1]     16863
Serving HTTP on 0.0.0.0 port 8000 ...
W$ (print -u9 'GET /grr.html HTTP/1.0\n';cat <&9) 9<>/dev/tcp/127.0.0.1/8000
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/2.6.1
Date: Thu, 14 Feb 2013 12:56:29 GMT
Content-type: text/html
Content-Length: 7
Last-Modified: Thu, 14 Feb 2013 12:55:44 GMT

arghhh
W$ 

Cette ligne unique ouvre le descripteur de fichier 9 pour la lecture et l'écriture dans le socket, imprime le HTTP GET sur le socket et utilise catpour lire à partir du socket.

merebagatelle
la source
1
@ABB Je pense que vous voulez dire qu'il se bloque pendant longtemps si le port ne répond pas, comme par exemple lorsqu'il est filtré, ou si rien n'est sur l'IP. Un port fermé n'entraîne pas de retard si le port refuse activement la connexion. À de nombreuses fins, cela est tout à fait acceptable.
mc0e
Cette technique a déjà été publiée avant cette réponse dans une réponse antérieure de kenorb . Quoi qu'il en soit, le plus gros point est qu'il se bloque cependant pendant longtemps si le port ne répond pas. Par exemple, essayez </dev/tcp/canyouseeme.org/80et ensuite </dev/tcp/canyouseeme.org/81.
Acumenus
9

Bien qu'il s'agisse d'une vieille question, je viens d'en traiter une variante, mais aucune des solutions ici n'était applicable, alors j'en ai trouvé une autre et je l'ajoute pour la postérité. Oui, je sais que le PO a dit qu'il était au courant de cette option et qu'elle ne lui convenait pas, mais pour toute personne qui suivrait, cela pourrait s'avérer utile.

Dans mon cas, je veux tester la disponibilité d'un apt-cacher-ngservice local à partir d'une dockerbuild. Cela signifie que rien ne peut être installé avant le test. Non nc, nmap, expect, telnetou python. perlcependant est présent, avec les bibliothèques de base, donc j'ai utilisé ceci:

perl -MIO::Socket::INET -e 'exit(! defined( IO::Socket::INET->new("172.17.42.1:3142")))'
mc0e
la source
7

vérifier les ports à l'aide de bash

Exemple

$ ./test_port_bash.sh 192.168.7.7 22

le port 22 est ouvert

Code

HOST=$1
PORT=$2
exec 3> /dev/tcp/${HOST}/${PORT}
if [ $? -eq 0 ];then echo "the port $2 is open";else echo "the port $2 is closed";fi
krbu
la source
6

Dans certains cas où des outils comme curl, telnet, nc o nmap ne sont pas disponibles, vous avez encore une chance avec wget

if [[ $(wget -q -t 1 --spider --dns-timeout 3 --connect-timeout 10  host:port; echo $?) -eq 0 ]]; then echo "OK"; else echo "FAIL"; fi
PRF
la source
4

Si vous souhaitez utiliser ncmais ne disposez pas d'une version qui prend en charge -z, essayez d'utiliser --send-only:

nc --send-only <IP> <PORT> </dev/null

et avec timeout:

nc -w 1 --send-only <IP> <PORT> </dev/null

et sans recherche DNS s'il s'agit d'une IP:

nc -n -w 1 --send-only <IP> <PORT> </dev/null

Il renvoie les codes en fonction de la -zpossibilité de se connecter ou non.

Chris Mendez
la source
1

Je suppose qu'il est trop tard pour une réponse, et ce n'est peut-être pas une bonne réponse, mais c'est parti ...

Qu'en est-il de le mettre à l'intérieur d'une boucle while avec une minuterie dessus? Je suis plus du genre Perl que Solaris, mais selon le shell que vous utilisez, vous devriez pouvoir faire quelque chose comme:

TIME = 'date +%s' + 15
while TIME != `date +%s'
do whatever

Et puis ajoutez simplement un indicateur dans la boucle while, de sorte que s'il expire avant la fin, vous pouvez citer le délai d'expiration comme raison de l'échec.

Je soupçonne que le telnet a également un commutateur de temporisation, mais juste au-dessus de ma tête, je pense que ce qui précède fonctionnera.

Thomas rouge
la source
1

J'avais besoin d'un script court qui a été exécuté dans cron et n'a pas de sortie. Je résous mes problèmes en utilisant nmap

open=`nmap -p $PORT $SERVER | grep "$PORT" | grep open`
if [ -z "$open" ]; then
  echo "Connection to $SERVER on port $PORT failed"
  exit 1
else
  echo "Connection to $SERVER on port $PORT succeeded"
  exit 0
fi

Pour l'exécuter Vous devez installer nmap car il ne s'agit pas d'un package installé par défaut.

Krystian Kulasza
la source
nmapLa sortie de grepable pourrait également être utile -oG -pour l'envoyer à stdout. (le grep aurait besoin d'un peu de modification)
Gert van den Berg
0

Cela utilise telnet dans les coulisses et semble bien fonctionner sur mac / linux. Il n'utilise pas netcat en raison des différences entre les versions sur linux / mac, et cela fonctionne avec une installation mac par défaut.

Exemple:

$ is_port_open.sh 80 google.com
OPEN

$ is_port_open.sh 8080 google.com
CLOSED

is_port_open.sh

PORT=$1
HOST=$2
TIMEOUT_IN_SEC=${3:-1}
VALUE_IF_OPEN=${4:-"OPEN"}
VALUE_IF_CLOSED=${5:-"CLOSED"}

function eztern()
{
  if [ "$1" == "$2" ]
  then
    echo $3
  else
    echo $4
  fi
}

# cross platform timeout util to support mac mostly
# https://gist.github.com/jaytaylor/6527607
function eztimeout() { perl -e 'alarm shift; exec @ARGV' "$@"; }

function testPort()
{
  OPTS=""

  # find out if port is open using telnet
  # by saving telnet output to temporary file
  # and looking for "Escape character" response
  # from telnet
  FILENAME="/tmp/__port_check_$(uuidgen)"
  RESULT=$(eztimeout $TIMEOUT_IN_SEC telnet $HOST $PORT &> $FILENAME; cat $FILENAME | tail -n1)
  rm -f $FILENAME;
  SUCCESS=$(eztern "$RESULT" "Escape character is '^]'." "$VALUE_IF_OPEN" "$VALUE_IF_CLOSED")

  echo "$SUCCESS"
}

testPort 
Brad Parks
la source
0

S'appuyant sur la réponse la plus votée, voici une fonction permettant d'attendre l'ouverture de deux ports, avec un délai d'attente également. Notez les deux ports qui doivent être ouverts, 8890 et 1111, ainsi que les max_attempts (1 par seconde).

function wait_for_server_to_boot()
{
    echo "Waiting for server to boot up..."
    attempts=0
    max_attempts=30
    while ( nc 127.0.0.1 8890 < /dev/null || nc 127.0.0.1 1111 < /dev/null )  && [[ $attempts < $max_attempts ]] ; do
        attempts=$((attempts+1))
        sleep 1;
        echo "waiting... (${attempts}/${max_attempts})"
    done
}
João Rocha da Silva
la source
-1

nmap-ncat pour tester le port local qui n'est pas déjà utilisé


availabletobindon() {
  port="$1"
  nc -w 2 -i 1 localhost "$port" 2>&1 | grep -v -q 'Idle timeout expired'
  return "$?"
}
isleofgray
la source
Il ne me dit pas que mon port 80 est ouvert.
totoro